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.
18 * Created by IntelliJ IDEA.
22 * To change template for new class use
23 * Code Style | Class Templates options (Tools | IDE Options).
25 package com
.intellij
.codeInsight
.intention
.impl
;
27 import com
.intellij
.codeInsight
.CodeInsightBundle
;
28 import com
.intellij
.codeInsight
.CodeInsightServicesUtil
;
29 import com
.intellij
.codeInsight
.CodeInsightUtilBase
;
30 import com
.intellij
.codeInsight
.intention
.PsiElementBaseIntentionAction
;
31 import com
.intellij
.openapi
.diagnostic
.Logger
;
32 import com
.intellij
.openapi
.editor
.Editor
;
33 import com
.intellij
.openapi
.project
.Project
;
34 import com
.intellij
.openapi
.util
.TextRange
;
35 import com
.intellij
.psi
.*;
36 import com
.intellij
.psi
.codeStyle
.CodeStyleManager
;
37 import com
.intellij
.psi
.controlFlow
.*;
38 import com
.intellij
.psi
.util
.PsiTreeUtil
;
39 import com
.intellij
.util
.IncorrectOperationException
;
40 import org
.jetbrains
.annotations
.NotNull
;
41 import org
.jetbrains
.annotations
.Nullable
;
43 import java
.util
.List
;
45 public class InvertIfConditionAction
extends PsiElementBaseIntentionAction
{
46 private static final Logger LOG
= Logger
.getInstance("#com.intellij.codeInsight.intention.impl.InvertIfConditionAction");
48 public boolean isAvailable(@NotNull Project project
, Editor editor
, @Nullable PsiElement element
) {
49 if (element
== null) return false;
51 int offset
= editor
.getCaretModel().getOffset();
52 final PsiIfStatement ifStatement
= PsiTreeUtil
.getParentOfType(element
, PsiIfStatement
.class);
53 if (ifStatement
== null) return false;
54 final PsiExpression condition
= ifStatement
.getCondition();
55 if (condition
== null) return false;
56 if (ifStatement
.getThenBranch() == null) return false;
57 if (element
instanceof PsiKeyword
) {
58 PsiKeyword keyword
= (PsiKeyword
) element
;
59 if ((keyword
.getTokenType() == JavaTokenType
.IF_KEYWORD
|| keyword
.getTokenType() == JavaTokenType
.ELSE_KEYWORD
)
60 && keyword
.getParent() == ifStatement
) {
64 final TextRange condTextRange
= condition
.getTextRange();
65 if (condTextRange
== null) return false;
66 if (!condTextRange
.contains(offset
)) return false;
67 PsiElement block
= findCodeBlock(ifStatement
);
72 public String
getText() {
73 return getFamilyName();
77 public String
getFamilyName() {
78 return CodeInsightBundle
.message("intention.invert.if.condition");
81 public void invoke(@NotNull Project project
, Editor editor
, PsiFile file
) throws IncorrectOperationException
{
82 if (!CodeInsightUtilBase
.prepareFileForWrite(file
)) return;
84 PsiIfStatement ifStatement
= PsiTreeUtil
.getParentOfType(file
.findElementAt(
85 editor
.getCaretModel().getOffset()), PsiIfStatement
.class);
87 LOG
.assertTrue(ifStatement
!= null);
88 PsiElement block
= findCodeBlock(ifStatement
);
90 ControlFlow controlFlow
= buildControlFlow(block
);
92 PsiExpression condition
= (PsiExpression
) ifStatement
.getCondition().copy();
94 setupBranches(ifStatement
, controlFlow
);
95 if (condition
!= null) {
96 ifStatement
.getCondition().replace(CodeInsightServicesUtil
.invertCondition(condition
));
99 formatIf(ifStatement
);
102 private static void formatIf(PsiIfStatement ifStatement
) throws IncorrectOperationException
{
103 final Project project
= ifStatement
.getProject();
104 PsiElementFactory factory
= JavaPsiFacade
.getInstance(project
).getElementFactory();
106 PsiElement thenBranch
= ifStatement
.getThenBranch().copy();
107 PsiElement elseBranch
= ifStatement
.getElseBranch() != null ? ifStatement
.getElseBranch().copy() : null;
108 PsiElement condition
= ifStatement
.getCondition().copy();
110 final CodeStyleManager codeStyle
= CodeStyleManager
.getInstance(project
);
112 PsiBlockStatement codeBlock
= (PsiBlockStatement
)factory
.createStatementFromText("{}", null);
113 codeBlock
= (PsiBlockStatement
)codeStyle
.reformat(codeBlock
);
115 ifStatement
.getThenBranch().replace(codeBlock
);
116 if (elseBranch
!= null) {
117 ifStatement
.getElseBranch().replace(codeBlock
);
119 ifStatement
.getCondition().replace(factory
.createExpressionFromText("true", null));
120 ifStatement
= (PsiIfStatement
)codeStyle
.reformat(ifStatement
);
122 if (!(thenBranch
instanceof PsiBlockStatement
)) {
123 PsiBlockStatement codeBlock1
= (PsiBlockStatement
)ifStatement
.getThenBranch().replace(codeBlock
);
124 codeBlock1
= (PsiBlockStatement
)codeStyle
.reformat(codeBlock1
);
125 codeBlock1
.getCodeBlock().add(thenBranch
);
128 ifStatement
.getThenBranch().replace(thenBranch
);
131 if (elseBranch
!= null) {
132 if (!(elseBranch
instanceof PsiBlockStatement
)) {
133 PsiBlockStatement codeBlock1
= (PsiBlockStatement
)ifStatement
.getElseBranch().replace(codeBlock
);
134 codeBlock1
= (PsiBlockStatement
)codeStyle
.reformat(codeBlock1
);
135 codeBlock1
.getCodeBlock().add(elseBranch
);
138 elseBranch
= ifStatement
.getElseBranch().replace(elseBranch
);
140 if (emptyBlock(((PsiBlockStatement
)elseBranch
).getCodeBlock())) {
141 ifStatement
.getElseBranch().delete();
146 ifStatement
.getCondition().replace(condition
);
149 private static boolean emptyBlock (PsiCodeBlock block
) {
150 PsiElement
[] children
= block
.getChildren();
151 for (PsiElement child
: children
) {
152 if (child
instanceof PsiComment
) return false;
153 if (!(child
instanceof PsiWhiteSpace
) && !(child
instanceof PsiJavaToken
)) return false;
158 private static PsiElement
findCodeBlock(PsiIfStatement ifStatement
) {
159 PsiElement e
= PsiTreeUtil
.getParentOfType(ifStatement
, PsiMethod
.class, PsiClassInitializer
.class);
160 if (e
instanceof PsiMethod
) return ((PsiMethod
) e
).getBody();
161 if (e
instanceof PsiClassInitializer
) return ((PsiClassInitializer
) e
).getBody();
165 private static PsiElement
findNearestCodeBlock(PsiIfStatement ifStatement
) {
166 return PsiTreeUtil
.getParentOfType(ifStatement
, PsiCodeBlock
.class);
169 private static ControlFlow
buildControlFlow(PsiElement element
) {
171 //return new ControlFlowAnalyzer(element, LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance(), false, false).buildControlFlow();
172 return ControlFlowFactory
.getInstance(element
.getProject()).getControlFlow(element
, LocalsOrMyInstanceFieldsControlFlowPolicy
.getInstance(), false);
174 catch (AnalysisCanceledException e
) {
175 return ControlFlow
.EMPTY
;
179 private static void setupBranches(PsiIfStatement ifStatement
, ControlFlow flow
) throws IncorrectOperationException
{
180 PsiElementFactory factory
= JavaPsiFacade
.getInstance(ifStatement
.getProject()).getElementFactory();
181 Project project
= ifStatement
.getProject();
183 PsiStatement thenBranch
= ifStatement
.getThenBranch();
184 PsiStatement elseBranch
= ifStatement
.getElseBranch();
186 if (elseBranch
!= null) {
187 elseBranch
= (PsiStatement
) elseBranch
.copy();
188 setElseBranch(ifStatement
, thenBranch
, flow
);
189 ifStatement
.getThenBranch().replace(elseBranch
);
193 final CodeStyleManager codeStyle
= CodeStyleManager
.getInstance(project
);
194 if (flow
.getSize() == 0) {
195 ifStatement
.setElseBranch(thenBranch
);
196 PsiStatement statement
= factory
.createStatementFromText("{}", null);
197 statement
= (PsiStatement
) codeStyle
.reformat(statement
);
198 statement
= (PsiStatement
) ifStatement
.getThenBranch().replace(statement
);
199 codeStyle
.reformat(statement
);
203 int endOffset
= calcEndOffset(flow
, ifStatement
);
205 LOG
.assertTrue(endOffset
>= 0);
207 if (endOffset
>= flow
.getSize()) {
208 PsiStatement statement
= factory
.createStatementFromText("return;", null);
209 statement
= (PsiStatement
) codeStyle
.reformat(statement
);
210 if (thenBranch
instanceof PsiBlockStatement
) {
211 PsiStatement
[] statements
= ((PsiBlockStatement
) thenBranch
).getCodeBlock().getStatements();
212 int len
= statements
.length
;
214 if (statements
[len
- 1] instanceof PsiReturnStatement
) len
--;
216 PsiElement firstElement
= statements
[0];
217 while (firstElement
.getPrevSibling() instanceof PsiWhiteSpace
|| firstElement
.getPrevSibling() instanceof PsiComment
) {
218 firstElement
= firstElement
.getPrevSibling();
220 ifStatement
.getParent().addRangeAfter(firstElement
, statements
[len
- 1], ifStatement
);
224 if (!(thenBranch
instanceof PsiReturnStatement
)) {
225 addAfter(ifStatement
, thenBranch
);
228 ifStatement
.getThenBranch().replace(statement
);
231 PsiElement element
= flow
.getElement(endOffset
);
232 while (element
!= null && !(element
instanceof PsiStatement
)) element
= element
.getParent();
234 if (element
!= null && element
.getParent() instanceof PsiForStatement
) {
235 PsiForStatement forStatement
= (PsiForStatement
) element
.getParent();
236 if (forStatement
.getUpdate() == element
) {
237 PsiStatement statement
= factory
.createStatementFromText("continue;", null);
238 statement
= (PsiStatement
) codeStyle
.reformat(statement
);
239 addAfter(ifStatement
, thenBranch
);
240 ifStatement
.getThenBranch().replace(statement
);
244 if (element
instanceof PsiWhileStatement
&& flow
.getStartOffset(element
) == endOffset
||
245 element
instanceof PsiForeachStatement
&& flow
.getStartOffset(element
) + 1 == endOffset
// Foreach doesn't loop on it's first instruction
246 // but rather on second. It only accesses collection initially.
248 PsiStatement statement
= factory
.createStatementFromText("continue;", null);
249 statement
= (PsiStatement
) codeStyle
.reformat(statement
);
250 addAfter(ifStatement
, thenBranch
);
251 ifStatement
.getThenBranch().replace(statement
);
255 if (element
instanceof PsiReturnStatement
) {
256 PsiReturnStatement returnStatement
= (PsiReturnStatement
) element
;
257 addAfter(ifStatement
, thenBranch
);
258 ifStatement
.getThenBranch().replace(returnStatement
.copy());
260 ControlFlow flow2
= buildControlFlow(findCodeBlock(ifStatement
));
261 if (!ControlFlowUtil
.isInstructionReachable(flow2
, flow2
.getStartOffset(returnStatement
), 0)) returnStatement
.delete();
265 boolean nextUnreachable
= flow
.getEndOffset(ifStatement
) == flow
.getSize();
266 if (!nextUnreachable
) {
267 PsiElement nearestCodeBlock
= findNearestCodeBlock(ifStatement
);
268 if (nearestCodeBlock
!= null) {
269 ControlFlow flow2
= buildControlFlow(nearestCodeBlock
);
270 nextUnreachable
= !ControlFlowUtil
.isInstructionReachable(flow2
, flow2
.getEndOffset(ifStatement
), getThenOffset(flow2
, ifStatement
));
273 if (nextUnreachable
) {
274 setElseBranch(ifStatement
, thenBranch
, flow
);
276 PsiElement first
= ifStatement
.getNextSibling();
277 // while (first instanceof PsiWhiteSpace) first = first.getNextSibling();
279 PsiElement last
= first
;
280 PsiElement next
= last
.getNextSibling();
281 while (next
!= null && !(next
instanceof PsiSwitchLabelStatement
)) {
283 next
= next
.getNextSibling();
285 while (first
!= last
&& (last
instanceof PsiWhiteSpace
||
286 last
instanceof PsiJavaToken
&& ((PsiJavaToken
) last
).getTokenType() == JavaTokenType
.RBRACE
))
287 last
= last
.getPrevSibling();
290 PsiBlockStatement codeBlock
= (PsiBlockStatement
) factory
.createStatementFromText("{}", null);
291 codeBlock
.getCodeBlock().addRange(first
, last
);
292 first
.getParent().deleteChildRange(first
, last
);
293 ifStatement
.getThenBranch().replace(codeBlock
);
295 codeStyle
.reformat(ifStatement
);
299 setElseBranch(ifStatement
, thenBranch
, flow
);
300 PsiStatement statement
= factory
.createStatementFromText("{}", null);
301 statement
= (PsiStatement
) codeStyle
.reformat(statement
);
302 statement
= (PsiStatement
) ifStatement
.getThenBranch().replace(statement
);
303 codeStyle
.reformat(statement
);
306 private static void setElseBranch(PsiIfStatement ifStatement
, PsiStatement thenBranch
, ControlFlow flow
) throws IncorrectOperationException
{
307 if (flow
.getEndOffset(ifStatement
) == flow
.getEndOffset(thenBranch
)) {
308 if (thenBranch
instanceof PsiContinueStatement
) {
309 PsiStatement elseBranch
= ifStatement
.getElseBranch();
310 if (elseBranch
!= null) {
315 else if (thenBranch
instanceof PsiBlockStatement
) {
316 PsiStatement
[] statements
= ((PsiBlockStatement
) thenBranch
).getCodeBlock().getStatements();
317 if (statements
.length
> 0 && statements
[statements
.length
- 1] instanceof PsiContinueStatement
) {
318 statements
[statements
.length
- 1].delete();
322 ifStatement
.setElseBranch(thenBranch
);
325 private static void addAfter(PsiIfStatement ifStatement
, PsiStatement thenBranch
) throws IncorrectOperationException
{
326 if (thenBranch
instanceof PsiBlockStatement
) {
327 PsiBlockStatement blockStatement
= (PsiBlockStatement
) thenBranch
;
328 PsiStatement
[] statements
= blockStatement
.getCodeBlock().getStatements();
329 if (statements
.length
> 0) {
330 ifStatement
.getParent().addRangeAfter(statements
[0], statements
[statements
.length
- 1], ifStatement
);
333 ifStatement
.getParent().addAfter(thenBranch
, ifStatement
);
337 private static int getThenOffset(ControlFlow controlFlow
, PsiIfStatement ifStatement
) {
338 PsiStatement thenBranch
= ifStatement
.getThenBranch();
340 for (int i
= 0; i
< controlFlow
.getSize(); i
++) {
341 if (PsiTreeUtil
.isAncestor(thenBranch
, controlFlow
.getElement(i
), false)) return i
;
346 private static int calcEndOffset(ControlFlow controlFlow
, PsiIfStatement ifStatement
) {
349 List
<Instruction
> instructions
= controlFlow
.getInstructions();
350 for (int i
= 0; i
< instructions
.size(); i
++) {
351 Instruction instruction
= instructions
.get(i
);
352 if (controlFlow
.getElement(i
) != ifStatement
) continue;
354 if (instruction
instanceof GoToInstruction
) {
355 GoToInstruction goToInstruction
= (GoToInstruction
)instruction
;
356 if (goToInstruction
.role
!= BranchingInstruction
.Role
.END
) continue;
358 endOffset
= goToInstruction
.offset
;
361 else if (instruction
instanceof ConditionalGoToInstruction
) {
362 ConditionalGoToInstruction goToInstruction
= (ConditionalGoToInstruction
)instruction
;
363 if (goToInstruction
.role
!= BranchingInstruction
.Role
.END
) continue;
365 endOffset
= goToInstruction
.offset
;
369 if (endOffset
== -1) {
370 endOffset
= controlFlow
.getSize();
372 while (endOffset
< instructions
.size() && instructions
.get(endOffset
) instanceof GoToInstruction
&& !((GoToInstruction
) instructions
.get(endOffset
)).isReturn
) {
373 endOffset
= ((BranchingInstruction
)instructions
.get(endOffset
)).offset
;