Refactoring to make PushDown use the same engine as other refactorings (documented)

This commit is contained in:
Ilya.Kazakevich
2014-02-11 01:05:16 +04:00
parent dd6e05bc32
commit 2a3f6b3549
13 changed files with 134 additions and 110 deletions
@@ -19,6 +19,7 @@ package com.intellij.refactoring.classMembers;
import com.intellij.psi.NavigatablePsiElement;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author Dennis.Ushakov
@@ -26,7 +27,7 @@ import org.jetbrains.annotations.NotNull;
public abstract class AbstractUsesDependencyMemberInfoModel<T extends NavigatablePsiElement, C extends PsiElement, M extends MemberInfoBase<T>> extends DependencyMemberInfoModel<T, M> {
protected final C myClass;
public AbstractUsesDependencyMemberInfoModel(C aClass, C superClass, boolean recursive) {
public AbstractUsesDependencyMemberInfoModel(C aClass, @Nullable C superClass, boolean recursive) {
super(new UsesMemberDependencyGraph<T, C, M>(aClass, superClass, recursive), ERROR);
myClass = aClass;
setTooltipProvider(new MemberInfoTooltipManager.TooltipProvider<T, M>() {
@@ -107,7 +107,10 @@ public class PyUtil {
return node != null && node.getElementType().equals(TokenType.WHITE_SPACE);
}
//TODO: Doctg
/**
* @param function function to check
* @return true if function is init
*/
public static boolean isInit(@NotNull final PyFunction function) {
return PyNames.INIT.equals(function.getName());
}
@@ -15,8 +15,8 @@
*/
package com.jetbrains.python.refactoring.classes;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.intellij.lang.ASTNode;
import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
@@ -40,7 +40,10 @@ import com.jetbrains.python.psi.resolve.QualifiedNameFinder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import static com.jetbrains.python.psi.PyFunction.Modifier.CLASSMETHOD;
import static com.jetbrains.python.psi.PyFunction.Modifier.STATICMETHOD;
@@ -48,30 +51,29 @@ import static com.jetbrains.python.psi.PyFunction.Modifier.STATICMETHOD;
/**
* @author Dennis.Ushakov
*/
public class PyClassRefactoringUtil {
public final class PyClassRefactoringUtil {
private static final Logger LOG = Logger.getInstance(PyClassRefactoringUtil.class.getName());
private static final Key<PsiNamedElement> ENCODED_IMPORT = Key.create("PyEncodedImport");
private static final Key<Boolean> ENCODED_USE_FROM_IMPORT = Key.create("PyEncodedUseFromImport");
private static final Key<String> ENCODED_IMPORT_AS = Key.create("PyEncodedImportAs");
private static final InitFirst INIT_FIRST = new InitFirst();
private PyClassRefactoringUtil() {
}
/**
* TODO: Doc
* Copies class field declarations to some other place
*
* @param assignmentStatement list of class fields
* @return new (copied) fields
*/
@NotNull
public static List<PyAssignmentStatement> copyFieldDeclarationToStatement(@NotNull final Collection<PyAssignmentStatement> assignmentStatement,
@NotNull final PyStatementList superClassStatement) {
List<PyAssignmentStatement> declations = new ArrayList<PyAssignmentStatement>(assignmentStatement.size());
final List<PyAssignmentStatement> declations = new ArrayList<PyAssignmentStatement>(assignmentStatement.size());
for (final PyAssignmentStatement expression : assignmentStatement) {
PyAssignmentStatement newDeclaration = (PyAssignmentStatement)expression.copy();
final PyAssignmentStatement newDeclaration = (PyAssignmentStatement)expression.copy();
declations.add(newDeclaration);
declations.add((PyAssignmentStatement)PyUtil.addElementToStatementList(newDeclaration, superClassStatement, true));
PyPsiUtils.removeRedundantPass(superClassStatement);
@@ -88,23 +90,27 @@ public class PyClassRefactoringUtil {
return addMethods(superClass, elements);
}
//TODO: Doct
/**
* Adds methods to class.
*
* @param destination where to add methods
* @param methods methods
* @return newly added methods
*/
@NotNull
public static List<PyFunction> addMethods(@NotNull PyClass destination, @NotNull PyFunction... methods) {
List<PyFunction> methodsToAdd = new ArrayList<PyFunction>(Arrays.asList(methods));
Collections.sort(methodsToAdd, INIT_FIRST);
public static List<PyFunction> addMethods(@NotNull final PyClass destination, @NotNull final PyFunction... methods) {
PyStatementList destStatementList = destination.getStatementList();
List<PyFunction> newlyCreatedMethods = new ArrayList<PyFunction>(methods.length);
final PyStatementList destStatementList = destination.getStatementList();
final List<PyFunction> newlyCreatedMethods = new ArrayList<PyFunction>(methods.length);
for (PyFunction method : methodsToAdd) {
for (final PyFunction method : methods) {
if (destination.findMethodByName(method.getName(), false) != null) {
continue; //TODO: Doc why
continue; //We skip adding if class already has this method. I am not sure if this behaviour is correct, but it was here, so I left if for backward compatibility
}
PyFunction newMethod = insertMethodInProperPlace(destStatementList, method);
final PyFunction newMethod = insertMethodInProperPlace(destStatementList, method);
newlyCreatedMethods.add(newMethod);
restoreNamedReferences(newMethod);
}
@@ -113,11 +119,18 @@ public class PyClassRefactoringUtil {
return newlyCreatedMethods;
}
//TODO: Doc algo
/**
* Adds init methods before all other methods (but after class vars and docs).
* Adds all other methods to the bottom
*
* @param destStatementList where to add methods
* @param method method to add
* @return newlty added method
*/
@NotNull
private static PyFunction insertMethodInProperPlace(
@NotNull PyStatementList destStatementList,
@NotNull PyFunction method) {
@NotNull final PyStatementList destStatementList,
@NotNull final PyFunction method) {
boolean methodIsInit = PyUtil.isInit(method);
if (!methodIsInit) {
//Not init method could be inserted in the bottom
@@ -125,11 +138,11 @@ public class PyClassRefactoringUtil {
}
//We should find appropriate place for init
for (PsiElement element : destStatementList.getChildren()) {
boolean elementComment = element instanceof PyExpressionStatement;
boolean elementClassField = element instanceof PyAssignmentStatement;
for (final PsiElement element : destStatementList.getChildren()) {
final boolean elementComment = element instanceof PyExpressionStatement;
final boolean elementClassField = element instanceof PyAssignmentStatement;
if ((!(elementComment || elementClassField))) {
if (!(elementComment || elementClassField)) {
return (PyFunction)destStatementList.addBefore(method, element);
}
}
@@ -419,51 +432,47 @@ public class PyClassRefactoringUtil {
return addMethods(pyClass, function).get(0);
}
//TODO: Doc
@NotNull
/**
* Adds super classes to certain class.
*
* @param project project where refactoring takes place
* @param clazz destination
* @param superClasses classes to add
*/
public static void addSuperclasses(@NotNull final Project project,
@NotNull final PyClass clazz,
@NotNull final PyClass... superClasses) {
final List<String> superClassNames = new ArrayList<String>();
final Collection<String> superClassNames = new ArrayList<String>();
for (final PyClass superClass : Collections2.filter(Arrays.asList(superClasses), NotNullPredicate.INSTANCE)) {
if (superClass.getName() != null) {
superClassNames.add(superClass.getName());
PyClassRefactoringUtil.insertImport(clazz, superClass);
insertImport(clazz, superClass);
}
}
PyArgumentList superClassExpressionList = clazz.getSuperClassExpressionList();
PyElementGenerator generator = PyElementGenerator.getInstance(project);
final PyArgumentList superClassExpressionList = clazz.getSuperClassExpressionList();
final PyElementGenerator generator = PyElementGenerator.getInstance(project);
if (superClassExpressionList != null) {
for (String superClassName : superClassNames) {
for (final String superClassName : superClassNames) {
superClassExpressionList.addArgument(generator.createExpressionFromText(superClassName));
}
}
//TODO: Doc why we do it manually
//If class has no expression list, then we need to add it manually.
//TODO: Investigate how to do that on PSI level, with out of stupid string concatenation
else {
String superClassText = String.format("(%s)", StringUtil.join(superClassNames, ","));
clazz.addAfter(generator.createExpressionFromText(superClassText),
clazz.getNameNode().getPsi());
}
}
private static class NameExtractor implements Function<PyElement, String> {
@SuppressWarnings("NullableProblems") //We sure collection has no null
@Nullable
@Override
public String apply(@NotNull final PyElement input) {
return input.getName();
}
}
private static class InitFirst implements Comparator<PyFunction> {
@Override
public int compare(PyFunction o1, PyFunction o2) {
return (PyUtil.isInit(o1) ? 1 : 0) - (PyUtil.isInit(o2) ? 1 : 0);
final String superClassText = String.format("(%s)", StringUtil.join(superClassNames, ","));
final ASTNode node = clazz.getNameNode();
if (node != null) {
clazz.addAfter(generator.createExpressionFromText(superClassText),
node.getPsi());
}
else {
LOG.error("Class has no name node nor superclass list " + clazz);
}
}
}
}
@@ -16,7 +16,6 @@
package com.jetbrains.python.refactoring.classes.extractSuperclass;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
@@ -24,16 +23,15 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.openapi.vfs.*;
import com.intellij.psi.*;
import com.intellij.util.PathUtil;
import com.jetbrains.NotNullPredicate;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PythonFileType;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyElementGenerator;
import com.jetbrains.python.psi.PyUtil;
import com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil;
import com.jetbrains.python.refactoring.classes.membersManager.MembersManager;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
@@ -80,12 +78,11 @@ public final class PyExtractSuperclassHelper {
MembersManager.moveAllMembers(selectedMemberInfos, clazz, newClass);
PyClassRefactoringUtil.addSuperclasses(project, clazz, null, newClass);
}
private static PyClass placeNewClass(Project project, PyClass newClass, @NotNull PyClass clazz, String targetFile) {
VirtualFile file = VirtualFileManager.getInstance()
.findFileByUrl(ApplicationManagerEx.getApplicationEx().isUnitTestMode() ? targetFile : VfsUtil.pathToUrl(targetFile));
.findFileByUrl(ApplicationManagerEx.getApplicationEx().isUnitTestMode() ? targetFile : VfsUtilCore.pathToUrl(targetFile));
// file is the same as the source
if (Comparing.equal(file, clazz.getContainingFile().getVirtualFile())) {
return (PyClass)clazz.getParent().addBefore(newClass, clazz);
@@ -22,11 +22,11 @@ class ClassFieldsManager extends FieldsManager {
@Override
protected void moveAssignments(@NotNull PyClass from,
@NotNull Collection<PyAssignmentStatement> statements,
@NotNull PyClass... to) {
protected void moveAssignments(@NotNull final PyClass from,
@NotNull final Collection<PyAssignmentStatement> statements,
@NotNull final PyClass... to) {
//TODO: Copy/paste with InstanceFieldsManager. Move to parent?
for (PyClass destClass : to) {
for (final PyClass destClass : to) {
PyClassRefactoringUtil.copyFieldDeclarationToStatement(statements, destClass.getStatementList());
}
deleteElements(statements);
@@ -39,17 +39,16 @@ abstract class FieldsManager extends MembersManager<PyTargetExpression> {
return Lists.<PyElement>newArrayList(Collections2.filter(getFieldsByClass(pyClass), SIMPLE_ASSIGNMENTS_ONLY));
}
@NotNull
@Override
protected void moveMembers(@NotNull PyClass from,
@NotNull Collection<PyTargetExpression> members,
@NotNull PyClass... to) {
protected void moveMembers(@NotNull final PyClass from,
@NotNull final Collection<PyTargetExpression> members,
@NotNull final PyClass... to) {
moveAssignments(from, Collections2.transform(members, new AssignmentTransform()), to);
}
protected abstract void moveAssignments(@NotNull PyClass from,
@NotNull Collection<PyAssignmentStatement> statements,
@NotNull PyClass... to);
protected abstract void moveAssignments(@NotNull PyClass from,
@NotNull Collection<PyAssignmentStatement> statements,
@NotNull PyClass... to);
/**
* Checks if class has fields. Only child may know how to obtain field
@@ -5,9 +5,7 @@ import com.jetbrains.python.psi.*;
import com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
@@ -20,12 +18,12 @@ class InstanceFieldsManager extends FieldsManager {
@Override
protected void moveAssignments(@NotNull PyClass from,
@NotNull Collection<PyAssignmentStatement> statements,
@NotNull PyClass... to) {
protected void moveAssignments(@NotNull final PyClass from,
@NotNull final Collection<PyAssignmentStatement> statements,
@NotNull final PyClass... to) {
//TODO: Copy/paste with ClassFieldsManager. Move to parent?
for (PyClass destClass : to) {
for (final PyClass destClass : to) {
copyInstanceFields(statements, destClass);
}
@@ -38,7 +36,13 @@ class InstanceFieldsManager extends FieldsManager {
}
}
//TODO: Doc
/**
* Copies class' fields in form of assignments (instance fields) to another class.
* Creates init method if there is no any
* @param members assignments to copy
* @param to destination
* @return newly created fields
*/
@NotNull
private static List<PyAssignmentStatement> copyInstanceFields(@NotNull final Collection<PyAssignmentStatement> members,
@NotNull final PyClass to) {
@@ -11,13 +11,15 @@ import com.intellij.psi.PsiNamedElement;
import com.jetbrains.NotNullPredicate;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.psi.PyUtil;
import com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
/**
* TODO: Extract "from, to[], members, deleteFromOrigin" to separate class?
@@ -68,7 +70,6 @@ public abstract class MembersManager<T extends PyElement> implements Function<T,
/**
* TODO: Doc deleteFromOrigin
* Moves members from one class to another
*
* @param memberInfos members to move
@@ -117,7 +118,11 @@ public abstract class MembersManager<T extends PyElement> implements Function<T,
}
/**
* TODO: Docuyment
* 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<PyMemberInfo> members, @NotNull final Predicate<PyMemberInfo> predicate) {
@@ -130,7 +135,11 @@ public abstract class MembersManager<T extends PyElement> implements Function<T,
}
/**
* TODO: Docuyment
* 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<PyMemberInfo> predicate) {
@@ -178,12 +187,8 @@ public abstract class MembersManager<T extends PyElement> implements Function<T,
/**
* Moves element from one class to another. Returns newly instered elements
* <p/>
* TODO: Doc
* TODO: Doc who and what should return (write about elemnents)
*/
@NotNull
protected abstract void moveMembers(
protected abstract void moveMembers(
@NotNull PyClass from,
@NotNull Collection<T> members,
@NotNull PyClass... to);
@@ -29,14 +29,14 @@ class MethodsManager extends MembersManager<PyFunction> {
}
@Override
protected void moveMembers(@NotNull PyClass from,
@NotNull Collection<PyFunction> members,
@NotNull PyClass... to) {
for (PyClass destClass : to) {
//We move copies here becase we there may be several destinations
List<PyFunction> copies = new ArrayList<PyFunction>(members.size());
for (PyFunction member : members) {
PyFunction newMethod = (PyFunction)member.copy();
protected void moveMembers(@NotNull final PyClass from,
@NotNull final Collection<PyFunction> members,
@NotNull final PyClass... to) {
for (final PyClass destClass : to) {
//We move copies here because we there may be several destinations
final List<PyFunction> copies = new ArrayList<PyFunction>(members.size());
for (final PyFunction member : members) {
final PyFunction newMethod = (PyFunction)member.copy();
copies.add(newMethod);
}
@@ -12,7 +12,8 @@ import java.util.Collection;
import java.util.List;
/**
* TODO: Doc (and why it extends 2 classes)
* Processor for member-based refactorings. It moves members from one place to another using {@link com.jetbrains.python.refactoring.classes.membersManager.MembersManager}.
* Inheritors only need to implement {@link com.intellij.usageView.UsageViewDescriptor} methods (while this interface is also implemented by this class)
*
* @author Ilya.Kazakevich
*/
@@ -25,7 +26,11 @@ public abstract class PyMembersRefactoringBaseProcessor extends BaseRefactoringP
@NotNull
private final PyClass[] myTo;
//TODO: Doc
/**
* @param membersToMove what to move
* @param from source
* @param to where to move
*/
protected PyMembersRefactoringBaseProcessor(
@NotNull final Collection<PyMemberInfo> membersToMove,
@NotNull final PyClass from,
@@ -48,7 +53,9 @@ public abstract class PyMembersRefactoringBaseProcessor extends BaseRefactoringP
return myTo.clone();
}
//TODO: Doc
/**
* @return destinations (so user would be able to choose if she wants to move member to certain place or not)
*/
@NotNull
@Override
protected final PyUsageInfo[] findUsages() {
@@ -59,7 +66,6 @@ public abstract class PyMembersRefactoringBaseProcessor extends BaseRefactoringP
return result.toArray(new PyUsageInfo[result.size()]);
}
//TODO: Doc
@Override
protected final void performRefactoring(final UsageInfo[] usages) {
final Collection<PyClass> destinations = new ArrayList<PyClass>(usages.length);
@@ -67,7 +73,7 @@ public abstract class PyMembersRefactoringBaseProcessor extends BaseRefactoringP
if (!(usage instanceof PyUsageInfo)) {
throw new IllegalArgumentException("Only PyUsageInfo is accepted here");
}
//TODO: Doc
//We collect destination info to pass it to members manager
destinations.add(((PyUsageInfo)usage).getTo());
}
MembersManager.moveAllMembers(myMembersToMove, myFrom, destinations.toArray(new PyClass[destinations.size()]));
@@ -6,7 +6,7 @@ import org.jetbrains.annotations.NotNull;
/**
* TODO: Make it generic to allow to reuse in another projects?
* TODO: Document
* Usage info that displays destination (where should member be moved)
*
* @author Ilya.Kazakevich
*/
@@ -14,9 +14,9 @@ class PyUsageInfo extends UsageInfo {
@NotNull
private final PyClass myTo;
PyUsageInfo(@NotNull PyClass to) {
PyUsageInfo(@NotNull final PyClass to) {
super(to, true); //TODO: Make super generic and get rid of field?
this.myTo = to;
myTo = to;
}
@NotNull
@@ -31,13 +31,13 @@ class SuperClassesManager extends MembersManager<PyClass> {
}
@Override
protected void moveMembers(@NotNull PyClass from, @NotNull Collection<PyClass> members, @NotNull PyClass... to) {
for (PyClass destClass : to) {
protected void moveMembers(@NotNull final PyClass from, @NotNull final Collection<PyClass> members, @NotNull final PyClass... to) {
for (final PyClass destClass : to) {
PyClassRefactoringUtil.addSuperclasses(from.getProject(), destClass, members.toArray(new PyClass[members.size()]));
}
for (PyExpression expression : from.getSuperClassExpressions()) {
for (PyClass member : members) {
for (final PyExpression expression : from.getSuperClassExpressions()) {
for (final PyClass member : members) {
if (expression.getText().equals(member.getName())) {
expression.delete();
}
@@ -10,7 +10,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.Collection;
/**
* TODO: Doc
*
*
* @author Ilya.Kazakevich
*/