Frames | No Frames |
1: /* AbstractDocument.java -- 2: Copyright (C) 2002, 2004, 2005 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.PrintStream; 42: import java.io.Serializable; 43: import java.util.Dictionary; 44: import java.util.Enumeration; 45: import java.util.EventListener; 46: import java.util.Hashtable; 47: import java.util.Vector; 48: 49: import javax.swing.event.DocumentEvent; 50: import javax.swing.event.DocumentListener; 51: import javax.swing.event.EventListenerList; 52: import javax.swing.event.UndoableEditEvent; 53: import javax.swing.event.UndoableEditListener; 54: import javax.swing.text.DocumentFilter; 55: import javax.swing.tree.TreeNode; 56: import javax.swing.undo.AbstractUndoableEdit; 57: import javax.swing.undo.CompoundEdit; 58: import javax.swing.undo.UndoableEdit; 59: 60: /** 61: * An abstract base implementation for the {@link Document} interface. 62: * This class provides some common functionality for all <code>Element</code>s, 63: * most notably it implements a locking mechanism to make document modification 64: * thread-safe. 65: * 66: * @author original author unknown 67: * @author Roman Kennke (roman@kennke.org) 68: */ 69: public abstract class AbstractDocument implements Document, Serializable 70: { 71: /** The serialization UID (compatible with JDK1.5). */ 72: private static final long serialVersionUID = 6842927725919637215L; 73: 74: /** 75: * Standard error message to indicate a bad location. 76: */ 77: protected static final String BAD_LOCATION = "document location failure"; 78: 79: /** 80: * Standard name for unidirectional <code>Element</code>s. 81: */ 82: public static final String BidiElementName = "bidi level"; 83: 84: /** 85: * Standard name for content <code>Element</code>s. These are usually 86: * {@link LeafElement}s. 87: */ 88: public static final String ContentElementName = "content"; 89: 90: /** 91: * Standard name for paragraph <code>Element</code>s. These are usually 92: * {@link BranchElement}s. 93: */ 94: public static final String ParagraphElementName = "paragraph"; 95: 96: /** 97: * Standard name for section <code>Element</code>s. These are usually 98: * {@link DefaultStyledDocument.SectionElement}s. 99: */ 100: public static final String SectionElementName = "section"; 101: 102: /** 103: * Attribute key for storing the element name. 104: */ 105: public static final String ElementNameAttribute = "$ename"; 106: 107: /** 108: * The actual content model of this <code>Document</code>. 109: */ 110: Content content; 111: 112: /** 113: * The AttributeContext for this <code>Document</code>. 114: */ 115: AttributeContext context; 116: 117: /** 118: * The currently installed <code>DocumentFilter</code>. 119: */ 120: DocumentFilter documentFilter; 121: 122: /** 123: * The documents properties. 124: */ 125: Dictionary properties; 126: 127: /** 128: * Manages event listeners for this <code>Document</code>. 129: */ 130: protected EventListenerList listenerList = new EventListenerList(); 131: 132: /** 133: * Stores the current writer thread. Used for locking. 134: */ 135: private Thread currentWriter = null; 136: 137: /** 138: * The number of readers. Used for locking. 139: */ 140: private int numReaders = 0; 141: 142: /** 143: * Tells if there are one or more writers waiting. 144: */ 145: private int numWritersWaiting = 0; 146: 147: /** 148: * A condition variable that readers and writers wait on. 149: */ 150: private Object documentCV = new Object(); 151: 152: /** An instance of a DocumentFilter.FilterBypass which allows calling 153: * the insert, remove and replace method without checking for an installed 154: * document filter. 155: */ 156: private DocumentFilter.FilterBypass bypass; 157: 158: /** 159: * The bidi root element. 160: */ 161: private Element bidiRoot; 162: 163: /** 164: * Creates a new <code>AbstractDocument</code> with the specified 165: * {@link Content} model. 166: * 167: * @param doc the <code>Content</code> model to be used in this 168: * <code>Document<code> 169: * 170: * @see GapContent 171: * @see StringContent 172: */ 173: protected AbstractDocument(Content doc) 174: { 175: this(doc, StyleContext.getDefaultStyleContext()); 176: } 177: 178: /** 179: * Creates a new <code>AbstractDocument</code> with the specified 180: * {@link Content} model and {@link AttributeContext}. 181: * 182: * @param doc the <code>Content</code> model to be used in this 183: * <code>Document<code> 184: * @param ctx the <code>AttributeContext</code> to use 185: * 186: * @see GapContent 187: * @see StringContent 188: */ 189: protected AbstractDocument(Content doc, AttributeContext ctx) 190: { 191: content = doc; 192: context = ctx; 193: 194: // FIXME: This is determined using a Mauve test. Make the document 195: // actually use this. 196: putProperty("i18n", Boolean.FALSE); 197: 198: // FIXME: Fully implement bidi. 199: bidiRoot = new BranchElement(null, null); 200: } 201: 202: /** Returns the DocumentFilter.FilterBypass instance for this 203: * document and create it if it does not exist yet. 204: * 205: * @return This document's DocumentFilter.FilterBypass instance. 206: */ 207: private DocumentFilter.FilterBypass getBypass() 208: { 209: if (bypass == null) 210: bypass = new Bypass(); 211: 212: return bypass; 213: } 214: 215: /** 216: * Returns the paragraph {@link Element} that holds the specified position. 217: * 218: * @param pos the position for which to get the paragraph element 219: * 220: * @return the paragraph {@link Element} that holds the specified position 221: */ 222: public abstract Element getParagraphElement(int pos); 223: 224: /** 225: * Returns the default root {@link Element} of this <code>Document</code>. 226: * Usual <code>Document</code>s only have one root element and return this. 227: * However, there may be <code>Document</code> implementations that 228: * support multiple root elements, they have to return a default root element 229: * here. 230: * 231: * @return the default root {@link Element} of this <code>Document</code> 232: */ 233: public abstract Element getDefaultRootElement(); 234: 235: /** 236: * Creates and returns a branch element with the specified 237: * <code>parent</code> and <code>attributes</code>. Note that the new 238: * <code>Element</code> is linked to the parent <code>Element</code> 239: * through {@link Element#getParentElement}, but it is not yet added 240: * to the parent <code>Element</code> as child. 241: * 242: * @param parent the parent <code>Element</code> for the new branch element 243: * @param attributes the text attributes to be installed in the new element 244: * 245: * @return the new branch <code>Element</code> 246: * 247: * @see BranchElement 248: */ 249: protected Element createBranchElement(Element parent, 250: AttributeSet attributes) 251: { 252: return new BranchElement(parent, attributes); 253: } 254: 255: /** 256: * Creates and returns a leaf element with the specified 257: * <code>parent</code> and <code>attributes</code>. Note that the new 258: * <code>Element</code> is linked to the parent <code>Element</code> 259: * through {@link Element#getParentElement}, but it is not yet added 260: * to the parent <code>Element</code> as child. 261: * 262: * @param parent the parent <code>Element</code> for the new branch element 263: * @param attributes the text attributes to be installed in the new element 264: * 265: * @return the new branch <code>Element</code> 266: * 267: * @see LeafElement 268: */ 269: protected Element createLeafElement(Element parent, AttributeSet attributes, 270: int start, int end) 271: { 272: return new LeafElement(parent, attributes, start, end); 273: } 274: 275: /** 276: * Creates a {@link Position} that keeps track of the location at the 277: * specified <code>offset</code>. 278: * 279: * @param offset the location in the document to keep track by the new 280: * <code>Position</code> 281: * 282: * @return the newly created <code>Position</code> 283: * 284: * @throws BadLocationException if <code>offset</code> is not a valid 285: * location in the documents content model 286: */ 287: public Position createPosition(final int offset) throws BadLocationException 288: { 289: return content.createPosition(offset); 290: } 291: 292: /** 293: * Notifies all registered listeners when the document model changes. 294: * 295: * @param event the <code>DocumentEvent</code> to be fired 296: */ 297: protected void fireChangedUpdate(DocumentEvent event) 298: { 299: DocumentListener[] listeners = getDocumentListeners(); 300: 301: for (int index = 0; index < listeners.length; ++index) 302: listeners[index].changedUpdate(event); 303: } 304: 305: /** 306: * Notifies all registered listeners when content is inserted in the document 307: * model. 308: * 309: * @param event the <code>DocumentEvent</code> to be fired 310: */ 311: protected void fireInsertUpdate(DocumentEvent event) 312: { 313: DocumentListener[] listeners = getDocumentListeners(); 314: 315: for (int index = 0; index < listeners.length; ++index) 316: listeners[index].insertUpdate(event); 317: } 318: 319: /** 320: * Notifies all registered listeners when content is removed from the 321: * document model. 322: * 323: * @param event the <code>DocumentEvent</code> to be fired 324: */ 325: protected void fireRemoveUpdate(DocumentEvent event) 326: { 327: DocumentListener[] listeners = getDocumentListeners(); 328: 329: for (int index = 0; index < listeners.length; ++index) 330: listeners[index].removeUpdate(event); 331: } 332: 333: /** 334: * Notifies all registered listeners when an <code>UndoableEdit</code> has 335: * been performed on this <code>Document</code>. 336: * 337: * @param event the <code>UndoableEditEvent</code> to be fired 338: */ 339: protected void fireUndoableEditUpdate(UndoableEditEvent event) 340: { 341: UndoableEditListener[] listeners = getUndoableEditListeners(); 342: 343: for (int index = 0; index < listeners.length; ++index) 344: listeners[index].undoableEditHappened(event); 345: } 346: 347: /** 348: * Returns the asynchronous loading priority. Returns <code>-1</code> if this 349: * document should not be loaded asynchronously. 350: * 351: * @return the asynchronous loading priority 352: */ 353: public int getAsynchronousLoadPriority() 354: { 355: return 0; 356: } 357: 358: /** 359: * Returns the {@link AttributeContext} used in this <code>Document</code>. 360: * 361: * @return the {@link AttributeContext} used in this <code>Document</code> 362: */ 363: protected final AttributeContext getAttributeContext() 364: { 365: return context; 366: } 367: 368: /** 369: * Returns the root element for bidirectional content. 370: * 371: * @return the root element for bidirectional content 372: */ 373: public Element getBidiRootElement() 374: { 375: return bidiRoot; 376: } 377: 378: /** 379: * Returns the {@link Content} model for this <code>Document</code> 380: * 381: * @return the {@link Content} model for this <code>Document</code> 382: * 383: * @see GapContent 384: * @see StringContent 385: */ 386: protected final Content getContent() 387: { 388: return content; 389: } 390: 391: /** 392: * Returns the thread that currently modifies this <code>Document</code> 393: * if there is one, otherwise <code>null</code>. This can be used to 394: * distinguish between a method call that is part of an ongoing modification 395: * or if it is a separate modification for which a new lock must be aquired. 396: * 397: * @return the thread that currently modifies this <code>Document</code> 398: * if there is one, otherwise <code>null</code> 399: */ 400: protected final Thread getCurrentWriter() 401: { 402: return currentWriter; 403: } 404: 405: /** 406: * Returns the properties of this <code>Document</code>. 407: * 408: * @return the properties of this <code>Document</code> 409: */ 410: public Dictionary getDocumentProperties() 411: { 412: // FIXME: make me thread-safe 413: if (properties == null) 414: properties = new Hashtable(); 415: 416: return properties; 417: } 418: 419: /** 420: * Returns a {@link Position} which will always mark the end of the 421: * <code>Document</code>. 422: * 423: * @return a {@link Position} which will always mark the end of the 424: * <code>Document</code> 425: */ 426: public final Position getEndPosition() 427: { 428: // FIXME: Properly implement this by calling Content.createPosition(). 429: return new Position() 430: { 431: public int getOffset() 432: { 433: return getLength(); 434: } 435: }; 436: } 437: 438: /** 439: * Returns the length of this <code>Document</code>'s content. 440: * 441: * @return the length of this <code>Document</code>'s content 442: */ 443: public int getLength() 444: { 445: // We return Content.getLength() -1 here because there is always an 446: // implicit \n at the end of the Content which does count in Content 447: // but not in Document. 448: return content.length() - 1; 449: } 450: 451: /** 452: * Returns all registered listeners of a given listener type. 453: * 454: * @param listenerType the type of the listeners to be queried 455: * 456: * @return all registered listeners of the specified type 457: */ 458: public EventListener[] getListeners(Class listenerType) 459: { 460: return listenerList.getListeners(listenerType); 461: } 462: 463: /** 464: * Returns a property from this <code>Document</code>'s property list. 465: * 466: * @param key the key of the property to be fetched 467: * 468: * @return the property for <code>key</code> or <code>null</code> if there 469: * is no such property stored 470: */ 471: public final Object getProperty(Object key) 472: { 473: // FIXME: make me thread-safe 474: Object value = null; 475: if (properties != null) 476: value = properties.get(key); 477: 478: return value; 479: } 480: 481: /** 482: * Returns all root elements of this <code>Document</code>. By default 483: * this just returns the single root element returned by 484: * {@link #getDefaultRootElement()}. <code>Document</code> implementations 485: * that support multiple roots must override this method and return all roots 486: * here. 487: * 488: * @return all root elements of this <code>Document</code> 489: */ 490: public Element[] getRootElements() 491: { 492: Element[] elements = new Element[2]; 493: elements[0] = getDefaultRootElement(); 494: elements[1] = getBidiRootElement(); 495: return elements; 496: } 497: 498: /** 499: * Returns a {@link Position} which will always mark the beginning of the 500: * <code>Document</code>. 501: * 502: * @return a {@link Position} which will always mark the beginning of the 503: * <code>Document</code> 504: */ 505: public final Position getStartPosition() 506: { 507: // FIXME: Properly implement this using Content.createPosition(). 508: return new Position() 509: { 510: public int getOffset() 511: { 512: return 0; 513: } 514: }; 515: } 516: 517: /** 518: * Returns a piece of this <code>Document</code>'s content. 519: * 520: * @param offset the start offset of the content 521: * @param length the length of the content 522: * 523: * @return the piece of content specified by <code>offset</code> and 524: * <code>length</code> 525: * 526: * @throws BadLocationException if <code>offset</code> or <code>offset + 527: * length</code> are invalid locations with this 528: * <code>Document</code> 529: */ 530: public String getText(int offset, int length) throws BadLocationException 531: { 532: return content.getString(offset, length); 533: } 534: 535: /** 536: * Fetches a piece of this <code>Document</code>'s content and stores 537: * it in the given {@link Segment}. 538: * 539: * @param offset the start offset of the content 540: * @param length the length of the content 541: * @param segment the <code>Segment</code> to store the content in 542: * 543: * @throws BadLocationException if <code>offset</code> or <code>offset + 544: * length</code> are invalid locations with this 545: * <code>Document</code> 546: */ 547: public void getText(int offset, int length, Segment segment) 548: throws BadLocationException 549: { 550: content.getChars(offset, length, segment); 551: } 552: 553: /** 554: * Inserts a String into this <code>Document</code> at the specified 555: * position and assigning the specified attributes to it. 556: * 557: * <p>If a {@link DocumentFilter} is installed in this document, the 558: * corresponding method of the filter object is called.</p> 559: * 560: * <p>The method has no effect when <code>text</code> is <code>null</code> 561: * or has a length of zero.</p> 562: * 563: * 564: * @param offset the location at which the string should be inserted 565: * @param text the content to be inserted 566: * @param attributes the text attributes to be assigned to that string 567: * 568: * @throws BadLocationException if <code>offset</code> is not a valid 569: * location in this <code>Document</code> 570: */ 571: public void insertString(int offset, String text, AttributeSet attributes) 572: throws BadLocationException 573: { 574: // Bail out if we have a bogus insertion (Behavior observed in RI). 575: if (text == null || text.length() == 0) 576: return; 577: 578: if (documentFilter == null) 579: insertStringImpl(offset, text, attributes); 580: else 581: documentFilter.insertString(getBypass(), offset, text, attributes); 582: } 583: 584: void insertStringImpl(int offset, String text, AttributeSet attributes) 585: throws BadLocationException 586: { 587: // Just return when no text to insert was given. 588: if (text == null || text.length() == 0) 589: return; 590: DefaultDocumentEvent event = 591: new DefaultDocumentEvent(offset, text.length(), 592: DocumentEvent.EventType.INSERT); 593: 594: try 595: { 596: writeLock(); 597: UndoableEdit undo = content.insertString(offset, text); 598: if (undo != null) 599: event.addEdit(undo); 600: 601: insertUpdate(event, attributes); 602: 603: fireInsertUpdate(event); 604: if (undo != null) 605: fireUndoableEditUpdate(new UndoableEditEvent(this, undo)); 606: } 607: finally 608: { 609: writeUnlock(); 610: } 611: } 612: 613: /** 614: * Called to indicate that text has been inserted into this 615: * <code>Document</code>. The default implementation does nothing. 616: * This method is executed within a write lock. 617: * 618: * @param chng the <code>DefaultDocumentEvent</code> describing the change 619: * @param attr the attributes of the changed content 620: */ 621: protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) 622: { 623: // Do nothing here. Subclasses may want to override this. 624: } 625: 626: /** 627: * Called after some content has been removed from this 628: * <code>Document</code>. The default implementation does nothing. 629: * This method is executed within a write lock. 630: * 631: * @param chng the <code>DefaultDocumentEvent</code> describing the change 632: */ 633: protected void postRemoveUpdate(DefaultDocumentEvent chng) 634: { 635: // Do nothing here. Subclasses may want to override this. 636: } 637: 638: /** 639: * Stores a property in this <code>Document</code>'s property list. 640: * 641: * @param key the key of the property to be stored 642: * @param value the value of the property to be stored 643: */ 644: public final void putProperty(Object key, Object value) 645: { 646: // FIXME: make me thread-safe 647: if (properties == null) 648: properties = new Hashtable(); 649: 650: properties.put(key, value); 651: } 652: 653: /** 654: * Blocks until a read lock can be obtained. Must block if there is 655: * currently a writer modifying the <code>Document</code>. 656: */ 657: public final void readLock() 658: { 659: if (currentWriter != null && currentWriter.equals(Thread.currentThread())) 660: return; 661: synchronized (documentCV) 662: { 663: while (currentWriter != null || numWritersWaiting > 0) 664: { 665: try 666: { 667: documentCV.wait(); 668: } 669: catch (InterruptedException ie) 670: { 671: throw new Error("interrupted trying to get a readLock"); 672: } 673: } 674: numReaders++; 675: } 676: } 677: 678: /** 679: * Releases the read lock. If this was the only reader on this 680: * <code>Document</code>, writing may begin now. 681: */ 682: public final void readUnlock() 683: { 684: // Note we could have a problem here if readUnlock was called without a 685: // prior call to readLock but the specs simply warn users to ensure that 686: // balance by using a finally block: 687: // readLock() 688: // try 689: // { 690: // doSomethingHere 691: // } 692: // finally 693: // { 694: // readUnlock(); 695: // } 696: 697: // All that the JDK seems to check for is that you don't call unlock 698: // more times than you've previously called lock, but it doesn't make 699: // sure that the threads calling unlock were the same ones that called lock 700: 701: // If the current thread holds the write lock, and attempted to also obtain 702: // a readLock, then numReaders hasn't been incremented and we don't need 703: // to unlock it here. 704: if (currentWriter == Thread.currentThread()) 705: return; 706: 707: // FIXME: the reference implementation throws a 708: // javax.swing.text.StateInvariantError here 709: if (numReaders == 0) 710: throw new IllegalStateException("document lock failure"); 711: 712: synchronized (documentCV) 713: { 714: // If currentWriter is not null, the application code probably had a 715: // writeLock and then tried to obtain a readLock, in which case 716: // numReaders wasn't incremented 717: if (currentWriter == null) 718: { 719: numReaders --; 720: if (numReaders == 0 && numWritersWaiting != 0) 721: documentCV.notify(); 722: } 723: } 724: } 725: 726: /** 727: * Removes a piece of content from this <code>Document</code>. 728: * 729: * <p>If a {@link DocumentFilter} is installed in this document, the 730: * corresponding method of the filter object is called. The 731: * <code>DocumentFilter</code> is called even if <code>length</code> 732: * is zero. This is different from {@link #replace}.</p> 733: * 734: * <p>Note: When <code>length</code> is zero or below the call is not 735: * forwarded to the underlying {@link AbstractDocument.Content} instance 736: * of this document and no exception is thrown.</p> 737: * 738: * @param offset the start offset of the fragment to be removed 739: * @param length the length of the fragment to be removed 740: * 741: * @throws BadLocationException if <code>offset</code> or 742: * <code>offset + length</code> or invalid locations within this 743: * document 744: */ 745: public void remove(int offset, int length) throws BadLocationException 746: { 747: if (documentFilter == null) 748: removeImpl(offset, length); 749: else 750: documentFilter.remove(getBypass(), offset, length); 751: } 752: 753: void removeImpl(int offset, int length) throws BadLocationException 754: { 755: // The RI silently ignores all requests that have a negative length. 756: // Don't ask my why, but that's how it is. 757: if (length > 0) 758: { 759: if (offset < 0 || offset > getLength()) 760: throw new BadLocationException("Invalid remove position", offset); 761: 762: if (offset + length > getLength()) 763: throw new BadLocationException("Invalid remove length", offset); 764: 765: DefaultDocumentEvent event = 766: new DefaultDocumentEvent(offset, length, 767: DocumentEvent.EventType.REMOVE); 768: 769: try 770: { 771: writeLock(); 772: 773: // The order of the operations below is critical! 774: removeUpdate(event); 775: UndoableEdit temp = content.remove(offset, length); 776: 777: postRemoveUpdate(event); 778: fireRemoveUpdate(event); 779: } 780: finally 781: { 782: writeUnlock(); 783: } 784: } 785: } 786: 787: /** 788: * Replaces a piece of content in this <code>Document</code> with 789: * another piece of content. 790: * 791: * <p>If a {@link DocumentFilter} is installed in this document, the 792: * corresponding method of the filter object is called.</p> 793: * 794: * <p>The method has no effect if <code>length</code> is zero (and 795: * only zero) and, at the same time, <code>text</code> is 796: * <code>null</code> or has zero length.</p> 797: * 798: * @param offset the start offset of the fragment to be removed 799: * @param length the length of the fragment to be removed 800: * @param text the text to replace the content with 801: * @param attributes the text attributes to assign to the new content 802: * 803: * @throws BadLocationException if <code>offset</code> or 804: * <code>offset + length</code> or invalid locations within this 805: * document 806: * 807: * @since 1.4 808: */ 809: public void replace(int offset, int length, String text, 810: AttributeSet attributes) 811: throws BadLocationException 812: { 813: // Bail out if we have a bogus replacement (Behavior observed in RI). 814: if (length == 0 815: && (text == null || text.length() == 0)) 816: return; 817: 818: if (documentFilter == null) 819: { 820: // It is important to call the methods which again do the checks 821: // of the arguments and the DocumentFilter because subclasses may 822: // have overridden these methods and provide crucial behavior 823: // which would be skipped if we call the non-checking variants. 824: // An example for this is PlainDocument where insertString can 825: // provide a filtering of newlines. 826: remove(offset, length); 827: insertString(offset, text, attributes); 828: } 829: else 830: documentFilter.replace(getBypass(), offset, length, text, attributes); 831: 832: } 833: 834: void replaceImpl(int offset, int length, String text, 835: AttributeSet attributes) 836: throws BadLocationException 837: { 838: removeImpl(offset, length); 839: insertStringImpl(offset, text, attributes); 840: } 841: 842: /** 843: * Adds a <code>DocumentListener</code> object to this document. 844: * 845: * @param listener the listener to add 846: */ 847: public void addDocumentListener(DocumentListener listener) 848: { 849: listenerList.add(DocumentListener.class, listener); 850: } 851: 852: /** 853: * Removes a <code>DocumentListener</code> object from this document. 854: * 855: * @param listener the listener to remove 856: */ 857: public void removeDocumentListener(DocumentListener listener) 858: { 859: listenerList.remove(DocumentListener.class, listener); 860: } 861: 862: /** 863: * Returns all registered <code>DocumentListener</code>s. 864: * 865: * @return all registered <code>DocumentListener</code>s 866: */ 867: public DocumentListener[] getDocumentListeners() 868: { 869: return (DocumentListener[]) getListeners(DocumentListener.class); 870: } 871: 872: /** 873: * Adds an {@link UndoableEditListener} to this <code>Document</code>. 874: * 875: * @param listener the listener to add 876: */ 877: public void addUndoableEditListener(UndoableEditListener listener) 878: { 879: listenerList.add(UndoableEditListener.class, listener); 880: } 881: 882: /** 883: * Removes an {@link UndoableEditListener} from this <code>Document</code>. 884: * 885: * @param listener the listener to remove 886: */ 887: public void removeUndoableEditListener(UndoableEditListener listener) 888: { 889: listenerList.remove(UndoableEditListener.class, listener); 890: } 891: 892: /** 893: * Returns all registered {@link UndoableEditListener}s. 894: * 895: * @return all registered {@link UndoableEditListener}s 896: */ 897: public UndoableEditListener[] getUndoableEditListeners() 898: { 899: return (UndoableEditListener[]) getListeners(UndoableEditListener.class); 900: } 901: 902: /** 903: * Called before some content gets removed from this <code>Document</code>. 904: * The default implementation does nothing but may be overridden by 905: * subclasses to modify the <code>Document</code> structure in response 906: * to a remove request. The method is executed within a write lock. 907: * 908: * @param chng the <code>DefaultDocumentEvent</code> describing the change 909: */ 910: protected void removeUpdate(DefaultDocumentEvent chng) 911: { 912: // Do nothing here. Subclasses may wish to override this. 913: } 914: 915: /** 916: * Called to render this <code>Document</code> visually. It obtains a read 917: * lock, ensuring that no changes will be made to the <code>document</code> 918: * during the rendering process. It then calls the {@link Runnable#run()} 919: * method on <code>runnable</code>. This method <em>must not</em> attempt 920: * to modifiy the <code>Document</code>, since a deadlock will occur if it 921: * tries to obtain a write lock. When the {@link Runnable#run()} method 922: * completes (either naturally or by throwing an exception), the read lock 923: * is released. Note that there is nothing in this method related to 924: * the actual rendering. It could be used to execute arbitrary code within 925: * a read lock. 926: * 927: * @param runnable the {@link Runnable} to execute 928: */ 929: public void render(Runnable runnable) 930: { 931: readLock(); 932: try 933: { 934: runnable.run(); 935: } 936: finally 937: { 938: readUnlock(); 939: } 940: } 941: 942: /** 943: * Sets the asynchronous loading priority for this <code>Document</code>. 944: * A value of <code>-1</code> indicates that this <code>Document</code> 945: * should be loaded synchronously. 946: * 947: * @param p the asynchronous loading priority to set 948: */ 949: public void setAsynchronousLoadPriority(int p) 950: { 951: // TODO: Implement this properly. 952: } 953: 954: /** 955: * Sets the properties of this <code>Document</code>. 956: * 957: * @param p the document properties to set 958: */ 959: public void setDocumentProperties(Dictionary p) 960: { 961: // FIXME: make me thread-safe 962: properties = p; 963: } 964: 965: /** 966: * Blocks until a write lock can be obtained. Must wait if there are 967: * readers currently reading or another thread is currently writing. 968: */ 969: protected final void writeLock() 970: { 971: if (currentWriter != null && currentWriter.equals(Thread.currentThread())) 972: return; 973: synchronized (documentCV) 974: { 975: numWritersWaiting++; 976: while (numReaders > 0) 977: { 978: try 979: { 980: documentCV.wait(); 981: } 982: catch (InterruptedException ie) 983: { 984: throw new Error("interruped while trying to obtain write lock"); 985: } 986: } 987: numWritersWaiting --; 988: currentWriter = Thread.currentThread(); 989: } 990: } 991: 992: /** 993: * Releases the write lock. This allows waiting readers or writers to 994: * obtain the lock. 995: */ 996: protected final void writeUnlock() 997: { 998: synchronized (documentCV) 999: { 1000: if (Thread.currentThread().equals(currentWriter)) 1001: { 1002: currentWriter = null; 1003: documentCV.notifyAll(); 1004: } 1005: } 1006: } 1007: 1008: /** 1009: * Returns the currently installed {@link DocumentFilter} for this 1010: * <code>Document</code>. 1011: * 1012: * @return the currently installed {@link DocumentFilter} for this 1013: * <code>Document</code> 1014: * 1015: * @since 1.4 1016: */ 1017: public DocumentFilter getDocumentFilter() 1018: { 1019: return documentFilter; 1020: } 1021: 1022: /** 1023: * Sets the {@link DocumentFilter} for this <code>Document</code>. 1024: * 1025: * @param filter the <code>DocumentFilter</code> to set 1026: * 1027: * @since 1.4 1028: */ 1029: public void setDocumentFilter(DocumentFilter filter) 1030: { 1031: this.documentFilter = filter; 1032: } 1033: 1034: /** 1035: * Dumps diagnostic information to the specified <code>PrintStream</code>. 1036: * 1037: * @param out the stream to write the diagnostic information to 1038: */ 1039: public void dump(PrintStream out) 1040: { 1041: ((AbstractElement) getDefaultRootElement()).dump(out, 0); 1042: } 1043: 1044: /** 1045: * Defines a set of methods for managing text attributes for one or more 1046: * <code>Document</code>s. 1047: * 1048: * Replicating {@link AttributeSet}s throughout a <code>Document</code> can 1049: * be very expensive. Implementations of this interface are intended to 1050: * provide intelligent management of <code>AttributeSet</code>s, eliminating 1051: * costly duplication. 1052: * 1053: * @see StyleContext 1054: */ 1055: public interface AttributeContext 1056: { 1057: /** 1058: * Returns an {@link AttributeSet} that contains the attributes 1059: * of <code>old</code> plus the new attribute specified by 1060: * <code>name</code> and <code>value</code>. 1061: * 1062: * @param old the attribute set to be merged with the new attribute 1063: * @param name the name of the attribute to be added 1064: * @param value the value of the attribute to be added 1065: * 1066: * @return the old attributes plus the new attribute 1067: */ 1068: AttributeSet addAttribute(AttributeSet old, Object name, Object value); 1069: 1070: /** 1071: * Returns an {@link AttributeSet} that contains the attributes 1072: * of <code>old</code> plus the new attributes in <code>attributes</code>. 1073: * 1074: * @param old the set of attributes where to add the new attributes 1075: * @param attributes the attributes to be added 1076: * 1077: * @return an {@link AttributeSet} that contains the attributes 1078: * of <code>old</code> plus the new attributes in 1079: * <code>attributes</code> 1080: */ 1081: AttributeSet addAttributes(AttributeSet old, AttributeSet attributes); 1082: 1083: /** 1084: * Returns an empty {@link AttributeSet}. 1085: * 1086: * @return an empty {@link AttributeSet} 1087: */ 1088: AttributeSet getEmptySet(); 1089: 1090: /** 1091: * Called to indicate that the attributes in <code>attributes</code> are 1092: * no longer used. 1093: * 1094: * @param attributes the attributes are no longer used 1095: */ 1096: void reclaim(AttributeSet attributes); 1097: 1098: /** 1099: * Returns a {@link AttributeSet} that has the attribute with the specified 1100: * <code>name</code> removed from <code>old</code>. 1101: * 1102: * @param old the attribute set from which an attribute is removed 1103: * @param name the name of the attribute to be removed 1104: * 1105: * @return the attributes of <code>old</code> minus the attribute 1106: * specified by <code>name</code> 1107: */ 1108: AttributeSet removeAttribute(AttributeSet old, Object name); 1109: 1110: /** 1111: * Removes all attributes in <code>attributes</code> from <code>old</code> 1112: * and returns the resulting <code>AttributeSet</code>. 1113: * 1114: * @param old the set of attributes from which to remove attributes 1115: * @param attributes the attributes to be removed from <code>old</code> 1116: * 1117: * @return the attributes of <code>old</code> minus the attributes in 1118: * <code>attributes</code> 1119: */ 1120: AttributeSet removeAttributes(AttributeSet old, AttributeSet attributes); 1121: 1122: /** 1123: * Removes all attributes specified by <code>names</code> from 1124: * <code>old</code> and returns the resulting <code>AttributeSet</code>. 1125: * 1126: * @param old the set of attributes from which to remove attributes 1127: * @param names the names of the attributes to be removed from 1128: * <code>old</code> 1129: * 1130: * @return the attributes of <code>old</code> minus the attributes in 1131: * <code>attributes</code> 1132: */ 1133: AttributeSet removeAttributes(AttributeSet old, Enumeration names); 1134: } 1135: 1136: /** 1137: * A sequence of data that can be edited. This is were the actual content 1138: * in <code>AbstractDocument</code>'s is stored. 1139: */ 1140: public interface Content 1141: { 1142: /** 1143: * Creates a {@link Position} that keeps track of the location at 1144: * <code>offset</code>. 1145: * 1146: * @return a {@link Position} that keeps track of the location at 1147: * <code>offset</code>. 1148: * 1149: * @throw BadLocationException if <code>offset</code> is not a valid 1150: * location in this <code>Content</code> model 1151: */ 1152: Position createPosition(int offset) throws BadLocationException; 1153: 1154: /** 1155: * Returns the length of the content. 1156: * 1157: * @return the length of the content 1158: */ 1159: int length(); 1160: 1161: /** 1162: * Inserts a string into the content model. 1163: * 1164: * @param where the offset at which to insert the string 1165: * @param str the string to be inserted 1166: * 1167: * @return an <code>UndoableEdit</code> or <code>null</code> if undo is 1168: * not supported by this <code>Content</code> model 1169: * 1170: * @throws BadLocationException if <code>where</code> is not a valid 1171: * location in this <code>Content</code> model 1172: */ 1173: UndoableEdit insertString(int where, String str) 1174: throws BadLocationException; 1175: 1176: /** 1177: * Removes a piece of content from the content model. 1178: * 1179: * @param where the offset at which to remove content 1180: * @param nitems the number of characters to be removed 1181: * 1182: * @return an <code>UndoableEdit</code> or <code>null</code> if undo is 1183: * not supported by this <code>Content</code> model 1184: * 1185: * @throws BadLocationException if <code>where</code> is not a valid 1186: * location in this <code>Content</code> model 1187: */ 1188: UndoableEdit remove(int where, int nitems) throws BadLocationException; 1189: 1190: /** 1191: * Returns a piece of content. 1192: * 1193: * @param where the start offset of the requested fragment 1194: * @param len the length of the requested fragment 1195: * 1196: * @return the requested fragment 1197: * @throws BadLocationException if <code>offset</code> or 1198: * <code>offset + len</code>is not a valid 1199: * location in this <code>Content</code> model 1200: */ 1201: String getString(int where, int len) throws BadLocationException; 1202: 1203: /** 1204: * Fetches a piece of content and stores it in <code>txt</code>. 1205: * 1206: * @param where the start offset of the requested fragment 1207: * @param len the length of the requested fragment 1208: * @param txt the <code>Segment</code> where to fragment is stored into 1209: * 1210: * @throws BadLocationException if <code>offset</code> or 1211: * <code>offset + len</code>is not a valid 1212: * location in this <code>Content</code> model 1213: */ 1214: void getChars(int where, int len, Segment txt) throws BadLocationException; 1215: } 1216: 1217: /** 1218: * An abstract base implementation of the {@link Element} interface. 1219: */ 1220: public abstract class AbstractElement 1221: implements Element, MutableAttributeSet, TreeNode, Serializable 1222: { 1223: /** The serialization UID (compatible with JDK1.5). */ 1224: private static final long serialVersionUID = 1712240033321461704L; 1225: 1226: /** The number of characters that this Element spans. */ 1227: int count; 1228: 1229: /** The starting offset of this Element. */ 1230: int offset; 1231: 1232: /** The attributes of this Element. */ 1233: AttributeSet attributes; 1234: 1235: /** The parent element. */ 1236: Element element_parent; 1237: 1238: /** The parent in the TreeNode interface. */ 1239: TreeNode tree_parent; 1240: 1241: /** The children of this element. */ 1242: Vector tree_children; 1243: 1244: /** 1245: * Creates a new instance of <code>AbstractElement</code> with a 1246: * specified parent <code>Element</code> and <code>AttributeSet</code>. 1247: * 1248: * @param p the parent of this <code>AbstractElement</code> 1249: * @param s the attributes to be assigned to this 1250: * <code>AbstractElement</code> 1251: */ 1252: public AbstractElement(Element p, AttributeSet s) 1253: { 1254: element_parent = p; 1255: AttributeContext ctx = getAttributeContext(); 1256: attributes = ctx.getEmptySet(); 1257: if (s != null) 1258: attributes = ctx.addAttributes(attributes, s); 1259: } 1260: 1261: /** 1262: * Returns the child nodes of this <code>Element</code> as an 1263: * <code>Enumeration</code> of {@link TreeNode}s. 1264: * 1265: * @return the child nodes of this <code>Element</code> as an 1266: * <code>Enumeration</code> of {@link TreeNode}s 1267: */ 1268: public abstract Enumeration children(); 1269: 1270: /** 1271: * Returns <code>true</code> if this <code>AbstractElement</code> 1272: * allows children. 1273: * 1274: * @return <code>true</code> if this <code>AbstractElement</code> 1275: * allows children 1276: */ 1277: public abstract boolean getAllowsChildren(); 1278: 1279: /** 1280: * Returns the child of this <code>AbstractElement</code> at 1281: * <code>index</code>. 1282: * 1283: * @param index the position in the child list of the child element to 1284: * be returned 1285: * 1286: * @return the child of this <code>AbstractElement</code> at 1287: * <code>index</code> 1288: */ 1289: public TreeNode getChildAt(int index) 1290: { 1291: return (TreeNode) tree_children.get(index); 1292: } 1293: 1294: /** 1295: * Returns the number of children of this <code>AbstractElement</code>. 1296: * 1297: * @return the number of children of this <code>AbstractElement</code> 1298: */ 1299: public int getChildCount() 1300: { 1301: return tree_children.size(); 1302: } 1303: 1304: /** 1305: * Returns the index of a given child <code>TreeNode</code> or 1306: * <code>-1</code> if <code>node</code> is not a child of this 1307: * <code>AbstractElement</code>. 1308: * 1309: * @param node the node for which the index is requested 1310: * 1311: * @return the index of a given child <code>TreeNode</code> or 1312: * <code>-1</code> if <code>node</code> is not a child of this 1313: * <code>AbstractElement</code> 1314: */ 1315: public int getIndex(TreeNode node) 1316: { 1317: return tree_children.indexOf(node); 1318: } 1319: 1320: /** 1321: * Returns the parent <code>TreeNode</code> of this 1322: * <code>AbstractElement</code> or <code>null</code> if this element 1323: * has no parent. 1324: * 1325: * @return the parent <code>TreeNode</code> of this 1326: * <code>AbstractElement</code> or <code>null</code> if this 1327: * element has no parent 1328: */ 1329: public TreeNode getParent() 1330: { 1331: return tree_parent; 1332: } 1333: 1334: /** 1335: * Returns <code>true</code> if this <code>AbstractElement</code> is a 1336: * leaf element, <code>false</code> otherwise. 1337: * 1338: * @return <code>true</code> if this <code>AbstractElement</code> is a 1339: * leaf element, <code>false</code> otherwise 1340: */ 1341: public abstract boolean isLeaf(); 1342: 1343: /** 1344: * Adds an attribute to this element. 1345: * 1346: * @param name the name of the attribute to be added 1347: * @param value the value of the attribute to be added 1348: */ 1349: public void addAttribute(Object name, Object value) 1350: { 1351: attributes = getAttributeContext().addAttribute(attributes, name, value); 1352: } 1353: 1354: /** 1355: * Adds a set of attributes to this element. 1356: * 1357: * @param attrs the attributes to be added to this element 1358: */ 1359: public void addAttributes(AttributeSet attrs) 1360: { 1361: attributes = getAttributeContext().addAttributes(attributes, attrs); 1362: } 1363: 1364: /** 1365: * Removes an attribute from this element. 1366: * 1367: * @param name the name of the attribute to be removed 1368: */ 1369: public void removeAttribute(Object name) 1370: { 1371: attributes = getAttributeContext().removeAttribute(attributes, name); 1372: } 1373: 1374: /** 1375: * Removes a set of attributes from this element. 1376: * 1377: * @param attrs the attributes to be removed 1378: */ 1379: public void removeAttributes(AttributeSet attrs) 1380: { 1381: attributes = getAttributeContext().removeAttributes(attributes, attrs); 1382: } 1383: 1384: /** 1385: * Removes a set of attribute from this element. 1386: * 1387: * @param names the names of the attributes to be removed 1388: */ 1389: public void removeAttributes(Enumeration names) 1390: { 1391: attributes = getAttributeContext().removeAttributes(attributes, names); 1392: } 1393: 1394: /** 1395: * Sets the parent attribute set against which the element can resolve 1396: * attributes that are not defined in itself. 1397: * 1398: * @param parent the resolve parent to set 1399: */ 1400: public void setResolveParent(AttributeSet parent) 1401: { 1402: attributes = getAttributeContext().addAttribute(attributes, 1403: ResolveAttribute, 1404: parent); 1405: } 1406: 1407: /** 1408: * Returns <code>true</code> if this element contains the specified 1409: * attribute. 1410: * 1411: * @param name the name of the attribute to check 1412: * @param value the value of the attribute to check 1413: * 1414: * @return <code>true</code> if this element contains the specified 1415: * attribute 1416: */ 1417: public boolean containsAttribute(Object name, Object value) 1418: { 1419: return attributes.containsAttribute(name, value); 1420: } 1421: 1422: /** 1423: * Returns <code>true</code> if this element contains all of the 1424: * specified attributes. 1425: * 1426: * @param attrs the attributes to check 1427: * 1428: * @return <code>true</code> if this element contains all of the 1429: * specified attributes 1430: */ 1431: public boolean containsAttributes(AttributeSet attrs) 1432: { 1433: return attributes.containsAttributes(attrs); 1434: } 1435: 1436: /** 1437: * Returns a copy of the attributes of this element. 1438: * 1439: * @return a copy of the attributes of this element 1440: */ 1441: public AttributeSet copyAttributes() 1442: { 1443: return attributes.copyAttributes(); 1444: } 1445: 1446: /** 1447: * Returns the attribute value with the specified key. If this attribute 1448: * is not defined in this element and this element has a resolving 1449: * parent, the search goes upward to the resolve parent chain. 1450: * 1451: * @param key the key of the requested attribute 1452: * 1453: * @return the attribute value for <code>key</code> of <code>null</code> 1454: * if <code>key</code> is not found locally and cannot be resolved 1455: * in this element's resolve parents 1456: */ 1457: public Object getAttribute(Object key) 1458: { 1459: Object result = attributes.getAttribute(key); 1460: if (result == null) 1461: { 1462: AttributeSet resParent = getResolveParent(); 1463: if (resParent != null) 1464: result = resParent.getAttribute(key); 1465: } 1466: return result; 1467: } 1468: 1469: /** 1470: * Returns the number of defined attributes in this element. 1471: * 1472: * @return the number of defined attributes in this element 1473: */ 1474: public int getAttributeCount() 1475: { 1476: return attributes.getAttributeCount(); 1477: } 1478: 1479: /** 1480: * Returns the names of the attributes of this element. 1481: * 1482: * @return the names of the attributes of this element 1483: */ 1484: public Enumeration getAttributeNames() 1485: { 1486: return attributes.getAttributeNames(); 1487: } 1488: 1489: /** 1490: * Returns the resolve parent of this element. 1491: * This is taken from the AttributeSet, but if this is null, 1492: * this method instead returns the Element's parent's 1493: * AttributeSet 1494: * 1495: * @return the resolve parent of this element 1496: * 1497: * @see #setResolveParent(AttributeSet) 1498: */ 1499: public AttributeSet getResolveParent() 1500: { 1501: return attributes.getResolveParent(); 1502: } 1503: 1504: /** 1505: * Returns <code>true</code> if an attribute with the specified name 1506: * is defined in this element, <code>false</code> otherwise. 1507: * 1508: * @param attrName the name of the requested attributes 1509: * 1510: * @return <code>true</code> if an attribute with the specified name 1511: * is defined in this element, <code>false</code> otherwise 1512: */ 1513: public boolean isDefined(Object attrName) 1514: { 1515: return attributes.isDefined(attrName); 1516: } 1517: 1518: /** 1519: * Returns <code>true</code> if the specified <code>AttributeSet</code> 1520: * is equal to this element's <code>AttributeSet</code>, <code>false</code> 1521: * otherwise. 1522: * 1523: * @param attrs the attributes to compare this element to 1524: * 1525: * @return <code>true</code> if the specified <code>AttributeSet</code> 1526: * is equal to this element's <code>AttributeSet</code>, 1527: * <code>false</code> otherwise 1528: */ 1529: public boolean isEqual(AttributeSet attrs) 1530: { 1531: return attributes.isEqual(attrs); 1532: } 1533: 1534: /** 1535: * Returns the attributes of this element. 1536: * 1537: * @return the attributes of this element 1538: */ 1539: public AttributeSet getAttributes() 1540: { 1541: return this; 1542: } 1543: 1544: /** 1545: * Returns the {@link Document} to which this element belongs. 1546: * 1547: * @return the {@link Document} to which this element belongs 1548: */ 1549: public Document getDocument() 1550: { 1551: return AbstractDocument.this; 1552: } 1553: 1554: /** 1555: * Returns the child element at the specified <code>index</code>. 1556: * 1557: * @param index the index of the requested child element 1558: * 1559: * @return the requested element 1560: */ 1561: public abstract Element getElement(int index); 1562: 1563: /** 1564: * Returns the name of this element. 1565: * 1566: * @return the name of this element 1567: */ 1568: public String getName() 1569: { 1570: return (String) getAttribute(NameAttribute); 1571: } 1572: 1573: /** 1574: * Returns the parent element of this element. 1575: * 1576: * @return the parent element of this element 1577: */ 1578: public Element getParentElement() 1579: { 1580: return element_parent; 1581: } 1582: 1583: /** 1584: * Returns the offset inside the document model that is after the last 1585: * character of this element. 1586: * 1587: * @return the offset inside the document model that is after the last 1588: * character of this element 1589: */ 1590: public abstract int getEndOffset(); 1591: 1592: /** 1593: * Returns the number of child elements of this element. 1594: * 1595: * @return the number of child elements of this element 1596: */ 1597: public abstract int getElementCount(); 1598: 1599: /** 1600: * Returns the index of the child element that spans the specified 1601: * offset in the document model. 1602: * 1603: * @param offset the offset for which the responsible element is searched 1604: * 1605: * @return the index of the child element that spans the specified 1606: * offset in the document model 1607: */ 1608: public abstract int getElementIndex(int offset); 1609: 1610: /** 1611: * Returns the start offset if this element inside the document model. 1612: * 1613: * @return the start offset if this element inside the document model 1614: */ 1615: public abstract int getStartOffset(); 1616: 1617: /** 1618: * Prints diagnostic output to the specified stream. 1619: * 1620: * @param stream the stream to write to 1621: * @param indent the indentation level 1622: */ 1623: public void dump(PrintStream stream, int indent) 1624: { 1625: StringBuffer b = new StringBuffer(); 1626: for (int i = 0; i < indent; ++i) 1627: b.append(' '); 1628: b.append('<'); 1629: b.append(getName()); 1630: // Dump attributes if there are any. 1631: if (getAttributeCount() > 0) 1632: { 1633: b.append('\n'); 1634: Enumeration attNames = getAttributeNames(); 1635: while (attNames.hasMoreElements()) 1636: { 1637: for (int i = 0; i < indent + 2; ++i) 1638: b.append(' '); 1639: Object attName = attNames.nextElement(); 1640: b.append(attName); 1641: b.append('='); 1642: Object attribute = getAttribute(attName); 1643: b.append(attribute); 1644: b.append('\n'); 1645: } 1646: } 1647: b.append(">\n"); 1648: 1649: // Dump element content for leaf elements. 1650: if (isLeaf()) 1651: { 1652: for (int i = 0; i < indent + 2; ++i) 1653: b.append(' '); 1654: int start = getStartOffset(); 1655: int end = getEndOffset(); 1656: b.append('['); 1657: b.append(start); 1658: b.append(','); 1659: b.append(end); 1660: b.append("]["); 1661: try 1662: { 1663: b.append(getDocument().getText(start, end - start)); 1664: } 1665: catch (BadLocationException ex) 1666: { 1667: AssertionError err = new AssertionError("BadLocationException " 1668: + "must not be thrown " 1669: + "here."); 1670: err.initCause(ex); 1671: throw err; 1672: } 1673: b.append("]\n"); 1674: } 1675: stream.print(b.toString()); 1676: 1677: // Dump child elements if any. 1678: int count = getElementCount(); 1679: for (int i = 0; i < count; ++i) 1680: { 1681: Element el = getElement(i); 1682: if (el instanceof AbstractElement) 1683: ((AbstractElement) el).dump(stream, indent + 2); 1684: } 1685: } 1686: } 1687: 1688: /** 1689: * An implementation of {@link Element} to represent composite 1690: * <code>Element</code>s that contain other <code>Element</code>s. 1691: */ 1692: public class BranchElement extends AbstractElement 1693: { 1694: /** The serialization UID (compatible with JDK1.5). */ 1695: private static final long serialVersionUID = -6037216547466333183L; 1696: 1697: /** 1698: * The child elements of this BranchElement. 1699: */ 1700: private Element[] children;; 1701: 1702: /** 1703: * The number of children in the branch element. 1704: */ 1705: private int numChildren; 1706: 1707: /** 1708: * Creates a new <code>BranchElement</code> with the specified 1709: * parent and attributes. 1710: * 1711: * @param parent the parent element of this <code>BranchElement</code> 1712: * @param attributes the attributes to set on this 1713: * <code>BranchElement</code> 1714: */ 1715: public BranchElement(Element parent, AttributeSet attributes) 1716: { 1717: super(parent, attributes); 1718: children = new Element[1]; 1719: numChildren = 0; 1720: } 1721: 1722: /** 1723: * Returns the children of this <code>BranchElement</code>. 1724: * 1725: * @return the children of this <code>BranchElement</code> 1726: */ 1727: public Enumeration children() 1728: { 1729: if (children.length == 0) 1730: return null; 1731: 1732: Vector tmp = new Vector(); 1733: 1734: for (int index = 0; index < numChildren; ++index) 1735: tmp.add(children[index]); 1736: 1737: return tmp.elements(); 1738: } 1739: 1740: /** 1741: * Returns <code>true</code> since <code>BranchElements</code> allow 1742: * child elements. 1743: * 1744: * @return <code>true</code> since <code>BranchElements</code> allow 1745: * child elements 1746: */ 1747: public boolean getAllowsChildren() 1748: { 1749: return true; 1750: } 1751: 1752: /** 1753: * Returns the child element at the specified <code>index</code>. 1754: * 1755: * @param index the index of the requested child element 1756: * 1757: * @return the requested element 1758: */ 1759: public Element getElement(int index) 1760: { 1761: if (index < 0 || index >= numChildren) 1762: return null; 1763: 1764: return children[index]; 1765: } 1766: 1767: /** 1768: * Returns the number of child elements of this element. 1769: * 1770: * @return the number of child elements of this element 1771: */ 1772: public int getElementCount() 1773: { 1774: return numChildren; 1775: } 1776: 1777: /** 1778: * Returns the index of the child element that spans the specified 1779: * offset in the document model. 1780: * 1781: * @param offset the offset for which the responsible element is searched 1782: * 1783: * @return the index of the child element that spans the specified 1784: * offset in the document model 1785: */ 1786: public int getElementIndex(int offset) 1787: { 1788: // If offset is less than the start offset of our first child, 1789: // return 0 1790: if (offset < getStartOffset()) 1791: return 0; 1792: 1793: // XXX: There is surely a better algorithm 1794: // as beginning from first element each time. 1795: for (int index = 0; index < numChildren - 1; ++index) 1796: { 1797: Element elem = children[index]; 1798: 1799: if ((elem.getStartOffset() <= offset) 1800: && (offset < elem.getEndOffset())) 1801: return index; 1802: // If the next element's start offset is greater than offset 1803: // then we have to return the closest Element, since no Elements 1804: // will contain the offset 1805: if (children[index + 1].getStartOffset() > offset) 1806: { 1807: if ((offset - elem.getEndOffset()) > (children[index + 1].getStartOffset() - offset)) 1808: return index + 1; 1809: else 1810: return index; 1811: } 1812: } 1813: 1814: // If offset is greater than the index of the last element, return 1815: // the index of the last element. 1816: return getElementCount() - 1; 1817: } 1818: 1819: /** 1820: * Returns the offset inside the document model that is after the last 1821: * character of this element. 1822: * This is the end offset of the last child element. If this element 1823: * has no children, this method throws a <code>NullPointerException</code>. 1824: * 1825: * @return the offset inside the document model that is after the last 1826: * character of this element 1827: * 1828: * @throws NullPointerException if this branch element has no children 1829: */ 1830: public int getEndOffset() 1831: { 1832: // This might accss one cached element or trigger an NPE for 1833: // numChildren == 0. This is checked by a Mauve test. 1834: Element child = numChildren > 0 ? children[numChildren - 1] 1835: : children[0]; 1836: return child.getEndOffset(); 1837: } 1838: 1839: /** 1840: * Returns the name of this element. This is {@link #ParagraphElementName} 1841: * in this case. 1842: * 1843: * @return the name of this element 1844: */ 1845: public String getName() 1846: { 1847: return ParagraphElementName; 1848: } 1849: 1850: /** 1851: * Returns the start offset of this element inside the document model. 1852: * This is the start offset of the first child element. If this element 1853: * has no children, this method throws a <code>NullPointerException</code>. 1854: * 1855: * @return the start offset of this element inside the document model 1856: * 1857: * @throws NullPointerException if this branch element has no children and 1858: * no startOffset value has been cached 1859: */ 1860: public int getStartOffset() 1861: { 1862: // Do not explicitly throw an NPE here. If the first element is null 1863: // then the NPE gets thrown anyway. If it isn't, then it either 1864: // holds a real value (for numChildren > 0) or a cached value 1865: // (for numChildren == 0) as we don't fully remove elements in replace() 1866: // when removing single elements. 1867: // This is checked by a Mauve test. 1868: return children[0].getStartOffset(); 1869: } 1870: 1871: /** 1872: * Returns <code>false</code> since <code>BranchElement</code> are no 1873: * leafes. 1874: * 1875: * @return <code>false</code> since <code>BranchElement</code> are no 1876: * leafes 1877: */ 1878: public boolean isLeaf() 1879: { 1880: return false; 1881: } 1882: 1883: /** 1884: * Returns the <code>Element</code> at the specified <code>Document</code> 1885: * offset. 1886: * 1887: * @return the <code>Element</code> at the specified <code>Document</code> 1888: * offset 1889: * 1890: * @see #getElementIndex(int) 1891: */ 1892: public Element positionToElement(int position) 1893: { 1894: // XXX: There is surely a better algorithm 1895: // as beginning from first element each time. 1896: for (int index = 0; index < numChildren; ++index) 1897: { 1898: Element elem = children[index]; 1899: 1900: if ((elem.getStartOffset() <= position) 1901: && (position < elem.getEndOffset())) 1902: return elem; 1903: } 1904: 1905: return null; 1906: } 1907: 1908: /** 1909: * Replaces a set of child elements with a new set of child elemens. 1910: * 1911: * @param offset the start index of the elements to be removed 1912: * @param length the number of elements to be removed 1913: * @param elements the new elements to be inserted 1914: */ 1915: public void replace(int offset, int length, Element[] elements) 1916: { 1917: int delta = elements.length - length; 1918: int copyFrom = offset + length; // From where to copy. 1919: int copyTo = copyFrom + delta; // Where to copy to. 1920: int numMove = numChildren - copyFrom; // How many elements are moved. 1921: if (numChildren + delta > children.length) 1922: { 1923: // Gotta grow the array. 1924: int newSize = Math.max(2 * children.length, numChildren + delta); 1925: Element[] target = new Element[newSize]; 1926: System.arraycopy(children, 0, target, 0, offset); 1927: System.arraycopy(elements, 0, target, offset, elements.length); 1928: System.arraycopy(children, copyFrom, target, copyTo, numMove); 1929: children = target; 1930: } 1931: else 1932: { 1933: System.arraycopy(children, copyFrom, children, copyTo, numMove); 1934: System.arraycopy(elements, 0, children, offset, elements.length); 1935: } 1936: numChildren += delta; 1937: } 1938: 1939: /** 1940: * Returns a string representation of this element. 1941: * 1942: * @return a string representation of this element 1943: */ 1944: public String toString() 1945: { 1946: return ("BranchElement(" + getName() + ") " 1947: + getStartOffset() + "," + getEndOffset() + "\n"); 1948: } 1949: } 1950: 1951: /** 1952: * Stores the changes when a <code>Document</code> is beeing modified. 1953: */ 1954: public class DefaultDocumentEvent extends CompoundEdit 1955: implements DocumentEvent 1956: { 1957: /** The serialization UID (compatible with JDK1.5). */ 1958: private static final long serialVersionUID = 5230037221564563284L; 1959: 1960: /** The starting offset of the change. */ 1961: private int offset; 1962: 1963: /** The length of the change. */ 1964: private int length; 1965: 1966: /** The type of change. */ 1967: private DocumentEvent.EventType type; 1968: 1969: /** 1970: * Maps <code>Element</code> to their change records. 1971: */ 1972: Hashtable changes; 1973: 1974: /** 1975: * Indicates if this event has been modified or not. This is used to 1976: * determine if this event is thrown. 1977: */ 1978: boolean modified; 1979: 1980: /** 1981: * Creates a new <code>DefaultDocumentEvent</code>. 1982: * 1983: * @param offset the starting offset of the change 1984: * @param length the length of the change 1985: * @param type the type of change 1986: */ 1987: public DefaultDocumentEvent(int offset, int length, 1988: DocumentEvent.EventType type) 1989: { 1990: this.offset = offset; 1991: this.length = length; 1992: this.type = type; 1993: changes = new Hashtable(); 1994: modified = false; 1995: } 1996: 1997: /** 1998: * Adds an UndoableEdit to this <code>DocumentEvent</code>. If this 1999: * edit is an instance of {@link ElementEdit}, then this record can 2000: * later be fetched by calling {@link #getChange}. 2001: * 2002: * @param edit the undoable edit to add 2003: */ 2004: public boolean addEdit(UndoableEdit edit) 2005: { 2006: // XXX - Fully qualify ElementChange to work around gcj bug #2499. 2007: if (edit instanceof DocumentEvent.ElementChange) 2008: { 2009: modified = true; 2010: DocumentEvent.ElementChange elEdit = 2011: (DocumentEvent.ElementChange) edit; 2012: changes.put(elEdit.getElement(), elEdit); 2013: } 2014: return super.addEdit(edit); 2015: } 2016: 2017: /** 2018: * Returns the document that has been modified. 2019: * 2020: * @return the document that has been modified 2021: */ 2022: public Document getDocument() 2023: { 2024: return AbstractDocument.this; 2025: } 2026: 2027: /** 2028: * Returns the length of the modification. 2029: * 2030: * @return the length of the modification 2031: */ 2032: public int getLength() 2033: { 2034: return length; 2035: } 2036: 2037: /** 2038: * Returns the start offset of the modification. 2039: * 2040: * @return the start offset of the modification 2041: */ 2042: public int getOffset() 2043: { 2044: return offset; 2045: } 2046: 2047: /** 2048: * Returns the type of the modification. 2049: * 2050: * @return the type of the modification 2051: */ 2052: public DocumentEvent.EventType getType() 2053: { 2054: return type; 2055: } 2056: 2057: /** 2058: * Returns the changes for an element. 2059: * 2060: * @param elem the element for which the changes are requested 2061: * 2062: * @return the changes for <code>elem</code> or <code>null</code> if 2063: * <code>elem</code> has not been changed 2064: */ 2065: public DocumentEvent.ElementChange getChange(Element elem) 2066: { 2067: // XXX - Fully qualify ElementChange to work around gcj bug #2499. 2068: return (DocumentEvent.ElementChange) changes.get(elem); 2069: } 2070: 2071: /** 2072: * Returns a String description of the change event. This returns the 2073: * toString method of the Vector of edits. 2074: */ 2075: public String toString() 2076: { 2077: return edits.toString(); 2078: } 2079: } 2080: 2081: /** 2082: * An implementation of {@link DocumentEvent.ElementChange} to be added 2083: * to {@link DefaultDocumentEvent}s. 2084: */ 2085: public static class ElementEdit extends AbstractUndoableEdit 2086: implements DocumentEvent.ElementChange 2087: { 2088: /** The serial version UID of ElementEdit. */ 2089: private static final long serialVersionUID = -1216620962142928304L; 2090: 2091: /** 2092: * The changed element. 2093: */ 2094: private Element elem; 2095: 2096: /** 2097: * The index of the change. 2098: */ 2099: private int index; 2100: 2101: /** 2102: * The removed elements. 2103: */ 2104: private Element[] removed; 2105: 2106: /** 2107: * The added elements. 2108: */ 2109: private Element[] added; 2110: 2111: /** 2112: * Creates a new <code>ElementEdit</code>. 2113: * 2114: * @param elem the changed element 2115: * @param index the index of the change 2116: * @param removed the removed elements 2117: * @param added the added elements 2118: */ 2119: public ElementEdit(Element elem, int index, 2120: Element[] removed, Element[] added) 2121: { 2122: this.elem = elem; 2123: this.index = index; 2124: this.removed = removed; 2125: this.added = added; 2126: } 2127: 2128: /** 2129: * Returns the added elements. 2130: * 2131: * @return the added elements 2132: */ 2133: public Element[] getChildrenAdded() 2134: { 2135: return added; 2136: } 2137: 2138: /** 2139: * Returns the removed elements. 2140: * 2141: * @return the removed elements 2142: */ 2143: public Element[] getChildrenRemoved() 2144: { 2145: return removed; 2146: } 2147: 2148: /** 2149: * Returns the changed element. 2150: * 2151: * @return the changed element 2152: */ 2153: public Element getElement() 2154: { 2155: return elem; 2156: } 2157: 2158: /** 2159: * Returns the index of the change. 2160: * 2161: * @return the index of the change 2162: */ 2163: public int getIndex() 2164: { 2165: return index; 2166: } 2167: } 2168: 2169: /** 2170: * An implementation of {@link Element} that represents a leaf in the 2171: * document structure. This is used to actually store content. 2172: */ 2173: public class LeafElement extends AbstractElement 2174: { 2175: /** The serialization UID (compatible with JDK1.5). */ 2176: private static final long serialVersionUID = -8906306331347768017L; 2177: 2178: /** 2179: * Manages the start offset of this element. 2180: */ 2181: private Position startPos; 2182: 2183: /** 2184: * Manages the end offset of this element. 2185: */ 2186: private Position endPos; 2187: 2188: /** 2189: * Creates a new <code>LeafElement</code>. 2190: * 2191: * @param parent the parent of this <code>LeafElement</code> 2192: * @param attributes the attributes to be set 2193: * @param start the start index of this element inside the document model 2194: * @param end the end index of this element inside the document model 2195: */ 2196: public LeafElement(Element parent, AttributeSet attributes, int start, 2197: int end) 2198: { 2199: super(parent, attributes); 2200: try 2201: { 2202: startPos = createPosition(start); 2203: endPos = createPosition(end); 2204: } 2205: catch (BadLocationException ex) 2206: { 2207: AssertionError as; 2208: as = new AssertionError("BadLocationException thrown " 2209: + "here. start=" + start 2210: + ", end=" + end 2211: + ", length=" + getLength()); 2212: as.initCause(ex); 2213: throw as; 2214: } 2215: } 2216: 2217: /** 2218: * Returns <code>null</code> since <code>LeafElement</code>s cannot have 2219: * children. 2220: * 2221: * @return <code>null</code> since <code>LeafElement</code>s cannot have 2222: * children 2223: */ 2224: public Enumeration children() 2225: { 2226: return null; 2227: } 2228: 2229: /** 2230: * Returns <code>false</code> since <code>LeafElement</code>s cannot have 2231: * children. 2232: * 2233: * @return <code>false</code> since <code>LeafElement</code>s cannot have 2234: * children 2235: */ 2236: public boolean getAllowsChildren() 2237: { 2238: return false; 2239: } 2240: 2241: /** 2242: * Returns <code>null</code> since <code>LeafElement</code>s cannot have 2243: * children. 2244: * 2245: * @return <code>null</code> since <code>LeafElement</code>s cannot have 2246: * children 2247: */ 2248: public Element getElement(int index) 2249: { 2250: return null; 2251: } 2252: 2253: /** 2254: * Returns <code>0</code> since <code>LeafElement</code>s cannot have 2255: * children. 2256: * 2257: * @return <code>0</code> since <code>LeafElement</code>s cannot have 2258: * children 2259: */ 2260: public int getElementCount() 2261: { 2262: return 0; 2263: } 2264: 2265: /** 2266: * Returns <code>-1</code> since <code>LeafElement</code>s cannot have 2267: * children. 2268: * 2269: * @return <code>-1</code> since <code>LeafElement</code>s cannot have 2270: * children 2271: */ 2272: public int getElementIndex(int offset) 2273: { 2274: return -1; 2275: } 2276: 2277: /** 2278: * Returns the end offset of this <code>Element</code> inside the 2279: * document. 2280: * 2281: * @return the end offset of this <code>Element</code> inside the 2282: * document 2283: */ 2284: public int getEndOffset() 2285: { 2286: return endPos.getOffset(); 2287: } 2288: 2289: /** 2290: * Returns the name of this <code>Element</code>. This is 2291: * {@link #ContentElementName} in this case. 2292: * 2293: * @return the name of this <code>Element</code> 2294: */ 2295: public String getName() 2296: { 2297: String name = super.getName(); 2298: if (name == null) 2299: name = ContentElementName; 2300: return name; 2301: } 2302: 2303: /** 2304: * Returns the start offset of this <code>Element</code> inside the 2305: * document. 2306: * 2307: * @return the start offset of this <code>Element</code> inside the 2308: * document 2309: */ 2310: public int getStartOffset() 2311: { 2312: return startPos.getOffset(); 2313: } 2314: 2315: /** 2316: * Returns <code>true</code>. 2317: * 2318: * @return <code>true</code> 2319: */ 2320: public boolean isLeaf() 2321: { 2322: return true; 2323: } 2324: 2325: /** 2326: * Returns a string representation of this <code>Element</code>. 2327: * 2328: * @return a string representation of this <code>Element</code> 2329: */ 2330: public String toString() 2331: { 2332: return ("LeafElement(" + getName() + ") " 2333: + getStartOffset() + "," + getEndOffset() + "\n"); 2334: } 2335: } 2336: 2337: /** A class whose methods delegate to the insert, remove and replace methods 2338: * of this document which do not check for an installed DocumentFilter. 2339: */ 2340: class Bypass extends DocumentFilter.FilterBypass 2341: { 2342: 2343: public Document getDocument() 2344: { 2345: return AbstractDocument.this; 2346: } 2347: 2348: public void insertString(int offset, String string, AttributeSet attr) 2349: throws BadLocationException 2350: { 2351: AbstractDocument.this.insertStringImpl(offset, string, attr); 2352: } 2353: 2354: public void remove(int offset, int length) 2355: throws BadLocationException 2356: { 2357: AbstractDocument.this.removeImpl(offset, length); 2358: } 2359: 2360: public void replace(int offset, int length, String string, 2361: AttributeSet attrs) 2362: throws BadLocationException 2363: { 2364: AbstractDocument.this.replaceImpl(offset, length, string, attrs); 2365: } 2366: 2367: } 2368: 2369: }