Merge branch 'master' of git.labs.intellij.net:idea/community
[idea/community.git] / java / java-impl / src / com / intellij / refactoring / inline / InlineToAnonymousClassHandler.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.refactoring.inline;
17
18 import com.intellij.codeInsight.TargetElementUtilBase;
19 import com.intellij.openapi.editor.Editor;
20 import com.intellij.openapi.project.Project;
21 import com.intellij.openapi.util.Ref;
22 import com.intellij.psi.*;
23 import com.intellij.psi.search.searches.ClassInheritorsSearch;
24 import com.intellij.psi.search.searches.ReferencesSearch;
25 import com.intellij.psi.util.PsiTreeUtil;
26 import com.intellij.refactoring.RefactoringBundle;
27 import com.intellij.refactoring.util.CommonRefactoringUtil;
28 import com.intellij.refactoring.util.RefactoringUtil;
29 import com.intellij.util.Processor;
30 import org.jetbrains.annotations.Nullable;
31
32 import java.util.Collection;
33
34 /**
35  * @author yole
36  */
37 public class InlineToAnonymousClassHandler extends JavaInlineActionHandler {
38   @Override
39   public boolean isEnabledOnElement(PsiElement element) {
40     return element instanceof PsiMethod || element instanceof PsiClass;
41   }
42
43   public boolean canInlineElement(PsiElement element) {
44     if (element instanceof PsiMethod) {
45       PsiMethod method = (PsiMethod)element;
46       if (method.isConstructor() && !InlineMethodHandler.isChainingConstructor(method)) {
47         return true;
48       }
49     }
50     if (!(element instanceof PsiClass)) return false;
51     Collection<PsiClass> inheritors = ClassInheritorsSearch.search((PsiClass)element).findAll();
52     return inheritors.size() == 0;
53   }
54
55   public boolean canInlineElementInEditor(PsiElement element) {
56     return canInlineElement(element);
57   }
58
59   public void inlineElement(final Project project, final Editor editor, final PsiElement psiElement) {
60     final PsiClass psiClass = psiElement instanceof PsiMethod ? ((PsiMethod) psiElement).getContainingClass() : (PsiClass) psiElement;
61     PsiCall callToInline = findCallToInline(editor);
62
63     String errorMessage = getCannotInlineMessage(psiClass);
64     if (errorMessage != null) {
65       CommonRefactoringUtil.showErrorHint(project, editor, errorMessage, RefactoringBundle.message("inline.to.anonymous.refactoring"), null);
66       return;
67     }
68
69     InlineToAnonymousClassDialog dlg = new InlineToAnonymousClassDialog(project, psiClass, callToInline);
70     dlg.show();
71   }
72
73   @Nullable
74   public static PsiCall findCallToInline(final Editor editor) {
75     PsiCall callToInline = null;
76     PsiReference reference = editor != null ? TargetElementUtilBase.findReference(editor) : null;
77     if (reference != null) {
78       final PsiElement element = reference.getElement();
79       if (element instanceof PsiJavaCodeReferenceElement) {
80         callToInline = RefactoringUtil.getEnclosingConstructorCall((PsiJavaCodeReferenceElement)element);
81       }
82     }
83     return callToInline;
84   }
85
86   @Nullable
87   public static String getCannotInlineMessage(final PsiClass psiClass) {
88     if (psiClass.isAnnotationType()) {
89       return "Annotation types cannot be inlined";
90     }
91     if (psiClass.isInterface()) {
92       return "Interfaces cannot be inlined";
93     }
94     if (psiClass.isEnum()) {
95       return "Enums cannot be inlined";
96     }
97     if (psiClass.hasModifierProperty(PsiModifier.ABSTRACT)) {
98       return RefactoringBundle.message("inline.to.anonymous.no.abstract");
99     }
100     if (!psiClass.getManager().isInProject(psiClass)) {
101       return "Library classes cannot be inlined";
102     }
103
104     PsiClassType[] classTypes = psiClass.getExtendsListTypes();
105     for(PsiClassType classType: classTypes) {
106       PsiClass superClass = classType.resolve();
107       if (superClass == null) {
108         return "Class cannot be inlined because its superclass cannot be resolved";
109       }
110     }
111
112     final PsiClassType[] interfaces = psiClass.getImplementsListTypes();
113     if (interfaces.length > 1) {
114       return RefactoringBundle.message("inline.to.anonymous.no.multiple.interfaces");
115     }
116     if (interfaces.length == 1) {
117       if (interfaces [0].resolve() == null) {
118         return "Class cannot be inlined because an interface implemented by it cannot be resolved";
119       }
120       final PsiClass superClass = psiClass.getSuperClass();
121       if (superClass != null && !CommonClassNames.JAVA_LANG_OBJECT.equals(superClass.getQualifiedName())) {
122         PsiClassType interfaceType = interfaces[0];
123         if (!isRedundantImplements(superClass, interfaceType)) {
124           return RefactoringBundle.message("inline.to.anonymous.no.superclass.and.interface");
125         }
126       }
127     }
128
129     final PsiMethod[] methods = psiClass.getMethods();
130     for(PsiMethod method: methods) {
131       if (method.isConstructor()) {
132         PsiReturnStatement stmt = findReturnStatement(method);
133         if (stmt != null) {
134           return "Class cannot be inlined because its constructor contains 'return' statements";
135         }
136       }
137       else if (method.findSuperMethods().length == 0) {
138         if (!ReferencesSearch.search(method).forEach(new AllowedUsagesProcessor(psiClass))) {
139           return "Class cannot be inlined because it has usages of methods not inherited from its superclass or interface";
140         }
141       }
142       if (method.hasModifierProperty(PsiModifier.STATIC)) {
143         return "Class cannot be inlined because it has static methods";
144       }
145     }
146
147     final PsiClass[] innerClasses = psiClass.getInnerClasses();
148     for(PsiClass innerClass: innerClasses) {
149       PsiModifierList classModifiers = innerClass.getModifierList();
150       if (classModifiers.hasModifierProperty(PsiModifier.STATIC)) {
151         return "Class cannot be inlined because it has static inner classes";
152       }
153       if (!ReferencesSearch.search(innerClass).forEach(new AllowedUsagesProcessor(psiClass))) {
154         return "Class cannot be inlined because it has usages of its inner classes";
155       }
156     }
157
158     final PsiField[] fields = psiClass.getFields();
159     for(PsiField field: fields) {
160       final PsiModifierList fieldModifiers = field.getModifierList();
161       if (fieldModifiers != null && fieldModifiers.hasModifierProperty(PsiModifier.STATIC)) {
162         if (!fieldModifiers.hasModifierProperty(PsiModifier.FINAL)) {
163           return "Class cannot be inlined because it has static non-final fields";
164         }
165         Object initValue = null;
166         final PsiExpression initializer = field.getInitializer();
167         if (initializer != null) {
168           initValue = JavaPsiFacade.getInstance(psiClass.getProject()).getConstantEvaluationHelper().computeConstantExpression(initializer);
169         }
170         if (initValue == null) {
171           return "Class cannot be inlined because it has static fields with non-constant initializers";
172         }
173       }
174       if (!ReferencesSearch.search(field).forEach(new AllowedUsagesProcessor(psiClass))) {
175         return "Class cannot be inlined because it has usages of fields not inherited from its superclass";
176       }
177     }
178
179     final PsiClassInitializer[] initializers = psiClass.getInitializers();
180     for(PsiClassInitializer initializer: initializers) {
181       final PsiModifierList modifiers = initializer.getModifierList();
182       if (modifiers != null && modifiers.hasModifierProperty(PsiModifier.STATIC)) {
183         return "Class cannot be inlined because it has static initializers";
184       }
185     }
186
187     return null;
188   }
189
190   static boolean isRedundantImplements(final PsiClass superClass, final PsiClassType interfaceType) {
191     boolean redundantImplements = false;
192     PsiClassType[] superClassInterfaces = superClass.getImplementsListTypes();
193     for(PsiClassType superClassInterface: superClassInterfaces) {
194       if (superClassInterface.equals(interfaceType)) {
195         redundantImplements = true;
196         break;
197       }
198     }
199     return redundantImplements;
200   }
201
202   private static PsiReturnStatement findReturnStatement(final PsiMethod method) {
203     final Ref<PsiReturnStatement> stmt = Ref.create(null);
204     method.accept(new JavaRecursiveElementWalkingVisitor() {
205       @Override public void visitReturnStatement(final PsiReturnStatement statement) {
206         super.visitReturnStatement(statement);
207         stmt.set(statement);
208       }
209     });
210     return stmt.get();
211   }
212
213   private static class AllowedUsagesProcessor implements Processor<PsiReference> {
214     private final PsiElement myPsiElement;
215
216     public AllowedUsagesProcessor(final PsiElement psiElement) {
217       myPsiElement = psiElement;
218     }
219
220     public boolean process(final PsiReference psiReference) {
221       if (PsiTreeUtil.isAncestor(myPsiElement, psiReference.getElement(), false)) {
222         return true;
223       }
224       PsiElement element = psiReference.getElement();
225       if (element instanceof PsiReferenceExpression) {
226         PsiExpression qualifier = ((PsiReferenceExpression)element).getQualifierExpression();
227         while (qualifier instanceof PsiParenthesizedExpression) {
228           qualifier = ((PsiParenthesizedExpression) qualifier).getExpression();
229         }
230         if (qualifier instanceof PsiNewExpression) {
231           PsiNewExpression newExpr = (PsiNewExpression) qualifier;
232           PsiJavaCodeReferenceElement classRef = newExpr.getClassReference();
233           if (classRef != null && myPsiElement.equals(classRef.resolve())) {
234             return true;
235           }
236         }
237       }
238       return false;
239     }
240   }
241 }