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 897036618d0d..dee26d6d80bb 100644 --- a/java/java-impl/src/com/intellij/refactoring/safeDelete/JavaSafeDeleteProcessor.java +++ b/java/java-impl/src/com/intellij/refactoring/safeDelete/JavaSafeDeleteProcessor.java @@ -48,7 +48,9 @@ import com.intellij.refactoring.util.RefactoringUIUtil; import com.intellij.usageView.UsageInfo; import com.intellij.usageView.UsageViewUtil; import com.intellij.usages.*; -import com.intellij.util.*; +import com.intellij.util.ArrayUtil; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.Processor; import com.intellij.util.containers.HashMap; import com.intellij.util.containers.MultiMap; import com.intellij.util.ui.tree.TreeUtil; @@ -56,7 +58,6 @@ 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"); @@ -262,10 +263,14 @@ public class JavaSafeDeleteProcessor extends SafeDeleteProcessorDelegateBase { final ArrayList result = new ArrayList(); final ArrayList overridingMethods = new ArrayList(); final ArrayList delegatingParams = new ArrayList(); + final ArrayList calleesSafeToDelete = new ArrayList(); for (UsageInfo usage : usages) { if (usage.isNonCodeUsage) { result.add(usage); } + else if (usage instanceof SafeDeleteMethodCalleeUsageInfo) { + calleesSafeToDelete.add((SafeDeleteMethodCalleeUsageInfo)usage); + } else if (usage instanceof SafeDeleteOverridingMethodUsageInfo) { overridingMethods.add(usage); } @@ -291,7 +296,7 @@ public class JavaSafeDeleteProcessor extends SafeDeleteProcessorDelegateBase { } if (delegatingParams.size() == 1) { - final SafeDeleteParameterCallHierarchyUsageInfo parameterHierarchyUsageInfo = (SafeDeleteParameterCallHierarchyUsageInfo)delegatingParams.get(0); + final SafeDeleteParameterCallHierarchyUsageInfo parameterHierarchyUsageInfo = delegatingParams.get(0); if (ApplicationManager.getApplication().isUnitTestMode()) { result.addAll(delegatingParams); } else { @@ -316,6 +321,38 @@ public class JavaSafeDeleteProcessor extends SafeDeleteProcessorDelegateBase { } } + if (!calleesSafeToDelete.isEmpty()) { + if (ApplicationManager.getApplication().isUnitTestMode()) { + result.addAll(calleesSafeToDelete); + } + else { + final PsiMethod method = calleesSafeToDelete.get(0).getCallerMethod(); + final ArrayList list = new ArrayList(); + JavaCallerChooser chooser = new SafeDeleteJavaCalleeChooser(method, project, list) { + @Override + protected ArrayList getTopLevelItems() { + return calleesSafeToDelete; + } + }; + TreeUtil.expand(chooser.getTree(), 2); + if (!chooser.showAndGet()) { + return null; + } + result.addAll(list); + final List methodsToDelete = new ArrayList(); + for (UsageInfo info : list) { + methodsToDelete.add(info.getElement()); + } + methodsToDelete.add(method); + final Condition insideDeletedCondition = getUsageInsideDeletedFilter(methodsToDelete.toArray(new PsiElement[methodsToDelete.size()])); + for (UsageInfo info : list) { + SafeDeleteProcessor.addNonCodeUsages(info.getElement(), result, insideDeletedCondition, + JavaRefactoringSettings.getInstance().RENAME_SEARCH_FOR_TEXT_FOR_METHOD, + JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_METHOD ); + } + } + } + return result.toArray(new UsageInfo[result.size()]); } @@ -498,7 +535,7 @@ public class JavaSafeDeleteProcessor extends SafeDeleteProcessorDelegateBase { } @Nullable - private static Condition findMethodUsages(PsiMethod psiMethod, final PsiElement[] allElementsToDelete, List usages) { + private static Condition findMethodUsages(final PsiMethod psiMethod, final PsiElement[] allElementsToDelete, List usages) { final Collection references = ReferencesSearch.search(psiMethod).findAll(); if(psiMethod.isConstructor()) { @@ -524,6 +561,14 @@ public class JavaSafeDeleteProcessor extends SafeDeleteProcessorDelegateBase { usages.add(new SafeDeleteReferenceJavaDeleteUsageInfo(element, psiMethod, PsiTreeUtil.getParentOfType(element, PsiImportStaticStatement.class) != null)); } } + + final List calleesSafeToDelete = SafeDeleteJavaCalleeChooser.computeCalleesSafeToDelete(psiMethod); + if (calleesSafeToDelete != null) { + for (PsiMethod method : calleesSafeToDelete) { + usages.add(new SafeDeleteMethodCalleeUsageInfo(method, psiMethod)); + } + } + return new Condition() { public boolean value(PsiElement usage) { if(usage instanceof PsiFile) return false; diff --git a/java/java-impl/src/com/intellij/refactoring/safeDelete/SafeDeleteJavaCalleeChooser.java b/java/java-impl/src/com/intellij/refactoring/safeDelete/SafeDeleteJavaCalleeChooser.java new file mode 100644 index 000000000000..245898aeb69f --- /dev/null +++ b/java/java-impl/src/com/intellij/refactoring/safeDelete/SafeDeleteJavaCalleeChooser.java @@ -0,0 +1,157 @@ +/* + * 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.project.Project; +import com.intellij.openapi.util.Condition; +import com.intellij.psi.*; +import com.intellij.psi.search.searches.ReferencesSearch; +import com.intellij.psi.util.PsiTreeUtil; +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.SafeDeleteMethodCalleeUsageInfo; +import com.intellij.refactoring.safeDelete.usageInfo.SafeDeleteReferenceJavaDeleteUsageInfo; +import com.intellij.usageView.UsageInfo; +import com.intellij.util.CommonProcessors; +import com.intellij.util.Consumer; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +abstract class SafeDeleteJavaCalleeChooser extends JavaCallerChooser { + private final Project myProject; + + public SafeDeleteJavaCalleeChooser(final PsiMethod method, Project project, final ArrayList result) { + super(method, project, "Select Methods To Cascade Safe Delete", null, new Consumer>() { + @Override + public void consume(Set methods) { + result.addAll(ContainerUtil.map(methods, new Function() { + @Override + public SafeDeleteReferenceJavaDeleteUsageInfo fun(PsiMethod m) { + return new SafeDeleteReferenceJavaDeleteUsageInfo(m, m, true); + } + })); + } + }); + myProject = project; + } + + @Nullable + static List computeCalleesSafeToDelete(final PsiMethod psiMethod) { + final PsiCodeBlock body = psiMethod.getBody(); + if (body != null) { + final PsiClass containingClass = psiMethod.getContainingClass(); + if (containingClass != null) { + final Set methodsToCheck = new HashSet(); + body.accept(new JavaRecursiveElementWalkingVisitor() { + @Override + public void visitMethodCallExpression(PsiMethodCallExpression expression) { + super.visitMethodCallExpression(expression); + ContainerUtil.addAllNotNull(methodsToCheck, expression.resolveMethod()); + } + }); + + return ContainerUtil.filter(methodsToCheck, new Condition() { + @Override + public boolean value(final PsiMethod m) { + return containingClass.equals(m.getContainingClass()) && + !psiMethod.equals(m) && + m.findDeepestSuperMethods().length == 0 && + ReferencesSearch.search(m).forEach(new CommonProcessors.CollectProcessor() { + @Override + public boolean process(PsiReference reference) { + final PsiElement element = reference.getElement(); + return PsiTreeUtil.isAncestor(psiMethod, element, true) || + PsiTreeUtil.isAncestor(m, element, true); + } + }); + } + }); + } + } + return null; + } + + protected abstract ArrayList getTopLevelItems(); + + @Override + protected JavaMethodNode createTreeNode(PsiMethod nodeMethod, + com.intellij.util.containers.HashSet callees, + Runnable cancelCallback) { + final SafeDeleteJavaMethodNode node = new SafeDeleteJavaMethodNode(nodeMethod, callees, cancelCallback, nodeMethod != null ? nodeMethod.getProject() : myProject); + if (getTopMethod().equals(nodeMethod)) { + node.setEnabled(false); + node.setChecked(true); + } + return node; + } + + @Override + protected MethodNodeBase getCalleeNode(MethodNodeBase node) { + return node; + } + + @Override + protected MethodNodeBase getCallerNode(MethodNodeBase node) { + return (MethodNodeBase)node.getParent(); + } + + private class SafeDeleteJavaMethodNode extends JavaMethodNode { + + public SafeDeleteJavaMethodNode(PsiMethod currentMethod, + HashSet callees, + Runnable cancelCallback, + Project project) { + super(currentMethod, callees, project, cancelCallback); + + } + + @Override + protected MethodNodeBase createNode(PsiMethod caller, HashSet callees) { + return new SafeDeleteJavaMethodNode(caller, callees, myCancelCallback, myProject); + } + + @Override + protected List computeCallers() { + if (getTopMethod().equals(getMethod())) { + return ContainerUtil.map(getTopLevelItems(), new Function() { + @Override + public PsiMethod fun(SafeDeleteMethodCalleeUsageInfo info) { + return info.getCalledMethod(); + } + }); + } + + final List callees = computeCalleesSafeToDelete(getMethod()); + return callees != null ? callees : Collections.emptyList(); + } + + @Override + protected Condition getFilter() { + return new Condition() { + @Override + public boolean value(PsiMethod method) { + return !myMethod.equals(method); + } + }; + } + + + } +} diff --git a/java/java-impl/src/com/intellij/refactoring/safeDelete/usageInfo/SafeDeleteMethodCalleeUsageInfo.java b/java/java-impl/src/com/intellij/refactoring/safeDelete/usageInfo/SafeDeleteMethodCalleeUsageInfo.java new file mode 100644 index 000000000000..d7a49f732894 --- /dev/null +++ b/java/java-impl/src/com/intellij/refactoring/safeDelete/usageInfo/SafeDeleteMethodCalleeUsageInfo.java @@ -0,0 +1,47 @@ +/* + * 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 SafeDeleteMethodCalleeUsageInfo extends SafeDeleteUsageInfo implements SafeDeleteCustomUsageInfo { + + private final PsiMethod myCalledMethod; + private final PsiMethod myCallerMethod; + + public SafeDeleteMethodCalleeUsageInfo(PsiMethod calledMethod, PsiMethod callerMethod) { + super(calledMethod, calledMethod); + myCalledMethod = calledMethod; + myCallerMethod = callerMethod; + } + + public void performRefactoring() throws IncorrectOperationException { + final PsiMethod callee = myCalledMethod; + if (callee != null && callee.isValid()) { + callee.delete(); + } + } + + public PsiMethod getCalledMethod() { + return myCalledMethod; + } + + public PsiMethod getCallerMethod() { + return myCallerMethod; + } +} diff --git a/java/java-tests/testData/refactoring/safeDelete/DeleteMethodCascade.java b/java/java-tests/testData/refactoring/safeDelete/DeleteMethodCascade.java new file mode 100644 index 000000000000..4872367be217 --- /dev/null +++ b/java/java-tests/testData/refactoring/safeDelete/DeleteMethodCascade.java @@ -0,0 +1,11 @@ +class Test { + void foo() { + bar(); + } + + void bar(){ + baz(); + } + + void baz(){} +} \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/safeDelete/DeleteMethodCascadeOverridden.java b/java/java-tests/testData/refactoring/safeDelete/DeleteMethodCascadeOverridden.java new file mode 100644 index 000000000000..9656aa033f0c --- /dev/null +++ b/java/java-tests/testData/refactoring/safeDelete/DeleteMethodCascadeOverridden.java @@ -0,0 +1,14 @@ +interface I { + void bar(); +} +class Test implements I{ + void foo() { + bar(); + } + + public void bar(){ + baz(); + } + + void baz(){} +} \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/safeDelete/DeleteMethodCascadeOverridden_after.java b/java/java-tests/testData/refactoring/safeDelete/DeleteMethodCascadeOverridden_after.java new file mode 100644 index 000000000000..852cd2318fb7 --- /dev/null +++ b/java/java-tests/testData/refactoring/safeDelete/DeleteMethodCascadeOverridden_after.java @@ -0,0 +1,11 @@ +interface I { + void bar(); +} +class Test implements I{ + + public void bar(){ + baz(); + } + + void baz(){} +} \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/safeDelete/DeleteMethodCascadeRecursive.java b/java/java-tests/testData/refactoring/safeDelete/DeleteMethodCascadeRecursive.java new file mode 100644 index 000000000000..1b4702b467d4 --- /dev/null +++ b/java/java-tests/testData/refactoring/safeDelete/DeleteMethodCascadeRecursive.java @@ -0,0 +1,17 @@ +class Test { + void foo() { + bar(); + if (false) { + foo(); + } + } + + void bar(){ + baz(); + if (false) { + bar(); + } + } + + void baz(){} +} \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/safeDelete/DeleteMethodCascadeRecursive_after.java b/java/java-tests/testData/refactoring/safeDelete/DeleteMethodCascadeRecursive_after.java new file mode 100644 index 000000000000..a6ce335d9e50 --- /dev/null +++ b/java/java-tests/testData/refactoring/safeDelete/DeleteMethodCascadeRecursive_after.java @@ -0,0 +1,4 @@ +class Test { + + void baz(){} +} \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/safeDelete/DeleteMethodCascade_after.java b/java/java-tests/testData/refactoring/safeDelete/DeleteMethodCascade_after.java new file mode 100644 index 000000000000..a6ce335d9e50 --- /dev/null +++ b/java/java-tests/testData/refactoring/safeDelete/DeleteMethodCascade_after.java @@ -0,0 +1,4 @@ +class Test { + + void baz(){} +} \ 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 428f978b9327..819922c4016f 100644 --- a/java/java-tests/testSrc/com/intellij/refactoring/SafeDeleteTest.java +++ b/java/java-tests/testSrc/com/intellij/refactoring/SafeDeleteTest.java @@ -88,6 +88,18 @@ public class SafeDeleteTest extends MultiFileTestCase { doSingleFileTest(); } + public void testDeleteMethodCascade() throws Exception { + doSingleFileTest(); + } + + public void testDeleteMethodCascadeRecursive() throws Exception { + doSingleFileTest(); + } + + public void testDeleteMethodCascadeOverridden() throws Exception { + doSingleFileTest(); + } + public void testDeleteConstructorParameterWithAnonymousClassUsage() throws Exception { doSingleFileTest(); } 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 a56554696d0f..6e0ec0b218c3 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/changeSignature/CallerChooserBase.java +++ b/platform/lang-impl/src/com/intellij/refactoring/changeSignature/CallerChooserBase.java @@ -143,8 +143,9 @@ public abstract class CallerChooserBase extends DialogWrap } private void updateEditorTexts(final MethodNodeBase node) { - final MethodNodeBase parentNode = (MethodNodeBase)node.getParent(); - final String callerText = node != myRoot ? getText(node.getMethod()) : getEmptyCallerText(); + final MethodNodeBase parentNode = getCalleeNode(node); + final MethodNodeBase callerNode = getCallerNode(node); + final String callerText = node != myRoot ? getText(callerNode.getMethod()) : getEmptyCallerText(); final Document callerDocument = myCallerEditor.getDocument(); final String calleeText = node != myRoot ? getText(parentNode.getMethod()) : getEmptyCalleeText(); final Document calleeDocument = myCalleeEditor.getDocument(); @@ -157,7 +158,7 @@ public abstract class CallerChooserBase extends DialogWrap } }); - final M caller = node.getMethod(); + final M caller = callerNode.getMethod(); final PsiElement callee = parentNode != null ? parentNode.getElementToSearch() : null; if (caller != null && caller.isPhysical() && callee != null) { HighlightManager highlighter = HighlightManager.getInstance(myProject); @@ -171,6 +172,14 @@ public abstract class CallerChooserBase extends DialogWrap } } + protected MethodNodeBase getCalleeNode(MethodNodeBase node) { + return (MethodNodeBase)node.getParent(); + } + + protected MethodNodeBase getCallerNode(MethodNodeBase node) { + return node; + } + protected Collection findElementsToHighlight(M caller, PsiElement callee) { Query references = ReferencesSearch.search(callee, new LocalSearchScope(caller), false); return ContainerUtil.mapNotNull(references, new Function() { diff --git a/platform/lang-impl/src/com/intellij/refactoring/safeDelete/SafeDeleteProcessor.java b/platform/lang-impl/src/com/intellij/refactoring/safeDelete/SafeDeleteProcessor.java index 8d95afea60d1..db18408e09b5 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/safeDelete/SafeDeleteProcessor.java +++ b/platform/lang-impl/src/com/intellij/refactoring/safeDelete/SafeDeleteProcessor.java @@ -138,7 +138,8 @@ public class SafeDeleteProcessor extends BaseRefactoringProcessor { final NonCodeUsageSearchInfo filter = delegate.findUsages(element, myElements, usages); if (filter != null) { for(PsiElement nonCodeUsageElement: filter.getElementsToSearch()) { - addNonCodeUsages(nonCodeUsageElement, usages, filter.getInsideDeletedCondition()); + addNonCodeUsages(nonCodeUsageElement, usages, filter.getInsideDeletedCondition(), mySearchNonJava, + mySearchInCommentsAndStrings); } } handled = true; @@ -147,7 +148,7 @@ public class SafeDeleteProcessor extends BaseRefactoringProcessor { } if (!handled && element instanceof PsiNamedElement) { findGenericElementUsages(element, usages, myElements); - addNonCodeUsages(element, usages, getDefaultInsideDeletedCondition(myElements)); + addNonCodeUsages(element, usages, getDefaultInsideDeletedCondition(myElements), mySearchNonJava, mySearchInCommentsAndStrings); } } final UsageInfo[] result = usages.toArray(new UsageInfo[usages.size()]); @@ -421,7 +422,11 @@ public class SafeDeleteProcessor extends BaseRefactoringProcessor { } - private void addNonCodeUsages(final PsiElement element, List usages, @Nullable final Condition insideElements) { + public static void addNonCodeUsages(final PsiElement element, + List usages, + @Nullable final Condition insideElements, + boolean searchNonJava, + boolean searchInCommentsAndStrings) { UsageInfoFactory nonCodeUsageFactory = new UsageInfoFactory() { @Override public UsageInfo createUsageInfo(@NotNull PsiElement usage, int startOffset, int endOffset) { @@ -431,13 +436,13 @@ public class SafeDeleteProcessor extends BaseRefactoringProcessor { return new SafeDeleteReferenceSimpleDeleteUsageInfo(usage, element, startOffset, endOffset, true, false); } }; - if (mySearchInCommentsAndStrings) { + if (searchInCommentsAndStrings) { String stringToSearch = ElementDescriptionUtil.getElementDescription(element, NonCodeSearchDescriptionLocation.STRINGS_AND_COMMENTS); TextOccurrencesUtil.addUsagesInStringsAndComments(element, stringToSearch, usages, nonCodeUsageFactory); } - if (mySearchNonJava) { + if (searchNonJava) { String stringToSearch = ElementDescriptionUtil.getElementDescription(element, NonCodeSearchDescriptionLocation.NON_JAVA); - TextOccurrencesUtil.addTextOccurences(element, stringToSearch, GlobalSearchScope.projectScope(myProject), usages, nonCodeUsageFactory); + TextOccurrencesUtil.addTextOccurences(element, stringToSearch, GlobalSearchScope.projectScope(element.getProject()), usages, nonCodeUsageFactory); } }