Code cleanup for "membersManager" package to make it type-safe

PullUp: Refactored to use Processor on MembersManager

StatementList refactoring

PullUp: processor added

PyExtractSuperClass and PyPullMembersUp refactored and covered with tests.

PyExtractSuperClass and PyPullMembersUp refactored and covered with tests.
This commit is contained in:
Ilya.Kazakevich
2014-02-06 18:31:49 +04:00
parent c07a1297d8
commit 24c879c5f5
66 changed files with 2053 additions and 1019 deletions
@@ -36,7 +36,7 @@ import java.util.List;
* Represents a class declaration in source.
*/
public interface PyClass extends PsiNameIdentifierOwner, PyStatement, NameDefiner, PyDocStringOwner, StubBasedPsiElement<PyClassStub>,
ScopeOwner, PyDecoratable, PyTypedElement, PyQualifiedNameOwner {
ScopeOwner, PyDecoratable, PyTypedElement, PyQualifiedNameOwner, PyStatementListContainer {
ArrayFactory<PyClass> ARRAY_FACTORY = new ArrayFactory<PyClass>() {
@NotNull
@Override
@@ -48,8 +48,6 @@ public interface PyClass extends PsiNameIdentifierOwner, PyStatement, NameDefine
@Nullable
ASTNode getNameNode();
@NotNull
PyStatementList getStatementList();
/**
* Returns types of all ancestors from the hierarchy.
@@ -34,7 +34,8 @@ import org.jetbrains.annotations.Nullable;
public interface PyFunction
extends
PsiNamedElement, StubBasedPsiElement<PyFunctionStub>,
PsiNameIdentifierOwner, PyStatement, Callable, NameDefiner, PyDocStringOwner, ScopeOwner, PyDecoratable, PyTypedElement {
PsiNameIdentifierOwner, PyStatement, Callable, NameDefiner, PyDocStringOwner, ScopeOwner, PyDecoratable, PyTypedElement,
PyStatementListContainer{
PyFunction[] EMPTY_ARRAY = new PyFunction[0];
ArrayFactory<PyFunction> ARRAY_FACTORY = new ArrayFactory<PyFunction>() {
@@ -54,9 +55,6 @@ extends
@Nullable
ASTNode getNameNode();
@Nullable
PyStatementList getStatementList();
@Nullable
PyClass getContainingClass();
@@ -0,0 +1,11 @@
package com.jetbrains.python.psi;
import org.jetbrains.annotations.NotNull;
/**
* @author Ilya.Kazakevich
*/
public interface PyStatementListContainer {
@NotNull
PyStatementList getStatementList();
}
@@ -0,0 +1,29 @@
package com.jetbrains;
import com.google.common.base.Predicate;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Filters out nullable elements allowing children to filter not-null elements
*
* @author Ilya.Kazakevich
*/
public class NotNullPredicate<T> implements Predicate<T> {
/**
* Simply filters nulls
*/
public static final Predicate<Object> INSTANCE = new NotNullPredicate<Object>();
@Override
public final boolean apply(@Nullable final T input) {
if (input == null) {
return false;
}
return applyNotNull(input);
}
protected boolean applyNotNull(@NotNull final T input) {
return true;
}
}
@@ -533,6 +533,8 @@ refactoring.introduce.parameter.dialog.title=Extract Parameter
# pull up
refactoring.pull.up.dialog.title=Pull members up to
refactoring.pull.up.dialog.move.members.to.class=Move members to class
refactoring.pull.up.dialog.members.to.be.moved=Following members would be moved
refactoring.pull.up.error.cannot.perform.refactoring.using.selected.elements=Cannot perform pull member up using selected element(s)
refactoring.pull.up.error.cannot.perform.refactoring.not.inside.class=Cannot perform pull member up: not inside the class
refactoring.pull.up.error.cannot.perform.refactoring.no.base.classes=Class {0} has no super classes or none of them could be used for refactoring
@@ -559,6 +561,7 @@ refactoring.extract.method.error.cannot.perform.refactoring.with.local=Cannot pe
# extract superclass
refactoring.extract.super.target.path.outside.roots=Target directory is outside the project.<br>Must be within content roots
refactoring.extract.super.name.0.must.be.ident=Name ''{0}'' is invalid.<br/>Must be a valid Python identifier
refactoring.extract.super.class.no.members.allowed=None of members could be extracted
# move
refactoring.move.class.or.function=Move class or function
@@ -148,6 +148,7 @@ public class PyClassImpl extends PyPresentableElementImpl<PyClassStub> implement
pyVisitor.visitPyClass(this);
}
@Override
@NotNull
public PyStatementList getStatementList() {
final PyStatementList statementList = childToPsi(PyElementTypes.STATEMENT_LIST);
@@ -143,9 +143,12 @@ public class PyFunctionImpl extends PyPresentableElementImpl<PyFunctionStub> imp
return getRequiredStubOrPsiChild(PyElementTypes.PARAMETER_LIST);
}
@Nullable
@Override
@NotNull
public PyStatementList getStatementList() {
return childToPsi(PyElementTypes.STATEMENT_LIST);
final PyStatementList statementList = childToPsi(PyElementTypes.STATEMENT_LIST);
assert statementList != null : "Statement list missing for function " + getText();
return statementList;
}
public PyClass getContainingClass() {
@@ -19,6 +19,7 @@ import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.LangDataKeys;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.editor.CaretModel;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
@@ -31,6 +32,7 @@ import com.intellij.refactoring.lang.ElementsHandler;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyUtil;
import org.jetbrains.annotations.NotNull;
/**
@@ -68,7 +70,27 @@ public abstract class PyClassRefactoringHandler implements RefactoringActionHand
doRefactor(project, elements[0], elements[elements.length - 1], editor, file, dataContext);
}
protected abstract void doRefactor(Project project, PsiElement element1, PsiElement element2, Editor editor, PsiFile file, DataContext dataContext);
private void doRefactor(Project project, PsiElement element1, PsiElement element2, Editor editor, PsiFile file, DataContext dataContext) {
if (ApplicationManagerEx.getApplicationEx().isUnitTestMode()) return;
CommonRefactoringUtil.checkReadOnlyStatus(project, file);
final PyClass clazz = PyUtil.getContainingClassOrSelf(element1);
if (!inClass(clazz, project, editor, "refactoring.pull.up.error.cannot.perform.refactoring.not.inside.class")) return;
assert clazz != null;
final PyMemberInfoStorage infoStorage = PyClassMembersRefactoringSupport.getSelectedMemberInfos(clazz, element1, element2);
doRefactorImpl(project, clazz, infoStorage, editor);
}
protected abstract void doRefactorImpl(@NotNull final Project project,
@NotNull final PyClass classUnderRefactoring,
@NotNull final PyMemberInfoStorage infoStorage,
@NotNull final Editor editor);
protected boolean inClass(PyClass clazz, Project project, Editor editor, String errorMessageId) {
if (clazz == null) {
@@ -99,8 +99,7 @@ public class PyClassRefactoringUtil {
return Collections.emptyList();
}
final List<PyExpression> result = new ArrayList<PyExpression>();
for (PyExpression superClassExpression : clazz.getSuperClassExpressions()) {
//TODO: We probably should use #getName() here, but #getText() was used in previous version so keeped temporary for backward comp.
for (final PyExpression superClassExpression : clazz.getSuperClassExpressions()) {
if (superClassesToRemove.contains(superClassExpression.getText())) {
result.add(superClassExpression);
superClassExpression.delete();
@@ -179,11 +178,11 @@ public class PyClassRefactoringUtil {
PyPsiUtils.removeElements(elements);
}
public static void insertPassIfNeeded(PyClass clazz) {
final PyStatementList statements = clazz.getStatementList();
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(clazz.getProject()).createFromText(LanguageLevel.getDefault(), PyPassStatement.class, PyNames.PASS));
PyElementGenerator.getInstance(element.getProject()).createFromText(LanguageLevel.getDefault(), PyPassStatement.class, PyNames.PASS));
}
}
@@ -1,218 +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.extractSuperclass;
import com.intellij.lang.LanguageNamesValidation;
import com.intellij.lang.refactoring.NamesValidator;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.ui.TextComponentAccessor;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.classMembers.AbstractUsesDependencyMemberInfoModel;
import com.intellij.refactoring.classMembers.DependencyMemberInfoModel;
import com.intellij.refactoring.ui.ConflictsDialog;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PythonLanguage;
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.UpDirectedMembersMovingDialog;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
/**
* @author Dennis.Ushakov
*/
public class PyExtractSuperclassDialog extends UpDirectedMembersMovingDialog {
private final NamesValidator myNamesValidator = LanguageNamesValidation.INSTANCE.forLanguage(PythonLanguage.getInstance());
protected JTextField mySourceClassField;
protected JLabel mySuperNameLabel;
protected JTextField myExtractedSuperNameField;
protected TextFieldWithBrowseButton myTargetDirField;
protected JLabel myDirLabel;
private static final String FILE_BROWSER_TITLE = "Extract superclass to file or directory:";
public PyExtractSuperclassDialog(Project project, PyClass clazz, PyMemberInfoStorage infoStorage) {
super(project, clazz);
myMemberInfos = infoStorage.getClassMemberInfos(myClass);
myExtractedSuperNameField = new JTextField();
myTargetDirField = new TextFieldWithBrowseButton();
initSourceClassField();
setTitle(PyExtractSuperclassHandler.REFACTORING_NAME);
init();
}
protected void initSourceClassField() {
mySourceClassField = new JTextField();
mySourceClassField.setEditable(false);
mySourceClassField.setText(myClass.getName());
}
@Override
protected void doOKAction() {
final String name = getSuperBaseName();
if (!myNamesValidator.isIdentifier(name, myClass.getProject())) {
setErrorText(PyBundle.message("refactoring.extract.super.name.0.must.be.ident", name));
return;
}
boolean found_root = false;
try {
String target_dir = FileUtil.toSystemIndependentName(new File(myTargetDirField.getText()).getCanonicalPath());
for (VirtualFile file : ProjectRootManager.getInstance(myClass.getProject()).getContentRoots()) {
if (StringUtil.startsWithIgnoreCase(target_dir, file.getPath())) {
found_root = true;
break;
}
}
}
catch (IOException ignore) {
}
if (! found_root) {
setErrorText(PyBundle.message("refactoring.extract.super.target.path.outside.roots"));
return;
}
super.doOKAction();
}
@Override
public JComponent getPreferredFocusedComponent() {
return myExtractedSuperNameField;
}
protected JPanel createNorthPanel() {
Box box = createBox();
box.add(Box.createVerticalStrut(10));
JPanel panel = new JPanel(new BorderLayout());
panel.add(box, BorderLayout.CENTER);
return panel;
}
protected Box createBox() {
Box box = Box.createVerticalBox();
JPanel _panel = new JPanel(new BorderLayout());
_panel.add(new JLabel(RefactoringBundle.message("extract.superclass.from")), BorderLayout.NORTH);
_panel.add(mySourceClassField, BorderLayout.CENTER);
box.add(_panel);
box.add(Box.createVerticalStrut(10));
mySuperNameLabel = new JLabel();
mySuperNameLabel.setText(RefactoringBundle.message("superclass.name"));
_panel = new JPanel(new BorderLayout());
_panel.add(mySuperNameLabel, BorderLayout.NORTH);
_panel.add(myExtractedSuperNameField, BorderLayout.CENTER);
box.add(_panel);
box.add(Box.createVerticalStrut(5));
final FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFileOrFolderDescriptor();
final VirtualFile root = getRoot();
assert root != null;
final Project project = myClass.getProject();
descriptor.setRoots(ProjectRootManager.getInstance(project).getContentRoots());
descriptor.setIsTreeRootVisible(true);
myTargetDirField.setText(FileUtil.toSystemDependentName(root.getPath()));
myTargetDirField.addBrowseFolderListener(FILE_BROWSER_TITLE,
null, project, descriptor, TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT);
_panel = new JPanel(new BorderLayout());
myDirLabel = new JLabel();
myDirLabel.setText(FILE_BROWSER_TITLE);
_panel.add(myDirLabel, BorderLayout.NORTH);
_panel.add(myTargetDirField, BorderLayout.CENTER);
box.add(_panel);
return box;
}
@Nullable
protected VirtualFile getRoot() {
return myClass.getContainingFile().getVirtualFile();
}
@Override
protected String getMembersBorderTitle() {
return RefactoringBundle.message("members.to.form.superclass");
}
@Override
protected String getHelpId() {
return "python.reference.extractSuperclass";
}
@Override
protected DependencyMemberInfoModel<PyElement, PyMemberInfo> createMemberInfoModel() {
return new MyMemberInfoModel(myClass);
}
@Override
public boolean checkConflicts() {
final Collection<PyMemberInfo> infos = getSelectedMemberInfos();
if (!checkWritable(myClass, infos)) return false;
if (infos.size() == 0) {
ConflictsDialog conflictsDialog = new ConflictsDialog(myClass.getProject(), RefactoringBundle.message("no.members.selected"));
conflictsDialog.show();
return conflictsDialog.isOK();
}
return true;
}
public String getSuperBaseName() {
return myExtractedSuperNameField.getText();
}
public String getTargetFile() {
return myTargetDirField.getText();
}
private static class MyMemberInfoModel extends AbstractUsesDependencyMemberInfoModel<PyElement, PyClass, PyMemberInfo> {
public MyMemberInfoModel(PyClass clazz) {
super(clazz, null, false);
}
public boolean isAbstractEnabled(PyMemberInfo member) {
return false;
}
public int checkForProblems(@NotNull PyMemberInfo member) {
return member.isChecked() ? OK : super.checkForProblems(member);
}
@Override
protected int doCheck(@NotNull PyMemberInfo memberInfo, int problem) {
return problem;
}
}
}
@@ -15,23 +15,17 @@
*/
package com.jetbrains.python.refactoring.classes.extractSuperclass;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.util.PsiNavigateUtil;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyUtil;
import com.jetbrains.python.refactoring.classes.PyClassMembersRefactoringSupport;
import com.jetbrains.python.refactoring.classes.PyClassRefactoringHandler;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
import com.jetbrains.python.refactoring.classes.PyMemberInfoStorage;
import java.util.Collection;
import com.jetbrains.python.vp.Creator;
import com.jetbrains.python.vp.ViewPresenterUtils;
import org.jetbrains.annotations.NotNull;
/**
* @author Dennis.Ushakov
@@ -39,27 +33,39 @@ import java.util.Collection;
public class PyExtractSuperclassHandler extends PyClassRefactoringHandler {
public static final String REFACTORING_NAME = RefactoringBundle.message("extract.superclass.title");
@Override
protected void doRefactor(Project project, PsiElement element1, PsiElement element2, Editor editor, PsiFile file, DataContext dataContext) {
CommonRefactoringUtil.checkReadOnlyStatus(project, file);
final PyClass clazz = PyUtil.getContainingClassOrSelf(element1);
if (!inClass(clazz, project, editor, "refactoring.pull.up.error.cannot.perform.refactoring.not.inside.class")) return;
final PyMemberInfoStorage infoStorage = PyClassMembersRefactoringSupport.getSelectedMemberInfos(clazz, element1, element2);
if (ApplicationManagerEx.getApplicationEx().isUnitTestMode()) return;
final PyExtractSuperclassDialog dialog = new PyExtractSuperclassDialog(project, clazz, infoStorage);
dialog.show();
if(dialog.isOK()) {
extractWithHelper(clazz, dialog.getSelectedMemberInfos(), dialog.getSuperBaseName(), dialog.getTargetFile());
protected void doRefactorImpl(@NotNull final Project project,
@NotNull final PyClass classUnderRefactoring,
@NotNull final PyMemberInfoStorage infoStorage,
@NotNull final Editor editor) {
//TODO: Move to presenter
if (PyExtractSuperclassHelper.filterOutDeniedMembers(infoStorage.getClassMemberInfos(classUnderRefactoring)).isEmpty()) {
CommonRefactoringUtil.showErrorHint(project, editor, PyBundle
.message("refactoring.extract.super.class.no.members.allowed"), RefactoringBundle.message("extract.superclass.elements.header"),
null);
return;
}
ViewPresenterUtils.linkViewWithPresenterAndLaunch(PyExtractSuperclassPresenter.class, PyExtractSuperclassView.class,
new Creator<PyExtractSuperclassView, PyExtractSuperclassPresenter>() {
@NotNull
@Override
public PyExtractSuperclassPresenter createPresenter(@NotNull final PyExtractSuperclassView view) {
return new PyExtractSuperclassPresenterImpl(view, classUnderRefactoring,
infoStorage);
}
@NotNull
@Override
public PyExtractSuperclassView createView(@NotNull final PyExtractSuperclassPresenter presenter) {
return new PyExtractSuperclassViewSwingImpl(classUnderRefactoring, project,
presenter);
}
}
);
}
private static void extractWithHelper(PyClass clazz, Collection<PyMemberInfo> selectedMemberInfos, String superBaseName, String targetFile) {
PsiNavigateUtil.navigate(PyExtractSuperclassHelper.extractSuperclass(clazz, selectedMemberInfos, superBaseName, targetFile));
}
@Override
protected String getTitle() {
@@ -15,95 +15,80 @@
*/
package com.jetbrains.python.refactoring.classes.extractSuperclass;
import com.intellij.openapi.application.ApplicationManager;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Ref;
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.psi.*;
import com.intellij.refactoring.RefactoringBundle;
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.impl.PyPsiUtils;
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.refactoring.classes.PyClassRefactoringUtil;
import com.jetbrains.python.refactoring.classes.membersManager.MembersManager;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.*;
import java.util.Collection;
import java.util.Collections;
/**
* @author Dennis.Ushakov
*/
public class PyExtractSuperclassHelper {
public final class PyExtractSuperclassHelper {
private static final Logger LOG = Logger.getInstance(PyExtractSuperclassHelper.class.getName());
/**
* Accepts only those members whose element is PyClass object (new classes)
*/
private static final Predicate<PyMemberInfo> ALLOW_OBJECT = new ObjectPredicate(true);
private PyExtractSuperclassHelper() {}
private PyExtractSuperclassHelper() {
}
public static PsiElement extractSuperclass(final PyClass clazz,
final Collection<PyMemberInfo> selectedMemberInfos,
final String superBaseName,
final String targetFile) {
final Set<String> superClasses = new HashSet<String>();
final Set<PsiNamedElement> extractedClasses = new HashSet<PsiNamedElement>();
final List<PyFunction> methods = new ArrayList<PyFunction>();
for (PyMemberInfo member : selectedMemberInfos) {
final PyElement element = member.getMember();
if (element instanceof PyFunction) methods.add((PyFunction)element);
else if (element instanceof PyClass) {
extractedClasses.add((PyClass)element);
superClasses.add(element.getName());
}
else LOG.error("unmatched member class " + element.getClass());
}
static void extractSuperclass(final PyClass clazz,
final Collection<PyMemberInfo> selectedMemberInfos,
final String superBaseName,
final String targetFile) {
// 'object' superclass is always pulled up, even if not selected explicitly
for (PyExpression expr : clazz.getSuperClassExpressions()) {
if (PyNames.OBJECT.equals(expr.getText()) && !superClasses.contains(PyNames.OBJECT)) {
superClasses.add(PyNames.OBJECT);
if (MembersManager.findMember(selectedMemberInfos, ALLOW_OBJECT) == null) {
final PyMemberInfo object = MembersManager.findMember(clazz, ALLOW_OBJECT);
if (object != null) {
selectedMemberInfos.add(object);
}
}
final Project project = clazz.getProject();
final Ref<PyClass> newClassRef = new Ref<PyClass>();
CommandProcessor.getInstance().executeCommand(project, new Runnable() {
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
public void run() {
final PyElement[] elements = methods.toArray(new PyElement[methods.size()]);
final String text = "class " + superBaseName + ":\n pass" + "\n";
PyClass newClass = PyElementGenerator.getInstance(project).createFromText(LanguageLevel.getDefault(), PyClass.class, text);
newClass = placeNewClass(project, newClass, clazz, targetFile);
newClassRef.set(newClass);
PyClassRefactoringUtil.moveMethods(methods, newClass);
PyClassRefactoringUtil.moveSuperclasses(clazz, superClasses, newClass);
PyClassRefactoringUtil.addSuperclasses(project, clazz, null, Collections.singleton(superBaseName));
PyClassRefactoringUtil.insertImport(newClass, extractedClasses);
if (elements.length > 0) {
PyPsiUtils.removeElements(elements);
}
PyClassRefactoringUtil.insertPassIfNeeded(clazz);
}
});
}
}, RefactoringBundle.message("extract.superclass.command.name", superBaseName, clazz.getName()), null);
return newClassRef.get();
final String text = "class " + superBaseName + ":\n pass" + "\n";
PyClass newClass = PyElementGenerator.getInstance(project).createFromText(LanguageLevel.getDefault(), PyClass.class, text);
newClass = placeNewClass(project, newClass, clazz, targetFile);
MembersManager.moveAllMembers(clazz, newClass, selectedMemberInfos);
PyClassRefactoringUtil.addSuperclasses(project, clazz, null, Collections.singleton(superBaseName));
}
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));
VirtualFile file = VirtualFileManager.getInstance()
.findFileByUrl(ApplicationManagerEx.getApplicationEx().isUnitTestMode() ? targetFile : VfsUtil.pathToUrl(targetFile));
// file is the same as the source
if (Comparing.equal(file, clazz.getContainingFile().getVirtualFile())) {
return (PyClass)clazz.getParent().addBefore(newClass, clazz);
@@ -131,7 +116,8 @@ public class PyExtractSuperclassHelper {
else { // existing file
psiFile = PsiManager.getInstance(project).findFile(file);
}
} catch (IOException e) {
}
catch (IOException e) {
LOG.error(e);
}
@@ -147,6 +133,7 @@ public class PyExtractSuperclassHelper {
/**
* Places a file at the end of given path, creating intermediate dirs and inits.
*
* @param project
* @param path
* @param filename
@@ -157,6 +144,7 @@ public class PyExtractSuperclassHelper {
return placeFile(project, path, filename, null);
}
//TODO: Mover to the other class? That is not good to dependent PyUtils on this class
public static PsiFile placeFile(Project project, String path, String filename, @Nullable String content) throws IOException {
PsiDirectory psiDir = createDirectories(project, path);
LOG.assertTrue(psiDir != null);
@@ -177,8 +165,9 @@ public class PyExtractSuperclassHelper {
/**
* Create all intermediate dirs with inits from one of roots up to target dir.
*
* @param project
* @param target a full path to target dir
* @param target a full path to target dir
* @return deepest child directory, or null if target is not in roots or process fails at some point.
*/
@Nullable
@@ -198,7 +187,7 @@ public class PyExtractSuperclassHelper {
}
}
if (the_root == null) {
throw new IOException("Can't find '"+ target +"' among roots");
throw new IOException("Can't find '" + target + "' among roots");
}
if (the_rest != null) {
final LocalFileSystem lfs = LocalFileSystem.getInstance();
@@ -213,7 +202,9 @@ public class PyExtractSuperclassHelper {
throw new IOException("Expected dir, but got non-dir: " + subdir.getPath());
}
}
else subdir = the_root.createChildDirectory(lfs, dirs[i]);
else {
subdir = the_root.createChildDirectory(lfs, dirs[i]);
}
VirtualFile init_vfile = subdir.findChild(PyNames.INIT_DOT_PY);
if (init_vfile == null) init_vfile = subdir.createChildData(lfs, PyNames.INIT_DOT_PY);
/*
@@ -236,4 +227,44 @@ public 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);
}
}
}
@@ -0,0 +1,31 @@
package com.jetbrains.python.refactoring.classes.extractSuperclass;
import com.intellij.refactoring.classMembers.AbstractUsesDependencyMemberInfoModel;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
import org.jetbrains.annotations.NotNull;
/**
* @author Ilya.Kazakevich
*/
class PyExtractSuperclassInfoModel extends AbstractUsesDependencyMemberInfoModel<PyElement, PyClass, PyMemberInfo> {
PyExtractSuperclassInfoModel(@NotNull final PyClass clazz) {
super(clazz, null, false);
}
@Override
public boolean isAbstractEnabled(final PyMemberInfo member) {
return false;
}
@Override
public int checkForProblems(@NotNull final PyMemberInfo member) {
return member.isChecked() ? OK : super.checkForProblems(member);
}
@Override
protected int doCheck(@NotNull final PyMemberInfo memberInfo, final int problem) {
return problem;
}
}
@@ -0,0 +1,46 @@
package com.jetbrains.python.refactoring.classes.extractSuperclass;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.refactoring.classMembers.MemberInfoModel;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
import com.jetbrains.python.refactoring.classes.membersManager.vp.MembersViewInitializationInfo;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
/**
* View configuration for "extract superclass"
*
* @author Ilya.Kazakevich
*/
class PyExtractSuperclassInitializationInfo extends MembersViewInitializationInfo {
@NotNull
private final String myDefaultFilePath;
@NotNull
private final VirtualFile[] myRoots;
/**
* @param defaultFilePath module file path to display. User will be able to change it later.
* @param roots virtual files where user may add new module
*/
PyExtractSuperclassInitializationInfo(@NotNull final MemberInfoModel<PyElement, PyMemberInfo> memberInfoModel,
@NotNull final Collection<PyMemberInfo> memberInfos,
@NotNull final String defaultFilePath,
@NotNull final VirtualFile... roots) {
super(memberInfoModel, memberInfos);
myDefaultFilePath = defaultFilePath;
myRoots = roots.clone();
}
@NotNull
public String getDefaultFilePath() {
return myDefaultFilePath;
}
@NotNull
public VirtualFile[] getRoots() {
return myRoots.clone();
}
}
@@ -0,0 +1,10 @@
package com.jetbrains.python.refactoring.classes.extractSuperclass;
import com.jetbrains.python.refactoring.classes.membersManager.vp.MembersBasedPresenter;
/**
* @author Ilya.Kazakevich
*/
public interface PyExtractSuperclassPresenter extends MembersBasedPresenter {
//TODO: Remove?
}
@@ -0,0 +1,97 @@
package com.jetbrains.python.refactoring.classes.extractSuperclass;
import com.intellij.lang.LanguageNamesValidation;
import com.intellij.lang.refactoring.NamesValidator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.refactoring.RefactoringBundle;
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.refactoring.classes.PyMemberInfoStorage;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
import com.jetbrains.python.refactoring.classes.membersManager.vp.BadDataException;
import com.jetbrains.python.refactoring.classes.membersManager.vp.MembersBasedPresenterNoPreviewImpl;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
/**
* @author Ilya.Kazakevich
*/
class PyExtractSuperclassPresenterImpl extends MembersBasedPresenterNoPreviewImpl<PyExtractSuperclassView>
implements PyExtractSuperclassPresenter {
private final NamesValidator myNamesValidator = LanguageNamesValidation.INSTANCE.forLanguage(PythonLanguage.getInstance());
@NotNull
private static final MultiMap<PsiElement, String> EMPTY_MAP = MultiMap.create();
PyExtractSuperclassPresenterImpl(@NotNull final PyExtractSuperclassView view,
@NotNull final PyClass classUnderRefactoring,
@NotNull final PyMemberInfoStorage infoStorage) {
super(view, classUnderRefactoring, infoStorage);
}
@NotNull
@Override
protected MultiMap<PsiElement, String> getConflicts() {
return EMPTY_MAP; //There are no conflicts for extracting
}
@Override
protected void validateView() throws BadDataException {
super.validateView();
final Project project = myClassUnderRefactoring.getProject();
if (!myNamesValidator.isIdentifier(myView.getSuperClassName(), project)) {
throw new BadDataException(PyBundle.message("refactoring.extract.super.name.0.must.be.ident", myView.getSuperClassName()));
}
boolean rootFound = false;
try {
final String targetDir = FileUtil.toSystemIndependentName(new File(myView.getModuleFile()).getCanonicalPath());
for (final VirtualFile file : ProjectRootManager.getInstance(project).getContentRoots()) {
if (StringUtil.startsWithIgnoreCase(targetDir, file.getPath())) {
rootFound = true;
break;
}
}
}
catch (final IOException ignore) {
}
if (!rootFound) {
throw new BadDataException(PyBundle.message("refactoring.extract.super.target.path.outside.roots"));
}
}
@Override
public void launch() {
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));
myView.configure(
new PyExtractSuperclassInitializationInfo(new PyExtractSuperclassInfoModel(myClassUnderRefactoring), pyMemberInfos, defaultFilePath,
roots));
myView.initAndShow();
}
@NotNull
@Override
protected String getCommandName() {
return RefactoringBundle.message("extract.superclass.command.name", myView.getSuperClassName(), myClassUnderRefactoring.getName());
}
@Override
protected void refactorNoPreview() {
PyExtractSuperclassHelper
.extractSuperclass(myClassUnderRefactoring, myView.getSelectedMemberInfos(), myView.getSuperClassName(), myView.getModuleFile());
}
}
@@ -0,0 +1,25 @@
package com.jetbrains.python.refactoring.classes.extractSuperclass;
import com.jetbrains.python.refactoring.classes.membersManager.vp.MembersBasedView;
import org.jetbrains.annotations.NotNull;
/**
* @author Ilya.Kazakevich
*/
public interface PyExtractSuperclassView extends MembersBasedView<PyExtractSuperclassInitializationInfo> {
/**
*
* @return path to destination file (module) where user wants to create new class
*/
@NotNull
String getModuleFile();
/**
*
* @return name user wants to give to new class
*/
@NotNull
String getSuperClassName();
}
@@ -0,0 +1,109 @@
package com.jetbrains.python.refactoring.classes.extractSuperclass;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.ui.TextComponentAccessor;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.refactoring.RefactoringBundle;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.refactoring.classes.membersManager.vp.MembersBasedViewSwingImpl;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
/**
* @author Ilya.Kazakevich
*/
class PyExtractSuperclassViewSwingImpl
extends MembersBasedViewSwingImpl<PyExtractSuperclassPresenter, PyExtractSuperclassInitializationInfo>
implements PyExtractSuperclassView {
private static final String FILE_OR_DIRECTORY = RefactoringBundle.message("extract.superclass.elements.header");
@NotNull
private final JTextArea myExtractedSuperNameField = new JTextArea();
@NotNull
private final FileChooserDescriptor myFileChooserDescriptor;
@NotNull
private final TextFieldWithBrowseButton myTargetDirField;
PyExtractSuperclassViewSwingImpl(@NotNull final PyClass classUnderRefactoring,
@NotNull final Project project,
@NotNull final PyExtractSuperclassPresenter presenter) {
super(project, presenter, RefactoringBundle.message("extract.superclass.from"));
setTitle(PyExtractSuperclassHandler.REFACTORING_NAME);
final Box box = Box.createVerticalBox();
JPanel panel = new JPanel(new BorderLayout());
panel.add(new JLabel(RefactoringBundle.message("extract.superclass.from")), BorderLayout.NORTH);
final JTextField sourceClassField = new JTextField();
sourceClassField.setEditable(false);
sourceClassField.setText(classUnderRefactoring.getName());
panel.add(sourceClassField, BorderLayout.CENTER);
box.add(panel);
box.add(Box.createVerticalStrut(10));
final JLabel superNameLabel = new JLabel();
superNameLabel.setText(RefactoringBundle.message("superclass.name"));
panel = new JPanel(new BorderLayout());
panel.add(superNameLabel, BorderLayout.NORTH);
panel.add(myExtractedSuperNameField, BorderLayout.CENTER);
box.add(panel);
box.add(Box.createVerticalStrut(5));
myFileChooserDescriptor = FileChooserDescriptorFactory.createSingleFileOrFolderDescriptor();
myFileChooserDescriptor.setRoots(ProjectRootManager.getInstance(project).getContentRoots());
myFileChooserDescriptor.setIsTreeRootVisible(true);
myTargetDirField = new TextFieldWithBrowseButton();
myTargetDirField
.addBrowseFolderListener(FILE_OR_DIRECTORY, null, project, myFileChooserDescriptor, TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT);
panel = new JPanel(new BorderLayout());
final JLabel dirLabel = new JLabel();
dirLabel.setText(FILE_OR_DIRECTORY); //u18n
panel.add(dirLabel, BorderLayout.NORTH);
panel.add(myTargetDirField, BorderLayout.CENTER);
box.add(panel);
box.add(Box.createVerticalStrut(10));
myTopPanel.add(box, BorderLayout.CENTER);
myCenterPanel.add(myPyMemberSelectionPanel, BorderLayout.CENTER);
setPreviewResults(false);
}
@Override
public JComponent getPreferredFocusedComponent() {
return myExtractedSuperNameField;
}
@Override
public void configure(@NotNull final PyExtractSuperclassInitializationInfo configInfo) {
super.configure(configInfo);
myFileChooserDescriptor.setRoots(configInfo.getRoots());
myTargetDirField.setText(configInfo.getDefaultFilePath());
}
@NotNull
@Override
public String getModuleFile() {
return myTargetDirField.getText();
}
@NotNull
@Override
public String getSuperClassName() {
return myExtractedSuperNameField.getText();
}
}
@@ -0,0 +1,5 @@
/**
* "Extract superclass" refactoring
* @author Ilya.Kazakevich
*/
package com.jetbrains.python.refactoring.classes.extractSuperclass;
@@ -34,7 +34,7 @@ class ClassFieldsManager extends FieldsManager {
@NotNull
@Override
protected List<PyTargetExpression> getFieldsByClass(@NotNull PyClass pyClass) {
protected List<PyTargetExpression> getFieldsByClass(@NotNull final PyClass pyClass) {
return pyClass.getClassAttributes();
}
}
@@ -1,22 +1,21 @@
package com.jetbrains.python.refactoring.classes.membersManager;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import com.intellij.psi.PsiElement;
import com.jetbrains.NotNullPredicate;
import com.jetbrains.python.psi.PyAssignmentStatement;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.psi.PyTargetExpression;
import com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.List;
/**
* Parent of all field-based plugins (like class fields, instance fields and so on)
*
* @author Ilya.Kazakevich
*/
abstract class FieldsManager extends MembersManager<PyTargetExpression> {
@@ -39,7 +38,8 @@ abstract class FieldsManager extends MembersManager<PyTargetExpression> {
/**
* Checks if class has fields. Only child may know how to obtain field
* @param pyClass class to check
*
* @param pyClass class to check
* @param fieldName field name
* @return true if has one
*/
@@ -47,6 +47,7 @@ abstract class FieldsManager extends MembersManager<PyTargetExpression> {
/**
* Returns all fields by class. Only child may know how to obtain fields
*
* @param pyClass class to check
* @return list of fields in target expression (declaration) form
*/
@@ -56,8 +57,8 @@ abstract class FieldsManager extends MembersManager<PyTargetExpression> {
@NotNull
@Override
public PyMemberInfo apply(@NotNull final PyElement input) {
return new PyMemberInfo(input, myStatic, input.getText(), isOverrides((PyTargetExpression)input), this);
public PyMemberInfo apply(@NotNull final PyTargetExpression input) {
return new PyMemberInfo(input, myStatic, input.getText(), isOverrides(input), this);
}
@Nullable
@@ -73,22 +74,12 @@ abstract class FieldsManager extends MembersManager<PyTargetExpression> {
return classHasField(aClass, name) ? true : null;
}
@Override
protected void moveMembers(@NotNull final PyClass from,
@NotNull final PyClass to,
@NotNull final Collection<PyTargetExpression> members) {
PyClassRefactoringUtil.moveFieldDeclarationToStatement(members, to.getStatementList());
}
private static class SimpleAssignmentsOnly implements Predicate<PyTargetExpression> {
private static class SimpleAssignmentsOnly extends NotNullPredicate<PyTargetExpression> {
//Support only simplest cases like CLASS_VAR = 42.
//Tuples (CLASS_VAR_1, CLASS_VAR_2) = "spam", "eggs" are not supported by now
@Override
public boolean apply(@Nullable final PyTargetExpression input) {
if (input == null) {
return false; //Filter out empties (which probably would never be here)
}
public boolean applyNotNull(@NotNull final PyTargetExpression input) {
final PsiElement parent = input.getParent();
return (parent != null) && PyAssignmentStatement.class.isAssignableFrom(parent.getClass());
}
@@ -22,15 +22,18 @@ class InstanceFieldsManager extends FieldsManager {
@NotNull final PyClass to,
@NotNull final Collection<PyTargetExpression> members) {
//We need __init__ method, and if there is no any -- we need to create it
PyFunction initMethod = to.findMethodByName(PyNames.INIT, false);
if (initMethod == null) {
initMethod = PyClassRefactoringUtil.createMethod(PyNames.INIT, to, null);
}
final PyStatementList statementList = initMethod.getStatementList();
if (statementList == null) {
return; //TODO: Investigate how could it be
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);
}
}
@Override
@@ -7,6 +7,7 @@ import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Collections2;
import com.google.common.collect.Multimap;
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.refactoring.classes.PyClassRefactoringUtil;
@@ -26,7 +27,7 @@ import java.util.List;
*
* @author Ilya.Kazakevich
*/
public abstract class MembersManager<T extends PyElement> implements Function<PyElement, PyMemberInfo> {
public abstract class MembersManager<T extends PyElement> implements Function<T, PyMemberInfo> {
/**
* List of managers. Class delegates all logic to them.
*/
@@ -52,11 +53,19 @@ public abstract class MembersManager<T extends PyElement> implements Function<Py
final List<PyMemberInfo> result = new ArrayList<PyMemberInfo>();
for (final MembersManager<?> manager : MANAGERS) {
result.addAll(Collections2.transform(manager.getMembersCouldBeMoved(pyClass), manager));
result.addAll(transformSafely(pyClass, manager));
}
return result;
}
@SuppressWarnings({"unchecked", "rawtypes"}) //We check type at runtime
@NotNull
private static Collection<PyMemberInfo> transformSafely(@NotNull final PyClass pyClass, @NotNull final MembersManager<?> manager) {
final List<PyElement> membersCouldBeMoved = manager.getMembersCouldBeMoved(pyClass);
manager.checkElementTypes(membersCouldBeMoved);
return (Collection<PyMemberInfo>)Collections2.transform(membersCouldBeMoved, (Function)manager);
}
/**
* Moves members from one class to another
@@ -85,14 +94,43 @@ public abstract class MembersManager<T extends PyElement> implements Function<Py
@NotNull final PyClass to,
@NotNull final MembersManager<?> manager,
@NotNull final Collection<PyElement> elementsToMove) {
for (final PyElement pyElement : elementsToMove) {
Preconditions.checkArgument(manager.myExpectedClass.isAssignableFrom(pyElement.getClass()),
String.format("Manager %s expected %s but got %s", manager, manager.myExpectedClass, pyElement));
}
manager.checkElementTypes(elementsToMove);
manager.moveMembers(from, to, (Collection)elementsToMove);
}
/**
* Checks that all elements has allowed type for manager
*
* @param elements elements to check against manager
*/
private void checkElementTypes(@NotNull final Collection<PyElement> elements) {
for (final PyElement pyElement : elements) {
Preconditions.checkArgument(myExpectedClass.isAssignableFrom(pyElement.getClass()),
String.format("Manager %s expected %s but got %s", this, myExpectedClass, pyElement));
}
}
/**
* TODO: Docuyment
*/
@Nullable
public static PyMemberInfo findMember(@NotNull final Collection<PyMemberInfo> members, @NotNull final Predicate<PyMemberInfo> predicate) {
for (final PyMemberInfo pyMemberInfo : members) {
if (predicate.apply(pyMemberInfo)) {
return pyMemberInfo;
}
}
return null;
}
/**
* TODO: Docuyment
*/
@Nullable
public static PyMemberInfo findMember(@NotNull final PyClass pyClass, @NotNull final Predicate<PyMemberInfo> predicate) {
return findMember(getAllMembersCouldBeMoved(pyClass), predicate);
}
/**
* Finds member in class. It is here only for backward compatibility with some tests.
*/
@@ -100,10 +138,9 @@ public abstract class MembersManager<T extends PyElement> implements Function<Py
@TestOnly
@NotNull
public static PyMemberInfo findMember(@NotNull final PyClass pyClass, @NotNull final PyElement pyElement) {
for (final PyMemberInfo pyMemberInfo : getAllMembersCouldBeMoved(pyClass)) {
if (pyMemberInfo.getMember().equals(pyElement)) {
return pyMemberInfo;
}
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));
}
@@ -145,13 +182,14 @@ public abstract class MembersManager<T extends PyElement> implements Function<Py
/**
* 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 PyElement input);
public abstract PyMemberInfo apply(@NotNull T input);
private static class PyMemberExtractor implements Function<PyMemberInfo, PyElement> {
@SuppressWarnings("NullableProblems") //IDEA-120100
@@ -161,10 +199,23 @@ public abstract class MembersManager<T extends PyElement> implements Function<Py
}
}
private static class NamelessFilter<T extends PyElement & PsiNamedElement> implements Predicate<T> {
private static class NamelessFilter<T extends PyElement & PsiNamedElement> extends NotNullPredicate<T> {
@Override
public boolean apply(@Nullable final T input) {
return (input != null) && (input.getName() != null);
public boolean applyNotNull(@NotNull final T input) {
return input.getName() != null;
}
}
private static class FindByElement extends NotNullPredicate<PyMemberInfo> {
private final PyElement myPyElement;
private FindByElement(final PyElement pyElement) {
myPyElement = pyElement;
}
@Override
public boolean applyNotNull(@NotNull final PyMemberInfo input) {
return input.getMember().equals(myPyElement);
}
}
}
@@ -34,13 +34,11 @@ class MethodsManager extends MembersManager<PyFunction> {
@NotNull
@Override
public PyMemberInfo apply(@NotNull final PyElement input) {
//TODO: Use generics to prevent casting in each subclass
final PyFunction pyFunction = (PyFunction)input;
public PyMemberInfo apply(@NotNull final PyFunction pyFunction) {
final PyUtil.MethodFlags flags = PyUtil.MethodFlags.of(pyFunction);
assert flags != null : "No flags return while element is function " + pyFunction;
final boolean isStatic = flags.isStaticMethod() || flags.isClassMethod();
return new PyMemberInfo(input, isStatic, buildDisplayMethodName(pyFunction), isOverrides(pyFunction), this);
return new PyMemberInfo(pyFunction, isStatic, buildDisplayMethodName(pyFunction), isOverrides(pyFunction), this);
}
@Nullable
@@ -0,0 +1,62 @@
package com.jetbrains.python.refactoring.classes.membersManager;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewDescriptor;
import com.jetbrains.python.psi.PyClass;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* @author Ilya.Kazakevich
*/
public abstract class PyMembersRefactoringBaseProcessor extends BaseRefactoringProcessor implements UsageViewDescriptor {
@NotNull
protected final Collection<PyMemberInfo> myMembersToMove;
@NotNull
protected final PyClass myTo;
@NotNull
protected final PyClass myFrom;
protected PyMembersRefactoringBaseProcessor(@NotNull final PyClass from,
@NotNull final PyClass to,
@NotNull final Collection<PyMemberInfo> membersToMove) {
super(from.getProject());
myFrom = from;
myTo = to;
myMembersToMove = new ArrayList<PyMemberInfo>(membersToMove);
}
@NotNull
@Override
protected UsageViewDescriptor createUsageViewDescriptor(final UsageInfo[] usages) {
return this;
}
@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()]);
}
@Override
protected void performRefactoring(final UsageInfo[] usages) {
final Collection<PyMemberInfo> membersToMoveFromUsage = new ArrayList<PyMemberInfo>(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());
}
MembersManager.moveAllMembers(myFrom, myTo, membersToMoveFromUsage);
}
}
@@ -0,0 +1,24 @@
package com.jetbrains.python.refactoring.classes.membersManager;
import com.intellij.usageView.UsageInfo;
import org.jetbrains.annotations.NotNull;
/**
* TODO: Make it generic to allow to reuse in another projects?
*
* @author Ilya.Kazakevich
*/
class PyUsageInfo extends UsageInfo {
@NotNull
private final PyMemberInfo myPyMemberInfo;
PyUsageInfo(@NotNull final PyMemberInfo pyMemberInfo) {
super(pyMemberInfo.getMember(), true);
myPyMemberInfo = pyMemberInfo;
}
@NotNull
PyMemberInfo getPyMemberInfo() {
return myPyMemberInfo;
}
}
@@ -1,11 +1,11 @@
package com.jetbrains.python.refactoring.classes.membersManager;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
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.refactoring.classes.PyClassRefactoringUtil;
@@ -26,7 +26,6 @@ class SuperClassesManager extends MembersManager<PyClass> {
}
private static final NameExtractor NAME_EXTRACTOR = new NameExtractor();
private static final NameFilter NAME_FILTER = new NameFilter();
@NotNull
@Override
@@ -37,21 +36,20 @@ 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), NAME_FILTER));
Sets.newHashSet(Collections2.filter(Collections2.transform(members, NAME_EXTRACTOR), NotNullPredicate.INSTANCE));
for (final PyElement member : members) {
superClassesToMove.add(member.getName());
}
PyClassRefactoringUtil.moveSuperclasses(from, superClassesToMove, to);
//TODO: Use generics!
PyClassRefactoringUtil.insertImport(to, new ArrayList<PsiNamedElement>(members));
}
@NotNull
@Override
public PyMemberInfo apply(@NotNull final PyElement input) {
final String name = RefactoringBundle.message("member.info.extends.0", PyClassCellRenderer.getClassText((PyClass)input));
public PyMemberInfo apply(@NotNull final PyClass input) {
final String name = RefactoringBundle.message("member.info.extends.0", PyClassCellRenderer.getClassText(input));
//TODO: Check for "overrides"
return new PyMemberInfo(input, false, name, false, this);
}
@@ -64,11 +62,4 @@ class SuperClassesManager extends MembersManager<PyClass> {
return input.getName();
}
}
private static class NameFilter implements Predicate<String> {
@Override
public boolean apply(@Nullable final String input) {
return input != null;
}
}
}
@@ -0,0 +1,18 @@
package com.jetbrains.python.refactoring.classes.membersManager.vp;
import org.jetbrains.annotations.NotNull;
/**
* To be thrown when {@link com.jetbrains.python.refactoring.classes.membersManager.vp.MembersBasedViewSwingImpl} or its children
* assumes that data entered by user in {@link com.jetbrains.python.refactoring.classes.membersManager.vp.MembersBasedView} is invalid.
* See {@link MembersBasedPresenterImpl#validateView()} for info why exception user
* @author Ilya.Kazakevich
*/
public class BadDataException extends Exception {
/**
* @param message what exactly is wrong with data
*/
public BadDataException(@NotNull final String message) {
super(message);
}
}
@@ -0,0 +1,21 @@
package com.jetbrains.python.refactoring.classes.membersManager.vp;
import com.jetbrains.python.vp.Presenter;
/**
* Presenter for dialogs that display members
*
* @author Ilya.Kazakevich
*/
public interface MembersBasedPresenter extends Presenter {
/**
* User clicked on "ok" button
*/
void okClicked();
/**
* @return true if dialog button "preview" should be displayed.
* Preview uses {@link com.jetbrains.python.refactoring.classes.membersManager.PyMembersRefactoringBaseProcessor}
*/
boolean showPreview();
}
@@ -0,0 +1,83 @@
package com.jetbrains.python.refactoring.classes.membersManager.vp;
import com.intellij.psi.PsiElement;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.util.containers.MultiMap;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.refactoring.classes.PyMemberInfoStorage;
import org.jetbrains.annotations.NotNull;
/**
* All presenters that use members inherits this class.
* <strong>Warning</strong>: Do not inherit it directly.
* Check {@link com.jetbrains.python.refactoring.classes.membersManager.vp.MembersBasedPresenterNoPreviewImpl}
* or {@link com.jetbrains.python.refactoring.classes.membersManager.vp.MembersBasedPresenterWithPreviewImpl} instead
*
* @param <T> view for that presenter
* @author Ilya.Kazakevich
*/
abstract class MembersBasedPresenterImpl<T extends MembersBasedView<?>> implements MembersBasedPresenter {
@NotNull
protected final T myView;
@NotNull
protected final PyClass myClassUnderRefactoring;
@NotNull
protected final PyMemberInfoStorage myStorage;
/**
* @param view View for presenter
* @param classUnderRefactoring class to be refactored
* @param infoStorage info storage
*/
MembersBasedPresenterImpl(@NotNull final T view,
@NotNull final PyClass classUnderRefactoring,
@NotNull final PyMemberInfoStorage infoStorage) {
myView = view;
myClassUnderRefactoring = classUnderRefactoring;
myStorage = infoStorage;
}
//TODO: Mark Async ?
@Override
public void okClicked() {
final MultiMap<PsiElement, String> conflicts = getConflicts();
if (conflicts.isEmpty() || myView.showConflictsDialog(conflicts)) {
try {
validateView();
doRefactor();
}
catch (final BadDataException e) {
myView.showError(e.getMessage()); //Show error message if presenter says view in invalid
}
}
}
/**
* Validates view (used by presenter to check if view is valid).
* When overwrite, <strong>always</strong> call "super" <strong>first</strong>!
* Throw {@link com.jetbrains.python.refactoring.classes.membersManager.vp.BadDataException} in case of error.
* Do nothing, otherwise.
* Method is designed to be overwritten and exception is used to simplify this process: children do not need parent's result.
* They just call super.
*
* @throws BadDataException
*/
protected void validateView() throws BadDataException {
if (myView.getSelectedMemberInfos().isEmpty()) {
throw new BadDataException(RefactoringBundle.message("no.members.selected"));
}
}
/**
* Does refactoring itself
*/
abstract void doRefactor();
/**
* @return map of conflicts (if any)
*/
@NotNull
protected abstract MultiMap<PsiElement, String> getConflicts();
}
@@ -0,0 +1,60 @@
package com.jetbrains.python.refactoring.classes.membersManager.vp;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.refactoring.classes.PyMemberInfoStorage;
import org.jetbrains.annotations.NotNull;
/**
* Presenter that has not preview. Children should implement {@link #refactorNoPreview()}.
* To "preview" button would be displayed
* @param <T> view for this presenter
* @author Ilya.Kazakevich
*/
public abstract class MembersBasedPresenterNoPreviewImpl<T extends MembersBasedView<?>> extends MembersBasedPresenterImpl<T> {
/**
*
* @param view view for this presenter
* @param classUnderRefactoring class to refactor
* @param infoStorage info storage
*/
protected MembersBasedPresenterNoPreviewImpl(@NotNull final T view,
@NotNull final PyClass classUnderRefactoring,
@NotNull final PyMemberInfoStorage infoStorage) {
super(view, classUnderRefactoring, infoStorage);
}
@Override
public boolean showPreview() {
return false;
}
@Override
void doRefactor() {
CommandProcessor.getInstance().executeCommand(myClassUnderRefactoring.getProject(), new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
refactorNoPreview();
}
});
}
}, getCommandName(), null);
myView.close();
}
/**
* @return Command name for this preview
*/
@NotNull
protected abstract String getCommandName();
/**
* Do refactor with out of preview. Implement this method to do refactoring.
*/
protected abstract void refactorNoPreview();
}
@@ -0,0 +1,45 @@
package com.jetbrains.python.refactoring.classes.membersManager.vp;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.refactoring.classes.PyMemberInfoStorage;
import org.jetbrains.annotations.NotNull;
/**
* Does refactoring with preview (based on {@link com.intellij.refactoring.BaseRefactoringProcessor}).
* Child must implement {@link #createProcessor()} and return appropriate processor.
* "Preview" button would be displayed.
*
* @param <T> view for this presenter
* @author Ilya.Kazakevich
*/
public abstract class MembersBasedPresenterWithPreviewImpl<T extends MembersBasedView<?>> extends MembersBasedPresenterImpl<T> {
/**
* @param view view for this presenter
* @param classUnderRefactoring class to refactor
* @param infoStorage info storage
*/
protected MembersBasedPresenterWithPreviewImpl(@NotNull final T view,
@NotNull final PyClass classUnderRefactoring,
@NotNull final PyMemberInfoStorage infoStorage) {
super(view, classUnderRefactoring, infoStorage);
}
@Override
public boolean showPreview() {
return true;
}
@Override
protected void doRefactor() {
myView.invokeRefactoring(createProcessor());
}
/**
* @return processor for refactoring
*/
@NotNull
public abstract BaseRefactoringProcessor createProcessor();
}
@@ -0,0 +1,68 @@
package com.jetbrains.python.refactoring.classes.membersManager.vp;
import com.intellij.psi.PsiElement;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.util.containers.MultiMap;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
/**
* View to display dialog with members.
* First, configure it with {@link #configure(MembersViewInitializationInfo)}.
* Then, display with {@link #initAndShow()}
*
* @param <C> initialization info for this view. See {@link com.jetbrains.python.refactoring.classes.membersManager.vp.MembersViewInitializationInfo}
* for more info
* @author Ilya.Kazakevich
*/
public interface MembersBasedView<C extends MembersViewInitializationInfo> {
/**
* Display conflict dialogs.
*
* @param conflicts conflicts.
* @return true if user's choice is "continue". False if "cancel"
*/
boolean showConflictsDialog(@NotNull MultiMap<PsiElement, String> conflicts);
/**
* Displays error message
*
* @param message message to display
*/
void showError(@NotNull String message);
/**
* Configures view and <strong>must</strong> be called once, before {@link #initAndShow()}
* It accepts configuration info class
* Children may rewrite method to do additional configuration, but they should <strong>always</strong> call "super" first!
*
* @param configInfo configuration info
*/
void configure(@NotNull C configInfo);
/**
* @return collection of member infos user selected
*/
@NotNull
Collection<PyMemberInfo> getSelectedMemberInfos();
/**
* Runs refactoring based on {@link com.intellij.refactoring.BaseRefactoringProcessor}.
* It may display "preview" first.
*
* @param processor refactoring processor
*/
void invokeRefactoring(@NotNull BaseRefactoringProcessor processor);
/**
* Displays dialog. Be sure to run {@link #configure(MembersViewInitializationInfo)} first
*/
void initAndShow();
/**
* Closes dialog
*/
void close();
}
@@ -0,0 +1,133 @@
package com.jetbrains.python.refactoring.classes.membersManager.vp;
import com.google.common.base.Preconditions;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.psi.PsiElement;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.refactoring.ui.ConflictsDialog;
import com.intellij.refactoring.ui.RefactoringDialog;
import com.intellij.util.containers.MultiMap;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
import com.jetbrains.python.refactoring.classes.ui.PyMemberSelectionPanel;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
import java.util.Collection;
/**
* {@link com.jetbrains.python.refactoring.classes.membersManager.vp.MembersBasedView} implementation on swing.
* Consists of {@link #myTopPanel} and {@link #myCenterPanel}. Children must fill them in constructor.
* Presenter is stored in {@link #myPresenter}.
* Panel with members in {@link #myPyMemberSelectionPanel}
*
* @param <P> View presenter class
* @param <C> View configuration class
*/
public abstract class MembersBasedViewSwingImpl<P extends MembersBasedPresenter, C extends MembersViewInitializationInfo>
extends RefactoringDialog implements MembersBasedView<C> {
/**
* Panel to be displayed on the top
*/
@NotNull
protected final JPanel myTopPanel;
/**
* Panel to be displayed at the center
*/
@NotNull
protected final JComponent myCenterPanel;
/**
* Presenter
*/
@NotNull
protected final P myPresenter;
/**
* Panel with members
*/
@NotNull
protected final PyMemberSelectionPanel myPyMemberSelectionPanel;
private boolean myConfigured;
/**
* @param project project this view runs
* @param presenter view's presenter
* @param title window title
*/
protected MembersBasedViewSwingImpl(@NotNull final Project project, @NotNull final P presenter, @NotNull final String title) {
super(project, true);
myTopPanel = new JPanel(new BorderLayout());
myCenterPanel = new JPanel(new BorderLayout());
myPresenter = presenter;
myPyMemberSelectionPanel = new PyMemberSelectionPanel(title);
}
@Override
public boolean showConflictsDialog(@NotNull final MultiMap<PsiElement, String> conflicts) {
Preconditions.checkArgument(!conflicts.isEmpty(), "Can't show dialog for empty conflicts");
final ConflictsDialog conflictsDialog = new ConflictsDialog(myProject, conflicts);
conflictsDialog.show();
return conflictsDialog.isOK();
}
@Override
public void showError(@NotNull final String message) {
Messages.showErrorDialog(getContentPane(), message);
}
@Override
protected boolean hasPreviewButton() {
return myPresenter.showPreview();
}
@Override
protected void doAction() {
myPresenter.okClicked();
}
@NotNull
@Override
protected JComponent createNorthPanel() {
return myTopPanel;
}
@Override
public void close() {
close(OK_EXIT_CODE);
}
@Override
protected JComponent createCenterPanel() {
return myCenterPanel;
}
@NotNull
@Override
public Collection<PyMemberInfo> getSelectedMemberInfos() {
return myPyMemberSelectionPanel.getSelectedMemberInfos();
}
@Override
public void invokeRefactoring(@NotNull final BaseRefactoringProcessor processor) {
super.invokeRefactoring(processor);
}
@Override
public void configure(@NotNull final C configInfo) {
Preconditions.checkArgument(!myConfigured, "Already configured");
myConfigured = true;
myPyMemberSelectionPanel.init(configInfo.getMemberInfoModel(), configInfo.getMemberInfos());
}
@Override
public void initAndShow() {
Preconditions.checkArgument(myConfigured, "Not configured, run 'configure' first!");
init();
show();
}
}
@@ -0,0 +1,49 @@
package com.jetbrains.python.refactoring.classes.membersManager.vp;
import com.intellij.refactoring.classMembers.MemberInfoModel;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
/**
* Configuration for {@link com.jetbrains.python.refactoring.classes.membersManager.vp.MembersBasedView}
*
* @author Ilya.Kazakevich
*/
public class MembersViewInitializationInfo {
@NotNull
private final MemberInfoModel<PyElement, PyMemberInfo> myMemberInfoModel;
@NotNull
private final Collection<PyMemberInfo> myMemberInfos;
/**
* @param memberInfoModel model to be used in members panel
* @param memberInfos members to displau
*/
public MembersViewInitializationInfo(@NotNull final MemberInfoModel<PyElement, PyMemberInfo> memberInfoModel,
@NotNull final Collection<PyMemberInfo> memberInfos) {
myMemberInfos = new ArrayList<PyMemberInfo>(memberInfos);
myMemberInfoModel = memberInfoModel;
}
/**
* @return model to be used in members panel
*/
@NotNull
public MemberInfoModel<PyElement, PyMemberInfo> getMemberInfoModel() {
return myMemberInfoModel;
}
/**
* @return members to display
*/
@NotNull
public Collection<PyMemberInfo> getMemberInfos() {
return Collections.unmodifiableCollection(myMemberInfos);
}
}
@@ -0,0 +1,6 @@
/**
* MVP ({@link com.jetbrains.python.vp}) implementation for refactorings that use members ({@link com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo})
* and package {@link com.jetbrains.python.refactoring.classes.membersManager}
* @author Ilya.Kazakevich
*/
package com.jetbrains.python.refactoring.classes.membersManager.vp;
@@ -1,176 +0,0 @@
/*
* 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.pullUp;
import com.google.common.base.Preconditions;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.ComboBox;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.psi.PsiElement;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.classMembers.MemberInfoModel;
import com.intellij.refactoring.ui.ConflictsDialog;
import com.intellij.util.containers.MultiMap;
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.ui.PyClassCellRenderer;
import com.jetbrains.python.refactoring.classes.ui.PyMemberSelectionPanel;
import com.jetbrains.python.refactoring.classes.ui.PyMemberSelectionTable;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.Collection;
import java.util.List;
//TODO: Merge with UpDirectedMembersMovingDialog and its children
/**
* @author Ilya.Kazakevich
* Pull up view implementation
*/
class PullUpViewSwingImpl extends DialogWrapper implements PyPullUpView {
@NotNull
private final PyPullUpPresenter myPresenter;
@NotNull
private final DefaultComboBoxModel myParentsmodel;
@NotNull
private final JPanel myTopPanel;
@NotNull
private final JComponent myCenterPanel;
@NotNull
private final PyMemberSelectionTable myMembersPanelTable;
@NotNull
private final Project myProject;
/**
* @param project project where refactoring takes place
* @param presenter presenter for this view
* @param clazz class to refactor
*/
PullUpViewSwingImpl(@NotNull Project project, @NotNull final PyPullUpPresenter presenter, @NotNull PyClass clazz) {
super(project);
this.myProject = project;
setTitle(PyPullUpHandler.REFACTORING_NAME);
myPresenter = presenter;
myParentsmodel = new DefaultComboBoxModel();
ComboBox parentsCombo = new ComboBox(myParentsmodel);
parentsCombo.setRenderer(new PyClassCellRenderer());
JLabel mainLabel = new JLabel();
mainLabel.setText(RefactoringBundle.message("pull.up.members.to", PyClassCellRenderer.getClassText(clazz)));
mainLabel.setLabelFor(parentsCombo);
myTopPanel = new JPanel();
myTopPanel.setLayout(new GridBagLayout());
GridBagConstraints gbConstraints = new GridBagConstraints();
gbConstraints.insets = new Insets(4, 8, 4, 8);
gbConstraints.weighty = 1;
gbConstraints.weightx = 1;
gbConstraints.gridy = 0;
gbConstraints.gridwidth = GridBagConstraints.REMAINDER;
gbConstraints.fill = GridBagConstraints.BOTH;
gbConstraints.anchor = GridBagConstraints.WEST;
myTopPanel.add(mainLabel, gbConstraints);
myTopPanel.add(mainLabel, gbConstraints);
gbConstraints.gridy++;
myTopPanel.add(parentsCombo, gbConstraints);
myCenterPanel = new JPanel(new BorderLayout());
PyMemberSelectionPanel membersPanel = new PyMemberSelectionPanel(RefactoringBundle.message("members.to.be.pulled.up"));
myMembersPanelTable = membersPanel.getTable();
gbConstraints.gridy++;
myCenterPanel.add(membersPanel, BorderLayout.CENTER);
parentsCombo.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
myMembersPanelTable.fireExternalDataChange();
}
}
});
}
@NotNull
@Override
protected JComponent createNorthPanel() {
return myTopPanel;
}
@NotNull
protected String getHelpId() {
return "python.reference.pullMembersUp";
}
protected JComponent createCenterPanel() {
return myCenterPanel;
}
@Override
protected void doOKAction() {
myPresenter.okClicked();
}
@NotNull
@Override
public Collection<PyMemberInfo> getSelectedMemberInfos() {
return myMembersPanelTable.getSelectedMemberInfos();
}
@Override
public boolean showConflictsDialog(@NotNull MultiMap<PsiElement, String> conflicts) {
Preconditions.checkArgument(!conflicts.isEmpty(), "Can't show dialog for empty conflicts");
ConflictsDialog conflictsDialog = new ConflictsDialog(myProject, conflicts);
conflictsDialog.show();
return conflictsDialog.isOK();
}
@Override
public void closeDialog() {
close(OK_EXIT_CODE);
}
@NotNull
@Override
public PyClass getSelectedParent() {
return (PyClass)myParentsmodel.getSelectedItem();
}
@Override
public void init(@NotNull Collection<PyClass> parents,
@NotNull MemberInfoModel<PyElement, PyMemberInfo> memberInfoModel,
@NotNull List<PyMemberInfo> members) {
Preconditions.checkState(!isVisible(), "Already initialzed");
for (PyClass parent : parents) {
myParentsmodel.addElement(parent);
}
myMembersPanelTable.setMemberInfoModel(memberInfoModel);
myMembersPanelTable.addMemberInfoChangeListener(memberInfoModel);
myMembersPanelTable.setMemberInfos(members);
init();
show();
}
}
@@ -16,11 +16,11 @@
package com.jetbrains.python.refactoring.classes.pullUp;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.Sets;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.NotNullPredicate;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyUtil;
import com.jetbrains.python.psi.types.TypeEvalContext;
@@ -33,7 +33,7 @@ import java.util.Set;
/**
* @author Ilya.Kazakevich
*/
class PyAncestorsUtils implements Predicate<PyClass> {
class PyAncestorsUtils extends NotNullPredicate<PyClass> {
@NotNull
private final Set<VirtualFile> mySourceRoots;
@@ -44,17 +44,17 @@ class PyAncestorsUtils implements Predicate<PyClass> {
* @return list of parents
*/
@NotNull
static Collection<PyClass> getAncestorsUnderUserControl(@NotNull PyClass pyClass) {
List<PyClass> allAncestors = pyClass.getAncestorClasses(TypeEvalContext.userInitiated(pyClass.getContainingFile()));
static Collection<PyClass> getAncestorsUnderUserControl(@NotNull final PyClass pyClass) {
final List<PyClass> allAncestors = pyClass.getAncestorClasses(TypeEvalContext.userInitiated(pyClass.getContainingFile()));
return Collections2.filter(allAncestors, new PyAncestorsUtils(PyUtil.getSourceRoots(pyClass)));
}
private PyAncestorsUtils(@NotNull Collection<VirtualFile> sourceRoots) {
private PyAncestorsUtils(@NotNull final Collection<VirtualFile> sourceRoots) {
mySourceRoots = Sets.newHashSet(sourceRoots);
}
@Override
public boolean apply(PyClass input) {
public boolean applyNotNull(@NotNull final PyClass input) {
return VfsUtilCore.isUnder(input.getContainingFile().getVirtualFile(), mySourceRoots);
}
}
@@ -29,11 +29,12 @@ import java.util.Collection;
/**
* @author Dennis.Ushakov
*/
public class PyPullUpConflictsUtil {
final class PyPullUpConflictsUtil {
private PyPullUpConflictsUtil() {
}
public static MultiMap<PsiElement, String> checkConflicts(final Collection<PyMemberInfo> infos, @NotNull final PyClass superClass) {
@NotNull
static MultiMap<PsiElement, String> checkConflicts(final Collection<PyMemberInfo> infos, @NotNull final PyClass superClass) {
final MultiMap<PsiElement, String> conflictsList = new MultiMap<PsiElement, String>();
for (PyMemberInfo info : infos) {
PsiElement member = info.getMember();
@@ -15,18 +15,12 @@
*/
package com.jetbrains.python.refactoring.classes.pullUp;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
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.PyClassMembersRefactoringSupport;
import com.jetbrains.python.refactoring.classes.PyClassRefactoringHandler;
import com.jetbrains.python.refactoring.classes.PyMemberInfoStorage;
import com.jetbrains.python.vp.Creator;
@@ -40,43 +34,38 @@ public class PyPullUpHandler extends PyClassRefactoringHandler {
public static final String REFACTORING_NAME = PyBundle.message("refactoring.pull.up.dialog.title");
@Override
protected void doRefactor(final Project project,
PsiElement element1,
PsiElement element2,
Editor editor,
PsiFile file,
DataContext dataContext) {
protected void doRefactorImpl(@NotNull final Project project,
@NotNull final PyClass classUnderRefactoring,
@NotNull final PyMemberInfoStorage infoStorage,
@NotNull final Editor editor) {
//TODO: Move to vp (presenter) as well
CommonRefactoringUtil.checkReadOnlyStatus(project, file);
final PyClass clazz = PyUtil.getContainingClassOrSelf(element1);
if (!inClass(clazz, project, editor, "refactoring.pull.up.error.cannot.perform.refactoring.not.inside.class")) return;
assert clazz != null;
final PyMemberInfoStorage infoStorage = PyClassMembersRefactoringSupport.getSelectedMemberInfos(clazz, element1, element2);
if (PyAncestorsUtils.getAncestorsUnderUserControl(clazz).isEmpty() || infoStorage.getClassMemberInfos(clazz).isEmpty()) {
if (PyAncestorsUtils.getAncestorsUnderUserControl(classUnderRefactoring).isEmpty() ||
infoStorage.getClassMemberInfos(classUnderRefactoring).isEmpty()) {
CommonRefactoringUtil.showErrorHint(project, editor, PyBundle
.message("refactoring.pull.up.error.cannot.perform.refactoring.no.base.classes", clazz.getName()),
RefactoringBundle.message("pull.members.up.title"), "members.pull.up");
.message("refactoring.pull.up.error.cannot.perform.refactoring.no.base.classes",
classUnderRefactoring.getName()), RefactoringBundle.message("pull.members.up.title"),
"members.pull.up"
);
return;
}
if (ApplicationManagerEx.getApplicationEx().isUnitTestMode()) return;
ViewPresenterUtils.linkViewWithPresenterAndLaunch(PyPullUpPresenter.class, PyPullUpView.class,
new Creator<PyPullUpView, PyPullUpPresenter>() {
@NotNull
@Override
public PyPullUpPresenter createPresenter(@NotNull PyPullUpView view) {
return new PyPullUpPresenterImpl(view, infoStorage, clazz);
}
ViewPresenterUtils
.linkViewWithPresenterAndLaunch(PyPullUpPresenter.class, PyPullUpView.class, new Creator<PyPullUpView, PyPullUpPresenter>() {
@NotNull
@Override
public PyPullUpPresenter createPresenter(@NotNull final PyPullUpView view) {
return new PyPullUpPresenterImpl(view, infoStorage, classUnderRefactoring);
}
@NotNull
@Override
public PyPullUpView createView(@NotNull PyPullUpPresenter presenter) {
return new PullUpViewSwingImpl(project, presenter, clazz);
}
});
@NotNull
@Override
public PyPullUpView createView(@NotNull final PyPullUpPresenter presenter) {
return new PyPullUpViewSwingImpl(project, presenter, classUnderRefactoring);
}
}
);
}
@@ -1,53 +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.pullUp;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.refactoring.RefactoringBundle;
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.membersManager.MembersManager;
import java.util.Collection;
/**
* @author Dennis.Ushakov
*/
public final class PyPullUpHelper {
private PyPullUpHelper() {
}
public static PyElement pullUp(final PyClass clazz, final Collection<PyMemberInfo> selectedMemberInfos, final PyClass superClass) {
CommandProcessor.getInstance().executeCommand(clazz.getProject(), new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
MembersManager.moveAllMembers(clazz, superClass, selectedMemberInfos);
}
});
}
}, RefactoringBundle.message("pull.members.up.title"), null);
return superClass;
}
}
@@ -16,16 +16,15 @@
package com.jetbrains.python.refactoring.classes.pullUp;
import com.jetbrains.python.vp.Presenter;
import com.jetbrains.python.refactoring.classes.membersManager.vp.MembersBasedPresenter;
/**
* Presenter for pull-up refactoring
*
* TODO: Interface left empty. Remove?
*
* @author Ilya.Kazakevich
*/
public interface PyPullUpPresenter extends Presenter {
public interface PyPullUpPresenter extends MembersBasedPresenter {
/**
* To be called by view when user clicks "ok"
*/
void okClicked();
}
@@ -18,43 +18,37 @@ package com.jetbrains.python.refactoring.classes.pullUp;
import com.google.common.base.Preconditions;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.refactoring.classMembers.AbstractUsesDependencyMemberInfoModel;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.util.PsiNavigateUtil;
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.refactoring.classes.membersManager.PyMemberInfo;
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 org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
/**
* Pull-up presenter implementation
*
* @author Ilya.Kazakevich
*/
//TODO: Merge logic with "extract superclass" refactoring
class PyPullUpPresenterImpl extends AbstractUsesDependencyMemberInfoModel<PyElement, PyClass, PyMemberInfo> implements PyPullUpPresenter {
@NotNull
private final PyPullUpView myView;
@NotNull
private final PyMemberInfoStorage myStorage;
class PyPullUpPresenterImpl extends MembersBasedPresenterWithPreviewImpl<PyPullUpView> implements PyPullUpPresenter {
@NotNull
private final Collection<PyClass> myParents;
/**
* @param view view
* @param view view
* @param infoStorage member storage
* @param clazz class to refactor
* @param clazz class to refactor
*/
PyPullUpPresenterImpl(@NotNull PyPullUpView view, @NotNull PyMemberInfoStorage infoStorage, @NotNull PyClass clazz) {
super(clazz, null, false);
this.myView = view;
this.myStorage = infoStorage;
PyPullUpPresenterImpl(@NotNull final PyPullUpView view, @NotNull final PyMemberInfoStorage infoStorage, @NotNull final PyClass clazz) {
super(view, clazz, infoStorage);
myParents = PyAncestorsUtils.getAncestorsUnderUserControl(clazz);
Preconditions.checkArgument(!myParents.isEmpty(), "No parents found");
}
@@ -62,63 +56,28 @@ class PyPullUpPresenterImpl extends AbstractUsesDependencyMemberInfoModel<PyElem
@Override
public void launch() {
myView.init(myParents, this, myStorage.getClassMemberInfos(myClass));
myView.configure(
new PyPullUpViewInitializationInfo(new PyPullUpInfoModel(), myStorage.getClassMemberInfos(myClassUnderRefactoring), myParents));
myView.initAndShow();
}
//TODO: Mark Async ?
@Override
public void okClicked() {
if (!isWritable()) {
return; //TODO: Strange behaviour
}
MultiMap<PsiElement, String> conflicts = getConflicts();
if (conflicts.isEmpty() || myView.showConflictsDialog(conflicts)) {
pullUpWithHelper(myClass, myView.getSelectedMemberInfos(), myView.getSelectedParent());
myView.closeDialog();
}
}
private static void pullUpWithHelper(PyClass clazz, Collection<PyMemberInfo> selectedMemberInfos, PyClass superClass) {
PsiNavigateUtil.navigate(PyPullUpHelper.pullUp(clazz, selectedMemberInfos, superClass));
}
public boolean isMemberEnabled(PyMemberInfo member) {
PyClass currentSuperClass = myView.getSelectedParent();
if (member.getMember() instanceof PyClass) {
//TODO: Delegate to Memebers Managers
PyClass memberClass = (PyClass)member.getMember();
if (memberClass.isSubclass(currentSuperClass) || currentSuperClass.isSubclass(memberClass)) {
return false; //Class is already parent of superclass
}
}
if (! PyPullUpConflictsUtil.checkConflicts(Collections.singletonList(member), myView.getSelectedParent()).isEmpty()) {
return false; //Member has conflict
}
return (!myStorage.getDuplicatedMemberInfos(currentSuperClass).contains(member)) && member.getMember() != currentSuperClass;
}
public boolean isAbstractEnabled(PyMemberInfo member) {
return false;
}
public int checkForProblems(@NotNull PyMemberInfo member) {
return member.isChecked() ? OK : super.checkForProblems(member);
super.okClicked();
}
@NotNull
@Override
protected int doCheck(@NotNull PyMemberInfo memberInfo, int problem) {
if (problem == ERROR && memberInfo.isStatic()) {
return WARNING;
}
return problem;
public BaseRefactoringProcessor createProcessor() {
return new PyPullUpProcessor(myClassUnderRefactoring, myView.getSelectedParent(), myView.getSelectedMemberInfos());
}
private boolean isWritable() {
Collection<PyMemberInfo> infos = myView.getSelectedMemberInfos();
if (infos.size() == 0) {
final Collection<PyMemberInfo> infos = myView.getSelectedMemberInfos();
if (infos.isEmpty()) {
return true;
}
final PyElement element = infos.iterator().next().getMember();
@@ -126,7 +85,7 @@ class PyPullUpPresenterImpl extends AbstractUsesDependencyMemberInfoModel<PyElem
if (!CommonRefactoringUtil.checkReadOnlyStatus(project, myView.getSelectedParent())) return false;
final PyClass container = PyUtil.getContainingClassOrSelf(element);
if (container == null || !CommonRefactoringUtil.checkReadOnlyStatus(project, container)) return false;
for (PyMemberInfo info : infos) {
for (final PyMemberInfo info : infos) {
final PyElement member = info.getMember();
if (!CommonRefactoringUtil.checkReadOnlyStatus(project, member)) return false;
}
@@ -134,10 +93,54 @@ class PyPullUpPresenterImpl extends AbstractUsesDependencyMemberInfoModel<PyElem
}
private MultiMap<PsiElement, String> getConflicts() {
@Override
@NotNull
public MultiMap<PsiElement, String> getConflicts() {
final Collection<PyMemberInfo> infos = myView.getSelectedMemberInfos();
PyClass superClass = myView.getSelectedParent();
final PyClass superClass = myView.getSelectedParent();
return PyPullUpConflictsUtil.checkConflicts(infos, superClass);
}
private class PyPullUpInfoModel extends AbstractUsesDependencyMemberInfoModel<PyElement, PyClass, PyMemberInfo> {
PyPullUpInfoModel() {
super(myClassUnderRefactoring, null, false);
}
@Override
public boolean isAbstractEnabled(final PyMemberInfo member) {
return false;
}
@Override
public int checkForProblems(@NotNull final PyMemberInfo member) {
return member.isChecked() ? OK : super.checkForProblems(member);
}
@Override
protected int doCheck(@NotNull final PyMemberInfo memberInfo, final int problem) {
if (problem == ERROR && memberInfo.isStatic()) {
return WARNING;
}
return problem;
}
@Override
public boolean isMemberEnabled(final PyMemberInfo member) {
final PyClass currentSuperClass = myView.getSelectedParent();
if (member.getMember() instanceof PyClass) {
//TODO: Delegate to Memebers Managers
final PyClass memberClass = (PyClass)member.getMember();
if (memberClass.isSubclass(currentSuperClass) || currentSuperClass.isSubclass(memberClass)) {
return false; //Class is already parent of superclass
}
}
if (!PyPullUpConflictsUtil.checkConflicts(Collections.singletonList(member), myView.getSelectedParent()).isEmpty()) {
return false; //Member has conflict
}
return (!myStorage.getDuplicatedMemberInfos(currentSuperClass).contains(member)) && member.getMember() != currentSuperClass;
}
}
}
@@ -0,0 +1,49 @@
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;
import com.jetbrains.python.refactoring.classes.membersManager.PyMembersRefactoringBaseProcessor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
/**
* @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};
}
@Override
protected String getCommandName() {
return PyPullUpHandler.REFACTORING_NAME;
}
@Override
public String getProcessedElementsHeader() {
return PyBundle.message("refactoring.pull.up.dialog.move.members.to.class");
}
@Override
public String getCodeReferencesText(final int usagesCount, final int filesCount) {
return PyBundle.message("refactoring.pull.up.dialog.members.to.be.moved");
}
@Nullable
@Override
public String getCommentReferencesText(final int usagesCount, final int filesCount) {
return getCodeReferencesText(usagesCount, filesCount);
}
}
@@ -16,55 +16,19 @@
package com.jetbrains.python.refactoring.classes.pullUp;
import com.intellij.psi.PsiElement;
import com.intellij.refactoring.classMembers.MemberInfoModel;
import com.intellij.util.containers.MultiMap;
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.membersManager.vp.MembersBasedView;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.List;
/**
* @author Ilya.Kazakevich
* View for pull-up refactoring
* View for pull-up refactoring
*/
public interface PyPullUpView {
/**
* Launches view.
*
* @param parents collection of class parents to display (first one would be displayed)
* @param memberInfoModel Member info model for members panel
* @param memberInfos Member infos: list of class members
*/
void init(@NotNull Collection<PyClass> parents,
@NotNull MemberInfoModel<PyElement, PyMemberInfo> memberInfoModel,
@NotNull List<PyMemberInfo> memberInfos);
public interface PyPullUpView extends MembersBasedView<PyPullUpViewInitializationInfo> {
/**
* @return Parent that user selected
*/
@NotNull
PyClass getSelectedParent();
/**
* Closes view
*/
void closeDialog();
/**
* @return List of members selected by user
*/
@NotNull
Collection<PyMemberInfo> getSelectedMemberInfos();
/**
* Displays conflicts dialog
*
* @param conflicts map of conflicts
* @return true when user clicked "yes". False otherwise
*/
boolean showConflictsDialog(@NotNull MultiMap<PsiElement, String> conflicts);
}
@@ -0,0 +1,38 @@
package com.jetbrains.python.refactoring.classes.pullUp;
import com.intellij.refactoring.classMembers.MemberInfoModel;
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.membersManager.vp.MembersViewInitializationInfo;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* Configuration for pull-up view
*
* @author Ilya.Kazakevich
*/
class PyPullUpViewInitializationInfo extends MembersViewInitializationInfo {
@NotNull
private final Collection<PyClass> myParents;
/**
* @param parents list of possible parents to display.
*/
PyPullUpViewInitializationInfo(@NotNull final MemberInfoModel<PyElement, PyMemberInfo> memberInfoModel,
@NotNull final List<PyMemberInfo> memberInfos,
@NotNull final Collection<PyClass> parents) {
super(memberInfoModel, memberInfos);
myParents = new ArrayList<PyClass>(parents);
}
@NotNull
public Collection<PyClass> getParents() {
return Collections.unmodifiableCollection(myParents);
}
}
@@ -0,0 +1,111 @@
/*
* 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.pullUp;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.ComboBox;
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.ui.PyClassCellRenderer;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
/**
* @author Ilya.Kazakevich
* Pull up view implementation
*/
class PyPullUpViewSwingImpl extends MembersBasedViewSwingImpl<PyPullUpPresenter, PyPullUpViewInitializationInfo> implements PyPullUpView,
ItemListener {
@NotNull
private final ComboBox myParentsCombo;
@NotNull
private final DefaultComboBoxModel myParentsComboBoxModel;
/**
* @param project project where refactoring takes place
* @param presenter presenter for this view
* @param clazz class to refactor
*/
PyPullUpViewSwingImpl(@NotNull final Project project, @NotNull final PyPullUpPresenter presenter, @NotNull final PyClass clazz) {
super(project, presenter, RefactoringBundle.message("members.to.be.pulled.up"));
setTitle(PyPullUpHandler.REFACTORING_NAME);
myParentsComboBoxModel = new DefaultComboBoxModel();
myParentsCombo = new ComboBox(myParentsComboBoxModel);
myParentsCombo.setRenderer(new PyClassCellRenderer());
final JLabel mainLabel = new JLabel();
mainLabel.setText(RefactoringBundle.message("pull.up.members.to", PyClassCellRenderer.getClassText(clazz)));
mainLabel.setLabelFor(myParentsCombo);
myTopPanel.setLayout(new GridBagLayout());
final GridBagConstraints gbConstraints = new GridBagConstraints();
gbConstraints.insets = new Insets(4, 8, 4, 8);
gbConstraints.weighty = 1;
gbConstraints.weightx = 1;
gbConstraints.gridy = 0;
gbConstraints.gridwidth = GridBagConstraints.REMAINDER;
gbConstraints.fill = GridBagConstraints.BOTH;
gbConstraints.anchor = GridBagConstraints.WEST;
myTopPanel.add(mainLabel, gbConstraints);
myTopPanel.add(mainLabel, gbConstraints);
gbConstraints.gridy++;
myTopPanel.add(myParentsCombo, gbConstraints);
gbConstraints.gridy++;
myCenterPanel.add(myPyMemberSelectionPanel, BorderLayout.CENTER);
}
@Override
@NotNull
protected String getHelpId() {
return "python.reference.pullMembersUp";
}
@NotNull
@Override
public PyClass getSelectedParent() {
return (PyClass)myParentsComboBoxModel.getSelectedItem();
}
@Override
public void configure(@NotNull final PyPullUpViewInitializationInfo configInfo) {
super.configure(configInfo);
for (final PyClass parent : configInfo.getParents()) {
myParentsComboBoxModel.addElement(parent);
}
myParentsCombo.addItemListener(this);
}
@Override
public void itemStateChanged(final ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
myPyMemberSelectionPanel.redraw();
}
}
}
@@ -0,0 +1,5 @@
/**
* "Pull members up" refactoring
* @author Ilya.Kazakevich
*/
package com.jetbrains.python.refactoring.classes.pullUp;
@@ -89,9 +89,7 @@ public class PyPushDownDialog extends RefactoringDialog {
myMemberInfoModel = new UsedByDependencyMemberInfoModel<PyElement, PyClass, PyMemberInfo>(myClass);
myMemberInfoModel.memberInfoChanged(new MemberInfoChange<PyElement, PyMemberInfo>(myMemberInfos));
memberSelectionPanel.getTable().setMemberInfoModel(myMemberInfoModel);
memberSelectionPanel.getTable().addMemberInfoChangeListener(myMemberInfoModel);
memberSelectionPanel.init(myMemberInfoModel, myMemberInfos);
return panel;
}
@@ -15,21 +15,16 @@
*/
package com.jetbrains.python.refactoring.classes.pushDown;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.util.Query;
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.PyClassMembersRefactoringSupport;
import com.jetbrains.python.refactoring.classes.PyClassRefactoringHandler;
import com.jetbrains.python.refactoring.classes.PyMemberInfoStorage;
import org.jetbrains.annotations.NotNull;
/**
* @author Dennis.Ushakov
@@ -38,25 +33,20 @@ public class PyPushDownHandler extends PyClassRefactoringHandler {
public static final String REFACTORING_NAME = RefactoringBundle.message("push.members.down.title");
@Override
protected void doRefactor(Project project, PsiElement element1, PsiElement element2, Editor editor, PsiFile file, DataContext dataContext) {
CommonRefactoringUtil.checkReadOnlyStatus(project, file);
protected void doRefactorImpl(@NotNull Project project,
@NotNull PyClass classUnderRefactoring,
@NotNull PyMemberInfoStorage infoStorage,
@NotNull Editor editor) {
final PyClass clazz = PyUtil.getContainingClassOrSelf(element1);
if (!inClass(clazz, project, editor, "refactoring.pull.up.error.cannot.perform.refactoring.not.inside.class")) return;
final Query<PyClass> query = PyClassInheritorsSearch.search(clazz, false);
final Query<PyClass> query = PyClassInheritorsSearch.search(classUnderRefactoring, false);
if (query.findFirst() == null) {
assert clazz != null;
final String message = RefactoringBundle.message("class.0.does.not.have.inheritors", clazz.getName());
final String message = RefactoringBundle.message("class.0.does.not.have.inheritors", classUnderRefactoring.getName());
CommonRefactoringUtil.showErrorHint(project, editor, message, getTitle(), getHelpId());
return;
}
final PyMemberInfoStorage infoStorage = PyClassMembersRefactoringSupport.getSelectedMemberInfos(clazz, element1, element2);
if (ApplicationManagerEx.getApplicationEx().isUnitTestMode()) return;
final PyPushDownDialog dialog = new PyPushDownDialog(project, clazz, infoStorage);
final PyPushDownDialog dialog = new PyPushDownDialog(project, classUnderRefactoring, infoStorage);
dialog.show();
}
@@ -15,6 +15,7 @@
*/
package com.jetbrains.python.refactoring.classes.ui;
import com.google.common.base.Preconditions;
import com.intellij.refactoring.classMembers.MemberInfoModel;
import com.intellij.ui.IdeBorderFactory;
import com.intellij.ui.ScrollPaneFactory;
@@ -25,22 +26,24 @@ import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.border.Border;
import java.awt.*;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* Panel that handles table with list of class members with selection checkboxes.
* @see com.jetbrains.python.refactoring.classes.ui.PyMemberSelectionTable
*
* @author Dennis.Ushakov
*/
public class PyMemberSelectionPanel extends JPanel {
private static final List<PyMemberInfo> EMPTY_MEMBER_INFO = Collections.emptyList();
private final PyMemberSelectionTable myTable;
private boolean myInitialized;
/**
* Creates empty panel to be filled later by {@link #getTable()}
* Creates empty panel to be filled later by {@link #init(com.intellij.refactoring.classMembers.MemberInfoModel, java.util.Collection)}
*
* @param title
*/
public PyMemberSelectionPanel(@NotNull String title) {
@@ -48,13 +51,13 @@ public class PyMemberSelectionPanel extends JPanel {
}
/**
* Creates panel and fills its table (see {@link #getTable()}) with members info
* @param title Title for panel
* Creates panel and fills its table (see {@link #init(com.intellij.refactoring.classMembers.MemberInfoModel, java.util.Collection)} ) with members info
*
* @param title Title for panel
* @param memberInfo list of members
* @param model model
* @param model model
*/
public PyMemberSelectionPanel(String title, List<PyMemberInfo> memberInfo, final MemberInfoModel<PyElement, PyMemberInfo> model) {
super();
Border titledBorder = IdeBorderFactory.createTitledBorder(title, false);
Border emptyBorder = BorderFactory.createEmptyBorder(0, 5, 5, 5);
Border border = BorderFactory.createCompoundBorder(titledBorder, emptyBorder);
@@ -68,7 +71,35 @@ public class PyMemberSelectionPanel extends JPanel {
add(scrollPane, BorderLayout.CENTER);
}
public PyMemberSelectionTable getTable() {
return myTable;
/**
* Inits panel.
*
* @param memberInfoModel model to display memebers in table
* @param members members to display
*/
public void init(@NotNull final MemberInfoModel<PyElement, PyMemberInfo> memberInfoModel,
@NotNull final Collection<PyMemberInfo> members) {
Preconditions.checkState(!myInitialized, "Already myInitialized");
myTable.setMemberInfos(members);
myTable.setMemberInfoModel(memberInfoModel);
myTable.addMemberInfoChangeListener(memberInfoModel);
myInitialized = true;
}
/**
* @return list of members, selected by user
*/
@NotNull
public Collection<PyMemberInfo> getSelectedMemberInfos() {
Preconditions.checkState(myInitialized, "Call #init first");
return myTable.getSelectedMemberInfos();
}
/**
* Redraws table. Call it when some new data is available.
*/
public void redraw() {
myTable.fireExternalDataChange();
}
}
@@ -1,100 +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.ui;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.refactoring.classMembers.DependencyMemberInfoModel;
import com.intellij.refactoring.classMembers.MemberInfoChange;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.psi.PyUtil;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* @author Dennis.Ushakov
*/
public abstract class UpDirectedMembersMovingDialog extends DialogWrapper {
protected DependencyMemberInfoModel<PyElement, PyMemberInfo> myMemberInfoModel;
protected PyMemberSelectionPanel myMemberSelectionPanel;
protected PyClass myClass;
protected List<PyMemberInfo> myMemberInfos;
public UpDirectedMembersMovingDialog(Project project, final PyClass clazz) {
super(project, true);
myClass = clazz;
}
protected JComponent createCenterPanel() {
JPanel panel = new JPanel(new BorderLayout());
myMemberSelectionPanel = new PyMemberSelectionPanel(getMembersBorderTitle(), myMemberInfos, null);
myMemberInfoModel = createMemberInfoModel();
myMemberInfoModel.memberInfoChanged(new MemberInfoChange<PyElement, PyMemberInfo>(myMemberInfos));
myMemberSelectionPanel.getTable().setMemberInfoModel(myMemberInfoModel);
myMemberSelectionPanel.getTable().addMemberInfoChangeListener(myMemberInfoModel);
panel.add(myMemberSelectionPanel, BorderLayout.CENTER);
return panel;
}
protected abstract String getMembersBorderTitle();
protected abstract DependencyMemberInfoModel<PyElement, PyMemberInfo> createMemberInfoModel();
protected void doOKAction() {
if(!checkConflicts()) return;
close(OK_EXIT_CODE);
}
public boolean isOKActionEnabled() {
return getSelectedMemberInfos().size() > 0 && super.isOKActionEnabled();
}
public abstract boolean checkConflicts();
protected static boolean checkWritable(final PyClass superClass, final Collection<PyMemberInfo> infos) {
if (infos.size() ==0) {
return true;
}
final PyElement element = infos.iterator().next().getMember();
final Project project = element.getProject();
if (!CommonRefactoringUtil.checkReadOnlyStatus(project, superClass)) return false;
final PyClass container = PyUtil.getContainingClassOrSelf(element);
if (container == null || !CommonRefactoringUtil.checkReadOnlyStatus(project, container)) return false;
for (PyMemberInfo info : infos) {
final PyElement member = info.getMember();
if (!CommonRefactoringUtil.checkReadOnlyStatus(project, member)) return false;
}
return true;
}
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;
}
}
@@ -31,6 +31,7 @@ public final class ViewPresenterUtils {
}
/**
* TODO: Write about do not call anything in constructor
* Creates link between view and presenter and launches them using {@link Presenter#launch()}. Be sure to read package info first.
*
* @param presenterInterface presenter interface
@@ -0,0 +1,10 @@
class ToClass(object):
CLASS_FIELD = 42
def __init__(self):
self.instance_field = 100500
class FromClass(ToClass):
def __init__(self):
pass
@@ -0,0 +1,4 @@
class FromClass(object):
CLASS_FIELD = 42
def __init__(self):
self.instance_field = 100500
@@ -0,0 +1,15 @@
from datetime import date
class Child(object, date):
CLASS_VAR = "spam"
def eggs(self):
pass
def __init__(self):
super(Child, self).__init__()
self.artur = "king"
class StaticOnly(object):
@staticmethod
def static_method():
pass
@@ -0,0 +1,21 @@
class Parent(object):
C = 12
def foo(self):
pass
def __init__(self):
self.foo = 12
class Child(Parent, object):
def __init__(self): pass
AC = 11
def foo(self):
pass
class Bar(object):
pass
@@ -0,0 +1,19 @@
class Parent(object):
C = 12
def foo(self):
pass
class Child(Parent, object):
def __init__(self):
self.foo = 12
AC = 11
def foo(self):
pass
class Bar(object):
pass
@@ -52,4 +52,11 @@ class HugeChild(SubParent1, date): #SubParent1 is disabled
@staticmethod
def bad_method(): #Code has errors, so method should be not be marked as static
pass
pass
class Bar(object):
C = 1
class Foo(Bar):
def __init__(self):
self.foo = 12
@@ -0,0 +1,24 @@
package com.jetbrains.python.refactoring.classes;
import com.google.common.base.Function;
import com.intellij.refactoring.classMembers.MemberInfoModel;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
import org.jetbrains.annotations.NotNull;
/**
* @author Ilya.Kazakevich
*/
public class NameAndStatusTransformer implements Function<PyMemberInfo, PyPresenterTestMemberEntry> {
@NotNull
private final MemberInfoModel<PyElement, PyMemberInfo> myMemberInfoModel;
public NameAndStatusTransformer(MemberInfoModel<PyElement, PyMemberInfo> memberInfoModel) {
myMemberInfoModel = memberInfoModel;
}
@Override
public PyPresenterTestMemberEntry apply(final PyMemberInfo input) {
return new PyPresenterTestMemberEntry(input.getDisplayName(), myMemberInfoModel.isMemberEnabled(input), input.isStatic());
}
}
@@ -0,0 +1,57 @@
package com.jetbrains.python.refactoring.classes;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
/**
* Test only {@link com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo} representation.
* @author Ilya.Kazakevich
*/
public class PyPresenterTestMemberEntry {
@NonNls @NotNull
private final String myName;
private final boolean myEnabled;
private final boolean myStaticEntry;
/**
* @param name name of the member
* @param enabled is member enabled or not
* @param staticEntry is member static entry
*/
public PyPresenterTestMemberEntry(@NotNull final String name, final boolean enabled, final boolean staticEntry) {
myName = name;
myEnabled = enabled;
myStaticEntry = staticEntry;
}
@Override
public String toString() {
return "Entry{" +
"myName='" + myName + '\'' +
", myEnabled=" + myEnabled +
", myStaticEntry=" + myStaticEntry +
'}';
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (!(o instanceof PyPresenterTestMemberEntry)) return false;
final PyPresenterTestMemberEntry entry = (PyPresenterTestMemberEntry)o;
if (myEnabled != entry.myEnabled) return false;
if (myStaticEntry != entry.myStaticEntry) return false;
if (!myName.equals(entry.myName)) return false;
return true;
}
@Override
public int hashCode() {
int result = myName.hashCode();
result = 31 * result + (myEnabled ? 1 : 0);
result = 31 * result + (myStaticEntry ? 1 : 0);
return result;
}
}
@@ -15,10 +15,11 @@
*/
package com.jetbrains.python.refactoring.classes;
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.membersManager.MembersManager;
import com.jetbrains.python.refactoring.classes.pullUp.PyPullUpHelper;
import java.util.Collections;
@@ -53,6 +54,7 @@ public class PyPullUpTest extends PyClassRefactoringTest {
public void testMoveClassAttributesSimple() {
doHelperTest("Child", "#CLASS_VAR", "Parent");
}
public void testMoveClassAttributesNoPass() {
doHelperTest("Child2", "#CLASS_VAR", "Parent2");
}
@@ -60,9 +62,13 @@ public class PyPullUpTest extends PyClassRefactoringTest {
public void testMoveInstanceAttributesSimple() {
doHelperTest("Child", "#instance_field", "Parent");
}
public void testMoveInstanceAttributesNoInit() {
doHelperTest("Child", "#instance_field", "Parent");
}
public void testMoveInstanceAttributesLeaveEmptyInit() {
doHelperTest("Child", "#foo", "Parent");
}
public void testMultiFile() { // PY-2810
doMultiFileTest();
@@ -91,6 +97,16 @@ public class PyPullUpTest extends PyClassRefactoringTest {
final PyClass clazz = findClass(className);
final PyElement member = findMember(className, memberName);
final PyClass superClass = findClass(superClassName);
PyPullUpHelper.pullUp(clazz, Collections.singleton(MembersManager.findMember(clazz, member)), superClass);
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);
}
}
@@ -0,0 +1,119 @@
package com.jetbrains.python.refactoring.classes;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.jetbrains.python.fixtures.PyTestCase;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
import com.jetbrains.python.refactoring.classes.membersManager.vp.MembersBasedView;
import com.jetbrains.python.refactoring.classes.membersManager.vp.MembersViewInitializationInfo;
import org.easymock.Capture;
import org.easymock.EasyMock;
import org.easymock.internal.MocksControl;
import org.hamcrest.Matcher;
import org.jetbrains.annotations.NotNull;
import org.junit.Assert;
import java.util.Collection;
/**
* Base class for {@link com.jetbrains.python.refactoring.classes.membersManager.vp.MembersBasedPresenter} tests.
* It takes file <pre>file.py</pre> from folder provided as "refactoringName" constructor argument.
* It configures {@link #myView}
*
* @param <C> configuration class for presenter view
* @param <V> presenter's view class
* @author Ilya.Kazakevich
*/
public abstract class PyRefactoringPresenterTestCase<C extends MembersViewInitializationInfo, V extends MembersBasedView<C>>
extends PyTestCase {
/**
* Converts class collection to name collection
*/
protected static final Function<PyClass, String> CLASS_TO_NAME = new ClassToName();
/**
* Easy mock control
*/
protected MocksControl myMocksControl;
/**
* View mock
*/
protected V myView;
/**
* Capture that stores config ({@link MembersViewInitializationInfo} or its children) after {@link com.jetbrains.python.vp.Presenter#launch()} is called
*/
protected Capture<C> myViewConfigCapture;
@NotNull
private final Class<V> myViewClass;
@NotNull
private final String myRefactoringName;
/**
* @param viewClass view class
* @param refactoringName name of the refactoring. Folder with its name would be used to take "file.py" from it.
*/
protected PyRefactoringPresenterTestCase(@NotNull final Class<V> viewClass, @NotNull final String refactoringName) {
myViewClass = viewClass;
this.myRefactoringName = refactoringName;
}
/**
* Compares member states (names and other fields) with provided matcher.
* @see PyPresenterTestMemberEntry
* @param members members to compare
* @param matcher to match them against
*/
protected static void compareMembers(@NotNull final Collection<PyPresenterTestMemberEntry> members,
@NotNull final Matcher<Iterable<? extends PyPresenterTestMemberEntry>> matcher) {
Assert.assertThat("Wrong members or their states", members, matcher);
}
/**
* Initializes test. Always run it <strong>first</strong> if overwrite.
*/
@Override
public void setUp() throws Exception {
super.setUp();
myFixture.copyDirectoryToProject("refactoring/" + myRefactoringName + "/presenter/", "");
myFixture.configureFromTempProjectFile("file.py");
myMocksControl = new MocksControl(MocksControl.MockType.NICE);
myView = myMocksControl.createMock(myViewClass);
myViewConfigCapture = new Capture<C>();
myView.configure(EasyMock.capture(myViewConfigCapture));
EasyMock.expectLastCall().once();
}
/**
* @param name
* @return class by its name from file
*/
@NotNull
protected PyClass getClassByName(@NotNull final String name) {
return myFixture.findElementByText("class " + name, PyClass.class);
}
/**
* @return collection of members displayed by presenter
*/
@NotNull
protected Collection<PyPresenterTestMemberEntry> getMembers() {
Assert.assertTrue("No members captured", myViewConfigCapture.hasCaptured());
final Collection<PyMemberInfo> members = myViewConfigCapture.getValue().getMemberInfos();
Assert.assertFalse("No members selected", members.isEmpty());
return Collections2.transform(members, new NameAndStatusTransformer(myViewConfigCapture.getValue().getMemberInfoModel()));
}
private static class ClassToName implements Function<PyClass, String> {
@Override
public String apply(@NotNull final PyClass input) {
return input.getName();
}
}
}
@@ -0,0 +1,141 @@
package com.jetbrains.python.refactoring.classes.extractSuperclass;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.refactoring.classes.PyMemberInfoStorage;
import com.jetbrains.python.refactoring.classes.PyPresenterTestMemberEntry;
import com.jetbrains.python.refactoring.classes.PyRefactoringPresenterTestCase;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
import org.easymock.Capture;
import org.easymock.EasyMock;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import org.jetbrains.annotations.NotNull;
import org.junit.Assert;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* Tests presenter for "extract superclass" refactoring
*
* @author Ilya.Kazakevich
*/
public class PyExtractSuperclassPresenterTest
extends PyRefactoringPresenterTestCase<PyExtractSuperclassInitializationInfo, PyExtractSuperclassView> {
public PyExtractSuperclassPresenterTest() {
super(PyExtractSuperclassView.class, "extractsuperclass");
}
/**
* Tests that static methods could be moved, but "extends object" is not in list
*/
public void testStaticNoObject() {
final Collection<PyPresenterTestMemberEntry> members = launchAndGetMembers("StaticOnly");
final Matcher<Iterable<? extends PyPresenterTestMemberEntry>> matcher =
Matchers.containsInAnyOrder(new PyPresenterTestMemberEntry("static_method()", true, true));
compareMembers(members, matcher);
}
/**
* Tests that if no members selected, presenter shows error
*/
public void testNoSelectedMembersLeadsToError() {
EasyMock.expect(myView.getSelectedMemberInfos()).andReturn(Collections.<PyMemberInfo>emptyList()).anyTimes();
final Capture<String> errorMessageCapture = configureViewToCaptureError();
final PyExtractSuperclassPresenterImpl sut = configureByClass("Child");
myMocksControl.replay();
sut.launch();
sut.okClicked();
Assert.assertTrue("No error displayed empty list of selected members", errorMessageCapture.hasCaptured());
}
/**
* Checks that presenter displays error if user enters invalid name for new class
*/
public void testInvalidSuperClassNameLeadsToError() {
final String className = "Child";
final PyClass aClass = getClassByName(className);
final List<PyMemberInfo> classMemberInfos = new PyMemberInfoStorage(aClass).getClassMemberInfos(aClass);
assert !classMemberInfos.isEmpty() : "No member infos for " + className;
final PyMemberInfo pyMemberInfo = classMemberInfos.get(0);
EasyMock.expect(myView.getSelectedMemberInfos()).andReturn(Collections.singletonList(pyMemberInfo)).anyTimes();
EasyMock.expect(myView.getSuperClassName()).andReturn("INVALID CLASS NAME").anyTimes();
final Capture<String> errorMessageCapture = configureViewToCaptureError();
final PyExtractSuperclassPresenterImpl sut = configureByClass(className);
myMocksControl.replay();
sut.launch();
sut.okClicked();
Assert.assertTrue("No error displayed for invalid class name", errorMessageCapture.hasCaptured());
}
/**
* Creates capture ready to capture error message and configures view to return it
*
* @return
*/
@NotNull
private Capture<String> configureViewToCaptureError() {
final Capture<String> errorMessageCapture = new Capture<String>();
myView.showError(EasyMock.capture(errorMessageCapture));
return errorMessageCapture;
}
/**
* Checks that class fields could be moved while "extends object" is not in list
*/
public void testFieldsAndNoObject() {
final Collection<PyPresenterTestMemberEntry> members = launchAndGetMembers("Child");
final Matcher<Iterable<? extends PyPresenterTestMemberEntry>> matcher = Matchers
.containsInAnyOrder(new PyPresenterTestMemberEntry("CLASS_VAR", true, true),
new PyPresenterTestMemberEntry("eggs(self)", true, false),
new PyPresenterTestMemberEntry("__init__(self)", true, false),
new PyPresenterTestMemberEntry("self.artur", true, false),
new PyPresenterTestMemberEntry("extends date", true, false));
compareMembers(members, matcher);
}
/**
* launches presenter and returns member it displayed to user
*
* @param className name of class to configure presenter by
* @return displayed members
*/
@NotNull
private Collection<PyPresenterTestMemberEntry> launchAndGetMembers(@NotNull final String className) {
final PyExtractSuperclassPresenterImpl sut = configureByClass(className);
myMocksControl.replay();
sut.launch();
return getMembers();
}
/**
* Configures presenter by class
*
* @param name name of class
* @return presenter
*/
@NotNull
private PyExtractSuperclassPresenterImpl configureByClass(@NotNull final String name) {
final PyClass childClass = getClassByName(name);
final PyMemberInfoStorage storage = new PyMemberInfoStorage(childClass);
return new PyExtractSuperclassPresenterImpl(myView, childClass, storage);
}
}
@@ -13,7 +13,7 @@
* 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.extractSuperclass;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.vfs.LocalFileSystem;
@@ -23,6 +23,7 @@ import com.intellij.psi.PsiManager;
import com.jetbrains.python.PyNames;
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.extractSuperclass.PyExtractSuperclassHelper;
import com.jetbrains.python.refactoring.classes.membersManager.MembersManager;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
@@ -47,6 +48,10 @@ public class PyExtractSuperclassTest extends PyClassRefactoringTest {
doSimpleTest("A", "Suppa", null, false, ".foo");
}
public void testMoveFields() throws Exception {
doSimpleTest("FromClass", "ToClass", null, true, "#instance_field", "#CLASS_FIELD");
}
private void doSimpleTest(final String className, final String superclassName, final String expectedError, final boolean sameFile, final String... membersName) throws Exception {
try {
String baseName = "/refactoring/extractsuperclass/" + getTestName(true);
@@ -1,65 +1,42 @@
package com.jetbrains.python.refactoring.classes.pullUp;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.intellij.refactoring.classMembers.MemberInfoModel;
import com.jetbrains.python.fixtures.PyTestCase;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.refactoring.classes.PyMemberInfoStorage;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
import org.easymock.Capture;
import com.jetbrains.python.refactoring.classes.PyPresenterTestMemberEntry;
import com.jetbrains.python.refactoring.classes.PyRefactoringPresenterTestCase;
import org.easymock.EasyMock;
import org.easymock.internal.MocksControl;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.junit.Assert;
import java.util.Collection;
import java.util.List;
/**
* Test presenter for pull-up refactoring
*
* @author Ilya.Kazakevich
*/
public class PyPullUpPresenterTest extends PyTestCase {
private static final ClassToName CLASS_TO_NAME = new ClassToName();
private MocksControl myMocksControl;
private PyPullUpView myView;
private Capture<Collection<PyClass>> myParentsCapture;
private Capture<List<PyMemberInfo>> myMemberInfos;
public class PyPullUpPresenterTest extends PyRefactoringPresenterTestCase<PyPullUpViewInitializationInfo, PyPullUpView> {
@Override
public void setUp() throws Exception {
super.setUp();
//TODO: Extract to some shared place?
myFixture.copyDirectoryToProject("refactoring/pullup/presenter/", "");
myFixture.configureFromTempProjectFile("file.py");
myMocksControl = new MocksControl(MocksControl.MockType.NICE);
myView = myMocksControl.createMock(PyPullUpView.class);
myParentsCapture = new Capture<Collection<PyClass>>();
Capture<MemberInfoModel<PyElement, PyMemberInfo>> memInfoModelCapture = new Capture<MemberInfoModel<PyElement, PyMemberInfo>>();
myMemberInfos = new Capture<List<PyMemberInfo>>();
myView.init(EasyMock.capture(myParentsCapture), EasyMock.capture(memInfoModelCapture), EasyMock.capture(myMemberInfos));
EasyMock.expectLastCall().once();
public PyPullUpPresenterTest() {
super(PyPullUpView.class, "pullup");
}
/**
* Checks that parents are returned in MRO order and no parents outside of source root are included
*/
public void testParentsOrder() throws Exception {
PyPullUpPresenter sut = configureByClass("Child");
final PyPullUpPresenter sut = configureByClass("Child");
myMocksControl.replay();
sut.launch();
Assert.assertTrue("Presenter did not show parents", myParentsCapture.hasCaptured());
Collection<PyClass> parents = myParentsCapture.getValue();
Assert.assertTrue("Presenter did not show parents", myViewConfigCapture.hasCaptured());
final Collection<PyClass> parents = myViewConfigCapture.getValue().getParents();
Assert.assertThat("Wrong list of parents or parents are listed in wrong order", Collections2.transform(parents, CLASS_TO_NAME),
Matchers.contains("SubParent1", "SubParent2", "MainParent"));
}
@@ -85,130 +62,90 @@ public class PyPullUpPresenterTest extends PyTestCase {
ensureNoMembers("BadMro");
}
/**
* Checks that parent can't be moved to itself
*/
public void testNoMoveParentToItSelf() throws Exception {
final Collection<PyPresenterTestMemberEntry> memberNamesAndStatus = launchAndGetMembers("Foo", "Bar");
compareMembers(memberNamesAndStatus, Matchers.containsInAnyOrder(new PyPresenterTestMemberEntry("__init__(self)", true, false),
new PyPresenterTestMemberEntry("self.foo", true, false),
new PyPresenterTestMemberEntry("extends Bar", false, false)));
}
/**
* Checks that some members are not allowed, while others are
*/
public void testMembers() throws Exception {
PyPullUpPresenterImpl sut = configureByClass("HugeChild");
EasyMock.expect(myView.getSelectedParent()).andReturn(getClassByName("SubParent1")).anyTimes();
myMocksControl.replay();
sut.launch();
Assert.assertTrue("No members selected", myMemberInfos.hasCaptured());
List<PyMemberInfo> members = myMemberInfos.getValue();
Assert.assertFalse("No members selected", members.isEmpty());
final Collection<Entry> memberNamesAndStatus = Collections2.transform(members, new NameAndStatusTransformer(sut));
final Collection<PyPresenterTestMemberEntry> memberNamesAndStatus = launchAndGetMembers("HugeChild", "SubParent1");
//Pair will return correct type
final Matcher<Iterable<? extends Entry>> matcher = Matchers
.containsInAnyOrder(new Entry("extends date", true, false),
new Entry("CLASS_FIELD", true, true),
new Entry("__init__(self)", true, false),
new Entry("extends SubParent1", false, false),
new Entry("foo(self)", false, false),
new Entry("bar(self)", true, false),
new Entry("static_1(cls)", true, true),
new Entry("static_2()", true, true),
new Entry("self.instance_field_1", true, false),
new Entry("self.instance_field_2", true, false),
new Entry("bad_method()", true, false)
);
Assert.assertThat("Wrong members or their states", memberNamesAndStatus, matcher);
final Matcher<Iterable<? extends PyPresenterTestMemberEntry>> matcher = Matchers
.containsInAnyOrder(new PyPresenterTestMemberEntry("extends date", true, false),
new PyPresenterTestMemberEntry("CLASS_FIELD", true, true),
new PyPresenterTestMemberEntry("__init__(self)", true, false),
new PyPresenterTestMemberEntry("extends SubParent1", false, false),
new PyPresenterTestMemberEntry("foo(self)", false, false),
new PyPresenterTestMemberEntry("bar(self)", true, false),
new PyPresenterTestMemberEntry("static_1(cls)", true, true),
new PyPresenterTestMemberEntry("static_2()", true, true),
new PyPresenterTestMemberEntry("self.instance_field_1", true, false),
new PyPresenterTestMemberEntry("self.instance_field_2", true, false),
new PyPresenterTestMemberEntry("bad_method()", true, false));
compareMembers(memberNamesAndStatus, matcher);
}
/**
* Launches presenter and returns members it displayed to user
*
* @param classUnderRefactoring class to refactor
* @param destinationClass where to move it
* @return members displayed to user
*/
@NotNull
private Collection<PyPresenterTestMemberEntry> launchAndGetMembers(@NotNull final String classUnderRefactoring,
@NotNull final String destinationClass) {
final PyPullUpPresenterImpl sut = configureByClass(classUnderRefactoring);
EasyMock.expect(myView.getSelectedParent()).andReturn(getClassByName(destinationClass)).anyTimes();
myMocksControl.replay();
sut.launch();
return getMembers();
}
/**
* Checks that refactoring does not work for classes with out of members
*/
private void ensureNoMembers(String className) throws Exception {
private void ensureNoMembers(@NotNull final String className) throws Exception {
try {
PyPullUpPresenter sut = configureByClass(className);
final PyPullUpPresenter sut = configureByClass(className);
myMocksControl.replay();
sut.launch();
}
catch (IllegalArgumentException ignored) {
catch (final IllegalArgumentException ignored) {
return;
}
Assert.fail("Presenter should throw exception, but it returned list of parents instead: " + myParentsCapture.getValue());
Assert
.fail("Presenter should throw exception, but it returned list of parents instead: " + myViewConfigCapture.getValue().getParents());
}
private PyPullUpPresenterImpl configureByClass(String name) {
PyClass childClass = getClassByName(name);
PyMemberInfoStorage storage = new PyMemberInfoStorage(childClass);
/**
* Creates presenter (sut) by class
*
* @param name name of class
* @return presenter
*/
private PyPullUpPresenterImpl configureByClass(@NotNull final String name) {
final PyClass childClass = getClassByName(name);
final PyMemberInfoStorage storage = new PyMemberInfoStorage(childClass);
return new PyPullUpPresenterImpl(myView, storage, childClass);
}
@NotNull
private PyClass getClassByName(String name) {
return myFixture.findElementByText("class " + name, PyClass.class);
}
private static class ClassToName implements Function<PyClass, String> {
@Override
public String apply(PyClass input) {
return input.getName();
}
}
private static class NameAndStatusTransformer implements Function<PyMemberInfo, Entry> {
private final PyPullUpPresenterImpl presenter;
private NameAndStatusTransformer(PyPullUpPresenterImpl presenter) {
this.presenter = presenter;
}
@Override
public Entry apply(final PyMemberInfo input) {
return new Entry(input.getDisplayName(), presenter.isMemberEnabled(input), input.isStatic());
}
}
private static class Entry {
@NonNls @NotNull
private final String myName;
private final boolean myEnabled;
private final boolean myStaticEntry;
private Entry(@NotNull final String name, final boolean enabled, final boolean staticEntry) {
myName = name;
myEnabled = enabled;
myStaticEntry = staticEntry;
}
@Override
public String toString() {
return "Entry{" +
"myName='" + myName + '\'' +
", myEnabled=" + myEnabled +
", myStaticEntry=" + myStaticEntry +
'}';
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (!(o instanceof Entry)) return false;
final Entry entry = (Entry)o;
if (myEnabled != entry.myEnabled) return false;
if (myStaticEntry != entry.myStaticEntry) return false;
if (!myName.equals(entry.myName)) return false;
return true;
}
@Override
public int hashCode() {
int result = myName.hashCode();
result = 31 * result + (myEnabled ? 1 : 0);
result = 31 * result + (myStaticEntry ? 1 : 0);
return result;
}
}
}