GNU Classpath (0.19) | ||
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: insertUpdate(event, attributes); 545: writeUnlock(); 546: 547: fireInsertUpdate(event); 548: if (undo != null) 549: fireUndoableEditUpdate(new UndoableEditEvent(this, undo)); 550: } 551: 552: /** 553: * Called to indicate that text has been inserted into this 554: * <code>Document</code>. The default implementation does nothing. 555: * This method is executed within a write lock. 556: * 557: * @param chng the <code>DefaultDocumentEvent</code> describing the change 558: * @param attr the attributes of the changed content 559: */ 560: protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) 561: { 562: // Do nothing here. Subclasses may want to override this. 563: } 564: 565: /** 566: * Called after some content has been removed from this 567: * <code>Document</code>. The default implementation does nothing. 568: * This method is executed within a write lock. 569: * 570: * @param chng the <code>DefaultDocumentEvent</code> describing the change 571: */ 572: protected void postRemoveUpdate(DefaultDocumentEvent chng) 573: { 574: // Do nothing here. Subclasses may want to override this. 575: } 576: 577: /** 578: * Stores a property in this <code>Document</code>'s property list. 579: * 580: * @param key the key of the property to be stored 581: * @param value the value of the property to be stored 582: */ 583: public void putProperty(Object key, Object value) 584: { 585: // FIXME: make me thread-safe 586: if (properties == null) 587: properties = new Hashtable(); 588: 589: properties.put(key, value); 590: } 591: 592: /** 593: * Blocks until a read lock can be obtained. Must block if there is 594: * currently a writer modifying the <code>Document</code>. 595: */ 596: public void readLock() 597: { 598: if (currentWriter != null && currentWriter.equals(Thread.currentThread())) 599: return; 600: synchronized (documentCV) 601: { 602: while (currentWriter != null || numWritersWaiting > 0) 603: { 604: try 605: { 606: documentCV.wait(); 607: } 608: catch (InterruptedException ie) 609: { 610: throw new Error("interrupted trying to get a readLock"); 611: } 612: } 613: numReaders++; 614: } 615: } 616: 617: /** 618: * Releases the read lock. If this was the only reader on this 619: * <code>Document</code>, writing may begin now. 620: */ 621: public void readUnlock() 622: { 623: // Note we could have a problem here if readUnlock was called without a 624: // prior call to readLock but the specs simply warn users to ensure that 625: // balance by using a finally block: 626: // readLock() 627: // try 628: // { 629: // doSomethingHere 630: // } 631: // finally 632: // { 633: // readUnlock(); 634: // } 635: 636: // All that the JDK seems to check for is that you don't call unlock 637: // more times than you've previously called lock, but it doesn't make 638: // sure that the threads calling unlock were the same ones that called lock 639: 640: // FIXME: the reference implementation throws a 641: // javax.swing.text.StateInvariantError here 642: if (numReaders == 0) 643: throw new IllegalStateException("document lock failure"); 644: 645: synchronized (documentCV) 646: { 647: // If currentWriter is not null, the application code probably had a 648: // writeLock and then tried to obtain a readLock, in which case 649: // numReaders wasn't incremented 650: if (currentWriter == null) 651: { 652: numReaders --; 653: if (numReaders == 0 && numWritersWaiting != 0) 654: documentCV.notify(); 655: } 656: } 657: } 658: 659: /** 660: * Removes a piece of content from this <code>Document</code>. 661: * 662: * @param offset the start offset of the fragment to be removed 663: * @param length the length of the fragment to be removed 664: * 665: * @throws BadLocationException if <code>offset</code> or 666: * <code>offset + length</code> or invalid locations within this 667: * document 668: */ 669: public void remove(int offset, int length) throws BadLocationException 670: { 671: DefaultDocumentEvent event = 672: new DefaultDocumentEvent(offset, length, 673: DocumentEvent.EventType.REMOVE); 674: 675: // Here we set up the parameters for an ElementChange, if one 676: // needs to be added to the DocumentEvent later 677: Element root = getDefaultRootElement(); 678: int start = root.getElementIndex(offset); 679: int end = root.getElementIndex(offset + length); 680: 681: Element[] removed = new Element[end - start + 1]; 682: for (int i = start; i <= end; i++) 683: removed[i - start] = root.getElement(i); 684: 685: removeUpdate(event); 686: 687: Element[] added = new Element[1]; 688: added[0] = root.getElement(start); 689: boolean shouldFire = content.getString(offset, length).length() != 0; 690: 691: writeLock(); 692: UndoableEdit temp = content.remove(offset, length); 693: writeUnlock(); 694: 695: postRemoveUpdate(event); 696: 697: GapContent.UndoRemove changes = null; 698: if (content instanceof GapContent) 699: changes = (GapContent.UndoRemove) temp; 700: 701: if (changes != null && !(start == end)) 702: { 703: // We need to add an ElementChange to our DocumentEvent 704: ElementEdit edit = new ElementEdit (root, start, removed, added); 705: event.addEdit(edit); 706: } 707: 708: if (shouldFire) 709: fireRemoveUpdate(event); 710: } 711: 712: /** 713: * Replaces a piece of content in this <code>Document</code> with 714: * another piece of content. 715: * 716: * @param offset the start offset of the fragment to be removed 717: * @param length the length of the fragment to be removed 718: * @param text the text to replace the content with 719: * @param attributes the text attributes to assign to the new content 720: * 721: * @throws BadLocationException if <code>offset</code> or 722: * <code>offset + length</code> or invalid locations within this 723: * document 724: * 725: * @since 1.4 726: */ 727: public void replace(int offset, int length, String text, 728: AttributeSet attributes) 729: throws BadLocationException 730: { 731: remove(offset, length); 732: insertString(offset, text, attributes); 733: } 734: 735: /** 736: * Adds a <code>DocumentListener</code> object to this document. 737: * 738: * @param listener the listener to add 739: */ 740: public void addDocumentListener(DocumentListener listener) 741: { 742: listenerList.add(DocumentListener.class, listener); 743: } 744: 745: /** 746: * Removes a <code>DocumentListener</code> object from this document. 747: * 748: * @param listener the listener to remove 749: */ 750: public void removeDocumentListener(DocumentListener listener) 751: { 752: listenerList.remove(DocumentListener.class, listener); 753: } 754: 755: /** 756: * Returns all registered <code>DocumentListener</code>s. 757: * 758: * @return all registered <code>DocumentListener</code>s 759: */ 760: public DocumentListener[] getDocumentListeners() 761: { 762: return (DocumentListener[]) getListeners(DocumentListener.class); 763: } 764: 765: /** 766: * Adds an {@link UndoableEditListener} to this <code>Document</code>. 767: * 768: * @param listener the listener to add 769: */ 770: public void addUndoableEditListener(UndoableEditListener listener) 771: { 772: listenerList.add(UndoableEditListener.class, listener); 773: } 774: 775: /** 776: * Removes an {@link UndoableEditListener} from this <code>Document</code>. 777: * 778: * @param listener the listener to remove 779: */ 780: public void removeUndoableEditListener(UndoableEditListener listener) 781: { 782: listenerList.remove(UndoableEditListener.class, listener); 783: } 784: 785: /** 786: * Returns all registered {@link UndoableEditListener}s. 787: * 788: * @return all registered {@link UndoableEditListener}s 789: */ 790: public UndoableEditListener[] getUndoableEditListeners() 791: { 792: return (UndoableEditListener[]) getListeners(UndoableEditListener.class); 793: } 794: 795: /** 796: * Called before some content gets removed from this <code>Document</code>. 797: * The default implementation does nothing but may be overridden by 798: * subclasses to modify the <code>Document</code> structure in response 799: * to a remove request. The method is executed within a write lock. 800: * 801: * @param chng the <code>DefaultDocumentEvent</code> describing the change 802: */ 803: protected void removeUpdate(DefaultDocumentEvent chng) 804: { 805: // Do nothing here. Subclasses may wish to override this. 806: } 807: 808: /** 809: * Called to render this <code>Document</code> visually. It obtains a read 810: * lock, ensuring that no changes will be made to the <code>document</code> 811: * during the rendering process. It then calls the {@link Runnable#run()} 812: * method on <code>runnable</code>. This method <em>must not</em> attempt 813: * to modifiy the <code>Document</code>, since a deadlock will occur if it 814: * tries to obtain a write lock. When the {@link Runnable#run()} method 815: * completes (either naturally or by throwing an exception), the read lock 816: * is released. Note that there is nothing in this method related to 817: * the actual rendering. It could be used to execute arbitrary code within 818: * a read lock. 819: * 820: * @param runnable the {@link Runnable} to execute 821: */ 822: public void render(Runnable runnable) 823: { 824: readLock(); 825: try 826: { 827: runnable.run(); 828: } 829: finally 830: { 831: readUnlock(); 832: } 833: } 834: 835: /** 836: * Sets the asynchronous loading priority for this <code>Document</code>. 837: * A value of <code>-1</code> indicates that this <code>Document</code> 838: * should be loaded synchronously. 839: * 840: * @param p the asynchronous loading priority to set 841: */ 842: public void setAsynchronousLoadPriority(int p) 843: { 844: // TODO: Implement this properly. 845: } 846: 847: /** 848: * Sets the properties of this <code>Document</code>. 849: * 850: * @param p the document properties to set 851: */ 852: public void setDocumentProperties(Dictionary p) 853: { 854: // FIXME: make me thread-safe 855: properties = p; 856: } 857: 858: /** 859: * Blocks until a write lock can be obtained. Must wait if there are 860: * readers currently reading or another thread is currently writing. 861: */ 862: protected void writeLock() 863: { 864: if (currentWriter!= null && currentWriter.equals(Thread.currentThread())) 865: return; 866: synchronized (documentCV) 867: { 868: numWritersWaiting++; 869: while (numReaders > 0) 870: { 871: try 872: { 873: documentCV.wait(); 874: } 875: catch (InterruptedException ie) 876: { 877: throw new Error("interruped while trying to obtain write lock"); 878: } 879: } 880: numWritersWaiting --; 881: currentWriter = Thread.currentThread(); 882: } 883: } 884: 885: /** 886: * Releases the write lock. This allows waiting readers or writers to 887: * obtain the lock. 888: */ 889: protected void writeUnlock() 890: { 891: synchronized (documentCV) 892: { 893: if (Thread.currentThread().equals(currentWriter)) 894: { 895: currentWriter = null; 896: documentCV.notifyAll(); 897: } 898: } 899: } 900: 901: /** 902: * Returns the currently installed {@link DocumentFilter} for this 903: * <code>Document</code>. 904: * 905: * @return the currently installed {@link DocumentFilter} for this 906: * <code>Document</code> 907: * 908: * @since 1.4 909: */ 910: public DocumentFilter getDocumentFilter() 911: { 912: return documentFilter; 913: } 914: 915: /** 916: * Sets the {@link DocumentFilter} for this <code>Document</code>. 917: * 918: * @param filter the <code>DocumentFilter</code> to set 919: * 920: * @since 1.4 921: */ 922: public void setDocumentFilter(DocumentFilter filter) 923: { 924: this.documentFilter = filter; 925: } 926: 927: /** 928: * Dumps diagnostic information to the specified <code>PrintStream</code>. 929: * 930: * @param out the stream to write the diagnostic information to 931: */ 932: public void dump(PrintStream out) 933: { 934: ((AbstractElement) getDefaultRootElement()).dump(out, 0); 935: } 936: 937: /** 938: * Defines a set of methods for managing text attributes for one or more 939: * <code>Document</code>s. 940: * 941: * Replicating {@link AttributeSet}s throughout a <code>Document</code> can 942: * be very expensive. Implementations of this interface are intended to 943: * provide intelligent management of <code>AttributeSet</code>s, eliminating 944: * costly duplication. 945: * 946: * @see StyleContext 947: */ 948: public interface AttributeContext 949: { 950: /** 951: * Returns an {@link AttributeSet} that contains the attributes 952: * of <code>old</code> plus the new attribute specified by 953: * <code>name</code> and <code>value</code>. 954: * 955: * @param old the attribute set to be merged with the new attribute 956: * @param name the name of the attribute to be added 957: * @param value the value of the attribute to be added 958: * 959: * @return the old attributes plus the new attribute 960: */ 961: AttributeSet addAttribute(AttributeSet old, Object name, Object value); 962: 963: /** 964: * Returns an {@link AttributeSet} that contains the attributes 965: * of <code>old</code> plus the new attributes in <code>attributes</code>. 966: * 967: * @param old the set of attributes where to add the new attributes 968: * @param attributes the attributes to be added 969: * 970: * @return an {@link AttributeSet} that contains the attributes 971: * of <code>old</code> plus the new attributes in 972: * <code>attributes</code> 973: */ 974: AttributeSet addAttributes(AttributeSet old, AttributeSet attributes); 975: 976: /** 977: * Returns an empty {@link AttributeSet}. 978: * 979: * @return an empty {@link AttributeSet} 980: */ 981: AttributeSet getEmptySet(); 982: 983: /** 984: * Called to indicate that the attributes in <code>attributes</code> are 985: * no longer used. 986: * 987: * @param attributes the attributes are no longer used 988: */ 989: void reclaim(AttributeSet attributes); 990: 991: /** 992: * Returns a {@link AttributeSet} that has the attribute with the specified 993: * <code>name</code> removed from <code>old</code>. 994: * 995: * @param old the attribute set from which an attribute is removed 996: * @param name the name of the attribute to be removed 997: * 998: * @return the attributes of <code>old</code> minus the attribute 999: * specified by <code>name</code> 1000: */ 1001: AttributeSet removeAttribute(AttributeSet old, Object name); 1002: 1003: /** 1004: * Removes all attributes in <code>attributes</code> from <code>old</code> 1005: * and returns the resulting <code>AttributeSet</code>. 1006: * 1007: * @param old the set of attributes from which to remove attributes 1008: * @param attributes the attributes to be removed from <code>old</code> 1009: * 1010: * @return the attributes of <code>old</code> minus the attributes in 1011: * <code>attributes</code> 1012: */ 1013: AttributeSet removeAttributes(AttributeSet old, AttributeSet attributes); 1014: 1015: /** 1016: * Removes all attributes specified by <code>names</code> from 1017: * <code>old</code> and returns the resulting <code>AttributeSet</code>. 1018: * 1019: * @param old the set of attributes from which to remove attributes 1020: * @param names the names of the attributes to be removed from 1021: * <code>old</code> 1022: * 1023: * @return the attributes of <code>old</code> minus the attributes in 1024: * <code>attributes</code> 1025: */ 1026: AttributeSet removeAttributes(AttributeSet old, Enumeration names); 1027: } 1028: 1029: /** 1030: * A sequence of data that can be edited. This is were the actual content 1031: * in <code>AbstractDocument</code>'s is stored. 1032: */ 1033: public interface Content 1034: { 1035: /** 1036: * Creates a {@link Position} that keeps track of the location at 1037: * <code>offset</code>. 1038: * 1039: * @return a {@link Position} that keeps track of the location at 1040: * <code>offset</code>. 1041: * 1042: * @throw BadLocationException if <code>offset</code> is not a valid 1043: * location in this <code>Content</code> model 1044: */ 1045: Position createPosition(int offset) throws BadLocationException; 1046: 1047: /** 1048: * Returns the length of the content. 1049: * 1050: * @return the length of the content 1051: */ 1052: int length(); 1053: 1054: /** 1055: * Inserts a string into the content model. 1056: * 1057: * @param where the offset at which to insert the string 1058: * @param str the string to be inserted 1059: * 1060: * @return an <code>UndoableEdit</code> or <code>null</code> if undo is 1061: * not supported by this <code>Content</code> model 1062: * 1063: * @throws BadLocationException if <code>where</code> is not a valid 1064: * location in this <code>Content</code> model 1065: */ 1066: UndoableEdit insertString(int where, String str) 1067: throws BadLocationException; 1068: 1069: /** 1070: * Removes a piece of content from the content model. 1071: * 1072: * @param where the offset at which to remove content 1073: * @param nitems the number of characters to be removed 1074: * 1075: * @return an <code>UndoableEdit</code> or <code>null</code> if undo is 1076: * not supported by this <code>Content</code> model 1077: * 1078: * @throws BadLocationException if <code>where</code> is not a valid 1079: * location in this <code>Content</code> model 1080: */ 1081: UndoableEdit remove(int where, int nitems) throws BadLocationException; 1082: 1083: /** 1084: * Returns a piece of content. 1085: * 1086: * @param where the start offset of the requested fragment 1087: * @param len the length of the requested fragment 1088: * 1089: * @return the requested fragment 1090: * @throws BadLocationException if <code>offset</code> or 1091: * <code>offset + len</code>is not a valid 1092: * location in this <code>Content</code> model 1093: */ 1094: String getString(int where, int len) throws BadLocationException; 1095: 1096: /** 1097: * Fetches a piece of content and stores it in <code>txt</code>. 1098: * 1099: * @param where the start offset of the requested fragment 1100: * @param len the length of the requested fragment 1101: * @param txt the <code>Segment</code> where to fragment is stored into 1102: * 1103: * @throws BadLocationException if <code>offset</code> or 1104: * <code>offset + len</code>is not a valid 1105: * location in this <code>Content</code> model 1106: */ 1107: void getChars(int where, int len, Segment txt) throws BadLocationException; 1108: } 1109: 1110: /** 1111: * An abstract base implementation of the {@link Element} interface. 1112: */ 1113: public abstract class AbstractElement 1114: implements Element, MutableAttributeSet, TreeNode, Serializable 1115: { 1116: /** The serialization UID (compatible with JDK1.5). */ 1117: private static final long serialVersionUID = 1712240033321461704L; 1118: 1119: /** The number of characters that this Element spans. */ 1120: int count; 1121: 1122: /** The starting offset of this Element. */ 1123: int offset; 1124: 1125: /** The attributes of this Element. */ 1126: AttributeSet attributes; 1127: 1128: /** The parent element. */ 1129: Element element_parent; 1130: 1131: /** The parent in the TreeNode interface. */ 1132: TreeNode tree_parent; 1133: 1134: /** The children of this element. */ 1135: Vector tree_children; 1136: 1137: /** 1138: * Creates a new instance of <code>AbstractElement</code> with a 1139: * specified parent <code>Element</code> and <code>AttributeSet</code>. 1140: * 1141: * @param p the parent of this <code>AbstractElement</code> 1142: * @param s the attributes to be assigned to this 1143: * <code>AbstractElement</code> 1144: */ 1145: public AbstractElement(Element p, AttributeSet s) 1146: { 1147: element_parent = p; 1148: AttributeContext ctx = getAttributeContext(); 1149: attributes = ctx.getEmptySet(); 1150: if (s != null) 1151: attributes = ctx.addAttributes(attributes, s); 1152: } 1153: 1154: /** 1155: * Returns the child nodes of this <code>Element</code> as an 1156: * <code>Enumeration</code> of {@link TreeNode}s. 1157: * 1158: * @return the child nodes of this <code>Element</code> as an 1159: * <code>Enumeration</code> of {@link TreeNode}s 1160: */ 1161: public abstract Enumeration children(); 1162: 1163: /** 1164: * Returns <code>true</code> if this <code>AbstractElement</code> 1165: * allows children. 1166: * 1167: * @return <code>true</code> if this <code>AbstractElement</code> 1168: * allows children 1169: */ 1170: public abstract boolean getAllowsChildren(); 1171: 1172: /** 1173: * Returns the child of this <code>AbstractElement</code> at 1174: * <code>index</code>. 1175: * 1176: * @param index the position in the child list of the child element to 1177: * be returned 1178: * 1179: * @return the child of this <code>AbstractElement</code> at 1180: * <code>index</code> 1181: */ 1182: public TreeNode getChildAt(int index) 1183: { 1184: return (TreeNode) tree_children.get(index); 1185: } 1186: 1187: /** 1188: * Returns the number of children of this <code>AbstractElement</code>. 1189: * 1190: * @return the number of children of this <code>AbstractElement</code> 1191: */ 1192: public int getChildCount() 1193: { 1194: return tree_children.size(); 1195: } 1196: 1197: /** 1198: * Returns the index of a given child <code>TreeNode</code> or 1199: * <code>-1</code> if <code>node</code> is not a child of this 1200: * <code>AbstractElement</code>. 1201: * 1202: * @param node the node for which the index is requested 1203: * 1204: * @return the index of a given child <code>TreeNode</code> or 1205: * <code>-1</code> if <code>node</code> is not a child of this 1206: * <code>AbstractElement</code> 1207: */ 1208: public int getIndex(TreeNode node) 1209: { 1210: return tree_children.indexOf(node); 1211: } 1212: 1213: /** 1214: * Returns the parent <code>TreeNode</code> of this 1215: * <code>AbstractElement</code> or <code>null</code> if this element 1216: * has no parent. 1217: * 1218: * @return the parent <code>TreeNode</code> of this 1219: * <code>AbstractElement</code> or <code>null</code> if this 1220: * element has no parent 1221: */ 1222: public TreeNode getParent() 1223: { 1224: return tree_parent; 1225: } 1226: 1227: /** 1228: * Returns <code>true</code> if this <code>AbstractElement</code> is a 1229: * leaf element, <code>false</code> otherwise. 1230: * 1231: * @return <code>true</code> if this <code>AbstractElement</code> is a 1232: * leaf element, <code>false</code> otherwise 1233: */ 1234: public abstract boolean isLeaf(); 1235: 1236: /** 1237: * Adds an attribute to this element. 1238: * 1239: * @param name the name of the attribute to be added 1240: * @param value the value of the attribute to be added 1241: */ 1242: public void addAttribute(Object name, Object value) 1243: { 1244: attributes = getAttributeContext().addAttribute(attributes, name, value); 1245: } 1246: 1247: /** 1248: * Adds a set of attributes to this element. 1249: * 1250: * @param attrs the attributes to be added to this element 1251: */ 1252: public void addAttributes(AttributeSet attrs) 1253: { 1254: attributes = getAttributeContext().addAttributes(attributes, attrs); 1255: } 1256: 1257: /** 1258: * Removes an attribute from this element. 1259: * 1260: * @param name the name of the attribute to be removed 1261: */ 1262: public void removeAttribute(Object name) 1263: { 1264: attributes = getAttributeContext().removeAttribute(attributes, name); 1265: } 1266: 1267: /** 1268: * Removes a set of attributes from this element. 1269: * 1270: * @param attrs the attributes to be removed 1271: */ 1272: public void removeAttributes(AttributeSet attrs) 1273: { 1274: attributes = getAttributeContext().removeAttributes(attributes, attrs); 1275: } 1276: 1277: /** 1278: * Removes a set of attribute from this element. 1279: * 1280: * @param names the names of the attributes to be removed 1281: */ 1282: public void removeAttributes(Enumeration names) 1283: { 1284: attributes = getAttributeContext().removeAttributes(attributes, names); 1285: } 1286: 1287: /** 1288: * Sets the parent attribute set against which the element can resolve 1289: * attributes that are not defined in itself. 1290: * 1291: * @param parent the resolve parent to set 1292: */ 1293: public void setResolveParent(AttributeSet parent) 1294: { 1295: attributes = getAttributeContext().addAttribute(attributes, 1296: ResolveAttribute, 1297: parent); 1298: } 1299: 1300: /** 1301: * Returns <code>true</code> if this element contains the specified 1302: * attribute. 1303: * 1304: * @param name the name of the attribute to check 1305: * @param value the value of the attribute to check 1306: * 1307: * @return <code>true</code> if this element contains the specified 1308: * attribute 1309: */ 1310: public boolean containsAttribute(Object name, Object value) 1311: { 1312: return attributes.containsAttribute(name, value); 1313: } 1314: 1315: /** 1316: * Returns <code>true</code> if this element contains all of the 1317: * specified attributes. 1318: * 1319: * @param attrs the attributes to check 1320: * 1321: * @return <code>true</code> if this element contains all of the 1322: * specified attributes 1323: */ 1324: public boolean containsAttributes(AttributeSet attrs) 1325: { 1326: return attributes.containsAttributes(attrs); 1327: } 1328: 1329: /** 1330: * Returns a copy of the attributes of this element. 1331: * 1332: * @return a copy of the attributes of this element 1333: */ 1334: public AttributeSet copyAttributes() 1335: { 1336: return attributes.copyAttributes(); 1337: } 1338: 1339: /** 1340: * Returns the attribute value with the specified key. If this attribute 1341: * is not defined in this element and this element has a resolving 1342: * parent, the search goes upward to the resolve parent chain. 1343: * 1344: * @param key the key of the requested attribute 1345: * 1346: * @return the attribute value for <code>key</code> of <code>null</code> 1347: * if <code>key</code> is not found locally and cannot be resolved 1348: * in this element's resolve parents 1349: */ 1350: public Object getAttribute(Object key) 1351: { 1352: return attributes.getAttribute(key); 1353: } 1354: 1355: /** 1356: * Returns the number of defined attributes in this element. 1357: * 1358: * @return the number of defined attributes in this element 1359: */ 1360: public int getAttributeCount() 1361: { 1362: return attributes.getAttributeCount(); 1363: } 1364: 1365: /** 1366: * Returns the names of the attributes of this element. 1367: * 1368: * @return the names of the attributes of this element 1369: */ 1370: public Enumeration getAttributeNames() 1371: { 1372: return attributes.getAttributeNames(); 1373: } 1374: 1375: /** 1376: * Returns the resolve parent of this element. 1377: * This is taken from the AttributeSet, but if this is null, 1378: * this method instead returns the Element's parent's 1379: * AttributeSet 1380: * 1381: * @return the resolve parent of this element 1382: * 1383: * @see #setResolveParent(AttributeSet) 1384: */ 1385: public AttributeSet getResolveParent() 1386: { 1387: if (attributes.getResolveParent() != null) 1388: return attributes.getResolveParent(); 1389: return element_parent.getAttributes(); 1390: } 1391: 1392: /** 1393: * Returns <code>true</code> if an attribute with the specified name 1394: * is defined in this element, <code>false</code> otherwise. 1395: * 1396: * @param attrName the name of the requested attributes 1397: * 1398: * @return <code>true</code> if an attribute with the specified name 1399: * is defined in this element, <code>false</code> otherwise 1400: */ 1401: public boolean isDefined(Object attrName) 1402: { 1403: return attributes.isDefined(attrName); 1404: } 1405: 1406: /** 1407: * Returns <code>true</code> if the specified <code>AttributeSet</code> 1408: * is equal to this element's <code>AttributeSet</code>, <code>false</code> 1409: * otherwise. 1410: * 1411: * @param attrs the attributes to compare this element to 1412: * 1413: * @return <code>true</code> if the specified <code>AttributeSet</code> 1414: * is equal to this element's <code>AttributeSet</code>, 1415: * <code>false</code> otherwise 1416: */ 1417: public boolean isEqual(AttributeSet attrs) 1418: { 1419: return attributes.isEqual(attrs); 1420: } 1421: 1422: /** 1423: * Returns the attributes of this element. 1424: * 1425: * @return the attributes of this element 1426: */ 1427: public AttributeSet getAttributes() 1428: { 1429: return this; 1430: } 1431: 1432: /** 1433: * Returns the {@link Document} to which this element belongs. 1434: * 1435: * @return the {@link Document} to which this element belongs 1436: */ 1437: public Document getDocument() 1438: { 1439: return AbstractDocument.this; 1440: } 1441: 1442: /** 1443: * Returns the child element at the specified <code>index</code>. 1444: * 1445: * @param index the index of the requested child element 1446: * 1447: * @return the requested element 1448: */ 1449: public abstract Element getElement(int index); 1450: 1451: /** 1452: * Returns the name of this element. 1453: * 1454: * @return the name of this element 1455: */ 1456: public String getName() 1457: { 1458: return (String) getAttribute(NameAttribute); 1459: } 1460: 1461: /** 1462: * Returns the parent element of this element. 1463: * 1464: * @return the parent element of this element 1465: */ 1466: public Element getParentElement() 1467: { 1468: return element_parent; 1469: } 1470: 1471: /** 1472: * Returns the offset inside the document model that is after the last 1473: * character of this element. 1474: * 1475: * @return the offset inside the document model that is after the last 1476: * character of this element 1477: */ 1478: public abstract int getEndOffset(); 1479: 1480: /** 1481: * Returns the number of child elements of this element. 1482: * 1483: * @return the number of child elements of this element 1484: */ 1485: public abstract int getElementCount(); 1486: 1487: /** 1488: * Returns the index of the child element that spans the specified 1489: * offset in the document model. 1490: * 1491: * @param offset the offset for which the responsible element is searched 1492: * 1493: * @return the index of the child element that spans the specified 1494: * offset in the document model 1495: */ 1496: public abstract int getElementIndex(int offset); 1497: 1498: /** 1499: * Returns the start offset if this element inside the document model. 1500: * 1501: * @return the start offset if this element inside the document model 1502: */ 1503: public abstract int getStartOffset(); 1504: 1505: /** 1506: * Prints diagnostic output to the specified stream. 1507: * 1508: * @param stream the stream to write to 1509: * @param indent the indentation level 1510: */ 1511: public void dump(PrintStream stream, int indent) 1512: { 1513: StringBuffer b = new StringBuffer(); 1514: for (int i = 0; i < indent; ++i) 1515: b.append(' '); 1516: b.append('<'); 1517: b.append(getName()); 1518: // Dump attributes if there are any. 1519: if (getAttributeCount() > 0) 1520: { 1521: b.append('\n'); 1522: Enumeration attNames = getAttributeNames(); 1523: while (attNames.hasMoreElements()) 1524: { 1525: for (int i = 0; i < indent + 2; ++i) 1526: b.append(' '); 1527: Object attName = attNames.nextElement(); 1528: b.append(attName); 1529: b.append('='); 1530: Object attribute = getAttribute(attName); 1531: b.append(attribute); 1532: b.append('\n'); 1533: } 1534: } 1535: b.append(">\n"); 1536: 1537: // Dump element content for leaf elements. 1538: if (isLeaf()) 1539: { 1540: for (int i = 0; i < indent + 2; ++i) 1541: b.append(' '); 1542: int start = getStartOffset(); 1543: int end = getEndOffset(); 1544: b.append('['); 1545: b.append(start); 1546: b.append(','); 1547: b.append(end); 1548: b.append("]["); 1549: try 1550: { 1551: b.append(getDocument().getText(start, end - start)); 1552: } 1553: catch (BadLocationException ex) 1554: { 1555: AssertionError err = new AssertionError("BadLocationException " 1556: + "must not be thrown " 1557: + "here."); 1558: err.initCause(ex); 1559: throw err; 1560: } 1561: b.append("]\n"); 1562: } 1563: stream.print(b.toString()); 1564: 1565: // Dump child elements if any. 1566: int count = getElementCount(); 1567: for (int i = 0; i < count; ++i) 1568: { 1569: Element el = getElement(i); 1570: if (el instanceof AbstractElement) 1571: ((AbstractElement) el).dump(stream, indent + 2); 1572: } 1573: } 1574: } 1575: 1576: /** 1577: * An implementation of {@link Element} to represent composite 1578: * <code>Element</code>s that contain other <code>Element</code>s. 1579: */ 1580: public class BranchElement extends AbstractElement 1581: { 1582: /** The serialization UID (compatible with JDK1.5). */ 1583: private static final long serialVersionUID = -6037216547466333183L; 1584: 1585: /** The child elements of this BranchElement. */ 1586: private Element[] children = new Element[0]; 1587: 1588: /** 1589: * Creates a new <code>BranchElement</code> with the specified 1590: * parent and attributes. 1591: * 1592: * @param parent the parent element of this <code>BranchElement</code> 1593: * @param attributes the attributes to set on this 1594: * <code>BranchElement</code> 1595: */ 1596: public BranchElement(Element parent, AttributeSet attributes) 1597: { 1598: super(parent, attributes); 1599: } 1600: 1601: /** 1602: * Returns the children of this <code>BranchElement</code>. 1603: * 1604: * @return the children of this <code>BranchElement</code> 1605: */ 1606: public Enumeration children() 1607: { 1608: if (children.length == 0) 1609: return null; 1610: 1611: Vector tmp = new Vector(); 1612: 1613: for (int index = 0; index < children.length; ++index) 1614: tmp.add(children[index]); 1615: 1616: return tmp.elements(); 1617: } 1618: 1619: /** 1620: * Returns <code>true</code> since <code>BranchElements</code> allow 1621: * child elements. 1622: * 1623: * @return <code>true</code> since <code>BranchElements</code> allow 1624: * child elements 1625: */ 1626: public boolean getAllowsChildren() 1627: { 1628: return true; 1629: } 1630: 1631: /** 1632: * Returns the child element at the specified <code>index</code>. 1633: * 1634: * @param index the index of the requested child element 1635: * 1636: * @return the requested element 1637: */ 1638: public Element getElement(int index) 1639: { 1640: if (index < 0 || index >= children.length) 1641: return null; 1642: 1643: return children[index]; 1644: } 1645: 1646: /** 1647: * Returns the number of child elements of this element. 1648: * 1649: * @return the number of child elements of this element 1650: */ 1651: public int getElementCount() 1652: { 1653: return children.length; 1654: } 1655: 1656: /** 1657: * Returns the index of the child element that spans the specified 1658: * offset in the document model. 1659: * 1660: * @param offset the offset for which the responsible element is searched 1661: * 1662: * @return the index of the child element that spans the specified 1663: * offset in the document model 1664: */ 1665: public int getElementIndex(int offset) 1666: { 1667: // If offset is less than the start offset of our first child, 1668: // return 0 1669: if (offset < getStartOffset()) 1670: return 0; 1671: 1672: // XXX: There is surely a better algorithm 1673: // as beginning from first element each time. 1674: for (int index = 0; index < children.length - 1; ++index) 1675: { 1676: Element elem = children[index]; 1677: 1678: if ((elem.getStartOffset() <= offset) 1679: && (offset < elem.getEndOffset())) 1680: return index; 1681: // If the next element's start offset is greater than offset 1682: // then we have to return the closest Element, since no Elements 1683: // will contain the offset 1684: if (children[index + 1].getStartOffset() > offset) 1685: { 1686: if ((offset - elem.getEndOffset()) > (children[index + 1].getStartOffset() - offset)) 1687: return index + 1; 1688: else 1689: return index; 1690: } 1691: } 1692: 1693: // If offset is greater than the index of the last element, return 1694: // the index of the last element. 1695: return getElementCount() - 1; 1696: } 1697: 1698: /** 1699: * Returns the offset inside the document model that is after the last 1700: * character of this element. 1701: * This is the end offset of the last child element. If this element 1702: * has no children, this method throws a <code>NullPointerException</code>. 1703: * 1704: * @return the offset inside the document model that is after the last 1705: * character of this element 1706: * 1707: * @throws NullPointerException if this branch element has no children 1708: */ 1709: public int getEndOffset() 1710: { 1711: if (getElementCount() == 0) 1712: throw new NullPointerException("This BranchElement has no children."); 1713: return children[children.length - 1].getEndOffset(); 1714: } 1715: 1716: /** 1717: * Returns the name of this element. This is {@link #ParagraphElementName} 1718: * in this case. 1719: * 1720: * @return the name of this element 1721: */ 1722: public String getName() 1723: { 1724: return ParagraphElementName; 1725: } 1726: 1727: /** 1728: * Returns the start offset of this element inside the document model. 1729: * This is the start offset of the first child element. If this element 1730: * has no children, this method throws a <code>NullPointerException</code>. 1731: * 1732: * @return the start offset of this element inside the document model 1733: * 1734: * @throws NullPointerException if this branch element has no children 1735: */ 1736: public int getStartOffset() 1737: { 1738: if (getElementCount() == 0) 1739: throw new NullPointerException("This BranchElement has no children."); 1740: return children[0].getStartOffset(); 1741: } 1742: 1743: /** 1744: * Returns <code>false</code> since <code>BranchElement</code> are no 1745: * leafes. 1746: * 1747: * @return <code>false</code> since <code>BranchElement</code> are no 1748: * leafes 1749: */ 1750: public boolean isLeaf() 1751: { 1752: return false; 1753: } 1754: 1755: /** 1756: * Returns the <code>Element</code> at the specified <code>Document</code> 1757: * offset. 1758: * 1759: * @return the <code>Element</code> at the specified <code>Document</code> 1760: * offset 1761: * 1762: * @see #getElementIndex(int) 1763: */ 1764: public Element positionToElement(int position) 1765: { 1766: // XXX: There is surely a better algorithm 1767: // as beginning from first element each time. 1768: for (int index = 0; index < children.length; ++index) 1769: { 1770: Element elem = children[index]; 1771: 1772: if ((elem.getStartOffset() <= position) 1773: && (position < elem.getEndOffset())) 1774: return elem; 1775: } 1776: 1777: return null; 1778: } 1779: 1780: /** 1781: * Replaces a set of child elements with a new set of child elemens. 1782: * 1783: * @param offset the start index of the elements to be removed 1784: * @param length the number of elements to be removed 1785: * @param elements the new elements to be inserted 1786: */ 1787: public void replace(int offset, int length, Element[] elements) 1788: { 1789: Element[] target = new Element[children.length - length 1790: + elements.length]; 1791: System.arraycopy(children, 0, target, 0, offset); 1792: System.arraycopy(elements, 0, target, offset, elements.length); 1793: System.arraycopy(children, offset + length, target, 1794: offset + elements.length, 1795: children.length - offset - length); 1796: children = target; 1797: } 1798: 1799: /** 1800: * Returns a string representation of this element. 1801: * 1802: * @return a string representation of this element 1803: */ 1804: public String toString() 1805: { 1806: return ("BranchElement(" + getName() + ") " 1807: + getStartOffset() + "," + getEndOffset() + "\n"); 1808: } 1809: } 1810: 1811: /** 1812: * Stores the changes when a <code>Document</code> is beeing modified. 1813: */ 1814: public class DefaultDocumentEvent extends CompoundEdit 1815: implements DocumentEvent 1816: { 1817: /** The serialization UID (compatible with JDK1.5). */ 1818: private static final long serialVersionUID = 5230037221564563284L; 1819: 1820: /** The starting offset of the change. */ 1821: private int offset; 1822: 1823: /** The length of the change. */ 1824: private int length; 1825: 1826: /** The type of change. */ 1827: private DocumentEvent.EventType type; 1828: 1829: /** 1830: * Maps <code>Element</code> to their change records. 1831: */ 1832: Hashtable changes; 1833: 1834: /** 1835: * Creates a new <code>DefaultDocumentEvent</code>. 1836: * 1837: * @param offset the starting offset of the change 1838: * @param length the length of the change 1839: * @param type the type of change 1840: */ 1841: public DefaultDocumentEvent(int offset, int length, 1842: DocumentEvent.EventType type) 1843: { 1844: this.offset = offset; 1845: this.length = length; 1846: this.type = type; 1847: changes = new Hashtable(); 1848: } 1849: 1850: /** 1851: * Adds an UndoableEdit to this <code>DocumentEvent</code>. If this 1852: * edit is an instance of {@link ElementEdit}, then this record can 1853: * later be fetched by calling {@link #getChange}. 1854: * 1855: * @param edit the undoable edit to add 1856: */ 1857: public boolean addEdit(UndoableEdit edit) 1858: { 1859: // XXX - Fully qualify ElementChange to work around gcj bug #2499. 1860: if (edit instanceof DocumentEvent.ElementChange) 1861: { 1862: DocumentEvent.ElementChange elEdit = 1863: (DocumentEvent.ElementChange) edit; 1864: changes.put(elEdit.getElement(), elEdit); 1865: } 1866: return super.addEdit(edit); 1867: } 1868: 1869: /** 1870: * Returns the document that has been modified. 1871: * 1872: * @return the document that has been modified 1873: */ 1874: public Document getDocument() 1875: { 1876: return AbstractDocument.this; 1877: } 1878: 1879: /** 1880: * Returns the length of the modification. 1881: * 1882: * @return the length of the modification 1883: */ 1884: public int getLength() 1885: { 1886: return length; 1887: } 1888: 1889: /** 1890: * Returns the start offset of the modification. 1891: * 1892: * @return the start offset of the modification 1893: */ 1894: public int getOffset() 1895: { 1896: return offset; 1897: } 1898: 1899: /** 1900: * Returns the type of the modification. 1901: * 1902: * @return the type of the modification 1903: */ 1904: public DocumentEvent.EventType getType() 1905: { 1906: return type; 1907: } 1908: 1909: /** 1910: * Returns the changes for an element. 1911: * 1912: * @param elem the element for which the changes are requested 1913: * 1914: * @return the changes for <code>elem</code> or <code>null</code> if 1915: * <code>elem</code> has not been changed 1916: */ 1917: public DocumentEvent.ElementChange getChange(Element elem) 1918: { 1919: // XXX - Fully qualify ElementChange to work around gcj bug #2499. 1920: return (DocumentEvent.ElementChange) changes.get(elem); 1921: } 1922: } 1923: 1924: /** 1925: * An implementation of {@link DocumentEvent.ElementChange} to be added 1926: * to {@link DefaultDocumentEvent}s. 1927: */ 1928: public static class ElementEdit extends AbstractUndoableEdit 1929: implements DocumentEvent.ElementChange 1930: { 1931: /** The serial version UID of ElementEdit. */ 1932: private static final long serialVersionUID = -1216620962142928304L; 1933: 1934: /** 1935: * The changed element. 1936: */ 1937: private Element elem; 1938: 1939: /** 1940: * The index of the change. 1941: */ 1942: private int index; 1943: 1944: /** 1945: * The removed elements. 1946: */ 1947: private Element[] removed; 1948: 1949: /** 1950: * The added elements. 1951: */ 1952: private Element[] added; 1953: 1954: /** 1955: * Creates a new <code>ElementEdit</code>. 1956: * 1957: * @param elem the changed element 1958: * @param index the index of the change 1959: * @param removed the removed elements 1960: * @param added the added elements 1961: */ 1962: public ElementEdit(Element elem, int index, 1963: Element[] removed, Element[] added) 1964: { 1965: this.elem = elem; 1966: this.index = index; 1967: this.removed = removed; 1968: this.added = added; 1969: } 1970: 1971: /** 1972: * Returns the added elements. 1973: * 1974: * @return the added elements 1975: */ 1976: public Element[] getChildrenAdded() 1977: { 1978: return added; 1979: } 1980: 1981: /** 1982: * Returns the removed elements. 1983: * 1984: * @return the removed elements 1985: */ 1986: public Element[] getChildrenRemoved() 1987: { 1988: return removed; 1989: } 1990: 1991: /** 1992: * Returns the changed element. 1993: * 1994: * @return the changed element 1995: */ 1996: public Element getElement() 1997: { 1998: return elem; 1999: } 2000: 2001: /** 2002: * Returns the index of the change. 2003: * 2004: * @return the index of the change 2005: */ 2006: public int getIndex() 2007: { 2008: return index; 2009: } 2010: } 2011: 2012: /** 2013: * An implementation of {@link Element} that represents a leaf in the 2014: * document structure. This is used to actually store content. 2015: */ 2016: public class LeafElement extends AbstractElement 2017: { 2018: /** The serialization UID (compatible with JDK1.5). */ 2019: private static final long serialVersionUID = -8906306331347768017L; 2020: 2021: /** Manages the start offset of this element. */ 2022: Position startPos; 2023: 2024: /** Manages the end offset of this element. */ 2025: Position endPos; 2026: 2027: /** 2028: * Creates a new <code>LeafElement</code>. 2029: * 2030: * @param parent the parent of this <code>LeafElement</code> 2031: * @param attributes the attributes to be set 2032: * @param start the start index of this element inside the document model 2033: * @param end the end index of this element inside the document model 2034: */ 2035: public LeafElement(Element parent, AttributeSet attributes, int start, 2036: int end) 2037: { 2038: super(parent, attributes); 2039: { 2040: try 2041: { 2042: if (parent != null) 2043: { 2044: startPos = parent.getDocument().createPosition(start); 2045: endPos = parent.getDocument().createPosition(end); 2046: } 2047: else 2048: { 2049: startPos = createPosition(start); 2050: endPos = createPosition(end); 2051: } 2052: } 2053: catch (BadLocationException ex) 2054: { 2055: AssertionError as; 2056: as = new AssertionError("BadLocationException thrown " 2057: + "here. start=" + start 2058: + ", end=" + end 2059: + ", length=" + getLength()); 2060: as.initCause(ex); 2061: throw as; 2062: } 2063: } 2064: } 2065: 2066: /** 2067: * Returns <code>null</code> since <code>LeafElement</code>s cannot have 2068: * children. 2069: * 2070: * @return <code>null</code> since <code>LeafElement</code>s cannot have 2071: * children 2072: */ 2073: public Enumeration children() 2074: { 2075: return null; 2076: } 2077: 2078: /** 2079: * Returns <code>false</code> since <code>LeafElement</code>s cannot have 2080: * children. 2081: * 2082: * @return <code>false</code> since <code>LeafElement</code>s cannot have 2083: * children 2084: */ 2085: public boolean getAllowsChildren() 2086: { 2087: return false; 2088: } 2089: 2090: /** 2091: * Returns <code>null</code> since <code>LeafElement</code>s cannot have 2092: * children. 2093: * 2094: * @return <code>null</code> since <code>LeafElement</code>s cannot have 2095: * children 2096: */ 2097: public Element getElement(int index) 2098: { 2099: return null; 2100: } 2101: 2102: /** 2103: * Returns <code>0</code> since <code>LeafElement</code>s cannot have 2104: * children. 2105: * 2106: * @return <code>0</code> since <code>LeafElement</code>s cannot have 2107: * children 2108: */ 2109: public int getElementCount() 2110: { 2111: return 0; 2112: } 2113: 2114: /** 2115: * Returns <code>-1</code> since <code>LeafElement</code>s cannot have 2116: * children. 2117: * 2118: * @return <code>-1</code> since <code>LeafElement</code>s cannot have 2119: * children 2120: */ 2121: public int getElementIndex(int offset) 2122: { 2123: return -1; 2124: } 2125: 2126: /** 2127: * Returns the end offset of this <code>Element</code> inside the 2128: * document. 2129: * 2130: * @return the end offset of this <code>Element</code> inside the 2131: * document 2132: */ 2133: public int getEndOffset() 2134: { 2135: return endPos.getOffset(); 2136: } 2137: 2138: /** 2139: * Returns the name of this <code>Element</code>. This is 2140: * {@link #ContentElementName} in this case. 2141: * 2142: * @return the name of this <code>Element</code> 2143: */ 2144: public String getName() 2145: { 2146: return ContentElementName; 2147: } 2148: 2149: /** 2150: * Returns the start offset of this <code>Element</code> inside the 2151: * document. 2152: * 2153: * @return the start offset of this <code>Element</code> inside the 2154: * document 2155: */ 2156: public int getStartOffset() 2157: { 2158: return startPos.getOffset(); 2159: } 2160: 2161: /** 2162: * Returns <code>true</code>. 2163: * 2164: * @return <code>true</code> 2165: */ 2166: public boolean isLeaf() 2167: { 2168: return true; 2169: } 2170: 2171: /** 2172: * Returns a string representation of this <code>Element</code>. 2173: * 2174: * @return a string representation of this <code>Element</code> 2175: */ 2176: public String toString() 2177: { 2178: return ("LeafElement(" + getName() + ") " 2179: + getStartOffset() + "," + getEndOffset() + "\n"); 2180: } 2181: } 2182: }
GNU Classpath (0.19) |