blob: b84e772f145be9198837ea3c62acff128b6d30b1 [file] [log] [blame]
/*
* Copyright 2000-2009 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.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.ReadonlyStatusHandler;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.refactoring.HelpID;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.refactoring.util.InlineUtil;
import com.intellij.refactoring.util.RefactoringUtil;
class InlineMethodHandler extends JavaInlineActionHandler {
private static final String REFACTORING_NAME = RefactoringBundle.message("inline.method.title");
private InlineMethodHandler() {
}
public boolean canInlineElement(PsiElement element) {
return element instanceof PsiMethod && element.getNavigationElement() instanceof PsiMethod && element.getLanguage() == StdLanguages.JAVA;
}
public void inlineElement(final Project project, Editor editor, PsiElement element) {
PsiMethod method = (PsiMethod)element.getNavigationElement();
final PsiCodeBlock methodBody = method.getBody();
if (methodBody == null){
String message;
if (method.hasModifierProperty(PsiModifier.ABSTRACT)) {
message = RefactoringBundle.message("refactoring.cannot.be.applied.to.abstract.methods", REFACTORING_NAME);
}
else {
message = RefactoringBundle.message("refactoring.cannot.be.applied.no.sources.attached", REFACTORING_NAME);
}
CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.INLINE_METHOD);
return;
}
PsiReference reference = editor != null ? TargetElementUtilBase.findReference(editor, editor.getCaretModel().getOffset()) : null;
if (reference != null) {
final PsiElement refElement = reference.getElement();
if (refElement != null && !isEnabledForLanguage(refElement.getLanguage())) {
String message = RefactoringBundle
.message("refactoring.is.not.supported.for.language", "Inline of Java method", refElement.getLanguage().getDisplayName());
CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.INLINE_METHOD);
return;
}
}
boolean allowInlineThisOnly = false;
if (InlineMethodProcessor.checkBadReturns(method) && !InlineUtil.allUsagesAreTailCalls(method)) {
if (reference != null && InlineUtil.getTailCallType(reference) != InlineUtil.TailCallType.None) {
allowInlineThisOnly = true;
}
else {
String message = RefactoringBundle.message("refactoring.is.not.supported.when.return.statement.interrupts.the.execution.flow", REFACTORING_NAME);
CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.INLINE_METHOD);
return;
}
}
if (reference == null && checkRecursive(method)) {
String message = RefactoringBundle.message("refactoring.is.not.supported.for.recursive.methods", REFACTORING_NAME);
CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.INLINE_METHOD);
return;
}
if (reference instanceof PsiMethodReferenceExpression) {
CommonRefactoringUtil.showErrorHint(project, editor, REFACTORING_NAME + " cannot be applied to method references", REFACTORING_NAME, HelpID.INLINE_METHOD);
return;
}
if (reference != null) {
final String errorMessage = InlineMethodProcessor.checkCalledInSuperOrThisExpr(methodBody, reference.getElement());
if (errorMessage != null) {
CommonRefactoringUtil.showErrorHint(project, editor, errorMessage, REFACTORING_NAME, HelpID.INLINE_METHOD);
return;
}
}
if (method.isConstructor()) {
if (method.isVarArgs()) {
String message = RefactoringBundle.message("refactoring.cannot.be.applied.to.vararg.constructors", REFACTORING_NAME);
CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.INLINE_CONSTRUCTOR);
return;
}
final boolean chainingConstructor = isChainingConstructor(method);
if (!chainingConstructor) {
if (!isThisReference(reference)) {
String message = RefactoringBundle.message("refactoring.cannot.be.applied.to.inline.non.chaining.constructors", REFACTORING_NAME);
CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.INLINE_CONSTRUCTOR);
return;
}
allowInlineThisOnly = true;
}
if (reference != null) {
final PsiElement refElement = reference.getElement();
PsiCall constructorCall = refElement instanceof PsiJavaCodeReferenceElement ? RefactoringUtil.getEnclosingConstructorCall((PsiJavaCodeReferenceElement)refElement) : null;
if (constructorCall == null || !method.equals(constructorCall.resolveMethod())) reference = null;
}
}
else {
if (reference != null && !method.getManager().areElementsEquivalent(method, reference.resolve())) {
reference = null;
}
}
final boolean invokedOnReference = reference != null;
if (!invokedOnReference) {
final VirtualFile vFile = method.getContainingFile().getVirtualFile();
ReadonlyStatusHandler.getInstance(project).ensureFilesWritable(vFile);
}
PsiJavaCodeReferenceElement refElement = null;
if (reference != null) {
final PsiElement referenceElement = reference.getElement();
if (referenceElement instanceof PsiJavaCodeReferenceElement) {
refElement = (PsiJavaCodeReferenceElement)referenceElement;
}
}
InlineMethodDialog dialog = new InlineMethodDialog(project, method, refElement, editor, allowInlineThisOnly);
dialog.show();
}
public static boolean isChainingConstructor(PsiMethod constructor) {
PsiCodeBlock body = constructor.getBody();
if (body != null) {
PsiStatement[] statements = body.getStatements();
if (statements.length == 1 && statements[0] instanceof PsiExpressionStatement) {
PsiExpression expression = ((PsiExpressionStatement)statements[0]).getExpression();
if (expression instanceof PsiMethodCallExpression) {
PsiReferenceExpression methodExpr = ((PsiMethodCallExpression)expression).getMethodExpression();
if ("this".equals(methodExpr.getReferenceName())) {
PsiElement resolved = methodExpr.resolve();
return resolved instanceof PsiMethod && ((PsiMethod)resolved).isConstructor(); //delegated via "this" call
}
}
}
}
return false;
}
public static boolean checkRecursive(PsiMethod method) {
return checkCalls(method.getBody(), method);
}
private static boolean checkCalls(PsiElement scope, PsiMethod method) {
if (scope instanceof PsiMethodCallExpression){
PsiMethod refMethod = (PsiMethod)((PsiMethodCallExpression)scope).getMethodExpression().resolve();
if (method.equals(refMethod)) return true;
}
for(PsiElement child = scope.getFirstChild(); child != null; child = child.getNextSibling()){
if (checkCalls(child, method)) return true;
}
return false;
}
public static boolean isThisReference(PsiReference reference) {
if (reference != null) {
final PsiElement referenceElement = reference.getElement();
if (referenceElement instanceof PsiJavaCodeReferenceElement &&
referenceElement.getParent() instanceof PsiMethodCallExpression &&
"this".equals(((PsiJavaCodeReferenceElement)referenceElement).getReferenceName())) {
return true;
}
}
return false;
}
}