package edu.ucsd.arcum.exceptions;
import java.util.*;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.ui.texteditor.MarkerUtilities;
import edu.ucsd.arcum.ArcumPlugin;
import edu.ucsd.arcum.interpreter.ast.ASTUtil;
import edu.ucsd.arcum.interpreter.ast.expressions.ConstraintExpression;
import edu.ucsd.arcum.interpreter.query.EntityDataBase;
import edu.ucsd.arcum.ui.UIUtil;
import edu.ucsd.arcum.util.AquireReleasePair;
import edu.ucsd.arcum.util.DynamicScope;
public class SourceLocation
{
public static final SourceLocation GENERATED = new SourceLocation();
private static DynamicScope<SourceLocation> currentLocation = DynamicScope.newInstance();
private IResource resource;
private int charStart;
private int charEnd;
private int line;
private boolean hasLineInformation;
public static SourceLocation fromEntity(Object entity, EntityDataBase edb) {
if (entity instanceof ASTNode) {
return new SourceLocation((ASTNode)entity);
}
else if (entity instanceof ITypeBinding) {
AbstractTypeDeclaration decl = edb.lookupTypeDeclaration((ITypeBinding)entity);
return new SourceLocation(decl);
}
else {
return new SourceLocation();
}
}
public SourceLocation(ASTNode node) {
CompilationUnit unit = ASTUtil.findCompilationUnit(node);
if (unit == null) {
this.hasLineInformation = false;
}
else {
if (node instanceof AbstractTypeDeclaration) {
// Highlight the class name as the source of the error instead of the
// entire class
node = ((AbstractTypeDeclaration)node).getName();
}
this.resource = unit.getJavaElement().getResource();
this.charStart = node.getStartPosition();
this.charEnd = charStart + node.getLength();
this.line = unit.getLineNumber(charStart);
this.hasLineInformation = true;
}
}
public SourceLocation(IResource resource, int charStart, int charEnd, int line) {
this.resource = resource;
this.charStart = charStart;
this.charEnd = charEnd;
this.line = line;
this.hasLineInformation = true;
}
public SourceLocation() {
this.hasLineInformation = false;
}
@Override
public String toString() {
if (hasLineInformation) {
return String.format("%s:%d:%d-%d", resource.getFullPath(), line, charStart,
charEnd);
}
return "<no line information available, internal error>";
}
public void createMarker(String message) throws CoreException {
if (resource == null) {
ArcumError.fatalError("Built-in .arcum source file error?");
}
Map<Object, Object> attributes = new HashMap<Object, Object>(7);
// message = String.format("%s cs=%d ce=%d line=%d", message, charStart, charEnd, line);
attributes.put(IMarker.MESSAGE, message);
attributes.put(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
attributes.put(IMarker.USER_EDITABLE, false);
if (hasLineInformation) {
attributes.put(IMarker.CHAR_START, charStart);
attributes.put(IMarker.CHAR_END, charEnd);
attributes.put(IMarker.LINE_NUMBER, line);
attributes.put(IMarker.LOCATION, String.format("line %d", line));
}
MarkerUtilities.createMarker(resource, attributes, ArcumPlugin.MARKER_ID);
}
public SourceLocation extendedTo(SourceLocation extendedPosition) {
if (this == GENERATED) {
return GENERATED;
}
else {
SourceLocation result = new SourceLocation(resource, charStart,
extendedPosition.charEnd, line);
result.hasLineInformation = hasLineInformation;
return result;
}
}
public void openInEditor() {
if (hasLineInformation) {
if (resource instanceof IFile) {
IFile file = (IFile)resource;
UIUtil.asyncOpenEditor(file, charStart, charEnd - charStart);
}
}
}
public String getFileName() {
if (hasLineInformation) {
return resource.getFullPath().lastSegment();
}
else {
return "-";
}
}
public String getPathName() {
if (hasLineInformation) {
return resource.getFullPath().toString();
}
else {
return "-";
}
}
public String getLineAsString() {
if (line != 0) {
return String.valueOf(line);
}
else {
return "-";
}
}
// EXAMPLE: this should be a collection of an interface type instead, something
// where the location can be grabbed from it
public static SourceLocation coverAll(Collection<ConstraintExpression> conditions) {
List<SourceLocation> positions = new ArrayList<SourceLocation>();
for (ConstraintExpression condition : conditions) {
positions.add(condition.getPosition());
}
SourceLocation first = positions.get(0);
if (positions.size() == 1) {
return first;
}
int lowestCharStart = first.charStart;
int highestCharEnd = first.charEnd;
int lowestLine = first.line;
for (SourceLocation position : positions) {
lowestCharStart = Math.min(lowestCharStart, position.charStart);
highestCharEnd = Math.max(highestCharEnd, position.charEnd);
lowestLine = Math.min(lowestLine, position.line);
if (!position.resource.equals(first.resource)) {
// EXAMPLE: find all calls to resource.getFullPath().toString() and
// replace with static method call Util.toPathString(resource)
ArcumError.fatalUserError(position, "Arcum: Strange internal error:"
+ " %s is not the same file as %s", position.resource.getFullPath()
.toString(), first.resource.getFullPath().toString());
}
}
SourceLocation result = new SourceLocation(first.resource, lowestCharStart,
highestCharEnd, lowestLine);
result.hasLineInformation = first.hasLineInformation;
return result;
}
@AquireReleasePair(aquire="pushLocation", release="popLocation")
public static void pushLocation(SourceLocation location) {
currentLocation.push(location);
}
public static void popLocation() {
currentLocation.pop();
}
// Attempts to identify a source location associated with the current context,
// set up by pushLocation/popLocation. If no location can be found, the
// special GENERATED value is returned instead
public static SourceLocation resolveSourceLocation() {
if (!currentLocation.isEmpty()) {
return currentLocation.peek();
}
else {
return GENERATED;
}
}
public boolean isGenerated() {
return this == GENERATED;
}
}