package org.xtest.ui.editor;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.ui.texteditor.MarkerAnnotation;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.ui.MarkerTypes;
import org.eclipse.xtext.ui.editor.model.IXtextDocument;
import org.eclipse.xtext.ui.editor.quickfix.IssueResolutionProvider;
import org.eclipse.xtext.ui.editor.validation.AnnotationIssueProcessor;
import org.eclipse.xtext.ui.editor.validation.XtextAnnotation;
import org.eclipse.xtext.validation.CheckMode;
import org.eclipse.xtext.validation.CheckType;
import org.eclipse.xtext.validation.Issue;
import org.xtest.preferences.PerFilePreferenceProvider;
import org.xtest.preferences.RuntimePref;
import org.xtest.ui.mediator.ValidationFinishedEvent;
import org.xtest.xTest.Body;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.eventbus.Subscribe;
/**
* Custom {@link AnnotationIssueProcessor} for Xtest to hook into to hook in expensive test
* annotation clearing
*
* @author Michael Barry
*/
public class XtestAnnotationProcessor extends AnnotationIssueProcessor {
private final AtomicBoolean clearLiveEdits;
private final AtomicBoolean clearOnSaveEdits;
private final IAnnotationModel fAnnotationModel;
private final PerFilePreferenceProvider fPreferences;
private final XtestDocument fXtextDocument;
public XtestAnnotationProcessor(IXtextDocument xtextDocument, IAnnotationModel annotationModel,
IssueResolutionProvider issueResolutionProvider, PerFilePreferenceProvider preferences) {
super(xtextDocument, annotationModel, issueResolutionProvider);
fAnnotationModel = annotationModel;
fXtextDocument = (XtestDocument) xtextDocument;
fPreferences = preferences;
clearLiveEdits = new AtomicBoolean(false);
clearOnSaveEdits = new AtomicBoolean(false);
}
/**
* Notify this annotation processor that validation has finished in order to clear old markers
* on next update
*
* @param e
* Validation finished event
*/
@Subscribe
public void finish(ValidationFinishedEvent e) {
CheckMode checkMode = e.getCheckMode();
if (e.getUri().equals(fXtextDocument.getResourceURI()) && checkMode != null
&& checkMode.shouldCheck(CheckType.EXPENSIVE)) {
// should check fast means this was a reconcile
clearLiveEdits.getAndSet(checkMode.shouldCheck(CheckType.FAST));
// should not check fast means this was an on-save validation
clearOnSaveEdits.getAndSet(!checkMode.shouldCheck(CheckType.FAST));
}
}
@Override
protected void updateMarkerAnnotations(IProgressMonitor monitor) {
if (monitor.isCanceled()) {
return;
}
if (clearOnSaveEdits.getAndSet(false)) {
// XtextAnnotations are already cleared during a reconcile validation, so we just need
// to clear expensive (test result) XtextAnnotations after an on-save validation as well
removeExpensiveXtextAnnotations(monitor);
}
if (runWhileEdit() && clearLiveEdits.getAndSet(false)) {
// Reconcile validations already clear FAST marker annotations because that is all Xtext
// normally does for reconciles. Xtest needs to add clearing expensive (test result)
// markers on a reconcile, only if run-while-edit is enabled
removeExpensiveAndFastMarkerAnnotations(monitor);
}
super.updateMarkerAnnotations(monitor);
}
private void removeExpensiveAndFastMarkerAnnotations(IProgressMonitor monitor) {
// every markerAnnotation produced by fast validation can be marked as deleted.
// If its predicate still holds, the validation annotation will be covered anyway.
Iterator<MarkerAnnotation> annotationIterator = Iterators.filter(
fAnnotationModel.getAnnotationIterator(), MarkerAnnotation.class);
while (annotationIterator.hasNext() && !monitor.isCanceled()) {
final MarkerAnnotation annotation = annotationIterator.next();
if (!annotation.isMarkedDeleted()) {
try {
IMarker marker = annotation.getMarker();
if (isRelevantAnnotationType(annotation.getType())
&& marker.isSubtypeOf(MarkerTypes.EXPENSIVE_VALIDATION)) {
annotation.markDeleted(true);
queueOrFireAnnotationChangedEvent(annotation);
}
} catch (CoreException e) {
// marker type cannot be resolved - keep state of annotation
}
}
}
}
private void removeExpensiveXtextAnnotations(IProgressMonitor monitor) {
List<Annotation> issues = Lists.newArrayList();
Iterator<XtextAnnotation> xtext = Iterators.filter(
fAnnotationModel.getAnnotationIterator(), XtextAnnotation.class);
while (xtext.hasNext() && !monitor.isCanceled()) {
final XtextAnnotation annotation = xtext.next();
Issue issue = annotation.getIssue();
if (!annotation.isMarkedDeleted() && isRelevantAnnotationType(annotation.getType())
&& issue.getType() == CheckType.EXPENSIVE) {
issues.add(annotation);
}
}
updateAnnotations(monitor, issues, Maps.<Annotation, Position> newHashMap());
}
private Boolean runWhileEdit() {
// Don't need to lock when accessing resource since this is called inside a write lock
XtextResource resource = fXtextDocument.getResource();
Boolean result = false;
if (resource != null && resource.getContents().size() > 0
&& resource.getContents().get(0) instanceof Body) {
Body body = (Body) resource.getContents().get(0);
result = fPreferences.get(body, RuntimePref.RUN_WHILE_EDITING);
}
return result;
}
}