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
.codeInsight
.daemon
.impl
.quickfix
;
18 import com
.intellij
.codeInsight
.CodeInsightUtilBase
;
19 import com
.intellij
.codeInsight
.daemon
.QuickFixBundle
;
20 import com
.intellij
.codeInsight
.highlighting
.HighlightManager
;
21 import com
.intellij
.codeInsight
.intention
.IntentionAction
;
22 import com
.intellij
.openapi
.application
.ApplicationManager
;
23 import com
.intellij
.openapi
.diagnostic
.Logger
;
24 import com
.intellij
.openapi
.editor
.Editor
;
25 import com
.intellij
.openapi
.editor
.colors
.EditorColors
;
26 import com
.intellij
.openapi
.editor
.colors
.EditorColorsManager
;
27 import com
.intellij
.openapi
.editor
.markup
.TextAttributes
;
28 import com
.intellij
.openapi
.project
.Project
;
29 import com
.intellij
.psi
.*;
30 import com
.intellij
.psi
.util
.InheritanceUtil
;
31 import com
.intellij
.psi
.util
.PsiUtil
;
32 import com
.intellij
.util
.IncorrectOperationException
;
33 import gnu
.trove
.THashSet
;
34 import org
.jetbrains
.annotations
.NonNls
;
35 import org
.jetbrains
.annotations
.NotNull
;
39 public class RemoveUnusedVariableFix
implements IntentionAction
{
40 private static final Logger LOG
= Logger
.getInstance("#com.intellij.codeInsight.daemon.impl.quickfix.RemoveUnusedVariableFix");
41 private final PsiVariable myVariable
;
42 @NonNls private static final String JAVA_LANG_PCKG
= "java.lang";
43 @NonNls private static final String JAVA_IO_PCKG
= "java.io";
45 public RemoveUnusedVariableFix(PsiVariable variable
) {
46 myVariable
= variable
;
50 public String
getText() {
51 return QuickFixBundle
.message(myVariable
instanceof PsiField ?
"remove.unused.field" : "remove.unused.variable",
52 myVariable
.getName());
56 public String
getFamilyName() {
57 return QuickFixBundle
.message("remove.unused.variable.family");
60 public boolean isAvailable(@NotNull Project project
, Editor editor
, PsiFile file
) {
63 && myVariable
.isValid()
64 && myVariable
.getManager().isInProject(myVariable
)
68 public void invoke(@NotNull Project project
, Editor editor
, PsiFile file
) {
69 if (!CodeInsightUtilBase
.prepareFileForWrite(myVariable
.getContainingFile())) return;
70 removeVariableAndReferencingStatements(editor
);
73 private static void deleteReferences(PsiVariable variable
, List
<PsiElement
> references
, int mode
) throws IncorrectOperationException
{
74 for (PsiElement expression
: references
) {
75 processUsage(expression
, variable
, null, mode
);
79 private static void collectReferences(@NotNull PsiElement context
, final PsiVariable variable
, final List
<PsiElement
> references
) {
80 context
.accept(new JavaRecursiveElementWalkingVisitor() {
81 @Override public void visitReferenceExpression(PsiReferenceExpression expression
) {
82 if (expression
.resolve() == variable
) references
.add(expression
);
83 super.visitReferenceExpression(expression
);
88 private void removeVariableAndReferencingStatements(Editor editor
) {
89 final List
<PsiElement
> references
= new ArrayList
<PsiElement
>();
90 final List
<PsiElement
> sideEffects
= new ArrayList
<PsiElement
>();
91 final boolean[] canCopeWithSideEffects
= {true};
93 PsiElement context
= myVariable
instanceof PsiField ?
((PsiField
)myVariable
).getContainingClass() : PsiUtil
.getVariableCodeBlock(myVariable
, null);
94 if (context
!= null) {
95 collectReferences(context
, myVariable
, references
);
97 // do not forget to delete variable declaration
98 references
.add(myVariable
);
99 // check for side effects
100 for (PsiElement element
: references
) {
101 Boolean result
= processUsage(element
, myVariable
, sideEffects
, SideEffectWarningDialog
.CANCEL
);
102 if (result
== null) return;
103 canCopeWithSideEffects
[0] &= result
;
106 catch (IncorrectOperationException e
) {
110 final int deleteMode
= showSideEffectsWarning(sideEffects
, myVariable
, editor
, canCopeWithSideEffects
[0]);
112 ApplicationManager
.getApplication().runWriteAction(new Runnable() {
115 deleteReferences(myVariable
, references
, deleteMode
);
117 catch (IncorrectOperationException e
) {
124 public static int showSideEffectsWarning(List
<PsiElement
> sideEffects
,
125 PsiVariable variable
,
127 boolean canCopeWithSideEffects
,
128 @NonNls String beforeText
,
129 @NonNls String afterText
) {
130 if (sideEffects
.isEmpty()) return SideEffectWarningDialog
.DELETE_ALL
;
131 if (ApplicationManager
.getApplication().isUnitTestMode()) {
132 return canCopeWithSideEffects
133 ? SideEffectWarningDialog
.MAKE_STATEMENT
134 : SideEffectWarningDialog
.DELETE_ALL
;
136 Project project
= variable
.getProject();
137 HighlightManager highlightManager
= HighlightManager
.getInstance(project
);
138 PsiElement
[] elements
= sideEffects
.toArray(new PsiElement
[sideEffects
.size()]);
139 EditorColorsManager manager
= EditorColorsManager
.getInstance();
140 TextAttributes attributes
= manager
.getGlobalScheme().getAttributes(EditorColors
.SEARCH_RESULT_ATTRIBUTES
);
141 highlightManager
.addOccurrenceHighlights(editor
, elements
, attributes
, true, null);
143 SideEffectWarningDialog dialog
= new SideEffectWarningDialog(project
, false, variable
, beforeText
, afterText
, canCopeWithSideEffects
);
145 return dialog
.getExitCode();
148 private static int showSideEffectsWarning(List
<PsiElement
> sideEffects
,
149 PsiVariable variable
,
151 boolean canCopeWithSideEffects
) {
152 String text
= sideEffects
.isEmpty() ?
"" : sideEffects
.get(0).getText();
153 return showSideEffectsWarning(sideEffects
, variable
, editor
, canCopeWithSideEffects
, text
, text
);
160 * @param sideEffects if null, delete usages, otherwise collect side effects
161 * @return true if there are at least one unrecoverable side effect found, false if no side effects,
162 * null if read usage found (may happen if interval between fix creation in invoke() call was long enough)
163 * @throws IncorrectOperationException
165 private static Boolean
processUsage(PsiElement element
, PsiVariable variable
, List
<PsiElement
> sideEffects
, int deleteMode
)
166 throws IncorrectOperationException
{
167 if (!element
.isValid()) return null;
168 PsiElementFactory factory
= JavaPsiFacade
.getInstance(variable
.getProject()).getElementFactory();
169 while (element
!= null) {
170 if (element
instanceof PsiAssignmentExpression
) {
171 PsiAssignmentExpression expression
= (PsiAssignmentExpression
)element
;
172 PsiExpression lExpression
= expression
.getLExpression();
173 // there should not be read access to the variable, otherwise it is not unused
174 if (!(lExpression
instanceof PsiReferenceExpression
) || variable
!= ((PsiReferenceExpression
)lExpression
).resolve()) {
177 PsiExpression rExpression
= expression
.getRExpression();
178 rExpression
= PsiUtil
.deparenthesizeExpression(rExpression
);
179 if (rExpression
== null) return true;
180 // replace assignment with expression and resimplify
181 boolean sideEffectFound
= checkSideEffects(rExpression
, variable
, sideEffects
);
182 if (!(element
.getParent() instanceof PsiExpressionStatement
) || PsiUtil
.isStatement(rExpression
)) {
183 if (deleteMode
== SideEffectWarningDialog
.MAKE_STATEMENT
||
184 deleteMode
== SideEffectWarningDialog
.DELETE_ALL
&& !(element
.getParent() instanceof PsiExpressionStatement
)) {
185 element
= replaceElementWithExpression(rExpression
, factory
, element
);
186 while (element
.getParent() instanceof PsiParenthesizedExpression
) {
187 element
= element
.getParent().replace(element
);
189 List
<PsiElement
> references
= new ArrayList
<PsiElement
>();
190 collectReferences(element
, variable
, references
);
191 deleteReferences(variable
, references
, deleteMode
);
193 else if (deleteMode
== SideEffectWarningDialog
.DELETE_ALL
) {
194 deleteWholeStatement(element
, factory
);
199 if (deleteMode
!= SideEffectWarningDialog
.CANCEL
) {
200 deleteWholeStatement(element
, factory
);
202 return !sideEffectFound
;
205 else if (element
instanceof PsiExpressionStatement
&& deleteMode
!= SideEffectWarningDialog
.CANCEL
) {
209 else if (element
instanceof PsiVariable
&& element
== variable
) {
210 PsiExpression expression
= variable
.getInitializer();
211 if (expression
!= null) {
212 expression
= PsiUtil
.deparenthesizeExpression(expression
);
214 boolean sideEffectsFound
= checkSideEffects(expression
, variable
, sideEffects
);
215 if (expression
!= null && PsiUtil
.isStatement(expression
) && variable
instanceof PsiLocalVariable
217 !(variable
.getParent() instanceof PsiDeclarationStatement
&&
218 ((PsiDeclarationStatement
)variable
.getParent()).getDeclaredElements().length
> 1)) {
219 if (deleteMode
== SideEffectWarningDialog
.MAKE_STATEMENT
) {
220 element
= element
.replace(createStatementIfNeeded(expression
, factory
, element
));
221 List
<PsiElement
> references
= new ArrayList
<PsiElement
>();
222 collectReferences(element
, variable
, references
);
223 deleteReferences(variable
, references
, deleteMode
);
225 else if (deleteMode
== SideEffectWarningDialog
.DELETE_ALL
) {
231 if (deleteMode
!= SideEffectWarningDialog
.CANCEL
) {
232 if (element
instanceof PsiField
) {
233 ((PsiField
)element
).normalizeDeclaration();
237 return !sideEffectsFound
;
240 element
= element
.getParent();
245 private static void deleteWholeStatement(PsiElement element
, PsiElementFactory factory
)
246 throws IncorrectOperationException
{
247 // just delete it altogether
248 if (element
.getParent() instanceof PsiExpressionStatement
) {
249 PsiExpressionStatement parent
= (PsiExpressionStatement
)element
.getParent();
250 if (parent
.getParent() instanceof PsiCodeBlock
) {
254 // replace with empty statement (to handle with 'if (..) i=0;' )
255 parent
.replace(createStatementIfNeeded(null, factory
, element
));
263 private static PsiElement
createStatementIfNeeded(PsiExpression expression
,
264 PsiElementFactory factory
,
265 PsiElement element
) throws IncorrectOperationException
{
266 // if element used in expression, subexpression will do
267 if (!(element
.getParent() instanceof PsiExpressionStatement
) &&
268 !(element
.getParent() instanceof PsiDeclarationStatement
)) {
271 return factory
.createStatementFromText((expression
== null ?
"" : expression
.getText()) + ";", null);
274 private static PsiElement
replaceElementWithExpression(PsiExpression expression
,
275 PsiElementFactory factory
,
276 PsiElement element
) throws IncorrectOperationException
{
277 PsiElement elementToReplace
= element
;
278 PsiElement expressionToReplaceWith
= expression
;
279 if (element
.getParent() instanceof PsiExpressionStatement
) {
280 elementToReplace
= element
.getParent();
281 expressionToReplaceWith
=
282 factory
.createStatementFromText((expression
== null ?
"" : expression
.getText()) + ";", null);
284 else if (element
.getParent() instanceof PsiDeclarationStatement
) {
285 expressionToReplaceWith
=
286 factory
.createStatementFromText((expression
== null ?
"" : expression
.getText()) + ";", null);
288 return elementToReplace
.replace(expressionToReplaceWith
);
291 public static boolean checkSideEffects(PsiElement element
, PsiVariable variable
, List
<PsiElement
> sideEffects
) {
292 if (sideEffects
== null || element
== null) return false;
293 if (element
instanceof PsiMethodCallExpression
) {
294 sideEffects
.add(element
);
297 if (element
instanceof PsiNewExpression
) {
298 PsiNewExpression newExpression
= (PsiNewExpression
)element
;
299 if (newExpression
.getArrayDimensions().length
== 0
300 && newExpression
.getArrayInitializer() == null
301 && !isSideEffectFreeConstructor(newExpression
)) {
302 sideEffects
.add(element
);
306 if (element
instanceof PsiAssignmentExpression
307 && !(((PsiAssignmentExpression
)element
).getLExpression() instanceof PsiReferenceExpression
308 && ((PsiReferenceExpression
)((PsiAssignmentExpression
)element
).getLExpression()).resolve() == variable
)) {
309 sideEffects
.add(element
);
312 PsiElement
[] children
= element
.getChildren();
314 for (PsiElement child
: children
) {
315 checkSideEffects(child
, variable
, sideEffects
);
317 return !sideEffects
.isEmpty();
320 private static final Set
<String
> ourSideEffectFreeClasses
= new THashSet
<String
>();
322 ourSideEffectFreeClasses
.add(Object
.class.getName());
323 ourSideEffectFreeClasses
.add(Short
.class.getName());
324 ourSideEffectFreeClasses
.add(Character
.class.getName());
325 ourSideEffectFreeClasses
.add(Byte
.class.getName());
326 ourSideEffectFreeClasses
.add(Integer
.class.getName());
327 ourSideEffectFreeClasses
.add(Long
.class.getName());
328 ourSideEffectFreeClasses
.add(Float
.class.getName());
329 ourSideEffectFreeClasses
.add(Double
.class.getName());
330 ourSideEffectFreeClasses
.add(String
.class.getName());
331 ourSideEffectFreeClasses
.add(StringBuffer
.class.getName());
332 ourSideEffectFreeClasses
.add(Boolean
.class.getName());
334 ourSideEffectFreeClasses
.add(ArrayList
.class.getName());
335 ourSideEffectFreeClasses
.add(Date
.class.getName());
336 ourSideEffectFreeClasses
.add(HashMap
.class.getName());
337 ourSideEffectFreeClasses
.add(HashSet
.class.getName());
338 ourSideEffectFreeClasses
.add(Hashtable
.class.getName());
339 ourSideEffectFreeClasses
.add(LinkedHashMap
.class.getName());
340 ourSideEffectFreeClasses
.add(LinkedHashSet
.class.getName());
341 ourSideEffectFreeClasses
.add(LinkedList
.class.getName());
342 ourSideEffectFreeClasses
.add(Stack
.class.getName());
343 ourSideEffectFreeClasses
.add(TreeMap
.class.getName());
344 ourSideEffectFreeClasses
.add(TreeSet
.class.getName());
345 ourSideEffectFreeClasses
.add(Vector
.class.getName());
346 ourSideEffectFreeClasses
.add(WeakHashMap
.class.getName());
349 private static boolean isSideEffectFreeConstructor(PsiNewExpression newExpression
) {
350 PsiJavaCodeReferenceElement classReference
= newExpression
.getClassReference();
351 PsiClass aClass
= classReference
== null ?
null : (PsiClass
)classReference
.resolve();
352 String qualifiedName
= aClass
== null ?
null : aClass
.getQualifiedName();
353 if (qualifiedName
== null) return false;
354 if (ourSideEffectFreeClasses
.contains(qualifiedName
)) return true;
356 PsiFile file
= aClass
.getContainingFile();
357 PsiDirectory directory
= file
.getContainingDirectory();
358 PsiPackage classPackage
= JavaDirectoryService
.getInstance().getPackage(directory
);
359 String packageName
= classPackage
.getQualifiedName();
361 // all Throwable descendants from java.lang are side effects free
362 if (JAVA_LANG_PCKG
.equals(packageName
) || JAVA_IO_PCKG
.equals(packageName
)) {
363 PsiClass throwableClass
= JavaPsiFacade
.getInstance(aClass
.getProject()).findClass("java.lang.Throwable", aClass
.getResolveScope());
364 if (throwableClass
!= null && InheritanceUtil
.isInheritorOrSelf(aClass
, throwableClass
, true)) {
371 public boolean startInWriteAction() {