blob: baf354f369ca309b1579d9758e9b95b1516daf54 [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;
import com.intellij.lang.findUsages.DescriptiveNameUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.searches.MethodReferencesSearch;
import com.intellij.psi.search.searches.OverridingMethodsSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtilBase;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.refactoring.IntroduceParameterRefactoring;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.introduceParameter.*;
import com.intellij.refactoring.ui.UsageViewDescriptorAdapter;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.refactoring.util.usageInfo.DefaultConstructorImplicitUsageInfo;
import com.intellij.refactoring.util.usageInfo.NoConstructorClassUsageInfo;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewDescriptor;
import com.intellij.usageView.UsageViewUtil;
import com.intellij.util.containers.MultiMap;
import gnu.trove.TIntArrayList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrOpenBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrAnonymousClassDefinition;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
import org.jetbrains.plugins.groovy.refactoring.introduce.GrIntroduceHandlerBase;
import org.jetbrains.plugins.groovy.refactoring.introduce.StringPartInfo;
import org.jetbrains.plugins.groovy.refactoring.util.AnySupers;
import java.util.ArrayList;
import java.util.Collection;
/**
* @author Maxim.Medvedev
*/
public class GrIntroduceParameterProcessor extends BaseRefactoringProcessor implements IntroduceParameterData {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.plugins.groovy.refactoring.introduce.parameter.GrIntroduceParameterProcessor");
private final GrIntroduceParameterSettings mySettings;
private final IntroduceParameterData.ExpressionWrapper myParameterInitializer;
public GrIntroduceParameterProcessor(GrIntroduceParameterSettings settings) {
this(settings, createExpressionWrapper(settings));
}
public GrIntroduceParameterProcessor(GrIntroduceParameterSettings settings, GrExpressionWrapper expr) {
super(settings.getProject());
mySettings = settings;
myParameterInitializer = expr;
}
private static GrExpressionWrapper createExpressionWrapper(GrIntroduceParameterSettings settings) {
LOG.assertTrue(settings.getToReplaceIn() instanceof GrMethod);
LOG.assertTrue(settings.getToSearchFor() instanceof PsiMethod);
final StringPartInfo stringPartInfo = settings.getStringPartInfo();
GrVariable var = settings.getVar();
final GrExpression expression = stringPartInfo != null ? stringPartInfo.createLiteralFromSelected() :
var != null ? var.getInitializerGroovy()
: settings.getExpression();
return new GrExpressionWrapper(expression);
}
@NotNull
@Override
protected UsageViewDescriptor createUsageViewDescriptor(final UsageInfo[] usages) {
return new UsageViewDescriptorAdapter() {
@NotNull
@Override
public PsiElement[] getElements() {
return new PsiElement[]{mySettings.getToSearchFor()};
}
@Override
public String getProcessedElementsHeader() {
return RefactoringBundle.message("introduce.parameter.elements.header");
}
};
}
@Override
protected boolean preprocessUsages(Ref<UsageInfo[]> refUsages) {
UsageInfo[] usagesIn = refUsages.get();
MultiMap<PsiElement, String> conflicts = new MultiMap<PsiElement, String>();
if (!mySettings.generateDelegate()) {
GroovyIntroduceParameterUtil.detectAccessibilityConflicts(mySettings.getExpression(), usagesIn, conflicts,
mySettings.replaceFieldsWithGetters() != IntroduceParameterRefactoring.REPLACE_FIELDS_WITH_GETTERS_NONE,
myProject
);
}
final GrMethod toReplaceIn = (GrMethod)mySettings.getToReplaceIn();
if (mySettings.getExpression() != null && !toReplaceIn.hasModifierProperty(PsiModifier.PRIVATE)) {
final AnySupers anySupers = new AnySupers();
mySettings.getExpression().accept(anySupers);
if (anySupers.containsSupers()) {
for (UsageInfo usageInfo : usagesIn) {
if (!(usageInfo.getElement() instanceof PsiMethod) && !(usageInfo instanceof InternalUsageInfo)) {
if (!PsiTreeUtil.isAncestor(toReplaceIn.getContainingClass(), usageInfo.getElement(), false)) {
conflicts.putValue(mySettings.getExpression(),
RefactoringBundle.message("parameter.initializer.contains.0.but.not.all.calls.to.method.are.in.its.class",
CommonRefactoringUtil.htmlEmphasize(PsiKeyword.SUPER)));
break;
}
}
}
}
}
for (IntroduceParameterMethodUsagesProcessor processor : IntroduceParameterMethodUsagesProcessor.EP_NAME.getExtensions()) {
processor.findConflicts(this, refUsages.get(), conflicts);
}
return showConflicts(conflicts, usagesIn);
}
@NotNull
@Override
protected UsageInfo[] findUsages() {
ArrayList<UsageInfo> result = new ArrayList<UsageInfo>();
final PsiMethod toSearchFor = ((PsiMethod)mySettings.getToSearchFor());
if (!mySettings.generateDelegate()) {
Collection<PsiReference> refs =
MethodReferencesSearch.search(toSearchFor, GlobalSearchScope.projectScope(myProject), true).findAll();
for (PsiReference ref1 : refs) {
PsiElement ref = ref1.getElement();
if (ref instanceof PsiMethod && ((PsiMethod)ref).isConstructor()) {
DefaultConstructorImplicitUsageInfo implicitUsageInfo =
new DefaultConstructorImplicitUsageInfo((PsiMethod)ref, ((PsiMethod)ref).getContainingClass(), toSearchFor);
result.add(implicitUsageInfo);
}
else if (ref instanceof PsiClass) {
if (ref instanceof GrAnonymousClassDefinition) {
result.add(new ExternalUsageInfo(((GrAnonymousClassDefinition)ref).getBaseClassReferenceGroovy()));
}
else if (ref instanceof PsiAnonymousClass) {
result.add(new ExternalUsageInfo(((PsiAnonymousClass)ref).getBaseClassReference()));
}
else {
result.add(new NoConstructorClassUsageInfo((PsiClass)ref));
}
}
else if (!PsiTreeUtil.isAncestor(mySettings.getToReplaceIn(), ref, false)) {
result.add(new ExternalUsageInfo(ref));
}
else {
result.add(new ChangedMethodCallInfo(ref));
}
}
}
if (mySettings.replaceAllOccurrences()) {
if (mySettings.getVar() != null) {
for (PsiElement element : GrIntroduceHandlerBase.collectVariableUsages(mySettings.getVar(), mySettings.getToReplaceIn())) {
result.add(new InternalUsageInfo(element));
}
}
else {
PsiElement[] exprs = GroovyIntroduceParameterUtil.getOccurrences(mySettings);
for (PsiElement expr : exprs) {
result.add(new InternalUsageInfo(expr));
}
}
}
else {
if (mySettings.getExpression() != null) {
result.add(new InternalUsageInfo(mySettings.getExpression()));
}
}
Collection<PsiMethod> overridingMethods = OverridingMethodsSearch.search(toSearchFor, true).findAll();
for (PsiMethod overridingMethod : overridingMethods) {
result.add(new UsageInfo(overridingMethod));
}
final UsageInfo[] usageInfos = result.toArray(new UsageInfo[result.size()]);
return UsageViewUtil.removeDuplicatedUsages(usageInfos);
}
@Override
protected void performRefactoring(UsageInfo[] usages) {
GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(myProject);
//PsiType initializerType = mySettings.getSelectedType();
// Changing external occurrences (the tricky part)
IntroduceParameterUtil.processUsages(usages, this);
final GrMethod toReplaceIn = (GrMethod)mySettings.getToReplaceIn();
final PsiMethod toSearchFor = (PsiMethod)mySettings.getToSearchFor();
final boolean methodsToProcessAreDifferent = toReplaceIn != toSearchFor;
if (mySettings.generateDelegate()) {
generateDelegate(toReplaceIn, toSearchFor, methodsToProcessAreDifferent);
}
// Changing signature of initial method
// (signature of myMethodToReplaceIn will be either changed now or have already been changed)
//LOG.assertTrue(initializerType == null || initializerType.isValid());
final FieldConflictsResolver fieldConflictsResolver = new FieldConflictsResolver(mySettings.getName(), toReplaceIn.getBlock());
processMethodSignature(usages, toReplaceIn, toSearchFor, methodsToProcessAreDifferent);
processUsages(usages, factory);
processStringPart();
processVar();
fieldConflictsResolver.fix();
}
private void processVar() {
final GrVariable var = mySettings.getVar();
if (var != null && mySettings.removeLocalVariable()) {
var.delete();
}
}
private void processStringPart() {
final StringPartInfo stringPartInfo = mySettings.getStringPartInfo();
if (stringPartInfo != null) {
final GrExpression
expr = mySettings.getStringPartInfo().replaceLiteralWithConcatenation(mySettings.getName());
final Editor editor = PsiUtilBase.findEditor(expr);
if (editor != null) {
editor.getSelectionModel().removeSelection();
editor.getCaretModel().moveToOffset(expr.getTextRange().getEndOffset());
}
}
}
private void processUsages(UsageInfo[] usages, GroovyPsiElementFactory factory) {
// Replacing expression occurrences
for (UsageInfo usage : usages) {
if (usage instanceof ChangedMethodCallInfo) {
PsiElement element = usage.getElement();
GroovyIntroduceParameterUtil.processChangedMethodCall(element, mySettings, myProject);
}
else if (usage instanceof InternalUsageInfo) {
PsiElement element = usage.getElement();
if (element == null) continue;
GrExpression newExpr = factory.createExpressionFromText(mySettings.getName());
if (element instanceof GrExpression) {
((GrExpression)element).replaceWithExpression(newExpr, true);
}
else {
element.replace(newExpr);
}
}
}
}
private void processMethodSignature(UsageInfo[] usages,
GrMethod toReplaceIn,
PsiMethod toSearchFor,
boolean methodsToProcessAreDifferent) {
IntroduceParameterUtil.changeMethodSignatureAndResolveFieldConflicts(new UsageInfo(toReplaceIn), usages, this);
if (methodsToProcessAreDifferent) {
IntroduceParameterUtil.changeMethodSignatureAndResolveFieldConflicts(new UsageInfo(toSearchFor), usages, this);
}
}
private void generateDelegate(GrMethod toReplaceIn, PsiMethod toSearchFor, boolean methodsToProcessAreDifferent) {
GroovyIntroduceParameterUtil.generateDelegate(toReplaceIn, myParameterInitializer, myProject);
if (methodsToProcessAreDifferent) {
final GrMethod method = GroovyIntroduceParameterUtil.generateDelegate(toSearchFor, myParameterInitializer, myProject);
final PsiClass containingClass = method.getContainingClass();
if (containingClass != null && containingClass.isInterface()) {
final GrOpenBlock block = method.getBlock();
if (block != null) {
block.delete();
}
}
}
}
@Override
protected String getCommandName() {
return RefactoringBundle.message("introduce.parameter.command", DescriptiveNameUtil.getDescriptiveName(mySettings.getToReplaceIn()));
}
@NotNull
@Override
public Project getProject() {
return mySettings.getProject();
}
@Override
public PsiMethod getMethodToReplaceIn() {
return (PsiMethod)mySettings.getToReplaceIn();
}
@NotNull
@Override
public PsiMethod getMethodToSearchFor() {
return (PsiMethod)mySettings.getToSearchFor();
}
@Override
public IntroduceParameterData.ExpressionWrapper getParameterInitializer() {
return myParameterInitializer;
}
@NotNull
@Override
public String getParameterName() {
return mySettings.getName();
}
@Override
public int getReplaceFieldsWithGetters() {
return mySettings.replaceFieldsWithGetters();
}
@Override
public boolean isDeclareFinal() {
return mySettings.declareFinal();
}
@Override
public boolean isGenerateDelegate() {
return mySettings.generateDelegate();
}
@NotNull
@Override
public PsiType getForcedType() {
final PsiType selectedType = mySettings.getSelectedType();
if (selectedType != null) return selectedType;
final PsiManager manager = PsiManager.getInstance(myProject);
final GlobalSearchScope resolveScope = mySettings.getToReplaceIn().getResolveScope();
return PsiType.getJavaLangObject(manager, resolveScope);
}
@NotNull
@Override
public TIntArrayList getParametersToRemove() {
return mySettings.parametersToRemove();
}
}