2 Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
39 package javax
.swing
.text
;
41 import java
.io
.Serializable
;
42 import java
.util
.ArrayList
;
43 import java
.util
.Collections
;
44 import java
.util
.Iterator
;
45 import java
.util
.ListIterator
;
46 import java
.util
.Vector
;
48 import javax
.swing
.undo
.AbstractUndoableEdit
;
49 import javax
.swing
.undo
.CannotRedoException
;
50 import javax
.swing
.undo
.CannotUndoException
;
51 import javax
.swing
.undo
.UndoableEdit
;
54 * This implementation of {@link AbstractDocument.Content} uses a gapped buffer.
55 * This takes advantage of the fact that text area content is mostly inserted
56 * sequentially. The buffer is a char array that maintains a gap at the current
57 * insertion point. If characters a inserted at gap boundaries, the cost is
58 * minimal (simple array access). The array only has to be shifted around when
59 * the insertion point moves (then the gap also moves and one array copy is
60 * necessary) or when the gap is filled up and the buffer has to be enlarged.
62 * TODO: Implement UndoableEdit support stuff
64 public class GapContent
65 implements AbstractDocument
.Content
, Serializable
69 * A {@link Position} implementation for <code>GapContent</code>.
71 class GapContentPosition
72 implements Position
, Comparable
75 /** The index within the buffer array. */
79 * Creates a new GapContentPosition object.
81 * @param mark the mark of this Position
83 GapContentPosition(int mark
)
89 * Comparable interface implementation. This is used to store all
90 * positions in an ordered fashion.
92 * @param o the object to be compared to this
94 * @return a negative integer if this is less than <code>o</code>, zero
95 * if both are equal or a positive integer if this is greater than
98 * @throws ClassCastException if <code>o</code> is not a
99 * GapContentPosition or Integer object
101 public int compareTo(Object o
)
103 if (o
instanceof Integer
)
105 int otherMark
= ((Integer
) o
).intValue();
106 return mark
- otherMark
;
110 GapContentPosition other
= (GapContentPosition
) o
;
111 return mark
- other
.mark
;
116 * Returns the current offset of this Position within the content.
118 * @return the current offset of this Position within the content.
120 public int getOffset()
122 // Check precondition.
123 assert mark
<= gapStart
|| mark
>= gapEnd
: "mark: " + mark
124 + ", gapStart: " + gapStart
125 + ", gapEnd: " + gapEnd
;
126 if (mark
<= gapStart
)
129 return mark
- (gapEnd
- gapStart
);
133 class InsertUndo
extends AbstractUndoableEdit
135 public int where
, length
;
137 public InsertUndo(int start
, int len
)
143 public void undo () throws CannotUndoException
148 text
= getString(where
, length
);
149 remove(where
, length
);
151 catch (BadLocationException ble
)
153 throw new CannotUndoException();
157 public void redo () throws CannotUndoException
162 insertString(where
, text
);
164 catch (BadLocationException ble
)
166 throw new CannotRedoException();
172 class UndoRemove
extends AbstractUndoableEdit
176 public UndoRemove(int start
, String removedText
)
182 public void undo () throws CannotUndoException
187 insertString(where
, text
);
189 catch (BadLocationException ble
)
191 throw new CannotUndoException();
195 public void redo () throws CannotUndoException
200 remove(where
, text
.length());
202 catch (BadLocationException ble
)
204 throw new CannotRedoException();
210 /** The serialization UID (compatible with JDK1.5). */
211 private static final long serialVersionUID
= -6226052713477823730L;
214 * This is the default buffer size and the amount of bytes that a buffer is
215 * extended if it is full.
217 static final int DEFAULT_BUFSIZE
= 10;
225 * The index of the first character of the gap.
230 * The index of the character after the last character of the gap.
235 * The positions generated by this GapContent. They are kept in an ordered
236 * fashion, so they can be looked up easily.
241 * Creates a new GapContent object.
245 this(DEFAULT_BUFSIZE
);
249 * Creates a new GapContent object with a specified initial size.
251 * @param size the initial size of the buffer
253 public GapContent(int size
)
255 buffer
= (char[]) allocateArray(size
);
259 positions
= new ArrayList();
263 * Allocates an array of the specified length that can then be used as
266 * @param size the size of the array to be allocated
268 * @return the allocated array
270 protected Object
allocateArray(int size
)
272 return new char[size
];
276 * Returns the length of the allocated buffer array.
278 * @return the length of the allocated buffer array
280 protected int getArrayLength()
282 return buffer
.length
;
286 * Returns the length of the content.
288 * @return the length of the content
292 return buffer
.length
- (gapEnd
- gapStart
);
296 * Inserts a string at the specified position.
298 * @param where the position where the string is inserted
299 * @param str the string that is to be inserted
301 * @return an UndoableEdit object
303 * @throws BadLocationException if <code>where</code> is not a valid
304 * location in the buffer
306 public UndoableEdit
insertString(int where
, String str
)
307 throws BadLocationException
310 int length
= length();
311 int strLen
= str
.length();
314 throw new BadLocationException("the where argument cannot be greater"
315 + " than the content length", where
);
317 replace(where
, 0, str
.toCharArray(), strLen
);
319 return new InsertUndo(where
, strLen
);
323 * Removes a piece of content at th specified position.
325 * @param where the position where the content is to be removed
326 * @param nitems number of characters to be removed
328 * @return an UndoableEdit object
330 * @throws BadLocationException if <code>where</code> is not a valid
331 * location in the buffer
333 public UndoableEdit
remove(int where
, int nitems
) throws BadLocationException
336 int length
= length();
339 throw new BadLocationException("the where argument cannot be greater"
340 + " than the content length", where
);
341 if ((where
+ nitems
) > length
)
342 throw new BadLocationException("where + nitems cannot be greater"
343 + " than the content length", where
+ nitems
);
345 String removedText
= getString(where
, nitems
);
346 replace(where
, nitems
, null, 0);
348 return new UndoRemove(where
, removedText
);
352 * Returns a piece of content as String.
354 * @param where the start location of the fragment
355 * @param len the length of the fragment
357 * @throws BadLocationException if <code>where</code> or
358 * <code>where + len</code> are no valid locations in the buffer
360 public String
getString(int where
, int len
) throws BadLocationException
362 Segment seg
= new Segment();
365 getChars(where
, len
, seg
);
366 return new String(seg
.array
, seg
.offset
, seg
.count
);
368 catch (StringIndexOutOfBoundsException ex
)
371 if (seg
.offset
< 0 || seg
.offset
>= seg
.array
.length
)
372 invalid
= seg
.offset
;
374 invalid
= seg
.offset
+ seg
.count
;
375 throw new BadLocationException("Illegal location: array.length = "
376 + seg
.array
.length
+ ", offset = "
377 + seg
.offset
+ ", count = "
378 + seg
.count
, invalid
);
383 * Fetches a piece of content and stores it in a {@link Segment} object.
385 * If the requested piece of text spans the gap, the content is copied into a
386 * new array. If it doesn't then it is contiguous and the actual content
389 * @param where the start location of the fragment
390 * @param len the length of the fragment
391 * @param txt the Segment object to store the fragment in
393 * @throws BadLocationException if <code>where</code> or
394 * <code>where + len</code> are no valid locations in the buffer
396 public void getChars(int where
, int len
, Segment txt
)
397 throws BadLocationException
400 int length
= length();
402 throw new BadLocationException("the where argument cannot be greater"
403 + " than the content length", where
);
404 if ((where
+ len
) > length
)
405 throw new BadLocationException("len plus where cannot be greater"
406 + " than the content length", len
+ where
);
408 // check if requested segment is contiguous
409 if ((where
< gapStart
) && ((gapStart
- where
) < len
))
411 // requested segment is not contiguous -> copy the pieces together
412 char[] copy
= new char[len
];
413 int lenFirst
= gapStart
- where
; // the length of the first segment
414 System
.arraycopy(buffer
, where
, copy
, 0, lenFirst
);
415 System
.arraycopy(buffer
, gapEnd
, copy
, lenFirst
, len
- lenFirst
);
422 // requested segment is contiguous -> we can simply return the
425 if (where
< gapStart
)
428 txt
.offset
= where
+ (gapEnd
- gapStart
);
434 * Creates and returns a mark at the specified position.
436 * @param offset the position at which to create the mark
438 * @return the create Position object for the mark
440 * @throws BadLocationException if the offset is not a valid position in the
443 public Position
createPosition(final int offset
) throws BadLocationException
445 if (offset
< 0 || offset
> length())
446 throw new BadLocationException("The offset was out of the bounds of this"
447 + " buffer", offset
);
449 // We store the actual array index in the GapContentPosition. The real
450 // offset is then calculated in the GapContentPosition.
452 if (offset
>= gapStart
)
453 mark
+= gapEnd
- gapStart
;
454 GapContentPosition pos
= new GapContentPosition(mark
);
456 // Add this into our list in a sorted fashion.
457 int index
= Collections
.binarySearch(positions
, pos
);
459 index
= -(index
+ 1);
460 positions
.add(index
, pos
);
465 * Enlarges the gap. This allocates a new bigger buffer array, copy the
466 * segment before the gap as it is and the segment after the gap at the end
467 * of the new buffer array. This does change the gapEnd mark but not the
470 * @param newSize the new size of the gap
472 protected void shiftEnd(int newSize
)
474 assert newSize
> (gapEnd
- gapStart
) : "The new gap size must be greater "
475 + "than the old gap size";
477 int delta
= newSize
- gapEnd
+ gapStart
;
478 // Update the marks after the gapEnd.
479 adjustPositionsInRange(gapEnd
, buffer
.length
- gapEnd
, delta
);
481 // Copy the data around.
482 char[] newBuf
= (char[]) allocateArray(length() + newSize
);
483 System
.arraycopy(buffer
, 0, newBuf
, 0, gapStart
);
484 System
.arraycopy(buffer
, gapEnd
, newBuf
, gapStart
+ newSize
, buffer
.length
486 gapEnd
= gapStart
+ newSize
;
492 * Shifts the gap to the specified position.
494 * @param newGapStart the new start position of the gap
496 protected void shiftGap(int newGapStart
)
498 if (newGapStart
== gapStart
)
501 int newGapEnd
= newGapStart
+ gapEnd
- gapStart
;
502 if (newGapStart
< gapStart
)
504 // Update the positions between newGapStart and (old) gapStart. The marks
505 // must be shifted by (gapEnd - gapStart).
506 adjustPositionsInRange(newGapStart
, gapStart
- newGapStart
, gapEnd
- gapStart
);
507 System
.arraycopy(buffer
, newGapStart
, buffer
, newGapEnd
, gapStart
509 gapStart
= newGapStart
;
514 // Update the positions between newGapEnd and (old) gapEnd. The marks
515 // must be shifted by (gapEnd - gapStart).
516 adjustPositionsInRange(gapEnd
, newGapEnd
- gapEnd
, -(gapEnd
- gapStart
));
517 System
.arraycopy(buffer
, gapEnd
, buffer
, gapStart
, newGapStart
519 gapStart
= newGapStart
;
527 * Shifts the gap start downwards. This does not affect the content of the
528 * buffer. This only updates the gap start and all the marks that are between
529 * the old gap start and the new gap start. They all are squeezed to the start
530 * of the gap, because their location has been removed.
532 * @param newGapStart the new gap start
534 protected void shiftGapStartDown(int newGapStart
)
536 if (newGapStart
== gapStart
)
539 assert newGapStart
< gapStart
: "The new gap start must be less than the "
541 setPositionsInRange(newGapStart
, gapStart
- newGapStart
, gapStart
);
542 gapStart
= newGapStart
;
546 * Shifts the gap end upwards. This does not affect the content of the
547 * buffer. This only updates the gap end and all the marks that are between
548 * the old gap end and the new end start. They all are squeezed to the end
549 * of the gap, because their location has been removed.
551 * @param newGapEnd the new gap start
553 protected void shiftGapEndUp(int newGapEnd
)
555 if (newGapEnd
== gapEnd
)
558 assert newGapEnd
> gapEnd
: "The new gap end must be greater than the "
560 setPositionsInRange(gapEnd
, newGapEnd
- gapEnd
, newGapEnd
+ 1);
565 * Returns the allocated buffer array.
567 * @return the allocated buffer array
569 protected Object
getArray()
575 * Replaces a portion of the storage with the specified items.
577 * @param position the position at which to remove items
578 * @param rmSize the number of items to remove
579 * @param addItems the items to add at location
580 * @param addSize the number of items to add
582 protected void replace(int position
, int rmSize
, Object addItems
,
585 if (gapStart
!= position
)
590 shiftGapEndUp(gapEnd
+ rmSize
);
592 // If gap is too small, enlarge the gap.
593 if ((gapEnd
- gapStart
) <= addSize
)
594 shiftEnd((addSize
- gapEnd
+ gapStart
+ 1) * 2 + gapEnd
+ DEFAULT_BUFSIZE
);
596 // Add new items to the buffer.
597 if (addItems
!= null)
599 System
.arraycopy(addItems
, 0, buffer
, gapStart
, addSize
);
605 * Returns the start index of the gap within the buffer array.
607 * @return the start index of the gap within the buffer array
609 protected final int getGapStart()
615 * Returns the end index of the gap within the buffer array.
617 * @return the end index of the gap within the buffer array
619 protected final int getGapEnd()
625 * Returns all <code>Position</code>s that are in the range specified by
626 * <code>offset</code> and </code>length</code> within the buffer array.
628 * @param v the vector to use; if <code>null</code>, a new Vector is allocated
629 * @param offset the start offset of the range to search
630 * @param length the length of the range to search
632 * @return the positions within the specified range
634 protected Vector
getPositionsInRange(Vector v
, int offset
, int length
)
642 int endOffset
= offset
+ length
;
644 int index1
= Collections
.binarySearch(positions
,
645 new GapContentPosition(offset
));
647 index1
= -(index1
+ 1);
649 // Search the first index with the specified offset. The binarySearch does
650 // not necessarily find the first one.
652 && ((GapContentPosition
) positions
.get(index1
- 1)).mark
== offset
)
655 for (ListIterator i
= positions
.listIterator(index1
); i
.hasNext();)
657 GapContentPosition p
= (GapContentPosition
) i
.next();
658 if (p
.mark
> endOffset
)
660 if (p
.mark
>= offset
&& p
.mark
<= endOffset
)
667 * Sets the mark of all <code>Position</code>s that are in the range
668 * specified by <code>offset</code> and </code>length</code> within
669 * the buffer array to <code>value</code>
671 * @param offset the start offset of the range to search
672 * @param length the length of the range to search
673 * @param value the new value for each mark
675 void setPositionsInRange(int offset
, int length
, int value
)
677 int endOffset
= offset
+ length
;
679 int index1
= Collections
.binarySearch(positions
,
680 new GapContentPosition(offset
));
682 index1
= -(index1
+ 1);
684 // Search the first index with the specified offset. The binarySearch does
685 // not necessarily find the first one.
687 && ((GapContentPosition
) positions
.get(index1
- 1)).mark
== offset
)
690 for (ListIterator i
= positions
.listIterator(index1
); i
.hasNext();)
692 GapContentPosition p
= (GapContentPosition
) i
.next();
693 if (p
.mark
> endOffset
)
696 if (p
.mark
>= offset
&& p
.mark
<= endOffset
)
702 * Adjusts the mark of all <code>Position</code>s that are in the range
703 * specified by <code>offset</code> and </code>length</code> within
704 * the buffer array by <code>increment</code>
706 * @param offset the start offset of the range to search
707 * @param length the length of the range to search
708 * @param incr the increment
710 void adjustPositionsInRange(int offset
, int length
, int incr
)
712 int endOffset
= offset
+ length
;
714 int index1
= Collections
.binarySearch(positions
,
715 new GapContentPosition(offset
));
717 index1
= -(index1
+ 1);
719 // Search the first index with the specified offset. The binarySearch does
720 // not necessarily find the first one.
722 && ((GapContentPosition
) positions
.get(index1
- 1)).mark
== offset
)
724 for (ListIterator i
= positions
.listIterator(index1
); i
.hasNext();)
726 GapContentPosition p
= (GapContentPosition
) i
.next();
727 if (p
.mark
> endOffset
)
730 if (p
.mark
>= offset
&& p
.mark
<= endOffset
)
736 * Resets all <code>Position</code> that have an offset of <code>0</code>,
737 * to also have an array index of <code>0</code>. This might be necessary
738 * after a call to <code>shiftGap(0)</code>, since then the marks at offset
739 * <code>0</code> get shifted to <code>gapEnd</code>.
741 protected void resetMarksAtZero()
746 setPositionsInRange(gapEnd
, 0, 0);
750 * Outputs debugging info to System.err. It prints out the buffer array,
751 * the gapStart is marked by a < sign, the gapEnd is marked by a >
752 * sign and each position is marked by a # sign.
756 System
.err
.println("GapContent debug information");
757 System
.err
.println("buffer length: " + buffer
.length
);
758 System
.err
.println("gap start: " + gapStart
);
759 System
.err
.println("gap end: " + gapEnd
);
760 for (int i
= 0; i
< buffer
.length
; i
++)
763 System
.err
.print('<');
765 System
.err
.print('>');
767 if (!Character
.isISOControl(buffer
[i
]))
768 System
.err
.print(buffer
[i
]);
770 System
.err
.print('.');
772 System
.err
.println();
775 private void dumpPositions()
777 for (Iterator i
= positions
.iterator(); i
.hasNext();)
779 GapContentPosition pos
= (GapContentPosition
) i
.next();
780 System
.err
.println("position at: " + pos
.mark
);