/*
* 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 de.plushnikov.intellij.plugin.action.inline;
import com.intellij.codeInsight.TargetElementUtil;
import com.intellij.lang.java.JavaLanguage;
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.PsiCall;
import com.intellij.psi.PsiCodeBlock;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiExpression;
import com.intellij.psi.PsiExpressionStatement;
import com.intellij.psi.PsiImportStaticStatement;
import com.intellij.psi.PsiJavaCodeReferenceElement;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiMethodCallExpression;
import com.intellij.psi.PsiModifier;
import com.intellij.psi.PsiReference;
import com.intellij.psi.PsiReferenceExpression;
import com.intellij.psi.PsiStatement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.HelpID;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.inline.InlineMethodDialog;
import com.intellij.refactoring.inline.InlineMethodProcessor;
import com.intellij.refactoring.inline.JavaInlineActionHandler;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.refactoring.util.InlineUtil;
import com.intellij.refactoring.util.RefactoringUtil;
import de.plushnikov.intellij.plugin.psi.LombokLightMethodBuilder;
/**
* Customized copy of com.intellij.refactoring.inline.InlineMethodHandler class to support lombok generated methods
*/
public class LombokInlineMethodHandler extends JavaInlineActionHandler {
private static final String REFACTORING_NAME = RefactoringBundle.message("inline.method.title");
private LombokInlineMethodHandler() {
}
public boolean canInlineElement(PsiElement element) {
return element instanceof LombokLightMethodBuilder && element.getLanguage() == JavaLanguage.INSTANCE;
}
public void inlineElement(final Project project, Editor editor, PsiElement element) {
final PsiMethod method = (PsiMethod) element;
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 ? TargetElementUtil.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;
}
}
if (InlineMethodProcessor.checkBadReturns(method) && !InlineUtil.allUsagesAreTailCalls(method)) {
if (reference == null || InlineUtil.getTailCallType(reference) == InlineUtil.TailCallType.None) {
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 != 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;
}
}
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;
}
}
if (reference != null && PsiTreeUtil.getParentOfType(reference.getElement(), PsiImportStaticStatement.class) != null) {
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;
}
}
//for lombok methods we are allowing only 'this inlines'
InlineMethodDialog dialog = new InlineMethodDialog(project, method, refElement, editor, true);
dialog.show();
}
private 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;
}
private 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;
}
private 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;
}
}