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