| /* |
| * 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); |
| } |
| } |
| } |
| |
| |