do not highlight method unused if referenced via overridden
[idea/community.git] / java / java-analysis-impl / src / com / intellij / codeInsight / daemon / impl / PostHighlightingPass.java
1 /*
2  * Copyright 2000-2014 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.codeInsight.daemon.HighlightDisplayKey;
20 import com.intellij.codeInsight.daemon.ImplicitUsageProvider;
21 import com.intellij.codeInsight.daemon.JavaErrorMessages;
22 import com.intellij.codeInsight.daemon.ProblemHighlightFilter;
23 import com.intellij.codeInsight.daemon.impl.analysis.*;
24 import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixAction;
25 import com.intellij.codeInsight.intention.EmptyIntentionAction;
26 import com.intellij.codeInsight.intention.IntentionAction;
27 import com.intellij.codeInsight.intention.QuickFixFactory;
28 import com.intellij.codeInspection.InspectionProfile;
29 import com.intellij.codeInspection.InspectionsBundle;
30 import com.intellij.codeInspection.SuppressionUtil;
31 import com.intellij.codeInspection.deadCode.UnusedDeclarationInspection;
32 import com.intellij.codeInspection.ex.EntryPointsManagerBase;
33 import com.intellij.codeInspection.reference.UnusedDeclarationFixProvider;
34 import com.intellij.codeInspection.unusedImport.UnusedImportLocalInspection;
35 import com.intellij.codeInspection.unusedSymbol.UnusedSymbolLocalInspectionBase;
36 import com.intellij.codeInspection.util.SpecialAnnotationsUtilBase;
37 import com.intellij.find.findUsages.*;
38 import com.intellij.lang.Language;
39 import com.intellij.lang.annotation.HighlightSeverity;
40 import com.intellij.openapi.application.ApplicationManager;
41 import com.intellij.openapi.diagnostic.Logger;
42 import com.intellij.openapi.editor.Document;
43 import com.intellij.openapi.editor.Editor;
44 import com.intellij.openapi.extensions.Extensions;
45 import com.intellij.openapi.progress.ProcessCanceledException;
46 import com.intellij.openapi.progress.ProgressIndicator;
47 import com.intellij.openapi.project.Project;
48 import com.intellij.openapi.roots.ProjectFileIndex;
49 import com.intellij.openapi.roots.ProjectRootManager;
50 import com.intellij.openapi.util.Key;
51 import com.intellij.openapi.vfs.VirtualFile;
52 import com.intellij.pom.PomNamedTarget;
53 import com.intellij.pom.java.LanguageLevel;
54 import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
55 import com.intellij.psi.*;
56 import com.intellij.psi.codeStyle.JavaCodeStyleManager;
57 import com.intellij.psi.impl.PsiClassImplUtil;
58 import com.intellij.psi.impl.source.PsiClassImpl;
59 import com.intellij.psi.search.GlobalSearchScope;
60 import com.intellij.psi.search.PsiNonJavaFileReferenceProcessor;
61 import com.intellij.psi.search.PsiSearchHelper;
62 import com.intellij.psi.search.SearchScope;
63 import com.intellij.psi.search.searches.OverridingMethodsSearch;
64 import com.intellij.psi.search.searches.SuperMethodsSearch;
65 import com.intellij.psi.util.PropertyUtil;
66 import com.intellij.psi.util.PsiModificationTracker;
67 import com.intellij.psi.util.PsiUtil;
68 import com.intellij.psi.util.PsiUtilCore;
69 import com.intellij.usageView.UsageInfo;
70 import com.intellij.util.Processor;
71 import com.intellij.util.containers.Predicate;
72 import gnu.trove.THashSet;
73 import org.jetbrains.annotations.NotNull;
74 import org.jetbrains.annotations.Nullable;
75 import org.jetbrains.annotations.PropertyKey;
76
77 import java.util.*;
78
79 public class PostHighlightingPass extends ProgressableTextEditorHighlightingPass {
80   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.PostHighlightingPass");
81   private static final Key<Long> LAST_POST_PASS_TIMESTAMP = Key.create("LAST_POST_PASS_TIMESTAMP");
82   private final LanguageLevel myLanguageLevel;
83   private RefCountHolder myRefCountHolder;
84   private final PsiFile myFile;
85   @Nullable private final Editor myEditor;
86   @NotNull private final Predicate<PsiElement> myIsEntryPointPredicate;
87   private final int myStartOffset;
88   private final int myEndOffset;
89
90   private Collection<HighlightInfo> myHighlights;
91   private boolean myHasRedundantImports;
92   private int myCurrentEntryIndex;
93   private boolean myHasMissortedImports;
94   private static final ImplicitUsageProvider[] ourImplicitUsageProviders = Extensions.getExtensions(ImplicitUsageProvider.EP_NAME);
95   private UnusedSymbolLocalInspectionBase myUnusedSymbolInspection;
96   private HighlightDisplayKey myUnusedSymbolKey;
97   private boolean myInLibrary;
98   private HighlightDisplayKey myDeadCodeKey;
99   private HighlightInfoType myDeadCodeInfoType;
100
101   public PostHighlightingPass(@NotNull Project project,
102                               @NotNull PsiFile file,
103                               @Nullable Editor editor,
104                               @NotNull Document document,
105                               @NotNull HighlightInfoProcessor highlightInfoProcessor,
106                               @NotNull Predicate<PsiElement> isEntryPoint) {
107     super(project, document, "Unused symbols", file, editor, file.getTextRange(), true, highlightInfoProcessor);
108     myFile = file;
109     myEditor = editor;
110     myIsEntryPointPredicate = isEntryPoint;
111     myStartOffset = 0;
112     myEndOffset = file.getTextLength();
113
114     myCurrentEntryIndex = -1;
115     myLanguageLevel = PsiUtil.getLanguageLevel(file);
116   }
117
118   static boolean isUpToDate(@NotNull PsiFile file) {
119     Long lastStamp = file.getUserData(LAST_POST_PASS_TIMESTAMP);
120     long currentStamp = PsiModificationTracker.SERVICE.getInstance(file.getProject()).getModificationCount();
121     return lastStamp != null && lastStamp == currentStamp || !ProblemHighlightFilter.shouldHighlightFile(file);
122   }
123
124   private static void markFileUpToDate(@NotNull PsiFile file) {
125     long lastStamp = PsiModificationTracker.SERVICE.getInstance(file.getProject()).getModificationCount();
126     file.putUserData(LAST_POST_PASS_TIMESTAMP, lastStamp);
127   }
128
129   private static boolean isInjected(@NotNull Project project, @NotNull PsiModifierListOwner modifierListOwner) {
130     return EntryPointsManagerBase.getInstance(project).isEntryPoint(modifierListOwner);
131   }
132
133   @Override
134   protected void collectInformationWithProgress(@NotNull final ProgressIndicator progress) {
135     DaemonCodeAnalyzerEx daemonCodeAnalyzer = DaemonCodeAnalyzerEx.getInstanceEx(myProject);
136     final FileStatusMap fileStatusMap = daemonCodeAnalyzer.getFileStatusMap();
137     final List<HighlightInfo> highlights = new ArrayList<HighlightInfo>();
138     final FileViewProvider viewProvider = myFile.getViewProvider();
139     final Set<Language> relevantLanguages = viewProvider.getLanguages();
140     final Set<PsiElement> elementSet = new THashSet<PsiElement>();
141     for (Language language : relevantLanguages) {
142       PsiElement psiRoot = viewProvider.getPsi(language);
143       if (!HighlightingLevelManager.getInstance(myProject).shouldHighlight(psiRoot)) continue;
144       List<PsiElement> elements = CollectHighlightsUtil.getElementsInRange(psiRoot, myStartOffset, myEndOffset);
145       elementSet.addAll(elements);
146     }
147
148     ProjectFileIndex fileIndex = ProjectRootManager.getInstance(myProject).getFileIndex();
149     VirtualFile virtualFile = viewProvider.getVirtualFile();
150     myInLibrary = fileIndex.isInLibraryClasses(virtualFile) || fileIndex.isInLibrarySource(virtualFile);
151
152     myRefCountHolder = RefCountHolder.endUsing(myFile, progress);
153     boolean r = true;
154     if (myRefCountHolder == null || !(r=myRefCountHolder.retrieveUnusedReferencesInfo(progress, new Runnable() {
155       @Override
156       public void run() {
157         boolean errorFound = collectHighlights(elementSet, highlights, progress);
158         myHighlights = highlights;
159         if (errorFound) {
160           fileStatusMap.setErrorFoundFlag(myDocument, true);
161         }
162       }
163     }))) {
164       // we must be sure GHP will restart
165       FileStatusMap.log("myRefCountHolder: ", myRefCountHolder, "; retrieved: ", r);
166       fileStatusMap.markFileScopeDirty(getDocument(), Pass.UPDATE_ALL);
167       GeneralHighlightingPass.cancelAndRestartDaemonLater(progress, myProject, this);
168     }
169   }
170
171   @NotNull
172   @Override
173   public List<HighlightInfo> getInfos() {
174     Collection<HighlightInfo> infos = myHighlights;
175     return infos == null ? Collections.<HighlightInfo>emptyList() : new ArrayList<HighlightInfo>(infos);
176   }
177
178   @Override
179   protected void applyInformationWithProgress() {
180     if (myHighlights == null) return;
181     UpdateHighlightersUtil.setHighlightersToEditor(myProject, myDocument, myStartOffset, myEndOffset, myHighlights, getColorsScheme(), Pass.POST_UPDATE_ALL);
182     markFileUpToDate(myFile);
183
184     Editor editor = myEditor;
185     if (editor != null) {
186       optimizeImportsOnTheFly(editor);
187     }
188   }
189
190   private void optimizeImportsOnTheFly(@NotNull final Editor editor) {
191     if (myHasRedundantImports || myHasMissortedImports) {
192       IntentionAction optimizeImportsFix = QuickFixFactory.getInstance().createOptimizeImportsFix(true);
193       if (optimizeImportsFix.isAvailable(myProject, editor, myFile) && myFile.isWritable()) {
194         optimizeImportsFix.invoke(myProject, editor, myFile);
195       }
196     }
197   }
198
199   // returns true if error highlight was created
200   private boolean collectHighlights(@NotNull Collection<PsiElement> elements,
201                                     @NotNull final List<HighlightInfo> result,
202                                     @NotNull ProgressIndicator progress) throws ProcessCanceledException {
203     ApplicationManager.getApplication().assertReadAccessAllowed();
204
205     InspectionProfile profile = InspectionProjectProfileManager.getInstance(myProject).getInspectionProfile();
206     myUnusedSymbolKey = HighlightDisplayKey.find(UnusedSymbolLocalInspectionBase.SHORT_NAME);
207     boolean unusedSymbolEnabled = profile.isToolEnabled(myUnusedSymbolKey, myFile);
208     myUnusedSymbolInspection = (UnusedSymbolLocalInspectionBase)profile.getUnwrappedTool(UnusedSymbolLocalInspectionBase.SHORT_NAME, myFile);
209     LOG.assertTrue(ApplicationManager.getApplication().isUnitTestMode() || myUnusedSymbolInspection != null);
210
211     myDeadCodeKey = HighlightDisplayKey.find(UnusedDeclarationInspection.SHORT_NAME);
212
213     HighlightDisplayKey unusedImportKey = HighlightDisplayKey.find(UnusedImportLocalInspection.SHORT_NAME);
214
215     myDeadCodeInfoType = myDeadCodeKey == null
216                          ? HighlightInfoType.UNUSED_SYMBOL
217                          : new HighlightInfoType.HighlightInfoTypeImpl(profile.getErrorLevel(myDeadCodeKey, myFile).getSeverity(),
218                                                                        HighlightInfoType.UNUSED_SYMBOL.getAttributesKey());
219
220     GlobalUsageHelper helper = new GlobalUsageHelper() {
221       @Override
222       public boolean shouldCheckUsages(@NotNull PsiMember member) {
223         return !myInLibrary && !myIsEntryPointPredicate.apply(member);
224       }
225
226       @Override
227       public boolean isCurrentFileAlreadyChecked() {
228         return true;
229       }
230
231       @Override
232       public boolean isLocallyUsed(@NotNull PsiNamedElement member) {
233         return myRefCountHolder.isReferenced(member);
234       }
235     };
236
237     boolean errorFound = false;
238     if (unusedSymbolEnabled) {
239       for (PsiElement element : elements) {
240         progress.checkCanceled();
241         if (element instanceof PsiIdentifier) {
242           PsiIdentifier identifier = (PsiIdentifier)element;
243           HighlightInfo info = processIdentifier(identifier, progress, helper);
244           if (info != null) {
245             errorFound |= info.getSeverity() == HighlightSeverity.ERROR;
246             result.add(info);
247           }
248         }
249       }
250     }
251     if (isUnusedImportEnabled(unusedImportKey)) {
252       PsiImportList importList = ((PsiJavaFile)myFile).getImportList();
253       if (importList != null) {
254         final PsiImportStatementBase[] imports = importList.getAllImportStatements();
255         for (PsiImportStatementBase statement : imports) {
256           progress.checkCanceled();
257           final HighlightInfo info = processImport(statement, unusedImportKey);
258           if (info != null) {
259             errorFound |= info.getSeverity() == HighlightSeverity.ERROR;
260             result.add(info);
261           }
262         }
263       }
264     }
265
266     return errorFound;
267   }
268
269   protected boolean isUnusedImportEnabled(HighlightDisplayKey unusedImportKey) {
270     InspectionProfile profile = InspectionProjectProfileManager.getInstance(myProject).getInspectionProfile();
271     if (!profile.isToolEnabled(unusedImportKey, myFile)) return false;
272     return myFile instanceof PsiJavaFile && HighlightingLevelManager.getInstance(myProject).shouldHighlight(myFile);
273   }
274
275   @Nullable
276   private HighlightInfo processIdentifier(@NotNull PsiIdentifier identifier, @NotNull ProgressIndicator progress, @NotNull GlobalUsageHelper helper) {
277     if (SuppressionUtil.inspectionResultSuppressed(identifier, myUnusedSymbolInspection)) return null;
278     PsiElement parent = identifier.getParent();
279     if (PsiUtilCore.hasErrorElementChild(parent)) return null;
280
281     if (parent instanceof PsiLocalVariable && myUnusedSymbolInspection.LOCAL_VARIABLE) {
282       return processLocalVariable((PsiLocalVariable)parent, identifier, progress);
283     }
284     if (parent instanceof PsiField && myUnusedSymbolInspection.FIELD) {
285       return processField(myProject, (PsiField)parent, identifier, progress, helper);
286     }
287     if (parent instanceof PsiParameter && myUnusedSymbolInspection.PARAMETER) {
288       if (SuppressionUtil.isSuppressed(identifier, UnusedSymbolLocalInspectionBase.UNUSED_PARAMETERS_SHORT_NAME)) return null;
289       return processParameter(myProject, (PsiParameter)parent, identifier, progress);
290     }
291     if (parent instanceof PsiMethod && myUnusedSymbolInspection.METHOD) {
292       return processMethod(myProject, (PsiMethod)parent, identifier, progress, helper);
293     }
294     if (parent instanceof PsiClass && myUnusedSymbolInspection.CLASS) {
295       return processClass(myProject, (PsiClass)parent, identifier, progress, helper);
296     }
297     return null;
298   }
299
300   @Nullable
301   private HighlightInfo processLocalVariable(@NotNull PsiLocalVariable variable,
302                                              @NotNull PsiIdentifier identifier,
303                                              @NotNull ProgressIndicator progress) {
304     if (variable instanceof PsiResourceVariable && PsiUtil.isIgnoredName(variable.getName())) return null;
305     if (isImplicitUsage(myProject, variable, progress)) return null;
306
307     if (!myRefCountHolder.isReferenced(variable)) {
308       String message = JavaErrorMessages.message("local.variable.is.never.used", identifier.getText());
309       HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL);
310       IntentionAction fix = variable instanceof PsiResourceVariable ? QuickFixFactory.getInstance().createRenameToIgnoredFix(variable) : QuickFixFactory.getInstance().createRemoveUnusedVariableFix(variable);
311       QuickFixAction.registerQuickFixAction(highlightInfo, fix, myUnusedSymbolKey);
312       return highlightInfo;
313     }
314
315     boolean referenced = myRefCountHolder.isReferencedForRead(variable);
316     if (!referenced && !isImplicitRead(myProject, variable, progress)) {
317       String message = JavaErrorMessages.message("local.variable.is.not.used.for.reading", identifier.getText());
318       HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL);
319       QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createRemoveUnusedVariableFix(variable), myUnusedSymbolKey);
320       return highlightInfo;
321     }
322
323     if (!variable.hasInitializer()) {
324       referenced = myRefCountHolder.isReferencedForWrite(variable);
325       if (!referenced && !isImplicitWrite(myProject, variable, progress)) {
326         String message = JavaErrorMessages.message("local.variable.is.not.assigned", identifier.getText());
327         final HighlightInfo unusedSymbolInfo = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL);
328         QuickFixAction.registerQuickFixAction(unusedSymbolInfo, new EmptyIntentionAction(UnusedSymbolLocalInspectionBase.DISPLAY_NAME), myUnusedSymbolKey);
329         return unusedSymbolInfo;
330       }
331     }
332
333     return null;
334   }
335
336   public static boolean isImplicitUsage(@NotNull Project project,
337                                         @NotNull PsiModifierListOwner element,
338                                         @NotNull ProgressIndicator progress) {
339     if (isInjected(project, element)) return true;
340     for (ImplicitUsageProvider provider : ourImplicitUsageProviders) {
341       progress.checkCanceled();
342       if (provider.isImplicitUsage(element)) {
343         return true;
344       }
345     }
346
347     return false;
348   }
349
350   private static boolean isImplicitRead(@NotNull Project project, @NotNull PsiVariable element, @NotNull ProgressIndicator progress) {
351     for(ImplicitUsageProvider provider: ourImplicitUsageProviders) {
352       progress.checkCanceled();
353       if (provider.isImplicitRead(element)) {
354         return true;
355       }
356     }
357     return isInjected(project, element);
358   }
359
360   private static boolean isImplicitWrite(@NotNull Project project,
361                                          @NotNull PsiVariable element,
362                                          @NotNull ProgressIndicator progress) {
363     for(ImplicitUsageProvider provider: ourImplicitUsageProviders) {
364       progress.checkCanceled();
365       if (provider.isImplicitWrite(element)) {
366         return true;
367       }
368     }
369     return isInjected(project, element);
370   }
371
372   @Nullable
373   public static HighlightInfo createUnusedSymbolInfo(@NotNull PsiElement element,
374                                                      @NotNull String message,
375                                                      @NotNull final HighlightInfoType highlightInfoType) {
376     HighlightInfo info = HighlightInfo.newHighlightInfo(highlightInfoType).range(element).descriptionAndTooltip(message).create();
377     if (info == null) {
378       return null; //filtered out
379     }
380     
381     UnusedDeclarationFixProvider[] fixProviders = Extensions.getExtensions(UnusedDeclarationFixProvider.EP_NAME);
382     for (UnusedDeclarationFixProvider provider : fixProviders) {
383       IntentionAction[] fixes = provider.getQuickFixes(element);
384       for (IntentionAction fix : fixes) {
385         QuickFixAction.registerQuickFixAction(info, fix);
386       }
387     }
388     return info;
389   }
390
391   @Nullable
392   private HighlightInfo processField(@NotNull final Project project,
393                                      @NotNull final PsiField field,
394                                      @NotNull PsiIdentifier identifier,
395                                      @NotNull ProgressIndicator progress,
396                                      @NotNull GlobalUsageHelper helper) {
397     if (HighlightUtil.isSerializationImplicitlyUsedField(field)) {
398       return null;
399     }
400     if (field.hasModifierProperty(PsiModifier.PRIVATE)) {
401       if (!myRefCountHolder.isReferenced(field) && !isImplicitUsage(myProject, field, progress)) {
402         String message = JavaErrorMessages.message("private.field.is.not.used", identifier.getText());
403
404         HighlightInfo highlightInfo = suggestionsToMakeFieldUsed(field, identifier, message);
405         if (!field.hasInitializer()) {
406           QuickFixAction.registerQuickFixAction(highlightInfo, HighlightMethodUtil.getFixRange(field), QuickFixFactory.getInstance().createCreateConstructorParameterFromFieldFix(field));
407         }
408         return highlightInfo;
409       }
410
411       final boolean readReferenced = myRefCountHolder.isReferencedForRead(field);
412       if (!readReferenced && !isImplicitRead(project, field, progress)) {
413         String message = JavaErrorMessages.message("private.field.is.not.used.for.reading", identifier.getText());
414         return suggestionsToMakeFieldUsed(field, identifier, message);
415       }
416
417       if (field.hasInitializer()) {
418         return null;
419       }
420       final boolean writeReferenced = myRefCountHolder.isReferencedForWrite(field);
421       if (!writeReferenced && !isImplicitWrite(project, field, progress)) {
422         String message = JavaErrorMessages.message("private.field.is.not.assigned", identifier.getText());
423         final HighlightInfo info = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL);
424
425         QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createCreateGetterOrSetterFix(false, true, field), myUnusedSymbolKey);
426         QuickFixAction.registerQuickFixAction(info, HighlightMethodUtil.getFixRange(field), QuickFixFactory.getInstance().createCreateConstructorParameterFromFieldFix(
427           field));
428         SpecialAnnotationsUtilBase.createAddToSpecialAnnotationFixes(field, new Processor<String>() {
429           @Override
430           public boolean process(final String annoName) {
431             QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance()
432               .createAddToDependencyInjectionAnnotationsFix(project, annoName, "fields"));
433             return true;
434           }
435         });
436         return info;
437       }
438     }
439     else if (isImplicitUsage(myProject, field, progress)) {
440       return null;
441     }
442     else if (isFieldUnused(myProject, myFile, field, progress, helper)) {
443       return formatUnusedSymbolHighlightInfo(project, "field.is.not.used", field, "fields", myDeadCodeKey, myDeadCodeInfoType, identifier);
444     }
445     return null;
446   }
447
448   public static boolean isFieldUnused(@NotNull Project project,
449                                       @NotNull PsiFile containingFile,
450                                       @NotNull PsiField field,
451                                       @NotNull ProgressIndicator progress,
452                                       @NotNull GlobalUsageHelper helper) {
453     if (helper.isLocallyUsed(field) || !weAreSureThereAreNoUsages(project, containingFile, field, progress, helper)) {
454       return false;
455     }
456     return !(field instanceof PsiEnumConstant) || !isEnumValuesMethodUsed(project, containingFile, field, progress, helper);
457   }
458
459   private HighlightInfo suggestionsToMakeFieldUsed(@NotNull PsiField field, @NotNull PsiIdentifier identifier, @NotNull String message) {
460     HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL);
461     QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createRemoveUnusedVariableFix(field), myUnusedSymbolKey);
462     QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createCreateGetterOrSetterFix(true, false, field), myUnusedSymbolKey);
463     QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createCreateGetterOrSetterFix(false, true, field), myUnusedSymbolKey);
464     QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createCreateGetterOrSetterFix(true, true, field), myUnusedSymbolKey);
465     return highlightInfo;
466   }
467
468   private static boolean isOverriddenOrOverrides(PsiMethod method) {
469     boolean overrides = SuperMethodsSearch.search(method, null, true, false).findFirst() != null;
470     return overrides || OverridingMethodsSearch.search(method).findFirst() != null;
471   }
472
473   @Nullable
474   private HighlightInfo processParameter(@NotNull Project project,
475                                          @NotNull PsiParameter parameter,
476                                          @NotNull PsiIdentifier identifier,
477                                          @NotNull ProgressIndicator progress) {
478     PsiElement declarationScope = parameter.getDeclarationScope();
479     if (declarationScope instanceof PsiMethod) {
480       PsiMethod method = (PsiMethod)declarationScope;
481       if (PsiUtilCore.hasErrorElementChild(method)) return null;
482       if ((method.isConstructor() ||
483            method.hasModifierProperty(PsiModifier.PRIVATE) ||
484            method.hasModifierProperty(PsiModifier.STATIC) ||
485            !method.hasModifierProperty(PsiModifier.ABSTRACT) &&
486            myUnusedSymbolInspection.REPORT_PARAMETER_FOR_PUBLIC_METHODS &&
487            !isOverriddenOrOverrides(method)) &&
488           !method.hasModifierProperty(PsiModifier.NATIVE) &&
489           !JavaHighlightUtil.isSerializationRelatedMethod(method, method.getContainingClass()) &&
490           !PsiClassImplUtil.isMainOrPremainMethod(method)) {
491         if (isInjected(project, method)) return null;
492         HighlightInfo highlightInfo = checkUnusedParameter(parameter, identifier, progress);
493         if (highlightInfo != null) {
494           QuickFixFactory.getInstance().registerFixesForUnusedParameter(parameter, highlightInfo);
495           return highlightInfo;
496         }
497       }
498     }
499     else if (declarationScope instanceof PsiForeachStatement && !PsiUtil.isIgnoredName(parameter.getName())) {
500       HighlightInfo highlightInfo = checkUnusedParameter(parameter, identifier, progress);
501       if (highlightInfo != null) {
502         QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createRenameToIgnoredFix(parameter), myUnusedSymbolKey);
503         return highlightInfo;
504       }
505     }
506
507     return null;
508   }
509
510   @Nullable
511   private HighlightInfo checkUnusedParameter(@NotNull PsiParameter parameter,
512                                              @NotNull PsiIdentifier identifier,
513                                              @NotNull ProgressIndicator progress) {
514     if (!myRefCountHolder.isReferenced(parameter) && !isImplicitUsage(myProject, parameter, progress)) {
515       //parameter is defined by functional interface
516       final PsiElement declarationScope = parameter.getDeclarationScope();
517       if (declarationScope instanceof PsiMethod && 
518           myRefCountHolder.isReferencedByMethodReference((PsiMethod)declarationScope, myLanguageLevel)) {
519         return null;
520       }
521       String message = JavaErrorMessages.message("parameter.is.not.used", identifier.getText());
522       return createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL);
523     }
524     return null;
525   }
526
527   @Nullable
528   private HighlightInfo processMethod(@NotNull final Project project,
529                                       @NotNull final PsiMethod method,
530                                       @NotNull PsiIdentifier identifier,
531                                       @NotNull ProgressIndicator progress,
532                                       @NotNull GlobalUsageHelper helper) {
533     if (isMethodReferenced(myProject, myFile, method, progress, helper)) return null;
534     HighlightInfoType highlightInfoType;
535     HighlightDisplayKey highlightDisplayKey;
536     String key;
537     if (method.hasModifierProperty(PsiModifier.PRIVATE)) {
538       highlightInfoType = HighlightInfoType.UNUSED_SYMBOL;
539       highlightDisplayKey = myUnusedSymbolKey;
540       key = method.isConstructor() ? "private.constructor.is.not.used" : "private.method.is.not.used";
541     }
542     else {
543       highlightInfoType = myDeadCodeInfoType;
544       highlightDisplayKey = myDeadCodeKey;
545       key = method.isConstructor() ? "constructor.is.not.used" : "method.is.not.used";
546     }
547     String symbolName = HighlightMessageUtil.getSymbolName(method, PsiSubstitutor.EMPTY);
548     String message = JavaErrorMessages.message(key, symbolName);
549     final HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, highlightInfoType);
550     QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createSafeDeleteFix(method), highlightDisplayKey);
551     SpecialAnnotationsUtilBase.createAddToSpecialAnnotationFixes(method, new Processor<String>() {
552       @Override
553       public boolean process(final String annoName) {
554         IntentionAction fix = QuickFixFactory.getInstance().createAddToDependencyInjectionAnnotationsFix(project, annoName, "methods");
555         QuickFixAction.registerQuickFixAction(highlightInfo, fix);
556         return true;
557       }
558     });
559     return highlightInfo;
560   }
561
562   public static boolean isMethodReferenced(@NotNull Project project,
563                                            @NotNull PsiFile containingFile,
564                                            @NotNull PsiMethod method,
565                                            @NotNull ProgressIndicator progress,
566                                            @NotNull GlobalUsageHelper helper) {
567     if (helper.isLocallyUsed(method)) return true;
568
569     boolean aPrivate = method.hasModifierProperty(PsiModifier.PRIVATE);
570     PsiClass containingClass = method.getContainingClass();
571     if (JavaHighlightUtil.isSerializationRelatedMethod(method, containingClass)) return true;
572     if (aPrivate) {
573       if (isIntentionalPrivateConstructor(method, containingClass)) {
574         return true;
575       }
576       if (isImplicitUsage(project, method, progress)) {
577         return true;
578       }
579       if (!helper.isCurrentFileAlreadyChecked()) {
580         return !weAreSureThereAreNoUsages(project, containingFile, method, progress, helper);
581       }
582     }
583     else {
584       //class maybe used in some weird way, e.g. from XML, therefore the only constructor is used too
585       if (containingClass != null && method.isConstructor()
586           && containingClass.getConstructors().length == 1
587           && isClassUsed(project, containingFile, containingClass, progress, helper)) {
588         return true;
589       }
590       if (isImplicitUsage(project, method, progress)) return true;
591
592       if (method.findSuperMethods().length != 0) {
593         return true;
594       }
595       if (!weAreSureThereAreNoUsages(project, containingFile, method, progress, helper)) {
596         return true;
597       }
598     }
599     return false;
600   }
601
602   private static boolean weAreSureThereAreNoUsages(@NotNull Project project,
603                                                    @NotNull PsiFile containingFile,
604                                                    @NotNull PsiMember member,
605                                                    @NotNull ProgressIndicator progress,
606                                                    @NotNull GlobalUsageHelper helper) {
607     if (!helper.shouldCheckUsages(member)) return false;
608
609     String name = member.getName();
610     if (name == null) return false;
611     SearchScope useScope = member.getUseScope();
612     PsiSearchHelper searchHelper = PsiSearchHelper.SERVICE.getInstance(project);
613     final PsiFile ignoreFile = helper.isCurrentFileAlreadyChecked() ? containingFile : null;
614     if (useScope instanceof GlobalSearchScope) {
615       // some classes may have references from within XML outside dependent modules, e.g. our actions
616       if (member instanceof PsiClass) {
617         useScope = GlobalSearchScope.projectScope(project).uniteWith((GlobalSearchScope)useScope);
618       }
619
620       PsiSearchHelper.SearchCostResult cheapEnough = searchHelper.isCheapEnoughToSearch(name, (GlobalSearchScope)useScope, ignoreFile, progress);
621       if (cheapEnough == PsiSearchHelper.SearchCostResult.TOO_MANY_OCCURRENCES) return false;
622
623       //search usages if it cheap
624       //if count is 0 there is no usages since we've called myRefCountHolder.isReferenced() before
625       if (cheapEnough == PsiSearchHelper.SearchCostResult.ZERO_OCCURRENCES && !canBeReferencedViaWeirdNames(member, containingFile)) {
626         return true;
627       }
628
629       if (member instanceof PsiMethod) {
630         String propertyName = PropertyUtil.getPropertyName(member);
631         if (propertyName != null) {
632           SearchScope fileScope = containingFile.getUseScope();
633           if (fileScope instanceof GlobalSearchScope &&
634               searchHelper.isCheapEnoughToSearch(propertyName, (GlobalSearchScope)fileScope, ignoreFile, progress) ==
635               PsiSearchHelper.SearchCostResult.TOO_MANY_OCCURRENCES) {
636             return false;
637           }
638         }
639       }
640     }
641     FindUsagesOptions options;
642     if (member instanceof PsiPackage) {
643       options = new JavaPackageFindUsagesOptions(project);
644     }
645     else if (member instanceof PsiClass) {
646       options = new JavaClassFindUsagesOptions(project);
647     }
648     else if (member instanceof PsiMethod) {
649       JavaMethodFindUsagesOptions o = new JavaMethodFindUsagesOptions(project);
650       //o.isIncludeOverloadUsages = true;
651       options = o;
652     }
653     else if (member instanceof PsiVariable) {
654       options = new JavaVariableFindUsagesOptions(project);
655     }
656     else {
657       options = new FindUsagesOptions(project);
658     }
659     options.isSearchForTextOccurrences = true;
660     options.isUsages = true;
661     boolean foundUsage = !JavaFindUsagesHelper.processElementUsages(member, options, new Processor<UsageInfo>() {
662       @Override
663       public boolean process(UsageInfo info) {
664         PsiFile psiFile = info.getFile();
665         if (psiFile == ignoreFile || psiFile == null) return true; // ignore usages in containingFile because isLocallyUsed() method would have caught that
666         int offset = info.getNavigationOffset();
667         if (offset == -1) return true;
668         PsiElement element = psiFile.findElementAt(offset);
669         return element instanceof PsiComment; // ignore comments
670       }
671     });
672     if (foundUsage) return false;
673     return true;//!(useScope instanceof GlobalSearchScope) || !foundUsageInText(member, (GlobalSearchScope)useScope, searchHelper, ignoreFile);
674   }
675
676   private static boolean foundUsageInText(@NotNull PsiMember member,
677                                           @NotNull GlobalSearchScope scope,
678                                           @NotNull PsiSearchHelper searchHelper,
679                                           final PsiFile ignoreFile) {
680     return !searchHelper.processUsagesInNonJavaFiles(member, member.getName(), new PsiNonJavaFileReferenceProcessor() {
681       @Override
682       public boolean process(final PsiFile psiFile, final int startOffset, final int endOffset) {
683         if (psiFile == ignoreFile) return true; // ignore usages in containingFile because isLocallyUsed() method would have caught that
684         PsiElement element = psiFile.findElementAt(startOffset);
685         return element instanceof PsiComment; // ignore comments
686       }
687     }, scope);
688   }
689
690   private static boolean isEnumValuesMethodUsed(@NotNull Project project,
691                                                 @NotNull PsiFile containingFile,
692                                                 @NotNull PsiMember member,
693                                                 @NotNull ProgressIndicator progress,
694                                                 @NotNull GlobalUsageHelper helper) {
695     final PsiClass containingClass = member.getContainingClass();
696     if (containingClass == null || !(containingClass instanceof PsiClassImpl)) return true;
697     final PsiMethod valuesMethod = ((PsiClassImpl)containingClass).getValuesMethod();
698     return valuesMethod == null || isMethodReferenced(project, containingFile, valuesMethod, progress, helper);
699   }
700
701   private static boolean canBeReferencedViaWeirdNames(@NotNull PsiMember member, @NotNull PsiFile containingFile) {
702     if (member instanceof PsiClass) return false;
703     if (!(containingFile instanceof PsiJavaFile)) return true;  // Groovy field can be referenced from Java by getter
704     if (member instanceof PsiField) return false;  //Java field cannot be referenced by anything but its name
705     if (member instanceof PsiMethod) {
706       return PropertyUtil.isSimplePropertyAccessor((PsiMethod)member);  //Java accessors can be referenced by field name from Groovy
707     }
708     return false;
709   }
710
711   @Nullable
712   private HighlightInfo processClass(@NotNull Project project,
713                                      @NotNull PsiClass aClass,
714                                      @NotNull PsiIdentifier identifier,
715                                      @NotNull ProgressIndicator progress,
716                                      @NotNull GlobalUsageHelper helper) {
717     if (isClassUsed(project, myFile, aClass, progress, helper)) return null;
718
719     String pattern;
720     HighlightDisplayKey highlightDisplayKey;
721     HighlightInfoType highlightInfoType;
722     if (aClass.getContainingClass() != null && aClass.hasModifierProperty(PsiModifier.PRIVATE)) {
723       pattern = aClass.isInterface()
724                        ? "private.inner.interface.is.not.used"
725                        : "private.inner.class.is.not.used";
726       highlightDisplayKey = myUnusedSymbolKey;
727       highlightInfoType = HighlightInfoType.UNUSED_SYMBOL;
728     }
729     else if (aClass.getParent() instanceof PsiDeclarationStatement) { // local class
730       pattern = "local.class.is.not.used";
731       highlightDisplayKey = myUnusedSymbolKey;
732       highlightInfoType = HighlightInfoType.UNUSED_SYMBOL;
733     }
734     else if (aClass instanceof PsiTypeParameter) {
735       pattern = "type.parameter.is.not.used";
736       highlightDisplayKey = myUnusedSymbolKey;
737       highlightInfoType = HighlightInfoType.UNUSED_SYMBOL;
738     }
739     else {
740       pattern = "class.is.not.used";
741       highlightDisplayKey = myDeadCodeKey;
742       highlightInfoType = myDeadCodeInfoType;
743     }
744     return formatUnusedSymbolHighlightInfo(myProject, pattern, aClass, "classes", highlightDisplayKey, highlightInfoType, identifier);
745   }
746
747   public static boolean isClassUsed(@NotNull Project project,
748                                     @NotNull PsiFile containingFile,
749                                     @NotNull PsiClass aClass,
750                                     @NotNull ProgressIndicator progress,
751                                     @NotNull GlobalUsageHelper helper) {
752     Boolean result = helper.unusedClassCache.get(aClass);
753     if (result == null) {
754       result = isReallyUsed(project, containingFile, aClass, progress, helper);
755       helper.unusedClassCache.put(aClass, result);
756     }
757     return result;
758   }
759
760   private static boolean isReallyUsed(@NotNull Project project,
761                                       @NotNull PsiFile containingFile,
762                                       @NotNull PsiClass aClass,
763                                       @NotNull ProgressIndicator progress,
764                                       @NotNull GlobalUsageHelper helper) {
765     if (isImplicitUsage(project, aClass, progress) || helper.isLocallyUsed(aClass)) return true;
766     if (helper.isCurrentFileAlreadyChecked()) {
767       if (aClass.getContainingClass() != null && aClass.hasModifierProperty(PsiModifier.PRIVATE) ||
768              aClass.getParent() instanceof PsiDeclarationStatement ||
769              aClass instanceof PsiTypeParameter) return false;
770     }
771     return !weAreSureThereAreNoUsages(project, containingFile, aClass, progress, helper);
772   }
773
774   private static HighlightInfo formatUnusedSymbolHighlightInfo(@NotNull final Project project,
775                                                                @NotNull @PropertyKey(resourceBundle = JavaErrorMessages.BUNDLE) String pattern,
776                                                                @NotNull final PsiNameIdentifierOwner aClass,
777                                                                @NotNull final String element,
778                                                                HighlightDisplayKey highlightDisplayKey,
779                                                                @NotNull HighlightInfoType highlightInfoType,
780                                                                @NotNull PsiElement identifier) {
781     String symbolName = aClass.getName();
782     String message = JavaErrorMessages.message(pattern, symbolName);
783     final HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, highlightInfoType);
784     QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createSafeDeleteFix(aClass), highlightDisplayKey);
785     SpecialAnnotationsUtilBase.createAddToSpecialAnnotationFixes((PsiModifierListOwner)aClass, new Processor<String>() {
786       @Override
787       public boolean process(final String annoName) {
788         QuickFixAction
789           .registerQuickFixAction(highlightInfo,
790                                   QuickFixFactory.getInstance().createAddToDependencyInjectionAnnotationsFix(project, annoName, element));
791         return true;
792       }
793     });
794     return highlightInfo;
795   }
796
797   @Nullable
798   private HighlightInfo processImport(@NotNull PsiImportStatementBase importStatement, @NotNull HighlightDisplayKey unusedImportKey) {
799     // jsp include directive hack
800     if (importStatement.isForeignFileImport()) return null;
801
802     if (PsiUtilCore.hasErrorElementChild(importStatement)) return null;
803
804     boolean isRedundant = myRefCountHolder.isRedundant(importStatement);
805     if (!isRedundant && !(importStatement instanceof PsiImportStaticStatement)) {
806       //check import from same package
807       String packageName = ((PsiClassOwner)importStatement.getContainingFile()).getPackageName();
808       PsiJavaCodeReferenceElement reference = importStatement.getImportReference();
809       PsiElement resolved = reference == null ? null : reference.resolve();
810       if (resolved instanceof PsiPackage) {
811         isRedundant = packageName.equals(((PsiQualifiedNamedElement)resolved).getQualifiedName());
812       }
813       else if (resolved instanceof PsiClass && !importStatement.isOnDemand()) {
814         String qName = ((PsiClass)resolved).getQualifiedName();
815         if (qName != null) {
816           String name = ((PomNamedTarget)resolved).getName();
817           isRedundant = qName.equals(packageName + '.' + name);
818         }
819       }
820     }
821
822     if (isRedundant) {
823       return registerRedundantImport(importStatement, unusedImportKey);
824     }
825
826     int entryIndex = JavaCodeStyleManager.getInstance(myProject).findEntryIndex(importStatement);
827     if (entryIndex < myCurrentEntryIndex) {
828       myHasMissortedImports = true;
829     }
830     myCurrentEntryIndex = entryIndex;
831
832     return null;
833   }
834
835   private HighlightInfo registerRedundantImport(@NotNull PsiImportStatementBase importStatement, @NotNull HighlightDisplayKey unusedImportKey) {
836     String description = InspectionsBundle.message("unused.import.statement");
837     HighlightInfo info =
838       HighlightInfo.newHighlightInfo(JavaHighlightInfoTypes.UNUSED_IMPORT).range(importStatement).descriptionAndTooltip(description)
839         .create();
840
841     QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createOptimizeImportsFix(false), unusedImportKey);
842     QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createEnableOptimizeImportsOnTheFlyFix(), unusedImportKey);
843     myHasRedundantImports = true;
844     return info;
845   }
846
847
848   private static boolean isIntentionalPrivateConstructor(@NotNull PsiMethod method, PsiClass containingClass) {
849     return method.isConstructor() &&
850            method.getParameterList().getParametersCount() == 0 &&
851            containingClass != null &&
852            containingClass.getConstructors().length == 1;
853   }
854 }