update copyright
[fedora-idea.git] / java / java-impl / src / com / intellij / refactoring / introduceParameter / IntroduceParameterProcessor.java
blobcf98a5ee8dfeff082cdc93f2acc9db8d29a36675
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 * Created by IntelliJ IDEA.
19 * User: dsl
20 * Date: 07.05.2002
21 * Time: 11:17:31
22 * To change template for new class use
23 * Code Style | Class Templates options (Tools | IDE Options).
25 package com.intellij.refactoring.introduceParameter;
27 import com.intellij.codeInsight.ChangeContextUtil;
28 import com.intellij.openapi.diagnostic.Logger;
29 import com.intellij.openapi.project.Project;
30 import com.intellij.openapi.util.Pair;
31 import com.intellij.openapi.util.Ref;
32 import com.intellij.psi.*;
33 import com.intellij.psi.search.GlobalSearchScope;
34 import com.intellij.psi.search.searches.MethodReferencesSearch;
35 import com.intellij.psi.search.searches.OverridingMethodsSearch;
36 import com.intellij.psi.util.PsiTreeUtil;
37 import com.intellij.refactoring.BaseRefactoringProcessor;
38 import com.intellij.refactoring.RefactoringBundle;
39 import com.intellij.refactoring.changeSignature.ChangeSignatureProcessor;
40 import com.intellij.refactoring.introduceVariable.IntroduceVariableBase;
41 import com.intellij.refactoring.util.*;
42 import com.intellij.refactoring.util.occurences.ExpressionOccurenceManager;
43 import com.intellij.refactoring.util.occurences.LocalVariableOccurenceManager;
44 import com.intellij.refactoring.util.occurences.OccurenceManager;
45 import com.intellij.refactoring.util.usageInfo.DefaultConstructorImplicitUsageInfo;
46 import com.intellij.refactoring.util.usageInfo.NoConstructorClassUsageInfo;
47 import com.intellij.usageView.UsageInfo;
48 import com.intellij.usageView.UsageViewDescriptor;
49 import com.intellij.usageView.UsageViewUtil;
50 import com.intellij.util.IncorrectOperationException;
51 import com.intellij.util.containers.HashSet;
52 import com.intellij.util.containers.MultiMap;
53 import gnu.trove.TIntArrayList;
54 import gnu.trove.TIntProcedure;
55 import org.jetbrains.annotations.NotNull;
56 import org.jetbrains.annotations.Nullable;
58 import java.util.ArrayList;
59 import java.util.Set;
61 public class IntroduceParameterProcessor extends BaseRefactoringProcessor implements IntroduceParameterData {
62 private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.introduceParameter.IntroduceParameterProcessor");
64 private final PsiMethod myMethodToReplaceIn;
65 private final PsiMethod myMethodToSearchFor;
66 private PsiExpression myParameterInitializer;
67 private final PsiExpression myExpressionToSearch;
68 private final PsiLocalVariable myLocalVariable;
69 private final boolean myRemoveLocalVariable;
70 private final String myParameterName;
71 private final boolean myReplaceAllOccurences;
73 private int myReplaceFieldsWithGetters;
74 private final boolean myDeclareFinal;
75 private final boolean myGenerateDelegate;
76 private PsiType myForcedType;
77 private final TIntArrayList myParametersToRemove;
78 private final PsiManager myManager;
80 /**
81 * if expressionToSearch is null, search for localVariable
83 public IntroduceParameterProcessor(@NotNull Project project,
84 PsiMethod methodToReplaceIn,
85 @NotNull PsiMethod methodToSearchFor,
86 PsiExpression parameterInitializer,
87 PsiExpression expressionToSearch,
88 PsiLocalVariable localVariable,
89 boolean removeLocalVariable,
90 String parameterName,
91 boolean replaceAllOccurences,
92 int replaceFieldsWithGetters,
93 boolean declareFinal,
94 boolean generateDelegate,
95 PsiType forcedType,
96 @NotNull TIntArrayList parametersToRemove) {
97 super(project);
99 myMethodToReplaceIn = methodToReplaceIn;
100 myMethodToSearchFor = methodToSearchFor;
101 myParameterInitializer = parameterInitializer;
102 myExpressionToSearch = expressionToSearch;
104 myLocalVariable = localVariable;
105 myRemoveLocalVariable = removeLocalVariable;
106 myParameterName = parameterName;
107 myReplaceAllOccurences = replaceAllOccurences;
108 myReplaceFieldsWithGetters = replaceFieldsWithGetters;
109 myDeclareFinal = declareFinal;
110 myGenerateDelegate = generateDelegate;
111 myForcedType = forcedType;
112 myManager = PsiManager.getInstance(project);
114 myParametersToRemove = parametersToRemove;
117 protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages) {
118 return new IntroduceParameterViewDescriptor(myMethodToSearchFor);
121 public PsiType getForcedType() {
122 return myForcedType;
125 public void setForcedType(PsiType forcedType) {
126 myForcedType = forcedType;
129 public int getReplaceFieldsWithGetters() {
130 return myReplaceFieldsWithGetters;
133 public void setReplaceFieldsWithGetters(int replaceFieldsWithGetters) {
134 myReplaceFieldsWithGetters = replaceFieldsWithGetters;
137 @NotNull
138 protected UsageInfo[] findUsages() {
139 ArrayList<UsageInfo> result = new ArrayList<UsageInfo>();
141 PsiMethod[] overridingMethods =
142 OverridingMethodsSearch.search(myMethodToSearchFor, myMethodToSearchFor.getUseScope(), true).toArray(PsiMethod.EMPTY_ARRAY);
143 for (PsiMethod overridingMethod : overridingMethods) {
144 result.add(new UsageInfo(overridingMethod));
146 if (!myGenerateDelegate) {
147 PsiReference[] refs =
148 MethodReferencesSearch.search(myMethodToSearchFor, GlobalSearchScope.projectScope(myProject), true).toArray(PsiReference.EMPTY_ARRAY);
151 for (PsiReference ref1 : refs) {
152 PsiElement ref = ref1.getElement();
153 if (ref instanceof PsiMethod && ((PsiMethod)ref).isConstructor()) {
154 DefaultConstructorImplicitUsageInfo implicitUsageInfo =
155 new DefaultConstructorImplicitUsageInfo((PsiMethod)ref, myMethodToSearchFor);
156 result.add(implicitUsageInfo);
158 else if (ref instanceof PsiClass) {
159 result.add(new NoConstructorClassUsageInfo((PsiClass)ref));
161 else if (!insideMethodToBeReplaced(ref)) {
162 result.add(new ExternalUsageInfo(ref));
164 else {
165 result.add(new ChangedMethodCallInfo(ref));
170 if (myReplaceAllOccurences) {
171 final OccurenceManager occurenceManager;
172 if (myLocalVariable == null) {
173 occurenceManager = new ExpressionOccurenceManager(myExpressionToSearch, myMethodToReplaceIn, null);
175 else {
176 occurenceManager = new LocalVariableOccurenceManager(myLocalVariable, null);
178 PsiElement[] exprs = occurenceManager.getOccurences();
179 for (PsiElement expr : exprs) {
180 result.add(new InternalUsageInfo(expr));
183 else {
184 if (myExpressionToSearch != null) {
185 result.add(new InternalUsageInfo(myExpressionToSearch));
189 final UsageInfo[] usageInfos = result.toArray(new UsageInfo[result.size()]);
190 return UsageViewUtil.removeDuplicatedUsages(usageInfos);
193 private static class ReferencedElementsCollector extends JavaRecursiveElementWalkingVisitor {
194 private final Set<PsiElement> myResult = new HashSet<PsiElement>();
196 @Override public void visitReferenceExpression(PsiReferenceExpression expression) {
197 visitReferenceElement(expression);
200 @Override public void visitReferenceElement(PsiJavaCodeReferenceElement reference) {
201 super.visitReferenceElement(reference);
202 final PsiElement element = reference.resolve();
203 if (element != null) {
204 myResult.add(element);
209 protected boolean preprocessUsages(Ref<UsageInfo[]> refUsages) {
210 UsageInfo[] usagesIn = refUsages.get();
211 MultiMap<PsiElement, String> conflicts = new MultiMap<PsiElement, String>();
213 AnySameNameVariables anySameNameVariables = new AnySameNameVariables();
214 myMethodToReplaceIn.accept(anySameNameVariables);
215 final Pair<PsiElement, String> conflictPair = anySameNameVariables.getConflict();
216 if (conflictPair != null) {
217 conflicts.putValue(conflictPair.first, conflictPair.second);
220 if (!myGenerateDelegate) {
221 detectAccessibilityConflicts(usagesIn, conflicts);
224 if (myParameterInitializer != null && !myMethodToReplaceIn.hasModifierProperty(PsiModifier.PRIVATE)) {
225 final AnySupers anySupers = new AnySupers();
226 myParameterInitializer.accept(anySupers);
227 if (anySupers.isResult()) {
228 for (UsageInfo usageInfo : usagesIn) {
229 if (!(usageInfo.getElement() instanceof PsiMethod) && !(usageInfo instanceof InternalUsageInfo)) {
230 if (!PsiTreeUtil.isAncestor(myMethodToReplaceIn.getContainingClass(), usageInfo.getElement(), false)) {
231 conflicts.putValue(myParameterInitializer, RefactoringBundle.message("parameter.initializer.contains.0.but.not.all.calls.to.method.are.in.its.class",
232 CommonRefactoringUtil.htmlEmphasize(PsiKeyword.SUPER)));
233 break;
240 for (IntroduceParameterMethodUsagesProcessor processor : IntroduceParameterMethodUsagesProcessor.EP_NAME.getExtensions()) {
241 processor.findConflicts(this, refUsages.get(), conflicts);
244 return showConflicts(conflicts);
247 private void detectAccessibilityConflicts(final UsageInfo[] usageArray, MultiMap<PsiElement, String> conflicts) {
248 if (myParameterInitializer != null) {
249 final ReferencedElementsCollector collector = new ReferencedElementsCollector();
250 myParameterInitializer.accept(collector);
251 final Set<PsiElement> result = collector.myResult;
252 if (!result.isEmpty()) {
253 for (final UsageInfo usageInfo : usageArray) {
254 if (usageInfo instanceof ExternalUsageInfo && isMethodUsage(usageInfo)) {
255 final PsiElement place = usageInfo.getElement();
256 for (final PsiElement element : result) {
257 if (element instanceof PsiMember &&
258 !JavaPsiFacade.getInstance(myProject).getResolveHelper().isAccessible((PsiMember)element, place, null)) {
259 String message =
260 RefactoringBundle.message(
261 "0.is.not.accesible.from.1.value.for.introduced.parameter.in.that.method.call.will.be.incorrect",
262 RefactoringUIUtil.getDescription(element, true),
263 RefactoringUIUtil.getDescription(ConflictsUtil.getContainer(place), true));
264 conflicts.putValue(element, message);
273 private static boolean isMethodUsage(UsageInfo usageInfo) {
274 for (IntroduceParameterMethodUsagesProcessor processor : IntroduceParameterMethodUsagesProcessor.EP_NAME.getExtensions()) {
275 if (processor.isMethodUsage(usageInfo)) return true;
277 return false;
280 public static class AnySupers extends JavaRecursiveElementWalkingVisitor {
281 private boolean myResult = false;
282 @Override public void visitSuperExpression(PsiSuperExpression expression) {
283 myResult = true;
286 public boolean isResult() {
287 return myResult;
290 @Override public void visitReferenceExpression(PsiReferenceExpression expression) {
291 visitElement(expression);
295 public class AnySameNameVariables extends JavaRecursiveElementWalkingVisitor {
296 private Pair<PsiElement, String> conflict = null;
298 public Pair<PsiElement, String> getConflict() {
299 return conflict;
302 @Override public void visitVariable(PsiVariable variable) {
303 if (variable == myLocalVariable) return;
304 if (myParameterName.equals(variable.getName())) {
305 String descr = RefactoringBundle.message("there.is.already.a.0.it.will.conflict.with.an.introduced.parameter",
306 RefactoringUIUtil.getDescription(variable, true));
308 conflict = Pair.<PsiElement, String>create(variable, CommonRefactoringUtil.capitalize(descr));
312 @Override public void visitReferenceExpression(PsiReferenceExpression expression) {
315 @Override public void visitElement(PsiElement element) {
316 if(conflict != null) return;
317 super.visitElement(element);
321 private boolean insideMethodToBeReplaced(PsiElement methodUsage) {
322 PsiElement parent = methodUsage.getParent();
323 while(parent != null) {
324 if (parent.equals(myMethodToReplaceIn)) {
325 return true;
327 parent = parent.getParent();
329 return false;
332 protected void refreshElements(PsiElement[] elements) {
335 protected void performRefactoring(UsageInfo[] usages) {
336 try {
337 PsiElementFactory factory = JavaPsiFacade.getInstance(myManager.getProject()).getElementFactory();
338 PsiType initializerType = getInitializerType(myForcedType, myParameterInitializer, myLocalVariable);
339 setForcedType(initializerType);
341 // Converting myParameterInitializer
342 if (myParameterInitializer == null) {
343 LOG.assertTrue(myLocalVariable != null);
344 myParameterInitializer = factory.createExpressionFromText(myLocalVariable.getName(), myLocalVariable);
346 else {
347 myParameterInitializer = RefactoringUtil.convertInitializerToNormalExpression(myParameterInitializer, initializerType);
350 // Changing external occurences (the tricky part)
352 for (UsageInfo usage : usages) {
353 if (!(usage instanceof InternalUsageInfo)) {
354 if (usage instanceof DefaultConstructorImplicitUsageInfo) {
355 addSuperCall(usage, usages);
357 else if (usage instanceof NoConstructorClassUsageInfo) {
358 addDefaultConstructor(usage, usages);
360 else {
361 PsiElement element = usage.getElement();
362 if (element instanceof PsiMethod) {
363 if (!myManager.areElementsEquivalent(element, myMethodToReplaceIn)) {
364 changeMethodSignatureAndResolveFieldConflicts(usage, usages);
367 else if (!myGenerateDelegate) {
368 changeExternalUsage(usage, usages);
374 if (myGenerateDelegate) {
375 generateDelegate();
378 // Changing signature of initial method
379 // (signature of myMethodToReplaceIn will be either changed now or have already been changed)
380 LOG.assertTrue(initializerType.isValid());
381 final FieldConflictsResolver fieldConflictsResolver = new FieldConflictsResolver(myParameterName, myMethodToReplaceIn.getBody());
382 changeMethodSignatureAndResolveFieldConflicts(new UsageInfo(myMethodToReplaceIn), usages);
383 if (myMethodToSearchFor != myMethodToReplaceIn) {
384 changeMethodSignatureAndResolveFieldConflicts(new UsageInfo(myMethodToSearchFor), usages);
386 ChangeContextUtil.clearContextInfo(myParameterInitializer);
388 // Replacing expression occurences
389 for (UsageInfo usage : usages) {
390 if (usage instanceof ChangedMethodCallInfo) {
391 PsiElement element = usage.getElement();
393 processChangedMethodCall(element);
395 else if (usage instanceof InternalUsageInfo) {
396 PsiElement element = usage.getElement();
397 if (element instanceof PsiExpression) {
398 element = RefactoringUtil.outermostParenthesizedExpression((PsiExpression)element);
400 if (element.getParent() instanceof PsiExpressionStatement) {
401 element.getParent().delete();
403 else {
404 PsiExpression newExpr = factory.createExpressionFromText(myParameterName, element);
405 IntroduceVariableBase.replace((PsiExpression)element, newExpr, myProject);
410 if(myLocalVariable != null && myRemoveLocalVariable) {
411 myLocalVariable.normalizeDeclaration();
412 myLocalVariable.getParent().delete();
414 fieldConflictsResolver.fix();
416 catch (IncorrectOperationException ex) {
417 LOG.error(ex);
421 private void generateDelegate() throws IncorrectOperationException {
422 final PsiMethod delegate = (PsiMethod)myMethodToReplaceIn.copy();
423 final PsiElementFactory elementFactory = JavaPsiFacade.getInstance(myManager.getProject()).getElementFactory();
424 ChangeSignatureProcessor.makeEmptyBody(elementFactory, delegate);
425 final PsiCallExpression callExpression = ChangeSignatureProcessor.addDelegatingCallTemplate(delegate, delegate.getName());
426 final PsiExpressionList argumentList = callExpression.getArgumentList();
427 assert argumentList != null;
428 final PsiParameter[] psiParameters = myMethodToReplaceIn.getParameterList().getParameters();
430 final PsiParameter anchorParameter = getAnchorParameter(myMethodToReplaceIn);
431 if (psiParameters.length == 0) {
432 argumentList.add(myParameterInitializer);
434 else {
435 for (int i = 0; i < psiParameters.length; i++) {
436 PsiParameter psiParameter = psiParameters[i];
437 if (!myParametersToRemove.contains(i)) {
438 final PsiExpression expression = elementFactory.createExpressionFromText(psiParameter.getName(), delegate);
439 argumentList.add(expression);
441 if (psiParameter == anchorParameter) {
442 argumentList.add(myParameterInitializer);
447 myMethodToReplaceIn.getContainingClass().addBefore(delegate, myMethodToReplaceIn);
450 private void addDefaultConstructor(UsageInfo usage, UsageInfo[] usages) throws IncorrectOperationException {
451 for (IntroduceParameterMethodUsagesProcessor processor : IntroduceParameterMethodUsagesProcessor.EP_NAME.getExtensions()) {
452 if (!processor.processAddDefaultConstructor(this, usage, usages)) break;
456 private void addSuperCall(UsageInfo usage, UsageInfo[] usages) throws IncorrectOperationException {
457 for (IntroduceParameterMethodUsagesProcessor processor : IntroduceParameterMethodUsagesProcessor.EP_NAME.getExtensions()) {
458 if (!processor.processAddSuperCall(this, usage, usages)) break;
462 static PsiType getInitializerType(PsiType forcedType, PsiExpression parameterInitializer, PsiLocalVariable localVariable) {
463 final PsiType initializerType;
464 if (forcedType == null) {
465 if (parameterInitializer == null) {
466 if (localVariable != null) {
467 initializerType = localVariable.getType();
468 } else {
469 LOG.assertTrue(false);
470 initializerType = null;
472 } else {
473 if (localVariable == null) {
474 initializerType = RefactoringUtil.getTypeByExpressionWithExpectedType(parameterInitializer);
475 } else {
476 initializerType = localVariable.getType();
479 } else {
480 initializerType = forcedType;
482 return initializerType;
485 private void processChangedMethodCall(PsiElement element) throws IncorrectOperationException {
486 if (element.getParent() instanceof PsiMethodCallExpression) {
487 PsiMethodCallExpression methodCall = (PsiMethodCallExpression)element.getParent();
489 PsiElementFactory factory = JavaPsiFacade.getInstance(methodCall.getProject()).getElementFactory();
490 PsiExpression expression = factory.createExpressionFromText(myParameterName, null);
491 final PsiExpressionList argList = methodCall.getArgumentList();
492 final PsiExpression[] exprs = argList.getExpressions();
494 if (exprs.length > 0) {
495 argList.addAfter(expression, exprs[exprs.length - 1]);
497 else {
498 argList.add(expression);
501 removeParametersFromCall(argList);
503 else {
504 LOG.error(element.getParent().toString());
508 private void removeParametersFromCall(final PsiExpressionList argList) {
509 final PsiExpression[] exprs = argList.getExpressions();
510 myParametersToRemove.forEachDescending(new TIntProcedure() {
511 public boolean execute(final int paramNum) {
512 try {
513 exprs[paramNum].delete();
515 catch (IncorrectOperationException e) {
516 LOG.error(e);
518 return true;
523 private void changeExternalUsage(UsageInfo usage, UsageInfo[] usages) throws IncorrectOperationException {
524 for (IntroduceParameterMethodUsagesProcessor processor: IntroduceParameterMethodUsagesProcessor.EP_NAME.getExtensions()) {
525 if (!processor.processChangeMethodUsage(this, usage, usages)) break;
529 protected String getCommandName() {
530 return RefactoringBundle.message("introduce.parameter.command", UsageViewUtil.getDescriptiveName(myMethodToReplaceIn));
533 private void changeMethodSignatureAndResolveFieldConflicts(UsageInfo usage, UsageInfo[] usages) throws IncorrectOperationException {
534 for (IntroduceParameterMethodUsagesProcessor processor : IntroduceParameterMethodUsagesProcessor.EP_NAME.getExtensions()) {
535 if (!processor.processChangeMethodSignature(this, usage, usages)) break;
539 @Nullable
540 private static PsiParameter getAnchorParameter(PsiMethod methodToReplaceIn) {
541 PsiParameterList parameterList = methodToReplaceIn.getParameterList();
542 final PsiParameter anchorParameter;
543 final PsiParameter[] parameters = parameterList.getParameters();
544 final int length = parameters.length;
545 if (!methodToReplaceIn.isVarArgs()) {
546 anchorParameter = length > 0 ? parameters[length-1] : null;
548 else {
549 LOG.assertTrue(length > 0);
550 LOG.assertTrue(parameters[length-1].isVarArgs());
551 anchorParameter = length > 1 ? parameters[length-2] : null;
553 return anchorParameter;
556 public PsiMethod getMethodToReplaceIn() {
557 return myMethodToReplaceIn;
560 @NotNull
561 public PsiMethod getMethodToSearchFor() {
562 return myMethodToSearchFor;
565 public PsiExpression getParameterInitializer() {
566 return myParameterInitializer;
569 public PsiExpression getExpressionToSearch() {
570 return myExpressionToSearch;
573 public PsiLocalVariable getLocalVariable() {
574 return myLocalVariable;
577 public boolean isRemoveLocalVariable() {
578 return myRemoveLocalVariable;
581 @NotNull
582 public String getParameterName() {
583 return myParameterName;
586 public boolean isReplaceAllOccurences() {
587 return myReplaceAllOccurences;
590 public boolean isDeclareFinal() {
591 return myDeclareFinal;
594 public boolean isGenerateDelegate() {
595 return myGenerateDelegate;
598 @NotNull
599 public TIntArrayList getParametersToRemove() {
600 return myParametersToRemove;
603 public PsiManager getManager() {
604 return myManager;
607 @NotNull
608 public Project getProject() {
609 return myProject;