update copyright
[fedora-idea.git] / java / java-impl / src / com / intellij / codeInsight / daemon / impl / PostHighlightingPass.java
blobf25a4c492c755532639ecaaaf6a16f1ca6c3916e
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.codeInsight.daemon.impl;
18 import com.intellij.codeHighlighting.Pass;
19 import com.intellij.codeHighlighting.TextEditorHighlightingPass;
20 import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
21 import com.intellij.codeInsight.daemon.HighlightDisplayKey;
22 import com.intellij.codeInsight.daemon.ImplicitUsageProvider;
23 import com.intellij.codeInsight.daemon.JavaErrorMessages;
24 import com.intellij.codeInsight.daemon.impl.analysis.HighlightLevelUtil;
25 import com.intellij.codeInsight.daemon.impl.analysis.HighlightMessageUtil;
26 import com.intellij.codeInsight.daemon.impl.analysis.HighlightMethodUtil;
27 import com.intellij.codeInsight.daemon.impl.analysis.HighlightUtil;
28 import com.intellij.codeInsight.daemon.impl.quickfix.*;
29 import com.intellij.codeInsight.intention.EmptyIntentionAction;
30 import com.intellij.codeInspection.InspectionProfile;
31 import com.intellij.codeInspection.InspectionsBundle;
32 import com.intellij.codeInspection.deadCode.DeadCodeInspection;
33 import com.intellij.codeInspection.ex.InspectionManagerEx;
34 import com.intellij.codeInspection.ex.LocalInspectionToolWrapper;
35 import com.intellij.codeInspection.unusedImport.UnusedImportLocalInspection;
36 import com.intellij.codeInspection.unusedSymbol.UnusedSymbolLocalInspection;
37 import com.intellij.codeInspection.util.SpecialAnnotationsUtil;
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.command.CommandProcessor;
42 import com.intellij.openapi.diagnostic.Logger;
43 import com.intellij.openapi.editor.Document;
44 import com.intellij.openapi.editor.Editor;
45 import com.intellij.openapi.extensions.Extensions;
46 import com.intellij.openapi.progress.ProcessCanceledException;
47 import com.intellij.openapi.progress.ProgressIndicator;
48 import com.intellij.openapi.progress.ProgressManager;
49 import com.intellij.openapi.project.Project;
50 import com.intellij.openapi.roots.ProjectRootManager;
51 import com.intellij.openapi.roots.ProjectFileIndex;
52 import com.intellij.openapi.vfs.VirtualFile;
53 import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
54 import com.intellij.psi.*;
55 import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
56 import com.intellij.psi.codeStyle.JavaCodeStyleManager;
57 import com.intellij.psi.impl.PsiClassImplUtil;
58 import com.intellij.psi.impl.PsiManagerEx;
59 import com.intellij.psi.impl.cache.CacheManager;
60 import com.intellij.psi.impl.source.jsp.jspJava.JspxImportStatement;
61 import com.intellij.psi.jsp.JspFile;
62 import com.intellij.psi.jsp.JspSpiUtil;
63 import com.intellij.psi.search.GlobalSearchScope;
64 import com.intellij.psi.search.SearchScope;
65 import com.intellij.psi.search.UsageSearchContext;
66 import com.intellij.psi.search.searches.MethodReferencesSearch;
67 import com.intellij.psi.search.searches.OverridingMethodsSearch;
68 import com.intellij.psi.search.searches.ReferencesSearch;
69 import com.intellij.psi.search.searches.SuperMethodsSearch;
70 import com.intellij.psi.util.PropertyUtil;
71 import com.intellij.psi.util.PsiUtilBase;
72 import com.intellij.util.Processor;
73 import com.intellij.util.Query;
74 import gnu.trove.THashMap;
75 import gnu.trove.THashSet;
76 import org.jetbrains.annotations.NotNull;
77 import org.jetbrains.annotations.Nullable;
78 import org.jetbrains.annotations.PropertyKey;
79 import org.jetbrains.annotations.TestOnly;
81 import java.util.*;
83 public class PostHighlightingPass extends TextEditorHighlightingPass {
84 private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.PostHighlightingPass");
85 private RefCountHolder myRefCountHolder;
86 private final PsiFile myFile;
87 @Nullable private final Editor myEditor;
88 private final int myStartOffset;
89 private final int myEndOffset;
91 private Collection<HighlightInfo> myHighlights;
92 private boolean myHasRedundantImports;
93 private final JavaCodeStyleManager myStyleManager;
94 private int myCurentEntryIndex;
95 private boolean myHasMissortedImports;
96 private final ImplicitUsageProvider[] myImplicitUsageProviders;
97 private DeadCodeInspection myDeadCodeInspection;
98 private UnusedSymbolLocalInspection myUnusedSymbolInspection;
99 private HighlightDisplayKey myUnusedSymbolKey;
100 private boolean myDeadCodeEnabled;
101 private boolean myInLibrary;
103 private PostHighlightingPass(@NotNull Project project,
104 @NotNull PsiFile file,
105 @Nullable Editor editor,
106 @NotNull Document document,
107 int startOffset,
108 int endOffset) {
109 super(project, document, true);
110 myFile = file;
111 myEditor = editor;
112 myStartOffset = startOffset;
113 myEndOffset = endOffset;
115 myStyleManager = JavaCodeStyleManager.getInstance(myProject);
116 myCurentEntryIndex = -1;
118 myImplicitUsageProviders = Extensions.getExtensions(ImplicitUsageProvider.EP_NAME);
121 PostHighlightingPass(@NotNull Project project, @NotNull PsiFile file, @NotNull Editor editor, int startOffset, int endOffset) {
122 this(project, file, editor, editor.getDocument(), startOffset, endOffset);
125 public PostHighlightingPass(@NotNull Project project, @NotNull PsiFile file, @NotNull Document document, int startOffset, int endOffset) {
126 this(project, file, null, document, startOffset, endOffset);
129 public void doCollectInformation(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);
143 ProjectFileIndex fileIndex = ProjectRootManager.getInstance(myProject).getFileIndex();
144 VirtualFile virtualFile = viewProvider.getVirtualFile();
145 myInLibrary = fileIndex.isInLibraryClasses(virtualFile) || fileIndex.isInLibrarySource(virtualFile);
147 myRefCountHolder = RefCountHolder.getInstance(myFile);
148 if (!myRefCountHolder.retrieveUnusedReferencesInfo(new Runnable() {
149 public void run() {
150 collectHighlights(elementSet, highlights);
151 myHighlights = highlights;
152 for (HighlightInfo info : highlights) {
153 if (info.getSeverity() == HighlightSeverity.ERROR) {
154 fileStatusMap.setErrorFoundFlag(myDocument, true);
155 break;
159 })) {
160 // we must be sure GHP will restart
161 fileStatusMap.markFileScopeDirty(getDocument(), Pass.UPDATE_ALL);
162 GeneralHighlightingPass.cancelAndRestartDaemonLater(progress, myProject, this);
166 public void doApplyInformationToEditor() {
167 if (myHighlights == null) return;
168 UpdateHighlightersUtil.setHighlightersToEditor(myProject, myDocument, myStartOffset, myEndOffset, myHighlights, Pass.POST_UPDATE_ALL);
170 DaemonCodeAnalyzer daemonCodeAnalyzer = DaemonCodeAnalyzer.getInstance(myProject);
171 ((DaemonCodeAnalyzerImpl)daemonCodeAnalyzer).getFileStatusMap().markFileUpToDate(myDocument, myFile, getId());
173 if (timeToOptimizeImports() && myEditor != null) {
174 optimizeImportsOnTheFly();
178 private void optimizeImportsOnTheFly() {
179 if (myHasRedundantImports || myHasMissortedImports) {
180 ApplicationManager.getApplication().invokeLater(new Runnable() {
181 public void run() {
182 CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() {
183 public void run() {
184 ApplicationManager.getApplication().runWriteAction(new Runnable() {
185 public void run() {
186 OptimizeImportsFix optimizeImportsFix = new OptimizeImportsFix();
187 if (optimizeImportsFix.isAvailable(myProject, myEditor, myFile) && myFile.isWritable()) {
188 PsiDocumentManager.getInstance(myProject).commitAllDocuments();
189 optimizeImportsFix.invoke(myProject, myEditor, myFile);
200 @TestOnly
201 public Collection<HighlightInfo> getHighlights() {
202 return myHighlights;
205 private void collectHighlights(Collection<PsiElement> elements, final List<HighlightInfo> result) throws ProcessCanceledException {
206 ApplicationManager.getApplication().assertReadAccessAllowed();
208 InspectionProfile profile = InspectionProjectProfileManager.getInstance(myProject).getInspectionProfile();
209 myUnusedSymbolKey = HighlightDisplayKey.find(UnusedSymbolLocalInspection.SHORT_NAME);
210 boolean unusedSymbolEnabled = profile.isToolEnabled(myUnusedSymbolKey, myFile);
211 HighlightDisplayKey unusedImportKey = HighlightDisplayKey.find(UnusedImportLocalInspection.SHORT_NAME);
212 boolean unusedImportEnabled = profile.isToolEnabled(unusedImportKey, myFile);
213 LocalInspectionToolWrapper unusedSymbolTool = (LocalInspectionToolWrapper)profile.getInspectionTool(UnusedSymbolLocalInspection.SHORT_NAME,
214 myFile);
215 myUnusedSymbolInspection = unusedSymbolTool == null ? null : (UnusedSymbolLocalInspection)unusedSymbolTool.getTool();
216 LOG.assertTrue(ApplicationManager.getApplication().isUnitTestMode() || myUnusedSymbolInspection != null);
218 HighlightDisplayKey deadCodeKey = HighlightDisplayKey.find(DeadCodeInspection.SHORT_NAME);
219 myDeadCodeInspection = (DeadCodeInspection)profile.getInspectionTool(DeadCodeInspection.SHORT_NAME, myFile);
220 myDeadCodeEnabled = profile.isToolEnabled(deadCodeKey, myFile);
221 if (unusedImportEnabled && JspPsiUtil.isInJspFile(myFile)) {
222 final JspFile jspFile = JspPsiUtil.getJspFile(myFile);
223 if (jspFile != null) {
224 unusedImportEnabled = !JspSpiUtil.isIncludedOrIncludesSomething(jspFile);
228 if (!unusedSymbolEnabled && !unusedImportEnabled) {
229 return;
231 for (PsiElement element : elements) {
232 ProgressManager.getInstance().checkCanceled();
234 if (unusedSymbolEnabled && element instanceof PsiIdentifier) {
235 PsiIdentifier identifier = (PsiIdentifier)element;
236 HighlightInfo info = processIdentifier(identifier);
237 if (info != null) {
238 result.add(info);
241 else if (unusedImportEnabled && element instanceof PsiImportList) {
242 final PsiImportStatementBase[] imports = ((PsiImportList)element).getAllImportStatements();
243 for (PsiImportStatementBase statement : imports) {
244 ProgressManager.getInstance().checkCanceled();
245 final HighlightInfo info = processImport(statement, unusedImportKey);
246 if (info != null) {
247 result.add(info);
254 @Nullable
255 private HighlightInfo processIdentifier(PsiIdentifier identifier) {
256 if (InspectionManagerEx.inspectionResultSuppressed(identifier, myUnusedSymbolInspection)) return null;
257 PsiElement parent = identifier.getParent();
258 if (PsiUtilBase.hasErrorElementChild(parent)) return null;
259 HighlightInfo info;
261 if (parent instanceof PsiLocalVariable && myUnusedSymbolInspection.LOCAL_VARIABLE) {
262 info = processLocalVariable((PsiLocalVariable)parent);
264 else if (parent instanceof PsiField && myUnusedSymbolInspection.FIELD) {
265 final PsiField psiField = (PsiField)parent;
266 info = processField(psiField, identifier);
268 else if (parent instanceof PsiParameter && myUnusedSymbolInspection.PARAMETER) {
269 info = processParameter((PsiParameter)parent);
271 else if (parent instanceof PsiMethod && myUnusedSymbolInspection.METHOD) {
272 info = processMethod((PsiMethod)parent);
274 else if (parent instanceof PsiClass && identifier.equals(((PsiClass)parent).getNameIdentifier()) && myUnusedSymbolInspection.CLASS) {
275 info = processClass((PsiClass)parent);
277 else {
278 return null;
280 return info;
284 @Nullable
285 private HighlightInfo processLocalVariable(PsiLocalVariable variable) {
286 PsiIdentifier identifier = variable.getNameIdentifier();
287 if (identifier == null) return null;
288 if (isImplicitUsage(variable)) return null;
289 if (!myRefCountHolder.isReferenced(variable)) {
290 String message = JavaErrorMessages.message("local.variable.is.never.used", identifier.getText());
291 HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message);
292 QuickFixAction.registerQuickFixAction(highlightInfo, new RemoveUnusedVariableFix(variable), myUnusedSymbolKey);
293 return highlightInfo;
296 boolean referenced = myRefCountHolder.isReferencedForRead(variable);
297 if (!referenced && !isImplicitRead(variable)) {
298 String message = JavaErrorMessages.message("local.variable.is.not.used.for.reading", identifier.getText());
299 HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message);
300 QuickFixAction.registerQuickFixAction(highlightInfo, new RemoveUnusedVariableFix(variable), myUnusedSymbolKey);
301 return highlightInfo;
304 if (!variable.hasInitializer()) {
305 referenced = myRefCountHolder.isReferencedForWrite(variable);
306 if (!referenced && !isImplicitWrite(variable)) {
307 String message = JavaErrorMessages.message("local.variable.is.not.assigned", identifier.getText());
308 final HighlightInfo unusedSymbolInfo = createUnusedSymbolInfo(identifier, message);
309 QuickFixAction.registerQuickFixAction(unusedSymbolInfo, new EmptyIntentionAction(UnusedSymbolLocalInspection.DISPLAY_NAME), myUnusedSymbolKey);
310 return unusedSymbolInfo;
314 return null;
318 private boolean isImplicitUsage(final PsiModifierListOwner element) {
319 if (UnusedSymbolLocalInspection.isInjected(element, myUnusedSymbolInspection)) return true;
320 for (ImplicitUsageProvider provider : myImplicitUsageProviders) {
321 ProgressManager.getInstance().checkCanceled();
322 if (provider.isImplicitUsage(element)) {
323 return true;
327 return false;
330 private boolean isImplicitRead(final PsiVariable element) {
331 for(ImplicitUsageProvider provider: myImplicitUsageProviders) {
332 ProgressManager.getInstance().checkCanceled();
333 if (provider.isImplicitRead(element)) {
334 return true;
337 return false;
340 private boolean isImplicitWrite(final PsiVariable element) {
341 for(ImplicitUsageProvider provider: myImplicitUsageProviders) {
342 ProgressManager.getInstance().checkCanceled();
343 if (provider.isImplicitWrite(element)) {
344 return true;
347 return false;
350 private static HighlightInfo createUnusedSymbolInfo(PsiElement element, String message) {
351 return HighlightInfo.createHighlightInfo(HighlightInfoType.UNUSED_SYMBOL, element, message);
354 @Nullable
355 private HighlightInfo processField(final PsiField field, final PsiIdentifier identifier) {
356 if (isImplicitUsage(field)) return null;
357 if (field.hasModifierProperty(PsiModifier.PRIVATE)) {
358 if (!myRefCountHolder.isReferenced(field)) {
359 if (HighlightUtil.isSerializationImplicitlyUsedField(field)) {
360 return null;
362 String message = JavaErrorMessages.message("private.field.is.not.used", identifier.getText());
364 HighlightInfo highlightInfo = suggestionsToMakeFieldUsed(field, identifier, message);
365 QuickFixAction.registerQuickFixAction(highlightInfo, HighlightMethodUtil.getFixRange(field), new CreateConstructorParameterFromFieldFix(field), null);
366 return highlightInfo;
369 final boolean readReferenced = myRefCountHolder.isReferencedForRead(field);
370 if (!readReferenced && !isImplicitRead(field)) {
371 String message = JavaErrorMessages.message("private.field.is.not.used.for.reading", identifier.getText());
372 return suggestionsToMakeFieldUsed(field, identifier, message);
375 if (field.hasInitializer()) {
376 return null;
378 final boolean writeReferenced = myRefCountHolder.isReferencedForWrite(field);
379 if (!writeReferenced && !isImplicitWrite(field)) {
380 String message = JavaErrorMessages.message("private.field.is.not.assigned", identifier.getText());
381 final HighlightInfo info = createUnusedSymbolInfo(identifier, message);
383 QuickFixAction.registerQuickFixAction(info, new CreateGetterOrSetterFix(false, true, field), myUnusedSymbolKey);
384 QuickFixAction.registerQuickFixAction(info, HighlightMethodUtil.getFixRange(field), new CreateConstructorParameterFromFieldFix(field), null);
385 SpecialAnnotationsUtil.createAddToSpecialAnnotationFixes(field, new Processor<String>() {
386 public boolean process(final String annoName) {
387 QuickFixAction.registerQuickFixAction(info, myUnusedSymbolInspection.createQuickFix(annoName, "fields"));
388 return true;
391 return info;
394 else if (!myRefCountHolder.isReferenced(field) && weAreSureThereAreNoUsages(field)) {
395 return formatUnusedSymbolHighlightInfo("field.is.not.used", field, "fields");
397 return null;
400 private HighlightInfo suggestionsToMakeFieldUsed(final PsiField field, final PsiIdentifier identifier, final String message) {
401 HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message);
402 QuickFixAction.registerQuickFixAction(highlightInfo, new RemoveUnusedVariableFix(field), myUnusedSymbolKey);
403 QuickFixAction.registerQuickFixAction(highlightInfo, new CreateGetterOrSetterFix(true, false, field), myUnusedSymbolKey);
404 QuickFixAction.registerQuickFixAction(highlightInfo, new CreateGetterOrSetterFix(false, true, field), myUnusedSymbolKey);
405 QuickFixAction.registerQuickFixAction(highlightInfo, new CreateGetterOrSetterFix(true, true, field), myUnusedSymbolKey);
406 return highlightInfo;
409 private static boolean isOverriddenOrOverrides(PsiMethod method) {
410 boolean overrides = SuperMethodsSearch.search(method, null, true, false).findFirst() != null;
411 return overrides || OverridingMethodsSearch.search(method).findFirst() != null;
414 @Nullable
415 private HighlightInfo processParameter(PsiParameter parameter) {
416 PsiElement declarationScope = parameter.getDeclarationScope();
417 if (declarationScope instanceof PsiMethod) {
418 PsiMethod method = (PsiMethod)declarationScope;
419 if (PsiUtilBase.hasErrorElementChild(method)) return null;
420 if ((method.isConstructor() ||
421 method.hasModifierProperty(PsiModifier.PRIVATE) ||
422 method.hasModifierProperty(PsiModifier.STATIC) ||
423 !method.hasModifierProperty(PsiModifier.ABSTRACT) &&
424 myUnusedSymbolInspection.REPORT_PARAMETER_FOR_PUBLIC_METHODS &&
425 !isOverriddenOrOverrides(method)) &&
426 !method.hasModifierProperty(PsiModifier.NATIVE) &&
427 !HighlightMethodUtil.isSerializationRelatedMethod(method, method.getContainingClass()) &&
428 !PsiClassImplUtil.isMainMethod(method)) {
429 HighlightInfo highlightInfo = checkUnusedParameter(parameter);
430 if (highlightInfo != null) {
431 QuickFixAction.registerQuickFixAction(highlightInfo, new RemoveUnusedParameterFix(parameter), myUnusedSymbolKey);
432 return highlightInfo;
436 else if (declarationScope instanceof PsiForeachStatement) {
437 HighlightInfo highlightInfo = checkUnusedParameter(parameter);
438 if (highlightInfo != null) {
439 QuickFixAction.registerQuickFixAction(highlightInfo, new EmptyIntentionAction(UnusedSymbolLocalInspection.DISPLAY_NAME), myUnusedSymbolKey);
440 return highlightInfo;
444 return null;
447 @Nullable
448 private HighlightInfo checkUnusedParameter(final PsiParameter parameter) {
449 if (!myRefCountHolder.isReferenced(parameter) && !isImplicitUsage(parameter)) {
450 PsiIdentifier identifier = parameter.getNameIdentifier();
451 assert identifier != null;
452 String message = JavaErrorMessages.message("parameter.is.not.used", identifier.getText());
453 return createUnusedSymbolInfo(identifier, message);
455 return null;
458 @Nullable
459 private HighlightInfo processMethod(final PsiMethod method) {
460 if (myRefCountHolder.isReferenced(method)) return null;
461 boolean isPrivate = method.hasModifierProperty(PsiModifier.PRIVATE);
462 PsiClass containingClass = method.getContainingClass();
463 if (isPrivate) {
464 if (HighlightMethodUtil.isSerializationRelatedMethod(method, containingClass) ||
465 isIntentionalPrivateConstructor(method, containingClass)) {
466 return null;
468 if (isImplicitUsage(method)) {
469 return null;
472 else {
473 //class maybe used in some weird way, e.g. from XML, therefore the only constructor is used too
474 if (containingClass != null && method.isConstructor() && containingClass.getConstructors().length == 1 && !isClassUnused(containingClass)) return null;
475 if (isImplicitUsage(method)) return null;
477 if (method.findSuperMethods().length != 0 || !weAreSureThereAreNoUsages(method)) {
478 return null;
481 String key = isPrivate
482 ? method.isConstructor() ? "private.constructor.is.not.used" : "private.method.is.not.used"
483 : method.isConstructor() ? "constructor.is.not.used" : "method.is.not.used";
484 String symbolName = HighlightMessageUtil.getSymbolName(method, PsiSubstitutor.EMPTY);
485 String message = JavaErrorMessages.message(key, symbolName);
486 PsiIdentifier identifier = method.getNameIdentifier();
487 final HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message);
488 QuickFixAction.registerQuickFixAction(highlightInfo, new SafeDeleteFix(method), myUnusedSymbolKey);
489 SpecialAnnotationsUtil.createAddToSpecialAnnotationFixes(method, new Processor<String>() {
490 public boolean process(final String annoName) {
491 QuickFixAction.registerQuickFixAction(highlightInfo, myUnusedSymbolInspection.createQuickFix(annoName, "methods"));
492 return true;
495 return highlightInfo;
498 private boolean weAreSureThereAreNoUsages(PsiMember member) {
499 if (myInLibrary) return false;
500 if (!myDeadCodeEnabled) return false;
501 if (myDeadCodeInspection.isEntryPoint(member)) return false;
503 String name = member.getName();
504 if (name == null) return false;
505 SearchScope useScope = member.getUseScope();
506 if (!(useScope instanceof GlobalSearchScope)) return false;
507 final int[] count = {0};
508 GlobalSearchScope scope = (GlobalSearchScope)useScope;
509 // some classes may have references from within XML outside dependent modules, e.g. our actions
510 if (member instanceof PsiClass) scope = scope.uniteWith(GlobalSearchScope.projectScope(myProject));
512 CacheManager cacheManager = ((PsiManagerEx)myFile.getManager()).getCacheManager();
513 if (!cacheManager.processFilesWithWord(new Processor<PsiFile>() {
514 public boolean process(PsiFile file) {
515 if (file == myFile) return true;
516 count[0]++;
517 return count[0] <= 10;
519 }, name, UsageSearchContext.ANY, scope, true)) return false;
521 //search usages if it cheap
522 //if count is 0 there is no usages since we've called myRefCountHolder.isReferenced() before
523 if (count[0] == 0 && !canbeReferencedViaWeirdNames(member)) return true;
525 Query<PsiReference> query = member instanceof PsiMethod
526 ? MethodReferencesSearch.search((PsiMethod)member, scope, false)
527 : ReferencesSearch.search(member, scope, true);
528 return query.findFirst() == null;
531 private static boolean canbeReferencedViaWeirdNames(PsiMember member) {
532 if (member instanceof PsiClass) return false;
533 PsiFile containingFile = member.getContainingFile();
534 if (!(containingFile instanceof PsiJavaFile)) return true; // Groovy field can be referenced from Java by getter
535 if (member instanceof PsiField) return false; //Java field cannot be referenced by anything but its name
536 if (member instanceof PsiMethod) {
537 return PropertyUtil.isSimplePropertyAccessor((PsiMethod)member); //Java accessors can be referenced by field name from Groovy
539 return false;
542 @Nullable
543 private HighlightInfo processClass(PsiClass aClass) {
544 if (!isClassUnused(aClass)) return null;
545 String element = "classes";
546 if (aClass.getContainingClass() != null && aClass.hasModifierProperty(PsiModifier.PRIVATE)) {
547 String pattern = aClass.isInterface()
548 ? "private.inner.interface.is.not.used"
549 : "private.inner.class.is.not.used";
550 return formatUnusedSymbolHighlightInfo(pattern, aClass, element);
552 if (aClass.getParent() instanceof PsiDeclarationStatement) { // local class
553 return formatUnusedSymbolHighlightInfo("local.class.is.not.used", aClass, element);
555 if (aClass instanceof PsiTypeParameter) {
556 return formatUnusedSymbolHighlightInfo("type.parameter.is.not.used", aClass, element);
558 return formatUnusedSymbolHighlightInfo("class.is.not.used", aClass, element);
561 private final Map<PsiClass, Boolean> unusedClassCache = new THashMap<PsiClass, Boolean>();
562 private boolean isClassUnused(PsiClass aClass) {
563 if (aClass == null) return false;
564 Boolean result = unusedClassCache.get(aClass);
565 if (result == null) {
566 result = isReallyUnused(aClass);
567 unusedClassCache.put(aClass, result);
569 return result;
572 private boolean isReallyUnused(PsiClass aClass) {
573 if (isImplicitUsage(aClass) || myRefCountHolder.isReferenced(aClass)) return false;
574 return aClass.getContainingClass() != null && aClass.hasModifierProperty(PsiModifier.PRIVATE) ||
575 aClass.getParent() instanceof PsiDeclarationStatement ||
576 aClass instanceof PsiTypeParameter ||
577 weAreSureThereAreNoUsages(aClass);
580 private HighlightInfo formatUnusedSymbolHighlightInfo(@PropertyKey(resourceBundle = JavaErrorMessages.BUNDLE) String pattern,
581 PsiNameIdentifierOwner aClass, final String element) {
582 String symbolName = aClass.getName();
583 String message = JavaErrorMessages.message(pattern, symbolName);
584 PsiElement identifier = aClass.getNameIdentifier();
585 final HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message);
586 QuickFixAction.registerQuickFixAction(highlightInfo, new SafeDeleteFix(aClass), myUnusedSymbolKey);
587 SpecialAnnotationsUtil.createAddToSpecialAnnotationFixes((PsiModifierListOwner)aClass, new Processor<String>() {
588 public boolean process(final String annoName) {
589 QuickFixAction.registerQuickFixAction(highlightInfo, myUnusedSymbolInspection.createQuickFix(annoName, element));
590 return true;
593 return highlightInfo;
596 @Nullable
597 private HighlightInfo processImport(PsiImportStatementBase importStatement, HighlightDisplayKey unusedImportKey) {
598 // jsp include directive hack
599 if (importStatement instanceof JspxImportStatement && ((JspxImportStatement)importStatement).isForeignFileImport()) return null;
601 if (PsiUtilBase.hasErrorElementChild(importStatement)) return null;
603 boolean isRedundant = myRefCountHolder.isRedundant(importStatement);
604 if (!isRedundant && !(importStatement instanceof PsiImportStaticStatement)) {
605 //check import from same package
606 String packageName = ((PsiClassOwner)importStatement.getContainingFile()).getPackageName();
607 PsiJavaCodeReferenceElement reference = importStatement.getImportReference();
608 PsiElement resolved = reference == null ? null : reference.resolve();
609 if (resolved instanceof PsiPackage) {
610 isRedundant = packageName.equals(((PsiPackage)resolved).getQualifiedName());
612 else if (resolved instanceof PsiClass && !importStatement.isOnDemand()) {
613 String qName = ((PsiClass)resolved).getQualifiedName();
614 if (qName != null) {
615 String name = ((PsiClass)resolved).getName();
616 isRedundant = qName.equals(packageName + '.' + name);
621 if (isRedundant) {
622 return registerRedundantImport(importStatement, unusedImportKey);
625 int entryIndex = myStyleManager.findEntryIndex(importStatement);
626 if (entryIndex < myCurentEntryIndex) {
627 myHasMissortedImports = true;
629 myCurentEntryIndex = entryIndex;
631 return null;
634 private HighlightInfo registerRedundantImport(PsiImportStatementBase importStatement, HighlightDisplayKey unusedImportKey) {
635 HighlightInfo info = HighlightInfo.createHighlightInfo(JavaHightlightInfoTypes.UNUSED_IMPORT, importStatement, InspectionsBundle.message("unused.import.statement"));
637 QuickFixAction.registerQuickFixAction(info, new OptimizeImportsFix(), unusedImportKey);
638 QuickFixAction.registerQuickFixAction(info, new EnableOptimizeImportsOnTheFlyFix(), unusedImportKey);
639 myHasRedundantImports = true;
640 return info;
643 private boolean timeToOptimizeImports() {
644 if (!CodeStyleSettingsManager.getSettings(myProject).OPTIMIZE_IMPORTS_ON_THE_FLY) return false;
646 DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(myProject);
647 PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(myDocument);
648 // dont optimize out imports in JSP since it can be included in other JSP
649 if (file == null || !codeAnalyzer.isHighlightingAvailable(file) || !(file instanceof PsiJavaFile) || file instanceof JspFile) return false;
651 if (!codeAnalyzer.isErrorAnalyzingFinished(file)) return false;
652 List<HighlightInfo> errors = DaemonCodeAnalyzerImpl.getHighlights(myDocument, HighlightSeverity.ERROR, myProject);
654 return errors.isEmpty() && codeAnalyzer.canChangeFileSilently(myFile);
657 private static boolean isIntentionalPrivateConstructor(PsiMethod method, PsiClass containingClass) {
658 return method.isConstructor() &&
659 method.getParameterList().getParametersCount() == 0 &&
660 containingClass != null &&
661 containingClass.getConstructors().length == 1;