2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com
.intellij
.refactoring
.extractMethod
;
18 import com
.intellij
.codeInsight
.PsiEquivalenceUtil
;
19 import com
.intellij
.openapi
.diagnostic
.Logger
;
20 import com
.intellij
.openapi
.project
.Project
;
21 import com
.intellij
.openapi
.util
.TextRange
;
22 import com
.intellij
.psi
.*;
23 import com
.intellij
.psi
.controlFlow
.*;
24 import com
.intellij
.psi
.search
.LocalSearchScope
;
25 import com
.intellij
.psi
.search
.searches
.ReferencesSearch
;
26 import com
.intellij
.psi
.util
.PsiTreeUtil
;
27 import com
.intellij
.psi
.util
.PsiUtil
;
28 import com
.intellij
.refactoring
.RefactoringBundle
;
29 import com
.intellij
.util
.containers
.HashSet
;
30 import com
.intellij
.util
.containers
.IntArrayList
;
34 public class ControlFlowWrapper
{
35 private static final Logger LOG
= Logger
.getInstance("#" + ControlFlowWrapper
.class.getName());
37 private ControlFlow myControlFlow
;
38 private int myFlowStart
;
40 private int myFlowEnd
;
41 private boolean myGenerateConditionalExit
;
42 private Collection
<PsiStatement
> myExitStatements
;
43 private PsiStatement myFirstExitStatementCopy
;
44 private IntArrayList myExitPoints
;
46 public ControlFlowWrapper(Project project
, PsiElement codeFragment
, PsiElement
[] elements
) throws PrepareFailedException
{
49 ControlFlowFactory
.getInstance(project
).getControlFlow(codeFragment
, new LocalsControlFlowPolicy(codeFragment
), false, true);
51 catch (AnalysisCanceledException e
) {
52 throw new PrepareFailedException(RefactoringBundle
.message("extract.method.control.flow.analysis.failed"), e
.getErrorElement());
55 if (LOG
.isDebugEnabled()) {
56 LOG
.debug(myControlFlow
.toString());
61 while (index
< elements
.length
) {
62 myFlowStart
= myControlFlow
.getStartOffset(elements
[index
]);
63 if (myFlowStart
>= 0) break;
66 if (myFlowStart
< 0) {
72 index
= elements
.length
- 1;
74 myFlowEnd
= myControlFlow
.getEndOffset(elements
[index
]);
75 if (myFlowEnd
>= 0) break;
79 if (LOG
.isDebugEnabled()) {
80 LOG
.debug("start offset:" + myFlowStart
);
81 LOG
.debug("end offset:" + myFlowEnd
);
85 public PsiStatement
getFirstExitStatementCopy() {
86 return myFirstExitStatementCopy
;
89 public Collection
<PsiStatement
> prepareExitStatements(final PsiElement
[] elements
) throws ExitStatementsNotSameException
{
90 myExitPoints
= new IntArrayList();
91 myExitStatements
= ControlFlowUtil
92 .findExitPointsAndStatements(myControlFlow
, myFlowStart
, myFlowEnd
, myExitPoints
, ControlFlowUtil
.DEFAULT_EXIT_STATEMENTS_CLASSES
);
93 if (LOG
.isDebugEnabled()) {
94 LOG
.debug("exit points:");
95 for (int i
= 0; i
< myExitPoints
.size(); i
++) {
96 LOG
.debug(" " + myExitPoints
.get(i
));
98 LOG
.debug("exit statements:");
99 for (PsiStatement exitStatement
: myExitStatements
) {
100 LOG
.debug(" " + exitStatement
);
103 if (myExitPoints
.isEmpty()) {
104 // if the fragment never exits assume as if it exits in the end
105 myExitPoints
.add(myControlFlow
.getEndOffset(elements
[elements
.length
- 1]));
108 if (myExitPoints
.size() != 1) {
109 areExitStatementsTheSame();
110 myGenerateConditionalExit
= true;
112 return myExitStatements
;
116 private void areExitStatementsTheSame() throws ExitStatementsNotSameException
{
117 if (myExitStatements
.isEmpty()) {
118 throw new ExitStatementsNotSameException();
120 PsiStatement first
= null;
121 for (PsiStatement statement
: myExitStatements
) {
126 if (!PsiEquivalenceUtil
.areElementsEquivalent(first
, statement
)) {
127 throw new ExitStatementsNotSameException();
131 myFirstExitStatementCopy
= (PsiStatement
)first
.copy();
134 public boolean isGenerateConditionalExit() {
135 return myGenerateConditionalExit
;
138 public Collection
<PsiStatement
> getExitStatements() {
139 return myExitStatements
;
142 public static class ExitStatementsNotSameException
extends Exception
{}
145 public PsiVariable
[] getOutputVariables() {
146 PsiVariable
[] myOutputVariables
= ControlFlowUtil
.getOutputVariables(myControlFlow
, myFlowStart
, myFlowEnd
, myExitPoints
.toArray());
147 if (myGenerateConditionalExit
) {
148 //variables declared in selected block used in return statements are to be considered output variables when extracting guard methods
149 final Set
<PsiVariable
> outputVariables
= new HashSet
<PsiVariable
>(Arrays
.asList(myOutputVariables
));
150 for (PsiStatement statement
: myExitStatements
) {
151 statement
.accept(new JavaRecursiveElementVisitor() {
154 public void visitReferenceExpression(PsiReferenceExpression expression
) {
155 super.visitReferenceExpression(expression
);
156 final PsiElement resolved
= expression
.resolve();
157 if (resolved
instanceof PsiVariable
) {
158 final PsiVariable variable
= (PsiVariable
)resolved
;
159 if (isWrittenInside(variable
)) {
160 outputVariables
.add(variable
);
165 private boolean isWrittenInside(final PsiVariable variable
) {
166 final List
<Instruction
> instructions
= myControlFlow
.getInstructions();
167 for (int i
= myFlowStart
; i
< myFlowEnd
; i
++) {
168 Instruction instruction
= instructions
.get(i
);
169 if (instruction
instanceof WriteVariableInstruction
&& variable
.equals(((WriteVariableInstruction
)instruction
).variable
)) {
179 myOutputVariables
= outputVariables
.toArray(new PsiVariable
[outputVariables
.size()]);
181 Arrays
.sort(myOutputVariables
, PsiUtil
.BY_POSITION
);
182 return myOutputVariables
;
185 public boolean isReturnPresentBetween() {
186 return ControlFlowUtil
.returnPresentBetween(myControlFlow
, myFlowStart
, myFlowEnd
);
189 private void removeParametersUsedInExitsOnly(PsiElement codeFragment
, List
<PsiVariable
> inputVariables
) {
190 LocalSearchScope scope
= new LocalSearchScope(codeFragment
);
192 for (Iterator
<PsiVariable
> iterator
= inputVariables
.iterator(); iterator
.hasNext();) {
193 PsiVariable variable
= iterator
.next();
194 for (PsiReference ref
: ReferencesSearch
.search(variable
, scope
)) {
195 PsiElement element
= ref
.getElement();
196 int elementOffset
= myControlFlow
.getStartOffset(element
);
197 if (elementOffset
>= myFlowStart
&& elementOffset
<= myFlowEnd
) {
198 if (!isInExitStatements(element
, myExitStatements
)) continue Variables
;
200 if (elementOffset
== -1) { //references in local/anonymous classes should not be skipped
201 final PsiClass psiClass
= PsiTreeUtil
.getParentOfType(element
, PsiClass
.class);
202 if (psiClass
!= null) {
203 final TextRange textRange
= psiClass
.getTextRange();
204 if (myControlFlow
.getElement(myFlowStart
).getTextOffset() <= textRange
.getStartOffset() &&
205 textRange
.getEndOffset() <= myControlFlow
.getElement(myFlowEnd
).getTextRange().getEndOffset()) {
216 private static boolean isInExitStatements(PsiElement element
, Collection
<PsiStatement
> exitStatements
) {
217 for (PsiStatement exitStatement
: exitStatements
) {
218 if (PsiTreeUtil
.isAncestor(exitStatement
, element
, false)) return true;
223 private boolean needExitStatement(final PsiStatement exitStatement
) {
224 if (exitStatement
instanceof PsiContinueStatement
) {
226 PsiStatement statement
= ((PsiContinueStatement
)exitStatement
).findContinuedStatement();
227 if (statement
== null) return true;
228 if (statement
instanceof PsiLoopStatement
) statement
= ((PsiLoopStatement
)statement
).getBody();
229 int endOffset
= myControlFlow
.getEndOffset(statement
);
230 return endOffset
> myFlowEnd
;
235 public List
<PsiVariable
> getInputVariables(final PsiElement codeFragment
) {
236 final List
<PsiVariable
> inputVariables
= ControlFlowUtil
.getInputVariables(myControlFlow
, myFlowStart
, myFlowEnd
);
237 List
<PsiVariable
> myInputVariables
;
238 if (myGenerateConditionalExit
) {
239 List
<PsiVariable
> inputVariableList
= new ArrayList
<PsiVariable
>(inputVariables
);
240 removeParametersUsedInExitsOnly(codeFragment
, inputVariableList
);
241 myInputVariables
= inputVariableList
;
244 myInputVariables
= inputVariables
;
246 //varargs variables go last, otherwise order is induced by original ordering
247 Collections
.sort(myInputVariables
, new Comparator
<PsiVariable
>() {
248 public int compare(final PsiVariable v1
, final PsiVariable v2
) {
249 if (v1
.getType() instanceof PsiEllipsisType
) {
252 if (v2
.getType() instanceof PsiEllipsisType
) {
255 return v1
.getTextOffset() - v2
.getTextOffset();
258 return myInputVariables
;
261 public PsiStatement
getExitStatementCopy(PsiElement returnStatement
,
262 final PsiElement
[] elements
) {
263 PsiStatement exitStatementCopy
= null;
264 // replace all exit-statements such as break's or continue's with appropriate return
265 for (PsiStatement exitStatement
: myExitStatements
) {
266 if (exitStatement
instanceof PsiReturnStatement
) {
267 if (!myGenerateConditionalExit
) continue;
269 else if (exitStatement
instanceof PsiBreakStatement
) {
270 PsiStatement statement
= ((PsiBreakStatement
)exitStatement
).findExitedStatement();
271 if (statement
== null) continue;
272 int startOffset
= myControlFlow
.getStartOffset(statement
);
273 int endOffset
= myControlFlow
.getEndOffset(statement
);
274 if (myFlowStart
<= startOffset
&& endOffset
<= myFlowEnd
) continue;
276 else if (exitStatement
instanceof PsiContinueStatement
) {
277 PsiStatement statement
= ((PsiContinueStatement
)exitStatement
).findContinuedStatement();
278 if (statement
== null) continue;
279 int startOffset
= myControlFlow
.getStartOffset(statement
);
280 int endOffset
= myControlFlow
.getEndOffset(statement
);
281 if (myFlowStart
<= startOffset
&& endOffset
<= myFlowEnd
) continue;
284 LOG
.assertTrue(false, exitStatement
);
289 for (int j
= 0; j
< elements
.length
; j
++) {
290 if (exitStatement
.equals(elements
[j
])) {
295 if (exitStatementCopy
== null) {
296 if (needExitStatement(exitStatement
)) {
297 exitStatementCopy
= (PsiStatement
)exitStatement
.copy();
300 PsiElement result
= exitStatement
.replace(returnStatement
);
302 elements
[index
] = result
;
305 return exitStatementCopy
;
308 public List
<PsiVariable
> getUsedVariables(int start
) {
309 return getUsedVariables(start
, myControlFlow
.getSize());
312 public List
<PsiVariable
> getUsedVariables(int start
, int end
) {
313 return ControlFlowUtil
.getUsedVariables(myControlFlow
, start
, end
);
316 public Collection
<ControlFlowUtil
.VariableInfo
> getInitializedTwice(int start
) {
317 return ControlFlowUtil
.getInitializedTwice(myControlFlow
, start
, myControlFlow
.getSize());
320 public List
<PsiVariable
> getUsedVariables() {
321 return getUsedVariables(myFlowEnd
);
324 public List
<PsiVariable
> getUsedVariablesInBody() {
325 return getUsedVariables(myFlowStart
, myFlowEnd
);
328 public Collection
<ControlFlowUtil
.VariableInfo
> getInitializedTwice() {
329 return getInitializedTwice(myFlowEnd
);
332 public void setGenerateConditionalExit(boolean generateConditionalExit
) {
333 myGenerateConditionalExit
= generateConditionalExit
;