From 2382102e7b6dd3ec66ab7a7e74d8465e47dc5f26 Mon Sep 17 00:00:00 2001 From: Anna Kozlova Date: Thu, 20 Nov 2014 11:19:20 +0100 Subject: [PATCH] deep delete parameter in method call hierarchy --- .../safeDelete/JavaSafeDeleteProcessor.java | 39 +++- .../SafeDeleteJavaCallerChooser.java | 202 ++++++++++++++++++ ...DeleteParameterCallHierarchyUsageInfo.java | 46 ++++ .../safeDelete/DeepDeleteParameterSimple.java | 8 + .../DeepDeleteParameterSimple_after.java | 8 + .../ImpossibleToDeepDeleteParameter.java | 9 + ...ImpossibleToDeepDeleteParameter_after.java | 9 + .../ToDeepDeleteParameterOverriders.java | 11 + ...ToDeepDeleteParameterOverriders_after.java | 11 + .../intellij/refactoring/SafeDeleteTest.java | 12 ++ .../changeSignature/CallerChooserBase.java | 21 +- .../changeSignature/MethodNodeBase.java | 8 +- 12 files changed, 375 insertions(+), 9 deletions(-) create mode 100644 java/java-impl/src/com/intellij/refactoring/safeDelete/SafeDeleteJavaCallerChooser.java create mode 100644 java/java-impl/src/com/intellij/refactoring/safeDelete/usageInfo/SafeDeleteParameterCallHierarchyUsageInfo.java create mode 100644 java/java-tests/testData/refactoring/safeDelete/DeepDeleteParameterSimple.java create mode 100644 java/java-tests/testData/refactoring/safeDelete/DeepDeleteParameterSimple_after.java create mode 100644 java/java-tests/testData/refactoring/safeDelete/ImpossibleToDeepDeleteParameter.java create mode 100644 java/java-tests/testData/refactoring/safeDelete/ImpossibleToDeepDeleteParameter_after.java create mode 100644 java/java-tests/testData/refactoring/safeDelete/ToDeepDeleteParameterOverriders.java create mode 100644 java/java-tests/testData/refactoring/safeDelete/ToDeepDeleteParameterOverriders_after.java diff --git a/java/java-impl/src/com/intellij/refactoring/safeDelete/JavaSafeDeleteProcessor.java b/java/java-impl/src/com/intellij/refactoring/safeDelete/JavaSafeDeleteProcessor.java index 29c2513c2ddc..64104181fbda 100644 --- a/java/java-impl/src/com/intellij/refactoring/safeDelete/JavaSafeDeleteProcessor.java +++ b/java/java-impl/src/com/intellij/refactoring/safeDelete/JavaSafeDeleteProcessor.java @@ -39,20 +39,20 @@ import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; import com.intellij.refactoring.JavaRefactoringSettings; import com.intellij.refactoring.RefactoringBundle; +import com.intellij.refactoring.changeSignature.inCallers.JavaCallerChooser; import com.intellij.refactoring.safeDelete.usageInfo.*; import com.intellij.refactoring.util.RefactoringMessageUtil; import com.intellij.refactoring.util.RefactoringUIUtil; import com.intellij.usageView.UsageInfo; import com.intellij.usageView.UsageViewUtil; import com.intellij.usages.*; -import com.intellij.util.ArrayUtil; -import com.intellij.util.IncorrectOperationException; -import com.intellij.util.Processor; +import com.intellij.util.*; import com.intellij.util.containers.HashMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; +import java.util.HashSet; public class JavaSafeDeleteProcessor extends SafeDeleteProcessorDelegateBase { private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.safeDelete.JavaSafeDeleteProcessor"); @@ -240,8 +240,9 @@ public class JavaSafeDeleteProcessor extends SafeDeleteProcessorDelegateBase { @Nullable public UsageInfo[] preprocessUsages(final Project project, final UsageInfo[] usages) { - ArrayList result = new ArrayList(); + final ArrayList result = new ArrayList(); ArrayList overridingMethods = new ArrayList(); + ArrayList delegatingParams = new ArrayList(); for (UsageInfo usage : usages) { if (usage.isNonCodeUsage) { result.add(usage); @@ -249,6 +250,9 @@ public class JavaSafeDeleteProcessor extends SafeDeleteProcessorDelegateBase { else if (usage instanceof SafeDeleteOverridingMethodUsageInfo) { overridingMethods.add(usage); } + else if (usage instanceof SafeDeleteParameterCallHierarchyUsageInfo) { + delegatingParams.add(usage); + } else { result.add(usage); } @@ -267,6 +271,21 @@ public class JavaSafeDeleteProcessor extends SafeDeleteProcessorDelegateBase { } } + if (delegatingParams.size() == 1) { + final SafeDeleteParameterCallHierarchyUsageInfo parameterHierarchyUsageInfo = (SafeDeleteParameterCallHierarchyUsageInfo)delegatingParams.get(0); + if (ApplicationManager.getApplication().isUnitTestMode()) { + result.addAll(delegatingParams); + } else { + final PsiMethod method = parameterHierarchyUsageInfo.getCalledMethod(); + final PsiParameter parameter = parameterHierarchyUsageInfo.getReferencedElement(); + final int parameterIndex = method.getParameterList().getParameterIndex(parameter); + final JavaCallerChooser chooser = new SafeDeleteJavaCallerChooser(method, project, parameterIndex, result); + if (!chooser.showAndGet()) { + return null; + } + } + } + return result.toArray(new UsageInfo[result.size()]); } @@ -709,12 +728,24 @@ public class JavaSafeDeleteProcessor extends SafeDeleteProcessorDelegateBase { private static void findParameterUsages(final PsiParameter parameter, final List usages) { final PsiMethod method = (PsiMethod)parameter.getDeclarationScope(); + final int parameterIndex = method.getParameterList().getParameterIndex(parameter); //search for refs to current method only, do not search for refs to overriding methods, they'll be searched separately ReferencesSearch.search(method).forEach(new Processor() { public boolean process(final PsiReference reference) { PsiElement element = reference.getElement(); if (element != null) { JavaSafeDeleteDelegate.EP.forLanguage(element.getLanguage()).createUsageInfoForParameter(reference, usages, parameter, method); + if (!parameter.isVarArgs()) { + final PsiParameter paramInCaller = SafeDeleteJavaCallerChooser.isTheOnlyOneParameterUsage(element.getParent(), parameterIndex, method); + if (paramInCaller != null) { + if (ApplicationManager.getApplication().isUnitTestMode()) { + usages.add(new SafeDeleteParameterCallHierarchyUsageInfo((PsiMethod)paramInCaller.getDeclarationScope(), paramInCaller)); + } + else { + usages.add(new SafeDeleteParameterCallHierarchyUsageInfo(method, parameter)); + } + } + } } return true; } diff --git a/java/java-impl/src/com/intellij/refactoring/safeDelete/SafeDeleteJavaCallerChooser.java b/java/java-impl/src/com/intellij/refactoring/safeDelete/SafeDeleteJavaCallerChooser.java new file mode 100644 index 000000000000..9bfca1895ebd --- /dev/null +++ b/java/java-impl/src/com/intellij/refactoring/safeDelete/SafeDeleteJavaCallerChooser.java @@ -0,0 +1,202 @@ +/* + * 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.refactoring.safeDelete; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.Ref; +import com.intellij.psi.*; +import com.intellij.psi.search.LocalSearchScope; +import com.intellij.psi.search.searches.ReferencesSearch; +import com.intellij.psi.util.PsiUtil; +import com.intellij.refactoring.changeSignature.MethodNodeBase; +import com.intellij.refactoring.changeSignature.inCallers.JavaCallerChooser; +import com.intellij.refactoring.changeSignature.inCallers.JavaMethodNode; +import com.intellij.refactoring.safeDelete.usageInfo.SafeDeleteParameterCallHierarchyUsageInfo; +import com.intellij.usageView.UsageInfo; +import com.intellij.util.Consumer; +import com.intellij.util.Processor; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +class SafeDeleteJavaCallerChooser extends JavaCallerChooser { + private final PsiMethod myMethod; + private final Project myProject; + private final int myParameterIndex; + private final ArrayList myResult; + + public SafeDeleteJavaCallerChooser(PsiMethod method, Project project, int parameterIndex, ArrayList result) { + super(method, project, "Select Methods To Propagate Parameter Deletion", null, Consumer.EMPTY_CONSUMER); + myMethod = method; + myProject = project; + myParameterIndex = parameterIndex; + myResult = result; + } + + @Override + protected JavaMethodNode createTreeNode(PsiMethod nodeMethod, + com.intellij.util.containers.HashSet called, + Runnable cancelCallback) { + return new SafeDeleteJavaMethodNode(nodeMethod, called, cancelCallback, myParameterIndex, nodeMethod != null ? nodeMethod.getProject() : myProject); + } + + @Override + protected void doOKAction() { + final List foreignMethodUsages = new ArrayList(); + final Runnable runnable = new Runnable() { + public void run() { + final Set> nodes = getSelectedNodes(); + for (MethodNodeBase node : nodes) { + final SafeDeleteJavaMethodNode methodNode = (SafeDeleteJavaMethodNode)node; + final PsiMethod nodeMethod = methodNode.myCurrentMethod; + if (nodeMethod.equals(myMethod)) continue; + final PsiParameter parameter = nodeMethod.getParameterList().getParameters()[methodNode.myParameterIdx]; + foreignMethodUsages.add(new SafeDeleteParameterCallHierarchyUsageInfo(nodeMethod, parameter)); + ReferencesSearch.search(nodeMethod).forEach(new Processor() { + public boolean process(final PsiReference reference) { + final PsiElement element = reference.getElement(); + if (element != null) { + JavaSafeDeleteDelegate.EP.forLanguage(element.getLanguage()) + .createUsageInfoForParameter(reference, foreignMethodUsages, parameter, nodeMethod); + } + return true; + } + }); + } + } + }; + + if (ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() { + @Override + public void run() { + ApplicationManager.getApplication().runReadAction(runnable); + } + }, "Search for caller method usages...", true, myProject)) { + myResult.addAll(foreignMethodUsages); + } + super.doOKAction(); + } + + /** + * @return parameter if it is used inside method only as argument in nodeMethod call at parameterIndex + */ + static PsiParameter isTheOnlyOneParameterUsage(PsiElement call, int parameterIndex, final PsiMethod nodeMethod) { + if (call instanceof PsiCallExpression) { + final PsiExpressionList argumentList = ((PsiCallExpression)call).getArgumentList(); + if (argumentList != null) { + final PsiExpression[] expressions = argumentList.getExpressions(); + if (expressions.length > parameterIndex) { + final PsiExpression expression = PsiUtil.deparenthesizeExpression(expressions[parameterIndex]); + if (expression instanceof PsiReferenceExpression) { + final PsiElement resolve = ((PsiReferenceExpression)expression).resolve(); + if (resolve instanceof PsiParameter && !((PsiParameter)resolve).isVarArgs()) { + final PsiElement scope = ((PsiParameter)resolve).getDeclarationScope(); + if (scope instanceof PsiMethod) { + if (ReferencesSearch.search(resolve, new LocalSearchScope(scope)).forEach(new Processor() { + @Override + public boolean process(PsiReference reference) { + final PsiElement element = reference.getElement(); + if (element instanceof PsiReferenceExpression) { + final PsiElement parent = element.getParent(); + if (parent instanceof PsiExpressionList) { + final PsiElement gParent = parent.getParent(); + if (gParent instanceof PsiCallExpression && + nodeMethod.equals(((PsiCallExpression)gParent).resolveMethod())) { + return true; + } + } + } + return false; + } + })) { + return (PsiParameter)resolve; + } + } + } + } + } + } + } + return null; + } + + private static class SafeDeleteJavaMethodNode extends JavaMethodNode { + + private final PsiMethod myCurrentMethod; + private final int myParameterIdx; + + public SafeDeleteJavaMethodNode(PsiMethod currentMethod, + HashSet called, + Runnable cancelCallback, + int idx, + Project project) { + super(currentMethod, called, project, cancelCallback); + myCurrentMethod = currentMethod; + myParameterIdx = idx; + } + + @Override + protected MethodNodeBase createNode(PsiMethod caller, HashSet called) { + return new SafeDeleteJavaMethodNode(caller, called, myCancelCallback, getParameterIndex(caller), myProject); + } + + @Override + protected Condition getFilter() { + return new Condition() { + @Override + public boolean value(PsiMethod method) { + return getParameter(method) != null; + } + }; + } + + private PsiParameter getParameter(PsiMethod caller) { + + //do not change hierarchy + if (caller.findDeepestSuperMethods().length > 0) { + return null; + } + + //find first method call + final Ref ref = new Ref(); + ReferencesSearch.search(myCurrentMethod, new LocalSearchScope(caller)).forEach(new Processor() { + @Override + public boolean process(PsiReference reference) { + final PsiElement element = reference.getElement(); + if (element instanceof PsiReferenceExpression) { + final PsiElement elementParent = element.getParent(); + if (elementParent instanceof PsiCallExpression) { + ref.set(isTheOnlyOneParameterUsage(elementParent, myParameterIdx, myCurrentMethod)); + return false; + } + } + return true; + } + }); + return ref.get(); + } + + private int getParameterIndex(PsiMethod caller) { + final PsiParameter parameter = getParameter(caller); + return parameter != null ? caller.getParameterList().getParameterIndex(parameter) : -1; + } + } +} diff --git a/java/java-impl/src/com/intellij/refactoring/safeDelete/usageInfo/SafeDeleteParameterCallHierarchyUsageInfo.java b/java/java-impl/src/com/intellij/refactoring/safeDelete/usageInfo/SafeDeleteParameterCallHierarchyUsageInfo.java new file mode 100644 index 000000000000..dfa8fc69b268 --- /dev/null +++ b/java/java-impl/src/com/intellij/refactoring/safeDelete/usageInfo/SafeDeleteParameterCallHierarchyUsageInfo.java @@ -0,0 +1,46 @@ +/* + * 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.refactoring.safeDelete.usageInfo; + +import com.intellij.psi.PsiMethod; +import com.intellij.psi.PsiParameter; +import com.intellij.util.IncorrectOperationException; + +public class SafeDeleteParameterCallHierarchyUsageInfo extends SafeDeleteUsageInfo implements SafeDeleteCustomUsageInfo { + + private PsiMethod myCalledMethod; + + public SafeDeleteParameterCallHierarchyUsageInfo(PsiMethod calledMethod, PsiParameter parameter) { + super(calledMethod, parameter); + myCalledMethod = calledMethod; + } + + @Override + public PsiParameter getReferencedElement() { + return (PsiParameter)super.getReferencedElement(); + } + + public void performRefactoring() throws IncorrectOperationException { + final PsiParameter parameter = getReferencedElement(); + if (parameter != null && parameter.isValid()) { + parameter.delete(); + } + } + + public PsiMethod getCalledMethod() { + return myCalledMethod; + } +} diff --git a/java/java-tests/testData/refactoring/safeDelete/DeepDeleteParameterSimple.java b/java/java-tests/testData/refactoring/safeDelete/DeepDeleteParameterSimple.java new file mode 100644 index 000000000000..15cfeb2429fd --- /dev/null +++ b/java/java-tests/testData/refactoring/safeDelete/DeepDeleteParameterSimple.java @@ -0,0 +1,8 @@ +class Test { + void foo(int i) { + bar(i); + bar(i); + } + + void bar(int i){} +} \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/safeDelete/DeepDeleteParameterSimple_after.java b/java/java-tests/testData/refactoring/safeDelete/DeepDeleteParameterSimple_after.java new file mode 100644 index 000000000000..d3f5fd3b4214 --- /dev/null +++ b/java/java-tests/testData/refactoring/safeDelete/DeepDeleteParameterSimple_after.java @@ -0,0 +1,8 @@ +class Test { + void foo() { + bar(); + bar(); + } + + void bar(){} +} \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/safeDelete/ImpossibleToDeepDeleteParameter.java b/java/java-tests/testData/refactoring/safeDelete/ImpossibleToDeepDeleteParameter.java new file mode 100644 index 000000000000..7dd9b11e1ef0 --- /dev/null +++ b/java/java-tests/testData/refactoring/safeDelete/ImpossibleToDeepDeleteParameter.java @@ -0,0 +1,9 @@ +class Test { + void foo(int i) { + bar(i); + baz(i); + } + + void bar(int i){} + void baz(int i){} +} \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/safeDelete/ImpossibleToDeepDeleteParameter_after.java b/java/java-tests/testData/refactoring/safeDelete/ImpossibleToDeepDeleteParameter_after.java new file mode 100644 index 000000000000..c7ee28db141c --- /dev/null +++ b/java/java-tests/testData/refactoring/safeDelete/ImpossibleToDeepDeleteParameter_after.java @@ -0,0 +1,9 @@ +class Test { + void foo(int i) { + bar(); + baz(i); + } + + void bar(){} + void baz(int i){} +} \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/safeDelete/ToDeepDeleteParameterOverriders.java b/java/java-tests/testData/refactoring/safeDelete/ToDeepDeleteParameterOverriders.java new file mode 100644 index 000000000000..b7fb118bdf16 --- /dev/null +++ b/java/java-tests/testData/refactoring/safeDelete/ToDeepDeleteParameterOverriders.java @@ -0,0 +1,11 @@ +interface I { + void foo(int i); +} +class Test implements I { + public void foo(int i) { + bar(i); + bar(i); + } + + void bar(int i){} +} \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/safeDelete/ToDeepDeleteParameterOverriders_after.java b/java/java-tests/testData/refactoring/safeDelete/ToDeepDeleteParameterOverriders_after.java new file mode 100644 index 000000000000..1278e856bc79 --- /dev/null +++ b/java/java-tests/testData/refactoring/safeDelete/ToDeepDeleteParameterOverriders_after.java @@ -0,0 +1,11 @@ +interface I { + void foo(int i); +} +class Test implements I { + public void foo(int i) { + bar(); + bar(); + } + + void bar(){} +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/refactoring/SafeDeleteTest.java b/java/java-tests/testSrc/com/intellij/refactoring/SafeDeleteTest.java index 4c241cd82ee5..80f890641737 100644 --- a/java/java-tests/testSrc/com/intellij/refactoring/SafeDeleteTest.java +++ b/java/java-tests/testSrc/com/intellij/refactoring/SafeDeleteTest.java @@ -76,6 +76,18 @@ public class SafeDeleteTest extends MultiFileTestCase { doSingleFileTest(); } + public void testDeepDeleteParameterSimple() throws Exception { + doSingleFileTest(); + } + + public void testImpossibleToDeepDeleteParameter() throws Exception { + doSingleFileTest(); + } + + public void testToDeepDeleteParameterOverriders() throws Exception { + doSingleFileTest(); + } + public void testParameterInHierarchy() throws Exception { doTest("C2"); } diff --git a/platform/lang-impl/src/com/intellij/refactoring/changeSignature/CallerChooserBase.java b/platform/lang-impl/src/com/intellij/refactoring/changeSignature/CallerChooserBase.java index 909b44799a02..13c5ddce9327 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/changeSignature/CallerChooserBase.java +++ b/platform/lang-impl/src/com/intellij/refactoring/changeSignature/CallerChooserBase.java @@ -53,10 +53,7 @@ import javax.swing.event.TreeSelectionListener; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import java.awt.*; -import java.util.Arrays; -import java.util.Collection; -import java.util.Enumeration; -import java.util.Set; +import java.util.*; public abstract class CallerChooserBase extends DialogWrapper { private final M myMethod; @@ -288,6 +285,22 @@ public abstract class CallerChooserBase extends DialogWrap } } + protected Set> getSelectedNodes() { + final Set> nodes = new LinkedHashSet>(); + collectSelectedNodes(myRoot, nodes); + return nodes; + } + + private void collectSelectedNodes(final MethodNodeBase node, final Set> nodes) { + if (node.isChecked()) { + nodes.add(node); + final Enumeration children = node.children(); + while (children.hasMoreElements()) { + collectSelectedNodes((MethodNodeBase)children.nextElement(), nodes); + } + } + } + @Override protected void doOKAction() { final Set selectedMethods = new HashSet(); diff --git a/platform/lang-impl/src/com/intellij/refactoring/changeSignature/MethodNodeBase.java b/platform/lang-impl/src/com/intellij/refactoring/changeSignature/MethodNodeBase.java index 782ff452fbf4..42be59976a9d 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/changeSignature/MethodNodeBase.java +++ b/platform/lang-impl/src/com/intellij/refactoring/changeSignature/MethodNodeBase.java @@ -18,12 +18,14 @@ package com.intellij.refactoring.changeSignature; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Iconable; import com.intellij.openapi.util.Ref; import com.intellij.psi.PsiElement; import com.intellij.refactoring.RefactoringBundle; import com.intellij.ui.CheckedTreeNode; import com.intellij.ui.ColoredTreeCellRenderer; +import com.intellij.util.containers.ContainerUtil; import javax.swing.tree.TreeNode; import java.util.*; @@ -41,6 +43,10 @@ public abstract class MethodNodeBase extends CheckedTreeNo protected abstract void customizeRendererText(ColoredTreeCellRenderer renderer); + protected Condition getFilter() { + return Condition.TRUE; + } + protected MethodNodeBase(final M method, Set called, Project project, Runnable cancelCallback) { super(method); myMethod = method; @@ -92,7 +98,7 @@ public abstract class MethodNodeBase extends CheckedTreeNo ApplicationManager.getApplication().runReadAction(new Runnable() { @Override public void run() { - callers.set(computeCallers()); + callers.set(ContainerUtil.filter(computeCallers(), getFilter())); } }); }