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 this template use Options | File Templates.
24 package com
.intellij
.codeInsight
.daemon
.impl
.quickfix
;
26 import com
.intellij
.codeInsight
.CodeInsightUtilBase
;
27 import com
.intellij
.codeInsight
.daemon
.QuickFixBundle
;
28 import com
.intellij
.codeInsight
.intention
.IntentionAction
;
29 import com
.intellij
.openapi
.diagnostic
.Logger
;
30 import com
.intellij
.openapi
.editor
.Editor
;
31 import com
.intellij
.openapi
.project
.Project
;
32 import com
.intellij
.psi
.*;
33 import com
.intellij
.psi
.codeStyle
.JavaCodeStyleManager
;
34 import com
.intellij
.psi
.controlFlow
.*;
35 import com
.intellij
.psi
.util
.PsiTreeUtil
;
36 import com
.intellij
.psi
.util
.PsiUtil
;
37 import com
.intellij
.util
.IncorrectOperationException
;
39 import java
.util
.ArrayList
;
40 import java
.util
.List
;
42 import org
.jetbrains
.annotations
.NotNull
;
44 public class DeferFinalAssignmentFix
implements IntentionAction
{
45 private static final Logger LOG
= Logger
.getInstance("#com.intellij.codeInsight.daemon.impl.quickfix.DeferFinalAssignmentFix");
47 private final PsiVariable variable
;
48 private final PsiReferenceExpression expression
;
50 public DeferFinalAssignmentFix(PsiVariable variable
, PsiReferenceExpression expression
) {
51 this.variable
= variable
;
52 this.expression
= expression
;
56 public String
getFamilyName() {
57 return QuickFixBundle
.message("defer.final.assignment.with.temp.family");
61 public String
getText() {
62 return QuickFixBundle
.message("defer.final.assignment.with.temp.text", variable
.getName());
65 public void invoke(@NotNull Project project
, Editor editor
, PsiFile file
) throws IncorrectOperationException
{
66 if (!CodeInsightUtilBase
.prepareFileForWrite(variable
.getContainingFile())) return;
68 if (variable
instanceof PsiField
) {
69 deferField((PsiField
)variable
);
72 deferLocalVariable((PsiLocalVariable
)variable
);
76 private void deferField(PsiField field
) throws IncorrectOperationException
{
77 PsiCodeBlock codeBlock
= getEnclosingCodeBlock(field
, expression
);
78 if (codeBlock
== null) return;
79 deferVariable(codeBlock
, field
, null);
82 private PsiCodeBlock
getEnclosingCodeBlock(PsiField field
, PsiElement element
) {
83 PsiClass aClass
= field
.getContainingClass();
84 if (aClass
== null) return null;
85 PsiMethod
[] constructors
= aClass
.getConstructors();
86 for (PsiMethod constructor
: constructors
) {
87 PsiCodeBlock body
= constructor
.getBody();
88 if (body
== null) continue;
89 if (PsiTreeUtil
.isAncestor(body
, element
, true)) return body
;
92 //maybe inside class initalizer ?
93 PsiClassInitializer
[] initializers
= aClass
.getInitializers();
94 for (PsiClassInitializer initializer
: initializers
) {
95 PsiCodeBlock body
= initializer
.getBody();
96 if (PsiTreeUtil
.isAncestor(body
, element
, true)) return body
;
101 private void deferLocalVariable(PsiLocalVariable variable
) throws IncorrectOperationException
{
102 PsiElement outerCodeBlock
= PsiUtil
.getVariableCodeBlock(variable
, null);
103 deferVariable(outerCodeBlock
, variable
, variable
.getParent());
106 private void deferVariable(PsiElement outerCodeBlock
, PsiVariable variable
, PsiElement tempDeclarationAnchor
) throws IncorrectOperationException
{
107 if (outerCodeBlock
== null) return;
108 List
<PsiReferenceExpression
> outerReferences
= new ArrayList
<PsiReferenceExpression
>();
109 collectReferences(outerCodeBlock
, variable
, outerReferences
);
111 PsiElementFactory factory
= JavaPsiFacade
.getInstance(variable
.getProject()).getElementFactory();
112 Project project
= variable
.getProject();
113 String tempName
= suggestNewName(project
, variable
);
114 PsiDeclarationStatement tempVariableDeclaration
= factory
.createVariableDeclarationStatement(tempName
, variable
.getType(), null);
116 ControlFlow controlFlow
;
118 controlFlow
= ControlFlowFactory
.getInstance(project
).getControlFlow(outerCodeBlock
, LocalsOrMyInstanceFieldsControlFlowPolicy
.getInstance(), false);
120 catch (AnalysisCanceledException e
) {
124 boolean writeReferenceOccurred
= false;
125 PsiReferenceExpression writeReference
= null;
126 for (int i
= outerReferences
.size()-1; i
>=0; i
--) {
127 PsiReferenceExpression reference
= outerReferences
.get(i
);
128 if (!writeReferenceOccurred
&& !PsiUtil
.isAccessedForWriting(reference
)) {
129 // trailing read references need not be converted to temp var references
130 outerReferences
.remove(i
);
133 writeReferenceOccurred
= true;
134 writeReference
= reference
;
135 PsiElement element
= PsiUtil
.getEnclosingStatement(reference
);
136 int endOffset
= element
== null ?
-1 : controlFlow
.getEndOffset(element
);
137 minOffset
= Math
.max(minOffset
, endOffset
);
139 LOG
.assertTrue(writeReference
!= null);
140 PsiStatement finalAssignment
= factory
.createStatementFromText(writeReference
.getText()+" = "+tempName
+";", outerCodeBlock
);
141 if (!insertToDefinitelyReachedPlace(outerCodeBlock
, finalAssignment
, controlFlow
, minOffset
, outerReferences
)) return;
143 outerCodeBlock
.addAfter(tempVariableDeclaration
, tempDeclarationAnchor
);
145 replaceReferences(outerReferences
, factory
.createExpressionFromText(tempName
, outerCodeBlock
));
149 private boolean insertToDefinitelyReachedPlace(PsiElement codeBlock
, PsiStatement finalAssignment
, ControlFlow controlFlow
, int minOffset
, List references
) throws IncorrectOperationException
{
150 int offset
= ControlFlowUtil
.getMinDefinitelyReachedOffset(controlFlow
, minOffset
, references
);
151 if (offset
== controlFlow
.getSize()) {
152 codeBlock
.add(finalAssignment
);
155 PsiElement element
= null; //controlFlow.getEndOffset(codeBlock) == offset ? getEnclosingStatement(controlFlow.getElement(offset)) : null;
156 while (offset
< controlFlow
.getSize()) {
157 element
= controlFlow
.getElement(offset
);
158 if (element
!= null) element
= PsiUtil
.getEnclosingStatement(element
);
159 int startOffset
= controlFlow
.getStartOffset(element
);
160 if (startOffset
!= -1 && startOffset
>= minOffset
&& element
instanceof PsiStatement
) break;
163 if (!(offset
< controlFlow
.getSize())) return false;
165 if (ControlFlowUtil
.isInstructionReachable(controlFlow
, offset
, offset
)) return false;
166 codeBlock
.addBefore(finalAssignment
, element
);
170 private static void replaceReferences(List references
, PsiElement newExpression
) throws IncorrectOperationException
{
171 for (Object reference1
: references
) {
172 PsiElement reference
= (PsiElement
)reference1
;
173 reference
.replace(newExpression
);
179 private static void collectReferences(PsiElement context
, final PsiVariable variable
, final List
<PsiReferenceExpression
> references
) {
180 context
.accept(new JavaRecursiveElementWalkingVisitor() {
181 @Override public void visitReferenceExpression(PsiReferenceExpression expression
) {
182 if (expression
.resolve() == variable
) references
.add(expression
);
183 super.visitReferenceExpression(expression
);
188 private static String
suggestNewName(Project project
, PsiVariable variable
) {
189 // new name should not conflict with another variable at the variable declaration level and usage level
190 String name
= variable
.getName();
191 // trim last digit to suggest variable names like i1,i2, i3...
192 if (name
.length() > 1 && Character
.isDigit(name
.charAt(name
.length()-1))) {
193 name
= name
.substring(0,name
.length()-1);
195 return JavaCodeStyleManager
.getInstance(project
).suggestUniqueVariableName(name
, variable
, true);
198 public boolean isAvailable(@NotNull Project project
, Editor editor
, PsiFile file
) {
201 && variable
.isValid()
202 && !(variable
instanceof PsiParameter
)
203 && !(variable
instanceof ImplicitVariable
)
204 && expression
!= null
205 && expression
.isValid()
206 && variable
.getManager().isInProject(variable
)
210 public boolean startInWriteAction() {