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