| /* |
| * 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.introduceParameter; |
| |
| import com.intellij.codeInsight.ChangeContextUtil; |
| import com.intellij.lang.Language; |
| import com.intellij.lang.StdLanguages; |
| import com.intellij.lang.java.JavaLanguage; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.psi.*; |
| import com.intellij.psi.codeStyle.CodeStyleManager; |
| import com.intellij.psi.codeStyle.JavaCodeStyleManager; |
| import com.intellij.psi.impl.ExpressionConverter; |
| import com.intellij.psi.impl.PsiDiamondTypeUtil; |
| import com.intellij.psi.impl.source.resolve.DefaultParameterTypeInferencePolicy; |
| import com.intellij.psi.javadoc.PsiDocTag; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.psi.util.PsiUtil; |
| import com.intellij.refactoring.util.FieldConflictsResolver; |
| import com.intellij.refactoring.util.RefactoringUtil; |
| import com.intellij.refactoring.util.javadoc.MethodJavaDocHelper; |
| import com.intellij.usageView.UsageInfo; |
| import com.intellij.util.IncorrectOperationException; |
| import com.intellij.util.VisibilityUtil; |
| import com.intellij.util.containers.MultiMap; |
| import gnu.trove.TIntArrayList; |
| import gnu.trove.TIntProcedure; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| /** |
| * @author Maxim.Medvedev |
| */ |
| public class JavaIntroduceParameterMethodUsagesProcessor implements IntroduceParameterMethodUsagesProcessor { |
| private static final Logger LOG = |
| Logger.getInstance("#com.intellij.refactoring.introduceParameter.JavaIntroduceParameterMethodUsagesProcessor"); |
| private static final JavaLanguage myLanguage = Language.findInstance(JavaLanguage.class); |
| |
| private static boolean isJavaUsage(UsageInfo usage) { |
| PsiElement e = usage.getElement(); |
| return e != null && e.getLanguage().is(myLanguage); |
| } |
| |
| public boolean isMethodUsage(UsageInfo usage) { |
| return RefactoringUtil.isMethodUsage(usage.getElement()) && isJavaUsage(usage); |
| } |
| |
| public boolean processChangeMethodUsage(IntroduceParameterData data, UsageInfo usage, UsageInfo[] usages) throws IncorrectOperationException { |
| if (!isMethodUsage(usage)) return true; |
| final PsiElement ref = usage.getElement(); |
| PsiCall callExpression = RefactoringUtil.getCallExpressionByMethodReference(ref); |
| PsiExpressionList argList = RefactoringUtil.getArgumentListByMethodReference(ref); |
| if (argList == null) return true; |
| PsiExpression[] oldArgs = argList.getExpressions(); |
| |
| final PsiExpression anchor; |
| final PsiMethod methodToSearchFor = data.getMethodToSearchFor(); |
| if (!methodToSearchFor.isVarArgs()) { |
| anchor = getLast(oldArgs); |
| } |
| else { |
| final PsiParameter[] parameters = methodToSearchFor.getParameterList().getParameters(); |
| if (parameters.length > oldArgs.length) { |
| anchor = getLast(oldArgs); |
| } |
| else { |
| LOG.assertTrue(parameters.length > 0); |
| final int lastNonVararg = parameters.length - 2; |
| anchor = lastNonVararg >= 0 ? oldArgs[lastNonVararg] : null; |
| } |
| } |
| |
| //if we insert parameter in method usage which is contained in method in which we insert this parameter too, we must insert parameter name instead of its initializer |
| PsiMethod method = PsiTreeUtil.getParentOfType(argList, PsiMethod.class); |
| if (method != null && IntroduceParameterUtil.isMethodInUsages(data, method, usages)) { |
| argList |
| .addAfter(JavaPsiFacade.getElementFactory(data.getProject()).createExpressionFromText(data.getParameterName(), argList), anchor); |
| } |
| else { |
| PsiElement initializer = |
| ExpressionConverter.getExpression(data.getParameterInitializer().getExpression(), StdLanguages.JAVA, data.getProject()); |
| assert initializer instanceof PsiExpression; |
| if (initializer instanceof PsiNewExpression) { |
| if (!PsiDiamondTypeUtil.canChangeContextForDiamond((PsiNewExpression)initializer, ((PsiNewExpression)initializer).getType())) { |
| initializer = PsiDiamondTypeUtil.expandTopLevelDiamondsInside((PsiNewExpression)initializer); |
| } |
| } |
| substituteTypeParametersInInitializer(initializer, callExpression, argList, methodToSearchFor); |
| ChangeContextUtil.encodeContextInfo(initializer, true); |
| PsiExpression newArg = (PsiExpression)argList.addAfter(initializer, anchor); |
| ChangeContextUtil.decodeContextInfo(newArg, null, null); |
| ChangeContextUtil.clearContextInfo(initializer); |
| |
| // here comes some postprocessing... |
| new OldReferenceResolver(callExpression, newArg, data.getMethodToReplaceIn(), data.getReplaceFieldsWithGetters(), initializer) |
| .resolve(); |
| } |
| |
| |
| final PsiExpressionList argumentList = callExpression.getArgumentList(); |
| LOG.assertTrue(argumentList != null, callExpression.getText()); |
| removeParametersFromCall(argumentList, data.getParametersToRemove()); |
| return false; |
| } |
| |
| private static void substituteTypeParametersInInitializer(PsiElement initializer, |
| PsiCall callExpression, |
| PsiExpressionList argList, |
| PsiMethod method) { |
| final Project project = method.getProject(); |
| final PsiSubstitutor psiSubstitutor = JavaPsiFacade.getInstance(project).getResolveHelper() |
| .inferTypeArguments(method.getTypeParameters(), method.getParameterList().getParameters(), |
| argList.getExpressions(), PsiSubstitutor.EMPTY, callExpression, |
| DefaultParameterTypeInferencePolicy.INSTANCE); |
| RefactoringUtil.replaceMovedMemberTypeParameters(initializer, PsiUtil.typeParametersIterable(method), psiSubstitutor, |
| JavaPsiFacade.getElementFactory(project)); |
| } |
| |
| private static void removeParametersFromCall(@NotNull final PsiExpressionList argList, TIntArrayList parametersToRemove) { |
| final PsiExpression[] exprs = argList.getExpressions(); |
| parametersToRemove.forEachDescending(new TIntProcedure() { |
| public boolean execute(final int paramNum) { |
| try { |
| if (paramNum < exprs.length) { |
| exprs[paramNum].delete(); |
| } |
| } |
| catch (IncorrectOperationException e) { |
| LOG.error(e); |
| } |
| return true; |
| } |
| }); |
| } |
| |
| @Nullable |
| private static PsiExpression getLast(PsiExpression[] oldArgs) { |
| PsiExpression anchor; |
| if (oldArgs.length > 0) { |
| anchor = oldArgs[oldArgs.length - 1]; |
| } |
| else { |
| anchor = null; |
| } |
| return anchor; |
| } |
| |
| |
| public void findConflicts(IntroduceParameterData data, UsageInfo[] usages, final MultiMap<PsiElement, String> conflicts) { |
| final PsiMethod method = data.getMethodToReplaceIn(); |
| final int parametersCount = method.getParameterList().getParametersCount(); |
| for (UsageInfo usage : usages) { |
| if (!isMethodUsage(usage)) continue; |
| final PsiElement element = usage.getElement(); |
| final PsiCall call = RefactoringUtil.getCallExpressionByMethodReference(element); |
| final PsiExpressionList argList = call.getArgumentList(); |
| if (argList != null) { |
| final int actualParamLength = argList.getExpressions().length; |
| if ((method.isVarArgs() && actualParamLength + 1 < parametersCount) || |
| (!method.isVarArgs() && actualParamLength < parametersCount)) { |
| conflicts.putValue(call, "Incomplete call(" + call.getText() +"): " + parametersCount + " parameters expected but only " + actualParamLength + " found"); |
| } |
| data.getParametersToRemove().forEach(new TIntProcedure() { |
| public boolean execute(int paramNum) { |
| if (paramNum >= actualParamLength) { |
| conflicts.putValue(call, "Incomplete call(" + call.getText() +"): expected to delete the " + paramNum + " parameter but only " + actualParamLength + " parameters found"); |
| } |
| return true; |
| } |
| }); |
| } |
| } |
| } |
| |
| public boolean processChangeMethodSignature(IntroduceParameterData data, UsageInfo usage, UsageInfo[] usages) throws IncorrectOperationException { |
| if (!(usage.getElement() instanceof PsiMethod) || !isJavaUsage(usage)) return true; |
| PsiMethod method = (PsiMethod)usage.getElement(); |
| |
| final FieldConflictsResolver fieldConflictsResolver = new FieldConflictsResolver(data.getParameterName(), method.getBody()); |
| final MethodJavaDocHelper javaDocHelper = new MethodJavaDocHelper(method); |
| PsiElementFactory factory = JavaPsiFacade.getInstance(data.getProject()).getElementFactory(); |
| |
| PsiParameter parameter = factory.createParameter(data.getParameterName(), data.getForcedType()); |
| PsiUtil.setModifierProperty(parameter, PsiModifier.FINAL, data.isDeclareFinal()); |
| |
| final PsiParameterList parameterList = method.getParameterList(); |
| final PsiParameter[] parameters = parameterList.getParameters(); |
| data.getParametersToRemove().forEachDescending(new TIntProcedure() { |
| public boolean execute(final int paramNum) { |
| try { |
| PsiParameter param = parameters[paramNum]; |
| PsiDocTag tag = javaDocHelper.getTagForParameter(param); |
| if (tag != null) { |
| tag.delete(); |
| } |
| param.delete(); |
| } |
| catch (IncorrectOperationException e) { |
| LOG.error(e); |
| } |
| return true; |
| } |
| }); |
| |
| final PsiParameter anchorParameter = getAnchorParameter(method); |
| parameter = (PsiParameter)parameterList.addAfter(parameter, anchorParameter); |
| JavaCodeStyleManager.getInstance(data.getProject()).shortenClassReferences(parameter); |
| final PsiDocTag tagForAnchorParameter = javaDocHelper.getTagForParameter(anchorParameter); |
| javaDocHelper.addParameterAfter(data.getParameterName(), tagForAnchorParameter); |
| |
| fieldConflictsResolver.fix(); |
| |
| return false; |
| } |
| |
| @Nullable |
| public static PsiParameter getAnchorParameter(PsiMethod methodToReplaceIn) { |
| PsiParameterList parameterList = methodToReplaceIn.getParameterList(); |
| final PsiParameter anchorParameter; |
| final PsiParameter[] parameters = parameterList.getParameters(); |
| final int length = parameters.length; |
| if (!methodToReplaceIn.isVarArgs()) { |
| anchorParameter = length > 0 ? parameters[length - 1] : null; |
| } |
| else { |
| LOG.assertTrue(length > 0); |
| LOG.assertTrue(parameters[length - 1].isVarArgs()); |
| anchorParameter = length > 1 ? parameters[length - 2] : null; |
| } |
| return anchorParameter; |
| } |
| |
| public boolean processAddDefaultConstructor(IntroduceParameterData data, UsageInfo usage, UsageInfo[] usages) { |
| if (!(usage.getElement() instanceof PsiClass) || !isJavaUsage(usage)) return true; |
| PsiClass aClass = (PsiClass)usage.getElement(); |
| if (!(aClass instanceof PsiAnonymousClass)) { |
| final PsiElementFactory factory = JavaPsiFacade.getInstance(data.getProject()).getElementFactory(); |
| PsiMethod constructor = factory.createMethodFromText(aClass.getName() + "(){}", aClass); |
| constructor = (PsiMethod)CodeStyleManager.getInstance(data.getProject()).reformat(constructor); |
| constructor = (PsiMethod)aClass.add(constructor); |
| PsiUtil.setModifierProperty(constructor, VisibilityUtil.getVisibilityModifier(aClass.getModifierList()), true); |
| processAddSuperCall(data, new UsageInfo(constructor), usages); |
| } |
| else { |
| return true; |
| } |
| return false; |
| } |
| |
| public boolean processAddSuperCall(IntroduceParameterData data, UsageInfo usage, UsageInfo[] usages) throws IncorrectOperationException { |
| if (!(usage.getElement() instanceof PsiMethod) || !isJavaUsage(usage)) return true; |
| PsiMethod constructor = (PsiMethod)usage.getElement(); |
| |
| if (!constructor.isConstructor()) return true; |
| |
| final PsiElementFactory factory = JavaPsiFacade.getInstance(data.getProject()).getElementFactory(); |
| PsiExpressionStatement superCall = (PsiExpressionStatement)factory.createStatementFromText("super();", constructor); |
| superCall = (PsiExpressionStatement)CodeStyleManager.getInstance(data.getProject()).reformat(superCall); |
| PsiCodeBlock body = constructor.getBody(); |
| final PsiStatement[] statements = body.getStatements(); |
| if (statements.length > 0) { |
| superCall = (PsiExpressionStatement)body.addBefore(superCall, statements[0]); |
| } |
| else { |
| superCall = (PsiExpressionStatement)body.add(superCall); |
| } |
| processChangeMethodUsage(data, new ExternalUsageInfo(((PsiMethodCallExpression)superCall.getExpression()).getMethodExpression()), usages); |
| return false; |
| } |
| } |