Merge branch 'master' of git.labs.intellij.net:idea/community

This commit is contained in:
Michael Golubev
2014-02-20 13:54:20 +01:00
59 changed files with 1061 additions and 418 deletions
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2013 JetBrains s.r.o.
* 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.
@@ -17,6 +17,7 @@ package com.intellij.codeInsight.template.postfix.settings;
import com.intellij.application.options.editor.EditorOptionsProvider;
import com.intellij.codeInsight.CodeInsightBundle;
import com.intellij.codeInsight.template.impl.LiveTemplateCompletionContributor;
import com.intellij.codeInsight.template.impl.TemplateSettings;
import com.intellij.codeInsight.template.postfix.templates.PostfixTemplate;
import com.intellij.openapi.options.Configurable;
@@ -155,6 +156,7 @@ public class PostfixTemplatesConfigurable implements SearchableConfigurable, Edi
private void updateComponents() {
boolean pluginEnabled = myPostfixTemplatesEnabled.isSelected();
myCompletionEnabledCheckbox.setVisible(!LiveTemplateCompletionContributor.shouldShowAllTemplates());
myCompletionEnabledCheckbox.setEnabled(pluginEnabled);
myShortcutComboBox.setEnabled(pluginEnabled);
if (myTemplatesListPanel != null) {
@@ -1,3 +1,18 @@
/*
* 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.intellij.codeInsight.template.postfix.templates;
import com.intellij.codeInsight.guess.GuessManager;
@@ -28,17 +43,17 @@ public class InstanceofExpressionPostfixTemplate extends PostfixTemplate {
@Override
public boolean isApplicable(@NotNull PsiElement context, @NotNull Document copyDocument, int newOffset) {
return getTopmostExpression(context) != null;
return PostfixTemplatesUtils.isNotPrimitiveTypeExpression(getTopmostExpression(context));
}
@Override
public void expand(@NotNull PsiElement context, @NotNull Editor editor) {
PsiExpression expression = getTopmostExpression(context);
if (expression == null) return;
if (!PostfixTemplatesUtils.isNotPrimitiveTypeExpression(expression)) return;
surroundExpression(context.getProject(), editor, expression);
}
private static void surroundExpression(Project project, Editor editor, PsiExpression expr) throws IncorrectOperationException {
private static void surroundExpression(@NotNull Project project, @NotNull Editor editor, @NotNull PsiExpression expr) throws IncorrectOperationException {
assert expr.isValid();
PsiType[] types = GuessManager.getInstance(project).guessTypeToCast(expr);
final boolean parenthesesNeeded = expr instanceof PsiPolyadicExpression ||
@@ -1,3 +1,18 @@
/*
* 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.intellij.codeInsight.template.postfix.templates;
import com.intellij.codeInsight.CodeInsightServicesUtil;
@@ -10,6 +25,13 @@ import org.jetbrains.annotations.NotNull;
@Aliases("!")
public class NotExpressionPostfixTemplate extends ExpressionPostfixTemplateWithChooser {
private static final Condition<PsiExpression> BOOLEAN_TYPE_CONDITION = new Condition<PsiExpression>() {
@Override
public boolean value(PsiExpression expression) {
return PostfixTemplatesUtils.isBoolean(expression.getType());
}
};
public NotExpressionPostfixTemplate() {
super("not", "Negates boolean expression", "!expr");
}
@@ -22,11 +44,6 @@ public class NotExpressionPostfixTemplate extends ExpressionPostfixTemplateWithC
@NotNull
@Override
protected Condition<PsiExpression> getTypeCondition() {
return new Condition<PsiExpression>() {
@Override
public boolean value(PsiExpression expression) {
return PostfixTemplatesUtils.isBoolean(expression.getType());
}
};
return BOOLEAN_TYPE_CONDITION;
}
}
@@ -39,13 +39,13 @@ public abstract class NullCheckPostfixTemplate extends PostfixTemplate {
@Override
public boolean isApplicable(@NotNull PsiElement context, @NotNull Document copyDocument, int newOffset) {
return getTopmostExpression(context) != null;
return PostfixTemplatesUtils.isNotPrimitiveTypeExpression(getTopmostExpression(context));
}
@Override
public void expand(@NotNull PsiElement context, @NotNull Editor editor) {
PsiExpression expr = getTopmostExpression(context);
if (expr == null) return;
if (!PostfixTemplatesUtils.isNotPrimitiveTypeExpression(expr)) return;
Project project = expr.getProject();
PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory();
@@ -19,6 +19,7 @@ import com.intellij.codeInsight.template.postfix.util.PostfixTemplatesUtils;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiExpression;
import org.jetbrains.annotations.NotNull;
public class ThrowExceptionPostfixTemplate extends PostfixTemplate {
@@ -28,7 +29,8 @@ public class ThrowExceptionPostfixTemplate extends PostfixTemplate {
@Override
public boolean isApplicable(@NotNull PsiElement context, @NotNull Document copyDocument, int newOffset) {
return getTopmostExpression(context) != null;
PsiExpression expression = getTopmostExpression(context);
return expression != null && PostfixTemplatesUtils.isThrowable(expression.getType());
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2013 JetBrains s.r.o.
* 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.
@@ -46,11 +46,21 @@ public abstract class PostfixTemplatesUtils {
editor.getCaretModel().moveToOffset(replace.getTextRange().getEndOffset());
}
@Contract("null -> false")
public static boolean isNotPrimitiveTypeExpression(@Nullable PsiExpression expression) {
return expression != null && !(expression.getType() instanceof PsiPrimitiveType);
}
@Contract("null -> false")
public static boolean isIterable(@Nullable PsiType type) {
return type != null && InheritanceUtil.isInheritor(type, CommonClassNames.JAVA_LANG_ITERABLE);
}
@Contract("null -> false")
public static boolean isThrowable(@Nullable PsiType type) {
return type != null && InheritanceUtil.isInheritor(type, CommonClassNames.JAVA_LANG_THROWABLE);
}
@Contract("null -> false")
public static boolean isArray(@Nullable PsiType type) {
return type != null && type instanceof PsiArrayType;
@@ -0,0 +1,5 @@
public class Foo {
void m(int o) {
o.instanceof<caret>
}
}
@@ -0,0 +1,5 @@
public class Foo {
void m(int o) {
o.instanceof <caret>
}
}
@@ -0,0 +1,5 @@
public class Foo {
void m(int o) {
o.notnull<caret>
}
}
@@ -0,0 +1,5 @@
public class Foo {
void m(int o) {
o.notnull <caret>
}
}
@@ -0,0 +1,5 @@
public class Foo {
void m(int o) {
o.null<caret>
}
}
@@ -0,0 +1,5 @@
public class Foo {
void m(int o) {
o.null <caret>
}
}
@@ -0,0 +1,5 @@
public class Foo {
void m(Object o) {
o.throw<caret>
}
}
@@ -0,0 +1,5 @@
public class Foo {
void m(Object o) {
throw o;<caret>
}
}
@@ -1,5 +1,7 @@
import java.lang.RuntimeException;
public class Foo {
void m(Object o) {
o.throw<caret>
void m() {
new RuntimeException("error").throw<caret>
}
}
@@ -1,5 +1,7 @@
import java.lang.RuntimeException;
public class Foo {
void m(Object o) {
throw o;<caret>
void m() {
throw new RuntimeException("error");
}
}
@@ -1,3 +1,18 @@
/*
* 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.intellij.codeInsight.template.postfix.templates;
import com.intellij.codeInsight.template.impl.TemplateManagerImpl;
@@ -7,6 +22,7 @@ import org.jetbrains.annotations.NotNull;
public class InstanceofPostfixTemplateTest extends PostfixTemplateTestCase {
public void testSingleExpression() { doTest(); }
public void testAlias() { doTest(); }
public void testPrimitive() { doTest(); }
public void testSingleExpressionTemplate() {
TemplateManagerImpl.setTemplateTesting(getProject(), getTestRootDisposable());
@@ -1,3 +1,18 @@
/*
* 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.intellij.codeInsight.template.postfix.templates;
import org.jetbrains.annotations.NotNull;
@@ -11,6 +26,7 @@ public class NotNullPostfixTemplateTest extends PostfixTemplateTestCase {
protected String getSuffix() { return "notnull"; }
public void testSimple() { doTest(); }
public void testPrimitive() { doTest(); }
public void testNn() { doTest(); }
public void testSecondStatement() { doTest(); }
}
@@ -1,3 +1,18 @@
/*
* 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.intellij.codeInsight.template.postfix.templates;
import org.jetbrains.annotations.NotNull;
@@ -11,5 +26,6 @@ public class NullPostfixTemplateTest extends PostfixTemplateTestCase {
protected String getSuffix() { return "null"; }
public void testSimple() { doTest(); }
public void testPrimitive() { doTest(); }
public void testSecondStatement() { doTest(); }
}
@@ -26,8 +26,8 @@ import java.util.Set;
*/
public abstract class DependentMembersCollectorBase<T extends PsiElement, C> {
protected final HashSet<T> myCollection = new HashSet<T>();
private final C myClass;
private final C mySuperClass;
protected final C myClass;
protected final C mySuperClass;
public DependentMembersCollectorBase(C clazz, C superClass) {
myClass = clazz;
@@ -3,7 +3,7 @@
<grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="20" y="20" width="500" height="400"/>
<xy x="20" y="20" width="631" height="400"/>
</constraints>
<properties/>
<border type="none"/>
@@ -28,8 +28,7 @@
<border type="none"/>
<children/>
</grid>
<grid id="177e3" layout-manager="GridLayoutManager" row-count="2" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<grid id="177e3" layout-manager="GridBagLayout">
<constraints>
<splitpane position="left"/>
</constraints>
@@ -38,7 +37,8 @@
<children>
<scrollpane id="86224" class="com.intellij.ui.components.JBScrollPane">
<constraints>
<grid row="0" column="0" row-span="1" col-span="3" vsize-policy="3" hsize-policy="7" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
<grid row="0" column="0" row-span="1" col-span="6" vsize-policy="3" hsize-policy="7" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
<gridbag weightx="1.0" weighty="1.0"/>
</constraints>
<properties/>
<border type="none"/>
@@ -49,27 +49,34 @@
</component>
</children>
</scrollpane>
<component id="c4746" class="javax.swing.JButton" binding="mySelectAllButton" default-binding="true">
<grid id="6e08e" layout-manager="GridBagLayout">
<constraints>
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
<grid row="1" column="0" row-span="1" col-span="6" vsize-policy="3" hsize-policy="3" anchor="4" fill="2" indent="0" use-parent-layout="false"/>
<gridbag weightx="0.0" weighty="0.0"/>
</constraints>
<properties>
<text resource-bundle="messages/RefactoringBundle" key="select.all.button"/>
</properties>
</component>
<component id="1217e" class="javax.swing.JButton" binding="myUnselectAllButton" default-binding="true">
<constraints>
<grid row="1" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text resource-bundle="messages/RefactoringBundle" key="unselect.all.button"/>
</properties>
</component>
<hspacer id="eacfb">
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
</hspacer>
<properties/>
<border type="none"/>
<children>
<component id="1217e" class="javax.swing.JButton" binding="myUnselectAllButton" default-binding="true">
<constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="4" fill="0" indent="0" use-parent-layout="false"/>
<gridbag top="0" left="5" bottom="0" right="0" weightx="0.0" weighty="0.0"/>
</constraints>
<properties>
<text resource-bundle="messages/RefactoringBundle" key="unselect.all.button"/>
</properties>
</component>
<component id="c4746" class="javax.swing.JButton" binding="mySelectAllButton" default-binding="true">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="4" fill="0" indent="0" use-parent-layout="false"/>
<gridbag weightx="0.0" weighty="0.0"/>
</constraints>
<properties>
<text resource-bundle="messages/RefactoringBundle" key="select.all.button"/>
</properties>
</component>
</children>
</grid>
</children>
</grid>
</children>
@@ -126,6 +126,14 @@ public abstract class AbstractMemberSelectionTable<T extends PsiElement, M exten
myTableModel.fireTableDataChanged();
}
/**
* Redraws table
*/
public void redraw() {
myTableModel.redraw(getSelectedMemberInfos());
myTableModel.fireTableDataChanged();
}
public void setMemberInfos(Collection<M> memberInfos) {
myMemberInfos = new ArrayList<M>(memberInfos);
fireMemberInfoChange(memberInfos);
@@ -313,9 +321,13 @@ public abstract class AbstractMemberSelectionTable<T extends PsiElement, M exten
}
Collection<M> changed = Collections.singletonList(myTable.myMemberInfos.get(rowIndex));
redraw(changed);
// fireTableRowsUpdated(rowIndex, rowIndex);
}
public void redraw(Collection<M> changed) {
myTable.fireMemberInfoChange(changed);
fireTableDataChanged();
// fireTableRowsUpdated(rowIndex, rowIndex);
}
}
@@ -19,6 +19,7 @@ import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.CaretAction;
import com.intellij.openapi.editor.Editor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
@@ -114,7 +115,7 @@ public abstract class EditorActionHandler {
* @param editor the editor in which the action is invoked.
* @param dataContext the data context for the action.
*/
public void executeInCaretContext(final Editor editor, Caret caret, final DataContext dataContext) {
public void executeInCaretContext(@NotNull final Editor editor, @Nullable Caret caret, final DataContext dataContext) {
if (caret == null && runForAllCarets()) {
editor.getCaretModel().runForEachCaret(new CaretAction() {
@Override
@@ -11,4 +11,5 @@ Pythonid 3.1
Karma 134.686 134.31
org.intellij.scala 0.32.512
org.jetbrains.kannotator 0.2.420
SBT 1.0.0 1.1.0 1.2.0 1.3.0 1.3.1 1.4.0 1.5.0
SBT 1.0.0 1.1.0 1.2.0 1.3.0 1.3.1 1.4.0 1.5.0
"JSTestDriver Plugin" 134.686 134.31 134.307 134.1039
@@ -29,13 +29,20 @@ public class SemVer {
private final int myMajor;
private final int myMinor;
private final int myPatch;
private final String myRawVersion;
public SemVer(int major, int minor, int patch) {
public SemVer(@NotNull String rawVersion, int major, int minor, int patch) {
myRawVersion = rawVersion;
myMajor = major;
myMinor = minor;
myPatch = patch;
}
@NotNull
public String getRawVersion() {
return myRawVersion;
}
public int getMajor() {
return myMajor;
}
@@ -85,7 +92,7 @@ public class SemVer {
}
Integer patch = toInteger(patchStr);
if (major != null && minor != null && patch != null) {
return new SemVer(major, minor, patch);
return new SemVer(text, major, minor, patch);
}
return null;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2013 JetBrains s.r.o.
* 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.
@@ -15,7 +15,7 @@
*/
package com.intellij.util.text;
import junit.framework.Assert;
import org.junit.Assert;
import org.junit.Test;
/**
@@ -24,15 +24,17 @@ import org.junit.Test;
public class SemVerTest {
@Test
public void testParsing() throws Exception {
SemVer semVer = SemVer.parseFromText("0.9.2");
String version = "0.9.2";
SemVer semVer = SemVer.parseFromText(version);
Assert.assertNotNull(semVer);
Assert.assertEquals(new SemVer(0, 9, 2), semVer);
Assert.assertEquals(new SemVer(version, 0, 9, 2), semVer);
}
@Test
public void testExtendedVersion() throws Exception {
SemVer semVer = SemVer.parseFromText("0.9.2-dart");
String version = "0.9.2-dart";
SemVer semVer = SemVer.parseFromText(version);
Assert.assertNotNull(semVer);
Assert.assertEquals(new SemVer(0, 9, 2), semVer);
Assert.assertEquals(new SemVer(version, 0, 9, 2), semVer);
}
}
@@ -516,6 +516,9 @@ INSP.NAME.assignment.to.loop.or.with.parameter.display.name=Assignment to 'for'
INSP.NAME.assignment.to.loop.or.with.parameter.display.message=Variable ''{0}'' already declared in ''for'' loop or ''with'' statement above
# Refactoring
refactoring.will.not.be.accessible=Member, you are trying to move depends on ''{0}'' which will not be accessible after this refactoring
# introduce
refactoring.introduce.name.error=Incorrect name
refactoring.introduce.selection.error=Cannot perform refactoring using selected element(s)
@@ -559,7 +562,7 @@ refactoring.extract.method.error.name.clash=Method name clashes with already exi
refactoring.extract.method.error.cannot.perform.refactoring.with.local=Cannot perform refactoring from expression with local variables modifications and return instructions inside code fragment
# extract superclass
refactoring.extract.super.target.path.outside.roots=Target directory is outside the project.<br>Must be within content roots
refactoring.extract.super.target.path.outside.roots=Target directory is outside the project. Must be within content roots
refactoring.extract.super.name.0.must.be.ident=Name ''{0}'' is invalid. Must be a valid Python identifier
refactoring.extract.super.class.no.members.allowed=None of members could be extracted
@@ -80,11 +80,11 @@ public final class PyClassRefactoringUtil {
if (methods.isEmpty()) {
return Collections.emptyList();
}
for (PsiElement e : methods) {
for (final PsiElement e : methods) {
rememberNamedReferences(e);
}
final PyFunction[] elements = methods.toArray(new PyFunction[methods.size()]);
return addMethods(superClass, elements);
return addMethods(superClass, true, elements);
}
/**
@@ -92,28 +92,31 @@ public final class PyClassRefactoringUtil {
*
* @param destination where to add methods
* @param methods methods
* @return newly added methods
* @param skipIfExist do not add anything if method already exists
* @return newly added methods or existing one (if skipIfExists is true and method already exists)
*/
@NotNull
public static List<PyFunction> addMethods(@NotNull final PyClass destination, @NotNull final PyFunction... methods) {
public static List<PyFunction> addMethods(@NotNull final PyClass destination, final boolean skipIfExist, @NotNull final PyFunction... methods) {
final PyStatementList destStatementList = destination.getStatementList();
final List<PyFunction> newlyCreatedMethods = new ArrayList<PyFunction>(methods.length);
final List<PyFunction> result = new ArrayList<PyFunction>(methods.length);
for (final PyFunction method : methods) {
if (destination.findMethodByName(method.getName(), false) != null) {
continue; //We skip adding if class already has this method. I am not sure if this behaviour is correct, but it was here, so I left if for backward compatibility
final PyFunction existingMethod = destination.findMethodByName(method.getName(), false);
if ((existingMethod != null) && skipIfExist) {
result.add(existingMethod);
continue; //We skip adding if class already has this method.
}
final PyFunction newMethod = insertMethodInProperPlace(destStatementList, method);
newlyCreatedMethods.add(newMethod);
result.add(newMethod);
restoreNamedReferences(newMethod);
}
PyPsiUtils.removeRedundantPass(destStatementList);
return newlyCreatedMethods;
return result;
}
/**
@@ -17,7 +17,7 @@ package com.jetbrains.python.refactoring.classes;
import com.intellij.refactoring.classMembers.DependentMembersCollectorBase;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.refactoring.classes.membersManager.MembersManager;
/**
* @author Dennis.Ushakov
@@ -28,26 +28,7 @@ public class PyDependentMembersCollector extends DependentMembersCollectorBase<P
}
@Override
public void collect(PyElement member) {
//TODO: Move to MembersManager as well
final PyRecursiveElementVisitor visitor = new PyRecursiveElementVisitor() {
@Override
public void visitPyCallExpression(PyCallExpression node) {
final Callable markedFunction = node.resolveCalleeFunction(PyResolveContext.noImplicits());
final PyFunction function = markedFunction != null ? markedFunction.asMethod() : null;
if (!existsInSuperClass(function)) {
myCollection.add(function);
}
}
};
member.accept(visitor);
}
private boolean existsInSuperClass(PyFunction classMember) {
if (getSuperClass() == null) return false;
final String name = classMember != null ? classMember.getName() : null;
if (name == null) return false;
final PyFunction methodBySignature = (getSuperClass()).findMethodByName(name, true);
return methodBySignature != null;
public void collect(final PyElement member) {
myCollection.addAll(MembersManager.getAllDependencies(myClass, member, getSuperClass()));
}
}
@@ -7,9 +7,7 @@ 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;
@@ -24,6 +22,7 @@ import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
/**
* @author Ilya.Kazakevich
@@ -32,20 +31,10 @@ class PyExtractSuperclassPresenterImpl extends MembersBasedPresenterNoPreviewImp
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
super(view, classUnderRefactoring, infoStorage, new PyExtractSuperclassInfoModel(classUnderRefactoring));
}
@Override
@@ -79,7 +68,7 @@ class PyExtractSuperclassPresenterImpl extends MembersBasedPresenterNoPreviewImp
final Collection<PyMemberInfo<PyElement>> pyMemberInfos =
PyUtil.filterOutObject(myStorage.getClassMemberInfos(myClassUnderRefactoring));
myView.configure(
new PyExtractSuperclassInitializationInfo(new PyExtractSuperclassInfoModel(myClassUnderRefactoring), pyMemberInfos, defaultFilePath,
new PyExtractSuperclassInitializationInfo(myModel, pyMemberInfos, defaultFilePath,
roots));
myView.initAndShow();
@@ -96,4 +85,10 @@ class PyExtractSuperclassPresenterImpl extends MembersBasedPresenterNoPreviewImp
PyExtractSuperclassHelper
.extractSuperclass(myClassUnderRefactoring, myView.getSelectedMemberInfos(), myView.getSuperClassName(), myView.getModuleFile());
}
@NotNull
@Override
protected Iterable<? extends PyClass> getDestClassesToCheckConflicts() {
return Collections.emptyList(); // No conflict can take place in newly created classes
}
}
@@ -1,9 +1,6 @@
package com.jetbrains.python.refactoring.classes.membersManager;
import com.jetbrains.python.psi.PyAssignmentStatement;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.psi.PyTargetExpression;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil;
import org.jetbrains.annotations.NotNull;
@@ -22,11 +19,15 @@ class ClassFieldsManager extends FieldsManager {
super(true);
}
@Override
public boolean hasConflict(@NotNull final PyTargetExpression member, @NotNull final PyClass aClass) {
return NamePredicate.hasElementWithSameName(member, aClass.getClassAttributes());
}
@Override
protected Collection<PyElement> moveAssignments(@NotNull final PyClass from,
@NotNull final Collection<PyAssignmentStatement> statements,
@NotNull final PyClass... to) {
@NotNull final Collection<PyAssignmentStatement> statements,
@NotNull final PyClass... to) {
//TODO: Copy/paste with InstanceFieldsManager. Move to parent?
final List<PyElement> result = new ArrayList<PyElement>();
for (final PyClass destClass : to) {
@@ -5,15 +5,14 @@ import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.containers.MultiMap;
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.psi.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
@@ -34,6 +33,21 @@ abstract class FieldsManager extends MembersManager<PyTargetExpression> {
myStatic = isStatic;
}
@NotNull
@Override
protected Collection<PyElement> getDependencies(@NotNull final MultiMap<PyClass, PyElement> usedElements) {
return Collections.emptyList();
}
@NotNull
@Override
protected MultiMap<PyClass, PyElement> getDependencies(@NotNull final PyElement member) {
final MultiMap<PyClass, PyElement> result = new MultiMap<PyClass, PyElement>();
member.accept(new MyPyRecursiveElementVisitor(result));
return result;
}
@Override
protected Collection<? extends PyElement> getElementsToStoreReferences(@NotNull final Collection<PyTargetExpression> elements) {
// We need to save references from assignments
@@ -50,7 +64,8 @@ abstract class FieldsManager extends MembersManager<PyTargetExpression> {
protected Collection<PyElement> moveMembers(@NotNull final PyClass from,
@NotNull final Collection<PyMemberInfo<PyTargetExpression>> members,
@NotNull final PyClass... to) {
return moveAssignments(from, Collections2.filter(Collections2.transform(fetchElements(members), ASSIGNMENT_TRANSFORM), NotNullPredicate.INSTANCE),
return moveAssignments(from, Collections2
.filter(Collections2.transform(fetchElements(members), ASSIGNMENT_TRANSFORM), NotNullPredicate.INSTANCE),
to);
}
@@ -116,4 +131,27 @@ abstract class FieldsManager extends MembersManager<PyTargetExpression> {
return PsiTreeUtil.getParentOfType(input, PyAssignmentStatement.class);
}
}
/**
* Fetches field declarations
*/
private static class MyPyRecursiveElementVisitor extends PyRecursiveElementVisitor {
@NotNull
private final MultiMap<PyClass, PyElement> myResult;
private MyPyRecursiveElementVisitor(@NotNull final MultiMap<PyClass, PyElement> result) {
myResult = result;
}
@Override
public void visitPyReferenceExpression(final PyReferenceExpression node) {
final PsiElement declaration = node.getReference().resolve();
if (declaration instanceof PyElement) {
final PyClass parent = PsiTreeUtil.getParentOfType(declaration, PyClass.class);
if (parent != null) {
myResult.putValue(parent, (PyElement)declaration);
}
}
}
}
}
@@ -8,6 +8,7 @@ import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyFunctionBuilder;
import com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
@@ -24,6 +25,10 @@ class InstanceFieldsManager extends FieldsManager {
super(false);
}
@Override
public boolean hasConflict(@NotNull final PyTargetExpression member, @NotNull final PyClass aClass) {
return NamePredicate.hasElementWithSameName(member, aClass.getInstanceAttributes());
}
@Override
protected Collection<PyElement> moveAssignments(@NotNull final PyClass from,
@@ -77,7 +82,7 @@ class InstanceFieldsManager extends FieldsManager {
final PyFunctionBuilder functionBuilder = new PyFunctionBuilder(PyNames.INIT);
functionBuilder.parameter(PyNames.CANONICAL_SELF); //TODO: Take param from codestyle?
final PyFunction function = functionBuilder.buildFunction(to.getProject(), LanguageLevel.forElement(to));
return PyClassRefactoringUtil.addMethods(to, function).get(0);
return PyClassRefactoringUtil.addMethods(to, true, function).get(0);
}
@Override
@@ -0,0 +1,62 @@
package com.jetbrains.python.refactoring.classes.membersManager;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.ui.ConflictsDialog;
import com.intellij.refactoring.util.RefactoringUIUtil;
import com.intellij.util.containers.MultiMap;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.psi.PyClass;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
/**
* Displays error messages about fact that destination class already contains some member infos
* or members under refactoring would not be available at the new place.
*
* @author Ilya.Kazakevich
*/
public class MembersConflictDialog extends ConflictsDialog {
/**
* @param project project under refactoring
* @param duplicatesConflict duplicates conflicts : that means destination class has the same member.
* If member "foo" already exists in class "bar": pass [bar] -] [foo].
* @param dependenciesConflicts dependency conflict: list of elements used by member under refactoring and would not be available
* at new destination. If user wants to move method, that uses field "bar" which would not be available at new class,
* pass [bar] field
*/
public MembersConflictDialog(
@NotNull final Project project,
@NotNull final MultiMap<PyClass, PyMemberInfo<?>> duplicatesConflict,
@NotNull final Collection<PyMemberInfo<?>> dependenciesConflicts) {
super(project, convertDescription(duplicatesConflict, dependenciesConflicts), null, true, false);
}
@NotNull
private static MultiMap<PsiElement, String> convertDescription(
@NotNull final MultiMap<PyClass, PyMemberInfo<?>> duplicateConflictDescriptions,
@NotNull final Collection<PyMemberInfo<?>> dependenciesConflicts) {
final MultiMap<PsiElement, String> result = new MultiMap<PsiElement, String>();
for (final PyClass aClass : duplicateConflictDescriptions.keySet()) {
for (final PyMemberInfo<?> pyMemberInfo : duplicateConflictDescriptions.get(aClass)) {
final String message = RefactoringBundle.message("0.already.contains.a.1",
RefactoringUIUtil.getDescription(aClass, false),
RefactoringUIUtil.getDescription(pyMemberInfo.getMember(), false));
result.putValue(aClass, message);
}
}
for (final PyMemberInfo<?> memberUnderConflict : dependenciesConflicts) {
result.putValue(memberUnderConflict.getMember(), PyBundle.message(
"refactoring.will.not.be.accessible",
RefactoringUIUtil.getDescription(memberUnderConflict.getMember(), false)
)
);
}
return result;
}
}
@@ -23,13 +23,14 @@ import com.google.common.collect.Collections2;
import com.google.common.collect.Multimap;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiNamedElement;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.MultiMap;
import com.jetbrains.NotNullPredicate;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import java.util.*;
@@ -74,6 +75,7 @@ public abstract class MembersManager<T extends PyElement> implements Function<T,
/**
* Transforms elements, manager says it could move to appropriate {@link com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo}.
* Types are checked at runtime.
*
* @param pyClass class whose members we want to move
* @param manager manager that should check class and report list of memebers
* @return member infos
@@ -81,9 +83,10 @@ public abstract class MembersManager<T extends PyElement> implements Function<T,
//TODO: Move to TypeSafeMovingStrategy
@NotNull
@SuppressWarnings({"unchecked", "rawtypes"}) //We check type at runtime
private static Collection<PyMemberInfo<PyElement>> transformSafely(@NotNull final PyClass pyClass, @NotNull final MembersManager<?> manager) {
private static Collection<PyMemberInfo<PyElement>> transformSafely(@NotNull final PyClass pyClass,
@NotNull final MembersManager<?> manager) {
final List<PyElement> membersCouldBeMoved = manager.getMembersCouldBeMoved(pyClass);
manager.checkElementTypes((Collection)membersCouldBeMoved);
manager.checkElementTypes((Iterable)membersCouldBeMoved);
return (Collection<PyMemberInfo<PyElement>>)Collections2.transform(membersCouldBeMoved, (Function)manager);
}
@@ -114,13 +117,12 @@ public abstract class MembersManager<T extends PyElement> implements Function<T,
}
/**
* Checks that all elements has allowed type for manager
*
* @param members elements to check against manager
* @param elements elements to check against manager
*/
void checkElementTypes(@NotNull final Collection<T> elements) {
void checkElementTypes(@NotNull final Iterable<T> elements) {
for (final PyElement pyElement : elements) {
Preconditions.checkArgument(myExpectedClass.isAssignableFrom(pyElement.getClass()),
String.format("Manager %s expected %s but got %s", this, myExpectedClass, pyElement));
@@ -159,10 +161,11 @@ public abstract class MembersManager<T extends PyElement> implements Function<T,
}
/**
* Finds member in class. It is here only for backward compatibility with some tests.
* Finds member in class.
* @param pyClass class to find member in
* @param pyElement element to find
* @return member info with element
*/
//TODO: mark deprecated?
@TestOnly
@NotNull
public static PyMemberInfo<PyElement> findMember(@NotNull final PyClass pyClass, @NotNull final PyElement pyElement) {
final PyMemberInfo<PyElement> result = findMember(pyClass, new FindByElement(pyElement));
@@ -245,8 +248,9 @@ public abstract class MembersManager<T extends PyElement> implements Function<T,
/**
* Fetches elements from member info.
*
* @param memberInfos member info to fetch elements from
* @param <T> type of element
* @param <T> type of element
* @return list of elements
*/
@NotNull
@@ -254,6 +258,84 @@ public abstract class MembersManager<T extends PyElement> implements Function<T,
return Collections2.transform(memberInfos, new PyMemberExtractor<T>());
}
/**
* Checks if moving certain member to certain class may lead to conflict (actually that means
* that class already has this member)
*
* @param member member to check
* @param aClass class where this member wanna be moved
* @return true if conflict exists.
*/
public abstract boolean hasConflict(@NotNull T member, @NotNull PyClass aClass);
/**
* Returns all elements this member depends on.
*
* @param classWhereMemberDeclared class where member declared
* @param member member itself
* @param destinationClass where this member would be moved (or null if new class is unknown)
* @return collection of elements this member depends on excluding those, would be available in destination class
*/
@NotNull
public static Collection<? extends PyElement> getAllDependencies(
@NotNull final PyClass classWhereMemberDeclared,
@NotNull final PyElement member,
@Nullable final PyClass destinationClass) {
final PyMemberInfo<PyElement> memberInfo = findMember(classWhereMemberDeclared, member);
final Collection<? extends PyElement> elementsToCheckDependency =
memberInfo.getMembersManager().getElementsToStoreReferences(Collections.singleton(member));
final MultiMap<PyClass, PyElement> dependencies = new MultiMap<PyClass, PyElement>();
final Collection<PyElement> result = new HashSet<PyElement>();
for (final MembersManager<? extends PyElement> manager : MANAGERS) {
for (final PyElement elementToCheckDependency : elementsToCheckDependency) {
dependencies.putAllValues(manager.getDependencies(elementToCheckDependency));
}
}
if (destinationClass != null) {
final Iterator<PyClass> classesIterator = dependencies.keySet().iterator();
while (classesIterator.hasNext()) {
final PyClass memberClass = classesIterator.next();
if (memberClass.equals(destinationClass) ||
ArrayUtil.contains(memberClass, destinationClass.getSuperClasses())) { // IF still would be available
classesIterator.remove();
}
}
}
for (final MembersManager<? extends PyElement> manager : MANAGERS) {
result.addAll(manager.getDependencies(dependencies));
}
result.addAll(dependencies.values());
return result;
}
/**
* Fetch dependencies this element depends on.
* Manager should return them in format "class, where member declared" -- "member itself".
* For example: if parameter is function, and this function uses field "foo" declared in class "bar", then manager (responsible for fields)
* returns "bar" -] reference to "foo"
*
* @param member member to check dependencies for
* @return dependencies
*/
@NotNull
protected abstract MultiMap<PyClass, PyElement> getDependencies(@NotNull PyElement member);
/**
* Get dependencies by members and classes they declared in (obtained from {@link #getDependencies(com.jetbrains.python.psi.PyElement)})
* For example manager, responsible for "extends SomeClass" members may return list of classes
*
* @param usedElements class-to-element dependencies
* @return dependencies
*/
@NotNull
protected abstract Collection<PyElement> getDependencies(@NotNull MultiMap<PyClass, PyElement> usedElements);
private static class PyMemberExtractor<T extends PyElement> implements Function<PyMemberInfo<T>, T> {
@SuppressWarnings("NullableProblems") //IDEA-120100
@Override
@@ -3,7 +3,10 @@ package com.jetbrains.python.refactoring.classes.membersManager;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiReference;
import com.intellij.util.containers.MultiMap;
import com.jetbrains.NotNullPredicate;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.codeInsight.imports.AddImportHelper;
@@ -37,6 +40,26 @@ class MethodsManager extends MembersManager<PyFunction> {
super(PyFunction.class);
}
@Override
public boolean hasConflict(@NotNull final PyFunction member, @NotNull final PyClass aClass) {
return NamePredicate.hasElementWithSameName(member, Arrays.asList(aClass.getMethods()));
}
@NotNull
@Override
protected Collection<PyElement> getDependencies(@NotNull final MultiMap<PyClass, PyElement> usedElements) {
return Collections.emptyList();
}
@NotNull
@Override
protected MultiMap<PyClass, PyElement> getDependencies(@NotNull final PyElement member) {
final MultiMap<PyClass, PyElement> result = new MultiMap<PyClass, PyElement>();
member.accept(new MyPyRecursiveElementVisitor(result));
return result;
}
@NotNull
@Override
protected List<PyElement> getMembersCouldBeMoved(@NotNull final PyClass pyClass) {
@@ -69,7 +92,7 @@ class MethodsManager extends MembersManager<PyFunction> {
final PyFunctionBuilder functionBuilder = PyFunctionBuilder.copySignature(function, DECORATORS_MAY_BE_COPIED_TO_ABSTRACT);
functionBuilder.decorate(PyNames.ABSTRACTMETHOD);
final LanguageLevel level = LanguageLevel.forElement(destClass);
PyClassRefactoringUtil.addMethods(destClass, functionBuilder.buildFunction(destClass.getProject(), level));
PyClassRefactoringUtil.addMethods(destClass, false, functionBuilder.buildFunction(destClass.getProject(), level));
classesToAddMetaAbc.add(destClass);
}
}
@@ -227,4 +250,35 @@ class MethodsManager extends MembersManager<PyFunction> {
return input.isToAbstract() == myAllowAbstractOnly;
}
}
private static class MyPyRecursiveElementVisitor extends PyRecursiveElementVisitor {
@NotNull
private final MultiMap<PyClass, PyElement> myResult;
private MyPyRecursiveElementVisitor(@NotNull final MultiMap<PyClass, PyElement> result) {
myResult = result;
}
@Override
public void visitPyCallExpression(final PyCallExpression node) {
// TODO: refactor, messy code
final PyExpression callee = node.getCallee();
if (callee != null) {
final PsiReference calleeRef = callee.getReference();
if (calleeRef != null) {
final PsiElement calleeDeclaration = calleeRef.resolve();
if (calleeDeclaration instanceof PyFunction) {
final PyFunction calleeFunction = (PyFunction)calleeDeclaration;
final PyClass clazz = calleeFunction.getContainingClass();
if (clazz != null) {
if (PyUtil.isInit(calleeFunction)) {
return; // Init call should not be marked as dependency
}
myResult.putValue(clazz, calleeFunction);
}
}
}
}
}
}
}
@@ -0,0 +1,45 @@
package com.jetbrains.python.refactoring.classes.membersManager;
import com.google.common.base.Optional;
import com.google.common.collect.Iterables;
import com.intellij.navigation.NavigationItem;
import com.jetbrains.NotNullPredicate;
import com.jetbrains.python.psi.PyElement;
import org.jetbrains.annotations.NotNull;
/**
* Finds elements by name
*
* @author Ilya.Kazakevich
*/
class NamePredicate extends NotNullPredicate<PyElement> {
@NotNull
private final String myName;
NamePredicate(@NotNull final String name) {
myName = name;
}
@Override
protected boolean applyNotNull(@NotNull final PyElement input) {
return myName.equals(input.getName());
}
/**
* Checks if collection has {@link com.jetbrains.python.psi.PyElement} with name equals to name of provided element.
* If element has no name -- returns false any way.
* @param needle element to take name from
* @param stock collection elements to search between
* @return true if stock contains element with name equal to needle's name
*/
static boolean hasElementWithSameName(@NotNull final NavigationItem needle, @NotNull final Iterable<? extends PyElement> stock) {
final String name = needle.getName();
if (name != null) {
final Optional<? extends PyElement> optional = Iterables.tryFind(stock, new NamePredicate(name));
return optional.isPresent();
}
return false;
}
}
@@ -16,6 +16,7 @@
package com.jetbrains.python.refactoring.classes.membersManager;
import com.intellij.refactoring.classMembers.MemberInfoBase;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -29,7 +30,7 @@ public class PyMemberInfo<T extends PyElement> extends MemberInfoBase<T> {
private final boolean myCouldBeAbstract;
/**
* TODO: Doc new param
* @param couldBeAbstract if element could be marked as abstract (like abstract method)
* @param member element itself
* @param isStatic is it static or not?
* @param displayName element display name
@@ -72,4 +73,13 @@ public class PyMemberInfo<T extends PyElement> extends MemberInfoBase<T> {
public int hashCode() {
return getMember().hashCode();
}
/**
* Checks if moving this member to some class may create conflict.
* @param destinationClass destination class to check
* @return true if conflict.
*/
public boolean hasConflict(@NotNull final PyClass destinationClass) {
return myMembersManager.hasConflict(myMember, destinationClass);
}
}
@@ -3,6 +3,7 @@ package com.jetbrains.python.refactoring.classes.membersManager;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.util.containers.MultiMap;
import com.jetbrains.NotNullPredicate;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.psi.*;
@@ -10,10 +11,7 @@ import com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil;
import com.jetbrains.python.refactoring.classes.ui.PyClassCellRenderer;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.*;
/**
* Plugin that moves superclasses from one class to another
@@ -29,6 +27,23 @@ class SuperClassesManager extends MembersManager<PyClass> {
}
@NotNull
@Override
protected Collection<PyElement> getDependencies(@NotNull final MultiMap<PyClass, PyElement> usedElements) {
return Lists.<PyElement>newArrayList(usedElements.keySet());
}
@NotNull
protected MultiMap<PyClass, PyElement> getDependencies(@NotNull PyElement member) {
return MultiMap.emptyInstance();
}
@Override
public boolean hasConflict(@NotNull final PyClass member, @NotNull final PyClass aClass) {
final List<PyExpression> expressionList = getExpressionsBySuperClass(aClass, Collections.singleton(member));
return !expressionList.isEmpty();
}
@NotNull
@Override
protected List<PyElement> getMembersCouldBeMoved(@NotNull final PyClass pyClass) {
@@ -44,19 +59,37 @@ class SuperClassesManager extends MembersManager<PyClass> {
PyClassRefactoringUtil.addSuperclasses(from.getProject(), destClass, elements.toArray(new PyClass[members.size()]));
}
final List<PyExpression> expressionsToDelete = getExpressionsBySuperClass(from, elements);
for (final PyExpression expressionToDelete : expressionsToDelete) {
expressionToDelete.delete();
}
return Collections.emptyList(); //Hack: we know that "superclass expression" can't have reference
}
/**
* Returns superclass expressions that are resolved to one or more classes from collection
* @param from class to get superclass expressions from
* @param classes classes to check superclasses against
* @return collection of expressions that are resolved to one or more class from classes param
*/
@NotNull
private static List<PyExpression> getExpressionsBySuperClass(@NotNull final PyClass from, @NotNull final Collection<PyClass> classes) {
final List<PyExpression> expressionsToDelete = new ArrayList<PyExpression>(classes.size());
for (final PyExpression expression : from.getSuperClassExpressions()) {
// Remove all superclass expressions that point to class from memberinfo
if (!(expression instanceof PyQualifiedExpression)) {
continue;
}
final PyReferenceExpression reference = (PyReferenceExpression)expression;
for (final PyClass element : elements) {
for (final PyClass element : classes) {
if (reference.getReference().isReferenceTo(element)) {
expression.delete();
expressionsToDelete.add(expression);
}
}
}
return Collections.emptyList(); //Hack: we know that "superclass expression" can't have reference
return expressionsToDelete;
}
@NotNull
@@ -1,12 +1,17 @@
package com.jetbrains.python.refactoring.classes.membersManager.vp;
import com.intellij.psi.PsiElement;
import com.intellij.refactoring.RefactoringBundle;
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.PyMemberInfoStorage;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
/**
* All presenters that use members inherits this class.
* <strong>Warning</strong>: Do not inherit it directly.
@@ -23,26 +28,40 @@ abstract class MembersBasedPresenterImpl<T extends MembersBasedView<?>> implemen
protected final PyClass myClassUnderRefactoring;
@NotNull
protected final PyMemberInfoStorage myStorage;
/**
* Member model
*/
@NotNull
protected final MemberInfoModel<PyElement, PyMemberInfo<PyElement>> myModel;
/**
* @param view View for presenter
* @param classUnderRefactoring class to be refactored
* @param infoStorage info storage
* @param model Member model (to be used for dependencies checking)
*/
MembersBasedPresenterImpl(@NotNull final T view,
@NotNull final PyClass classUnderRefactoring,
@NotNull final PyMemberInfoStorage infoStorage) {
@NotNull final PyMemberInfoStorage infoStorage,
@NotNull final MemberInfoModel<PyElement, PyMemberInfo<PyElement>> model) {
myView = view;
myClassUnderRefactoring = classUnderRefactoring;
myStorage = infoStorage;
myModel = model;
}
//TODO: Mark Async ?
@Override
public void okClicked() {
final MultiMap<PyClass, PyMemberInfo<?>> conflicts = getConflicts();
final Collection<PyMemberInfo<?>> dependencyConflicts = new ArrayList<PyMemberInfo<?>>();
for (final PyMemberInfo<PyElement> memberInfo : myStorage.getClassMemberInfos(myClassUnderRefactoring)) {
if (myModel.checkForProblems(memberInfo) != MemberInfoModel.OK) {
dependencyConflicts.add(memberInfo);
}
}
final MultiMap<PsiElement, String> conflicts = getConflicts();
if (conflicts.isEmpty() || myView.showConflictsDialog(conflicts)) {
if ((conflicts.isEmpty() && dependencyConflicts.isEmpty()) || myView.showConflictsDialog(conflicts, dependencyConflicts)) {
try {
validateView();
doRefactor();
@@ -75,9 +94,29 @@ abstract class MembersBasedPresenterImpl<T extends MembersBasedView<?>> implemen
abstract void doRefactor();
/**
* Checks if one of destination classes already has members that should be moved, so conflict would take place.
*
* @return map of conflicts (if any)
* @see #getDestClassesToCheckConflicts()
*/
@NotNull
protected abstract MultiMap<PsiElement, String> getConflicts();
protected final MultiMap<PyClass, PyMemberInfo<?>> getConflicts() {
final MultiMap<PyClass, PyMemberInfo<?>> result = new MultiMap<PyClass, PyMemberInfo<?>>();
final Collection<PyMemberInfo<PyElement>> memberInfos = myView.getSelectedMemberInfos();
for (final PyClass destinationClass : getDestClassesToCheckConflicts()) {
for (final PyMemberInfo<PyElement> pyMemberInfo : memberInfos) {
if (pyMemberInfo.hasConflict(destinationClass)) {
result.putValue(destinationClass, pyMemberInfo);
}
}
}
return result;
}
/**
* @return classes where this refactoring will move members. To be used to check for conflicts (if one of target classes already has members)
* @see #getConflicts()
*/
@NotNull
protected abstract Iterable<? extends PyClass> getDestClassesToCheckConflicts();
}
@@ -2,28 +2,33 @@ package com.jetbrains.python.refactoring.classes.membersManager.vp;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.refactoring.classMembers.MemberInfoModel;
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.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 view view for this presenter
* @param classUnderRefactoring class to refactor
* @param infoStorage info storage
* @param infoStorage info storage
* @param model Member model (to be used for dependencies checking)
*/
protected MembersBasedPresenterNoPreviewImpl(@NotNull final T view,
@NotNull final PyClass classUnderRefactoring,
@NotNull final PyMemberInfoStorage infoStorage) {
super(view, classUnderRefactoring, infoStorage);
@NotNull final PyMemberInfoStorage infoStorage,
@NotNull final MemberInfoModel<PyElement, PyMemberInfo<PyElement>> model) {
super(view, classUnderRefactoring, infoStorage, model);
}
@Override
@@ -36,12 +41,7 @@ public abstract class MembersBasedPresenterNoPreviewImpl<T extends MembersBasedV
CommandProcessor.getInstance().executeCommand(myClassUnderRefactoring.getProject(), new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
refactorNoPreview();
}
});
ApplicationManager.getApplication().runWriteAction(new MyRunnableRefactoring());
}
}, getCommandName(), null);
myView.close();
@@ -57,4 +57,11 @@ public abstract class MembersBasedPresenterNoPreviewImpl<T extends MembersBasedV
* Do refactor with out of preview. Implement this method to do refactoring.
*/
protected abstract void refactorNoPreview();
private class MyRunnableRefactoring implements Runnable {
@Override
public void run() {
refactorNoPreview();
}
}
}
@@ -1,8 +1,11 @@
package com.jetbrains.python.refactoring.classes.membersManager.vp;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.refactoring.classMembers.MemberInfoModel;
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.jetbrains.annotations.NotNull;
/**
@@ -20,11 +23,13 @@ public abstract class MembersBasedPresenterWithPreviewImpl<T extends MembersBase
* @param view view for this presenter
* @param classUnderRefactoring class to refactor
* @param infoStorage info storage
* @param model Member model (to be used for dependencies checking)
*/
protected MembersBasedPresenterWithPreviewImpl(@NotNull final T view,
@NotNull final PyClass classUnderRefactoring,
@NotNull final PyMemberInfoStorage infoStorage) {
super(view, classUnderRefactoring, infoStorage);
@NotNull final PyMemberInfoStorage infoStorage,
@NotNull final MemberInfoModel<PyElement, PyMemberInfo<PyElement>> model) {
super(view, classUnderRefactoring, infoStorage, model);
}
@Override
@@ -1,8 +1,8 @@
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.psi.PyClass;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
import org.jetbrains.annotations.NotNull;
@@ -22,10 +22,16 @@ public interface MembersBasedView<C extends MembersViewInitializationInfo> {
/**
* Display conflict dialogs.
*
* @param conflicts conflicts.
* @param duplicatesConflict duplicates conflicts : that means destination class has the same member.
* If member "foo" already exists in class "bar": pass [bar] -] [foo].
* @param dependenciesConflicts dependency conflict: list of elements used by member under refactoring and would not be available
* at new destination. If user wants to move method, that uses field "bar" which would not be available at new class,
* pass [bar] field
* @return true if user's choice is "continue". False if "cancel"
*/
boolean showConflictsDialog(@NotNull MultiMap<PsiElement, String> conflicts);
boolean showConflictsDialog(
@NotNull MultiMap<PyClass, PyMemberInfo<?>> duplicatesConflict,
@NotNull Collection<PyMemberInfo<?>> dependenciesConflicts);
/**
* Displays error message
@@ -2,13 +2,15 @@ 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.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.psi.PsiElement;
import com.intellij.openapi.util.Pair;
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.psi.PyClass;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.refactoring.classes.membersManager.MembersConflictDialog;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
import com.jetbrains.python.refactoring.classes.ui.PyMemberSelectionPanel;
import org.jetbrains.annotations.NotNull;
@@ -72,10 +74,12 @@ public abstract class MembersBasedViewSwingImpl<P extends MembersBasedPresenter,
//TODO: Take this from presenter to prevent inconsistence: now it is possible to create view that supports abstract backed by presenter that does not. And vice versa.
}
@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);
public boolean showConflictsDialog(@NotNull final MultiMap<PyClass, PyMemberInfo<?>> duplicatesConflict,
@NotNull final Collection<PyMemberInfo<?>> dependenciesConflicts) {
Preconditions.checkArgument(!(duplicatesConflict.isEmpty() && dependenciesConflicts.isEmpty()), "Can't show dialog for empty conflicts");
final DialogWrapper conflictsDialog = new MembersConflictDialog(myProject, duplicatesConflict, dependenciesConflicts);
conflictsDialog.show();
return conflictsDialog.isOK();
}
@@ -133,6 +137,7 @@ public abstract class MembersBasedViewSwingImpl<P extends MembersBasedPresenter,
public void initAndShow() {
Preconditions.checkArgument(myConfigured, "Not configured, run 'configure' first!");
init();
myPyMemberSelectionPanel.redraw(); // To display errors for checked member
show();
}
}
}
@@ -1,69 +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.psi.PsiElement;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.util.RefactoringUIUtil;
import com.intellij.util.containers.MultiMap;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
/**
* @author Dennis.Ushakov
*/
final class PyPullUpConflictsUtil {
private PyPullUpConflictsUtil() {
}
@NotNull
static MultiMap<PsiElement, String> checkConflicts(final Collection<PyMemberInfo<PyElement>> infos, @NotNull final PyClass superClass) {
final MultiMap<PsiElement, String> conflictsList = new MultiMap<PsiElement, String>();
for (PyMemberInfo<PyElement> info : infos) {
PsiElement member = info.getMember();
boolean isConflict = false;
//TODO: Delegate to MemeberManagers here
if (member instanceof PyFunction) {
final String name = ((PyFunction)member).getName();
if (name == null) continue;
final PyFunction superClassMethod = superClass.findMethodByName(name, false);
isConflict = superClassMethod != null;
} else if (member instanceof PyClass) {
final PyClass clazz = (PyClass)member;
for (PyClass aClass : superClass.getSuperClasses()) {
if (aClass == clazz) {
conflictsList.putValue(superClass,
RefactoringUIUtil.getDescription(superClass, false) + " already extends " + RefactoringUIUtil.getDescription(clazz, false));
}
}
}
if (isConflict) {
final String message = RefactoringBundle.message("0.already.contains.a.1",
RefactoringUIUtil.getDescription(superClass, false),
RefactoringUIUtil.getDescription(member, false));
conflictsList.putValue(superClass, message);
}
}
return conflictsList;
}
}
@@ -0,0 +1,52 @@
package com.jetbrains.python.refactoring.classes.pullUp;
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;
/**
* Dependencies model for PyPullUp refactoring
* @author Ilya.Kazakevich
*/
class PyPullUpInfoModel extends AbstractUsesDependencyMemberInfoModel<PyElement, PyClass, PyMemberInfo<PyElement>> {
@NotNull
private final PyPullUpView myView;
PyPullUpInfoModel(@NotNull final PyClass classUnderRefactoring,
@NotNull final PyPullUpView view) {
super(classUnderRefactoring, null, false);
myView = view;
}
@Override
public boolean isAbstractEnabled(final PyMemberInfo<PyElement> member) {
return member.isCouldBeAbstract() && isMemberEnabled(member); // TODO: copy paste with other models, get rid of
}
@Override
public int checkForProblems(@NotNull final PyMemberInfo<PyElement> member) {
return member.isChecked() ? OK : super.checkForProblems(member);
}
@Override
protected int doCheck(@NotNull final PyMemberInfo<PyElement> memberInfo, final int problem) {
return problem;
}
@Override
public boolean isMemberEnabled(final PyMemberInfo<PyElement> 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
}
}
return true;
}
}
@@ -17,11 +17,8 @@ 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.containers.MultiMap;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.psi.PyUtil;
@@ -48,7 +45,7 @@ class PyPullUpPresenterImpl extends MembersBasedPresenterWithPreviewImpl<PyPullU
* @param clazz class to refactor
*/
PyPullUpPresenterImpl(@NotNull final PyPullUpView view, @NotNull final PyMemberInfoStorage infoStorage, @NotNull final PyClass clazz) {
super(view, clazz, infoStorage);
super(view, clazz, infoStorage, new PyPullUpInfoModel(clazz, view));
myParents = PyAncestorsUtils.getAncestorsUnderUserControl(clazz);
Preconditions.checkArgument(!myParents.isEmpty(), "No parents found");
}
@@ -57,7 +54,7 @@ class PyPullUpPresenterImpl extends MembersBasedPresenterWithPreviewImpl<PyPullU
@Override
public void launch() {
myView.configure(
new PyPullUpViewInitializationInfo(new PyPullUpInfoModel(), myStorage.getClassMemberInfos(myClassUnderRefactoring), myParents));
new PyPullUpViewInitializationInfo(myModel, myStorage.getClassMemberInfos(myClassUnderRefactoring), myParents));
myView.initAndShow();
}
@@ -93,54 +90,10 @@ class PyPullUpPresenterImpl extends MembersBasedPresenterWithPreviewImpl<PyPullU
}
@Override
@NotNull
public MultiMap<PsiElement, String> getConflicts() {
final Collection<PyMemberInfo<PyElement>> infos = myView.getSelectedMemberInfos();
final PyClass superClass = myView.getSelectedParent();
return PyPullUpConflictsUtil.checkConflicts(infos, superClass);
}
private class PyPullUpInfoModel extends AbstractUsesDependencyMemberInfoModel<PyElement, PyClass, PyMemberInfo<PyElement>> {
PyPullUpInfoModel() {
super(myClassUnderRefactoring, null, false);
}
@Override
public boolean isAbstractEnabled(final PyMemberInfo<PyElement> member) {
return member.isCouldBeAbstract() && isMemberEnabled(member); // TODO: copy paste with other models, get rid of
}
@Override
public int checkForProblems(@NotNull final PyMemberInfo<PyElement> member) {
return member.isChecked() ? OK : super.checkForProblems(member);
}
@Override
protected int doCheck(@NotNull final PyMemberInfo<PyElement> memberInfo, final int problem) {
if (problem == ERROR && memberInfo.isStatic()) {
return WARNING;
}
return problem;
}
@Override
public boolean isMemberEnabled(final PyMemberInfo<PyElement> 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;
}
@Override
protected Iterable<? extends PyClass> getDestClassesToCheckConflicts() {
return Collections.singletonList(myView.getSelectedParent());
}
}
@@ -1,115 +0,0 @@
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.refactoring.classes.pushDown;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Comparing;
import com.intellij.psi.PsiElement;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.util.RefactoringUIUtil;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* @author Dennis.Ushakov
*/
class PyPushDownConflicts {
private static final Logger LOG = Logger.getInstance(PyPushDownProcessor.class.getName());
private final PyClass myClass;
private final Collection<PyMemberInfo<PyElement>> myMembers;
private final MultiMap<PsiElement, String> myConflicts;
public PyPushDownConflicts(final PyClass clazz, final Collection<PyMemberInfo<PyElement>> members) {
myClass = clazz;
myMembers = members;
myConflicts = new MultiMap<PsiElement, String>();
}
MultiMap<PsiElement, String> getConflicts() {
return myConflicts;
}
void checkTargetClassConflicts(PyClass clazz) {
checkPlacementConflicts(clazz);
}
private void checkPlacementConflicts(PyClass clazz) {
for (PyMemberInfo<PyElement> member : myMembers) {
final PyElement element = member.getMember();
if (element instanceof PyFunction) {
for (PyFunction function : clazz.getMethods()) {
if (Comparing.strEqual(function.getName(), element.getName())) {
final String message = RefactoringBundle.message("0.is.already.overridden.in.1",
RefactoringUIUtil.getDescription(element, false),
RefactoringUIUtil.getDescription(clazz, false));
myConflicts.putValue(element, message);
}
}
} else if (element instanceof PyClass) {
} else LOG.error("unmatched member class " + clazz.getClass());
}
}
public void checkSourceClassConflicts() {
final List<PyElement> elements = ContainerUtil.map(myMembers, new Function<PyMemberInfo<PyElement>, PyElement>() {
public PyElement fun(PyMemberInfo<PyElement> pyMemberInfo) {
return pyMemberInfo.getMember();
}
});
for (PyFunction pyFunction : myClass.getMethods()) {
final UsedMembersCollector collector = new UsedMembersCollector(elements);
pyFunction.accept(collector);
final List<PyElement> conflicts = collector.getCollection();
for (PyElement conflict : conflicts) {
final String message = RefactoringBundle.message("0.uses.1.which.is.pushed.down",
RefactoringUIUtil.getDescription(pyFunction, false),
RefactoringUIUtil.getDescription(conflict, false));
myConflicts.putValue(pyFunction, message);
}
}
}
private static class UsedMembersCollector extends PyRecursiveElementVisitor {
private final List<PyElement> myCollection = new ArrayList<PyElement>();
private final Collection<PyElement> myMovedMembers;
private UsedMembersCollector(Collection<PyElement> movedMembers) {
myMovedMembers = movedMembers;
}
@Override
public void visitPyCallExpression(PyCallExpression node) {
final Callable function = node.resolveCalleeFunction(PyResolveContext.noImplicits());
if (function != null && myMovedMembers.contains(function)) {
myCollection.add(function);
}
}
public List<PyElement> getCollection() {
return myCollection;
}
}
}
@@ -1,10 +1,8 @@
package com.jetbrains.python.refactoring.classes.pushDown;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.refactoring.classMembers.UsedByDependencyMemberInfoModel;
import com.intellij.util.containers.MultiMap;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.psi.PyUtil;
@@ -25,7 +23,7 @@ public class PyPushDownPresenterImpl extends MembersBasedPresenterWithPreviewImp
@NotNull final PyPushDownView view,
@NotNull final PyClass classUnderRefactoring,
@NotNull final PyMemberInfoStorage infoStorage) {
super(view, classUnderRefactoring, infoStorage);
super(view, classUnderRefactoring, infoStorage, new UsedByDependencyMemberInfoModel<PyElement, PyClass, PyMemberInfo<PyElement>>(classUnderRefactoring));
myProject = project;
}
@@ -37,16 +35,14 @@ public class PyPushDownPresenterImpl extends MembersBasedPresenterWithPreviewImp
@NotNull
@Override
protected MultiMap<PsiElement, String> getConflicts() {
return new PyPushDownConflicts(myClassUnderRefactoring, myStorage.getClassMemberInfos(myClassUnderRefactoring)).getConflicts();
protected Iterable<? extends PyClass> getDestClassesToCheckConflicts() {
return PyPushDownProcessor.getInheritors(myClassUnderRefactoring);
}
@Override
public void launch() {
UsedByDependencyMemberInfoModel<PyElement, PyClass, PyMemberInfo<PyElement>> model =
new UsedByDependencyMemberInfoModel<PyElement, PyClass, PyMemberInfo<PyElement>>(myClassUnderRefactoring);
myView
.configure(new MembersViewInitializationInfo(model, PyUtil.filterOutObject(myStorage.getClassMemberInfos(myClassUnderRefactoring))));
.configure(new MembersViewInitializationInfo(myModel, PyUtil.filterOutObject(myStorage.getClassMemberInfos(myClassUnderRefactoring))));
myView.initAndShow();
}
}
@@ -44,10 +44,19 @@ public class PyPushDownProcessor extends PyMembersRefactoringBaseProcessor {
@NotNull
private static PyClass[] getChildren(@NotNull final PyClass from) {
Collection<PyClass> all = PyClassInheritorsSearch.search(from, false).findAll();
final Collection<PyClass> all = getInheritors(from);
return all.toArray(new PyClass[all.size()]);
}
/**
* @param from class to check for inheritors
* @return inheritors of class
*/
@NotNull
static Collection<PyClass> getInheritors(@NotNull final PyClass from) {
return PyClassInheritorsSearch.search(from, false).findAll();
}
public String getProcessedElementsHeader() {
return HEADER;
@@ -104,6 +104,6 @@ public class PyMemberSelectionPanel extends JPanel {
* Redraws table. Call it when some new data is available.
*/
public void redraw() {
myTable.fireExternalDataChange();
myTable.redraw();
}
}
@@ -59,4 +59,24 @@ class Bar(object):
class Foo(Bar):
def __init__(self):
self.foo = 12
self.foo = 12
class ParentWithConflicts(Bar):
CLASS_FIELD = 42
def __init__(self):
self.instance_field = 12
def my_func(self):
pass
class ChildWithConflicts(ParentWithConflicts, Bar): # Bar -> conflict
CLASS_FIELD = 42 # Conflict
GOOD_FIELD = 32
def __init__(self):
self.instance_field = 12 # Conflict
self.good_instance_field = "egg"
def my_func(self): # Conflict
pass
@@ -0,0 +1,39 @@
class EmptyParent:pass
class SomeParent:
PARENT_CLASS_FIELD = 42
def __init__(self):
self.parent_instance_field = "egg"
def parent_func(self):
pass
class ChildWithDependencies(SomeParent, EmptyParent):
CLASS_FIELD_FOO = 42
CLASS_FIELD_DEPENDS_ON_CLASS_FIELD_FOO = CLASS_FIELD_FOO
CLASS_FIELD_DEPENDS_ON_PARENT_FIELD = SomeParent.PARENT_CLASS_FIELD
def __init__(self):
SomeParent.__init__(self)
self.instance_field_bar = 42
self.depends_on_instance_field_bar = self.instance_field_bar
self.depends_on_class_field_foo = ChildWithDependencies.CLASS_FIELD_FOO
def normal_method(self):
pass
def method_depends_on_parent_method(self):
self.parent_func()
pass
def method_depends_on_parent_field(self):
i = self.parent_instance_field
pass
def method_depends_on_normal_method(self):
self.normal_method()
def method_depends_on_instance_field_bar(self):
eggs = self.instance_field_bar
@@ -41,6 +41,7 @@ import com.jetbrains.python.PythonHelpersLocator;
import com.jetbrains.python.PythonMockSdk;
import com.jetbrains.python.PythonTestUtil;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyFile;
import com.jetbrains.python.psi.impl.PyFileImpl;
import com.jetbrains.python.psi.impl.PythonLanguageLevelPusher;
@@ -130,6 +131,15 @@ public abstract class PyTestCase extends UsefulTestCase {
assertNull(PARSED_ERROR_MSG, ((PyFileImpl)file).getTreeElement());
}
/**
* @param name
* @return class by its name from file
*/
@NotNull
protected PyClass getClassByName(@NotNull final String name) {
return myFixture.findElementByText("class " + name, PyClass.class);
}
protected static class PyLightProjectDescriptor implements LightProjectDescriptor {
private final String myPythonVersion;
@@ -0,0 +1,25 @@
package com.jetbrains.python.refactoring.classes;
import com.google.common.base.Function;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
import org.jetbrains.annotations.NotNull;
/**
* Transforms {@link com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo} to its display names
* @author Ilya.Kazakevich
*/
public class NameTransformer implements Function<PyMemberInfo<? extends PyElement>, String> {
/**
* To be used instead of creation
*/
public static final NameTransformer INSTANCE = new NameTransformer();
private NameTransformer() {
}
@Override
public String apply(@NotNull final PyMemberInfo<? extends PyElement> input) {
return input.getDisplayName();
}
}
@@ -6,6 +6,7 @@ import com.jetbrains.python.fixtures.PyTestCase;
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.MembersBasedPresenter;
import com.jetbrains.python.refactoring.classes.membersManager.vp.MembersBasedView;
import com.jetbrains.python.refactoring.classes.membersManager.vp.MembersViewInitializationInfo;
import org.easymock.Capture;
@@ -83,6 +84,13 @@ public abstract class PyRefactoringPresenterTestCase<C extends MembersViewInitia
myMocksControl = new MocksControl(MocksControl.MockType.NICE);
myView = myMocksControl.createMock(myViewClass);
configureMockCapture();
}
/**
* Configures view to capture config info
*/
private void configureMockCapture() {
myViewConfigCapture = new Capture<C>();
myView.configure(EasyMock.capture(myViewConfigCapture));
@@ -90,15 +98,6 @@ public abstract class PyRefactoringPresenterTestCase<C extends MembersViewInitia
}
/**
* @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
*/
@@ -117,4 +116,19 @@ public abstract class PyRefactoringPresenterTestCase<C extends MembersViewInitia
return input.getName();
}
}
/**
* Returns member infos presenter wants to display
* @param presenter presenter to check
* @return collection of member infos
*/
@NotNull
protected Collection<PyMemberInfo<PyElement>> getMemberInfos(@NotNull MembersBasedPresenter presenter) {
myMocksControl.replay();
presenter.launch();
final Collection<PyMemberInfo<PyElement>> result = myViewConfigCapture.getValue().getMemberInfos();
myMocksControl.reset();
configureMockCapture();
return result;
}
}
@@ -53,7 +53,7 @@ public class PyExtractSuperclassPresenterTest
*
* @param py3k if py 3?
*/
private void ensureStaticNoObject( final boolean py3k) {
private void ensureStaticNoObject(final boolean py3k) {
final Collection<PyPresenterTestMemberEntry> members = launchAndGetMembers("StaticOnly");
final Matcher<Iterable<? extends PyPresenterTestMemberEntry>> matcher =
@@ -77,7 +77,6 @@ public class PyExtractSuperclassPresenterTest
sut.okClicked();
Assert.assertTrue("No error displayed empty list of selected members", errorMessageCapture.hasCaptured());
}
/**
@@ -101,7 +100,6 @@ public class PyExtractSuperclassPresenterTest
sut.okClicked();
Assert.assertTrue("No error displayed for invalid class name", errorMessageCapture.hasCaptured());
}
/**
@@ -0,0 +1,135 @@
package com.jetbrains.python.refactoring.classes.pullUp;
import com.google.common.collect.Iterables;
import com.intellij.refactoring.classMembers.MemberInfoModel;
import com.jetbrains.NotNullPredicate;
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.internal.MocksControl;
import org.hamcrest.Matchers;
import org.jetbrains.annotations.NotNull;
import org.junit.Assert;
import java.util.ArrayList;
import java.util.List;
/**
* Tests dependencies management for PyPullUp refactoring
*
* @author Ilya.Kazakevich
*/
public class PyPullUpInfoModelTest extends PyTestCase {
private PyPullUpInfoModel mySut;
private List<PyMemberInfo<PyElement>> myMemberInfos;
@Override
public void setUp() throws Exception {
super.setUp();
myFixture.configureByFile("/refactoring/pullup/pyPullUpInfoModel.py");
final PyClass childClass = getClassByName("ChildWithDependencies");
final PyClass parentClass = getClassByName("SomeParent");
mySut = new PyPullUpInfoModel(childClass, new MocksControl(MocksControl.MockType.NICE).createMock(PyPullUpView.class));
mySut.setSuperClass(parentClass);
myMemberInfos = new PyMemberInfoStorage(childClass).getClassMemberInfos(childClass);
}
/**
* Checks class field depends on class field
*/
public void testClassMemberDependencies() throws Exception {
checkMembers("CLASS_FIELD_DEPENDS_ON_CLASS_FIELD_FOO");
Assert.assertThat("Class member dependencies failed", getErrorMemberNames(), Matchers.containsInAnyOrder("CLASS_FIELD_FOO"));
}
/**
* Checks instance field depends on class field
*/
public void testInstanceMemberDependencies() throws Exception {
checkMembers("self.depends_on_class_field_foo");
Assert.assertThat("Instance member dependencies failed", getErrorMemberNames(), Matchers.containsInAnyOrder("CLASS_FIELD_FOO"));
}
/**
* Checks method depends on another class field
*/
public void testMethodMemberDependencies() throws Exception {
checkMembers("method_depends_on_normal_method(self)");
Assert.assertThat("Method dependencies failed", getErrorMemberNames(), Matchers.containsInAnyOrder("normal_method(self)"));
}
/**
* Checks method depends on method
*/
public void testMethodOnInstanceMemberDependencies() throws Exception {
checkMembers("method_depends_on_instance_field_bar(self)");
Assert.assertThat("Instance on member dependencies failed", getErrorMemberNames(), Matchers.containsInAnyOrder("self.instance_field_bar"));
}
/**
* All dependencies are met: new (destination) class has all of them
*/
public void testParentDependenciesOk() throws Exception {
checkMembers("CLASS_FIELD_DEPENDS_ON_PARENT_FIELD",
"method_depends_on_parent_method(self)",
"method_depends_on_parent_field(self)");
Assert.assertThat("Dependence check false positive: parent has all required members", getErrorMemberNames(), Matchers.empty());
}
/**
* New (destination) class has no members, required by member under refactoring.
* Error should be displayed.
*/
public void testNoParentDependenciesOk() throws Exception {
mySut.setSuperClass(getClassByName("EmptyParent"));
checkMembers("CLASS_FIELD_DEPENDS_ON_PARENT_FIELD",
"method_depends_on_parent_method(self)",
"method_depends_on_parent_field(self)");
Assert.assertThat("Dependence check false positive: parent has all required members", getErrorMemberNames(), Matchers.contains("extends SomeParent"));
}
/**
* @return names of error members (displayed with red)
*/
@NotNull
private List<String> getErrorMemberNames() {
final List<String> result = new ArrayList<String>();
for (final PyMemberInfo<PyElement> info : myMemberInfos) {
if (mySut.checkForProblems(info) != MemberInfoModel.OK) {
result.add(info.getDisplayName());
}
}
return result;
}
/**
* Marks members to be moved (sets checkbox on them)
* @param memberNames names of members to check
*/
private void checkMembers(@NotNull final String... memberNames) {
for (final String memberName : memberNames) {
Iterables.find(myMemberInfos, new NamePredicate(memberName)).setChecked(true);
}
mySut.memberInfoChanged(myMemberInfos);
}
private static class NamePredicate extends NotNullPredicate<PyMemberInfo<?>> {
@NotNull
private final String myNameToSearch;
private NamePredicate(@NotNull final String nameToSearch) {
myNameToSearch = nameToSearch;
}
@Override
protected boolean applyNotNull(@NotNull final PyMemberInfo<?> input) {
return input.getDisplayName().equals(myNameToSearch);
}
}
}
@@ -1,11 +1,16 @@
package com.jetbrains.python.refactoring.classes.pullUp;
import com.google.common.collect.Collections2;
import com.intellij.util.containers.MultiMap;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.refactoring.classes.NameTransformer;
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;
@@ -49,6 +54,35 @@ public class PyPullUpPresenterTest extends PyRefactoringPresenterTestCase<PyPull
ensureNoMembers("NoParentsAllowed");
}
/**
* Ensures that presenter displays conflicts if destination class already has that members
*/
public void testConflicts() throws Exception {
final PyPullUpPresenterImpl sut = configureByClass("ChildWithConflicts");
final Collection<PyMemberInfo<PyElement>> infos = getMemberInfos(sut);
final Capture<MultiMap<PyClass, PyMemberInfo<?>>> conflictCapture = new Capture<MultiMap<PyClass, PyMemberInfo<?>>>();
EasyMock.expect(myView.showConflictsDialog(EasyMock.capture(conflictCapture), EasyMock.<Collection<PyMemberInfo<?>>>anyObject())).andReturn(false).anyTimes();
EasyMock.expect(myView.getSelectedMemberInfos()).andReturn(infos).anyTimes();
final PyClass parent = getClassByName("ParentWithConflicts");
EasyMock.expect(myView.getSelectedParent()).andReturn(parent).anyTimes();
myMocksControl.replay();
sut.okClicked();
final MultiMap<PyClass, PyMemberInfo<?>> conflictMap = conflictCapture.getValue();
Assert.assertTrue("No conflicts found, while it should", conflictMap.containsKey(parent));
final Collection<String> conflictedMemberNames = Collections2.transform(conflictMap.get(parent), NameTransformer.INSTANCE);
Assert.assertThat("Failed to find right conflicts", conflictedMemberNames, Matchers.containsInAnyOrder(
"extends Bar",
"CLASS_FIELD",
"self.instance_field",
"my_func(self)",
"__init__(self)"
));
}
/**
* Checks that refactoring does not work for classes with out of members
*/
@@ -104,7 +138,7 @@ public class PyPullUpPresenterTest extends PyRefactoringPresenterTestCase<PyPull
new PyPresenterTestMemberEntry("CLASS_FIELD", true, true, false),
new PyPresenterTestMemberEntry("__init__(self)", true, false, false),
new PyPresenterTestMemberEntry("extends SubParent1", false, false, false),
new PyPresenterTestMemberEntry("foo(self)", false, false, false),
new PyPresenterTestMemberEntry("foo(self)", true, false, true),
new PyPresenterTestMemberEntry("bar(self)", true, false, true),
new PyPresenterTestMemberEntry("static_1(cls)", true, true, py3K),
new PyPresenterTestMemberEntry("static_2()", true, true, py3K),