/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.plugin.ij.refactor.signature;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Comparing;
import com.intellij.psi.*;
import com.intellij.psi.javadoc.PsiDocTagValue;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.searches.MethodReferencesSearch;
import com.intellij.psi.search.searches.OverridingMethodsSearch;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.xml.XmlElement;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.changeSignature.*;
import com.intellij.refactoring.rename.JavaUnresolvableLocalCollisionDetector;
import com.intellij.refactoring.rename.UnresolvableCollisionUsageInfo;
import com.intellij.refactoring.util.MoveRenameUsageInfo;
import com.intellij.refactoring.util.RefactoringUIUtil;
import com.intellij.refactoring.util.RefactoringUtil;
import com.intellij.refactoring.util.usageInfo.DefaultConstructorImplicitUsageInfo;
import com.intellij.refactoring.util.usageInfo.NoConstructorClassUsageInfo;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;
class GosuChangeSignatureUsageSearcher {
private final JavaChangeInfo myChangeInfo;
private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.changeSignature.GosuChangeSignatureUsageSearcher");
GosuChangeSignatureUsageSearcher(JavaChangeInfo changeInfo) {
this.myChangeInfo = changeInfo;
}
public UsageInfo[] findUsages() {
ArrayList<UsageInfo> result = new ArrayList<UsageInfo>();
final PsiElement element = myChangeInfo.getMethod();
if (element instanceof PsiMethod) {
final PsiMethod method = (PsiMethod)element;
findSimpleUsages(method, result);
final UsageInfo[] usageInfos = result.toArray(new UsageInfo[result.size()]);
return UsageViewUtil.removeDuplicatedUsages(usageInfos);
}
return UsageInfo.EMPTY_ARRAY;
}
private void findSimpleUsages(final PsiMethod method, final ArrayList<UsageInfo> result) {
PsiMethod[] overridingMethods = findSimpleUsagesWithoutParameters(method, result, true, true, true);
findUsagesInCallers(result);
//Parameter name changes are not propagated
findParametersUsage(method, result, overridingMethods);
}
private void findUsagesInCallers(final ArrayList<UsageInfo> usages) {
if (myChangeInfo instanceof JavaChangeInfo) {
JavaChangeInfo changeInfo = (JavaChangeInfo)myChangeInfo;
Set<PsiMethod> merged = new HashSet<PsiMethod>();
merged.addAll(changeInfo.getMethodsToPropagateParameters());
for (final PsiMethod method : merged) {
findSimpleUsagesWithoutParameters(method, usages, changeInfo.getMethodsToPropagateParameters().contains(method),
false, false);
}
}
}
private void detectLocalsCollisionsInMethod(final PsiMethod method, final ArrayList<UsageInfo> result, boolean isOriginal) {
if (!JavaLanguage.INSTANCE.equals(method.getLanguage())) return;
final PsiParameter[] parameters = method.getParameterList().getParameters();
final Set<PsiParameter> deletedOrRenamedParameters = new HashSet<PsiParameter>();
if (isOriginal) {
ContainerUtil.addAll(deletedOrRenamedParameters, parameters);
for (ParameterInfo parameterInfo : myChangeInfo.getNewParameters()) {
if (parameterInfo.getOldIndex() >= 0 && parameterInfo.getOldIndex() < parameters.length) {
final PsiParameter parameter = parameters[parameterInfo.getOldIndex()];
if (parameterInfo.getName().equals(parameter.getName())) {
deletedOrRenamedParameters.remove(parameter);
}
}
}
}
for (ParameterInfo parameterInfo : myChangeInfo.getNewParameters()) {
final int oldParameterIndex = parameterInfo.getOldIndex();
final String newName = parameterInfo.getName();
if (oldParameterIndex >= 0 ) {
if (isOriginal && oldParameterIndex < parameters.length && !newName.equals(myChangeInfo.getOldParameterNames()[oldParameterIndex])) {
//Name changes take place only in primary method when name was actually changed
final PsiParameter parameter = parameters[oldParameterIndex];
if (!newName.equals(parameter.getName())) {
JavaUnresolvableLocalCollisionDetector.visitLocalsCollisions(
parameter, newName, method.getBody(), null,
new JavaUnresolvableLocalCollisionDetector.CollidingVariableVisitor() {
public void visitCollidingElement(final PsiVariable collidingVariable) {
if (!deletedOrRenamedParameters.contains(collidingVariable)) {
result.add(new RenamedParameterCollidesWithLocalUsageInfo(parameter, collidingVariable, method));
}
}
});
}
}
}
else {
JavaUnresolvableLocalCollisionDetector.visitLocalsCollisions(
method, newName, method.getBody(), null,
new JavaUnresolvableLocalCollisionDetector.CollidingVariableVisitor() {
public void visitCollidingElement(PsiVariable collidingVariable) {
if (!deletedOrRenamedParameters.contains(collidingVariable)) {
result.add(new NewParameterCollidesWithLocalUsageInfo(
collidingVariable, collidingVariable, method));
}
}
});
}
}
}
private void findParametersUsage(final PsiMethod method, ArrayList<UsageInfo> result, PsiMethod[] overriders) {
if (JavaLanguage.INSTANCE.equals(myChangeInfo.getLanguage())) {
PsiParameter[] parameters = method.getParameterList().getParameters();
for (ParameterInfo info : myChangeInfo.getNewParameters()) {
if (info.getOldIndex() >= 0) {
PsiParameter parameter = parameters[info.getOldIndex()];
if (!info.getName().equals(parameter.getName())) {
addParameterUsages(parameter, result, info);
for (PsiMethod overrider : overriders) {
PsiParameter parameter1 = overrider.getParameterList().getParameters()[info.getOldIndex()];
if (parameter1 != null && Comparing.strEqual(parameter.getName(), parameter1.getName())) {
addParameterUsages(parameter1, result, info);
}
}
}
}
}
}
}
private static boolean shouldPropagateToNonPhysicalMethod(PsiMethod method,
ArrayList<UsageInfo> result,
PsiClass containingClass,
final Collection<PsiMethod> propagateMethods) {
for (PsiMethod psiMethod : propagateMethods) {
if (!psiMethod.isPhysical() && Comparing.strEqual(psiMethod.getName(), containingClass.getName())) {
result.add(new DefaultConstructorImplicitUsageInfo(psiMethod, containingClass, method));
return true;
}
}
return false;
}
private PsiMethod[] findSimpleUsagesWithoutParameters(final PsiMethod method,
final ArrayList<UsageInfo> result,
boolean isToModifyArgs,
boolean isToThrowExceptions,
boolean isOriginal) {
GlobalSearchScope projectScope = GlobalSearchScope.projectScope(method.getProject());
PsiMethod[] overridingMethods = OverridingMethodsSearch.search(method, true).toArray(PsiMethod.EMPTY_ARRAY);
for (PsiMethod overridingMethod : overridingMethods) {
result.add(new OverriderUsageInfo(overridingMethod, method, isOriginal, isToModifyArgs, isToThrowExceptions));
}
boolean needToChangeCalls =
!myChangeInfo.isGenerateDelegate() && (myChangeInfo.isNameChanged() ||
myChangeInfo.isParameterSetOrOrderChanged() ||
myChangeInfo.isExceptionSetOrOrderChanged() ||
myChangeInfo.isVisibilityChanged()/*for checking inaccessible*/);
if (needToChangeCalls) {
int parameterCount = method.getParameterList().getParametersCount();
PsiReference[] refs = MethodReferencesSearch.search(method, projectScope, true).toArray(PsiReference.EMPTY_ARRAY);
for (PsiReference ref : refs) {
PsiElement element = ref.getElement();
boolean isToCatchExceptions = isToThrowExceptions && needToCatchExceptions(RefactoringUtil.getEnclosingMethod(element));
if (!isToCatchExceptions) {
if (RefactoringUtil.isMethodUsage(element)) {
PsiExpressionList list = RefactoringUtil.getArgumentListByMethodReference(element);
if (list == null || !method.isVarArgs() && list.getExpressions().length != parameterCount) continue;
}
}
if (RefactoringUtil.isMethodUsage(element)) {
result.add(new MethodCallUsageInfo(element, isToModifyArgs, isToCatchExceptions));
}
else if (element instanceof PsiDocTagValue) {
result.add(new UsageInfo(element));
}
else if (element instanceof PsiMethod && ((PsiMethod)element).isConstructor()) {
if (JavaLanguage.INSTANCE.equals(element.getLanguage())) {
DefaultConstructorImplicitUsageInfo implicitUsageInfo =
new DefaultConstructorImplicitUsageInfo((PsiMethod)element, ((PsiMethod)element).getContainingClass(), method);
result.add(implicitUsageInfo);
}
}
else if (element instanceof PsiClass) {
LOG.assertTrue(method.isConstructor());
final PsiClass psiClass = (PsiClass)element;
if (JavaLanguage.INSTANCE.equals(psiClass.getLanguage())) {
if (myChangeInfo instanceof JavaChangeInfo) {
if (shouldPropagateToNonPhysicalMethod(method, result, psiClass,
((JavaChangeInfo)myChangeInfo).getMethodsToPropagateParameters())) {
continue;
}
}
result.add(new NoConstructorClassUsageInfo(psiClass));
}
}
else if (ref instanceof PsiCallReference) {
result.add(new CallReferenceUsageInfo((PsiCallReference)ref));
}
else {
result.add(new MoveRenameUsageInfo(element, ref, method));
}
}
//if (method.isConstructor() && parameterCount == 0) {
// RefactoringUtil.visitImplicitConstructorUsages(method.getContainingClass(),
// new DefaultConstructorUsageCollector(result));
//}
}
else if (myChangeInfo.isParameterTypesChanged()) {
PsiReference[] refs = MethodReferencesSearch.search(method, projectScope, true).toArray(PsiReference.EMPTY_ARRAY);
for (PsiReference reference : refs) {
final PsiElement element = reference.getElement();
if (element instanceof PsiDocTagValue) {
result.add(new UsageInfo(reference));
}
else if (element instanceof XmlElement) {
result.add(new MoveRenameUsageInfo(reference, method));
}
else if (element instanceof PsiMethodReferenceExpression) {
result.add(new UsageInfo(reference));
}
}
}
// Conflicts
detectLocalsCollisionsInMethod(method, result, isOriginal);
for (final PsiMethod overridingMethod : overridingMethods) {
detectLocalsCollisionsInMethod(overridingMethod, result, isOriginal);
}
return overridingMethods;
}
private static void addParameterUsages(PsiParameter parameter, ArrayList<UsageInfo> results, ParameterInfo info) {
PsiManager manager = parameter.getManager();
GlobalSearchScope projectScope = GlobalSearchScope.projectScope(manager.getProject());
for (PsiReference psiReference : ReferencesSearch.search(parameter, projectScope, false)) {
PsiElement parmRef = psiReference.getElement();
UsageInfo usageInfo = new ChangeSignatureParameterUsageInfo(parmRef, parameter.getName(), info.getName());
results.add(usageInfo);
}
}
private boolean needToCatchExceptions(PsiMethod caller) {
if (myChangeInfo instanceof JavaChangeInfo) {
return myChangeInfo.isExceptionSetOrOrderChanged() &&
!((JavaChangeInfo)myChangeInfo).getMethodsToPropagateParameters().contains(caller);
}
else {
return myChangeInfo.isExceptionSetOrOrderChanged();
}
}
private static class RenamedParameterCollidesWithLocalUsageInfo extends UnresolvableCollisionUsageInfo {
private final PsiElement myCollidingElement;
private final PsiMethod myMethod;
public RenamedParameterCollidesWithLocalUsageInfo(PsiParameter parameter, PsiElement collidingElement, PsiMethod method) {
super(parameter, collidingElement);
myCollidingElement = collidingElement;
myMethod = method;
}
public String getDescription() {
return RefactoringBundle.message("there.is.already.a.0.in.the.1.it.will.conflict.with.the.renamed.parameter",
RefactoringUIUtil.getDescription(myCollidingElement, true),
RefactoringUIUtil.getDescription(myMethod, true));
}
}
}