package net.sf.eclipsefp.haskell.buildwrapper.types; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Map; import net.sf.eclipsefp.haskell.buildwrapper.BWFacade; import net.sf.eclipsefp.haskell.buildwrapper.BuildWrapperPlugin; import net.sf.eclipsefp.haskell.buildwrapper.util.BWText; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.DefaultLineTracker; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.ILineTracker; import org.eclipse.jface.text.IRegion; import org.eclipse.ui.texteditor.MarkerUtilities; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; /** * A span of text within a file, in line:column format. * Lines are one-based, columns are zero-based. * The start is inclusive; the end is exclusive. * * @author Thomas ten Cate */ public class Location { private String fileName; private String otherName; private int startLine, startColumn, endLine, endColumn; public Location(JSONObject json) throws JSONException { this(null,json); } public Location(IFile f, JSONArray json) throws JSONException { this(f!=null?f.getLocation().toOSString():"",json); } public Location(String fn, JSONArray json) throws JSONException { startLine=json.getInt(0); startColumn=json.getInt(1)-1; // we're zero based, Haskell code 1 based if (json.length()>3){ endLine=json.getInt(2); endColumn=json.getInt(3)-1;// we're zero based, Haskell code 1 based } else if (json.length()>2){ endLine=startLine; endColumn=json.getInt(2)-1;// we're zero based, Haskell code 1 based } else { endLine=startLine; endColumn=startColumn+1; } if (endColumn==-1 && endLine>startLine){ endLine--; } this.fileName = fn; } public Location(IFile f, JSONObject json) throws JSONException { this.fileName = json.optString("file"); this.otherName = json.optString("other"); if ( f != null && (this.fileName == null || this.fileName.length() == 0) && (this.otherName == null || this.otherName.length() == 0) ) { // Default the file name to the Java file resource this.fileName = f.getLocation().toOSString(); } if (json.optString("no-location").length() == 0) { JSONArray region = json.getJSONArray("region"); startLine = region.getInt(0); startColumn = region.getInt(1); if (startColumn<0){ startColumn=0; } endLine = region.getInt(2); endColumn = region.getInt(3); } } /** * clone * @param l */ public Location(Location l) { this.fileName = l.getFileName(); this.startLine = l.getStartLine(); this.startColumn = l.getStartColumn(); this.endLine = l.getEndLine(); this.endColumn = l.getEndColumn(); } public Location(String fileName, int startLine, int startColumn, int endLine, int endColumn) { this.fileName = fileName; this.startLine = startLine; this.startColumn = startColumn; int mv=0; if (startColumn<0){ mv=0-startColumn; this.startColumn=0; } this.endLine = endLine; this.endColumn = endColumn+mv; } public Location(String fileName, IDocument document, IRegion region) throws BadLocationException { this.fileName = fileName; int startOffset = region.getOffset(); int endOffset = startOffset + region.getLength(); int docLine=document.getLineOfOffset(startOffset); this.startLine =docLine+1 ; this.startColumn = startOffset - document.getLineOffset(docLine); docLine=document.getLineOfOffset(endOffset); this.endLine = docLine+1; this.endColumn = endOffset - document.getLineOffset(docLine); } /** * Create a location from a buildwrapper location, expanding empty spans when possible. */ public Location(IProject project, String fileName, int startLin, int startCol, int endLin, int endCol) { this.fileName = fileName; startLine = startLin; startColumn = startCol - 1; // Buildwrapper columns start at 1 endLine = endLin; endColumn = endCol - 1; // Buildwrapper columns start at 1 if (endLine > startLine) { // IMarker does not support multi-line spans, so we reduce it to an empty span endLine = startLine; endColumn = startColumn; } if (startLine==endLine && startColumn==endColumn) { // span is empty // If the span is empty, we try to extend it to extend it to a single character span. ILineTracker lineTracker = getLineTracker(project.getLocation().toOSString() +"/"+ BWFacade.DIST_FOLDER+"/"+fileName); if (lineTracker != null) { try { if (startLine>lineTracker.getNumberOfLines()){ startLine=lineTracker.getNumberOfLines(); } if (endLine>lineTracker.getNumberOfLines()){ endLine=lineTracker.getNumberOfLines(); } //System.err.println("Initial span: "+startLine+":"+startColumn+" to "+endLine+":"+endColumn); String delimiter = lineTracker.getLineDelimiter(startLine-1); // apparently this can return null int lineLength = lineTracker.getLineLength(startLine-1 /*LineTracker is 0 based*/ ) - (delimiter == null ? 0 : delimiter.length()); // subtract the delimiter length if (startColumn < lineLength) { // not past the last character, so we can extend to the right. endColumn += 1; } else { if (startColumn > 0) { // past last character, but there are characters to the left. startColumn -= 1; } // else, we have startColumn == lineLength == 0, so the line is empty and we cannot extend the span. } //System.err.println("Fixed span: "+startLine+":"+startColumn+" to "+endLine+":"+endColumn); } catch (BadLocationException e){ BuildWrapperPlugin.logError(BWText.process_parse_note_error, e); } } else { //System.err.println("LineTracker is null for file "+fileName); } } } /** * Create a LineTracker for the file at filePath, for easy querying line lengths. * If any exception occurs, we simply return null. * Note that line numbers are 0 based, and the returned length includes the separator. */ private static ILineTracker getLineTracker(String filePath) { ILineTracker lineTracker; try (InputStream input = new FileInputStream( filePath ) ){ lineTracker = new DefaultLineTracker(); byte[] contents = new byte[input.available()]; input.read(contents); String stringContents = new String(contents); lineTracker.set(stringContents); } catch(IOException e) { lineTracker = null; } return lineTracker; } /** * Returns the offset within the given document * that the start of this {@link Location} object represents. */ public int getStartOffset(IDocument document) throws BadLocationException { return document.getLineOffset(startLine-1) + startColumn; } /** * Returns the offset within the given document * that the end of this {@link Location} object represents. */ public int getEndOffset(IDocument document) throws BadLocationException { return document.getLineOffset(endLine-1) + endColumn; } public int getLength(IDocument document) throws BadLocationException { return getEndOffset(document) - getStartOffset(document); } public String getContents(IDocument document) throws BadLocationException { int st=getStartOffset(document); return document.get(st,getEndOffset(document)-st); } public String getFileName() { return fileName; } public String getOtherName() { return otherName; } public int getStartLine() { return startLine; } public int getStartColumn() { return startColumn; } public int getEndLine() { return endLine; } public int getEndColumn() { return endColumn; } @Override public boolean equals(Object obj) { if (!(obj instanceof Location)) { return false; } Location other = (Location)obj; return fileName.equals(other.fileName) && startLine == other.startLine && startColumn == other.startColumn && endLine == other.endLine && endColumn == other.endColumn; } @Override public int hashCode() { return fileName.hashCode()<<16+startLine<<8+startColumn; } @Override public String toString() { return String.format("%d:%d-%d:%d", startLine, startColumn, endLine, endColumn); } public IFile getIFile(IProject p){ String pl=p.getLocation().toOSString(); String loc=getFileName(); if (loc!=null && loc.startsWith(pl)){ return p.getFile(loc.substring(pl.length())); } else if (!new File(loc).isAbsolute()) { return p.getFile(loc); } return null; } public Map<Object,Object> getMarkerProperties(IDocument d){ int line= Math.min(getStartLine(),d.getNumberOfLines()); final Map<Object,Object> attributes=new HashMap<>(); //MarkerUtilities.setLineNumber(attributes, line); //if (getStartLine()==getEndLine()){ MarkerUtilities.setLineNumber(attributes, line); //} try { int offset=d.getLineOffset(line-1); int start=getStartColumn(); MarkerUtilities.setCharStart(attributes, offset+start); offset=d.getLineOffset(Math.min(getEndLine(),d.getNumberOfLines())-1); int end=offset+getEndColumn(); if (end>=d.getLength()){ end=d.getLength()-1; } MarkerUtilities.setCharEnd(attributes, end); } catch (BadLocationException ble){ // ignore } //} return attributes; } public Map<Object,Object> getMarkerProperties(int maxLines){ int line= Math.min(getStartLine(),maxLines); final Map<Object,Object> attributes=new HashMap<>(); MarkerUtilities.setLineNumber(attributes, line); // if (getStartLine()==getEndLine()){ // int start=getStartColumn(); // int end=getEndColumn(); // // if we have startColumn==endColumn we could take end+1 // // BUT if end goes over the document size, or start is zero, or if Eclipse feels like it, the marker is not shown on the document // // so it's better to just show the line without more info // if (end>start){ // MarkerUtilities.setCharStart(attributes, start); // // exclusive // MarkerUtilities.setCharEnd(attributes, end-1); // } // } return attributes; } public void setStartLine(int startLine) { this.startLine = startLine; } public void setStartColumn(int startColumn) { this.startColumn = startColumn; } public void setEndLine(int endLine) { this.endLine = endLine; } public void setEndColumn(int endColumn) { this.endColumn = endColumn; } public boolean contains(int line,int column){ if( startLine<=line && endLine>=line){ if (startLine==line){ if (startColumn>column){ return false; } } if (endLine==line){ if (endColumn<column){ return false; } } return true; } return false; } }