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
17package android.databinding.tool.store;
18import org.antlr.v4.runtime.ParserRuleContext;
19import org.antlr.v4.runtime.Token;
20import org.apache.commons.lang3.StringUtils;
21
22import android.databinding.tool.processing.scopes.LocationScopeProvider;
23
24import java.util.Arrays;
25import java.util.List;
26
27import javax.xml.bind.annotation.XmlAccessType;
28import javax.xml.bind.annotation.XmlAccessorType;
29import javax.xml.bind.annotation.XmlAttribute;
30import 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)
39public 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
53    public Location() {
54        startOffset = endOffset = startLine = endLine = NaN;
55    }
56
57    public Location(Location other) {
58        startOffset = other.startOffset;
59        endOffset = other.endOffset;
60        startLine = other.startLine;
61        endLine = other.endLine;
62    }
63
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
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
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
199    public String toUserReadableString() {
200        return startLine + ":" + startOffset + " - " + endLine + ":" + endOffset;
201    }
202
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
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
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