/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.databinding.tool.store;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Token;
import org.apache.commons.lang3.StringUtils;
import android.databinding.tool.processing.scopes.LocationScopeProvider;
import java.util.Arrays;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
/**
* Identifies the range of a code block inside a file or a string.
* Note that, unlike antlr4 tokens, the line positions start from 0 (to be compatible with Studio).
* <p>
* Both start and end line/column indices are inclusive.
*/
@XmlAccessorType(XmlAccessType.NONE)
public class Location {
public static final int NaN = -1;
@XmlAttribute(name = "startLine")
public int startLine;
@XmlAttribute(name = "startOffset")
public int startOffset;
@XmlAttribute(name = "endLine")
public int endLine;
@XmlAttribute(name = "endOffset")
public int endOffset;
@XmlElement(name = "parentLocation")
public Location parentLocation;
// for XML unmarshalling
public Location() {
startOffset = endOffset = startLine = endLine = NaN;
}
public Location(Location other) {
startOffset = other.startOffset;
endOffset = other.endOffset;
startLine = other.startLine;
endLine = other.endLine;
}
public Location(Token start, Token end) {
if (start == null) {
startLine = startOffset = NaN;
} else {
startLine = start.getLine() - 1; //token lines start from 1
startOffset = start.getCharPositionInLine();
}
if (end == null) {
endLine = endOffset = NaN;
} else {
endLine = end.getLine() - 1; // token lines start from 1
String endText = end.getText();
int lastLineStart = endText.lastIndexOf(System.lineSeparator());
String lastLine = lastLineStart < 0 ? endText : endText.substring(lastLineStart + 1);
endOffset = end.getCharPositionInLine() + lastLine.length() - 1;//end is inclusive
}
}
public Location(ParserRuleContext context) {
this(context == null ? null : context.getStart(),
context == null ? null : context.getStop());
}
public Location(int startLine, int startOffset, int endLine, int endOffset) {
this.startOffset = startOffset;
this.startLine = startLine;
this.endLine = endLine;
this.endOffset = endOffset;
}
@Override
public String toString() {
return "Location{" +
"startLine=" + startLine +
", startOffset=" + startOffset +
", endLine=" + endLine +
", endOffset=" + endOffset +
", parentLocation=" + parentLocation +
'}';
}
public void setParentLocation(Location parentLocation) {
this.parentLocation = parentLocation;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Location location = (Location) o;
if (endLine != location.endLine) {
return false;
}
if (endOffset != location.endOffset) {
return false;
}
if (startLine != location.startLine) {
return false;
}
if (startOffset != location.startOffset) {
return false;
}
if (parentLocation != null ? !parentLocation.equals(location.parentLocation)
: location.parentLocation != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = startLine;
result = 31 * result + startOffset;
result = 31 * result + endLine;
result = 31 * result + endOffset;
return result;
}
public boolean isValid() {
return startLine != NaN && endLine != NaN && startOffset != NaN && endOffset != NaN;
}
public boolean contains(Location other) {
if (startLine > other.startLine) {
return false;
}
if (startLine == other.startLine && startOffset > other.startOffset) {
return false;
}
if (endLine < other.endLine) {
return false;
}
if (endLine == other.endLine && endOffset < other.endOffset) {
return false;
}
return true;
}
private Location getValidParentAbsoluteLocation() {
if (parentLocation == null) {
return null;
}
if (parentLocation.isValid()) {
return parentLocation.toAbsoluteLocation();
}
return parentLocation.getValidParentAbsoluteLocation();
}
public Location toAbsoluteLocation() {
Location absoluteParent = getValidParentAbsoluteLocation();
if (absoluteParent == null) {
return this;
}
Location copy = new Location(this);
boolean sameLine = copy.startLine == copy.endLine;
if (copy.startLine == 0) {
copy.startOffset += absoluteParent.startOffset;
}
if (sameLine) {
copy.endOffset += absoluteParent.startOffset;
}
copy.startLine += absoluteParent.startLine;
copy.endLine += absoluteParent.startLine;
return copy;
}
public String toUserReadableString() {
return startLine + ":" + startOffset + " - " + endLine + ":" + endOffset;
}
public static Location fromUserReadableString(String str) {
int glue = str.indexOf('-');
if (glue == -1) {
return new Location();
}
String start = str.substring(0, glue);
String end = str.substring(glue + 1);
int[] point = new int[]{-1, -1};
Location location = new Location();
parsePoint(start, point);
location.startLine = point[0];
location.startOffset = point[1];
point[0] = point[1] = -1;
parsePoint(end, point);
location.endLine = point[0];
location.endOffset = point[1];
return location;
}
private static boolean parsePoint(String content, int[] into) {
int index = content.indexOf(':');
if (index == -1) {
return false;
}
into[0] = Integer.parseInt(content.substring(0, index).trim());
into[1] = Integer.parseInt(content.substring(index + 1).trim());
return true;
}
public LocationScopeProvider createScope() {
return new LocationScopeProvider() {
@Override
public List<Location> provideScopeLocation() {
return Arrays.asList(Location.this);
}
};
}
}