IDEA-125474 Local inspections run globally should use multiple cores

This commit is contained in:
Alexey Kudravtsev
2015-04-27 15:19:36 +03:00
parent 997cf3c9b6
commit 8b9f120dff
8 changed files with 594 additions and 273 deletions
@@ -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> T getExtension(Key<T> key);
<T> T getExtension(@NotNull Key<T> key);
void incrementJobDoneAmount(JobDescriptor job, String message);
void incrementJobDoneAmount(@NotNull JobDescriptor job, @NotNull String message);
@NotNull
StdJobDescriptors getStdJobDescriptors();
@@ -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<JobDescriptor> myJobDescriptors;
private final List<JobDescriptor> myJobDescriptors = new ArrayList<JobDescriptor>();
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> T getExtension(final Key<T> key) {
public <T> T getExtension(@NotNull final Key<T> 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<Tools> outGlobalTools,
@NotNull List<Tools> outLocalTools,
@NotNull List<Tools> outGlobalSimpleTools) {
myJobDescriptors = new ArrayList<JobDescriptor>();
final List<Tools> 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<Tools> getUsedTools() {
InspectionProfileImpl profile = new InspectionProfileImpl((InspectionProfileImpl)getCurrentProfile());
List<Tools> 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<SmartPsiElementPointer<PsiElement>> elements = new ArrayList<SmartPsiElementPointer<PsiElement>>();
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();
@@ -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<? extends PsiElement> 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<PsiFileSystemItem> 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";
}
}
}
@@ -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<ContentManager> 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> 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<Element, Tools> globalTools = new HashMap<Element, Tools>();
for (Map.Entry<String,Tools> 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, ("</" + InspectionsBundle.message("inspection.problems") + ">").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<File> inspectionsResults, @Nullable String outputPath) {
@NonNls final String ext = ".xml";
final Map<Element, Tools> globalTools = new HashMap<Element, Tools>();
for (Map.Entry<String,Tools> 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, ("</" + InspectionsBundle.message("inspection.problems") + ">").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<Element, Tools> 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<Element, Tools> 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<Tools> globalTools = new ArrayList<Tools>();
final List<Tools> localTools = new ArrayList<Tools>();
@@ -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<String, InspectionToolWrapper> map = getInspectionWrappersMap(localTools);
final BlockingQueue<List<PsiFile>> chunksToInspect = new ArrayBlockingQueue<List<PsiFile>>(10);
startIterateScopeIntoChunks(scope, localScopeFiles, headlessEnvironment, chunksToInspect);
try {
for (List<PsiFile> chunk = chunksToInspect.take(); !chunk.isEmpty(); chunk = chunksToInspect.take()) {
JobLauncher.getInstance().invokeConcurrentlyUnderProgress(chunk, myProgressIndicator, true, false, new Processor<PsiFile>() {
final BlockingQueue<PsiFile> filesToInspect = new ArrayBlockingQueue<PsiFile>(1000);
final Queue<PsiFile> filesFailedToInspect = new LinkedBlockingQueue<PsiFile>();
// use original progress indicator here since we don't want it to cancel on write action start
startIterateScope(scope, localScopeFiles, headlessEnvironment, filesToInspect, progressIndicator);
Processor<PsiFile> processor = new Processor<PsiFile>() {
@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<String, InspectionToolWrapper> 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<VirtualFile> localScopeFiles,
final boolean headlessEnvironment,
@NotNull final BlockingQueue<List<PsiFile>> chunksToInspect) {
private static final PsiFile TOMBSTONE = PsiUtilCore.NULL_PSI_FILE;
private void startIterateScope(@NotNull final AnalysisScope scope,
@Nullable final Collection<VirtualFile> localScopeFiles,
final boolean headlessEnvironment,
@NotNull final BlockingQueue<PsiFile> outFilesToInspect,
@NotNull final ProgressIndicator progressIndicator) {
ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
@Override
public void run() {
final List<PsiFile> chunk = new ArrayList<PsiFile>();
try {
scope.accept(new Processor<VirtualFile>() {
@Override
public boolean process(final VirtualFile file) {
progressIndicator.checkCanceled();
final PsiFile[] psiFile = new PsiFile[1];
Document document = ApplicationManager.getApplication().runReadAction(new Computable<Document>() {
@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<PsiFile>(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<PsiFile>(chunk));
chunk.clear();
}
}
catch (InterruptedException e) {
LOG.error(e);
catch (ProcessCanceledException e) {
// ignore, but put tombstone
}
finally {
try {
chunksToInspect.put(Collections.<PsiFile>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<VirtualFile> localScopeFiles) {
private Document shouldProcess(@NotNull PsiFile file, boolean headlessEnvironment, @Nullable Collection<VirtualFile> 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<Tools> 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<Tools> globalTools,
boolean isOfflineInspections) {
LOG.assertTrue(!ApplicationManager.getApplication().isReadAccessAllowed() || isOfflineInspections, "Must not run under read action, too unresponsive");
final List<InspectionToolWrapper> needRepeatSearchRequest = new ArrayList<InspectionToolWrapper>();
final boolean canBeExternalUsages = scope.getScopeType() != AnalysisScope.PROJECT;
@@ -584,9 +652,11 @@ public class GlobalInspectionContextImpl extends GlobalInspectionContextBase imp
}
}
private static <T extends InspectionToolWrapper> List<T> getWrappersFromTools(List<Tools> localTools, PsiFile file) {
@NotNull
private static <T extends InspectionToolWrapper> List<T> getWrappersFromTools(@NotNull List<Tools> localTools, @NotNull PsiFile file) {
final List<T> lTools = new ArrayList<T>();
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<LocalInspectionToolWrapper> lTools = new ArrayList<LocalInspectionToolWrapper>();
@@ -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();
}
}
@@ -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;
@@ -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<String, Set<RefEntity>> contents,
@NotNull final Map<RefEntity, CommonProblemDescriptor[]> problems,
DefaultTreeModel model) {
ApplicationManager.getApplication().assertIsDispatchThread();
final InspectionToolWrapper toolWrapper = toolNode.getToolWrapper();
Function<RefEntity, UserObjectContainer<RefEntity>> computeContainer = new Function<RefEntity, UserObjectContainer<RefEntity>>() {
@@ -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<RefEntity, CommonProblemDescriptor[]> 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<RefEntity, CommonProblemDescriptor[]> problems = new HashMap<RefEntity, CommonProblemDescriptor[]>();
problems.put(refElement, descriptors);
final Map<String, Set<RefEntity>> contents = new HashMap<String, Set<RefEntity>>();
final String groupName = refElement.getRefManager().getGroupName((RefElement)refElement);
Set<RefEntity> content = contents.get(groupName);
if (content == null) {
content = new HashSet<RefEntity>();
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<RefEntity, CommonProblemDescriptor[]> problems = new HashMap<RefEntity, CommonProblemDescriptor[]>();
problems.put(refElement, descriptors);
final Map<String, Set<RefEntity>> contents = new HashMap<String, Set<RefEntity>>();
final String groupName = refElement.getRefManager().getGroupName((RefElement)refElement);
Set<RefEntity> content = contents.get(groupName);
if (content == null) {
content = new HashSet<RefEntity>();
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<CommonProblemDescriptor> equality = new Equality<CommonProblemDescriptor>() {
@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;
@@ -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<? super T> 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<T> applier = new ApplierCompleter<T>(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 <T> Boolean processImmediatelyIfTooFew(@NotNull final List<T> things,
final ProgressIndicator progress,
boolean runInReadAction,
@NotNull final Processor<? super T> 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<Void> submitToJobThread(@NotNull final Runnable action, final Consumer<Future> onDoneCallback) {
public Job<Void> submitToJobThread(@NotNull final Runnable action, @Nullable Consumer<Future> 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 <T> boolean processQueue(@NotNull final BlockingQueue<T> things,
@NotNull final Queue<T> failedToProcess,
@NotNull final ProgressIndicator progress,
@NotNull final T tombStone,
@NotNull final Processor<? super T> thingProcessor) {
class MyTask implements Callable<Boolean> {
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<ForkJoinTask<Boolean>> tasks = new ArrayList<ForkJoinTask<Boolean>>();
for (int i = 0; i < JobSchedulerImpl.CORES_COUNT; i++) {
tasks.add(pool.submit(new MyTask(i)));
}
boolean result = true;
RuntimeException exception = null;
for (ForkJoinTask<Boolean> task : tasks) {
try {
result &= task.join();
}
catch (RuntimeException e) {
exception = e;
}
}
if (exception != null) {
throw exception;
}
return result;
}
}