highlight globally unused groovy classes (IDEA-75803)
[idea/community.git] / java / java-impl / src / com / intellij / codeInsight / daemon / impl / PostHighlightingPass.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.codeInsight.daemon.impl;
17
18 import com.intellij.codeHighlighting.Pass;
19 import com.intellij.codeHighlighting.TextEditorHighlightingPass;
20 import com.intellij.codeInsight.CodeInsightSettings;
21 import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
22 import com.intellij.codeInsight.daemon.HighlightDisplayKey;
23 import com.intellij.codeInsight.daemon.ImplicitUsageProvider;
24 import com.intellij.codeInsight.daemon.JavaErrorMessages;
25 import com.intellij.codeInsight.daemon.impl.analysis.HighlightLevelUtil;
26 import com.intellij.codeInsight.daemon.impl.analysis.HighlightMessageUtil;
27 import com.intellij.codeInsight.daemon.impl.analysis.HighlightMethodUtil;
28 import com.intellij.codeInsight.daemon.impl.analysis.HighlightUtil;
29 import com.intellij.codeInsight.daemon.impl.quickfix.*;
30 import com.intellij.codeInsight.intention.EmptyIntentionAction;
31 import com.intellij.codeInsight.intention.IntentionAction;
32 import com.intellij.codeInsight.intention.IntentionManager;
33 import com.intellij.codeInspection.InspectionProfile;
34 import com.intellij.codeInspection.InspectionProfileEntry;
35 import com.intellij.codeInspection.InspectionsBundle;
36 import com.intellij.codeInspection.deadCode.UnusedDeclarationInspection;
37 import com.intellij.codeInspection.ex.GlobalInspectionToolWrapper;
38 import com.intellij.codeInspection.ex.InspectionManagerEx;
39 import com.intellij.codeInspection.ex.LocalInspectionToolWrapper;
40 import com.intellij.codeInspection.reference.UnusedDeclarationFixProvider;
41 import com.intellij.codeInspection.unusedImport.UnusedImportLocalInspection;
42 import com.intellij.codeInspection.unusedParameters.UnusedParametersInspection;
43 import com.intellij.codeInspection.unusedSymbol.UnusedSymbolLocalInspection;
44 import com.intellij.codeInspection.util.SpecialAnnotationsUtil;
45 import com.intellij.diagnostic.LogMessageEx;
46 import com.intellij.diagnostic.errordialog.Attachment;
47 import com.intellij.find.FindManager;
48 import com.intellij.find.findUsages.*;
49 import com.intellij.find.impl.FindManagerImpl;
50 import com.intellij.lang.Language;
51 import com.intellij.lang.annotation.HighlightSeverity;
52 import com.intellij.openapi.application.ApplicationManager;
53 import com.intellij.openapi.command.CommandProcessor;
54 import com.intellij.openapi.command.undo.UndoManager;
55 import com.intellij.openapi.diagnostic.Logger;
56 import com.intellij.openapi.editor.Document;
57 import com.intellij.openapi.editor.Editor;
58 import com.intellij.openapi.extensions.Extensions;
59 import com.intellij.openapi.progress.ProcessCanceledException;
60 import com.intellij.openapi.progress.ProgressIndicator;
61 import com.intellij.openapi.project.Project;
62 import com.intellij.openapi.roots.ProjectFileIndex;
63 import com.intellij.openapi.roots.ProjectRootManager;
64 import com.intellij.openapi.util.Comparing;
65 import com.intellij.openapi.util.TextRange;
66 import com.intellij.openapi.vfs.VirtualFile;
67 import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
68 import com.intellij.psi.*;
69 import com.intellij.psi.codeStyle.JavaCodeStyleManager;
70 import com.intellij.psi.impl.PsiClassImplUtil;
71 import com.intellij.psi.impl.source.PsiClassImpl;
72 import com.intellij.psi.impl.source.jsp.jspJava.JspxImportStatement;
73 import com.intellij.psi.jsp.JspFile;
74 import com.intellij.psi.jsp.JspSpiUtil;
75 import com.intellij.psi.search.GlobalSearchScope;
76 import com.intellij.psi.search.PsiSearchHelper;
77 import com.intellij.psi.search.SearchScope;
78 import com.intellij.psi.search.searches.OverridingMethodsSearch;
79 import com.intellij.psi.search.searches.SuperMethodsSearch;
80 import com.intellij.psi.util.PropertyUtil;
81 import com.intellij.psi.util.PsiUtilCore;
82 import com.intellij.refactoring.changeSignature.ChangeSignatureGestureDetector;
83 import com.intellij.util.Processor;
84 import gnu.trove.THashSet;
85 import gnu.trove.TObjectIntHashMap;
86 import org.jetbrains.annotations.NotNull;
87 import org.jetbrains.annotations.Nullable;
88 import org.jetbrains.annotations.PropertyKey;
89
90 import java.util.*;
91
92 public class PostHighlightingPass extends TextEditorHighlightingPass {
93   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.PostHighlightingPass");
94   private RefCountHolder myRefCountHolder;
95   private final PsiFile myFile;
96   @Nullable private final Editor myEditor;
97   private final int myStartOffset;
98   private final int myEndOffset;
99
100   private Collection<HighlightInfo> myHighlights;
101   private boolean myHasRedundantImports;
102   private final JavaCodeStyleManager myStyleManager;
103   private int myCurrentEntryIndex;
104   private boolean myHasMissortedImports;
105   private final ImplicitUsageProvider[] myImplicitUsageProviders;
106   private UnusedDeclarationInspection myDeadCodeInspection;
107   private UnusedSymbolLocalInspection myUnusedSymbolInspection;
108   private HighlightDisplayKey myUnusedSymbolKey;
109   private boolean myDeadCodeEnabled;
110   private boolean myInLibrary;
111   private HighlightDisplayKey myDeadCodeKey;
112   private HighlightInfoType myDeadCodeInfoType;
113   private UnusedParametersInspection myUnusedParametersInspection;
114
115   PostHighlightingPass(@NotNull Project project,
116                        @NotNull PsiFile file,
117                        @Nullable Editor editor,
118                        @NotNull Document document) {
119     super(project, document, true);
120     myFile = file;
121     myEditor = editor;
122     myStartOffset = 0;
123     myEndOffset = file.getTextLength();
124
125     myStyleManager = JavaCodeStyleManager.getInstance(myProject);
126     myCurrentEntryIndex = -1;
127
128     myImplicitUsageProviders = Extensions.getExtensions(ImplicitUsageProvider.EP_NAME);
129   }
130
131   @Override
132   public void doCollectInformation(final ProgressIndicator progress) {
133     DaemonCodeAnalyzer daemonCodeAnalyzer = DaemonCodeAnalyzer.getInstance(myProject);
134     final FileStatusMap fileStatusMap = ((DaemonCodeAnalyzerImpl)daemonCodeAnalyzer).getFileStatusMap();
135     final List<HighlightInfo> highlights = new ArrayList<HighlightInfo>();
136     final FileViewProvider viewProvider = myFile.getViewProvider();
137     final Set<Language> relevantLanguages = viewProvider.getLanguages();
138     final Set<PsiElement> elementSet = new THashSet<PsiElement>();
139     for (Language language : relevantLanguages) {
140       PsiElement psiRoot = viewProvider.getPsi(language);
141       if (!HighlightLevelUtil.shouldHighlight(psiRoot)) continue;
142       List<PsiElement> elements = CollectHighlightsUtil.getElementsInRange(psiRoot, myStartOffset, myEndOffset);
143       elementSet.addAll(elements);
144     }
145
146     ProjectFileIndex fileIndex = ProjectRootManager.getInstance(myProject).getFileIndex();
147     VirtualFile virtualFile = viewProvider.getVirtualFile();
148     myInLibrary = fileIndex.isInLibraryClasses(virtualFile) || fileIndex.isInLibrarySource(virtualFile);
149
150     myRefCountHolder = RefCountHolder.getInstance(myFile);
151     if (!myRefCountHolder.retrieveUnusedReferencesInfo(new Runnable() {
152       @Override
153       public void run() {
154         boolean errorFound = collectHighlights(elementSet, highlights, progress);
155         myHighlights = highlights;
156         if (errorFound) {
157           fileStatusMap.setErrorFoundFlag(myDocument, true);
158         }
159       }
160     })) {
161       // we must be sure GHP will restart
162       fileStatusMap.markFileScopeDirty(getDocument(), Pass.UPDATE_ALL);
163       GeneralHighlightingPass.cancelAndRestartDaemonLater(progress, myProject, this);
164     }
165   }
166
167   @Override
168   public List<HighlightInfo> getInfos() {
169     return myHighlights == null ? null : new ArrayList<HighlightInfo>(myHighlights);
170   }
171
172   @Override
173   public void doApplyInformationToEditor() {
174     if (myHighlights == null) return;
175     UpdateHighlightersUtil.setHighlightersToEditor(myProject, myDocument, myStartOffset, myEndOffset, myHighlights, getColorsScheme(), Pass.POST_UPDATE_ALL);
176     PostHighlightingPassFactory.markFileUpToDate(myFile);
177
178     Editor editor = myEditor;
179     if (editor != null && timeToOptimizeImports()) {
180       optimizeImportsOnTheFly(editor);
181     }
182   }
183
184   private void optimizeImportsOnTheFly(@NotNull final Editor editor) {
185     if (myHasRedundantImports || myHasMissortedImports) {
186       final OptimizeImportsFix optimizeImportsFix = new OptimizeImportsFix();
187       if (optimizeImportsFix.isAvailable(myProject, editor, myFile) && myFile.isWritable()) {
188         invokeOnTheFlyImportOptimizer(new Runnable() {
189           @Override
190           public void run() {
191             optimizeImportsFix.invoke(myProject, editor, myFile);
192           }
193         }, myFile, editor);
194       }
195     }
196   }
197
198   public static void invokeOnTheFlyImportOptimizer(@NotNull final Runnable runnable, @NotNull final PsiFile file, @NotNull final Editor editor) {
199     final long stamp = editor.getDocument().getModificationStamp();
200     ApplicationManager.getApplication().invokeLater(new Runnable() {
201       @Override
202       public void run() {
203         if (file.getProject().isDisposed() || editor.isDisposed() || editor.getDocument().getModificationStamp() != stamp) return;
204         //no need to optimize imports on the fly during undo/redo
205         final UndoManager undoManager = UndoManager.getInstance(editor.getProject());
206         if (undoManager.isUndoInProgress() || undoManager.isRedoInProgress()) return;
207         PsiDocumentManager.getInstance(file.getProject()).commitAllDocuments();
208         String beforeText = file.getText();
209         final long oldStamp = editor.getDocument().getModificationStamp();
210         CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() {
211           @Override
212           public void run() {
213             ApplicationManager.getApplication().runWriteAction(runnable);
214           }
215         });
216         if (oldStamp != editor.getDocument().getModificationStamp()) {
217           String afterText = file.getText();
218           if (Comparing.strEqual(beforeText, afterText)) {
219             LOG.error(LogMessageEx.createEvent("Import optimizer  hasn't optimized any imports", file.getViewProvider().getVirtualFile().getPath(),
220                                                new Attachment(file.getViewProvider().getVirtualFile())));
221           }
222         }
223       }
224     });
225   }
226
227   // returns true if error highlight was created
228   private boolean collectHighlights(@NotNull Collection<PsiElement> elements, @NotNull final List<HighlightInfo> result, @NotNull ProgressIndicator progress) throws ProcessCanceledException {
229     ApplicationManager.getApplication().assertReadAccessAllowed();
230
231     InspectionProfile profile = InspectionProjectProfileManager.getInstance(myProject).getInspectionProfile();
232     myUnusedSymbolKey = HighlightDisplayKey.find(UnusedSymbolLocalInspection.SHORT_NAME);
233     boolean unusedSymbolEnabled = profile.isToolEnabled(myUnusedSymbolKey, myFile);
234     HighlightDisplayKey unusedImportKey = HighlightDisplayKey.find(UnusedImportLocalInspection.SHORT_NAME);
235     boolean unusedImportEnabled = profile.isToolEnabled(unusedImportKey, myFile);
236     LocalInspectionToolWrapper unusedSymbolTool = (LocalInspectionToolWrapper)profile.getInspectionTool(UnusedSymbolLocalInspection.SHORT_NAME,
237                                                                                                         myFile);
238     myUnusedSymbolInspection = unusedSymbolTool == null ? null : (UnusedSymbolLocalInspection)unusedSymbolTool.getTool();
239     LOG.assertTrue(ApplicationManager.getApplication().isUnitTestMode() || myUnusedSymbolInspection != null);
240
241     myDeadCodeKey = HighlightDisplayKey.find(UnusedDeclarationInspection.SHORT_NAME);
242     myDeadCodeInspection = (UnusedDeclarationInspection)profile.getInspectionTool(UnusedDeclarationInspection.SHORT_NAME, myFile);
243     myDeadCodeEnabled = profile.isToolEnabled(myDeadCodeKey, myFile);
244
245     final InspectionProfileEntry inspectionTool = profile.getInspectionTool(UnusedParametersInspection.SHORT_NAME, myFile);
246     myUnusedParametersInspection = inspectionTool != null ?  (UnusedParametersInspection)((GlobalInspectionToolWrapper)inspectionTool).getTool() : null;
247     LOG.assertTrue(ApplicationManager.getApplication().isUnitTestMode() || myUnusedParametersInspection != null);
248     if (unusedImportEnabled && JspPsiUtil.isInJspFile(myFile)) {
249       final JspFile jspFile = JspPsiUtil.getJspFile(myFile);
250       if (jspFile != null) {
251         unusedImportEnabled = !JspSpiUtil.isIncludedOrIncludesSomething(jspFile);
252       }
253     }
254
255     myDeadCodeInfoType = myDeadCodeKey == null ? null : new HighlightInfoType.HighlightInfoTypeImpl(profile.getErrorLevel(myDeadCodeKey, myFile).getSeverity(), HighlightInfoType.UNUSED_SYMBOL.getAttributesKey());
256
257     boolean errorFound = false;
258     if (unusedSymbolEnabled) {
259       for (PsiElement element : elements) {
260         progress.checkCanceled();
261         if (element instanceof PsiIdentifier) {
262           PsiIdentifier identifier = (PsiIdentifier)element;
263           HighlightInfo info = processIdentifier(identifier, progress);
264           if (info != null) {
265             errorFound |= info.getSeverity() == HighlightSeverity.ERROR;
266             result.add(info);
267           }
268         }
269       }
270     }
271     if (unusedImportEnabled && myFile instanceof PsiJavaFile && HighlightLevelUtil.shouldHighlight(myFile)) {
272       PsiImportList importList = ((PsiJavaFile)myFile).getImportList();
273       if (importList != null) {
274         final PsiImportStatementBase[] imports = importList.getAllImportStatements();
275         for (PsiImportStatementBase statement : imports) {
276           progress.checkCanceled();
277           final HighlightInfo info = processImport(statement, unusedImportKey);
278           if (info != null) {
279             errorFound |= info.getSeverity() == HighlightSeverity.ERROR;
280             result.add(info);
281           }
282         }
283       }
284     }
285
286     return errorFound;
287   }
288
289   @Nullable
290   private HighlightInfo processIdentifier(PsiIdentifier identifier, ProgressIndicator progress) {
291     if (InspectionManagerEx.inspectionResultSuppressed(identifier, myUnusedSymbolInspection)) return null;
292     PsiElement parent = identifier.getParent();
293     if (PsiUtilCore.hasErrorElementChild(parent)) return null;
294
295     if (parent instanceof PsiLocalVariable && myUnusedSymbolInspection.LOCAL_VARIABLE) {
296       return processLocalVariable((PsiLocalVariable)parent, progress);
297     }
298     if (parent instanceof PsiField && myUnusedSymbolInspection.FIELD) {
299       return processField((PsiField)parent, identifier, progress);
300     }
301     if (parent instanceof PsiParameter && myUnusedSymbolInspection.PARAMETER) {
302       if (InspectionManagerEx.isSuppressed(identifier, UnusedParametersInspection.SHORT_NAME)) return null;
303       return processParameter((PsiParameter)parent, progress);
304     }
305     if (parent instanceof PsiMethod && myUnusedSymbolInspection.METHOD) {
306       return processMethod((PsiMethod)parent, progress);
307     }
308     if (parent instanceof PsiClass && myUnusedSymbolInspection.CLASS) {
309       return processClass((PsiClass)parent, progress);
310     }
311     return null;
312   }
313
314
315   @Nullable
316   private HighlightInfo processLocalVariable(PsiLocalVariable variable, ProgressIndicator progress) {
317     PsiIdentifier identifier = variable.getNameIdentifier();
318     if (identifier == null) return null;
319     if (isImplicitUsage(variable, progress)) return null;
320     if (!myRefCountHolder.isReferenced(variable)) {
321       String message = JavaErrorMessages.message("local.variable.is.never.used", identifier.getText());
322       HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL);
323       QuickFixAction.registerQuickFixAction(highlightInfo, new RemoveUnusedVariableFix(variable), myUnusedSymbolKey);
324       return highlightInfo;
325     }
326
327     boolean referenced = myRefCountHolder.isReferencedForRead(variable);
328     if (!referenced && !isImplicitRead(variable, progress)) {
329       String message = JavaErrorMessages.message("local.variable.is.not.used.for.reading", identifier.getText());
330       HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL);
331       QuickFixAction.registerQuickFixAction(highlightInfo, new RemoveUnusedVariableFix(variable), myUnusedSymbolKey);
332       return highlightInfo;
333     }
334
335     if (!variable.hasInitializer()) {
336       referenced = myRefCountHolder.isReferencedForWrite(variable);
337       if (!referenced && !isImplicitWrite(variable, progress)) {
338         String message = JavaErrorMessages.message("local.variable.is.not.assigned", identifier.getText());
339         final HighlightInfo unusedSymbolInfo = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL);
340         QuickFixAction.registerQuickFixAction(unusedSymbolInfo, new EmptyIntentionAction(UnusedSymbolLocalInspection.DISPLAY_NAME), myUnusedSymbolKey);
341         return unusedSymbolInfo;
342       }
343     }
344
345     return null;
346   }
347
348
349   private boolean isImplicitUsage(final PsiModifierListOwner element, ProgressIndicator progress) {
350     if (UnusedSymbolLocalInspection.isInjected(element)) return true;
351     for (ImplicitUsageProvider provider : myImplicitUsageProviders) {
352       progress.checkCanceled();
353       if (provider.isImplicitUsage(element)) {
354         return true;
355       }
356     }
357
358     return false;
359   }
360
361   private boolean isImplicitRead(final PsiVariable element, ProgressIndicator progress) {
362     for(ImplicitUsageProvider provider: myImplicitUsageProviders) {
363       progress.checkCanceled();
364       if (provider.isImplicitRead(element)) {
365         return true;
366       }
367     }
368     return UnusedSymbolLocalInspection.isInjected(element);
369   }
370
371   private boolean isImplicitWrite(final PsiVariable element, ProgressIndicator progress) {
372     for(ImplicitUsageProvider provider: myImplicitUsageProviders) {
373       progress.checkCanceled();
374       if (provider.isImplicitWrite(element)) {
375         return true;
376       }
377     }
378     return UnusedSymbolLocalInspection.isInjected(element);
379   }
380
381   public static HighlightInfo createUnusedSymbolInfo(PsiElement element, String message, final HighlightInfoType highlightInfoType) {
382     HighlightInfo info = HighlightInfo.createHighlightInfo(highlightInfoType, element, message);
383     UnusedDeclarationFixProvider[] fixProviders = Extensions.getExtensions(UnusedDeclarationFixProvider.EP_NAME);
384     for (UnusedDeclarationFixProvider provider : fixProviders) {
385       IntentionAction[] fixes = provider.getQuickFixes(element);
386       for (IntentionAction fix : fixes) {
387         QuickFixAction.registerQuickFixAction(info, fix);
388       }
389     }
390     return info;
391   }
392
393   @Nullable
394   private HighlightInfo processField(final PsiField field, final PsiIdentifier identifier, ProgressIndicator progress) {
395     if (field.hasModifierProperty(PsiModifier.PRIVATE)) {
396       if (!myRefCountHolder.isReferenced(field) && !isImplicitUsage(field, progress)) {
397         if (HighlightUtil.isSerializationImplicitlyUsedField(field)) {
398           return null;
399         }
400         String message = JavaErrorMessages.message("private.field.is.not.used", identifier.getText());
401
402         HighlightInfo highlightInfo = suggestionsToMakeFieldUsed(field, identifier, message);
403         if (!field.hasInitializer()) {
404           QuickFixAction.registerQuickFixAction(highlightInfo, HighlightMethodUtil.getFixRange(field), new CreateConstructorParameterFromFieldFix(field), null);
405         }
406         return highlightInfo;
407       }
408
409       final boolean readReferenced = myRefCountHolder.isReferencedForRead(field);
410       if (!readReferenced && !isImplicitRead(field, progress)) {
411         String message = JavaErrorMessages.message("private.field.is.not.used.for.reading", identifier.getText());
412         return suggestionsToMakeFieldUsed(field, identifier, message);
413       }
414
415       if (field.hasInitializer()) {
416         return null;
417       }
418       final boolean writeReferenced = myRefCountHolder.isReferencedForWrite(field);
419       if (!writeReferenced && !isImplicitWrite(field, progress)) {
420         String message = JavaErrorMessages.message("private.field.is.not.assigned", identifier.getText());
421         final HighlightInfo info = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL);
422
423         QuickFixAction.registerQuickFixAction(info, new CreateGetterOrSetterFix(false, true, field), myUnusedSymbolKey);
424         QuickFixAction.registerQuickFixAction(info, HighlightMethodUtil.getFixRange(field), new CreateConstructorParameterFromFieldFix(field), null);
425         SpecialAnnotationsUtil.createAddToSpecialAnnotationFixes(field, new Processor<String>() {
426           @Override
427           public boolean process(final String annoName) {
428             QuickFixAction.registerQuickFixAction(info, UnusedSymbolLocalInspection.createQuickFix(annoName, "fields", field.getProject()));
429             return true;
430           }
431         });
432         return info;
433       }
434     }
435     else if (isImplicitUsage(field, progress)) {
436       return null;
437     }
438     else if (!myRefCountHolder.isReferenced(field) && weAreSureThereAreNoUsages(field, progress)) {
439       if (field instanceof PsiEnumConstant && isEnumValuesMethodUsed(field, progress)) {
440         return null;
441       }
442       return formatUnusedSymbolHighlightInfo("field.is.not.used", field, "fields", myDeadCodeKey, myDeadCodeInfoType);
443     }
444     return null;
445   }
446
447   private HighlightInfo suggestionsToMakeFieldUsed(final PsiField field, final PsiIdentifier identifier, final String message) {
448     HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL);
449     QuickFixAction.registerQuickFixAction(highlightInfo, new RemoveUnusedVariableFix(field), myUnusedSymbolKey);
450     QuickFixAction.registerQuickFixAction(highlightInfo, new CreateGetterOrSetterFix(true, false, field), myUnusedSymbolKey);
451     QuickFixAction.registerQuickFixAction(highlightInfo, new CreateGetterOrSetterFix(false, true, field), myUnusedSymbolKey);
452     QuickFixAction.registerQuickFixAction(highlightInfo, new CreateGetterOrSetterFix(true, true, field), myUnusedSymbolKey);
453     return highlightInfo;
454   }
455
456   private static boolean isOverriddenOrOverrides(PsiMethod method) {
457     boolean overrides = SuperMethodsSearch.search(method, null, true, false).findFirst() != null;
458     return overrides || OverridingMethodsSearch.search(method).findFirst() != null;
459   }
460
461   @Nullable
462   private HighlightInfo processParameter(PsiParameter parameter, ProgressIndicator progress) {
463     PsiElement declarationScope = parameter.getDeclarationScope();
464     if (declarationScope instanceof PsiMethod) {
465       PsiMethod method = (PsiMethod)declarationScope;
466       if (PsiUtilCore.hasErrorElementChild(method)) return null;
467       if ((method.isConstructor() ||
468            method.hasModifierProperty(PsiModifier.PRIVATE) ||
469            method.hasModifierProperty(PsiModifier.STATIC) ||
470            !method.hasModifierProperty(PsiModifier.ABSTRACT) &&
471            myUnusedSymbolInspection.REPORT_PARAMETER_FOR_PUBLIC_METHODS &&
472            !isOverriddenOrOverrides(method)) &&
473           !method.hasModifierProperty(PsiModifier.NATIVE) &&
474           !HighlightMethodUtil.isSerializationRelatedMethod(method, method.getContainingClass()) &&
475           !PsiClassImplUtil.isMainMethod(method)) {
476         if (UnusedSymbolLocalInspection.isInjected(method)) return null;
477         HighlightInfo highlightInfo = checkUnusedParameter(parameter, progress);
478         if (highlightInfo != null) {
479           final ArrayList<IntentionAction> options = new ArrayList<IntentionAction>();
480           options.addAll(IntentionManager.getInstance().getStandardIntentionOptions(myUnusedSymbolKey, myFile));
481           if (myUnusedParametersInspection != null) {
482             Collections.addAll(options, myUnusedParametersInspection.getSuppressActions(parameter));
483           }
484           //need suppress from Unused Parameters but settings from Unused Symbol
485           QuickFixAction.registerQuickFixAction(highlightInfo, new RemoveUnusedParameterFix(parameter),
486                                                 options, HighlightDisplayKey.getDisplayNameByKey(myUnusedSymbolKey));
487           return highlightInfo;
488         }
489       }
490     }
491     else if (declarationScope instanceof PsiForeachStatement) {
492       HighlightInfo highlightInfo = checkUnusedParameter(parameter, progress);
493       if (highlightInfo != null) {
494         QuickFixAction.registerQuickFixAction(highlightInfo, new EmptyIntentionAction(UnusedSymbolLocalInspection.DISPLAY_NAME), myUnusedSymbolKey);
495         return highlightInfo;
496       }
497     }
498
499     return null;
500   }
501
502   @Nullable
503   private HighlightInfo checkUnusedParameter(final PsiParameter parameter, ProgressIndicator progress) {
504     if (!myRefCountHolder.isReferenced(parameter) && !isImplicitUsage(parameter, progress)) {
505       PsiIdentifier identifier = parameter.getNameIdentifier();
506       assert identifier != null;
507       String message = JavaErrorMessages.message("parameter.is.not.used", identifier.getText());
508       return createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL);
509     }
510     return null;
511   }
512
513   @Nullable
514   private HighlightInfo processMethod(final PsiMethod method, ProgressIndicator progress) {
515     boolean isPrivate = method.hasModifierProperty(PsiModifier.PRIVATE);
516     PsiClass containingClass = method.getContainingClass();
517     if (isMethodReferenced(method, progress, isPrivate, containingClass)) return null;
518     HighlightInfoType highlightInfoType;
519     HighlightDisplayKey highlightDisplayKey;
520     String key;
521     if (isPrivate) {
522       highlightInfoType = HighlightInfoType.UNUSED_SYMBOL;
523       highlightDisplayKey = myUnusedSymbolKey;
524       key = method.isConstructor() ? "private.constructor.is.not.used" : "private.method.is.not.used";
525     }
526     else {
527       highlightInfoType = myDeadCodeInfoType;
528       highlightDisplayKey = myDeadCodeKey;
529       key = method.isConstructor() ? "constructor.is.not.used" : "method.is.not.used";
530     }
531     String symbolName = HighlightMessageUtil.getSymbolName(method, PsiSubstitutor.EMPTY);
532     String message = JavaErrorMessages.message(key, symbolName);
533     PsiIdentifier identifier = method.getNameIdentifier();
534     final HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, highlightInfoType);
535     QuickFixAction.registerQuickFixAction(highlightInfo, new SafeDeleteFix(method), highlightDisplayKey);
536     SpecialAnnotationsUtil.createAddToSpecialAnnotationFixes(method, new Processor<String>() {
537       @Override
538       public boolean process(final String annoName) {
539         QuickFixAction.registerQuickFixAction(highlightInfo, UnusedSymbolLocalInspection.createQuickFix(annoName, "methods", method.getProject()));
540         return true;
541       }
542     });
543     if (method.getReturnType() != null || containingClass != null && Comparing.strEqual(containingClass.getName(), method.getName())) {
544       //ignore methods with deleted return types as they are always marked as unused without any reason
545       ChangeSignatureGestureDetector.getInstance(myProject).dismissForElement(method);
546     }
547     return highlightInfo;
548   }
549
550   private boolean isMethodReferenced(PsiMethod method, ProgressIndicator progress, boolean aPrivate, PsiClass containingClass) {
551     if (myRefCountHolder.isReferenced(method)) return true;
552
553     if (HighlightMethodUtil.isSerializationRelatedMethod(method, containingClass)) return true;
554     if (aPrivate) {
555       if (isIntentionalPrivateConstructor(method, containingClass)) {
556         return true;
557       }
558       if (isImplicitUsage(method, progress)) {
559         return true;
560       }
561     }
562     else {
563       //class maybe used in some weird way, e.g. from XML, therefore the only constructor is used too
564       if (containingClass != null && method.isConstructor()
565           && containingClass.getConstructors().length == 1
566           && isClassUnused(containingClass, progress) == USED) {
567         return true;
568       }
569       if (isImplicitUsage(method, progress)) return true;
570
571       if (method.findSuperMethods().length != 0) {
572         return true;
573       }
574       if (!weAreSureThereAreNoUsages(method, progress)) {
575         return true;
576       }
577     }
578     return false;
579   }
580
581   private boolean weAreSureThereAreNoUsages(PsiMember member, ProgressIndicator progress) {
582     if (myInLibrary) return false;
583     if (!myDeadCodeEnabled) return false;
584     if (myDeadCodeInspection.isEntryPoint(member)) return false;
585
586     return isGloballyUnused(member, progress, myFile, member.getName());
587   }
588
589   public static boolean isGloballyUnused(PsiMember member, ProgressIndicator progress, @Nullable PsiFile fileToIgnoreOccurrencesIn, String name) {
590     if (name == null) return false;
591     SearchScope useScope = member.getUseScope();
592     if (!(useScope instanceof GlobalSearchScope)) return false;
593     GlobalSearchScope scope = (GlobalSearchScope)useScope;
594     // some classes may have references from within XML outside dependent modules, e.g. our actions
595     Project project = member.getProject();
596     if (member instanceof PsiClass) scope = GlobalSearchScope.projectScope(project).uniteWith(scope);
597
598     PsiSearchHelper.SearchCostResult cheapEnough = PsiSearchHelper.SERVICE.getInstance(project).isCheapEnoughToSearch(name, scope, fileToIgnoreOccurrencesIn, progress);
599     if (cheapEnough == PsiSearchHelper.SearchCostResult.TOO_MANY_OCCURRENCES) return false;
600
601     //search usages if it cheap
602     //if count is 0 there is no usages since we've called myRefCountHolder.isReferenced() before
603     if (cheapEnough == PsiSearchHelper.SearchCostResult.ZERO_OCCURRENCES) {
604       if (!canBeReferencedViaWeirdNames(member)) return true;
605     }
606     FindUsagesManager findUsagesManager = ((FindManagerImpl)FindManager.getInstance(project)).getFindUsagesManager();
607     FindUsagesHandler handler = new JavaFindUsagesHandler(member, new JavaFindUsagesHandlerFactory(project));
608     FindUsagesOptions findUsagesOptions = handler.getFindUsagesOptions();
609     findUsagesOptions.searchScope = scope;
610     return !findUsagesManager.isUsed(member, findUsagesOptions);
611   }
612
613   private boolean isEnumValuesMethodUsed(PsiMember member, ProgressIndicator progress) {
614     final PsiClassImpl containingClass = (PsiClassImpl)member.getContainingClass();
615     if (containingClass == null) return true;
616     final PsiMethod valuesMethod = containingClass.getValuesMethod();
617     if (valuesMethod == null) return true;
618     boolean isPrivate = valuesMethod.hasModifierProperty(PsiModifier.PRIVATE);
619     return isMethodReferenced(valuesMethod, progress, isPrivate, containingClass);
620   }
621
622   private static boolean canBeReferencedViaWeirdNames(PsiMember member) {
623     if (member instanceof PsiClass) return false;
624     PsiFile containingFile = member.getContainingFile();
625     if (!(containingFile instanceof PsiJavaFile)) return true;  // Groovy field can be referenced from Java by getter
626     if (member instanceof PsiField) return false;  //Java field cannot be referenced by anything but its name
627     if (member instanceof PsiMethod) {
628       return PropertyUtil.isSimplePropertyAccessor((PsiMethod)member);  //Java accessors can be referenced by field name from Groovy
629     }
630     return false;
631   }
632
633   @Nullable
634   private HighlightInfo processClass(PsiClass aClass, ProgressIndicator progress) {
635     int usage = isClassUnused(aClass, progress);
636     if (usage == USED) return null;
637
638     String pattern;
639     HighlightDisplayKey highlightDisplayKey;
640     HighlightInfoType highlightInfoType;
641     if (aClass.getContainingClass() != null && aClass.hasModifierProperty(PsiModifier.PRIVATE)) {
642       pattern = aClass.isInterface()
643                        ? "private.inner.interface.is.not.used"
644                        : "private.inner.class.is.not.used";
645       highlightDisplayKey = myUnusedSymbolKey;
646       highlightInfoType = HighlightInfoType.UNUSED_SYMBOL;
647     }
648     else if (aClass.getParent() instanceof PsiDeclarationStatement) { // local class
649       pattern = "local.class.is.not.used";
650       highlightDisplayKey = myUnusedSymbolKey;
651       highlightInfoType = HighlightInfoType.UNUSED_SYMBOL;
652     }
653     else if (aClass instanceof PsiTypeParameter) {
654       pattern = "type.parameter.is.not.used";
655       highlightDisplayKey = myUnusedSymbolKey;
656       highlightInfoType = HighlightInfoType.UNUSED_SYMBOL;
657     }
658     else {
659       pattern = "class.is.not.used";
660       highlightDisplayKey = myDeadCodeKey;
661       highlightInfoType = myDeadCodeInfoType;
662     }
663     return formatUnusedSymbolHighlightInfo(pattern, aClass, "classes", highlightDisplayKey, highlightInfoType);
664   }
665
666   private static final int USED = 1;
667   private static final int UNUSED_LOCALLY = 2;
668   private static final int UNUSED_GLOBALLY = 3;
669   private final TObjectIntHashMap<PsiClass> unusedClassCache = new TObjectIntHashMap<PsiClass>();
670   private int isClassUnused(PsiClass aClass, ProgressIndicator progress) {
671     if (aClass == null) return USED;
672     int result = unusedClassCache.get(aClass);
673     if (result == 0) {
674       result = isReallyUnused(aClass, progress);
675       unusedClassCache.put(aClass, result);
676     }
677     return result;
678   }
679
680   private int isReallyUnused(PsiClass aClass, ProgressIndicator progress) {
681     if (isImplicitUsage(aClass, progress) || myRefCountHolder.isReferenced(aClass)) return USED;
682     if (aClass.getContainingClass() != null && aClass.hasModifierProperty(PsiModifier.PRIVATE) ||
683            aClass.getParent() instanceof PsiDeclarationStatement ||
684            aClass instanceof PsiTypeParameter) return UNUSED_LOCALLY;
685     if (weAreSureThereAreNoUsages(aClass, progress)) return UNUSED_GLOBALLY;
686     return USED;
687   }
688
689   private static HighlightInfo formatUnusedSymbolHighlightInfo(@PropertyKey(resourceBundle = JavaErrorMessages.BUNDLE) String pattern,
690                                                                final PsiNameIdentifierOwner aClass,
691                                                                final String element,
692                                                                final HighlightDisplayKey highlightDisplayKey,
693                                                                final HighlightInfoType highlightInfoType) {
694     String symbolName = aClass.getName();
695     String message = JavaErrorMessages.message(pattern, symbolName);
696     PsiElement identifier = aClass.getNameIdentifier();
697     final HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, highlightInfoType);
698     QuickFixAction.registerQuickFixAction(highlightInfo, new SafeDeleteFix(aClass), highlightDisplayKey);
699     SpecialAnnotationsUtil.createAddToSpecialAnnotationFixes((PsiModifierListOwner)aClass, new Processor<String>() {
700       @Override
701       public boolean process(final String annoName) {
702         QuickFixAction.registerQuickFixAction(highlightInfo, UnusedSymbolLocalInspection.createQuickFix(annoName, element, aClass.getProject()));
703         return true;
704       }
705     });
706     return highlightInfo;
707   }
708
709   @Nullable
710   private HighlightInfo processImport(PsiImportStatementBase importStatement, HighlightDisplayKey unusedImportKey) {
711     // jsp include directive hack
712     if (importStatement instanceof JspxImportStatement && ((JspxImportStatement)importStatement).isForeignFileImport()) return null;
713
714     if (PsiUtilCore.hasErrorElementChild(importStatement)) return null;
715
716     boolean isRedundant = myRefCountHolder.isRedundant(importStatement);
717     if (!isRedundant && !(importStatement instanceof PsiImportStaticStatement)) {
718       //check import from same package
719       String packageName = ((PsiClassOwner)importStatement.getContainingFile()).getPackageName();
720       PsiJavaCodeReferenceElement reference = importStatement.getImportReference();
721       PsiElement resolved = reference == null ? null : reference.resolve();
722       if (resolved instanceof PsiPackage) {
723         isRedundant = packageName.equals(((PsiPackage)resolved).getQualifiedName());
724       }
725       else if (resolved instanceof PsiClass && !importStatement.isOnDemand()) {
726         String qName = ((PsiClass)resolved).getQualifiedName();
727         if (qName != null) {
728           String name = ((PsiClass)resolved).getName();
729           isRedundant = qName.equals(packageName + '.' + name);
730         }
731       }
732     }
733
734     if (isRedundant) {
735       return registerRedundantImport(importStatement, unusedImportKey);
736     }
737
738     int entryIndex = myStyleManager.findEntryIndex(importStatement);
739     if (entryIndex < myCurrentEntryIndex) {
740       myHasMissortedImports = true;
741     }
742     myCurrentEntryIndex = entryIndex;
743
744     return null;
745   }
746
747   private HighlightInfo registerRedundantImport(PsiImportStatementBase importStatement, HighlightDisplayKey unusedImportKey) {
748     HighlightInfo info = HighlightInfo.createHighlightInfo(JavaHighlightInfoTypes.UNUSED_IMPORT, importStatement, InspectionsBundle.message("unused.import.statement"));
749
750     QuickFixAction.registerQuickFixAction(info, new OptimizeImportsFix(), unusedImportKey);
751     QuickFixAction.registerQuickFixAction(info, new EnableOptimizeImportsOnTheFlyFix(), unusedImportKey);
752     myHasRedundantImports = true;
753     return info;
754   }
755
756   private boolean timeToOptimizeImports() {
757     if (!CodeInsightSettings.getInstance().OPTIMIZE_IMPORTS_ON_THE_FLY) return false;
758
759     DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(myProject);
760     PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(myDocument);
761     // dont optimize out imports in JSP since it can be included in other JSP
762     if (file == null || !codeAnalyzer.isHighlightingAvailable(file) || !(file instanceof PsiJavaFile) || file instanceof JspFile) return false;
763
764     if (!codeAnalyzer.isErrorAnalyzingFinished(file)) return false;
765     boolean errors = containsErrorsPreventingOptimize(file);
766
767     return !errors && codeAnalyzer.canChangeFileSilently(myFile);
768   }
769
770   private boolean containsErrorsPreventingOptimize(PsiFile file) {
771     // ignore unresolved imports errors
772     PsiImportList importList = ((PsiJavaFile)file).getImportList();
773     final TextRange importsRange = importList == null ? TextRange.EMPTY_RANGE : importList.getTextRange();
774     boolean hasErrorsExceptUnresolvedImports = !DaemonCodeAnalyzerImpl.processHighlights(myDocument, myProject, HighlightSeverity.ERROR, 0, myDocument.getTextLength(), new Processor<HighlightInfo>() {
775       @Override
776       public boolean process(HighlightInfo error) {
777         int infoStart = error.getActualStartOffset();
778         int infoEnd = error.getActualEndOffset();
779
780         return importsRange.containsRange(infoStart,infoEnd) && error.type.equals(HighlightInfoType.WRONG_REF);
781       }
782     });
783
784     return hasErrorsExceptUnresolvedImports;
785   }
786
787   private static boolean isIntentionalPrivateConstructor(PsiMethod method, PsiClass containingClass) {
788     return method.isConstructor() &&
789            method.getParameterList().getParametersCount() == 0 &&
790            containingClass != null &&
791            containingClass.getConstructors().length == 1;
792   }
793 }