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
.highlighting
.HighlightManager
;
20 import com
.intellij
.codeInsight
.intention
.QuickFixFactory
;
21 import com
.intellij
.codeInsight
.TargetElementUtilBase
;
22 import com
.intellij
.lang
.refactoring
.InlineActionHandler
;
23 import com
.intellij
.openapi
.application
.ApplicationManager
;
24 import com
.intellij
.openapi
.command
.CommandProcessor
;
25 import com
.intellij
.openapi
.diagnostic
.Logger
;
26 import com
.intellij
.openapi
.editor
.Editor
;
27 import com
.intellij
.openapi
.editor
.colors
.EditorColors
;
28 import com
.intellij
.openapi
.editor
.colors
.EditorColorsManager
;
29 import com
.intellij
.openapi
.editor
.markup
.TextAttributes
;
30 import com
.intellij
.openapi
.project
.Project
;
31 import com
.intellij
.openapi
.wm
.WindowManager
;
32 import com
.intellij
.psi
.*;
33 import com
.intellij
.psi
.controlFlow
.DefUseUtil
;
34 import com
.intellij
.psi
.search
.GlobalSearchScope
;
35 import com
.intellij
.psi
.search
.searches
.ReferencesSearch
;
36 import com
.intellij
.psi
.util
.PsiTreeUtil
;
37 import com
.intellij
.psi
.util
.PsiUtil
;
38 import com
.intellij
.refactoring
.HelpID
;
39 import com
.intellij
.refactoring
.RefactoringBundle
;
40 import com
.intellij
.refactoring
.util
.CommonRefactoringUtil
;
41 import com
.intellij
.refactoring
.util
.InlineUtil
;
42 import com
.intellij
.refactoring
.util
.RefactoringMessageDialog
;
43 import com
.intellij
.util
.ArrayUtil
;
44 import com
.intellij
.util
.IncorrectOperationException
;
45 import com
.intellij
.util
.Processor
;
46 import com
.intellij
.util
.Query
;
47 import org
.jetbrains
.annotations
.NotNull
;
48 import org
.jetbrains
.annotations
.Nullable
;
50 import java
.util
.ArrayList
;
51 import java
.util
.Collections
;
52 import java
.util
.List
;
54 public class InlineLocalHandler
extends JavaInlineActionHandler
{
55 private static final Logger LOG
= Logger
.getInstance("#com.intellij.refactoring.inline.InlineLocalHandler");
57 private static final String REFACTORING_NAME
= RefactoringBundle
.message("inline.variable.title");
59 public boolean canInlineElement(PsiElement element
) {
60 return element
instanceof PsiLocalVariable
;
63 public void inlineElement(Project project
, Editor editor
, PsiElement element
) {
64 final PsiReference psiReference
= TargetElementUtilBase
.findReference(editor
);
65 final PsiReferenceExpression refExpr
= psiReference
instanceof PsiReferenceExpression ?
((PsiReferenceExpression
)psiReference
) : null;
66 invoke(project
, editor
, (PsiLocalVariable
) element
, refExpr
);
70 * should be called in AtomicAction
72 public static void invoke(@NotNull final Project project
, final Editor editor
, final PsiLocalVariable local
, PsiReferenceExpression refExpr
) {
73 if (!CommonRefactoringUtil
.checkReadOnlyStatus(project
, local
)) return;
75 final HighlightManager highlightManager
= HighlightManager
.getInstance(project
);
77 final String localName
= local
.getName();
79 final Query
<PsiReference
> query
= ReferencesSearch
.search(local
, GlobalSearchScope
.allScope(project
), false);
80 if (query
.findFirst() == null){
81 LOG
.assertTrue(refExpr
== null);
82 String message
= RefactoringBundle
.message("variable.is.never.used", localName
);
83 CommonRefactoringUtil
.showErrorHint(project
, editor
, message
, REFACTORING_NAME
, HelpID
.INLINE_VARIABLE
);
87 final PsiClass containingClass
= PsiTreeUtil
.getParentOfType(local
, PsiClass
.class);
88 final List
<PsiClass
> innerClassesWithUsages
= new ArrayList
<PsiClass
>();
89 final List
<PsiElement
> innerClassUsages
= new ArrayList
<PsiElement
>();
90 query
.forEach(new Processor
<PsiReference
>() {
91 public boolean process(final PsiReference psiReference
) {
92 final PsiElement element
= psiReference
.getElement();
93 final PsiClass psiClass
= PsiTreeUtil
.getParentOfType(element
, PsiClass
.class);
94 if (psiClass
!= containingClass
) {
95 innerClassesWithUsages
.add(psiClass
);
96 innerClassUsages
.add(element
);
102 final PsiCodeBlock containerBlock
= PsiTreeUtil
.getParentOfType(local
, PsiCodeBlock
.class);
103 LOG
.assertTrue(containerBlock
!= null);
105 final PsiExpression defToInline
= innerClassesWithUsages
.isEmpty()
106 ?
getDefToInline(local
, refExpr
, containerBlock
)
107 : getDefToInline(local
, innerClassesWithUsages
.get(0), containerBlock
);
108 if (defToInline
== null){
109 final String key
= refExpr
== null ?
"variable.has.no.initializer" : "variable.has.no.dominating.definition";
110 String message
= RefactoringBundle
.getCannotRefactorMessage(RefactoringBundle
.message(key
, localName
));
111 CommonRefactoringUtil
.showErrorHint(project
, editor
, message
, REFACTORING_NAME
, HelpID
.INLINE_VARIABLE
);
115 final List
<PsiElement
> refsToInlineList
= new ArrayList
<PsiElement
>();
116 Collections
.addAll(refsToInlineList
, DefUseUtil
.getRefs(containerBlock
, local
, defToInline
));
117 for (PsiElement innerClassUsage
: innerClassUsages
) {
118 if (!refsToInlineList
.contains(innerClassUsage
)) {
119 refsToInlineList
.add(innerClassUsage
);
122 if (refsToInlineList
.size() == 0) {
123 String message
= RefactoringBundle
.message("variable.is.never.used.before.modification", localName
);
124 CommonRefactoringUtil
.showErrorHint(project
, editor
, message
, REFACTORING_NAME
, HelpID
.INLINE_VARIABLE
);
127 final PsiElement
[] refsToInline
= refsToInlineList
.toArray(new PsiElement
[refsToInlineList
.size()]);
129 EditorColorsManager manager
= EditorColorsManager
.getInstance();
130 final TextAttributes attributes
= manager
.getGlobalScheme().getAttributes(EditorColors
.SEARCH_RESULT_ATTRIBUTES
);
131 final TextAttributes writeAttributes
= manager
.getGlobalScheme().getAttributes(EditorColors
.WRITE_SEARCH_RESULT_ATTRIBUTES
);
132 if (refExpr
!= null && PsiUtil
.isAccessedForReading(refExpr
) && ArrayUtil
.find(refsToInline
, refExpr
) < 0) {
133 final PsiElement
[] defs
= DefUseUtil
.getDefs(containerBlock
, local
, refExpr
);
134 LOG
.assertTrue(defs
.length
> 0);
135 highlightManager
.addOccurrenceHighlights(editor
, defs
, attributes
, true, null);
136 String message
= RefactoringBundle
.getCannotRefactorMessage(RefactoringBundle
.message("variable.is.accessed.for.writing", localName
));
137 CommonRefactoringUtil
.showErrorHint(project
, editor
, message
, REFACTORING_NAME
, HelpID
.INLINE_VARIABLE
);
138 WindowManager
.getInstance().getStatusBar(project
).setInfo(RefactoringBundle
.message("press.escape.to.remove.the.highlighting"));
142 PsiFile workingFile
= local
.getContainingFile();
143 for (PsiElement ref
: refsToInline
) {
144 final PsiFile otherFile
= ref
.getContainingFile();
145 if (!otherFile
.equals(workingFile
)) {
146 String message
= RefactoringBundle
.message("variable.is.referenced.in.multiple.files", localName
);
147 CommonRefactoringUtil
.showErrorHint(project
, editor
, message
, REFACTORING_NAME
, HelpID
.INLINE_VARIABLE
);
152 for (final PsiElement ref
: refsToInline
) {
153 final PsiElement
[] defs
= DefUseUtil
.getDefs(containerBlock
, local
, ref
);
154 boolean isSameDefinition
= true;
155 for (PsiElement def
: defs
) {
156 isSameDefinition
&= isSameDefinition(def
, defToInline
);
158 if (!isSameDefinition
) {
159 highlightManager
.addOccurrenceHighlights(editor
, defs
, writeAttributes
, true, null);
160 highlightManager
.addOccurrenceHighlights(editor
, new PsiElement
[]{ref
}, attributes
, true, null);
162 RefactoringBundle
.getCannotRefactorMessage(RefactoringBundle
.message("variable.is.accessed.for.writing.and.used.with.inlined", localName
));
163 CommonRefactoringUtil
.showErrorHint(project
, editor
, message
, REFACTORING_NAME
, HelpID
.INLINE_VARIABLE
);
164 WindowManager
.getInstance().getStatusBar(project
).setInfo(RefactoringBundle
.message("press.escape.to.remove.the.highlighting"));
169 if (checkRefsInAugmentedAssignment(refsToInline
, project
, editor
, localName
)) {
173 if (editor
!= null && !ApplicationManager
.getApplication().isUnitTestMode()) {
174 // TODO : check if initializer uses fieldNames that possibly will be hidden by other
175 // locals with the same names after inlining
176 highlightManager
.addOccurrenceHighlights(
179 attributes
, true, null
181 int occurrencesCount
= refsToInline
.length
;
182 String occurencesString
= RefactoringBundle
.message("occurences.string", occurrencesCount
);
183 final String promptKey
= isInliningVariableInitializer(defToInline
)
184 ?
"inline.local.variable.prompt" : "inline.local.variable.definition.prompt";
185 final String question
= RefactoringBundle
.message(promptKey
, localName
) + " " + occurencesString
;
186 RefactoringMessageDialog dialog
= new RefactoringMessageDialog(
189 HelpID
.INLINE_VARIABLE
,
190 "OptionPane.questionIcon",
195 WindowManager
.getInstance().getStatusBar(project
).setInfo(RefactoringBundle
.message("press.escape.to.remove.the.highlighting"));
200 final Runnable runnable
= new Runnable() {
203 PsiExpression
[] exprs
= new PsiExpression
[refsToInline
.length
];
204 for(int idx
= 0; idx
< refsToInline
.length
; idx
++){
205 PsiJavaCodeReferenceElement refElement
= (PsiJavaCodeReferenceElement
)refsToInline
[idx
];
206 exprs
[idx
] = InlineUtil
.inlineVariable(local
, defToInline
, refElement
);
209 if (!isInliningVariableInitializer(defToInline
)) {
210 defToInline
.getParent().delete();
212 defToInline
.delete();
215 if (ReferencesSearch
.search(local
).findFirst() == null) {
216 QuickFixFactory
.getInstance().createRemoveUnusedVariableFix(local
).invoke(project
, editor
, local
.getContainingFile());
219 if (editor
!= null && !ApplicationManager
.getApplication().isUnitTestMode()) {
220 highlightManager
.addOccurrenceHighlights(editor
, exprs
, attributes
, true, null);
221 WindowManager
.getInstance().getStatusBar(project
).setInfo(RefactoringBundle
.message("press.escape.to.remove.the.highlighting"));
224 for (final PsiExpression expr
: exprs
) {
225 InlineUtil
.tryToInlineArrayCreationForVarargs(expr
);
228 catch (IncorrectOperationException e
){
234 CommandProcessor
.getInstance().executeCommand(project
, new Runnable() {
236 ApplicationManager
.getApplication().runWriteAction(runnable
);
238 }, RefactoringBundle
.message("inline.command", localName
), null);
241 private static boolean checkRefsInAugmentedAssignment(final PsiElement
[] refsToInline
, final Project project
, final Editor editor
,
242 final String localName
) {
243 for(PsiElement element
: refsToInline
) {
244 if (element
.getParent() instanceof PsiAssignmentExpression
) {
245 PsiAssignmentExpression assignmentExpression
= (PsiAssignmentExpression
) element
.getParent();
246 if (element
== assignmentExpression
.getLExpression()) {
247 EditorColorsManager manager
= EditorColorsManager
.getInstance();
248 final TextAttributes writeAttributes
= manager
.getGlobalScheme().getAttributes(EditorColors
.WRITE_SEARCH_RESULT_ATTRIBUTES
);
249 HighlightManager
.getInstance(project
).addOccurrenceHighlights(editor
, new PsiElement
[]{element
}, writeAttributes
, true, null);
251 String message
= RefactoringBundle
.getCannotRefactorMessage(RefactoringBundle
.message("variable.is.accessed.for.writing", localName
));
252 CommonRefactoringUtil
.showErrorHint(project
, editor
, message
, REFACTORING_NAME
, HelpID
.INLINE_VARIABLE
);
253 WindowManager
.getInstance().getStatusBar(project
).setInfo(RefactoringBundle
.message("press.escape.to.remove.the.highlighting"));
261 private static boolean isSameDefinition(final PsiElement def
, final PsiExpression defToInline
) {
262 if (def
instanceof PsiLocalVariable
) return defToInline
.equals(((PsiLocalVariable
)def
).getInitializer());
263 final PsiElement parent
= def
.getParent();
264 return parent
instanceof PsiAssignmentExpression
&& defToInline
.equals(((PsiAssignmentExpression
)parent
).getRExpression());
267 private static boolean isInliningVariableInitializer(final PsiExpression defToInline
) {
268 return defToInline
.getParent() instanceof PsiVariable
;
272 private static PsiExpression
getDefToInline(final PsiLocalVariable local
,
273 final PsiElement refExpr
,
274 final PsiCodeBlock block
) {
275 if (refExpr
!= null) {
277 if (refExpr
instanceof PsiReferenceExpression
&& PsiUtil
.isAccessedForWriting((PsiExpression
) refExpr
)) {
281 final PsiElement
[] defs
= DefUseUtil
.getDefs(block
, local
, refExpr
);
282 if (defs
.length
== 1) {
290 if (def
instanceof PsiReferenceExpression
&& def
.getParent() instanceof PsiAssignmentExpression
) {
291 final PsiExpression rExpr
= ((PsiAssignmentExpression
)def
.getParent()).getRExpression();
292 if (rExpr
!= null) return rExpr
;
295 return local
.getInitializer();