mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
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:
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-218
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
-27
@@ -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() {
|
||||
|
||||
+87
-56
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+31
@@ -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;
|
||||
}
|
||||
}
|
||||
+46
@@ -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();
|
||||
}
|
||||
}
|
||||
+10
@@ -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?
|
||||
}
|
||||
+97
@@ -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());
|
||||
}
|
||||
}
|
||||
+25
@@ -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();
|
||||
|
||||
}
|
||||
+109
@@ -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();
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* "Extract superclass" refactoring
|
||||
* @author Ilya.Kazakevich
|
||||
*/
|
||||
package com.jetbrains.python.refactoring.classes.extractSuperclass;
|
||||
+1
-1
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
+9
-18
@@ -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());
|
||||
}
|
||||
|
||||
+10
-7
@@ -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
|
||||
|
||||
+66
-15
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-4
@@ -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
|
||||
|
||||
+62
@@ -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;
|
||||
}
|
||||
}
|
||||
+4
-13
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+18
@@ -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);
|
||||
}
|
||||
}
|
||||
+21
@@ -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();
|
||||
}
|
||||
+83
@@ -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();
|
||||
|
||||
}
|
||||
+60
@@ -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();
|
||||
}
|
||||
+45
@@ -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();
|
||||
}
|
||||
+68
@@ -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();
|
||||
}
|
||||
+133
@@ -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();
|
||||
}
|
||||
}
|
||||
+49
@@ -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);
|
||||
}
|
||||
}
|
||||
+6
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -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();
|
||||
}
|
||||
|
||||
+65
-62
@@ -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);
|
||||
}
|
||||
|
||||
+38
@@ -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);
|
||||
}
|
||||
}
|
||||
+111
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+8
-18
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
-100
@@ -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());
|
||||
}
|
||||
}
|
||||
+57
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+119
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+141
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+6
-1
@@ -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);
|
||||
+70
-133
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user