/* * Copyright 2000-2015 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.codeInspection.ex; import com.intellij.analysis.AnalysisScope; import com.intellij.analysis.AnalysisUIOptions; import com.intellij.analysis.PerformAnalysisInBackgroundOption; import com.intellij.codeInsight.FileModificationService; import com.intellij.codeInsight.daemon.impl.HighlightInfo; import com.intellij.codeInsight.daemon.impl.HighlightInfoProcessor; import com.intellij.codeInsight.daemon.impl.LocalInspectionsPass; import com.intellij.codeInspection.*; import com.intellij.codeInspection.lang.GlobalInspectionContextExtension; import com.intellij.codeInspection.reference.RefElement; import com.intellij.codeInspection.reference.RefEntity; import com.intellij.codeInspection.reference.RefManagerImpl; import com.intellij.codeInspection.reference.RefVisitor; import com.intellij.codeInspection.ui.DefaultInspectionToolPresentation; import com.intellij.codeInspection.ui.InspectionResultsView; import com.intellij.codeInspection.ui.InspectionToolPresentation; import com.intellij.concurrency.JobLauncher; import com.intellij.concurrency.JobLauncherImpl; import com.intellij.concurrency.SensitiveProgressWrapper; import com.intellij.lang.annotation.ProblemGroup; import com.intellij.lang.injection.InjectedLanguageManager; import com.intellij.notification.NotificationGroup; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.ToggleAction; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.TransactionGuard; import com.intellij.openapi.application.ex.ApplicationManagerEx; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.components.PathMacroManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.progress.*; import com.intellij.openapi.progress.util.ProgressIndicatorUtils; import com.intellij.openapi.project.IndexNotReadyException; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectCoreUtil; import com.intellij.openapi.project.ProjectUtilCore; import com.intellij.openapi.roots.FileIndex; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.util.*; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.CharsetToolkit; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.ToolWindowId; import com.intellij.openapi.wm.ToolWindowManager; import com.intellij.psi.*; import com.intellij.psi.search.LocalSearchScope; import com.intellij.psi.search.SearchScope; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtilCore; import com.intellij.ui.content.*; import com.intellij.util.*; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashMap; import com.intellij.util.ui.UIUtil; import gnu.trove.THashSet; import org.jdom.Element; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.lang.reflect.Constructor; import java.util.*; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.LinkedBlockingQueue; public class GlobalInspectionContextImpl extends GlobalInspectionContextBase implements GlobalInspectionContext { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.ex.GlobalInspectionContextImpl"); private static final NotificationGroup NOTIFICATION_GROUP = NotificationGroup.toolWindowGroup("Inspection Results", ToolWindowId.INSPECTION); private final NotNullLazyValue<ContentManager> myContentManager; private InspectionResultsView myView; private Content myContent; @NotNull private AnalysisUIOptions myUIOptions; public GlobalInspectionContextImpl(@NotNull Project project, @NotNull NotNullLazyValue<ContentManager> contentManager) { super(project); myUIOptions = AnalysisUIOptions.getInstance(project).copy(); myContentManager = contentManager; } @NotNull private ContentManager getContentManager() { return myContentManager.getValue(); } public synchronized void addView(@NotNull InspectionResultsView view, @NotNull String title) { if (myContent != null) return; myContentManager.getValue().addContentManagerListener(new ContentManagerAdapter() { @Override public void contentRemoved(ContentManagerEvent event) { if (event.getContent() == myContent) { if (myView != null) { close(false); } myContent = null; } } }); myView = view; myContent = ContentFactory.SERVICE.getInstance().createContent(view, title, false); myContent.setDisposer(myView); ContentManager contentManager = getContentManager(); contentManager.addContent(myContent); contentManager.setSelectedContent(myContent); ToolWindowManager.getInstance(getProject()).getToolWindow(ToolWindowId.INSPECTION).activate(null); } public void addView(@NotNull InspectionResultsView view) { addView(view, view.getCurrentProfileName() == null ? InspectionsBundle.message("inspection.results.title") : InspectionsBundle.message("inspection.results.for.profile.toolwindow.title", view.getCurrentProfileName())); } @Override public void doInspections(@NotNull final AnalysisScope scope) { if (myContent != null) { getContentManager().removeContent(myContent, true); } super.doInspections(scope); } public void launchInspectionsOffline(@NotNull final AnalysisScope scope, @Nullable final String outputPath, final boolean runGlobalToolsOnly, @NotNull final List<File> inspectionsResults) { performInspectionsWithProgressAndExportResults(scope, runGlobalToolsOnly, true, outputPath, inspectionsResults); } public void performInspectionsWithProgressAndExportResults(@NotNull final AnalysisScope scope, final boolean runGlobalToolsOnly, final boolean isOfflineInspections, @Nullable final String outputPath, @NotNull final List<File> inspectionsResults) { cleanupTools(); setCurrentScope(scope); final Runnable action = new Runnable() { @Override public void run() { DefaultInspectionToolPresentation.setOutputPath(outputPath); try { performInspectionsWithProgress(scope, runGlobalToolsOnly, isOfflineInspections); exportResults(inspectionsResults, outputPath); } finally { DefaultInspectionToolPresentation.setOutputPath(null); } } }; if (isOfflineInspections) { ApplicationManager.getApplication().runReadAction(action); } else { action.run(); } } private void exportResults(@NotNull List<File> inspectionsResults, @Nullable String outputPath) { @NonNls final String ext = ".xml"; final Map<Element, Tools> globalTools = new HashMap<Element, Tools>(); for (Map.Entry<String, Tools> entry : myTools.entrySet()) { final Tools sameTools = entry.getValue(); boolean hasProblems = false; String toolName = entry.getKey(); if (sameTools != null) { for (ScopeToolState toolDescr : sameTools.getTools()) { InspectionToolWrapper toolWrapper = toolDescr.getTool(); if (toolWrapper instanceof LocalInspectionToolWrapper) { hasProblems = new File(outputPath, toolName + ext).exists(); } else { InspectionToolPresentation presentation = getPresentation(toolWrapper); presentation.updateContent(); if (presentation.hasReportedProblems()) { final Element root = new Element(InspectionsBundle.message("inspection.problems")); globalTools.put(root, sameTools); LOG.assertTrue(!hasProblems, toolName); break; } } } } if (hasProblems) { try { new File(outputPath).mkdirs(); final File file = new File(outputPath, toolName + ext); inspectionsResults.add(file); FileUtil.writeToFile(file, ("</" + InspectionsBundle.message("inspection.problems") + ">").getBytes(CharsetToolkit.UTF8_CHARSET), true); } catch (IOException e) { LOG.error(e); } } } getRefManager().iterate(new RefVisitor() { @Override public void visitElement(@NotNull final RefEntity refEntity) { for (Map.Entry<Element, Tools> entry : globalTools.entrySet()) { Tools tools = entry.getValue(); Element element = entry.getKey(); for (ScopeToolState state : tools.getTools()) { try { InspectionToolWrapper toolWrapper = state.getTool(); InspectionToolPresentation presentation = getPresentation(toolWrapper); presentation.exportResults(element, refEntity); } catch (Throwable e) { LOG.error("Problem when exporting: " + refEntity.getExternalName(), e); } } } } }); for (Map.Entry<Element, Tools> entry : globalTools.entrySet()) { final String toolName = entry.getValue().getShortName(); Element element = entry.getKey(); element.setAttribute(LOCAL_TOOL_ATTRIBUTE, Boolean.toString(false)); final org.jdom.Document doc = new org.jdom.Document(element); PathMacroManager.getInstance(getProject()).collapsePaths(doc.getRootElement()); try { new File(outputPath).mkdirs(); final File file = new File(outputPath, toolName + ext); inspectionsResults.add(file); OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(file), CharsetToolkit.UTF8_CHARSET); try { JDOMUtil.writeDocument(doc, writer, "\n"); } finally { writer.close(); } } catch (IOException e) { LOG.error(e); } } } public void ignoreElement(@NotNull InspectionProfileEntry tool, @NotNull PsiElement element) { final RefElement refElement = getRefManager().getReference(element); final Tools tools = myTools.get(tool.getShortName()); if (tools != null) { for (ScopeToolState state : tools.getTools()) { InspectionToolWrapper toolWrapper = state.getTool(); ignoreElementRecursively(toolWrapper, refElement); } } } public InspectionResultsView getView() { return myView; } private void ignoreElementRecursively(@NotNull InspectionToolWrapper toolWrapper, final RefEntity refElement) { if (refElement != null) { InspectionToolPresentation presentation = getPresentation(toolWrapper); presentation.ignoreCurrentElement(refElement); final List<RefEntity> children = refElement.getChildren(); if (children != null) { for (RefEntity child : children) { ignoreElementRecursively(toolWrapper, child); } } } } @NotNull public AnalysisUIOptions getUIOptions() { return myUIOptions; } public void setSplitterProportion(final float proportion) { myUIOptions.SPLITTER_PROPORTION = proportion; } @NotNull public ToggleAction createToggleAutoscrollAction() { return myUIOptions.getAutoScrollToSourceHandler().createToggleAction(); } @Override protected void launchInspections(@NotNull final AnalysisScope scope) { myUIOptions = AnalysisUIOptions.getInstance(getProject()).copy(); myView = new InspectionResultsView(getProject(), getCurrentProfile(), scope, this, new InspectionRVContentProviderImpl(getProject())); super.launchInspections(scope); } @NotNull @Override protected PerformInBackgroundOption createOption() { return new PerformAnalysisInBackgroundOption(getProject()); } @Override protected void notifyInspectionsFinished() { if (ApplicationManager.getApplication().isUnitTestMode()) return; UIUtil.invokeLaterIfNeeded(new Runnable() { @Override public void run() { LOG.info("Code inspection finished"); if (myView != null) { if (!myView.update() && !getUIOptions().SHOW_ONLY_DIFF) { NOTIFICATION_GROUP.createNotification(InspectionsBundle.message("inspection.no.problems.message"), MessageType.INFO).notify(getProject()); close(true); } else { addView(myView); } } } }); } @Override protected void runTools(@NotNull final AnalysisScope scope, boolean runGlobalToolsOnly, boolean isOfflineInspections) { final ProgressIndicator progressIndicator = ProgressIndicatorProvider.getGlobalProgressIndicator(); if (progressIndicator == null) { throw new IncorrectOperationException("Must be run under progress"); } if (!isOfflineInspections && ApplicationManager.getApplication().isDispatchThread()) { throw new IncorrectOperationException("Must not start inspections from within EDT"); } if (ApplicationManager.getApplication().isWriteAccessAllowed()) { throw new IncorrectOperationException("Must not start inspections from within write action"); } // in offline inspection application we don't care about global read action if (!isOfflineInspections && ApplicationManager.getApplication().isReadAccessAllowed()) { throw new IncorrectOperationException("Must not start inspections from within global read action"); } final InspectionManager inspectionManager = InspectionManager.getInstance(getProject()); final List<Tools> globalTools = new ArrayList<Tools>(); final List<Tools> localTools = new ArrayList<Tools>(); final List<Tools> globalSimpleTools = new ArrayList<Tools>(); initializeTools(globalTools, localTools, globalSimpleTools); appendPairedInspectionsForUnfairTools(globalTools, globalSimpleTools, localTools); ((RefManagerImpl)getRefManager()).initializeAnnotators(); runGlobalTools(scope, inspectionManager, globalTools, isOfflineInspections); if (runGlobalToolsOnly) return; final Set<VirtualFile> localScopeFiles = scope.toSearchScope() instanceof LocalSearchScope ? new THashSet<VirtualFile>() : null; for (Tools tools : globalSimpleTools) { GlobalInspectionToolWrapper toolWrapper = (GlobalInspectionToolWrapper)tools.getTool(); GlobalSimpleInspectionTool tool = (GlobalSimpleInspectionTool)toolWrapper.getTool(); tool.inspectionStarted(inspectionManager, this, getPresentation(toolWrapper)); } final boolean headlessEnvironment = ApplicationManager.getApplication().isHeadlessEnvironment(); final Map<String, InspectionToolWrapper> map = getInspectionWrappersMap(localTools); final BlockingQueue<PsiFile> filesToInspect = new ArrayBlockingQueue<PsiFile>(1000); final Queue<PsiFile> filesFailedToInspect = new LinkedBlockingQueue<PsiFile>(); // use original progress indicator here since we don't want it to cancel on write action start startIterateScope(scope, localScopeFiles, headlessEnvironment, filesToInspect, progressIndicator); Processor<PsiFile> processor = new Processor<PsiFile>() { @Override public boolean process(final PsiFile file) { ProgressManager.checkCanceled(); if (!ApplicationManagerEx.getApplicationEx().tryRunReadAction(new Runnable() { @Override public void run() { if (!file.isValid()) { return; } inspectFile(file, inspectionManager, localTools, globalSimpleTools, map); } })) { throw new ProcessCanceledException(); } return true; } }; while (true) { Disposable disposable = Disposer.newDisposable(); ProgressIndicator wrapper = new SensitiveProgressWrapper(progressIndicator); wrapper.start(); ProgressIndicatorUtils.forceWriteActionPriority(wrapper, disposable); try { // use wrapper here to cancel early when write action start but do not affect the original indicator ((JobLauncherImpl)JobLauncher.getInstance()).processQueue(filesToInspect, filesFailedToInspect, wrapper, TOMBSTONE, processor); break; } catch (ProcessCanceledException ignored) { progressIndicator.checkCanceled(); // PCE may be thrown from inside wrapper when write action started // go on with the write and then resume processing the rest of the queue assert !ApplicationManager.getApplication().isReadAccessAllowed(); assert !ApplicationManager.getApplication().isDispatchThread(); // wait for write action to complete ApplicationManager.getApplication().runReadAction(EmptyRunnable.getInstance()); } finally { Disposer.dispose(disposable); } } progressIndicator.checkCanceled(); for (Tools tools : globalSimpleTools) { GlobalInspectionToolWrapper toolWrapper = (GlobalInspectionToolWrapper)tools.getTool(); GlobalSimpleInspectionTool tool = (GlobalSimpleInspectionTool)toolWrapper.getTool(); ProblemDescriptionsProcessor problemDescriptionProcessor = getProblemDescriptionProcessor(toolWrapper, map); tool.inspectionFinished(inspectionManager, this, problemDescriptionProcessor); } } private boolean inspectFile(@NotNull final PsiFile file, @NotNull final InspectionManager inspectionManager, @NotNull List<Tools> localTools, @NotNull List<Tools> globalSimpleTools, @NotNull final Map<String, InspectionToolWrapper> wrappersMap) { Document document = PsiDocumentManager.getInstance(getProject()).getDocument(file); if (document == null) return true; VirtualFile virtualFile = file.getVirtualFile(); String url = ProjectUtilCore.displayUrlRelativeToProject(virtualFile, virtualFile.getPresentableUrl(), getProject(), true, false); incrementJobDoneAmount(getStdJobDescriptors().LOCAL_ANALYSIS, url); final LocalInspectionsPass pass = new LocalInspectionsPass(file, document, 0, file.getTextLength(), LocalInspectionsPass.EMPTY_PRIORITY_RANGE, true, HighlightInfoProcessor.getEmpty()); try { final List<LocalInspectionToolWrapper> lTools = getWrappersFromTools(localTools, file); pass.doInspectInBatch(this, inspectionManager, lTools); final List<GlobalInspectionToolWrapper> tools = getWrappersFromTools(globalSimpleTools, file); JobLauncher.getInstance().invokeConcurrentlyUnderProgress(tools, myProgressIndicator, false, new Processor<GlobalInspectionToolWrapper>() { @Override public boolean process(GlobalInspectionToolWrapper toolWrapper) { GlobalSimpleInspectionTool tool = (GlobalSimpleInspectionTool)toolWrapper.getTool(); ProblemsHolder holder = new ProblemsHolder(inspectionManager, file, false); ProblemDescriptionsProcessor problemDescriptionProcessor = getProblemDescriptionProcessor(toolWrapper, wrappersMap); tool.checkFile(file, inspectionManager, holder, GlobalInspectionContextImpl.this, problemDescriptionProcessor); InspectionToolPresentation toolPresentation = getPresentation(toolWrapper); LocalDescriptorsUtil.addProblemDescriptors(holder.getResults(), false, GlobalInspectionContextImpl.this, null, CONVERT, toolPresentation); return true; } }); } catch (ProcessCanceledException e) { final Throwable cause = e.getCause(); if (cause == null) { throw e; } LOG.error("In file: " + file, cause); } catch (IndexNotReadyException e) { throw e; } catch (Throwable e) { LOG.error("In file: " + file.getName(), e); } finally { InjectedLanguageManager.getInstance(getProject()).dropFileCaches(file); } return true; } private static final PsiFile TOMBSTONE = PsiUtilCore.NULL_PSI_FILE; private void startIterateScope(@NotNull final AnalysisScope scope, @Nullable final Collection<VirtualFile> localScopeFiles, final boolean headlessEnvironment, @NotNull final BlockingQueue<PsiFile> outFilesToInspect, @NotNull final ProgressIndicator progressIndicator) { ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { @Override public void run() { try { final FileIndex fileIndex = ProjectRootManager.getInstance(getProject()).getFileIndex(); scope.accept(new Processor<VirtualFile>() { @Override public boolean process(final VirtualFile file) { progressIndicator.checkCanceled(); if (ProjectCoreUtil.isProjectOrWorkspaceFile(file) || !fileIndex.isInContent(file)) return true; final PsiFile[] psiFile = new PsiFile[1]; Document document = ApplicationManager.getApplication().runReadAction(new Computable<Document>() { @Override public Document compute() { if (getProject().isDisposed()) throw new ProcessCanceledException(); PsiFile psi = PsiManager.getInstance(getProject()).findFile(file); Document document = psi == null ? null : shouldProcess(psi, headlessEnvironment, localScopeFiles); if (document != null) { psiFile[0] = psi; } return document; } }); //do not inspect binary files if (document != null && psiFile[0] != null) { try { LOG.assertTrue(!ApplicationManager.getApplication().isReadAccessAllowed()); outFilesToInspect.put(psiFile[0]); } catch (InterruptedException e) { LOG.error(e); } } return true; } }); } catch (ProcessCanceledException e) { // ignore, but put tombstone } finally { try { outFilesToInspect.put(TOMBSTONE); } catch (InterruptedException e) { LOG.error(e); } } } }); } private Document shouldProcess(@NotNull PsiFile file, boolean headlessEnvironment, @Nullable Collection<VirtualFile> localScopeFiles) { final VirtualFile virtualFile = file.getVirtualFile(); if (virtualFile == null) return null; if (isBinary(file)) return null; //do not inspect binary files if (myView == null && !headlessEnvironment) { throw new ProcessCanceledException(); } if (LOG.isDebugEnabled()) { LOG.debug("Running local inspections on " + virtualFile.getPath()); } if (SingleRootFileViewProvider.isTooLargeForIntelligence(virtualFile)) return null; if (localScopeFiles != null && !localScopeFiles.add(virtualFile)) return null; return PsiDocumentManager.getInstance(getProject()).getDocument(file); } private void runGlobalTools(@NotNull final AnalysisScope scope, @NotNull final InspectionManager inspectionManager, @NotNull List<Tools> globalTools, boolean isOfflineInspections) { LOG.assertTrue(!ApplicationManager.getApplication().isReadAccessAllowed() || isOfflineInspections, "Must not run under read action, too unresponsive"); final List<InspectionToolWrapper> needRepeatSearchRequest = new ArrayList<InspectionToolWrapper>(); final boolean canBeExternalUsages = scope.getScopeType() != AnalysisScope.PROJECT; for (Tools tools : globalTools) { for (ScopeToolState state : tools.getTools()) { final InspectionToolWrapper toolWrapper = state.getTool(); final GlobalInspectionTool tool = (GlobalInspectionTool)toolWrapper.getTool(); final InspectionToolPresentation toolPresentation = getPresentation(toolWrapper); try { if (tool.isGraphNeeded()) { try { ((RefManagerImpl)getRefManager()).findAllDeclarations(); } catch (Throwable e) { getStdJobDescriptors().BUILD_GRAPH.setDoneAmount(0); throw e; } } ApplicationManager.getApplication().runReadAction(new Runnable() { @Override public void run() { tool.runInspection(scope, inspectionManager, GlobalInspectionContextImpl.this, toolPresentation); //skip phase when we are sure that scope already contains everything if (canBeExternalUsages && tool.queryExternalUsagesRequests(inspectionManager, GlobalInspectionContextImpl.this, toolPresentation)) { needRepeatSearchRequest.add(toolWrapper); } } }); } catch (ProcessCanceledException e) { throw e; } catch (IndexNotReadyException e) { throw e; } catch (Throwable e) { LOG.error(e); } } } for (GlobalInspectionContextExtension extension : myExtensions.values()) { try { extension.performPostRunActivities(needRepeatSearchRequest, this); } catch (ProcessCanceledException e) { throw e; } catch (IndexNotReadyException e) { throw e; } catch (Throwable e) { LOG.error(e); } } } private void appendPairedInspectionsForUnfairTools(@NotNull List<Tools> globalTools, @NotNull List<Tools> globalSimpleTools, @NotNull List<Tools> localTools) { Tools[] larray = localTools.toArray(new Tools[localTools.size()]); for (Tools tool : larray) { LocalInspectionToolWrapper toolWrapper = (LocalInspectionToolWrapper)tool.getTool(); LocalInspectionTool localTool = toolWrapper.getTool(); if (localTool instanceof PairedUnfairLocalInspectionTool) { String batchShortName = ((PairedUnfairLocalInspectionTool)localTool).getInspectionForBatchShortName(); InspectionProfile currentProfile = getCurrentProfile(); InspectionToolWrapper batchInspection; if (currentProfile == null) { batchInspection = null; } else { final InspectionToolWrapper pairedWrapper = currentProfile.getInspectionTool(batchShortName, getProject()); batchInspection = pairedWrapper != null ? pairedWrapper.createCopy() : null; } if (batchInspection != null && !myTools.containsKey(batchShortName)) { // add to existing inspections to run InspectionProfileEntry batchTool = batchInspection.getTool(); Tools newTool = new ToolsImpl(batchInspection, batchInspection.getDefaultLevel(), true, true); if (batchTool instanceof LocalInspectionTool) { localTools.add(newTool); } else if (batchTool instanceof GlobalSimpleInspectionTool) { globalSimpleTools.add(newTool); } else if (batchTool instanceof GlobalInspectionTool) { globalTools.add(newTool); } else { throw new AssertionError(batchTool); } myTools.put(batchShortName, newTool); batchInspection.initialize(this); } } } } @NotNull private static <T extends InspectionToolWrapper> List<T> getWrappersFromTools(@NotNull List<Tools> localTools, @NotNull PsiFile file) { final List<T> lTools = new ArrayList<T>(); for (Tools tool : localTools) { //noinspection unchecked final T enabledTool = (T)tool.getEnabledTool(file); if (enabledTool != null) { lTools.add(enabledTool); } } return lTools; } @NotNull private ProblemDescriptionsProcessor getProblemDescriptionProcessor(@NotNull final GlobalInspectionToolWrapper toolWrapper, @NotNull final Map<String, InspectionToolWrapper> wrappersMap) { return new ProblemDescriptionsProcessor() { @Nullable @Override public CommonProblemDescriptor[] getDescriptions(@NotNull RefEntity refEntity) { return new CommonProblemDescriptor[0]; } @Override public void ignoreElement(@NotNull RefEntity refEntity) { } @Override public void addProblemElement(@Nullable RefEntity refEntity, @NotNull CommonProblemDescriptor... commonProblemDescriptors) { for (CommonProblemDescriptor problemDescriptor : commonProblemDescriptors) { if (!(problemDescriptor instanceof ProblemDescriptor)) { continue; } ProblemGroup problemGroup = ((ProblemDescriptor)problemDescriptor).getProblemGroup(); InspectionToolWrapper targetWrapper = problemGroup == null ? toolWrapper : wrappersMap.get(problemGroup.getProblemName()); if (targetWrapper != null) { // Else it's switched off InspectionToolPresentation toolPresentation = getPresentation(targetWrapper); toolPresentation.addProblemElement(refEntity, problemDescriptor); } } } @Override public RefEntity getElement(@NotNull CommonProblemDescriptor descriptor) { return null; } }; } @NotNull private static Map<String, InspectionToolWrapper> getInspectionWrappersMap(@NotNull List<Tools> tools) { Map<String, InspectionToolWrapper> name2Inspection = new HashMap<String, InspectionToolWrapper>(tools.size()); for (Tools tool : tools) { InspectionToolWrapper toolWrapper = tool.getTool(); name2Inspection.put(toolWrapper.getShortName(), toolWrapper); } return name2Inspection; } private static final TripleFunction<LocalInspectionTool, PsiElement, GlobalInspectionContext, RefElement> CONVERT = new TripleFunction<LocalInspectionTool, PsiElement, GlobalInspectionContext, RefElement>() { @Override public RefElement fun(LocalInspectionTool tool, PsiElement elt, GlobalInspectionContext context) { final PsiNamedElement problemElement = PsiTreeUtil.getNonStrictParentOfType(elt, PsiFile.class); RefElement refElement = context.getRefManager().getReference(problemElement); if (refElement == null && problemElement != null) { // no need to lose collected results refElement = GlobalInspectionContextUtil.retrieveRefElement(elt, context); } return refElement; } }; @Override public void close(boolean noSuspisiousCodeFound) { if (!noSuspisiousCodeFound && (myView == null || myView.isRerun())) return; AnalysisUIOptions.getInstance(getProject()).save(myUIOptions); if (myContent != null) { final ContentManager contentManager = getContentManager(); contentManager.removeContent(myContent, true); } myView = null; super.close(noSuspisiousCodeFound); } @Override public void cleanup() { ((InspectionManagerEx)InspectionManager.getInstance(getProject())).closeRunningContext(this); for (Tools tools : myTools.values()) { for (ScopeToolState state : tools.getTools()) { InspectionToolWrapper toolWrapper = state.getTool(); getPresentation(toolWrapper).finalCleanup(); } } super.cleanup(); } public void refreshViews() { if (myView != null) { myView.updateView(false); } } private final ConcurrentMap<InspectionToolWrapper, InspectionToolPresentation> myPresentationMap = ContainerUtil.newConcurrentMap(); @NotNull public InspectionToolPresentation getPresentation(@NotNull InspectionToolWrapper toolWrapper) { InspectionToolPresentation presentation = myPresentationMap.get(toolWrapper); if (presentation == null) { Class<?> presentationClass = null; if (toolWrapper.myEP != null && !StringUtil.isEmpty(toolWrapper.myEP.presentation)) { presentationClass = toolWrapper.myEP.findClassNoExceptions(toolWrapper.myEP.presentation); } if (presentationClass == null) { presentationClass = DefaultInspectionToolPresentation.class; } try { Constructor<?> constructor = presentationClass.getConstructor(InspectionToolWrapper.class, GlobalInspectionContextImpl.class); presentation = (InspectionToolPresentation)constructor.newInstance(toolWrapper, this); } catch (Exception e) { LOG.error(e); presentation = new DefaultInspectionToolPresentation(toolWrapper, this); } presentation = ConcurrencyUtil.cacheOrGet(myPresentationMap, toolWrapper, presentation); } return presentation; } @Override public void codeCleanup(@NotNull final Project project, @NotNull final AnalysisScope scope, @NotNull final InspectionProfile profile, @Nullable final String commandName, @Nullable final Runnable postRunnable, final boolean modal) { Task task = modal ? new Task.Modal(project, "Inspect code...", true) { @Override public void run(@NotNull ProgressIndicator indicator) { cleanup(scope, profile, project, postRunnable, commandName); } } : new Task.Backgroundable(project, "Inspect code...", true) { @Override public void run(@NotNull ProgressIndicator indicator) { cleanup(scope, profile, project, postRunnable, commandName); } }; ProgressManager.getInstance().run(task); } private void cleanup(@NotNull AnalysisScope scope, @NotNull InspectionProfile profile, @NotNull final Project project, @Nullable final Runnable postRunnable, @Nullable final String commandName) { final int fileCount = scope.getFileCount(); final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator(); final List<LocalInspectionToolWrapper> lTools = new ArrayList<LocalInspectionToolWrapper>(); final LinkedHashMap<PsiFile, List<HighlightInfo>> results = new LinkedHashMap<PsiFile, List<HighlightInfo>>(); final SearchScope searchScope = scope.toSearchScope(); final TextRange range; if (searchScope instanceof LocalSearchScope) { final PsiElement[] elements = ((LocalSearchScope)searchScope).getScope(); range = elements.length == 1 ? ApplicationManager.getApplication().runReadAction(new Computable<TextRange>() { @Override public TextRange compute() { return elements[0].getTextRange(); } }) : null; } else { range = null; } final Iterable<Tools> inspectionTools = ContainerUtil.filter(profile.getAllEnabledInspectionTools(project), new Condition<Tools>() { @Override public boolean value(Tools tools) { assert tools != null; return tools.getTool().getTool() instanceof CleanupLocalInspectionTool; } }); scope.accept(new PsiElementVisitor() { private int myCount; @Override public void visitFile(PsiFile file) { if (progressIndicator != null) { progressIndicator.setFraction(((double)++myCount) / fileCount); } if (isBinary(file)) return; for (final Tools tools : inspectionTools) { final InspectionToolWrapper tool = tools.getEnabledTool(file); if (tool instanceof LocalInspectionToolWrapper) { lTools.add((LocalInspectionToolWrapper)tool); tool.initialize(GlobalInspectionContextImpl.this); } } if (!lTools.isEmpty()) { final LocalInspectionsPass pass = new LocalInspectionsPass(file, PsiDocumentManager.getInstance(project).getDocument(file), range != null ? range.getStartOffset() : 0, range != null ? range.getEndOffset() : file.getTextLength(), LocalInspectionsPass.EMPTY_PRIORITY_RANGE, true, HighlightInfoProcessor.getEmpty()); Runnable runnable = new Runnable() { @Override public void run() { pass.doInspectInBatch(GlobalInspectionContextImpl.this, InspectionManager.getInstance(project), lTools); } }; ApplicationManager.getApplication().runReadAction(runnable); final List<HighlightInfo> infos = pass.getInfos(); if (searchScope instanceof LocalSearchScope) { for (Iterator<HighlightInfo> iterator = infos.iterator(); iterator.hasNext(); ) { final HighlightInfo info = iterator.next(); final TextRange infoRange = new TextRange(info.getStartOffset(), info.getEndOffset()); if (!((LocalSearchScope)searchScope).containsRange(file, infoRange)) { iterator.remove(); } } } if (!infos.isEmpty()) { results.put(file, infos); } } } }); if (results.isEmpty()) { UIUtil.invokeLaterIfNeeded(new Runnable() { @Override public void run() { if (commandName != null) { NOTIFICATION_GROUP.createNotification(InspectionsBundle.message("inspection.no.problems.message"), MessageType.INFO).notify(getProject()); } if (postRunnable != null) { postRunnable.run(); } } }); return; } Runnable runnable = new Runnable() { @Override public void run() { if (!FileModificationService.getInstance().preparePsiElementsForWrite(results.keySet())) return; final SequentialModalProgressTask progressTask = new SequentialModalProgressTask(project, "Code Cleanup", true); progressTask.setMinIterationTime(200); progressTask.setTask(new SequentialCleanupTask(project, results, progressTask)); CommandProcessor.getInstance().executeCommand(project, new Runnable() { @Override public void run() { if (commandName != null) { CommandProcessor.getInstance().markCurrentCommandAsGlobal(project); } ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { ProgressManager.getInstance().run(progressTask); } }); if (postRunnable != null) { ApplicationManager.getApplication().invokeLater(postRunnable); } } }, commandName, null); } }; TransactionGuard.submitTransaction(getProject(), runnable); } private static boolean isBinary(@NotNull PsiFile file) { return file instanceof PsiBinaryFile || file.getFileType().isBinary(); } }