/* * 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)); } } }