blob: 6a44f2529284fee2317de044adfe22954b19a210 [file] [log] [blame]
/*
* 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.changeSignature;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInsight.ExceptionUtil;
import com.intellij.codeInsight.InferredAnnotationsManager;
import com.intellij.codeInsight.daemon.impl.analysis.JavaHighlightUtil;
import com.intellij.codeInspection.dataFlow.ControlFlowAnalyzer;
import com.intellij.lang.StdLanguages;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.scope.processor.VariablesProcessor;
import com.intellij.psi.scope.util.PsiScopesUtil;
import com.intellij.psi.util.*;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.rename.RenameUtil;
import com.intellij.refactoring.rename.ResolveSnapshotProvider;
import com.intellij.refactoring.util.*;
import com.intellij.refactoring.util.usageInfo.DefaultConstructorImplicitUsageInfo;
import com.intellij.refactoring.util.usageInfo.NoConstructorClassUsageInfo;
import com.intellij.usageView.UsageInfo;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.VisibilityUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashSet;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* @author Maxim.Medvedev
*/
public class JavaChangeSignatureUsageProcessor implements ChangeSignatureUsageProcessor {
private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.changeSignature.JavaChangeSignatureUsageProcessor");
private static boolean isJavaUsage(UsageInfo info) {
final PsiElement element = info.getElement();
if (element == null) return false;
return element.getLanguage() == StdLanguages.JAVA;
}
@Override
public UsageInfo[] findUsages(ChangeInfo info) {
if (info instanceof JavaChangeInfo) {
return new JavaChangeSignatureUsageSearcher((JavaChangeInfo)info).findUsages();
}
else {
return UsageInfo.EMPTY_ARRAY;
}
}
@Override
public MultiMap<PsiElement, String> findConflicts(ChangeInfo info, Ref<UsageInfo[]> refUsages) {
if (info instanceof JavaChangeInfo) {
return new ConflictSearcher((JavaChangeInfo)info).findConflicts(refUsages);
}
else {
return new MultiMap<PsiElement, String>();
}
}
@Override
public boolean processUsage(ChangeInfo changeInfo, UsageInfo usage, boolean beforeMethodChange, UsageInfo[] usages) {
if (!isJavaUsage(usage)) return false;
if (!(changeInfo instanceof JavaChangeInfo)) return false;
if (beforeMethodChange) {
if (usage instanceof CallerUsageInfo) {
final CallerUsageInfo callerUsageInfo = (CallerUsageInfo)usage;
processCallerMethod((JavaChangeInfo)changeInfo, callerUsageInfo.getMethod(), null, callerUsageInfo.isToInsertParameter(),
callerUsageInfo.isToInsertException());
return true;
}
else if (usage instanceof OverriderUsageInfo) {
OverriderUsageInfo info = (OverriderUsageInfo)usage;
final PsiMethod method = info.getElement();
final PsiMethod baseMethod = info.getBaseMethod();
if (info.isOriginalOverrider()) {
processPrimaryMethod((JavaChangeInfo)changeInfo, method, baseMethod, false);
}
else {
processCallerMethod((JavaChangeInfo)changeInfo, method, baseMethod, info.isToInsertArgs(), info.isToCatchExceptions());
}
return true;
}
}
else {
PsiElement element = usage.getElement();
LOG.assertTrue(element != null);
if (usage instanceof DefaultConstructorImplicitUsageInfo) {
final DefaultConstructorImplicitUsageInfo defConstructorUsage = (DefaultConstructorImplicitUsageInfo)usage;
PsiMethod constructor = defConstructorUsage.getConstructor();
if (!constructor.isPhysical()) {
final boolean toPropagate =
changeInfo instanceof JavaChangeInfoImpl && ((JavaChangeInfoImpl)changeInfo).propagateParametersMethods.remove(constructor);
final PsiClass containingClass = defConstructorUsage.getContainingClass();
constructor = (PsiMethod)containingClass.add(constructor);
PsiUtil.setModifierProperty(constructor, VisibilityUtil.getVisibilityModifier(containingClass.getModifierList()), true);
if (toPropagate) {
((JavaChangeInfoImpl)changeInfo).propagateParametersMethods.add(constructor);
}
}
addSuperCall((JavaChangeInfo)changeInfo, constructor, defConstructorUsage.getBaseConstructor(), usages);
return true;
}
else if (usage instanceof NoConstructorClassUsageInfo) {
addDefaultConstructor(((JavaChangeInfo)changeInfo), ((NoConstructorClassUsageInfo)usage).getPsiClass(), usages);
return true;
}
else if (usage instanceof MethodCallUsageInfo) {
final MethodCallUsageInfo methodCallInfo = (MethodCallUsageInfo)usage;
processMethodUsage(methodCallInfo.getElement(), (JavaChangeInfo)changeInfo, methodCallInfo.isToChangeArguments(),
methodCallInfo.isToCatchExceptions(), methodCallInfo.getReferencedMethod(), methodCallInfo.getSubstitutor(), usages);
return true;
}
else if (usage instanceof ChangeSignatureParameterUsageInfo) {
String newName = ((ChangeSignatureParameterUsageInfo)usage).newParameterName;
String oldName = ((ChangeSignatureParameterUsageInfo)usage).oldParameterName;
processParameterUsage((PsiReferenceExpression)element, oldName, newName);
return true;
}
else if (usage instanceof CallReferenceUsageInfo) {
((CallReferenceUsageInfo)usage).getReference().handleChangeSignature(changeInfo);
return true;
}
else if (element instanceof PsiEnumConstant) {
fixActualArgumentsList(((PsiEnumConstant)element).getArgumentList(), (JavaChangeInfo)changeInfo, true, PsiSubstitutor.EMPTY);
return true;
}
else if (!(usage instanceof OverriderUsageInfo)) {
PsiReference reference = usage instanceof MoveRenameUsageInfo ? usage.getReference() : element.getReference();
if (reference != null) {
PsiElement target = changeInfo.getMethod();
if (target != null) {
reference.bindToElement(target);
}
}
}
}
return false;
}
private static void processParameterUsage(PsiReferenceExpression ref, String oldName, String newName)
throws IncorrectOperationException {
PsiElement last = ref.getReferenceNameElement();
if (last instanceof PsiIdentifier && last.getText().equals(oldName)) {
PsiElementFactory factory = JavaPsiFacade.getInstance(ref.getProject()).getElementFactory();
PsiIdentifier newNameIdentifier = factory.createIdentifier(newName);
last.replace(newNameIdentifier);
}
}
private static void addDefaultConstructor(JavaChangeInfo changeInfo, PsiClass aClass, final UsageInfo[] usages)
throws IncorrectOperationException {
if (!(aClass instanceof PsiAnonymousClass)) {
PsiElementFactory factory = JavaPsiFacade.getElementFactory(aClass.getProject());
PsiMethod defaultConstructor = factory.createMethodFromText(aClass.getName() + "(){}", aClass);
defaultConstructor = (PsiMethod)CodeStyleManager.getInstance(aClass.getProject()).reformat(defaultConstructor);
defaultConstructor = (PsiMethod)aClass.add(defaultConstructor);
PsiUtil.setModifierProperty(defaultConstructor, VisibilityUtil.getVisibilityModifier(aClass.getModifierList()), true);
addSuperCall(changeInfo, defaultConstructor, null, usages);
}
else {
final PsiElement parent = aClass.getParent();
if (parent instanceof PsiNewExpression) {
final PsiExpressionList argumentList = ((PsiNewExpression)parent).getArgumentList();
final PsiClass baseClass = changeInfo.getMethod().getContainingClass();
final PsiSubstitutor substitutor = TypeConversionUtil.getSuperClassSubstitutor(baseClass, aClass, PsiSubstitutor.EMPTY);
fixActualArgumentsList(argumentList, changeInfo, true, substitutor);
}
}
}
private static void addSuperCall(JavaChangeInfo changeInfo, PsiMethod constructor, PsiMethod callee, final UsageInfo[] usages)
throws IncorrectOperationException {
final PsiElementFactory factory = JavaPsiFacade.getElementFactory(constructor.getProject());
PsiExpressionStatement superCall = (PsiExpressionStatement)factory.createStatementFromText("super();", constructor);
PsiCodeBlock body = constructor.getBody();
assert body != null;
PsiStatement[] statements = body.getStatements();
if (statements.length > 0) {
superCall = (PsiExpressionStatement)body.addBefore(superCall, statements[0]);
}
else {
superCall = (PsiExpressionStatement)body.add(superCall);
}
PsiMethodCallExpression callExpression = (PsiMethodCallExpression)superCall.getExpression();
final PsiClass aClass = constructor.getContainingClass();
final PsiClass baseClass = changeInfo.getMethod().getContainingClass();
final PsiSubstitutor substitutor = TypeConversionUtil.getSuperClassSubstitutor(baseClass, aClass, PsiSubstitutor.EMPTY);
processMethodUsage(callExpression.getMethodExpression(), changeInfo, true, false, callee, substitutor, usages);
}
private static void processMethodUsage(PsiElement ref,
JavaChangeInfo changeInfo,
boolean toChangeArguments,
boolean toCatchExceptions,
PsiMethod callee, PsiSubstitutor subsitutor, final UsageInfo[] usages) throws IncorrectOperationException {
if (changeInfo.isNameChanged()) {
if (ref instanceof PsiJavaCodeReferenceElement) {
PsiElement last = ((PsiJavaCodeReferenceElement)ref).getReferenceNameElement();
if (last instanceof PsiIdentifier && last.getText().equals(changeInfo.getOldName())) {
last.replace(changeInfo.getNewNameIdentifier());
}
}
}
final PsiMethod caller = RefactoringUtil.getEnclosingMethod(ref);
if (toChangeArguments) {
final PsiExpressionList list = RefactoringUtil.getArgumentListByMethodReference(ref);
LOG.assertTrue(list != null);
boolean toInsertDefaultValue = needDefaultValue(changeInfo, caller);
if (toInsertDefaultValue && ref instanceof PsiReferenceExpression) {
final PsiExpression qualifierExpression = ((PsiReferenceExpression)ref).getQualifierExpression();
if (qualifierExpression instanceof PsiSuperExpression && callerSignatureIsAboutToChangeToo(caller, usages)) {
toInsertDefaultValue = false;
}
}
fixActualArgumentsList(list, changeInfo, toInsertDefaultValue, subsitutor);
}
if (toCatchExceptions) {
if (!(ref instanceof PsiReferenceExpression &&
JavaHighlightUtil.isSuperOrThisCall(PsiTreeUtil.getParentOfType(ref, PsiStatement.class), true, false))) {
if (needToCatchExceptions(changeInfo, caller)) {
PsiClassType[] newExceptions =
callee != null ? getCalleeChangedExceptionInfo(callee) : getPrimaryChangedExceptionInfo(changeInfo);
fixExceptions(ref, newExceptions);
}
}
}
}
private static boolean callerSignatureIsAboutToChangeToo(final PsiMethod caller, final UsageInfo[] usages) {
for (UsageInfo usage : usages) {
if (usage instanceof MethodCallUsageInfo &&
MethodSignatureUtil.isSuperMethod(((MethodCallUsageInfo)usage).getReferencedMethod(), caller)) {
return true;
}
}
return false;
}
private static PsiClassType[] getCalleeChangedExceptionInfo(final PsiMethod callee) {
return callee.getThrowsList().getReferencedTypes(); //Callee method's throws list is already modified!
}
private static void fixExceptions(PsiElement ref, PsiClassType[] newExceptions) throws IncorrectOperationException {
//methods' throws lists are already modified, may use ExceptionUtil.collectUnhandledExceptions
newExceptions = filterCheckedExceptions(newExceptions);
PsiElement context = PsiTreeUtil.getParentOfType(ref, PsiTryStatement.class, PsiMethod.class);
if (context instanceof PsiTryStatement) {
PsiTryStatement tryStatement = (PsiTryStatement)context;
PsiCodeBlock tryBlock = tryStatement.getTryBlock();
//Remove unused catches
Collection<PsiClassType> classes = ExceptionUtil.collectUnhandledExceptions(tryBlock, tryBlock);
PsiParameter[] catchParameters = tryStatement.getCatchBlockParameters();
for (PsiParameter parameter : catchParameters) {
final PsiType caughtType = parameter.getType();
if (!(caughtType instanceof PsiClassType)) continue;
if (ExceptionUtil.isUncheckedExceptionOrSuperclass((PsiClassType)caughtType)) continue;
if (!isCatchParameterRedundant((PsiClassType)caughtType, classes)) continue;
parameter.getParent().delete(); //delete catch section
}
PsiClassType[] exceptionsToAdd = filterUnhandledExceptions(newExceptions, tryBlock);
addExceptions(exceptionsToAdd, tryStatement);
adjustPossibleEmptyTryStatement(tryStatement);
}
else {
newExceptions = filterUnhandledExceptions(newExceptions, ref);
if (newExceptions.length > 0) {
//Add new try statement
PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(ref.getProject());
PsiTryStatement tryStatement = (PsiTryStatement)elementFactory.createStatementFromText("try {} catch (Exception e) {}", null);
PsiStatement anchor = PsiTreeUtil.getParentOfType(ref, PsiStatement.class);
LOG.assertTrue(anchor != null);
tryStatement.getTryBlock().add(anchor);
tryStatement = (PsiTryStatement)anchor.getParent().addAfter(tryStatement, anchor);
addExceptions(newExceptions, tryStatement);
anchor.delete();
tryStatement.getCatchSections()[0].delete(); //Delete dummy catch section
}
}
}
private static PsiClassType[] filterCheckedExceptions(PsiClassType[] exceptions) {
List<PsiClassType> result = new ArrayList<PsiClassType>();
for (PsiClassType exceptionType : exceptions) {
if (!ExceptionUtil.isUncheckedException(exceptionType)) result.add(exceptionType);
}
return result.toArray(new PsiClassType[result.size()]);
}
private static void adjustPossibleEmptyTryStatement(PsiTryStatement tryStatement) throws IncorrectOperationException {
PsiCodeBlock tryBlock = tryStatement.getTryBlock();
if (tryBlock != null) {
if (tryStatement.getCatchSections().length == 0 &&
tryStatement.getFinallyBlock() == null) {
PsiElement firstBodyElement = tryBlock.getFirstBodyElement();
if (firstBodyElement != null) {
tryStatement.getParent().addRangeAfter(firstBodyElement, tryBlock.getLastBodyElement(), tryStatement);
}
tryStatement.delete();
}
}
}
private static void addExceptions(PsiClassType[] exceptionsToAdd, PsiTryStatement tryStatement) throws IncorrectOperationException {
for (PsiClassType type : exceptionsToAdd) {
final JavaCodeStyleManager styleManager = JavaCodeStyleManager.getInstance(tryStatement.getProject());
String name = styleManager.suggestVariableName(VariableKind.PARAMETER, null, null, type).names[0];
name = styleManager.suggestUniqueVariableName(name, tryStatement, false);
PsiCatchSection catchSection =
JavaPsiFacade.getInstance(tryStatement.getProject()).getElementFactory().createCatchSection(type, name, tryStatement);
tryStatement.add(catchSection);
}
}
private static PsiClassType[] filterUnhandledExceptions(PsiClassType[] exceptions, PsiElement place) {
List<PsiClassType> result = new ArrayList<PsiClassType>();
for (PsiClassType exception : exceptions) {
if (!ExceptionUtil.isHandled(exception, place)) result.add(exception);
}
return result.toArray(new PsiClassType[result.size()]);
}
private static boolean isCatchParameterRedundant(PsiClassType catchParamType, Collection<PsiClassType> thrownTypes) {
for (PsiType exceptionType : thrownTypes) {
if (exceptionType.isConvertibleFrom(catchParamType)) return false;
}
return true;
}
//This methods works equally well for primary usages as well as for propagated callers' usages
private static void fixActualArgumentsList(PsiExpressionList list,
JavaChangeInfo changeInfo,
boolean toInsertDefaultValue, PsiSubstitutor substitutor) throws IncorrectOperationException {
final PsiElementFactory factory = JavaPsiFacade.getInstance(list.getProject()).getElementFactory();
if (changeInfo.isParameterSetOrOrderChanged()) {
if (changeInfo instanceof JavaChangeInfoImpl && ((JavaChangeInfoImpl)changeInfo).isPropagationEnabled) {
final ParameterInfoImpl[] createdParmsInfo = ((JavaChangeInfoImpl)changeInfo).getCreatedParmsInfoWithoutVarargs();
for (ParameterInfoImpl info : createdParmsInfo) {
PsiExpression newArg;
if (toInsertDefaultValue) {
newArg = createDefaultValue(changeInfo, factory, info, list);
}
else {
newArg = factory.createExpressionFromText(info.getName(), list);
}
if (newArg != null) JavaCodeStyleManager.getInstance(list.getProject()).shortenClassReferences(list.add(newArg));
}
}
else {
final PsiExpression[] args = list.getExpressions();
final int nonVarargCount = getNonVarargCount(changeInfo, args);
final int varargCount = args.length - nonVarargCount;
if (varargCount<0) return;
PsiExpression[] newVarargInitializers = null;
final int newArgsLength;
final int newNonVarargCount;
final JavaParameterInfo[] newParms = changeInfo.getNewParameters();
if (changeInfo.isArrayToVarargs()) {
newNonVarargCount = newParms.length - 1;
final JavaParameterInfo lastNewParm = newParms[newParms.length - 1];
final PsiExpression arrayToConvert = args[lastNewParm.getOldIndex()];
if (arrayToConvert instanceof PsiNewExpression) {
final PsiNewExpression expression = (PsiNewExpression)arrayToConvert;
final PsiArrayInitializerExpression arrayInitializer = expression.getArrayInitializer();
if (arrayInitializer != null) {
newVarargInitializers = arrayInitializer.getInitializers();
}
}
newArgsLength = newVarargInitializers == null ? newParms.length : newNonVarargCount + newVarargInitializers.length;
}
else if (changeInfo.isRetainsVarargs()) {
newNonVarargCount = newParms.length - 1;
newArgsLength = newNonVarargCount + varargCount;
}
else if (changeInfo.isObtainsVarags()) {
newNonVarargCount = newParms.length - 1;
newArgsLength = newNonVarargCount;
}
else {
newNonVarargCount = newParms.length;
newArgsLength = newParms.length;
}
String[] oldVarargs = null;
if (changeInfo.wasVararg() && !changeInfo.isRetainsVarargs()) {
oldVarargs = new String[varargCount];
for (int i = nonVarargCount; i < args.length; i++) {
oldVarargs[i - nonVarargCount] = args[i].getText();
}
}
final PsiExpression[] newArgs = new PsiExpression[newArgsLength];
for (int i = 0; i < newNonVarargCount; i++) {
if (newParms[i].getOldIndex() == nonVarargCount && oldVarargs != null) {
PsiType type = newParms[i].createType(changeInfo.getMethod(), list.getManager());
if (type instanceof PsiArrayType) {
type = substitutor.substitute(type);
type = TypeConversionUtil.erasure(type);
String typeText = type.getCanonicalText();
if (type instanceof PsiEllipsisType) {
typeText = typeText.replace("...", "[]");
}
String text = "new " + typeText + "{" + StringUtil.join(oldVarargs, ",") + "}";
newArgs[i] = factory.createExpressionFromText(text, changeInfo.getMethod());
continue;
}
}
newArgs[i] = createActualArgument(changeInfo, list, newParms[i], toInsertDefaultValue, args);
}
if (changeInfo.isArrayToVarargs()) {
if (newVarargInitializers == null) {
newArgs[newNonVarargCount] =
createActualArgument(changeInfo, list, newParms[newNonVarargCount], toInsertDefaultValue, args);
}
else {
System.arraycopy(newVarargInitializers, 0, newArgs, newNonVarargCount, newVarargInitializers.length);
}
}
else {
final int newVarargCount = newArgsLength - newNonVarargCount;
LOG.assertTrue(newVarargCount == 0 || newVarargCount == varargCount);
for (int i = newNonVarargCount; i < newArgsLength; i++){
final int oldIndex = newParms[newNonVarargCount].getOldIndex();
if (oldIndex >= 0 && oldIndex != nonVarargCount) {
newArgs[i] = createActualArgument(changeInfo, list, newParms[newNonVarargCount], toInsertDefaultValue, args);
} else {
System.arraycopy(args, nonVarargCount, newArgs, newNonVarargCount, newVarargCount);
break;
}
}
}
ChangeSignatureUtil.synchronizeList(list, Arrays.asList(newArgs), ExpressionList.INSTANCE, changeInfo.toRemoveParm());
}
}
}
private static int getNonVarargCount(JavaChangeInfo changeInfo, PsiExpression[] args) {
if (!changeInfo.wasVararg()) return args.length;
return changeInfo.getOldParameterTypes().length - 1;
}
@Nullable
private static PsiExpression createActualArgument(JavaChangeInfo changeInfo,
final PsiExpressionList list,
final JavaParameterInfo info,
final boolean toInsertDefaultValue,
final PsiExpression[] args) throws IncorrectOperationException {
final PsiElementFactory factory = JavaPsiFacade.getInstance(list.getProject()).getElementFactory();
final int index = info.getOldIndex();
if (index >= 0) {
return args[index];
}
else {
if (toInsertDefaultValue) {
return createDefaultValue(changeInfo, factory, info, list);
}
else {
return factory.createExpressionFromText(info.getName(), list);
}
}
}
@Nullable
private static PsiExpression createDefaultValue(JavaChangeInfo changeInfo,
final PsiElementFactory factory,
final JavaParameterInfo info,
final PsiExpressionList list)
throws IncorrectOperationException {
if (info.isUseAnySingleVariable()) {
final PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(list.getProject()).getResolveHelper();
final PsiType type = info.getTypeWrapper().getType(changeInfo.getMethod(), list.getManager());
final VariablesProcessor processor = new VariablesProcessor(false) {
@Override
protected boolean check(PsiVariable var, ResolveState state) {
if (var instanceof PsiField && !resolveHelper.isAccessible((PsiField)var, list, null)) return false;
if (var instanceof PsiLocalVariable && list.getTextRange().getStartOffset() <= var.getTextRange().getStartOffset()) return false;
if (PsiTreeUtil.isAncestor(var, list, false)) return false;
final PsiType varType = state.get(PsiSubstitutor.KEY).substitute(var.getType());
return type.isAssignableFrom(varType);
}
@Override
public boolean execute(@NotNull PsiElement pe, @NotNull ResolveState state) {
super.execute(pe, state);
return size() < 2;
}
};
PsiScopesUtil.treeWalkUp(processor, list, null);
if (processor.size() == 1) {
final PsiVariable result = processor.getResult(0);
return factory.createExpressionFromText(result.getName(), list);
}
if (processor.size() == 0) {
final PsiClass parentClass = PsiTreeUtil.getParentOfType(list, PsiClass.class);
if (parentClass != null) {
PsiClass containingClass = parentClass;
final Set<PsiClass> containingClasses = new HashSet<PsiClass>();
while (containingClass != null) {
if (type.isAssignableFrom(factory.createType(containingClass, PsiSubstitutor.EMPTY))) {
containingClasses.add(containingClass);
}
containingClass = PsiTreeUtil.getParentOfType(containingClass, PsiClass.class);
}
if (containingClasses.size() == 1) {
return RefactoringChangeUtil.createThisExpression(parentClass.getManager(), containingClasses.contains(parentClass) ? null
: containingClasses
.iterator().next());
}
}
}
}
final PsiCallExpression callExpression = PsiTreeUtil.getParentOfType(list, PsiCallExpression.class);
final String defaultValue = info.getDefaultValue();
return callExpression != null ? info.getValue(callExpression) : !defaultValue.isEmpty()
? factory.createExpressionFromText(defaultValue, list) : null;
}
@Override
public boolean processPrimaryMethod(ChangeInfo changeInfo) {
if (!StdLanguages.JAVA.equals(changeInfo.getLanguage()) || !(changeInfo instanceof JavaChangeInfo)) return false;
final PsiElement element = changeInfo.getMethod();
LOG.assertTrue(element instanceof PsiMethod);
if (changeInfo.isGenerateDelegate()) {
generateDelegate((JavaChangeInfo)changeInfo);
}
processPrimaryMethod((JavaChangeInfo)changeInfo, (PsiMethod)element, null, true);
return true;
}
@Override
public boolean shouldPreviewUsages(ChangeInfo changeInfo, UsageInfo[] usages) {
return false;
}
@Override
public boolean setupDefaultValues(ChangeInfo changeInfo, Ref<UsageInfo[]> refUsages, Project project) {
if (!(changeInfo instanceof JavaChangeInfo)) return true;
for (UsageInfo usageInfo : refUsages.get()) {
if (usageInfo instanceof MethodCallUsageInfo) {
MethodCallUsageInfo methodCallUsageInfo = (MethodCallUsageInfo)usageInfo;
if (methodCallUsageInfo.isToChangeArguments()){
final PsiElement element = methodCallUsageInfo.getElement();
if (element == null) continue;
final PsiMethod caller = RefactoringUtil.getEnclosingMethod(element);
final boolean needDefaultValue = needDefaultValue(changeInfo, caller);
if (needDefaultValue && (caller == null || !MethodSignatureUtil.isSuperMethod(methodCallUsageInfo.getReferencedMethod(), caller))) {
final ParameterInfo[] parameters = changeInfo.getNewParameters();
for (ParameterInfo parameter : parameters) {
final String defaultValue = parameter.getDefaultValue();
if (defaultValue == null && parameter.getOldIndex() == -1) {
((ParameterInfoImpl)parameter).setDefaultValue("");
if (!ApplicationManager.getApplication().isUnitTestMode()) {
final PsiType type = ((ParameterInfoImpl)parameter).getTypeWrapper().getType(element, element.getManager());
final DefaultValueChooser chooser = new DefaultValueChooser(project, parameter.getName(), PsiTypesUtil.getDefaultValueOfType(type));
chooser.show();
if (chooser.isOK()) {
if (chooser.feelLucky()) {
parameter.setUseAnySingleVariable(true);
} else {
((ParameterInfoImpl)parameter).setDefaultValue(chooser.getDefaultValue());
}
} else {
return false;
}
}
}
}
}
}
}
}
return true;
}
@Override
public void registerConflictResolvers(List<ResolveSnapshotProvider.ResolveSnapshot> snapshots,
@NotNull ResolveSnapshotProvider resolveSnapshotProvider,
UsageInfo[] usages, ChangeInfo changeInfo) {
snapshots.add(resolveSnapshotProvider.createSnapshot(changeInfo.getMethod()));
for (UsageInfo usage : usages) {
if (usage instanceof OverriderUsageInfo) {
snapshots.add(resolveSnapshotProvider.createSnapshot(usage.getElement()));
}
}
}
private static boolean needDefaultValue(ChangeInfo changeInfo, PsiMethod method) {
return !(changeInfo instanceof JavaChangeInfoImpl) ||
!((JavaChangeInfoImpl)changeInfo).propagateParametersMethods.contains(method);
}
private static void generateDelegate(JavaChangeInfo changeInfo) throws IncorrectOperationException {
final PsiMethod delegate = (PsiMethod)changeInfo.getMethod().copy();
final PsiClass targetClass = changeInfo.getMethod().getContainingClass();
LOG.assertTrue(!targetClass.isInterface());
PsiElementFactory factory = JavaPsiFacade.getElementFactory(targetClass.getProject());
ChangeSignatureProcessor.makeEmptyBody(factory, delegate);
final PsiCallExpression callExpression = ChangeSignatureProcessor.addDelegatingCallTemplate(delegate, changeInfo.getNewName());
addDelegateArguments(changeInfo, factory, callExpression);
targetClass.addBefore(delegate, changeInfo.getMethod());
}
private static void addDelegateArguments(JavaChangeInfo changeInfo, PsiElementFactory factory, final PsiCallExpression callExpression) throws IncorrectOperationException {
final JavaParameterInfo[] newParms = changeInfo.getNewParameters();
final String[] oldParameterNames = changeInfo.getOldParameterNames();
for (int i = 0; i < newParms.length; i++) {
JavaParameterInfo newParm = newParms[i];
final PsiExpression actualArg;
if (newParm.getOldIndex() >= 0) {
actualArg = factory.createExpressionFromText(oldParameterNames[newParm.getOldIndex()], callExpression);
}
else {
actualArg = changeInfo.getValue(i, callExpression);
}
final PsiExpressionList argumentList = callExpression.getArgumentList();
if (actualArg != null && argumentList != null) {
JavaCodeStyleManager.getInstance(callExpression.getProject()).shortenClassReferences(argumentList.add(actualArg));
}
}
}
private static void processPrimaryMethod(JavaChangeInfo changeInfo, PsiMethod method,
PsiMethod baseMethod,
boolean isOriginal) throws IncorrectOperationException {
PsiElementFactory factory = JavaPsiFacade.getInstance(method.getProject()).getElementFactory();
if (changeInfo.isVisibilityChanged()) {
PsiModifierList modifierList = method.getModifierList();
final String highestVisibility = isOriginal
? changeInfo.getNewVisibility()
: VisibilityUtil.getHighestVisibility(changeInfo.getNewVisibility(),
VisibilityUtil.getVisibilityModifier(modifierList));
VisibilityUtil.setVisibility(modifierList, highestVisibility);
}
if (changeInfo.isNameChanged()) {
String newName = baseMethod == null ? changeInfo.getNewName() :
RefactoringUtil.suggestNewOverriderName(method.getName(), baseMethod.getName(), changeInfo.getNewName());
if (newName != null && !newName.equals(method.getName())) {
final PsiIdentifier nameId = method.getNameIdentifier();
assert nameId != null;
nameId.replace(JavaPsiFacade.getInstance(method.getProject()).getElementFactory().createIdentifier(newName));
}
}
final PsiSubstitutor substitutor =
baseMethod == null ? PsiSubstitutor.EMPTY : ChangeSignatureProcessor.calculateSubstitutor(method, baseMethod);
if (changeInfo.isReturnTypeChanged()) {
PsiType newTypeElement = changeInfo.getNewReturnType().getType(changeInfo.getMethod().getParameterList(), method.getManager());
final PsiType returnType = substitutor.substitute(newTypeElement);
// don't modify return type for non-Java overriders (EJB)
if (method.getName().equals(changeInfo.getNewName())) {
final PsiTypeElement typeElement = method.getReturnTypeElement();
if (typeElement != null) {
typeElement.replace(factory.createTypeElement(returnType));
}
}
}
PsiParameterList list = method.getParameterList();
PsiParameter[] parameters = list.getParameters();
final JavaParameterInfo[] parameterInfos = changeInfo.getNewParameters();
final int delta = baseMethod != null ? baseMethod.getParameterList().getParametersCount() - method.getParameterList().getParametersCount() : 0;
PsiParameter[] newParms = new PsiParameter[Math.max(parameterInfos.length - delta, 0)];
final String[] oldParameterNames = changeInfo.getOldParameterNames();
final String[] oldParameterTypes = changeInfo.getOldParameterTypes();
for (int i = 0; i < newParms.length; i++) {
JavaParameterInfo info = parameterInfos[i];
int index = info.getOldIndex();
if (index >= 0) {
PsiParameter parameter = parameters[index];
newParms[i] = parameter;
String oldName = oldParameterNames[index];
if (!oldName.equals(info.getName()) && oldName.equals(parameter.getName())) {
PsiIdentifier newIdentifier = factory.createIdentifier(info.getName());
parameter.getNameIdentifier().replace(newIdentifier);
}
String oldType = oldParameterTypes[index];
if (!oldType.equals(info.getTypeText())) {
parameter.normalizeDeclaration();
PsiType newType = substitutor.substitute(info.createType(changeInfo.getMethod().getParameterList(), method.getManager()));
parameter.getTypeElement().replace(factory.createTypeElement(newType));
}
}
else {
newParms[i] = createNewParameter(changeInfo, info, substitutor);
}
}
resolveParameterVsFieldsConflicts(newParms, method, list, changeInfo.toRemoveParm());
fixJavadocsForChangedMethod(method, changeInfo, newParms.length);
if (changeInfo.isExceptionSetOrOrderChanged()) {
final PsiClassType[] newExceptions = getPrimaryChangedExceptionInfo(changeInfo);
fixPrimaryThrowsLists(method, newExceptions);
}
}
private static PsiClassType[] getPrimaryChangedExceptionInfo(JavaChangeInfo changeInfo) throws IncorrectOperationException {
final ThrownExceptionInfo[] newExceptionInfos = changeInfo.getNewExceptions();
PsiClassType[] newExceptions = new PsiClassType[newExceptionInfos.length];
final PsiMethod method = changeInfo.getMethod();
for (int i = 0; i < newExceptions.length; i++) {
newExceptions[i] =
(PsiClassType)newExceptionInfos[i].createType(method, method.getManager()); //context really does not matter here
}
return newExceptions;
}
private static void processCallerMethod(JavaChangeInfo changeInfo, PsiMethod caller,
PsiMethod baseMethod,
boolean toInsertParams,
boolean toInsertThrows) throws IncorrectOperationException {
LOG.assertTrue(toInsertParams || toInsertThrows);
if (toInsertParams) {
List<PsiParameter> newParameters = new ArrayList<PsiParameter>();
ContainerUtil.addAll(newParameters, caller.getParameterList().getParameters());
final JavaParameterInfo[] primaryNewParms = changeInfo.getNewParameters();
PsiSubstitutor substitutor =
baseMethod == null ? PsiSubstitutor.EMPTY : ChangeSignatureProcessor.calculateSubstitutor(caller, baseMethod);
final PsiClass aClass = changeInfo.getMethod().getContainingClass();
final PsiClass callerContainingClass = caller.getContainingClass();
final PsiSubstitutor psiSubstitutor = aClass != null && callerContainingClass != null && callerContainingClass.isInheritor(aClass, true)
? TypeConversionUtil.getSuperClassSubstitutor(aClass, callerContainingClass, substitutor)
: PsiSubstitutor.EMPTY;
for (JavaParameterInfo info : primaryNewParms) {
if (info.getOldIndex() < 0) newParameters.add(createNewParameter(changeInfo, info, psiSubstitutor, substitutor));
}
PsiParameter[] arrayed = newParameters.toArray(new PsiParameter[newParameters.size()]);
boolean[] toRemoveParm = new boolean[arrayed.length];
Arrays.fill(toRemoveParm, false);
resolveParameterVsFieldsConflicts(arrayed, caller, caller.getParameterList(), toRemoveParm);
}
if (toInsertThrows) {
List<PsiJavaCodeReferenceElement> newThrowns = new ArrayList<PsiJavaCodeReferenceElement>();
final PsiReferenceList throwsList = caller.getThrowsList();
ContainerUtil.addAll(newThrowns, throwsList.getReferenceElements());
final ThrownExceptionInfo[] primaryNewExns = changeInfo.getNewExceptions();
for (ThrownExceptionInfo thrownExceptionInfo : primaryNewExns) {
if (thrownExceptionInfo.getOldIndex() < 0) {
final PsiClassType type = (PsiClassType)thrownExceptionInfo.createType(caller, caller.getManager());
final PsiJavaCodeReferenceElement ref =
JavaPsiFacade.getInstance(caller.getProject()).getElementFactory().createReferenceElementByType(type);
newThrowns.add(ref);
}
}
PsiJavaCodeReferenceElement[] arrayed = newThrowns.toArray(new PsiJavaCodeReferenceElement[newThrowns.size()]);
boolean[] toRemoveParm = new boolean[arrayed.length];
Arrays.fill(toRemoveParm, false);
ChangeSignatureUtil.synchronizeList(throwsList, Arrays.asList(arrayed), ThrowsList.INSTANCE, toRemoveParm);
}
}
private static void fixPrimaryThrowsLists(PsiMethod method, PsiClassType[] newExceptions) throws IncorrectOperationException {
PsiElementFactory elementFactory = JavaPsiFacade.getInstance(method.getProject()).getElementFactory();
PsiJavaCodeReferenceElement[] refs = new PsiJavaCodeReferenceElement[newExceptions.length];
for (int i = 0; i < refs.length; i++) {
refs[i] = elementFactory.createReferenceElementByType(newExceptions[i]);
}
PsiReferenceList throwsList = elementFactory.createReferenceList(refs);
PsiReferenceList methodThrowsList = (PsiReferenceList)method.getThrowsList().replace(throwsList);
methodThrowsList = (PsiReferenceList)JavaCodeStyleManager.getInstance(method.getProject()).shortenClassReferences(methodThrowsList);
CodeStyleManager.getInstance(method.getManager().getProject())
.reformatRange(method, method.getParameterList().getTextRange().getEndOffset(),
methodThrowsList.getTextRange().getEndOffset());
}
private static void fixJavadocsForChangedMethod(final PsiMethod method, final JavaChangeInfo changeInfo, int newParamsLength) throws IncorrectOperationException {
final PsiParameter[] parameters = method.getParameterList().getParameters();
final JavaParameterInfo[] newParms = changeInfo.getNewParameters();
LOG.assertTrue(parameters.length <= newParamsLength);
final Set<PsiParameter> newParameters = new HashSet<PsiParameter>();
final String[] oldParameterNames = changeInfo.getOldParameterNames();
for (int i = 0; i < newParamsLength; i++) {
JavaParameterInfo newParm = newParms[i];
if (newParm.getOldIndex() < 0 ||
newParm.getOldIndex() == i && !(newParm.getName().equals(oldParameterNames[newParm.getOldIndex()]) && newParm.getTypeText().equals(changeInfo.getOldParameterTypes()[newParm.getOldIndex()]))) {
newParameters.add(parameters[i]);
}
}
RefactoringUtil.fixJavadocsForParams(method, newParameters, new Condition<Pair<PsiParameter, String>>() {
@Override
public boolean value(Pair<PsiParameter, String> pair) {
final PsiParameter parameter = pair.first;
final String oldParamName = pair.second;
final int idx = Arrays.binarySearch(oldParameterNames, oldParamName);
return idx >= 0 && idx == method.getParameterList().getParameterIndex(parameter) && changeInfo.getNewParameters()[idx].getOldIndex() == idx;
}
}, new Condition<String>() {
@Override
public boolean value(String paramName) {
return Arrays.binarySearch(oldParameterNames, paramName) >= 0;
}
});
}
private static PsiParameter createNewParameter(JavaChangeInfo changeInfo, JavaParameterInfo newParm,
PsiSubstitutor... substitutor) throws IncorrectOperationException {
final PsiParameterList list = changeInfo.getMethod().getParameterList();
final PsiElementFactory factory = JavaPsiFacade.getInstance(list.getProject()).getElementFactory();
PsiType type = newParm.createType(list, list.getManager());
for (PsiSubstitutor psiSubstitutor : substitutor) {
type = psiSubstitutor.substitute(type);
}
return factory.createParameter(newParm.getName(), type);
}
private static void resolveParameterVsFieldsConflicts(final PsiParameter[] newParms,
final PsiMethod method,
final PsiParameterList list,
boolean[] toRemoveParm) throws IncorrectOperationException {
List<FieldConflictsResolver> conflictResolvers = new ArrayList<FieldConflictsResolver>();
for (PsiParameter parameter : newParms) {
conflictResolvers.add(new FieldConflictsResolver(parameter.getName(), method.getBody()));
}
ChangeSignatureUtil.synchronizeList(list, Arrays.asList(newParms), ParameterList.INSTANCE, toRemoveParm);
JavaCodeStyleManager.getInstance(list.getProject()).shortenClassReferences(list);
for (FieldConflictsResolver fieldConflictsResolver : conflictResolvers) {
fieldConflictsResolver.fix();
}
}
private static boolean needToCatchExceptions(JavaChangeInfo changeInfo, PsiMethod caller) {
return changeInfo.isExceptionSetOrOrderChanged() &&
!(changeInfo instanceof JavaChangeInfoImpl && ((JavaChangeInfoImpl)changeInfo).propagateExceptionsMethods.contains(caller));
}
private static class ParameterList implements ChangeSignatureUtil.ChildrenGenerator<PsiParameterList, PsiParameter> {
public static final ParameterList INSTANCE = new ParameterList();
@Override
public List<PsiParameter> getChildren(PsiParameterList psiParameterList) {
return Arrays.asList(psiParameterList.getParameters());
}
}
private static class ThrowsList implements ChangeSignatureUtil.ChildrenGenerator<PsiReferenceList, PsiJavaCodeReferenceElement> {
public static final ThrowsList INSTANCE = new ThrowsList();
@Override
public List<PsiJavaCodeReferenceElement> getChildren(PsiReferenceList throwsList) {
return Arrays.asList(throwsList.getReferenceElements());
}
}
private static class ConflictSearcher {
private final JavaChangeInfo myChangeInfo;
private ConflictSearcher(@NotNull JavaChangeInfo changeInfo) {
this.myChangeInfo = changeInfo;
}
public MultiMap<PsiElement, String> findConflicts(Ref<UsageInfo[]> refUsages) {
MultiMap<PsiElement, String> conflictDescriptions = new MultiMap<PsiElement, String>();
addMethodConflicts(conflictDescriptions);
Set<UsageInfo> usagesSet = new HashSet<UsageInfo>(Arrays.asList(refUsages.get()));
RenameUtil.removeConflictUsages(usagesSet);
if (myChangeInfo.isVisibilityChanged()) {
try {
addInaccessibilityDescriptions(usagesSet, conflictDescriptions);
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
checkContract(conflictDescriptions, myChangeInfo.getMethod());
for (UsageInfo usageInfo : usagesSet) {
final PsiElement element = usageInfo.getElement();
if (usageInfo instanceof OverriderUsageInfo) {
final PsiMethod method = (PsiMethod)element;
final PsiMethod baseMethod = ((OverriderUsageInfo)usageInfo).getBaseMethod();
final int delta = baseMethod.getParameterList().getParametersCount() - method.getParameterList().getParametersCount();
if (delta > 0) {
final boolean[] toRemove = myChangeInfo.toRemoveParm();
if (toRemove[toRemove.length - 1]) { //todo check if implicit parameter is not the last one
conflictDescriptions.putValue(baseMethod, "Implicit last parameter should not be deleted");
}
}
checkContract(conflictDescriptions, method);
} else if (element instanceof PsiMethodReferenceExpression) {
conflictDescriptions.putValue(element, "Changed method is used in method reference");
}
}
return conflictDescriptions;
}
private static void checkContract(MultiMap<PsiElement, String> conflictDescriptions, PsiMethod method) {
PsiAnnotation contract = ControlFlowAnalyzer.findContractAnnotation(method);
if (contract != null && !AnnotationUtil.isInferredAnnotation(contract)) {
conflictDescriptions.putValue(method, "@Contract annotation will have to be changed manually");
}
}
private boolean needToChangeCalls() {
return myChangeInfo.isNameChanged() || myChangeInfo.isParameterSetOrOrderChanged() || myChangeInfo.isExceptionSetOrOrderChanged();
}
private void addInaccessibilityDescriptions(Set<UsageInfo> usages, MultiMap<PsiElement, String> conflictDescriptions)
throws IncorrectOperationException {
PsiMethod method = myChangeInfo.getMethod();
PsiModifierList modifierList = (PsiModifierList)method.getModifierList().copy();
VisibilityUtil.setVisibility(modifierList, myChangeInfo.getNewVisibility());
for (Iterator<UsageInfo> iterator = usages.iterator(); iterator.hasNext();) {
UsageInfo usageInfo = iterator.next();
PsiElement element = usageInfo.getElement();
if (element != null) {
if (element instanceof PsiQualifiedReference) {
PsiClass accessObjectClass = null;
PsiElement qualifier = ((PsiQualifiedReference)element).getQualifier();
if (qualifier instanceof PsiExpression) {
accessObjectClass = (PsiClass)PsiUtil.getAccessObjectClass((PsiExpression)qualifier).getElement();
}
if (!JavaPsiFacade.getInstance(element.getProject()).getResolveHelper()
.isAccessible(method, modifierList, element, accessObjectClass, null)) {
String message =
RefactoringBundle.message("0.with.1.visibility.is.not.accessible.from.2",
RefactoringUIUtil.getDescription(method, true),
VisibilityUtil.toPresentableText(myChangeInfo.getNewVisibility()),
RefactoringUIUtil.getDescription(ConflictsUtil.getContainer(element), true));
conflictDescriptions.putValue(method, message);
if (!needToChangeCalls()) {
iterator.remove();
}
}
}
}
}
}
private void addMethodConflicts(MultiMap<PsiElement, String> conflicts) {
String newMethodName = myChangeInfo.getNewName();
try {
PsiMethod prototype;
final PsiMethod method = myChangeInfo.getMethod();
if (!StdLanguages.JAVA.equals(method.getLanguage())) return;
PsiManager manager = method.getManager();
PsiElementFactory factory = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory();
final CanonicalTypes.Type returnType = myChangeInfo.getNewReturnType();
if (returnType != null) {
prototype = factory.createMethod(newMethodName, returnType.getType(method, manager));
}
else {
prototype = factory.createConstructor();
prototype.setName(newMethodName);
}
JavaParameterInfo[] parameters = myChangeInfo.getNewParameters();
for (JavaParameterInfo info : parameters) {
PsiType parameterType = info.createType(method, manager);
if (parameterType == null) {
parameterType =
JavaPsiFacade.getElementFactory(method.getProject()).createTypeFromText(CommonClassNames.JAVA_LANG_OBJECT, method);
}
PsiParameter param = factory.createParameter(info.getName(), parameterType);
prototype.getParameterList().add(param);
}
ConflictsUtil.checkMethodConflicts(method.getContainingClass(), method, prototype, conflicts);
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
}
private static class ExpressionList implements ChangeSignatureUtil.ChildrenGenerator<PsiExpressionList, PsiExpression> {
public static final ExpressionList INSTANCE = new ExpressionList();
@Override
public List<PsiExpression> getChildren(PsiExpressionList psiExpressionList) {
return Arrays.asList(psiExpressionList.getExpressions());
}
}
}