diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/impl/quickfix/ChangeMethodSignatureFromUsageFix.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/impl/quickfix/ChangeMethodSignatureFromUsageFix.java index 2782c412c..9ad55d0a9 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/impl/quickfix/ChangeMethodSignatureFromUsageFix.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/impl/quickfix/ChangeMethodSignatureFromUsageFix.java @@ -15,9 +15,9 @@ import com.intellij.java.language.psi.util.PsiTypesUtil; import com.intellij.java.language.psi.util.PsiUtil; import com.intellij.java.language.psi.util.TypeConversionUtil; -import consulo.application.ApplicationManager; +import consulo.annotation.access.RequiredReadAction; +import consulo.application.Application; import consulo.application.progress.ProgressManager; -import consulo.application.util.function.Processor; import consulo.codeEditor.Editor; import consulo.component.ProcessCanceledException; import consulo.container.PluginException; @@ -27,6 +27,7 @@ import consulo.find.FindUsagesHandler; import consulo.ide.impl.idea.find.findUsages.FindUsagesManager; import consulo.ide.impl.idea.find.impl.FindManagerImpl; +import consulo.ide.localize.IdeLocalize; import consulo.java.analysis.impl.localize.JavaQuickFixLocalize; import consulo.java.impl.codeInsight.JavaTargetElementUtilEx; import consulo.language.editor.FileModificationService; @@ -40,487 +41,581 @@ import consulo.localize.LocalizeValue; import consulo.logging.Logger; import consulo.project.Project; +import consulo.ui.annotation.RequiredUIAccess; import consulo.undoRedo.CommandProcessor; import consulo.usage.UsageInfo; import consulo.util.collection.ArrayUtil; import consulo.util.lang.StringUtil; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; -import org.jetbrains.annotations.NonNls; import java.util.*; import java.util.function.Consumer; +import java.util.function.Predicate; public class ChangeMethodSignatureFromUsageFix implements SyntheticIntentionAction { - final PsiMethod myTargetMethod; - final PsiExpression[] myExpressions; - final PsiSubstitutor mySubstitutor; - final PsiElement myContext; - private final boolean myChangeAllUsages; - private final int myMinUsagesNumberToShowDialog; - ParameterInfoImpl[] myNewParametersInfo; - private LocalizeValue myShortName = LocalizeValue.empty(); - private static final Logger LOG = Logger.getInstance(ChangeMethodSignatureFromUsageFix.class); + private static class ParameterTypes { + @Nullable + private final PsiType[] myParamTypes; - public ChangeMethodSignatureFromUsageFix(@Nonnull PsiMethod targetMethod, - @Nonnull PsiExpression[] expressions, - @Nonnull PsiSubstitutor substitutor, - @Nonnull PsiElement context, - boolean changeAllUsages, int minUsagesNumberToShowDialog) { - myTargetMethod = targetMethod; - myExpressions = expressions; - mySubstitutor = substitutor; - myContext = context; - myChangeAllUsages = changeAllUsages; - myMinUsagesNumberToShowDialog = minUsagesNumberToShowDialog; - } - - @Override - @Nonnull - public LocalizeValue getText() { - final LocalizeValue shortText = myShortName; - if (shortText != LocalizeValue.of()) { - return shortText; - } - return JavaQuickFixLocalize.changeMethodSignatureFromUsageText(JavaHighlightUtil.formatMethod(myTargetMethod), myTargetMethod.getName(), formatTypesList(myNewParametersInfo, myContext)); - } - - private LocalizeValue getShortText(final StringBuilder buf, - final HashSet newParams, - final HashSet removedParams, - final HashSet changedParams) { - final String targetMethodName = myTargetMethod.getName(); - if (myTargetMethod.getContainingClass().findMethodsByName(targetMethodName, true).length == 1) { - if (newParams.size() == 1) { - final ParameterInfoImpl p = newParams.iterator().next(); - return JavaQuickFixLocalize.addParameterFromUsageText(p.getTypeText(), ArrayUtil.find(myNewParametersInfo, p) + 1, targetMethodName); - } - if (removedParams.size() == 1) { - final ParameterInfoImpl p = removedParams.iterator().next(); - return JavaQuickFixLocalize.removeParameterFromUsageText(p.getOldIndex() + 1, targetMethodName); - } - if (changedParams.size() == 1) { - final ParameterInfoImpl p = changedParams.iterator().next(); - return JavaQuickFixLocalize.changeParameterFromUsageText(p.getOldIndex() + 1, targetMethodName, Objects.requireNonNull(myTargetMethod.getParameterList().getParameter(p.getOldIndex())).getType().getPresentableText(), p.getTypeText()); - } - } - return LocalizeValue.localizeTODO(" Change signature of " + targetMethodName + "(" + buf + ")"); - } - - @Nullable - private static String formatTypesList(ParameterInfoImpl[] infos, PsiElement context) { - if (infos == null) { - return null; - } - StringBuilder result = new StringBuilder(); - try { - for (ParameterInfoImpl info : infos) { - PsiType type = info.createType(context); - if (type == null) { - return null; + private ParameterTypes(@Nullable ParameterInfoImpl[] parameterInfos, @Nonnull PsiElement context) { + if (parameterInfos == null) { + myParamTypes = null; + return; + } + int n = parameterInfos.length; + myParamTypes = new PsiType[n]; + for (int i = 0; i < n; i++) { + PsiType paramType; + try { + paramType = parameterInfos[i].createType(context); + } + catch (IncorrectOperationException e) { + paramType = null; + } + myParamTypes[i] = paramType; + } } - if (result.length() != 0) { - result.append(", "); + + @Nonnull + public String getPresentableText() { + if (myParamTypes == null) { + return IdeLocalize.textNotApplicable().get(); + } + StringBuilder result = new StringBuilder(); + for (PsiType paramType : myParamTypes) { + if (result.length() != 0) { + result.append(", "); + } + result.append(paramType != null ? paramType.getPresentableText() : IdeLocalize.textNotApplicable().get()); + } + return result.toString(); } - result.append(type.getPresentableText()); - } - return result.toString(); - } catch (IncorrectOperationException e) { - return null; - } - } - @Override - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { - if (!myTargetMethod.isValid() || myTargetMethod.getContainingClass() == null) { - return false; - } - for (PsiExpression expression : myExpressions) { - if (!expression.isValid()) { - return false; - } - } - if (!mySubstitutor.isValid()) { - return false; + public boolean isValid() { + if (myParamTypes == null) { + return false; + } + for (PsiType paramType : myParamTypes) { + if (paramType == null) { + return false; + } + } + return true; + } } - final StringBuilder buf = new StringBuilder(); - final HashSet newParams = new HashSet<>(); - final HashSet removedParams = new HashSet<>(); - final HashSet changedParams = new HashSet<>(); - myNewParametersInfo = getNewParametersInfo(myExpressions, myTargetMethod, mySubstitutor, buf, newParams, removedParams, changedParams); - if (myNewParametersInfo == null || formatTypesList(myNewParametersInfo, myContext) == null) { - return false; - } - myShortName = getShortText(buf, newParams, removedParams, changedParams); - return !isMethodSignatureExists(); - } + final PsiMethod myTargetMethod; + final PsiExpression[] myExpressions; + final PsiSubstitutor mySubstitutor; + @Nonnull + final PsiElement myContext; + private final boolean myChangeAllUsages; + private final int myMinUsagesNumberToShowDialog; + ParameterInfoImpl[] myNewParametersInfo; + private LocalizeValue myShortName = LocalizeValue.empty(); + private static final Logger LOG = Logger.getInstance(ChangeMethodSignatureFromUsageFix.class); - public boolean isMethodSignatureExists() { - PsiClass target = myTargetMethod.getContainingClass(); - LOG.assertTrue(target != null); - PsiMethod[] methods = target.findMethodsByName(myTargetMethod.getName(), false); - for (PsiMethod method : methods) { - if (PsiUtil.isApplicable(method, PsiSubstitutor.EMPTY, myExpressions)) { - return true; - } + public ChangeMethodSignatureFromUsageFix( + @Nonnull PsiMethod targetMethod, + @Nonnull PsiExpression[] expressions, + @Nonnull PsiSubstitutor substitutor, + @Nonnull PsiElement context, + boolean changeAllUsages, int minUsagesNumberToShowDialog + ) { + myTargetMethod = targetMethod; + myExpressions = expressions; + mySubstitutor = substitutor; + myContext = context; + myChangeAllUsages = changeAllUsages; + myMinUsagesNumberToShowDialog = minUsagesNumberToShowDialog; } - return false; - } - @Override - public void invoke(@Nonnull final Project project, Editor editor, final PsiFile file) { - if (!FileModificationService.getInstance().prepareFileForWrite(file)) { - return; + @Nonnull + @Override + public LocalizeValue getText() { + LocalizeValue shortText = myShortName; + if (shortText != LocalizeValue.of()) { + return shortText; + } + return JavaQuickFixLocalize.changeMethodSignatureFromUsageText( + JavaHighlightUtil.formatMethod(myTargetMethod), + myTargetMethod.getName(), + new ParameterTypes(myNewParametersInfo, myContext).getPresentableText() + ); } - final PsiMethod method = SuperMethodWarningUtil.checkSuperMethod(myTargetMethod, RefactoringLocalize.toRefactor().get()); - if (method == null) { - return; + private LocalizeValue getShortText( + StringBuilder buf, + Set newParams, + Set removedParams, + Set changedParams + ) { + String targetMethodName = myTargetMethod.getName(); + if (myTargetMethod.getContainingClass().findMethodsByName(targetMethodName, true).length == 1) { + if (newParams.size() == 1) { + ParameterInfoImpl p = newParams.iterator().next(); + return JavaQuickFixLocalize.addParameterFromUsageText( + p.getTypeText(), + ArrayUtil.find(myNewParametersInfo, p) + 1, + targetMethodName + ); + } + if (removedParams.size() == 1) { + ParameterInfoImpl p = removedParams.iterator().next(); + return JavaQuickFixLocalize.removeParameterFromUsageText(p.getOldIndex() + 1, targetMethodName); + } + if (changedParams.size() == 1) { + ParameterInfoImpl p = changedParams.iterator().next(); + return JavaQuickFixLocalize.changeParameterFromUsageText( + p.getOldIndex() + 1, + targetMethodName, + Objects.requireNonNull(myTargetMethod.getParameterList().getParameter(p.getOldIndex())).getType().getPresentableText(), + p.getTypeText() + ); + } + } + return LocalizeValue.localizeTODO(" Change signature of " + targetMethodName + "(" + buf + ")"); } - myNewParametersInfo = getNewParametersInfo(myExpressions, myTargetMethod, mySubstitutor); - final List parameterInfos = - performChange(project, editor, file, method, myMinUsagesNumberToShowDialog, myNewParametersInfo, myChangeAllUsages, false, null); - if (parameterInfos != null) { - myNewParametersInfo = parameterInfos.toArray(new ParameterInfoImpl[0]); - } - } + @Override + @RequiredReadAction + public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { + if (!myTargetMethod.isValid() || myTargetMethod.getContainingClass() == null) { + return false; + } + for (PsiExpression expression : myExpressions) { + if (!expression.isValid()) { + return false; + } + } + if (!mySubstitutor.isValid()) { + return false; + } - static List performChange(@Nonnull Project project, - final Editor editor, - final PsiFile file, - @Nonnull PsiMethod method, - final int minUsagesNumber, - final ParameterInfoImpl[] newParametersInfo, - final boolean changeAllUsages, - final boolean allowDelegation, - @Nullable final Consumer> callback) { - if (!FileModificationService.getInstance().prepareFileForWrite(method.getContainingFile())) { - return null; - } - final FindUsagesManager findUsagesManager = ((FindManagerImpl) FindManager.getInstance(project)).getFindUsagesManager(); - final FindUsagesHandler handler = findUsagesManager.getFindUsagesHandler(method, false); - if (handler == null) { - return null;//on failure or cancel (e.g. cancel of super methods dialog) + StringBuilder buf = new StringBuilder(); + Set newParams = new HashSet<>(); + Set removedParams = new HashSet<>(); + Set changedParams = new HashSet<>(); + myNewParametersInfo = + getNewParametersInfo(myExpressions, myTargetMethod, mySubstitutor, buf, newParams, removedParams, changedParams); + if (!new ParameterTypes(myNewParametersInfo, myContext).isValid()) { + return false; + } + myShortName = getShortText(buf, newParams, removedParams, changedParams); + return !isMethodSignatureExists(); } - final JavaMethodFindUsagesOptions options = new JavaMethodFindUsagesOptions(project); - options.isImplementingMethods = true; - options.isOverridingMethods = true; - options.isUsages = true; - options.isSearchForTextOccurrences = false; - final int[] usagesFound = new int[1]; - Runnable runnable = () -> { - Processor processor = t -> ++usagesFound[0] < minUsagesNumber; - - handler.processElementUsages(method, processor, options); - }; - LocalizeValue progressTitle = JavaQuickFixLocalize.searchingForUsagesProgressTitle(); - if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(runnable, progressTitle, true, project)) { - return null; + @RequiredReadAction + public boolean isMethodSignatureExists() { + PsiClass target = myTargetMethod.getContainingClass(); + LOG.assertTrue(target != null); + PsiMethod[] methods = target.findMethodsByName(myTargetMethod.getName(), false); + for (PsiMethod method : methods) { + if (PsiUtil.isApplicable(method, PsiSubstitutor.EMPTY, myExpressions)) { + return true; + } + } + return false; } - if (ApplicationManager.getApplication().isUnitTestMode() || usagesFound[0] < minUsagesNumber) { - ChangeSignatureProcessor processor = new ChangeSignatureProcessor( - project, - method, - false, null, - method.getName(), - method.getReturnType(), - newParametersInfo) { - @Override - protected UsageInfo[] findUsages() { - return changeAllUsages ? super.findUsages() : UsageInfo.EMPTY_ARRAY; + @Override + @RequiredUIAccess + public void invoke(@Nonnull Project project, Editor editor, PsiFile file) { + if (!FileModificationService.getInstance().prepareFileForWrite(file)) { + return; } - @Override - protected void performRefactoring(UsageInfo[] usages) { - CommandProcessor.getInstance().setCurrentCommandName(getCommandName()); - super.performRefactoring(usages); - if (callback != null) { - callback.accept(Arrays.asList(newParametersInfo)); - } + PsiMethod method = SuperMethodWarningUtil.checkSuperMethod(myTargetMethod, RefactoringLocalize.toRefactor().get()); + if (method == null) { + return; } - }; - processor.run(); - ApplicationManager.getApplication().runWriteAction(() -> LanguageUndoUtil.markPsiFileForUndo(file)); - return Arrays.asList(newParametersInfo); - } else { - final List parameterInfos = newParametersInfo != null - ? new ArrayList<>(Arrays.asList(newParametersInfo)) - : new ArrayList<>(); - final PsiReferenceExpression refExpr = JavaTargetElementUtilEx.findReferenceExpression(editor); - JavaChangeSignatureDialog dialog = JavaChangeSignatureDialog.createAndPreselectNew(project, method, parameterInfos, allowDelegation, refExpr, callback); - dialog.setParameterInfos(parameterInfos); - dialog.show(); - return dialog.isOK() ? dialog.getParameters() : null; - } - } + myNewParametersInfo = getNewParametersInfo(myExpressions, myTargetMethod, mySubstitutor); - public static String getNewParameterNameByOldIndex(int oldIndex, final ParameterInfoImpl[] parametersInfo) { - if (parametersInfo == null) { - return null; - } - for (ParameterInfoImpl info : parametersInfo) { - if (info.oldParameterIndex == oldIndex) { - return info.getName(); - } + List parameterInfos = performChange( + project, + editor, + file, + method, + myMinUsagesNumberToShowDialog, + myNewParametersInfo, + myChangeAllUsages, + false, + null + ); + if (parameterInfos != null) { + myNewParametersInfo = parameterInfos.toArray(new ParameterInfoImpl[0]); + } } - return null; - } - protected ParameterInfoImpl[] getNewParametersInfo(PsiExpression[] expressions, - PsiMethod targetMethod, - PsiSubstitutor substitutor) { - return getNewParametersInfo(expressions, targetMethod, substitutor, new StringBuilder(), new HashSet<>(), - new HashSet<>(), - new HashSet<>()); - } + @RequiredUIAccess + static List performChange( + @Nonnull Project project, + Editor editor, + PsiFile file, + @Nonnull PsiMethod method, + int minUsagesNumber, + final ParameterInfoImpl[] newParametersInfo, + final boolean changeAllUsages, + boolean allowDelegation, + @Nullable final Consumer> callback + ) { + if (!FileModificationService.getInstance().prepareFileForWrite(method.getContainingFile())) { + return null; + } + FindUsagesManager findUsagesManager = ((FindManagerImpl) FindManager.getInstance(project)).getFindUsagesManager(); + FindUsagesHandler handler = findUsagesManager.getFindUsagesHandler(method, false); + if (handler == null) { + return null;//on failure or cancel (e.g. cancel of super methods dialog) + } - private ParameterInfoImpl[] getNewParametersInfo(PsiExpression[] expressions, - PsiMethod targetMethod, - PsiSubstitutor substitutor, - final StringBuilder buf, - final HashSet newParams, - final HashSet removedParams, - final HashSet changedParams) { - PsiParameter[] parameters = targetMethod.getParameterList().getParameters(); - List result = new ArrayList<>(); - if (expressions.length < parameters.length) { - // find which parameters to remove - int ei = 0; - int pi = 0; + JavaMethodFindUsagesOptions options = new JavaMethodFindUsagesOptions(project); + options.isImplementingMethods = true; + options.isOverridingMethods = true; + options.isUsages = true; + options.isSearchForTextOccurrences = false; + int[] usagesFound = new int[1]; + Runnable runnable = () -> { + Predicate processor = t -> ++usagesFound[0] < minUsagesNumber; - while (ei < expressions.length && pi < parameters.length) { - PsiExpression expression = expressions[ei]; - PsiParameter parameter = parameters[pi]; - PsiType paramType = substitutor.substitute(parameter.getType()); - if (buf.length() > 0) { - buf.append(", "); - } - final PsiType parameterType = PsiUtil.convertAnonymousToBaseType(paramType); - final String presentableText = escapePresentableType(parameterType); - final ParameterInfoImpl parameterInfo = ParameterInfoImpl.create(pi).withName(parameter.getName()).withType(parameter.getType()); - if (TypeConversionUtil.areTypesAssignmentCompatible(paramType, expression)) { - buf.append(presentableText); - result.add(parameterInfo); - pi++; - ei++; - } else { - buf.append("").append(presentableText).append(""); - removedParams.add(parameterInfo); - pi++; + handler.processElementUsages(method, processor, options); + }; + LocalizeValue progressTitle = JavaQuickFixLocalize.searchingForUsagesProgressTitle(); + if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(runnable, progressTitle, true, project)) { + return null; } - } - if (result.size() != expressions.length) { - return null; - } - for (int i = pi; i < parameters.length; i++) { - if (buf.length() > 0) { - buf.append(", "); + + Application application = project.getApplication(); + if (application.isUnitTestMode() || usagesFound[0] < minUsagesNumber) { + ChangeSignatureProcessor processor = new ChangeSignatureProcessor( + project, + method, + false, null, + method.getName(), + method.getReturnType(), + newParametersInfo + ) { + @Nonnull + @Override + protected UsageInfo[] findUsages() { + return changeAllUsages ? super.findUsages() : UsageInfo.EMPTY_ARRAY; + } + + @Override + @RequiredReadAction + protected void performRefactoring(@Nonnull UsageInfo[] usages) { + CommandProcessor.getInstance().setCurrentCommandName(getCommandName()); + super.performRefactoring(usages); + if (callback != null) { + callback.accept(Arrays.asList(newParametersInfo)); + } + } + }; + processor.run(); + application.runWriteAction(() -> LanguageUndoUtil.markPsiFileForUndo(file)); + return Arrays.asList(newParametersInfo); } - buf.append("").append(escapePresentableType(parameters[i].getType())).append(""); - final ParameterInfoImpl parameterInfo = ParameterInfoImpl.create(pi) - .withName(parameters[i].getName()) - .withType(parameters[i].getType()); - removedParams.add(parameterInfo); - } - } else if (expressions.length > parameters.length) { - if (!findNewParamsPlace(expressions, targetMethod, substitutor, buf, newParams, parameters, result)) { - return null; - } - } else { - //parameter type changed - for (int i = 0; i < parameters.length; i++) { - if (buf.length() > 0) { - buf.append(", "); + else { + List parameterInfos = newParametersInfo != null + ? new ArrayList<>(Arrays.asList(newParametersInfo)) + : new ArrayList<>(); + PsiReferenceExpression refExpr = JavaTargetElementUtilEx.findReferenceExpression(editor); + JavaChangeSignatureDialog dialog = + JavaChangeSignatureDialog.createAndPreselectNew(project, method, parameterInfos, allowDelegation, refExpr, callback); + dialog.setParameterInfos(parameterInfos); + dialog.show(); + return dialog.isOK() ? dialog.getParameters() : null; } - PsiParameter parameter = parameters[i]; - PsiExpression expression = expressions[i]; - PsiType bareParamType = parameter.getType(); - if (!bareParamType.isValid()) { - try { - PsiUtil.ensureValidType(bareParamType); - } catch (ProcessCanceledException e) { - throw e; - } catch (Throwable e) { - PluginDescriptor plugin = PluginManager.getPlugin(parameter.getClass()); + } - throw new PluginException(parameter.getClass() + "; valid=" + parameter.isValid() + "; method.valid=" + targetMethod.isValid(), e, plugin.getPluginId()); - } - } - PsiType paramType = substitutor.substitute(bareParamType); - PsiUtil.ensureValidType(paramType); - final String presentableText = escapePresentableType(paramType); - if (TypeConversionUtil.areTypesAssignmentCompatible(paramType, expression)) { - result.add(ParameterInfoImpl.create(i).withName(parameter.getName()).withType(paramType)); - buf.append(presentableText); - } else { - if (PsiPolyExpressionUtil.isPolyExpression(expression)) { + public static String getNewParameterNameByOldIndex(int oldIndex, ParameterInfoImpl[] parametersInfo) { + if (parametersInfo == null) { return null; - } - PsiType exprType = RefactoringUtil.getTypeByExpression(expression); - if (exprType == null || PsiType.VOID.equals(exprType)) { - return null; - } - if (exprType instanceof PsiDisjunctionType) { - exprType = ((PsiDisjunctionType) exprType).getLeastUpperBound(); - } - if (!PsiTypesUtil.allTypeParametersResolved(myTargetMethod, exprType)) { - return null; - } - final ParameterInfoImpl changedParameterInfo = ParameterInfoImpl.create(i).withName(parameter.getName()).withType(exprType); - result.add(changedParameterInfo); - changedParams.add(changedParameterInfo); - buf.append("").append(presentableText).append(" ").append(escapePresentableType(exprType)).append(""); } - } - // do not perform silly refactorings - boolean isSilly = true; - for (int i = 0; i < result.size(); i++) { - PsiParameter parameter = parameters[i]; - PsiType paramType = substitutor.substitute(parameter.getType()); - ParameterInfoImpl parameterInfo = result.get(i); - String typeText = parameterInfo.getTypeText(); - if (!paramType.equalsToText(typeText) && !paramType.getPresentableText().equals(typeText)) { - isSilly = false; - break; + for (ParameterInfoImpl info : parametersInfo) { + if (info.oldParameterIndex == oldIndex) { + return info.getName(); + } } - } - if (isSilly) { return null; - } } - return result.toArray(new ParameterInfoImpl[0]); - } - @Nonnull - protected static String escapePresentableType(@Nonnull PsiType exprType) { - return StringUtil.escapeXmlEntities(exprType.getPresentableText()); - } - - protected boolean findNewParamsPlace(PsiExpression[] expressions, - PsiMethod targetMethod, - PsiSubstitutor substitutor, - StringBuilder buf, - HashSet newParams, - PsiParameter[] parameters, - List result) { - // find which parameters to introduce and where - Set existingNames = new HashSet<>(); - for (PsiParameter parameter : parameters) { - existingNames.add(parameter.getName()); + @RequiredReadAction + protected ParameterInfoImpl[] getNewParametersInfo( + PsiExpression[] expressions, + PsiMethod targetMethod, + PsiSubstitutor substitutor + ) { + return getNewParametersInfo( + expressions, + targetMethod, + substitutor, + new StringBuilder(), + new HashSet<>(), + new HashSet<>(), + new HashSet<>() + ); } - int ei = 0; - int pi = 0; - PsiParameter varargParam = targetMethod.isVarArgs() ? parameters[parameters.length - 1] : null; - while (ei < expressions.length || pi < parameters.length) { - if (buf.length() > 0) { - buf.append(", "); - } - PsiExpression expression = ei < expressions.length ? expressions[ei] : null; - PsiParameter parameter = pi < parameters.length ? parameters[pi] : null; - PsiType paramType = parameter == null ? null : substitutor.substitute(parameter.getType()); - boolean parameterAssignable = paramType != null && (expression == null || TypeConversionUtil - .areTypesAssignmentCompatible(paramType, expression)); - if (parameterAssignable) { - final PsiType type = parameter.getType(); - result.add(ParameterInfoImpl.create(pi).withName(parameter.getName()).withType(type)); - buf.append(escapePresentableType(type)); - pi++; - ei++; - } else if (isArgumentInVarargPosition(expressions, ei, varargParam, substitutor)) { - if (pi == parameters.length - 1) { - assert varargParam != null; - final PsiType type = varargParam.getType(); - result.add(ParameterInfoImpl.create(pi).withName(varargParam.getName()).withType(type)); - buf.append(escapePresentableType(type)); - } - pi++; - ei++; - } else if (expression != null) { - if (varargParam != null && pi >= parameters.length) { - return false; - } - if (PsiPolyExpressionUtil.isPolyExpression(expression)) { - return false; - } - PsiType exprType = RefactoringUtil.getTypeByExpression(expression); - if (exprType == null || PsiType.VOID.equals(exprType)) { - return false; + + @RequiredReadAction + private ParameterInfoImpl[] getNewParametersInfo( + PsiExpression[] expressions, + PsiMethod targetMethod, + PsiSubstitutor substitutor, + StringBuilder buf, + Set newParams, + Set removedParams, + Set changedParams + ) { + PsiParameter[] parameters = targetMethod.getParameterList().getParameters(); + List result = new ArrayList<>(); + if (expressions.length < parameters.length) { + // find which parameters to remove + int ei = 0; + int pi = 0; + + while (ei < expressions.length && pi < parameters.length) { + PsiExpression expression = expressions[ei]; + PsiParameter parameter = parameters[pi]; + PsiType paramType = substitutor.substitute(parameter.getType()); + if (buf.length() > 0) { + buf.append(", "); + } + PsiType parameterType = PsiUtil.convertAnonymousToBaseType(paramType); + String presentableText = escapePresentableType(parameterType); + ParameterInfoImpl parameterInfo = + ParameterInfoImpl.create(pi).withName(parameter.getName()).withType(parameter.getType()); + if (TypeConversionUtil.areTypesAssignmentCompatible(paramType, expression)) { + buf.append(presentableText); + result.add(parameterInfo); + pi++; + ei++; + } + else { + buf.append("").append(presentableText).append(""); + removedParams.add(parameterInfo); + pi++; + } + } + if (result.size() != expressions.length) { + return null; + } + for (int i = pi; i < parameters.length; i++) { + if (buf.length() > 0) { + buf.append(", "); + } + buf.append("").append(escapePresentableType(parameters[i].getType())).append(""); + ParameterInfoImpl parameterInfo = ParameterInfoImpl.create(pi) + .withName(parameters[i].getName()) + .withType(parameters[i].getType()); + removedParams.add(parameterInfo); + } } - if (exprType instanceof PsiDisjunctionType) { - exprType = ((PsiDisjunctionType) exprType).getLeastUpperBound(); + else if (expressions.length > parameters.length) { + if (!findNewParamsPlace(expressions, targetMethod, substitutor, buf, newParams, parameters, result)) { + return null; + } } - if (!PsiTypesUtil.allTypeParametersResolved(myTargetMethod, exprType)) { - return false; + else { + //parameter type changed + for (int i = 0; i < parameters.length; i++) { + if (buf.length() > 0) { + buf.append(", "); + } + PsiParameter parameter = parameters[i]; + PsiExpression expression = expressions[i]; + PsiType bareParamType = parameter.getType(); + if (!bareParamType.isValid()) { + try { + PsiUtil.ensureValidType(bareParamType); + } + catch (ProcessCanceledException e) { + throw e; + } + catch (Throwable e) { + PluginDescriptor plugin = PluginManager.getPlugin(parameter.getClass()); + + throw new PluginException( + parameter.getClass() + "; valid=" + parameter.isValid() + "; method.valid=" + targetMethod.isValid(), + e, + plugin.getPluginId() + ); + } + } + PsiType paramType = substitutor.substitute(bareParamType); + PsiUtil.ensureValidType(paramType); + String presentableText = escapePresentableType(paramType); + if (TypeConversionUtil.areTypesAssignmentCompatible(paramType, expression)) { + result.add(ParameterInfoImpl.create(i).withName(parameter.getName()).withType(paramType)); + buf.append(presentableText); + } + else { + if (PsiPolyExpressionUtil.isPolyExpression(expression)) { + return null; + } + PsiType exprType = RefactoringUtil.getTypeByExpression(expression); + if (exprType == null || PsiType.VOID.equals(exprType)) { + return null; + } + if (exprType instanceof PsiDisjunctionType) { + exprType = ((PsiDisjunctionType) exprType).getLeastUpperBound(); + } + if (!PsiTypesUtil.allTypeParametersResolved(myTargetMethod, exprType)) { + return null; + } + ParameterInfoImpl changedParameterInfo = + ParameterInfoImpl.create(i).withName(parameter.getName()).withType(exprType); + result.add(changedParameterInfo); + changedParams.add(changedParameterInfo); + buf.append("").append(presentableText).append(" ").append(escapePresentableType(exprType)).append(""); + } + } + // do not perform silly refactorings + boolean isSilly = true; + for (int i = 0; i < result.size(); i++) { + PsiParameter parameter = parameters[i]; + PsiType paramType = substitutor.substitute(parameter.getType()); + ParameterInfoImpl parameterInfo = result.get(i); + String typeText = parameterInfo.getTypeText(); + if (!paramType.equalsToText(typeText) && !paramType.getPresentableText().equals(typeText)) { + isSilly = false; + break; + } + } + if (isSilly) { + return null; + } } - JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(expression.getProject()); - String name = suggestUniqueParameterName(codeStyleManager, expression, exprType, existingNames); - final ParameterInfoImpl newParameterInfo = ParameterInfoImpl.createNew() - .withName(name) - .withType(exprType) - .withDefaultValue(expression.getText().replace('\n', ' ')); - result.add(newParameterInfo); - newParams.add(newParameterInfo); - buf.append("").append(escapePresentableType(exprType)).append(""); - ei++; - } - } - if (result.size() != expressions.length && varargParam == null) { - return false; + return result.toArray(new ParameterInfoImpl[0]); } - return true; - } - static boolean isArgumentInVarargPosition(PsiExpression[] expressions, int ei, PsiParameter varargParam, PsiSubstitutor substitutor) { - if (varargParam == null) { - return false; + @Nonnull + protected static String escapePresentableType(@Nonnull PsiType exprType) { + return StringUtil.escapeXmlEntities(exprType.getPresentableText()); } - final PsiExpression expression = expressions[ei]; - if (expression == null || TypeConversionUtil.areTypesAssignmentCompatible(substitutor.substitute(((PsiEllipsisType) varargParam.getType()).getComponentType()), expression)) { - final int lastExprIdx = expressions.length - 1; - if (ei == lastExprIdx) { - return true; - } - return expressions[lastExprIdx].getType() != PsiType.NULL; - } - return false; - } - static String suggestUniqueParameterName(JavaCodeStyleManager codeStyleManager, - PsiExpression expression, - PsiType exprType, - Set existingNames) { - SuggestedNameInfo nameInfo = codeStyleManager.suggestVariableName(VariableKind.PARAMETER, null, expression, exprType); - @NonNls String[] names = nameInfo.names; - if (expression instanceof PsiReferenceExpression) { - final PsiElement resolve = ((PsiReferenceExpression) expression).resolve(); - if (resolve instanceof PsiVariable) { - final VariableKind variableKind = codeStyleManager.getVariableKind((PsiVariable) resolve); - final String propertyName = codeStyleManager.variableNameToPropertyName(((PsiVariable) resolve).getName(), variableKind); - final String parameterName = codeStyleManager.propertyNameToVariableName(propertyName, VariableKind.PARAMETER); - names = ArrayUtil.mergeArrays(new String[]{parameterName}, names); - } + @RequiredReadAction + protected boolean findNewParamsPlace( + PsiExpression[] expressions, + PsiMethod targetMethod, + PsiSubstitutor substitutor, + StringBuilder buf, + Set newParams, + PsiParameter[] parameters, + List result + ) { + // find which parameters to introduce and where + Set existingNames = new HashSet<>(); + for (PsiParameter parameter : parameters) { + existingNames.add(parameter.getName()); + } + int ei = 0; + int pi = 0; + PsiParameter varargParam = targetMethod.isVarArgs() ? parameters[parameters.length - 1] : null; + while (ei < expressions.length || pi < parameters.length) { + if (buf.length() > 0) { + buf.append(", "); + } + PsiExpression expression = ei < expressions.length ? expressions[ei] : null; + PsiParameter parameter = pi < parameters.length ? parameters[pi] : null; + PsiType paramType = parameter == null ? null : substitutor.substitute(parameter.getType()); + boolean parameterAssignable = paramType != null + && (expression == null || TypeConversionUtil.areTypesAssignmentCompatible(paramType, expression)); + if (parameterAssignable) { + PsiType type = parameter.getType(); + result.add(ParameterInfoImpl.create(pi).withName(parameter.getName()).withType(type)); + buf.append(escapePresentableType(type)); + pi++; + ei++; + } + else if (isArgumentInVarargPosition(expressions, ei, varargParam, substitutor)) { + if (pi == parameters.length - 1) { + PsiType type = varargParam.getType(); + result.add(ParameterInfoImpl.create(pi).withName(varargParam.getName()).withType(type)); + buf.append(escapePresentableType(type)); + } + pi++; + ei++; + } + else if (expression != null) { + if (varargParam != null && pi >= parameters.length) { + return false; + } + if (PsiPolyExpressionUtil.isPolyExpression(expression)) { + return false; + } + PsiType exprType = RefactoringUtil.getTypeByExpression(expression); + if (exprType == null || PsiType.VOID.equals(exprType)) { + return false; + } + if (exprType instanceof PsiDisjunctionType) { + exprType = ((PsiDisjunctionType) exprType).getLeastUpperBound(); + } + if (!PsiTypesUtil.allTypeParametersResolved(myTargetMethod, exprType)) { + return false; + } + JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(expression.getProject()); + String name = suggestUniqueParameterName(codeStyleManager, expression, exprType, existingNames); + ParameterInfoImpl newParameterInfo = ParameterInfoImpl.createNew() + .withName(name) + .withType(exprType) + .withDefaultValue(expression.getText().replace('\n', ' ')); + result.add(newParameterInfo); + newParams.add(newParameterInfo); + buf.append("").append(escapePresentableType(exprType)).append(""); + ei++; + } + } + return result.size() == expressions.length || varargParam != null; } - if (names.length == 0) { - names = new String[]{"param"}; + + static boolean isArgumentInVarargPosition(PsiExpression[] expressions, int ei, PsiParameter varargParam, PsiSubstitutor substitutor) { + if (varargParam == null) { + return false; + } + PsiExpression expression = expressions[ei]; + if (expression == null || TypeConversionUtil.areTypesAssignmentCompatible( + substitutor.substitute(((PsiEllipsisType) varargParam.getType()).getComponentType()), + expression + )) { + int lastExprIdx = expressions.length - 1; + return ei == lastExprIdx || expressions[lastExprIdx].getType() != PsiType.NULL; + } + return false; } - int suffix = 0; - while (true) { - for (String name : names) { - String suggested = name + (suffix == 0 ? "" : String.valueOf(suffix)); - if (existingNames.add(suggested)) { - return suggested; + + @RequiredReadAction + static String suggestUniqueParameterName( + JavaCodeStyleManager codeStyleManager, + PsiExpression expression, + PsiType exprType, + Set existingNames + ) { + SuggestedNameInfo nameInfo = codeStyleManager.suggestVariableName(VariableKind.PARAMETER, null, expression, exprType); + String[] names = nameInfo.names; + if (expression instanceof PsiReferenceExpression refExpr && refExpr.resolve() instanceof PsiVariable variable) { + VariableKind variableKind = codeStyleManager.getVariableKind(variable); + String propertyName = codeStyleManager.variableNameToPropertyName(variable.getName(), variableKind); + String parameterName = codeStyleManager.propertyNameToVariableName(propertyName, VariableKind.PARAMETER); + names = ArrayUtil.mergeArrays(new String[]{parameterName}, names); + } + if (names.length == 0) { + names = new String[]{"param"}; + } + int suffix = 0; + while (true) { + for (String name : names) { + String suggested = name + (suffix == 0 ? "" : String.valueOf(suffix)); + if (existingNames.add(suggested)) { + return suggested; + } + } + suffix++; } - } - suffix++; } - } - @Override - public boolean startInWriteAction() { - return false; - } + @Override + public boolean startInWriteAction() { + return false; + } } diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/impl/quickfix/ChangeMethodSignatureFromUsageReverseOrderFix.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/impl/quickfix/ChangeMethodSignatureFromUsageReverseOrderFix.java index ab0760323..3e5ed093e 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/impl/quickfix/ChangeMethodSignatureFromUsageReverseOrderFix.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/impl/quickfix/ChangeMethodSignatureFromUsageReverseOrderFix.java @@ -2,6 +2,7 @@ package com.intellij.java.impl.codeInsight.daemon.impl.quickfix; import com.intellij.java.language.psi.*; +import consulo.annotation.access.RequiredReadAction; import consulo.codeEditor.Editor; import consulo.project.Project; import consulo.util.lang.StringUtil; @@ -17,113 +18,115 @@ import java.util.List; import java.util.Set; -public class ChangeMethodSignatureFromUsageReverseOrderFix extends ChangeMethodSignatureFromUsageFix -{ - public ChangeMethodSignatureFromUsageReverseOrderFix(@Nonnull PsiMethod targetMethod, - @Nonnull PsiExpression[] expressions, - @Nonnull PsiSubstitutor substitutor, - @Nonnull PsiElement context, - boolean changeAllUsages, - int minUsagesNumberToShowDialog) - { - super(targetMethod, expressions, substitutor, context, changeAllUsages, minUsagesNumberToShowDialog); - } +public class ChangeMethodSignatureFromUsageReverseOrderFix extends ChangeMethodSignatureFromUsageFix { + public ChangeMethodSignatureFromUsageReverseOrderFix( + @Nonnull PsiMethod targetMethod, + @Nonnull PsiExpression[] expressions, + @Nonnull PsiSubstitutor substitutor, + @Nonnull PsiElement context, + boolean changeAllUsages, + int minUsagesNumberToShowDialog + ) { + super(targetMethod, expressions, substitutor, context, changeAllUsages, minUsagesNumberToShowDialog); + } - @Override - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) - { - if(myTargetMethod.isValid() && myExpressions.length > myTargetMethod.getParameterList().getParametersCount()) - { - if(super.isAvailable(project, editor, file)) - { - final ArrayList result = new ArrayList<>(); - if(super.findNewParamsPlace(myExpressions, myTargetMethod, mySubstitutor, - new StringBuilder(), new HashSet<>(), myTargetMethod.getParameterList().getParameters(), result)) - { + @Override + @RequiredReadAction + public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { + if (myTargetMethod.isValid() + && myExpressions.length > myTargetMethod.getParameterList().getParametersCount() + && super.isAvailable(project, editor, file)) { + List result = new ArrayList<>(); + if (super.findNewParamsPlace( + myExpressions, + myTargetMethod, + mySubstitutor, + new StringBuilder(), + new HashSet<>(), + myTargetMethod.getParameterList().getParameters(), + result + )) { + if (myNewParametersInfo.length != result.size()) { + return true; + } + for (int i = 0, size = result.size(); i < size; i++) { + ParameterInfoImpl info = result.get(i); + info.setName(myNewParametersInfo[i].getName()); + if (!myNewParametersInfo[i].equals(info)) { + return true; + } + } + } + } + return false; + } - if(myNewParametersInfo.length != result.size()) - return true; - for(int i = 0, size = result.size(); i < size; i++) - { - ParameterInfoImpl info = result.get(i); - info.setName(myNewParametersInfo[i].getName()); - if(!myNewParametersInfo[i].equals(info)) - return true; - } - } - } - } - return false; - } - - @Override - protected boolean findNewParamsPlace(PsiExpression[] expressions, - PsiMethod targetMethod, - PsiSubstitutor substitutor, - StringBuilder buf, - HashSet newParams, - PsiParameter[] parameters, - List result) - { - // find which parameters to introduce and where - Set existingNames = new HashSet<>(); - for(PsiParameter parameter : parameters) - { - existingNames.add(parameter.getName()); - } - int ei = expressions.length - 1; - int pi = parameters.length - 1; - final PsiParameter varargParam = targetMethod.isVarArgs() ? parameters[parameters.length - 1] : null; - final List params = new ArrayList<>(); - while(ei >= 0 || pi >= 0) - { - PsiExpression expression = ei >= 0 ? expressions[ei] : null; - PsiParameter parameter = pi >= 0 ? parameters[pi] : null; - PsiType paramType = parameter == null ? null : substitutor.substitute(parameter.getType()); - boolean parameterAssignable = paramType != null && (expression == null || TypeConversionUtil - .areTypesAssignmentCompatible(paramType, expression)); - if(parameterAssignable) - { - final PsiType type = parameter.getType(); - result.add(0, ParameterInfoImpl.create(pi).withName(parameter.getName()).withType(type)); - params.add(0, escapePresentableType(type)); - pi--; - ei--; - } - else if(isArgumentInVarargPosition(expressions, ei, varargParam, substitutor)) - { - if(pi == parameters.length - 1) - { - assert varargParam != null; - final PsiType type = varargParam.getType(); - result.add(0, ParameterInfoImpl.create(pi).withName(varargParam.getName()).withType(type)); - params.add(0, escapePresentableType(type)); - } - pi--; - ei--; - } - else if(expression != null) - { - if(varargParam != null && pi >= parameters.length) - return false; - PsiType exprType = RefactoringUtil.getTypeByExpression(expression); - if(exprType == null) - return false; - JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(expression.getProject()); - String name = suggestUniqueParameterName(codeStyleManager, expression, exprType, existingNames); - final ParameterInfoImpl newParameterInfo = ParameterInfoImpl.createNew() - .withName(name) - .withType(exprType) - .withDefaultValue(expression.getText().replace('\n', ' ')); - result.add(0, newParameterInfo); - newParams.add(newParameterInfo); - params.add(0, "" + escapePresentableType(exprType) + ""); - ei--; - } - } - if(result.size() != expressions.length && varargParam == null) - return false; - buf.append(StringUtil.join(params, ", ")); - return true; - } + @Override + @RequiredReadAction + protected boolean findNewParamsPlace( + PsiExpression[] expressions, + PsiMethod targetMethod, + PsiSubstitutor substitutor, + StringBuilder buf, + Set newParams, + PsiParameter[] parameters, + List result + ) { + // find which parameters to introduce and where + Set existingNames = new HashSet<>(); + for (PsiParameter parameter : parameters) { + existingNames.add(parameter.getName()); + } + int ei = expressions.length - 1; + int pi = parameters.length - 1; + PsiParameter varargParam = targetMethod.isVarArgs() ? parameters[parameters.length - 1] : null; + List params = new ArrayList<>(); + while (ei >= 0 || pi >= 0) { + PsiExpression expression = ei >= 0 ? expressions[ei] : null; + PsiParameter parameter = pi >= 0 ? parameters[pi] : null; + PsiType paramType = parameter == null ? null : substitutor.substitute(parameter.getType()); + boolean parameterAssignable = paramType != null + && (expression == null || TypeConversionUtil.areTypesAssignmentCompatible(paramType, expression)); + if (parameterAssignable) { + PsiType type = parameter.getType(); + result.add(0, ParameterInfoImpl.create(pi).withName(parameter.getName()).withType(type)); + params.add(0, escapePresentableType(type)); + pi--; + ei--; + } + else if (isArgumentInVarargPosition(expressions, ei, varargParam, substitutor)) { + if (pi == parameters.length - 1) { + PsiType type = varargParam.getType(); + result.add(0, ParameterInfoImpl.create(pi).withName(varargParam.getName()).withType(type)); + params.add(0, escapePresentableType(type)); + } + pi--; + ei--; + } + else if (expression != null) { + if (varargParam != null && pi >= parameters.length) { + return false; + } + PsiType exprType = RefactoringUtil.getTypeByExpression(expression); + if (exprType == null) { + return false; + } + JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(expression.getProject()); + String name = suggestUniqueParameterName(codeStyleManager, expression, exprType, existingNames); + ParameterInfoImpl newParameterInfo = ParameterInfoImpl.createNew() + .withName(name) + .withType(exprType) + .withDefaultValue(expression.getText().replace('\n', ' ')); + result.add(0, newParameterInfo); + newParams.add(newParameterInfo); + params.add(0, "" + escapePresentableType(exprType) + ""); + ei--; + } + } + if (result.size() != expressions.length && varargParam == null) { + return false; + } + buf.append(StringUtil.join(params, ", ")); + return true; + } }