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