deep delete parameter in method call hierarchy

This commit is contained in:
Anna Kozlova
2014-11-20 11:49:13 +01:00
parent 7f63e0b5bc
commit 2382102e7b
12 changed files with 375 additions and 9 deletions
@@ -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<UsageInfo> result = new ArrayList<UsageInfo>();
final ArrayList<UsageInfo> result = new ArrayList<UsageInfo>();
ArrayList<UsageInfo> overridingMethods = new ArrayList<UsageInfo>();
ArrayList<UsageInfo> delegatingParams = new ArrayList<UsageInfo>();
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<UsageInfo> 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<PsiReference>() {
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;
}
@@ -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<UsageInfo> myResult;
public SafeDeleteJavaCallerChooser(PsiMethod method, Project project, int parameterIndex, ArrayList<UsageInfo> 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<PsiMethod> called,
Runnable cancelCallback) {
return new SafeDeleteJavaMethodNode(nodeMethod, called, cancelCallback, myParameterIndex, nodeMethod != null ? nodeMethod.getProject() : myProject);
}
@Override
protected void doOKAction() {
final List<UsageInfo> foreignMethodUsages = new ArrayList<UsageInfo>();
final Runnable runnable = new Runnable() {
public void run() {
final Set<MethodNodeBase<PsiMethod>> nodes = getSelectedNodes();
for (MethodNodeBase<PsiMethod> 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<PsiReference>() {
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<PsiReference>() {
@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<PsiMethod> called,
Runnable cancelCallback,
int idx,
Project project) {
super(currentMethod, called, project, cancelCallback);
myCurrentMethod = currentMethod;
myParameterIdx = idx;
}
@Override
protected MethodNodeBase<PsiMethod> createNode(PsiMethod caller, HashSet<PsiMethod> called) {
return new SafeDeleteJavaMethodNode(caller, called, myCancelCallback, getParameterIndex(caller), myProject);
}
@Override
protected Condition<PsiMethod> getFilter() {
return new Condition<PsiMethod>() {
@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<PsiParameter> ref = new Ref<PsiParameter>();
ReferencesSearch.search(myCurrentMethod, new LocalSearchScope(caller)).forEach(new Processor<PsiReference>() {
@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;
}
}
}
@@ -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;
}
}
@@ -0,0 +1,8 @@
class Test {
void foo(int i) {
bar(i);
bar(i);
}
void bar(int <caret>i){}
}
@@ -0,0 +1,8 @@
class Test {
void foo() {
bar();
bar();
}
void bar(){}
}
@@ -0,0 +1,9 @@
class Test {
void foo(int i) {
bar(i);
baz(i);
}
void bar(int <caret>i){}
void baz(int i){}
}
@@ -0,0 +1,9 @@
class Test {
void foo(int i) {
bar();
baz(i);
}
void bar(){}
void baz(int i){}
}
@@ -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 <caret>i){}
}
@@ -0,0 +1,11 @@
interface I {
void foo(int i);
}
class Test implements I {
public void foo(int i) {
bar();
bar();
}
void bar(){}
}
@@ -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");
}
@@ -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<M extends PsiElement> extends DialogWrapper {
private final M myMethod;
@@ -288,6 +285,22 @@ public abstract class CallerChooserBase<M extends PsiElement> extends DialogWrap
}
}
protected Set<MethodNodeBase<M>> getSelectedNodes() {
final Set<MethodNodeBase<M>> nodes = new LinkedHashSet<MethodNodeBase<M>>();
collectSelectedNodes(myRoot, nodes);
return nodes;
}
private void collectSelectedNodes(final MethodNodeBase<M> node, final Set<MethodNodeBase<M>> 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<M> selectedMethods = new HashSet<M>();
@@ -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<M extends PsiElement> extends CheckedTreeNo
protected abstract void customizeRendererText(ColoredTreeCellRenderer renderer);
protected Condition<M> getFilter() {
return Condition.TRUE;
}
protected MethodNodeBase(final M method, Set<M> called, Project project, Runnable cancelCallback) {
super(method);
myMethod = method;
@@ -92,7 +98,7 @@ public abstract class MethodNodeBase<M extends PsiElement> extends CheckedTreeNo
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
callers.set(computeCallers());
callers.set(ContainerUtil.filter(computeCallers(), getFilter()));
}
});
}