GNU Classpath (0.95) | |
Frames | No Frames |
1: /* StringContent.java -- 2: Copyright (C) 2005, 2006 Free Software Foundation, Inc. 3: 4: This file is part of GNU Classpath. 5: 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) 9: any later version. 10: 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. 15: 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 19: 02110-1301 USA. 20: 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 24: combination. 25: 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. */ 37: 38: 39: package javax.swing.text; 40: 41: import java.io.Serializable; 42: import java.lang.ref.Reference; 43: import java.lang.ref.ReferenceQueue; 44: import java.lang.ref.WeakReference; 45: import java.util.Iterator; 46: import java.util.Vector; 47: 48: import javax.swing.undo.AbstractUndoableEdit; 49: import javax.swing.undo.CannotRedoException; 50: import javax.swing.undo.CannotUndoException; 51: import javax.swing.undo.UndoableEdit; 52: 53: /** 54: * An implementation of the <code>AbstractDocument.Content</code> 55: * interface useful for small documents or debugging. The character 56: * content is a simple character array. It's not really efficient. 57: * 58: * <p>Do not use this class for large size.</p> 59: */ 60: public final class StringContent 61: implements AbstractDocument.Content, Serializable 62: { 63: /** 64: * Stores a reference to a mark that can be resetted to the original value 65: * after a mark has been moved. This is used for undoing actions. 66: */ 67: private class UndoPosRef 68: { 69: /** 70: * The mark that might need to be reset. 71: */ 72: private Mark mark; 73: 74: /** 75: * The original offset to reset the mark to. 76: */ 77: private int undoOffset; 78: 79: /** 80: * Creates a new UndoPosRef. 81: * 82: * @param m the mark 83: */ 84: UndoPosRef(Mark m) 85: { 86: mark = m; 87: undoOffset = mark.mark; 88: } 89: 90: /** 91: * Resets the position of the mark to the value that it had when 92: * creating this UndoPosRef. 93: */ 94: void reset() 95: { 96: mark.mark = undoOffset; 97: } 98: } 99: 100: /** 101: * Holds a mark into the buffer that is used by StickyPosition to find 102: * the actual offset of the position. This is pulled out of the 103: * GapContentPosition object so that the mark and position can be handled 104: * independently, and most important, so that the StickyPosition can 105: * be garbage collected while we still hold a reference to the Mark object. 106: */ 107: private class Mark 108: { 109: /** 110: * The actual mark into the buffer. 111: */ 112: int mark; 113: 114: 115: /** 116: * The number of GapContentPosition object that reference this mark. If 117: * it reaches zero, it get's deleted by 118: * {@link StringContent#garbageCollect()}. 119: */ 120: int refCount; 121: 122: /** 123: * Creates a new Mark object for the specified offset. 124: * 125: * @param offset the offset 126: */ 127: Mark(int offset) 128: { 129: mark = offset; 130: } 131: } 132: 133: /** The serialization UID (compatible with JDK1.5). */ 134: private static final long serialVersionUID = 4755994433709540381L; 135: 136: // This is package-private to avoid an accessor method. 137: char[] content; 138: 139: private int count; 140: 141: /** 142: * Holds the marks for the positions. 143: * 144: * This is package private to avoid accessor methods. 145: */ 146: Vector marks; 147: 148: private class InsertUndo extends AbstractUndoableEdit 149: { 150: private int start; 151: 152: private int length; 153: 154: private String redoContent; 155: 156: private Vector positions; 157: 158: public InsertUndo(int start, int length) 159: { 160: super(); 161: this.start = start; 162: this.length = length; 163: } 164: 165: public void undo() 166: { 167: super.undo(); 168: try 169: { 170: if (marks != null) 171: positions = getPositionsInRange(null, start, length); 172: redoContent = getString(start, length); 173: remove(start, length); 174: } 175: catch (BadLocationException b) 176: { 177: throw new CannotUndoException(); 178: } 179: } 180: 181: public void redo() 182: { 183: super.redo(); 184: try 185: { 186: insertString(start, redoContent); 187: redoContent = null; 188: if (positions != null) 189: { 190: updateUndoPositions(positions); 191: positions = null; 192: } 193: } 194: catch (BadLocationException b) 195: { 196: throw new CannotRedoException(); 197: } 198: } 199: } 200: 201: private class RemoveUndo extends AbstractUndoableEdit 202: { 203: private int start; 204: private int len; 205: private String undoString; 206: 207: Vector positions; 208: 209: public RemoveUndo(int start, String str) 210: { 211: super(); 212: this.start = start; 213: len = str.length(); 214: this.undoString = str; 215: if (marks != null) 216: positions = getPositionsInRange(null, start, str.length()); 217: } 218: 219: public void undo() 220: { 221: super.undo(); 222: try 223: { 224: StringContent.this.insertString(this.start, this.undoString); 225: if (positions != null) 226: { 227: updateUndoPositions(positions); 228: positions = null; 229: } 230: undoString = null; 231: } 232: catch (BadLocationException bad) 233: { 234: throw new CannotUndoException(); 235: } 236: } 237: 238: public void redo() 239: { 240: super.redo(); 241: try 242: { 243: undoString = getString(start, len); 244: if (marks != null) 245: positions = getPositionsInRange(null, start, len); 246: remove(this.start, len); 247: } 248: catch (BadLocationException bad) 249: { 250: throw new CannotRedoException(); 251: } 252: } 253: } 254: 255: private class StickyPosition implements Position 256: { 257: Mark mark; 258: 259: public StickyPosition(int offset) 260: { 261: // Try to make space. 262: garbageCollect(); 263: 264: mark = new Mark(offset); 265: mark.refCount++; 266: marks.add(mark); 267: 268: new WeakReference(this, queueOfDeath); 269: } 270: 271: /** 272: * Should be >=0. 273: */ 274: public int getOffset() 275: { 276: return mark.mark; 277: } 278: } 279: 280: /** 281: * Used in {@link #remove(int,int)}. 282: */ 283: private static final char[] EMPTY = new char[0]; 284: 285: /** 286: * Queues all references to GapContentPositions that are about to be 287: * GC'ed. This is used to remove the corresponding marks from the 288: * positionMarks array if the number of references to that mark reaches zero. 289: * 290: * This is package private to avoid accessor synthetic methods. 291: */ 292: ReferenceQueue queueOfDeath; 293: 294: /** 295: * Creates a new instance containing the string "\n". This is equivalent 296: * to calling {@link #StringContent(int)} with an <code>initialLength</code> 297: * of 10. 298: */ 299: public StringContent() 300: { 301: this(10); 302: } 303: 304: /** 305: * Creates a new instance containing the string "\n". 306: * 307: * @param initialLength the initial length of the underlying character 308: * array used to store the content. 309: */ 310: public StringContent(int initialLength) 311: { 312: super(); 313: queueOfDeath = new ReferenceQueue(); 314: if (initialLength < 1) 315: initialLength = 1; 316: this.content = new char[initialLength]; 317: this.content[0] = '\n'; 318: this.count = 1; 319: } 320: 321: protected Vector getPositionsInRange(Vector v, 322: int offset, 323: int length) 324: { 325: Vector refPos = v == null ? new Vector() : v; 326: Iterator iter = marks.iterator(); 327: while(iter.hasNext()) 328: { 329: Mark m = (Mark) iter.next(); 330: if (offset <= m.mark && m.mark <= offset + length) 331: refPos.add(new UndoPosRef(m)); 332: } 333: return refPos; 334: } 335: 336: /** 337: * Creates a position reference for the character at the given offset. The 338: * position offset will be automatically updated when new characters are 339: * inserted into or removed from the content. 340: * 341: * @param offset the character offset. 342: * 343: * @throws BadLocationException if offset is outside the bounds of the 344: * content. 345: */ 346: public Position createPosition(int offset) throws BadLocationException 347: { 348: // Lazily create marks vector. 349: if (marks == null) 350: marks = new Vector(); 351: StickyPosition sp = new StickyPosition(offset); 352: return sp; 353: } 354: 355: /** 356: * Returns the length of the string content, including the '\n' character at 357: * the end. 358: * 359: * @return The length of the string content. 360: */ 361: public int length() 362: { 363: return count; 364: } 365: 366: /** 367: * Inserts <code>str</code> at the given position and returns an 368: * {@link UndoableEdit} that enables undo/redo support. 369: * 370: * @param where the insertion point (must be less than 371: * <code>length()</code>). 372: * @param str the string to insert (<code>null</code> not permitted). 373: * 374: * @return An object that can undo the insertion. 375: */ 376: public UndoableEdit insertString(int where, String str) 377: throws BadLocationException 378: { 379: checkLocation(where, 0); 380: if (where == this.count) 381: throw new BadLocationException("Invalid location", 1); 382: if (str == null) 383: throw new NullPointerException(); 384: char[] insert = str.toCharArray(); 385: replace(where, 0, insert); 386: 387: // Move all the positions. 388: if (marks != null) 389: { 390: Iterator iter = marks.iterator(); 391: int start = where; 392: if (start == 0) 393: start = 1; 394: while (iter.hasNext()) 395: { 396: Mark m = (Mark) iter.next(); 397: if (m.mark >= start) 398: m.mark += str.length(); 399: } 400: } 401: 402: InsertUndo iundo = new InsertUndo(where, insert.length); 403: return iundo; 404: } 405: 406: /** 407: * Removes the specified range of characters and returns an 408: * {@link UndoableEdit} that enables undo/redo support. 409: * 410: * @param where the starting index. 411: * @param nitems the number of characters. 412: * 413: * @return An object that can undo the removal. 414: * 415: * @throws BadLocationException if the character range extends outside the 416: * bounds of the content OR includes the last character. 417: */ 418: public UndoableEdit remove(int where, int nitems) throws BadLocationException 419: { 420: checkLocation(where, nitems + 1); 421: RemoveUndo rundo = new RemoveUndo(where, new String(this.content, where, 422: nitems)); 423: 424: replace(where, nitems, EMPTY); 425: // Move all the positions. 426: if (marks != null) 427: { 428: Iterator iter = marks.iterator(); 429: while (iter.hasNext()) 430: { 431: Mark m = (Mark) iter.next(); 432: if (m.mark >= where + nitems) 433: m.mark -= nitems; 434: else if (m.mark >= where) 435: m.mark = where; 436: } 437: } 438: return rundo; 439: } 440: 441: private void replace(int offs, int numRemove, char[] insert) 442: { 443: int insertLength = insert.length; 444: int delta = insertLength - numRemove; 445: int src = offs + numRemove; 446: int numMove = count - src; 447: int dest = src + delta; 448: if (count + delta >= content.length) 449: { 450: // Grow data array. 451: int newLength = Math.max(2 * content.length, count + delta); 452: char[] newContent = new char[newLength]; 453: System.arraycopy(content, 0, newContent, 0, offs); 454: System.arraycopy(insert, 0, newContent, offs, insertLength); 455: System.arraycopy(content, src, newContent, dest, numMove); 456: content = newContent; 457: } 458: else 459: { 460: System.arraycopy(content, src, content, dest, numMove); 461: System.arraycopy(insert, 0, content, offs, insertLength); 462: } 463: count += delta; 464: } 465: 466: /** 467: * Returns a new <code>String</code> containing the characters in the 468: * specified range. 469: * 470: * @param where the start index. 471: * @param len the number of characters. 472: * 473: * @return A string. 474: * 475: * @throws BadLocationException if the requested range of characters extends 476: * outside the bounds of the content. 477: */ 478: public String getString(int where, int len) throws BadLocationException 479: { 480: // The RI throws a StringIndexOutOfBoundsException here, which 481: // smells like a bug. We throw a BadLocationException instead. 482: checkLocation(where, len); 483: return new String(this.content, where, len); 484: } 485: 486: /** 487: * Updates <code>txt</code> to contain a direct reference to the underlying 488: * character array. 489: * 490: * @param where the index of the first character. 491: * @param len the number of characters. 492: * @param txt a carrier for the return result (<code>null</code> not 493: * permitted). 494: * 495: * @throws BadLocationException if the requested character range is not 496: * within the bounds of the content. 497: * @throws NullPointerException if <code>txt</code> is <code>null</code>. 498: */ 499: public void getChars(int where, int len, Segment txt) 500: throws BadLocationException 501: { 502: if (where + len > count) 503: throw new BadLocationException("Invalid location", where + len); 504: txt.array = content; 505: txt.offset = where; 506: txt.count = len; 507: } 508: 509: 510: /** 511: * Resets the positions in the specified vector to their original offset 512: * after a undo operation is performed. For example, after removing some 513: * content, the positions in the removed range will all be set to one 514: * offset. This method restores the positions to their original offsets 515: * after an undo. 516: */ 517: protected void updateUndoPositions(Vector positions) 518: { 519: for (Iterator i = positions.iterator(); i.hasNext();) 520: { 521: UndoPosRef pos = (UndoPosRef) i.next(); 522: pos.reset(); 523: } 524: } 525: 526: /** 527: * A utility method that checks the validity of the specified character 528: * range. 529: * 530: * @param where the first character in the range. 531: * @param len the number of characters in the range. 532: * 533: * @throws BadLocationException if the specified range is not within the 534: * bounds of the content. 535: */ 536: void checkLocation(int where, int len) throws BadLocationException 537: { 538: if (where < 0) 539: throw new BadLocationException("Invalid location", 1); 540: else if (where > this.count) 541: throw new BadLocationException("Invalid location", this.count); 542: else if ((where + len) > this.count) 543: throw new BadLocationException("Invalid range", this.count); 544: } 545: 546: /** 547: * Polls the queue of death for GapContentPositions, updates the 548: * corresponding reference count and removes the corresponding mark 549: * if the refcount reaches zero. 550: * 551: * This is package private to avoid accessor synthetic methods. 552: */ 553: void garbageCollect() 554: { 555: Reference ref = queueOfDeath.poll(); 556: while (ref != null) 557: { 558: if (ref != null) 559: { 560: StickyPosition pos = (StickyPosition) ref.get(); 561: Mark m = pos.mark; 562: m.refCount--; 563: if (m.refCount == 0) 564: marks.remove(m); 565: } 566: ref = queueOfDeath.poll(); 567: } 568: } 569: }
GNU Classpath (0.95) |