diff options
Diffstat (limited to 'plugins/coverage-common/src/com/intellij/coverage/CoverageDataManagerImpl.java')
-rw-r--r-- | plugins/coverage-common/src/com/intellij/coverage/CoverageDataManagerImpl.java | 711 |
1 files changed, 711 insertions, 0 deletions
diff --git a/plugins/coverage-common/src/com/intellij/coverage/CoverageDataManagerImpl.java b/plugins/coverage-common/src/com/intellij/coverage/CoverageDataManagerImpl.java new file mode 100644 index 000000000000..2db8835ff8b2 --- /dev/null +++ b/plugins/coverage-common/src/com/intellij/coverage/CoverageDataManagerImpl.java @@ -0,0 +1,711 @@ +package com.intellij.coverage; + +import com.intellij.CommonBundle; +import com.intellij.codeInsight.CodeInsightBundle; +import com.intellij.coverage.view.CoverageViewManager; +import com.intellij.coverage.view.CoverageViewSuiteListener; +import com.intellij.execution.configurations.RunConfigurationBase; +import com.intellij.execution.configurations.RunnerSettings; +import com.intellij.execution.configurations.coverage.CoverageEnabledConfiguration; +import com.intellij.execution.process.ProcessAdapter; +import com.intellij.execution.process.ProcessEvent; +import com.intellij.execution.process.ProcessHandler; +import com.intellij.ide.projectView.ProjectView; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.PathManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.EditorFactory; +import com.intellij.openapi.editor.colors.EditorColorsAdapter; +import com.intellij.openapi.editor.colors.EditorColorsManager; +import com.intellij.openapi.editor.colors.EditorColorsScheme; +import com.intellij.openapi.editor.event.EditorFactoryEvent; +import com.intellij.openapi.editor.event.EditorFactoryListener; +import com.intellij.openapi.fileEditor.FileEditor; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileEditor.TextEditor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.project.ProjectManagerAdapter; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.InvalidDataException; +import com.intellij.openapi.util.WriteExternalException; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; +import com.intellij.rt.coverage.data.ClassData; +import com.intellij.rt.coverage.data.LineCoverage; +import com.intellij.rt.coverage.data.LineData; +import com.intellij.rt.coverage.data.ProjectData; +import com.intellij.util.Alarm; +import com.intellij.util.ArrayUtil; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.HashMap; +import com.intellij.util.containers.HashSet; +import com.intellij.util.ui.UIUtil; +import org.jdom.Element; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * @author ven + */ +public class CoverageDataManagerImpl extends CoverageDataManager { + private static final String REPLACE_ACTIVE_SUITES = "&Replace active suites"; + private static final String ADD_TO_ACTIVE_SUITES = "&Add to active suites"; + private static final String DO_NOT_APPLY_COLLECTED_COVERAGE = "Do not apply &collected coverage"; + + private final List<CoverageSuiteListener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); + private static final Logger LOG = Logger.getInstance("#" + CoverageDataManagerImpl.class.getName()); + @NonNls + private static final String SUITE = "SUITE"; + + private final Project myProject; + private final Set<CoverageSuite> myCoverageSuites = new HashSet<CoverageSuite>(); + private boolean myIsProjectClosing = false; + + private final Object myLock = new Object(); + private boolean mySubCoverageIsActive; + + public CoverageSuitesBundle getCurrentSuitesBundle() { + return myCurrentSuitesBundle; + } + + private CoverageSuitesBundle myCurrentSuitesBundle; + + private final Object ANNOTATORS_LOCK = new Object(); + private final Map<Editor, SrcFileAnnotator> myAnnotators = new HashMap<Editor, SrcFileAnnotator>(); + + public CoverageDataManagerImpl(final Project project) { + myProject = project; + EditorColorsManager.getInstance().addEditorColorsListener(new EditorColorsAdapter() { + @Override + public void globalSchemeChange(EditorColorsScheme scheme) { + chooseSuitesBundle(myCurrentSuitesBundle); + } + }, project); + addSuiteListener(new CoverageViewSuiteListener(this, myProject), myProject); + } + + + @NotNull @NonNls + public String getComponentName() { + return "CoverageDataManager"; + } + + public void initComponent() { + } + + public void disposeComponent() { + } + + public void readExternal(Element element) throws InvalidDataException { + //noinspection unchecked + for (Element suiteElement : element.getChildren(SUITE)) { + final CoverageRunner coverageRunner = BaseCoverageSuite.readRunnerAttribute(suiteElement); + // skip unknown runners + if (coverageRunner == null) { + // collect gc + final CoverageFileProvider fileProvider = BaseCoverageSuite.readDataFileProviderAttribute(suiteElement); + if (fileProvider.isValid()) { + //deleteCachedCoverage(fileProvider.getCoverageDataFilePath()); + } + continue; + } + + CoverageSuite suite = null; + for (CoverageEngine engine : CoverageEngine.EP_NAME.getExtensions()) { + if (coverageRunner.acceptsCoverageEngine(engine)) { + suite = engine.createEmptyCoverageSuite(coverageRunner); + if (suite != null) { + break; + } + } + } + if (suite != null) { + try { + suite.readExternal(suiteElement); + myCoverageSuites.add(suite); + } + catch (NumberFormatException e) { + //try next suite + } + } + } + } + + public void writeExternal(final Element element) throws WriteExternalException { + for (CoverageSuite coverageSuite : myCoverageSuites) { + final Element suiteElement = new Element(SUITE); + element.addContent(suiteElement); + coverageSuite.writeExternal(suiteElement); + } + } + + public CoverageSuite addCoverageSuite(final String name, final CoverageFileProvider fileProvider, final String[] filters, final long lastCoverageTimeStamp, + @Nullable final String suiteToMergeWith, + final CoverageRunner coverageRunner, + final boolean collectLineInfo, + final boolean tracingEnabled) { + final CoverageSuite suite = createCoverageSuite(coverageRunner, name, fileProvider, filters, lastCoverageTimeStamp, suiteToMergeWith, collectLineInfo, tracingEnabled); + if (suiteToMergeWith == null || !name.equals(suiteToMergeWith)) { + removeCoverageSuite(suite); + } + myCoverageSuites.remove(suite); // remove previous instance + myCoverageSuites.add(suite); // add new instance + return suite; + } + + @Override + public CoverageSuite addExternalCoverageSuite(String selectedFileName, + long timeStamp, + CoverageRunner coverageRunner, + CoverageFileProvider fileProvider) { + final CoverageSuite suite = createCoverageSuite(coverageRunner, selectedFileName, fileProvider, ArrayUtil.EMPTY_STRING_ARRAY, timeStamp, null, false, false); + myCoverageSuites.add(suite); + return suite; + } + + @Override + public CoverageSuite addCoverageSuite(final CoverageEnabledConfiguration config) { + final String name = config.getName() + " Coverage Results"; + final String covFilePath = config.getCoverageFilePath(); + assert covFilePath != null; // Shouldn't be null here! + + final CoverageRunner coverageRunner = config.getCoverageRunner(); + LOG.assertTrue(coverageRunner != null, "Coverage runner id = " + config.getRunnerId()); + + final DefaultCoverageFileProvider fileProvider = new DefaultCoverageFileProvider(new File(covFilePath)); + final CoverageSuite suite = createCoverageSuite(config, name, coverageRunner, fileProvider); + + // remove previous instance + removeCoverageSuite(suite); + + // add new instance + myCoverageSuites.add(suite); + return suite; + } + + public void removeCoverageSuite(final CoverageSuite suite) { + final String fileName = suite.getCoverageDataFileName(); + + boolean deleteTraces = suite.isTracingEnabled(); + if (!FileUtil.isAncestor(PathManager.getSystemPath(), fileName, false)) { + String message = "Would you like to delete file \'" + fileName + "\' "; + if (deleteTraces) { + message += "and traces directory \'" + FileUtil.getNameWithoutExtension(new File(fileName)) + "\' "; + } + message += "on disk?"; + if (Messages.showYesNoDialog(myProject, message, CommonBundle.getWarningTitle(), Messages.getWarningIcon()) == Messages.YES) { + deleteCachedCoverage(fileName, deleteTraces); + } + } else { + deleteCachedCoverage(fileName, deleteTraces); + } + + myCoverageSuites.remove(suite); + if (myCurrentSuitesBundle != null && myCurrentSuitesBundle.contains(suite)) { + CoverageSuite[] suites = myCurrentSuitesBundle.getSuites(); + suites = ArrayUtil.remove(suites, suite); + chooseSuitesBundle(suites.length > 0 ? new CoverageSuitesBundle(suites) : null); + } + } + + private void deleteCachedCoverage(String coverageDataFileName, boolean deleteTraces) { + FileUtil.delete(new File(coverageDataFileName)); + if (deleteTraces) { + FileUtil.delete(getTracesDirectory(coverageDataFileName)); + } + } + + public CoverageSuite[] getSuites() { + return myCoverageSuites.toArray(new CoverageSuite[myCoverageSuites.size()]); + } + + public void chooseSuitesBundle(final CoverageSuitesBundle suite) { + if (myCurrentSuitesBundle == suite && suite == null) { + return; + } + + LOG.assertTrue(!myProject.isDefault()); + + fireBeforeSuiteChosen(); + + mySubCoverageIsActive = false; + if (myCurrentSuitesBundle != null) { + myCurrentSuitesBundle.getCoverageEngine().getCoverageAnnotator(myProject).onSuiteChosen(suite); + } + + myCurrentSuitesBundle = suite; + disposeAnnotators(); + + if (suite == null) { + triggerPresentationUpdate(); + return; + } + + for (CoverageSuite coverageSuite : myCurrentSuitesBundle.getSuites()) { + final boolean suiteFileExists = coverageSuite.getCoverageDataFileProvider().ensureFileExists(); + if (!suiteFileExists) { + chooseSuitesBundle(null); + return; + } + } + + renewCoverageData(suite); + + fireAfterSuiteChosen(); + } + + public void coverageGathered(@NotNull final CoverageSuite suite) { + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + if (myProject.isDisposed()) return; + if (myCurrentSuitesBundle != null) { + final String message = CodeInsightBundle.message("display.coverage.prompt", suite.getPresentableName()); + + final CoverageOptionsProvider coverageOptionsProvider = CoverageOptionsProvider.getInstance(myProject); + final DialogWrapper.DoNotAskOption doNotAskOption = new DialogWrapper.DoNotAskOption() { + @Override + public boolean isToBeShown() { + return coverageOptionsProvider.getOptionToReplace() == 3; + } + + @Override + public void setToBeShown(boolean value, int exitCode) { + coverageOptionsProvider.setOptionsToReplace(value ? 3 : exitCode); + } + + @Override + public boolean canBeHidden() { + return true; + } + + @Override + public boolean shouldSaveOptionsOnCancel() { + return true; + } + + @NotNull + @Override + public String getDoNotShowMessage() { + return CommonBundle.message("dialog.options.do.not.show"); + } + }; + final String[] options = myCurrentSuitesBundle.getCoverageEngine() == suite.getCoverageEngine() ? + new String[] {REPLACE_ACTIVE_SUITES, ADD_TO_ACTIVE_SUITES, DO_NOT_APPLY_COLLECTED_COVERAGE} : + new String[] {REPLACE_ACTIVE_SUITES, DO_NOT_APPLY_COLLECTED_COVERAGE}; + final int answer = doNotAskOption.isToBeShown() ? Messages.showDialog(message, CodeInsightBundle.message("code.coverage"), + options, 1, Messages.getQuestionIcon(), + doNotAskOption) : coverageOptionsProvider.getOptionToReplace(); + if (answer == DialogWrapper.OK_EXIT_CODE) { + chooseSuitesBundle(new CoverageSuitesBundle(suite)); + } + else if (answer == 1) { + chooseSuitesBundle(new CoverageSuitesBundle(ArrayUtil.append(myCurrentSuitesBundle.getSuites(), suite))); + } + } + else { + chooseSuitesBundle(new CoverageSuitesBundle(suite)); + } + } + }); + } + + public void triggerPresentationUpdate() { + renewInformationInEditors(); + UIUtil.invokeLaterIfNeeded(new Runnable() { + public void run() { + if (myProject.isDisposed()) return; + ProjectView.getInstance(myProject).refresh(); + CoverageViewManager.getInstance(myProject).setReady(true); + } + }); + } + + public void attachToProcess(@NotNull final ProcessHandler handler, + @NotNull final RunConfigurationBase configuration, + final RunnerSettings runnerSettings) { + handler.addProcessListener(new ProcessAdapter() { + public void processTerminated(final ProcessEvent event) { + processGatheredCoverage(configuration, runnerSettings); + } + }); + } + + @Override + public void processGatheredCoverage(@NotNull RunConfigurationBase configuration, RunnerSettings runnerSettings) { + if (runnerSettings instanceof CoverageRunnerData) { + processGatheredCoverage(configuration); + } + } + + public static void processGatheredCoverage(RunConfigurationBase configuration) { + final Project project = configuration.getProject(); + if (project.isDisposed()) return; + final CoverageDataManager coverageDataManager = CoverageDataManager.getInstance(project); + final CoverageEnabledConfiguration coverageEnabledConfiguration = CoverageEnabledConfiguration.getOrCreate(configuration); + //noinspection ConstantConditions + final CoverageSuite coverageSuite = coverageEnabledConfiguration.getCurrentCoverageSuite(); + if (coverageSuite != null) { + ((BaseCoverageSuite)coverageSuite).setConfiguration(configuration); + coverageDataManager.coverageGathered(coverageSuite); + } + } + + protected void renewCoverageData(@NotNull final CoverageSuitesBundle suite) { + if (myCurrentSuitesBundle != null) { + myCurrentSuitesBundle.getCoverageEngine().getCoverageAnnotator(myProject).renewCoverageData(suite, this); + } + } + + private void renewInformationInEditors() { + final FileEditorManager fileEditorManager = FileEditorManager.getInstance(myProject); + final VirtualFile[] openFiles = fileEditorManager.getOpenFiles(); + for (VirtualFile openFile : openFiles) { + final FileEditor[] allEditors = fileEditorManager.getAllEditors(openFile); + applyInformationToEditor(allEditors, openFile); + } + } + + private void applyInformationToEditor(FileEditor[] editors, final VirtualFile file) { + final PsiFile psiFile = doInReadActionIfProjectOpen(new Computable<PsiFile>() { + @Nullable + @Override + public PsiFile compute() { + return PsiManager.getInstance(myProject).findFile(file); + } + }); + if (psiFile != null && myCurrentSuitesBundle != null && psiFile.isPhysical()) { + final CoverageEngine engine = myCurrentSuitesBundle.getCoverageEngine(); + if (!engine.coverageEditorHighlightingApplicableTo(psiFile)) { + return; + } + + for (FileEditor editor : editors) { + if (editor instanceof TextEditor) { + final Editor textEditor = ((TextEditor)editor).getEditor(); + SrcFileAnnotator annotator; + synchronized (ANNOTATORS_LOCK) { + annotator = myAnnotators.remove(textEditor); + } + if (annotator != null) { + Disposer.dispose(annotator); + } + break; + } + } + + for (FileEditor editor : editors) { + if (editor instanceof TextEditor) { + final Editor textEditor = ((TextEditor)editor).getEditor(); + SrcFileAnnotator annotator = getAnnotator(textEditor); + if (annotator == null) { + annotator = new SrcFileAnnotator(psiFile, textEditor); + synchronized (ANNOTATORS_LOCK) { + myAnnotators.put(textEditor, annotator); + } + } + + if (myCurrentSuitesBundle != null && engine.acceptedByFilters(psiFile, myCurrentSuitesBundle)) { + annotator.showCoverageInformation(myCurrentSuitesBundle); + } + } + } + } + } + + public void projectOpened() { + EditorFactory.getInstance().addEditorFactoryListener(new CoverageEditorFactoryListener(), myProject); + ProjectManagerAdapter projectManagerListener = new ProjectManagerAdapter() { + public void projectClosing(Project project) { + synchronized (myLock) { + myIsProjectClosing = true; + } + } + }; + ProjectManager.getInstance().addProjectManagerListener(myProject, projectManagerListener); + } + + public void projectClosed() { + } + + public <T> T doInReadActionIfProjectOpen(Computable<T> computation) { + synchronized(myLock) { + if (myIsProjectClosing) return null; + } + return ApplicationManager.getApplication().runReadAction(computation); + } + + public void selectSubCoverage(@NotNull final CoverageSuitesBundle suite, final List<String> testNames) { + suite.restoreCoverageData(); + final ProjectData data = suite.getCoverageData(); + if (data == null) return; + mySubCoverageIsActive = true; + final Map<String, Set<Integer>> executionTrace = new HashMap<String, Set<Integer>>(); + for (CoverageSuite coverageSuite : suite.getSuites()) { + final String fileName = coverageSuite.getCoverageDataFileName(); + final File tracesDir = getTracesDirectory(fileName); + for (String testName : testNames) { + final File file = new File(tracesDir, FileUtil.sanitizeFileName(testName) + ".tr"); + if (file.exists()) { + DataInputStream in = null; + try { + in = new DataInputStream(new FileInputStream(file)); + int traceSize = in.readInt(); + for (int i = 0; i < traceSize; i++) { + final String className = in.readUTF(); + final int linesSize = in.readInt(); + Set<Integer> lines = executionTrace.get(className); + if (lines == null) { + lines = new HashSet<Integer>(); + executionTrace.put(className, lines); + } + for(int l = 0; l < linesSize; l++) { + lines.add(in.readInt()); + } + } + } + catch (Exception e) { + LOG.error(e); + } + finally { + try { + in.close(); + } + catch (IOException e) { + LOG.error(e); + } + } + } + } + } + final ProjectData projectData = new ProjectData(); + for (String className : executionTrace.keySet()) { + ClassData loadedClassData = projectData.getClassData(className); + if (loadedClassData == null) { + loadedClassData = projectData.getOrCreateClassData(className); + } + final Set<Integer> lineNumbers = executionTrace.get(className); + final ClassData oldData = data.getClassData(className); + LOG.assertTrue(oldData != null, "missed className: \"" + className + "\""); + final Object[] oldLines = oldData.getLines(); + LOG.assertTrue(oldLines != null); + int maxNumber = oldLines.length; + for (Integer lineNumber : lineNumbers) { + if (lineNumber >= maxNumber) { + maxNumber = lineNumber + 1; + } + } + final LineData[] lines = new LineData[maxNumber]; + for (Integer line : lineNumbers) { + final int lineIdx = line.intValue() - 1; + String methodSig = null; + if (lineIdx < oldData.getLines().length) { + final LineData oldLineData = oldData.getLineData(lineIdx); + if (oldLineData != null) { + methodSig = oldLineData.getMethodSignature(); + } + } + final LineData lineData = new LineData(lineIdx, methodSig); + if (methodSig != null) { + loadedClassData.registerMethodSignature(lineData); + } + lineData.setStatus(LineCoverage.FULL); + lines[lineIdx] = lineData; + } + loadedClassData.setLines(lines); + } + suite.setCoverageData(projectData); + renewCoverageData(suite); + } + + private File getTracesDirectory(final String fileName) { + return new File(new File(fileName).getParentFile(), FileUtil.getNameWithoutExtension(new File(fileName))); + } + + public void restoreMergedCoverage(@NotNull final CoverageSuitesBundle suite) { + mySubCoverageIsActive = false; + suite.restoreCoverageData(); + renewCoverageData(suite); + } + + @Override + public void addSuiteListener(final CoverageSuiteListener listener, Disposable parentDisposable) { + myListeners.add(listener); + Disposer.register(parentDisposable, new Disposable() { + public void dispose() { + myListeners.remove(listener); + } + }); + } + + public void fireBeforeSuiteChosen() { + for (CoverageSuiteListener listener : myListeners) { + listener.beforeSuiteChosen(); + } + } + + public void fireAfterSuiteChosen() { + for (CoverageSuiteListener listener : myListeners) { + listener.afterSuiteChosen(); + } + } + + public boolean isSubCoverageActive() { + return mySubCoverageIsActive; + } + + @Nullable + public SrcFileAnnotator getAnnotator(Editor editor) { + synchronized (ANNOTATORS_LOCK) { + return myAnnotators.get(editor); + } + } + + public void disposeAnnotators() { + synchronized (ANNOTATORS_LOCK) { + for (SrcFileAnnotator annotator : myAnnotators.values()) { + if (annotator != null) { + Disposer.dispose(annotator); + } + } + myAnnotators.clear(); + } + } + + @NotNull + private CoverageSuite createCoverageSuite(final CoverageEnabledConfiguration config, + final String name, + final CoverageRunner coverageRunner, + final DefaultCoverageFileProvider fileProvider) { + CoverageSuite suite = null; + for (CoverageEngine engine : CoverageEngine.EP_NAME.getExtensions()) { + if (coverageRunner.acceptsCoverageEngine(engine) && engine.isApplicableTo(config.getConfiguration())) { + suite = engine.createCoverageSuite(coverageRunner, name, fileProvider, config); + if (suite != null) { + break; + } + } + } + LOG.assertTrue(suite != null, "Cannot create coverage suite for runner: " + coverageRunner.getPresentableName()); + return suite; + } + + @NotNull + private CoverageSuite createCoverageSuite(final CoverageRunner coverageRunner, + final String name, + final CoverageFileProvider fileProvider, + final String[] filters, + final long lastCoverageTimeStamp, + final String suiteToMergeWith, + final boolean collectLineInfo, + final boolean tracingEnabled) { + + CoverageSuite suite = null; + for (CoverageEngine engine : CoverageEngine.EP_NAME.getExtensions()) { + if (coverageRunner.acceptsCoverageEngine(engine)) { + suite = engine.createCoverageSuite(coverageRunner, name, fileProvider, filters, lastCoverageTimeStamp, + suiteToMergeWith, collectLineInfo, tracingEnabled, false, myProject); + if (suite != null) { + break; + } + } + } + + LOG.assertTrue(suite != null, "Cannot create coverage suite for runner: " + coverageRunner.getPresentableName()); + return suite; + } + + private class CoverageEditorFactoryListener implements EditorFactoryListener { + private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.OWN_THREAD, myProject); + private final Map<Editor, Runnable> myCurrentEditors = new HashMap<Editor, Runnable>(); + + public void editorCreated(@NotNull EditorFactoryEvent event) { + synchronized (myLock) { + if (myIsProjectClosing) return; + } + + final Editor editor = event.getEditor(); + if (editor.getProject() != myProject) return; + final PsiFile psiFile = ApplicationManager.getApplication().runReadAction(new Computable<PsiFile>() { + @Nullable + @Override + public PsiFile compute() { + if (myProject.isDisposed()) return null; + final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject); + final Document document = editor.getDocument(); + return documentManager.getPsiFile(document); + } + }); + + if (psiFile != null && myCurrentSuitesBundle != null && psiFile.isPhysical()) { + final CoverageEngine engine = myCurrentSuitesBundle.getCoverageEngine(); + if (!engine.coverageEditorHighlightingApplicableTo(psiFile)) { + return; + } + + SrcFileAnnotator annotator = getAnnotator(editor); + if (annotator == null) { + annotator = new SrcFileAnnotator(psiFile, editor); + } + + final SrcFileAnnotator finalAnnotator = annotator; + + synchronized (ANNOTATORS_LOCK) { + myAnnotators.put(editor, finalAnnotator); + } + + final Runnable request = new Runnable() { + @Override + public void run() { + if (myProject.isDisposed()) return; + if (myCurrentSuitesBundle != null) { + if (engine.acceptedByFilters(psiFile, myCurrentSuitesBundle)) { + finalAnnotator.showCoverageInformation(myCurrentSuitesBundle); + } + } + } + }; + myCurrentEditors.put(editor, request); + myAlarm.addRequest(request, 100); + } + } + + public void editorReleased(@NotNull EditorFactoryEvent event) { + final Editor editor = event.getEditor(); + if (editor.getProject() != myProject) return; + try { + final SrcFileAnnotator fileAnnotator; + synchronized (ANNOTATORS_LOCK) { + fileAnnotator = myAnnotators.remove(editor); + } + if (fileAnnotator != null) { + Disposer.dispose(fileAnnotator); + } + } + finally { + final Runnable request = myCurrentEditors.remove(editor); + if (request != null) { + myAlarm.cancelRequest(request); + } + } + } + } +} |