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