update copyright
[fedora-idea.git] / java / java-impl / src / com / intellij / codeInsight / daemon / impl / quickfix / RemoveUnusedVariableFix.java
blobf178977c43b9a02c29c62f2bea7d598483011701
1 /*
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;
37 import java.util.*;
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;
49 @NotNull
50 public String getText() {
51 return QuickFixBundle.message(myVariable instanceof PsiField ? "remove.unused.field" : "remove.unused.variable",
52 myVariable.getName());
55 @NotNull
56 public String getFamilyName() {
57 return QuickFixBundle.message("remove.unused.variable.family");
60 public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
61 return
62 myVariable != null
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);
85 });
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};
92 try {
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) {
107 LOG.error(e);
110 final int deleteMode = showSideEffectsWarning(sideEffects, myVariable, editor, canCopeWithSideEffects[0]);
112 ApplicationManager.getApplication().runWriteAction(new Runnable() {
113 public void run() {
114 try {
115 deleteReferences(myVariable, references, deleteMode);
117 catch (IncorrectOperationException e) {
118 LOG.error(e);
124 public static int showSideEffectsWarning(List<PsiElement> sideEffects,
125 PsiVariable variable,
126 Editor editor,
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);
144 dialog.show();
145 return dialog.getExitCode();
148 private static int showSideEffectsWarning(List<PsiElement> sideEffects,
149 PsiVariable variable,
150 Editor editor,
151 boolean canCopeWithSideEffects) {
152 String text = sideEffects.isEmpty() ? "" : sideEffects.get(0).getText();
153 return showSideEffectsWarning(sideEffects, variable, editor, canCopeWithSideEffects, text, text);
158 * @param element
159 * @param variable
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()) {
175 return null;
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);
196 return true;
198 else {
199 if (deleteMode != SideEffectWarningDialog.CANCEL) {
200 deleteWholeStatement(element, factory);
202 return !sideEffectFound;
205 else if (element instanceof PsiExpressionStatement && deleteMode != SideEffectWarningDialog.CANCEL) {
206 element.delete();
207 break;
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) {
226 element.delete();
228 return true;
230 else {
231 if (deleteMode != SideEffectWarningDialog.CANCEL) {
232 if (element instanceof PsiField) {
233 ((PsiField)element).normalizeDeclaration();
235 element.delete();
237 return !sideEffectsFound;
240 element = element.getParent();
242 return true;
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) {
251 parent.delete();
253 else {
254 // replace with empty statement (to handle with 'if (..) i=0;' )
255 parent.replace(createStatementIfNeeded(null, factory, element));
258 else {
259 element.delete();
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)) {
269 return expression;
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);
295 return true;
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);
303 return true;
306 if (element instanceof PsiAssignmentExpression
307 && !(((PsiAssignmentExpression)element).getLExpression() instanceof PsiReferenceExpression
308 && ((PsiReferenceExpression)((PsiAssignmentExpression)element).getLExpression()).resolve() == variable)) {
309 sideEffects.add(element);
310 return true;
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>();
321 static {
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)) {
365 return true;
368 return false;
371 public boolean startInWriteAction() {
372 return false;