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