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