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.inline;
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;
33 import java.util.Collection;
38 public class InlineToAnonymousClassHandler extends JavaInlineActionHandler {
40 public boolean isEnabledOnElement(PsiElement element) {
41 return element instanceof PsiMethod || element instanceof PsiClass;
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)) {
52 if (!(element instanceof PsiClass)) return false;
53 Collection<PsiClass> inheritors = ClassInheritorsSearch.search((PsiClass)element).findAll();
54 return inheritors.size() == 0;
57 public boolean canInlineElementInEditor(PsiElement element) {
58 return canInlineElement(element);
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);
65 String errorMessage = getCannotInlineMessage(psiClass);
66 if (errorMessage != null) {
67 CommonRefactoringUtil.showErrorHint(project, editor, errorMessage, RefactoringBundle.message("inline.to.anonymous.refactoring"), null);
71 InlineToAnonymousClassDialog dlg = new InlineToAnonymousClassDialog(project, psiClass, callToInline);
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);
89 public static String getCannotInlineMessage(final PsiClass psiClass) {
90 if (psiClass.isAnnotationType()) {
91 return "Annotation types cannot be inlined";
93 if (psiClass.isInterface()) {
94 return "Interfaces cannot be inlined";
96 if (psiClass.isEnum()) {
97 return "Enums cannot be inlined";
99 if (psiClass.hasModifierProperty(PsiModifier.ABSTRACT)) {
100 return RefactoringBundle.message("inline.to.anonymous.no.abstract");
102 if (!psiClass.getManager().isInProject(psiClass)) {
103 return "Library classes cannot be inlined";
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";
114 final PsiClassType[] interfaces = psiClass.getImplementsListTypes();
115 if (interfaces.length > 1) {
116 return RefactoringBundle.message("inline.to.anonymous.no.multiple.interfaces");
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";
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");
131 final PsiMethod[] methods = psiClass.getMethods();
132 for(PsiMethod method: methods) {
133 if (method.isConstructor()) {
134 PsiReturnStatement stmt = findReturnStatement(method);
136 return "Class cannot be inlined because its constructor contains 'return' statements";
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";
144 if (method.hasModifierProperty(PsiModifier.STATIC)) {
145 return "Class cannot be inlined because it has static methods";
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";
155 if (!ReferencesSearch.search(innerClass).forEach(new AllowedUsagesProcessor(psiClass))) {
156 return "Class cannot be inlined because it has usages of its inner classes";
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";
167 Object initValue = null;
168 final PsiExpression initializer = field.getInitializer();
169 if (initializer != null) {
170 initValue = JavaPsiFacade.getInstance(psiClass.getProject()).getConstantEvaluationHelper().computeConstantExpression(initializer);
172 if (initValue == null) {
173 return "Class cannot be inlined because it has static fields with non-constant initializers";
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";
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";
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;
201 return redundantImplements;
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);
215 private static class AllowedUsagesProcessor implements Processor<PsiReference> {
216 private final PsiElement myPsiElement;
218 public AllowedUsagesProcessor(final PsiElement psiElement) {
219 myPsiElement = psiElement;
222 public boolean process(final PsiReference psiReference) {
223 if (PsiTreeUtil.isAncestor(myPsiElement, psiReference.getElement(), false)) {
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();
232 if (qualifier instanceof PsiNewExpression) {
233 PsiNewExpression newExpr = (PsiNewExpression) qualifier;
234 PsiJavaCodeReferenceElement classRef = newExpr.getClassReference();
235 if (classRef != null && myPsiElement.equals(classRef.resolve())) {