• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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 com.android.tools.lint.detector.api;
18 
19 import com.android.annotations.NonNull;
20 import com.android.annotations.Nullable;
21 import com.google.common.annotations.Beta;
22 
23 import java.io.File;
24 
25 /**
26  * Location information for a warning
27  * <p/>
28  * <b>NOTE: This is not a public or final API; if you rely on this be prepared
29  * to adjust your code for the next tools release.</b>
30  */
31 @Beta
32 public class Location {
33     private final File mFile;
34     private final Position mStart;
35     private final Position mEnd;
36     private String mMessage;
37     private Location mSecondary;
38     private Object mClientData;
39 
40     /**
41      * (Private constructor, use one of the factory methods
42      * {@link Location#create(File)},
43      * {@link Location#create(File, Position, Position)}, or
44      * {@link Location#create(File, String, int, int)}.
45      * <p>
46      * Constructs a new location range for the given file, from start to end. If
47      * the length of the range is not known, end may be null.
48      *
49      * @param file the associated file (but see the documentation for
50      *            {@link #getFile()} for more information on what the file
51      *            represents)
52      * @param start the starting position, or null
53      * @param end the ending position, or null
54      */
Location(@onNull File file, @Nullable Position start, @Nullable Position end)55     protected Location(@NonNull File file, @Nullable Position start, @Nullable Position end) {
56         super();
57         this.mFile = file;
58         this.mStart = start;
59         this.mEnd = end;
60     }
61 
62     /**
63      * Returns the file containing the warning. Note that the file *itself* may
64      * not yet contain the error. When editing a file in the IDE for example,
65      * the tool could generate warnings in the background even before the
66      * document is saved. However, the file is used as a identifying token for
67      * the document being edited, and the IDE integration can map this back to
68      * error locations in the editor source code.
69      *
70      * @return the file handle for the location
71      */
72     @NonNull
getFile()73     public File getFile() {
74         return mFile;
75     }
76 
77     /**
78      * The start position of the range
79      *
80      * @return the start position of the range, or null
81      */
82     @Nullable
getStart()83     public Position getStart() {
84         return mStart;
85     }
86 
87     /**
88      * The end position of the range
89      *
90      * @return the start position of the range, may be null for an empty range
91      */
92     @Nullable
getEnd()93     public Position getEnd() {
94         return mEnd;
95     }
96 
97     /**
98      * Returns a secondary location associated with this location (if
99      * applicable), or null.
100      *
101      * @return a secondary location or null
102      */
103     @Nullable
getSecondary()104     public Location getSecondary() {
105         return mSecondary;
106     }
107 
108     /**
109      * Sets a secondary location for this location.
110      *
111      * @param secondary a secondary location associated with this location
112      */
setSecondary(@onNull Location secondary)113     public void setSecondary(@NonNull Location secondary) {
114         this.mSecondary = secondary;
115     }
116 
117     /**
118      * Sets a custom message for this location. This is typically used for
119      * secondary locations, to describe the significance of this alternate
120      * location. For example, for a duplicate id warning, the primary location
121      * might say "This is a duplicate id", pointing to the second occurrence of
122      * id declaration, and then the secondary location could point to the
123      * original declaration with the custom message "Originally defined here".
124      *
125      * @param message the message to apply to this location
126      */
setMessage(@onNull String message)127     public void setMessage(@NonNull String message) {
128         mMessage = message;
129     }
130 
131     /**
132      * Returns the custom message for this location, if any. This is typically
133      * used for secondary locations, to describe the significance of this
134      * alternate location. For example, for a duplicate id warning, the primary
135      * location might say "This is a duplicate id", pointing to the second
136      * occurrence of id declaration, and then the secondary location could point
137      * to the original declaration with the custom message
138      * "Originally defined here".
139      *
140      * @return the custom message for this location, or null
141      */
142     @Nullable
getMessage()143     public String getMessage() {
144         return mMessage;
145     }
146 
147     /**
148      * Sets the client data associated with this location. This is an optional
149      * field which can be used by the creator of the {@link Location} to store
150      * temporary state associated with the location.
151      *
152      * @param clientData the data to store with this location
153      */
setClientData(@ullable Object clientData)154     public void setClientData(@Nullable Object clientData) {
155         mClientData = clientData;
156     }
157 
158     /**
159      * Returns the client data associated with this location - an optional field
160      * which can be used by the creator of the {@link Location} to store
161      * temporary state associated with the location.
162      *
163      * @return the data associated with this location
164      */
165     @Nullable
getClientData()166     public Object getClientData() {
167         return mClientData;
168     }
169 
170     @Override
toString()171     public String toString() {
172         return "Location [file=" + mFile + ", start=" + mStart + ", end=" + mEnd + ", message="
173                 + mMessage + "]";
174     }
175 
176     /**
177      * Creates a new location for the given file
178      *
179      * @param file the file to create a location for
180      * @return a new location
181      */
182     @NonNull
create(@onNull File file)183     public static Location create(@NonNull File file) {
184         return new Location(file, null /*start*/, null /*end*/);
185     }
186 
187     /**
188      * Creates a new location for the given file and starting and ending
189      * positions.
190      *
191      * @param file the file containing the positions
192      * @param start the starting position
193      * @param end the ending position
194      * @return a new location
195      */
196     @NonNull
create( @onNull File file, @NonNull Position start, @NonNull Position end)197     public static Location create(
198             @NonNull File file,
199             @NonNull Position start,
200             @NonNull Position end) {
201         return new Location(file, start, end);
202     }
203 
204     /**
205      * Creates a new location for the given file, with the given contents, for
206      * the given offset range.
207      *
208      * @param file the file containing the location
209      * @param contents the current contents of the file
210      * @param startOffset the starting offset
211      * @param endOffset the ending offset
212      * @return a new location
213      */
214     @NonNull
create( @onNull File file, @Nullable String contents, int startOffset, int endOffset)215     public static Location create(
216             @NonNull File file,
217             @Nullable String contents,
218             int startOffset,
219             int endOffset) {
220         if (startOffset < 0 || endOffset < startOffset) {
221             throw new IllegalArgumentException("Invalid offsets");
222         }
223 
224         if (contents == null) {
225             return new Location(file,
226                     new DefaultPosition(-1, -1, startOffset),
227                     new DefaultPosition(-1, -1, endOffset));
228         }
229 
230         int size = contents.length();
231         endOffset = Math.min(endOffset, size);
232         startOffset = Math.min(startOffset, endOffset);
233         Position start = null;
234         int line = 0;
235         int lineOffset = 0;
236         for (int offset = 0; offset <= size; offset++) {
237             if (offset == startOffset) {
238                 start = new DefaultPosition(line, offset - lineOffset, offset);
239             }
240             if (offset == endOffset) {
241                 Position end = new DefaultPosition(line, offset - lineOffset, offset);
242                 return new Location(file, start, end);
243             }
244             char c = contents.charAt(offset);
245             if (c == '\n') {
246                 lineOffset = offset;
247                 line++;
248             }
249         }
250         return Location.create(file);
251     }
252 
253     /**
254      * Creates a new location for the given file, with the given contents, for
255      * the given line number.
256      *
257      * @param file the file containing the location
258      * @param contents the current contents of the file
259      * @param line the line number (0-based) for the position
260      * @return a new location
261      */
262     @NonNull
create(@onNull File file, @NonNull String contents, int line)263     public static Location create(@NonNull File file, @NonNull String contents, int line) {
264         return create(file, contents, line, null, null);
265     }
266 
267     /**
268      * Creates a new location for the given file, with the given contents, for
269      * the given line number.
270      *
271      * @param file the file containing the location
272      * @param contents the current contents of the file
273      * @param line the line number (0-based) for the position
274      * @param patternStart an optional pattern to search for from the line
275      *            match; if found, adjust the column and offsets to begin at the
276      *            pattern start
277      * @param patternEnd an optional pattern to search for behind the start
278      *            pattern; if found, adjust the end offset to match the end of
279      *            the pattern
280      * @return a new location
281      */
282     @NonNull
create(@onNull File file, @NonNull String contents, int line, @Nullable String patternStart, @Nullable String patternEnd)283     public static Location create(@NonNull File file, @NonNull String contents, int line,
284             @Nullable String patternStart, @Nullable String patternEnd) {
285         int currentLine = 0;
286         int offset = 0;
287         while (currentLine < line) {
288             offset = contents.indexOf('\n', offset);
289             if (offset == -1) {
290                 return Location.create(file);
291             }
292             currentLine++;
293             offset++;
294         }
295 
296         if (line == currentLine) {
297             if (patternStart != null) {
298                 int index = contents.indexOf(patternStart, offset);
299                 if (index == -1) {
300                     // Allow some flexibility: peek at previous couple of lines
301                     // as well (for example, bytecode line numbers are sometimes
302                     // a few lines off from their location in a source file
303                     // since they are attached to executable lines of code)
304                     int lineStart = offset;
305                     for (int i = 0; i < 4; i++) {
306                         int prevLineStart = contents.lastIndexOf('\n', lineStart - 1);
307                         if (prevLineStart == -1) {
308                             break;
309                         }
310                         index = contents.indexOf(patternStart, prevLineStart);
311                         if (index != -1 || prevLineStart == 0) {
312                             break;
313                         }
314                         lineStart = prevLineStart;
315                     }
316                 }
317 
318                 if (index != -1) {
319                     int lineStart = contents.lastIndexOf('\n', index);
320                     if (lineStart == -1) {
321                         lineStart = 0;
322                     } else {
323                         lineStart++; // was pointing to the previous line's CR, not line start
324                     }
325                     int column = index - lineStart;
326                     if (patternEnd != null) {
327                         int end = contents.indexOf(patternEnd, offset + patternStart.length());
328                         if (end != -1) {
329                             return new Location(file, new DefaultPosition(line, column, index),
330                                     new DefaultPosition(line, -1, end + patternEnd.length()));
331                         }
332                     }
333                     return new Location(file, new DefaultPosition(line, column, index),
334                             new DefaultPosition(line, column, index + patternStart.length()));
335                 }
336             }
337 
338             Position position = new DefaultPosition(line, -1, offset);
339             return new Location(file, position, position);
340         }
341 
342         return Location.create(file);
343     }
344 
345     /**
346      * Reverses the secondary location list initiated by the given location
347      *
348      * @param location the first location in the list
349      * @return the first location in the reversed list
350      */
reverse(Location location)351     public static Location reverse(Location location) {
352         Location next = location.getSecondary();
353         location.setSecondary(null);
354         while (next != null) {
355             Location nextNext = next.getSecondary();
356             next.setSecondary(location);
357             location = next;
358             next = nextNext;
359         }
360 
361         return location;
362     }
363 
364     /**
365      * A {@link Handle} is a reference to a location. The point of a location
366      * handle is to be able to create them cheaply, and then resolve them into
367      * actual locations later (if needed). This makes it possible to for example
368      * delay looking up line numbers, for locations that are offset based.
369      */
370     public static interface Handle {
371         /**
372          * Compute a full location for the given handle
373          *
374          * @return create a location for this handle
375          */
376         @NonNull
resolve()377         Location resolve();
378 
379         /**
380          * Sets the client data associated with this location. This is an optional
381          * field which can be used by the creator of the {@link Location} to store
382          * temporary state associated with the location.
383          *
384          * @param clientData the data to store with this location
385          */
setClientData(@ullable Object clientData)386         public void setClientData(@Nullable Object clientData);
387 
388         /**
389          * Returns the client data associated with this location - an optional field
390          * which can be used by the creator of the {@link Location} to store
391          * temporary state associated with the location.
392          *
393          * @return the data associated with this location
394          */
395         @Nullable
getClientData()396         public Object getClientData();
397     }
398 
399     /** A default {@link Handle} implementation for simple file offsets */
400     public static class DefaultLocationHandle implements Handle {
401         private File mFile;
402         private String mContents;
403         private int mStartOffset;
404         private int mEndOffset;
405         private Object mClientData;
406 
407         /**
408          * Constructs a new {@link DefaultLocationHandle}
409          *
410          * @param context the context pointing to the file and its contents
411          * @param startOffset the start offset within the file
412          * @param endOffset the end offset within the file
413          */
DefaultLocationHandle(@onNull Context context, int startOffset, int endOffset)414         public DefaultLocationHandle(@NonNull Context context, int startOffset, int endOffset) {
415             mFile = context.file;
416             mContents = context.getContents();
417             mStartOffset = startOffset;
418             mEndOffset = endOffset;
419         }
420 
421         @Override
422         @NonNull
resolve()423         public Location resolve() {
424             return Location.create(mFile, mContents, mStartOffset, mEndOffset);
425         }
426 
427         @Override
setClientData(@ullable Object clientData)428         public void setClientData(@Nullable Object clientData) {
429             mClientData = clientData;
430         }
431 
432         @Override
433         @Nullable
getClientData()434         public Object getClientData() {
435             return mClientData;
436         }
437     }
438 }
439