| |
| /* |
| * 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; |
| } |
| } |