blob: b2d8dac2c4a84ed87a1ecb5fddb17b3a6b6e4652 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.refactoring.classes.membersManager;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.intellij.psi.PsiElement;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.MultiMap;
import com.jetbrains.NotNullPredicate;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil;
import com.jetbrains.python.refactoring.classes.PyDependenciesComparator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* Moves members between classes via its plugins (managers).
* To move members use {@link #getAllMembersCouldBeMoved(com.jetbrains.python.psi.PyClass)} and {@link #moveAllMembers(java.util.Collection, com.jetbrains.python.psi.PyClass, com.jetbrains.python.psi.PyClass...)}
* To add new manager, extend this class and add it to {@link #MANAGERS}
*
* @author Ilya.Kazakevich
*/
public abstract class MembersManager<T extends PyElement> implements Function<T, PyMemberInfo<T>> {
/**
* List of managers. Class delegates all logic to them.
*/
private static final Collection<? extends MembersManager<? extends PyElement>> MANAGERS =
Arrays.asList(new MethodsManager(),
new SuperClassesManager(),
new ClassFieldsManager(),
new InstanceFieldsManager(),
new PropertiesManager());
@NotNull
private final Class<T> myExpectedClass;
protected MembersManager(@NotNull final Class<T> expectedClass) {
myExpectedClass = expectedClass;
}
/**
* Get all members that could be moved out of certain class
*
* @param pyClass class to find members
* @return list of members could be moved
*/
@NotNull
public static List<PyMemberInfo<PyElement>> getAllMembersCouldBeMoved(@NotNull final PyClass pyClass) {
final List<PyMemberInfo<PyElement>> result = new ArrayList<PyMemberInfo<PyElement>>();
for (final MembersManager<? extends PyElement> manager : MANAGERS) {
result.addAll(transformSafely(pyClass, manager));
}
return result;
}
/**
* Transforms elements, manager says it could move to appropriate {@link com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo}.
* Types are checked at runtime.
*
* @param pyClass class whose members we want to move
* @param manager manager that should check class and report list of memebers
* @return member infos
*/
//TODO: Move to TypeSafeMovingStrategy
@NotNull
@SuppressWarnings({"unchecked", "rawtypes"}) //We check type at runtime
private static Collection<PyMemberInfo<PyElement>> transformSafely(@NotNull final PyClass pyClass,
@NotNull final MembersManager<?> manager) {
final List<? extends PyElement> membersCouldBeMoved = manager.getMembersCouldBeMoved(pyClass);
manager.checkElementTypes((Iterable)membersCouldBeMoved);
return (Collection<PyMemberInfo<PyElement>>)Collections2.transform(membersCouldBeMoved, (Function)manager);
}
/**
* Moves members from one class to another
*
* @param memberInfos members to move
* @param from source
* @param to destination
*/
public static void moveAllMembers(
@NotNull final Collection<PyMemberInfo<PyElement>> memberInfos,
@NotNull final PyClass from,
@NotNull final PyClass... to
) {
List<PyMemberInfo<PyElement>> memberInfosSorted = new ArrayList<PyMemberInfo<PyElement>>(memberInfos);
Collections.sort(memberInfosSorted, new Comparator<PyMemberInfo<PyElement>>() {
@Override
public int compare(PyMemberInfo<PyElement> o1, PyMemberInfo<PyElement> o2) {
return PyDependenciesComparator.INSTANCE.compare(o1.getMember(), o2.getMember());
}
});
for (PyMemberInfo<PyElement> info : memberInfosSorted) {
TypeSafeMovingStrategy.moveCheckingTypesAtRunTime(from, info.getMembersManager(), Collections.singleton(info), to);
}
/*//Move at once, sort
final Multimap<MembersManager<PyElement>, PyMemberInfo<PyElement>> managerToMember = ArrayListMultimap.create();
//Collect map (manager)->(list_of_memebers)
for (final PyMemberInfo<PyElement> memberInfo : memberInfos) {
managerToMember.put(memberInfo.getMembersManager(), memberInfo);
}
//Move members via manager
for (final MembersManager<PyElement> membersManager : managerToMember.keySet()) {
final Collection<PyMemberInfo<PyElement>> members = managerToMember.get(membersManager);
TypeSafeMovingStrategy.moveCheckingTypesAtRunTime(from, membersManager, members, to);
}*/
PyClassRefactoringUtil.insertPassIfNeeded(from);
}
/**
* Checks that all elements has allowed type for manager
*
* @param elements elements to check against manager
*/
void checkElementTypes(@NotNull final Iterable<T> elements) {
for (final PyElement pyElement : elements) {
Preconditions.checkArgument(myExpectedClass.isAssignableFrom(pyElement.getClass()),
String.format("Manager %s expected %s but got %s", this, myExpectedClass, pyElement));
}
}
/**
* Finds member by predicate
*
* @param members where to find
* @param predicate what to find
* @return member or null if not found
*/
@Nullable
public static PyMemberInfo<PyElement> findMember(@NotNull final Collection<PyMemberInfo<PyElement>> members,
@NotNull final Predicate<PyMemberInfo<PyElement>> predicate) {
for (final PyMemberInfo<PyElement> pyMemberInfo : members) {
if (predicate.apply(pyMemberInfo)) {
return pyMemberInfo;
}
}
return null;
}
/**
* Finds member of class by predicate
*
* @param predicate what to find
* @param pyClass class to find members
* @return member or null if not found
*/
@Nullable
public static PyMemberInfo<PyElement> findMember(@NotNull final PyClass pyClass,
@NotNull final Predicate<PyMemberInfo<PyElement>> predicate) {
return findMember(getAllMembersCouldBeMoved(pyClass), predicate);
}
/**
* Finds member in class.
*
* @param pyClass class to find member in
* @param pyElement element to find
* @return member info with element
*/
@NotNull
public static PyMemberInfo<PyElement> findMember(@NotNull final PyClass pyClass, @NotNull final PyElement pyElement) {
final PyMemberInfo<PyElement> result = findMember(pyClass, new FindByElement(pyElement));
if (result != null) {
return result;
}
throw new IllegalArgumentException(String.format("Element %s not found in class %s or can't be moved", pyElement, pyClass));
}
/**
* Get list of elements certain plugin could move out of the class
*
* @param pyClass class with members
* @return list of members
*/
@NotNull
protected abstract List<? extends PyElement> getMembersCouldBeMoved(@NotNull PyClass pyClass);
/**
* Returns list of elements that may require reference storing aid from {@link com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil#rememberNamedReferences(com.intellij.psi.PsiElement, String...)}
*
* @param elements members chosen by user. In most cases members their selves could be stored, but different managers may support other strategies
* @return elements to store
* @see #moveAllMembers(java.util.Collection, com.jetbrains.python.psi.PyClass, com.jetbrains.python.psi.PyClass...)
*/
protected Collection<? extends PyElement> getElementsToStoreReferences(@NotNull final Collection<T> elements) {
return elements;
}
/**
* Moves element from one class to another. Returns members that may require reference restoring aid from
* ({@link com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil#restoreNamedReferences(com.intellij.psi.PsiElement)})
* Sort members according to their dependncies, before calling this method
*
* @see #getElementsToStoreReferences(java.util.Collection)
*/
protected abstract Collection<PyElement> moveMembers(
@NotNull PyClass from,
@NotNull Collection<PyMemberInfo<T>> members,
@NotNull PyClass... to);
/**
* Creates {@link com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo} from {@link com.jetbrains.python.psi.PyElement}
* This process is plugin-specific and should be implemented in each plugin
*
* @param input element
* @return member info
*/
@SuppressWarnings("NullableProblems") //IDEA-120100
@NotNull
@Override
public abstract PyMemberInfo<T> apply(@NotNull T input);
/**
* Deletes all elements
*
* @param pyElementsToDelete elements to delete
*/
protected static void deleteElements(@NotNull final Collection<? extends PsiElement> pyElementsToDelete) {
for (final PsiElement element : pyElementsToDelete) {
element.delete();
}
}
/**
* Fetches elements from member info.
*
* @param memberInfos member info to fetch elements from
* @param <T> type of element
* @return list of elements
*/
@NotNull
protected static <T extends PyElement> Collection<T> fetchElements(@NotNull final Collection<PyMemberInfo<T>> memberInfos) {
return Collections2.transform(memberInfos, new PyMemberExtractor<T>());
}
/**
* Checks if moving certain member to certain class may lead to conflict (actually that means
* that class already has this member)
*
* @param member member to check
* @param aClass class where this member wanna be moved
* @return true if conflict exists.
*/
public abstract boolean hasConflict(@NotNull T member, @NotNull PyClass aClass);
/**
* Returns all elements this member depends on.
*
* @param classWhereMemberDeclared class where member declared
* @param member member itself
* @param destinationClass where this member would be moved (or null if new class is unknown)
* @return collection of elements this member depends on excluding those, would be available in destination class
*/
@NotNull
public static Collection<? extends PyElement> getAllDependencies(
@NotNull final PyClass classWhereMemberDeclared,
@NotNull final PyElement member,
@Nullable final PyClass destinationClass) {
final PyMemberInfo<PyElement> memberInfo = findMember(classWhereMemberDeclared, member);
final Collection<? extends PyElement> elementsToCheckDependency =
memberInfo.getMembersManager().getElementsToStoreReferences(Collections.singleton(member));
final MultiMap<PyClass, PyElement> dependencies = new MultiMap<PyClass, PyElement>();
final Collection<PyElement> result = new HashSet<PyElement>();
for (final MembersManager<? extends PyElement> manager : MANAGERS) {
for (final PyElement elementToCheckDependency : elementsToCheckDependency) {
dependencies.putAllValues(manager.getDependencies(elementToCheckDependency));
}
}
if (destinationClass != null) {
final Iterator<PyClass> classesIterator = dependencies.keySet().iterator();
while (classesIterator.hasNext()) {
final PyClass memberClass = classesIterator.next();
if (memberClass.equals(destinationClass) ||
ArrayUtil.contains(memberClass, destinationClass.getSuperClasses())) { // IF still would be available
classesIterator.remove();
}
}
}
for (final MembersManager<? extends PyElement> manager : MANAGERS) {
result.addAll(manager.getDependencies(dependencies));
}
result.addAll(dependencies.values());
return result;
}
/**
* Fetch dependencies this element depends on.
* Manager should return them in format "class, where member declared" -- "member itself".
* For example: if parameter is function, and this function uses field "foo" declared in class "bar", then manager (responsible for fields)
* returns "bar" -] reference to "foo"
*
* @param member member to check dependencies for
* @return dependencies
*/
@NotNull
protected abstract MultiMap<PyClass, PyElement> getDependencies(@NotNull PyElement member);
/**
* Get dependencies by members and classes they declared in (obtained from {@link #getDependencies(com.jetbrains.python.psi.PyElement)})
* For example manager, responsible for "extends SomeClass" members may return list of classes
*
* @param usedElements class-to-element dependencies
* @return dependencies
*/
@NotNull
protected abstract Collection<PyElement> getDependencies(@NotNull MultiMap<PyClass, PyElement> usedElements);
private static class PyMemberExtractor<T extends PyElement> implements Function<PyMemberInfo<T>, T> {
@SuppressWarnings("NullableProblems") //IDEA-120100
@Override
public T apply(@NotNull final PyMemberInfo<T> input) {
return input.getMember();
}
}
private static class FindByElement extends NotNullPredicate<PyMemberInfo<PyElement>> {
private final PyElement myPyElement;
private FindByElement(final PyElement pyElement) {
myPyElement = pyElement;
}
@Override
public boolean applyNotNull(@NotNull final PyMemberInfo<PyElement> input) {
return input.getMember().equals(myPyElement);
}
}
}