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