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;
18
19import org.antlr.v4.runtime.ParserRuleContext;
20import org.antlr.v4.runtime.Token;
21
22import android.databinding.tool.processing.scopes.LocationScopeProvider;
23import android.databinding.tool.util.StringUtils;
24
25import java.util.Collections;
26import java.util.List;
27
28import javax.xml.bind.annotation.XmlAccessType;
29import javax.xml.bind.annotation.XmlAccessorType;
30import javax.xml.bind.annotation.XmlAttribute;
31import 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)
40public 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