try to reuse coverage infrastructure:
[idea/community.git] / plugins / coverage-common / src / com / intellij / coverage / SrcFileAnnotator.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.intellij.coverage;
18
19 import com.intellij.codeInsight.CodeInsightBundle;
20 import com.intellij.history.FileRevisionTimestampComparator;
21 import com.intellij.history.LocalHistory;
22 import com.intellij.icons.AllIcons;
23 import com.intellij.openapi.Disposable;
24 import com.intellij.openapi.application.ApplicationManager;
25 import com.intellij.openapi.diagnostic.Logger;
26 import com.intellij.openapi.editor.Document;
27 import com.intellij.openapi.editor.Editor;
28 import com.intellij.openapi.editor.colors.EditorColorsManager;
29 import com.intellij.openapi.editor.colors.EditorColorsScheme;
30 import com.intellij.openapi.editor.event.DocumentAdapter;
31 import com.intellij.openapi.editor.event.DocumentEvent;
32 import com.intellij.openapi.editor.event.DocumentListener;
33 import com.intellij.openapi.editor.impl.DocumentMarkupModel;
34 import com.intellij.openapi.editor.markup.*;
35 import com.intellij.openapi.fileEditor.FileEditor;
36 import com.intellij.openapi.fileEditor.FileEditorManager;
37 import com.intellij.openapi.fileEditor.TextEditor;
38 import com.intellij.openapi.fileEditor.impl.LoadTextUtil;
39 import com.intellij.openapi.module.Module;
40 import com.intellij.openapi.module.ModuleUtilCore;
41 import com.intellij.openapi.project.Project;
42 import com.intellij.openapi.roots.ProjectFileIndex;
43 import com.intellij.openapi.roots.ProjectRootManager;
44 import com.intellij.openapi.util.Computable;
45 import com.intellij.openapi.util.Key;
46 import com.intellij.openapi.util.Ref;
47 import com.intellij.openapi.util.TextRange;
48 import com.intellij.openapi.util.text.LineTokenizer;
49 import com.intellij.openapi.vcs.AbstractVcs;
50 import com.intellij.openapi.vcs.FilePath;
51 import com.intellij.openapi.vcs.actions.VcsContextFactory;
52 import com.intellij.openapi.vcs.history.VcsFileRevision;
53 import com.intellij.openapi.vcs.history.VcsHistoryProvider;
54 import com.intellij.openapi.vcs.history.VcsHistorySession;
55 import com.intellij.openapi.vfs.VirtualFile;
56 import com.intellij.psi.PsiFile;
57 import com.intellij.reference.SoftReference;
58 import com.intellij.rt.coverage.data.ClassData;
59 import com.intellij.rt.coverage.data.LineCoverage;
60 import com.intellij.rt.coverage.data.LineData;
61 import com.intellij.rt.coverage.data.ProjectData;
62 import com.intellij.ui.EditorNotificationPanel;
63 import com.intellij.util.Alarm;
64 import com.intellij.util.Function;
65 import com.intellij.util.diff.Diff;
66 import com.intellij.util.diff.FilesTooBigForDiffException;
67 import com.intellij.vcsUtil.VcsUtil;
68 import gnu.trove.TIntIntHashMap;
69 import org.jetbrains.annotations.NotNull;
70 import org.jetbrains.annotations.Nullable;
71
72 import java.io.File;
73 import java.util.*;
74
75 /**
76  * @author ven
77  */
78 public class SrcFileAnnotator implements Disposable {
79   private static final Logger LOG = Logger.getInstance("#com.intellij.coverage.SrcFileAnnotator");
80   public static final Key<List<RangeHighlighter>> COVERAGE_HIGHLIGHTERS = Key.create("COVERAGE_HIGHLIGHTERS");
81   private static final Key<DocumentListener> COVERAGE_DOCUMENT_LISTENER = Key.create("COVERAGE_DOCUMENT_LISTENER");
82   public static final Key<Map<FileEditor, EditorNotificationPanel>> NOTIFICATION_PANELS = Key.create("NOTIFICATION_PANELS");
83
84   private PsiFile myFile;
85   private Editor myEditor;
86   private Document myDocument;
87   private final Project myProject;
88
89   private SoftReference<TIntIntHashMap> myNewToOldLines;
90   private SoftReference<TIntIntHashMap> myOldToNewLines;
91   private SoftReference<byte[]> myOldContent;
92   private final static Object LOCK = new Object();
93   
94   private final Alarm myUpdateAlarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, this);
95
96   public SrcFileAnnotator(final PsiFile file, final Editor editor) {
97     myFile = file;
98     myEditor = editor;
99     myProject = file.getProject();
100     myDocument = myEditor.getDocument();
101   }
102
103   
104   public void hideCoverageData() {
105     Editor editor = myEditor;
106     PsiFile file = myFile;
107     Document document = myDocument;
108     if (editor == null || editor.isDisposed() || file == null || document == null) return;
109     final FileEditorManager fileEditorManager = FileEditorManager.getInstance(myProject);
110     final List<RangeHighlighter> highlighters = editor.getUserData(COVERAGE_HIGHLIGHTERS);
111     if (highlighters != null) {
112       for (final RangeHighlighter highlighter : highlighters) {
113         ApplicationManager.getApplication().invokeLater(() -> highlighter.dispose());
114       }
115       editor.putUserData(COVERAGE_HIGHLIGHTERS, null);
116     }
117
118     final Map<FileEditor, EditorNotificationPanel> map = file.getCopyableUserData(NOTIFICATION_PANELS);
119     if (map != null) {
120       final VirtualFile vFile = getVirtualFile(file);
121       boolean freeAll = !fileEditorManager.isFileOpen(vFile);
122       file.putCopyableUserData(NOTIFICATION_PANELS, null);
123       for (FileEditor fileEditor : map.keySet()) {
124         if (!freeAll && !isCurrentEditor(fileEditor)) {
125           continue;
126         }
127         fileEditorManager.removeTopComponent(fileEditor, map.get(fileEditor));
128       }
129     }
130
131
132     final DocumentListener documentListener = editor.getUserData(COVERAGE_DOCUMENT_LISTENER);
133     if (documentListener != null) {
134       document.removeDocumentListener(documentListener);
135       editor.putUserData(COVERAGE_DOCUMENT_LISTENER, null);
136     }
137   }
138
139   @NotNull
140   private static String[] getCoveredLines(@NotNull byte[] oldContent, VirtualFile vFile) {
141     final String text = LoadTextUtil.getTextByBinaryPresentation(oldContent, vFile, false, false).toString();
142     return LineTokenizer.tokenize(text, false);
143   }
144
145   @NotNull private static String[] getUpToDateLines(final Document document) {
146     final Ref<String[]> linesRef = new Ref<>();
147     final Runnable runnable = () -> {
148       final int lineCount = document.getLineCount();
149       final String[] lines = new String[lineCount];
150       final CharSequence chars = document.getCharsSequence();
151       for (int i = 0; i < lineCount; i++) {
152         lines[i] = chars.subSequence(document.getLineStartOffset(i), document.getLineEndOffset(i)).toString();
153       }
154       linesRef.set(lines);
155     };
156     ApplicationManager.getApplication().runReadAction(runnable);
157
158     return linesRef.get();
159   }
160
161   private static TIntIntHashMap getCoverageVersionToCurrentLineMapping(Diff.Change change, int firstNLines) {
162     TIntIntHashMap result = new TIntIntHashMap();
163     int prevLineInFirst = 0;
164     int prevLineInSecond = 0;
165     while (change != null) {
166
167       for (int l = 0; l < change.line0 - prevLineInFirst; l++) {
168         result.put(prevLineInFirst + l, prevLineInSecond + l);
169       }
170
171       prevLineInFirst = change.line0 + change.deleted;
172       prevLineInSecond = change.line1 + change.inserted;
173
174       change = change.link;
175     }
176
177     for (int i = prevLineInFirst; i < firstNLines; i++) {
178       result.put(i, prevLineInSecond + i - prevLineInFirst);
179     }
180
181     return result;
182   }
183
184   @Nullable
185   private TIntIntHashMap getOldToNewLineMapping(final long date, MyEditorBean editorBean) {
186     if (myOldToNewLines == null) {
187       myOldToNewLines = doGetLineMapping(date, true, editorBean);
188       if (myOldToNewLines == null) return null;
189     }
190     return myOldToNewLines.get();
191   }
192
193   @Nullable
194   private TIntIntHashMap getNewToOldLineMapping(final long date, MyEditorBean editorBean) {
195     if (myNewToOldLines == null) {
196       myNewToOldLines = doGetLineMapping(date, false, editorBean);
197       if (myNewToOldLines == null) return null;
198     }
199     return myNewToOldLines.get();
200   }
201
202   @Nullable
203   private SoftReference<TIntIntHashMap> doGetLineMapping(final long date, boolean oldToNew, MyEditorBean editorBean) {
204     VirtualFile virtualFile = editorBean.getVFile();
205     final byte[] oldContent;
206     synchronized (LOCK) {
207       if (myOldContent == null) {
208         if (ApplicationManager.getApplication().isDispatchThread()) return null;
209         final LocalHistory localHistory = LocalHistory.getInstance();
210         byte[] byteContent = localHistory.getByteContent(virtualFile, new FileRevisionTimestampComparator() {
211           public boolean isSuitable(long revisionTimestamp) {
212             return revisionTimestamp < date;
213           }
214         });
215
216         if (byteContent == null && virtualFile.getTimeStamp() > date) {
217           byteContent = loadFromVersionControl(date, virtualFile);
218         } 
219         myOldContent = new SoftReference<>(byteContent);
220       }
221       oldContent = myOldContent.get();
222     }
223
224     if (oldContent == null) return null;
225     String[] coveredLines = getCoveredLines(oldContent, virtualFile);
226     final Document document = editorBean.getDocument();
227     if (document == null) return null;
228     String[] currentLines = getUpToDateLines(document);
229
230     String[] oldLines = oldToNew ? coveredLines : currentLines;
231     String[] newLines = oldToNew ? currentLines : coveredLines;
232
233     Diff.Change change;
234     try {
235       change = Diff.buildChanges(oldLines, newLines);
236     }
237     catch (FilesTooBigForDiffException e) {
238       LOG.info(e);
239       return null;
240     }
241     return new SoftReference<>(getCoverageVersionToCurrentLineMapping(change, oldLines.length));
242   }
243
244   @Nullable
245   private byte[] loadFromVersionControl(long date, VirtualFile f) {
246     try {
247       final AbstractVcs vcs = VcsUtil.getVcsFor(myProject, f);
248       if (vcs == null) return null;
249
250       final VcsHistoryProvider historyProvider = vcs.getVcsHistoryProvider();
251       if (historyProvider == null) return null;
252
253       final FilePath filePath = VcsContextFactory.SERVICE.getInstance().createFilePathOn(f);
254       final VcsHistorySession session = historyProvider.createSessionFor(filePath);
255       if (session == null) return null;
256
257       final List<VcsFileRevision> list = session.getRevisionList();
258
259       if (list != null) {
260         for (VcsFileRevision revision : list) {
261           final Date revisionDate = revision.getRevisionDate();
262           if (revisionDate == null) {
263             return null;
264           }
265
266           if (revisionDate.getTime() < date) {
267             return revision.loadContent();
268           }
269         }
270       }
271     }
272     catch (Exception e) {
273       LOG.info(e);
274       return null;
275     }
276     return null;
277   }
278
279   public void showCoverageInformation(final CoverageSuitesBundle suite) {
280     // Store the values of myFile and myEditor in local variables to avoid an NPE after dispose() has been called in the EDT.
281     final PsiFile psiFile = myFile;
282     final Editor editor = myEditor;
283     final Document document = myDocument;
284     if (editor == null || psiFile == null || document == null) return;
285     final VirtualFile file = getVirtualFile(psiFile);
286     final MyEditorBean editorBean = new MyEditorBean(editor, file, document);
287     final MarkupModel markupModel = DocumentMarkupModel.forDocument(document, myProject, true);
288     final List<RangeHighlighter> highlighters = new ArrayList<>();
289     final ProjectData data = suite.getCoverageData();
290     if (data == null) {
291       coverageDataNotFound(suite);
292       return;
293     }
294     final CoverageEngine engine = suite.getCoverageEngine();
295     final Set<String> qualifiedNames = engine.getQualifiedNames(psiFile);
296
297     // let's find old content in local history and build mapping from old lines to new one
298     // local history doesn't index libraries, so let's distinguish libraries content with other one
299     final ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(myProject).getFileIndex();
300     final long fileTimeStamp = file.getTimeStamp();
301     final long coverageTimeStamp = suite.getLastCoverageTimeStamp();
302     final TIntIntHashMap oldToNewLineMapping;
303
304     //do not show coverage info over cls
305     if (engine.isInLibraryClasses(myProject, file)) {
306       return;
307     }
308     // if in libraries content
309     if (projectFileIndex.isInLibrarySource(file)) {
310       // compare file and coverage timestamps
311       if (fileTimeStamp > coverageTimeStamp) {
312         showEditorWarningMessage(CodeInsightBundle.message("coverage.data.outdated"));
313         return;
314       }
315       oldToNewLineMapping = null;
316     }
317     else {
318       // check local history
319       oldToNewLineMapping = getOldToNewLineMapping(coverageTimeStamp, editorBean);
320       if (oldToNewLineMapping == null) {
321
322         // if history for file isn't available let's check timestamps
323         if (fileTimeStamp > coverageTimeStamp && classesArePresentInCoverageData(data, qualifiedNames)) {
324           showEditorWarningMessage(CodeInsightBundle.message("coverage.data.outdated"));
325           return;
326         }
327       }
328     }
329
330     if (editor.getUserData(COVERAGE_HIGHLIGHTERS) != null) {
331       //highlighters already collected - no need to do it twice
332       return;
333     }
334
335     final Module module = ApplicationManager.getApplication().runReadAction(new Computable<Module>() {
336       @Nullable
337       @Override
338       public Module compute() {
339         return ModuleUtilCore.findModuleForPsiElement(psiFile);
340       }
341     });
342     if (module != null) {
343       if (engine.recompileProjectAndRerunAction(module, suite, () -> CoverageDataManager.getInstance(myProject).chooseSuitesBundle(suite))) {
344         return;
345       }
346     }
347
348     // now if oldToNewLineMapping is null we should use f(x)=id(x) mapping
349
350     // E.g. all *.class files for java source file with several classes
351     final Set<File> outputFiles = engine.getCorrespondingOutputFiles(psiFile, module, suite);
352
353     final boolean subCoverageActive = CoverageDataManager.getInstance(myProject).isSubCoverageActive();
354     final boolean coverageByTestApplicable = suite.isCoverageByTestApplicable() && !(subCoverageActive && suite.isCoverageByTestEnabled());
355     final TreeMap<Integer, LineData> executableLines = new TreeMap<>();
356     final TreeMap<Integer, Object[]> classLines = new TreeMap<>();
357     final TreeMap<Integer, String> classNames = new TreeMap<>();
358     class HighlightersCollector {
359       private void collect(File outputFile, final String qualifiedName) {
360         final ClassData fileData = data.getClassData(qualifiedName);
361         if (fileData != null) {
362           final Object[] lines = fileData.getLines();
363           if (lines != null) {
364             final Object[] postProcessedLines = suite.getCoverageEngine().postProcessExecutableLines(lines, editor);
365             for (Object lineData : postProcessedLines) {
366               if (lineData instanceof LineData) {
367                 final int line = ((LineData)lineData).getLineNumber() - 1;
368                 final int lineNumberInCurrent;
369                 if (oldToNewLineMapping != null) {
370                   // use mapping based on local history
371                   if (!oldToNewLineMapping.contains(line)) {
372                     continue;
373                   }
374                   lineNumberInCurrent = oldToNewLineMapping.get(line);
375                 }
376                 else {
377                   // use id mapping
378                   lineNumberInCurrent = line;
379                 }
380                 LOG.assertTrue(lineNumberInCurrent < document.getLineCount());
381                 executableLines.put(line, (LineData)lineData);
382   
383                 classLines.put(line, postProcessedLines);
384                 classNames.put(line, qualifiedName);
385   
386                 ApplicationManager.getApplication().invokeLater(() -> {
387                   if (lineNumberInCurrent >= document.getLineCount()) return;
388                   if (editorBean.isDisposed()) return;
389                   final RangeHighlighter highlighter =
390                     createRangeHighlighter(suite.getLastCoverageTimeStamp(), markupModel, coverageByTestApplicable, executableLines,
391                                            qualifiedName, line, lineNumberInCurrent, suite, postProcessedLines, editorBean);
392                   highlighters.add(highlighter);
393                 });
394               }
395             }
396           }
397         }
398         else if (outputFile != null &&
399                  !subCoverageActive &&
400                  engine.includeUntouchedFileInCoverage(qualifiedName, outputFile, psiFile, suite)) {
401           collectNonCoveredFileInfo(outputFile, highlighters, markupModel, executableLines, coverageByTestApplicable, editorBean);
402         }
403       }
404     }
405
406     final HighlightersCollector collector = new HighlightersCollector();
407     if (!outputFiles.isEmpty()) {
408       for (File outputFile : outputFiles) {
409         final String qualifiedName = engine.getQualifiedName(outputFile, psiFile);
410         if (qualifiedName != null) {
411           collector.collect(outputFile, qualifiedName);
412         }
413       }
414     }
415     else { //check non-compilable classes which present in ProjectData
416       for (String qName : qualifiedNames) {
417         collector.collect(null, qName);
418       }
419     }
420     ApplicationManager.getApplication().invokeLater(() -> {
421       if (!editorBean.isDisposed() && highlighters.size() > 0) {
422         editor.putUserData(COVERAGE_HIGHLIGHTERS, highlighters);
423       }
424     });
425
426     final DocumentListener documentListener = new DocumentAdapter() {
427       @Override
428       public void documentChanged(final DocumentEvent e) {
429         myNewToOldLines = null;
430         myOldToNewLines = null;
431         List<RangeHighlighter> rangeHighlighters = editor.getUserData(COVERAGE_HIGHLIGHTERS);
432         if (rangeHighlighters == null) rangeHighlighters = new ArrayList<>();
433         int offset = e.getOffset();
434         final int lineNumber = document.getLineNumber(offset);
435         final int lastLineNumber = document.getLineNumber(offset + e.getNewLength());
436         final TextRange changeRange =
437           new TextRange(document.getLineStartOffset(lineNumber), document.getLineEndOffset(lastLineNumber));
438         for (Iterator<RangeHighlighter> it = rangeHighlighters.iterator(); it.hasNext(); ) {
439           final RangeHighlighter highlighter = it.next();
440           if (!highlighter.isValid() || TextRange.create(highlighter).intersects(changeRange)) {
441             highlighter.dispose();
442             it.remove();
443           }
444         }
445         final List<RangeHighlighter> highlighters = rangeHighlighters;
446         myUpdateAlarm.cancelAllRequests();
447         if (!myUpdateAlarm.isDisposed()) {
448           myUpdateAlarm.addRequest(() -> {
449             final TIntIntHashMap newToOldLineMapping = getNewToOldLineMapping(suite.getLastCoverageTimeStamp(), editorBean);
450             if (newToOldLineMapping != null) {
451               ApplicationManager.getApplication().invokeLater(() -> {
452                 if (editorBean.isDisposed()) return;
453                 for (int line = lineNumber; line <= lastLineNumber; line++) {
454                   final int oldLineNumber = newToOldLineMapping.get(line);
455                   final LineData lineData = executableLines.get(oldLineNumber);
456                   if (lineData != null) {
457                     RangeHighlighter rangeHighlighter =
458                       createRangeHighlighter(suite.getLastCoverageTimeStamp(), markupModel, coverageByTestApplicable, executableLines,
459                                              classNames.get(oldLineNumber), oldLineNumber, line, suite,
460                                              classLines.get(oldLineNumber), editorBean);
461                     highlighters.add(rangeHighlighter);
462                   }
463                 }
464                 editor.putUserData(COVERAGE_HIGHLIGHTERS, highlighters.size() > 0 ? highlighters : null);
465               });
466             }
467           }, 100);
468         }
469       }
470     };
471     document.addDocumentListener(documentListener);
472     editor.putUserData(COVERAGE_DOCUMENT_LISTENER, documentListener);
473   }
474
475   private static boolean classesArePresentInCoverageData(ProjectData data, Set<String> qualifiedNames) {
476     for (String qualifiedName : qualifiedNames) {
477       if (data.getClassData(qualifiedName) != null) {
478         return true;
479       }
480     }
481     return false;
482   }
483
484   private RangeHighlighter createRangeHighlighter(final long date, final MarkupModel markupModel,
485                                                   final boolean coverageByTestApplicable,
486                                                   final TreeMap<Integer, LineData> executableLines, @Nullable final String className,
487                                                   final int line,
488                                                   final int lineNumberInCurrent,
489                                                   @NotNull final CoverageSuitesBundle coverageSuite, Object[] lines,
490                                                   @NotNull MyEditorBean editorBean) {
491     EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme();
492     final TextAttributes attributes = scheme.getAttributes(CoverageLineMarkerRenderer.getAttributesKey(line, executableLines));
493     TextAttributes textAttributes = null;
494     if (attributes.getBackgroundColor() != null) {
495       textAttributes = attributes;
496     }
497     Document document = editorBean.getDocument();
498     Editor editor = editorBean.getEditor();
499     final int startOffset = document.getLineStartOffset(lineNumberInCurrent);
500     final int endOffset = document.getLineEndOffset(lineNumberInCurrent);
501     final RangeHighlighter highlighter =
502       markupModel.addRangeHighlighter(startOffset, endOffset, HighlighterLayer.SELECTION - 1, textAttributes, HighlighterTargetArea.LINES_IN_RANGE);
503     final Function<Integer, Integer> newToOldConverter = newLine -> {
504       if (editor == null) return -1;
505       final TIntIntHashMap oldLineMapping = getNewToOldLineMapping(date, editorBean);
506       return oldLineMapping != null ? oldLineMapping.get(newLine.intValue()) : newLine.intValue();
507     };
508     final Function<Integer, Integer> oldToNewConverter = newLine -> {
509       if (editor == null) return -1;
510       final TIntIntHashMap newLineMapping = getOldToNewLineMapping(date, editorBean);
511       return newLineMapping != null ? newLineMapping.get(newLine.intValue()) : newLine.intValue();
512     };
513     final LineMarkerRendererWithErrorStripe markerRenderer = coverageSuite
514       .getLineMarkerRenderer(line, className, executableLines, coverageByTestApplicable, coverageSuite, newToOldConverter,
515                              oldToNewConverter, CoverageDataManager.getInstance(myProject).isSubCoverageActive());
516     highlighter.setLineMarkerRenderer(markerRenderer);
517
518     final LineData lineData = className != null ? (LineData)lines[line + 1] : null;
519     if (lineData != null && lineData.getStatus() == LineCoverage.NONE) {
520       highlighter.setErrorStripeMarkColor(markerRenderer.getErrorStripeColor(editor));
521       highlighter.setThinErrorStripeMark(true);
522       highlighter.setGreedyToLeft(true);
523       highlighter.setGreedyToRight(true);
524     }
525     return highlighter;
526   }
527
528   private void showEditorWarningMessage(final String message) {
529     Editor textEditor = myEditor;
530     PsiFile file = myFile;
531     ApplicationManager.getApplication().invokeLater(() -> {
532       if (textEditor == null || textEditor.isDisposed() || file == null) return;
533       final FileEditorManager fileEditorManager = FileEditorManager.getInstance(myProject);
534       final VirtualFile vFile = file.getVirtualFile();
535       assert vFile != null;
536       Map<FileEditor, EditorNotificationPanel> map = file.getCopyableUserData(NOTIFICATION_PANELS);
537       if (map == null) {
538         map = new HashMap<>();
539         file.putCopyableUserData(NOTIFICATION_PANELS, map);
540       }
541
542       final FileEditor[] editors = fileEditorManager.getAllEditors(vFile);
543       for (final FileEditor editor : editors) {
544         if (isCurrentEditor(editor)) {
545           final EditorNotificationPanel panel = new EditorNotificationPanel() {
546             {
547               myLabel.setIcon(AllIcons.General.ExclMark);
548               myLabel.setText(message);
549             }
550           };
551           panel.createActionLabel("Close", () -> fileEditorManager.removeTopComponent(editor, panel));
552           map.put(editor, panel);
553           fileEditorManager.addTopComponent(editor, panel);
554           break;
555         }
556       }
557     });
558   }
559
560   private boolean isCurrentEditor(FileEditor editor) {
561     return editor instanceof TextEditor && ((TextEditor)editor).getEditor() == myEditor;
562   }
563
564   private void collectNonCoveredFileInfo(final File outputFile,
565                                          final List<RangeHighlighter> highlighters, final MarkupModel markupModel,
566                                          final TreeMap<Integer, LineData> executableLines,
567                                          final boolean coverageByTestApplicable,
568                                          @NotNull MyEditorBean editorBean) {
569     final CoverageSuitesBundle coverageSuite = CoverageDataManager.getInstance(myProject).getCurrentSuitesBundle();
570     if (coverageSuite == null) return;
571     Document document = editorBean.getDocument();
572     VirtualFile file = editorBean.getVFile();
573     final TIntIntHashMap mapping;
574     if (outputFile.lastModified() < file.getTimeStamp()) {
575       mapping = getOldToNewLineMapping(outputFile.lastModified(), editorBean);
576       if (mapping == null) return;
577     }
578     else {
579       mapping = null;
580     }
581
582
583     final List<Integer> uncoveredLines = coverageSuite.getCoverageEngine().collectSrcLinesForUntouchedFile(outputFile, coverageSuite);
584
585     final int lineCount = document.getLineCount();
586     if (uncoveredLines == null) {
587       for (int lineNumber = 0; lineNumber < lineCount; lineNumber++) {
588         addHighlighter(outputFile, highlighters, markupModel, executableLines, coverageByTestApplicable, coverageSuite,
589                        lineNumber, lineNumber, editorBean);
590       }
591     }
592     else {
593       for (int lineNumber : uncoveredLines) {
594         if (lineNumber >= lineCount) {
595           continue;
596         }
597
598         final int updatedLineNumber = mapping != null ? mapping.get(lineNumber) : lineNumber;
599
600         addHighlighter(outputFile, highlighters, markupModel, executableLines, coverageByTestApplicable, coverageSuite,
601                        lineNumber, updatedLineNumber, editorBean);
602       }
603     }
604   }
605
606   private void addHighlighter(final File outputFile,
607                               final List<RangeHighlighter> highlighters,
608                               final MarkupModel markupModel,
609                               final TreeMap<Integer, LineData> executableLines,
610                               final boolean coverageByTestApplicable,
611                               final CoverageSuitesBundle coverageSuite,
612                               final int lineNumber,
613                               final int updatedLineNumber,
614                               @NotNull MyEditorBean editorBean) {
615     executableLines.put(updatedLineNumber, null);
616     ApplicationManager.getApplication().invokeLater(() -> {
617       if (editorBean.isDisposed()) return;
618       final RangeHighlighter highlighter =
619         createRangeHighlighter(outputFile.lastModified(), markupModel, coverageByTestApplicable, executableLines, null, lineNumber,
620                                updatedLineNumber, coverageSuite, null, editorBean);
621       highlighters.add(highlighter);
622     });
623   }
624
625   private static VirtualFile getVirtualFile(PsiFile file) {
626     final VirtualFile vFile = file.getVirtualFile();
627     LOG.assertTrue(vFile != null);
628     return vFile;
629   }
630
631
632   private void coverageDataNotFound(final CoverageSuitesBundle suite) {
633     showEditorWarningMessage(CodeInsightBundle.message("coverage.data.not.found"));
634     for (CoverageSuite coverageSuite : suite.getSuites()) {
635       CoverageDataManager.getInstance(myProject).removeCoverageSuite(coverageSuite);
636     }
637   }
638
639   public void dispose() {
640     hideCoverageData();
641     myEditor = null;
642     myDocument = null;
643     myFile = null;
644   }
645
646   static class MyEditorBean {
647     private final Editor myEditor;
648     private final VirtualFile myVFile;
649     private final Document myDocument;
650
651     public MyEditorBean(Editor editor, VirtualFile VFile, Document document) {
652       myEditor = editor;
653       myVFile = VFile;
654       myDocument = document;
655     }
656
657     public boolean isDisposed() {
658       return myEditor == null ||
659              myEditor.isDisposed() ||
660              myVFile == null ||
661              myDocument == null;
662     }
663
664     public Document getDocument() {
665       return myDocument;
666     }
667
668     public Editor getEditor() {
669       return myEditor;
670     }
671
672     public VirtualFile getVFile() {
673       return myVFile;
674     }
675   }
676 }