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