diff --git a/java/java-impl/src/com/intellij/codeInsight/template/postfix/settings/PostfixTemplatesConfigurable.java b/java/java-impl/src/com/intellij/codeInsight/template/postfix/settings/PostfixTemplatesConfigurable.java index 78e0475c8c3b..7238c7ab8dcf 100644 --- a/java/java-impl/src/com/intellij/codeInsight/template/postfix/settings/PostfixTemplatesConfigurable.java +++ b/java/java-impl/src/com/intellij/codeInsight/template/postfix/settings/PostfixTemplatesConfigurable.java @@ -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) { diff --git a/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/InstanceofExpressionPostfixTemplate.java b/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/InstanceofExpressionPostfixTemplate.java index aac8bf9bd27d..8c5b58e79123 100644 --- a/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/InstanceofExpressionPostfixTemplate.java +++ b/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/InstanceofExpressionPostfixTemplate.java @@ -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 || diff --git a/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/NotExpressionPostfixTemplate.java b/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/NotExpressionPostfixTemplate.java index 14c723f1fca0..9900c0a4c57d 100644 --- a/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/NotExpressionPostfixTemplate.java +++ b/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/NotExpressionPostfixTemplate.java @@ -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 BOOLEAN_TYPE_CONDITION = new Condition() { + @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 getTypeCondition() { - return new Condition() { - @Override - public boolean value(PsiExpression expression) { - return PostfixTemplatesUtils.isBoolean(expression.getType()); - } - }; + return BOOLEAN_TYPE_CONDITION; } } \ No newline at end of file diff --git a/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/NullCheckPostfixTemplate.java b/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/NullCheckPostfixTemplate.java index 77126b8df9fb..6a9b6769e548 100644 --- a/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/NullCheckPostfixTemplate.java +++ b/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/NullCheckPostfixTemplate.java @@ -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(); diff --git a/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/ThrowExceptionPostfixTemplate.java b/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/ThrowExceptionPostfixTemplate.java index 7c2017ffe290..b1bb654d6dff 100644 --- a/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/ThrowExceptionPostfixTemplate.java +++ b/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/ThrowExceptionPostfixTemplate.java @@ -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 diff --git a/java/java-impl/src/com/intellij/codeInsight/template/postfix/util/PostfixTemplatesUtils.java b/java/java-impl/src/com/intellij/codeInsight/template/postfix/util/PostfixTemplatesUtils.java index 923dfdc4e0ab..09cf4fb19112 100644 --- a/java/java-impl/src/com/intellij/codeInsight/template/postfix/util/PostfixTemplatesUtils.java +++ b/java/java-impl/src/com/intellij/codeInsight/template/postfix/util/PostfixTemplatesUtils.java @@ -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; diff --git a/java/java-tests/testData/codeInsight/template/postfix/templates/instanceof/primitive.java b/java/java-tests/testData/codeInsight/template/postfix/templates/instanceof/primitive.java new file mode 100644 index 000000000000..2e1d76a54481 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/postfix/templates/instanceof/primitive.java @@ -0,0 +1,5 @@ +public class Foo { + void m(int o) { + o.instanceof + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/postfix/templates/instanceof/primitive_after.java b/java/java-tests/testData/codeInsight/template/postfix/templates/instanceof/primitive_after.java new file mode 100644 index 000000000000..29fc33cc5109 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/postfix/templates/instanceof/primitive_after.java @@ -0,0 +1,5 @@ +public class Foo { + void m(int o) { + o.instanceof + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/postfix/templates/notnull/primitive.java b/java/java-tests/testData/codeInsight/template/postfix/templates/notnull/primitive.java new file mode 100644 index 000000000000..1d9e1eab5651 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/postfix/templates/notnull/primitive.java @@ -0,0 +1,5 @@ +public class Foo { + void m(int o) { + o.notnull + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/postfix/templates/notnull/primitive_after.java b/java/java-tests/testData/codeInsight/template/postfix/templates/notnull/primitive_after.java new file mode 100644 index 000000000000..0f3e8ac4a558 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/postfix/templates/notnull/primitive_after.java @@ -0,0 +1,5 @@ +public class Foo { + void m(int o) { + o.notnull + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/postfix/templates/null/primitive.java b/java/java-tests/testData/codeInsight/template/postfix/templates/null/primitive.java new file mode 100644 index 000000000000..4496ceed194f --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/postfix/templates/null/primitive.java @@ -0,0 +1,5 @@ +public class Foo { + void m(int o) { + o.null + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/postfix/templates/null/primitive_after.java b/java/java-tests/testData/codeInsight/template/postfix/templates/null/primitive_after.java new file mode 100644 index 000000000000..1084e03d19f0 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/postfix/templates/null/primitive_after.java @@ -0,0 +1,5 @@ +public class Foo { + void m(int o) { + o.null + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/postfix/templates/throw/notThrowable.java b/java/java-tests/testData/codeInsight/template/postfix/templates/throw/notThrowable.java new file mode 100644 index 000000000000..edfbaabf3674 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/postfix/templates/throw/notThrowable.java @@ -0,0 +1,5 @@ +public class Foo { + void m(Object o) { + o.throw + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/postfix/templates/throw/notThrowable_after.java b/java/java-tests/testData/codeInsight/template/postfix/templates/throw/notThrowable_after.java new file mode 100644 index 000000000000..900e720607eb --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/postfix/templates/throw/notThrowable_after.java @@ -0,0 +1,5 @@ +public class Foo { + void m(Object o) { + throw o; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/postfix/templates/throw/simple.java b/java/java-tests/testData/codeInsight/template/postfix/templates/throw/simple.java index edfbaabf3674..2d183ba79714 100644 --- a/java/java-tests/testData/codeInsight/template/postfix/templates/throw/simple.java +++ b/java/java-tests/testData/codeInsight/template/postfix/templates/throw/simple.java @@ -1,5 +1,7 @@ +import java.lang.RuntimeException; + public class Foo { - void m(Object o) { - o.throw + void m() { + new RuntimeException("error").throw } } \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/postfix/templates/throw/simple_after.java b/java/java-tests/testData/codeInsight/template/postfix/templates/throw/simple_after.java index 900e720607eb..a0219e42ffc7 100644 --- a/java/java-tests/testData/codeInsight/template/postfix/templates/throw/simple_after.java +++ b/java/java-tests/testData/codeInsight/template/postfix/templates/throw/simple_after.java @@ -1,5 +1,7 @@ +import java.lang.RuntimeException; + public class Foo { - void m(Object o) { - throw o; + void m() { + throw new RuntimeException("error"); } } \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/InstanceofPostfixTemplateTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/InstanceofPostfixTemplateTest.java index f5e363c4d3d0..090447d6e884 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/InstanceofPostfixTemplateTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/InstanceofPostfixTemplateTest.java @@ -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()); diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/NotNullPostfixTemplateTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/NotNullPostfixTemplateTest.java index ab3b93cd8625..ca08b6867e6e 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/NotNullPostfixTemplateTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/NotNullPostfixTemplateTest.java @@ -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(); } } \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/NullPostfixTemplateTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/NullPostfixTemplateTest.java index 7f501b657e19..9c3fce6acaff 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/NullPostfixTemplateTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/template/postfix/templates/NullPostfixTemplateTest.java @@ -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(); } } \ No newline at end of file diff --git a/platform/lang-api/src/com/intellij/refactoring/classMembers/DependentMembersCollectorBase.java b/platform/lang-api/src/com/intellij/refactoring/classMembers/DependentMembersCollectorBase.java index ec0a198fa2e4..1b774124d681 100644 --- a/platform/lang-api/src/com/intellij/refactoring/classMembers/DependentMembersCollectorBase.java +++ b/platform/lang-api/src/com/intellij/refactoring/classMembers/DependentMembersCollectorBase.java @@ -26,8 +26,8 @@ import java.util.Set; */ public abstract class DependentMembersCollectorBase { protected final HashSet myCollection = new HashSet(); - private final C myClass; - private final C mySuperClass; + protected final C myClass; + protected final C mySuperClass; public DependentMembersCollectorBase(C clazz, C superClass) { myClass = clazz; diff --git a/platform/lang-impl/src/com/intellij/refactoring/rename/AutomaticRenamingDialog.form b/platform/lang-impl/src/com/intellij/refactoring/rename/AutomaticRenamingDialog.form index 525be912c9b2..b17b2e48e36b 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/rename/AutomaticRenamingDialog.form +++ b/platform/lang-impl/src/com/intellij/refactoring/rename/AutomaticRenamingDialog.form @@ -3,7 +3,7 @@ - + @@ -28,8 +28,7 @@ - - + @@ -38,7 +37,8 @@ - + + @@ -49,27 +49,34 @@ - + - + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/platform/lang-impl/src/com/intellij/refactoring/ui/AbstractMemberSelectionTable.java b/platform/lang-impl/src/com/intellij/refactoring/ui/AbstractMemberSelectionTable.java index 7d8b57b1266f..865df79f2944 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/ui/AbstractMemberSelectionTable.java +++ b/platform/lang-impl/src/com/intellij/refactoring/ui/AbstractMemberSelectionTable.java @@ -126,6 +126,14 @@ public abstract class AbstractMemberSelectionTable memberInfos) { myMemberInfos = new ArrayList(memberInfos); fireMemberInfoChange(memberInfos); @@ -313,9 +321,13 @@ public abstract class AbstractMemberSelectionTable changed = Collections.singletonList(myTable.myMemberInfos.get(rowIndex)); + redraw(changed); +// fireTableRowsUpdated(rowIndex, rowIndex); + } + + public void redraw(Collection changed) { myTable.fireMemberInfoChange(changed); fireTableDataChanged(); -// fireTableRowsUpdated(rowIndex, rowIndex); } } diff --git a/platform/platform-api/src/com/intellij/openapi/editor/actionSystem/EditorActionHandler.java b/platform/platform-api/src/com/intellij/openapi/editor/actionSystem/EditorActionHandler.java index 9633d2d3875b..d4b6b74eb4b2 100644 --- a/platform/platform-api/src/com/intellij/openapi/editor/actionSystem/EditorActionHandler.java +++ b/platform/platform-api/src/com/intellij/openapi/editor/actionSystem/EditorActionHandler.java @@ -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 diff --git a/platform/platform-resources/src/brokenPlugins.txt b/platform/platform-resources/src/brokenPlugins.txt index 4d28956dfd5d..3064ab061e42 100644 --- a/platform/platform-resources/src/brokenPlugins.txt +++ b/platform/platform-resources/src/brokenPlugins.txt @@ -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 \ No newline at end of file +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 \ No newline at end of file diff --git a/platform/util/src/com/intellij/util/text/SemVer.java b/platform/util/src/com/intellij/util/text/SemVer.java index c0ca07f1c15e..5c5cdbfe849c 100644 --- a/platform/util/src/com/intellij/util/text/SemVer.java +++ b/platform/util/src/com/intellij/util/text/SemVer.java @@ -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; } diff --git a/platform/util/testSrc/com/intellij/util/text/SemVerTest.java b/platform/util/testSrc/com/intellij/util/text/SemVerTest.java index 19839b424af5..5edd5d925e86 100644 --- a/platform/util/testSrc/com/intellij/util/text/SemVerTest.java +++ b/platform/util/testSrc/com/intellij/util/text/SemVerTest.java @@ -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); } } diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index 7bc546972af3..6581eb0e4a5b 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -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.
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 diff --git a/python/src/com/jetbrains/python/refactoring/classes/PyClassRefactoringUtil.java b/python/src/com/jetbrains/python/refactoring/classes/PyClassRefactoringUtil.java index 103366182f28..50ebb54a6919 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/PyClassRefactoringUtil.java +++ b/python/src/com/jetbrains/python/refactoring/classes/PyClassRefactoringUtil.java @@ -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 addMethods(@NotNull final PyClass destination, @NotNull final PyFunction... methods) { + public static List addMethods(@NotNull final PyClass destination, final boolean skipIfExist, @NotNull final PyFunction... methods) { final PyStatementList destStatementList = destination.getStatementList(); - final List newlyCreatedMethods = new ArrayList(methods.length); + final List result = new ArrayList(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; } /** diff --git a/python/src/com/jetbrains/python/refactoring/classes/PyDependentMembersCollector.java b/python/src/com/jetbrains/python/refactoring/classes/PyDependentMembersCollector.java index 5efb3525d5ba..c47c474fba5c 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/PyDependentMembersCollector.java +++ b/python/src/com/jetbrains/python/refactoring/classes/PyDependentMembersCollector.java @@ -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

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 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> 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 getDestClassesToCheckConflicts() { + return Collections.emptyList(); // No conflict can take place in newly created classes + } } diff --git a/python/src/com/jetbrains/python/refactoring/classes/membersManager/ClassFieldsManager.java b/python/src/com/jetbrains/python/refactoring/classes/membersManager/ClassFieldsManager.java index b6faa3aa3cb5..42444df279c3 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/membersManager/ClassFieldsManager.java +++ b/python/src/com/jetbrains/python/refactoring/classes/membersManager/ClassFieldsManager.java @@ -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 moveAssignments(@NotNull final PyClass from, - @NotNull final Collection statements, - @NotNull final PyClass... to) { + @NotNull final Collection statements, + @NotNull final PyClass... to) { //TODO: Copy/paste with InstanceFieldsManager. Move to parent? final List result = new ArrayList(); for (final PyClass destClass : to) { diff --git a/python/src/com/jetbrains/python/refactoring/classes/membersManager/FieldsManager.java b/python/src/com/jetbrains/python/refactoring/classes/membersManager/FieldsManager.java index d27ec8e4e8e9..cb15b2b561a4 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/membersManager/FieldsManager.java +++ b/python/src/com/jetbrains/python/refactoring/classes/membersManager/FieldsManager.java @@ -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 { myStatic = isStatic; } + + @NotNull + @Override + protected Collection getDependencies(@NotNull final MultiMap usedElements) { + return Collections.emptyList(); + } + + @NotNull + @Override + protected MultiMap getDependencies(@NotNull final PyElement member) { + final MultiMap result = new MultiMap(); + member.accept(new MyPyRecursiveElementVisitor(result)); + return result; + } + @Override protected Collection getElementsToStoreReferences(@NotNull final Collection elements) { // We need to save references from assignments @@ -50,7 +64,8 @@ abstract class FieldsManager extends MembersManager { protected Collection moveMembers(@NotNull final PyClass from, @NotNull final Collection> 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 { return PsiTreeUtil.getParentOfType(input, PyAssignmentStatement.class); } } + + /** + * Fetches field declarations + */ + private static class MyPyRecursiveElementVisitor extends PyRecursiveElementVisitor { + @NotNull + private final MultiMap myResult; + + private MyPyRecursiveElementVisitor(@NotNull final MultiMap 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); + } + } + } + } } diff --git a/python/src/com/jetbrains/python/refactoring/classes/membersManager/InstanceFieldsManager.java b/python/src/com/jetbrains/python/refactoring/classes/membersManager/InstanceFieldsManager.java index ba6cb0c2f497..7ae68cd6b646 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/membersManager/InstanceFieldsManager.java +++ b/python/src/com/jetbrains/python/refactoring/classes/membersManager/InstanceFieldsManager.java @@ -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 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 diff --git a/python/src/com/jetbrains/python/refactoring/classes/membersManager/MembersConflictDialog.java b/python/src/com/jetbrains/python/refactoring/classes/membersManager/MembersConflictDialog.java new file mode 100644 index 000000000000..b288f955e228 --- /dev/null +++ b/python/src/com/jetbrains/python/refactoring/classes/membersManager/MembersConflictDialog.java @@ -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> duplicatesConflict, + @NotNull final Collection> dependenciesConflicts) { + super(project, convertDescription(duplicatesConflict, dependenciesConflicts), null, true, false); + } + + @NotNull + private static MultiMap convertDescription( + @NotNull final MultiMap> duplicateConflictDescriptions, + @NotNull final Collection> dependenciesConflicts) { + final MultiMap result = new MultiMap(); + 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; + } +} diff --git a/python/src/com/jetbrains/python/refactoring/classes/membersManager/MembersManager.java b/python/src/com/jetbrains/python/refactoring/classes/membersManager/MembersManager.java index 6be3f9d3a2b9..ff4d20d68c75 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/membersManager/MembersManager.java +++ b/python/src/com/jetbrains/python/refactoring/classes/membersManager/MembersManager.java @@ -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 implements Function implements Function> transformSafely(@NotNull final PyClass pyClass, @NotNull final MembersManager manager) { + private static Collection> transformSafely(@NotNull final PyClass pyClass, + @NotNull final MembersManager manager) { final List membersCouldBeMoved = manager.getMembersCouldBeMoved(pyClass); - manager.checkElementTypes((Collection)membersCouldBeMoved); + manager.checkElementTypes((Iterable)membersCouldBeMoved); return (Collection>)Collections2.transform(membersCouldBeMoved, (Function)manager); } @@ -114,13 +117,12 @@ public abstract class MembersManager implements Function elements) { + void checkElementTypes(@NotNull final Iterable 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 implements Function findMember(@NotNull final PyClass pyClass, @NotNull final PyElement pyElement) { final PyMemberInfo result = findMember(pyClass, new FindByElement(pyElement)); @@ -245,8 +248,9 @@ public abstract class MembersManager implements Function type of element + * @param type of element * @return list of elements */ @NotNull @@ -254,6 +258,84 @@ public abstract class MembersManager implements Function()); } + /** + * 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 getAllDependencies( + @NotNull final PyClass classWhereMemberDeclared, + @NotNull final PyElement member, + @Nullable final PyClass destinationClass) { + final PyMemberInfo memberInfo = findMember(classWhereMemberDeclared, member); + + + final Collection elementsToCheckDependency = + memberInfo.getMembersManager().getElementsToStoreReferences(Collections.singleton(member)); + + final MultiMap dependencies = new MultiMap(); + + final Collection result = new HashSet(); + for (final MembersManager manager : MANAGERS) { + for (final PyElement elementToCheckDependency : elementsToCheckDependency) { + dependencies.putAllValues(manager.getDependencies(elementToCheckDependency)); + } + } + + if (destinationClass != null) { + final Iterator 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 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 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 getDependencies(@NotNull MultiMap usedElements); + private static class PyMemberExtractor implements Function, T> { @SuppressWarnings("NullableProblems") //IDEA-120100 @Override diff --git a/python/src/com/jetbrains/python/refactoring/classes/membersManager/MethodsManager.java b/python/src/com/jetbrains/python/refactoring/classes/membersManager/MethodsManager.java index 6fd96bb99c67..7cba0a2a639c 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/membersManager/MethodsManager.java +++ b/python/src/com/jetbrains/python/refactoring/classes/membersManager/MethodsManager.java @@ -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 { 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 getDependencies(@NotNull final MultiMap usedElements) { + return Collections.emptyList(); + } + + @NotNull + @Override + protected MultiMap getDependencies(@NotNull final PyElement member) { + final MultiMap result = new MultiMap(); + member.accept(new MyPyRecursiveElementVisitor(result)); + + return result; + } + @NotNull @Override protected List getMembersCouldBeMoved(@NotNull final PyClass pyClass) { @@ -69,7 +92,7 @@ class MethodsManager extends MembersManager { 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 { return input.isToAbstract() == myAllowAbstractOnly; } } + + private static class MyPyRecursiveElementVisitor extends PyRecursiveElementVisitor { + @NotNull + private final MultiMap myResult; + + private MyPyRecursiveElementVisitor(@NotNull final MultiMap 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); + } + } + } + } + } + } } diff --git a/python/src/com/jetbrains/python/refactoring/classes/membersManager/NamePredicate.java b/python/src/com/jetbrains/python/refactoring/classes/membersManager/NamePredicate.java new file mode 100644 index 000000000000..7a5ddc6945ba --- /dev/null +++ b/python/src/com/jetbrains/python/refactoring/classes/membersManager/NamePredicate.java @@ -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 { + @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 stock) { + final String name = needle.getName(); + if (name != null) { + final Optional optional = Iterables.tryFind(stock, new NamePredicate(name)); + return optional.isPresent(); + } + return false; + } +} diff --git a/python/src/com/jetbrains/python/refactoring/classes/membersManager/PyMemberInfo.java b/python/src/com/jetbrains/python/refactoring/classes/membersManager/PyMemberInfo.java index 650350b4850d..819ab90532ff 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/membersManager/PyMemberInfo.java +++ b/python/src/com/jetbrains/python/refactoring/classes/membersManager/PyMemberInfo.java @@ -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 extends MemberInfoBase { 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 extends MemberInfoBase { 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); + } } diff --git a/python/src/com/jetbrains/python/refactoring/classes/membersManager/SuperClassesManager.java b/python/src/com/jetbrains/python/refactoring/classes/membersManager/SuperClassesManager.java index 82ce209931f7..92ffe76666a3 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/membersManager/SuperClassesManager.java +++ b/python/src/com/jetbrains/python/refactoring/classes/membersManager/SuperClassesManager.java @@ -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 { } + @NotNull + @Override + protected Collection getDependencies(@NotNull final MultiMap usedElements) { + return Lists.newArrayList(usedElements.keySet()); + } + + @NotNull + protected MultiMap getDependencies(@NotNull PyElement member) { + return MultiMap.emptyInstance(); + } + + @Override + public boolean hasConflict(@NotNull final PyClass member, @NotNull final PyClass aClass) { + final List expressionList = getExpressionsBySuperClass(aClass, Collections.singleton(member)); + return !expressionList.isEmpty(); + } + @NotNull @Override protected List getMembersCouldBeMoved(@NotNull final PyClass pyClass) { @@ -44,19 +59,37 @@ class SuperClassesManager extends MembersManager { PyClassRefactoringUtil.addSuperclasses(from.getProject(), destClass, elements.toArray(new PyClass[members.size()])); } + final List 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 getExpressionsBySuperClass(@NotNull final PyClass from, @NotNull final Collection classes) { + final List expressionsToDelete = new ArrayList(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 diff --git a/python/src/com/jetbrains/python/refactoring/classes/membersManager/vp/MembersBasedPresenterImpl.java b/python/src/com/jetbrains/python/refactoring/classes/membersManager/vp/MembersBasedPresenterImpl.java index 118b3d688bcc..e0d99d4e176b 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/membersManager/vp/MembersBasedPresenterImpl.java +++ b/python/src/com/jetbrains/python/refactoring/classes/membersManager/vp/MembersBasedPresenterImpl.java @@ -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. * Warning: Do not inherit it directly. @@ -23,26 +28,40 @@ abstract class MembersBasedPresenterImpl> implemen protected final PyClass myClassUnderRefactoring; @NotNull protected final PyMemberInfoStorage myStorage; + /** + * Member model + */ + @NotNull + protected final MemberInfoModel> 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> model) { myView = view; myClassUnderRefactoring = classUnderRefactoring; myStorage = infoStorage; + myModel = model; } //TODO: Mark Async ? @Override public void okClicked() { + final MultiMap> conflicts = getConflicts(); + final Collection> dependencyConflicts = new ArrayList>(); + for (final PyMemberInfo memberInfo : myStorage.getClassMemberInfos(myClassUnderRefactoring)) { + if (myModel.checkForProblems(memberInfo) != MemberInfoModel.OK) { + dependencyConflicts.add(memberInfo); + } + } - final MultiMap 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> 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 getConflicts(); + protected final MultiMap> getConflicts() { + final MultiMap> result = new MultiMap>(); + final Collection> memberInfos = myView.getSelectedMemberInfos(); + for (final PyClass destinationClass : getDestClassesToCheckConflicts()) { + for (final PyMemberInfo 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 getDestClassesToCheckConflicts(); } diff --git a/python/src/com/jetbrains/python/refactoring/classes/membersManager/vp/MembersBasedPresenterNoPreviewImpl.java b/python/src/com/jetbrains/python/refactoring/classes/membersManager/vp/MembersBasedPresenterNoPreviewImpl.java index 199ac7a7ad01..5ec793a8a405 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/membersManager/vp/MembersBasedPresenterNoPreviewImpl.java +++ b/python/src/com/jetbrains/python/refactoring/classes/membersManager/vp/MembersBasedPresenterNoPreviewImpl.java @@ -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 view for this presenter * @author Ilya.Kazakevich */ public abstract class MembersBasedPresenterNoPreviewImpl> extends MembersBasedPresenterImpl { /** - * - * @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> model) { + super(view, classUnderRefactoring, infoStorage, model); } @Override @@ -36,12 +41,7 @@ public abstract class MembersBasedPresenterNoPreviewImpl> model) { + super(view, classUnderRefactoring, infoStorage, model); } @Override diff --git a/python/src/com/jetbrains/python/refactoring/classes/membersManager/vp/MembersBasedView.java b/python/src/com/jetbrains/python/refactoring/classes/membersManager/vp/MembersBasedView.java index 70496d0e0cac..01a7ecad76a8 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/membersManager/vp/MembersBasedView.java +++ b/python/src/com/jetbrains/python/refactoring/classes/membersManager/vp/MembersBasedView.java @@ -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 { /** * 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 conflicts); + boolean showConflictsDialog( + @NotNull MultiMap> duplicatesConflict, + @NotNull Collection> dependenciesConflicts); /** * Displays error message diff --git a/python/src/com/jetbrains/python/refactoring/classes/membersManager/vp/MembersBasedViewSwingImpl.java b/python/src/com/jetbrains/python/refactoring/classes/membersManager/vp/MembersBasedViewSwingImpl.java index 1fc5e532d81e..a8df16eded68 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/membersManager/vp/MembersBasedViewSwingImpl.java +++ b/python/src/com/jetbrains/python/refactoring/classes/membersManager/vp/MembersBasedViewSwingImpl.java @@ -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

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> duplicatesConflict, + @NotNull final Collection> 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

checkConflicts(final Collection> infos, @NotNull final PyClass superClass) { - final MultiMap conflictsList = new MultiMap(); - for (PyMemberInfo 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; - } -} diff --git a/python/src/com/jetbrains/python/refactoring/classes/pullUp/PyPullUpInfoModel.java b/python/src/com/jetbrains/python/refactoring/classes/pullUp/PyPullUpInfoModel.java new file mode 100644 index 000000000000..c86907dcc4ee --- /dev/null +++ b/python/src/com/jetbrains/python/refactoring/classes/pullUp/PyPullUpInfoModel.java @@ -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> { + @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 member) { + return member.isCouldBeAbstract() && isMemberEnabled(member); // TODO: copy paste with other models, get rid of + } + + @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; + } + + @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 + } + } + return true; + } +} diff --git a/python/src/com/jetbrains/python/refactoring/classes/pullUp/PyPullUpPresenterImpl.java b/python/src/com/jetbrains/python/refactoring/classes/pullUp/PyPullUpPresenterImpl.java index 83c2f0b0e43b..b15fd2afda88 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/pullUp/PyPullUpPresenterImpl.java +++ b/python/src/com/jetbrains/python/refactoring/classes/pullUp/PyPullUpPresenterImpl.java @@ -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 getConflicts() { - final Collection> infos = myView.getSelectedMemberInfos(); - final PyClass superClass = myView.getSelectedParent(); - return PyPullUpConflictsUtil.checkConflicts(infos, superClass); - } - - private class PyPullUpInfoModel extends AbstractUsesDependencyMemberInfoModel> { - - PyPullUpInfoModel() { - super(myClassUnderRefactoring, null, false); - } - - @Override - public boolean isAbstractEnabled(final PyMemberInfo member) { - return member.isCouldBeAbstract() && isMemberEnabled(member); // TODO: copy paste with other models, get rid of - } - - @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; - } + @Override + protected Iterable getDestClassesToCheckConflicts() { + return Collections.singletonList(myView.getSelectedParent()); } } diff --git a/python/src/com/jetbrains/python/refactoring/classes/pushDown/PyPushDownConflicts.java b/python/src/com/jetbrains/python/refactoring/classes/pushDown/PyPushDownConflicts.java deleted file mode 100644 index 9eee6c9b83b0..000000000000 --- a/python/src/com/jetbrains/python/refactoring/classes/pushDown/PyPushDownConflicts.java +++ /dev/null @@ -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> myMembers; - private final MultiMap myConflicts; - - public PyPushDownConflicts(final PyClass clazz, final Collection> members) { - myClass = clazz; - myMembers = members; - myConflicts = new MultiMap(); - } - - MultiMap getConflicts() { - return myConflicts; - } - - void checkTargetClassConflicts(PyClass clazz) { - checkPlacementConflicts(clazz); - } - - private void checkPlacementConflicts(PyClass clazz) { - for (PyMemberInfo 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 elements = ContainerUtil.map(myMembers, new Function, PyElement>() { - public PyElement fun(PyMemberInfo pyMemberInfo) { - return pyMemberInfo.getMember(); - } - }); - for (PyFunction pyFunction : myClass.getMethods()) { - final UsedMembersCollector collector = new UsedMembersCollector(elements); - pyFunction.accept(collector); - final List 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 myCollection = new ArrayList(); - private final Collection myMovedMembers; - - private UsedMembersCollector(Collection 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 getCollection() { - return myCollection; - } - } -} diff --git a/python/src/com/jetbrains/python/refactoring/classes/pushDown/PyPushDownPresenterImpl.java b/python/src/com/jetbrains/python/refactoring/classes/pushDown/PyPushDownPresenterImpl.java index 1e9cabd18dff..11a8b86fab0f 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/pushDown/PyPushDownPresenterImpl.java +++ b/python/src/com/jetbrains/python/refactoring/classes/pushDown/PyPushDownPresenterImpl.java @@ -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>(classUnderRefactoring)); myProject = project; } @@ -37,16 +35,14 @@ public class PyPushDownPresenterImpl extends MembersBasedPresenterWithPreviewImp @NotNull @Override - protected MultiMap getConflicts() { - return new PyPushDownConflicts(myClassUnderRefactoring, myStorage.getClassMemberInfos(myClassUnderRefactoring)).getConflicts(); + protected Iterable getDestClassesToCheckConflicts() { + return PyPushDownProcessor.getInheritors(myClassUnderRefactoring); } @Override public void launch() { - UsedByDependencyMemberInfoModel> model = - new UsedByDependencyMemberInfoModel>(myClassUnderRefactoring); myView - .configure(new MembersViewInitializationInfo(model, PyUtil.filterOutObject(myStorage.getClassMemberInfos(myClassUnderRefactoring)))); + .configure(new MembersViewInitializationInfo(myModel, PyUtil.filterOutObject(myStorage.getClassMemberInfos(myClassUnderRefactoring)))); myView.initAndShow(); } } diff --git a/python/src/com/jetbrains/python/refactoring/classes/pushDown/PyPushDownProcessor.java b/python/src/com/jetbrains/python/refactoring/classes/pushDown/PyPushDownProcessor.java index a0fcf53c1780..283dd725f14c 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/pushDown/PyPushDownProcessor.java +++ b/python/src/com/jetbrains/python/refactoring/classes/pushDown/PyPushDownProcessor.java @@ -44,10 +44,19 @@ public class PyPushDownProcessor extends PyMembersRefactoringBaseProcessor { @NotNull private static PyClass[] getChildren(@NotNull final PyClass from) { - Collection all = PyClassInheritorsSearch.search(from, false).findAll(); + final Collection all = getInheritors(from); return all.toArray(new PyClass[all.size()]); } + /** + * @param from class to check for inheritors + * @return inheritors of class + */ + @NotNull + static Collection getInheritors(@NotNull final PyClass from) { + return PyClassInheritorsSearch.search(from, false).findAll(); + } + public String getProcessedElementsHeader() { return HEADER; diff --git a/python/src/com/jetbrains/python/refactoring/classes/ui/PyMemberSelectionPanel.java b/python/src/com/jetbrains/python/refactoring/classes/ui/PyMemberSelectionPanel.java index 6b380b4e3d0e..0b6ef0748985 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/ui/PyMemberSelectionPanel.java +++ b/python/src/com/jetbrains/python/refactoring/classes/ui/PyMemberSelectionPanel.java @@ -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(); } } diff --git a/python/testData/refactoring/pullup/presenter/file.py b/python/testData/refactoring/pullup/presenter/file.py index 5caadf07936f..cb6ca9511209 100644 --- a/python/testData/refactoring/pullup/presenter/file.py +++ b/python/testData/refactoring/pullup/presenter/file.py @@ -59,4 +59,24 @@ class Bar(object): class Foo(Bar): def __init__(self): - self.foo = 12 \ No newline at end of file + 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 \ No newline at end of file diff --git a/python/testData/refactoring/pullup/pyPullUpInfoModel.py b/python/testData/refactoring/pullup/pyPullUpInfoModel.py new file mode 100644 index 000000000000..04f6430f2518 --- /dev/null +++ b/python/testData/refactoring/pullup/pyPullUpInfoModel.py @@ -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 \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/fixtures/PyTestCase.java b/python/testSrc/com/jetbrains/python/fixtures/PyTestCase.java index 09c74b76c4d4..3404194efd65 100644 --- a/python/testSrc/com/jetbrains/python/fixtures/PyTestCase.java +++ b/python/testSrc/com/jetbrains/python/fixtures/PyTestCase.java @@ -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; diff --git a/python/testSrc/com/jetbrains/python/refactoring/classes/NameTransformer.java b/python/testSrc/com/jetbrains/python/refactoring/classes/NameTransformer.java new file mode 100644 index 000000000000..1002ceac17b0 --- /dev/null +++ b/python/testSrc/com/jetbrains/python/refactoring/classes/NameTransformer.java @@ -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, String> { + /** + * To be used instead of creation + */ + public static final NameTransformer INSTANCE = new NameTransformer(); + + private NameTransformer() { + } + + @Override + public String apply(@NotNull final PyMemberInfo input) { + return input.getDisplayName(); + } +} diff --git a/python/testSrc/com/jetbrains/python/refactoring/classes/PyRefactoringPresenterTestCase.java b/python/testSrc/com/jetbrains/python/refactoring/classes/PyRefactoringPresenterTestCase.java index 66c478e6c7fd..d0c5f2a94e51 100644 --- a/python/testSrc/com/jetbrains/python/refactoring/classes/PyRefactoringPresenterTestCase.java +++ b/python/testSrc/com/jetbrains/python/refactoring/classes/PyRefactoringPresenterTestCase.java @@ -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(); myView.configure(EasyMock.capture(myViewConfigCapture)); @@ -90,15 +98,6 @@ public abstract class PyRefactoringPresenterTestCase> getMemberInfos(@NotNull MembersBasedPresenter presenter) { + myMocksControl.replay(); + presenter.launch(); + final Collection> result = myViewConfigCapture.getValue().getMemberInfos(); + myMocksControl.reset(); + configureMockCapture(); + return result; + } } diff --git a/python/testSrc/com/jetbrains/python/refactoring/classes/extractSuperclass/PyExtractSuperclassPresenterTest.java b/python/testSrc/com/jetbrains/python/refactoring/classes/extractSuperclass/PyExtractSuperclassPresenterTest.java index accfbab8bd3f..dd89f633ebc3 100644 --- a/python/testSrc/com/jetbrains/python/refactoring/classes/extractSuperclass/PyExtractSuperclassPresenterTest.java +++ b/python/testSrc/com/jetbrains/python/refactoring/classes/extractSuperclass/PyExtractSuperclassPresenterTest.java @@ -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 members = launchAndGetMembers("StaticOnly"); final Matcher> 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()); - } /** diff --git a/python/testSrc/com/jetbrains/python/refactoring/classes/pullUp/PyPullUpInfoModelTest.java b/python/testSrc/com/jetbrains/python/refactoring/classes/pullUp/PyPullUpInfoModelTest.java new file mode 100644 index 000000000000..9fe02c302c9a --- /dev/null +++ b/python/testSrc/com/jetbrains/python/refactoring/classes/pullUp/PyPullUpInfoModelTest.java @@ -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> 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 getErrorMemberNames() { + final List result = new ArrayList(); + for (final PyMemberInfo 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> { + @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); + } + } +} diff --git a/python/testSrc/com/jetbrains/python/refactoring/classes/pullUp/PyPullUpPresenterTest.java b/python/testSrc/com/jetbrains/python/refactoring/classes/pullUp/PyPullUpPresenterTest.java index 81833f6b9a54..a19edaa099d0 100644 --- a/python/testSrc/com/jetbrains/python/refactoring/classes/pullUp/PyPullUpPresenterTest.java +++ b/python/testSrc/com/jetbrains/python/refactoring/classes/pullUp/PyPullUpPresenterTest.java @@ -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> infos = getMemberInfos(sut); + + final Capture>> conflictCapture = new Capture>>(); + EasyMock.expect(myView.showConflictsDialog(EasyMock.capture(conflictCapture), EasyMock.>>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> conflictMap = conflictCapture.getValue(); + Assert.assertTrue("No conflicts found, while it should", conflictMap.containsKey(parent)); + final Collection 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