Merge branch 'master' into vplyashkun/valgrind
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInspection / ex / GlobalInspectionContextImpl.java
1 /*
2  * Copyright 2000-2017 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.intellij.codeInspection.ex;
18
19 import com.intellij.analysis.AnalysisScope;
20 import com.intellij.analysis.AnalysisUIOptions;
21 import com.intellij.analysis.PerformAnalysisInBackgroundOption;
22 import com.intellij.codeInsight.FileModificationService;
23 import com.intellij.codeInsight.daemon.ProblemHighlightFilter;
24 import com.intellij.codeInsight.daemon.impl.HighlightInfoProcessor;
25 import com.intellij.codeInsight.daemon.impl.LocalInspectionsPass;
26 import com.intellij.codeInspection.*;
27 import com.intellij.codeInspection.actions.CleanupInspectionIntention;
28 import com.intellij.codeInspection.lang.GlobalInspectionContextExtension;
29 import com.intellij.codeInspection.reference.RefElement;
30 import com.intellij.codeInspection.reference.RefEntity;
31 import com.intellij.codeInspection.reference.RefManagerImpl;
32 import com.intellij.codeInspection.reference.RefVisitor;
33 import com.intellij.codeInspection.ui.DefaultInspectionToolPresentation;
34 import com.intellij.codeInspection.ui.InspectionResultsView;
35 import com.intellij.codeInspection.ui.InspectionToolPresentation;
36 import com.intellij.codeInspection.ui.InspectionTreeState;
37 import com.intellij.concurrency.JobLauncher;
38 import com.intellij.concurrency.JobLauncherImpl;
39 import com.intellij.concurrency.SensitiveProgressWrapper;
40 import com.intellij.diagnostic.ThreadDumper;
41 import com.intellij.lang.annotation.ProblemGroup;
42 import com.intellij.lang.injection.InjectedLanguageManager;
43 import com.intellij.notification.NotificationGroup;
44 import com.intellij.openapi.Disposable;
45 import com.intellij.openapi.actionSystem.ToggleAction;
46 import com.intellij.openapi.application.*;
47 import com.intellij.openapi.application.ex.ApplicationManagerEx;
48 import com.intellij.openapi.components.PathMacroManager;
49 import com.intellij.openapi.diagnostic.Logger;
50 import com.intellij.openapi.editor.Document;
51 import com.intellij.openapi.progress.*;
52 import com.intellij.openapi.progress.impl.CoreProgressManager;
53 import com.intellij.openapi.progress.util.ProgressIndicatorUtils;
54 import com.intellij.openapi.project.IndexNotReadyException;
55 import com.intellij.openapi.project.Project;
56 import com.intellij.openapi.project.ProjectUtil;
57 import com.intellij.openapi.project.ProjectUtilCore;
58 import com.intellij.openapi.roots.FileIndex;
59 import com.intellij.openapi.roots.ProjectRootManager;
60 import com.intellij.openapi.ui.MessageType;
61 import com.intellij.openapi.util.*;
62 import com.intellij.openapi.util.io.FileUtil;
63 import com.intellij.openapi.util.text.StringUtil;
64 import com.intellij.openapi.vfs.CharsetToolkit;
65 import com.intellij.openapi.vfs.VirtualFile;
66 import com.intellij.openapi.wm.ToolWindowId;
67 import com.intellij.openapi.wm.ToolWindowManager;
68 import com.intellij.psi.*;
69 import com.intellij.psi.search.GlobalSearchScopesCore;
70 import com.intellij.psi.search.LocalSearchScope;
71 import com.intellij.psi.search.SearchScope;
72 import com.intellij.psi.search.scope.packageSet.NamedScope;
73 import com.intellij.psi.util.PsiTreeUtil;
74 import com.intellij.psi.util.PsiUtilCore;
75 import com.intellij.ui.GuiUtils;
76 import com.intellij.ui.content.*;
77 import com.intellij.util.ConcurrencyUtil;
78 import com.intellij.util.IncorrectOperationException;
79 import com.intellij.util.Processor;
80 import com.intellij.util.TripleFunction;
81 import com.intellij.util.containers.ContainerUtil;
82 import com.intellij.util.containers.HashMap;
83 import com.intellij.util.containers.HashSet;
84 import gnu.trove.THashSet;
85 import org.jdom.Element;
86 import org.jetbrains.annotations.NonNls;
87 import org.jetbrains.annotations.NotNull;
88 import org.jetbrains.annotations.Nullable;
89
90 import java.io.File;
91 import java.io.FileOutputStream;
92 import java.io.IOException;
93 import java.io.OutputStreamWriter;
94 import java.lang.reflect.Constructor;
95 import java.util.*;
96 import java.util.concurrent.*;
97 import java.util.stream.Collectors;
98 import java.util.stream.Stream;
99
100 public class GlobalInspectionContextImpl extends GlobalInspectionContextBase implements GlobalInspectionContext {
101   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.ex.GlobalInspectionContextImpl");
102
103   public static final NotificationGroup NOTIFICATION_GROUP = NotificationGroup.toolWindowGroup("Inspection Results", ToolWindowId.INSPECTION);
104
105   private final NotNullLazyValue<ContentManager> myContentManager;
106   private volatile InspectionResultsView myView;
107   private volatile String myOutputPath;
108   private Content myContent;
109   private volatile boolean myViewClosed = true;
110   private long myInspectionStartedTimestamp;
111
112   @NotNull
113   private AnalysisUIOptions myUIOptions;
114   private InspectionTreeState myTreeState;
115
116   public GlobalInspectionContextImpl(@NotNull Project project, @NotNull NotNullLazyValue<ContentManager> contentManager) {
117     super(project);
118     myUIOptions = AnalysisUIOptions.getInstance(project).copy();
119     myContentManager = contentManager;
120   }
121
122   @NotNull
123   private ContentManager getContentManager() {
124     return myContentManager.getValue();
125   }
126
127   public void setTreeState(InspectionTreeState treeState) {
128     myTreeState = treeState;
129   }
130
131   public void addView(@NotNull InspectionResultsView view,
132                                    @NotNull String title,
133                                    boolean isOffline) {
134     LOG.assertTrue(myContent == null, "GlobalInspectionContext is busy under other view now");
135     myContentManager.getValue().addContentManagerListener(new ContentManagerAdapter() {
136       @Override
137       public void contentRemoved(ContentManagerEvent event) {
138         if (event.getContent() == myContent) {
139           if (myView != null) {
140             close(false);
141           }
142           myContent = null;
143         }
144       }
145     });
146
147     myView = view;
148     if (!isOffline) {
149       myView.setUpdating(true);
150     }
151     if (myTreeState != null) {
152       myView.getTree().setTreeState(myTreeState);
153     }
154     myContent = ContentFactory.SERVICE.getInstance().createContent(view, title, false);
155
156     myContent.setDisposer(myView);
157
158     ContentManager contentManager = getContentManager();
159     contentManager.addContent(myContent);
160     contentManager.setSelectedContent(myContent);
161
162     ToolWindowManager.getInstance(getProject()).getToolWindow(ToolWindowId.INSPECTION).activate(null);
163   }
164
165   public void addView(@NotNull InspectionResultsView view) {
166     addView(view, InspectionsBundle.message(view.isSingleInspectionRun() ?
167                                             "inspection.results.for.inspection.toolwindow.title" :
168                                             "inspection.results.for.profile.toolwindow.title",
169                                             view.getCurrentProfileName(), getCurrentScope().getShortenName()), false);
170
171   }
172
173   @Override
174   public void doInspections(@NotNull final AnalysisScope scope) {
175     if (myContent != null) {
176       getContentManager().removeContent(myContent, true);
177     }
178     super.doInspections(scope);
179   }
180
181   public void launchInspectionsOffline(@NotNull final AnalysisScope scope,
182                                        @Nullable final String outputPath,
183                                        final boolean runGlobalToolsOnly,
184                                        @NotNull final List<File> inspectionsResults) {
185     performInspectionsWithProgressAndExportResults(scope, runGlobalToolsOnly, true, outputPath, inspectionsResults);
186   }
187
188   public void performInspectionsWithProgressAndExportResults(@NotNull final AnalysisScope scope,
189                                                              final boolean runGlobalToolsOnly,
190                                                              final boolean isOfflineInspections,
191                                                              @Nullable final String outputPath,
192                                                              @NotNull final List<File> inspectionsResults) {
193     cleanupTools();
194     setCurrentScope(scope);
195
196     final Runnable action = () -> {
197       myOutputPath = outputPath;
198       try {
199         performInspectionsWithProgress(scope, runGlobalToolsOnly, isOfflineInspections);
200         exportResults(inspectionsResults, outputPath);
201       }
202       finally {
203         myOutputPath = null;
204       }
205     };
206     if (isOfflineInspections) {
207       ApplicationManager.getApplication().runReadAction(action);
208     }
209     else {
210       action.run();
211     }
212   }
213
214   private void exportResults(@NotNull List<File> inspectionsResults, @Nullable String outputPath) {
215     @NonNls final String ext = ".xml";
216     final Map<Element, Tools> globalTools = new HashMap<>();
217     for (Map.Entry<String,Tools> entry : getTools().entrySet()) {
218       final Tools sameTools = entry.getValue();
219       boolean hasProblems = false;
220       String toolName = entry.getKey();
221       if (sameTools != null) {
222         for (ScopeToolState toolDescr : sameTools.getTools()) {
223           InspectionToolWrapper toolWrapper = toolDescr.getTool();
224           if (toolWrapper instanceof LocalInspectionToolWrapper) {
225             hasProblems = new File(outputPath, toolName + ext).exists();
226           }
227           else {
228             InspectionToolPresentation presentation = getPresentation(toolWrapper);
229             presentation.updateContent();
230             if (presentation.hasReportedProblems()) {
231               final Element root = new Element(InspectionsBundle.message("inspection.problems"));
232               globalTools.put(root, sameTools);
233               LOG.assertTrue(!hasProblems, toolName);
234               break;
235             }
236           }
237         }
238       }
239       if (hasProblems) {
240         try {
241           new File(outputPath).mkdirs();
242           final File file = new File(outputPath, toolName + ext);
243           inspectionsResults.add(file);
244           FileUtil
245             .writeToFile(file, ("</" + InspectionsBundle.message("inspection.problems") + ">").getBytes(CharsetToolkit.UTF8_CHARSET), true);
246         }
247         catch (IOException e) {
248           LOG.error(e);
249         }
250       }
251     }
252
253     getRefManager().iterate(new RefVisitor() {
254       @Override
255       public void visitElement(@NotNull final RefEntity refEntity) {
256         for (Map.Entry<Element, Tools> entry : globalTools.entrySet()) {
257           Tools tools = entry.getValue();
258           Element element = entry.getKey();
259           for (ScopeToolState state : tools.getTools()) {
260             try {
261               InspectionToolWrapper toolWrapper = state.getTool();
262               InspectionToolPresentation presentation = getPresentation(toolWrapper);
263               presentation.exportResults(element, refEntity, d -> false);
264             }
265             catch (Throwable e) {
266               LOG.error("Problem when exporting: " + refEntity.getExternalName(), e);
267             }
268           }
269         }
270       }
271     });
272
273     for (Map.Entry<Element, Tools> entry : globalTools.entrySet()) {
274       final String toolName = entry.getValue().getShortName();
275       Element element = entry.getKey();
276       element.setAttribute(LOCAL_TOOL_ATTRIBUTE, Boolean.toString(false));
277       final org.jdom.Document doc = new org.jdom.Document(element);
278       PathMacroManager.getInstance(getProject()).collapsePaths(doc.getRootElement());
279       try {
280         new File(outputPath).mkdirs();
281         final File file = new File(outputPath, toolName + ext);
282         inspectionsResults.add(file);
283
284         try (OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(file), CharsetToolkit.UTF8_CHARSET)) {
285           JDOMUtil.writeDocument(doc, writer, "\n");
286         }
287       }
288       catch (IOException e) {
289         LOG.error(e);
290       }
291     }
292   }
293
294   public void resolveElement(@NotNull InspectionProfileEntry tool, @NotNull PsiElement element) {
295     final RefElement refElement = getRefManager().getReference(element);
296     if (refElement == null) return;
297     final Tools tools = getTools().get(tool.getShortName());
298     if (tools != null){
299       for (ScopeToolState state : tools.getTools()) {
300         InspectionToolWrapper toolWrapper = state.getTool();
301         InspectionToolPresentation presentation = getPresentationOrNull(toolWrapper);
302         if (presentation != null) {
303           resolveElementRecursively(presentation, refElement);
304         }
305       }
306     }
307   }
308
309   public InspectionResultsView getView() {
310     return myView;
311   }
312
313   public String getOutputPath() {
314     return myOutputPath;
315   }
316
317   private static void resolveElementRecursively(@NotNull InspectionToolPresentation presentation, @NotNull RefEntity refElement) {
318     presentation.suppressProblem(refElement);
319     final List<RefEntity> children = refElement.getChildren();
320     for (RefEntity child : children) {
321       resolveElementRecursively(presentation, child);
322     }
323   }
324
325   @NotNull
326   public AnalysisUIOptions getUIOptions() {
327     return myUIOptions;
328   }
329
330   public void setSplitterProportion(final float proportion) {
331     myUIOptions.SPLITTER_PROPORTION = proportion;
332   }
333
334   @NotNull
335   public ToggleAction createToggleAutoscrollAction() {
336     return myUIOptions.getAutoScrollToSourceHandler().createToggleAction();
337   }
338
339   @Override
340   protected void launchInspections(@NotNull final AnalysisScope scope) {
341     if (!ApplicationManager.getApplication().isUnitTestMode()) {
342       myUIOptions = AnalysisUIOptions.getInstance(getProject()).copy();
343     }
344     myViewClosed = false;
345     super.launchInspections(scope);
346   }
347
348   @NotNull
349   @Override
350   protected PerformInBackgroundOption createOption() {
351     return new PerformAnalysisInBackgroundOption(getProject());
352   }
353
354   @Override
355   protected void notifyInspectionsFinished(@NotNull final AnalysisScope scope) {
356     if (ApplicationManager.getApplication().isUnitTestMode()) return;
357     LOG.assertTrue(ApplicationManager.getApplication().isDispatchThread());
358     long elapsed = System.currentTimeMillis() - myInspectionStartedTimestamp;
359     LOG.info("Code inspection finished. Took " + elapsed + "ms");
360     if (getProject().isDisposed()) return;
361
362     InspectionResultsView view = myView == null ? new InspectionResultsView(this, createContentProvider()) : null;
363     if (!(myView == null ? view : myView).hasProblems()) {
364       NOTIFICATION_GROUP.createNotification(InspectionsBundle.message("inspection.no.problems.message",
365                                                                       scope.getFileCount(),
366                                                                       scope.getShortenName()),
367                                             MessageType.INFO).notify(getProject());
368       close(true);
369       if (view != null) {
370         Disposer.dispose(view);
371       }
372     }
373     else if (view != null && !view.isDisposed() && getCurrentScope() != null) {
374       addView(view);
375       view.update();
376     }
377     if (myView != null) {
378       myView.setUpdating(false);
379     }
380   }
381
382   @Override
383   protected void runTools(@NotNull final AnalysisScope scope, boolean runGlobalToolsOnly, boolean isOfflineInspections) {
384     myInspectionStartedTimestamp = System.currentTimeMillis();
385     final ProgressIndicator progressIndicator = ProgressIndicatorProvider.getGlobalProgressIndicator();
386     if (progressIndicator == null) {
387       throw new IncorrectOperationException("Must be run under progress");
388     }
389     if (!isOfflineInspections && ApplicationManager.getApplication().isDispatchThread()) {
390       throw new IncorrectOperationException("Must not start inspections from within EDT");
391     }
392     if (ApplicationManager.getApplication().isWriteAccessAllowed()) {
393       throw new IncorrectOperationException("Must not start inspections from within write action");
394     }
395     // in offline inspection application we don't care about global read action
396     if (!isOfflineInspections && ApplicationManager.getApplication().isReadAccessAllowed()) {
397       throw new IncorrectOperationException("Must not start inspections from within global read action");
398     }
399     final InspectionManager inspectionManager = InspectionManager.getInstance(getProject());
400     ((RefManagerImpl)getRefManager()).initializeAnnotators();
401     final List<Tools> globalTools = new ArrayList<>();
402     final List<Tools> localTools = new ArrayList<>();
403     final List<Tools> globalSimpleTools = new ArrayList<>();
404     initializeTools(globalTools, localTools, globalSimpleTools);
405     appendPairedInspectionsForUnfairTools(globalTools, globalSimpleTools, localTools);
406
407     runGlobalTools(scope, inspectionManager, globalTools, isOfflineInspections);
408
409     if (runGlobalToolsOnly || localTools.isEmpty() && globalSimpleTools.isEmpty()) return;
410
411     SearchScope searchScope = ReadAction.compute(scope::toSearchScope);
412     final Set<VirtualFile> localScopeFiles = searchScope instanceof LocalSearchScope ? new THashSet<>() : null;
413     for (Tools tools : globalSimpleTools) {
414       GlobalInspectionToolWrapper toolWrapper = (GlobalInspectionToolWrapper)tools.getTool();
415       GlobalSimpleInspectionTool tool = (GlobalSimpleInspectionTool)toolWrapper.getTool();
416       tool.inspectionStarted(inspectionManager, this, getPresentation(toolWrapper));
417     }
418
419     final boolean headlessEnvironment = ApplicationManager.getApplication().isHeadlessEnvironment();
420     final Map<String, InspectionToolWrapper> map = getInspectionWrappersMap(localTools);
421
422     final BlockingQueue<PsiFile> filesToInspect = new ArrayBlockingQueue<>(1000);
423     // use original progress indicator here since we don't want it to cancel on write action start
424     ProgressIndicator iteratingIndicator = new SensitiveProgressWrapper(progressIndicator);
425     Future<?> future = startIterateScopeInBackground(scope, localScopeFiles, headlessEnvironment, filesToInspect, iteratingIndicator);
426
427     Processor<PsiFile> processor = file -> {
428       ProgressManager.checkCanceled();
429       if (!ApplicationManagerEx.getApplicationEx().tryRunReadAction(() -> {
430         if (!file.isValid()) {
431           return;
432         }
433         VirtualFile virtualFile = file.getVirtualFile();
434         if (!scope.contains(virtualFile)) {
435           LOG.error(file.getName()+"; scope: "+scope+"; "+virtualFile);
436         }
437         inspectFile(file, getEffectiveRange(searchScope, file), inspectionManager, localTools, globalSimpleTools, map);
438       })) {
439         throw new ProcessCanceledException();
440       }
441
442       boolean includeDoNotShow = includeDoNotShow(getCurrentProfile());
443       Stream.concat(getWrappersFromTools(localTools, file, includeDoNotShow).stream(),
444                     getWrappersFromTools(globalSimpleTools, file, includeDoNotShow).stream())
445         .filter(wrapper -> wrapper.getTool() instanceof ExternalAnnotatorBatchInspection)
446         .forEach(wrapper -> {
447           ProblemDescriptor[] descriptors = ((ExternalAnnotatorBatchInspection)wrapper.getTool()).checkFile(file, this, inspectionManager);
448           InspectionToolPresentation toolPresentation = getPresentation(wrapper);
449           ReadAction.run(() -> LocalDescriptorsUtil.addProblemDescriptors(Arrays.asList(descriptors), false, this, null, CONVERT, toolPresentation));
450         });
451
452       return true;
453     };
454     try {
455       final Queue<PsiFile> filesFailedToInspect = new LinkedBlockingQueue<>();
456       while (true) {
457         Disposable disposable = Disposer.newDisposable();
458         ProgressIndicator wrapper = new SensitiveProgressWrapper(progressIndicator);
459
460         try {
461           // avoid "attach listener"/"write action" race
462           ReadAction.run(() -> {
463             wrapper.start();
464             ProgressIndicatorUtils.forceWriteActionPriority(wrapper, disposable);
465             // there is a chance we are racing with write action, in which case just registered listener might not be called, retry.
466             if (ApplicationManagerEx.getApplicationEx().isWriteActionPending()) {
467               throw new ProcessCanceledException();
468             }
469           });
470           // use wrapper here to cancel early when write action start but do not affect the original indicator
471           ((JobLauncherImpl)JobLauncher.getInstance()).processQueue(filesToInspect, filesFailedToInspect, wrapper, TOMBSTONE, processor);
472           break;
473         }
474         catch (ProcessCanceledException ignored) {
475           progressIndicator.checkCanceled();
476           // PCE may be thrown from inside wrapper when write action started
477           // go on with the write and then resume processing the rest of the queue
478           assert !ApplicationManager.getApplication().isReadAccessAllowed();
479           assert !ApplicationManager.getApplication().isDispatchThread();
480
481           // wait for write action to complete
482           ApplicationManager.getApplication().runReadAction(EmptyRunnable.getInstance());
483         }
484         finally {
485           Disposer.dispose(disposable);
486         }
487       }
488     }
489     finally {
490       iteratingIndicator.cancel(); // tell file scanning thread to stop
491       filesToInspect.clear(); // let file scanning thread a chance to put TOMBSTONE and complete
492       try {
493         future.get(30, TimeUnit.SECONDS);
494       }
495       catch (Exception e) {
496         LOG.error("Thread dump: \n"+ThreadDumper.dumpThreadsToString(), e);
497       }
498     }
499
500     ProgressManager.checkCanceled();
501
502     for (Tools tools : globalSimpleTools) {
503       GlobalInspectionToolWrapper toolWrapper = (GlobalInspectionToolWrapper)tools.getTool();
504       GlobalSimpleInspectionTool tool = (GlobalSimpleInspectionTool)toolWrapper.getTool();
505       ProblemDescriptionsProcessor problemDescriptionProcessor = getProblemDescriptionProcessor(toolWrapper, map);
506       tool.inspectionFinished(inspectionManager, this, problemDescriptionProcessor);
507
508     }
509
510     addProblemsToView(globalSimpleTools);
511   }
512
513   private static TextRange getEffectiveRange(SearchScope searchScope, PsiFile file) {
514     if (searchScope instanceof LocalSearchScope) {
515       PsiElement[] scopeFileElements = Arrays.stream(((LocalSearchScope)searchScope).getScope())
516         .filter(e -> e.getContainingFile() == file)
517         .toArray(PsiElement[]::new);
518       if (scopeFileElements.length > 0) {
519         int start = -1;
520         int end = -1;
521         for (PsiElement scopeElement : scopeFileElements) {
522           TextRange elementRange = scopeElement.getTextRange();
523           start = start == -1 ? elementRange.getStartOffset() : Math.min(elementRange.getStartOffset(), start);
524           end = end == -1 ? elementRange.getEndOffset() : Math.max(elementRange.getEndOffset(), end);
525         }
526         return new TextRange(start, end);
527       }
528     }
529     return new TextRange(0, file.getTextLength());
530   }
531
532   private void inspectFile(@NotNull final PsiFile file,
533                            @NotNull final TextRange range,
534                            @NotNull final InspectionManager inspectionManager,
535                            @NotNull List<Tools> localTools,
536                            @NotNull List<Tools> globalSimpleTools,
537                            @NotNull final Map<String, InspectionToolWrapper> wrappersMap) {
538     Document document = PsiDocumentManager.getInstance(getProject()).getDocument(file);
539     if (document == null) return;
540
541     VirtualFile virtualFile = file.getVirtualFile();
542     String url = ProjectUtilCore.displayUrlRelativeToProject(virtualFile, virtualFile.getPresentableUrl(), getProject(), true, false);
543     incrementJobDoneAmount(getStdJobDescriptors().LOCAL_ANALYSIS, url);
544
545     final LocalInspectionsPass pass = new LocalInspectionsPass(file, document, range.getStartOffset(),
546                                                                range.getEndOffset(), LocalInspectionsPass.EMPTY_PRIORITY_RANGE, true,
547                                                                HighlightInfoProcessor.getEmpty());
548     try {
549       boolean includeDoNotShow = includeDoNotShow(getCurrentProfile());
550       final List<LocalInspectionToolWrapper> lTools = getWrappersFromTools(localTools, file, includeDoNotShow);
551       List<LocalInspectionToolWrapper> nonExternalAnnotators = lTools.stream().filter(wrapper -> !(wrapper.getTool() instanceof ExternalAnnotatorBatchInspection)).collect(Collectors.toList());
552       pass.doInspectInBatch(this, inspectionManager, nonExternalAnnotators);
553
554       List<GlobalInspectionToolWrapper> globalSTools = getWrappersFromTools(globalSimpleTools, file, includeDoNotShow);
555       final List<GlobalInspectionToolWrapper> tools = globalSTools.stream()
556         .filter(wrapper -> !(wrapper.getTool() instanceof ExternalAnnotatorBatchInspection)).collect(Collectors.toList());
557       JobLauncher.getInstance().invokeConcurrentlyUnderProgress(tools, myProgressIndicator, false, toolWrapper -> {
558         GlobalSimpleInspectionTool tool = (GlobalSimpleInspectionTool)toolWrapper.getTool();
559         ProblemsHolder holder = new ProblemsHolder(inspectionManager, file, false);
560         ProblemDescriptionsProcessor problemDescriptionProcessor = getProblemDescriptionProcessor(toolWrapper, wrappersMap);
561         tool.checkFile(file, inspectionManager, holder, this, problemDescriptionProcessor);
562         InspectionToolPresentation toolPresentation = getPresentation(toolWrapper);
563         LocalDescriptorsUtil.addProblemDescriptors(holder.getResults(), false, this, null, CONVERT, toolPresentation);
564         return true;
565       });
566     }
567     catch (ProcessCanceledException e) {
568       final Throwable cause = e.getCause();
569       if (cause == null) {
570         throw e;
571       }
572       LOG.error("In file: " + file, cause);
573     }
574     catch (IndexNotReadyException e) {
575       throw e;
576     }
577     catch (Throwable e) {
578       LOG.error("In file: " + file.getName(), e);
579     }
580     finally {
581       InjectedLanguageManager.getInstance(getProject()).dropFileCaches(file);
582     }
583   }
584
585   protected boolean includeDoNotShow(final InspectionProfile profile) {
586     return profile.getSingleTool() != null;
587   }
588
589   private static final PsiFile TOMBSTONE = PsiUtilCore.NULL_PSI_FILE;
590
591   @NotNull
592   private Future<?> startIterateScopeInBackground(@NotNull final AnalysisScope scope,
593                                                   @Nullable final Collection<VirtualFile> localScopeFiles,
594                                                   final boolean headlessEnvironment,
595                                                   @NotNull final BlockingQueue<PsiFile> outFilesToInspect,
596                                                   @NotNull final ProgressIndicator progressIndicator) {
597     Task.Backgroundable task = new Task.Backgroundable(getProject(), "Scanning Files to Inspect") {
598       @Override
599       public void run(@NotNull ProgressIndicator indicator) {
600         try {
601           final FileIndex fileIndex = ProjectRootManager.getInstance(getProject()).getFileIndex();
602           scope.accept(file -> {
603             ProgressManager.checkCanceled();
604             if (ProjectUtil.isProjectOrWorkspaceFile(file) || !fileIndex.isInContent(file)) return true;
605
606             PsiFile psiFile = ReadAction.compute(() -> {
607               if (getProject().isDisposed()) throw new ProcessCanceledException();
608               PsiFile psi = PsiManager.getInstance(getProject()).findFile(file);
609               Document document = psi == null ? null : shouldProcess(psi, headlessEnvironment, localScopeFiles);
610               if (document != null) {
611                 return psi;
612               }
613               return null;
614             });
615             // do not inspect binary files
616             if (psiFile != null) {
617               try {
618                 if (ApplicationManager.getApplication().isReadAccessAllowed()) {
619                   throw new IllegalStateException("Must not have read action");
620                 }
621                 outFilesToInspect.put(psiFile);
622               }
623               catch (InterruptedException e) {
624                 LOG.error(e);
625               }
626             }
627             ProgressManager.checkCanceled();
628             return true;
629           });
630         }
631         catch (ProcessCanceledException e) {
632           // ignore, but put tombstone
633         }
634         finally {
635           try {
636             outFilesToInspect.put(TOMBSTONE);
637           }
638           catch (InterruptedException e) {
639             LOG.error(e);
640           }
641         }
642       }
643     };
644     return ((CoreProgressManager)ProgressManager.getInstance()).runProcessWithProgressAsynchronously(task, progressIndicator, null);
645   }
646
647   private Document shouldProcess(@NotNull PsiFile file, boolean headlessEnvironment, @Nullable Collection<VirtualFile> localScopeFiles) {
648     final VirtualFile virtualFile = file.getVirtualFile();
649     if (virtualFile == null) return null;
650     if (isBinary(file)) return null; //do not inspect binary files
651
652     if (myViewClosed && !headlessEnvironment) {
653       throw new ProcessCanceledException();
654     }
655
656     if (LOG.isDebugEnabled()) {
657       LOG.debug("Running local inspections on " + virtualFile.getPath());
658     }
659
660     if (SingleRootFileViewProvider.isTooLargeForIntelligence(virtualFile)) return null;
661     if (localScopeFiles != null && !localScopeFiles.add(virtualFile)) return null;
662     if (!ProblemHighlightFilter.shouldProcessFileInBatch(file)) return null;
663
664     return PsiDocumentManager.getInstance(getProject()).getDocument(file);
665   }
666
667   private void runGlobalTools(@NotNull final AnalysisScope scope,
668                               @NotNull final InspectionManager inspectionManager,
669                               @NotNull List<Tools> globalTools,
670                               boolean isOfflineInspections) {
671     LOG.assertTrue(!ApplicationManager.getApplication().isReadAccessAllowed() || isOfflineInspections, "Must not run under read action, too unresponsive");
672     final List<InspectionToolWrapper> needRepeatSearchRequest = new ArrayList<>();
673
674     final boolean canBeExternalUsages = !(scope.getScopeType() == AnalysisScope.PROJECT && scope.isIncludeTestSource());
675     for (Tools tools : globalTools) {
676       for (ScopeToolState state : tools.getTools()) {
677         if (!state.isEnabled()) continue;
678         NamedScope stateScope = state.getScope(getProject());
679         if (stateScope == null) continue;
680         AnalysisScope scopeForState = new AnalysisScope(GlobalSearchScopesCore.filterScope(getProject(), stateScope), getProject());
681         final InspectionToolWrapper toolWrapper = state.getTool();
682         final GlobalInspectionTool tool = (GlobalInspectionTool)toolWrapper.getTool();
683         final InspectionToolPresentation toolPresentation = getPresentation(toolWrapper);
684         try {
685           if (tool.isGraphNeeded()) {
686             try {
687               ((RefManagerImpl)getRefManager()).findAllDeclarations();
688             }
689             catch (Throwable e) {
690               getStdJobDescriptors().BUILD_GRAPH.setDoneAmount(0);
691               throw e;
692             }
693           }
694           ApplicationManager.getApplication().runReadAction(() -> {
695             tool.runInspection(scopeForState, inspectionManager, this, toolPresentation);
696             //skip phase when we are sure that scope already contains everything, unused declaration though needs to proceed with its suspicious code
697             if ((canBeExternalUsages || tool.getAdditionalJobs(this) != null) &&
698                 tool.queryExternalUsagesRequests(inspectionManager, this, toolPresentation)) {
699               needRepeatSearchRequest.add(toolWrapper);
700             }
701           });
702         }
703         catch (ProcessCanceledException | IndexNotReadyException e) {
704           throw e;
705         }
706         catch (Throwable e) {
707           LOG.error(e);
708         }
709       }
710     }
711
712     for (GlobalInspectionContextExtension extension : myExtensions.values()) {
713       try {
714         extension.performPostRunActivities(needRepeatSearchRequest, this);
715       }
716       catch (ProcessCanceledException | IndexNotReadyException e) {
717         throw e;
718       }
719       catch (Throwable e) {
720         LOG.error(e);
721       }
722     }
723
724     addProblemsToView(globalTools);
725   }
726
727   public ActionCallback initializeViewIfNeed() {
728     if (myView != null) {
729       return ActionCallback.DONE;
730     }
731     final Application app = ApplicationManager.getApplication();
732     final Runnable createView = () -> {
733       InspectionResultsView view = getView();
734       if (view == null) {
735         view = new InspectionResultsView(this, createContentProvider());
736         addView(view);
737       }
738     };
739     if (app.isUnitTestMode()) {
740       createView.run();
741       return ActionCallback.DONE;
742     } else {
743       return app.getInvokator().invokeLater(createView);
744     }
745   }
746
747   private void appendPairedInspectionsForUnfairTools(@NotNull List<Tools> globalTools,
748                                                      @NotNull List<Tools> globalSimpleTools,
749                                                      @NotNull List<Tools> localTools) {
750     Tools[] larray = localTools.toArray(new Tools[localTools.size()]);
751     for (Tools tool : larray) {
752       LocalInspectionToolWrapper toolWrapper = (LocalInspectionToolWrapper)tool.getTool();
753       LocalInspectionTool localTool = toolWrapper.getTool();
754       if (localTool instanceof PairedUnfairLocalInspectionTool) {
755         String batchShortName = ((PairedUnfairLocalInspectionTool)localTool).getInspectionForBatchShortName();
756         InspectionProfile currentProfile = getCurrentProfile();
757         InspectionToolWrapper batchInspection;
758         if (currentProfile == null) {
759           batchInspection = null;
760         }
761         else {
762           final InspectionToolWrapper pairedWrapper = currentProfile.getInspectionTool(batchShortName, getProject());
763           batchInspection = pairedWrapper != null ? pairedWrapper.createCopy() : null;
764         }
765         if (batchInspection != null && !getTools().containsKey(batchShortName)) {
766           // add to existing inspections to run
767           InspectionProfileEntry batchTool = batchInspection.getTool();
768           final ScopeToolState defaultState = tool.getDefaultState();
769           ToolsImpl newTool = new ToolsImpl(batchInspection, defaultState.getLevel(), true, defaultState.isEnabled());
770           for (ScopeToolState state : tool.getTools()) {
771             final NamedScope scope = state.getScope(getProject());
772             if (scope != null) {
773               newTool.addTool(scope, batchInspection, state.isEnabled(), state.getLevel());
774             }
775           }
776           if (batchTool instanceof LocalInspectionTool) localTools.add(newTool);
777           else if (batchTool instanceof GlobalSimpleInspectionTool) globalSimpleTools.add(newTool);
778           else if (batchTool instanceof GlobalInspectionTool) globalTools.add(newTool);
779           else throw new AssertionError(batchTool);
780           myTools.put(batchShortName, newTool);
781           batchInspection.initialize(this);
782         }
783       }
784     }
785   }
786
787   @NotNull
788   private static <T extends InspectionToolWrapper> List<T> getWrappersFromTools(@NotNull List<Tools> localTools,
789                                                                                 @NotNull PsiFile file,
790                                                                                 boolean includeDoNotShow) {
791     final List<T> lTools = new ArrayList<>();
792     for (Tools tool : localTools) {
793       //noinspection unchecked
794       final T enabledTool = (T)tool.getEnabledTool(file, includeDoNotShow);
795       if (enabledTool != null) {
796         lTools.add(enabledTool);
797       }
798     }
799     return lTools;
800   }
801
802   @NotNull
803   private ProblemDescriptionsProcessor getProblemDescriptionProcessor(@NotNull final GlobalInspectionToolWrapper toolWrapper,
804                                                                       @NotNull final Map<String, InspectionToolWrapper> wrappersMap) {
805     return new ProblemDescriptionsProcessor() {
806       @Override
807       public void addProblemElement(@Nullable RefEntity refEntity, @NotNull CommonProblemDescriptor... commonProblemDescriptors) {
808         for (CommonProblemDescriptor problemDescriptor : commonProblemDescriptors) {
809           if (!(problemDescriptor instanceof ProblemDescriptor)) {
810             continue;
811           }
812           ProblemGroup problemGroup = ((ProblemDescriptor)problemDescriptor).getProblemGroup();
813
814           InspectionToolWrapper targetWrapper = problemGroup == null ? toolWrapper : wrappersMap.get(problemGroup.getProblemName());
815           if (targetWrapper != null) { // Else it's switched off
816             InspectionToolPresentation toolPresentation = getPresentation(targetWrapper);
817             toolPresentation.addProblemElement(refEntity, problemDescriptor);
818           }
819         }
820       }
821     };
822   }
823
824   @NotNull
825   private static Map<String, InspectionToolWrapper> getInspectionWrappersMap(@NotNull List<Tools> tools) {
826     Map<String, InspectionToolWrapper> name2Inspection = new HashMap<>(tools.size());
827     for (Tools tool : tools) {
828       InspectionToolWrapper toolWrapper = tool.getTool();
829       name2Inspection.put(toolWrapper.getShortName(), toolWrapper);
830     }
831
832     return name2Inspection;
833   }
834
835   private static final TripleFunction<LocalInspectionTool,PsiElement,GlobalInspectionContext,RefElement> CONVERT =
836     (tool, elt, context) -> {
837       PsiNamedElement problemElement = PsiTreeUtil.getNonStrictParentOfType(elt, PsiFile.class);
838
839       RefElement refElement = context.getRefManager().getReference(problemElement);
840       if (refElement == null && problemElement != null) {  // no need to lose collected results
841         refElement = GlobalInspectionContextUtil.retrieveRefElement(elt, context);
842       }
843       return refElement;
844     };
845
846
847   @Override
848   public void close(boolean noSuspiciousCodeFound) {
849     if (!noSuspiciousCodeFound) {
850       if (myView.isRerun()) {
851         myViewClosed = true;
852         myView = null;
853       }
854       if (myView == null) {
855         return;
856       }
857     }
858     AnalysisUIOptions.getInstance(getProject()).save(myUIOptions);
859     if (myContent != null) {
860       final ContentManager contentManager = getContentManager();
861       contentManager.removeContent(myContent, true);
862     }
863     myViewClosed = true;
864     myView = null;
865     ((InspectionManagerEx)InspectionManager.getInstance(getProject())).closeRunningContext(this);
866     myPresentationMap.clear();
867     super.close(noSuspiciousCodeFound);
868   }
869
870   @Override
871   public void cleanup() {
872     if (myView != null) {
873       myView.setUpdating(false);
874     } else {
875       myPresentationMap.clear();
876       super.cleanup();
877     }
878   }
879
880   public void refreshViews() {
881     if (myView != null) {
882       myView.getTree().queueUpdate();
883     }
884   }
885
886   private final ConcurrentMap<InspectionToolWrapper, InspectionToolPresentation> myPresentationMap = ContainerUtil.newConcurrentMap();
887
888   @Nullable
889   public InspectionToolPresentation getPresentationOrNull(@NotNull InspectionToolWrapper toolWrapper) {
890     return myPresentationMap.get(toolWrapper);
891   }
892   @NotNull
893   public InspectionToolPresentation getPresentation(@NotNull InspectionToolWrapper toolWrapper) {
894     InspectionToolPresentation presentation = myPresentationMap.get(toolWrapper);
895     if (presentation == null) {
896       String presentationClass = StringUtil.notNullize(toolWrapper.myEP == null ? null : toolWrapper.myEP.presentation, DefaultInspectionToolPresentation.class.getName());
897
898       try {
899         Constructor<?> constructor = Class.forName(presentationClass).getConstructor(InspectionToolWrapper.class, GlobalInspectionContextImpl.class);
900         presentation = (InspectionToolPresentation)constructor.newInstance(toolWrapper, this);
901       }
902       catch (Exception e) {
903         LOG.error(e);
904         throw new RuntimeException(e);
905       }
906       presentation = ConcurrencyUtil.cacheOrGet(myPresentationMap, toolWrapper, presentation);
907     }
908     return presentation;
909   }
910
911   @Override
912   public void codeCleanup(@NotNull final AnalysisScope scope,
913                           @NotNull final InspectionProfile profile,
914                           @Nullable final String commandName,
915                           @Nullable final Runnable postRunnable,
916                           final boolean modal) {
917     String title = "Inspect Code...";
918     Task task = modal ? new Task.Modal(getProject(), title, true) {
919       @Override
920       public void run(@NotNull ProgressIndicator indicator) {
921         cleanup(scope, profile, postRunnable, commandName);
922       }
923     } : new Task.Backgroundable(getProject(), title, true) {
924       @Override
925       public void run(@NotNull ProgressIndicator indicator) {
926         cleanup(scope, profile, postRunnable, commandName);
927       }
928     };
929     ProgressManager.getInstance().run(task);
930   }
931
932   private void cleanup(@NotNull final AnalysisScope scope,
933                        @NotNull InspectionProfile profile,
934                        @Nullable final Runnable postRunnable,
935                        @Nullable final String commandName) {
936     setCurrentScope(scope);
937     final int fileCount = scope.getFileCount();
938     final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator();
939
940     final SearchScope searchScope = ReadAction.compute(scope::toSearchScope);
941     final TextRange range;
942     if (searchScope instanceof LocalSearchScope) {
943       final PsiElement[] elements = ((LocalSearchScope)searchScope).getScope();
944       range = elements.length == 1 ? ReadAction.compute(elements[0]::getTextRange) : null;
945     }
946     else {
947       range = null;
948     }
949     final Iterable<Tools> inspectionTools = ContainerUtil.filter(profile.getAllEnabledInspectionTools(getProject()), tools -> {
950       assert tools != null;
951       return tools.getTool().isCleanupTool();
952     });
953     boolean includeDoNotShow = includeDoNotShow(profile);
954     final RefManagerImpl refManager = (RefManagerImpl)getRefManager();
955     refManager.inspectionReadActionStarted();
956     List<ProblemDescriptor> descriptors = new ArrayList<>();
957     Set<PsiFile> files = new HashSet<>();
958     try {
959       scope.accept(new PsiElementVisitor() {
960         private int myCount;
961         @Override
962         public void visitFile(PsiFile file) {
963           if (progressIndicator != null) {
964             progressIndicator.setFraction((double)++myCount / fileCount);
965           }
966           if (isBinary(file)) return;
967           final List<LocalInspectionToolWrapper> lTools = new ArrayList<>();
968           for (final Tools tools : inspectionTools) {
969             InspectionToolWrapper tool = tools.getEnabledTool(file, includeDoNotShow);
970             if (tool instanceof GlobalInspectionToolWrapper) {
971               tool = ((GlobalInspectionToolWrapper)tool).getSharedLocalInspectionToolWrapper();
972             }
973             if (tool != null) {
974               lTools.add((LocalInspectionToolWrapper)tool);
975               tool.initialize(GlobalInspectionContextImpl.this);
976             }
977           }
978
979           if (!lTools.isEmpty()) {
980             try {
981               final LocalInspectionsPass pass = new LocalInspectionsPass(file, PsiDocumentManager.getInstance(getProject()).getDocument(file), range != null ? range.getStartOffset() : 0,
982                                                                          range != null ? range.getEndOffset() : file.getTextLength(), LocalInspectionsPass.EMPTY_PRIORITY_RANGE, true,
983                                                                          HighlightInfoProcessor.getEmpty());
984               Runnable runnable = () -> pass.doInspectInBatch(GlobalInspectionContextImpl.this, InspectionManager.getInstance(getProject()), lTools);
985               ApplicationManager.getApplication().runReadAction(runnable);
986
987               final Set<ProblemDescriptor> localDescriptors = new TreeSet<>(CommonProblemDescriptor.DESCRIPTOR_COMPARATOR);
988               for (LocalInspectionToolWrapper tool : lTools) {
989                 InspectionToolPresentation toolPresentation = getPresentation(tool);
990                 for (CommonProblemDescriptor descriptor : toolPresentation.getProblemDescriptors()) {
991                   if (descriptor instanceof ProblemDescriptor) {
992                     localDescriptors.add((ProblemDescriptor)descriptor);
993                   }
994                 }
995               }
996
997               if (searchScope instanceof LocalSearchScope) {
998                 for (Iterator<ProblemDescriptor> iterator = localDescriptors.iterator(); iterator.hasNext(); ) {
999                   final ProblemDescriptor descriptor = iterator.next();
1000                   final TextRange infoRange = descriptor instanceof ProblemDescriptorBase ? ((ProblemDescriptorBase)descriptor).getTextRange() : null;
1001                   if (infoRange != null && !((LocalSearchScope)searchScope).containsRange(file, infoRange)) {
1002                     iterator.remove();
1003                   }
1004                 }
1005               }
1006               if (!localDescriptors.isEmpty()) {
1007                 descriptors.addAll(localDescriptors);
1008                 files.add(file);
1009               }
1010             }
1011             finally {
1012               myPresentationMap.clear();
1013             }
1014           }
1015         }
1016       });
1017     }
1018     finally {
1019       refManager.inspectionReadActionFinished();
1020     }
1021
1022     if (files.isEmpty()) {
1023       GuiUtils.invokeLaterIfNeeded(() -> {
1024         if (commandName != null) {
1025           NOTIFICATION_GROUP.createNotification(InspectionsBundle.message("inspection.no.problems.message", scope.getFileCount(), scope.getDisplayName()), MessageType.INFO).notify(getProject());
1026         }
1027         if (postRunnable != null) {
1028           postRunnable.run();
1029         }
1030       }, ModalityState.defaultModalityState());
1031       return;
1032     }
1033
1034     Runnable runnable = () -> {
1035       if (!FileModificationService.getInstance().preparePsiElementsForWrite(files)) return;
1036       CleanupInspectionIntention.applyFixesNoSort(getProject(), "Code Cleanup", descriptors, null, false);
1037       if (postRunnable != null) {
1038         postRunnable.run();
1039       }
1040     };
1041     TransactionGuard.submitTransaction(getProject(), runnable);
1042   }
1043
1044   private static boolean isBinary(@NotNull PsiFile file) {
1045     return file instanceof PsiBinaryFile || file.getFileType().isBinary();
1046   }
1047
1048   public boolean isViewClosed() {
1049     return myViewClosed;
1050   }
1051
1052   private InspectionRVContentProvider createContentProvider() {
1053     return new InspectionRVContentProviderImpl(getProject());
1054   }
1055
1056   private void addProblemsToView(List<Tools> tools) {
1057     if (ApplicationManager.getApplication().isUnitTestMode() || ApplicationManager.getApplication().isHeadlessEnvironment()) {
1058       return;
1059     }
1060     if (myView == null && !ReadAction.compute(() -> InspectionResultsView.hasProblems(tools, this, createContentProvider())).booleanValue()) {
1061       return;
1062     }
1063     initializeViewIfNeed().doWhenDone(() -> myView.addTools(tools));
1064   }
1065 }