3 * Copyright 2000-2009 JetBrains s.r.o.
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 package com
.intellij
.refactoring
.inline
;
19 import com
.intellij
.codeInsight
.TargetElementUtilBase
;
20 import com
.intellij
.openapi
.editor
.Editor
;
21 import com
.intellij
.openapi
.progress
.ProgressManager
;
22 import com
.intellij
.openapi
.project
.Project
;
23 import com
.intellij
.openapi
.vfs
.ReadonlyStatusHandler
;
24 import com
.intellij
.openapi
.vfs
.VirtualFile
;
25 import com
.intellij
.psi
.*;
26 import com
.intellij
.psi
.search
.searches
.ReferencesSearch
;
27 import com
.intellij
.psi
.util
.PsiTreeUtil
;
28 import com
.intellij
.refactoring
.HelpID
;
29 import com
.intellij
.refactoring
.RefactoringBundle
;
30 import com
.intellij
.refactoring
.util
.CommonRefactoringUtil
;
31 import com
.intellij
.refactoring
.util
.RefactoringUtil
;
32 import com
.intellij
.util
.Processor
;
33 import com
.intellij
.lang
.StdLanguages
;
35 import java
.util
.ArrayList
;
36 import java
.util
.List
;
38 class InlineMethodHandler
extends JavaInlineActionHandler
{
39 private static final String REFACTORING_NAME
= RefactoringBundle
.message("inline.method.title");
41 private InlineMethodHandler() {
44 public boolean canInlineElement(PsiElement element
) {
45 return element
instanceof PsiMethod
&& element
.getNavigationElement() instanceof PsiMethod
&& element
.getLanguage() == StdLanguages
.JAVA
;
48 public void inlineElement(final Project project
, Editor editor
, PsiElement element
) {
49 PsiMethod method
= (PsiMethod
)element
.getNavigationElement();
50 if (method
.getBody() == null){
52 if (method
.hasModifierProperty(PsiModifier
.ABSTRACT
)) {
53 message
= RefactoringBundle
.message("refactoring.cannot.be.applied.to.abstract.methods", REFACTORING_NAME
);
56 message
= RefactoringBundle
.message("refactoring.cannot.be.applied.no.sources.attached", REFACTORING_NAME
);
58 CommonRefactoringUtil
.showErrorHint(project
, editor
, message
, REFACTORING_NAME
, HelpID
.INLINE_METHOD
);
62 PsiReference reference
= editor
!= null ? TargetElementUtilBase
.findReference(editor
, editor
.getCaretModel().getOffset()) : null;
63 boolean allowInlineThisOnly
= false;
64 if (InlineMethodProcessor
.checkBadReturns(method
) && !allUsagesAreTailCalls(method
)) {
65 if (reference
!= null && getTailCallType(reference
) != TailCallType
.None
) {
66 allowInlineThisOnly
= true;
69 String message
= RefactoringBundle
.message("refactoring.is.not.supported.when.return.statement.interrupts.the.execution.flow", REFACTORING_NAME
);
70 CommonRefactoringUtil
.showErrorHint(project
, editor
, message
, REFACTORING_NAME
, HelpID
.INLINE_METHOD
);
75 if (reference
== null && checkRecursive(method
)) {
76 String message
= RefactoringBundle
.message("refactoring.is.not.supported.for.recursive.methods", REFACTORING_NAME
);
77 CommonRefactoringUtil
.showErrorHint(project
, editor
, message
, REFACTORING_NAME
, HelpID
.INLINE_METHOD
);
81 if (method
.isConstructor()) {
82 if (method
.isVarArgs()) {
83 String message
= RefactoringBundle
.message("refactoring.cannot.be.applied.to.vararg.constructors", REFACTORING_NAME
);
84 CommonRefactoringUtil
.showErrorHint(project
, editor
, message
, REFACTORING_NAME
, HelpID
.INLINE_CONSTRUCTOR
);
87 if (!isChainingConstructor(method
)) {
88 String message
= RefactoringBundle
.message("refactoring.cannot.be.applied.to.inline.non.chaining.constructors", REFACTORING_NAME
);
89 CommonRefactoringUtil
.showErrorHint(project
, editor
, message
, REFACTORING_NAME
, HelpID
.INLINE_CONSTRUCTOR
);
92 if (reference
!= null) {
93 final PsiElement refElement
= reference
.getElement();
94 PsiCall constructorCall
= refElement
instanceof PsiJavaCodeReferenceElement ? RefactoringUtil
.getEnclosingConstructorCall((PsiJavaCodeReferenceElement
)refElement
) : null;
95 if (constructorCall
== null || !method
.equals(constructorCall
.resolveMethod())) reference
= null;
99 if (reference
!= null && !method
.getManager().areElementsEquivalent(method
, reference
.resolve())) {
104 final boolean invokedOnReference
= reference
!= null;
105 if (!invokedOnReference
) {
106 final VirtualFile vFile
= method
.getContainingFile().getVirtualFile();
107 ReadonlyStatusHandler
.getInstance(project
).ensureFilesWritable(vFile
);
109 PsiJavaCodeReferenceElement refElement
= reference
!= null ?
(PsiJavaCodeReferenceElement
)reference
.getElement() : null;
110 InlineMethodDialog dialog
= new InlineMethodDialog(project
, method
, refElement
, editor
, allowInlineThisOnly
);
114 public static boolean allUsagesAreTailCalls(final PsiMethod method
) {
115 final List
<PsiReference
> nonTailCallUsages
= new ArrayList
<PsiReference
>();
116 boolean result
= ProgressManager
.getInstance().runProcessWithProgressSynchronously(new Runnable() {
118 ReferencesSearch
.search(method
).forEach(new Processor
<PsiReference
>() {
119 public boolean process(final PsiReference psiReference
) {
120 ProgressManager
.getInstance().checkCanceled();
121 if (getTailCallType(psiReference
) == TailCallType
.None
) {
122 nonTailCallUsages
.add(psiReference
);
129 }, RefactoringBundle
.message("inline.method.checking.tail.calls.progress"), true, method
.getProject());
130 return result
&& nonTailCallUsages
.isEmpty();
133 public enum TailCallType
{
137 public static TailCallType
getTailCallType(final PsiReference psiReference
) {
138 PsiElement element
= psiReference
.getElement();
139 PsiExpression methodCall
= PsiTreeUtil
.getParentOfType(element
, PsiMethodCallExpression
.class);
140 if (methodCall
== null) return TailCallType
.None
;
141 if (methodCall
.getParent() instanceof PsiReturnStatement
) return TailCallType
.Return
;
142 if (methodCall
.getParent() instanceof PsiExpressionStatement
) {
143 PsiStatement callStatement
= (PsiStatement
) methodCall
.getParent();
144 PsiMethod callerMethod
= PsiTreeUtil
.getParentOfType(callStatement
, PsiMethod
.class);
145 if (callerMethod
!= null) {
146 final PsiStatement
[] psiStatements
= callerMethod
.getBody().getStatements();
147 return psiStatements
.length
> 0 && callStatement
== psiStatements
[psiStatements
.length
-1] ? TailCallType
.Simple
: TailCallType
.None
;
150 return TailCallType
.None
;
153 public static boolean isChainingConstructor(PsiMethod constructor
) {
154 PsiCodeBlock body
= constructor
.getBody();
156 PsiStatement
[] statements
= body
.getStatements();
157 if (statements
.length
== 1 && statements
[0] instanceof PsiExpressionStatement
) {
158 PsiExpression expression
= ((PsiExpressionStatement
)statements
[0]).getExpression();
159 if (expression
instanceof PsiMethodCallExpression
) {
160 PsiReferenceExpression methodExpr
= ((PsiMethodCallExpression
)expression
).getMethodExpression();
161 if ("this".equals(methodExpr
.getReferenceName())) {
162 PsiElement resolved
= methodExpr
.resolve();
163 return resolved
instanceof PsiMethod
&& ((PsiMethod
)resolved
).isConstructor(); //delegated via "this" call
171 public static boolean checkRecursive(PsiMethod method
) {
172 return checkCalls(method
.getBody(), method
);
175 private static boolean checkCalls(PsiElement scope
, PsiMethod method
) {
176 if (scope
instanceof PsiMethodCallExpression
){
177 PsiMethod refMethod
= (PsiMethod
)((PsiMethodCallExpression
)scope
).getMethodExpression().resolve();
178 if (method
.equals(refMethod
)) return true;
181 for(PsiElement child
= scope
.getFirstChild(); child
!= null; child
= child
.getNextSibling()){
182 if (checkCalls(child
, method
)) return true;