Java tutorial
/* * Copyright 2000-2012 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.codeInsight.daemon.impl; import static com.intellij.psi.search.PsiSearchHelper.SearchCostResult.TOO_MANY_OCCURRENCES; import static com.intellij.psi.search.PsiSearchHelper.SearchCostResult.ZERO_OCCURRENCES; import gnu.trove.THashSet; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; import org.consulo.psi.PsiPackage; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.PropertyKey; import com.intellij.codeHighlighting.Pass; import com.intellij.codeInsight.CodeInsightSettings; import com.intellij.codeInsight.daemon.HighlightDisplayKey; import com.intellij.codeInsight.daemon.ImplicitUsageProvider; import com.intellij.codeInsight.daemon.JavaErrorMessages; import com.intellij.codeInsight.daemon.impl.analysis.HighlightMessageUtil; import com.intellij.codeInsight.daemon.impl.analysis.HighlightMethodUtil; import com.intellij.codeInsight.daemon.impl.analysis.HighlightUtil; import com.intellij.codeInsight.daemon.impl.analysis.HighlightingLevelManager; import com.intellij.codeInsight.daemon.impl.analysis.JavaHighlightUtil; import com.intellij.codeInsight.daemon.impl.quickfix.CreateConstructorParameterFromFieldFix; import com.intellij.codeInsight.daemon.impl.quickfix.CreateGetterOrSetterFix; import com.intellij.codeInsight.daemon.impl.quickfix.EnableOptimizeImportsOnTheFlyFix; import com.intellij.codeInsight.daemon.impl.quickfix.OptimizeImportsFix; import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixAction; import com.intellij.codeInsight.daemon.impl.quickfix.RemoveUnusedParameterFix; import com.intellij.codeInsight.daemon.impl.quickfix.RemoveUnusedVariableFix; import com.intellij.codeInsight.daemon.impl.quickfix.RenameToIgnoredFix; import com.intellij.codeInsight.daemon.impl.quickfix.SafeDeleteFix; import com.intellij.codeInsight.intention.EmptyIntentionAction; import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.codeInsight.intention.IntentionManager; import com.intellij.codeInspection.InspectionProfile; import com.intellij.codeInspection.InspectionsBundle; import com.intellij.codeInspection.SuppressIntentionActionFromFix; import com.intellij.codeInspection.SuppressQuickFix; import com.intellij.codeInspection.SuppressionUtil; import com.intellij.codeInspection.deadCode.UnusedDeclarationInspection; import com.intellij.codeInspection.reference.UnusedDeclarationFixProvider; import com.intellij.codeInspection.unusedImport.UnusedImportLocalInspection; import com.intellij.codeInspection.unusedParameters.UnusedParametersInspection; import com.intellij.codeInspection.unusedSymbol.UnusedSymbolLocalInspection; import com.intellij.codeInspection.util.SpecialAnnotationsUtilBase; import com.intellij.diagnostic.AttachmentFactory; import com.intellij.diagnostic.LogMessageEx; import com.intellij.find.FindManager; import com.intellij.find.findUsages.FindUsagesHandler; import com.intellij.find.findUsages.FindUsagesManager; import com.intellij.find.findUsages.FindUsagesOptions; import com.intellij.find.findUsages.JavaFindUsagesHandler; import com.intellij.find.findUsages.JavaFindUsagesHandlerFactory; import com.intellij.find.impl.FindManagerImpl; import com.intellij.lang.Language; import com.intellij.lang.annotation.HighlightSeverity; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.command.undo.UndoManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ProjectFileIndex; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.pom.PomNamedTarget; import com.intellij.profile.codeInspection.InspectionProjectProfileManager; import com.intellij.psi.*; import com.intellij.psi.codeStyle.JavaCodeStyleManager; import com.intellij.psi.impl.PsiClassImplUtil; import com.intellij.psi.impl.source.PsiClassImpl; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.PsiNonJavaFileReferenceProcessor; import com.intellij.psi.search.PsiSearchHelper; import com.intellij.psi.search.SearchScope; import com.intellij.psi.search.searches.OverridingMethodsSearch; import com.intellij.psi.search.searches.ReferencesSearch; import com.intellij.psi.search.searches.SuperMethodsSearch; import com.intellij.psi.util.PropertyUtil; import com.intellij.psi.util.PsiUtil; import com.intellij.psi.util.PsiUtilCore; import com.intellij.refactoring.changeSignature.ChangeSignatureGestureDetector; import com.intellij.util.Processor; public class PostHighlightingPass extends ProgressableTextEditorHighlightingPass { private static final Logger LOG = Logger .getInstance("#com.intellij.codeInsight.daemon.impl.PostHighlightingPass"); private RefCountHolder myRefCountHolder; private final PsiFile myFile; @Nullable private final Editor myEditor; private final int myStartOffset; private final int myEndOffset; private Collection<HighlightInfo> myHighlights; private boolean myHasRedundantImports; private final JavaCodeStyleManager myStyleManager; private int myCurrentEntryIndex; private boolean myHasMissortedImports; private static final ImplicitUsageProvider[] ourImplicitUsageProviders = Extensions .getExtensions(ImplicitUsageProvider.EP_NAME); private UnusedDeclarationInspection myDeadCodeInspection; private UnusedSymbolLocalInspection myUnusedSymbolInspection; private HighlightDisplayKey myUnusedSymbolKey; private boolean myDeadCodeEnabled; private boolean myInLibrary; private HighlightDisplayKey myDeadCodeKey; private HighlightInfoType myDeadCodeInfoType; private UnusedParametersInspection myUnusedParametersInspection; PostHighlightingPass(@NotNull Project project, @NotNull PsiFile file, @Nullable Editor editor, @NotNull Document document, @NotNull HighlightInfoProcessor highlightInfoProcessor) { super(project, document, "Unused symbols", file, editor, file.getTextRange(), true, highlightInfoProcessor); myFile = file; myEditor = editor; myStartOffset = 0; myEndOffset = file.getTextLength(); myStyleManager = JavaCodeStyleManager.getInstance(myProject); myCurrentEntryIndex = -1; } @Override protected void collectInformationWithProgress(@NotNull final ProgressIndicator progress) { DaemonCodeAnalyzerEx daemonCodeAnalyzer = DaemonCodeAnalyzerEx.getInstanceEx(myProject); final FileStatusMap fileStatusMap = daemonCodeAnalyzer.getFileStatusMap(); final List<HighlightInfo> highlights = new ArrayList<HighlightInfo>(); final FileViewProvider viewProvider = myFile.getViewProvider(); final Set<Language> relevantLanguages = viewProvider.getLanguages(); final Set<PsiElement> elementSet = new THashSet<PsiElement>(); for (Language language : relevantLanguages) { PsiElement psiRoot = viewProvider.getPsi(language); if (!HighlightingLevelManager.getInstance(myProject).shouldHighlight(psiRoot)) { continue; } List<PsiElement> elements = CollectHighlightsUtil.getElementsInRange(psiRoot, myStartOffset, myEndOffset); elementSet.addAll(elements); } ProjectFileIndex fileIndex = ProjectRootManager.getInstance(myProject).getFileIndex(); VirtualFile virtualFile = viewProvider.getVirtualFile(); myInLibrary = fileIndex.isInLibraryClasses(virtualFile) || fileIndex.isInLibrarySource(virtualFile); myRefCountHolder = RefCountHolder.endUsing(myFile, progress); if (myRefCountHolder == null || !myRefCountHolder.retrieveUnusedReferencesInfo(progress, new Runnable() { @Override public void run() { boolean errorFound = collectHighlights(elementSet, highlights, progress); myHighlights = highlights; if (errorFound) { fileStatusMap.setErrorFoundFlag(myDocument, true); } } })) { // we must be sure GHP will restart fileStatusMap.markFileScopeDirty(getDocument(), Pass.UPDATE_ALL); GeneralHighlightingPass.cancelAndRestartDaemonLater(progress, myProject, this); } } @Override public List<HighlightInfo> getInfos() { return myHighlights == null ? null : new ArrayList<HighlightInfo>(myHighlights); } @Override protected void applyInformationWithProgress() { if (myHighlights == null) { return; } UpdateHighlightersUtil.setHighlightersToEditor(myProject, myDocument, myStartOffset, myEndOffset, myHighlights, getColorsScheme(), Pass.POST_UPDATE_ALL); PostHighlightingPassFactory.markFileUpToDate(myFile); Editor editor = myEditor; if (editor != null && timeToOptimizeImports()) { optimizeImportsOnTheFly(editor); } } private void optimizeImportsOnTheFly(@NotNull final Editor editor) { if (myHasRedundantImports || myHasMissortedImports) { final OptimizeImportsFix optimizeImportsFix = new OptimizeImportsFix(); if (optimizeImportsFix.isAvailable(myProject, editor, myFile) && myFile.isWritable()) { invokeOnTheFlyImportOptimizer(new Runnable() { @Override public void run() { optimizeImportsFix.invoke(myProject, editor, myFile); } }, myFile, editor); } } } public static void invokeOnTheFlyImportOptimizer(@NotNull final Runnable runnable, @NotNull final PsiFile file, @NotNull final Editor editor) { final long stamp = editor.getDocument().getModificationStamp(); ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { if (file.getProject().isDisposed() || editor.isDisposed() || editor.getDocument().getModificationStamp() != stamp) { return; } //no need to optimize imports on the fly during undo/redo final UndoManager undoManager = UndoManager.getInstance(editor.getProject()); if (undoManager.isUndoInProgress() || undoManager.isRedoInProgress()) { return; } PsiDocumentManager.getInstance(file.getProject()).commitAllDocuments(); String beforeText = file.getText(); final long oldStamp = editor.getDocument().getModificationStamp(); CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() { @Override public void run() { ApplicationManager.getApplication().runWriteAction(runnable); } }); if (oldStamp != editor.getDocument().getModificationStamp()) { String afterText = file.getText(); if (Comparing.strEqual(beforeText, afterText)) { LOG.error(LogMessageEx.createEvent("Import optimizer hasn't optimized any imports", file.getViewProvider().getVirtualFile().getPath(), AttachmentFactory.createAttachment(file.getViewProvider().getVirtualFile()))); } } } }); } // returns true if error highlight was created private boolean collectHighlights(@NotNull Collection<PsiElement> elements, @NotNull final List<HighlightInfo> result, @NotNull ProgressIndicator progress) throws ProcessCanceledException { ApplicationManager.getApplication().assertReadAccessAllowed(); InspectionProfile profile = InspectionProjectProfileManager.getInstance(myProject).getInspectionProfile(); myUnusedSymbolKey = HighlightDisplayKey.find(UnusedSymbolLocalInspection.SHORT_NAME); boolean unusedSymbolEnabled = profile.isToolEnabled(myUnusedSymbolKey, myFile); HighlightDisplayKey unusedImportKey = HighlightDisplayKey.find(UnusedImportLocalInspection.SHORT_NAME); boolean unusedImportEnabled = profile.isToolEnabled(unusedImportKey, myFile); myUnusedSymbolInspection = (UnusedSymbolLocalInspection) profile .getUnwrappedTool(UnusedSymbolLocalInspection.SHORT_NAME, myFile); LOG.assertTrue(ApplicationManager.getApplication().isUnitTestMode() || myUnusedSymbolInspection != null); myDeadCodeKey = HighlightDisplayKey.find(UnusedDeclarationInspection.SHORT_NAME); myDeadCodeInspection = (UnusedDeclarationInspection) profile .getUnwrappedTool(UnusedDeclarationInspection.SHORT_NAME, myFile); myDeadCodeEnabled = profile.isToolEnabled(myDeadCodeKey, myFile); myUnusedParametersInspection = (UnusedParametersInspection) profile .getUnwrappedTool(UnusedParametersInspection.SHORT_NAME, myFile); LOG.assertTrue( ApplicationManager.getApplication().isUnitTestMode() || myUnusedParametersInspection != null); /* if (unusedImportEnabled && JspPsiUtil.isInJspFile(myFile)) { final JspFile jspFile = JspPsiUtil.getJspFile(myFile); if (jspFile != null) { unusedImportEnabled = !JspSpiUtil.isIncludedOrIncludesSomething(jspFile); } } */ myDeadCodeInfoType = myDeadCodeKey == null ? null : new HighlightInfoType.HighlightInfoTypeImpl( profile.getErrorLevel(myDeadCodeKey, myFile).getSeverity(), HighlightInfoType.UNUSED_SYMBOL.getAttributesKey()); GlobalUsageHelper helper = new GlobalUsageHelper() { @Override public boolean shouldCheckUsages(@NotNull PsiMember member) { return !myInLibrary && myDeadCodeEnabled && !myDeadCodeInspection.isEntryPoint(member); } @Override public boolean isCurrentFileAlreadyChecked() { return true; } @Override public boolean isLocallyUsed(@NotNull PsiNamedElement member) { return myRefCountHolder.isReferenced(member); } }; boolean errorFound = false; if (unusedSymbolEnabled) { for (PsiElement element : elements) { progress.checkCanceled(); if (element instanceof PsiIdentifier) { PsiIdentifier identifier = (PsiIdentifier) element; HighlightInfo info = processIdentifier(identifier, progress, helper); if (info != null) { errorFound |= info.getSeverity() == HighlightSeverity.ERROR; result.add(info); } } } } if (unusedImportEnabled && myFile instanceof PsiJavaFile && HighlightingLevelManager.getInstance(myProject).shouldHighlight(myFile)) { PsiImportList importList = ((PsiJavaFile) myFile).getImportList(); if (importList != null) { final PsiImportStatementBase[] imports = importList.getAllImportStatements(); for (PsiImportStatementBase statement : imports) { progress.checkCanceled(); final HighlightInfo info = processImport(statement, unusedImportKey); if (info != null) { errorFound |= info.getSeverity() == HighlightSeverity.ERROR; result.add(info); } } } } return errorFound; } @Nullable private HighlightInfo processIdentifier(PsiIdentifier identifier, ProgressIndicator progress, GlobalUsageHelper helper) { if (SuppressionUtil.inspectionResultSuppressed(identifier, myUnusedSymbolInspection)) { return null; } PsiElement parent = identifier.getParent(); if (PsiUtilCore.hasErrorElementChild(parent)) { return null; } if (parent instanceof PsiLocalVariable && myUnusedSymbolInspection.LOCAL_VARIABLE) { return processLocalVariable((PsiLocalVariable) parent, identifier, progress); } if (parent instanceof PsiField && myUnusedSymbolInspection.FIELD) { return processField((PsiField) parent, identifier, progress, helper); } if (parent instanceof PsiParameter && myUnusedSymbolInspection.PARAMETER) { if (SuppressionUtil.isSuppressed(identifier, UnusedParametersInspection.SHORT_NAME)) { return null; } return processParameter((PsiParameter) parent, identifier, progress); } if (parent instanceof PsiMethod && myUnusedSymbolInspection.METHOD) { return processMethod((PsiMethod) parent, identifier, progress, helper); } if (parent instanceof PsiClass && myUnusedSymbolInspection.CLASS) { return processClass((PsiClass) parent, identifier, progress, helper); } return null; } @Nullable private HighlightInfo processLocalVariable(@NotNull PsiLocalVariable variable, @NotNull PsiIdentifier identifier, @NotNull ProgressIndicator progress) { if (variable instanceof PsiResourceVariable && PsiUtil.isIgnoredName(variable.getName())) { return null; } if (isImplicitUsage(variable, progress)) { return null; } if (!myRefCountHolder.isReferenced(variable)) { String message = JavaErrorMessages.message("local.variable.is.never.used", identifier.getText()); HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL); IntentionAction fix = variable instanceof PsiResourceVariable ? new RenameToIgnoredFix(variable) : new RemoveUnusedVariableFix(variable); QuickFixAction.registerQuickFixAction(highlightInfo, fix, myUnusedSymbolKey); return highlightInfo; } boolean referenced = myRefCountHolder.isReferencedForRead(variable); if (!referenced && !isImplicitRead(variable, progress)) { String message = JavaErrorMessages.message("local.variable.is.not.used.for.reading", identifier.getText()); HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL); QuickFixAction.registerQuickFixAction(highlightInfo, new RemoveUnusedVariableFix(variable), myUnusedSymbolKey); return highlightInfo; } if (!variable.hasInitializer()) { referenced = myRefCountHolder.isReferencedForWrite(variable); if (!referenced && !isImplicitWrite(variable, progress)) { String message = JavaErrorMessages.message("local.variable.is.not.assigned", identifier.getText()); final HighlightInfo unusedSymbolInfo = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL); QuickFixAction.registerQuickFixAction(unusedSymbolInfo, new EmptyIntentionAction(UnusedSymbolLocalInspection.DISPLAY_NAME), myUnusedSymbolKey); return unusedSymbolInfo; } } return null; } public static boolean isImplicitUsage(final PsiModifierListOwner element, ProgressIndicator progress) { if (UnusedSymbolLocalInspection.isInjected(element)) { return true; } for (ImplicitUsageProvider provider : ourImplicitUsageProviders) { progress.checkCanceled(); if (provider.isImplicitUsage(element)) { return true; } } return false; } private static boolean isImplicitRead(final PsiVariable element, ProgressIndicator progress) { for (ImplicitUsageProvider provider : ourImplicitUsageProviders) { progress.checkCanceled(); if (provider.isImplicitRead(element)) { return true; } } return UnusedSymbolLocalInspection.isInjected(element); } private static boolean isImplicitWrite(final PsiVariable element, ProgressIndicator progress) { for (ImplicitUsageProvider provider : ourImplicitUsageProviders) { progress.checkCanceled(); if (provider.isImplicitWrite(element)) { return true; } } return UnusedSymbolLocalInspection.isInjected(element); } public static HighlightInfo createUnusedSymbolInfo(@NotNull PsiElement element, @NotNull String message, @NotNull final HighlightInfoType highlightInfoType) { HighlightInfo info = HighlightInfo.newHighlightInfo(highlightInfoType).range(element) .descriptionAndTooltip(message).create(); UnusedDeclarationFixProvider[] fixProviders = Extensions .getExtensions(UnusedDeclarationFixProvider.EP_NAME); for (UnusedDeclarationFixProvider provider : fixProviders) { IntentionAction[] fixes = provider.getQuickFixes(element); for (IntentionAction fix : fixes) { QuickFixAction.registerQuickFixAction(info, fix); } } return info; } @Nullable private HighlightInfo processField(@NotNull final PsiField field, @NotNull PsiIdentifier identifier, @NotNull ProgressIndicator progress, @NotNull GlobalUsageHelper helper) { if (HighlightUtil.isSerializationImplicitlyUsedField(field)) { return null; } if (field.hasModifierProperty(PsiModifier.PRIVATE)) { if (!myRefCountHolder.isReferenced(field) && !isImplicitUsage(field, progress)) { String message = JavaErrorMessages.message("private.field.is.not.used", identifier.getText()); HighlightInfo highlightInfo = suggestionsToMakeFieldUsed(field, identifier, message); if (!field.hasInitializer()) { QuickFixAction.registerQuickFixAction(highlightInfo, HighlightMethodUtil.getFixRange(field), new CreateConstructorParameterFromFieldFix(field)); } return highlightInfo; } final boolean readReferenced = myRefCountHolder.isReferencedForRead(field); if (!readReferenced && !isImplicitRead(field, progress)) { String message = JavaErrorMessages.message("private.field.is.not.used.for.reading", identifier.getText()); return suggestionsToMakeFieldUsed(field, identifier, message); } if (field.hasInitializer()) { return null; } final boolean writeReferenced = myRefCountHolder.isReferencedForWrite(field); if (!writeReferenced && !isImplicitWrite(field, progress)) { String message = JavaErrorMessages.message("private.field.is.not.assigned", identifier.getText()); final HighlightInfo info = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL); QuickFixAction.registerQuickFixAction(info, new CreateGetterOrSetterFix(false, true, field), myUnusedSymbolKey); QuickFixAction.registerQuickFixAction(info, HighlightMethodUtil.getFixRange(field), new CreateConstructorParameterFromFieldFix(field)); SpecialAnnotationsUtilBase.createAddToSpecialAnnotationFixes(field, new Processor<String>() { @Override public boolean process(final String annoName) { QuickFixAction.registerQuickFixAction(info, UnusedSymbolLocalInspection.createQuickFix(annoName, "fields", field.getProject())); return true; } }); return info; } } else if (isImplicitUsage(field, progress)) { return null; } else if (isFieldUnused(field, progress, helper)) { return formatUnusedSymbolHighlightInfo("field.is.not.used", field, "fields", myDeadCodeKey, myDeadCodeInfoType, identifier); } return null; } public static boolean isFieldUnused(PsiField field, ProgressIndicator progress, GlobalUsageHelper helper) { if (helper.isLocallyUsed(field) || !weAreSureThereAreNoUsages(field, progress, helper)) { return false; } return !(field instanceof PsiEnumConstant) || !isEnumValuesMethodUsed(field, progress, helper); } private HighlightInfo suggestionsToMakeFieldUsed(final PsiField field, final PsiIdentifier identifier, final String message) { HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL); QuickFixAction.registerQuickFixAction(highlightInfo, new RemoveUnusedVariableFix(field), myUnusedSymbolKey); QuickFixAction.registerQuickFixAction(highlightInfo, new CreateGetterOrSetterFix(true, false, field), myUnusedSymbolKey); QuickFixAction.registerQuickFixAction(highlightInfo, new CreateGetterOrSetterFix(false, true, field), myUnusedSymbolKey); QuickFixAction.registerQuickFixAction(highlightInfo, new CreateGetterOrSetterFix(true, true, field), myUnusedSymbolKey); return highlightInfo; } private static boolean isOverriddenOrOverrides(PsiMethod method) { boolean overrides = SuperMethodsSearch.search(method, null, true, false).findFirst() != null; return overrides || OverridingMethodsSearch.search(method).findFirst() != null; } @Nullable private HighlightInfo processParameter(@NotNull PsiParameter parameter, @NotNull PsiIdentifier identifier, @NotNull ProgressIndicator progress) { PsiElement declarationScope = parameter.getDeclarationScope(); if (declarationScope instanceof PsiMethod) { PsiMethod method = (PsiMethod) declarationScope; if (PsiUtilCore.hasErrorElementChild(method)) { return null; } if ((method.isConstructor() || method.hasModifierProperty(PsiModifier.PRIVATE) || method.hasModifierProperty(PsiModifier.STATIC) || !method.hasModifierProperty(PsiModifier.ABSTRACT) && myUnusedSymbolInspection.REPORT_PARAMETER_FOR_PUBLIC_METHODS && !isOverriddenOrOverrides(method)) && !method.hasModifierProperty(PsiModifier.NATIVE) && !JavaHighlightUtil.isSerializationRelatedMethod(method, method.getContainingClass()) && !PsiClassImplUtil.isMainOrPremainMethod(method)) { if (UnusedSymbolLocalInspection.isInjected(method)) { return null; } HighlightInfo highlightInfo = checkUnusedParameter(parameter, identifier, progress); if (highlightInfo != null) { List<IntentionAction> options = new ArrayList<IntentionAction>(); options.addAll( IntentionManager.getInstance().getStandardIntentionOptions(myUnusedSymbolKey, myFile)); if (myUnusedParametersInspection != null) { SuppressQuickFix[] batchSuppressActions = myUnusedParametersInspection .getBatchSuppressActions(parameter); Collections.addAll(options, SuppressIntentionActionFromFix .convertBatchToSuppressIntentionActions(batchSuppressActions)); } //need suppress from Unused Parameters but settings from Unused Symbol QuickFixAction.registerQuickFixAction(highlightInfo, new RemoveUnusedParameterFix(parameter), options, HighlightDisplayKey.getDisplayNameByKey(myUnusedSymbolKey)); return highlightInfo; } } } else if (declarationScope instanceof PsiForeachStatement && !PsiUtil.isIgnoredName(parameter.getName())) { HighlightInfo highlightInfo = checkUnusedParameter(parameter, identifier, progress); if (highlightInfo != null) { QuickFixAction.registerQuickFixAction(highlightInfo, new RenameToIgnoredFix(parameter), myUnusedSymbolKey); return highlightInfo; } } return null; } @Nullable private HighlightInfo checkUnusedParameter(@NotNull PsiParameter parameter, @NotNull PsiIdentifier identifier, @NotNull ProgressIndicator progress) { if (!myRefCountHolder.isReferenced(parameter) && !isImplicitUsage(parameter, progress)) { String message = JavaErrorMessages.message("parameter.is.not.used", identifier.getText()); return createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL); } return null; } @Nullable private HighlightInfo processMethod(@NotNull final PsiMethod method, @NotNull PsiIdentifier identifier, @NotNull ProgressIndicator progress, @NotNull GlobalUsageHelper helper) { if (isMethodReferenced(method, progress, helper)) { return null; } HighlightInfoType highlightInfoType; HighlightDisplayKey highlightDisplayKey; String key; if (method.hasModifierProperty(PsiModifier.PRIVATE)) { highlightInfoType = HighlightInfoType.UNUSED_SYMBOL; highlightDisplayKey = myUnusedSymbolKey; key = method.isConstructor() ? "private.constructor.is.not.used" : "private.method.is.not.used"; } else { highlightInfoType = myDeadCodeInfoType; highlightDisplayKey = myDeadCodeKey; key = method.isConstructor() ? "constructor.is.not.used" : "method.is.not.used"; } String symbolName = HighlightMessageUtil.getSymbolName(method, PsiSubstitutor.EMPTY); String message = JavaErrorMessages.message(key, symbolName); final HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, highlightInfoType); QuickFixAction.registerQuickFixAction(highlightInfo, new SafeDeleteFix(method), highlightDisplayKey); SpecialAnnotationsUtilBase.createAddToSpecialAnnotationFixes(method, new Processor<String>() { @Override public boolean process(final String annoName) { QuickFixAction.registerQuickFixAction(highlightInfo, UnusedSymbolLocalInspection.createQuickFix(annoName, "methods", method.getProject())); return true; } }); PsiClass containingClass = method.getContainingClass(); if (method.getReturnType() != null || containingClass != null && Comparing.strEqual(containingClass.getName(), method.getName())) { //ignore methods with deleted return types as they are always marked as unused without any reason ChangeSignatureGestureDetector.getInstance(myProject).dismissForElement(method); } return highlightInfo; } public static boolean isMethodReferenced(PsiMethod method, ProgressIndicator progress, GlobalUsageHelper helper) { if (helper.isLocallyUsed(method)) { return true; } boolean aPrivate = method.hasModifierProperty(PsiModifier.PRIVATE); PsiClass containingClass = method.getContainingClass(); if (JavaHighlightUtil.isSerializationRelatedMethod(method, containingClass)) { return true; } if (aPrivate) { if (isIntentionalPrivateConstructor(method, containingClass)) { return true; } if (isImplicitUsage(method, progress)) { return true; } if (!helper.isCurrentFileAlreadyChecked()) { return !weAreSureThereAreNoUsages(method, progress, helper); } } else { //class maybe used in some weird way, e.g. from XML, therefore the only constructor is used too if (containingClass != null && method.isConstructor() && containingClass.getConstructors().length == 1 && isClassUsed(containingClass, progress, helper)) { return true; } if (isImplicitUsage(method, progress)) { return true; } if (method.findSuperMethods().length != 0) { return true; } if (!weAreSureThereAreNoUsages(method, progress, helper)) { return true; } } return false; } private static boolean weAreSureThereAreNoUsages(@NotNull PsiMember member, ProgressIndicator progress, GlobalUsageHelper helper) { if (!helper.shouldCheckUsages(member)) { return false; } String name = member.getName(); if (name == null) { return false; } PsiFile file = member.getContainingFile(); SearchScope useScope = member.getUseScope(); Project project = member.getProject(); PsiSearchHelper searchHelper = PsiSearchHelper.SERVICE.getInstance(project); PsiFile ignoreFile = helper.isCurrentFileAlreadyChecked() ? file : null; if (useScope instanceof GlobalSearchScope) { // some classes may have references from within XML outside dependent modules, e.g. our actions if (member instanceof PsiClass) { useScope = GlobalSearchScope.projectScope(project).uniteWith((GlobalSearchScope) useScope); } PsiSearchHelper.SearchCostResult cheapEnough = searchHelper.isCheapEnoughToSearch(name, (GlobalSearchScope) useScope, ignoreFile, progress); if (cheapEnough == TOO_MANY_OCCURRENCES) { return false; } //search usages if it cheap //if count is 0 there is no usages since we've called myRefCountHolder.isReferenced() before if (cheapEnough == ZERO_OCCURRENCES) { if (!canBeReferencedViaWeirdNames(member)) { return true; } } if (member instanceof PsiMethod) { String propertyName = PropertyUtil.getPropertyName(member); if (propertyName != null && file != null) { SearchScope fileScope = file.getUseScope(); if (fileScope instanceof GlobalSearchScope && searchHelper.isCheapEnoughToSearch(propertyName, (GlobalSearchScope) fileScope, ignoreFile, progress) == TOO_MANY_OCCURRENCES) { return false; } } } } if (ReferencesSearch.search(member, useScope, true).findFirst() != null) { return false; } FindUsagesManager findUsagesManager = ((FindManagerImpl) FindManager.getInstance(project)) .getFindUsagesManager(); FindUsagesHandler handler = new JavaFindUsagesHandler(member, new JavaFindUsagesHandlerFactory(project)); FindUsagesOptions findUsagesOptions = handler.getFindUsagesOptions().clone(); findUsagesOptions.searchScope = useScope; findUsagesOptions.isSearchForTextOccurrences = true; return !(useScope instanceof GlobalSearchScope) || !foundUsageInText(member, (GlobalSearchScope) useScope, searchHelper, ignoreFile); } private static boolean foundUsageInText(@NotNull PsiMember member, @NotNull GlobalSearchScope scope, @NotNull PsiSearchHelper searchHelper, final PsiFile ignoreFile) { return !searchHelper.processUsagesInNonJavaFiles(member, member.getName(), new PsiNonJavaFileReferenceProcessor() { @Override public boolean process(final PsiFile psiFile, final int startOffset, final int endOffset) { if (psiFile == ignoreFile) { return true; // ignore usages in containingFile because isLocallyUsed() method would have caught that } PsiElement element = psiFile.findElementAt(startOffset); return element instanceof PsiComment; // ignore comments } }, scope); } private static boolean isEnumValuesMethodUsed(PsiMember member, ProgressIndicator progress, GlobalUsageHelper helper) { final PsiClass containingClass = member.getContainingClass(); if (containingClass == null || !(containingClass instanceof PsiClassImpl)) { return true; } final PsiMethod valuesMethod = ((PsiClassImpl) containingClass).getValuesMethod(); return valuesMethod == null || isMethodReferenced(valuesMethod, progress, helper); } private static boolean canBeReferencedViaWeirdNames(PsiMember member) { if (member instanceof PsiClass) { return false; } PsiFile containingFile = member.getContainingFile(); if (!(containingFile instanceof PsiJavaFile)) { return true; // Groovy field can be referenced from Java by getter } if (member instanceof PsiField) { return false; //Java field cannot be referenced by anything but its name } if (member instanceof PsiMethod) { return PropertyUtil.isSimplePropertyAccessor((PsiMethod) member); //Java accessors can be referenced by field name from Groovy } return false; } @Nullable private HighlightInfo processClass(@NotNull PsiClass aClass, @NotNull PsiIdentifier identifier, @NotNull ProgressIndicator progress, @NotNull GlobalUsageHelper helper) { if (isClassUsed(aClass, progress, helper)) { return null; } String pattern; HighlightDisplayKey highlightDisplayKey; HighlightInfoType highlightInfoType; if (aClass.getContainingClass() != null && aClass.hasModifierProperty(PsiModifier.PRIVATE)) { pattern = aClass.isInterface() ? "private.inner.interface.is.not.used" : "private.inner.class.is.not.used"; highlightDisplayKey = myUnusedSymbolKey; highlightInfoType = HighlightInfoType.UNUSED_SYMBOL; } else if (aClass.getParent() instanceof PsiDeclarationStatement) { // local class pattern = "local.class.is.not.used"; highlightDisplayKey = myUnusedSymbolKey; highlightInfoType = HighlightInfoType.UNUSED_SYMBOL; } else if (aClass instanceof PsiTypeParameter) { pattern = "type.parameter.is.not.used"; highlightDisplayKey = myUnusedSymbolKey; highlightInfoType = HighlightInfoType.UNUSED_SYMBOL; } else { pattern = "class.is.not.used"; highlightDisplayKey = myDeadCodeKey; highlightInfoType = myDeadCodeInfoType; } return formatUnusedSymbolHighlightInfo(pattern, aClass, "classes", highlightDisplayKey, highlightInfoType, identifier); } public static boolean isClassUsed(PsiClass aClass, ProgressIndicator progress, GlobalUsageHelper helper) { if (aClass == null) { return true; } Boolean result = helper.unusedClassCache.get(aClass); if (result == null) { result = isReallyUsed(aClass, progress, helper); helper.unusedClassCache.put(aClass, result); } return result; } private static boolean isReallyUsed(PsiClass aClass, ProgressIndicator progress, GlobalUsageHelper helper) { if (isImplicitUsage(aClass, progress) || helper.isLocallyUsed(aClass)) { return true; } if (helper.isCurrentFileAlreadyChecked()) { if (aClass.getContainingClass() != null && aClass.hasModifierProperty(PsiModifier.PRIVATE) || aClass.getParent() instanceof PsiDeclarationStatement || aClass instanceof PsiTypeParameter) { return false; } } return !weAreSureThereAreNoUsages(aClass, progress, helper); } private static HighlightInfo formatUnusedSymbolHighlightInfo( @NotNull @PropertyKey(resourceBundle = JavaErrorMessages.BUNDLE) String pattern, @NotNull final PsiNameIdentifierOwner aClass, @NotNull final String element, @NotNull HighlightDisplayKey highlightDisplayKey, @NotNull HighlightInfoType highlightInfoType, @NotNull PsiElement identifier) { String symbolName = aClass.getName(); String message = JavaErrorMessages.message(pattern, symbolName); final HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, highlightInfoType); QuickFixAction.registerQuickFixAction(highlightInfo, new SafeDeleteFix(aClass), highlightDisplayKey); SpecialAnnotationsUtilBase.createAddToSpecialAnnotationFixes((PsiModifierListOwner) aClass, new Processor<String>() { @Override public boolean process(final String annoName) { QuickFixAction.registerQuickFixAction(highlightInfo, UnusedSymbolLocalInspection.createQuickFix(annoName, element, aClass.getProject())); return true; } }); return highlightInfo; } @Nullable private HighlightInfo processImport(@NotNull PsiImportStatementBase importStatement, @NotNull HighlightDisplayKey unusedImportKey) { // jsp include directive hack // if (importStatement instanceof JspxImportStatement && ((JspxImportStatement)importStatement).isForeignFileImport()) return null; if (PsiUtilCore.hasErrorElementChild(importStatement)) { return null; } boolean isRedundant = myRefCountHolder.isRedundant(importStatement); if (!isRedundant && !(importStatement instanceof PsiImportStaticStatement)) { //check import from same package String packageName = ((PsiClassOwner) importStatement.getContainingFile()).getPackageName(); PsiJavaCodeReferenceElement reference = importStatement.getImportReference(); PsiElement resolved = reference == null ? null : reference.resolve(); if (resolved instanceof PsiPackage) { isRedundant = packageName.equals(((PsiQualifiedNamedElement) resolved).getQualifiedName()); } else if (resolved instanceof PsiClass && !importStatement.isOnDemand()) { String qName = ((PsiClass) resolved).getQualifiedName(); if (qName != null) { String name = ((PomNamedTarget) resolved).getName(); isRedundant = qName.equals(packageName + '.' + name); } } } if (isRedundant) { return registerRedundantImport(importStatement, unusedImportKey); } int entryIndex = myStyleManager.findEntryIndex(importStatement); if (entryIndex < myCurrentEntryIndex) { myHasMissortedImports = true; } myCurrentEntryIndex = entryIndex; return null; } private HighlightInfo registerRedundantImport(@NotNull PsiImportStatementBase importStatement, @NotNull HighlightDisplayKey unusedImportKey) { String description = InspectionsBundle.message("unused.import.statement"); HighlightInfo info = HighlightInfo.newHighlightInfo(JavaHighlightInfoTypes.UNUSED_IMPORT) .range(importStatement).descriptionAndTooltip(description).create(); QuickFixAction.registerQuickFixAction(info, new OptimizeImportsFix(), unusedImportKey); QuickFixAction.registerQuickFixAction(info, new EnableOptimizeImportsOnTheFlyFix(), unusedImportKey); myHasRedundantImports = true; return info; } private boolean timeToOptimizeImports() { if (!CodeInsightSettings.getInstance().OPTIMIZE_IMPORTS_ON_THE_FLY) { return false; } DaemonCodeAnalyzerEx codeAnalyzer = DaemonCodeAnalyzerEx.getInstanceEx(myProject); PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(myDocument); // dont optimize out imports in JSP since it can be included in other JSP if (file == null || !codeAnalyzer.isHighlightingAvailable(file) || !(file instanceof PsiJavaFile) || file instanceof ServerPageFile) { return false; } if (!codeAnalyzer.isErrorAnalyzingFinished(file)) { return false; } boolean errors = containsErrorsPreventingOptimize(file); return !errors && DaemonListeners.canChangeFileSilently(myFile); } private boolean containsErrorsPreventingOptimize(@NotNull PsiFile file) { // ignore unresolved imports errors PsiImportList importList = ((PsiJavaFile) file).getImportList(); final TextRange importsRange = importList == null ? TextRange.EMPTY_RANGE : importList.getTextRange(); boolean hasErrorsExceptUnresolvedImports = !DaemonCodeAnalyzerEx.processHighlights(myDocument, myProject, HighlightSeverity.ERROR, 0, myDocument.getTextLength(), new Processor<HighlightInfo>() { @Override public boolean process(HighlightInfo error) { int infoStart = error.getActualStartOffset(); int infoEnd = error.getActualEndOffset(); return importsRange.containsRange(infoStart, infoEnd) && error.type.equals(HighlightInfoType.WRONG_REF); } }); return hasErrorsExceptUnresolvedImports; } private static boolean isIntentionalPrivateConstructor(@NotNull PsiMethod method, PsiClass containingClass) { return method.isConstructor() && method.getParameterList().getParametersCount() == 0 && containingClass != null && containingClass.getConstructors().length == 1; } }