renamed
[fedora-idea.git] / java / java-impl / src / com / intellij / codeInsight / daemon / impl / PostHighlightingPass.java
bloba2a26bdf6229cd79d226c7a53855d4944c81a7e9
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.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.codeInspection.InspectionProfile;
32 import com.intellij.codeInspection.InspectionsBundle;
33 import com.intellij.codeInspection.deadCode.UnusedDeclarationInspection;
34 import com.intellij.codeInspection.ex.InspectionManagerEx;
35 import com.intellij.codeInspection.ex.LocalInspectionToolWrapper;
36 import com.intellij.codeInspection.unusedImport.UnusedImportLocalInspection;
37 import com.intellij.codeInspection.unusedSymbol.UnusedSymbolLocalInspection;
38 import com.intellij.codeInspection.util.SpecialAnnotationsUtil;
39 import com.intellij.lang.Language;
40 import com.intellij.lang.annotation.HighlightSeverity;
41 import com.intellij.openapi.application.ApplicationManager;
42 import com.intellij.openapi.command.CommandProcessor;
43 import com.intellij.openapi.diagnostic.Logger;
44 import com.intellij.openapi.editor.Document;
45 import com.intellij.openapi.editor.Editor;
46 import com.intellij.openapi.extensions.Extensions;
47 import com.intellij.openapi.progress.ProcessCanceledException;
48 import com.intellij.openapi.progress.ProgressIndicator;
49 import com.intellij.openapi.progress.ProgressManager;
50 import com.intellij.openapi.project.Project;
51 import com.intellij.openapi.roots.ProjectFileIndex;
52 import com.intellij.openapi.roots.ProjectRootManager;
53 import com.intellij.openapi.vfs.VirtualFile;
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.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.THashSet;
75 import gnu.trove.TObjectIntHashMap;
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.ArrayList;
82 import java.util.Collection;
83 import java.util.List;
84 import java.util.Set;
86 public class PostHighlightingPass extends TextEditorHighlightingPass {
87 private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.PostHighlightingPass");
88 private RefCountHolder myRefCountHolder;
89 private final PsiFile myFile;
90 @Nullable private final Editor myEditor;
91 private final int myStartOffset;
92 private final int myEndOffset;
94 private Collection<HighlightInfo> myHighlights;
95 private boolean myHasRedundantImports;
96 private final JavaCodeStyleManager myStyleManager;
97 private int myCurentEntryIndex;
98 private boolean myHasMissortedImports;
99 private final ImplicitUsageProvider[] myImplicitUsageProviders;
100 private UnusedDeclarationInspection myDeadCodeInspection;
101 private UnusedSymbolLocalInspection myUnusedSymbolInspection;
102 private HighlightDisplayKey myUnusedSymbolKey;
103 private boolean myDeadCodeEnabled;
104 private boolean myInLibrary;
105 private HighlightDisplayKey myDeadCodeKey;
106 private HighlightInfoType myDeadCodeInfoType;
108 private PostHighlightingPass(@NotNull Project project,
109 @NotNull PsiFile file,
110 @Nullable Editor editor,
111 @NotNull Document document,
112 int startOffset,
113 int endOffset) {
114 super(project, document, true);
115 myFile = file;
116 myEditor = editor;
117 myStartOffset = startOffset;
118 myEndOffset = endOffset;
120 myStyleManager = JavaCodeStyleManager.getInstance(myProject);
121 myCurentEntryIndex = -1;
123 myImplicitUsageProviders = Extensions.getExtensions(ImplicitUsageProvider.EP_NAME);
126 PostHighlightingPass(@NotNull Project project, @NotNull PsiFile file, @NotNull Editor editor, int startOffset, int endOffset) {
127 this(project, file, editor, editor.getDocument(), startOffset, endOffset);
130 public PostHighlightingPass(@NotNull Project project, @NotNull PsiFile file, @NotNull Document document, int startOffset, int endOffset) {
131 this(project, file, null, document, startOffset, endOffset);
134 public void doCollectInformation(ProgressIndicator progress) {
135 DaemonCodeAnalyzer daemonCodeAnalyzer = DaemonCodeAnalyzer.getInstance(myProject);
136 final FileStatusMap fileStatusMap = ((DaemonCodeAnalyzerImpl)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 (!HighlightLevelUtil.shouldHighlight(psiRoot)) continue;
144 List<PsiElement> elements = CollectHighlightsUtil.getElementsInRange(psiRoot, myStartOffset, myEndOffset);
145 elementSet.addAll(elements);
148 ProjectFileIndex fileIndex = ProjectRootManager.getInstance(myProject).getFileIndex();
149 VirtualFile virtualFile = viewProvider.getVirtualFile();
150 myInLibrary = fileIndex.isInLibraryClasses(virtualFile) || fileIndex.isInLibrarySource(virtualFile);
152 myRefCountHolder = RefCountHolder.getInstance(myFile);
153 if (!myRefCountHolder.retrieveUnusedReferencesInfo(new Runnable() {
154 public void run() {
155 collectHighlights(elementSet, highlights);
156 myHighlights = highlights;
157 for (HighlightInfo info : highlights) {
158 if (info.getSeverity() == HighlightSeverity.ERROR) {
159 fileStatusMap.setErrorFoundFlag(myDocument, true);
160 break;
164 })) {
165 // we must be sure GHP will restart
166 fileStatusMap.markFileScopeDirty(getDocument(), Pass.UPDATE_ALL);
167 GeneralHighlightingPass.cancelAndRestartDaemonLater(progress, myProject, this);
171 public void doApplyInformationToEditor() {
172 if (myHighlights == null) return;
173 UpdateHighlightersUtil.setHighlightersToEditor(myProject, myDocument, myStartOffset, myEndOffset, myHighlights, Pass.POST_UPDATE_ALL);
175 DaemonCodeAnalyzer daemonCodeAnalyzer = DaemonCodeAnalyzer.getInstance(myProject);
176 ((DaemonCodeAnalyzerImpl)daemonCodeAnalyzer).getFileStatusMap().markFileUpToDate(myDocument, myFile, getId());
178 if (timeToOptimizeImports() && myEditor != null) {
179 optimizeImportsOnTheFly();
183 private void optimizeImportsOnTheFly() {
184 if (myHasRedundantImports || myHasMissortedImports) {
185 ApplicationManager.getApplication().invokeLater(new Runnable() {
186 public void run() {
187 CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() {
188 public void run() {
189 ApplicationManager.getApplication().runWriteAction(new Runnable() {
190 public void run() {
191 OptimizeImportsFix optimizeImportsFix = new OptimizeImportsFix();
192 if (optimizeImportsFix.isAvailable(myProject, myEditor, myFile) && myFile.isWritable()) {
193 PsiDocumentManager.getInstance(myProject).commitAllDocuments();
194 optimizeImportsFix.invoke(myProject, myEditor, myFile);
205 @TestOnly
206 public Collection<HighlightInfo> getHighlights() {
207 return myHighlights;
210 private void collectHighlights(Collection<PsiElement> elements, final List<HighlightInfo> result) throws ProcessCanceledException {
211 ApplicationManager.getApplication().assertReadAccessAllowed();
213 InspectionProfile profile = InspectionProjectProfileManager.getInstance(myProject).getInspectionProfile();
214 myUnusedSymbolKey = HighlightDisplayKey.find(UnusedSymbolLocalInspection.SHORT_NAME);
215 boolean unusedSymbolEnabled = profile.isToolEnabled(myUnusedSymbolKey, myFile);
216 HighlightDisplayKey unusedImportKey = HighlightDisplayKey.find(UnusedImportLocalInspection.SHORT_NAME);
217 boolean unusedImportEnabled = profile.isToolEnabled(unusedImportKey, myFile);
218 LocalInspectionToolWrapper unusedSymbolTool = (LocalInspectionToolWrapper)profile.getInspectionTool(UnusedSymbolLocalInspection.SHORT_NAME,
219 myFile);
220 myUnusedSymbolInspection = unusedSymbolTool == null ? null : (UnusedSymbolLocalInspection)unusedSymbolTool.getTool();
221 LOG.assertTrue(ApplicationManager.getApplication().isUnitTestMode() || myUnusedSymbolInspection != null);
223 myDeadCodeKey = HighlightDisplayKey.find(UnusedDeclarationInspection.SHORT_NAME);
224 myDeadCodeInspection = (UnusedDeclarationInspection)profile.getInspectionTool(UnusedDeclarationInspection.SHORT_NAME, myFile);
225 myDeadCodeEnabled = profile.isToolEnabled(myDeadCodeKey, myFile);
226 if (unusedImportEnabled && JspPsiUtil.isInJspFile(myFile)) {
227 final JspFile jspFile = JspPsiUtil.getJspFile(myFile);
228 if (jspFile != null) {
229 unusedImportEnabled = !JspSpiUtil.isIncludedOrIncludesSomething(jspFile);
233 myDeadCodeInfoType = myDeadCodeKey == null ? null : new HighlightInfoType.HighlightInfoTypeImpl(profile.getErrorLevel(myDeadCodeKey, myFile).getSeverity(), HighlightInfoType.UNUSED_SYMBOL.getAttributesKey());
235 if (!unusedSymbolEnabled && !unusedImportEnabled) {
236 return;
238 for (PsiElement element : elements) {
239 ProgressManager.checkCanceled();
241 if (unusedSymbolEnabled && element instanceof PsiIdentifier) {
242 PsiIdentifier identifier = (PsiIdentifier)element;
243 HighlightInfo info = processIdentifier(identifier);
244 if (info != null) {
245 result.add(info);
248 else if (unusedImportEnabled && element instanceof PsiImportList) {
249 final PsiImportStatementBase[] imports = ((PsiImportList)element).getAllImportStatements();
250 for (PsiImportStatementBase statement : imports) {
251 ProgressManager.checkCanceled();
252 final HighlightInfo info = processImport(statement, unusedImportKey);
253 if (info != null) {
254 result.add(info);
261 @Nullable
262 private HighlightInfo processIdentifier(PsiIdentifier identifier) {
263 if (InspectionManagerEx.inspectionResultSuppressed(identifier, myUnusedSymbolInspection)) return null;
264 PsiElement parent = identifier.getParent();
265 if (PsiUtilBase.hasErrorElementChild(parent)) return null;
266 HighlightInfo info;
268 if (parent instanceof PsiLocalVariable && myUnusedSymbolInspection.LOCAL_VARIABLE) {
269 info = processLocalVariable((PsiLocalVariable)parent);
271 else if (parent instanceof PsiField && myUnusedSymbolInspection.FIELD) {
272 final PsiField psiField = (PsiField)parent;
273 info = processField(psiField, identifier);
275 else if (parent instanceof PsiParameter && myUnusedSymbolInspection.PARAMETER) {
276 info = processParameter((PsiParameter)parent);
278 else if (parent instanceof PsiMethod && myUnusedSymbolInspection.METHOD) {
279 info = processMethod((PsiMethod)parent);
281 else if (parent instanceof PsiClass && identifier.equals(((PsiClass)parent).getNameIdentifier()) && myUnusedSymbolInspection.CLASS) {
282 info = processClass((PsiClass)parent);
284 else {
285 return null;
287 return info;
291 @Nullable
292 private HighlightInfo processLocalVariable(PsiLocalVariable variable) {
293 PsiIdentifier identifier = variable.getNameIdentifier();
294 if (identifier == null) return null;
295 if (isImplicitUsage(variable)) return null;
296 if (!myRefCountHolder.isReferenced(variable)) {
297 String message = JavaErrorMessages.message("local.variable.is.never.used", identifier.getText());
298 HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL);
299 QuickFixAction.registerQuickFixAction(highlightInfo, new RemoveUnusedVariableFix(variable), myUnusedSymbolKey);
300 return highlightInfo;
303 boolean referenced = myRefCountHolder.isReferencedForRead(variable);
304 if (!referenced && !isImplicitRead(variable)) {
305 String message = JavaErrorMessages.message("local.variable.is.not.used.for.reading", identifier.getText());
306 HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL);
307 QuickFixAction.registerQuickFixAction(highlightInfo, new RemoveUnusedVariableFix(variable), myUnusedSymbolKey);
308 return highlightInfo;
311 if (!variable.hasInitializer()) {
312 referenced = myRefCountHolder.isReferencedForWrite(variable);
313 if (!referenced && !isImplicitWrite(variable)) {
314 String message = JavaErrorMessages.message("local.variable.is.not.assigned", identifier.getText());
315 final HighlightInfo unusedSymbolInfo = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL);
316 QuickFixAction.registerQuickFixAction(unusedSymbolInfo, new EmptyIntentionAction(UnusedSymbolLocalInspection.DISPLAY_NAME), myUnusedSymbolKey);
317 return unusedSymbolInfo;
321 return null;
325 private boolean isImplicitUsage(final PsiModifierListOwner element) {
326 if (UnusedSymbolLocalInspection.isInjected(element, myUnusedSymbolInspection)) return true;
327 for (ImplicitUsageProvider provider : myImplicitUsageProviders) {
328 ProgressManager.checkCanceled();
329 if (provider.isImplicitUsage(element)) {
330 return true;
334 return false;
337 private boolean isImplicitRead(final PsiVariable element) {
338 for(ImplicitUsageProvider provider: myImplicitUsageProviders) {
339 ProgressManager.checkCanceled();
340 if (provider.isImplicitRead(element)) {
341 return true;
344 return false;
347 private boolean isImplicitWrite(final PsiVariable element) {
348 for(ImplicitUsageProvider provider: myImplicitUsageProviders) {
349 ProgressManager.checkCanceled();
350 if (provider.isImplicitWrite(element)) {
351 return true;
354 return false;
357 private HighlightInfo createUnusedSymbolInfo(PsiElement element, String message, final HighlightInfoType highlightInfoType) {
358 return HighlightInfo.createHighlightInfo(highlightInfoType, element, message);
361 private HighlightInfo createDeadCodeInfo(PsiElement element, String message) {
362 return HighlightInfo.createHighlightInfo(myDeadCodeInfoType, element, message);
365 @Nullable
366 private HighlightInfo processField(final PsiField field, final PsiIdentifier identifier) {
367 if (isImplicitUsage(field)) return null;
368 if (field.hasModifierProperty(PsiModifier.PRIVATE)) {
369 if (!myRefCountHolder.isReferenced(field)) {
370 if (HighlightUtil.isSerializationImplicitlyUsedField(field)) {
371 return null;
373 String message = JavaErrorMessages.message("private.field.is.not.used", identifier.getText());
375 HighlightInfo highlightInfo = suggestionsToMakeFieldUsed(field, identifier, message);
376 QuickFixAction.registerQuickFixAction(highlightInfo, HighlightMethodUtil.getFixRange(field), new CreateConstructorParameterFromFieldFix(field), null);
377 return highlightInfo;
380 final boolean readReferenced = myRefCountHolder.isReferencedForRead(field);
381 if (!readReferenced && !isImplicitRead(field)) {
382 String message = JavaErrorMessages.message("private.field.is.not.used.for.reading", identifier.getText());
383 return suggestionsToMakeFieldUsed(field, identifier, message);
386 if (field.hasInitializer()) {
387 return null;
389 final boolean writeReferenced = myRefCountHolder.isReferencedForWrite(field);
390 if (!writeReferenced && !isImplicitWrite(field)) {
391 String message = JavaErrorMessages.message("private.field.is.not.assigned", identifier.getText());
392 final HighlightInfo info = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL);
394 QuickFixAction.registerQuickFixAction(info, new CreateGetterOrSetterFix(false, true, field), myUnusedSymbolKey);
395 QuickFixAction.registerQuickFixAction(info, HighlightMethodUtil.getFixRange(field), new CreateConstructorParameterFromFieldFix(field), null);
396 SpecialAnnotationsUtil.createAddToSpecialAnnotationFixes(field, new Processor<String>() {
397 public boolean process(final String annoName) {
398 QuickFixAction.registerQuickFixAction(info, myUnusedSymbolInspection.createQuickFix(annoName, "fields"));
399 return true;
402 return info;
405 else if (!myRefCountHolder.isReferenced(field) && weAreSureThereAreNoUsages(field)) {
406 return formatUnusedSymbolHighlightInfo("field.is.not.used", field, "fields", myDeadCodeKey, myDeadCodeInfoType);
408 return null;
411 private HighlightInfo suggestionsToMakeFieldUsed(final PsiField field, final PsiIdentifier identifier, final String message) {
412 HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL);
413 QuickFixAction.registerQuickFixAction(highlightInfo, new RemoveUnusedVariableFix(field), myUnusedSymbolKey);
414 QuickFixAction.registerQuickFixAction(highlightInfo, new CreateGetterOrSetterFix(true, false, field), myUnusedSymbolKey);
415 QuickFixAction.registerQuickFixAction(highlightInfo, new CreateGetterOrSetterFix(false, true, field), myUnusedSymbolKey);
416 QuickFixAction.registerQuickFixAction(highlightInfo, new CreateGetterOrSetterFix(true, true, field), myUnusedSymbolKey);
417 return highlightInfo;
420 private static boolean isOverriddenOrOverrides(PsiMethod method) {
421 boolean overrides = SuperMethodsSearch.search(method, null, true, false).findFirst() != null;
422 return overrides || OverridingMethodsSearch.search(method).findFirst() != null;
425 @Nullable
426 private HighlightInfo processParameter(PsiParameter parameter) {
427 PsiElement declarationScope = parameter.getDeclarationScope();
428 if (declarationScope instanceof PsiMethod) {
429 PsiMethod method = (PsiMethod)declarationScope;
430 if (PsiUtilBase.hasErrorElementChild(method)) return null;
431 if ((method.isConstructor() ||
432 method.hasModifierProperty(PsiModifier.PRIVATE) ||
433 method.hasModifierProperty(PsiModifier.STATIC) ||
434 !method.hasModifierProperty(PsiModifier.ABSTRACT) &&
435 myUnusedSymbolInspection.REPORT_PARAMETER_FOR_PUBLIC_METHODS &&
436 !isOverriddenOrOverrides(method)) &&
437 !method.hasModifierProperty(PsiModifier.NATIVE) &&
438 !HighlightMethodUtil.isSerializationRelatedMethod(method, method.getContainingClass()) &&
439 !PsiClassImplUtil.isMainMethod(method)) {
440 HighlightInfo highlightInfo = checkUnusedParameter(parameter);
441 if (highlightInfo != null) {
442 QuickFixAction.registerQuickFixAction(highlightInfo, new RemoveUnusedParameterFix(parameter), myUnusedSymbolKey);
443 return highlightInfo;
447 else if (declarationScope instanceof PsiForeachStatement) {
448 HighlightInfo highlightInfo = checkUnusedParameter(parameter);
449 if (highlightInfo != null) {
450 QuickFixAction.registerQuickFixAction(highlightInfo, new EmptyIntentionAction(UnusedSymbolLocalInspection.DISPLAY_NAME), myUnusedSymbolKey);
451 return highlightInfo;
455 return null;
458 @Nullable
459 private HighlightInfo checkUnusedParameter(final PsiParameter parameter) {
460 if (!myRefCountHolder.isReferenced(parameter) && !isImplicitUsage(parameter)) {
461 PsiIdentifier identifier = parameter.getNameIdentifier();
462 assert identifier != null;
463 String message = JavaErrorMessages.message("parameter.is.not.used", identifier.getText());
464 return createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL);
466 return null;
469 @Nullable
470 private HighlightInfo processMethod(final PsiMethod method) {
471 if (myRefCountHolder.isReferenced(method)) return null;
472 boolean isPrivate = method.hasModifierProperty(PsiModifier.PRIVATE);
473 PsiClass containingClass = method.getContainingClass();
474 HighlightInfoType highlightInfoType = HighlightInfoType.UNUSED_SYMBOL;
475 HighlightDisplayKey highlightDisplayKey = myUnusedSymbolKey;
477 if (isPrivate) {
478 if (HighlightMethodUtil.isSerializationRelatedMethod(method, containingClass) ||
479 isIntentionalPrivateConstructor(method, containingClass)) {
480 return null;
482 if (isImplicitUsage(method)) {
483 return null;
486 else {
487 //class maybe used in some weird way, e.g. from XML, therefore the only constructor is used too
488 if (containingClass != null && method.isConstructor() && containingClass.getConstructors().length == 1 && isClassUnused(containingClass) == USED) return null;
489 if (isImplicitUsage(method)) return null;
491 if (method.findSuperMethods().length != 0) {
492 return null;
494 if (!weAreSureThereAreNoUsages(method)) {
495 return null;
497 highlightInfoType = myDeadCodeInfoType;
498 highlightDisplayKey = myDeadCodeKey;
500 String key = isPrivate
501 ? method.isConstructor() ? "private.constructor.is.not.used" : "private.method.is.not.used"
502 : method.isConstructor() ? "constructor.is.not.used" : "method.is.not.used";
503 String symbolName = HighlightMessageUtil.getSymbolName(method, PsiSubstitutor.EMPTY);
504 String message = JavaErrorMessages.message(key, symbolName);
505 PsiIdentifier identifier = method.getNameIdentifier();
506 final HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, highlightInfoType);
507 QuickFixAction.registerQuickFixAction(highlightInfo, new SafeDeleteFix(method), highlightDisplayKey);
508 SpecialAnnotationsUtil.createAddToSpecialAnnotationFixes(method, new Processor<String>() {
509 public boolean process(final String annoName) {
510 QuickFixAction.registerQuickFixAction(highlightInfo, myUnusedSymbolInspection.createQuickFix(annoName, "methods"));
511 return true;
514 return highlightInfo;
517 private boolean weAreSureThereAreNoUsages(PsiMember member) {
518 if (myInLibrary) return false;
519 if (!myDeadCodeEnabled) return false;
520 if (myDeadCodeInspection.isEntryPoint(member)) return false;
522 String name = member.getName();
523 if (name == null) return false;
524 SearchScope useScope = member.getUseScope();
525 if (!(useScope instanceof GlobalSearchScope)) return false;
526 final int[] count = {0};
527 GlobalSearchScope scope = (GlobalSearchScope)useScope;
528 // some classes may have references from within XML outside dependent modules, e.g. our actions
529 if (member instanceof PsiClass) scope = scope.uniteWith(GlobalSearchScope.projectScope(myProject));
531 CacheManager cacheManager = ((PsiManagerEx)myFile.getManager()).getCacheManager();
532 if (!cacheManager.processFilesWithWord(new Processor<PsiFile>() {
533 public boolean process(PsiFile file) {
534 if (file == myFile) return true;
535 count[0]++;
536 return count[0] <= 10;
538 }, name, UsageSearchContext.ANY, scope, true)) return false;
540 //search usages if it cheap
541 //if count is 0 there is no usages since we've called myRefCountHolder.isReferenced() before
542 if (count[0] == 0 && !canbeReferencedViaWeirdNames(member)) return true;
544 Query<PsiReference> query = member instanceof PsiMethod
545 ? MethodReferencesSearch.search((PsiMethod)member, scope, false)
546 : ReferencesSearch.search(member, scope, true);
547 return query.findFirst() == null;
550 private static boolean canbeReferencedViaWeirdNames(PsiMember member) {
551 if (member instanceof PsiClass) return false;
552 PsiFile containingFile = member.getContainingFile();
553 if (!(containingFile instanceof PsiJavaFile)) return true; // Groovy field can be referenced from Java by getter
554 if (member instanceof PsiField) return false; //Java field cannot be referenced by anything but its name
555 if (member instanceof PsiMethod) {
556 return PropertyUtil.isSimplePropertyAccessor((PsiMethod)member); //Java accessors can be referenced by field name from Groovy
558 return false;
561 @Nullable
562 private HighlightInfo processClass(PsiClass aClass) {
563 int usage = isClassUnused(aClass);
564 if (usage == USED) return null;
566 String pattern;
567 HighlightDisplayKey highlightDisplayKey;
568 HighlightInfoType highlightInfoType;
569 if (aClass.getContainingClass() != null && aClass.hasModifierProperty(PsiModifier.PRIVATE)) {
570 pattern = aClass.isInterface()
571 ? "private.inner.interface.is.not.used"
572 : "private.inner.class.is.not.used";
573 highlightDisplayKey = myUnusedSymbolKey;
574 highlightInfoType = HighlightInfoType.UNUSED_SYMBOL;
576 else if (aClass.getParent() instanceof PsiDeclarationStatement) { // local class
577 pattern = "local.class.is.not.used";
578 highlightDisplayKey = myUnusedSymbolKey;
579 highlightInfoType = HighlightInfoType.UNUSED_SYMBOL;
581 else if (aClass instanceof PsiTypeParameter) {
582 pattern = "type.parameter.is.not.used";
583 highlightDisplayKey = myUnusedSymbolKey;
584 highlightInfoType = HighlightInfoType.UNUSED_SYMBOL;
586 else {
587 pattern = "class.is.not.used";
588 highlightDisplayKey = myDeadCodeKey;
589 highlightInfoType = myDeadCodeInfoType;
591 return formatUnusedSymbolHighlightInfo(pattern, aClass, "classes", highlightDisplayKey, highlightInfoType);
594 private static final int USED = 1;
595 private static final int UNUSED_LOCALLY = 2;
596 private static final int UNUSED_GLOBALLY = 3;
597 private final TObjectIntHashMap<PsiClass> unusedClassCache = new TObjectIntHashMap<PsiClass>();
598 private int isClassUnused(PsiClass aClass) {
599 if (aClass == null) return USED;
600 int result = unusedClassCache.get(aClass);
601 if (result == 0) {
602 result = isReallyUnused(aClass);
603 unusedClassCache.put(aClass, result);
605 return result;
608 private int isReallyUnused(PsiClass aClass) {
609 if (isImplicitUsage(aClass) || myRefCountHolder.isReferenced(aClass)) return USED;
610 if (aClass.getContainingClass() != null && aClass.hasModifierProperty(PsiModifier.PRIVATE) ||
611 aClass.getParent() instanceof PsiDeclarationStatement ||
612 aClass instanceof PsiTypeParameter) return UNUSED_LOCALLY;
613 if (weAreSureThereAreNoUsages(aClass)) return UNUSED_GLOBALLY;
614 return USED;
617 private HighlightInfo formatUnusedSymbolHighlightInfo(@PropertyKey(resourceBundle = JavaErrorMessages.BUNDLE) String pattern,
618 PsiNameIdentifierOwner aClass,
619 final String element,
620 final HighlightDisplayKey highlightDisplayKey,
621 final HighlightInfoType highlightInfoType) {
622 String symbolName = aClass.getName();
623 String message = JavaErrorMessages.message(pattern, symbolName);
624 PsiElement identifier = aClass.getNameIdentifier();
625 final HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, highlightInfoType);
626 QuickFixAction.registerQuickFixAction(highlightInfo, new SafeDeleteFix(aClass), highlightDisplayKey);
627 SpecialAnnotationsUtil.createAddToSpecialAnnotationFixes((PsiModifierListOwner)aClass, new Processor<String>() {
628 public boolean process(final String annoName) {
629 QuickFixAction.registerQuickFixAction(highlightInfo, myUnusedSymbolInspection.createQuickFix(annoName, element));
630 return true;
633 return highlightInfo;
636 @Nullable
637 private HighlightInfo processImport(PsiImportStatementBase importStatement, HighlightDisplayKey unusedImportKey) {
638 // jsp include directive hack
639 if (importStatement instanceof JspxImportStatement && ((JspxImportStatement)importStatement).isForeignFileImport()) return null;
641 if (PsiUtilBase.hasErrorElementChild(importStatement)) return null;
643 boolean isRedundant = myRefCountHolder.isRedundant(importStatement);
644 if (!isRedundant && !(importStatement instanceof PsiImportStaticStatement)) {
645 //check import from same package
646 String packageName = ((PsiClassOwner)importStatement.getContainingFile()).getPackageName();
647 PsiJavaCodeReferenceElement reference = importStatement.getImportReference();
648 PsiElement resolved = reference == null ? null : reference.resolve();
649 if (resolved instanceof PsiPackage) {
650 isRedundant = packageName.equals(((PsiPackage)resolved).getQualifiedName());
652 else if (resolved instanceof PsiClass && !importStatement.isOnDemand()) {
653 String qName = ((PsiClass)resolved).getQualifiedName();
654 if (qName != null) {
655 String name = ((PsiClass)resolved).getName();
656 isRedundant = qName.equals(packageName + '.' + name);
661 if (isRedundant) {
662 return registerRedundantImport(importStatement, unusedImportKey);
665 int entryIndex = myStyleManager.findEntryIndex(importStatement);
666 if (entryIndex < myCurentEntryIndex) {
667 myHasMissortedImports = true;
669 myCurentEntryIndex = entryIndex;
671 return null;
674 private HighlightInfo registerRedundantImport(PsiImportStatementBase importStatement, HighlightDisplayKey unusedImportKey) {
675 HighlightInfo info = HighlightInfo.createHighlightInfo(JavaHightlightInfoTypes.UNUSED_IMPORT, importStatement, InspectionsBundle.message("unused.import.statement"));
677 QuickFixAction.registerQuickFixAction(info, new OptimizeImportsFix(), unusedImportKey);
678 QuickFixAction.registerQuickFixAction(info, new EnableOptimizeImportsOnTheFlyFix(), unusedImportKey);
679 myHasRedundantImports = true;
680 return info;
683 private boolean timeToOptimizeImports() {
684 if (!CodeInsightSettings.getInstance().OPTIMIZE_IMPORTS_ON_THE_FLY) return false;
686 DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(myProject);
687 PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(myDocument);
688 // dont optimize out imports in JSP since it can be included in other JSP
689 if (file == null || !codeAnalyzer.isHighlightingAvailable(file) || !(file instanceof PsiJavaFile) || file instanceof JspFile) return false;
691 if (!codeAnalyzer.isErrorAnalyzingFinished(file)) return false;
692 List<HighlightInfo> errors = DaemonCodeAnalyzerImpl.getHighlights(myDocument, HighlightSeverity.ERROR, myProject);
694 return errors.isEmpty() && codeAnalyzer.canChangeFileSilently(myFile);
697 private static boolean isIntentionalPrivateConstructor(PsiMethod method, PsiClass containingClass) {
698 return method.isConstructor() &&
699 method.getParameterList().getParametersCount() == 0 &&
700 containingClass != null &&
701 containingClass.getConstructors().length == 1;