5a429287899606d3858c578ef68662e77f6d09e8
[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   private 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       return formatUnusedSymbolHighlightInfo("field.is.not.used", field, "fields", myDeadCodeKey, myDeadCodeInfoType);
440     }
441     return null;
442   }
443
444   private HighlightInfo suggestionsToMakeFieldUsed(final PsiField field, final PsiIdentifier identifier, final String message) {
445     HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL);
446     QuickFixAction.registerQuickFixAction(highlightInfo, new RemoveUnusedVariableFix(field), myUnusedSymbolKey);
447     QuickFixAction.registerQuickFixAction(highlightInfo, new CreateGetterOrSetterFix(true, false, field), myUnusedSymbolKey);
448     QuickFixAction.registerQuickFixAction(highlightInfo, new CreateGetterOrSetterFix(false, true, field), myUnusedSymbolKey);
449     QuickFixAction.registerQuickFixAction(highlightInfo, new CreateGetterOrSetterFix(true, true, field), myUnusedSymbolKey);
450     return highlightInfo;
451   }
452
453   private static boolean isOverriddenOrOverrides(PsiMethod method) {
454     boolean overrides = SuperMethodsSearch.search(method, null, true, false).findFirst() != null;
455     return overrides || OverridingMethodsSearch.search(method).findFirst() != null;
456   }
457
458   @Nullable
459   private HighlightInfo processParameter(PsiParameter parameter, ProgressIndicator progress) {
460     PsiElement declarationScope = parameter.getDeclarationScope();
461     if (declarationScope instanceof PsiMethod) {
462       PsiMethod method = (PsiMethod)declarationScope;
463       if (PsiUtilCore.hasErrorElementChild(method)) return null;
464       if ((method.isConstructor() ||
465            method.hasModifierProperty(PsiModifier.PRIVATE) ||
466            method.hasModifierProperty(PsiModifier.STATIC) ||
467            !method.hasModifierProperty(PsiModifier.ABSTRACT) &&
468            myUnusedSymbolInspection.REPORT_PARAMETER_FOR_PUBLIC_METHODS &&
469            !isOverriddenOrOverrides(method)) &&
470           !method.hasModifierProperty(PsiModifier.NATIVE) &&
471           !HighlightMethodUtil.isSerializationRelatedMethod(method, method.getContainingClass()) &&
472           !PsiClassImplUtil.isMainMethod(method)) {
473         if (UnusedSymbolLocalInspection.isInjected(method)) return null;
474         HighlightInfo highlightInfo = checkUnusedParameter(parameter, progress);
475         if (highlightInfo != null) {
476           final ArrayList<IntentionAction> options = new ArrayList<IntentionAction>();
477           options.addAll(IntentionManager.getInstance().getStandardIntentionOptions(myUnusedSymbolKey, myFile));
478           if (myUnusedParametersInspection != null) {
479             Collections.addAll(options, myUnusedParametersInspection.getSuppressActions(parameter));
480           }
481           //need suppress from Unused Parameters but settings from Unused Symbol
482           QuickFixAction.registerQuickFixAction(highlightInfo, new RemoveUnusedParameterFix(parameter),
483                                                 options, HighlightDisplayKey.getDisplayNameByKey(myUnusedSymbolKey));
484           return highlightInfo;
485         }
486       }
487     }
488     else if (declarationScope instanceof PsiForeachStatement) {
489       HighlightInfo highlightInfo = checkUnusedParameter(parameter, progress);
490       if (highlightInfo != null) {
491         QuickFixAction.registerQuickFixAction(highlightInfo, new EmptyIntentionAction(UnusedSymbolLocalInspection.DISPLAY_NAME), myUnusedSymbolKey);
492         return highlightInfo;
493       }
494     }
495
496     return null;
497   }
498
499   @Nullable
500   private HighlightInfo checkUnusedParameter(final PsiParameter parameter, ProgressIndicator progress) {
501     if (!myRefCountHolder.isReferenced(parameter) && !isImplicitUsage(parameter, progress)) {
502       PsiIdentifier identifier = parameter.getNameIdentifier();
503       assert identifier != null;
504       String message = JavaErrorMessages.message("parameter.is.not.used", identifier.getText());
505       return createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL);
506     }
507     return null;
508   }
509
510   @Nullable
511   private HighlightInfo processMethod(final PsiMethod method, ProgressIndicator progress) {
512     boolean isPrivate = method.hasModifierProperty(PsiModifier.PRIVATE);
513     PsiClass containingClass = method.getContainingClass();
514     if (isMethodReferenced(method, progress, isPrivate, containingClass)) return null;
515     HighlightInfoType highlightInfoType;
516     HighlightDisplayKey highlightDisplayKey;
517     String key;
518     if (isPrivate) {
519       highlightInfoType = HighlightInfoType.UNUSED_SYMBOL;
520       highlightDisplayKey = myUnusedSymbolKey;
521       key = method.isConstructor() ? "private.constructor.is.not.used" : "private.method.is.not.used";
522     }
523     else {
524       highlightInfoType = myDeadCodeInfoType;
525       highlightDisplayKey = myDeadCodeKey;
526       key = method.isConstructor() ? "constructor.is.not.used" : "method.is.not.used";
527     }
528     String symbolName = HighlightMessageUtil.getSymbolName(method, PsiSubstitutor.EMPTY);
529     String message = JavaErrorMessages.message(key, symbolName);
530     PsiIdentifier identifier = method.getNameIdentifier();
531     final HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, highlightInfoType);
532     QuickFixAction.registerQuickFixAction(highlightInfo, new SafeDeleteFix(method), highlightDisplayKey);
533     SpecialAnnotationsUtil.createAddToSpecialAnnotationFixes(method, new Processor<String>() {
534       @Override
535       public boolean process(final String annoName) {
536         QuickFixAction.registerQuickFixAction(highlightInfo, UnusedSymbolLocalInspection.createQuickFix(annoName, "methods", method.getProject()));
537         return true;
538       }
539     });
540     if (method.getReturnType() != null || containingClass != null && Comparing.strEqual(containingClass.getName(), method.getName())) {
541       //ignore methods with deleted return types as they are always marked as unused without any reason
542       ChangeSignatureGestureDetector.getInstance(myProject).dismissForElement(method);
543     }
544     return highlightInfo;
545   }
546
547   private boolean isMethodReferenced(PsiMethod method, ProgressIndicator progress, boolean aPrivate, PsiClass containingClass) {
548     if (myRefCountHolder.isReferenced(method)) return true;
549
550     if (HighlightMethodUtil.isSerializationRelatedMethod(method, containingClass)) return true;
551     if (aPrivate) {
552       if (isIntentionalPrivateConstructor(method, containingClass)) {
553         return true;
554       }
555       if (isImplicitUsage(method, progress)) {
556         return true;
557       }
558     }
559     else {
560       //class maybe used in some weird way, e.g. from XML, therefore the only constructor is used too
561       if (containingClass != null && method.isConstructor()
562           && containingClass.getConstructors().length == 1
563           && isClassUnused(containingClass, progress) == USED) {
564         return true;
565       }
566       if (isImplicitUsage(method, progress)) return true;
567
568       if (method.findSuperMethods().length != 0) {
569         return true;
570       }
571       if (!weAreSureThereAreNoUsages(method, progress)) {
572         return true;
573       }
574     }
575     return false;
576   }
577
578   private boolean weAreSureThereAreNoUsages(PsiMember member, ProgressIndicator progress) {
579     if (myInLibrary) return false;
580     if (!myDeadCodeEnabled) return false;
581     if (myDeadCodeInspection.isEntryPoint(member)) return false;
582
583     String name = member.getName();
584     if (name == null) return false;
585     SearchScope useScope = member.getUseScope();
586     if (!(useScope instanceof GlobalSearchScope)) return false;
587     GlobalSearchScope scope = (GlobalSearchScope)useScope;
588     // some classes may have references from within XML outside dependent modules, e.g. our actions
589     if (member instanceof PsiClass) scope = GlobalSearchScope.projectScope(myProject).uniteWith(scope);
590
591     PsiSearchHelper.SearchCostResult cheapEnough = PsiSearchHelper.SERVICE.getInstance(myFile.getProject())
592         .isCheapEnoughToSearch(name, scope, myFile, progress);
593     if (cheapEnough == PsiSearchHelper.SearchCostResult.TOO_MANY_OCCURRENCES) return false;
594
595     //search usages if it cheap
596     //if count is 0 there is no usages since we've called myRefCountHolder.isReferenced() before
597     if (cheapEnough == PsiSearchHelper.SearchCostResult.ZERO_OCCURRENCES) {
598       if (member instanceof PsiEnumConstant) {
599         return !isEnumValuesMethodUsed(member, progress);
600       }
601       if (!canBeReferencedViaWeirdNames(member)) return true;
602     }
603     FindUsagesManager findUsagesManager = ((FindManagerImpl)FindManager.getInstance(myProject)).getFindUsagesManager();
604     FindUsagesOptions findUsagesOptions;
605     if (member instanceof PsiClass) {
606       findUsagesOptions = new JavaClassFindUsagesOptions(myProject);
607     }
608     else if (member instanceof PsiMethod) {
609       findUsagesOptions = new JavaMethodFindUsagesOptions(myProject);
610     }
611     else if (member instanceof PsiField) {
612       findUsagesOptions = new JavaVariableFindUsagesOptions(myProject);
613     }
614     else {
615       LOG.error("unknown member: " + member);
616       return false;
617     }
618     findUsagesOptions.searchScope = scope;
619
620     boolean used = findUsagesManager.isUsed(member, findUsagesOptions);
621
622     if (!used && member instanceof PsiEnumConstant) {
623       return !isEnumValuesMethodUsed(member, progress);
624     }
625     return !used;
626   }
627
628   private boolean isEnumValuesMethodUsed(PsiMember member, ProgressIndicator progress) {
629     final PsiClassImpl containingClass = (PsiClassImpl)member.getContainingClass();
630     if (containingClass == null) return true;
631     final PsiMethod valuesMethod = containingClass.getValuesMethod();
632     if (valuesMethod == null) return true;
633     boolean isPrivate = valuesMethod.hasModifierProperty(PsiModifier.PRIVATE);
634     return isMethodReferenced(valuesMethod, progress, isPrivate, containingClass);
635   }
636
637   private static boolean canBeReferencedViaWeirdNames(PsiMember member) {
638     if (member instanceof PsiClass) return false;
639     PsiFile containingFile = member.getContainingFile();
640     if (!(containingFile instanceof PsiJavaFile)) return true;  // Groovy field can be referenced from Java by getter
641     if (member instanceof PsiField) return false;  //Java field cannot be referenced by anything but its name
642     if (member instanceof PsiMethod) {
643       return PropertyUtil.isSimplePropertyAccessor((PsiMethod)member);  //Java accessors can be referenced by field name from Groovy
644     }
645     return false;
646   }
647
648   @Nullable
649   private HighlightInfo processClass(PsiClass aClass, ProgressIndicator progress) {
650     int usage = isClassUnused(aClass, progress);
651     if (usage == USED) return null;
652
653     String pattern;
654     HighlightDisplayKey highlightDisplayKey;
655     HighlightInfoType highlightInfoType;
656     if (aClass.getContainingClass() != null && aClass.hasModifierProperty(PsiModifier.PRIVATE)) {
657       pattern = aClass.isInterface()
658                        ? "private.inner.interface.is.not.used"
659                        : "private.inner.class.is.not.used";
660       highlightDisplayKey = myUnusedSymbolKey;
661       highlightInfoType = HighlightInfoType.UNUSED_SYMBOL;
662     }
663     else if (aClass.getParent() instanceof PsiDeclarationStatement) { // local class
664       pattern = "local.class.is.not.used";
665       highlightDisplayKey = myUnusedSymbolKey;
666       highlightInfoType = HighlightInfoType.UNUSED_SYMBOL;
667     }
668     else if (aClass instanceof PsiTypeParameter) {
669       pattern = "type.parameter.is.not.used";
670       highlightDisplayKey = myUnusedSymbolKey;
671       highlightInfoType = HighlightInfoType.UNUSED_SYMBOL;
672     }
673     else {
674       pattern = "class.is.not.used";
675       highlightDisplayKey = myDeadCodeKey;
676       highlightInfoType = myDeadCodeInfoType;
677     }
678     return formatUnusedSymbolHighlightInfo(pattern, aClass, "classes", highlightDisplayKey, highlightInfoType);
679   }
680
681   private static final int USED = 1;
682   private static final int UNUSED_LOCALLY = 2;
683   private static final int UNUSED_GLOBALLY = 3;
684   private final TObjectIntHashMap<PsiClass> unusedClassCache = new TObjectIntHashMap<PsiClass>();
685   private int isClassUnused(PsiClass aClass, ProgressIndicator progress) {
686     if (aClass == null) return USED;
687     int result = unusedClassCache.get(aClass);
688     if (result == 0) {
689       result = isReallyUnused(aClass, progress);
690       unusedClassCache.put(aClass, result);
691     }
692     return result;
693   }
694
695   private int isReallyUnused(PsiClass aClass, ProgressIndicator progress) {
696     if (isImplicitUsage(aClass, progress) || myRefCountHolder.isReferenced(aClass)) return USED;
697     if (aClass.getContainingClass() != null && aClass.hasModifierProperty(PsiModifier.PRIVATE) ||
698            aClass.getParent() instanceof PsiDeclarationStatement ||
699            aClass instanceof PsiTypeParameter) return UNUSED_LOCALLY;
700     if (weAreSureThereAreNoUsages(aClass, progress)) return UNUSED_GLOBALLY;
701     return USED;
702   }
703
704   private static HighlightInfo formatUnusedSymbolHighlightInfo(@PropertyKey(resourceBundle = JavaErrorMessages.BUNDLE) String pattern,
705                                                                final PsiNameIdentifierOwner aClass,
706                                                                final String element,
707                                                                final HighlightDisplayKey highlightDisplayKey,
708                                                                final HighlightInfoType highlightInfoType) {
709     String symbolName = aClass.getName();
710     String message = JavaErrorMessages.message(pattern, symbolName);
711     PsiElement identifier = aClass.getNameIdentifier();
712     final HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, highlightInfoType);
713     QuickFixAction.registerQuickFixAction(highlightInfo, new SafeDeleteFix(aClass), highlightDisplayKey);
714     SpecialAnnotationsUtil.createAddToSpecialAnnotationFixes((PsiModifierListOwner)aClass, new Processor<String>() {
715       @Override
716       public boolean process(final String annoName) {
717         QuickFixAction.registerQuickFixAction(highlightInfo, UnusedSymbolLocalInspection.createQuickFix(annoName, element, aClass.getProject()));
718         return true;
719       }
720     });
721     return highlightInfo;
722   }
723
724   @Nullable
725   private HighlightInfo processImport(PsiImportStatementBase importStatement, HighlightDisplayKey unusedImportKey) {
726     // jsp include directive hack
727     if (importStatement instanceof JspxImportStatement && ((JspxImportStatement)importStatement).isForeignFileImport()) return null;
728
729     if (PsiUtilCore.hasErrorElementChild(importStatement)) return null;
730
731     boolean isRedundant = myRefCountHolder.isRedundant(importStatement);
732     if (!isRedundant && !(importStatement instanceof PsiImportStaticStatement)) {
733       //check import from same package
734       String packageName = ((PsiClassOwner)importStatement.getContainingFile()).getPackageName();
735       PsiJavaCodeReferenceElement reference = importStatement.getImportReference();
736       PsiElement resolved = reference == null ? null : reference.resolve();
737       if (resolved instanceof PsiPackage) {
738         isRedundant = packageName.equals(((PsiPackage)resolved).getQualifiedName());
739       }
740       else if (resolved instanceof PsiClass && !importStatement.isOnDemand()) {
741         String qName = ((PsiClass)resolved).getQualifiedName();
742         if (qName != null) {
743           String name = ((PsiClass)resolved).getName();
744           isRedundant = qName.equals(packageName + '.' + name);
745         }
746       }
747     }
748
749     if (isRedundant) {
750       return registerRedundantImport(importStatement, unusedImportKey);
751     }
752
753     int entryIndex = myStyleManager.findEntryIndex(importStatement);
754     if (entryIndex < myCurrentEntryIndex) {
755       myHasMissortedImports = true;
756     }
757     myCurrentEntryIndex = entryIndex;
758
759     return null;
760   }
761
762   private HighlightInfo registerRedundantImport(PsiImportStatementBase importStatement, HighlightDisplayKey unusedImportKey) {
763     HighlightInfo info = HighlightInfo.createHighlightInfo(JavaHighlightInfoTypes.UNUSED_IMPORT, importStatement, InspectionsBundle.message("unused.import.statement"));
764
765     QuickFixAction.registerQuickFixAction(info, new OptimizeImportsFix(), unusedImportKey);
766     QuickFixAction.registerQuickFixAction(info, new EnableOptimizeImportsOnTheFlyFix(), unusedImportKey);
767     myHasRedundantImports = true;
768     return info;
769   }
770
771   private boolean timeToOptimizeImports() {
772     if (!CodeInsightSettings.getInstance().OPTIMIZE_IMPORTS_ON_THE_FLY) return false;
773
774     DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(myProject);
775     PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(myDocument);
776     // dont optimize out imports in JSP since it can be included in other JSP
777     if (file == null || !codeAnalyzer.isHighlightingAvailable(file) || !(file instanceof PsiJavaFile) || file instanceof JspFile) return false;
778
779     if (!codeAnalyzer.isErrorAnalyzingFinished(file)) return false;
780     boolean errors = containsErrorsPreventingOptimize(file);
781
782     return !errors && codeAnalyzer.canChangeFileSilently(myFile);
783   }
784
785   private boolean containsErrorsPreventingOptimize(PsiFile file) {
786     // ignore unresolved imports errors
787     PsiImportList importList = ((PsiJavaFile)file).getImportList();
788     final TextRange importsRange = importList == null ? TextRange.EMPTY_RANGE : importList.getTextRange();
789     boolean hasErrorsExceptUnresolvedImports = !DaemonCodeAnalyzerImpl.processHighlights(myDocument, myProject, HighlightSeverity.ERROR, 0, myDocument.getTextLength(), new Processor<HighlightInfo>() {
790       @Override
791       public boolean process(HighlightInfo error) {
792         int infoStart = error.getActualStartOffset();
793         int infoEnd = error.getActualEndOffset();
794
795         return importsRange.containsRange(infoStart,infoEnd) && error.type.equals(HighlightInfoType.WRONG_REF);
796       }
797     });
798
799     return hasErrorsExceptUnresolvedImports;
800   }
801
802   private static boolean isIntentionalPrivateConstructor(PsiMethod method, PsiClass containingClass) {
803     return method.isConstructor() &&
804            method.getParameterList().getParametersCount() == 0 &&
805            containingClass != null &&
806            containingClass.getConstructors().length == 1;
807   }
808 }