| /* |
| * Copyright 2000-2012 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.codeInsight.daemon.impl.quickfix; |
| |
| import com.intellij.codeInsight.FileModificationService; |
| import com.intellij.codeInsight.TargetElementUtil; |
| import com.intellij.codeInsight.daemon.QuickFixBundle; |
| import com.intellij.codeInsight.daemon.impl.analysis.JavaHighlightUtil; |
| import com.intellij.codeInsight.intention.IntentionAction; |
| import com.intellij.find.FindManager; |
| import com.intellij.find.findUsages.FindUsagesHandler; |
| import com.intellij.find.findUsages.FindUsagesManager; |
| import com.intellij.find.findUsages.JavaMethodFindUsagesOptions; |
| import com.intellij.find.impl.FindManagerImpl; |
| import com.intellij.ide.util.SuperMethodWarningUtil; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.command.CommandProcessor; |
| import com.intellij.openapi.command.undo.UndoUtil; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.progress.ProgressManager; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.psi.*; |
| import com.intellij.psi.codeStyle.JavaCodeStyleManager; |
| import com.intellij.psi.codeStyle.SuggestedNameInfo; |
| import com.intellij.psi.codeStyle.VariableKind; |
| import com.intellij.psi.util.PsiUtil; |
| import com.intellij.psi.util.TypeConversionUtil; |
| import com.intellij.refactoring.RefactoringBundle; |
| import com.intellij.refactoring.changeSignature.ChangeSignatureProcessor; |
| import com.intellij.refactoring.changeSignature.JavaChangeSignatureDialog; |
| import com.intellij.refactoring.changeSignature.ParameterInfoImpl; |
| import com.intellij.refactoring.util.RefactoringUtil; |
| import com.intellij.usageView.UsageInfo; |
| import com.intellij.util.ArrayUtil; |
| import com.intellij.util.IncorrectOperationException; |
| import com.intellij.util.Processor; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.*; |
| |
| /** |
| * @author cdr |
| * @since Nov 13, 2002 |
| */ |
| public class ChangeMethodSignatureFromUsageFix implements IntentionAction/*, HighPriorityAction*/ { |
| final PsiMethod myTargetMethod; |
| final PsiExpression[] myExpressions; |
| final PsiSubstitutor mySubstitutor; |
| final PsiElement myContext; |
| private final boolean myChangeAllUsages; |
| private final int myMinUsagesNumberToShowDialog; |
| ParameterInfoImpl[] myNewParametersInfo; |
| private static final Logger LOG = Logger.getInstance(ChangeMethodSignatureFromUsageFix.class); |
| |
| public ChangeMethodSignatureFromUsageFix(@NotNull PsiMethod targetMethod, |
| @NotNull PsiExpression[] expressions, |
| @NotNull PsiSubstitutor substitutor, |
| @NotNull PsiElement context, |
| boolean changeAllUsages, int minUsagesNumberToShowDialog) { |
| myTargetMethod = targetMethod; |
| myExpressions = expressions; |
| mySubstitutor = substitutor; |
| myContext = context; |
| myChangeAllUsages = changeAllUsages; |
| myMinUsagesNumberToShowDialog = minUsagesNumberToShowDialog; |
| } |
| |
| @Override |
| @NotNull |
| public String getText() { |
| final String shortText = getShortText(); |
| if (shortText != null) return shortText; |
| return QuickFixBundle.message("change.method.signature.from.usage.text", |
| JavaHighlightUtil.formatMethod(myTargetMethod), |
| myTargetMethod.getName(), |
| formatTypesList(myNewParametersInfo, myContext)); |
| } |
| |
| @Nullable |
| private String getShortText() { |
| final StringBuilder buf = new StringBuilder(); |
| final HashSet<ParameterInfoImpl> newParams = new HashSet<ParameterInfoImpl>(); |
| final HashSet<ParameterInfoImpl> removedParams = new HashSet<ParameterInfoImpl>(); |
| final HashSet<ParameterInfoImpl> changedParams = new HashSet<ParameterInfoImpl>(); |
| getNewParametersInfo(myExpressions, myTargetMethod, mySubstitutor, buf, newParams, removedParams, 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 QuickFixBundle.message("add.parameter.from.usage.text", p.getTypeText(), (ArrayUtil.find(myNewParametersInfo, p) + 1), targetMethodName); |
| } |
| if (removedParams.size() == 1) { |
| final ParameterInfoImpl p = removedParams.iterator().next(); |
| return QuickFixBundle.message("remove.parameter.from.usage.text", (p.getOldIndex() + 1), targetMethodName); |
| } |
| if (changedParams.size() == 1) { |
| final ParameterInfoImpl p = changedParams.iterator().next(); |
| return QuickFixBundle.message("change.parameter.from.usage.text", (p.getOldIndex() + 1), targetMethodName, myTargetMethod.getParameterList().getParameters()[p.getOldIndex()].getType().getPresentableText(), p.getTypeText()); |
| } |
| } |
| return "<html> Change signature of " + targetMethodName + "(" + buf.toString() + ")</html>"; |
| } |
| |
| @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, context.getManager()); |
| if (type == null) return null; |
| if (result.length() != 0) result.append(", "); |
| result.append(type.getPresentableText()); |
| } |
| return result.toString(); |
| } |
| catch (IncorrectOperationException e) { |
| return null; |
| } |
| } |
| |
| @Override |
| @NotNull |
| public String getFamilyName() { |
| return QuickFixBundle.message("change.method.signature.from.usage.family"); |
| } |
| |
| @Override |
| public boolean isAvailable(@NotNull 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; |
| |
| myNewParametersInfo = getNewParametersInfo(myExpressions, myTargetMethod, mySubstitutor); |
| if (myNewParametersInfo == null || formatTypesList(myNewParametersInfo, myContext) == null) return false; |
| return !isMethodSignatureExists(); |
| } |
| |
| 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; |
| } |
| |
| @Override |
| public void invoke(@NotNull final Project project, Editor editor, final PsiFile file) { |
| if (!FileModificationService.getInstance().prepareFileForWrite(file)) return; |
| |
| final PsiMethod method = SuperMethodWarningUtil.checkSuperMethod(myTargetMethod, RefactoringBundle.message("to.refactor")); |
| if (method == null) return; |
| myNewParametersInfo = getNewParametersInfo(myExpressions, myTargetMethod, mySubstitutor); |
| |
| final List<ParameterInfoImpl> parameterInfos = |
| performChange(project, editor, file, method, myMinUsagesNumberToShowDialog, myNewParametersInfo, myChangeAllUsages, false); |
| if (parameterInfos != null) { |
| myNewParametersInfo = parameterInfos.toArray(new ParameterInfoImpl[parameterInfos.size()]); |
| } |
| } |
| |
| public static List<ParameterInfoImpl> performChange(final Project project, |
| final Editor editor, |
| final PsiFile file, |
| final PsiMethod method, |
| final int minUsagesNumber, |
| final ParameterInfoImpl[] newParametersInfo, |
| final boolean changeAllUsages, |
| final boolean allowDelegation) { |
| 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) |
| |
| 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 = new Runnable() { |
| @Override |
| public void run() { |
| Processor<UsageInfo> processor = new Processor<UsageInfo>() { |
| @Override |
| public boolean process(final UsageInfo t) { |
| return ++usagesFound[0] < minUsagesNumber; |
| } |
| }; |
| |
| handler.processElementUsages(method, processor, options); |
| } |
| }; |
| String progressTitle = QuickFixBundle.message("searching.for.usages.progress.title"); |
| if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(runnable, progressTitle, true, project)) return null; |
| |
| if (ApplicationManager.getApplication().isUnitTestMode() || usagesFound[0] < minUsagesNumber) { |
| ChangeSignatureProcessor processor = new ChangeSignatureProcessor( |
| project, |
| method, |
| false, null, |
| method.getName(), |
| method.getReturnType(), |
| newParametersInfo){ |
| @Override |
| @NotNull |
| protected UsageInfo[] findUsages() { |
| return changeAllUsages ? super.findUsages() : UsageInfo.EMPTY_ARRAY; |
| } |
| |
| @Override |
| protected void performRefactoring(UsageInfo[] usages) { |
| CommandProcessor.getInstance().setCurrentCommandName(getCommandName()); |
| super.performRefactoring(usages); |
| } |
| }; |
| processor.run(); |
| ApplicationManager.getApplication().runWriteAction(new Runnable() { |
| @Override |
| public void run() { |
| UndoUtil.markPsiFileForUndo(file); |
| } |
| }); |
| } |
| else { |
| final List<ParameterInfoImpl> parameterInfos = newParametersInfo != null |
| ? new ArrayList<ParameterInfoImpl>(Arrays.asList(newParametersInfo)) |
| : new ArrayList<ParameterInfoImpl>(); |
| final PsiReferenceExpression refExpr = TargetElementUtil.findReferenceExpression(editor); |
| JavaChangeSignatureDialog dialog = JavaChangeSignatureDialog.createAndPreselectNew(project, method, parameterInfos, allowDelegation, refExpr); |
| dialog.setParameterInfos(parameterInfos); |
| dialog.show(); |
| return dialog.getParameters(); |
| } |
| return null; |
| } |
| |
| 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(); |
| } |
| } |
| return null; |
| } |
| |
| protected ParameterInfoImpl[] getNewParametersInfo(PsiExpression[] expressions, |
| PsiMethod targetMethod, |
| PsiSubstitutor substitutor) { |
| return getNewParametersInfo(expressions, targetMethod, substitutor, new StringBuilder(), new HashSet<ParameterInfoImpl>(), new HashSet<ParameterInfoImpl>(), new HashSet<ParameterInfoImpl>()); |
| } |
| |
| private ParameterInfoImpl[] getNewParametersInfo(PsiExpression[] expressions, |
| PsiMethod targetMethod, |
| PsiSubstitutor substitutor, |
| final StringBuilder buf, |
| final HashSet<ParameterInfoImpl> newParams, |
| final HashSet<ParameterInfoImpl> removedParams, |
| final HashSet<ParameterInfoImpl> changedParams) { |
| PsiParameter[] parameters = targetMethod.getParameterList().getParameters(); |
| List<ParameterInfoImpl> result = new ArrayList<ParameterInfoImpl>(); |
| 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(", "); |
| final PsiType parameterType = PsiUtil.convertAnonymousToBaseType(paramType); |
| final String presentableText = escapePresentableType(parameterType); |
| final ParameterInfoImpl parameterInfo = new ParameterInfoImpl(pi, parameter.getName(), parameterType); |
| if (TypeConversionUtil.areTypesAssignmentCompatible(paramType, expression)) { |
| buf.append(presentableText); |
| result.add(parameterInfo); |
| pi++; |
| ei++; |
| } |
| else { |
| buf.append("<s>").append(presentableText).append("</s>"); |
| 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("<s>").append(escapePresentableType(parameters[i].getType())).append("</s>"); |
| final ParameterInfoImpl parameterInfo = new ParameterInfoImpl(pi, parameters[i].getName(), 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(", "); |
| PsiParameter parameter = parameters[i]; |
| PsiExpression expression = expressions[i]; |
| PsiType paramType = substitutor.substitute(parameter.getType()); |
| final String presentableText = escapePresentableType(paramType); |
| if (TypeConversionUtil.areTypesAssignmentCompatible(paramType, expression)) { |
| result.add(new ParameterInfoImpl(i, parameter.getName(), paramType)); |
| buf.append(presentableText); |
| } |
| else { |
| PsiType exprType = RefactoringUtil.getTypeByExpression(expression); |
| if (exprType == null) return null; |
| if (exprType instanceof PsiDisjunctionType) { |
| exprType = ((PsiDisjunctionType)exprType).getLeastUpperBound(); |
| } |
| final ParameterInfoImpl changedParameterInfo = new ParameterInfoImpl(i, parameter.getName(), exprType); |
| result.add(changedParameterInfo); |
| changedParams.add(changedParameterInfo); |
| buf.append("<s>").append(presentableText).append("</s> <b>").append(escapePresentableType(exprType)).append("</b>"); |
| } |
| } |
| // 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; |
| } |
| return result.toArray(new ParameterInfoImpl[result.size()]); |
| } |
| |
| protected static String escapePresentableType(PsiType exprType) { |
| return StringUtil.escapeXml(exprType.getPresentableText()); |
| } |
| |
| protected boolean findNewParamsPlace(PsiExpression[] expressions, |
| PsiMethod targetMethod, |
| PsiSubstitutor substitutor, |
| StringBuilder buf, |
| HashSet<ParameterInfoImpl> newParams, |
| PsiParameter[] parameters, |
| List<ParameterInfoImpl> result) { |
| // find which parameters to introduce and where |
| Set<String> existingNames = new HashSet<String>(); |
| 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) { |
| final PsiType type = parameter.getType(); |
| result.add(new ParameterInfoImpl(pi, parameter.getName(), 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(new ParameterInfoImpl(pi, varargParam.getName(), type)); |
| buf.append(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; |
| if (exprType instanceof PsiDisjunctionType) { |
| exprType = ((PsiDisjunctionType)exprType).getLeastUpperBound(); |
| } |
| JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(expression.getProject()); |
| String name = suggestUniqueParameterName(codeStyleManager, expression, exprType, existingNames); |
| final ParameterInfoImpl newParameterInfo = new ParameterInfoImpl(-1, name, exprType, expression.getText().replace('\n', ' ')); |
| result.add(newParameterInfo); |
| newParams.add(newParameterInfo); |
| buf.append("<b>").append(escapePresentableType(exprType)).append("</b>"); |
| ei++; |
| } |
| } |
| if (result.size() != expressions.length && varargParam == null) return false; |
| return true; |
| } |
| |
| static boolean isArgumentInVarargPosition(PsiExpression[] expressions, int ei, PsiParameter varargParam, PsiSubstitutor substitutor) { |
| if (varargParam == null) return false; |
| 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<String> 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); |
| } |
| } |
| 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++; |
| } |
| } |
| |
| @Override |
| public boolean startInWriteAction() { |
| return false; |
| } |
| } |