blob: a8844614585a17827d269fc6e6da0d7454cf10fa [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.refactoring.inline;
import com.intellij.codeInsight.TargetElementUtilBase;
import com.intellij.lang.StdLanguages;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.patterns.ElementPattern;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.psi.*;
import com.intellij.psi.search.searches.ClassInheritorsSearch;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.refactoring.util.RefactoringUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Processor;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
/**
* @author yole
*/
public class InlineToAnonymousClassHandler extends JavaInlineActionHandler {
static final ElementPattern ourCatchClausePattern = PlatformPatterns.psiElement(PsiTypeElement.class).withParent(PlatformPatterns.psiElement(PsiParameter.class).withParent(
PlatformPatterns.psiElement(PsiCatchSection.class)));
static final ElementPattern ourThrowsClausePattern = PlatformPatterns.psiElement().withParent(PlatformPatterns.psiElement(PsiReferenceList.class).withFirstChild(
PlatformPatterns.psiElement().withText(PsiKeyword.THROWS)));
@Override
public boolean isEnabledOnElement(PsiElement element) {
return element instanceof PsiMethod || element instanceof PsiClass;
}
@Override
public boolean canInlineElement(final PsiElement element) {
if (element.getLanguage() != StdLanguages.JAVA) return false;
if (element instanceof PsiMethod) {
PsiMethod method = (PsiMethod)element;
if (method.isConstructor() && !InlineMethodHandler.isChainingConstructor(method)) {
final PsiClass containingClass = method.getContainingClass();
if (containingClass == null) return false;
return findClassInheritors(containingClass);
}
}
if (!(element instanceof PsiClass)) return false;
if (element instanceof PsiAnonymousClass) return false;
return findClassInheritors((PsiClass)element);
}
private static boolean findClassInheritors(final PsiClass element) {
final Collection<PsiClass> inheritors = new ArrayList<PsiClass>();
if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable(){
@Override
public void run() {
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
inheritors.addAll(ClassInheritorsSearch.search(element).findAll());
}
});
}
}, "Searching for class \"" + element.getQualifiedName() + "\" inheritors ...", true, element.getProject())) return false;
return inheritors.size() == 0;
}
@Override
public boolean canInlineElementInEditor(PsiElement element, Editor editor) {
if (canInlineElement(element)) {
PsiReference reference = editor != null ? TargetElementUtilBase.findReference(editor, editor.getCaretModel().getOffset()) : null;
if (!InlineMethodHandler.isThisReference(reference)) {
if (element instanceof PsiMethod && reference != null) {
final PsiElement referenceElement = reference.getElement();
return referenceElement != null && !PsiTreeUtil.isAncestor(((PsiMethod)element).getContainingClass(), referenceElement, false);
}
return true;
}
}
return false;
}
@Override
public void inlineElement(final Project project, final Editor editor, final PsiElement psiElement) {
final PsiClass psiClass = psiElement instanceof PsiMethod ? ((PsiMethod) psiElement).getContainingClass() : (PsiClass) psiElement;
PsiCall callToInline = findCallToInline(editor);
final PsiClassType superType = InlineToAnonymousClassProcessor.getSuperType(psiClass);
if (superType == null) {
CommonRefactoringUtil.showErrorHint(project, editor, "java.lang.Object is not found", RefactoringBundle.message("inline.to.anonymous.refactoring"), null);
return;
}
final Ref<String> errorMessage = new Ref<String>();
if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable(){
@Override
public void run() {
errorMessage.set(getCannotInlineMessage(psiClass));
}
}, "Check if inline is possible...", true, project)) return;
if (errorMessage.get() != null) {
CommonRefactoringUtil.showErrorHint(project, editor, errorMessage.get(), RefactoringBundle.message("inline.to.anonymous.refactoring"), null);
return;
}
new InlineToAnonymousClassDialog(project, psiClass, callToInline, canBeInvokedOnReference(callToInline, superType)).show();
}
public static boolean canBeInvokedOnReference(PsiCall callToInline, PsiType superType) {
if (callToInline != null) {
final PsiElement parent = callToInline.getParent();
if (parent instanceof PsiExpressionStatement || parent instanceof PsiSynchronizedStatement) {
return true;
}
else if (parent instanceof PsiReferenceExpression) {
return true;
}
else if (parent instanceof PsiExpressionList) {
final PsiMethodCallExpression methodCallExpression = PsiTreeUtil.getParentOfType(parent, PsiMethodCallExpression.class);
if (methodCallExpression != null) {
int paramIdx = ArrayUtil.find(methodCallExpression.getArgumentList().getExpressions(), callToInline);
if (paramIdx != -1) {
final JavaResolveResult resolveResult = methodCallExpression.resolveMethodGenerics();
final PsiElement resolvedMethod = resolveResult.getElement();
if (resolvedMethod instanceof PsiMethod) {
PsiType paramType;
final PsiParameter[] parameters = ((PsiMethod)resolvedMethod).getParameterList().getParameters();
if (paramIdx >= parameters.length) {
final PsiParameter varargParameter = parameters[parameters.length - 1];
paramType = varargParameter.getType();
}
else {
paramType = parameters[paramIdx].getType();
}
if (paramType instanceof PsiEllipsisType) {
paramType = ((PsiEllipsisType)paramType).getComponentType();
}
paramType = resolveResult.getSubstitutor().substitute(paramType);
final PsiJavaCodeReferenceElement classReference = ((PsiNewExpression)callToInline).getClassOrAnonymousClassReference();
if (classReference != null) {
superType = classReference.advancedResolve(false).getSubstitutor().substitute(superType);
if (TypeConversionUtil.isAssignable(paramType, superType)) {
return true;
}
}
}
}
}
}
}
return false;
}
@Nullable
public static PsiCall findCallToInline(final Editor editor) {
PsiCall callToInline = null;
PsiReference reference = editor != null ? TargetElementUtilBase.findReference(editor) : null;
if (reference != null) {
final PsiElement element = reference.getElement();
if (element instanceof PsiJavaCodeReferenceElement) {
callToInline = RefactoringUtil.getEnclosingConstructorCall((PsiJavaCodeReferenceElement)element);
}
}
return callToInline;
}
@Nullable
public static String getCannotInlineMessage(final PsiClass psiClass) {
if (psiClass.isAnnotationType()) {
return "Annotation types cannot be inlined";
}
if (psiClass.isInterface()) {
return "Interfaces cannot be inlined";
}
if (psiClass.isEnum()) {
return "Enums cannot be inlined";
}
if (psiClass.hasModifierProperty(PsiModifier.ABSTRACT)) {
return RefactoringBundle.message("inline.to.anonymous.no.abstract");
}
if (!psiClass.getManager().isInProject(psiClass)) {
return "Library classes cannot be inlined";
}
PsiClassType[] classTypes = psiClass.getExtendsListTypes();
for(PsiClassType classType: classTypes) {
PsiClass superClass = classType.resolve();
if (superClass == null) {
return "Class cannot be inlined because its superclass cannot be resolved";
}
}
final PsiClassType[] interfaces = psiClass.getImplementsListTypes();
if (interfaces.length > 1) {
return RefactoringBundle.message("inline.to.anonymous.no.multiple.interfaces");
}
if (interfaces.length == 1) {
if (interfaces [0].resolve() == null) {
return "Class cannot be inlined because an interface implemented by it cannot be resolved";
}
final PsiClass superClass = psiClass.getSuperClass();
if (superClass != null && !CommonClassNames.JAVA_LANG_OBJECT.equals(superClass.getQualifiedName())) {
PsiClassType interfaceType = interfaces[0];
if (!isRedundantImplements(superClass, interfaceType)) {
return RefactoringBundle.message("inline.to.anonymous.no.superclass.and.interface");
}
}
}
final PsiMethod[] methods = psiClass.getMethods();
for(PsiMethod method: methods) {
if (method.isConstructor()) {
PsiReturnStatement stmt = findReturnStatement(method);
if (stmt != null) {
return "Class cannot be inlined because its constructor contains 'return' statements";
}
}
else if (method.findSuperMethods().length == 0) {
if (!ReferencesSearch.search(method).forEach(new AllowedUsagesProcessor(psiClass))) {
return "Class cannot be inlined because it has usages of methods not inherited from its superclass or interface";
}
}
if (method.hasModifierProperty(PsiModifier.STATIC)) {
return "Class cannot be inlined because it has static methods";
}
}
final PsiClass[] innerClasses = psiClass.getInnerClasses();
for(PsiClass innerClass: innerClasses) {
PsiModifierList classModifiers = innerClass.getModifierList();
if (classModifiers.hasModifierProperty(PsiModifier.STATIC)) {
return "Class cannot be inlined because it has static inner classes";
}
if (!ReferencesSearch.search(innerClass).forEach(new AllowedUsagesProcessor(psiClass))) {
return "Class cannot be inlined because it has usages of its inner classes";
}
}
final PsiField[] fields = psiClass.getFields();
for(PsiField field: fields) {
final PsiModifierList fieldModifiers = field.getModifierList();
if (fieldModifiers != null && fieldModifiers.hasModifierProperty(PsiModifier.STATIC)) {
if (!fieldModifiers.hasModifierProperty(PsiModifier.FINAL)) {
return "Class cannot be inlined because it has static non-final fields";
}
Object initValue = null;
final PsiExpression initializer = field.getInitializer();
if (initializer != null) {
initValue = JavaPsiFacade.getInstance(psiClass.getProject()).getConstantEvaluationHelper().computeConstantExpression(initializer);
}
if (initValue == null) {
return "Class cannot be inlined because it has static fields with non-constant initializers";
}
}
if (!ReferencesSearch.search(field).forEach(new AllowedUsagesProcessor(psiClass))) {
return "Class cannot be inlined because it has usages of fields not inherited from its superclass";
}
}
final PsiClassInitializer[] initializers = psiClass.getInitializers();
for(PsiClassInitializer initializer: initializers) {
final PsiModifierList modifiers = initializer.getModifierList();
if (modifiers != null && modifiers.hasModifierProperty(PsiModifier.STATIC)) {
return "Class cannot be inlined because it has static initializers";
}
}
return getCannotInlineDueToUsagesMessage(psiClass);
}
static boolean isRedundantImplements(final PsiClass superClass, final PsiClassType interfaceType) {
boolean redundantImplements = false;
PsiClassType[] superClassInterfaces = superClass.getImplementsListTypes();
for(PsiClassType superClassInterface: superClassInterfaces) {
if (superClassInterface.equals(interfaceType)) {
redundantImplements = true;
break;
}
}
return redundantImplements;
}
private static PsiReturnStatement findReturnStatement(final PsiMethod method) {
final Ref<PsiReturnStatement> stmt = Ref.create(null);
method.accept(new JavaRecursiveElementWalkingVisitor() {
@Override public void visitReturnStatement(final PsiReturnStatement statement) {
super.visitReturnStatement(statement);
stmt.set(statement);
}
});
return stmt.get();
}
@Nullable
private static String getCannotInlineDueToUsagesMessage(final PsiClass aClass) {
boolean hasUsages = false;
for(PsiReference reference : ReferencesSearch.search(aClass)) {
final PsiElement element = reference.getElement();
if (element == null) continue;
if (!PsiTreeUtil.isAncestor(aClass, element, false)) {
hasUsages = true;
}
final PsiElement parentElement = element.getParent();
if (parentElement != null) {
final PsiElement grandPa = parentElement.getParent();
if (grandPa instanceof PsiClassObjectAccessExpression) {
return "Class cannot be inlined because it has usages of its class literal";
}
if (ourCatchClausePattern.accepts(parentElement)) {
return "Class cannot be inlined because it is used in a 'catch' clause";
}
}
if (ourThrowsClausePattern.accepts(element)) {
return "Class cannot be inlined because it is used in a 'throws' clause";
}
if (parentElement instanceof PsiThisExpression) {
return "Class cannot be inlined because it is used as a 'this' qualifier";
}
if (parentElement instanceof PsiNewExpression) {
final PsiNewExpression newExpression = (PsiNewExpression)parentElement;
final PsiMethod[] constructors = aClass.getConstructors();
if (constructors.length == 0) {
PsiExpressionList newArgumentList = newExpression.getArgumentList();
if (newArgumentList != null && newArgumentList.getExpressions().length > 0) {
return "Class cannot be inlined because a call to its constructor is unresolved";
}
}
else {
final JavaResolveResult resolveResult = newExpression.resolveMethodGenerics();
if (!resolveResult.isValidResult()) {
return "Class cannot be inlined because a call to its constructor is unresolved";
}
}
}
}
if (!hasUsages) {
return RefactoringBundle.message("class.is.never.used");
}
return null;
}
private static class AllowedUsagesProcessor implements Processor<PsiReference> {
private final PsiElement myPsiElement;
public AllowedUsagesProcessor(final PsiElement psiElement) {
myPsiElement = psiElement;
}
@Override
public boolean process(final PsiReference psiReference) {
if (PsiTreeUtil.isAncestor(myPsiElement, psiReference.getElement(), false)) {
return true;
}
PsiElement element = psiReference.getElement();
if (element instanceof PsiReferenceExpression) {
PsiExpression qualifier = ((PsiReferenceExpression)element).getQualifierExpression();
while (qualifier instanceof PsiParenthesizedExpression) {
qualifier = ((PsiParenthesizedExpression) qualifier).getExpression();
}
if (qualifier instanceof PsiNewExpression) {
PsiNewExpression newExpr = (PsiNewExpression) qualifier;
PsiJavaCodeReferenceElement classRef = newExpr.getClassReference();
if (classRef != null && myPsiElement.equals(classRef.resolve())) {
return true;
}
}
}
return false;
}
}
}