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

This commit is contained in:
Ilya.Kazakevich
2014-02-10 23:57:19 +04:00
parent 7070677236
commit 735084d8d4
38 changed files with 635 additions and 586 deletions
@@ -66,7 +66,7 @@ public abstract class MemberInfoBase<T extends PsiElement> {
}
public T getMember() {
LOG.assertTrue(myMember.isValid());
LOG.assertTrue(myMember.isValid(), myMember.getText() + " is invalid");
return myMember;
}
@@ -19,6 +19,9 @@ import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* TODO: Document
*/
public interface UsageViewDescriptor {
/**
* @return an array of elements whose usages were searched or {@link com.intellij.psi.PsiElement#EMPTY_ARRAY} if not available
+48 -13
View File
@@ -15,6 +15,7 @@
*/
package com.jetbrains.python.psi;
import com.google.common.collect.Collections2;
import com.google.common.collect.Maps;
import com.intellij.codeInsight.FileModificationService;
import com.intellij.codeInsight.completion.PrioritizedLookupElement;
@@ -52,6 +53,7 @@ import com.intellij.util.PlatformIcons;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashSet;
import com.jetbrains.NotNullPredicate;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PyTokenTypes;
@@ -63,6 +65,7 @@ import com.jetbrains.python.psi.impl.PyBuiltinCache;
import com.jetbrains.python.psi.impl.PyPsiUtils;
import com.jetbrains.python.psi.types.*;
import com.jetbrains.python.refactoring.classes.extractSuperclass.PyExtractSuperclassHelper;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -104,19 +107,9 @@ public class PyUtil {
return node != null && node.getElementType().equals(TokenType.WHITE_SPACE);
}
@NotNull
public static Set<PsiElement> getComments(PsiElement start) {
final Set<PsiElement> comments = new HashSet<PsiElement>();
PsiElement seeker = start.getPrevSibling();
if (seeker == null) seeker = start.getParent().getPrevSibling();
while (seeker instanceof PsiWhiteSpace || seeker instanceof PsiComment) {
if (seeker instanceof PsiComment) {
comments.add(seeker);
}
seeker = seeker.getPrevSibling();
}
return comments;
//TODO: Doctg
public static boolean isInit(@NotNull final PyFunction function) {
return PyNames.INIT.equals(function.getName());
}
@Nullable
@@ -1406,4 +1399,46 @@ public class PyUtil {
}
return false;
}
private static boolean isObject(@NotNull final PyMemberInfo classMemberInfo) {
final PyElement element = classMemberInfo.getMember();
if ((element instanceof PyClass) && PyNames.OBJECT.equals(element.getName())) {
return true;
}
return false;
}
/**
* Filters out {@link com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo}
* that should not be displayed in this refactoring (like object)
*
* @param pyMemberInfos collection to sort
* @return sorted collection
*/
@NotNull
public static Collection<PyMemberInfo> filterOutObject(@NotNull final Collection<PyMemberInfo> pyMemberInfos) {
return Collections2.filter(pyMemberInfos, new ObjectPredicate(false));
}
/**
* Filters only pyclass object (new class)
*/
public static class ObjectPredicate extends NotNullPredicate<PyMemberInfo> {
private final boolean myAllowObjects;
/**
* @param allowObjects allows only objects if true. Allows all but objects otherwise.
*/
public ObjectPredicate(final boolean allowObjects) {
myAllowObjects = allowObjects;
}
@Override
public boolean applyNotNull(@NotNull final PyMemberInfo input) {
return myAllowObjects == isObject(input);
}
}
}
@@ -25,6 +25,8 @@ import com.jetbrains.python.PythonFileType;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.psi.PyElementGenerator;
import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.psi.PyUtil;
import com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil;
import java.util.ArrayList;
import java.util.Collections;
@@ -15,19 +15,20 @@
*/
package com.jetbrains.python.refactoring.classes;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtilBase;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.psi.util.QualifiedName;
import com.jetbrains.NotNullPredicate;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PythonFileType;
import com.jetbrains.python.codeInsight.PyCodeInsightSettings;
import com.jetbrains.python.codeInsight.imports.AddImportHelper;
import com.jetbrains.python.psi.*;
@@ -53,160 +54,99 @@ public class PyClassRefactoringUtil {
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() {
}
public static void moveSuperclasses(PyClass clazz, Set<String> superClasses, PyClass superClass) {
if (superClasses.size() == 0) return;
final Project project = clazz.getProject();
final List<PyExpression> toAdd = removeAndGetSuperClasses(clazz, superClasses);
addSuperclasses(project, superClass, toAdd, superClasses);
}
public static void addSuperclasses(Project project,
PyClass superClass,
@Nullable Collection<PyExpression> superClassesAsPsi,
Collection<String> superClassesAsStrings) {
if (superClassesAsStrings.size() == 0) return;
PyArgumentList argList = superClass.getSuperClassExpressionList();
if (argList != null) {
if (superClassesAsPsi != null) {
for (PyExpression element : superClassesAsPsi) {
argList.addArgument(element);
}
}
else {
for (String s : superClassesAsStrings) {
argList.addArgument(PyElementGenerator.getInstance(project).createExpressionFromText(s));
}
}
}
else {
addSuperclasses(project, superClass, superClassesAsStrings);
}
}
/**
* Removes super classes by name and returns list of removed
* TODO: Doc
* Copies class field declarations to some other place
*
* @param clazz class to find super classes to remove
* @param superClassesToRemove list of super class names
* @return list of removed classes
* @param assignmentStatement list of class fields
*/
@NotNull
public static List<PyExpression> removeAndGetSuperClasses(@NotNull PyClass clazz, @NotNull Set<String> superClassesToRemove) {
if (superClassesToRemove.isEmpty()) {
return Collections.emptyList();
}
final List<PyExpression> result = new ArrayList<PyExpression>();
for (final PyExpression superClassExpression : clazz.getSuperClassExpressions()) {
if (superClassesToRemove.contains(superClassExpression.getText())) {
result.add(superClassExpression);
superClassExpression.delete();
}
}
return result;
}
public static void addSuperclasses(Project project, PyClass superClass, Collection<String> superClasses) {
if (superClasses.size() == 0) return;
final StringBuilder builder = new StringBuilder("(");
boolean hasChanges = false;
for (String element : superClasses) {
if (builder.length() > 1) builder.append(",");
if (!alreadyHasSuperClass(superClass, element)) {
builder.append(element);
hasChanges = true;
}
}
builder.append(")");
if (!hasChanges) return;
final PsiFile file =
PsiFileFactory.getInstance(project).createFileFromText(superClass.getName() + "temp", PythonFileType.INSTANCE, builder.toString());
final PsiElement expression = file.getFirstChild().getFirstChild();
PsiElement colon = superClass.getFirstChild();
while (colon != null && !colon.getText().equals(":")) {
colon = colon.getNextSibling();
}
LOG.assertTrue(colon != null && expression != null);
PyPsiUtils.addBeforeInParent(colon, expression);
}
private static boolean alreadyHasSuperClass(PyClass superClass, String className) {
for (PyClass aClass : superClass.getSuperClasses()) {
if (Comparing.strEqual(aClass.getName(), className)) {
return true;
}
}
return false;
}
/**
* Moves class field declarations to some other place
* @param expressions list of class fields
* @param superClass where to move them
*/
public static void moveFieldDeclarationToStatement(@NotNull final Collection<PyTargetExpression> expressions,
@NotNull final PyStatementList superClassStatement) {
for (final PyTargetExpression expression : expressions) {
final PyAssignmentStatement expAssignmentStatement = PsiTreeUtil.getParentOfType(expression, PyAssignmentStatement.class);
assert expAssignmentStatement != null: "Target expression has no assignment statement";
PyUtil.addElementToStatementList(expAssignmentStatement.copy(), superClassStatement, true);
expAssignmentStatement.delete();
public static List<PyAssignmentStatement> copyFieldDeclarationToStatement(@NotNull final Collection<PyAssignmentStatement> assignmentStatement,
@NotNull final PyStatementList superClassStatement) {
List<PyAssignmentStatement> declations = new ArrayList<PyAssignmentStatement>(assignmentStatement.size());
for (final PyAssignmentStatement expression : assignmentStatement) {
PyAssignmentStatement newDeclaration = (PyAssignmentStatement)expression.copy();
declations.add(newDeclaration);
declations.add((PyAssignmentStatement)PyUtil.addElementToStatementList(newDeclaration, superClassStatement, true));
PyPsiUtils.removeRedundantPass(superClassStatement);
}
return declations;
}
public static void moveMethods(Collection<PyFunction> methods, PyClass superClass) {
if (methods.size() == 0) return;
public static List<PyFunction> copyMethods(Collection<PyFunction> methods, PyClass superClass) {
if (methods.size() == 0) return null;
for (PsiElement e : methods) {
rememberNamedReferences(e);
}
final PyElement[] elements = methods.toArray(new PyElement[methods.size()]);
addMethods(superClass, elements, true);
removeMethodsWithComments(elements);
final PyFunction[] elements = methods.toArray(new PyFunction[methods.size()]);
return addMethods(superClass, elements);
}
private static void removeMethodsWithComments(PyElement[] elements) {
for (PyElement element : elements) {
final Set<PsiElement> comments = PyUtil.getComments(element);
if (comments.size() > 0) {
PyPsiUtils.removeElements(PsiUtilCore.toPsiElementArray(comments));
//TODO: Doct
@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);
PyStatementList destStatementList = destination.getStatementList();
List<PyFunction> newlyCreatedMethods = new ArrayList<PyFunction>(methods.length);
for (PyFunction method : methodsToAdd) {
if (destination.findMethodByName(method.getName(), false) != null) {
continue; //TODO: Doc why
}
PyFunction newMethod = insertMethodInProperPlace(destStatementList, method);
newlyCreatedMethods.add(newMethod);
restoreNamedReferences(newMethod);
}
PyPsiUtils.removeRedundantPass(destStatementList);
return newlyCreatedMethods;
}
//TODO: Doc algo
@NotNull
private static PyFunction insertMethodInProperPlace(
@NotNull PyStatementList destStatementList,
@NotNull PyFunction method) {
boolean methodIsInit = PyUtil.isInit(method);
if (!methodIsInit) {
//Not init method could be inserted in the bottom
return (PyFunction)destStatementList.add(method);
}
//We should find appropriate place for init
for (PsiElement element : destStatementList.getChildren()) {
boolean elementComment = element instanceof PyExpressionStatement;
boolean elementClassField = element instanceof PyAssignmentStatement;
if ((!(elementComment || elementClassField))) {
return (PyFunction)destStatementList.addBefore(method, element);
}
}
PyPsiUtils.removeElements(elements);
return (PyFunction)destStatementList.add(method);
}
public static <T extends PyElement & PyStatementListContainer>void insertPassIfNeeded(@NotNull T element) {
public static <T extends PyElement & PyStatementListContainer> void insertPassIfNeeded(@NotNull T element) {
final PyStatementList statements = element.getStatementList();
if (statements.getStatements().length == 0) {
statements.add(
PyElementGenerator.getInstance(element.getProject()).createFromText(LanguageLevel.getDefault(), PyPassStatement.class, PyNames.PASS));
PyElementGenerator.getInstance(element.getProject())
.createFromText(LanguageLevel.getDefault(), PyPassStatement.class, PyNames.PASS)
);
}
}
public static void addMethods(final PyClass superClass, final PyElement[] elements, final boolean up) {
if (elements.length == 0) return;
final PyStatementList statements = superClass.getStatementList();
for (PyElement newStatement : elements) {
if (up && newStatement instanceof PyFunction) {
final String name = newStatement.getName();
if (name != null && superClass.findMethodByName(name, false) != null) {
continue;
}
}
if (newStatement instanceof PyExpressionStatement && newStatement.getFirstChild() instanceof PyStringLiteralExpression) continue;
final PsiElement anchor = statements.add(newStatement);
restoreNamedReferences(anchor);
final Set<PsiElement> comments = PyUtil.getComments(newStatement);
for (PsiElement comment : comments) {
statements.addBefore(comment, anchor);
}
}
PyPsiUtils.removeRedundantPass(statements);
}
public static void restoreNamedReferences(@NotNull PsiElement element) {
restoreNamedReferences(element, null);
}
@@ -231,6 +171,7 @@ public class PyClassRefactoringUtil {
});
}
private static void restoreReference(final PyReferenceExpression node) {
PsiNamedElement target = node.getCopyableUserData(ENCODED_IMPORT);
final String asName = node.getCopyableUserData(ENCODED_IMPORT_AS);
@@ -443,9 +384,10 @@ public class PyClassRefactoringUtil {
/**
* Creates class method
* @param methodName name if new method (be sure to check {@link com.jetbrains.python.PyNames} for special methods)
* @param pyClass class to add method
* @param modifier if method static or class or simple instance method (null)>
*
* @param methodName name if new method (be sure to check {@link com.jetbrains.python.PyNames} for special methods)
* @param pyClass class to add method
* @param modifier if method static or class or simple instance method (null)>
* @param parameterNames method parameters
* @return newly created method
*/
@@ -473,8 +415,55 @@ public class PyClassRefactoringUtil {
builder.parameter(parameterName);
}
final PyFunction function = builder.addFunction(pyClass.getStatementList(), LanguageLevel.getDefault());
addMethods(pyClass, new PyElement[]{function}, true);
return function;
final PyFunction function = builder.buildFunction(pyClass.getProject(), LanguageLevel.getDefault());
return addMethods(pyClass, function).get(0);
}
//TODO: Doc
@NotNull
public static void addSuperclasses(@NotNull final Project project,
@NotNull final PyClass clazz,
@NotNull final PyClass... superClasses) {
final List<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);
}
}
PyArgumentList superClassExpressionList = clazz.getSuperClassExpressionList();
PyElementGenerator generator = PyElementGenerator.getInstance(project);
if (superClassExpressionList != null) {
for (String superClassName : superClassNames) {
superClassExpressionList.addArgument(generator.createExpressionFromText(superClassName));
}
}
//TODO: Doc why we do it manually
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);
}
}
}
@@ -21,6 +21,7 @@ import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyUtil;
import com.jetbrains.python.refactoring.classes.PyClassRefactoringHandler;
import com.jetbrains.python.refactoring.classes.PyMemberInfoStorage;
import com.jetbrains.python.vp.Creator;
@@ -40,7 +41,7 @@ public class PyExtractSuperclassHandler extends PyClassRefactoringHandler {
@NotNull final PyMemberInfoStorage infoStorage,
@NotNull final Editor editor) {
//TODO: Move to presenter
if (PyExtractSuperclassHelper.filterOutDeniedMembers(infoStorage.getClassMemberInfos(classUnderRefactoring)).isEmpty()) {
if (PyUtil.filterOutObject(infoStorage.getClassMemberInfos(classUnderRefactoring)).isEmpty()) {
CommonRefactoringUtil.showErrorHint(project, editor, PyBundle
.message("refactoring.extract.super.class.no.members.allowed"), RefactoringBundle.message("extract.superclass.elements.header"),
null);
@@ -33,10 +33,7 @@ import com.intellij.util.PathUtil;
import com.jetbrains.NotNullPredicate;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PythonFileType;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.psi.PyElementGenerator;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil;
import com.jetbrains.python.refactoring.classes.membersManager.MembersManager;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
@@ -55,7 +52,7 @@ public final class PyExtractSuperclassHelper {
/**
* Accepts only those members whose element is PyClass object (new classes)
*/
private static final Predicate<PyMemberInfo> ALLOW_OBJECT = new ObjectPredicate(true);
private static final Predicate<PyMemberInfo> ALLOW_OBJECT = new PyUtil.ObjectPredicate(true);
private PyExtractSuperclassHelper() {
}
@@ -81,8 +78,8 @@ public final class PyExtractSuperclassHelper {
newClass = placeNewClass(project, newClass, clazz, targetFile);
MembersManager.moveAllMembers(clazz, newClass, selectedMemberInfos);
PyClassRefactoringUtil.addSuperclasses(project, clazz, null, Collections.singleton(superBaseName));
MembersManager.moveAllMembers(selectedMemberInfos, clazz, newClass);
PyClassRefactoringUtil.addSuperclasses(project, clazz, null, newClass);
}
@@ -226,45 +223,4 @@ public final class PyExtractSuperclassHelper {
}
return ret;
}
private static boolean isObject(@NotNull final PyMemberInfo classMemberInfo) {
final PyElement element = classMemberInfo.getMember();
if ((element instanceof PyClass) && PyNames.OBJECT.equals(element.getName())) {
return true;
}
return false;
}
/**
* Filters out {@link com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo}
* that should not be displayed in this refactoring (like object)
*
* @param pyMemberInfos collection to sort
* @return sorted collection
*/
@NotNull
static Collection<PyMemberInfo> filterOutDeniedMembers(@NotNull final Collection<PyMemberInfo> pyMemberInfos) {
return Collections2.filter(pyMemberInfos, new ObjectPredicate(false));
}
/**
* Filters only pyclass object (new class)
*/
private static class ObjectPredicate extends NotNullPredicate<PyMemberInfo> {
private final boolean myAllowObjects;
/**
* @param allowObjects allows only objects if true. Allows all but objects otherwise.
*/
private ObjectPredicate(final boolean allowObjects) {
myAllowObjects = allowObjects;
}
@Override
public boolean applyNotNull(@NotNull final PyMemberInfo input) {
return myAllowObjects == isObject(input);
}
}
}
@@ -13,6 +13,7 @@ import com.intellij.util.containers.MultiMap;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PythonLanguage;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyUtil;
import com.jetbrains.python.refactoring.classes.PyMemberInfoStorage;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
import com.jetbrains.python.refactoring.classes.membersManager.vp.BadDataException;
@@ -75,7 +76,7 @@ class PyExtractSuperclassPresenterImpl extends MembersBasedPresenterNoPreviewImp
final String defaultFilePath = FileUtil.toSystemDependentName(myClassUnderRefactoring.getContainingFile().getVirtualFile().getPath());
final VirtualFile[] roots = ProjectRootManager.getInstance(myClassUnderRefactoring.getProject()).getContentRoots();
final Collection<PyMemberInfo> pyMemberInfos =
PyExtractSuperclassHelper.filterOutDeniedMembers(myStorage.getClassMemberInfos(myClassUnderRefactoring));
PyUtil.filterOutObject(myStorage.getClassMemberInfos(myClassUnderRefactoring));
myView.configure(
new PyExtractSuperclassInitializationInfo(new PyExtractSuperclassInfoModel(myClassUnderRefactoring), pyMemberInfos, defaultFilePath,
roots));
@@ -1,5 +1,6 @@
package com.jetbrains.python.refactoring.classes.membersManager;
import com.jetbrains.python.psi.PyAssignmentStatement;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyTargetExpression;
import com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil;
@@ -21,10 +22,15 @@ class ClassFieldsManager extends FieldsManager {
@Override
protected void moveMembers(@NotNull final PyClass from,
@NotNull final PyClass to,
@NotNull final Collection<PyTargetExpression> members) {
PyClassRefactoringUtil.moveFieldDeclarationToStatement(members, to.getStatementList());
protected void moveAssignments(@NotNull PyClass from,
@NotNull Collection<PyAssignmentStatement> statements,
@NotNull PyClass... to) {
//TODO: Copy/paste with InstanceFieldsManager. Move to parent?
for (PyClass destClass : to) {
PyClassRefactoringUtil.copyFieldDeclarationToStatement(statements, destClass.getStatementList());
}
deleteElements(statements);
PyClassRefactoringUtil.insertPassIfNeeded(from);
}
@Override
@@ -1,8 +1,10 @@
package com.jetbrains.python.refactoring.classes.membersManager;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.NotNullPredicate;
import com.jetbrains.python.psi.PyAssignmentStatement;
import com.jetbrains.python.psi.PyClass;
@@ -11,6 +13,7 @@ import com.jetbrains.python.psi.PyTargetExpression;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.List;
/**
@@ -36,6 +39,18 @@ 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) {
moveAssignments(from, Collections2.transform(members, new AssignmentTransform()), 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
*
@@ -84,4 +99,12 @@ abstract class FieldsManager extends MembersManager<PyTargetExpression> {
return (parent != null) && PyAssignmentStatement.class.isAssignableFrom(parent.getClass());
}
}
private class AssignmentTransform implements Function<PyTargetExpression, PyAssignmentStatement> {
@Override
public PyAssignmentStatement apply(PyTargetExpression input) {
return PsiTreeUtil.getParentOfType(input, PyAssignmentStatement.class);
}
}
}
@@ -5,7 +5,9 @@ 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;
/**
@@ -18,22 +20,35 @@ class InstanceFieldsManager extends FieldsManager {
@Override
protected void moveMembers(@NotNull final PyClass from,
@NotNull final PyClass to,
@NotNull final Collection<PyTargetExpression> members) {
protected void moveAssignments(@NotNull PyClass from,
@NotNull Collection<PyAssignmentStatement> statements,
@NotNull PyClass... to) {
//TODO: Copy/paste with ClassFieldsManager. Move to parent?
for (PyClass destClass : to) {
copyInstanceFields(statements, destClass);
}
deleteElements(statements);
final PyFunction fromInitMethod = from.findMethodByName(PyNames.INIT, false);
if (fromInitMethod != null) {
//We can't leave class constructor with empty body
PyClassRefactoringUtil.insertPassIfNeeded(fromInitMethod);
}
}
//TODO: Doc
@NotNull
private static List<PyAssignmentStatement> copyInstanceFields(@NotNull final Collection<PyAssignmentStatement> members,
@NotNull final PyClass to) {
//We need __init__ method, and if there is no any -- we need to create it
PyFunction toInitMethod = to.findMethodByName(PyNames.INIT, false);
if (toInitMethod == null) {
toInitMethod = PyClassRefactoringUtil.createMethod(PyNames.INIT, to, null);
}
final PyStatementList statementList = toInitMethod.getStatementList();
PyClassRefactoringUtil.moveFieldDeclarationToStatement(members, statementList);
final PyFunction fromInitMethod = from.findMethodByName(PyNames.INIT, false);
if (fromInitMethod !=null) {
//We can't leave class constructor with empty body
PyClassRefactoringUtil.insertPassIfNeeded(fromInitMethod);
}
return PyClassRefactoringUtil.copyFieldDeclarationToStatement(members, statementList);
}
@Override
@@ -6,23 +6,23 @@ import com.google.common.base.Predicate;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Collections2;
import com.google.common.collect.Multimap;
import com.intellij.psi.PsiElement;
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.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.*;
/**
* TODO: Extract "from, to[], members, deleteFromOrigin" to separate class?
* Moves members between classes via its plugins (managers).
* To move members use {@link #getAllMembersCouldBeMoved(com.jetbrains.python.psi.PyClass)} and {@link #moveMembers(com.jetbrains.python.psi.PyClass, com.jetbrains.python.psi.PyClass, java.util.Collection)}
* To move members use {@link #getAllMembersCouldBeMoved(com.jetbrains.python.psi.PyClass)} and {@link #moveMembers(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
@@ -68,15 +68,18 @@ 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
* @param from source
* @param to destination
* @param memberInfos members to move
*/
public static void moveAllMembers(@NotNull final PyClass from,
@NotNull final PyClass to,
@NotNull final Collection<PyMemberInfo> memberInfos) {
public static void moveAllMembers(
@NotNull final Collection<PyMemberInfo> memberInfos,
@NotNull final PyClass from,
@NotNull final PyClass... to
) {
final Multimap<MembersManager<?>, PyMemberInfo> managerToMember = ArrayListMultimap.create();
//Collect map (manager)->(list_of_memebers)
for (final PyMemberInfo memberInfo : memberInfos) {
@@ -84,20 +87,23 @@ public abstract class MembersManager<T extends PyElement> implements Function<T,
}
//Move members via manager
for (final MembersManager<?> membersManager : managerToMember.keySet()) {
moveSafely(from, to, membersManager, Collections2.transform(managerToMember.get(membersManager), PY_MEMBER_EXTRACTOR));
Collection<PyElement> elementsToMove = Collections2.transform(managerToMember.get(membersManager), PY_MEMBER_EXTRACTOR);
moveSafely(from, membersManager, elementsToMove, to);
}
PyClassRefactoringUtil.insertPassIfNeeded(from);
}
@SuppressWarnings({"unchecked", "rawtypes"}) //We check classes at runtime
private static void moveSafely(@NotNull final PyClass from,
@NotNull final PyClass to,
@NotNull final MembersManager<?> manager,
@NotNull final Collection<PyElement> elementsToMove) {
private static void moveSafely(
@NotNull final PyClass from,
@NotNull final MembersManager<?> manager,
@NotNull final Collection<PyElement> elementsToMove,
@NotNull final PyClass... to) {
manager.checkElementTypes(elementsToMove);
manager.moveMembers(from, to, (Collection)elementsToMove);
manager.moveMembers(from, (Collection)elementsToMove, to);
}
/**
* Checks that all elements has allowed type for manager
*
@@ -171,13 +177,17 @@ public abstract class MembersManager<T extends PyElement> implements Function<T,
}
/**
* Moves element from one class to another
*
* @param from source
* @param to destination
* @param members collection of memebrs to move
* Moves element from one class to another. Returns newly instered elements
* <p/>
* TODO: Doc
* TODO: Doc who and what should return (write about elemnents)
*/
protected abstract void moveMembers(@NotNull PyClass from, @NotNull PyClass to, @NotNull Collection<T> members);
@NotNull
protected abstract void moveMembers(
@NotNull PyClass from,
@NotNull Collection<T> members,
@NotNull PyClass... to);
/**
* Creates {@link com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo} from {@link com.jetbrains.python.psi.PyElement}
@@ -191,6 +201,13 @@ public abstract class MembersManager<T extends PyElement> implements Function<T,
@Override
public abstract PyMemberInfo apply(@NotNull T input);
//TODO: Doct
protected static void deleteElements(@NotNull Collection<? extends PsiElement> pyElementsToDelete) {
for (PsiElement element : pyElementsToDelete) {
element.delete();
}
}
private static class PyMemberExtractor implements Function<PyMemberInfo, PyElement> {
@SuppressWarnings("NullableProblems") //IDEA-120100
@Override
@@ -6,6 +6,7 @@ import com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
@@ -28,8 +29,22 @@ class MethodsManager extends MembersManager<PyFunction> {
}
@Override
protected void moveMembers(@NotNull final PyClass from, @NotNull final PyClass to, @NotNull final Collection<PyFunction> members) {
PyClassRefactoringUtil.moveMethods(members, to);
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();
copies.add(newMethod);
}
PyClassRefactoringUtil.copyMethods(copies, destClass);
}
deleteElements(members);
PyClassRefactoringUtil.insertPassIfNeeded(from);
}
@NotNull
@@ -1,5 +1,6 @@
package com.jetbrains.python.refactoring.classes.membersManager;
import com.intellij.psi.PsiElement;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewDescriptor;
@@ -11,6 +12,8 @@ import java.util.Collection;
import java.util.List;
/**
* TODO: Doc (and why it extends 2 classes)
*
* @author Ilya.Kazakevich
*/
public abstract class PyMembersRefactoringBaseProcessor extends BaseRefactoringProcessor implements UsageViewDescriptor {
@@ -18,17 +21,19 @@ public abstract class PyMembersRefactoringBaseProcessor extends BaseRefactoringP
@NotNull
protected final Collection<PyMemberInfo> myMembersToMove;
@NotNull
protected final PyClass myTo;
@NotNull
protected final PyClass myFrom;
@NotNull
private final PyClass[] myTo;
protected PyMembersRefactoringBaseProcessor(@NotNull final PyClass from,
@NotNull final PyClass to,
@NotNull final Collection<PyMemberInfo> membersToMove) {
//TODO: Doc
protected PyMembersRefactoringBaseProcessor(
@NotNull final Collection<PyMemberInfo> membersToMove,
@NotNull final PyClass from,
@NotNull final PyClass... to) {
super(from.getProject());
myFrom = from;
myTo = to;
myMembersToMove = new ArrayList<PyMemberInfo>(membersToMove);
myTo = to.clone();
}
@NotNull
@@ -39,24 +44,32 @@ public abstract class PyMembersRefactoringBaseProcessor extends BaseRefactoringP
@NotNull
@Override
protected UsageInfo[] findUsages() {
final List<PyUsageInfo> result = new ArrayList<PyUsageInfo>(myMembersToMove.size());
for (final PyMemberInfo pyMemberInfo : myMembersToMove) {
result.add(new PyUsageInfo(pyMemberInfo));
}
return result.toArray(new UsageInfo[result.size()]);
public PsiElement[] getElements() {
return myTo.clone();
}
//TODO: Doc
@NotNull
@Override
protected void performRefactoring(final UsageInfo[] usages) {
final Collection<PyMemberInfo> membersToMoveFromUsage = new ArrayList<PyMemberInfo>(usages.length);
protected final PyUsageInfo[] findUsages() {
final List<PyUsageInfo> result = new ArrayList<PyUsageInfo>(myTo.length);
for (final PyClass pyDestinationClass : myTo) {
result.add(new PyUsageInfo(pyDestinationClass));
}
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);
for (final UsageInfo usage : usages) {
if (!(usage instanceof PyUsageInfo)) {
throw new IllegalArgumentException("Only PyUsageInfo is accepted here");
}
//TODO: Doc
membersToMoveFromUsage.add(((PyUsageInfo)usage).getPyMemberInfo());
destinations.add(((PyUsageInfo)usage).getTo());
}
MembersManager.moveAllMembers(myFrom, myTo, membersToMoveFromUsage);
MembersManager.moveAllMembers(myMembersToMove, myFrom, destinations.toArray(new PyClass[destinations.size()]));
}
}
@@ -1,24 +1,26 @@
package com.jetbrains.python.refactoring.classes.membersManager;
import com.intellij.usageView.UsageInfo;
import com.jetbrains.python.psi.PyClass;
import org.jetbrains.annotations.NotNull;
/**
* TODO: Make it generic to allow to reuse in another projects?
* TODO: Document
*
* @author Ilya.Kazakevich
*/
class PyUsageInfo extends UsageInfo {
@NotNull
private final PyMemberInfo myPyMemberInfo;
private final PyClass myTo;
PyUsageInfo(@NotNull final PyMemberInfo pyMemberInfo) {
super(pyMemberInfo.getMember(), true);
myPyMemberInfo = pyMemberInfo;
PyUsageInfo(@NotNull PyClass to) {
super(to, true); //TODO: Make super generic and get rid of field?
this.myTo = to;
}
@NotNull
PyMemberInfo getPyMemberInfo() {
return myPyMemberInfo;
public PyClass getTo() {
return myTo;
}
}
@@ -1,19 +1,17 @@
package com.jetbrains.python.refactoring.classes.membersManager;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.google.common.collect.Sets;
import com.intellij.psi.PsiNamedElement;
import com.intellij.refactoring.RefactoringBundle;
import com.jetbrains.NotNullPredicate;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.psi.PyExpression;
import com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil;
import com.jetbrains.python.refactoring.classes.ui.PyClassCellRenderer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* Plugin that moves superclasses from one class to another
@@ -25,7 +23,6 @@ class SuperClassesManager extends MembersManager<PyClass> {
super(PyClass.class);
}
private static final NameExtractor NAME_EXTRACTOR = new NameExtractor();
@NotNull
@Override
@@ -34,16 +31,18 @@ class SuperClassesManager extends MembersManager<PyClass> {
}
@Override
protected void moveMembers(@NotNull final PyClass from, @NotNull final PyClass to, @NotNull final Collection<PyClass> members) {
final Set<String> superClassesToMove =
Sets.newHashSet(Collections2.filter(Collections2.transform(members, NAME_EXTRACTOR), NotNullPredicate.INSTANCE));
for (final PyElement member : members) {
superClassesToMove.add(member.getName());
protected void moveMembers(@NotNull PyClass from, @NotNull Collection<PyClass> members, @NotNull PyClass... to) {
for (PyClass destClass : to) {
PyClassRefactoringUtil.addSuperclasses(from.getProject(), destClass, members.toArray(new PyClass[members.size()]));
}
PyClassRefactoringUtil.moveSuperclasses(from, superClassesToMove, to);
PyClassRefactoringUtil.insertImport(to, new ArrayList<PsiNamedElement>(members));
for (PyExpression expression : from.getSuperClassExpressions()) {
for (PyClass member : members) {
if (expression.getText().equals(member.getName())) {
expression.delete();
}
}
}
}
@NotNull
@@ -53,13 +52,4 @@ class SuperClassesManager extends MembersManager<PyClass> {
//TODO: Check for "overrides"
return new PyMemberInfo(input, false, name, false, this);
}
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();
}
}
}
@@ -1,7 +1,7 @@
/**
* Incapsulates knowledge about class members that could be moved to some other class.
* To use (get list of members to move or actually move them) use {@link com.jetbrains.python.refactoring.classes.membersManager.MembersManager#getAllMembersCouldBeMoved(com.jetbrains.python.psi.PyClass)}
* and {@link com.jetbrains.python.refactoring.classes.membersManager.MembersManager#moveAllMembers(com.jetbrains.python.psi.PyClass, com.jetbrains.python.psi.PyClass, java.util.Collection)}
* and {@link com.jetbrains.python.refactoring.classes.membersManager.MembersManager#moveAllMembers(java.util.Collection, com.jetbrains.python.psi.PyClass, com.jetbrains.python.psi.PyClass)}
*
* This class delegates its behaviour to its managers (some kind of plugins). There is one for each member type (one for method, one for field etc).
* You need to extend {@link com.jetbrains.python.refactoring.classes.membersManager.MembersManager} to add some. See its javadoc for more info.
@@ -1,6 +1,5 @@
package com.jetbrains.python.refactoring.classes.pullUp;
import com.intellij.psi.PsiElement;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
@@ -11,18 +10,14 @@ import org.jetbrains.annotations.Nullable;
import java.util.Collection;
/**
* TODO: Doc
*
* @author Ilya.Kazakevich
*/
class PyPullUpProcessor extends PyMembersRefactoringBaseProcessor {
PyPullUpProcessor(@NotNull final PyClass from, @NotNull final PyClass to, @NotNull final Collection<PyMemberInfo> membersToMove) {
super(from, to, membersToMove);
}
@NotNull
@Override
public PsiElement[] getElements() {
return new PsiElement[]{myTo};
super(membersToMove, from, to);
}
@@ -34,7 +34,7 @@ import java.util.List;
/**
* @author Dennis.Ushakov
*/
public class PyPushDownConflicts {
class PyPushDownConflicts {
private static final Logger LOG = Logger.getInstance(PyPushDownProcessor.class.getName());
private final PyClass myClass;
@@ -47,11 +47,11 @@ public class PyPushDownConflicts {
myConflicts = new MultiMap<PsiElement, String>();
}
public MultiMap<PsiElement, String> getConflicts() {
MultiMap<PsiElement, String> getConflicts() {
return myConflicts;
}
public void checkTargetClassConflicts(PyClass clazz) {
void checkTargetClassConflicts(PyClass clazz) {
checkPlacementConflicts(clazz);
}
@@ -1,105 +0,0 @@
/*
* Copyright 2000-2013 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.pushDown;
import com.intellij.openapi.project.Project;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.classMembers.MemberInfoChange;
import com.intellij.refactoring.classMembers.MemberInfoModel;
import com.intellij.refactoring.classMembers.UsedByDependencyMemberInfoModel;
import com.intellij.refactoring.ui.RefactoringDialog;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
import com.jetbrains.python.refactoring.classes.PyMemberInfoStorage;
import com.jetbrains.python.refactoring.classes.ui.PyMemberSelectionPanel;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* @author Dennis.Ushakov
*/
public class PyPushDownDialog extends RefactoringDialog {
private final List<PyMemberInfo> myMemberInfos;
private final PyClass myClass;
private MemberInfoModel<PyElement, PyMemberInfo> myMemberInfoModel;
public PyPushDownDialog(Project project, PyClass aClass, PyMemberInfoStorage memberInfos) {
super(project, true);
myMemberInfos = memberInfos.getClassMemberInfos(aClass);
myClass = aClass;
setTitle(PyPushDownHandler.REFACTORING_NAME);
init();
}
protected String getHelpId() {
return "python.reference.pushMembersDown";
}
@Override
protected void doAction() {
if(!isOKActionEnabled()) return;
final PyPushDownProcessor processor = new PyPushDownProcessor(getProject(), myClass, getSelectedMemberInfos());
invokeRefactoring(processor);
}
protected JComponent createNorthPanel() {
GridBagConstraints gbConstraints = new GridBagConstraints();
JPanel panel = new JPanel(new GridBagLayout());
gbConstraints.insets = new Insets(4, 0, 4, 8);
gbConstraints.weighty = 1;
gbConstraints.weightx = 1;
gbConstraints.gridy = 0;
gbConstraints.gridwidth = GridBagConstraints.REMAINDER;
gbConstraints.fill = GridBagConstraints.BOTH;
gbConstraints.anchor = GridBagConstraints.WEST;
final String fqn = myClass.getName();
panel.add(new JLabel(RefactoringBundle.message("push.members.from.0.down.label", fqn)), gbConstraints);
return panel;
}
protected JComponent createCenterPanel() {
JPanel panel = new JPanel(new BorderLayout());
final PyMemberSelectionPanel memberSelectionPanel = new PyMemberSelectionPanel(
RefactoringBundle.message("members.to.be.pushed.down.panel.title"),
myMemberInfos, null);
panel.add(memberSelectionPanel, BorderLayout.CENTER);
myMemberInfoModel = new UsedByDependencyMemberInfoModel<PyElement, PyClass, PyMemberInfo>(myClass);
myMemberInfoModel.memberInfoChanged(new MemberInfoChange<PyElement, PyMemberInfo>(myMemberInfos));
memberSelectionPanel.init(myMemberInfoModel, myMemberInfos);
return panel;
}
public Collection<PyMemberInfo> getSelectedMemberInfos() {
ArrayList<PyMemberInfo> list = new ArrayList<PyMemberInfo>(myMemberInfos.size());
for (PyMemberInfo info : myMemberInfos) {
if (info.isChecked() && myMemberInfoModel.isMemberEnabled(info)) {
list.add(info);
}
}
return list;
}
}
@@ -20,10 +20,14 @@ import com.intellij.openapi.project.Project;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.util.Query;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyUtil;
import com.jetbrains.python.psi.search.PyClassInheritorsSearch;
import com.jetbrains.python.refactoring.classes.PyClassRefactoringHandler;
import com.jetbrains.python.refactoring.classes.PyMemberInfoStorage;
import com.jetbrains.python.vp.Creator;
import com.jetbrains.python.vp.ViewPresenterUtils;
import org.jetbrains.annotations.NotNull;
/**
@@ -33,12 +37,12 @@ public class PyPushDownHandler extends PyClassRefactoringHandler {
public static final String REFACTORING_NAME = RefactoringBundle.message("push.members.down.title");
@Override
protected void doRefactorImpl(@NotNull Project project,
@NotNull PyClass classUnderRefactoring,
@NotNull PyMemberInfoStorage infoStorage,
protected void doRefactorImpl(@NotNull final Project project,
@NotNull final PyClass classUnderRefactoring,
@NotNull final PyMemberInfoStorage infoStorage,
@NotNull Editor editor) {
//TODO: Move to presenter?
final Query<PyClass> query = PyClassInheritorsSearch.search(classUnderRefactoring, false);
if (query.findFirst() == null) {
final String message = RefactoringBundle.message("class.0.does.not.have.inheritors", classUnderRefactoring.getName());
@@ -46,8 +50,19 @@ public class PyPushDownHandler extends PyClassRefactoringHandler {
return;
}
final PyPushDownDialog dialog = new PyPushDownDialog(project, classUnderRefactoring, infoStorage);
dialog.show();
ViewPresenterUtils.linkViewWithPresenterAndLaunch(PyPushDownPresenter.class, PyPushDownView.class, new Creator<PyPushDownView, PyPushDownPresenter>() {
@NotNull
@Override
public PyPushDownPresenter createPresenter(@NotNull PyPushDownView view) {
return new PyPushDownPresenterImpl(view, classUnderRefactoring, infoStorage);
}
@NotNull
@Override
public PyPushDownView createView(@NotNull PyPushDownPresenter presenter) {
return new PyPushDownViewSwingImpl(classUnderRefactoring, project, presenter);
}
});
}
@Override
@@ -0,0 +1,9 @@
package com.jetbrains.python.refactoring.classes.pushDown;
import com.jetbrains.python.refactoring.classes.membersManager.vp.MembersBasedPresenter;
/**
* @author Ilya.Kazakevich
*/
public interface PyPushDownPresenter extends MembersBasedPresenter {
}
@@ -0,0 +1,48 @@
package com.jetbrains.python.refactoring.classes.pushDown;
import com.intellij.psi.PsiElement;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.refactoring.classMembers.UsedByDependencyMemberInfoModel;
import com.intellij.util.containers.MultiMap;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.psi.PyUtil;
import com.jetbrains.python.psi.search.PyClassInheritorsSearch;
import com.jetbrains.python.refactoring.classes.PyMemberInfoStorage;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
import com.jetbrains.python.refactoring.classes.membersManager.vp.MembersBasedPresenterWithPreviewImpl;
import com.jetbrains.python.refactoring.classes.membersManager.vp.MembersViewInitializationInfo;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
/**
* Created by Ilya.Kazakevich on 10.02.14.
*/
public class PyPushDownPresenterImpl extends MembersBasedPresenterWithPreviewImpl<PyPushDownView> implements PyPushDownPresenter {
public PyPushDownPresenterImpl(@NotNull PyPushDownView view,
@NotNull PyClass classUnderRefactoring,
@NotNull PyMemberInfoStorage infoStorage) {
super(view, classUnderRefactoring, infoStorage);
}
@NotNull
@Override
public BaseRefactoringProcessor createProcessor() {
return new PyPushDownProcessor(myView.getSelectedMemberInfos(), myClassUnderRefactoring);
}
@NotNull
@Override
protected MultiMap<PsiElement, String> getConflicts() {
return new PyPushDownConflicts(myClassUnderRefactoring, myStorage.getClassMemberInfos(myClassUnderRefactoring)).getConflicts();
}
@Override
public void launch() {
UsedByDependencyMemberInfoModel<PyElement, PyClass, PyMemberInfo> model =
new UsedByDependencyMemberInfoModel<PyElement, PyClass, PyMemberInfo>(myClassUnderRefactoring);
myView.configure(new MembersViewInitializationInfo(model, PyUtil.filterOutObject(myStorage.getClassMemberInfos(myClassUnderRefactoring))));
myView.initAndShow();
}
}
@@ -15,120 +15,47 @@
*/
package com.jetbrains.python.refactoring.classes.pushDown;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiNamedElement;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewDescriptor;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.usageView.UsageViewBundle;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.psi.PyExpression;
import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.psi.impl.PyPsiUtils;
import com.jetbrains.python.psi.search.PyClassInheritorsSearch;
import com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
import com.jetbrains.python.refactoring.classes.membersManager.PyMembersRefactoringBaseProcessor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.Collection;
/**
* @author Dennis.Ushakov
*/
public class PyPushDownProcessor extends BaseRefactoringProcessor {
private static final Logger LOG = Logger.getInstance(PyPushDownProcessor.class.getName());
public class PyPushDownProcessor extends PyMembersRefactoringBaseProcessor {
private PyClass myClass;
private final Collection<PyMemberInfo> mySelectedMemberInfos;
private static final String HEADER = RefactoringBundle.message("push.down.members.elements.header");
public PyPushDownProcessor(Project project, PyClass clazz, Collection<PyMemberInfo> selectedMemberInfos) {
super(project);
myClass = clazz;
mySelectedMemberInfos = selectedMemberInfos;
public PyPushDownProcessor(@NotNull Collection<PyMemberInfo> membersToMove,
@NotNull PyClass from) {
super(membersToMove, from, getChildren(from));
}
@NotNull
@Override
protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages) {
return new PyPushDownUsageViewDescriptor(myClass);
private static PyClass[] getChildren(@NotNull final PyClass from) {
Collection<PyClass> all = PyClassInheritorsSearch.search(from, false).findAll();
return all.toArray(new PyClass[all.size()]);
}
@NotNull
@Override
protected UsageInfo[] findUsages() {
final Collection<PyClass> subClasses = PyClassInheritorsSearch.search(myClass, false).findAll();
final UsageInfo[] result = new UsageInfo[subClasses.size()];
ContainerUtil.map2Array(subClasses, result, new Function<PyClass, UsageInfo>() {
public UsageInfo fun(PyClass pyClass) {
return new UsageInfo(pyClass);
}
});
return result;
public String getProcessedElementsHeader() {
return HEADER;
}
@Override
protected void refreshElements(PsiElement[] elements) {
if (elements.length == 1 && elements[0] instanceof PyClass) {
myClass = (PyClass)elements[0];
}
public String getCodeReferencesText(int usagesCount, int filesCount) {
return RefactoringBundle.message("classes.to.push.down.members.to", UsageViewBundle.getReferencesString(usagesCount, filesCount));
}
@Override
protected void performRefactoring(UsageInfo[] usages) {
final Set<String> superClasses = new HashSet<String>();
final Set<PsiNamedElement> extractedClasses = new HashSet<PsiNamedElement>();
final List<PyFunction> methods = new ArrayList<PyFunction>();
for (PyMemberInfo member : mySelectedMemberInfos) {
final PyElement element = member.getMember();
if (element instanceof PyFunction) methods.add((PyFunction)element);
else if (element instanceof PyClass) {
superClasses.add(element.getName());
extractedClasses.add((PyClass)element);
}
else LOG.error("unmatched member class " + element.getClass());
}
final PyElement[] elements = methods.toArray(new PyElement[methods.size()]);
final List<PyExpression> superClassesElements = PyClassRefactoringUtil.removeAndGetSuperClasses(myClass, superClasses);
for (UsageInfo usage : usages) {
final PyClass targetClass = (PyClass)usage.getElement();
PyClassRefactoringUtil.addMethods(targetClass, elements, false);
PyClassRefactoringUtil.addSuperclasses(myClass.getProject(), targetClass, superClassesElements, superClasses);
PyClassRefactoringUtil.insertImport(targetClass, extractedClasses);
}
if (methods.size() != 0) {
PyPsiUtils.removeElements(elements);
PyClassRefactoringUtil.insertPassIfNeeded(myClass);
}
}
@Override
protected boolean preprocessUsages(Ref<UsageInfo[]> ref) {
final UsageInfo[] usages = ref.get();
final PyPushDownConflicts conflicts = new PyPushDownConflicts(myClass, mySelectedMemberInfos);
conflicts.checkSourceClassConflicts();
if (usages.length == 0) {
final String message = RefactoringBundle.message("class.0.does.not.have.inheritors", myClass.getName()) + "\nPushing members down will result in them being deleted";
final int answer = Messages.showYesNoDialog(message, PyPushDownHandler.REFACTORING_NAME, Messages.getWarningIcon());
if (answer != Messages.YES) {
return false;
}
}
for (UsageInfo usage : usages) {
conflicts.checkTargetClassConflicts((PyClass)usage.getElement());
}
return showConflicts(conflicts.getConflicts(), usages);
@Nullable
public String getCommentReferencesText(int usagesCount, int filesCount) {
return null;
}
@Override
@@ -1,54 +0,0 @@
/*
* Copyright 2000-2013 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.pushDown;
import com.intellij.psi.PsiElement;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.usageView.UsageViewBundle;
import com.intellij.usageView.UsageViewDescriptor;
import com.jetbrains.python.psi.PyClass;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author Dennis.Ushakov
*/
public class PyPushDownUsageViewDescriptor implements UsageViewDescriptor {
private final PyClass myClass;
private static final String HEADER = RefactoringBundle.message("push.down.members.elements.header");
public PyPushDownUsageViewDescriptor(final PyClass clazz) {
myClass = clazz;
}
@NotNull
public PsiElement[] getElements() {
return new PsiElement[] {myClass};
}
public String getProcessedElementsHeader() {
return HEADER;
}
public String getCodeReferencesText(int usagesCount, int filesCount) {
return RefactoringBundle.message("classes.to.push.down.members.to", UsageViewBundle.getReferencesString(usagesCount, filesCount));
}
@Nullable
public String getCommentReferencesText(int usagesCount, int filesCount) {
return null;
}
}
@@ -0,0 +1,10 @@
package com.jetbrains.python.refactoring.classes.pushDown;
import com.jetbrains.python.refactoring.classes.membersManager.vp.MembersBasedView;
import com.jetbrains.python.refactoring.classes.membersManager.vp.MembersViewInitializationInfo;
/**
* @author Ilya.Kazakevich
*/
public interface PyPushDownView extends MembersBasedView<MembersViewInitializationInfo> {
}
@@ -0,0 +1,26 @@
package com.jetbrains.python.refactoring.classes.pushDown;
import com.intellij.openapi.project.Project;
import com.intellij.refactoring.RefactoringBundle;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.refactoring.classes.membersManager.vp.MembersBasedViewSwingImpl;
import com.jetbrains.python.refactoring.classes.membersManager.vp.MembersViewInitializationInfo;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
/**
* Created by Ilya.Kazakevich on 10.02.14.
*/
public class PyPushDownViewSwingImpl extends MembersBasedViewSwingImpl<PyPushDownPresenter, MembersViewInitializationInfo>
implements PyPushDownView {
public PyPushDownViewSwingImpl(
@NotNull final PyClass classUnderRefactoring,
@NotNull final Project project,
@NotNull final PyPushDownPresenter presenter) {
super(project, presenter, RefactoringBundle.message("push.members.from.0.down.label", classUnderRefactoring.getName()));
myCenterPanel.add(myPyMemberSelectionPanel, BorderLayout.CENTER);
setTitle(PyPushDownHandler.REFACTORING_NAME);
}
}
@@ -23,6 +23,7 @@ package com.jetbrains.python.vp;
public interface Presenter {
/**
* Launches dialog. Presenter should fetch data and start view.
* TODO: Say you run initand show and launch
*/
void launch();
}
@@ -1,12 +1,12 @@
class Parent(object):
C = 12
def foo(self):
pass
def __init__(self):
self.foo = 12
def foo(self):
pass
class Child(Parent, object):
def __init__(self): pass
@@ -2,8 +2,10 @@ class Foo:
def foo(self):
print("a")
# this is boo
def boo(self):
"""
In python we use multi-line comments inside of method body
"""
print "rrrrr"
@@ -3,6 +3,8 @@ class Foo:
print("a")
class Boo(Foo):
# this is boo
def boo(self):
"""
In python we use multi-line comments inside of method body
"""
print "rrrrr"
@@ -1,5 +1,7 @@
class Foo:
pass
class Boo():
class Boo(Foo):
def foo(self):
print "rrrrr"
@@ -0,0 +1,48 @@
class Dummny(object):
pass
class Parent(object):
"""
This class is usefull.
"""
CLASS_VAR_2 = 2
def __init__(self): pass
def method_2(self):
i = 1
class Child_1(Parent, Dummny):
CLASS_VAR_1 = 1
def __init__(self):
self.inst_var = 12
self.bar = 64
def method_1(self):
"""
Some text
"""
pass
class Child_2(Parent, Dummny):
"""
This class implements most sophisticated algorithm
"""
CLASS_VAR_1 = 1
def __init__(self):
self.inst_var = 12
def lala(self):
pass
def method_1(self):
"""
Some text
"""
pass
@@ -0,0 +1,37 @@
class Dummny(object):
pass
class Parent(object, Dummny):
"""
This class is usefull.
"""
CLASS_VAR_1 = 1
CLASS_VAR_2 = 2
def __init__(self):
self.inst_var = 12
def method_1(self):
"""
Some text
"""
pass
def method_2(self):
i = 1
class Child_1(Parent):
def __init__(self):
self.bar = 64
pass
class Child_2(Parent):
"""
This class implements most sophisticated algorithm
"""
def lala(self):
pass
@@ -15,18 +15,25 @@
*/
package com.jetbrains.python.refactoring.classes;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.project.Project;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.jetbrains.python.fixtures.PyTestCase;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.psi.PyTargetExpression;
import com.jetbrains.python.psi.stubs.PyClassNameIndex;
import com.jetbrains.python.refactoring.classes.membersManager.MembersManager;
import org.hamcrest.Matchers;
import org.jetbrains.annotations.NotNull;
import org.junit.Assert;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* @author Dennis.Ushakov
@@ -76,4 +83,19 @@ public abstract class PyClassRefactoringTest extends PyTestCase {
Assert.assertThat(String.format("Expected one class named %s", name), classes, Matchers.hasSize(1));
return classes.iterator().next();
}
protected void moveViaProcessor(@NotNull Project project, @NotNull final BaseRefactoringProcessor processor) {
CommandProcessor.getInstance().executeCommand(project, new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
processor.run();
}
});
}
}, null, null);
}
}
@@ -15,6 +15,8 @@
*/
package com.jetbrains.python.refactoring.classes.extractSuperclass;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
@@ -30,6 +32,7 @@ import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
@@ -137,6 +140,7 @@ public class PyExtractSuperclassTest extends PyClassRefactoringTest {
new WriteCommandAction.Simple(myFixture.getProject()) {
@Override
protected void run() throws Throwable {
//TODO: Test via presenter
//noinspection ConstantConditions
final String path = base_dir.getPath() + "/a/b";
PyExtractSuperclassHelper.extractSuperclass(clazz, members, superclassName, path + "/foo.py");
@@ -13,12 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.refactoring.classes;
package com.jetbrains.python.refactoring.classes.pullUp;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.refactoring.classes.PyClassRefactoringTest;
import com.jetbrains.python.refactoring.classes.membersManager.MembersManager;
import java.util.Collections;
@@ -66,6 +65,7 @@ public class PyPullUpTest extends PyClassRefactoringTest {
public void testMoveInstanceAttributesNoInit() {
doHelperTest("Child", "#instance_field", "Parent");
}
public void testMoveInstanceAttributesLeaveEmptyInit() {
doHelperTest("Child", "#foo", "Parent");
}
@@ -97,16 +97,7 @@ public class PyPullUpTest extends PyClassRefactoringTest {
final PyClass clazz = findClass(className);
final PyElement member = findMember(className, memberName);
final PyClass superClass = findClass(superClassName);
CommandProcessor.getInstance().executeCommand(clazz.getProject(), new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
MembersManager.moveAllMembers(clazz, superClass, Collections.singleton(MembersManager.findMember(clazz, member)));
}
});
}
}, null, null);
moveViaProcessor(clazz.getProject(),
new PyPullUpProcessor(clazz, superClass, Collections.singleton(MembersManager.findMember(clazz, member))));
}
}
@@ -13,16 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.refactoring.classes;
package com.jetbrains.python.refactoring.classes.pushDown;
import com.intellij.openapi.command.WriteCommandAction;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.refactoring.classes.PyClassRefactoringTest;
import com.jetbrains.python.refactoring.classes.membersManager.MembersManager;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
import com.jetbrains.python.refactoring.classes.pushDown.PyPushDownProcessor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
@@ -37,6 +39,10 @@ public class PyPushDownTest extends PyClassRefactoringTest {
doProcessorTest("Zope", null, "Foo");
}
public void testFull() throws Exception {
doProcessorTest("Parent", null, "#CLASS_VAR_1", "#inst_var", ".method_1", "Dummny");
}
public void testMultiple() throws Exception {
doProcessorTest("Foo", null, ".foo");
}
@@ -49,25 +55,20 @@ public class PyPushDownTest extends PyClassRefactoringTest {
doProcessorTest("Foo", "method <b><code>foo</code></b> is already overridden in class <b><code>Boo</code></b>. Method will not be pushed down to that class.", ".foo");
}
private void doProcessorTest(final String className, final String expectedError, final String... membersName) throws Exception {
private void doProcessorTest(final String className, final String expectedError, final String... memberNames) throws Exception {
try {
String baseName = "/refactoring/pushdown/" + getTestName(true);
myFixture.configureByFile(baseName + ".before.py");
final PyClass clazz = findClass(className);
final List<PyMemberInfo> members = new ArrayList<PyMemberInfo>();
for (String memberName : membersName) {
for (String memberName : memberNames) {
final PyElement member = findMember(className, memberName);
members.add(MembersManager.findMember(clazz, member));
}
final PyPushDownProcessor processor = new PyPushDownProcessor(myFixture.getProject(), clazz, members);
new WriteCommandAction.Simple(myFixture.getProject()) {
@Override
protected void run() throws Throwable {
processor.run();
}
}.execute().throwException();
myFixture.checkResultByFile(baseName + ".after.py");
final PyPushDownProcessor processor = new PyPushDownProcessor(members, clazz);
moveViaProcessor(myFixture.getProject(), processor);
myFixture.checkResultByFile(baseName + ".after.py");
} catch (Exception e) {
if (expectedError == null) throw e;
assertTrue(e.getMessage(), e.getMessage().contains(expectedError));