1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.databinding.tool.store; 18 19 import org.antlr.v4.runtime.ParserRuleContext; 20 import org.antlr.v4.runtime.Token; 21 22 import android.databinding.tool.processing.scopes.LocationScopeProvider; 23 import android.databinding.tool.util.StringUtils; 24 25 import java.util.Collections; 26 import java.util.List; 27 28 import javax.xml.bind.annotation.XmlAccessType; 29 import javax.xml.bind.annotation.XmlAccessorType; 30 import javax.xml.bind.annotation.XmlAttribute; 31 import javax.xml.bind.annotation.XmlElement; 32 33 /** 34 * Identifies the range of a code block inside a file or a string. 35 * Note that, unlike antlr4 tokens, the line positions start from 0 (to be compatible with Studio). 36 * <p> 37 * Both start and end line/column indices are inclusive. 38 */ 39 @XmlAccessorType(XmlAccessType.NONE) 40 public class Location { 41 public static final int NaN = -1; 42 @XmlAttribute(name = "startLine") 43 public int startLine; 44 @XmlAttribute(name = "startOffset") 45 public int startOffset; 46 @XmlAttribute(name = "endLine") 47 public int endLine; 48 @XmlAttribute(name = "endOffset") 49 public int endOffset; 50 @XmlElement(name = "parentLocation") 51 public Location parentLocation; 52 53 // for XML unmarshalling 54 public Location() { 55 startOffset = endOffset = startLine = endLine = NaN; 56 } 57 58 public Location(Location other) { 59 startOffset = other.startOffset; 60 endOffset = other.endOffset; 61 startLine = other.startLine; 62 endLine = other.endLine; 63 } 64 65 public Location(Token start, Token end) { 66 if (start == null) { 67 startLine = startOffset = NaN; 68 } else { 69 startLine = start.getLine() - 1; //token lines start from 1 70 startOffset = start.getCharPositionInLine(); 71 } 72 73 if (end == null) { 74 endLine = endOffset = NaN; 75 } else { 76 endLine = end.getLine() - 1; // token lines start from 1 77 String endText = end.getText(); 78 int lastLineStart = endText.lastIndexOf(StringUtils.LINE_SEPARATOR); 79 String lastLine = lastLineStart < 0 ? endText : endText.substring(lastLineStart + 1); 80 endOffset = end.getCharPositionInLine() + lastLine.length() - 1;//end is inclusive 81 } 82 } 83 84 public Location(ParserRuleContext context) { 85 this(context == null ? null : context.getStart(), 86 context == null ? null : context.getStop()); 87 } 88 89 public Location(int startLine, int startOffset, int endLine, int endOffset) { 90 this.startOffset = startOffset; 91 this.startLine = startLine; 92 this.endLine = endLine; 93 this.endOffset = endOffset; 94 } 95 96 @Override 97 public String toString() { 98 return "Location{" + 99 "startLine=" + startLine + 100 ", startOffset=" + startOffset + 101 ", endLine=" + endLine + 102 ", endOffset=" + endOffset + 103 ", parentLocation=" + parentLocation + 104 '}'; 105 } 106 107 public void setParentLocation(Location parentLocation) { 108 this.parentLocation = parentLocation; 109 } 110 111 @Override 112 public boolean equals(Object o) { 113 if (this == o) { 114 return true; 115 } 116 if (o == null || getClass() != o.getClass()) { 117 return false; 118 } 119 120 Location location = (Location) o; 121 122 if (endLine != location.endLine) { 123 return false; 124 } 125 if (endOffset != location.endOffset) { 126 return false; 127 } 128 if (startLine != location.startLine) { 129 return false; 130 } 131 if (startOffset != location.startOffset) { 132 return false; 133 } 134 return !(parentLocation != null ? !parentLocation.equals(location.parentLocation) 135 : location.parentLocation != null); 136 137 } 138 139 @Override 140 public int hashCode() { 141 int result = startLine; 142 result = 31 * result + startOffset; 143 result = 31 * result + endLine; 144 result = 31 * result + endOffset; 145 return result; 146 } 147 148 public boolean isValid() { 149 return startLine != NaN && endLine != NaN && startOffset != NaN && endOffset != NaN; 150 } 151 152 public boolean contains(Location other) { 153 if (startLine > other.startLine) { 154 return false; 155 } 156 if (startLine == other.startLine && startOffset > other.startOffset) { 157 return false; 158 } 159 if (endLine < other.endLine) { 160 return false; 161 } 162 return !(endLine == other.endLine && endOffset < other.endOffset); 163 } 164 165 private Location getValidParentAbsoluteLocation() { 166 if (parentLocation == null) { 167 return null; 168 } 169 if (parentLocation.isValid()) { 170 return parentLocation.toAbsoluteLocation(); 171 } 172 return parentLocation.getValidParentAbsoluteLocation(); 173 } 174 175 public Location toAbsoluteLocation() { 176 Location absoluteParent = getValidParentAbsoluteLocation(); 177 if (absoluteParent == null) { 178 return this; 179 } 180 Location copy = new Location(this); 181 boolean sameLine = copy.startLine == copy.endLine; 182 if (copy.startLine == 0) { 183 copy.startOffset += absoluteParent.startOffset; 184 } 185 if (sameLine) { 186 copy.endOffset += absoluteParent.startOffset; 187 } 188 189 copy.startLine += absoluteParent.startLine; 190 copy.endLine += absoluteParent.startLine; 191 return copy; 192 } 193 194 public String toUserReadableString() { 195 return startLine + ":" + startOffset + " - " + endLine + ":" + endOffset; 196 } 197 198 public static Location fromUserReadableString(String str) { 199 int glue = str.indexOf('-'); 200 if (glue == -1) { 201 return new Location(); 202 } 203 String start = str.substring(0, glue); 204 String end = str.substring(glue + 1); 205 int[] point = new int[]{-1, -1}; 206 Location location = new Location(); 207 parsePoint(start, point); 208 location.startLine = point[0]; 209 location.startOffset = point[1]; 210 point[0] = point[1] = -1; 211 parsePoint(end, point); 212 location.endLine = point[0]; 213 location.endOffset = point[1]; 214 return location; 215 } 216 217 private static boolean parsePoint(String content, int[] into) { 218 int index = content.indexOf(':'); 219 if (index == -1) { 220 return false; 221 } 222 into[0] = Integer.parseInt(content.substring(0, index).trim()); 223 into[1] = Integer.parseInt(content.substring(index + 1).trim()); 224 return true; 225 } 226 227 public LocationScopeProvider createScope() { 228 return new LocationScopeProvider() { 229 @Override 230 public List<Location> provideScopeLocation() { 231 return Collections.singletonList(Location.this); 232 } 233 }; 234 } 235 } 236