1/* 2 * Copyright (c) 1997, 2017, 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 */ 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™ 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 54public 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 @SuppressWarnings({"rawtypes", "unchecked"}) // UndoPosRef type cannot be exposed 275 protected Vector getPositionsInRange(Vector v, int offset, 276 int length) { 277 int n = marks.size(); 278 int end = offset + length; 279 Vector placeIn = (v == null) ? new Vector() : v; 280 for (int i = 0; i < n; i++) { 281 PosRec mark = marks.elementAt(i); 282 if (mark.unused) { 283 // this record is no longer used, get rid of it 284 marks.removeElementAt(i); 285 i -= 1; 286 n -= 1; 287 } else if(mark.offset >= offset && mark.offset <= end) 288 placeIn.addElement(new UndoPosRef(mark)); 289 } 290 return placeIn; 291 } 292 293 /** 294 * Resets the location for all the UndoPosRef instances 295 * in <code>positions</code>. 296 * <p> 297 * This is meant for internal usage, and is generally not of interest 298 * to subclasses. 299 * 300 * @param positions the positions of the instances 301 */ 302 @SuppressWarnings("rawtypes") // UndoPosRef type cannot be exposed 303 protected void updateUndoPositions(Vector positions) { 304 for(int counter = positions.size() - 1; counter >= 0; counter--) { 305 UndoPosRef ref = (UndoPosRef) positions.elementAt(counter); 306 // Check if the Position is still valid. 307 if(ref.rec.unused) { 308 positions.removeElementAt(counter); 309 } 310 else 311 ref.resetLocation(); 312 } 313 } 314 315 private static final char[] empty = new char[0]; 316 private char[] data; 317 private int count; 318 transient Vector<PosRec> marks; 319 320 /** 321 * holds the data for a mark... separately from 322 * the real mark so that the real mark can be 323 * collected if there are no more references to 324 * it.... the update table holds only a reference 325 * to this grungy thing. 326 */ 327 final class PosRec { 328 329 PosRec(int offset) { 330 this.offset = offset; 331 } 332 333 int offset; 334 boolean unused; 335 } 336 337 /** 338 * This really wants to be a weak reference but 339 * in 1.1 we don't have a 100% pure solution for 340 * this... so this class trys to hack a solution 341 * to causing the marks to be collected. 342 */ 343 final class StickyPosition implements Position { 344 345 StickyPosition(int offset) { 346 rec = new PosRec(offset); 347 marks.addElement(rec); 348 } 349 350 public int getOffset() { 351 return rec.offset; 352 } 353 354 @SuppressWarnings("deprecation") 355 protected void finalize() throws Throwable { 356 // schedule the record to be removed later 357 // on another thread. 358 rec.unused = true; 359 } 360 361 public String toString() { 362 return Integer.toString(getOffset()); 363 } 364 365 PosRec rec; 366 } 367 368 /** 369 * Used to hold a reference to a Position that is being reset as the 370 * result of removing from the content. 371 */ 372 final class UndoPosRef { 373 UndoPosRef(PosRec rec) { 374 this.rec = rec; 375 this.undoLocation = rec.offset; 376 } 377 378 /** 379 * Resets the location of the Position to the offset when the 380 * receiver was instantiated. 381 */ 382 protected void resetLocation() { 383 rec.offset = undoLocation; 384 } 385 386 /** Location to reset to when resetLocatino is invoked. */ 387 protected int undoLocation; 388 /** Position to reset offset. */ 389 protected PosRec rec; 390 } 391 392 /** 393 * UnoableEdit created for inserts. 394 */ 395 class InsertUndo extends AbstractUndoableEdit { 396 protected InsertUndo(int offset, int length) { 397 super(); 398 this.offset = offset; 399 this.length = length; 400 } 401 402 public void undo() throws CannotUndoException { 403 super.undo(); 404 try { 405 synchronized(StringContent.this) { 406 // Get the Positions in the range being removed. 407 if(marks != null) 408 posRefs = getPositionsInRange(null, offset, length); 409 string = getString(offset, length); 410 remove(offset, length); 411 } 412 } catch (BadLocationException bl) { 413 throw new CannotUndoException(); 414 } 415 } 416 417 public void redo() throws CannotRedoException { 418 super.redo(); 419 try { 420 synchronized(StringContent.this) { 421 insertString(offset, string); 422 string = null; 423 // Update the Positions that were in the range removed. 424 if(posRefs != null) { 425 updateUndoPositions(posRefs); 426 posRefs = null; 427 } 428 } 429 } catch (BadLocationException bl) { 430 throw new CannotRedoException(); 431 } 432 } 433 434 // Where the string goes. 435 protected int offset; 436 // Length of the string. 437 protected int length; 438 // The string that was inserted. To cut down on space needed this 439 // will only be valid after an undo. 440 protected String string; 441 // An array of instances of UndoPosRef for the Positions in the 442 // range that was removed, valid after undo. 443 @SuppressWarnings("rawtypes") // UndoPosRef type cannot be exposed 444 protected Vector posRefs; 445 } 446 447 448 /** 449 * UndoableEdit created for removes. 450 */ 451 class RemoveUndo extends AbstractUndoableEdit { 452 @SuppressWarnings("unchecked") 453 protected RemoveUndo(int offset, String string) { 454 super(); 455 this.offset = offset; 456 this.string = string; 457 this.length = string.length(); 458 if(marks != null) 459 posRefs = getPositionsInRange(null, offset, length); 460 } 461 462 public void undo() throws CannotUndoException { 463 super.undo(); 464 try { 465 synchronized(StringContent.this) { 466 insertString(offset, string); 467 // Update the Positions that were in the range removed. 468 if(posRefs != null) { 469 updateUndoPositions(posRefs); 470 posRefs = null; 471 } 472 string = null; 473 } 474 } catch (BadLocationException bl) { 475 throw new CannotUndoException(); 476 } 477 } 478 479 @SuppressWarnings("unchecked") 480 public void redo() throws CannotRedoException { 481 super.redo(); 482 try { 483 synchronized(StringContent.this) { 484 string = getString(offset, length); 485 // Get the Positions in the range being removed. 486 if(marks != null) 487 posRefs = getPositionsInRange(null, offset, length); 488 remove(offset, length); 489 } 490 } catch (BadLocationException bl) { 491 throw new CannotRedoException(); 492 } 493 } 494 495 // Where the string goes. 496 protected int offset; 497 // Length of the string. 498 protected int length; 499 // The string that was inserted. This will be null after an undo. 500 protected String string; 501 // An array of instances of UndoPosRef for the Positions in the 502 // range that was removed, valid before undo. 503 protected Vector<UndoPosRef> posRefs; 504 } 505} 506