blob: 202a990b76b77034e6ed64177832493c89b13a1d [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.replaceConstructorWithFactory;
import com.intellij.lang.findUsages.DescriptiveNameUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.search.GlobalSearchScope;
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.util.ConflictsUtil;
import com.intellij.refactoring.util.RefactoringUIUtil;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewDescriptor;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.VisibilityUtil;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import java.util.*;
/**
* @author dsl
*/
public class ReplaceConstructorWithFactoryProcessor extends BaseRefactoringProcessor {
private static final Logger LOG = Logger.getInstance(
"#com.intellij.refactoring.replaceConstructorWithFactory.ReplaceConstructorWithFactoryProcessor");
private final PsiMethod myConstructor;
private final String myFactoryName;
private final PsiElementFactory myFactory;
private final PsiClass myOriginalClass;
private final PsiClass myTargetClass;
private final PsiManager myManager;
private final boolean myIsInner;
public ReplaceConstructorWithFactoryProcessor(Project project,
PsiMethod originalConstructor,
PsiClass originalClass,
PsiClass targetClass,
@NonNls String factoryName) {
super(project);
myOriginalClass = originalClass;
myConstructor = originalConstructor;
myTargetClass = targetClass;
myFactoryName = factoryName;
myManager = PsiManager.getInstance(project);
myFactory = JavaPsiFacade.getInstance(myManager.getProject()).getElementFactory();
myIsInner = isInner(myOriginalClass);
}
private boolean isInner(PsiClass originalClass) {
final boolean result = PsiUtil.isInnerClass(originalClass);
if (result) {
LOG.assertTrue(PsiTreeUtil.isAncestor(myTargetClass, originalClass, false));
}
return result;
}
@NotNull
protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages) {
if (myConstructor != null) {
return new ReplaceConstructorWithFactoryViewDescriptor(myConstructor);
}
else {
return new ReplaceConstructorWithFactoryViewDescriptor(myOriginalClass);
}
}
private List<PsiElement> myNonNewConstructorUsages;
@NotNull
protected UsageInfo[] findUsages() {
GlobalSearchScope projectScope = GlobalSearchScope.projectScope(myProject);
ArrayList<UsageInfo> usages = new ArrayList<UsageInfo>();
myNonNewConstructorUsages = new ArrayList<PsiElement>();
for (PsiReference reference : ReferencesSearch.search(myConstructor == null ? myOriginalClass : myConstructor, projectScope, false)) {
PsiElement element = reference.getElement();
if (element.getParent() instanceof PsiNewExpression) {
usages.add(new UsageInfo(element.getParent()));
}
else if ("super".equals(element.getText()) || "this".equals(element.getText())) {
myNonNewConstructorUsages.add(element);
}
else if (element instanceof PsiMethod && ((PsiMethod)element).isConstructor()) {
myNonNewConstructorUsages.add(element);
}
else if (element instanceof PsiClass) {
myNonNewConstructorUsages.add(element);
}
}
//if (myConstructor != null && myConstructor.getParameterList().getParametersCount() == 0) {
// RefactoringUtil.visitImplicitConstructorUsages(getConstructorContainingClass(), new RefactoringUtil.ImplicitConstructorUsageVisitor() {
// @Override public void visitConstructor(PsiMethod constructor, PsiMethod baseConstructor) {
// myNonNewConstructorUsages.add(constructor);
// }
//
// @Override public void visitClassWithoutConstructors(PsiClass aClass) {
// myNonNewConstructorUsages.add(aClass);
// }
// });
//}
return usages.toArray(new UsageInfo[usages.size()]);
}
protected boolean preprocessUsages(Ref<UsageInfo[]> refUsages) {
UsageInfo[] usages = refUsages.get();
MultiMap<PsiElement, String> conflicts = new MultiMap<PsiElement, String>();
final PsiResolveHelper helper = JavaPsiFacade.getInstance(myProject).getResolveHelper();
final PsiClass constructorContainingClass = getConstructorContainingClass();
if (!helper.isAccessible(constructorContainingClass, myTargetClass, null)) {
String message = RefactoringBundle.message("class.0.is.not.accessible.from.target.1",
RefactoringUIUtil.getDescription(constructorContainingClass, true),
RefactoringUIUtil.getDescription(myTargetClass, true));
conflicts.putValue(constructorContainingClass, message);
}
HashSet<PsiElement> reportedContainers = new HashSet<PsiElement>();
final String targetClassDescription = RefactoringUIUtil.getDescription(myTargetClass, true);
for (UsageInfo usage : usages) {
final PsiElement container = ConflictsUtil.getContainer(usage.getElement());
if (!reportedContainers.contains(container)) {
reportedContainers.add(container);
if (!helper.isAccessible(myTargetClass, usage.getElement(), null)) {
String message = RefactoringBundle.message("target.0.is.not.accessible.from.1",
targetClassDescription,
RefactoringUIUtil.getDescription(container, true));
conflicts.putValue(myTargetClass, message);
}
}
}
if (myIsInner) {
for (UsageInfo usage : usages) {
final PsiField field = PsiTreeUtil.getParentOfType(usage.getElement(), PsiField.class);
if (field != null) {
final PsiClass containingClass = field.getContainingClass();
if (PsiTreeUtil.isAncestor(containingClass, myTargetClass, true)) {
String message = RefactoringBundle.message("constructor.being.refactored.is.used.in.initializer.of.0",
RefactoringUIUtil.getDescription(field, true), RefactoringUIUtil.getDescription(
constructorContainingClass, false));
conflicts.putValue(field, message);
}
}
}
}
return showConflicts(conflicts, usages);
}
private PsiClass getConstructorContainingClass() {
if (myConstructor != null) {
return myConstructor.getContainingClass();
}
else {
return myOriginalClass;
}
}
protected void performRefactoring(UsageInfo[] usages) {
try {
PsiReferenceExpression classReferenceExpression =
myFactory.createReferenceExpression(myTargetClass);
PsiReferenceExpression qualifiedMethodReference =
(PsiReferenceExpression)myFactory.createExpressionFromText("A." + myFactoryName, null);
PsiMethod factoryMethod = (PsiMethod)myTargetClass.add(createFactoryMethod());
if (myConstructor != null) {
PsiUtil.setModifierProperty(myConstructor, PsiModifier.PRIVATE, true);
VisibilityUtil.escalateVisibility(myConstructor, factoryMethod);
for (PsiElement place : myNonNewConstructorUsages) {
VisibilityUtil.escalateVisibility(myConstructor, place);
}
}
if (myConstructor == null) {
PsiMethod constructor = myFactory.createConstructor();
PsiUtil.setModifierProperty(constructor, PsiModifier.PRIVATE, true);
constructor = (PsiMethod)getConstructorContainingClass().add(constructor);
VisibilityUtil.escalateVisibility(constructor, myTargetClass);
}
for (UsageInfo usage : usages) {
PsiNewExpression newExpression = (PsiNewExpression)usage.getElement();
if (newExpression == null) continue;
VisibilityUtil.escalateVisibility(factoryMethod, newExpression);
PsiMethodCallExpression factoryCall =
(PsiMethodCallExpression)myFactory.createExpressionFromText(myFactoryName + "()", newExpression);
factoryCall.getArgumentList().replace(newExpression.getArgumentList());
boolean replaceMethodQualifier = false;
PsiExpression newQualifier = newExpression.getQualifier();
PsiElement resolvedFactoryMethod = factoryCall.getMethodExpression().resolve();
if (resolvedFactoryMethod != factoryMethod || newQualifier != null) {
factoryCall.getMethodExpression().replace(qualifiedMethodReference);
replaceMethodQualifier = true;
}
if (replaceMethodQualifier) {
if (newQualifier == null) {
factoryCall.getMethodExpression().getQualifierExpression().replace(classReferenceExpression);
}
else {
factoryCall.getMethodExpression().getQualifierExpression().replace(newQualifier);
}
}
newExpression.replace(factoryCall);
}
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
private PsiMethod createFactoryMethod() throws IncorrectOperationException {
final PsiClass containingClass = getConstructorContainingClass();
PsiClassType type = myFactory.createType(containingClass, PsiSubstitutor.EMPTY);
final PsiMethod factoryMethod = myFactory.createMethod(myFactoryName, type);
if (myConstructor != null) {
factoryMethod.getParameterList().replace(myConstructor.getParameterList());
factoryMethod.getThrowsList().replace(myConstructor.getThrowsList());
}
Collection<String> names = new HashSet<String>();
for (PsiTypeParameter typeParameter : PsiUtil.typeParametersIterable(myConstructor != null ? myConstructor : containingClass)) {
if (!names.contains(typeParameter.getName())) { //Otherwise type parameter is hidden in the constructor
names.add(typeParameter.getName());
factoryMethod.getTypeParameterList().addAfter(typeParameter, null);
}
}
PsiReturnStatement returnStatement =
(PsiReturnStatement)myFactory.createStatementFromText("return new A();", null);
PsiNewExpression newExpression = (PsiNewExpression)returnStatement.getReturnValue();
PsiJavaCodeReferenceElement classRef = myFactory.createReferenceElementByType(type);
newExpression.getClassReference().replace(classRef);
final PsiExpressionList argumentList = newExpression.getArgumentList();
PsiParameter[] params = factoryMethod.getParameterList().getParameters();
for (PsiParameter parameter : params) {
PsiExpression paramRef = myFactory.createExpressionFromText(parameter.getName(), null);
argumentList.add(paramRef);
}
factoryMethod.getBody().add(returnStatement);
PsiUtil.setModifierProperty(factoryMethod, getDefaultFactoryVisibility(), true);
if (!myIsInner) {
PsiUtil.setModifierProperty(factoryMethod, PsiModifier.STATIC, true);
}
return (PsiMethod)CodeStyleManager.getInstance(myProject).reformat(factoryMethod);
}
@PsiModifier.ModifierConstant
private String getDefaultFactoryVisibility() {
final PsiModifierList modifierList;
if (myConstructor != null) {
modifierList = myConstructor.getModifierList();
}
else {
modifierList = myOriginalClass.getModifierList();
}
return VisibilityUtil.getVisibilityModifier(modifierList);
}
protected String getCommandName() {
if (myConstructor != null) {
return RefactoringBundle.message("replace.constructor.0.with.a.factory.method",
DescriptiveNameUtil.getDescriptiveName(myConstructor));
}
else {
return RefactoringBundle.message("replace.default.constructor.of.0.with.a.factory.method",
DescriptiveNameUtil.getDescriptiveName(myOriginalClass));
}
}
public PsiClass getOriginalClass() {
return getConstructorContainingClass();
}
public PsiClass getTargetClass() {
return myTargetClass;
}
public PsiMethod getConstructor() {
return myConstructor;
}
public String getFactoryName() {
return myFactoryName;
}
}