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