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