blob: c730d9c92a63e61100c0b3e4fc6611e4fbdd1f9f [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 org.jetbrains.plugins.groovy.refactoring.introduce.parameter.java2groovy;
import com.intellij.codeInsight.ChangeContextUtil;
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.javadoc.PsiDocTag;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.impl.ExpressionConverter;
import com.intellij.refactoring.introduceParameter.IntroduceParameterData;
import com.intellij.refactoring.introduceParameter.IntroduceParameterMethodUsagesProcessor;
import com.intellij.refactoring.introduceParameter.IntroduceParameterUtil;
import com.intellij.refactoring.util.javadoc.MethodJavaDocHelper;
import com.intellij.usageView.UsageInfo;
import com.intellij.util.ArrayUtil;
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;
import org.jetbrains.plugins.groovy.GroovyLanguage;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
import org.jetbrains.plugins.groovy.lang.psi.api.signatures.GrClosureSignature;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrConstructorInvocation;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrParametersOwner;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrNamedArgument;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrOpenBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrCall;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameterList;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil;
import org.jetbrains.plugins.groovy.lang.psi.impl.signatures.GrClosureSignatureUtil;
import org.jetbrains.plugins.groovy.refactoring.GroovyRefactoringUtil;
import org.jetbrains.plugins.groovy.refactoring.introduce.parameter.GroovyIntroduceParameterUtil;
/**
* @author Maxim.Medvedev
* Date: Apr 18, 2009 3:16:24 PM
*/
public class GroovyIntroduceParameterMethodUsagesProcessor implements IntroduceParameterMethodUsagesProcessor {
private static final Logger LOG = Logger.getInstance(GroovyIntroduceParameterMethodUsagesProcessor.class);
private static boolean isGroovyUsage(UsageInfo usage) {
final PsiElement el = usage.getElement();
return el != null && GroovyLanguage.INSTANCE.equals(el.getLanguage());
}
@Override
public boolean isMethodUsage(UsageInfo usage) {
return GroovyRefactoringUtil.isMethodUsage(usage.getElement()) && isGroovyUsage(usage);
}
@Override
public void findConflicts(IntroduceParameterData data, UsageInfo[] usages, MultiMap<PsiElement, String> conflicts) {
}
@Override
public boolean processChangeMethodUsage(IntroduceParameterData data, UsageInfo usage, UsageInfo[] usages) throws IncorrectOperationException {
GrCall callExpression = GroovyRefactoringUtil.getCallExpressionByMethodReference(usage.getElement());
if (callExpression == null) return true;
GrArgumentList argList = callExpression.getArgumentList();
GrExpression[] oldArgs = argList.getExpressionArguments();
final GrExpression anchor;
if (!data.getMethodToSearchFor().isVarArgs()) {
anchor = getLast(oldArgs);
}
else {
final PsiParameter[] parameters = data.getMethodToSearchFor().getParameterList().getParameters();
if (parameters.length > oldArgs.length) {
anchor = getLast(oldArgs);
}
else {
final int lastNonVararg = parameters.length - 2;
anchor = lastNonVararg >= 0 ? oldArgs[lastNonVararg] : null;
}
}
PsiMethod method = PsiTreeUtil.getParentOfType(argList, PsiMethod.class);
GrClosureSignature signature = GrClosureSignatureUtil.createSignature(callExpression);
if (signature == null) signature = GrClosureSignatureUtil.createSignature(data.getMethodToSearchFor(), PsiSubstitutor.EMPTY);
final GrClosureSignatureUtil.ArgInfo<PsiElement>[] actualArgs = GrClosureSignatureUtil
.mapParametersToArguments(signature, callExpression.getNamedArguments(), callExpression.getExpressionArguments(),
callExpression.getClosureArguments(), callExpression, false, true);
final GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(data.getProject());
if (method != null && IntroduceParameterUtil.isMethodInUsages(data, method, usages)) {
argList.addAfter(factory.createExpressionFromText(data.getParameterName()), anchor);
}
else {
final PsiElement _expr = data.getParameterInitializer().getExpression();
PsiElement initializer = ExpressionConverter.getExpression(_expr, GroovyLanguage.INSTANCE, data.getProject());
LOG.assertTrue(initializer instanceof GrExpression);
GrExpression newArg = GroovyIntroduceParameterUtil.addClosureToCall(initializer, argList);
if (newArg == null) {
final PsiElement dummy = argList.addAfter(factory.createExpressionFromText("1"), anchor);
newArg = ((GrExpression)dummy).replaceWithExpression((GrExpression)initializer, true);
}
final PsiMethod methodToReplaceIn = data.getMethodToReplaceIn();
new OldReferencesResolver(callExpression, newArg, methodToReplaceIn, data.getReplaceFieldsWithGetters(), initializer,
signature, actualArgs, methodToReplaceIn.getParameterList().getParameters()).resolve();
ChangeContextUtil.clearContextInfo(initializer);
//newArg can be replaced by OldReferenceResolver
if (newArg.isValid()) {
JavaCodeStyleManager.getInstance(newArg.getProject()).shortenClassReferences(newArg);
CodeStyleManager.getInstance(data.getProject()).reformat(newArg);
}
}
if (actualArgs == null) {
removeParamsFromUnresolvedCall(callExpression, data);
}
else {
removeParametersFromCall(actualArgs, data.getParametersToRemove());
}
if (argList.getAllArguments().length == 0 && PsiImplUtil.hasClosureArguments(callExpression)) {
final GrArgumentList emptyArgList = ((GrMethodCallExpression)factory.createExpressionFromText("foo{}")).getArgumentList();
LOG.assertTrue(emptyArgList != null);
argList.replace(emptyArgList);
}
return false;
}
@Nullable
private static GrExpression getLast(GrExpression[] oldArgs) {
GrExpression anchor;
if (oldArgs.length > 0) {
anchor = oldArgs[oldArgs.length - 1];
}
else {
anchor = null;
}
return anchor;
}
private static void removeParametersFromCall(final GrClosureSignatureUtil.ArgInfo<PsiElement>[] actualArgs,final TIntArrayList parametersToRemove) {
parametersToRemove.forEach(new TIntProcedure() {
@Override
public boolean execute(final int paramNum) {
try {
final GrClosureSignatureUtil.ArgInfo<PsiElement> actualArg = actualArgs[paramNum];
if (actualArg == null) return true;
for (PsiElement arg : actualArg.args) {
arg.delete();
}
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
return true;
}
});
}
private static void removeParamsFromUnresolvedCall(GrCall callExpression, IntroduceParameterData data) {
final GrExpression[] arguments = callExpression.getExpressionArguments();
final GrClosableBlock[] closureArguments = callExpression.getClosureArguments();
final GrNamedArgument[] namedArguments = callExpression.getNamedArguments();
final boolean hasNamedArgs;
if (namedArguments.length > 0) {
final PsiMethod method = data.getMethodToSearchFor();
final PsiParameter[] parameters = method.getParameterList().getParameters();
if (parameters.length > 0) {
final PsiType type = parameters[0].getType();
hasNamedArgs = InheritanceUtil.isInheritor(type, CommonClassNames.JAVA_UTIL_MAP);
}
else {
hasNamedArgs = false;
}
}
else {
hasNamedArgs = false;
}
data.getParametersToRemove().forEachDescending(new TIntProcedure() {
@Override
public boolean execute(int paramNum) {
try {
if (paramNum == 0 && hasNamedArgs) {
for (GrNamedArgument namedArgument : namedArguments) {
namedArgument.delete();
}
}
else {
if (hasNamedArgs) paramNum--;
if (paramNum < arguments.length) {
arguments[paramNum].delete();
}
else if (paramNum < arguments.length + closureArguments.length) {
closureArguments[paramNum - arguments.length].delete();
}
}
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
return true;
}
});
}
@Override
public boolean processChangeMethodSignature(IntroduceParameterData data, UsageInfo usage, UsageInfo[] usages) throws IncorrectOperationException {
if (!(usage.getElement() instanceof GrMethod) || !isGroovyUsage(usage)) return true;
GrMethod method = (GrMethod)usage.getElement();
final FieldConflictsResolver fieldConflictsResolver = new FieldConflictsResolver(data.getParameterName(), method.getBlock());
final MethodJavaDocHelper javaDocHelper = new MethodJavaDocHelper(method);
final PsiParameter[] parameters = method.getParameterList().getParameters();
data.getParametersToRemove().forEachDescending(new TIntProcedure() {
@Override
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;
}
});
addParameter(method, javaDocHelper, data.getForcedType(), data.getParameterName(), data.isDeclareFinal(), data.getProject());
fieldConflictsResolver.fix();
return false;
}
@NotNull
public static GrParameter addParameter(@NotNull GrParametersOwner parametersOwner,
@Nullable MethodJavaDocHelper javaDocHelper,
@NotNull PsiType forcedType,
@NotNull String parameterName,
boolean isFinal,
@NotNull Project project) {
GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(project);
final String typeText = forcedType.equalsToText(CommonClassNames.JAVA_LANG_OBJECT) || forcedType == PsiType.NULL ? null : forcedType.getCanonicalText();
GrParameter parameter = factory.createParameter(parameterName, typeText, parametersOwner);
parameter.getModifierList().setModifierProperty(PsiModifier.FINAL, isFinal);
final PsiParameter anchorParameter = getAnchorParameter(parametersOwner);
final GrParameterList parameterList = parametersOwner.getParameterList();
parameter = (GrParameter)parameterList.addAfter(parameter, anchorParameter);
JavaCodeStyleManager.getInstance(project).shortenClassReferences(parameter);
if (javaDocHelper != null) {
final PsiDocTag tagForAnchorParameter = javaDocHelper.getTagForParameter(anchorParameter);
javaDocHelper.addParameterAfter(parameterName, tagForAnchorParameter);
}
return parameter;
}
@Nullable
private static PsiParameter getAnchorParameter(GrParametersOwner parametersOwner) {
PsiParameterList parameterList = parametersOwner.getParameterList();
final PsiParameter anchorParameter;
final PsiParameter[] parameters = parameterList.getParameters();
final int length = parameters.length;
if (!parametersOwner.isVarArgs()) {
anchorParameter = length > 0 ? parameters[length - 1] : null;
}
else {
anchorParameter = length > 1 ? parameters[length - 2] : null;
}
return anchorParameter;
}
@Override
public boolean processAddDefaultConstructor(IntroduceParameterData data, UsageInfo usage, UsageInfo[] usages) {
if (!(usage.getElement() instanceof PsiClass) || !isGroovyUsage(usage)) return true;
PsiClass aClass = (PsiClass)usage.getElement();
final GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(data.getProject());
GrMethod constructor =
factory.createConstructorFromText(aClass.getName(), ArrayUtil.EMPTY_STRING_ARRAY, ArrayUtil.EMPTY_STRING_ARRAY, "{}");
constructor = (GrMethod)aClass.add(constructor);
constructor.getModifierList().setModifierProperty(VisibilityUtil.getVisibilityModifier(aClass.getModifierList()), true);
processAddSuperCall(data, new UsageInfo(constructor), usages);
return false;
}
@Override
public boolean processAddSuperCall(IntroduceParameterData data, UsageInfo usage, UsageInfo[] usages) throws IncorrectOperationException {
if (!(usage.getElement() instanceof GrMethod) || !isGroovyUsage(usage)) return true;
GrMethod constructor = (GrMethod)usage.getElement();
if (!constructor.isConstructor()) return true;
final GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(data.getProject());
GrConstructorInvocation superCall = factory.createConstructorInvocation("super();");
GrOpenBlock body = constructor.getBlock();
final GrStatement[] statements = body.getStatements();
if (statements.length > 0) {
superCall = (GrConstructorInvocation)body.addStatementBefore(superCall, statements[0]);
}
else {
superCall = (GrConstructorInvocation)body.addStatementBefore(superCall, null);
}
processChangeMethodUsage(data, new UsageInfo(superCall), usages);
return false;
}
}