46 |
import javax.swing.undo.UndoableEdit; |
import javax.swing.undo.UndoableEdit; |
47 |
|
|
48 |
/** |
/** |
49 |
* This implementation of {@link AbstractDocument.Content} uses a gapped |
* This implementation of {@link AbstractDocument.Content} uses a gapped buffer. |
50 |
* buffer. This takes advantage of the fact that text area content is |
* This takes advantage of the fact that text area content is mostly inserted |
51 |
* mostly inserted sequentially. The buffer is a char array that maintains |
* sequentially. The buffer is a char array that maintains a gap at the current |
52 |
* a gap at the current insertion point. If characters a inserted at |
* insertion point. If characters a inserted at gap boundaries, the cost is |
53 |
* gap boundaries, the cost is minimal (simple array access). The array only |
* minimal (simple array access). The array only has to be shifted around when |
54 |
* has to be shifted around when the insertion point moves (then the gap also |
* the insertion point moves (then the gap also moves and one array copy is |
55 |
* moves and one array copy is necessary) or when the gap is filled up and |
* necessary) or when the gap is filled up and the buffer has to be enlarged. |
56 |
* the buffer has to be enlarged. |
* |
|
* |
|
57 |
* TODO: Implement UndoableEdit support stuff |
* TODO: Implement UndoableEdit support stuff |
58 |
*/ |
*/ |
59 |
public class GapContent |
public class GapContent |
60 |
implements AbstractDocument.Content, Serializable |
implements AbstractDocument.Content, Serializable |
61 |
{ |
{ |
62 |
|
|
63 |
/** |
/** |
64 |
* A {@link Position} implementation for <code>GapContent</code>. |
* A {@link Position} implementation for <code>GapContent</code>. |
65 |
*/ |
*/ |
66 |
class GapContentPosition implements Position, Comparable |
class GapContentPosition |
67 |
|
implements Position, Comparable |
68 |
{ |
{ |
69 |
|
|
70 |
/** The index within the buffer array. */ |
/** The index within the buffer array. */ |
72 |
|
|
73 |
/** |
/** |
74 |
* Creates a new GapContentPosition object. |
* Creates a new GapContentPosition object. |
75 |
* |
* |
76 |
* @param mark the mark of this Position |
* @param mark the mark of this Position |
77 |
*/ |
*/ |
78 |
GapContentPosition(int mark) |
GapContentPosition(int mark) |
83 |
/** |
/** |
84 |
* Comparable interface implementation. This is used to store all |
* Comparable interface implementation. This is used to store all |
85 |
* positions in an ordered fashion. |
* positions in an ordered fashion. |
86 |
* |
* |
87 |
* @param o the object to be compared to this |
* @param o the object to be compared to this |
88 |
* |
* |
89 |
* @return a negative integer if this is less than <code>o</code>, zero |
* @return a negative integer if this is less than <code>o</code>, zero |
90 |
* if both are equal or a positive integer if this is greater |
* if both are equal or a positive integer if this is greater than |
91 |
* than <code>o</code> |
* <code>o</code> |
92 |
* |
* |
93 |
* @throws ClassCastException if <code>o</code> is not a GapContentPosition |
* @throws ClassCastException if <code>o</code> is not a |
94 |
* or Integer object |
* GapContentPosition or Integer object |
95 |
*/ |
*/ |
96 |
public int compareTo(Object o) |
public int compareTo(Object o) |
97 |
{ |
{ |
98 |
if (o instanceof Integer) |
if (o instanceof Integer) |
99 |
{ |
{ |
100 |
int otherMark = ((Integer) o).intValue(); |
int otherMark = ((Integer) o).intValue(); |
101 |
return mark - otherMark; |
return mark - otherMark; |
102 |
} |
} |
103 |
else |
else |
104 |
{ |
{ |
105 |
GapContentPosition other = (GapContentPosition) o; |
GapContentPosition other = (GapContentPosition) o; |
106 |
return mark - other.mark; |
return mark - other.mark; |
107 |
} |
} |
108 |
} |
} |
109 |
|
|
110 |
/** |
/** |
111 |
* Returns the current offset of this Position within the content. |
* Returns the current offset of this Position within the content. |
112 |
* |
* |
113 |
* @return the current offset of this Position within the content. |
* @return the current offset of this Position within the content. |
114 |
*/ |
*/ |
115 |
public int getOffset() |
public int getOffset() |
116 |
{ |
{ |
117 |
if (mark <= gapStart) |
if (mark <= gapStart) |
118 |
return mark; |
return mark; |
119 |
else |
else |
120 |
return mark - (gapEnd - gapStart); |
return mark - (gapEnd - gapStart); |
121 |
} |
} |
122 |
} |
} |
123 |
|
|
124 |
private static final long serialVersionUID = 8374645204155842629L; |
private static final long serialVersionUID = 8374645204155842629L; |
125 |
|
|
126 |
/** |
/** |
127 |
* This is the default buffer size and the amount of bytes that |
* This is the default buffer size and the amount of bytes that a buffer is |
128 |
* a buffer is extended if it is full. |
* extended if it is full. |
129 |
*/ |
*/ |
130 |
static final int DEFAULT_BUFSIZE = 64; |
static final int DEFAULT_BUFSIZE = 64; |
131 |
|
|
145 |
int gapEnd; |
int gapEnd; |
146 |
|
|
147 |
/** |
/** |
148 |
* The positions generated by this GapContent. They are kept in an |
* The positions generated by this GapContent. They are kept in an ordered |
149 |
* ordered fashion, so they can be looked up easily. |
* fashion, so they can be looked up easily. |
150 |
*/ |
*/ |
151 |
LinkedList positions; |
LinkedList positions; |
152 |
|
|
160 |
|
|
161 |
/** |
/** |
162 |
* Creates a new GapContent object with a specified initial size. |
* Creates a new GapContent object with a specified initial size. |
163 |
* |
* |
164 |
* @param size the initial size of the buffer |
* @param size the initial size of the buffer |
165 |
*/ |
*/ |
166 |
public GapContent(int size) |
public GapContent(int size) |
175 |
/** |
/** |
176 |
* Allocates an array of the specified length that can then be used as |
* Allocates an array of the specified length that can then be used as |
177 |
* buffer. |
* buffer. |
178 |
* |
* |
179 |
* @param size the size of the array to be allocated |
* @param size the size of the array to be allocated |
180 |
* |
* |
181 |
* @return the allocated array |
* @return the allocated array |
182 |
*/ |
*/ |
183 |
protected Object allocateArray(int size) |
protected Object allocateArray(int size) |
187 |
|
|
188 |
/** |
/** |
189 |
* Returns the length of the allocated buffer array. |
* Returns the length of the allocated buffer array. |
190 |
* |
* |
191 |
* @return the length of the allocated buffer array |
* @return the length of the allocated buffer array |
192 |
*/ |
*/ |
193 |
protected int getArrayLength() |
protected int getArrayLength() |
197 |
|
|
198 |
/** |
/** |
199 |
* Returns the length of the content. |
* Returns the length of the content. |
200 |
* |
* |
201 |
* @return the length of the content |
* @return the length of the content |
202 |
*/ |
*/ |
203 |
public int length() |
public int length() |
207 |
|
|
208 |
/** |
/** |
209 |
* Inserts a string at the specified position. |
* Inserts a string at the specified position. |
210 |
* |
* |
211 |
* @param where the position where the string is inserted |
* @param where the position where the string is inserted |
212 |
* @param str the string that is to be inserted |
* @param str the string that is to be inserted |
213 |
* |
* |
214 |
* @return an UndoableEdit object (currently not supported, so |
* @return an UndoableEdit object (currently not supported, so |
215 |
* <code>null</code> is returned) |
* <code>null</code> is returned) |
216 |
* |
* |
217 |
* @throws BadLocationException if <code>where</code> is not a valid location |
* @throws BadLocationException if <code>where</code> is not a valid |
218 |
* in the buffer |
* location in the buffer |
219 |
*/ |
*/ |
220 |
public UndoableEdit insertString(int where, String str) |
public UndoableEdit insertString(int where, String str) |
221 |
throws BadLocationException |
throws BadLocationException |
222 |
{ |
{ |
223 |
// check arguments |
// check arguments |
224 |
int length = length(); |
int length = length(); |
226 |
|
|
227 |
if (where >= length) |
if (where >= length) |
228 |
throw new BadLocationException("the where argument cannot be greater" |
throw new BadLocationException("the where argument cannot be greater" |
229 |
+ " than the content length", where); |
+ " than the content length", where); |
230 |
|
|
231 |
// check if the gap is big enough to hold the string |
// check if the gap is big enough to hold the string |
232 |
if ((gapEnd - gapStart) < strLen) |
if ((gapEnd - gapStart) < strLen) |
246 |
|
|
247 |
/** |
/** |
248 |
* Removes a piece of content at th specified position. |
* Removes a piece of content at th specified position. |
249 |
* |
* |
250 |
* @param where the position where the content is to be removed |
* @param where the position where the content is to be removed |
251 |
* @param nitems number of characters to be removed |
* @param nitems number of characters to be removed |
252 |
* |
* |
253 |
* @return an UndoableEdit object (currently not supported, so |
* @return an UndoableEdit object (currently not supported, so |
254 |
* <code>null</code> is returned) |
* <code>null</code> is returned) |
255 |
* |
* |
256 |
* @throws BadLocationException if <code>where</code> is not a valid location |
* @throws BadLocationException if <code>where</code> is not a valid |
257 |
* in the buffer |
* location in the buffer |
258 |
*/ |
*/ |
259 |
public UndoableEdit remove(int where, int nitems) |
public UndoableEdit remove(int where, int nitems) throws BadLocationException |
|
throws BadLocationException |
|
260 |
{ |
{ |
261 |
// check arguments |
// check arguments |
262 |
int length = length(); |
int length = length(); |
263 |
|
|
264 |
if (where >= length) |
if (where >= length) |
265 |
throw new BadLocationException("the where argument cannot be greater" |
throw new BadLocationException("the where argument cannot be greater" |
266 |
+ " than the content length", where); |
+ " than the content length", where); |
267 |
if ((where + nitems) > length) |
if ((where + nitems) > length) |
268 |
throw new BadLocationException("where + nitems cannot be greater" |
throw new BadLocationException("where + nitems cannot be greater" |
269 |
+ " than the content length", |
+ " than the content length", where + nitems); |
|
where + nitems); |
|
270 |
|
|
271 |
// check if we are at the gap boundary |
// check if we are at the gap boundary |
272 |
if (where != gapStart) |
if (where != gapStart) |
279 |
|
|
280 |
/** |
/** |
281 |
* Returns a piece of content as String. |
* Returns a piece of content as String. |
282 |
* |
* |
283 |
* @param where the start location of the fragment |
* @param where the start location of the fragment |
284 |
* @param len the length of the fragment |
* @param len the length of the fragment |
285 |
* |
* |
286 |
* @throws BadLocationException if <code>where</code> or |
* @throws BadLocationException if <code>where</code> or |
287 |
* <code>where + len</code> are no valid locations in the buffer |
* <code>where + len</code> are no valid locations in the buffer |
288 |
*/ |
*/ |
295 |
|
|
296 |
/** |
/** |
297 |
* Fetches a piece of content and stores it in a {@link Segment} object. |
* Fetches a piece of content and stores it in a {@link Segment} object. |
298 |
* |
* |
299 |
* If the requested piece of text spans the gap, the content is copied |
* If the requested piece of text spans the gap, the content is copied into a |
300 |
* into a new array. If it doesn't then it is contiguous and the |
* new array. If it doesn't then it is contiguous and the actual content |
301 |
* actual content store is returned. |
* store is returned. |
302 |
* |
* |
303 |
* @param where the start location of the fragment |
* @param where the start location of the fragment |
304 |
* @param len the length of the fragment |
* @param len the length of the fragment |
305 |
* @param txt the Segment object to store the fragment in |
* @param txt the Segment object to store the fragment in |
306 |
* |
* |
307 |
* @throws BadLocationException if <code>where</code> or |
* @throws BadLocationException if <code>where</code> or |
308 |
* <code>where + len</code> are no valid locations in the buffer |
* <code>where + len</code> are no valid locations in the buffer |
309 |
*/ |
*/ |
310 |
public void getChars(int where, int len, Segment txt) |
public void getChars(int where, int len, Segment txt) |
311 |
throws BadLocationException |
throws BadLocationException |
312 |
{ |
{ |
313 |
// check arguments |
// check arguments |
314 |
int length = length(); |
int length = length(); |
315 |
if (where >= length) |
if (where >= length) |
316 |
throw new BadLocationException("the where argument cannot be greater" |
throw new BadLocationException("the where argument cannot be greater" |
317 |
+ " than the content length", where); |
+ " than the content length", where); |
318 |
if ((where + len) > length) |
if ((where + len) > length) |
319 |
throw new BadLocationException("len plus where cannot be greater" |
throw new BadLocationException("len plus where cannot be greater" |
320 |
+ " than the content length", |
+ " than the content length", len + where); |
|
len + where); |
|
321 |
|
|
322 |
// check if requested segment is contiguous |
// check if requested segment is contiguous |
323 |
if ((where < gapStart) && ((gapStart - where) < len)) |
if ((where < gapStart) && ((gapStart - where) < len)) |
324 |
{ |
{ |
325 |
// requested segment is not contiguous -> copy the pieces together |
// requested segment is not contiguous -> copy the pieces together |
326 |
char[] copy = new char[len]; |
char[] copy = new char[len]; |
327 |
int lenFirst = gapStart - where; // the length of the first segment |
int lenFirst = gapStart - where; // the length of the first segment |
328 |
System.arraycopy(buffer, where, copy, 0, lenFirst); |
System.arraycopy(buffer, where, copy, 0, lenFirst); |
329 |
System.arraycopy(buffer, gapEnd, copy, lenFirst, len - lenFirst); |
System.arraycopy(buffer, gapEnd, copy, lenFirst, len - lenFirst); |
330 |
txt.array = copy; |
txt.array = copy; |
331 |
txt.offset = 0; |
txt.offset = 0; |
332 |
txt.count = len; |
txt.count = len; |
333 |
} |
} |
334 |
else |
else |
335 |
{ |
{ |
336 |
// requested segment is contiguous -> we can simply return the |
// requested segment is contiguous -> we can simply return the |
337 |
// actual content |
// actual content |
338 |
txt.array = buffer; |
txt.array = buffer; |
339 |
if (where < gapStart) |
if (where < gapStart) |
340 |
txt.offset = where; |
txt.offset = where; |
341 |
else |
else |
342 |
txt.offset = where + (gapEnd - gapStart); |
txt.offset = where + (gapEnd - gapStart); |
343 |
txt.count = len; |
txt.count = len; |
344 |
} |
} |
345 |
} |
} |
346 |
|
|
347 |
/** |
/** |
348 |
* Creates and returns a mark at the specified position. |
* Creates and returns a mark at the specified position. |
349 |
* |
* |
350 |
* @param offset the position at which to create the mark |
* @param offset the position at which to create the mark |
351 |
* |
* |
352 |
* @return the create Position object for the mark |
* @return the create Position object for the mark |
353 |
* |
* |
354 |
* @throws BadLocationException if the offset is not a valid position in |
* @throws BadLocationException if the offset is not a valid position in the |
355 |
* the buffer |
* buffer |
356 |
*/ |
*/ |
357 |
public Position createPosition(final int offset) throws BadLocationException |
public Position createPosition(final int offset) throws BadLocationException |
358 |
{ |
{ |
359 |
if (offset < 0 || offset > length()) |
if (offset < 0 || offset > length()) |
360 |
throw new BadLocationException("The offset was out of the bounds of this" |
throw new BadLocationException("The offset was out of the bounds of this" |
361 |
+ " buffer", offset); |
+ " buffer", offset); |
362 |
|
|
363 |
// We store the actual array index in the GapContentPosition. The real |
// We store the actual array index in the GapContentPosition. The real |
364 |
// offset is then calculated in the GapContentPosition. |
// offset is then calculated in the GapContentPosition. |
372 |
if (index < 0) |
if (index < 0) |
373 |
index = -(index + 1); |
index = -(index + 1); |
374 |
positions.add(index, pos); |
positions.add(index, pos); |
375 |
|
|
376 |
return pos; |
return pos; |
377 |
} |
} |
378 |
|
|
379 |
/** |
/** |
380 |
* Enlarges the gap. This allocates a new bigger buffer array, copy the |
* Enlarges the gap. This allocates a new bigger buffer array, copy the |
381 |
* segment before the gap as it is and the segment after the gap at |
* segment before the gap as it is and the segment after the gap at the end |
382 |
* the end of the new buffer array. This does change the gapEnd mark |
* of the new buffer array. This does change the gapEnd mark but not the |
383 |
* but not the gapStart mark. |
* gapStart mark. |
384 |
* |
* |
385 |
* @param newSize the new size of the gap |
* @param newSize the new size of the gap |
386 |
*/ |
*/ |
387 |
protected void shiftEnd(int newSize) |
protected void shiftEnd(int newSize) |
389 |
int delta = (gapEnd - gapStart) - newSize; |
int delta = (gapEnd - gapStart) - newSize; |
390 |
char[] newBuf = (char[]) allocateArray(length() + newSize); |
char[] newBuf = (char[]) allocateArray(length() + newSize); |
391 |
System.arraycopy(buffer, 0, newBuf, 0, gapStart); |
System.arraycopy(buffer, 0, newBuf, 0, gapStart); |
392 |
System.arraycopy(buffer, gapEnd, newBuf, gapStart + newSize, |
System.arraycopy(buffer, gapEnd, newBuf, gapStart + newSize, buffer.length |
393 |
buffer.length - gapEnd); |
- gapEnd); |
394 |
gapEnd = gapStart + newSize; |
gapEnd = gapStart + newSize; |
395 |
buffer = newBuf; |
buffer = newBuf; |
396 |
|
|
397 |
// Update the marks after the gapEnd. |
// Update the marks after the gapEnd. |
398 |
int index = Collections.binarySearch(positions, new Integer(gapEnd)); |
int index = Collections.binarySearch(positions, new GapContentPosition( |
399 |
|
gapEnd)); |
400 |
if (index < 0) |
if (index < 0) |
401 |
{ |
{ |
402 |
index = -(index + 1); |
index = -(index + 1); |
403 |
} |
} |
404 |
for (ListIterator i = positions.listIterator(index); i.hasNext();) |
for (ListIterator i = positions.listIterator(index); i.hasNext();) |
405 |
{ |
{ |
406 |
GapContentPosition p = (GapContentPosition) i.next(); |
GapContentPosition p = (GapContentPosition) i.next(); |
407 |
p.mark += delta; |
p.mark += delta; |
408 |
} |
} |
409 |
} |
} |
410 |
|
|
411 |
/** |
/** |
412 |
* Shifts the gap to the specified position. |
* Shifts the gap to the specified position. |
413 |
* |
* |
414 |
* @param newGapStart the new start position of the gap |
* @param newGapStart the new start position of the gap |
415 |
*/ |
*/ |
416 |
protected void shiftGap(int newGapStart) |
protected void shiftGap(int newGapStart) |
417 |
{ |
{ |
418 |
int newGapEnd = newGapStart + (gapEnd - gapStart); |
int newGapEnd = newGapStart + (gapEnd - gapStart); |
419 |
|
|
|
|
|
420 |
// Update the positions between newGapEnd and (old) gapEnd. The marks |
// Update the positions between newGapEnd and (old) gapEnd. The marks |
421 |
// must be shifted by (gapEnd - newGapEnd). |
// must be shifted by (gapEnd - newGapEnd). |
422 |
int index1 = Collections.binarySearch(positions, new Integer(gapEnd)); |
int index1 = Collections.binarySearch(positions, new GapContentPosition( |
423 |
int index2 = Collections.binarySearch(positions, new Integer(newGapEnd)); |
gapEnd)); |
424 |
int i1 = Math.min(index1, index2); |
int index2 = Collections.binarySearch(positions, new GapContentPosition( |
425 |
int i2 = Math.max(index1, index2); |
newGapEnd)); |
426 |
for (ListIterator i = positions.listIterator(i1); i.hasNext();) |
if (index1 > 0 && index2 > 0) |
427 |
|
{ |
428 |
|
int i1 = Math.min(index1, index2); |
429 |
|
int i2 = Math.max(index1, index2); |
430 |
|
for (ListIterator i = positions.listIterator(i1); i.hasNext();) |
431 |
{ |
{ |
432 |
if (i.nextIndex() > i2) |
if (i.nextIndex() > i2) |
433 |
break; |
break; |
434 |
|
|
435 |
GapContentPosition p = (GapContentPosition) i.next(); |
GapContentPosition p = (GapContentPosition) i.next(); |
436 |
p.mark += gapEnd - newGapEnd; |
p.mark += gapEnd - newGapEnd; |
437 |
} |
} |
438 |
|
|
439 |
if (newGapStart == gapStart) |
if (newGapStart == gapStart) |
440 |
return; |
return; |
441 |
else if (newGapStart < gapStart) |
else if (newGapStart < gapStart) |
442 |
{ |
{ |
443 |
System.arraycopy(buffer, newGapStart, buffer, newGapEnd, |
System.arraycopy(buffer, newGapStart, buffer, newGapEnd, gapStart |
444 |
gapStart - newGapStart); |
- newGapStart); |
445 |
gapStart = newGapStart; |
gapStart = newGapStart; |
446 |
gapEnd = newGapEnd; |
gapEnd = newGapEnd; |
447 |
} |
} |
448 |
else |
else |
449 |
{ |
{ |
450 |
System.arraycopy(buffer, gapEnd, buffer, gapStart, |
System.arraycopy(buffer, gapEnd, buffer, gapStart, newGapStart |
451 |
newGapStart - gapStart); |
- gapStart); |
452 |
gapStart = newGapStart; |
gapStart = newGapStart; |
453 |
gapEnd = newGapEnd; |
gapEnd = newGapEnd; |
454 |
} |
} |
455 |
|
} |
456 |
} |
} |
457 |
|
|
458 |
/** |
/** |
459 |
* Returns the allocated buffer array. |
* Returns the allocated buffer array. |
460 |
* |
* |
461 |
* @return the allocated buffer array |
* @return the allocated buffer array |
462 |
*/ |
*/ |
463 |
protected Object getArray() |
protected Object getArray() |
467 |
|
|
468 |
/** |
/** |
469 |
* Replaces a portion of the storage with the specified items. |
* Replaces a portion of the storage with the specified items. |
470 |
* |
* |
471 |
* @param position the position at which to remove items |
* @param position the position at which to remove items |
472 |
* @param rmSize the number of items to remove |
* @param rmSize the number of items to remove |
473 |
* @param addItems the items to add at location |
* @param addItems the items to add at location |
474 |
* @param addSize the number of items to add |
* @param addSize the number of items to add |
475 |
*/ |
*/ |
476 |
protected void replace(int position, int rmSize, Object addItems, |
protected void replace(int position, int rmSize, Object addItems, int addSize) |
|
int addSize) |
|
477 |
{ |
{ |
478 |
// Remove content |
// Remove content |
479 |
shiftGap(position); |
shiftGap(position); |