Frames | No Frames |
1: /* AbstractDocument.java -- 2: Copyright (C) 2002, 2004, 2005, 2006 Free Software Foundation, Inc. 3: 4: This file is part of GNU Classpath. 5: 6: GNU Classpath is free software; you can redistribute it and/or modify 7: it under the terms of the GNU General Public License as published by 8: the Free Software Foundation; either version 2, or (at your option) 9: any later version. 10: 11: GNU Classpath is distributed in the hope that it will be useful, but 12: WITHOUT ANY WARRANTY; without even the implied warranty of 13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14: General Public License for more details. 15: 16: You should have received a copy of the GNU General Public License 17: along with GNU Classpath; see the file COPYING. If not, write to the 18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19: 02110-1301 USA. 20: 21: Linking this library statically or dynamically with other modules is 22: making a combined work based on this library. Thus, the terms and 23: conditions of the GNU General Public License cover the whole 24: combination. 25: 26: As a special exception, the copyright holders of this library give you 27: permission to link this library with independent modules to produce an 28: executable, regardless of the license terms of these independent 29: modules, and to copy and distribute the resulting executable under 30: terms of your choice, provided that you also meet, for each linked 31: independent module, the terms and conditions of the license of that 32: module. An independent module is a module which is not derived from 33: or based on this library. If you modify this library, you may extend 34: this exception to your version of the library, but you are not 35: obligated to do so. If you do not wish to do so, delete this 36: exception statement from your version. */ 37: 38: 39: package javax.swing.text; 40: 41: import gnu.java.lang.CPStringBuilder; 42: 43: import java.awt.font.TextAttribute; 44: import java.io.PrintStream; 45: import java.io.Serializable; 46: import java.text.Bidi; 47: import java.util.ArrayList; 48: import java.util.Dictionary; 49: import java.util.Enumeration; 50: import java.util.EventListener; 51: import java.util.HashMap; 52: import java.util.Hashtable; 53: import java.util.Vector; 54: 55: import javax.swing.event.DocumentEvent; 56: import javax.swing.event.DocumentListener; 57: import javax.swing.event.EventListenerList; 58: import javax.swing.event.UndoableEditEvent; 59: import javax.swing.event.UndoableEditListener; 60: import javax.swing.text.DocumentFilter; 61: import javax.swing.tree.TreeNode; 62: import javax.swing.undo.AbstractUndoableEdit; 63: import javax.swing.undo.CompoundEdit; 64: import javax.swing.undo.UndoableEdit; 65: 66: /** 67: * An abstract base implementation for the {@link Document} interface. 68: * This class provides some common functionality for all <code>Element</code>s, 69: * most notably it implements a locking mechanism to make document modification 70: * thread-safe. 71: * 72: * @author original author unknown 73: * @author Roman Kennke (roman@kennke.org) 74: */ 75: public abstract class AbstractDocument implements Document, Serializable 76: { 77: /** The serialization UID (compatible with JDK1.5). */ 78: private static final long serialVersionUID = 6842927725919637215L; 79: 80: /** 81: * Standard error message to indicate a bad location. 82: */ 83: protected static final String BAD_LOCATION = "document location failure"; 84: 85: /** 86: * Standard name for unidirectional <code>Element</code>s. 87: */ 88: public static final String BidiElementName = "bidi level"; 89: 90: /** 91: * Standard name for content <code>Element</code>s. These are usually 92: * {@link LeafElement}s. 93: */ 94: public static final String ContentElementName = "content"; 95: 96: /** 97: * Standard name for paragraph <code>Element</code>s. These are usually 98: * {@link BranchElement}s. 99: */ 100: public static final String ParagraphElementName = "paragraph"; 101: 102: /** 103: * Standard name for section <code>Element</code>s. These are usually 104: * {@link DefaultStyledDocument.SectionElement}s. 105: */ 106: public static final String SectionElementName = "section"; 107: 108: /** 109: * Attribute key for storing the element name. 110: */ 111: public static final String ElementNameAttribute = "$ename"; 112: 113: /** 114: * Standard name for the bidi root element. 115: */ 116: private static final String BidiRootName = "bidi root"; 117: 118: /** 119: * Key for storing the asynchronous load priority. 120: */ 121: private static final String AsyncLoadPriority = "load priority"; 122: 123: /** 124: * Key for storing the I18N state. 125: */ 126: private static final String I18N = "i18n"; 127: 128: /** 129: * The actual content model of this <code>Document</code>. 130: */ 131: Content content; 132: 133: /** 134: * The AttributeContext for this <code>Document</code>. 135: */ 136: AttributeContext context; 137: 138: /** 139: * The currently installed <code>DocumentFilter</code>. 140: */ 141: DocumentFilter documentFilter; 142: 143: /** 144: * The documents properties. 145: */ 146: Dictionary properties; 147: 148: /** 149: * Manages event listeners for this <code>Document</code>. 150: */ 151: protected EventListenerList listenerList = new EventListenerList(); 152: 153: /** 154: * Stores the current writer thread. Used for locking. 155: */ 156: private Thread currentWriter = null; 157: 158: /** 159: * The number of readers. Used for locking. 160: */ 161: private int numReaders = 0; 162: 163: /** 164: * The number of current writers. If this is > 1 then the same thread entered 165: * the write lock more than once. 166: */ 167: private int numWriters = 0; 168: 169: /** An instance of a DocumentFilter.FilterBypass which allows calling 170: * the insert, remove and replace method without checking for an installed 171: * document filter. 172: */ 173: private DocumentFilter.FilterBypass bypass; 174: 175: /** 176: * The bidi root element. 177: */ 178: private BidiRootElement bidiRoot; 179: 180: /** 181: * True when we are currently notifying any listeners. This is used 182: * to detect illegal situations in writeLock(). 183: */ 184: private transient boolean notifyListeners; 185: 186: /** 187: * Creates a new <code>AbstractDocument</code> with the specified 188: * {@link Content} model. 189: * 190: * @param doc the <code>Content</code> model to be used in this 191: * <code>Document<code> 192: * 193: * @see GapContent 194: * @see StringContent 195: */ 196: protected AbstractDocument(Content doc) 197: { 198: this(doc, StyleContext.getDefaultStyleContext()); 199: } 200: 201: /** 202: * Creates a new <code>AbstractDocument</code> with the specified 203: * {@link Content} model and {@link AttributeContext}. 204: * 205: * @param doc the <code>Content</code> model to be used in this 206: * <code>Document<code> 207: * @param ctx the <code>AttributeContext</code> to use 208: * 209: * @see GapContent 210: * @see StringContent 211: */ 212: protected AbstractDocument(Content doc, AttributeContext ctx) 213: { 214: content = doc; 215: context = ctx; 216: 217: // FIXME: Fully implement bidi. 218: bidiRoot = new BidiRootElement(); 219: 220: // FIXME: This is determined using a Mauve test. Make the document 221: // actually use this. 222: putProperty(I18N, Boolean.FALSE); 223: 224: // Add one child to the bidi root. 225: writeLock(); 226: try 227: { 228: Element[] children = new Element[1]; 229: children[0] = new BidiElement(bidiRoot, 0, 1, 0); 230: bidiRoot.replace(0, 0, children); 231: } 232: finally 233: { 234: writeUnlock(); 235: } 236: } 237: 238: /** Returns the DocumentFilter.FilterBypass instance for this 239: * document and create it if it does not exist yet. 240: * 241: * @return This document's DocumentFilter.FilterBypass instance. 242: */ 243: private DocumentFilter.FilterBypass getBypass() 244: { 245: if (bypass == null) 246: bypass = new Bypass(); 247: 248: return bypass; 249: } 250: 251: /** 252: * Returns the paragraph {@link Element} that holds the specified position. 253: * 254: * @param pos the position for which to get the paragraph element 255: * 256: * @return the paragraph {@link Element} that holds the specified position 257: */ 258: public abstract Element getParagraphElement(int pos); 259: 260: /** 261: * Returns the default root {@link Element} of this <code>Document</code>. 262: * Usual <code>Document</code>s only have one root element and return this. 263: * However, there may be <code>Document</code> implementations that 264: * support multiple root elements, they have to return a default root element 265: * here. 266: * 267: * @return the default root {@link Element} of this <code>Document</code> 268: */ 269: public abstract Element getDefaultRootElement(); 270: 271: /** 272: * Creates and returns a branch element with the specified 273: * <code>parent</code> and <code>attributes</code>. Note that the new 274: * <code>Element</code> is linked to the parent <code>Element</code> 275: * through {@link Element#getParentElement}, but it is not yet added 276: * to the parent <code>Element</code> as child. 277: * 278: * @param parent the parent <code>Element</code> for the new branch element 279: * @param attributes the text attributes to be installed in the new element 280: * 281: * @return the new branch <code>Element</code> 282: * 283: * @see BranchElement 284: */ 285: protected Element createBranchElement(Element parent, 286: AttributeSet attributes) 287: { 288: return new BranchElement(parent, attributes); 289: } 290: 291: /** 292: * Creates and returns a leaf element with the specified 293: * <code>parent</code> and <code>attributes</code>. Note that the new 294: * <code>Element</code> is linked to the parent <code>Element</code> 295: * through {@link Element#getParentElement}, but it is not yet added 296: * to the parent <code>Element</code> as child. 297: * 298: * @param parent the parent <code>Element</code> for the new branch element 299: * @param attributes the text attributes to be installed in the new element 300: * 301: * @return the new branch <code>Element</code> 302: * 303: * @see LeafElement 304: */ 305: protected Element createLeafElement(Element parent, AttributeSet attributes, 306: int start, int end) 307: { 308: return new LeafElement(parent, attributes, start, end); 309: } 310: 311: /** 312: * Creates a {@link Position} that keeps track of the location at the 313: * specified <code>offset</code>. 314: * 315: * @param offset the location in the document to keep track by the new 316: * <code>Position</code> 317: * 318: * @return the newly created <code>Position</code> 319: * 320: * @throws BadLocationException if <code>offset</code> is not a valid 321: * location in the documents content model 322: */ 323: public synchronized Position createPosition(final int offset) 324: throws BadLocationException 325: { 326: return content.createPosition(offset); 327: } 328: 329: /** 330: * Notifies all registered listeners when the document model changes. 331: * 332: * @param event the <code>DocumentEvent</code> to be fired 333: */ 334: protected void fireChangedUpdate(DocumentEvent event) 335: { 336: notifyListeners = true; 337: try 338: { 339: DocumentListener[] listeners = getDocumentListeners(); 340: for (int index = 0; index < listeners.length; ++index) 341: listeners[index].changedUpdate(event); 342: } 343: finally 344: { 345: notifyListeners = false; 346: } 347: } 348: 349: /** 350: * Notifies all registered listeners when content is inserted in the document 351: * model. 352: * 353: * @param event the <code>DocumentEvent</code> to be fired 354: */ 355: protected void fireInsertUpdate(DocumentEvent event) 356: { 357: notifyListeners = true; 358: try 359: { 360: DocumentListener[] listeners = getDocumentListeners(); 361: for (int index = 0; index < listeners.length; ++index) 362: listeners[index].insertUpdate(event); 363: } 364: finally 365: { 366: notifyListeners = false; 367: } 368: } 369: 370: /** 371: * Notifies all registered listeners when content is removed from the 372: * document model. 373: * 374: * @param event the <code>DocumentEvent</code> to be fired 375: */ 376: protected void fireRemoveUpdate(DocumentEvent event) 377: { 378: notifyListeners = true; 379: try 380: { 381: DocumentListener[] listeners = getDocumentListeners(); 382: for (int index = 0; index < listeners.length; ++index) 383: listeners[index].removeUpdate(event); 384: } 385: finally 386: { 387: notifyListeners = false; 388: } 389: } 390: 391: /** 392: * Notifies all registered listeners when an <code>UndoableEdit</code> has 393: * been performed on this <code>Document</code>. 394: * 395: * @param event the <code>UndoableEditEvent</code> to be fired 396: */ 397: protected void fireUndoableEditUpdate(UndoableEditEvent event) 398: { 399: UndoableEditListener[] listeners = getUndoableEditListeners(); 400: 401: for (int index = 0; index < listeners.length; ++index) 402: listeners[index].undoableEditHappened(event); 403: } 404: 405: /** 406: * Returns the asynchronous loading priority. Returns <code>-1</code> if this 407: * document should not be loaded asynchronously. 408: * 409: * @return the asynchronous loading priority 410: */ 411: public int getAsynchronousLoadPriority() 412: { 413: Object val = getProperty(AsyncLoadPriority); 414: int prio = -1; 415: if (val != null) 416: prio = ((Integer) val).intValue(); 417: return prio; 418: } 419: 420: /** 421: * Returns the {@link AttributeContext} used in this <code>Document</code>. 422: * 423: * @return the {@link AttributeContext} used in this <code>Document</code> 424: */ 425: protected final AttributeContext getAttributeContext() 426: { 427: return context; 428: } 429: 430: /** 431: * Returns the root element for bidirectional content. 432: * 433: * @return the root element for bidirectional content 434: */ 435: public Element getBidiRootElement() 436: { 437: return bidiRoot; 438: } 439: 440: /** 441: * Returns the {@link Content} model for this <code>Document</code> 442: * 443: * @return the {@link Content} model for this <code>Document</code> 444: * 445: * @see GapContent 446: * @see StringContent 447: */ 448: protected final Content getContent() 449: { 450: return content; 451: } 452: 453: /** 454: * Returns the thread that currently modifies this <code>Document</code> 455: * if there is one, otherwise <code>null</code>. This can be used to 456: * distinguish between a method call that is part of an ongoing modification 457: * or if it is a separate modification for which a new lock must be aquired. 458: * 459: * @return the thread that currently modifies this <code>Document</code> 460: * if there is one, otherwise <code>null</code> 461: */ 462: protected final synchronized Thread getCurrentWriter() 463: { 464: return currentWriter; 465: } 466: 467: /** 468: * Returns the properties of this <code>Document</code>. 469: * 470: * @return the properties of this <code>Document</code> 471: */ 472: public Dictionary<Object, Object> getDocumentProperties() 473: { 474: // FIXME: make me thread-safe 475: if (properties == null) 476: properties = new Hashtable(); 477: 478: return properties; 479: } 480: 481: /** 482: * Returns a {@link Position} which will always mark the end of the 483: * <code>Document</code>. 484: * 485: * @return a {@link Position} which will always mark the end of the 486: * <code>Document</code> 487: */ 488: public final Position getEndPosition() 489: { 490: Position p; 491: try 492: { 493: p = createPosition(content.length()); 494: } 495: catch (BadLocationException ex) 496: { 497: // Shouldn't really happen. 498: p = null; 499: } 500: return p; 501: } 502: 503: /** 504: * Returns the length of this <code>Document</code>'s content. 505: * 506: * @return the length of this <code>Document</code>'s content 507: */ 508: public int getLength() 509: { 510: // We return Content.getLength() -1 here because there is always an 511: // implicit \n at the end of the Content which does count in Content 512: // but not in Document. 513: return content.length() - 1; 514: } 515: 516: /** 517: * Returns all registered listeners of a given listener type. 518: * 519: * @param listenerType the type of the listeners to be queried 520: * 521: * @return all registered listeners of the specified type 522: */ 523: public <T extends EventListener> T[] getListeners(Class<T> listenerType) 524: { 525: return listenerList.getListeners(listenerType); 526: } 527: 528: /** 529: * Returns a property from this <code>Document</code>'s property list. 530: * 531: * @param key the key of the property to be fetched 532: * 533: * @return the property for <code>key</code> or <code>null</code> if there 534: * is no such property stored 535: */ 536: public final Object getProperty(Object key) 537: { 538: // FIXME: make me thread-safe 539: Object value = null; 540: if (properties != null) 541: value = properties.get(key); 542: 543: return value; 544: } 545: 546: /** 547: * Returns all root elements of this <code>Document</code>. By default 548: * this just returns the single root element returned by 549: * {@link #getDefaultRootElement()}. <code>Document</code> implementations 550: * that support multiple roots must override this method and return all roots 551: * here. 552: * 553: * @return all root elements of this <code>Document</code> 554: */ 555: public Element[] getRootElements() 556: { 557: Element[] elements = new Element[2]; 558: elements[0] = getDefaultRootElement(); 559: elements[1] = getBidiRootElement(); 560: return elements; 561: } 562: 563: /** 564: * Returns a {@link Position} which will always mark the beginning of the 565: * <code>Document</code>. 566: * 567: * @return a {@link Position} which will always mark the beginning of the 568: * <code>Document</code> 569: */ 570: public final Position getStartPosition() 571: { 572: Position p; 573: try 574: { 575: p = createPosition(0); 576: } 577: catch (BadLocationException ex) 578: { 579: // Shouldn't really happen. 580: p = null; 581: } 582: return p; 583: } 584: 585: /** 586: * Returns a piece of this <code>Document</code>'s content. 587: * 588: * @param offset the start offset of the content 589: * @param length the length of the content 590: * 591: * @return the piece of content specified by <code>offset</code> and 592: * <code>length</code> 593: * 594: * @throws BadLocationException if <code>offset</code> or <code>offset + 595: * length</code> are invalid locations with this 596: * <code>Document</code> 597: */ 598: public String getText(int offset, int length) throws BadLocationException 599: { 600: return content.getString(offset, length); 601: } 602: 603: /** 604: * Fetches a piece of this <code>Document</code>'s content and stores 605: * it in the given {@link Segment}. 606: * 607: * @param offset the start offset of the content 608: * @param length the length of the content 609: * @param segment the <code>Segment</code> to store the content in 610: * 611: * @throws BadLocationException if <code>offset</code> or <code>offset + 612: * length</code> are invalid locations with this 613: * <code>Document</code> 614: */ 615: public void getText(int offset, int length, Segment segment) 616: throws BadLocationException 617: { 618: content.getChars(offset, length, segment); 619: } 620: 621: /** 622: * Inserts a String into this <code>Document</code> at the specified 623: * position and assigning the specified attributes to it. 624: * 625: * <p>If a {@link DocumentFilter} is installed in this document, the 626: * corresponding method of the filter object is called.</p> 627: * 628: * <p>The method has no effect when <code>text</code> is <code>null</code> 629: * or has a length of zero.</p> 630: * 631: * 632: * @param offset the location at which the string should be inserted 633: * @param text the content to be inserted 634: * @param attributes the text attributes to be assigned to that string 635: * 636: * @throws BadLocationException if <code>offset</code> is not a valid 637: * location in this <code>Document</code> 638: */ 639: public void insertString(int offset, String text, AttributeSet attributes) 640: throws BadLocationException 641: { 642: // Bail out if we have a bogus insertion (Behavior observed in RI). 643: if (text == null || text.length() == 0) 644: return; 645: 646: writeLock(); 647: try 648: { 649: if (documentFilter == null) 650: insertStringImpl(offset, text, attributes); 651: else 652: documentFilter.insertString(getBypass(), offset, text, attributes); 653: } 654: finally 655: { 656: writeUnlock(); 657: } 658: } 659: 660: void insertStringImpl(int offset, String text, AttributeSet attributes) 661: throws BadLocationException 662: { 663: // Just return when no text to insert was given. 664: if (text == null || text.length() == 0) 665: return; 666: DefaultDocumentEvent event = 667: new DefaultDocumentEvent(offset, text.length(), 668: DocumentEvent.EventType.INSERT); 669: 670: UndoableEdit undo = content.insertString(offset, text); 671: if (undo != null) 672: event.addEdit(undo); 673: 674: // Check if we need bidi layout. 675: if (getProperty(I18N).equals(Boolean.FALSE)) 676: { 677: Object dir = getProperty(TextAttribute.RUN_DIRECTION); 678: if (TextAttribute.RUN_DIRECTION_RTL.equals(dir)) 679: putProperty(I18N, Boolean.TRUE); 680: else 681: { 682: char[] chars = text.toCharArray(); 683: if (Bidi.requiresBidi(chars, 0, chars.length)) 684: putProperty(I18N, Boolean.TRUE); 685: } 686: } 687: 688: insertUpdate(event, attributes); 689: 690: fireInsertUpdate(event); 691: 692: if (undo != null) 693: fireUndoableEditUpdate(new UndoableEditEvent(this, undo)); 694: } 695: 696: /** 697: * Called to indicate that text has been inserted into this 698: * <code>Document</code>. The default implementation does nothing. 699: * This method is executed within a write lock. 700: * 701: * @param chng the <code>DefaultDocumentEvent</code> describing the change 702: * @param attr the attributes of the changed content 703: */ 704: protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) 705: { 706: if (Boolean.TRUE.equals(getProperty(I18N))) 707: updateBidi(chng); 708: } 709: 710: /** 711: * Called after some content has been removed from this 712: * <code>Document</code>. The default implementation does nothing. 713: * This method is executed within a write lock. 714: * 715: * @param chng the <code>DefaultDocumentEvent</code> describing the change 716: */ 717: protected void postRemoveUpdate(DefaultDocumentEvent chng) 718: { 719: if (Boolean.TRUE.equals(getProperty(I18N))) 720: updateBidi(chng); 721: } 722: 723: /** 724: * Stores a property in this <code>Document</code>'s property list. 725: * 726: * @param key the key of the property to be stored 727: * @param value the value of the property to be stored 728: */ 729: public final void putProperty(Object key, Object value) 730: { 731: // FIXME: make me thread-safe 732: if (properties == null) 733: properties = new Hashtable(); 734: 735: if (value == null) 736: properties.remove(key); 737: else 738: properties.put(key, value); 739: 740: // Update bidi structure if the RUN_DIRECTION is set. 741: if (TextAttribute.RUN_DIRECTION.equals(key)) 742: { 743: if (TextAttribute.RUN_DIRECTION_RTL.equals(value) 744: && Boolean.FALSE.equals(getProperty(I18N))) 745: putProperty(I18N, Boolean.TRUE); 746: 747: if (Boolean.TRUE.equals(getProperty(I18N))) 748: { 749: writeLock(); 750: try 751: { 752: DefaultDocumentEvent ev = 753: new DefaultDocumentEvent(0, getLength(), 754: DocumentEvent.EventType.INSERT); 755: updateBidi(ev); 756: } 757: finally 758: { 759: writeUnlock(); 760: } 761: } 762: } 763: } 764: 765: /** 766: * Updates the bidi element structure. 767: * 768: * @param ev the document event for the change 769: */ 770: private void updateBidi(DefaultDocumentEvent ev) 771: { 772: // Determine start and end offset of the paragraphs to be scanned. 773: int start = 0; 774: int end = 0; 775: DocumentEvent.EventType type = ev.getType(); 776: if (type == DocumentEvent.EventType.INSERT 777: || type == DocumentEvent.EventType.CHANGE) 778: { 779: int offs = ev.getOffset(); 780: int endOffs = offs + ev.getLength(); 781: start = getParagraphElement(offs).getStartOffset(); 782: end = getParagraphElement(endOffs).getEndOffset(); 783: } 784: else if (type == DocumentEvent.EventType.REMOVE) 785: { 786: Element par = getParagraphElement(ev.getOffset()); 787: start = par.getStartOffset(); 788: end = par.getEndOffset(); 789: } 790: else 791: assert false : "Unknown event type"; 792: 793: // Determine the bidi levels for the affected range. 794: Bidi[] bidis = getBidis(start, end); 795: 796: int removeFrom = 0; 797: int removeTo = 0; 798: 799: int offs = 0; 800: int lastRunStart = 0; 801: int lastRunEnd = 0; 802: int lastRunLevel = 0; 803: ArrayList newEls = new ArrayList(); 804: for (int i = 0; i < bidis.length; i++) 805: { 806: Bidi bidi = bidis[i]; 807: int numRuns = bidi.getRunCount(); 808: for (int r = 0; r < numRuns; r++) 809: { 810: if (r == 0 && i == 0) 811: { 812: if (start > 0) 813: { 814: // Try to merge with the previous element if it has the 815: // same bidi level as the first run. 816: int prevElIndex = bidiRoot.getElementIndex(start - 1); 817: removeFrom = prevElIndex; 818: Element prevEl = bidiRoot.getElement(prevElIndex); 819: AttributeSet atts = prevEl.getAttributes(); 820: int prevElLevel = StyleConstants.getBidiLevel(atts); 821: if (prevElLevel == bidi.getRunLevel(r)) 822: { 823: // Merge previous element with current run. 824: lastRunStart = prevEl.getStartOffset() - start; 825: lastRunEnd = bidi.getRunLimit(r); 826: lastRunLevel = bidi.getRunLevel(r); 827: } 828: else if (prevEl.getEndOffset() > start) 829: { 830: // Split previous element and replace by 2 new elements. 831: lastRunStart = 0; 832: lastRunEnd = bidi.getRunLimit(r); 833: lastRunLevel = bidi.getRunLevel(r); 834: newEls.add(new BidiElement(bidiRoot, 835: prevEl.getStartOffset(), 836: start, prevElLevel)); 837: } 838: else 839: { 840: // Simply start new run at start location. 841: lastRunStart = 0; 842: lastRunEnd = bidi.getRunLimit(r); 843: lastRunLevel = bidi.getRunLevel(r); 844: removeFrom++; 845: } 846: } 847: else 848: { 849: // Simply start new run at start location. 850: lastRunStart = 0; 851: lastRunEnd = bidi.getRunLimit(r); 852: lastRunLevel = bidi.getRunLevel(r); 853: removeFrom = 0; 854: } 855: } 856: if (i == bidis.length - 1 && r == numRuns - 1) 857: { 858: if (end <= getLength()) 859: { 860: // Try to merge last element with next element. 861: int nextIndex = bidiRoot.getElementIndex(end); 862: Element nextEl = bidiRoot.getElement(nextIndex); 863: AttributeSet atts = nextEl.getAttributes(); 864: int nextLevel = StyleConstants.getBidiLevel(atts); 865: int level = bidi.getRunLevel(r); 866: if (lastRunLevel == level && level == nextLevel) 867: { 868: // Merge runs together. 869: if (lastRunStart + start == nextEl.getStartOffset()) 870: removeTo = nextIndex - 1; 871: else 872: { 873: newEls.add(new BidiElement(bidiRoot, start + lastRunStart, 874: nextEl.getEndOffset(), level)); 875: removeTo = nextIndex; 876: } 877: } 878: else if (lastRunLevel == level) 879: { 880: // Merge current and last run. 881: int endOffs = offs + bidi.getRunLimit(r); 882: newEls.add(new BidiElement(bidiRoot, start + lastRunStart, 883: start + endOffs, level)); 884: if (start + endOffs == nextEl.getStartOffset()) 885: removeTo = nextIndex - 1; 886: else 887: { 888: newEls.add(new BidiElement(bidiRoot, start + endOffs, 889: nextEl.getEndOffset(), 890: nextLevel)); 891: removeTo = nextIndex; 892: } 893: } 894: else if (level == nextLevel) 895: { 896: // Merge current and next run. 897: newEls.add(new BidiElement(bidiRoot, start + lastRunStart, 898: start + lastRunEnd, 899: lastRunLevel)); 900: newEls.add(new BidiElement(bidiRoot, start + lastRunEnd, 901: nextEl.getEndOffset(), level)); 902: removeTo = nextIndex; 903: } 904: else 905: { 906: // Split next element. 907: int endOffs = offs + bidi.getRunLimit(r); 908: newEls.add(new BidiElement(bidiRoot, start + lastRunStart, 909: start + lastRunEnd, 910: lastRunLevel)); 911: newEls.add(new BidiElement(bidiRoot, start + lastRunEnd, 912: start + endOffs, level)); 913: newEls.add(new BidiElement(bidiRoot, start + endOffs, 914: nextEl.getEndOffset(), 915: nextLevel)); 916: removeTo = nextIndex; 917: } 918: } 919: else 920: { 921: removeTo = bidiRoot.getElementIndex(end); 922: int level = bidi.getRunLevel(r); 923: int runEnd = offs + bidi.getRunLimit(r); 924: 925: if (level == lastRunLevel) 926: { 927: // Merge with previous. 928: lastRunEnd = offs + runEnd; 929: newEls.add(new BidiElement(bidiRoot, 930: start + lastRunStart, 931: start + runEnd, level)); 932: } 933: else 934: { 935: // Create element for last run and current run. 936: newEls.add(new BidiElement(bidiRoot, start + lastRunStart, 937: start + lastRunEnd, 938: lastRunLevel)); 939: newEls.add(new BidiElement(bidiRoot, 940: start + lastRunEnd, 941: start + runEnd, 942: level)); 943: } 944: } 945: } 946: else 947: { 948: int level = bidi.getRunLevel(r); 949: int runEnd = bidi.getRunLimit(r); 950: 951: if (level == lastRunLevel) 952: { 953: // Merge with previous. 954: lastRunEnd = offs + runEnd; 955: } 956: else 957: { 958: // Create element for last run and update values for 959: // current run. 960: newEls.add(new BidiElement(bidiRoot, start + lastRunStart, 961: start + lastRunEnd, 962: lastRunLevel)); 963: lastRunStart = lastRunEnd; 964: lastRunEnd = offs + runEnd; 965: lastRunLevel = level; 966: } 967: } 968: } 969: offs += bidi.getLength(); 970: } 971: 972: // Determine the bidi elements which are to be removed. 973: int numRemoved = 0; 974: if (bidiRoot.getElementCount() > 0) 975: numRemoved = removeTo - removeFrom + 1; 976: Element[] removed = new Element[numRemoved]; 977: for (int i = 0; i < numRemoved; i++) 978: removed[i] = bidiRoot.getElement(removeFrom + i); 979: 980: Element[] added = new Element[newEls.size()]; 981: added = (Element[]) newEls.toArray(added); 982: 983: // Update the event. 984: ElementEdit edit = new ElementEdit(bidiRoot, removeFrom, removed, added); 985: ev.addEdit(edit); 986: 987: // Update the structure. 988: bidiRoot.replace(removeFrom, numRemoved, added); 989: } 990: 991: /** 992: * Determines the Bidi objects for the paragraphs in the specified range. 993: * 994: * @param start the start of the range 995: * @param end the end of the range 996: * 997: * @return the Bidi analysers for the paragraphs in the range 998: */ 999: private Bidi[] getBidis(int start, int end) 1000: { 1001: // Determine the default run direction from the document property. 1002: Boolean defaultDir = null; 1003: Object o = getProperty(TextAttribute.RUN_DIRECTION); 1004: if (o instanceof Boolean) 1005: defaultDir = (Boolean) o; 1006: 1007: // Scan paragraphs and add their level arrays to the overall levels array. 1008: ArrayList bidis = new ArrayList(); 1009: Segment s = new Segment(); 1010: for (int i = start; i < end;) 1011: { 1012: Element par = getParagraphElement(i); 1013: int pStart = par.getStartOffset(); 1014: int pEnd = par.getEndOffset(); 1015: 1016: // Determine the default run direction of the paragraph. 1017: Boolean dir = defaultDir; 1018: o = par.getAttributes().getAttribute(TextAttribute.RUN_DIRECTION); 1019: if (o instanceof Boolean) 1020: dir = (Boolean) o; 1021: 1022: // Bidi over the paragraph. 1023: try 1024: { 1025: getText(pStart, pEnd - pStart, s); 1026: } 1027: catch (BadLocationException ex) 1028: { 1029: assert false : "Must not happen"; 1030: } 1031: int flag = Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT; 1032: if (dir != null) 1033: { 1034: if (TextAttribute.RUN_DIRECTION_LTR.equals(dir)) 1035: flag = Bidi.DIRECTION_LEFT_TO_RIGHT; 1036: else 1037: flag = Bidi.DIRECTION_RIGHT_TO_LEFT; 1038: } 1039: Bidi bidi = new Bidi(s.array, s.offset, null, 0, s.count, flag); 1040: bidis.add(bidi); 1041: i = pEnd; 1042: } 1043: Bidi[] ret = new Bidi[bidis.size()]; 1044: ret = (Bidi[]) bidis.toArray(ret); 1045: return ret; 1046: } 1047: 1048: /** 1049: * Blocks until a read lock can be obtained. Must block if there is 1050: * currently a writer modifying the <code>Document</code>. 1051: */ 1052: public final synchronized void readLock() 1053: { 1054: try 1055: { 1056: while (currentWriter != null) 1057: { 1058: if (currentWriter == Thread.currentThread()) 1059: return; 1060: wait(); 1061: } 1062: numReaders++; 1063: } 1064: catch (InterruptedException ex) 1065: { 1066: throw new Error("Interrupted during grab read lock"); 1067: } 1068: } 1069: 1070: /** 1071: * Releases the read lock. If this was the only reader on this 1072: * <code>Document</code>, writing may begin now. 1073: */ 1074: public final synchronized void readUnlock() 1075: { 1076: // Note we could have a problem here if readUnlock was called without a 1077: // prior call to readLock but the specs simply warn users to ensure that 1078: // balance by using a finally block: 1079: // readLock() 1080: // try 1081: // { 1082: // doSomethingHere 1083: // } 1084: // finally 1085: // { 1086: // readUnlock(); 1087: // } 1088: 1089: // All that the JDK seems to check for is that you don't call unlock 1090: // more times than you've previously called lock, but it doesn't make 1091: // sure that the threads calling unlock were the same ones that called lock 1092: 1093: // If the current thread holds the write lock, and attempted to also obtain 1094: // a readLock, then numReaders hasn't been incremented and we don't need 1095: // to unlock it here. 1096: if (currentWriter == Thread.currentThread()) 1097: return; 1098: 1099: // FIXME: the reference implementation throws a 1100: // javax.swing.text.StateInvariantError here 1101: if (numReaders <= 0) 1102: throw new IllegalStateException("document lock failure"); 1103: 1104: // If currentWriter is not null, the application code probably had a 1105: // writeLock and then tried to obtain a readLock, in which case 1106: // numReaders wasn't incremented 1107: numReaders--; 1108: notify(); 1109: } 1110: 1111: /** 1112: * Removes a piece of content from this <code>Document</code>. 1113: * 1114: * <p>If a {@link DocumentFilter} is installed in this document, the 1115: * corresponding method of the filter object is called. The 1116: * <code>DocumentFilter</code> is called even if <code>length</code> 1117: * is zero. This is different from {@link #replace}.</p> 1118: * 1119: * <p>Note: When <code>length</code> is zero or below the call is not 1120: * forwarded to the underlying {@link AbstractDocument.Content} instance 1121: * of this document and no exception is thrown.</p> 1122: * 1123: * @param offset the start offset of the fragment to be removed 1124: * @param length the length of the fragment to be removed 1125: * 1126: * @throws BadLocationException if <code>offset</code> or 1127: * <code>offset + length</code> or invalid locations within this 1128: * document 1129: */ 1130: public void remove(int offset, int length) throws BadLocationException 1131: { 1132: writeLock(); 1133: try 1134: { 1135: DocumentFilter f = getDocumentFilter(); 1136: if (f == null) 1137: removeImpl(offset, length); 1138: else 1139: f.remove(getBypass(), offset, length); 1140: } 1141: finally 1142: { 1143: writeUnlock(); 1144: } 1145: } 1146: 1147: void removeImpl(int offset, int length) throws BadLocationException 1148: { 1149: // The RI silently ignores all requests that have a negative length. 1150: // Don't ask my why, but that's how it is. 1151: if (length > 0) 1152: { 1153: if (offset < 0 || offset > getLength()) 1154: throw new BadLocationException("Invalid remove position", offset); 1155: 1156: if (offset + length > getLength()) 1157: throw new BadLocationException("Invalid remove length", offset); 1158: 1159: DefaultDocumentEvent event = 1160: new DefaultDocumentEvent(offset, length, 1161: DocumentEvent.EventType.REMOVE); 1162: 1163: // The order of the operations below is critical! 1164: removeUpdate(event); 1165: UndoableEdit temp = content.remove(offset, length); 1166: 1167: postRemoveUpdate(event); 1168: fireRemoveUpdate(event); 1169: } 1170: } 1171: 1172: /** 1173: * Replaces a piece of content in this <code>Document</code> with 1174: * another piece of content. 1175: * 1176: * <p>If a {@link DocumentFilter} is installed in this document, the 1177: * corresponding method of the filter object is called.</p> 1178: * 1179: * <p>The method has no effect if <code>length</code> is zero (and 1180: * only zero) and, at the same time, <code>text</code> is 1181: * <code>null</code> or has zero length.</p> 1182: * 1183: * @param offset the start offset of the fragment to be removed 1184: * @param length the length of the fragment to be removed 1185: * @param text the text to replace the content with 1186: * @param attributes the text attributes to assign to the new content 1187: * 1188: * @throws BadLocationException if <code>offset</code> or 1189: * <code>offset + length</code> or invalid locations within this 1190: * document 1191: * 1192: * @since 1.4 1193: */ 1194: public void replace(int offset, int length, String text, 1195: AttributeSet attributes) 1196: throws BadLocationException 1197: { 1198: // Bail out if we have a bogus replacement (Behavior observed in RI). 1199: if (length == 0 1200: && (text == null || text.length() == 0)) 1201: return; 1202: 1203: writeLock(); 1204: try 1205: { 1206: if (documentFilter == null) 1207: { 1208: // It is important to call the methods which again do the checks 1209: // of the arguments and the DocumentFilter because subclasses may 1210: // have overridden these methods and provide crucial behavior 1211: // which would be skipped if we call the non-checking variants. 1212: // An example for this is PlainDocument where insertString can 1213: // provide a filtering of newlines. 1214: remove(offset, length); 1215: insertString(offset, text, attributes); 1216: } 1217: else 1218: documentFilter.replace(getBypass(), offset, length, text, attributes); 1219: } 1220: finally 1221: { 1222: writeUnlock(); 1223: } 1224: } 1225: 1226: void replaceImpl(int offset, int length, String text, 1227: AttributeSet attributes) 1228: throws BadLocationException 1229: { 1230: removeImpl(offset, length); 1231: insertStringImpl(offset, text, attributes); 1232: } 1233: 1234: /** 1235: * Adds a <code>DocumentListener</code> object to this document. 1236: * 1237: * @param listener the listener to add 1238: */ 1239: public void addDocumentListener(DocumentListener listener) 1240: { 1241: listenerList.add(DocumentListener.class, listener); 1242: } 1243: 1244: /** 1245: * Removes a <code>DocumentListener</code> object from this document. 1246: * 1247: * @param listener the listener to remove 1248: */ 1249: public void removeDocumentListener(DocumentListener listener) 1250: { 1251: listenerList.remove(DocumentListener.class, listener); 1252: } 1253: 1254: /** 1255: * Returns all registered <code>DocumentListener</code>s. 1256: * 1257: * @return all registered <code>DocumentListener</code>s 1258: */ 1259: public DocumentListener[] getDocumentListeners() 1260: { 1261: return (DocumentListener[]) getListeners(DocumentListener.class); 1262: } 1263: 1264: /** 1265: * Adds an {@link UndoableEditListener} to this <code>Document</code>. 1266: * 1267: * @param listener the listener to add 1268: */ 1269: public void addUndoableEditListener(UndoableEditListener listener) 1270: { 1271: listenerList.add(UndoableEditListener.class, listener); 1272: } 1273: 1274: /** 1275: * Removes an {@link UndoableEditListener} from this <code>Document</code>. 1276: * 1277: * @param listener the listener to remove 1278: */ 1279: public void removeUndoableEditListener(UndoableEditListener listener) 1280: { 1281: listenerList.remove(UndoableEditListener.class, listener); 1282: } 1283: 1284: /** 1285: * Returns all registered {@link UndoableEditListener}s. 1286: * 1287: * @return all registered {@link UndoableEditListener}s 1288: */ 1289: public UndoableEditListener[] getUndoableEditListeners() 1290: { 1291: return (UndoableEditListener[]) getListeners(UndoableEditListener.class); 1292: } 1293: 1294: /** 1295: * Called before some content gets removed from this <code>Document</code>. 1296: * The default implementation does nothing but may be overridden by 1297: * subclasses to modify the <code>Document</code> structure in response 1298: * to a remove request. The method is executed within a write lock. 1299: * 1300: * @param chng the <code>DefaultDocumentEvent</code> describing the change 1301: */ 1302: protected void removeUpdate(DefaultDocumentEvent chng) 1303: { 1304: // Do nothing here. Subclasses may wish to override this. 1305: } 1306: 1307: /** 1308: * Called to render this <code>Document</code> visually. It obtains a read 1309: * lock, ensuring that no changes will be made to the <code>document</code> 1310: * during the rendering process. It then calls the {@link Runnable#run()} 1311: * method on <code>runnable</code>. This method <em>must not</em> attempt 1312: * to modifiy the <code>Document</code>, since a deadlock will occur if it 1313: * tries to obtain a write lock. When the {@link Runnable#run()} method 1314: * completes (either naturally or by throwing an exception), the read lock 1315: * is released. Note that there is nothing in this method related to 1316: * the actual rendering. It could be used to execute arbitrary code within 1317: * a read lock. 1318: * 1319: * @param runnable the {@link Runnable} to execute 1320: */ 1321: public void render(Runnable runnable) 1322: { 1323: readLock(); 1324: try 1325: { 1326: runnable.run(); 1327: } 1328: finally 1329: { 1330: readUnlock(); 1331: } 1332: } 1333: 1334: /** 1335: * Sets the asynchronous loading priority for this <code>Document</code>. 1336: * A value of <code>-1</code> indicates that this <code>Document</code> 1337: * should be loaded synchronously. 1338: * 1339: * @param p the asynchronous loading priority to set 1340: */ 1341: public void setAsynchronousLoadPriority(int p) 1342: { 1343: Integer val = p >= 0 ? new Integer(p) : null; 1344: putProperty(AsyncLoadPriority, val); 1345: } 1346: 1347: /** 1348: * Sets the properties of this <code>Document</code>. 1349: * 1350: * @param p the document properties to set 1351: */ 1352: public void setDocumentProperties(Dictionary<Object, Object> p) 1353: { 1354: // FIXME: make me thread-safe 1355: properties = p; 1356: } 1357: 1358: /** 1359: * Blocks until a write lock can be obtained. Must wait if there are 1360: * readers currently reading or another thread is currently writing. 1361: */ 1362: protected synchronized final void writeLock() 1363: { 1364: try 1365: { 1366: while (numReaders > 0 || currentWriter != null) 1367: { 1368: if (Thread.currentThread() == currentWriter) 1369: { 1370: if (notifyListeners) 1371: throw new IllegalStateException("Mutation during notify"); 1372: numWriters++; 1373: return; 1374: } 1375: wait(); 1376: } 1377: currentWriter = Thread.currentThread(); 1378: numWriters = 1; 1379: } 1380: catch (InterruptedException ex) 1381: { 1382: throw new Error("Interupted during grab write lock"); 1383: } 1384: } 1385: 1386: /** 1387: * Releases the write lock. This allows waiting readers or writers to 1388: * obtain the lock. 1389: */ 1390: protected final synchronized void writeUnlock() 1391: { 1392: if (--numWriters <= 0) 1393: { 1394: numWriters = 0; 1395: currentWriter = null; 1396: notifyAll(); 1397: } 1398: } 1399: 1400: /** 1401: * Returns the currently installed {@link DocumentFilter} for this 1402: * <code>Document</code>. 1403: * 1404: * @return the currently installed {@link DocumentFilter} for this 1405: * <code>Document</code> 1406: * 1407: * @since 1.4 1408: */ 1409: public DocumentFilter getDocumentFilter() 1410: { 1411: return documentFilter; 1412: } 1413: 1414: /** 1415: * Sets the {@link DocumentFilter} for this <code>Document</code>. 1416: * 1417: * @param filter the <code>DocumentFilter</code> to set 1418: * 1419: * @since 1.4 1420: */ 1421: public void setDocumentFilter(DocumentFilter filter) 1422: { 1423: this.documentFilter = filter; 1424: } 1425: 1426: /** 1427: * Dumps diagnostic information to the specified <code>PrintStream</code>. 1428: * 1429: * @param out the stream to write the diagnostic information to 1430: */ 1431: public void dump(PrintStream out) 1432: { 1433: ((AbstractElement) getDefaultRootElement()).dump(out, 0); 1434: ((AbstractElement) getBidiRootElement()).dump(out, 0); 1435: } 1436: 1437: /** 1438: * Defines a set of methods for managing text attributes for one or more 1439: * <code>Document</code>s. 1440: * 1441: * Replicating {@link AttributeSet}s throughout a <code>Document</code> can 1442: * be very expensive. Implementations of this interface are intended to 1443: * provide intelligent management of <code>AttributeSet</code>s, eliminating 1444: * costly duplication. 1445: * 1446: * @see StyleContext 1447: */ 1448: public interface AttributeContext 1449: { 1450: /** 1451: * Returns an {@link AttributeSet} that contains the attributes 1452: * of <code>old</code> plus the new attribute specified by 1453: * <code>name</code> and <code>value</code>. 1454: * 1455: * @param old the attribute set to be merged with the new attribute 1456: * @param name the name of the attribute to be added 1457: * @param value the value of the attribute to be added 1458: * 1459: * @return the old attributes plus the new attribute 1460: */ 1461: AttributeSet addAttribute(AttributeSet old, Object name, Object value); 1462: 1463: /** 1464: * Returns an {@link AttributeSet} that contains the attributes 1465: * of <code>old</code> plus the new attributes in <code>attributes</code>. 1466: * 1467: * @param old the set of attributes where to add the new attributes 1468: * @param attributes the attributes to be added 1469: * 1470: * @return an {@link AttributeSet} that contains the attributes 1471: * of <code>old</code> plus the new attributes in 1472: * <code>attributes</code> 1473: */ 1474: AttributeSet addAttributes(AttributeSet old, AttributeSet attributes); 1475: 1476: /** 1477: * Returns an empty {@link AttributeSet}. 1478: * 1479: * @return an empty {@link AttributeSet} 1480: */ 1481: AttributeSet getEmptySet(); 1482: 1483: /** 1484: * Called to indicate that the attributes in <code>attributes</code> are 1485: * no longer used. 1486: * 1487: * @param attributes the attributes are no longer used 1488: */ 1489: void reclaim(AttributeSet attributes); 1490: 1491: /** 1492: * Returns a {@link AttributeSet} that has the attribute with the specified 1493: * <code>name</code> removed from <code>old</code>. 1494: * 1495: * @param old the attribute set from which an attribute is removed 1496: * @param name the name of the attribute to be removed 1497: * 1498: * @return the attributes of <code>old</code> minus the attribute 1499: * specified by <code>name</code> 1500: */ 1501: AttributeSet removeAttribute(AttributeSet old, Object name); 1502: 1503: /** 1504: * Removes all attributes in <code>attributes</code> from <code>old</code> 1505: * and returns the resulting <code>AttributeSet</code>. 1506: * 1507: * @param old the set of attributes from which to remove attributes 1508: * @param attributes the attributes to be removed from <code>old</code> 1509: * 1510: * @return the attributes of <code>old</code> minus the attributes in 1511: * <code>attributes</code> 1512: */ 1513: AttributeSet removeAttributes(AttributeSet old, AttributeSet attributes); 1514: 1515: /** 1516: * Removes all attributes specified by <code>names</code> from 1517: * <code>old</code> and returns the resulting <code>AttributeSet</code>. 1518: * 1519: * @param old the set of attributes from which to remove attributes 1520: * @param names the names of the attributes to be removed from 1521: * <code>old</code> 1522: * 1523: * @return the attributes of <code>old</code> minus the attributes in 1524: * <code>attributes</code> 1525: */ 1526: AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names); 1527: } 1528: 1529: /** 1530: * A sequence of data that can be edited. This is were the actual content 1531: * in <code>AbstractDocument</code>'s is stored. 1532: */ 1533: public interface Content 1534: { 1535: /** 1536: * Creates a {@link Position} that keeps track of the location at 1537: * <code>offset</code>. 1538: * 1539: * @return a {@link Position} that keeps track of the location at 1540: * <code>offset</code>. 1541: * 1542: * @throw BadLocationException if <code>offset</code> is not a valid 1543: * location in this <code>Content</code> model 1544: */ 1545: Position createPosition(int offset) throws BadLocationException; 1546: 1547: /** 1548: * Returns the length of the content. 1549: * 1550: * @return the length of the content 1551: */ 1552: int length(); 1553: 1554: /** 1555: * Inserts a string into the content model. 1556: * 1557: * @param where the offset at which to insert the string 1558: * @param str the string to be inserted 1559: * 1560: * @return an <code>UndoableEdit</code> or <code>null</code> if undo is 1561: * not supported by this <code>Content</code> model 1562: * 1563: * @throws BadLocationException if <code>where</code> is not a valid 1564: * location in this <code>Content</code> model 1565: */ 1566: UndoableEdit insertString(int where, String str) 1567: throws BadLocationException; 1568: 1569: /** 1570: * Removes a piece of content from the content model. 1571: * 1572: * @param where the offset at which to remove content 1573: * @param nitems the number of characters to be removed 1574: * 1575: * @return an <code>UndoableEdit</code> or <code>null</code> if undo is 1576: * not supported by this <code>Content</code> model 1577: * 1578: * @throws BadLocationException if <code>where</code> is not a valid 1579: * location in this <code>Content</code> model 1580: */ 1581: UndoableEdit remove(int where, int nitems) throws BadLocationException; 1582: 1583: /** 1584: * Returns a piece of content. 1585: * 1586: * @param where the start offset of the requested fragment 1587: * @param len the length of the requested fragment 1588: * 1589: * @return the requested fragment 1590: * @throws BadLocationException if <code>offset</code> or 1591: * <code>offset + len</code>is not a valid 1592: * location in this <code>Content</code> model 1593: */ 1594: String getString(int where, int len) throws BadLocationException; 1595: 1596: /** 1597: * Fetches a piece of content and stores it in <code>txt</code>. 1598: * 1599: * @param where the start offset of the requested fragment 1600: * @param len the length of the requested fragment 1601: * @param txt the <code>Segment</code> where to fragment is stored into 1602: * 1603: * @throws BadLocationException if <code>offset</code> or 1604: * <code>offset + len</code>is not a valid 1605: * location in this <code>Content</code> model 1606: */ 1607: void getChars(int where, int len, Segment txt) throws BadLocationException; 1608: } 1609: 1610: /** 1611: * An abstract base implementation of the {@link Element} interface. 1612: */ 1613: public abstract class AbstractElement 1614: implements Element, MutableAttributeSet, TreeNode, Serializable 1615: { 1616: /** The serialization UID (compatible with JDK1.5). */ 1617: private static final long serialVersionUID = 1712240033321461704L; 1618: 1619: /** The number of characters that this Element spans. */ 1620: int count; 1621: 1622: /** The starting offset of this Element. */ 1623: int offset; 1624: 1625: /** The attributes of this Element. */ 1626: AttributeSet attributes; 1627: 1628: /** The parent element. */ 1629: Element element_parent; 1630: 1631: /** The parent in the TreeNode interface. */ 1632: TreeNode tree_parent; 1633: 1634: /** The children of this element. */ 1635: Vector tree_children; 1636: 1637: /** 1638: * Creates a new instance of <code>AbstractElement</code> with a 1639: * specified parent <code>Element</code> and <code>AttributeSet</code>. 1640: * 1641: * @param p the parent of this <code>AbstractElement</code> 1642: * @param s the attributes to be assigned to this 1643: * <code>AbstractElement</code> 1644: */ 1645: public AbstractElement(Element p, AttributeSet s) 1646: { 1647: element_parent = p; 1648: AttributeContext ctx = getAttributeContext(); 1649: attributes = ctx.getEmptySet(); 1650: if (s != null) 1651: addAttributes(s); 1652: } 1653: 1654: /** 1655: * Returns the child nodes of this <code>Element</code> as an 1656: * <code>Enumeration</code> of {@link TreeNode}s. 1657: * 1658: * @return the child nodes of this <code>Element</code> as an 1659: * <code>Enumeration</code> of {@link TreeNode}s 1660: */ 1661: public abstract Enumeration children(); 1662: 1663: /** 1664: * Returns <code>true</code> if this <code>AbstractElement</code> 1665: * allows children. 1666: * 1667: * @return <code>true</code> if this <code>AbstractElement</code> 1668: * allows children 1669: */ 1670: public abstract boolean getAllowsChildren(); 1671: 1672: /** 1673: * Returns the child of this <code>AbstractElement</code> at 1674: * <code>index</code>. 1675: * 1676: * @param index the position in the child list of the child element to 1677: * be returned 1678: * 1679: * @return the child of this <code>AbstractElement</code> at 1680: * <code>index</code> 1681: */ 1682: public TreeNode getChildAt(int index) 1683: { 1684: return (TreeNode) tree_children.get(index); 1685: } 1686: 1687: /** 1688: * Returns the number of children of this <code>AbstractElement</code>. 1689: * 1690: * @return the number of children of this <code>AbstractElement</code> 1691: */ 1692: public int getChildCount() 1693: { 1694: return tree_children.size(); 1695: } 1696: 1697: /** 1698: * Returns the index of a given child <code>TreeNode</code> or 1699: * <code>-1</code> if <code>node</code> is not a child of this 1700: * <code>AbstractElement</code>. 1701: * 1702: * @param node the node for which the index is requested 1703: * 1704: * @return the index of a given child <code>TreeNode</code> or 1705: * <code>-1</code> if <code>node</code> is not a child of this 1706: * <code>AbstractElement</code> 1707: */ 1708: public int getIndex(TreeNode node) 1709: { 1710: return tree_children.indexOf(node); 1711: } 1712: 1713: /** 1714: * Returns the parent <code>TreeNode</code> of this 1715: * <code>AbstractElement</code> or <code>null</code> if this element 1716: * has no parent. 1717: * 1718: * @return the parent <code>TreeNode</code> of this 1719: * <code>AbstractElement</code> or <code>null</code> if this 1720: * element has no parent 1721: */ 1722: public TreeNode getParent() 1723: { 1724: return tree_parent; 1725: } 1726: 1727: /** 1728: * Returns <code>true</code> if this <code>AbstractElement</code> is a 1729: * leaf element, <code>false</code> otherwise. 1730: * 1731: * @return <code>true</code> if this <code>AbstractElement</code> is a 1732: * leaf element, <code>false</code> otherwise 1733: */ 1734: public abstract boolean isLeaf(); 1735: 1736: /** 1737: * Adds an attribute to this element. 1738: * 1739: * @param name the name of the attribute to be added 1740: * @param value the value of the attribute to be added 1741: */ 1742: public void addAttribute(Object name, Object value) 1743: { 1744: attributes = getAttributeContext().addAttribute(attributes, name, value); 1745: } 1746: 1747: /** 1748: * Adds a set of attributes to this element. 1749: * 1750: * @param attrs the attributes to be added to this element 1751: */ 1752: public void addAttributes(AttributeSet attrs) 1753: { 1754: attributes = getAttributeContext().addAttributes(attributes, attrs); 1755: } 1756: 1757: /** 1758: * Removes an attribute from this element. 1759: * 1760: * @param name the name of the attribute to be removed 1761: */ 1762: public void removeAttribute(Object name) 1763: { 1764: attributes = getAttributeContext().removeAttribute(attributes, name); 1765: } 1766: 1767: /** 1768: * Removes a set of attributes from this element. 1769: * 1770: * @param attrs the attributes to be removed 1771: */ 1772: public void removeAttributes(AttributeSet attrs) 1773: { 1774: attributes = getAttributeContext().removeAttributes(attributes, attrs); 1775: } 1776: 1777: /** 1778: * Removes a set of attribute from this element. 1779: * 1780: * @param names the names of the attributes to be removed 1781: */ 1782: public void removeAttributes(Enumeration<?> names) 1783: { 1784: attributes = getAttributeContext().removeAttributes(attributes, names); 1785: } 1786: 1787: /** 1788: * Sets the parent attribute set against which the element can resolve 1789: * attributes that are not defined in itself. 1790: * 1791: * @param parent the resolve parent to set 1792: */ 1793: public void setResolveParent(AttributeSet parent) 1794: { 1795: attributes = getAttributeContext().addAttribute(attributes, 1796: ResolveAttribute, 1797: parent); 1798: } 1799: 1800: /** 1801: * Returns <code>true</code> if this element contains the specified 1802: * attribute. 1803: * 1804: * @param name the name of the attribute to check 1805: * @param value the value of the attribute to check 1806: * 1807: * @return <code>true</code> if this element contains the specified 1808: * attribute 1809: */ 1810: public boolean containsAttribute(Object name, Object value) 1811: { 1812: return attributes.containsAttribute(name, value); 1813: } 1814: 1815: /** 1816: * Returns <code>true</code> if this element contains all of the 1817: * specified attributes. 1818: * 1819: * @param attrs the attributes to check 1820: * 1821: * @return <code>true</code> if this element contains all of the 1822: * specified attributes 1823: */ 1824: public boolean containsAttributes(AttributeSet attrs) 1825: { 1826: return attributes.containsAttributes(attrs); 1827: } 1828: 1829: /** 1830: * Returns a copy of the attributes of this element. 1831: * 1832: * @return a copy of the attributes of this element 1833: */ 1834: public AttributeSet copyAttributes() 1835: { 1836: return attributes.copyAttributes(); 1837: } 1838: 1839: /** 1840: * Returns the attribute value with the specified key. If this attribute 1841: * is not defined in this element and this element has a resolving 1842: * parent, the search goes upward to the resolve parent chain. 1843: * 1844: * @param key the key of the requested attribute 1845: * 1846: * @return the attribute value for <code>key</code> of <code>null</code> 1847: * if <code>key</code> is not found locally and cannot be resolved 1848: * in this element's resolve parents 1849: */ 1850: public Object getAttribute(Object key) 1851: { 1852: Object result = attributes.getAttribute(key); 1853: if (result == null) 1854: { 1855: AttributeSet resParent = getResolveParent(); 1856: if (resParent != null) 1857: result = resParent.getAttribute(key); 1858: } 1859: return result; 1860: } 1861: 1862: /** 1863: * Returns the number of defined attributes in this element. 1864: * 1865: * @return the number of defined attributes in this element 1866: */ 1867: public int getAttributeCount() 1868: { 1869: return attributes.getAttributeCount(); 1870: } 1871: 1872: /** 1873: * Returns the names of the attributes of this element. 1874: * 1875: * @return the names of the attributes of this element 1876: */ 1877: public Enumeration<?> getAttributeNames() 1878: { 1879: return attributes.getAttributeNames(); 1880: } 1881: 1882: /** 1883: * Returns the resolve parent of this element. 1884: * This is taken from the AttributeSet, but if this is null, 1885: * this method instead returns the Element's parent's 1886: * AttributeSet 1887: * 1888: * @return the resolve parent of this element 1889: * 1890: * @see #setResolveParent(AttributeSet) 1891: */ 1892: public AttributeSet getResolveParent() 1893: { 1894: return attributes.getResolveParent(); 1895: } 1896: 1897: /** 1898: * Returns <code>true</code> if an attribute with the specified name 1899: * is defined in this element, <code>false</code> otherwise. 1900: * 1901: * @param attrName the name of the requested attributes 1902: * 1903: * @return <code>true</code> if an attribute with the specified name 1904: * is defined in this element, <code>false</code> otherwise 1905: */ 1906: public boolean isDefined(Object attrName) 1907: { 1908: return attributes.isDefined(attrName); 1909: } 1910: 1911: /** 1912: * Returns <code>true</code> if the specified <code>AttributeSet</code> 1913: * is equal to this element's <code>AttributeSet</code>, <code>false</code> 1914: * otherwise. 1915: * 1916: * @param attrs the attributes to compare this element to 1917: * 1918: * @return <code>true</code> if the specified <code>AttributeSet</code> 1919: * is equal to this element's <code>AttributeSet</code>, 1920: * <code>false</code> otherwise 1921: */ 1922: public boolean isEqual(AttributeSet attrs) 1923: { 1924: return attributes.isEqual(attrs); 1925: } 1926: 1927: /** 1928: * Returns the attributes of this element. 1929: * 1930: * @return the attributes of this element 1931: */ 1932: public AttributeSet getAttributes() 1933: { 1934: return this; 1935: } 1936: 1937: /** 1938: * Returns the {@link Document} to which this element belongs. 1939: * 1940: * @return the {@link Document} to which this element belongs 1941: */ 1942: public Document getDocument() 1943: { 1944: return AbstractDocument.this; 1945: } 1946: 1947: /** 1948: * Returns the child element at the specified <code>index</code>. 1949: * 1950: * @param index the index of the requested child element 1951: * 1952: * @return the requested element 1953: */ 1954: public abstract Element getElement(int index); 1955: 1956: /** 1957: * Returns the name of this element. 1958: * 1959: * @return the name of this element 1960: */ 1961: public String getName() 1962: { 1963: return (String) attributes.getAttribute(ElementNameAttribute); 1964: } 1965: 1966: /** 1967: * Returns the parent element of this element. 1968: * 1969: * @return the parent element of this element 1970: */ 1971: public Element getParentElement() 1972: { 1973: return element_parent; 1974: } 1975: 1976: /** 1977: * Returns the offset inside the document model that is after the last 1978: * character of this element. 1979: * 1980: * @return the offset inside the document model that is after the last 1981: * character of this element 1982: */ 1983: public abstract int getEndOffset(); 1984: 1985: /** 1986: * Returns the number of child elements of this element. 1987: * 1988: * @return the number of child elements of this element 1989: */ 1990: public abstract int getElementCount(); 1991: 1992: /** 1993: * Returns the index of the child element that spans the specified 1994: * offset in the document model. 1995: * 1996: * @param offset the offset for which the responsible element is searched 1997: * 1998: * @return the index of the child element that spans the specified 1999: * offset in the document model 2000: */ 2001: public abstract int getElementIndex(int offset); 2002: 2003: /** 2004: * Returns the start offset if this element inside the document model. 2005: * 2006: * @return the start offset if this element inside the document model 2007: */ 2008: public abstract int getStartOffset(); 2009: 2010: /** 2011: * Prints diagnostic output to the specified stream. 2012: * 2013: * @param stream the stream to write to 2014: * @param indent the indentation level 2015: */ 2016: public void dump(PrintStream stream, int indent) 2017: { 2018: CPStringBuilder b = new CPStringBuilder(); 2019: for (int i = 0; i < indent; ++i) 2020: b.append(' '); 2021: b.append('<'); 2022: b.append(getName()); 2023: // Dump attributes if there are any. 2024: if (getAttributeCount() > 0) 2025: { 2026: b.append('\n'); 2027: Enumeration attNames = getAttributeNames(); 2028: while (attNames.hasMoreElements()) 2029: { 2030: for (int i = 0; i < indent + 2; ++i) 2031: b.append(' '); 2032: Object attName = attNames.nextElement(); 2033: b.append(attName); 2034: b.append('='); 2035: Object attribute = getAttribute(attName); 2036: b.append(attribute); 2037: b.append('\n'); 2038: } 2039: } 2040: if (getAttributeCount() > 0) 2041: { 2042: for (int i = 0; i < indent; ++i) 2043: b.append(' '); 2044: } 2045: b.append(">\n"); 2046: 2047: // Dump element content for leaf elements. 2048: if (isLeaf()) 2049: { 2050: for (int i = 0; i < indent + 2; ++i) 2051: b.append(' '); 2052: int start = getStartOffset(); 2053: int end = getEndOffset(); 2054: b.append('['); 2055: b.append(start); 2056: b.append(','); 2057: b.append(end); 2058: b.append("]["); 2059: try 2060: { 2061: b.append(getDocument().getText(start, end - start)); 2062: } 2063: catch (BadLocationException ex) 2064: { 2065: AssertionError err = new AssertionError("BadLocationException " 2066: + "must not be thrown " 2067: + "here."); 2068: err.initCause(ex); 2069: throw err; 2070: } 2071: b.append("]\n"); 2072: } 2073: stream.print(b.toString()); 2074: 2075: // Dump child elements if any. 2076: int count = getElementCount(); 2077: for (int i = 0; i < count; ++i) 2078: { 2079: Element el = getElement(i); 2080: if (el instanceof AbstractElement) 2081: ((AbstractElement) el).dump(stream, indent + 2); 2082: } 2083: } 2084: } 2085: 2086: /** 2087: * An implementation of {@link Element} to represent composite 2088: * <code>Element</code>s that contain other <code>Element</code>s. 2089: */ 2090: public class BranchElement extends AbstractElement 2091: { 2092: /** The serialization UID (compatible with JDK1.5). */ 2093: private static final long serialVersionUID = -6037216547466333183L; 2094: 2095: /** 2096: * The child elements of this BranchElement. 2097: */ 2098: private Element[] children; 2099: 2100: /** 2101: * The number of children in the branch element. 2102: */ 2103: private int numChildren; 2104: 2105: /** 2106: * The last found index in getElementIndex(). Used for faster searching. 2107: */ 2108: private int lastIndex; 2109: 2110: /** 2111: * Creates a new <code>BranchElement</code> with the specified 2112: * parent and attributes. 2113: * 2114: * @param parent the parent element of this <code>BranchElement</code> 2115: * @param attributes the attributes to set on this 2116: * <code>BranchElement</code> 2117: */ 2118: public BranchElement(Element parent, AttributeSet attributes) 2119: { 2120: super(parent, attributes); 2121: children = new Element[1]; 2122: numChildren = 0; 2123: lastIndex = -1; 2124: } 2125: 2126: /** 2127: * Returns the children of this <code>BranchElement</code>. 2128: * 2129: * @return the children of this <code>BranchElement</code> 2130: */ 2131: public Enumeration children() 2132: { 2133: if (numChildren == 0) 2134: return null; 2135: 2136: Vector tmp = new Vector(); 2137: 2138: for (int index = 0; index < numChildren; ++index) 2139: tmp.add(children[index]); 2140: 2141: return tmp.elements(); 2142: } 2143: 2144: /** 2145: * Returns <code>true</code> since <code>BranchElements</code> allow 2146: * child elements. 2147: * 2148: * @return <code>true</code> since <code>BranchElements</code> allow 2149: * child elements 2150: */ 2151: public boolean getAllowsChildren() 2152: { 2153: return true; 2154: } 2155: 2156: /** 2157: * Returns the child element at the specified <code>index</code>. 2158: * 2159: * @param index the index of the requested child element 2160: * 2161: * @return the requested element 2162: */ 2163: public Element getElement(int index) 2164: { 2165: if (index < 0 || index >= numChildren) 2166: return null; 2167: 2168: return children[index]; 2169: } 2170: 2171: /** 2172: * Returns the number of child elements of this element. 2173: * 2174: * @return the number of child elements of this element 2175: */ 2176: public int getElementCount() 2177: { 2178: return numChildren; 2179: } 2180: 2181: /** 2182: * Returns the index of the child element that spans the specified 2183: * offset in the document model. 2184: * 2185: * @param offset the offset for which the responsible element is searched 2186: * 2187: * @return the index of the child element that spans the specified 2188: * offset in the document model 2189: */ 2190: public int getElementIndex(int offset) 2191: { 2192: // Implemented using an improved linear search. 2193: // This makes use of the fact that searches are not random but often 2194: // close to the previous search. So we try to start the binary 2195: // search at the last found index. 2196: 2197: int i0 = 0; // The lower bounds. 2198: int i1 = numChildren - 1; // The upper bounds. 2199: int index = -1; // The found index. 2200: 2201: int p0 = getStartOffset(); 2202: int p1; // Start and end offset local variables. 2203: 2204: if (numChildren == 0) 2205: index = 0; 2206: else if (offset >= getEndOffset()) 2207: index = numChildren - 1; 2208: else 2209: { 2210: // Try lastIndex. 2211: if (lastIndex >= i0 && lastIndex <= i1) 2212: { 2213: Element last = getElement(lastIndex); 2214: p0 = last.getStartOffset(); 2215: p1 = last.getEndOffset(); 2216: if (offset >= p0 && offset < p1) 2217: index = lastIndex; 2218: else 2219: { 2220: // Narrow the search bounds using the lastIndex, even 2221: // if it hasn't been a hit. 2222: if (offset < p0) 2223: i1 = lastIndex; 2224: else 2225: i0 = lastIndex; 2226: } 2227: } 2228: // The actual search. 2229: int i = 0; 2230: while (i0 <= i1 && index == -1) 2231: { 2232: i = i0 + (i1 - i0) / 2; 2233: Element el = getElement(i); 2234: p0 = el.getStartOffset(); 2235: p1 = el.getEndOffset(); 2236: if (offset >= p0 && offset < p1) 2237: { 2238: // Found it! 2239: index = i; 2240: } 2241: else if (offset < p0) 2242: i1 = i - 1; 2243: else 2244: i0 = i + 1; 2245: } 2246: 2247: if (index == -1) 2248: { 2249: // Didn't find it. Return the boundary index. 2250: if (offset < p0) 2251: index = i; 2252: else 2253: index = i + 1; 2254: } 2255: 2256: lastIndex = index; 2257: } 2258: return index; 2259: } 2260: 2261: /** 2262: * Returns the offset inside the document model that is after the last 2263: * character of this element. 2264: * This is the end offset of the last child element. If this element 2265: * has no children, this method throws a <code>NullPointerException</code>. 2266: * 2267: * @return the offset inside the document model that is after the last 2268: * character of this element 2269: * 2270: * @throws NullPointerException if this branch element has no children 2271: */ 2272: public int getEndOffset() 2273: { 2274: // This might accss one cached element or trigger an NPE for 2275: // numChildren == 0. This is checked by a Mauve test. 2276: Element child = numChildren > 0 ? children[numChildren - 1] 2277: : children[0]; 2278: return child.getEndOffset(); 2279: } 2280: 2281: /** 2282: * Returns the name of this element. This is {@link #ParagraphElementName} 2283: * in this case. 2284: * 2285: * @return the name of this element 2286: */ 2287: public String getName() 2288: { 2289: return ParagraphElementName; 2290: } 2291: 2292: /** 2293: * Returns the start offset of this element inside the document model. 2294: * This is the start offset of the first child element. If this element 2295: * has no children, this method throws a <code>NullPointerException</code>. 2296: * 2297: * @return the start offset of this element inside the document model 2298: * 2299: * @throws NullPointerException if this branch element has no children and 2300: * no startOffset value has been cached 2301: */ 2302: public int getStartOffset() 2303: { 2304: // Do not explicitly throw an NPE here. If the first element is null 2305: // then the NPE gets thrown anyway. If it isn't, then it either 2306: // holds a real value (for numChildren > 0) or a cached value 2307: // (for numChildren == 0) as we don't fully remove elements in replace() 2308: // when removing single elements. 2309: // This is checked by a Mauve test. 2310: return children[0].getStartOffset(); 2311: } 2312: 2313: /** 2314: * Returns <code>false</code> since <code>BranchElement</code> are no 2315: * leafes. 2316: * 2317: * @return <code>false</code> since <code>BranchElement</code> are no 2318: * leafes 2319: */ 2320: public boolean isLeaf() 2321: { 2322: return false; 2323: } 2324: 2325: /** 2326: * Returns the <code>Element</code> at the specified <code>Document</code> 2327: * offset. 2328: * 2329: * @return the <code>Element</code> at the specified <code>Document</code> 2330: * offset 2331: * 2332: * @see #getElementIndex(int) 2333: */ 2334: public Element positionToElement(int position) 2335: { 2336: // XXX: There is surely a better algorithm 2337: // as beginning from first element each time. 2338: for (int index = 0; index < numChildren; ++index) 2339: { 2340: Element elem = children[index]; 2341: 2342: if ((elem.getStartOffset() <= position) 2343: && (position < elem.getEndOffset())) 2344: return elem; 2345: } 2346: 2347: return null; 2348: } 2349: 2350: /** 2351: * Replaces a set of child elements with a new set of child elemens. 2352: * 2353: * @param offset the start index of the elements to be removed 2354: * @param length the number of elements to be removed 2355: * @param elements the new elements to be inserted 2356: */ 2357: public void replace(int offset, int length, Element[] elements) 2358: { 2359: int delta = elements.length - length; 2360: int copyFrom = offset + length; // From where to copy. 2361: int copyTo = copyFrom + delta; // Where to copy to. 2362: int numMove = numChildren - copyFrom; // How many elements are moved. 2363: if (numChildren + delta > children.length) 2364: { 2365: // Gotta grow the array. 2366: int newSize = Math.max(2 * children.length, numChildren + delta); 2367: Element[] target = new Element[newSize]; 2368: System.arraycopy(children, 0, target, 0, offset); 2369: System.arraycopy(elements, 0, target, offset, elements.length); 2370: System.arraycopy(children, copyFrom, target, copyTo, numMove); 2371: children = target; 2372: } 2373: else 2374: { 2375: System.arraycopy(children, copyFrom, children, copyTo, numMove); 2376: System.arraycopy(elements, 0, children, offset, elements.length); 2377: } 2378: numChildren += delta; 2379: } 2380: 2381: /** 2382: * Returns a string representation of this element. 2383: * 2384: * @return a string representation of this element 2385: */ 2386: public String toString() 2387: { 2388: return ("BranchElement(" + getName() + ") " 2389: + getStartOffset() + "," + getEndOffset() + "\n"); 2390: } 2391: } 2392: 2393: /** 2394: * Stores the changes when a <code>Document</code> is beeing modified. 2395: */ 2396: public class DefaultDocumentEvent extends CompoundEdit 2397: implements DocumentEvent 2398: { 2399: /** The serialization UID (compatible with JDK1.5). */ 2400: private static final long serialVersionUID = 5230037221564563284L; 2401: 2402: /** 2403: * The threshold that indicates when we switch to using a Hashtable. 2404: */ 2405: private static final int THRESHOLD = 10; 2406: 2407: /** The starting offset of the change. */ 2408: private int offset; 2409: 2410: /** The length of the change. */ 2411: private int length; 2412: 2413: /** The type of change. */ 2414: private DocumentEvent.EventType type; 2415: 2416: /** 2417: * Maps <code>Element</code> to their change records. This is only 2418: * used when the changes array gets too big. We can use an 2419: * (unsync'ed) HashMap here, since changes to this are (should) always 2420: * be performed inside a write lock. 2421: */ 2422: private HashMap changes; 2423: 2424: /** 2425: * Indicates if this event has been modified or not. This is used to 2426: * determine if this event is thrown. 2427: */ 2428: private boolean modified; 2429: 2430: /** 2431: * Creates a new <code>DefaultDocumentEvent</code>. 2432: * 2433: * @param offset the starting offset of the change 2434: * @param length the length of the change 2435: * @param type the type of change 2436: */ 2437: public DefaultDocumentEvent(int offset, int length, 2438: DocumentEvent.EventType type) 2439: { 2440: this.offset = offset; 2441: this.length = length; 2442: this.type = type; 2443: modified = false; 2444: } 2445: 2446: /** 2447: * Adds an UndoableEdit to this <code>DocumentEvent</code>. If this 2448: * edit is an instance of {@link ElementEdit}, then this record can 2449: * later be fetched by calling {@link #getChange}. 2450: * 2451: * @param edit the undoable edit to add 2452: */ 2453: public boolean addEdit(UndoableEdit edit) 2454: { 2455: // Start using Hashtable when we pass a certain threshold. This 2456: // gives a good memory/performance compromise. 2457: if (changes == null && edits.size() > THRESHOLD) 2458: { 2459: changes = new HashMap(); 2460: int count = edits.size(); 2461: for (int i = 0; i < count; i++) 2462: { 2463: Object o = edits.elementAt(i); 2464: if (o instanceof ElementChange) 2465: { 2466: ElementChange ec = (ElementChange) o; 2467: changes.put(ec.getElement(), ec); 2468: } 2469: } 2470: } 2471: 2472: if (changes != null && edit instanceof ElementChange) 2473: { 2474: ElementChange elEdit = (ElementChange) edit; 2475: changes.put(elEdit.getElement(), elEdit); 2476: } 2477: return super.addEdit(edit); 2478: } 2479: 2480: /** 2481: * Returns the document that has been modified. 2482: * 2483: * @return the document that has been modified 2484: */ 2485: public Document getDocument() 2486: { 2487: return AbstractDocument.this; 2488: } 2489: 2490: /** 2491: * Returns the length of the modification. 2492: * 2493: * @return the length of the modification 2494: */ 2495: public int getLength() 2496: { 2497: return length; 2498: } 2499: 2500: /** 2501: * Returns the start offset of the modification. 2502: * 2503: * @return the start offset of the modification 2504: */ 2505: public int getOffset() 2506: { 2507: return offset; 2508: } 2509: 2510: /** 2511: * Returns the type of the modification. 2512: * 2513: * @return the type of the modification 2514: */ 2515: public DocumentEvent.EventType getType() 2516: { 2517: return type; 2518: } 2519: 2520: /** 2521: * Returns the changes for an element. 2522: * 2523: * @param elem the element for which the changes are requested 2524: * 2525: * @return the changes for <code>elem</code> or <code>null</code> if 2526: * <code>elem</code> has not been changed 2527: */ 2528: public ElementChange getChange(Element elem) 2529: { 2530: ElementChange change = null; 2531: if (changes != null) 2532: { 2533: change = (ElementChange) changes.get(elem); 2534: } 2535: else 2536: { 2537: int count = edits.size(); 2538: for (int i = 0; i < count && change == null; i++) 2539: { 2540: Object o = edits.get(i); 2541: if (o instanceof ElementChange) 2542: { 2543: ElementChange ec = (ElementChange) o; 2544: if (elem.equals(ec.getElement())) 2545: change = ec; 2546: } 2547: } 2548: } 2549: return change; 2550: } 2551: 2552: /** 2553: * Returns a String description of the change event. This returns the 2554: * toString method of the Vector of edits. 2555: */ 2556: public String toString() 2557: { 2558: return edits.toString(); 2559: } 2560: } 2561: 2562: /** 2563: * An implementation of {@link DocumentEvent.ElementChange} to be added 2564: * to {@link DefaultDocumentEvent}s. 2565: */ 2566: public static class ElementEdit extends AbstractUndoableEdit 2567: implements DocumentEvent.ElementChange 2568: { 2569: /** The serial version UID of ElementEdit. */ 2570: private static final long serialVersionUID = -1216620962142928304L; 2571: 2572: /** 2573: * The changed element. 2574: */ 2575: private Element elem; 2576: 2577: /** 2578: * The index of the change. 2579: */ 2580: private int index; 2581: 2582: /** 2583: * The removed elements. 2584: */ 2585: private Element[] removed; 2586: 2587: /** 2588: * The added elements. 2589: */ 2590: private Element[] added; 2591: 2592: /** 2593: * Creates a new <code>ElementEdit</code>. 2594: * 2595: * @param elem the changed element 2596: * @param index the index of the change 2597: * @param removed the removed elements 2598: * @param added the added elements 2599: */ 2600: public ElementEdit(Element elem, int index, 2601: Element[] removed, Element[] added) 2602: { 2603: this.elem = elem; 2604: this.index = index; 2605: this.removed = removed; 2606: this.added = added; 2607: } 2608: 2609: /** 2610: * Returns the added elements. 2611: * 2612: * @return the added elements 2613: */ 2614: public Element[] getChildrenAdded() 2615: { 2616: return added; 2617: } 2618: 2619: /** 2620: * Returns the removed elements. 2621: * 2622: * @return the removed elements 2623: */ 2624: public Element[] getChildrenRemoved() 2625: { 2626: return removed; 2627: } 2628: 2629: /** 2630: * Returns the changed element. 2631: * 2632: * @return the changed element 2633: */ 2634: public Element getElement() 2635: { 2636: return elem; 2637: } 2638: 2639: /** 2640: * Returns the index of the change. 2641: * 2642: * @return the index of the change 2643: */ 2644: public int getIndex() 2645: { 2646: return index; 2647: } 2648: } 2649: 2650: /** 2651: * An implementation of {@link Element} that represents a leaf in the 2652: * document structure. This is used to actually store content. 2653: */ 2654: public class LeafElement extends AbstractElement 2655: { 2656: /** The serialization UID (compatible with JDK1.5). */ 2657: private static final long serialVersionUID = -8906306331347768017L; 2658: 2659: /** 2660: * Manages the start offset of this element. 2661: */ 2662: private Position startPos; 2663: 2664: /** 2665: * Manages the end offset of this element. 2666: */ 2667: private Position endPos; 2668: 2669: /** 2670: * Creates a new <code>LeafElement</code>. 2671: * 2672: * @param parent the parent of this <code>LeafElement</code> 2673: * @param attributes the attributes to be set 2674: * @param start the start index of this element inside the document model 2675: * @param end the end index of this element inside the document model 2676: */ 2677: public LeafElement(Element parent, AttributeSet attributes, int start, 2678: int end) 2679: { 2680: super(parent, attributes); 2681: try 2682: { 2683: startPos = createPosition(start); 2684: endPos = createPosition(end); 2685: } 2686: catch (BadLocationException ex) 2687: { 2688: AssertionError as; 2689: as = new AssertionError("BadLocationException thrown " 2690: + "here. start=" + start 2691: + ", end=" + end 2692: + ", length=" + getLength()); 2693: as.initCause(ex); 2694: throw as; 2695: } 2696: } 2697: 2698: /** 2699: * Returns <code>null</code> since <code>LeafElement</code>s cannot have 2700: * children. 2701: * 2702: * @return <code>null</code> since <code>LeafElement</code>s cannot have 2703: * children 2704: */ 2705: public Enumeration children() 2706: { 2707: return null; 2708: } 2709: 2710: /** 2711: * Returns <code>false</code> since <code>LeafElement</code>s cannot have 2712: * children. 2713: * 2714: * @return <code>false</code> since <code>LeafElement</code>s cannot have 2715: * children 2716: */ 2717: public boolean getAllowsChildren() 2718: { 2719: return false; 2720: } 2721: 2722: /** 2723: * Returns <code>null</code> since <code>LeafElement</code>s cannot have 2724: * children. 2725: * 2726: * @return <code>null</code> since <code>LeafElement</code>s cannot have 2727: * children 2728: */ 2729: public Element getElement(int index) 2730: { 2731: return null; 2732: } 2733: 2734: /** 2735: * Returns <code>0</code> since <code>LeafElement</code>s cannot have 2736: * children. 2737: * 2738: * @return <code>0</code> since <code>LeafElement</code>s cannot have 2739: * children 2740: */ 2741: public int getElementCount() 2742: { 2743: return 0; 2744: } 2745: 2746: /** 2747: * Returns <code>-1</code> since <code>LeafElement</code>s cannot have 2748: * children. 2749: * 2750: * @return <code>-1</code> since <code>LeafElement</code>s cannot have 2751: * children 2752: */ 2753: public int getElementIndex(int offset) 2754: { 2755: return -1; 2756: } 2757: 2758: /** 2759: * Returns the end offset of this <code>Element</code> inside the 2760: * document. 2761: * 2762: * @return the end offset of this <code>Element</code> inside the 2763: * document 2764: */ 2765: public int getEndOffset() 2766: { 2767: return endPos.getOffset(); 2768: } 2769: 2770: /** 2771: * Returns the name of this <code>Element</code>. This is 2772: * {@link #ContentElementName} in this case. 2773: * 2774: * @return the name of this <code>Element</code> 2775: */ 2776: public String getName() 2777: { 2778: String name = super.getName(); 2779: if (name == null) 2780: name = ContentElementName; 2781: return name; 2782: } 2783: 2784: /** 2785: * Returns the start offset of this <code>Element</code> inside the 2786: * document. 2787: * 2788: * @return the start offset of this <code>Element</code> inside the 2789: * document 2790: */ 2791: public int getStartOffset() 2792: { 2793: return startPos.getOffset(); 2794: } 2795: 2796: /** 2797: * Returns <code>true</code>. 2798: * 2799: * @return <code>true</code> 2800: */ 2801: public boolean isLeaf() 2802: { 2803: return true; 2804: } 2805: 2806: /** 2807: * Returns a string representation of this <code>Element</code>. 2808: * 2809: * @return a string representation of this <code>Element</code> 2810: */ 2811: public String toString() 2812: { 2813: return ("LeafElement(" + getName() + ") " 2814: + getStartOffset() + "," + getEndOffset() + "\n"); 2815: } 2816: } 2817: 2818: /** 2819: * The root element for bidirectional text. 2820: */ 2821: private class BidiRootElement 2822: extends BranchElement 2823: { 2824: /** 2825: * Creates a new bidi root element. 2826: */ 2827: BidiRootElement() 2828: { 2829: super(null, null); 2830: } 2831: 2832: /** 2833: * Returns the name of the element. 2834: * 2835: * @return the name of the element 2836: */ 2837: public String getName() 2838: { 2839: return BidiRootName; 2840: } 2841: } 2842: 2843: /** 2844: * A leaf element for the bidi structure. 2845: */ 2846: private class BidiElement 2847: extends LeafElement 2848: { 2849: /** 2850: * Creates a new BidiElement. 2851: * 2852: * @param parent the parent element 2853: * @param start the start offset 2854: * @param end the end offset 2855: * @param level the bidi level 2856: */ 2857: BidiElement(Element parent, int start, int end, int level) 2858: { 2859: super(parent, new SimpleAttributeSet(), start, end); 2860: addAttribute(StyleConstants.BidiLevel, new Integer(level)); 2861: } 2862: 2863: /** 2864: * Returns the name of the element. 2865: * 2866: * @return the name of the element 2867: */ 2868: public String getName() 2869: { 2870: return BidiElementName; 2871: } 2872: } 2873: 2874: /** A class whose methods delegate to the insert, remove and replace methods 2875: * of this document which do not check for an installed DocumentFilter. 2876: */ 2877: class Bypass extends DocumentFilter.FilterBypass 2878: { 2879: 2880: public Document getDocument() 2881: { 2882: return AbstractDocument.this; 2883: } 2884: 2885: public void insertString(int offset, String string, AttributeSet attr) 2886: throws BadLocationException 2887: { 2888: AbstractDocument.this.insertStringImpl(offset, string, attr); 2889: } 2890: 2891: public void remove(int offset, int length) 2892: throws BadLocationException 2893: { 2894: AbstractDocument.this.removeImpl(offset, length); 2895: } 2896: 2897: public void replace(int offset, int length, String string, 2898: AttributeSet attrs) 2899: throws BadLocationException 2900: { 2901: AbstractDocument.this.replaceImpl(offset, length, string, attrs); 2902: } 2903: 2904: } 2905: 2906: }