All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jetbrains.python.refactoring.classes.membersManager.MembersManager Maven / Gradle / Ivy

Go to download

A packaging of the IntelliJ Community Edition python-community library. This is release number 1 of trunk branch 142.

The newest version!
/*
 * 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.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 implements Function> {
  /**
   * List of managers. Class delegates all logic to them.
   */
  private static final Collection> MANAGERS =
    Arrays.asList(new MethodsManager(),
                  new SuperClassesManager(),
                  new ClassFieldsManager(),
                  new InstanceFieldsManager(),
                  new PropertiesManager());

  @NotNull
  private final Class myExpectedClass;

  protected MembersManager(@NotNull final Class 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> getAllMembersCouldBeMoved(@NotNull final PyClass pyClass) {
    final List> result = new ArrayList>();

    for (final MembersManager 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> transformSafely(@NotNull final PyClass pyClass,
                                                                     @NotNull final MembersManager manager) {
    final List membersCouldBeMoved = manager.getMembersCouldBeMoved(pyClass);
    manager.checkElementTypes((Iterable)membersCouldBeMoved);
    return (Collection>)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> memberInfos,
    @NotNull final PyClass from,
    @NotNull final PyClass... to
  ) {
    List> memberInfosSorted = new ArrayList>(memberInfos);
    Collections.sort(memberInfosSorted, new Comparator>() {
      @Override
      public int compare(PyMemberInfo o1, PyMemberInfo o2) {
        return PyDependenciesComparator.INSTANCE.compare(o1.getMember(), o2.getMember());
      }
    });

    for (PyMemberInfo info : memberInfosSorted) {
      TypeSafeMovingStrategy.moveCheckingTypesAtRunTime(from, info.getMembersManager(), Collections.singleton(info), to);
    }


    /*//Move at once, sort
    final Multimap, PyMemberInfo> managerToMember = ArrayListMultimap.create();
    //Collect map (manager)->(list_of_memebers)
    for (final PyMemberInfo memberInfo : memberInfos) {
      managerToMember.put(memberInfo.getMembersManager(), memberInfo);
    }
    //Move members via manager
    for (final MembersManager membersManager : managerToMember.keySet()) {
      final Collection> members = managerToMember.get(membersManager);
      TypeSafeMovingStrategy.moveCheckingTypesAtRunTime(from, membersManager, members, to);
    }*/
  }


  /**
   * Checks that all elements has allowed type for manager
   *
   * @param elements elements to check against manager
   */
  void checkElementTypes(@NotNull final Iterable 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 findMember(@NotNull final Collection> members,
                                                   @NotNull final Predicate> predicate) {
    for (final PyMemberInfo 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 findMember(@NotNull final PyClass pyClass,
                                                   @NotNull final Predicate> 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 findMember(@NotNull final PyClass pyClass, @NotNull final PyElement pyElement) {
    final PyMemberInfo 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 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 getElementsToStoreReferences(@NotNull final Collection 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 moveMembers(
    @NotNull PyClass from,
    @NotNull Collection> 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 apply(@NotNull T input);

  /**
   * Deletes all elements
   *
   * @param pyElementsToDelete elements to delete
   */
  protected static void deleteElements(@NotNull final Collection pyElementsToDelete) {
    for (final PsiElement element : pyElementsToDelete) {
      element.delete();
    }
  }

  /**
   * Fetches elements from member info.
   *
   * @param memberInfos member info to fetch elements from
   * @param          type of element
   * @return list of elements
   */
  @NotNull
  protected static  Collection fetchElements(@NotNull final Collection> memberInfos) {
    return Collections2.transform(memberInfos, new PyMemberExtractor());
  }

  /**
   * 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 getAllDependencies(
    @NotNull final PyClass classWhereMemberDeclared,
    @NotNull final PyElement member,
    @Nullable final PyClass destinationClass) {
    final PyMemberInfo memberInfo = findMember(classWhereMemberDeclared, member);


    final Collection elementsToCheckDependency =
      memberInfo.getMembersManager().getElementsToStoreReferences(Collections.singleton(member));

    final MultiMap dependencies = new MultiMap();

    final Collection result = new HashSet();
    for (final MembersManager manager : MANAGERS) {
      for (final PyElement elementToCheckDependency : elementsToCheckDependency) {
        dependencies.putAllValues(manager.getDependencies(elementToCheckDependency));
      }
    }

    if (destinationClass != null) {
      final Iterator 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 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 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 getDependencies(@NotNull MultiMap usedElements);

  private static class PyMemberExtractor implements Function, T> {
    @SuppressWarnings("NullableProblems") //IDEA-120100
    @Override
    public T apply(@NotNull final PyMemberInfo input) {
      return input.getMember();
    }
  }

  private static class FindByElement extends NotNullPredicate> {
    private final PyElement myPyElement;

    private FindByElement(final PyElement pyElement) {
      myPyElement = pyElement;
    }

    @Override
    public boolean applyNotNull(@NotNull final PyMemberInfo input) {
      return input.getMember().equals(myPyElement);
    }
  }
}






© 2015 - 2025 Weber Informatics LLC | Privacy Policy