blob: ca844f0a5bb9e719f9b099318f9f204a836e72eb [file] [log] [blame]
/*
* Copyright 2000-2009 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.move.moveInstanceMethod;
import com.intellij.codeInsight.ChangeContextUtil;
import com.intellij.codeInsight.generation.OverrideImplementUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.*;
import com.intellij.psi.javadoc.PsiDocTagValue;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.searches.ClassInheritorsSearch;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.move.MoveInstanceMembersUtil;
import com.intellij.refactoring.util.*;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewDescriptor;
import com.intellij.usageView.UsageViewUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.VisibilityUtil;
import com.intellij.util.containers.HashSet;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import java.util.*;
/**
* @author ven
*/
public class MoveInstanceMethodProcessor extends BaseRefactoringProcessor{
private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.move.moveInstanceMethod.MoveInstanceMethodProcessor");
public PsiMethod getMethod() {
return myMethod;
}
public PsiVariable getTargetVariable() {
return myTargetVariable;
}
private PsiMethod myMethod;
private PsiVariable myTargetVariable;
private PsiClass myTargetClass;
private final String myNewVisibility;
private final Map<PsiClass, String> myOldClassParameterNames;
public MoveInstanceMethodProcessor(final Project project,
final PsiMethod method,
final PsiVariable targetVariable,
final String newVisibility,
final Map<PsiClass, String> oldClassParameterNames) {
super(project);
myMethod = method;
myTargetVariable = targetVariable;
myOldClassParameterNames = oldClassParameterNames;
LOG.assertTrue(myTargetVariable instanceof PsiParameter || myTargetVariable instanceof PsiField);
LOG.assertTrue(myTargetVariable.getType() instanceof PsiClassType);
final PsiType type = myTargetVariable.getType();
LOG.assertTrue(type instanceof PsiClassType);
myTargetClass = ((PsiClassType) type).resolve();
myNewVisibility = newVisibility;
}
@NotNull
protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages) {
return new MoveInstanceMethodViewDescriptor(myMethod, myTargetVariable, myTargetClass);
}
protected boolean preprocessUsages(Ref<UsageInfo[]> refUsages) {
final UsageInfo[] usages = refUsages.get();
MultiMap<PsiElement, String> conflicts = new MultiMap<PsiElement, String>();
final Set<PsiMember> members = new HashSet<PsiMember>();
members.add(myMethod);
if (myTargetVariable instanceof PsiField) members.add((PsiMember)myTargetVariable);
if (!myTargetClass.isInterface()) {
RefactoringConflictsUtil.analyzeAccessibilityConflicts(members, myTargetClass, conflicts, myNewVisibility);
}
else {
for (final UsageInfo usage : usages) {
if (usage instanceof InheritorUsageInfo) {
RefactoringConflictsUtil.analyzeAccessibilityConflicts(
members, ((InheritorUsageInfo)usage).getInheritor(), conflicts, myNewVisibility);
}
}
}
if (myTargetVariable instanceof PsiParameter) {
PsiParameter parameter = (PsiParameter)myTargetVariable;
for (final UsageInfo usageInfo : usages) {
if (usageInfo instanceof MethodCallUsageInfo) {
final PsiElement methodCall = ((MethodCallUsageInfo)usageInfo).getMethodCallExpression();
if (methodCall instanceof PsiMethodCallExpression) {
final PsiExpression[] expressions = ((PsiMethodCallExpression)methodCall).getArgumentList().getExpressions();
final int index = myMethod.getParameterList().getParameterIndex(parameter);
if (index < expressions.length) {
PsiExpression instanceValue = expressions[index];
instanceValue = RefactoringUtil.unparenthesizeExpression(instanceValue);
if (instanceValue instanceof PsiLiteralExpression && ((PsiLiteralExpression)instanceValue).getValue() == null) {
String message = RefactoringBundle.message("0.contains.call.with.null.argument.for.parameter.1",
RefactoringUIUtil.getDescription(ConflictsUtil.getContainer(methodCall), true),
CommonRefactoringUtil.htmlEmphasize(parameter.getName()));
conflicts.putValue(instanceValue, message);
}
}
} else if (methodCall instanceof PsiMethodReferenceExpression) {
conflicts.putValue(methodCall, "Method reference would be broken after move");
}
}
}
}
try {
ConflictsUtil.checkMethodConflicts(myTargetClass, myMethod, getPatternMethod(), conflicts);
}
catch (IncorrectOperationException e) {}
return showConflicts(conflicts, usages);
}
@NotNull
protected UsageInfo[] findUsages() {
final PsiManager manager = myMethod.getManager();
final GlobalSearchScope searchScope = GlobalSearchScope.allScope(manager.getProject());
final List<UsageInfo> usages = new ArrayList<UsageInfo>();
for (PsiReference ref : ReferencesSearch.search(myMethod, searchScope, false)) {
final PsiElement element = ref.getElement();
if (element instanceof PsiReferenceExpression) {
boolean isInternal = PsiTreeUtil.isAncestor(myMethod, element, true);
usages.add(new MethodCallUsageInfo((PsiReferenceExpression)element, isInternal));
}
else if (element instanceof PsiDocTagValue) {
usages.add(new JavadocUsageInfo(((PsiDocTagValue)element)));
}
else {
throw new UnknownReferenceTypeException(element.getLanguage());
}
}
if (myTargetClass.isInterface()) {
addInheritorUsages(myTargetClass, searchScope, usages);
}
final PsiCodeBlock body = myMethod.getBody();
if (body != null) {
body.accept(new JavaRecursiveElementWalkingVisitor() {
@Override public void visitNewExpression(PsiNewExpression expression) {
if (MoveInstanceMembersUtil.getClassReferencedByThis(expression) != null) {
usages.add(new InternalUsageInfo(expression));
}
super.visitNewExpression(expression);
}
@Override public void visitReferenceExpression(PsiReferenceExpression expression) {
if (MoveInstanceMembersUtil.getClassReferencedByThis(expression) != null) {
usages.add(new InternalUsageInfo(expression));
} else if (!expression.isQualified()) {
final PsiElement resolved = expression.resolve();
if (myTargetVariable.equals(resolved)) {
usages.add(new InternalUsageInfo(expression));
}
}
super.visitReferenceExpression(expression);
}
});
}
return usages.toArray(new UsageInfo[usages.size()]);
}
private static void addInheritorUsages(PsiClass aClass, final GlobalSearchScope searchScope, final List<UsageInfo> usages) {
for (PsiClass inheritor : ClassInheritorsSearch.search(aClass, searchScope, false).findAll()) {
if (!inheritor.isInterface()) {
usages.add(new InheritorUsageInfo(inheritor));
}
else {
addInheritorUsages(inheritor, searchScope, usages);
}
}
}
protected void refreshElements(PsiElement[] elements) {
LOG.assertTrue(elements.length == 3);
myMethod = (PsiMethod) elements[0];
myTargetVariable = (PsiVariable) elements[1];
myTargetClass = (PsiClass) elements[2];
}
protected String getCommandName() {
return RefactoringBundle.message("move.instance.method.command");
}
public PsiClass getTargetClass() {
return myTargetClass;
}
protected void performRefactoring(UsageInfo[] usages) {
if (!CommonRefactoringUtil.checkReadOnlyStatus(myProject, myTargetClass)) return;
PsiMethod patternMethod = createMethodToAdd();
final List<PsiReference> docRefs = new ArrayList<PsiReference>();
for (UsageInfo usage : usages) {
if (usage instanceof InheritorUsageInfo) {
final PsiClass inheritor = ((InheritorUsageInfo)usage).getInheritor();
addMethodToClass(inheritor, patternMethod, true);
}
else if (usage instanceof MethodCallUsageInfo && !((MethodCallUsageInfo)usage).isInternal()) {
final PsiElement expression = ((MethodCallUsageInfo)usage).getMethodCallExpression();
if (expression instanceof PsiMethodCallExpression) {
correctMethodCall((PsiMethodCallExpression)expression, false);
} else if (expression instanceof PsiMethodReferenceExpression) {
PsiExpression newQualifier = JavaPsiFacade.getInstance(myProject).getElementFactory().createExpressionFromText(myTargetVariable.getType().getCanonicalText(), null);
((PsiMethodReferenceExpression)expression).setQualifierExpression(newQualifier);
}
}
else if (usage instanceof JavadocUsageInfo) {
docRefs.add(usage.getElement().getReference());
}
}
try {
if (myTargetClass.isInterface()) patternMethod.getBody().delete();
final PsiMethod method = addMethodToClass(myTargetClass, patternMethod, false);
myMethod.delete();
for (PsiReference reference : docRefs) {
reference.bindToElement(method);
}
VisibilityUtil.fixVisibility(UsageViewUtil.toElements(usages), method, myNewVisibility);
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
private void correctMethodCall(final PsiMethodCallExpression expression, final boolean isInternalCall) {
try {
final PsiManager manager = myMethod.getManager();
PsiReferenceExpression methodExpression = expression.getMethodExpression();
if (!methodExpression.isReferenceTo(myMethod)) return;
final PsiExpression oldQualifier = methodExpression.getQualifierExpression();
PsiExpression newQualifier = null;
final PsiClass classReferencedByThis = MoveInstanceMembersUtil.getClassReferencedByThis(methodExpression);
if (myTargetVariable instanceof PsiParameter) {
final int index = myMethod.getParameterList().getParameterIndex((PsiParameter)myTargetVariable);
final PsiExpression[] arguments = expression.getArgumentList().getExpressions();
if (index < arguments.length) {
newQualifier = (PsiExpression)arguments[index].copy();
arguments[index].delete();
}
}
else {
VisibilityUtil.escalateVisibility((PsiField)myTargetVariable, expression);
String newQualifierName = myTargetVariable.getName();
if (myTargetVariable instanceof PsiField && oldQualifier != null) {
final PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(oldQualifier.getType());
if (aClass == ((PsiField)myTargetVariable).getContainingClass()) {
newQualifierName = oldQualifier.getText() + "." + newQualifierName;
}
}
newQualifier = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory().createExpressionFromText(newQualifierName, null);
}
PsiExpression newArgument = null;
if (classReferencedByThis != null) {
@NonNls String thisArgumentText = null;
if (manager.areElementsEquivalent(myMethod.getContainingClass(), classReferencedByThis)) {
if (myOldClassParameterNames.containsKey(myMethod.getContainingClass())) {
thisArgumentText = "this";
}
}
else {
thisArgumentText = classReferencedByThis.getName() + ".this";
}
if (thisArgumentText != null) {
newArgument = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory().createExpressionFromText(thisArgumentText, null);
}
} else {
if (!isInternalCall && oldQualifier != null) {
final PsiType type = oldQualifier.getType();
if (type instanceof PsiClassType) {
final PsiClass resolved = ((PsiClassType)type).resolve();
if (resolved != null && getParameterNameToCreate(resolved) != null) {
newArgument = replaceRefsToTargetVariable(oldQualifier); //replace is needed in case old qualifier is e.g. the same as field as target variable
}
}
}
}
if (newArgument != null) {
expression.getArgumentList().add(newArgument);
}
if (newQualifier != null) {
if (newQualifier instanceof PsiThisExpression && ((PsiThisExpression)newQualifier).getQualifier() == null) {
//Remove now redundant 'this' qualifier
if (oldQualifier != null) oldQualifier.delete();
}
else {
final PsiReferenceExpression refExpr = (PsiReferenceExpression)JavaPsiFacade.getInstance(manager.getProject()).getElementFactory()
.createExpressionFromText("q." + myMethod.getName(), null);
refExpr.getQualifierExpression().replace(newQualifier);
methodExpression.replace(refExpr);
}
}
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
private PsiExpression replaceRefsToTargetVariable(final PsiExpression expression) {
final PsiManager manager = expression.getManager();
if (expression instanceof PsiReferenceExpression &&
((PsiReferenceExpression)expression).isReferenceTo(myTargetVariable)) {
return createThisExpr(manager);
}
expression.accept(new JavaRecursiveElementVisitor() {
@Override public void visitReferenceExpression(PsiReferenceExpression expression) {
super.visitReferenceExpression(expression);
if (expression.isReferenceTo(myTargetVariable)) {
try {
expression.replace(createThisExpr(manager));
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
}
});
return expression;
}
private static PsiExpression createThisExpr(final PsiManager manager) {
try {
return JavaPsiFacade.getInstance(manager.getProject()).getElementFactory().createExpressionFromText("this", null);
}
catch (IncorrectOperationException e) {
LOG.error(e);
return null;
}
}
private static PsiMethod addMethodToClass(final PsiClass aClass, final PsiMethod patternMethod, boolean canAddOverride) {
try {
final PsiMethod method = (PsiMethod)aClass.add(patternMethod);
ChangeContextUtil.decodeContextInfo(method, null, null);
if (canAddOverride && OverrideImplementUtil.isInsertOverride(method, aClass)) {
method.getModifierList().addAnnotation(CommonClassNames.JAVA_LANG_OVERRIDE);
}
return method;
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
return null;
}
private PsiMethod createMethodToAdd () {
ChangeContextUtil.encodeContextInfo(myMethod, true);
try {
final PsiManager manager = myMethod.getManager();
final PsiElementFactory factory = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory();
//correct internal references
final PsiCodeBlock body = myMethod.getBody();
if (body != null) {
final Map<PsiElement, PsiElement> replaceMap = new HashMap<PsiElement, PsiElement>();
body.accept(new JavaRecursiveElementVisitor() {
@Override public void visitThisExpression(PsiThisExpression expression) {
final PsiClass classReferencedByThis = MoveInstanceMembersUtil.getClassReferencedByThis(expression);
if (classReferencedByThis != null && !PsiTreeUtil.isAncestor(myMethod, classReferencedByThis, false)) {
final PsiElementFactory factory = JavaPsiFacade.getInstance(myProject).getElementFactory();
String paramName = getParameterNameToCreate(classReferencedByThis);
try {
final PsiExpression refExpression = factory.createExpressionFromText(paramName, null);
replaceMap.put(expression, refExpression);
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
}
@Override public void visitReferenceExpression(PsiReferenceExpression expression) {
try {
final PsiExpression qualifier = expression.getQualifierExpression();
final PsiElement resolved = expression.resolve();
if (qualifier instanceof PsiReferenceExpression && ((PsiReferenceExpression)qualifier).isReferenceTo(myTargetVariable)) {
if (resolved instanceof PsiField) {
for (PsiParameter parameter : myMethod.getParameterList().getParameters()) {
if (Comparing.strEqual(parameter.getName(), ((PsiField)resolved).getName())) {
qualifier.replace(factory.createExpressionFromText("this", null));
return;
}
}
}
//Target is a field, replace target.m -> m
qualifier.delete();
return;
}
if (myTargetVariable.equals(resolved)) {
PsiThisExpression thisExpression = RefactoringChangeUtil.createThisExpression(manager, PsiTreeUtil.isAncestor(myMethod,
PsiTreeUtil
.getParentOfType(
expression,
PsiClass.class),
true)
? myTargetClass
: null);
replaceMap.put(expression, thisExpression);
return;
}
else if (myMethod.equals(resolved)) {
}
else {
PsiClass classReferencedByThis = MoveInstanceMembersUtil.getClassReferencedByThis(expression);
if (classReferencedByThis != null) {
final String paramName = getParameterNameToCreate(classReferencedByThis);
if (paramName != null) {
PsiReferenceExpression newQualifier = (PsiReferenceExpression)factory.createExpressionFromText(paramName, null);
expression.setQualifierExpression(newQualifier);
return;
}
}
}
super.visitReferenceExpression(expression);
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
@Override public void visitNewExpression(PsiNewExpression expression) {
try {
final PsiExpression qualifier = expression.getQualifier();
if (qualifier instanceof PsiReferenceExpression && ((PsiReferenceExpression)qualifier).isReferenceTo(myTargetVariable)) {
//Target is a field, replace target.new A() -> new A()
qualifier.delete();
} else {
final PsiClass classReferencedByThis = MoveInstanceMembersUtil.getClassReferencedByThis(expression);
if (classReferencedByThis != null) {
if (qualifier != null) qualifier.delete();
final String paramName = getParameterNameToCreate(classReferencedByThis);
final PsiExpression newExpression = factory.createExpressionFromText(paramName + "." + expression.getText(), null);
replaceMap.put(expression, newExpression);
}
}
super.visitNewExpression(expression);
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
@Override public void visitMethodCallExpression(PsiMethodCallExpression expression) {
correctMethodCall(expression, true);
super.visitMethodCallExpression(expression);
}
});
for (PsiElement element : replaceMap.keySet()) {
final PsiElement replacement = replaceMap.get(element);
element.replace(replacement);
}
}
final PsiMethod methodCopy = getPatternMethod();
final List<PsiParameter> newParameters = Arrays.asList(methodCopy.getParameterList().getParameters());
RefactoringUtil.fixJavadocsForParams(methodCopy, new HashSet<PsiParameter>(newParameters));
return methodCopy;
}
catch (IncorrectOperationException e) {
LOG.error(e);
return myMethod;
}
}
private PsiMethod getPatternMethod() throws IncorrectOperationException {
final PsiMethod methodCopy = (PsiMethod)myMethod.copy();
String name = myTargetClass.isInterface()
? PsiModifier.PUBLIC :
!Comparing.strEqual(myNewVisibility, VisibilityUtil.ESCALATE_VISIBILITY) ? myNewVisibility : null;
if (name != null) {
PsiUtil.setModifierProperty(methodCopy, name, true);
}
if (myTargetVariable instanceof PsiParameter) {
final int index = myMethod.getParameterList().getParameterIndex((PsiParameter)myTargetVariable);
methodCopy.getParameterList().getParameters()[index].delete();
}
addParameters(JavaPsiFacade.getInstance(myProject).getElementFactory(), methodCopy, myTargetClass.isInterface());
return methodCopy;
}
private void addParameters(final PsiElementFactory factory, final PsiMethod methodCopy, final boolean isInterface) throws IncorrectOperationException {
final Set<Map.Entry<PsiClass, String>> entries = myOldClassParameterNames.entrySet();
for (final Map.Entry<PsiClass, String> entry : entries) {
final PsiClassType type = factory.createType(entry.getKey());
final PsiParameter parameter = factory.createParameter(entry.getValue(), type);
if (isInterface) {
PsiUtil.setModifierProperty(parameter, PsiModifier.FINAL, false);
}
methodCopy.getParameterList().add(parameter);
}
}
private String getParameterNameToCreate(@NotNull PsiClass aClass) {
return myOldClassParameterNames.get(aClass);
}
}