From 8b9f120dff3d7bb37cbab21297e5ca8ca47ff2f1 Mon Sep 17 00:00:00 2001 From: Alexey Kudravtsev Date: Mon, 27 Apr 2015 15:18:26 +0300 Subject: [PATCH] IDEA-125474 Local inspections run globally should use multiple cores --- .../GlobalInspectionContext.java | 13 +- .../ex/GlobalInspectionContextBase.java | 68 ++-- .../com/intellij/psi/util/PsiUtilCore.java | 127 +++++- .../ex/GlobalInspectionContextImpl.java | 381 ++++++++++-------- .../ex/InspectionRVContentProvider.java | 3 +- .../ex/InspectionRVContentProviderImpl.java | 4 +- .../ui/DefaultInspectionToolPresentation.java | 91 +++-- .../intellij/concurrency/JobLauncherImpl.java | 180 +++++++-- 8 files changed, 594 insertions(+), 273 deletions(-) diff --git a/platform/analysis-api/src/com/intellij/codeInspection/GlobalInspectionContext.java b/platform/analysis-api/src/com/intellij/codeInspection/GlobalInspectionContext.java index 768c96f346ad..d94394f37f31 100644 --- a/platform/analysis-api/src/com/intellij/codeInspection/GlobalInspectionContext.java +++ b/platform/analysis-api/src/com/intellij/codeInspection/GlobalInspectionContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2015 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,7 +41,8 @@ public interface GlobalInspectionContext extends UserDataHolder { * * @return the reference graph instance. */ - @NotNull RefManager getRefManager(); + @NotNull + RefManager getRefManager(); /** * Checks if the inspection with the specified ID is suppressed for the @@ -52,7 +53,7 @@ public interface GlobalInspectionContext extends UserDataHolder { * @return true if the inspection is suppressed, false otherwise. * @deprecated use #shouldCheck instead */ - boolean isSuppressed(RefEntity entity, String inspectionToolId); + boolean isSuppressed(@NotNull RefEntity entity, @NotNull String inspectionToolId); /** * Checks if the inspection is suppressed for the specified reference graph node. Should not be called manually in normal case. @@ -61,7 +62,7 @@ public interface GlobalInspectionContext extends UserDataHolder { * @param tool the inspection to check. * @return true if the inspection is suppressed, false otherwise. */ - boolean shouldCheck(RefEntity entity, GlobalInspectionTool tool); + boolean shouldCheck(@NotNull RefEntity entity, @NotNull GlobalInspectionTool tool); /** * Checks if the inspection with the specified ID is suppressed for the @@ -76,9 +77,9 @@ public interface GlobalInspectionContext extends UserDataHolder { @NotNull Project getProject(); - T getExtension(Key key); + T getExtension(@NotNull Key key); - void incrementJobDoneAmount(JobDescriptor job, String message); + void incrementJobDoneAmount(@NotNull JobDescriptor job, @NotNull String message); @NotNull StdJobDescriptors getStdJobDescriptors(); diff --git a/platform/analysis-impl/src/com/intellij/codeInspection/ex/GlobalInspectionContextBase.java b/platform/analysis-impl/src/com/intellij/codeInspection/ex/GlobalInspectionContextBase.java index 27451fae29fd..c68ac291d33b 100644 --- a/platform/analysis-impl/src/com/intellij/codeInspection/ex/GlobalInspectionContextBase.java +++ b/platform/analysis-impl/src/com/intellij/codeInspection/ex/GlobalInspectionContextBase.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2015 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,7 +47,7 @@ import gnu.trove.THashSet; import gnu.trove.TObjectHashingStrategy; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.TestOnly; +import org.jetbrains.annotations.Nullable; import java.util.*; @@ -69,10 +69,10 @@ public class GlobalInspectionContextBase extends UserDataHolderBase implements G private AnalysisScope myCurrentScope; private final Project myProject; - private List myJobDescriptors; + private final List myJobDescriptors = new ArrayList(); private final StdJobDescriptors myStdJobDescriptors = new StdJobDescriptors(); - protected ProgressIndicator myProgressIndicator; + protected ProgressIndicator myProgressIndicator = new EmptyProgressIndicator(); private InspectionProfile myExternalProfile; @@ -82,8 +82,6 @@ public class GlobalInspectionContextBase extends UserDataHolderBase implements G @NonNls public static final String LOCAL_TOOL_ATTRIBUTE = "is_local_tool"; - private boolean myUseProgressIndicatorInTests = false; - public GlobalInspectionContextBase(@NotNull Project project) { myProject = project; @@ -93,7 +91,7 @@ public class GlobalInspectionContextBase extends UserDataHolderBase implements G } } - public AnalysisScope getCurrentScope() { + AnalysisScope getCurrentScope() { return myCurrentScope; } @@ -104,7 +102,7 @@ public class GlobalInspectionContextBase extends UserDataHolderBase implements G } @Override - public T getExtension(final Key key) { + public T getExtension(@NotNull final Key key) { //noinspection unchecked return (T)myExtensions.get(key); } @@ -130,12 +128,12 @@ public class GlobalInspectionContextBase extends UserDataHolderBase implements G } @Override - public boolean isSuppressed(RefEntity entity, String id) { + public boolean isSuppressed(@NotNull RefEntity entity, @NotNull String id) { return entity instanceof RefElementImpl && ((RefElementImpl)entity).isSuppressed(id); } @Override - public boolean shouldCheck(RefEntity entity, GlobalInspectionTool tool) { + public boolean shouldCheck(@NotNull RefEntity entity, @NotNull GlobalInspectionTool tool) { return !(entity instanceof RefElementImpl) || isToCheckMember((RefElementImpl)entity, tool); } @@ -150,9 +148,8 @@ public class GlobalInspectionContextBase extends UserDataHolderBase implements G } - protected void cleanupTools() { - myProgressIndicator = null; - + void cleanupTools() { + myProgressIndicator.cancel(); for (GlobalInspectionContextExtension extension : myExtensions.values()) { extension.cleanup(); } @@ -178,6 +175,7 @@ public class GlobalInspectionContextBase extends UserDataHolderBase implements G myCurrentScope = null; } } + myJobDescriptors.clear(); } public void setCurrentScope(@NotNull AnalysisScope currentScope) { @@ -231,6 +229,7 @@ public class GlobalInspectionContextBase extends UserDataHolderBase implements G } protected void launchInspections(@NotNull final AnalysisScope scope) { + ApplicationManager.getApplication().assertIsDispatchThread(); PsiDocumentManager.getInstance(myProject).commitAllDocuments(); LOG.info("Code inspection started"); @@ -238,7 +237,7 @@ public class GlobalInspectionContextBase extends UserDataHolderBase implements G createOption()) { @Override public void run(@NotNull ProgressIndicator indicator) { - performInspectionsWithProgress(scope, false); + performInspectionsWithProgress(scope, false, false); } @Override @@ -248,6 +247,7 @@ public class GlobalInspectionContextBase extends UserDataHolderBase implements G }); } + @NotNull protected PerformInBackgroundOption createOption() { return new PerformInBackgroundOption(){ @Override @@ -265,9 +265,12 @@ public class GlobalInspectionContextBase extends UserDataHolderBase implements G protected void notifyInspectionsFinished() { } - public void performInspectionsWithProgress(@NotNull final AnalysisScope scope, final boolean runGlobalToolsOnly) { + public void performInspectionsWithProgress(@NotNull final AnalysisScope scope, final boolean runGlobalToolsOnly, final boolean isOfflineInspections) { + myProgressIndicator = ProgressManager.getInstance().getProgressIndicator(); + if (myProgressIndicator == null) { + throw new IllegalStateException("Inspections must be run under progress"); + } final PsiManager psiManager = PsiManager.getInstance(myProject); - myProgressIndicator = getProgressIndicator(); //init manager in read action RefManagerImpl refManager = (RefManagerImpl)getRefManager(); try { @@ -280,7 +283,7 @@ public class GlobalInspectionContextBase extends UserDataHolderBase implements G ProgressManager.getInstance().executeProcessUnderProgress(new Runnable() { @Override public void run() { - runTools(scope, runGlobalToolsOnly); + runTools(scope, runGlobalToolsOnly, isOfflineInspections); } }, ProgressWrapper.wrap(myProgressIndicator)); } @@ -302,24 +305,13 @@ public class GlobalInspectionContextBase extends UserDataHolderBase implements G } } - private ProgressIndicator getProgressIndicator() { - return ApplicationManager.getApplication().isUnitTestMode() && !myUseProgressIndicatorInTests - ? new EmptyProgressIndicator() : ProgressManager.getInstance().getProgressIndicator(); - } - - @TestOnly - public void setUseProgressIndicatorInTests(boolean useProgressIndicatorInTests) { - myUseProgressIndicatorInTests = useProgressIndicatorInTests; - } - - protected void runTools(@NotNull AnalysisScope scope, boolean runGlobalToolsOnly) { + protected void runTools(@NotNull AnalysisScope scope, boolean runGlobalToolsOnly, boolean isOfflineInspections) { } public void initializeTools(@NotNull List outGlobalTools, @NotNull List outLocalTools, @NotNull List outGlobalSimpleTools) { - myJobDescriptors = new ArrayList(); final List usedTools = getUsedTools(); for (Tools currentTools : usedTools) { final String shortName = currentTools.getShortName(); @@ -341,6 +333,7 @@ public class GlobalInspectionContextBase extends UserDataHolderBase implements G } } + @NotNull protected List getUsedTools() { InspectionProfileImpl profile = new InspectionProfileImpl((InspectionProfileImpl)getCurrentProfile()); List tools = profile.getAllEnabledInspectionTools(myProject); @@ -391,26 +384,27 @@ public class GlobalInspectionContextBase extends UserDataHolderBase implements G return myTools; } - public void appendJobDescriptor(@NotNull JobDescriptor job) { + private void appendJobDescriptor(@NotNull JobDescriptor job) { if (!myJobDescriptors.contains(job)) { myJobDescriptors.add(job); job.setDoneAmount(0); } } - public void codeCleanup(final Project project, - final AnalysisScope scope, - final InspectionProfile profile, - final String commandName, Runnable postRunnable, + public void codeCleanup(@NotNull Project project, + @NotNull AnalysisScope scope, + @NotNull InspectionProfile profile, + @Nullable String commandName, + @Nullable Runnable postRunnable, final boolean modal) {} - public static void codeCleanup(Project project, AnalysisScope scope, Runnable runnable) { + public static void codeCleanup(@NotNull Project project, @NotNull AnalysisScope scope, @Nullable Runnable runnable) { GlobalInspectionContextBase globalContext = (GlobalInspectionContextBase)InspectionManager.getInstance(project).createNewGlobalContext(false); final InspectionProfile profile = InspectionProjectProfileManager.getInstance(project).getInspectionProfile(); globalContext.codeCleanup(project, scope, profile, null, runnable, false); } - public static void cleanupElements(final Project project, final Runnable runnable, final PsiElement... scope) { + public static void cleanupElements(@NotNull final Project project, @Nullable final Runnable runnable, @NotNull PsiElement... scope) { final List> elements = new ArrayList>(); final SmartPointerManager manager = SmartPointerManager.getInstance(project); for (PsiElement element : scope) { @@ -456,7 +450,7 @@ public class GlobalInspectionContextBase extends UserDataHolderBase implements G } @Override - public void incrementJobDoneAmount(JobDescriptor job, String message) { + public void incrementJobDoneAmount(@NotNull JobDescriptor job, @NotNull String message) { if (myProgressIndicator == null) return; ProgressManager.checkCanceled(); diff --git a/platform/core-api/src/com/intellij/psi/util/PsiUtilCore.java b/platform/core-api/src/com/intellij/psi/util/PsiUtilCore.java index 0cc292a430fb..5db672fa8fcb 100644 --- a/platform/core-api/src/com/intellij/psi/util/PsiUtilCore.java +++ b/platform/core-api/src/com/intellij/psi/util/PsiUtilCore.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2015 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,12 @@ package com.intellij.psi.util; import com.intellij.lang.ASTNode; +import com.intellij.lang.FileASTNode; import com.intellij.lang.Language; +import com.intellij.navigation.ItemPresentation; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Computable; @@ -30,10 +33,13 @@ import com.intellij.psi.meta.PsiMetaData; import com.intellij.psi.meta.PsiMetaOwner; import com.intellij.psi.scope.PsiScopeProcessor; import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.PsiElementProcessor; import com.intellij.psi.search.SearchScope; import com.intellij.psi.tree.IElementType; +import com.intellij.util.IncorrectOperationException; import com.intellij.util.TimeoutUtil; import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -45,7 +51,8 @@ import java.util.Collection; */ public class PsiUtilCore { private static final Logger LOG = Logger.getInstance("#com.intellij.psi.util.PsiUtilCore"); - public static final PsiElement NULL_PSI_ELEMENT = new PsiElement() { + public static final PsiElement NULL_PSI_ELEMENT = new NullPsiElement(); + private static class NullPsiElement implements PsiElement { @Override @NotNull public Project getProject() { @@ -250,8 +257,8 @@ public class PsiUtilCore { throw createException(); } - private PsiInvalidElementAccessException createException() { - return new PsiInvalidElementAccessException(this, "NULL_PSI_ELEMENT", null); + protected PsiInvalidElementAccessException createException() { + return new PsiInvalidElementAccessException(this, toString(), null); } @Override @@ -335,7 +342,7 @@ public class PsiUtilCore { public String toString() { return "NULL_PSI_ELEMENT"; } - }; + } @NotNull public static PsiElement[] toPsiElementArray(@NotNull Collection collection) { @@ -467,7 +474,6 @@ public class PsiUtilCore { * Checks if the element is valid. If not, throws {@link com.intellij.psi.PsiInvalidElementAccessException} with * a meaningful message that points to the reasons why the element is not valid and may contain the stack trace * when it was invalidated. - * @param element */ public static void ensureValid(@NotNull PsiElement element) { if (!element.isValid()) { @@ -547,4 +553,113 @@ public class PsiUtilCore { public static IElementType getElementType(@Nullable PsiElement element) { return element == null ? null : getElementType(element.getNode()); } + + public static final PsiFile NULL_PSI_FILE = new NullPsiFile(); + private static class NullPsiFile extends NullPsiElement implements PsiFile { + @Override + public FileASTNode getNode() { + throw createException(); + } + + @Override + public PsiDirectory getParent() { + throw createException(); + } + + @Override + public VirtualFile getVirtualFile() { + throw createException(); + } + + @Override + public PsiDirectory getContainingDirectory() { + throw createException(); + } + + @Override + public long getModificationStamp() { + throw createException(); + } + + @NotNull + @Override + public PsiFile getOriginalFile() { + throw createException(); + } + + @NotNull + @Override + public FileType getFileType() { + throw createException(); + } + + @NotNull + @Override + public PsiFile[] getPsiRoots() { + throw createException(); + } + + @NotNull + @Override + public FileViewProvider getViewProvider() { + throw createException(); + } + + @Override + public void subtreeChanged() { + throw createException(); + } + + @Override + public boolean isDirectory() { + throw createException(); + } + + @NotNull + @Override + public String getName() { + throw createException(); + } + + @Override + public boolean processChildren(PsiElementProcessor processor) { + throw createException(); + } + + @Nullable + @Override + public ItemPresentation getPresentation() { + throw createException(); + } + + @Override + public void navigate(boolean requestFocus) { + throw createException(); + } + + @Override + public boolean canNavigate() { + throw createException(); + } + + @Override + public boolean canNavigateToSource() { + throw createException(); + } + + @Override + public void checkSetName(String name) throws IncorrectOperationException { + throw createException(); + } + + @Override + public PsiElement setName(@NonNls @NotNull String name) throws IncorrectOperationException { + throw createException(); + } + + @Override + public String toString() { + return "NULL_PSI_FILE"; + } + } } diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ex/GlobalInspectionContextImpl.java b/platform/lang-impl/src/com/intellij/codeInspection/ex/GlobalInspectionContextImpl.java index 9a65c11b467d..dcbaa1a965be 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ex/GlobalInspectionContextImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ex/GlobalInspectionContextImpl.java @@ -33,17 +33,21 @@ import com.intellij.codeInspection.ui.DefaultInspectionToolPresentation; import com.intellij.codeInspection.ui.InspectionResultsView; import com.intellij.codeInspection.ui.InspectionToolPresentation; import com.intellij.concurrency.JobLauncher; -import com.intellij.concurrency.JobSchedulerImpl; +import com.intellij.concurrency.JobLauncherImpl; +import com.intellij.concurrency.SensitiveProgressWrapper; import com.intellij.lang.annotation.ProblemGroup; import com.intellij.lang.injection.InjectedLanguageManager; import com.intellij.notification.NotificationGroup; +import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.ToggleAction; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ex.ApplicationManagerEx; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.components.PathMacroManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.progress.*; +import com.intellij.openapi.progress.util.ProgressIndicatorUtils; import com.intellij.openapi.project.IndexNotReadyException; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectUtilCore; @@ -59,6 +63,7 @@ import com.intellij.psi.*; import com.intellij.psi.search.LocalSearchScope; import com.intellij.psi.search.SearchScope; import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtilCore; import com.intellij.ui.content.*; import com.intellij.util.*; import com.intellij.util.containers.ContainerUtil; @@ -79,14 +84,16 @@ import java.util.*; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.LinkedBlockingQueue; public class GlobalInspectionContextImpl extends GlobalInspectionContextBase implements GlobalInspectionContext { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.ex.GlobalInspectionContextImpl"); private static final NotificationGroup NOTIFICATION_GROUP = NotificationGroup.toolWindowGroup("Inspection Results", ToolWindowId.INSPECTION); private final NotNullLazyValue myContentManager; - private InspectionResultsView myView = null; - private Content myContent = null; + private InspectionResultsView myView; + private Content myContent; + @NotNull private AnalysisUIOptions myUIOptions; public GlobalInspectionContextImpl(@NotNull Project project, @NotNull NotNullLazyValue contentManager) { @@ -96,11 +103,12 @@ public class GlobalInspectionContextImpl extends GlobalInspectionContextBase imp myContentManager = contentManager; } - public ContentManager getContentManager() { + @NotNull + private ContentManager getContentManager() { return myContentManager.getValue(); } - public synchronized void addView(@NotNull InspectionResultsView view, String title) { + public synchronized void addView(@NotNull InspectionResultsView view, @NotNull String title) { if (myContent != null) return; myContentManager.getValue().addContentManagerListener(new ContentManagerAdapter() { @Override @@ -153,84 +161,8 @@ public class GlobalInspectionContextImpl extends GlobalInspectionContextBase imp ApplicationManager.getApplication().runReadAction(new Runnable() { @Override public void run() { - performInspectionsWithProgress(scope, runGlobalToolsOnly); - @NonNls final String ext = ".xml"; - final Map globalTools = new HashMap(); - for (Map.Entry stringSetEntry : myTools.entrySet()) { - final Tools sameTools = stringSetEntry.getValue(); - boolean hasProblems = false; - String toolName = stringSetEntry.getKey(); - if (sameTools != null) { - for (ScopeToolState toolDescr : sameTools.getTools()) { - InspectionToolWrapper toolWrapper = toolDescr.getTool(); - if (toolWrapper instanceof LocalInspectionToolWrapper) { - hasProblems = new File(outputPath, toolName + ext).exists(); - } - else { - InspectionToolPresentation presentation = getPresentation(toolWrapper); - presentation.updateContent(); - if (presentation.hasReportedProblems()) { - final Element root = new Element(InspectionsBundle.message("inspection.problems")); - globalTools.put(root, sameTools); - LOG.assertTrue(!hasProblems, toolName); - break; - } - } - } - } - if (!hasProblems) continue; - try { - new File(outputPath).mkdirs(); - final File file = new File(outputPath, toolName + ext); - inspectionsResults.add(file); - FileUtil.writeToFile(file, ("").getBytes(CharsetToolkit.UTF8_CHARSET), true); - } - catch (IOException e) { - LOG.error(e); - } - } - - getRefManager().iterate(new RefVisitor() { - @Override - public void visitElement(@NotNull final RefEntity refEntity) { - for (Element element : globalTools.keySet()) { - final Tools tools = globalTools.get(element); - for (ScopeToolState state : tools.getTools()) { - try { - InspectionToolWrapper toolWrapper = state.getTool(); - InspectionToolPresentation presentation = getPresentation(toolWrapper); - presentation.exportResults(element, refEntity); - } - catch (Throwable e) { - LOG.error("Problem when exporting: " + refEntity.getExternalName(), e); - } - } - } - } - }); - - for (Element element : globalTools.keySet()) { - final String toolName = globalTools.get(element).getShortName(); - element.setAttribute(LOCAL_TOOL_ATTRIBUTE, Boolean.toString(false)); - final org.jdom.Document doc = new org.jdom.Document(element); - PathMacroManager.getInstance(getProject()).collapsePaths(doc.getRootElement()); - try { - new File(outputPath).mkdirs(); - final File file = new File(outputPath, toolName + ext); - inspectionsResults.add(file); - - OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(file), CharsetToolkit.UTF8_CHARSET); - try { - JDOMUtil.writeDocument(doc, writer, "\n"); - } - finally { - writer.close(); - } - } - catch (IOException e) { - LOG.error(e); - } - } + performInspectionsWithProgress(scope, runGlobalToolsOnly, true); + exportResults(inspectionsResults, outputPath); } }); } @@ -239,7 +171,91 @@ public class GlobalInspectionContextImpl extends GlobalInspectionContextBase imp } } - public void ignoreElement(@NotNull InspectionProfileEntry tool, final PsiElement element) { + private void exportResults(@NotNull List inspectionsResults, @Nullable String outputPath) { + @NonNls final String ext = ".xml"; + final Map globalTools = new HashMap(); + for (Map.Entry entry : myTools.entrySet()) { + final Tools sameTools = entry.getValue(); + boolean hasProblems = false; + String toolName = entry.getKey(); + if (sameTools != null) { + for (ScopeToolState toolDescr : sameTools.getTools()) { + InspectionToolWrapper toolWrapper = toolDescr.getTool(); + if (toolWrapper instanceof LocalInspectionToolWrapper) { + hasProblems = new File(outputPath, toolName + ext).exists(); + } + else { + InspectionToolPresentation presentation = getPresentation(toolWrapper); + presentation.updateContent(); + if (presentation.hasReportedProblems()) { + final Element root = new Element(InspectionsBundle.message("inspection.problems")); + globalTools.put(root, sameTools); + LOG.assertTrue(!hasProblems, toolName); + break; + } + } + } + } + if (hasProblems) { + try { + new File(outputPath).mkdirs(); + final File file = new File(outputPath, toolName + ext); + inspectionsResults.add(file); + FileUtil + .writeToFile(file, ("").getBytes(CharsetToolkit.UTF8_CHARSET), true); + } + catch (IOException e) { + LOG.error(e); + } + } + } + + getRefManager().iterate(new RefVisitor() { + @Override + public void visitElement(@NotNull final RefEntity refEntity) { + for (Map.Entry entry : globalTools.entrySet()) { + Tools tools = entry.getValue(); + Element element = entry.getKey(); + for (ScopeToolState state : tools.getTools()) { + try { + InspectionToolWrapper toolWrapper = state.getTool(); + InspectionToolPresentation presentation = getPresentation(toolWrapper); + presentation.exportResults(element, refEntity); + } + catch (Throwable e) { + LOG.error("Problem when exporting: " + refEntity.getExternalName(), e); + } + } + } + } + }); + + for (Map.Entry entry : globalTools.entrySet()) { + final String toolName = entry.getValue().getShortName(); + Element element = entry.getKey(); + element.setAttribute(LOCAL_TOOL_ATTRIBUTE, Boolean.toString(false)); + final org.jdom.Document doc = new org.jdom.Document(element); + PathMacroManager.getInstance(getProject()).collapsePaths(doc.getRootElement()); + try { + new File(outputPath).mkdirs(); + final File file = new File(outputPath, toolName + ext); + inspectionsResults.add(file); + + OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(file), CharsetToolkit.UTF8_CHARSET); + try { + JDOMUtil.writeDocument(doc, writer, "\n"); + } + finally { + writer.close(); + } + } + catch (IOException e) { + LOG.error(e); + } + } + } + + public void ignoreElement(@NotNull InspectionProfileEntry tool, @NotNull PsiElement element) { final RefElement refElement = getRefManager().getReference(element); final Tools tools = myTools.get(tool.getShortName()); if (tools != null){ @@ -267,6 +283,7 @@ public class GlobalInspectionContextImpl extends GlobalInspectionContextBase imp } } + @NotNull public AnalysisUIOptions getUIOptions() { return myUIOptions; } @@ -275,6 +292,7 @@ public class GlobalInspectionContextImpl extends GlobalInspectionContextBase imp myUIOptions.SPLITTER_PROPORTION = proportion; } + @NotNull public ToggleAction createToggleAutoscrollAction() { return myUIOptions.getAutoScrollToSourceHandler().createToggleAction(); } @@ -286,6 +304,7 @@ public class GlobalInspectionContextImpl extends GlobalInspectionContextBase imp super.launchInspections(scope); } + @NotNull @Override protected PerformInBackgroundOption createOption() { return new PerformAnalysisInBackgroundOption(getProject()); @@ -313,10 +332,21 @@ public class GlobalInspectionContextImpl extends GlobalInspectionContextBase imp } @Override - protected void runTools(@NotNull final AnalysisScope scope, boolean runGlobalToolsOnly) { + protected void runTools(@NotNull final AnalysisScope scope, boolean runGlobalToolsOnly, boolean isOfflineInspections) { + final ProgressIndicator progressIndicator = ProgressIndicatorProvider.getGlobalProgressIndicator(); + if (progressIndicator == null) { + throw new IncorrectOperationException("Must be run under progress"); + } + if (!isOfflineInspections && ApplicationManager.getApplication().isDispatchThread()) { + throw new IncorrectOperationException("Must not start inspections from within EDT"); + } if (ApplicationManager.getApplication().isWriteAccessAllowed()) { throw new IncorrectOperationException("Must not start inspections from within write action"); } + // in offline inspection application we don't care about global read action + if (!isOfflineInspections && ApplicationManager.getApplication().isReadAccessAllowed()) { + throw new IncorrectOperationException("Must not start inspections from within global read action"); + } final InspectionManager inspectionManager = InspectionManager.getInstance(getProject()); final List globalTools = new ArrayList(); final List localTools = new ArrayList(); @@ -325,7 +355,7 @@ public class GlobalInspectionContextImpl extends GlobalInspectionContextBase imp appendPairedInspectionsForUnfairTools(globalTools, globalSimpleTools, localTools); ((RefManagerImpl)getRefManager()).initializeAnnotators(); - runGlobalTools(scope, inspectionManager, globalTools); + runGlobalTools(scope, inspectionManager, globalTools, isOfflineInspections); if (runGlobalToolsOnly) return; @@ -339,20 +369,57 @@ public class GlobalInspectionContextImpl extends GlobalInspectionContextBase imp final boolean headlessEnvironment = ApplicationManager.getApplication().isHeadlessEnvironment(); final Map map = getInspectionWrappersMap(localTools); - final BlockingQueue> chunksToInspect = new ArrayBlockingQueue>(10); - startIterateScopeIntoChunks(scope, localScopeFiles, headlessEnvironment, chunksToInspect); - try { - for (List chunk = chunksToInspect.take(); !chunk.isEmpty(); chunk = chunksToInspect.take()) { - JobLauncher.getInstance().invokeConcurrentlyUnderProgress(chunk, myProgressIndicator, true, false, new Processor() { + final BlockingQueue filesToInspect = new ArrayBlockingQueue(1000); + final Queue filesFailedToInspect = new LinkedBlockingQueue(); + // use original progress indicator here since we don't want it to cancel on write action start + startIterateScope(scope, localScopeFiles, headlessEnvironment, filesToInspect, progressIndicator); + + Processor processor = new Processor() { + @Override + public boolean process(final PsiFile file) { + ProgressManager.checkCanceled(); + if (!ApplicationManagerEx.getApplicationEx().tryRunReadAction(new Runnable() { @Override - public boolean process(final PsiFile file) { - return inspectFile(file, inspectionManager, localTools, globalSimpleTools, map); + public void run() { + if (!file.isValid()) { + return; + } + inspectFile(file, inspectionManager, localTools, globalSimpleTools, map); } - }); + })) { + throw new ProcessCanceledException(); + } + + return true; + } + }; + while (true) { + Disposable disposable = Disposer.newDisposable(); + ProgressIndicator wrapper = new SensitiveProgressWrapper(progressIndicator); + wrapper.start(); + ProgressIndicatorUtils.forceWriteActionPriority(wrapper, disposable); + + try { + // use wrapper here to cancel early when write action start but do not affect the original indicator + ((JobLauncherImpl)JobLauncher.getInstance()).processQueue(filesToInspect, filesFailedToInspect, wrapper, TOMBSTONE, processor); + break; + } + catch (ProcessCanceledException ignored) { + progressIndicator.checkCanceled(); + // PCE may be thrown from inside wrapper when write action started + // go on with the write and then resume processing the rest of the queue + assert !ApplicationManager.getApplication().isReadAccessAllowed(); + assert !ApplicationManager.getApplication().isDispatchThread(); + + // wait for write action to complete + ApplicationManager.getApplication().runReadAction(EmptyRunnable.getInstance()); + } + finally { + Disposer.dispose(disposable); } } - catch (InterruptedException ignored) { - } + progressIndicator.checkCanceled(); + for (Tools tools : globalSimpleTools) { GlobalInspectionToolWrapper toolWrapper = (GlobalInspectionToolWrapper)tools.getTool(); GlobalSimpleInspectionTool tool = (GlobalSimpleInspectionTool)toolWrapper.getTool(); @@ -368,6 +435,11 @@ public class GlobalInspectionContextImpl extends GlobalInspectionContextBase imp @NotNull final Map wrappersMap) { Document document = PsiDocumentManager.getInstance(getProject()).getDocument(file); if (document == null) return true; + + VirtualFile virtualFile = file.getVirtualFile(); + String url = ProjectUtilCore.displayUrlRelativeToProject(virtualFile, virtualFile.getPresentableUrl(), getProject(), true, false); + incrementJobDoneAmount(getStdJobDescriptors().LOCAL_ANALYSIS, url); + final LocalInspectionsPass pass = new LocalInspectionsPass(file, document, 0, file.getTextLength(), LocalInspectionsPass.EMPTY_PRIORITY_RANGE, true, HighlightInfoProcessor.getEmpty()); @@ -409,59 +481,54 @@ public class GlobalInspectionContextImpl extends GlobalInspectionContextBase imp return true; } - private void startIterateScopeIntoChunks(@NotNull final AnalysisScope scope, - final Set localScopeFiles, - final boolean headlessEnvironment, - @NotNull final BlockingQueue> chunksToInspect) { + private static final PsiFile TOMBSTONE = PsiUtilCore.NULL_PSI_FILE; + + private void startIterateScope(@NotNull final AnalysisScope scope, + @Nullable final Collection localScopeFiles, + final boolean headlessEnvironment, + @NotNull final BlockingQueue outFilesToInspect, + @NotNull final ProgressIndicator progressIndicator) { ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { @Override public void run() { - final List chunk = new ArrayList(); try { scope.accept(new Processor() { @Override public boolean process(final VirtualFile file) { + progressIndicator.checkCanceled(); + final PsiFile[] psiFile = new PsiFile[1]; + Document document = ApplicationManager.getApplication().runReadAction(new Computable() { @Override public Document compute() { - PsiFile psiFile = PsiManager.getInstance(getProject()).findFile(file); - Document document = psiFile == null ? null : shouldProcess(psiFile, headlessEnvironment, localScopeFiles); + PsiFile psi = PsiManager.getInstance(getProject()).findFile(file); + Document document = psi == null ? null : shouldProcess(psi, headlessEnvironment, localScopeFiles); if (document != null) { - chunk.add(psiFile); + psiFile[0] = psi; } return document; } }); //do not inspect binary files - if (document != null) { - document.getText(); // preload text - - if (chunk.size() >= JobSchedulerImpl.CORES_COUNT) { - try { - LOG.assertTrue(!ApplicationManager.getApplication().isReadAccessAllowed()); - chunksToInspect.put(new ArrayList(chunk)); - } - catch (InterruptedException e) { - LOG.error(e); - } - chunk.clear(); + if (document != null && psiFile[0] != null) { + try { + LOG.assertTrue(!ApplicationManager.getApplication().isReadAccessAllowed()); + outFilesToInspect.put(psiFile[0]); + } + catch (InterruptedException e) { + LOG.error(e); } } return true; } }); - if (!chunk.isEmpty()) { - LOG.assertTrue(!ApplicationManager.getApplication().isReadAccessAllowed()); - chunksToInspect.put(new ArrayList(chunk)); - chunk.clear(); - } } - catch (InterruptedException e) { - LOG.error(e); + catch (ProcessCanceledException e) { + // ignore, but put tombstone } finally { try { - chunksToInspect.put(Collections.emptyList()); // tombstone + outFilesToInspect.put(TOMBSTONE); } catch (InterruptedException e) { LOG.error(e); @@ -471,7 +538,7 @@ public class GlobalInspectionContextImpl extends GlobalInspectionContextBase imp }); } - private Document shouldProcess(@NotNull PsiFile file, boolean headlessEnvironment, Set localScopeFiles) { + private Document shouldProcess(@NotNull PsiFile file, boolean headlessEnvironment, @Nullable Collection localScopeFiles) { final VirtualFile virtualFile = file.getVirtualFile(); if (virtualFile == null) return null; if (isBinary(file)) return null; //do not inspect binary files @@ -484,16 +551,17 @@ public class GlobalInspectionContextImpl extends GlobalInspectionContextBase imp LOG.debug("Running local inspections on " + virtualFile.getPath()); } - String url = ProjectUtilCore.displayUrlRelativeToProject(virtualFile, virtualFile.getPresentableUrl(), getProject(), true, false); - incrementJobDoneAmount(getStdJobDescriptors().LOCAL_ANALYSIS, url); if (SingleRootFileViewProvider.isTooLargeForIntelligence(virtualFile)) return null; if (localScopeFiles != null && !localScopeFiles.add(virtualFile)) return null; return PsiDocumentManager.getInstance(getProject()).getDocument(file); } - private void runGlobalTools(@NotNull final AnalysisScope scope, @NotNull final InspectionManager inspectionManager, @NotNull List globalTools) { - LOG.assertTrue(!ApplicationManager.getApplication().isReadAccessAllowed() || ApplicationManager.getApplication().isHeadlessEnvironment(), "Must not run under read action, too unresponsive"); + private void runGlobalTools(@NotNull final AnalysisScope scope, + @NotNull final InspectionManager inspectionManager, + @NotNull List globalTools, + boolean isOfflineInspections) { + LOG.assertTrue(!ApplicationManager.getApplication().isReadAccessAllowed() || isOfflineInspections, "Must not run under read action, too unresponsive"); final List needRepeatSearchRequest = new ArrayList(); final boolean canBeExternalUsages = scope.getScopeType() != AnalysisScope.PROJECT; @@ -584,9 +652,11 @@ public class GlobalInspectionContextImpl extends GlobalInspectionContextBase imp } } - private static List getWrappersFromTools(List localTools, PsiFile file) { + @NotNull + private static List getWrappersFromTools(@NotNull List localTools, @NotNull PsiFile file) { final List lTools = new ArrayList(); for (Tools tool : localTools) { + //noinspection unchecked final T enabledTool = (T)tool.getEnabledTool(file); if (enabledTool != null) { lTools.add(enabledTool); @@ -667,9 +737,7 @@ public class GlobalInspectionContextImpl extends GlobalInspectionContextBase imp AnalysisUIOptions.getInstance(getProject()).save(myUIOptions); if (myContent != null) { final ContentManager contentManager = getContentManager(); - if (contentManager != null) { //null for tests - contentManager.removeContent(myContent, true); - } + contentManager.removeContent(myContent, true); } myView = null; super.close(noSuspisiousCodeFound); @@ -714,36 +782,31 @@ public class GlobalInspectionContextImpl extends GlobalInspectionContextBase imp } @Override - public void codeCleanup(final Project project, - final AnalysisScope scope, - final InspectionProfile profile, - final String commandName, - final Runnable postRunnable, + public void codeCleanup(@NotNull final Project project, + @NotNull final AnalysisScope scope, + @NotNull final InspectionProfile profile, + @Nullable final String commandName, + @Nullable final Runnable postRunnable, final boolean modal) { - Task task; - if (modal) { - task = new Task.Modal(project, "Inspect code...", true) { - @Override - public void run(@NotNull ProgressIndicator indicator) { - cleanup(scope, profile, project, postRunnable, commandName); - } - }; - } else { - task = new Task.Backgroundable(project, "Inspect code...", true) { - @Override - public void run(@NotNull ProgressIndicator indicator) { - cleanup(scope, profile, project, postRunnable, commandName); - } - }; - } + Task task = modal ? new Task.Modal(project, "Inspect code...", true) { + @Override + public void run(@NotNull ProgressIndicator indicator) { + cleanup(scope, profile, project, postRunnable, commandName); + } + } : new Task.Backgroundable(project, "Inspect code...", true) { + @Override + public void run(@NotNull ProgressIndicator indicator) { + cleanup(scope, profile, project, postRunnable, commandName); + } + }; ProgressManager.getInstance().run(task); } private void cleanup(@NotNull AnalysisScope scope, @NotNull InspectionProfile profile, - final Project project, - final Runnable postRunnable, - final String commandName) { + @NotNull final Project project, + @Nullable final Runnable postRunnable, + @Nullable final String commandName) { final int fileCount = scope.getFileCount(); final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator(); final List lTools = new ArrayList(); @@ -772,7 +835,7 @@ public class GlobalInspectionContextImpl extends GlobalInspectionContextBase imp } }); scope.accept(new PsiElementVisitor() { - private int myCount = 0; + private int myCount; @Override public void visitFile(PsiFile file) { if (progressIndicator != null) { @@ -863,7 +926,7 @@ public class GlobalInspectionContextImpl extends GlobalInspectionContextBase imp } } - private static boolean isBinary(PsiFile file) { + private static boolean isBinary(@NotNull PsiFile file) { return file instanceof PsiBinaryFile || file.getFileType().isBinary(); } } diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ex/InspectionRVContentProvider.java b/platform/lang-impl/src/com/intellij/codeInspection/ex/InspectionRVContentProvider.java index 24b7b44de2c9..f476a9c8a52c 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ex/InspectionRVContentProvider.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ex/InspectionRVContentProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2015 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,6 @@ package com.intellij.codeInspection.ex; import com.intellij.codeInspection.CommonProblemDescriptor; import com.intellij.codeInspection.QuickFix; -import com.intellij.codeInspection.offlineViewer.OfflineInspectionRVContentProvider; import com.intellij.codeInspection.reference.RefEntity; import com.intellij.codeInspection.ui.*; import com.intellij.openapi.application.ApplicationManager; diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ex/InspectionRVContentProviderImpl.java b/platform/lang-impl/src/com/intellij/codeInspection/ex/InspectionRVContentProviderImpl.java index 28d4861e5348..1cf2714e7840 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ex/InspectionRVContentProviderImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ex/InspectionRVContentProviderImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2015 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ package com.intellij.codeInspection.ex; import com.intellij.codeInspection.CommonProblemDescriptor; import com.intellij.codeInspection.reference.*; import com.intellij.codeInspection.ui.*; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.TextRange; @@ -94,6 +95,7 @@ public class InspectionRVContentProviderImpl extends InspectionRVContentProvider @NotNull final Map> contents, @NotNull final Map problems, DefaultTreeModel model) { + ApplicationManager.getApplication().assertIsDispatchThread(); final InspectionToolWrapper toolWrapper = toolNode.getToolWrapper(); Function> computeContainer = new Function>() { diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/DefaultInspectionToolPresentation.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/DefaultInspectionToolPresentation.java index ff706074942e..ea423cdc6e7b 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/DefaultInspectionToolPresentation.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/DefaultInspectionToolPresentation.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2015 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,6 +42,7 @@ import com.intellij.util.IncorrectOperationException; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashSet; import com.intellij.util.ui.UIUtil; +import gnu.trove.Equality; import gnu.trove.THashMap; import gnu.trove.THashSet; import org.jdom.Element; @@ -195,7 +196,7 @@ public class DefaultInspectionToolPresentation implements ProblemDescriptionsPro } @Override - public void addProblemElement(RefEntity refElement, boolean filterSuppressed, @NotNull CommonProblemDescriptor... descriptors) { + public void addProblemElement(final RefEntity refElement, boolean filterSuppressed, @NotNull final CommonProblemDescriptor... descriptors) { if (refElement == null) return; if (descriptors.length == 0) return; if (filterSuppressed) { @@ -203,7 +204,7 @@ public class DefaultInspectionToolPresentation implements ProblemDescriptionsPro synchronized (lock) { Map problemElements = getProblemElements(); CommonProblemDescriptor[] problems = problemElements.get(refElement); - problems = problems == null ? descriptors : ArrayUtil.mergeArrays(problems, descriptors, CommonProblemDescriptor.ARRAY_FACTORY); + problems = problems == null ? descriptors : mergeDescriptors(problems, descriptors); problemElements.put(refElement, problems); } for (CommonProblemDescriptor description : descriptors) { @@ -227,34 +228,34 @@ public class DefaultInspectionToolPresentation implements ProblemDescriptionsPro if (view == null || !(refElement instanceof RefElement)) { return; } - final InspectionNode toolNode; - synchronized (myToolLock) { - if (myToolNode == null) { - final HighlightSeverity currentSeverity = getSeverity((RefElement)refElement); - toolNode = view.addTool(myToolWrapper, HighlightDisplayLevel.find(currentSeverity), context.getUIOptions().GROUP_BY_SEVERITY); - } - else { - toolNode = myToolNode; - if (toolNode.isTooBigForOnlineRefresh()) { - return; - } - } - } - final Map problems = new HashMap(); - problems.put(refElement, descriptors); - final Map> contents = new HashMap>(); - final String groupName = refElement.getRefManager().getGroupName((RefElement)refElement); - Set content = contents.get(groupName); - if (content == null) { - content = new HashSet(); - contents.put(groupName, content); - } - content.add(refElement); - UIUtil.invokeLaterIfNeeded(new Runnable() { @Override public void run() { if (!isDisposed()) { + final InspectionNode toolNode; + synchronized (myToolLock) { + if (myToolNode == null) { + final HighlightSeverity currentSeverity = getSeverity((RefElement)refElement); + toolNode = view.addTool(myToolWrapper, HighlightDisplayLevel.find(currentSeverity), context.getUIOptions().GROUP_BY_SEVERITY); + } + else { + toolNode = myToolNode; + if (toolNode.isTooBigForOnlineRefresh()) { + return; + } + } + } + final Map problems = new HashMap(); + problems.put(refElement, descriptors); + final Map> contents = new HashMap>(); + final String groupName = refElement.getRefManager().getGroupName((RefElement)refElement); + Set content = contents.get(groupName); + if (content == null) { + content = new HashSet(); + contents.put(groupName, content); + } + content.add(refElement); + view.getProvider().appendToolNodeContent(context, toolNode, (InspectionTreeNode)toolNode.getParent(), context.getUIOptions().SHOW_STRUCTURE, contents, problems, (DefaultTreeModel)view.getTree().getModel()); @@ -262,11 +263,43 @@ public class DefaultInspectionToolPresentation implements ProblemDescriptionsPro } } }); - } } - + @NotNull + private static CommonProblemDescriptor[] mergeDescriptors(@NotNull CommonProblemDescriptor[] problems1, + @NotNull CommonProblemDescriptor[] problems2) { + CommonProblemDescriptor[] out = new CommonProblemDescriptor[problems1.length + problems2.length]; + int o = problems1.length; + Equality equality = new Equality() { + @Override + public boolean equals(CommonProblemDescriptor o1, CommonProblemDescriptor o2) { + if (o1 instanceof ProblemDescriptor) { + ProblemDescriptorBase p1 = (ProblemDescriptorBase)o1; + ProblemDescriptorBase p2 = (ProblemDescriptorBase)o2; + if (!Comparing.equal(p1.getDescriptionTemplate(), p2.getDescriptionTemplate())) return false; + if (!Comparing.equal(p1.getTextRange(), p2.getTextRange())) return false; + if (!Comparing.equal(p1.getHighlightType(), p2.getHighlightType())) return false; + if (!Comparing.equal(p1.getProblemGroup(), p2.getProblemGroup())) return false; + if (!Comparing.equal(p1.getStartElement(), p2.getStartElement())) return false; + if (!Comparing.equal(p1.getEndElement(), p2.getEndElement())) return false; + } + else { + if (!o1.toString().equals(o2.toString())) return false; + } + return true; + } + }; + for (CommonProblemDescriptor descriptor : problems2) { + if (ArrayUtil.indexOf(problems1, descriptor, equality) == -1) { + out[o++] = descriptor; + } + } + System.arraycopy(problems1, 0, out, 0, problems1.length); + return Arrays.copyOfRange(out, 0, o); + } + + public void setToolNode(InspectionNode toolNode) { synchronized (myToolLock) { myToolNode = toolNode; diff --git a/platform/platform-impl/src/com/intellij/concurrency/JobLauncherImpl.java b/platform/platform-impl/src/com/intellij/concurrency/JobLauncherImpl.java index db3d1011bd55..74750adb1555 100644 --- a/platform/platform-impl/src/com/intellij/concurrency/JobLauncherImpl.java +++ b/platform/platform-impl/src/com/intellij/concurrency/JobLauncherImpl.java @@ -29,7 +29,9 @@ import jsr166e.ForkJoinWorkerThread; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; import java.util.List; +import java.util.Queue; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; @@ -84,41 +86,11 @@ public class JobLauncherImpl extends JobLauncher { boolean runInReadAction, boolean failFastOnAcquireReadAction, @NotNull final Processor thingProcessor) throws ProcessCanceledException { - //if (runInReadAction && ApplicationManager.getApplication().isWriteAccessAllowed()) { - // throw new RuntimeException("Must not run invokeConcurrentlyUnderProgress() from under write action because of imminent deadlock"); - //} - if (things.isEmpty()) return true; // supply our own indicator even if we haven't given one - to support cancellation final ProgressIndicator wrapper = progress == null ? new AbstractProgressIndicatorBase() : new SensitiveProgressWrapper(progress); - if (things.size() <= 1 || JobSchedulerImpl.CORES_COUNT <= CORES_FORK_THRESHOLD) { - final AtomicBoolean result = new AtomicBoolean(true); - Runnable runnable = new Runnable() { - @Override - public void run() { - ProgressManager.getInstance().executeProcessUnderProgress(new Runnable() { - @Override - public void run() { - //noinspection ForLoopReplaceableByForEach - for (int i = 0; i < things.size(); i++) { - T thing = things.get(i); - if (!thingProcessor.process(thing)) { - result.set(false); - break; - } - } - } - }, wrapper); - } - }; - if (runInReadAction) { - if (!ApplicationManagerEx.getApplicationEx().tryRunReadAction(runnable)) return false; - } - else { - runnable.run(); - } - return result.get(); - } + Boolean result = processImmediatelyIfTooFew(things, wrapper, runInReadAction, thingProcessor); + if (result != null) return result.booleanValue(); ApplierCompleter applier = new ApplierCompleter(null, runInReadAction, wrapper, things, thingProcessor, 0, things.size(), null); try { @@ -146,6 +118,49 @@ public class JobLauncherImpl extends JobLauncher { return applier.completeTaskWhichFailToAcquireReadAction(); } + // if {@code things} are too few to be processed in the real pool, returns TRUE if processed successfully, FALSE if not + // returns null if things need to be processed in the real pool + private static Boolean processImmediatelyIfTooFew(@NotNull final List things, + final ProgressIndicator progress, + boolean runInReadAction, + @NotNull final Processor thingProcessor) { + // commit can be invoked from within write action + //if (runInReadAction && ApplicationManager.getApplication().isWriteAccessAllowed()) { + // throw new RuntimeException("Must not run invokeConcurrentlyUnderProgress() from under write action because of imminent deadlock"); + //} + if (things.isEmpty()) return true; + + if (things.size() <= 1 || JobSchedulerImpl.CORES_COUNT <= CORES_FORK_THRESHOLD) { + final AtomicBoolean result = new AtomicBoolean(true); + Runnable runnable = new Runnable() { + @Override + public void run() { + ProgressManager.getInstance().executeProcessUnderProgress(new Runnable() { + @Override + public void run() { + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < things.size(); i++) { + T thing = things.get(i); + if (!thingProcessor.process(thing)) { + result.set(false); + break; + } + } + } + }, progress); + } + }; + if (runInReadAction) { + if (!ApplicationManagerEx.getApplicationEx().tryRunReadAction(runnable)) return false; + } + else { + runnable.run(); + } + return result.get(); + } + return null; + } + // This implementation is not really async @NotNull @@ -159,7 +174,7 @@ public class JobLauncherImpl extends JobLauncher { @NotNull @Override - public Job submitToJobThread(@NotNull final Runnable action, final Consumer onDoneCallback) { + public Job submitToJobThread(@NotNull final Runnable action, @Nullable Consumer onDoneCallback) { VoidForkJoinTask task = new VoidForkJoinTask(action, onDoneCallback); pool.submit(task); return task; @@ -246,4 +261,103 @@ public class JobLauncherImpl extends JobLauncher { get(millis, TimeUnit.MILLISECONDS); } } + + /** + * Process all elements from the {@code failedToProcess} and then {@code things} concurrently in the underlying pool maintaining its load. + * Stop when {@code tombStone} element is occurred. + * If was unable to process some element, add it back to the {@code failedToProcess} queue. + * @return true if all elements processed successfully, false if at least one processor returned false or exception occurred + */ + public boolean processQueue(@NotNull final BlockingQueue things, + @NotNull final Queue failedToProcess, + @NotNull final ProgressIndicator progress, + @NotNull final T tombStone, + @NotNull final Processor thingProcessor) { + class MyTask implements Callable { + private final int mySeq; + private boolean result; + + private MyTask(int seq) { + mySeq = seq; + } + + @Override + public Boolean call() throws Exception { + ProgressManager.getInstance().executeProcessUnderProgress(new Runnable() { + @Override + public void run() { + try { + while (true) { + progress.checkCanceled(); + T element = failedToProcess.poll(); + if (element == null) element = things.take(); + + if (element == tombStone) { + things.offer(element); + result = true; + break; + } + try { + if (!thingProcessor.process(element)) { + result = false; + break; + } + } + catch (RuntimeException e) { + failedToProcess.add(element); + throw e; + } + } + } + catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + }, progress); + return result; + } + + @Override + public String toString() { + return super.toString() + " seq="+mySeq; + } + } + + boolean isSmallEnough = things.contains(tombStone); + if (isSmallEnough) { + try { + // do not distribute for small queues + return new MyTask(0).call(); + } + catch (RuntimeException e) { + throw e; + } + catch (Error e) { + throw e; + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + + List> tasks = new ArrayList>(); + for (int i = 0; i < JobSchedulerImpl.CORES_COUNT; i++) { + tasks.add(pool.submit(new MyTask(i))); + } + + boolean result = true; + RuntimeException exception = null; + for (ForkJoinTask task : tasks) { + try { + result &= task.join(); + } + catch (RuntimeException e) { + exception = e; + } + } + if (exception != null) { + throw exception; + } + return result; + } }