package org.xtest.ui.outline;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IStorage;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.swt.graphics.Image;
import org.eclipse.xtext.diagnostics.Severity;
import org.eclipse.xtext.nodemodel.ICompositeNode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.eclipse.xtext.ui.editor.outline.IOutlineNode;
import org.eclipse.xtext.ui.editor.outline.impl.DefaultOutlineTreeProvider;
import org.eclipse.xtext.ui.editor.outline.impl.EObjectNode;
import org.eclipse.xtext.ui.resource.IStorage2UriMapper;
import org.eclipse.xtext.ui.util.IssueUtil;
import org.eclipse.xtext.util.ITextRegion;
import org.eclipse.xtext.util.Pair;
import org.eclipse.xtext.util.TextRegion;
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.results.XTestResult;
import org.xtest.results.XTestState;
import org.xtest.runner.external.ContinuousTestRunner;
import org.xtest.ui.internal.XtestPluginImages;
import org.xtest.ui.mediator.XtestResultsCache;
import org.xtest.xTest.Body;
import org.xtest.xTest.impl.BodyImplCustom;
import com.google.common.collect.Lists;
import com.google.inject.Inject;
/**
* Customize the tree outline to display the the results of running the tests graphically
*
* @author Michael Barry
*/
public class XTestOutlineTreeProvider extends DefaultOutlineTreeProvider {
@Inject
private XtestPluginImages images;
@Inject
private IssueUtil issueUtil;
@Inject
private IStorage2UriMapper mapper;
@Inject
private XtestResultsCache mediator;
private IAnnotationModel model;
@Inject
private PerFilePreferenceProvider prefs;
@Override
public void createChildren(IOutlineNode parentNode, EObject body) {
if (body instanceof BodyImplCustom) {
String fileName = ((BodyImplCustom) body).getFileName();
List<Issue> issues = getIssues((Body) body);
URI uri = body.eResource().getURI();
XTestResult last = mediator.getLast(uri);
if (last != null) {
Object text = parentNode.getText();
if (!fileName.equals(text)) {
createNode(parentNode, last, fileName, issues);
}
} else {
scheduleValidation(body);
}
}
}
/**
* Sets the annotation model to get errors and warnings from
*
* @param annotationModel
* The annotation model
*/
public void setAnnotationModel(IAnnotationModel annotationModel) {
this.model = annotationModel;
}
@Override
protected EObjectNode createEObjectNode(IOutlineNode parentNode, EObject modelElement,
Image image, Object text, boolean isLeaf) {
EObjectNode eObjectNode = new XTestEObjectNode(modelElement, parentNode, image, text,
isLeaf);
ITextRegion region = getRegion(parentNode, modelElement);
if (region != null) {
eObjectNode.setShortTextRegion(region);
}
return eObjectNode;
}
/**
* Creates a new node for the test result, setting the name and icon appropriately given the
* pass/fail/not run state of the test
*
* @param parentNode
* The parent node
* @param result
* The test result
* @param suggestedName
* The suggested name to use
* @return The new tree node
*/
private EObjectNode createEObjectNode(IOutlineNode parentNode, XTestResult result,
String suggestedName, List<Issue> issues) {
EObject eObject = result.getEObject();
String name = result.getName();
if (name == null) {
name = suggestedName;
}
Image image;
Severity severity = getSeverity(result, issues);
boolean isLeaf = result.getSubTests().isEmpty();
if (isLeaf && parentNode.getParent() != null) {
image = severity == null ? images.getTestImage() : images.getTestImage(severity);
} else {
image = severity == null ? images.getSuiteImage() : images.getSuiteImage(severity);
}
EObjectNode createEObjectNode = createEObjectNode(parentNode, eObject, image, name, isLeaf);
if (severity == Severity.ERROR) {
((XTestEObjectNode) createEObjectNode).setFailed();
}
return createEObjectNode;
}
/**
* Creates a node for the test and sub-tests
*
* @param parentNode
* The parent tree node
* @param test
* The sub test
* @param suggestedName
* Name to use for the node
*/
private void createNode(IOutlineNode parentNode, XTestResult test, String suggestedName,
List<Issue> issues) {
EObjectNode thisNode = createEObjectNode(parentNode, test, suggestedName, issues);
for (XTestResult subTest : test.getSubTests()) {
createNode(thisNode, subTest, null, issues);
}
}
private IFile getFile(Body body) {
URI uri = body.eResource().getURI();
Iterable<Pair<IStorage, IProject>> storages = mapper.getStorages(uri);
IFile first = (IFile) storages.iterator().next().getFirst();
return first;
}
private List<Issue> getIssues(Body body) {
List<Issue> result = Lists.newArrayList();
if (prefs.get(body, RuntimePref.RUN_WHILE_EDITING) && model != null) {
// use annotation model
Iterator<?> iterator;
iterator = model.getAnnotationIterator();
while (iterator.hasNext()) {
final Annotation a = (Annotation) iterator.next();
if (!a.isMarkedDeleted()) {
Issue issue = issueUtil.getIssueFromAnnotation(a);
if (issue != null && issue.getSeverity() != Severity.INFO
&& issue.getOffset() >= 0 && issue.getType() != CheckType.EXPENSIVE) {
result.add(issue);
}
}
}
} else {
// use file markers
IFile first = getFile(body);
try {
IMarker[] findMarkers = first.findMarkers(null, true, IResource.DEPTH_ZERO);
for (IMarker marker : findMarkers) {
Issue createIssue = issueUtil.createIssue(marker);
if (createIssue != null && createIssue.getOffset() >= 0
&& createIssue.getLength() >= 0 && createIssue.getOffset() >= 0
&& createIssue.getType() != CheckType.EXPENSIVE) {
result.add(createIssue);
}
}
} catch (CoreException e) {
}
}
return result;
}
private ITextRegion getRegion(IOutlineNode parentNode, EObject modelElement) {
ITextRegion region = null;
ICompositeNode parserNode = NodeModelUtils.getNode(modelElement);
if (parserNode != null) {
region = new TextRegion(parserNode.getOffset(), parserNode.getLength());
}
if (isLocalElement(parentNode, modelElement)) {
region = locationInFileProvider.getSignificantTextRegion(modelElement);
}
return region;
}
private Severity getSeverity(List<Issue> issues, final int offset, final int length) {
boolean hasWarnings = false;
for (Issue issue : issues) {
Position position = new Position(issue.getOffset(), issue.getLength());
if (position.overlapsWith(offset, length)) {
if (issue.getSeverity() == Severity.ERROR) {
return Severity.ERROR;
} else if (issue.getSeverity() == Severity.WARNING) {
hasWarnings = true;
}
}
}
if (hasWarnings) {
return Severity.WARNING;
} else {
return Severity.INFO;
}
}
/**
* Returns the severity for the EObject derived from its test result status and any issues on
* contained {@link EObject}s
*
* @param result
* The test result
* @return The {@link Severity} of the node, or null if no issues and no tests have run
*/
private Severity getSeverity(XTestResult result, List<Issue> issues) {
Severity severity = null;
XTestState state = result.getState();
if (state == XTestState.FAIL) {
severity = Severity.ERROR;
} else {
EObject eObject = result.getEObject();
ICompositeNode node = NodeModelUtils.findActualNodeFor(eObject);
severity = getSeverity(issues, node.getOffset(), node.getTotalLength());
}
return severity;
}
private void scheduleValidation(EObject body) {
if (prefs.get((Body) body, RuntimePref.RUN_ON_SAVE)) {
Iterable<Pair<IStorage, IProject>> storages = mapper.getStorages(body.eResource()
.getURI());
for (Pair<IStorage, IProject> pair : storages) {
IStorage first = pair.getFirst();
if (first instanceof IFile) {
ContinuousTestRunner.schedule((IFile) first);
}
}
}
}
}