1 | /* |
2 | * Copyright 1999-2006 Sun Microsystems, Inc. All Rights Reserved. |
3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
4 | * |
5 | * This code is free software; you can redistribute it and/or modify it |
6 | * under the terms of the GNU General Public License version 2 only, as |
7 | * published by the Free Software Foundation. Sun designates this |
8 | * particular file as subject to the "Classpath" exception as provided |
9 | * by Sun in the LICENSE file that accompanied this code. |
10 | * |
11 | * This code is distributed in the hope that it will be useful, but WITHOUT |
12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
14 | * version 2 for more details (a copy is included in the LICENSE file that |
15 | * accompanied this code). |
16 | * |
17 | * You should have received a copy of the GNU General Public License version |
18 | * 2 along with this work; if not, write to the Free Software Foundation, |
19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
20 | * |
21 | * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
22 | * CA 95054 USA or visit www.sun.com if you need additional information or |
23 | * have any questions. |
24 | */ |
25 | |
26 | package com.sun.tools.javac.util; |
27 | |
28 | import java.util.BitSet; |
29 | import static com.sun.tools.javac.util.LayoutCharacters.*; |
30 | |
31 | /** A class that defines source code positions as simple character |
32 | * offsets from the beginning of the file. The first character |
33 | * is at position 0. |
34 | * |
35 | * Support is also provided for (line,column) coordinates, but tab |
36 | * expansion is optional and no Unicode excape translation is considered. |
37 | * The first character is at location (1,1). |
38 | * |
39 | * <p><b>This is NOT part of any API supported by Sun Microsystems. If |
40 | * you write code that depends on this, you do so at your own risk. |
41 | * This code and its internal interfaces are subject to change or |
42 | * deletion without notice.</b> |
43 | */ |
44 | public class Position { |
45 | public static final int NOPOS = -1; |
46 | |
47 | public static final int FIRSTPOS = 0; |
48 | public static final int FIRSTLINE = 1; |
49 | public static final int FIRSTCOLUMN = 1; |
50 | |
51 | public static final int LINESHIFT = 10; |
52 | public static final int MAXCOLUMN = (1<<LINESHIFT) - 1; |
53 | public static final int MAXLINE = (1<<(Integer.SIZE-LINESHIFT)) - 1; |
54 | |
55 | public static final int MAXPOS = Integer.MAX_VALUE; |
56 | |
57 | /** |
58 | * This is class is not supposed to be instantiated. |
59 | */ |
60 | private Position() {} |
61 | |
62 | /** A two-way map between line/column numbers and positions, |
63 | * derived from a scan done at creation time. Tab expansion is |
64 | * optionally supported via a character map. Text content |
65 | * is not retained. |
66 | *<p> |
67 | * Notes: The first character position FIRSTPOS is at |
68 | * (FIRSTLINE,FIRSTCOLUMN). No account is taken of Unicode escapes. |
69 | * |
70 | * @param src Source characters |
71 | * @param max Number of characters to read |
72 | * @param expandTabs If true, expand tabs when calculating columns |
73 | */ |
74 | public static LineMap makeLineMap(char[] src, int max, boolean expandTabs) { |
75 | LineMapImpl lineMap = expandTabs ? |
76 | new LineTabMapImpl(max) : new LineMapImpl(); |
77 | lineMap.build(src, max); |
78 | return lineMap; |
79 | } |
80 | |
81 | /** Encode line and column numbers in an integer as: |
82 | * line-number << LINESHIFT + column-number |
83 | * {@link Position.NOPOS represents an undefined position. |
84 | * |
85 | * @param line number of line (first is 1) |
86 | * @param col number of character on line (first is 1) |
87 | * @return an encoded position or {@link Position.NOPOS |
88 | * if the line or column number is too big to |
89 | * represent in the encoded format |
90 | * @throws IllegalArgumentException if line or col is less than 1 |
91 | */ |
92 | public static int encodePosition(int line, int col) { |
93 | if (line < 1) |
94 | throw new IllegalArgumentException("line must be greater than 0"); |
95 | if (col < 1) |
96 | throw new IllegalArgumentException("column must be greater than 0"); |
97 | |
98 | if (line > MAXLINE || col > MAXCOLUMN) { |
99 | return NOPOS; |
100 | } |
101 | return (line << LINESHIFT) + col; |
102 | } |
103 | |
104 | public static interface LineMap extends com.sun.source.tree.LineMap { |
105 | /** Find the start position of a line. |
106 | * |
107 | * @param line number of line (first is 1) |
108 | * @return position of first character in line |
109 | * @throws ArrayIndexOutOfBoundsException |
110 | * if <tt>lineNumber < 1</tt> |
111 | * if <tt>lineNumber > no. of lines</tt> |
112 | */ |
113 | int getStartPosition(int line); |
114 | |
115 | /** Find the position corresponding to a (line,column). |
116 | * |
117 | * @param line number of line (first is 1) |
118 | * @param column number of character on line (first is 1) |
119 | * |
120 | * @return position of character |
121 | * @throws ArrayIndexOutOfBoundsException |
122 | * if <tt>line < 1</tt> |
123 | * if <tt>line > no. of lines</tt> |
124 | */ |
125 | int getPosition(int line, int column); |
126 | |
127 | /** Find the line containing a position; a line termination |
128 | * character is on the line it terminates. |
129 | * |
130 | * @param pos character offset of the position |
131 | * @return the line number on which pos occurs (first line is 1) |
132 | */ |
133 | int getLineNumber(int pos); |
134 | |
135 | /** Find the column for a character position. |
136 | * Note: this method does not handle tab expansion. |
137 | * If tab expansion is needed, use a LineTabMap instead. |
138 | * |
139 | * @param pos character offset of the position |
140 | * @return the column number at which pos occurs |
141 | */ |
142 | int getColumnNumber(int pos); |
143 | } |
144 | |
145 | static class LineMapImpl implements LineMap { |
146 | protected int[] startPosition; // start position of each line |
147 | |
148 | protected LineMapImpl() {} |
149 | |
150 | protected void build(char[] src, int max) { |
151 | int c = 0; |
152 | int i = 0; |
153 | int[] linebuf = new int[max]; |
154 | while (i < max) { |
155 | linebuf[c++] = i; |
156 | do { |
157 | char ch = src[i]; |
158 | if (ch == '\r' || ch == '\n') { |
159 | if (ch == '\r' && (i+1) < max && src[i+1] == '\n') |
160 | i += 2; |
161 | else |
162 | ++i; |
163 | break; |
164 | } |
165 | else if (ch == '\t') |
166 | setTabPosition(i); |
167 | } while (++i < max); |
168 | } |
169 | this.startPosition = new int[c]; |
170 | System.arraycopy(linebuf, 0, startPosition, 0, c); |
171 | } |
172 | |
173 | public int getStartPosition(int line) { |
174 | return startPosition[line - FIRSTLINE]; |
175 | } |
176 | |
177 | public long getStartPosition(long line) { |
178 | return getStartPosition(longToInt(line)); |
179 | } |
180 | |
181 | public int getPosition(int line, int column) { |
182 | return startPosition[line - FIRSTLINE] + column - FIRSTCOLUMN; |
183 | } |
184 | |
185 | public long getPosition(long line, long column) { |
186 | return getPosition(longToInt(line), longToInt(column)); |
187 | } |
188 | |
189 | // Cache of last line number lookup |
190 | private int lastPosition = Position.FIRSTPOS; |
191 | private int lastLine = Position.FIRSTLINE; |
192 | |
193 | public int getLineNumber(int pos) { |
194 | if (pos == lastPosition) { |
195 | return lastLine; |
196 | } |
197 | lastPosition = pos; |
198 | |
199 | int low = 0; |
200 | int high = startPosition.length-1; |
201 | while (low <= high) { |
202 | int mid = (low + high) >> 1; |
203 | int midVal = startPosition[mid]; |
204 | |
205 | if (midVal < pos) |
206 | low = mid + 1; |
207 | else if (midVal > pos) |
208 | high = mid - 1; |
209 | else { |
210 | lastLine = mid + 1; // pos is at beginning of this line |
211 | return lastLine; |
212 | } |
213 | } |
214 | lastLine = low; |
215 | return lastLine; // pos is on this line |
216 | } |
217 | |
218 | public long getLineNumber(long pos) { |
219 | return getLineNumber(longToInt(pos)); |
220 | } |
221 | |
222 | public int getColumnNumber(int pos) { |
223 | return pos - startPosition[getLineNumber(pos) - FIRSTLINE] + FIRSTCOLUMN; |
224 | } |
225 | |
226 | public long getColumnNumber(long pos) { |
227 | return getColumnNumber(longToInt(pos)); |
228 | } |
229 | |
230 | private static int longToInt(long longValue) { |
231 | int intValue = (int)longValue; |
232 | if (intValue != longValue) |
233 | throw new IndexOutOfBoundsException(); |
234 | return intValue; |
235 | } |
236 | |
237 | protected void setTabPosition(int offset) {} |
238 | } |
239 | |
240 | /** |
241 | * A LineMap that handles tab expansion correctly. The cost is |
242 | * an additional bit per character in the source array. |
243 | */ |
244 | public static class LineTabMapImpl extends LineMapImpl { |
245 | private BitSet tabMap; // bits set for tab positions. |
246 | |
247 | public LineTabMapImpl(int max) { |
248 | super(); |
249 | tabMap = new BitSet(max); |
250 | } |
251 | |
252 | protected void setTabPosition(int offset) { |
253 | tabMap.set(offset); |
254 | } |
255 | |
256 | public int getColumnNumber(int pos) { |
257 | int lineStart = startPosition[getLineNumber(pos) - FIRSTLINE]; |
258 | int column = 0; |
259 | for (int bp = lineStart; bp < pos; bp++) { |
260 | if (tabMap.get(bp)) |
261 | column = (column / TabInc * TabInc) + TabInc; |
262 | else |
263 | column++; |
264 | } |
265 | return column + FIRSTCOLUMN; |
266 | } |
267 | |
268 | public int getPosition(int line, int column) { |
269 | int pos = startPosition[line - FIRSTLINE]; |
270 | column -= FIRSTCOLUMN; |
271 | int col = 0; |
272 | while (col < column) { |
273 | pos++; |
274 | if (tabMap.get(pos)) |
275 | col = (col / TabInc * TabInc) + TabInc; |
276 | else |
277 | col++; |
278 | } |
279 | return pos; |
280 | } |
281 | } |
282 | } |