blob: df65c78e213173667ed1532adb85826b6e2d432e [file] [log] [blame]
Stuart McCulloch42151ee2012-07-16 13:43:38 +00001package aQute.bnd.properties;
Stuart McCulloch669423b2012-06-26 16:34:24 +00002
3import java.util.ArrayList;
4import java.util.List;
5
Stuart McCulloch42151ee2012-07-16 13:43:38 +00006import aQute.bnd.properties.Document.DelimiterInfo;
Stuart McCulloch669423b2012-06-26 16:34:24 +00007
8public class LineTracker {
9
10 /** The line information */
11 private final List<Line> fLines = new ArrayList<Line>();
12 /** The length of the tracked text */
13 private int fTextLength;
14
15 /**
16 * Creates a new line tracker.
17 */
18 protected LineTracker() {}
19
20 /**
21 * Binary search for the line at a given offset.
22 *
23 * @param offset
24 * the offset whose line should be found
25 * @return the line of the offset
26 */
27 private int findLine(int offset) {
28
29 if (fLines.size() == 0)
30 return -1;
31
32 int left = 0;
33 int right = fLines.size() - 1;
34 int mid = 0;
35 Line line = null;
36
37 while (left < right) {
38
39 mid = (left + right) / 2;
40
41 line = fLines.get(mid);
42 if (offset < line.offset) {
43 if (left == mid)
44 right = left;
45 else
46 right = mid - 1;
47 } else if (offset > line.offset) {
48 if (right == mid)
49 left = right;
50 else
51 left = mid + 1;
52 } else if (offset == line.offset) {
53 left = right = mid;
54 }
55 }
56
57 line = fLines.get(left);
58 if (line.offset > offset)
59 --left;
60 return left;
61 }
62
63 /**
64 * Returns the number of lines covered by the specified text range.
65 *
66 * @param startLine
67 * the line where the text range starts
68 * @param offset
69 * the start offset of the text range
70 * @param length
71 * the length of the text range
72 * @return the number of lines covered by this text range
73 * @exception BadLocationException
74 * if range is undefined in this tracker
75 */
76 private int getNumberOfLines(int startLine, int offset, int length) throws BadLocationException {
77
78 if (length == 0)
79 return 1;
80
81 int target = offset + length;
82
83 Line l = fLines.get(startLine);
84
85 if (l.delimiter == null)
86 return 1;
87
88 if (l.offset + l.length > target)
89 return 1;
90
91 if (l.offset + l.length == target)
92 return 2;
93
94 return getLineNumberOfOffset(target) - startLine + 1;
95 }
96
97 /*
98 * @see org.eclipse.jface.text.ILineTracker#getLineLength(int)
99 */
100 public final int getLineLength(int line) throws BadLocationException {
101 int lines = fLines.size();
102
103 if (line < 0 || line > lines)
104 throw new BadLocationException();
105
106 if (lines == 0 || lines == line)
107 return 0;
108
109 Line l = fLines.get(line);
110 return l.length;
111 }
112
113 /*
114 * @see org.eclipse.jface.text.ILineTracker#getLineNumberOfOffset(int)
115 */
116 public final int getLineNumberOfOffset(int position) throws BadLocationException {
117 if (position < 0 || position > fTextLength)
118 throw new BadLocationException();
119
120 if (position == fTextLength) {
121
122 int lastLine = fLines.size() - 1;
123 if (lastLine < 0)
124 return 0;
125
126 Line l = fLines.get(lastLine);
127 return (l.delimiter != null ? lastLine + 1 : lastLine);
128 }
129
130 return findLine(position);
131 }
132
133 /*
134 * @see org.eclipse.jface.text.ILineTracker#getLineInformationOfOffset(int)
135 */
136 public final IRegion getLineInformationOfOffset(int position) throws BadLocationException {
137 if (position > fTextLength)
138 throw new BadLocationException();
139
140 if (position == fTextLength) {
141 int size = fLines.size();
142 if (size == 0)
143 return new Region(0, 0);
144 Line l = fLines.get(size - 1);
145 return (l.delimiter != null ? new Line(fTextLength, 0) : new Line(fTextLength - l.length, l.length));
146 }
147
148 return getLineInformation(findLine(position));
149 }
150
151 /*
152 * @see org.eclipse.jface.text.ILineTracker#getLineInformation(int)
153 */
154 public final IRegion getLineInformation(int line) throws BadLocationException {
155 int lines = fLines.size();
156
157 if (line < 0 || line > lines)
158 throw new BadLocationException();
159
160 if (lines == 0)
161 return new Line(0, 0);
162
163 if (line == lines) {
164 Line l = fLines.get(line - 1);
165 return new Line(l.offset + l.length, 0);
166 }
167
168 Line l = fLines.get(line);
169 return (l.delimiter != null ? new Line(l.offset, l.length - l.delimiter.length()) : l);
170 }
171
172 /*
173 * @see org.eclipse.jface.text.ILineTracker#getLineOffset(int)
174 */
175 public final int getLineOffset(int line) throws BadLocationException {
176 int lines = fLines.size();
177
178 if (line < 0 || line > lines)
179 throw new BadLocationException();
180
181 if (lines == 0)
182 return 0;
183
184 if (line == lines) {
185 Line l = fLines.get(line - 1);
186 if (l.delimiter != null)
187 return l.offset + l.length;
188 throw new BadLocationException();
189 }
190
191 Line l = fLines.get(line);
192 return l.offset;
193 }
194
195 /*
196 * @see org.eclipse.jface.text.ILineTracker#getNumberOfLines()
197 */
198 public final int getNumberOfLines() {
199 int lines = fLines.size();
200
201 if (lines == 0)
202 return 1;
203
204 Line l = fLines.get(lines - 1);
205 return (l.delimiter != null ? lines + 1 : lines);
206 }
207
208 /*
209 * @see org.eclipse.jface.text.ILineTracker#getNumberOfLines(int, int)
210 */
211 public final int getNumberOfLines(int position, int length) throws BadLocationException {
212
213 if (position < 0 || position + length > fTextLength)
214 throw new BadLocationException();
215
216 if (length == 0) // optimization
217 return 1;
218
219 return getNumberOfLines(getLineNumberOfOffset(position), position, length);
220 }
221
222 /*
223 * @see
224 * org.eclipse.jface.text.ILineTracker#computeNumberOfLines(java.lang.String
225 * )
226 */
227 public final int computeNumberOfLines(String text) {
228 int count = 0;
229 int start = 0;
230 DelimiterInfo delimiterInfo = nextDelimiterInfo(text, start);
231 while (delimiterInfo != null && delimiterInfo.delimiterIndex > -1) {
232 ++count;
233 start = delimiterInfo.delimiterIndex + delimiterInfo.delimiterLength;
234 delimiterInfo = nextDelimiterInfo(text, start);
235 }
236 return count;
237 }
238
239 /*
240 * @see org.eclipse.jface.text.ILineTracker#getLineDelimiter(int)
241 */
242 public final String getLineDelimiter(int line) throws BadLocationException {
243 int lines = fLines.size();
244
245 if (line < 0 || line > lines)
246 throw new BadLocationException();
247
248 if (lines == 0)
249 return null;
250
251 if (line == lines)
252 return null;
253
254 Line l = fLines.get(line);
255 return l.delimiter;
256 }
257
258 /**
259 * Returns the information about the first delimiter found in the given text
260 * starting at the given offset.
261 *
262 * @param text
263 * the text to be searched
264 * @param offset
265 * the offset in the given text
266 * @return the information of the first found delimiter or <code>null</code>
267 */
268 protected DelimiterInfo nextDelimiterInfo(String text, int offset) {
269
270 char ch;
271 int length = text.length();
272 for (int i = offset; i < length; i++) {
273
274 ch = text.charAt(i);
275 if (ch == '\r') {
276
277 if (i + 1 < length) {
278 if (text.charAt(i + 1) == '\n') {
279 DelimiterInfo fDelimiterInfo = new DelimiterInfo();
280 fDelimiterInfo.delimiter = Document.DELIMITERS[2];
281 fDelimiterInfo.delimiterIndex = i;
282 fDelimiterInfo.delimiterLength = 2;
283 return fDelimiterInfo;
284 }
285 }
286 DelimiterInfo fDelimiterInfo = new DelimiterInfo();
287 fDelimiterInfo.delimiter = Document.DELIMITERS[0];
288 fDelimiterInfo.delimiterIndex = i;
289 fDelimiterInfo.delimiterLength = 1;
290 return fDelimiterInfo;
291
292 } else if (ch == '\n') {
293 DelimiterInfo fDelimiterInfo = new DelimiterInfo();
294 fDelimiterInfo.delimiter = Document.DELIMITERS[1];
295 fDelimiterInfo.delimiterIndex = i;
296 fDelimiterInfo.delimiterLength = 1;
297 return fDelimiterInfo;
298 }
299 }
300
301 return null;
302 }
303
304 /**
305 * Creates the line structure for the given text. Newly created lines are
306 * inserted into the line structure starting at the given position. Returns
307 * the number of newly created lines.
308 *
309 * @param text
310 * the text for which to create a line structure
311 * @param insertPosition
312 * the position at which the newly created lines are inserted
313 * into the tracker's line structure
314 * @param offset
315 * the offset of all newly created lines
316 * @return the number of newly created lines
317 */
318 private int createLines(String text, int insertPosition, int offset) {
319
320 int count = 0;
321 int start = 0;
322 DelimiterInfo delimiterInfo = nextDelimiterInfo(text, 0);
323
324 while (delimiterInfo != null && delimiterInfo.delimiterIndex > -1) {
325
326 int index = delimiterInfo.delimiterIndex + (delimiterInfo.delimiterLength - 1);
327
328 if (insertPosition + count >= fLines.size())
329 fLines.add(new Line(offset + start, offset + index, delimiterInfo.delimiter));
330 else
331 fLines.add(insertPosition + count, new Line(offset + start, offset + index, delimiterInfo.delimiter));
332
333 ++count;
334 start = index + 1;
335 delimiterInfo = nextDelimiterInfo(text, start);
336 }
337
338 if (start < text.length()) {
339 if (insertPosition + count < fLines.size()) {
340 // there is a line below the current
341 Line l = fLines.get(insertPosition + count);
342 int delta = text.length() - start;
343 l.offset -= delta;
344 l.length += delta;
345 } else {
346 fLines.add(new Line(offset + start, offset + text.length() - 1, null));
347 ++count;
348 }
349 }
350
351 return count;
352 }
353
354 /*
355 * @see org.eclipse.jface.text.ILineTracker#replace(int, int,
356 * java.lang.String)
357 */
358 public final void replace(@SuppressWarnings("unused") int position, @SuppressWarnings("unused") int length, @SuppressWarnings("unused") String text) throws BadLocationException {
359 throw new UnsupportedOperationException();
360 }
361
362 /*
363 * @see org.eclipse.jface.text.ILineTracker#set(java.lang.String)
364 */
365 public final void set(String text) {
366 fLines.clear();
367 if (text != null) {
368 fTextLength = text.length();
369 createLines(text, 0, 0);
370 }
371 }
372
373 /**
374 * Returns the internal data structure, a {@link List} of {@link Line}s.
375 * Used only by {@link TreeLineTracker#TreeLineTracker(ListLineTracker)}.
376 *
377 * @return the internal list of lines.
378 */
379 final List<Line> getLines() {
380 return fLines;
381 }
382}