blob: 804332003764e32b5a976131cd7c4873b10cc738 [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.search.LocalSearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.IntroduceParameterRefactoring;
import com.intellij.refactoring.util.RefactoringUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.HashMap;
import com.intellij.util.containers.hash.HashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
import org.jetbrains.plugins.groovy.lang.psi.api.signatures.GrClosureSignature;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariableDeclaration;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.*;
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.typedef.members.GrAccessorMethod;
import org.jetbrains.plugins.groovy.lang.psi.impl.signatures.GrClosureSignatureUtil;
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyPropertyUtils;
import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
import org.jetbrains.plugins.groovy.refactoring.GroovyRefactoringUtil;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author Maxim.Medvedev
* Date: Apr 18, 2009 3:21:45 PM
*/
public class OldReferencesResolver {
private static final Logger LOG = Logger.getInstance(OldReferencesResolver.class);
private final GrCall myContext;
private final GrExpression myExpr;
private final HashMap<GrExpression, String> myTempVars;
private final GrExpression myInstanceRef;
private final GrClosureSignatureUtil.ArgInfo<PsiElement>[] myActualArgs;
private final PsiElement myToReplaceIn;
private final Project myProject;
private final int myReplaceFieldsWithGetters;
private final PsiElement myParameterInitializer;
private final PsiManager myManager;
private final PsiParameter[] myParameters;
private final GrClosureSignature mySignature;
private final Set<PsiParameter> myParamsToNotInline = new HashSet<PsiParameter>();
public OldReferencesResolver(GrCall context,
GrExpression expr,
PsiElement toReplaceIn,
int replaceFieldsWithGetters,
PsiElement parameterInitializer,
final GrClosureSignature signature,
final GrClosureSignatureUtil.ArgInfo<PsiElement>[] actualArgs, PsiParameter[] parameters) throws IncorrectOperationException {
myContext = context;
myExpr = expr;
myReplaceFieldsWithGetters = replaceFieldsWithGetters;
myParameterInitializer = parameterInitializer;
myParameters = parameters;
myTempVars = new HashMap<GrExpression, String>();
mySignature = signature;
myActualArgs = actualArgs;
myToReplaceIn = toReplaceIn;
myProject = myContext.getProject();
myManager = myContext.getManager();
if (myContext instanceof GrMethodCallExpression) {
final GrMethodCallExpression methodCall = (GrMethodCallExpression)myContext;
final GrExpression methodExpression = methodCall.getInvokedExpression();
if (methodExpression instanceof GrReferenceExpression) {
myInstanceRef = ((GrReferenceExpression)methodExpression).getQualifierExpression();
}
else if (methodExpression instanceof GrMethodCall) {
myInstanceRef = getQualifierFromGetterCall((GrMethodCall)methodExpression);
}
else {
myInstanceRef = null;
}
}
else {
myInstanceRef = null;
}
}
/**
* checks for the case: qualifier.getFoo()(args)
* @param methodExpression
*/
@Nullable
private static GrExpression getQualifierFromGetterCall(GrMethodCall methodExpression) {
final GroovyResolveResult result = methodExpression.advancedResolve();
if (!(result.getElement() instanceof GrAccessorMethod) || result.isInvokedOnProperty()) return null;
final GrExpression invoked = methodExpression.getInvokedExpression();
if (invoked instanceof GrReferenceExpression) return ((GrReferenceExpression)invoked).getQualifier();
return null;
}
public void resolve() throws IncorrectOperationException {
resolveOldReferences(myExpr, myParameterInitializer);
Set<Map.Entry<GrExpression, String>> mappingsSet = myTempVars.entrySet();
GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(myProject);
for (Map.Entry<GrExpression, String> entry : mappingsSet) {
GrExpression oldRef = entry.getKey();
PsiElement newRef = factory.createExpressionFromText(entry.getValue());
oldRef.replace(newRef);
}
}
private void resolveOldReferences(PsiElement expr, PsiElement oldExpr) throws IncorrectOperationException {
if (expr == null || !expr.isValid() || oldExpr == null) return;
GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(myProject);
PsiElement newExpr = expr; // references continue being resolved in the children of newExpr
if (oldExpr instanceof GrReferenceExpression) {
if (isThisReferenceToContainingClass(oldExpr) || isSimpleSuperReference(oldExpr)) {
if (myInstanceRef != null) {
newExpr.replace(getInstanceRef(factory));
}
return;
}
final GrReferenceExpression oldRef = (GrReferenceExpression)oldExpr;
newExpr = newExpr.replace(decodeReferenceExpression((GrReferenceExpression)newExpr, oldRef));
//newExpr = ((GrReferenceExpression)newExpr).getReferenceNameElement();
final GroovyResolveResult adv = oldRef.advancedResolve();
final PsiElement scope = getClassContainingResolve(adv);
final PsiElement owner = PsiUtil.getContextClass(oldExpr);
if (myToReplaceIn instanceof GrClosableBlock || (owner != null && scope != null && PsiTreeUtil.isContextAncestor(owner, scope, false))) {
final PsiElement subj = adv.getElement();
// Parameters
if (subj instanceof PsiParameter) {
int index = ArrayUtil.indexOf(myParameters, subj);
if (index < 0) return;
if (index < myParameters.length) {
newExpr = inlineParam(newExpr, getActualArg(index), ((PsiParameter)subj));
}
}
// "naked" field and methods (should become qualified)
else if ((subj instanceof PsiField || subj instanceof PsiMethod) && oldRef.getQualifierExpression() == null) {
PsiElement newResolved = newExpr instanceof GrReferenceExpression ? ((GrReferenceExpression)newExpr).resolve() : null;
if (myInstanceRef != null || !subj.getManager().areElementsEquivalent(newResolved, subj)) {
boolean isStatic = subj instanceof PsiField && ((PsiField)subj).hasModifierProperty(PsiModifier.STATIC) ||
subj instanceof PsiMethod && ((PsiMethod)subj).hasModifierProperty(PsiModifier.STATIC);
String name = ((PsiNamedElement)subj).getName();
boolean shouldBeAt = subj instanceof PsiField &&
!PsiTreeUtil.isAncestor(((PsiMember)subj).getContainingClass(), newExpr, true) &&
GroovyPropertyUtils.findGetterForField((PsiField)subj) != null;
final GrReferenceExpression fromText = factory.createReferenceExpressionFromText("qualifier." + (shouldBeAt ? "@" : "") + name);
if (isStatic) {
final GrReferenceExpression qualifier = factory.createReferenceElementForClass(((PsiMember)subj).getContainingClass());
newExpr = newExpr.replace(fromText);
((GrReferenceExpression)newExpr).setQualifier(qualifier);
newExpr = ((GrReferenceExpression)newExpr).getReferenceNameElement();
}
else {
if (myInstanceRef != null) {
GrExpression instanceRef = getInstanceRef(factory);
fromText.setQualifier(instanceRef);
newExpr = newExpr.replace(fromText);
newExpr = ((GrReferenceExpression)newExpr).getReferenceNameElement();
}
}
}
}
if (subj instanceof PsiField) {
// probably replacing field with a getter
if (myReplaceFieldsWithGetters != IntroduceParameterRefactoring.REPLACE_FIELDS_WITH_GETTERS_NONE) {
if (myReplaceFieldsWithGetters == IntroduceParameterRefactoring.REPLACE_FIELDS_WITH_GETTERS_ALL ||
myReplaceFieldsWithGetters == IntroduceParameterRefactoring.REPLACE_FIELDS_WITH_GETTERS_INACCESSIBLE &&
!JavaPsiFacade.getInstance(myProject).getResolveHelper().isAccessible((PsiMember)subj, newExpr, null)) {
newExpr = replaceFieldWithGetter(newExpr, (PsiField)subj);
}
}
}
}
}
else {
PsiClass refClass = oldExpr.getCopyableUserData(ChangeContextUtil.REF_CLASS_KEY);
if (refClass != null && refClass.isValid()) {
PsiReference ref = newExpr.getReference();
if (ref != null) {
final String qualifiedName = refClass.getQualifiedName();
if (qualifiedName != null) {
if (JavaPsiFacade.getInstance(refClass.getProject()).findClass(qualifiedName, oldExpr.getResolveScope()) != null) {
newExpr = ref.bindToElement(refClass);
}
}
}
}
}
PsiElement[] oldChildren = oldExpr.getChildren();
PsiElement[] newChildren = newExpr.getChildren();
if (oldExpr instanceof GrNewExpression && newExpr instanceof GrNewExpression) { //special new-expression case
resolveOldReferences(((GrNewExpression)newExpr).getReferenceElement(),
((GrNewExpression)oldExpr).getReferenceElement());
resolveOldReferences(((GrNewExpression)newExpr).getArgumentList(), ((GrNewExpression)oldExpr).getArgumentList());
if (newChildren[1] instanceof GrArrayDeclaration) {
for (GrExpression expression : ((GrArrayDeclaration)newChildren[1]).getBoundExpressions()) {
resolveOldReferences(expression, oldChildren[1]);
}
}
}
else {
if (oldExpr instanceof GrReferenceExpression && newExpr instanceof GrReferenceExpression) {
final GrExpression oldQualifier = ((GrReferenceExpression)oldExpr).getQualifierExpression();
final GrExpression newQualifier = ((GrReferenceExpression)newExpr).getQualifierExpression();
if (oldQualifier != null && newQualifier != null) {
resolveOldReferences(newQualifier, oldQualifier);
return;
}
}
if (oldChildren.length == newChildren.length) {
for (int i = 0; i < newChildren.length; i++) {
resolveOldReferences(newChildren[i], oldChildren[i]);
}
}
}
}
private PsiElement inlineParam(PsiElement newExpr, GrExpression actualArg, PsiParameter parameter) {
if (myParamsToNotInline.contains(parameter)) return newExpr;
GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(myProject);
if (myExpr instanceof GrClosableBlock) {
int count = 0;
for (PsiReference reference : ReferencesSearch.search(parameter, new LocalSearchScope(myParameterInitializer))) {
count++;
if (count > 1) break;
}
if (count > 1) {
myParamsToNotInline.add(parameter);
final PsiType type;
if (parameter instanceof GrParameter) {
type = ((GrParameter)parameter).getDeclaredType();
}
else {
type = parameter.getType();
}
final GrVariableDeclaration declaration =
factory.createVariableDeclaration(ArrayUtil.EMPTY_STRING_ARRAY, actualArg, type, parameter.getName());
final GrStatement[] statements = ((GrClosableBlock)myExpr).getStatements();
GrStatement anchor = statements.length > 0 ? statements[0] : null;
return ((GrClosableBlock)myExpr).addStatementBefore(declaration, anchor);
}
}
int copyingSafetyLevel = GroovyRefactoringUtil.verifySafeCopyExpression(actualArg);
if (copyingSafetyLevel == RefactoringUtil.EXPR_COPY_PROHIBITED) {
actualArg = factory.createExpressionFromText(getTempVar(actualArg));
}
newExpr = newExpr.replace(actualArg);
return newExpr;
}
private static boolean isSimpleSuperReference(PsiElement oldExpr) {
if (oldExpr instanceof GrReferenceExpression) {
GrReferenceExpression ref = (GrReferenceExpression)oldExpr;
if (ref.getQualifier() == null) {
PsiElement nameElement = ref.getReferenceNameElement();
if (nameElement != null) {
return nameElement.getNode().getElementType() == GroovyTokenTypes.kSUPER;
}
}
}
return false;
}
private boolean isThisReferenceToContainingClass(PsiElement oldExpr) {
if (!(oldExpr instanceof GrReferenceExpression && PsiUtil.isThisReference(oldExpr))) return false;
final GrReferenceExpression qualifier = (GrReferenceExpression)((GrReferenceExpression)oldExpr).getQualifier();
if (qualifier == null) return true;
final PsiClass contextClass = PsiUtil.getContextClass(myToReplaceIn);
final PsiElement resolved = qualifier.resolve();
return myManager.areElementsEquivalent(resolved, contextClass);
}
@NotNull
private GrExpression getActualArg(int index) {
if (myActualArgs == null || myActualArgs[index] == null) {
final GrExpression[] arguments = myContext.getArgumentList().getExpressionArguments();
if (index < arguments.length) return arguments[index];
index -= arguments.length;
final GrClosableBlock[] closureArguments = myContext.getClosureArguments();
if (index < closureArguments.length) return closureArguments[index];
throw new IncorrectOperationException("fail :(");
}
final GrClosureSignatureUtil.ArgInfo<PsiElement> argInfo = myActualArgs[index];
final List<PsiElement> args = argInfo.args;
if (argInfo.isMultiArg) {
return GroovyRefactoringUtil.generateArgFromMultiArg(mySignature.getSubstitutor(), args, myParameters[index].getType(),
myContext.getProject());
}
else if (args.isEmpty()) {
final PsiParameter parameter = myParameters[index];
LOG.assertTrue(parameter instanceof GrParameter);
final GrExpression initializer = ((GrParameter)parameter).getInitializerGroovy();
LOG.assertTrue(initializer != null);
return (GrExpression)initializer.copy();
}
else {
return (GrExpression)args.get(0);
}
}
private GrExpression getInstanceRef(GroovyPsiElementFactory factory) throws IncorrectOperationException {
int copyingSafetyLevel = GroovyRefactoringUtil.verifySafeCopyExpression(myInstanceRef);
GrExpression instanceRef = myInstanceRef;
if (copyingSafetyLevel == RefactoringUtil.EXPR_COPY_PROHIBITED) {
instanceRef = factory.createExpressionFromText(getTempVar(myInstanceRef));
}
return instanceRef;
}
private String getTempVar(GrExpression expr) throws IncorrectOperationException {
String id = myTempVars.get(expr);
if (id != null) {
return id;
}
else {
id = GroovyRefactoringUtil.createTempVar(expr, myContext, true);
myTempVars.put(expr, id);
return id;
}
}
private static PsiElement replaceFieldWithGetter(PsiElement expr, PsiField psiField) throws IncorrectOperationException {
if (RefactoringUtil.isAssignmentLHS(expr)) {
// todo: warning
return expr;
}
PsiElement newExpr = expr;
PsiMethod getter = GroovyPropertyUtils.findGetterForField(psiField);
if (getter != null) {
if (JavaPsiFacade.getInstance(psiField.getProject()).getResolveHelper().isAccessible(getter, newExpr, null)) {
GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(newExpr.getProject());
String id = getter.getName();
final PsiElement parent = newExpr.getParent();
String qualifier = null;
if (parent instanceof GrReferenceExpression) {
final GrExpression qualifierExpression = ((GrReferenceExpression)parent).getQualifierExpression();
if (qualifierExpression != null) {
qualifier = qualifierExpression.getText();
}
}
GrExpression getterCall;
if (PsiTreeUtil.isAncestor(psiField.getContainingClass(), expr, true)) {
getterCall = factory.createExpressionFromText((qualifier != null ? qualifier + "." : "") + id + "()");
}
else {
getterCall = factory.createExpressionFromText((qualifier != null ? qualifier + "." : "") + psiField.getName());
}
if (parent != null) {
newExpr = parent.replace(getterCall);
}
else {
newExpr = expr.replace(getterCall);
}
}
else {
// todo: warning
}
}
return newExpr;
}
@Nullable
private static PsiElement getClassContainingResolve(final GroovyResolveResult result) {
final PsiElement elem = result.getElement();
if (elem != null) {
if (elem instanceof PsiMember) {
return ((PsiMember)elem).getContainingClass();
}
else {
return PsiUtil.getContextClass(elem);
}
}
return null;
}
private static GrReferenceExpression decodeReferenceExpression(GrReferenceExpression newExpr, GrReferenceExpression refExpr)
throws IncorrectOperationException {
PsiManager manager = refExpr.getManager();
GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(manager.getProject());
GrExpression qualifier = refExpr.getQualifier();
if (qualifier == null) {
PsiMember refMember = refExpr.getCopyableUserData(ChangeContextUtil.REF_MEMBER_KEY);
refExpr.putCopyableUserData(ChangeContextUtil.REF_MEMBER_KEY, null);
if (refMember != null && refMember.isValid()) {
PsiClass containingClass = refMember.getContainingClass();
if (refMember.hasModifierProperty(PsiModifier.STATIC)) {
PsiElement refElement = newExpr.resolve();
if (!manager.areElementsEquivalent(refMember, refElement)) {
newExpr.setQualifier(factory.createReferenceExpressionFromText("" + containingClass.getQualifiedName()));
}
}
}
else {
PsiClass refClass = refExpr.getCopyableUserData(ChangeContextUtil.REF_CLASS_KEY);
refExpr.putCopyableUserData(ChangeContextUtil.REF_CLASS_KEY, null);
if (refClass != null && refClass.isValid()) {
newExpr = (GrReferenceExpression)newExpr.bindToElement(refClass);
}
}
}
else {
Boolean couldRemove = refExpr.getCopyableUserData(ChangeContextUtil.CAN_REMOVE_QUALIFIER_KEY);
refExpr.putCopyableUserData(ChangeContextUtil.CAN_REMOVE_QUALIFIER_KEY, null);
if (couldRemove == Boolean.FALSE && canRemoveQualifier(refExpr)) {
GrReferenceExpression newRefExpr = (GrReferenceExpression)factory.createExpressionFromText(refExpr.getReferenceName());
newExpr = (GrReferenceExpression)newExpr.replace(newRefExpr);
}
}
return newExpr;
}
private static boolean canRemoveQualifier(GrReferenceExpression refExpr) {
try {
GrExpression qualifier = refExpr.getQualifier();
if (!(qualifier instanceof GrReferenceExpression)) return false;
PsiElement qualifierRefElement = ((GrReferenceExpression)qualifier).resolve();
if (!(qualifierRefElement instanceof PsiClass)) return false;
PsiElement refElement = refExpr.resolve();
if (refElement == null) return false;
final GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(refExpr.getProject());
if (refExpr.getParent() instanceof GrMethodCallExpression) {
GrMethodCallExpression methodCall = (GrMethodCallExpression)refExpr.getParent();
GrMethodCallExpression newMethodCall =
(GrMethodCallExpression)factory.createExpressionFromText(refExpr.getReferenceName() + "()", refExpr);
newMethodCall.getArgumentList().replace(methodCall.getArgumentList());
PsiElement newRefElement = ((GrReferenceExpression)newMethodCall.getInvokedExpression()).resolve();
return refElement.equals(newRefElement);
}
else {
GrReferenceExpression newRefExpr = (GrReferenceExpression)factory.createExpressionFromText(refExpr.getReferenceName(), refExpr);
PsiElement newRefElement = newRefExpr.resolve();
return refElement.equals(newRefElement);
}
}
catch (IncorrectOperationException e) {
return false;
}
}
}