blob: d51ca95d261f89ea2e080d1e0daf7b764c46e35a [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1997-2001 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 */
25package javax.swing.text;
26
27import java.util.Vector;
28import java.io.Serializable;
29import javax.swing.undo.*;
30import javax.swing.SwingUtilities;
31
32/**
33 * An implementation of the AbstractDocument.Content interface that is
34 * a brute force implementation that is useful for relatively small
35 * documents and/or debugging. It manages the character content
36 * as a simple character array. It is also quite inefficient.
37 * <p>
38 * It is generally recommended that the gap buffer or piece table
39 * implementations be used instead. This buffer does not scale up
40 * to large sizes.
41 * <p>
42 * <strong>Warning:</strong>
43 * Serialized objects of this class will not be compatible with
44 * future Swing releases. The current serialization support is
45 * appropriate for short term storage or RMI between applications running
46 * the same version of Swing. As of 1.4, support for long term storage
47 * of all JavaBeans<sup><font size="-2">TM</font></sup>
48 * has been added to the <code>java.beans</code> package.
49 * Please see {@link java.beans.XMLEncoder}.
50 *
51 * @author Timothy Prinzing
52 */
53public final class StringContent implements AbstractDocument.Content, Serializable {
54
55 /**
56 * Creates a new StringContent object. Initial size defaults to 10.
57 */
58 public StringContent() {
59 this(10);
60 }
61
62 /**
63 * Creates a new StringContent object, with the initial
64 * size specified. If the length is < 1, a size of 1 is used.
65 *
66 * @param initialLength the initial size
67 */
68 public StringContent(int initialLength) {
69 if (initialLength < 1) {
70 initialLength = 1;
71 }
72 data = new char[initialLength];
73 data[0] = '\n';
74 count = 1;
75 }
76
77 /**
78 * Returns the length of the content.
79 *
80 * @return the length >= 1
81 * @see AbstractDocument.Content#length
82 */
83 public int length() {
84 return count;
85 }
86
87 /**
88 * Inserts a string into the content.
89 *
90 * @param where the starting position >= 0 && < length()
91 * @param str the non-null string to insert
92 * @return an UndoableEdit object for undoing
93 * @exception BadLocationException if the specified position is invalid
94 * @see AbstractDocument.Content#insertString
95 */
96 public UndoableEdit insertString(int where, String str) throws BadLocationException {
97 if (where >= count || where < 0) {
98 throw new BadLocationException("Invalid location", count);
99 }
100 char[] chars = str.toCharArray();
101 replace(where, 0, chars, 0, chars.length);
102 if (marks != null) {
103 updateMarksForInsert(where, str.length());
104 }
105 return new InsertUndo(where, str.length());
106 }
107
108 /**
109 * Removes part of the content. where + nitems must be < length().
110 *
111 * @param where the starting position >= 0
112 * @param nitems the number of characters to remove >= 0
113 * @return an UndoableEdit object for undoing
114 * @exception BadLocationException if the specified position is invalid
115 * @see AbstractDocument.Content#remove
116 */
117 public UndoableEdit remove(int where, int nitems) throws BadLocationException {
118 if (where + nitems >= count) {
119 throw new BadLocationException("Invalid range", count);
120 }
121 String removedString = getString(where, nitems);
122 UndoableEdit edit = new RemoveUndo(where, removedString);
123 replace(where, nitems, empty, 0, 0);
124 if (marks != null) {
125 updateMarksForRemove(where, nitems);
126 }
127 return edit;
128
129 }
130
131 /**
132 * Retrieves a portion of the content. where + len must be <= length().
133 *
134 * @param where the starting position >= 0
135 * @param len the length to retrieve >= 0
136 * @return a string representing the content; may be empty
137 * @exception BadLocationException if the specified position is invalid
138 * @see AbstractDocument.Content#getString
139 */
140 public String getString(int where, int len) throws BadLocationException {
141 if (where + len > count) {
142 throw new BadLocationException("Invalid range", count);
143 }
144 return new String(data, where, len);
145 }
146
147 /**
148 * Retrieves a portion of the content. where + len must be <= length()
149 *
150 * @param where the starting position >= 0
151 * @param len the number of characters to retrieve >= 0
152 * @param chars the Segment object to return the characters in
153 * @exception BadLocationException if the specified position is invalid
154 * @see AbstractDocument.Content#getChars
155 */
156 public void getChars(int where, int len, Segment chars) throws BadLocationException {
157 if (where + len > count) {
158 throw new BadLocationException("Invalid location", count);
159 }
160 chars.array = data;
161 chars.offset = where;
162 chars.count = len;
163 }
164
165 /**
166 * Creates a position within the content that will
167 * track change as the content is mutated.
168 *
169 * @param offset the offset to create a position for >= 0
170 * @return the position
171 * @exception BadLocationException if the specified position is invalid
172 */
173 public Position createPosition(int offset) throws BadLocationException {
174 // some small documents won't have any sticky positions
175 // at all, so the buffer is created lazily.
176 if (marks == null) {
177 marks = new Vector();
178 }
179 return new StickyPosition(offset);
180 }
181
182 // --- local methods ---------------------------------------
183
184 /**
185 * Replaces some of the characters in the array
186 * @param offset offset into the array to start the replace
187 * @param length number of characters to remove
188 * @param replArray replacement array
189 * @param replOffset offset into the replacement array
190 * @param replLength number of character to use from the
191 * replacement array.
192 */
193 void replace(int offset, int length,
194 char[] replArray, int replOffset, int replLength) {
195 int delta = replLength - length;
196 int src = offset + length;
197 int nmove = count - src;
198 int dest = src + delta;
199 if ((count + delta) >= data.length) {
200 // need to grow the array
201 int newLength = Math.max(2*data.length, count + delta);
202 char[] newData = new char[newLength];
203 System.arraycopy(data, 0, newData, 0, offset);
204 System.arraycopy(replArray, replOffset, newData, offset, replLength);
205 System.arraycopy(data, src, newData, dest, nmove);
206 data = newData;
207 } else {
208 // patch the existing array
209 System.arraycopy(data, src, data, dest, nmove);
210 System.arraycopy(replArray, replOffset, data, offset, replLength);
211 }
212 count = count + delta;
213 }
214
215 void resize(int ncount) {
216 char[] ndata = new char[ncount];
217 System.arraycopy(data, 0, ndata, 0, Math.min(ncount, count));
218 data = ndata;
219 }
220
221 synchronized void updateMarksForInsert(int offset, int length) {
222 if (offset == 0) {
223 // zero is a special case where we update only
224 // marks after it.
225 offset = 1;
226 }
227 int n = marks.size();
228 for (int i = 0; i < n; i++) {
229 PosRec mark = (PosRec) marks.elementAt(i);
230 if (mark.unused) {
231 // this record is no longer used, get rid of it
232 marks.removeElementAt(i);
233 i -= 1;
234 n -= 1;
235 } else if (mark.offset >= offset) {
236 mark.offset += length;
237 }
238 }
239 }
240
241 synchronized void updateMarksForRemove(int offset, int length) {
242 int n = marks.size();
243 for (int i = 0; i < n; i++) {
244 PosRec mark = (PosRec) marks.elementAt(i);
245 if (mark.unused) {
246 // this record is no longer used, get rid of it
247 marks.removeElementAt(i);
248 i -= 1;
249 n -= 1;
250 } else if (mark.offset >= (offset + length)) {
251 mark.offset -= length;
252 } else if (mark.offset >= offset) {
253 mark.offset = offset;
254 }
255 }
256 }
257
258 /**
259 * Returns a Vector containing instances of UndoPosRef for the
260 * Positions in the range
261 * <code>offset</code> to <code>offset</code> + <code>length</code>.
262 * If <code>v</code> is not null the matching Positions are placed in
263 * there. The vector with the resulting Positions are returned.
264 * <p>
265 * This is meant for internal usage, and is generally not of interest
266 * to subclasses.
267 *
268 * @param v the Vector to use, with a new one created on null
269 * @param offset the starting offset >= 0
270 * @param length the length >= 0
271 * @return the set of instances
272 */
273 protected Vector getPositionsInRange(Vector v, int offset,
274 int length) {
275 int n = marks.size();
276 int end = offset + length;
277 Vector placeIn = (v == null) ? new Vector() : v;
278 for (int i = 0; i < n; i++) {
279 PosRec mark = (PosRec) marks.elementAt(i);
280 if (mark.unused) {
281 // this record is no longer used, get rid of it
282 marks.removeElementAt(i);
283 i -= 1;
284 n -= 1;
285 } else if(mark.offset >= offset && mark.offset <= end)
286 placeIn.addElement(new UndoPosRef(mark));
287 }
288 return placeIn;
289 }
290
291 /**
292 * Resets the location for all the UndoPosRef instances
293 * in <code>positions</code>.
294 * <p>
295 * This is meant for internal usage, and is generally not of interest
296 * to subclasses.
297 *
298 * @param positions the positions of the instances
299 */
300 protected void updateUndoPositions(Vector positions) {
301 for(int counter = positions.size() - 1; counter >= 0; counter--) {
302 UndoPosRef ref = (UndoPosRef)positions.elementAt(counter);
303 // Check if the Position is still valid.
304 if(ref.rec.unused) {
305 positions.removeElementAt(counter);
306 }
307 else
308 ref.resetLocation();
309 }
310 }
311
312 private static final char[] empty = new char[0];
313 private char[] data;
314 private int count;
315 transient Vector marks;
316
317 /**
318 * holds the data for a mark... separately from
319 * the real mark so that the real mark can be
320 * collected if there are no more references to
321 * it.... the update table holds only a reference
322 * to this grungy thing.
323 */
324 final class PosRec {
325
326 PosRec(int offset) {
327 this.offset = offset;
328 }
329
330 int offset;
331 boolean unused;
332 }
333
334 /**
335 * This really wants to be a weak reference but
336 * in 1.1 we don't have a 100% pure solution for
337 * this... so this class trys to hack a solution
338 * to causing the marks to be collected.
339 */
340 final class StickyPosition implements Position {
341
342 StickyPosition(int offset) {
343 rec = new PosRec(offset);
344 marks.addElement(rec);
345 }
346
347 public int getOffset() {
348 return rec.offset;
349 }
350
351 protected void finalize() throws Throwable {
352 // schedule the record to be removed later
353 // on another thread.
354 rec.unused = true;
355 }
356
357 public String toString() {
358 return Integer.toString(getOffset());
359 }
360
361 PosRec rec;
362 }
363
364 /**
365 * Used to hold a reference to a Position that is being reset as the
366 * result of removing from the content.
367 */
368 final class UndoPosRef {
369 UndoPosRef(PosRec rec) {
370 this.rec = rec;
371 this.undoLocation = rec.offset;
372 }
373
374 /**
375 * Resets the location of the Position to the offset when the
376 * receiver was instantiated.
377 */
378 protected void resetLocation() {
379 rec.offset = undoLocation;
380 }
381
382 /** Location to reset to when resetLocatino is invoked. */
383 protected int undoLocation;
384 /** Position to reset offset. */
385 protected PosRec rec;
386 }
387
388 /**
389 * UnoableEdit created for inserts.
390 */
391 class InsertUndo extends AbstractUndoableEdit {
392 protected InsertUndo(int offset, int length) {
393 super();
394 this.offset = offset;
395 this.length = length;
396 }
397
398 public void undo() throws CannotUndoException {
399 super.undo();
400 try {
401 synchronized(StringContent.this) {
402 // Get the Positions in the range being removed.
403 if(marks != null)
404 posRefs = getPositionsInRange(null, offset, length);
405 string = getString(offset, length);
406 remove(offset, length);
407 }
408 } catch (BadLocationException bl) {
409 throw new CannotUndoException();
410 }
411 }
412
413 public void redo() throws CannotRedoException {
414 super.redo();
415 try {
416 synchronized(StringContent.this) {
417 insertString(offset, string);
418 string = null;
419 // Update the Positions that were in the range removed.
420 if(posRefs != null) {
421 updateUndoPositions(posRefs);
422 posRefs = null;
423 }
424 }
425 } catch (BadLocationException bl) {
426 throw new CannotRedoException();
427 }
428 }
429
430 // Where the string goes.
431 protected int offset;
432 // Length of the string.
433 protected int length;
434 // The string that was inserted. To cut down on space needed this
435 // will only be valid after an undo.
436 protected String string;
437 // An array of instances of UndoPosRef for the Positions in the
438 // range that was removed, valid after undo.
439 protected Vector posRefs;
440 }
441
442
443 /**
444 * UndoableEdit created for removes.
445 */
446 class RemoveUndo extends AbstractUndoableEdit {
447 protected RemoveUndo(int offset, String string) {
448 super();
449 this.offset = offset;
450 this.string = string;
451 this.length = string.length();
452 if(marks != null)
453 posRefs = getPositionsInRange(null, offset, length);
454 }
455
456 public void undo() throws CannotUndoException {
457 super.undo();
458 try {
459 synchronized(StringContent.this) {
460 insertString(offset, string);
461 // Update the Positions that were in the range removed.
462 if(posRefs != null) {
463 updateUndoPositions(posRefs);
464 posRefs = null;
465 }
466 string = null;
467 }
468 } catch (BadLocationException bl) {
469 throw new CannotUndoException();
470 }
471 }
472
473 public void redo() throws CannotRedoException {
474 super.redo();
475 try {
476 synchronized(StringContent.this) {
477 string = getString(offset, length);
478 // Get the Positions in the range being removed.
479 if(marks != null)
480 posRefs = getPositionsInRange(null, offset, length);
481 remove(offset, length);
482 }
483 } catch (BadLocationException bl) {
484 throw new CannotRedoException();
485 }
486 }
487
488 // Where the string goes.
489 protected int offset;
490 // Length of the string.
491 protected int length;
492 // The string that was inserted. This will be null after an undo.
493 protected String string;
494 // An array of instances of UndoPosRef for the Positions in the
495 // range that was removed, valid before undo.
496 protected Vector posRefs;
497 }
498}