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