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