update copyright
[fedora-idea.git] / java / java-impl / src / com / intellij / refactoring / move / moveInstanceMethod / MoveInstanceMethodProcessor.java
blob8d44b16afef9dc273dbe9c641c02debc8debb55e
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.
16 package com.intellij.refactoring.move.moveInstanceMethod;
18 import com.intellij.codeInsight.ChangeContextUtil;
19 import com.intellij.openapi.diagnostic.Logger;
20 import com.intellij.openapi.project.Project;
21 import com.intellij.openapi.util.Ref;
22 import com.intellij.psi.*;
23 import com.intellij.psi.javadoc.PsiDocTagValue;
24 import com.intellij.psi.search.GlobalSearchScope;
25 import com.intellij.psi.search.searches.ClassInheritorsSearch;
26 import com.intellij.psi.search.searches.ReferencesSearch;
27 import com.intellij.psi.util.PsiTreeUtil;
28 import com.intellij.psi.util.PsiUtil;
29 import com.intellij.refactoring.BaseRefactoringProcessor;
30 import com.intellij.refactoring.RefactoringBundle;
31 import com.intellij.refactoring.move.MoveInstanceMembersUtil;
32 import com.intellij.refactoring.util.*;
33 import com.intellij.usageView.UsageInfo;
34 import com.intellij.usageView.UsageViewDescriptor;
35 import com.intellij.util.IncorrectOperationException;
36 import com.intellij.util.VisibilityUtil;
37 import com.intellij.util.containers.HashSet;
38 import com.intellij.util.containers.MultiMap;
39 import org.jetbrains.annotations.NonNls;
40 import org.jetbrains.annotations.NotNull;
42 import java.util.*;
44 /**
45 * @author ven
47 public class MoveInstanceMethodProcessor extends BaseRefactoringProcessor{
48 private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.move.moveInstanceMethod.MoveInstanceMethodProcessor");
50 public PsiMethod getMethod() {
51 return myMethod;
54 public PsiVariable getTargetVariable() {
55 return myTargetVariable;
58 private PsiMethod myMethod;
59 private PsiVariable myTargetVariable;
60 private PsiClass myTargetClass;
61 private final String myNewVisibility;
62 private final Map<PsiClass, String> myOldClassParameterNames;
64 public MoveInstanceMethodProcessor(final Project project,
65 final PsiMethod method,
66 final PsiVariable targetVariable,
67 final String newVisibility,
68 final Map<PsiClass, String> oldClassParameterNames) {
69 super(project);
70 myMethod = method;
71 myTargetVariable = targetVariable;
72 myOldClassParameterNames = oldClassParameterNames;
73 LOG.assertTrue(myTargetVariable instanceof PsiParameter || myTargetVariable instanceof PsiField);
74 LOG.assertTrue(myTargetVariable.getType() instanceof PsiClassType);
75 final PsiType type = myTargetVariable.getType();
76 LOG.assertTrue(type instanceof PsiClassType);
77 myTargetClass = ((PsiClassType) type).resolve();
78 myNewVisibility = newVisibility;
81 protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages) {
82 return new MoveInstanceMethodViewDescriptor(myMethod, myTargetVariable, myTargetClass);
85 protected boolean preprocessUsages(Ref<UsageInfo[]> refUsages) {
86 final UsageInfo[] usages = refUsages.get();
87 MultiMap<PsiElement, String> conflicts = new MultiMap<PsiElement, String>();
88 final Set<PsiMember> members = new HashSet<PsiMember>();
89 members.add(myMethod);
90 if (myTargetVariable instanceof PsiField) members.add((PsiMember)myTargetVariable);
91 if (!myTargetClass.isInterface()) {
92 RefactoringConflictsUtil.analyzeAccessibilityConflicts(members, myTargetClass, conflicts, myNewVisibility);
94 else {
95 for (final UsageInfo usage : usages) {
96 if (usage instanceof InheritorUsageInfo) {
97 RefactoringConflictsUtil.analyzeAccessibilityConflicts(
98 members, ((InheritorUsageInfo)usage).getInheritor(), conflicts, myNewVisibility);
103 if (myTargetVariable instanceof PsiParameter) {
104 PsiParameter parameter = (PsiParameter)myTargetVariable;
105 for (final UsageInfo usageInfo : usages) {
106 if (usageInfo instanceof MethodCallUsageInfo) {
107 final PsiMethodCallExpression methodCall = ((MethodCallUsageInfo)usageInfo).getMethodCallExpression();
108 final PsiExpression[] expressions = methodCall.getArgumentList().getExpressions();
109 final int index = myMethod.getParameterList().getParameterIndex(parameter);
110 if (index < expressions.length) {
111 PsiExpression instanceValue = expressions[index];
112 instanceValue = RefactoringUtil.unparenthesizeExpression(instanceValue);
113 if (instanceValue instanceof PsiLiteralExpression && ((PsiLiteralExpression)instanceValue).getValue() == null) {
114 String message = RefactoringBundle.message("0.contains.call.with.null.argument.for.parameter.1",
115 RefactoringUIUtil.getDescription(ConflictsUtil.getContainer(methodCall), true),
116 CommonRefactoringUtil.htmlEmphasize(parameter.getName()));
117 conflicts.putValue(instanceValue, message);
124 try {
125 ConflictsUtil.checkMethodConflicts(myTargetClass, myMethod, getPatternMethod(), conflicts);
127 catch (IncorrectOperationException e) {}
129 return showConflicts(conflicts);
132 @NotNull
133 protected UsageInfo[] findUsages() {
134 final PsiManager manager = myMethod.getManager();
135 final GlobalSearchScope searchScope = GlobalSearchScope.allScope(manager.getProject());
136 final List<UsageInfo> usages = new ArrayList<UsageInfo>();
137 for (PsiReference ref : ReferencesSearch.search(myMethod, searchScope, false)) {
138 final PsiElement element = ref.getElement();
139 if (element instanceof PsiReferenceExpression) {
140 boolean isInternal = PsiTreeUtil.isAncestor(myMethod, element, true);
141 usages.add(new MethodCallUsageInfo((PsiReferenceExpression)element, isInternal));
143 else if (element instanceof PsiDocTagValue) {
144 usages.add(new JavadocUsageInfo(((PsiDocTagValue)element)));
146 else {
147 throw new UnknownReferenceTypeException(element.getLanguage());
151 if (myTargetClass.isInterface()) {
152 addInheritorUsages(myTargetClass, searchScope, usages);
155 final PsiCodeBlock body = myMethod.getBody();
156 if (body != null) {
157 body.accept(new JavaRecursiveElementWalkingVisitor() {
158 @Override public void visitNewExpression(PsiNewExpression expression) {
159 if (MoveInstanceMembersUtil.getClassReferencedByThis(expression) != null) {
160 usages.add(new InternalUsageInfo(expression));
162 super.visitNewExpression(expression);
165 @Override public void visitReferenceExpression(PsiReferenceExpression expression) {
166 if (MoveInstanceMembersUtil.getClassReferencedByThis(expression) != null) {
167 usages.add(new InternalUsageInfo(expression));
168 } else if (!expression.isQualified()) {
169 final PsiElement resolved = expression.resolve();
170 if (myTargetVariable.equals(resolved)) {
171 usages.add(new InternalUsageInfo(expression));
175 super.visitReferenceExpression(expression);
180 return usages.toArray(new UsageInfo[usages.size()]);
183 private static void addInheritorUsages(PsiClass aClass, final GlobalSearchScope searchScope, final List<UsageInfo> usages) {
184 for (PsiClass inheritor : ClassInheritorsSearch.search(aClass, searchScope, false).findAll()) {
185 if (!inheritor.isInterface()) {
186 usages.add(new InheritorUsageInfo(inheritor));
188 else {
189 addInheritorUsages(inheritor, searchScope, usages);
194 protected void refreshElements(PsiElement[] elements) {
195 LOG.assertTrue(elements.length == 3);
196 myMethod = (PsiMethod) elements[0];
197 myTargetVariable = (PsiVariable) elements[1];
198 myTargetClass = (PsiClass) elements[2];
201 protected String getCommandName() {
202 return RefactoringBundle.message("move.instance.method.command");
205 public PsiClass getTargetClass() {
206 return myTargetClass;
209 protected void performRefactoring(UsageInfo[] usages) {
210 if (!CommonRefactoringUtil.checkReadOnlyStatus(myProject, myTargetClass)) return;
212 PsiMethod patternMethod = createMethodToAdd();
213 final List<PsiReference> docRefs = new ArrayList<PsiReference>();
214 for (UsageInfo usage : usages) {
215 if (usage instanceof InheritorUsageInfo) {
216 final PsiClass inheritor = ((InheritorUsageInfo)usage).getInheritor();
217 addMethodToClass(inheritor, patternMethod);
219 else if (usage instanceof MethodCallUsageInfo && !((MethodCallUsageInfo)usage).isInternal()) {
220 correctMethodCall(((MethodCallUsageInfo)usage).getMethodCallExpression(), false);
222 else if (usage instanceof JavadocUsageInfo) {
223 docRefs.add(usage.getElement().getReference());
227 try {
228 if (myTargetClass.isInterface()) patternMethod.getBody().delete();
230 final PsiMethod method = addMethodToClass(myTargetClass, patternMethod);
231 myMethod.delete();
232 for (PsiReference reference : docRefs) {
233 reference.bindToElement(method);
236 catch (IncorrectOperationException e) {
237 LOG.error(e);
241 private void correctMethodCall(final PsiMethodCallExpression expression, final boolean isInternalCall) {
242 try {
243 final PsiManager manager = myMethod.getManager();
244 PsiReferenceExpression methodExpression = expression.getMethodExpression();
245 if (!methodExpression.isReferenceTo(myMethod)) return;
246 final PsiExpression oldQualifier = methodExpression.getQualifierExpression();
247 PsiExpression newQualifier = null;
249 if (myTargetVariable instanceof PsiParameter) {
250 final int index = myMethod.getParameterList().getParameterIndex((PsiParameter)myTargetVariable);
251 final PsiExpression[] arguments = expression.getArgumentList().getExpressions();
252 if (index < arguments.length) {
253 newQualifier = (PsiExpression)arguments[index].copy();
254 arguments[index].delete();
257 else {
258 VisibilityUtil.escalateVisibility((PsiField)myTargetVariable, expression);
259 newQualifier = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory().createExpressionFromText(myTargetVariable.getName(), null);
262 PsiExpression newArgument = null;
264 final PsiClass classReferencedByThis = MoveInstanceMembersUtil.getClassReferencedByThis(methodExpression);
265 if (classReferencedByThis != null) {
266 @NonNls String thisArgumentText = null;
267 if (manager.areElementsEquivalent(myMethod.getContainingClass(), classReferencedByThis)) {
268 if (myOldClassParameterNames.containsKey(myMethod.getContainingClass())) {
269 thisArgumentText = "this";
272 else {
273 thisArgumentText = classReferencedByThis.getName() + ".this";
276 if (thisArgumentText != null) {
277 newArgument = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory().createExpressionFromText(thisArgumentText, null);
279 } else {
280 if (!isInternalCall && oldQualifier != null) {
281 final PsiType type = oldQualifier.getType();
282 if (type instanceof PsiClassType) {
283 final PsiClass resolved = ((PsiClassType)type).resolve();
284 if (resolved != null && getParameterNameToCreate(resolved) != null) {
285 newArgument = replaceRefsToTargetVariable(oldQualifier); //replace is needed in case old qualifier is e.g. the same as field as target variable
292 if (newArgument != null) {
293 expression.getArgumentList().add(newArgument);
296 if (newQualifier != null) {
297 if (newQualifier instanceof PsiThisExpression && ((PsiThisExpression)newQualifier).getQualifier() == null) {
298 //Remove now redundant 'this' qualifier
299 if (oldQualifier != null) oldQualifier.delete();
301 else {
302 final PsiReferenceExpression refExpr = (PsiReferenceExpression)JavaPsiFacade.getInstance(manager.getProject()).getElementFactory()
303 .createExpressionFromText("q." + myMethod.getName(), null);
304 refExpr.getQualifierExpression().replace(newQualifier);
305 methodExpression.replace(refExpr);
309 catch (IncorrectOperationException e) {
310 LOG.error(e);
314 private PsiExpression replaceRefsToTargetVariable(final PsiExpression expression) {
315 final PsiManager manager = expression.getManager();
316 if (expression instanceof PsiReferenceExpression &&
317 ((PsiReferenceExpression)expression).isReferenceTo(myTargetVariable)) {
318 return createThisExpr(manager);
321 expression.accept(new JavaRecursiveElementVisitor() {
322 @Override public void visitReferenceExpression(PsiReferenceExpression expression) {
323 super.visitReferenceExpression(expression);
324 if (expression.isReferenceTo(myTargetVariable)) {
325 try {
326 expression.replace(createThisExpr(manager));
328 catch (IncorrectOperationException e) {
329 LOG.error(e);
335 return expression;
338 private static PsiExpression createThisExpr(final PsiManager manager) {
339 try {
340 return JavaPsiFacade.getInstance(manager.getProject()).getElementFactory().createExpressionFromText("this", null);
342 catch (IncorrectOperationException e) {
343 LOG.error(e);
344 return null;
348 private static PsiMethod addMethodToClass(final PsiClass aClass, final PsiMethod patternMethod) {
349 try {
350 final PsiMethod method = (PsiMethod)aClass.add(patternMethod);
351 ChangeContextUtil.decodeContextInfo(method, null, null);
352 return method;
354 catch (IncorrectOperationException e) {
355 LOG.error(e);
358 return null;
361 private PsiMethod createMethodToAdd () {
362 ChangeContextUtil.encodeContextInfo(myMethod, true);
363 try {
364 final PsiManager manager = myMethod.getManager();
365 final PsiElementFactory factory = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory();
367 //correct internal references
368 final PsiCodeBlock body = myMethod.getBody();
369 if (body != null) {
370 body.accept(new JavaRecursiveElementVisitor() {
371 @Override public void visitThisExpression(PsiThisExpression expression) {
372 final PsiClass classReferencedByThis = MoveInstanceMembersUtil.getClassReferencedByThis(expression);
373 if (classReferencedByThis != null) {
374 final PsiElementFactory factory = JavaPsiFacade.getInstance(myProject).getElementFactory();
375 String paramName = getParameterNameToCreate(classReferencedByThis);
376 try {
377 final PsiExpression refExpression = factory.createExpressionFromText(paramName, null);
378 expression.replace(refExpression);
380 catch (IncorrectOperationException e) {
381 LOG.error(e);
386 @Override public void visitReferenceExpression(PsiReferenceExpression expression) {
387 try {
388 final PsiExpression qualifier = expression.getQualifierExpression();
389 if (qualifier instanceof PsiReferenceExpression && ((PsiReferenceExpression)qualifier).isReferenceTo(myTargetVariable)) {
390 //Target is a field, replace target.m -> m
391 qualifier.delete();
392 return;
394 final PsiElement resolved = expression.resolve();
395 if (myTargetVariable.equals(resolved)) {
396 PsiThisExpression thisExpression = (PsiThisExpression)factory.createExpressionFromText("this", null);
397 expression.replace(thisExpression);
398 return;
400 else if (myMethod.equals(resolved)) {
402 else {
403 PsiClass classReferencedByThis = MoveInstanceMembersUtil.getClassReferencedByThis(expression);
404 if (classReferencedByThis != null) {
405 final String paramName = getParameterNameToCreate(classReferencedByThis);
406 PsiReferenceExpression newQualifier = (PsiReferenceExpression)factory.createExpressionFromText(paramName, null);
407 expression.setQualifierExpression(newQualifier);
408 return;
411 super.visitReferenceExpression(expression);
413 catch (IncorrectOperationException e) {
414 LOG.error(e);
418 @Override public void visitNewExpression(PsiNewExpression expression) {
419 try {
420 final PsiExpression qualifier = expression.getQualifier();
421 if (qualifier instanceof PsiReferenceExpression && ((PsiReferenceExpression)qualifier).isReferenceTo(myTargetVariable)) {
422 //Target is a field, replace target.new A() -> new A()
423 qualifier.delete();
424 } else {
425 final PsiClass classReferencedByThis = MoveInstanceMembersUtil.getClassReferencedByThis(expression);
426 if (classReferencedByThis != null) {
427 if (qualifier != null) qualifier.delete();
428 final String paramName = getParameterNameToCreate(classReferencedByThis);
429 final PsiExpression newExpression = factory.createExpressionFromText(paramName + "." + expression.getText(), null);
430 expression = (PsiNewExpression)expression.replace(newExpression);
433 super.visitNewExpression(expression);
435 catch (IncorrectOperationException e) {
436 LOG.error(e);
440 @Override public void visitMethodCallExpression(PsiMethodCallExpression expression) {
441 super.visitMethodCallExpression(expression);
442 correctMethodCall(expression, true);
447 final PsiMethod methodCopy = getPatternMethod();
449 final List<PsiParameter> newParameters = Arrays.asList(methodCopy.getParameterList().getParameters());
450 RefactoringUtil.fixJavadocsForParams(methodCopy, new HashSet<PsiParameter>(newParameters));
451 return methodCopy;
453 catch (IncorrectOperationException e) {
454 LOG.error(e);
455 return myMethod;
459 private PsiMethod getPatternMethod() throws IncorrectOperationException {
460 final PsiMethod methodCopy = (PsiMethod)myMethod.copy();
461 String name = myTargetClass.isInterface() ? PsiModifier.PUBLIC : myNewVisibility;
462 if (name != null) {
463 PsiUtil.setModifierProperty(methodCopy, name, true);
465 if (myTargetVariable instanceof PsiParameter) {
466 final int index = myMethod.getParameterList().getParameterIndex((PsiParameter)myTargetVariable);
467 methodCopy.getParameterList().getParameters()[index].delete();
470 addParameters(JavaPsiFacade.getInstance(myProject).getElementFactory(), methodCopy, myTargetClass.isInterface());
471 return methodCopy;
474 private void addParameters(final PsiElementFactory factory, final PsiMethod methodCopy, final boolean isInterface) throws IncorrectOperationException {
475 final Set<Map.Entry<PsiClass, String>> entries = myOldClassParameterNames.entrySet();
476 for (final Map.Entry<PsiClass, String> entry : entries) {
477 final PsiClassType type = factory.createType(entry.getKey());
478 final PsiParameter parameter = factory.createParameter(entry.getValue(), type);
479 if (isInterface) {
480 PsiUtil.setModifierProperty(parameter, PsiModifier.FINAL, false);
482 methodCopy.getParameterList().add(parameter);
486 private String getParameterNameToCreate(@NotNull PsiClass aClass) {
487 return myOldClassParameterNames.get(aClass);