1 /*
   2  * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package javax.swing.text;
  26 
  27 import java.util.*;
  28 import java.io.*;
  29 import java.awt.font.TextAttribute;
  30 import java.text.Bidi;
  31 
  32 import javax.swing.UIManager;
  33 import javax.swing.undo.*;
  34 import javax.swing.event.*;
  35 import javax.swing.tree.TreeNode;
  36 
  37 import sun.font.BidiUtils;
  38 import sun.swing.SwingUtilities2;
  39 
  40 /**
  41  * An implementation of the document interface to serve as a
  42  * basis for implementing various kinds of documents.  At this
  43  * level there is very little policy, so there is a corresponding
  44  * increase in difficulty of use.
  45  * <p>
  46  * This class implements a locking mechanism for the document.  It
  47  * allows multiple readers or one writer, and writers must wait until
  48  * all observers of the document have been notified of a previous
  49  * change before beginning another mutation to the document.  The
  50  * read lock is acquired and released using the <code>render</code>
  51  * method.  A write lock is acquired by the methods that mutate the
  52  * document, and are held for the duration of the method call.
  53  * Notification is done on the thread that produced the mutation,
  54  * and the thread has full read access to the document for the
  55  * duration of the notification, but other readers are kept out
  56  * until the notification has finished.  The notification is a
  57  * beans event notification which does not allow any further
  58  * mutations until all listeners have been notified.
  59  * <p>
  60  * Any models subclassed from this class and used in conjunction
  61  * with a text component that has a look and feel implementation
  62  * that is derived from BasicTextUI may be safely updated
  63  * asynchronously, because all access to the View hierarchy
  64  * is serialized by BasicTextUI if the document is of type
  65  * <code>AbstractDocument</code>.  The locking assumes that an
  66  * independent thread will access the View hierarchy only from
  67  * the DocumentListener methods, and that there will be only
  68  * one event thread active at a time.
  69  * <p>
  70  * If concurrency support is desired, there are the following
  71  * additional implications.  The code path for any DocumentListener
  72  * implementation and any UndoListener implementation must be threadsafe,
  73  * and not access the component lock if trying to be safe from deadlocks.
  74  * The <code>repaint</code> and <code>revalidate</code> methods
  75  * on JComponent are safe.
  76  * <p>
  77  * AbstractDocument models an implied break at the end of the document.
  78  * Among other things this allows you to position the caret after the last
  79  * character. As a result of this, <code>getLength</code> returns one less
  80  * than the length of the Content. If you create your own Content, be
  81  * sure and initialize it to have an additional character. Refer to
  82  * StringContent and GapContent for examples of this. Another implication
  83  * of this is that Elements that model the implied end character will have
  84  * an endOffset == (getLength() + 1). For example, in DefaultStyledDocument
  85  * <code>getParagraphElement(getLength()).getEndOffset() == getLength() + 1
  86  * </code>.
  87  * <p>
  88  * <strong>Warning:</strong>
  89  * Serialized objects of this class will not be compatible with
  90  * future Swing releases. The current serialization support is
  91  * appropriate for short term storage or RMI between applications running
  92  * the same version of Swing.  As of 1.4, support for long term storage
  93  * of all JavaBeans&trade;
  94  * has been added to the <code>java.beans</code> package.
  95  * Please see {@link java.beans.XMLEncoder}.
  96  *
  97  * @author  Timothy Prinzing
  98  */
  99 @SuppressWarnings("serial") // Same-version serialization only
 100 public abstract class AbstractDocument implements Document, Serializable {
 101 
 102     /**
 103      * Constructs a new <code>AbstractDocument</code>, wrapped around some
 104      * specified content storage mechanism.
 105      *
 106      * @param data the content
 107      */
 108     protected AbstractDocument(Content data) {
 109         this(data, StyleContext.getDefaultStyleContext());
 110     }
 111 
 112     /**
 113      * Constructs a new <code>AbstractDocument</code>, wrapped around some
 114      * specified content storage mechanism.
 115      *
 116      * @param data the content
 117      * @param context the attribute context
 118      */
 119     protected AbstractDocument(Content data, AttributeContext context) {
 120         this.data = data;
 121         this.context = context;
 122         bidiRoot = new BidiRootElement();
 123 
 124         if (defaultI18NProperty == null) {
 125             // determine default setting for i18n support
 126             String o = java.security.AccessController.doPrivileged(
 127                 new java.security.PrivilegedAction<String>() {
 128                     public String run() {
 129                         return System.getProperty(I18NProperty);
 130                     }
 131                 }
 132             );
 133             if (o != null) {
 134                 defaultI18NProperty = Boolean.valueOf(o);
 135             } else {
 136                 defaultI18NProperty = Boolean.FALSE;
 137             }
 138         }
 139         putProperty( I18NProperty, defaultI18NProperty);
 140 
 141         //REMIND(bcb) This creates an initial bidi element to account for
 142         //the \n that exists by default in the content.  Doing it this way
 143         //seems to expose a little too much knowledge of the content given
 144         //to us by the sub-class.  Consider having the sub-class' constructor
 145         //make an initial call to insertUpdate.
 146         writeLock();
 147         try {
 148             Element[] p = new Element[1];
 149             p[0] = new BidiElement( bidiRoot, 0, 1, 0 );
 150             bidiRoot.replace(0,0,p);
 151         } finally {
 152             writeUnlock();
 153         }
 154     }
 155 
 156     /**
 157      * Supports managing a set of properties. Callers
 158      * can use the <code>documentProperties</code> dictionary
 159      * to annotate the document with document-wide properties.
 160      *
 161      * @return a non-<code>null</code> <code>Dictionary</code>
 162      * @see #setDocumentProperties
 163      */
 164     public Dictionary<Object,Object> getDocumentProperties() {
 165         if (documentProperties == null) {
 166             documentProperties = new Hashtable<Object, Object>(2);
 167         }
 168         return documentProperties;
 169     }
 170 
 171     /**
 172      * Replaces the document properties dictionary for this document.
 173      *
 174      * @param x the new dictionary
 175      * @see #getDocumentProperties
 176      */
 177     public void setDocumentProperties(Dictionary<Object,Object> x) {
 178         documentProperties = x;
 179     }
 180 
 181     /**
 182      * Notifies all listeners that have registered interest for
 183      * notification on this event type.  The event instance
 184      * is lazily created using the parameters passed into
 185      * the fire method.
 186      *
 187      * @param e the event
 188      * @see EventListenerList
 189      */
 190     protected void fireInsertUpdate(DocumentEvent e) {
 191         notifyingListeners = true;
 192         try {
 193             // Guaranteed to return a non-null array
 194             Object[] listeners = listenerList.getListenerList();
 195             // Process the listeners last to first, notifying
 196             // those that are interested in this event
 197             for (int i = listeners.length-2; i>=0; i-=2) {
 198                 if (listeners[i]==DocumentListener.class) {
 199                     // Lazily create the event:
 200                     // if (e == null)
 201                     // e = new ListSelectionEvent(this, firstIndex, lastIndex);
 202                     ((DocumentListener)listeners[i+1]).insertUpdate(e);
 203                 }
 204             }
 205         } finally {
 206             notifyingListeners = false;
 207         }
 208     }
 209 
 210     /**
 211      * Notifies all listeners that have registered interest for
 212      * notification on this event type.  The event instance
 213      * is lazily created using the parameters passed into
 214      * the fire method.
 215      *
 216      * @param e the event
 217      * @see EventListenerList
 218      */
 219     protected void fireChangedUpdate(DocumentEvent e) {
 220         notifyingListeners = true;
 221         try {
 222             // Guaranteed to return a non-null array
 223             Object[] listeners = listenerList.getListenerList();
 224             // Process the listeners last to first, notifying
 225             // those that are interested in this event
 226             for (int i = listeners.length-2; i>=0; i-=2) {
 227                 if (listeners[i]==DocumentListener.class) {
 228                     // Lazily create the event:
 229                     // if (e == null)
 230                     // e = new ListSelectionEvent(this, firstIndex, lastIndex);
 231                     ((DocumentListener)listeners[i+1]).changedUpdate(e);
 232                 }
 233             }
 234         } finally {
 235             notifyingListeners = false;
 236         }
 237     }
 238 
 239     /**
 240      * Notifies all listeners that have registered interest for
 241      * notification on this event type.  The event instance
 242      * is lazily created using the parameters passed into
 243      * the fire method.
 244      *
 245      * @param e the event
 246      * @see EventListenerList
 247      */
 248     protected void fireRemoveUpdate(DocumentEvent e) {
 249         notifyingListeners = true;
 250         try {
 251             // Guaranteed to return a non-null array
 252             Object[] listeners = listenerList.getListenerList();
 253             // Process the listeners last to first, notifying
 254             // those that are interested in this event
 255             for (int i = listeners.length-2; i>=0; i-=2) {
 256                 if (listeners[i]==DocumentListener.class) {
 257                     // Lazily create the event:
 258                     // if (e == null)
 259                     // e = new ListSelectionEvent(this, firstIndex, lastIndex);
 260                     ((DocumentListener)listeners[i+1]).removeUpdate(e);
 261                 }
 262             }
 263         } finally {
 264             notifyingListeners = false;
 265         }
 266     }
 267 
 268     /**
 269      * Notifies all listeners that have registered interest for
 270      * notification on this event type.  The event instance
 271      * is lazily created using the parameters passed into
 272      * the fire method.
 273      *
 274      * @param e the event
 275      * @see EventListenerList
 276      */
 277     protected void fireUndoableEditUpdate(UndoableEditEvent e) {
 278         // Guaranteed to return a non-null array
 279         Object[] listeners = listenerList.getListenerList();
 280         // Process the listeners last to first, notifying
 281         // those that are interested in this event
 282         for (int i = listeners.length-2; i>=0; i-=2) {
 283             if (listeners[i]==UndoableEditListener.class) {
 284                 // Lazily create the event:
 285                 // if (e == null)
 286                 // e = new ListSelectionEvent(this, firstIndex, lastIndex);
 287                 ((UndoableEditListener)listeners[i+1]).undoableEditHappened(e);
 288             }
 289         }
 290     }
 291 
 292     /**
 293      * Returns an array of all the objects currently registered
 294      * as <code><em>Foo</em>Listener</code>s
 295      * upon this document.
 296      * <code><em>Foo</em>Listener</code>s are registered using the
 297      * <code>add<em>Foo</em>Listener</code> method.
 298      *
 299      * <p>
 300      * You can specify the <code>listenerType</code> argument
 301      * with a class literal, such as
 302      * <code><em>Foo</em>Listener.class</code>.
 303      * For example, you can query a
 304      * document <code>d</code>
 305      * for its document listeners with the following code:
 306      *
 307      * <pre>DocumentListener[] mls = (DocumentListener[])(d.getListeners(DocumentListener.class));</pre>
 308      *
 309      * If no such listeners exist, this method returns an empty array.
 310      *
 311      * @param <T> the listener type
 312      * @param listenerType the type of listeners requested
 313      * @return an array of all objects registered as
 314      *          <code><em>Foo</em>Listener</code>s on this component,
 315      *          or an empty array if no such
 316      *          listeners have been added
 317      * @exception ClassCastException if <code>listenerType</code>
 318      *          doesn't specify a class or interface that implements
 319      *          <code>java.util.EventListener</code>
 320      *
 321      * @see #getDocumentListeners
 322      * @see #getUndoableEditListeners
 323      *
 324      * @since 1.3
 325      */
 326     public <T extends EventListener> T[] getListeners(Class<T> listenerType) {
 327         return listenerList.getListeners(listenerType);
 328     }
 329 
 330     /**
 331      * Gets the asynchronous loading priority.  If less than zero,
 332      * the document should not be loaded asynchronously.
 333      *
 334      * @return the asynchronous loading priority, or <code>-1</code>
 335      *   if the document should not be loaded asynchronously
 336      */
 337     public int getAsynchronousLoadPriority() {
 338         Integer loadPriority = (Integer)
 339             getProperty(AbstractDocument.AsyncLoadPriority);
 340         if (loadPriority != null) {
 341             return loadPriority.intValue();
 342         }
 343         return -1;
 344     }
 345 
 346     /**
 347      * Sets the asynchronous loading priority.
 348      * @param p the new asynchronous loading priority; a value
 349      *   less than zero indicates that the document should not be
 350      *   loaded asynchronously
 351      */
 352     public void setAsynchronousLoadPriority(int p) {
 353         Integer loadPriority = (p >= 0) ? Integer.valueOf(p) : null;
 354         putProperty(AbstractDocument.AsyncLoadPriority, loadPriority);
 355     }
 356 
 357     /**
 358      * Sets the <code>DocumentFilter</code>. The <code>DocumentFilter</code>
 359      * is passed <code>insert</code> and <code>remove</code> to conditionally
 360      * allow inserting/deleting of the text.  A <code>null</code> value
 361      * indicates that no filtering will occur.
 362      *
 363      * @param filter the <code>DocumentFilter</code> used to constrain text
 364      * @see #getDocumentFilter
 365      * @since 1.4
 366      */
 367     public void setDocumentFilter(DocumentFilter filter) {
 368         documentFilter = filter;
 369     }
 370 
 371     /**
 372      * Returns the <code>DocumentFilter</code> that is responsible for
 373      * filtering of insertion/removal. A <code>null</code> return value
 374      * implies no filtering is to occur.
 375      *
 376      * @since 1.4
 377      * @see #setDocumentFilter
 378      * @return the DocumentFilter
 379      */
 380     public DocumentFilter getDocumentFilter() {
 381         return documentFilter;
 382     }
 383 
 384     // --- Document methods -----------------------------------------
 385 
 386     /**
 387      * This allows the model to be safely rendered in the presence
 388      * of currency, if the model supports being updated asynchronously.
 389      * The given runnable will be executed in a way that allows it
 390      * to safely read the model with no changes while the runnable
 391      * is being executed.  The runnable itself may <em>not</em>
 392      * make any mutations.
 393      * <p>
 394      * This is implemented to acquire a read lock for the duration
 395      * of the runnables execution.  There may be multiple runnables
 396      * executing at the same time, and all writers will be blocked
 397      * while there are active rendering runnables.  If the runnable
 398      * throws an exception, its lock will be safely released.
 399      * There is no protection against a runnable that never exits,
 400      * which will effectively leave the document locked for it's
 401      * lifetime.
 402      * <p>
 403      * If the given runnable attempts to make any mutations in
 404      * this implementation, a deadlock will occur.  There is
 405      * no tracking of individual rendering threads to enable
 406      * detecting this situation, but a subclass could incur
 407      * the overhead of tracking them and throwing an error.
 408      * <p>
 409      * This method is thread safe, although most Swing methods
 410      * are not. Please see
 411      * <A HREF="http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency
 412      * in Swing</A> for more information.
 413      *
 414      * @param r the renderer to execute
 415      */
 416     public void render(Runnable r) {
 417         readLock();
 418         try {
 419             r.run();
 420         } finally {
 421             readUnlock();
 422         }
 423     }
 424 
 425     /**
 426      * Returns the length of the data.  This is the number of
 427      * characters of content that represents the users data.
 428      *
 429      * @return the length &gt;= 0
 430      * @see Document#getLength
 431      */
 432     public int getLength() {
 433         return data.length() - 1;
 434     }
 435 
 436     /**
 437      * Adds a document listener for notification of any changes.
 438      *
 439      * @param listener the <code>DocumentListener</code> to add
 440      * @see Document#addDocumentListener
 441      */
 442     public void addDocumentListener(DocumentListener listener) {
 443         listenerList.add(DocumentListener.class, listener);
 444     }
 445 
 446     /**
 447      * Removes a document listener.
 448      *
 449      * @param listener the <code>DocumentListener</code> to remove
 450      * @see Document#removeDocumentListener
 451      */
 452     public void removeDocumentListener(DocumentListener listener) {
 453         listenerList.remove(DocumentListener.class, listener);
 454     }
 455 
 456     /**
 457      * Returns an array of all the document listeners
 458      * registered on this document.
 459      *
 460      * @return all of this document's <code>DocumentListener</code>s
 461      *         or an empty array if no document listeners are
 462      *         currently registered
 463      *
 464      * @see #addDocumentListener
 465      * @see #removeDocumentListener
 466      * @since 1.4
 467      */
 468     public DocumentListener[] getDocumentListeners() {
 469         return listenerList.getListeners(DocumentListener.class);
 470     }
 471 
 472     /**
 473      * Adds an undo listener for notification of any changes.
 474      * Undo/Redo operations performed on the <code>UndoableEdit</code>
 475      * will cause the appropriate DocumentEvent to be fired to keep
 476      * the view(s) in sync with the model.
 477      *
 478      * @param listener the <code>UndoableEditListener</code> to add
 479      * @see Document#addUndoableEditListener
 480      */
 481     public void addUndoableEditListener(UndoableEditListener listener) {
 482         listenerList.add(UndoableEditListener.class, listener);
 483     }
 484 
 485     /**
 486      * Removes an undo listener.
 487      *
 488      * @param listener the <code>UndoableEditListener</code> to remove
 489      * @see Document#removeDocumentListener
 490      */
 491     public void removeUndoableEditListener(UndoableEditListener listener) {
 492         listenerList.remove(UndoableEditListener.class, listener);
 493     }
 494 
 495     /**
 496      * Returns an array of all the undoable edit listeners
 497      * registered on this document.
 498      *
 499      * @return all of this document's <code>UndoableEditListener</code>s
 500      *         or an empty array if no undoable edit listeners are
 501      *         currently registered
 502      *
 503      * @see #addUndoableEditListener
 504      * @see #removeUndoableEditListener
 505      *
 506      * @since 1.4
 507      */
 508     public UndoableEditListener[] getUndoableEditListeners() {
 509         return listenerList.getListeners(UndoableEditListener.class);
 510     }
 511 
 512     /**
 513      * A convenience method for looking up a property value. It is
 514      * equivalent to:
 515      * <pre>
 516      * getDocumentProperties().get(key);
 517      * </pre>
 518      *
 519      * @param key the non-<code>null</code> property key
 520      * @return the value of this property or <code>null</code>
 521      * @see #getDocumentProperties
 522      */
 523     public final Object getProperty(Object key) {
 524         return getDocumentProperties().get(key);
 525     }
 526 
 527 
 528     /**
 529      * A convenience method for storing up a property value.  It is
 530      * equivalent to:
 531      * <pre>
 532      * getDocumentProperties().put(key, value);
 533      * </pre>
 534      * If <code>value</code> is <code>null</code> this method will
 535      * remove the property.
 536      *
 537      * @param key the non-<code>null</code> key
 538      * @param value the property value
 539      * @see #getDocumentProperties
 540      */
 541     public final void putProperty(Object key, Object value) {
 542         if (value != null) {
 543             getDocumentProperties().put(key, value);
 544         } else {
 545             getDocumentProperties().remove(key);
 546         }
 547         if( key == TextAttribute.RUN_DIRECTION
 548             && Boolean.TRUE.equals(getProperty(I18NProperty)) )
 549         {
 550             //REMIND - this needs to flip on the i18n property if run dir
 551             //is rtl and the i18n property is not already on.
 552             writeLock();
 553             try {
 554                 DefaultDocumentEvent e
 555                     = new DefaultDocumentEvent(0, getLength(),
 556                                                DocumentEvent.EventType.INSERT);
 557                 updateBidi( e );
 558             } finally {
 559                 writeUnlock();
 560             }
 561         }
 562     }
 563 
 564     /**
 565      * Removes some content from the document.
 566      * Removing content causes a write lock to be held while the
 567      * actual changes are taking place.  Observers are notified
 568      * of the change on the thread that called this method.
 569      * <p>
 570      * This method is thread safe, although most Swing methods
 571      * are not. Please see
 572      * <A HREF="http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency
 573      * in Swing</A> for more information.
 574      *
 575      * @param offs the starting offset &gt;= 0
 576      * @param len the number of characters to remove &gt;= 0
 577      * @exception BadLocationException  the given remove position is not a valid
 578      *   position within the document
 579      * @see Document#remove
 580      */
 581     public void remove(int offs, int len) throws BadLocationException {
 582         DocumentFilter filter = getDocumentFilter();
 583 
 584         writeLock();
 585         try {
 586             if (filter != null) {
 587                 filter.remove(getFilterBypass(), offs, len);
 588             }
 589             else {
 590                 handleRemove(offs, len);
 591             }
 592         } finally {
 593             writeUnlock();
 594         }
 595     }
 596 
 597     /**
 598      * Performs the actual work of the remove. It is assumed the caller
 599      * will have obtained a <code>writeLock</code> before invoking this.
 600      */
 601     void handleRemove(int offs, int len) throws BadLocationException {
 602         if (len > 0) {
 603             if (offs < 0 || (offs + len) > getLength()) {
 604                 throw new BadLocationException("Invalid remove",
 605                                                getLength() + 1);
 606             }
 607             DefaultDocumentEvent chng =
 608                     new DefaultDocumentEvent(offs, len, DocumentEvent.EventType.REMOVE);
 609 
 610             boolean isComposedTextElement;
 611             // Check whether the position of interest is the composed text
 612             isComposedTextElement = Utilities.isComposedTextElement(this, offs);
 613 
 614             removeUpdate(chng);
 615             UndoableEdit u = data.remove(offs, len);
 616             if (u != null) {
 617                 chng.addEdit(u);
 618             }
 619             postRemoveUpdate(chng);
 620             // Mark the edit as done.
 621             chng.end();
 622             fireRemoveUpdate(chng);
 623             // only fire undo if Content implementation supports it
 624             // undo for the composed text is not supported for now
 625             if ((u != null) && !isComposedTextElement) {
 626                 fireUndoableEditUpdate(new UndoableEditEvent(this, chng));
 627             }
 628         }
 629     }
 630 
 631     /**
 632      * Deletes the region of text from <code>offset</code> to
 633      * <code>offset + length</code>, and replaces it with <code>text</code>.
 634      * It is up to the implementation as to how this is implemented, some
 635      * implementations may treat this as two distinct operations: a remove
 636      * followed by an insert, others may treat the replace as one atomic
 637      * operation.
 638      *
 639      * @param offset index of child element
 640      * @param length length of text to delete, may be 0 indicating don't
 641      *               delete anything
 642      * @param text text to insert, <code>null</code> indicates no text to insert
 643      * @param attrs AttributeSet indicating attributes of inserted text,
 644      *              <code>null</code>
 645      *              is legal, and typically treated as an empty attributeset,
 646      *              but exact interpretation is left to the subclass
 647      * @exception BadLocationException the given position is not a valid
 648      *            position within the document
 649      * @since 1.4
 650      */
 651     public void replace(int offset, int length, String text,
 652                         AttributeSet attrs) throws BadLocationException {
 653         if (length == 0 && (text == null || text.length() == 0)) {
 654             return;
 655         }
 656         DocumentFilter filter = getDocumentFilter();
 657 
 658         writeLock();
 659         try {
 660             if (filter != null) {
 661                 filter.replace(getFilterBypass(), offset, length, text,
 662                                attrs);
 663             }
 664             else {
 665                 if (length > 0) {
 666                     remove(offset, length);
 667                 }
 668                 if (text != null && text.length() > 0) {
 669                     insertString(offset, text, attrs);
 670                 }
 671             }
 672         } finally {
 673             writeUnlock();
 674         }
 675     }
 676 
 677     /**
 678      * Inserts some content into the document.
 679      * Inserting content causes a write lock to be held while the
 680      * actual changes are taking place, followed by notification
 681      * to the observers on the thread that grabbed the write lock.
 682      * <p>
 683      * This method is thread safe, although most Swing methods
 684      * are not. Please see
 685      * <A HREF="http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency
 686      * in Swing</A> for more information.
 687      *
 688      * @param offs the starting offset &gt;= 0
 689      * @param str the string to insert; does nothing with null/empty strings
 690      * @param a the attributes for the inserted content
 691      * @exception BadLocationException  the given insert position is not a valid
 692      *   position within the document
 693      * @see Document#insertString
 694      */
 695     public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
 696         if ((str == null) || (str.length() == 0)) {
 697             return;
 698         }
 699         DocumentFilter filter = getDocumentFilter();
 700 
 701         writeLock();
 702 
 703         try {
 704             if (filter != null) {
 705                 filter.insertString(getFilterBypass(), offs, str, a);
 706             } else {
 707                 handleInsertString(offs, str, a);
 708             }
 709         } finally {
 710             writeUnlock();
 711         }
 712     }
 713 
 714     /**
 715      * Performs the actual work of inserting the text; it is assumed the
 716      * caller has obtained a write lock before invoking this.
 717      */
 718     private void handleInsertString(int offs, String str, AttributeSet a)
 719             throws BadLocationException {
 720         if ((str == null) || (str.length() == 0)) {
 721             return;
 722         }
 723         UndoableEdit u = data.insertString(offs, str);
 724         DefaultDocumentEvent e =
 725             new DefaultDocumentEvent(offs, str.length(), DocumentEvent.EventType.INSERT);
 726         if (u != null) {
 727             e.addEdit(u);
 728         }
 729 
 730         // see if complex glyph layout support is needed
 731         if( getProperty(I18NProperty).equals( Boolean.FALSE ) ) {
 732             // if a default direction of right-to-left has been specified,
 733             // we want complex layout even if the text is all left to right.
 734             Object d = getProperty(TextAttribute.RUN_DIRECTION);
 735             if ((d != null) && (d.equals(TextAttribute.RUN_DIRECTION_RTL))) {
 736                 putProperty( I18NProperty, Boolean.TRUE);
 737             } else {
 738                 char[] chars = str.toCharArray();
 739                 if (SwingUtilities2.isComplexLayout(chars, 0, chars.length)) {
 740                     putProperty( I18NProperty, Boolean.TRUE);
 741                 }
 742             }
 743         }
 744 
 745         insertUpdate(e, a);
 746         // Mark the edit as done.
 747         e.end();
 748         fireInsertUpdate(e);
 749         // only fire undo if Content implementation supports it
 750         // undo for the composed text is not supported for now
 751         if (u != null && (a == null || !a.isDefined(StyleConstants.ComposedTextAttribute))) {
 752             fireUndoableEditUpdate(new UndoableEditEvent(this, e));
 753         }
 754     }
 755 
 756     /**
 757      * Gets a sequence of text from the document.
 758      *
 759      * @param offset the starting offset &gt;= 0
 760      * @param length the number of characters to retrieve &gt;= 0
 761      * @return the text
 762      * @exception BadLocationException  the range given includes a position
 763      *   that is not a valid position within the document
 764      * @see Document#getText
 765      */
 766     public String getText(int offset, int length) throws BadLocationException {
 767         if (length < 0) {
 768             throw new BadLocationException("Length must be positive", length);
 769         }
 770         String str = data.getString(offset, length);
 771         return str;
 772     }
 773 
 774     /**
 775      * Fetches the text contained within the given portion
 776      * of the document.
 777      * <p>
 778      * If the partialReturn property on the txt parameter is false, the
 779      * data returned in the Segment will be the entire length requested and
 780      * may or may not be a copy depending upon how the data was stored.
 781      * If the partialReturn property is true, only the amount of text that
 782      * can be returned without creating a copy is returned.  Using partial
 783      * returns will give better performance for situations where large
 784      * parts of the document are being scanned.  The following is an example
 785      * of using the partial return to access the entire document:
 786      *
 787      * <pre>
 788      * &nbsp; int nleft = doc.getDocumentLength();
 789      * &nbsp; Segment text = new Segment();
 790      * &nbsp; int offs = 0;
 791      * &nbsp; text.setPartialReturn(true);
 792      * &nbsp; while (nleft &gt; 0) {
 793      * &nbsp;     doc.getText(offs, nleft, text);
 794      * &nbsp;     // do something with text
 795      * &nbsp;     nleft -= text.count;
 796      * &nbsp;     offs += text.count;
 797      * &nbsp; }
 798      * </pre>
 799      *
 800      * @param offset the starting offset &gt;= 0
 801      * @param length the number of characters to retrieve &gt;= 0
 802      * @param txt the Segment object to retrieve the text into
 803      * @exception BadLocationException  the range given includes a position
 804      *   that is not a valid position within the document
 805      */
 806     public void getText(int offset, int length, Segment txt) throws BadLocationException {
 807         if (length < 0) {
 808             throw new BadLocationException("Length must be positive", length);
 809         }
 810         data.getChars(offset, length, txt);
 811     }
 812 
 813     /**
 814      * Returns a position that will track change as the document
 815      * is altered.
 816      * <p>
 817      * This method is thread safe, although most Swing methods
 818      * are not. Please see
 819      * <A HREF="http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency
 820      * in Swing</A> for more information.
 821      *
 822      * @param offs the position in the model &gt;= 0
 823      * @return the position
 824      * @exception BadLocationException  if the given position does not
 825      *   represent a valid location in the associated document
 826      * @see Document#createPosition
 827      */
 828     public synchronized Position createPosition(int offs) throws BadLocationException {
 829         return data.createPosition(offs);
 830     }
 831 
 832     /**
 833      * Returns a position that represents the start of the document.  The
 834      * position returned can be counted on to track change and stay
 835      * located at the beginning of the document.
 836      *
 837      * @return the position
 838      */
 839     public final Position getStartPosition() {
 840         Position p;
 841         try {
 842             p = createPosition(0);
 843         } catch (BadLocationException bl) {
 844             p = null;
 845         }
 846         return p;
 847     }
 848 
 849     /**
 850      * Returns a position that represents the end of the document.  The
 851      * position returned can be counted on to track change and stay
 852      * located at the end of the document.
 853      *
 854      * @return the position
 855      */
 856     public final Position getEndPosition() {
 857         Position p;
 858         try {
 859             p = createPosition(data.length());
 860         } catch (BadLocationException bl) {
 861             p = null;
 862         }
 863         return p;
 864     }
 865 
 866     /**
 867      * Gets all root elements defined.  Typically, there
 868      * will only be one so the default implementation
 869      * is to return the default root element.
 870      *
 871      * @return the root element
 872      */
 873     public Element[] getRootElements() {
 874         Element[] elems = new Element[2];
 875         elems[0] = getDefaultRootElement();
 876         elems[1] = getBidiRootElement();
 877         return elems;
 878     }
 879 
 880     /**
 881      * Returns the root element that views should be based upon
 882      * unless some other mechanism for assigning views to element
 883      * structures is provided.
 884      *
 885      * @return the root element
 886      * @see Document#getDefaultRootElement
 887      */
 888     public abstract Element getDefaultRootElement();
 889 
 890     // ---- local methods -----------------------------------------
 891 
 892     /**
 893      * Returns the <code>FilterBypass</code>. This will create one if one
 894      * does not yet exist.
 895      */
 896     private DocumentFilter.FilterBypass getFilterBypass() {
 897         if (filterBypass == null) {
 898             filterBypass = new DefaultFilterBypass();
 899         }
 900         return filterBypass;
 901     }
 902 
 903     /**
 904      * Returns the root element of the bidirectional structure for this
 905      * document.  Its children represent character runs with a given
 906      * Unicode bidi level.
 907      * @return the root element of the bidirectional structure for this
 908      * document
 909      */
 910     public Element getBidiRootElement() {
 911         return bidiRoot;
 912     }
 913 
 914     /**
 915      * Returns true if the text in the range <code>p0</code> to
 916      * <code>p1</code> is left to right.
 917      */
 918     static boolean isLeftToRight(Document doc, int p0, int p1) {
 919         if (Boolean.TRUE.equals(doc.getProperty(I18NProperty))) {
 920             if (doc instanceof AbstractDocument) {
 921                 AbstractDocument adoc = (AbstractDocument) doc;
 922                 Element bidiRoot = adoc.getBidiRootElement();
 923                 int index = bidiRoot.getElementIndex(p0);
 924                 Element bidiElem = bidiRoot.getElement(index);
 925                 if (bidiElem.getEndOffset() >= p1) {
 926                     AttributeSet bidiAttrs = bidiElem.getAttributes();
 927                     return ((StyleConstants.getBidiLevel(bidiAttrs) % 2) == 0);
 928                 }
 929             }
 930         }
 931         return true;
 932     }
 933 
 934     /**
 935      * Get the paragraph element containing the given position.  Sub-classes
 936      * must define for themselves what exactly constitutes a paragraph.  They
 937      * should keep in mind however that a paragraph should at least be the
 938      * unit of text over which to run the Unicode bidirectional algorithm.
 939      *
 940      * @param pos the starting offset &gt;= 0
 941      * @return the element */
 942     public abstract Element getParagraphElement(int pos);
 943 
 944 
 945     /**
 946      * Fetches the context for managing attributes.  This
 947      * method effectively establishes the strategy used
 948      * for compressing AttributeSet information.
 949      *
 950      * @return the context
 951      */
 952     protected final AttributeContext getAttributeContext() {
 953         return context;
 954     }
 955 
 956     /**
 957      * Updates document structure as a result of text insertion.  This
 958      * will happen within a write lock.  If a subclass of
 959      * this class reimplements this method, it should delegate to the
 960      * superclass as well.
 961      *
 962      * @param chng a description of the change
 963      * @param attr the attributes for the change
 964      */
 965     protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) {
 966         if( getProperty(I18NProperty).equals( Boolean.TRUE ) )
 967             updateBidi( chng );
 968 
 969         // Check if a multi byte is encountered in the inserted text.
 970         if (chng.type == DocumentEvent.EventType.INSERT &&
 971                         chng.getLength() > 0 &&
 972                         !Boolean.TRUE.equals(getProperty(MultiByteProperty))) {
 973             Segment segment = SegmentCache.getSharedSegment();
 974             try {
 975                 getText(chng.getOffset(), chng.getLength(), segment);
 976                 segment.first();
 977                 do {
 978                     if ((int)segment.current() > 255) {
 979                         putProperty(MultiByteProperty, Boolean.TRUE);
 980                         break;
 981                     }
 982                 } while (segment.next() != Segment.DONE);
 983             } catch (BadLocationException ble) {
 984                 // Should never happen
 985             }
 986             SegmentCache.releaseSharedSegment(segment);
 987         }
 988     }
 989 
 990     /**
 991      * Updates any document structure as a result of text removal.  This
 992      * method is called before the text is actually removed from the Content.
 993      * This will happen within a write lock. If a subclass
 994      * of this class reimplements this method, it should delegate to the
 995      * superclass as well.
 996      *
 997      * @param chng a description of the change
 998      */
 999     protected void removeUpdate(DefaultDocumentEvent chng) {
1000     }
1001 
1002     /**
1003      * Updates any document structure as a result of text removal.  This
1004      * method is called after the text has been removed from the Content.
1005      * This will happen within a write lock. If a subclass
1006      * of this class reimplements this method, it should delegate to the
1007      * superclass as well.
1008      *
1009      * @param chng a description of the change
1010      */
1011     protected void postRemoveUpdate(DefaultDocumentEvent chng) {
1012         if( getProperty(I18NProperty).equals( Boolean.TRUE ) )
1013             updateBidi( chng );
1014     }
1015 
1016 
1017     /**
1018      * Update the bidi element structure as a result of the given change
1019      * to the document.  The given change will be updated to reflect the
1020      * changes made to the bidi structure.
1021      *
1022      * This method assumes that every offset in the model is contained in
1023      * exactly one paragraph.  This method also assumes that it is called
1024      * after the change is made to the default element structure.
1025      */
1026     void updateBidi( DefaultDocumentEvent chng ) {
1027 
1028         // Calculate the range of paragraphs affected by the change.
1029         int firstPStart;
1030         int lastPEnd;
1031         if( chng.type == DocumentEvent.EventType.INSERT
1032             || chng.type == DocumentEvent.EventType.CHANGE )
1033         {
1034             int chngStart = chng.getOffset();
1035             int chngEnd =  chngStart + chng.getLength();
1036             firstPStart = getParagraphElement(chngStart).getStartOffset();
1037             lastPEnd = getParagraphElement(chngEnd).getEndOffset();
1038         } else if( chng.type == DocumentEvent.EventType.REMOVE ) {
1039             Element paragraph = getParagraphElement( chng.getOffset() );
1040             firstPStart = paragraph.getStartOffset();
1041             lastPEnd = paragraph.getEndOffset();
1042         } else {
1043             throw new Error("Internal error: unknown event type.");
1044         }
1045         //System.out.println("updateBidi: firstPStart = " + firstPStart + " lastPEnd = " + lastPEnd );
1046 
1047 
1048         // Calculate the bidi levels for the affected range of paragraphs.  The
1049         // levels array will contain a bidi level for each character in the
1050         // affected text.
1051         byte levels[] = calculateBidiLevels( firstPStart, lastPEnd );
1052 
1053 
1054         Vector<Element> newElements = new Vector<Element>();
1055 
1056         // Calculate the first span of characters in the affected range with
1057         // the same bidi level.  If this level is the same as the level of the
1058         // previous bidi element (the existing bidi element containing
1059         // firstPStart-1), then merge in the previous element.  If not, but
1060         // the previous element overlaps the affected range, truncate the
1061         // previous element at firstPStart.
1062         int firstSpanStart = firstPStart;
1063         int removeFromIndex = 0;
1064         if( firstSpanStart > 0 ) {
1065             int prevElemIndex = bidiRoot.getElementIndex(firstPStart-1);
1066             removeFromIndex = prevElemIndex;
1067             Element prevElem = bidiRoot.getElement(prevElemIndex);
1068             int prevLevel=StyleConstants.getBidiLevel(prevElem.getAttributes());
1069             //System.out.println("createbidiElements: prevElem= " + prevElem  + " prevLevel= " + prevLevel + "level[0] = " + levels[0]);
1070             if( prevLevel==levels[0] ) {
1071                 firstSpanStart = prevElem.getStartOffset();
1072             } else if( prevElem.getEndOffset() > firstPStart ) {
1073                 newElements.addElement(new BidiElement(bidiRoot,
1074                                                        prevElem.getStartOffset(),
1075                                                        firstPStart, prevLevel));
1076             } else {
1077                 removeFromIndex++;
1078             }
1079         }
1080 
1081         int firstSpanEnd = 0;
1082         while((firstSpanEnd<levels.length) && (levels[firstSpanEnd]==levels[0]))
1083             firstSpanEnd++;
1084 
1085 
1086         // Calculate the last span of characters in the affected range with
1087         // the same bidi level.  If this level is the same as the level of the
1088         // next bidi element (the existing bidi element containing lastPEnd),
1089         // then merge in the next element.  If not, but the next element
1090         // overlaps the affected range, adjust the next element to start at
1091         // lastPEnd.
1092         int lastSpanEnd = lastPEnd;
1093         Element newNextElem = null;
1094         int removeToIndex = bidiRoot.getElementCount() - 1;
1095         if( lastSpanEnd <= getLength() ) {
1096             int nextElemIndex = bidiRoot.getElementIndex( lastPEnd );
1097             removeToIndex = nextElemIndex;
1098             Element nextElem = bidiRoot.getElement( nextElemIndex );
1099             int nextLevel = StyleConstants.getBidiLevel(nextElem.getAttributes());
1100             if( nextLevel == levels[levels.length-1] ) {
1101                 lastSpanEnd = nextElem.getEndOffset();
1102             } else if( nextElem.getStartOffset() < lastPEnd ) {
1103                 newNextElem = new BidiElement(bidiRoot, lastPEnd,
1104                                               nextElem.getEndOffset(),
1105                                               nextLevel);
1106             } else {
1107                 removeToIndex--;
1108             }
1109         }
1110 
1111         int lastSpanStart = levels.length;
1112         while( (lastSpanStart>firstSpanEnd)
1113                && (levels[lastSpanStart-1]==levels[levels.length-1]) )
1114             lastSpanStart--;
1115 
1116 
1117         // If the first and last spans are contiguous and have the same level,
1118         // merge them and create a single new element for the entire span.
1119         // Otherwise, create elements for the first and last spans as well as
1120         // any spans in between.
1121         if((firstSpanEnd==lastSpanStart)&&(levels[0]==levels[levels.length-1])){
1122             newElements.addElement(new BidiElement(bidiRoot, firstSpanStart,
1123                                                    lastSpanEnd, levels[0]));
1124         } else {
1125             // Create an element for the first span.
1126             newElements.addElement(new BidiElement(bidiRoot, firstSpanStart,
1127                                                    firstSpanEnd+firstPStart,
1128                                                    levels[0]));
1129             // Create elements for the spans in between the first and last
1130             for( int i=firstSpanEnd; i<lastSpanStart; ) {
1131                 //System.out.println("executed line 872");
1132                 int j;
1133                 for( j=i;  (j<levels.length) && (levels[j] == levels[i]); j++ );
1134                 newElements.addElement(new BidiElement(bidiRoot, firstPStart+i,
1135                                                        firstPStart+j,
1136                                                        (int)levels[i]));
1137                 i=j;
1138             }
1139             // Create an element for the last span.
1140             newElements.addElement(new BidiElement(bidiRoot,
1141                                                    lastSpanStart+firstPStart,
1142                                                    lastSpanEnd,
1143                                                    levels[levels.length-1]));
1144         }
1145 
1146         if( newNextElem != null )
1147             newElements.addElement( newNextElem );
1148 
1149 
1150         // Calculate the set of existing bidi elements which must be
1151         // removed.
1152         int removedElemCount = 0;
1153         if( bidiRoot.getElementCount() > 0 ) {
1154             removedElemCount = removeToIndex - removeFromIndex + 1;
1155         }
1156         Element[] removedElems = new Element[removedElemCount];
1157         for( int i=0; i<removedElemCount; i++ ) {
1158             removedElems[i] = bidiRoot.getElement(removeFromIndex+i);
1159         }
1160 
1161         Element[] addedElems = new Element[ newElements.size() ];
1162         newElements.copyInto( addedElems );
1163 
1164         // Update the change record.
1165         ElementEdit ee = new ElementEdit( bidiRoot, removeFromIndex,
1166                                           removedElems, addedElems );
1167         chng.addEdit( ee );
1168 
1169         // Update the bidi element structure.
1170         bidiRoot.replace( removeFromIndex, removedElems.length, addedElems );
1171     }
1172 
1173 
1174     /**
1175      * Calculate the levels array for a range of paragraphs.
1176      */
1177     private byte[] calculateBidiLevels( int firstPStart, int lastPEnd ) {
1178 
1179         byte levels[] = new byte[ lastPEnd - firstPStart ];
1180         int  levelsEnd = 0;
1181         Boolean defaultDirection = null;
1182         Object d = getProperty(TextAttribute.RUN_DIRECTION);
1183         if (d instanceof Boolean) {
1184             defaultDirection = (Boolean) d;
1185         }
1186 
1187         // For each paragraph in the given range of paragraphs, get its
1188         // levels array and add it to the levels array for the entire span.
1189         for(int o=firstPStart; o<lastPEnd; ) {
1190             Element p = getParagraphElement( o );
1191             int pStart = p.getStartOffset();
1192             int pEnd = p.getEndOffset();
1193 
1194             // default run direction for the paragraph.  This will be
1195             // null if there is no direction override specified (i.e.
1196             // the direction will be determined from the content).
1197             Boolean direction = defaultDirection;
1198             d = p.getAttributes().getAttribute(TextAttribute.RUN_DIRECTION);
1199             if (d instanceof Boolean) {
1200                 direction = (Boolean) d;
1201             }
1202 
1203             //System.out.println("updateBidi: paragraph start = " + pStart + " paragraph end = " + pEnd);
1204 
1205             // Create a Bidi over this paragraph then get the level
1206             // array.
1207             Segment seg = SegmentCache.getSharedSegment();
1208             try {
1209                 getText(pStart, pEnd-pStart, seg);
1210             } catch (BadLocationException e ) {
1211                 throw new Error("Internal error: " + e.toString());
1212             }
1213             // REMIND(bcb) we should really be using a Segment here.
1214             Bidi bidiAnalyzer;
1215             int bidiflag = Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT;
1216             if (direction != null) {
1217                 if (TextAttribute.RUN_DIRECTION_LTR.equals(direction)) {
1218                     bidiflag = Bidi.DIRECTION_LEFT_TO_RIGHT;
1219                 } else {
1220                     bidiflag = Bidi.DIRECTION_RIGHT_TO_LEFT;
1221                 }
1222             }
1223             bidiAnalyzer = new Bidi(seg.array, seg.offset, null, 0, seg.count,
1224                     bidiflag);
1225             BidiUtils.getLevels(bidiAnalyzer, levels, levelsEnd);
1226             levelsEnd += bidiAnalyzer.getLength();
1227 
1228             o =  p.getEndOffset();
1229             SegmentCache.releaseSharedSegment(seg);
1230         }
1231 
1232         // REMIND(bcb) remove this code when debugging is done.
1233         if( levelsEnd != levels.length )
1234             throw new Error("levelsEnd assertion failed.");
1235 
1236         return levels;
1237     }
1238 
1239     /**
1240      * Gives a diagnostic dump.
1241      *
1242      * @param out the output stream
1243      */
1244     public void dump(PrintStream out) {
1245         Element root = getDefaultRootElement();
1246         if (root instanceof AbstractElement) {
1247             ((AbstractElement)root).dump(out, 0);
1248         }
1249         bidiRoot.dump(out,0);
1250     }
1251 
1252     /**
1253      * Gets the content for the document.
1254      *
1255      * @return the content
1256      */
1257     protected final Content getContent() {
1258         return data;
1259     }
1260 
1261     /**
1262      * Creates a document leaf element.
1263      * Hook through which elements are created to represent the
1264      * document structure.  Because this implementation keeps
1265      * structure and content separate, elements grow automatically
1266      * when content is extended so splits of existing elements
1267      * follow.  The document itself gets to decide how to generate
1268      * elements to give flexibility in the type of elements used.
1269      *
1270      * @param parent the parent element
1271      * @param a the attributes for the element
1272      * @param p0 the beginning of the range &gt;= 0
1273      * @param p1 the end of the range &gt;= p0
1274      * @return the new element
1275      */
1276     protected Element createLeafElement(Element parent, AttributeSet a, int p0, int p1) {
1277         return new LeafElement(parent, a, p0, p1);
1278     }
1279 
1280     /**
1281      * Creates a document branch element, that can contain other elements.
1282      *
1283      * @param parent the parent element
1284      * @param a the attributes
1285      * @return the element
1286      */
1287     protected Element createBranchElement(Element parent, AttributeSet a) {
1288         return new BranchElement(parent, a);
1289     }
1290 
1291     // --- Document locking ----------------------------------
1292 
1293     /**
1294      * Fetches the current writing thread if there is one.
1295      * This can be used to distinguish whether a method is
1296      * being called as part of an existing modification or
1297      * if a lock needs to be acquired and a new transaction
1298      * started.
1299      *
1300      * @return the thread actively modifying the document
1301      *  or <code>null</code> if there are no modifications in progress
1302      */
1303     protected synchronized final Thread getCurrentWriter() {
1304         return currWriter;
1305     }
1306 
1307     /**
1308      * Acquires a lock to begin mutating the document this lock
1309      * protects.  There can be no writing, notification of changes, or
1310      * reading going on in order to gain the lock.  Additionally a thread is
1311      * allowed to gain more than one <code>writeLock</code>,
1312      * as long as it doesn't attempt to gain additional <code>writeLock</code>s
1313      * from within document notification.  Attempting to gain a
1314      * <code>writeLock</code> from within a DocumentListener notification will
1315      * result in an <code>IllegalStateException</code>.  The ability
1316      * to obtain more than one <code>writeLock</code> per thread allows
1317      * subclasses to gain a writeLock, perform a number of operations, then
1318      * release the lock.
1319      * <p>
1320      * Calls to <code>writeLock</code>
1321      * must be balanced with calls to <code>writeUnlock</code>, else the
1322      * <code>Document</code> will be left in a locked state so that no
1323      * reading or writing can be done.
1324      *
1325      * @exception IllegalStateException thrown on illegal lock
1326      *  attempt.  If the document is implemented properly, this can
1327      *  only happen if a document listener attempts to mutate the
1328      *  document.  This situation violates the bean event model
1329      *  where order of delivery is not guaranteed and all listeners
1330      *  should be notified before further mutations are allowed.
1331      */
1332     protected synchronized final void writeLock() {
1333         try {
1334             while ((numReaders > 0) || (currWriter != null)) {
1335                 if (Thread.currentThread() == currWriter) {
1336                     if (notifyingListeners) {
1337                         // Assuming one doesn't do something wrong in a
1338                         // subclass this should only happen if a
1339                         // DocumentListener tries to mutate the document.
1340                         throw new IllegalStateException(
1341                                       "Attempt to mutate in notification");
1342                     }
1343                     numWriters++;
1344                     return;
1345                 }
1346                 wait();
1347             }
1348             currWriter = Thread.currentThread();
1349             numWriters = 1;
1350         } catch (InterruptedException e) {
1351             throw new Error("Interrupted attempt to acquire write lock");
1352         }
1353     }
1354 
1355     /**
1356      * Releases a write lock previously obtained via <code>writeLock</code>.
1357      * After decrementing the lock count if there are no outstanding locks
1358      * this will allow a new writer, or readers.
1359      *
1360      * @see #writeLock
1361      */
1362     protected synchronized final void writeUnlock() {
1363         if (--numWriters <= 0) {
1364             numWriters = 0;
1365             currWriter = null;
1366             notifyAll();
1367         }
1368     }
1369 
1370     /**
1371      * Acquires a lock to begin reading some state from the
1372      * document.  There can be multiple readers at the same time.
1373      * Writing blocks the readers until notification of the change
1374      * to the listeners has been completed.  This method should
1375      * be used very carefully to avoid unintended compromise
1376      * of the document.  It should always be balanced with a
1377      * <code>readUnlock</code>.
1378      *
1379      * @see #readUnlock
1380      */
1381     public synchronized final void readLock() {
1382         try {
1383             while (currWriter != null) {
1384                 if (currWriter == Thread.currentThread()) {
1385                     // writer has full read access.... may try to acquire
1386                     // lock in notification
1387                     return;
1388                 }
1389                 wait();
1390             }
1391             numReaders += 1;
1392         } catch (InterruptedException e) {
1393             throw new Error("Interrupted attempt to acquire read lock");
1394         }
1395     }
1396 
1397     /**
1398      * Does a read unlock.  This signals that one
1399      * of the readers is done.  If there are no more readers
1400      * then writing can begin again.  This should be balanced
1401      * with a readLock, and should occur in a finally statement
1402      * so that the balance is guaranteed.  The following is an
1403      * example.
1404      * <pre><code>
1405      * &nbsp;   readLock();
1406      * &nbsp;   try {
1407      * &nbsp;       // do something
1408      * &nbsp;   } finally {
1409      * &nbsp;       readUnlock();
1410      * &nbsp;   }
1411      * </code></pre>
1412      *
1413      * @see #readLock
1414      */
1415     public synchronized final void readUnlock() {
1416         if (currWriter == Thread.currentThread()) {
1417             // writer has full read access.... may try to acquire
1418             // lock in notification
1419             return;
1420         }
1421         if (numReaders <= 0) {
1422             throw new StateInvariantError(BAD_LOCK_STATE);
1423         }
1424         numReaders -= 1;
1425         notify();
1426     }
1427 
1428     // --- serialization ---------------------------------------------
1429 
1430     @SuppressWarnings("unchecked")
1431     private void readObject(ObjectInputStream s)
1432       throws ClassNotFoundException, IOException
1433     {
1434         ObjectInputStream.GetField f = s.readFields();
1435 
1436         documentProperties =
1437             (Dictionary<Object, Object>) f.get("documentProperties", null);
1438         listenerList = new EventListenerList();
1439         data = (Content) f.get("data", null);
1440         context = (AttributeContext) f.get("context", null);
1441         documentFilter = (DocumentFilter) f.get("documentFilter", null);
1442 
1443         // Restore bidi structure
1444         //REMIND(bcb) This creates an initial bidi element to account for
1445         //the \n that exists by default in the content.
1446         bidiRoot = new BidiRootElement();
1447         try {
1448             writeLock();
1449             Element[] p = new Element[1];
1450             p[0] = new BidiElement( bidiRoot, 0, 1, 0 );
1451             bidiRoot.replace(0,0,p);
1452         } finally {
1453             writeUnlock();
1454         }
1455         // At this point bidi root is only partially correct. To fully
1456         // restore it we need access to getDefaultRootElement. But, this
1457         // is created by the subclass and at this point will be null. We
1458         // thus use registerValidation.
1459         s.registerValidation(new ObjectInputValidation() {
1460             public void validateObject() {
1461                 try {
1462                     writeLock();
1463                     DefaultDocumentEvent e = new DefaultDocumentEvent
1464                                    (0, getLength(),
1465                                     DocumentEvent.EventType.INSERT);
1466                     updateBidi( e );
1467                 }
1468                 finally {
1469                     writeUnlock();
1470                 }
1471             }
1472         }, 0);
1473     }
1474 
1475     // ----- member variables ------------------------------------------
1476 
1477     private transient int numReaders;
1478     private transient Thread currWriter;
1479     /**
1480      * The number of writers, all obtained from <code>currWriter</code>.
1481      */
1482     private transient int numWriters;
1483     /**
1484      * True will notifying listeners.
1485      */
1486     private transient boolean notifyingListeners;
1487 
1488     private static Boolean defaultI18NProperty;
1489 
1490     /**
1491      * Storage for document-wide properties.
1492      */
1493     private Dictionary<Object,Object> documentProperties = null;
1494 
1495     /**
1496      * The event listener list for the document.
1497      */
1498     protected EventListenerList listenerList = new EventListenerList();
1499 
1500     /**
1501      * Where the text is actually stored, and a set of marks
1502      * that track change as the document is edited are managed.
1503      */
1504     private Content data;
1505 
1506     /**
1507      * Factory for the attributes.  This is the strategy for
1508      * attribute compression and control of the lifetime of
1509      * a set of attributes as a collection.  This may be shared
1510      * with other documents.
1511      */
1512     private AttributeContext context;
1513 
1514     /**
1515      * The root of the bidirectional structure for this document.  Its children
1516      * represent character runs with the same Unicode bidi level.
1517      */
1518     private transient BranchElement bidiRoot;
1519 
1520     /**
1521      * Filter for inserting/removing of text.
1522      */
1523     private DocumentFilter documentFilter;
1524 
1525     /**
1526      * Used by DocumentFilter to do actual insert/remove.
1527      */
1528     private transient DocumentFilter.FilterBypass filterBypass;
1529 
1530     private static final String BAD_LOCK_STATE = "document lock failure";
1531 
1532     /**
1533      * Error message to indicate a bad location.
1534      */
1535     protected static final String BAD_LOCATION = "document location failure";
1536 
1537     /**
1538      * Name of elements used to represent paragraphs
1539      */
1540     public static final String ParagraphElementName = "paragraph";
1541 
1542     /**
1543      * Name of elements used to represent content
1544      */
1545     public static final String ContentElementName = "content";
1546 
1547     /**
1548      * Name of elements used to hold sections (lines/paragraphs).
1549      */
1550     public static final String SectionElementName = "section";
1551 
1552     /**
1553      * Name of elements used to hold a unidirectional run
1554      */
1555     public static final String BidiElementName = "bidi level";
1556 
1557     /**
1558      * Name of the attribute used to specify element
1559      * names.
1560      */
1561     public static final String ElementNameAttribute = "$ename";
1562 
1563     /**
1564      * Document property that indicates whether internationalization
1565      * functions such as text reordering or reshaping should be
1566      * performed. This property should not be publicly exposed,
1567      * since it is used for implementation convenience only.  As a
1568      * side effect, copies of this property may be in its subclasses
1569      * that live in different packages (e.g. HTMLDocument as of now),
1570      * so those copies should also be taken care of when this property
1571      * needs to be modified.
1572      */
1573     static final String I18NProperty = "i18n";
1574 
1575     /**
1576      * Document property that indicates if a character has been inserted
1577      * into the document that is more than one byte long.  GlyphView uses
1578      * this to determine if it should use BreakIterator.
1579      */
1580     static final Object MultiByteProperty = "multiByte";
1581 
1582     /**
1583      * Document property that indicates asynchronous loading is
1584      * desired, with the thread priority given as the value.
1585      */
1586     static final String AsyncLoadPriority = "load priority";
1587 
1588     /**
1589      * Interface to describe a sequence of character content that
1590      * can be edited.  Implementations may or may not support a
1591      * history mechanism which will be reflected by whether or not
1592      * mutations return an UndoableEdit implementation.
1593      * @see AbstractDocument
1594      */
1595     public interface Content {
1596 
1597         /**
1598          * Creates a position within the content that will
1599          * track change as the content is mutated.
1600          *
1601          * @param offset the offset in the content &gt;= 0
1602          * @return a Position
1603          * @exception BadLocationException for an invalid offset
1604          */
1605         public Position createPosition(int offset) throws BadLocationException;
1606 
1607         /**
1608          * Current length of the sequence of character content.
1609          *
1610          * @return the length &gt;= 0
1611          */
1612         public int length();
1613 
1614         /**
1615          * Inserts a string of characters into the sequence.
1616          *
1617          * @param where   offset into the sequence to make the insertion &gt;= 0
1618          * @param str     string to insert
1619          * @return  if the implementation supports a history mechanism,
1620          *    a reference to an <code>Edit</code> implementation will be returned,
1621          *    otherwise returns <code>null</code>
1622          * @exception BadLocationException  thrown if the area covered by
1623          *   the arguments is not contained in the character sequence
1624          */
1625         public UndoableEdit insertString(int where, String str) throws BadLocationException;
1626 
1627         /**
1628          * Removes some portion of the sequence.
1629          *
1630          * @param where   The offset into the sequence to make the
1631          *   insertion &gt;= 0.
1632          * @param nitems  The number of items in the sequence to remove &gt;= 0.
1633          * @return  If the implementation supports a history mechanism,
1634          *    a reference to an Edit implementation will be returned,
1635          *    otherwise null.
1636          * @exception BadLocationException  Thrown if the area covered by
1637          *   the arguments is not contained in the character sequence.
1638          */
1639         public UndoableEdit remove(int where, int nitems) throws BadLocationException;
1640 
1641         /**
1642          * Fetches a string of characters contained in the sequence.
1643          *
1644          * @param where   Offset into the sequence to fetch &gt;= 0.
1645          * @param len     number of characters to copy &gt;= 0.
1646          * @return the string
1647          * @exception BadLocationException  Thrown if the area covered by
1648          *   the arguments is not contained in the character sequence.
1649          */
1650         public String getString(int where, int len) throws BadLocationException;
1651 
1652         /**
1653          * Gets a sequence of characters and copies them into a Segment.
1654          *
1655          * @param where the starting offset &gt;= 0
1656          * @param len the number of characters &gt;= 0
1657          * @param txt the target location to copy into
1658          * @exception BadLocationException  Thrown if the area covered by
1659          *   the arguments is not contained in the character sequence.
1660          */
1661         public void getChars(int where, int len, Segment txt) throws BadLocationException;
1662     }
1663 
1664     /**
1665      * An interface that can be used to allow MutableAttributeSet
1666      * implementations to use pluggable attribute compression
1667      * techniques.  Each mutation of the attribute set can be
1668      * used to exchange a previous AttributeSet instance with
1669      * another, preserving the possibility of the AttributeSet
1670      * remaining immutable.  An implementation is provided by
1671      * the StyleContext class.
1672      *
1673      * The Element implementations provided by this class use
1674      * this interface to provide their MutableAttributeSet
1675      * implementations, so that different AttributeSet compression
1676      * techniques can be employed.  The method
1677      * <code>getAttributeContext</code> should be implemented to
1678      * return the object responsible for implementing the desired
1679      * compression technique.
1680      *
1681      * @see StyleContext
1682      */
1683     public interface AttributeContext {
1684 
1685         /**
1686          * Adds an attribute to the given set, and returns
1687          * the new representative set.
1688          *
1689          * @param old the old attribute set
1690          * @param name the non-null attribute name
1691          * @param value the attribute value
1692          * @return the updated attribute set
1693          * @see MutableAttributeSet#addAttribute
1694          */
1695         public AttributeSet addAttribute(AttributeSet old, Object name, Object value);
1696 
1697         /**
1698          * Adds a set of attributes to the element.
1699          *
1700          * @param old the old attribute set
1701          * @param attr the attributes to add
1702          * @return the updated attribute set
1703          * @see MutableAttributeSet#addAttribute
1704          */
1705         public AttributeSet addAttributes(AttributeSet old, AttributeSet attr);
1706 
1707         /**
1708          * Removes an attribute from the set.
1709          *
1710          * @param old the old attribute set
1711          * @param name the non-null attribute name
1712          * @return the updated attribute set
1713          * @see MutableAttributeSet#removeAttribute
1714          */
1715         public AttributeSet removeAttribute(AttributeSet old, Object name);
1716 
1717         /**
1718          * Removes a set of attributes for the element.
1719          *
1720          * @param old the old attribute set
1721          * @param names the attribute names
1722          * @return the updated attribute set
1723          * @see MutableAttributeSet#removeAttributes
1724          */
1725         public AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names);
1726 
1727         /**
1728          * Removes a set of attributes for the element.
1729          *
1730          * @param old the old attribute set
1731          * @param attrs the attributes
1732          * @return the updated attribute set
1733          * @see MutableAttributeSet#removeAttributes
1734          */
1735         public AttributeSet removeAttributes(AttributeSet old, AttributeSet attrs);
1736 
1737         /**
1738          * Fetches an empty AttributeSet.
1739          *
1740          * @return the attribute set
1741          */
1742         public AttributeSet getEmptySet();
1743 
1744         /**
1745          * Reclaims an attribute set.
1746          * This is a way for a MutableAttributeSet to mark that it no
1747          * longer need a particular immutable set.  This is only necessary
1748          * in 1.1 where there are no weak references.  A 1.1 implementation
1749          * would call this in its finalize method.
1750          *
1751          * @param a the attribute set to reclaim
1752          */
1753         public void reclaim(AttributeSet a);
1754     }
1755 
1756     /**
1757      * Implements the abstract part of an element.  By default elements
1758      * support attributes by having a field that represents the immutable
1759      * part of the current attribute set for the element.  The element itself
1760      * implements MutableAttributeSet which can be used to modify the set
1761      * by fetching a new immutable set.  The immutable sets are provided
1762      * by the AttributeContext associated with the document.
1763      * <p>
1764      * <strong>Warning:</strong>
1765      * Serialized objects of this class will not be compatible with
1766      * future Swing releases. The current serialization support is
1767      * appropriate for short term storage or RMI between applications running
1768      * the same version of Swing.  As of 1.4, support for long term storage
1769      * of all JavaBeans&trade;
1770      * has been added to the <code>java.beans</code> package.
1771      * Please see {@link java.beans.XMLEncoder}.
1772      */
1773     @SuppressWarnings("serial") // Same-version serialization only
1774     public abstract class AbstractElement implements Element, MutableAttributeSet, Serializable, TreeNode {
1775 
1776         /**
1777          * Creates a new AbstractElement.
1778          *
1779          * @param parent the parent element
1780          * @param a the attributes for the element
1781          * @since 1.4
1782          */
1783         public AbstractElement(Element parent, AttributeSet a) {
1784             this.parent = parent;
1785             attributes = getAttributeContext().getEmptySet();
1786             if (a != null) {
1787                 addAttributes(a);
1788             }
1789         }
1790 
1791         private final void indent(PrintWriter out, int n) {
1792             for (int i = 0; i < n; i++) {
1793                 out.print("  ");
1794             }
1795         }
1796 
1797         /**
1798          * Dumps a debugging representation of the element hierarchy.
1799          *
1800          * @param psOut the output stream
1801          * @param indentAmount the indentation level &gt;= 0
1802          */
1803         public void dump(PrintStream psOut, int indentAmount) {
1804             PrintWriter out;
1805             try {
1806                 out = new PrintWriter(new OutputStreamWriter(psOut,"JavaEsc"),
1807                                       true);
1808             } catch (UnsupportedEncodingException e){
1809                 out = new PrintWriter(psOut,true);
1810             }
1811             indent(out, indentAmount);
1812             if (getName() == null) {
1813                 out.print("<??");
1814             } else {
1815                 out.print("<" + getName());
1816             }
1817             if (getAttributeCount() > 0) {
1818                 out.println("");
1819                 // dump the attributes
1820                 Enumeration<?> names = attributes.getAttributeNames();
1821                 while (names.hasMoreElements()) {
1822                     Object name = names.nextElement();
1823                     indent(out, indentAmount + 1);
1824                     out.println(name + "=" + getAttribute(name));
1825                 }
1826                 indent(out, indentAmount);
1827             }
1828             out.println(">");
1829 
1830             if (isLeaf()) {
1831                 indent(out, indentAmount+1);
1832                 out.print("[" + getStartOffset() + "," + getEndOffset() + "]");
1833                 Content c = getContent();
1834                 try {
1835                     String contentStr = c.getString(getStartOffset(),
1836                                                     getEndOffset() - getStartOffset())/*.trim()*/;
1837                     if (contentStr.length() > 40) {
1838                         contentStr = contentStr.substring(0, 40) + "...";
1839                     }
1840                     out.println("["+contentStr+"]");
1841                 } catch (BadLocationException e) {
1842                 }
1843 
1844             } else {
1845                 int n = getElementCount();
1846                 for (int i = 0; i < n; i++) {
1847                     AbstractElement e = (AbstractElement) getElement(i);
1848                     e.dump(psOut, indentAmount+1);
1849                 }
1850             }
1851         }
1852 
1853         // --- AttributeSet ----------------------------
1854         // delegated to the immutable field "attributes"
1855 
1856         /**
1857          * Gets the number of attributes that are defined.
1858          *
1859          * @return the number of attributes &gt;= 0
1860          * @see AttributeSet#getAttributeCount
1861          */
1862         public int getAttributeCount() {
1863             return attributes.getAttributeCount();
1864         }
1865 
1866         /**
1867          * Checks whether a given attribute is defined.
1868          *
1869          * @param attrName the non-null attribute name
1870          * @return true if the attribute is defined
1871          * @see AttributeSet#isDefined
1872          */
1873         public boolean isDefined(Object attrName) {
1874             return attributes.isDefined(attrName);
1875         }
1876 
1877         /**
1878          * Checks whether two attribute sets are equal.
1879          *
1880          * @param attr the attribute set to check against
1881          * @return true if the same
1882          * @see AttributeSet#isEqual
1883          */
1884         public boolean isEqual(AttributeSet attr) {
1885             return attributes.isEqual(attr);
1886         }
1887 
1888         /**
1889          * Copies a set of attributes.
1890          *
1891          * @return the copy
1892          * @see AttributeSet#copyAttributes
1893          */
1894         public AttributeSet copyAttributes() {
1895             return attributes.copyAttributes();
1896         }
1897 
1898         /**
1899          * Gets the value of an attribute.
1900          *
1901          * @param attrName the non-null attribute name
1902          * @return the attribute value
1903          * @see AttributeSet#getAttribute
1904          */
1905         public Object getAttribute(Object attrName) {
1906             Object value = attributes.getAttribute(attrName);
1907             if (value == null) {
1908                 // The delegate nor it's resolvers had a match,
1909                 // so we'll try to resolve through the parent
1910                 // element.
1911                 AttributeSet a = (parent != null) ? parent.getAttributes() : null;
1912                 if (a != null) {
1913                     value = a.getAttribute(attrName);
1914                 }
1915             }
1916             return value;
1917         }
1918 
1919         /**
1920          * Gets the names of all attributes.
1921          *
1922          * @return the attribute names as an enumeration
1923          * @see AttributeSet#getAttributeNames
1924          */
1925         public Enumeration<?> getAttributeNames() {
1926             return attributes.getAttributeNames();
1927         }
1928 
1929         /**
1930          * Checks whether a given attribute name/value is defined.
1931          *
1932          * @param name the non-null attribute name
1933          * @param value the attribute value
1934          * @return true if the name/value is defined
1935          * @see AttributeSet#containsAttribute
1936          */
1937         public boolean containsAttribute(Object name, Object value) {
1938             return attributes.containsAttribute(name, value);
1939         }
1940 
1941 
1942         /**
1943          * Checks whether the element contains all the attributes.
1944          *
1945          * @param attrs the attributes to check
1946          * @return true if the element contains all the attributes
1947          * @see AttributeSet#containsAttributes
1948          */
1949         public boolean containsAttributes(AttributeSet attrs) {
1950             return attributes.containsAttributes(attrs);
1951         }
1952 
1953         /**
1954          * Gets the resolving parent.
1955          * If not overridden, the resolving parent defaults to
1956          * the parent element.
1957          *
1958          * @return the attributes from the parent, <code>null</code> if none
1959          * @see AttributeSet#getResolveParent
1960          */
1961         public AttributeSet getResolveParent() {
1962             AttributeSet a = attributes.getResolveParent();
1963             if ((a == null) && (parent != null)) {
1964                 a = parent.getAttributes();
1965             }
1966             return a;
1967         }
1968 
1969         // --- MutableAttributeSet ----------------------------------
1970         // should fetch a new immutable record for the field
1971         // "attributes".
1972 
1973         /**
1974          * Adds an attribute to the element.
1975          *
1976          * @param name the non-null attribute name
1977          * @param value the attribute value
1978          * @see MutableAttributeSet#addAttribute
1979          */
1980         public void addAttribute(Object name, Object value) {
1981             checkForIllegalCast();
1982             AttributeContext context = getAttributeContext();
1983             attributes = context.addAttribute(attributes, name, value);
1984         }
1985 
1986         /**
1987          * Adds a set of attributes to the element.
1988          *
1989          * @param attr the attributes to add
1990          * @see MutableAttributeSet#addAttribute
1991          */
1992         public void addAttributes(AttributeSet attr) {
1993             checkForIllegalCast();
1994             AttributeContext context = getAttributeContext();
1995             attributes = context.addAttributes(attributes, attr);
1996         }
1997 
1998         /**
1999          * Removes an attribute from the set.
2000          *
2001          * @param name the non-null attribute name
2002          * @see MutableAttributeSet#removeAttribute
2003          */
2004         public void removeAttribute(Object name) {
2005             checkForIllegalCast();
2006             AttributeContext context = getAttributeContext();
2007             attributes = context.removeAttribute(attributes, name);
2008         }
2009 
2010         /**
2011          * Removes a set of attributes for the element.
2012          *
2013          * @param names the attribute names
2014          * @see MutableAttributeSet#removeAttributes
2015          */
2016         public void removeAttributes(Enumeration<?> names) {
2017             checkForIllegalCast();
2018             AttributeContext context = getAttributeContext();
2019             attributes = context.removeAttributes(attributes, names);
2020         }
2021 
2022         /**
2023          * Removes a set of attributes for the element.
2024          *
2025          * @param attrs the attributes
2026          * @see MutableAttributeSet#removeAttributes
2027          */
2028         public void removeAttributes(AttributeSet attrs) {
2029             checkForIllegalCast();
2030             AttributeContext context = getAttributeContext();
2031             if (attrs == this) {
2032                 attributes = context.getEmptySet();
2033             } else {
2034                 attributes = context.removeAttributes(attributes, attrs);
2035             }
2036         }
2037 
2038         /**
2039          * Sets the resolving parent.
2040          *
2041          * @param parent the parent, null if none
2042          * @see MutableAttributeSet#setResolveParent
2043          */
2044         public void setResolveParent(AttributeSet parent) {
2045             checkForIllegalCast();
2046             AttributeContext context = getAttributeContext();
2047             if (parent != null) {
2048                 attributes =
2049                     context.addAttribute(attributes, StyleConstants.ResolveAttribute,
2050                                          parent);
2051             } else {
2052                 attributes =
2053                     context.removeAttribute(attributes, StyleConstants.ResolveAttribute);
2054             }
2055         }
2056 
2057         private final void checkForIllegalCast() {
2058             Thread t = getCurrentWriter();
2059             if ((t == null) || (t != Thread.currentThread())) {
2060                 throw new StateInvariantError("Illegal cast to MutableAttributeSet");
2061             }
2062         }
2063 
2064         // --- Element methods -------------------------------------
2065 
2066         /**
2067          * Retrieves the underlying model.
2068          *
2069          * @return the model
2070          */
2071         public Document getDocument() {
2072             return AbstractDocument.this;
2073         }
2074 
2075         /**
2076          * Gets the parent of the element.
2077          *
2078          * @return the parent
2079          */
2080         public Element getParentElement() {
2081             return parent;
2082         }
2083 
2084         /**
2085          * Gets the attributes for the element.
2086          *
2087          * @return the attribute set
2088          */
2089         public AttributeSet getAttributes() {
2090             return this;
2091         }
2092 
2093         /**
2094          * Gets the name of the element.
2095          *
2096          * @return the name, null if none
2097          */
2098         public String getName() {
2099             if (attributes.isDefined(ElementNameAttribute)) {
2100                 return (String) attributes.getAttribute(ElementNameAttribute);
2101             }
2102             return null;
2103         }
2104 
2105         /**
2106          * Gets the starting offset in the model for the element.
2107          *
2108          * @return the offset &gt;= 0
2109          */
2110         public abstract int getStartOffset();
2111 
2112         /**
2113          * Gets the ending offset in the model for the element.
2114          *
2115          * @return the offset &gt;= 0
2116          */
2117         public abstract int getEndOffset();
2118 
2119         /**
2120          * Gets a child element.
2121          *
2122          * @param index the child index, &gt;= 0 &amp;&amp; &lt; getElementCount()
2123          * @return the child element
2124          */
2125         public abstract Element getElement(int index);
2126 
2127         /**
2128          * Gets the number of children for the element.
2129          *
2130          * @return the number of children &gt;= 0
2131          */
2132         public abstract int getElementCount();
2133 
2134         /**
2135          * Gets the child element index closest to the given model offset.
2136          *
2137          * @param offset the offset &gt;= 0
2138          * @return the element index &gt;= 0
2139          */
2140         public abstract int getElementIndex(int offset);
2141 
2142         /**
2143          * Checks whether the element is a leaf.
2144          *
2145          * @return true if a leaf
2146          */
2147         public abstract boolean isLeaf();
2148 
2149         // --- TreeNode methods -------------------------------------
2150 
2151         /**
2152          * Returns the child <code>TreeNode</code> at index
2153          * <code>childIndex</code>.
2154          */
2155         public TreeNode getChildAt(int childIndex) {
2156             return (TreeNode)getElement(childIndex);
2157         }
2158 
2159         /**
2160          * Returns the number of children <code>TreeNode</code>'s
2161          * receiver contains.
2162          * @return the number of children <code>TreeNodews</code>'s
2163          * receiver contains
2164          */
2165         public int getChildCount() {
2166             return getElementCount();
2167         }
2168 
2169         /**
2170          * Returns the parent <code>TreeNode</code> of the receiver.
2171          * @return the parent <code>TreeNode</code> of the receiver
2172          */
2173         public TreeNode getParent() {
2174             return (TreeNode)getParentElement();
2175         }
2176 
2177         /**
2178          * Returns the index of <code>node</code> in the receivers children.
2179          * If the receiver does not contain <code>node</code>, -1 will be
2180          * returned.
2181          * @param node the location of interest
2182          * @return the index of <code>node</code> in the receiver's
2183          * children, or -1 if absent
2184          */
2185         public int getIndex(TreeNode node) {
2186             for(int counter = getChildCount() - 1; counter >= 0; counter--)
2187                 if(getChildAt(counter) == node)
2188                     return counter;
2189             return -1;
2190         }
2191 
2192         /**
2193          * Returns true if the receiver allows children.
2194          * @return true if the receiver allows children, otherwise false
2195          */
2196         public abstract boolean getAllowsChildren();
2197 
2198 
2199         /**
2200          * Returns the children of the receiver as an
2201          * <code>Enumeration</code>.
2202          * @return the children of the receiver as an <code>Enumeration</code>
2203          */
2204         public abstract Enumeration<TreeNode> children();
2205 
2206 
2207         // --- serialization ---------------------------------------------
2208 
2209         private void writeObject(ObjectOutputStream s) throws IOException {
2210             s.defaultWriteObject();
2211             StyleContext.writeAttributeSet(s, attributes);
2212         }
2213 
2214         private void readObject(ObjectInputStream s)
2215             throws ClassNotFoundException, IOException
2216         {
2217             s.defaultReadObject();
2218             MutableAttributeSet attr = new SimpleAttributeSet();
2219             StyleContext.readAttributeSet(s, attr);
2220             AttributeContext context = getAttributeContext();
2221             attributes = context.addAttributes(SimpleAttributeSet.EMPTY, attr);
2222         }
2223 
2224         // ---- variables -----------------------------------------------------
2225 
2226         private Element parent;
2227         private transient AttributeSet attributes;
2228 
2229     }
2230 
2231     /**
2232      * Implements a composite element that contains other elements.
2233      * <p>
2234      * <strong>Warning:</strong>
2235      * Serialized objects of this class will not be compatible with
2236      * future Swing releases. The current serialization support is
2237      * appropriate for short term storage or RMI between applications running
2238      * the same version of Swing.  As of 1.4, support for long term storage
2239      * of all JavaBeans&trade;
2240      * has been added to the <code>java.beans</code> package.
2241      * Please see {@link java.beans.XMLEncoder}.
2242      */
2243     @SuppressWarnings("serial") // Same-version serialization only
2244     public class BranchElement extends AbstractElement {
2245 
2246         /**
2247          * Constructs a composite element that initially contains
2248          * no children.
2249          *
2250          * @param parent  The parent element
2251          * @param a the attributes for the element
2252          * @since 1.4
2253          */
2254         public BranchElement(Element parent, AttributeSet a) {
2255             super(parent, a);
2256             children = new AbstractElement[1];
2257             nchildren = 0;
2258             lastIndex = -1;
2259         }
2260 
2261         /**
2262          * Gets the child element that contains
2263          * the given model position.
2264          *
2265          * @param pos the position &gt;= 0
2266          * @return the element, null if none
2267          */
2268         public Element positionToElement(int pos) {
2269             int index = getElementIndex(pos);
2270             Element child = children[index];
2271             int p0 = child.getStartOffset();
2272             int p1 = child.getEndOffset();
2273             if ((pos >= p0) && (pos < p1)) {
2274                 return child;
2275             }
2276             return null;
2277         }
2278 
2279         /**
2280          * Replaces content with a new set of elements.
2281          *
2282          * @param offset the starting offset &gt;= 0
2283          * @param length the length to replace &gt;= 0
2284          * @param elems the new elements
2285          */
2286         public void replace(int offset, int length, Element[] elems) {
2287             int delta = elems.length - length;
2288             int src = offset + length;
2289             int nmove = nchildren - src;
2290             int dest = src + delta;
2291             if ((nchildren + delta) >= children.length) {
2292                 // need to grow the array
2293                 int newLength = Math.max(2*children.length, nchildren + delta);
2294                 AbstractElement[] newChildren = new AbstractElement[newLength];
2295                 System.arraycopy(children, 0, newChildren, 0, offset);
2296                 System.arraycopy(elems, 0, newChildren, offset, elems.length);
2297                 System.arraycopy(children, src, newChildren, dest, nmove);
2298                 children = newChildren;
2299             } else {
2300                 // patch the existing array
2301                 System.arraycopy(children, src, children, dest, nmove);
2302                 System.arraycopy(elems, 0, children, offset, elems.length);
2303             }
2304             nchildren = nchildren + delta;
2305         }
2306 
2307         /**
2308          * Converts the element to a string.
2309          *
2310          * @return the string
2311          */
2312         public String toString() {
2313             return "BranchElement(" + getName() + ") " + getStartOffset() + "," +
2314                 getEndOffset() + "\n";
2315         }
2316 
2317         // --- Element methods -----------------------------------
2318 
2319         /**
2320          * Gets the element name.
2321          *
2322          * @return the element name
2323          */
2324         public String getName() {
2325             String nm = super.getName();
2326             if (nm == null) {
2327                 nm = ParagraphElementName;
2328             }
2329             return nm;
2330         }
2331 
2332         /**
2333          * Gets the starting offset in the model for the element.
2334          *
2335          * @return the offset &gt;= 0
2336          */
2337         public int getStartOffset() {
2338             return children[0].getStartOffset();
2339         }
2340 
2341         /**
2342          * Gets the ending offset in the model for the element.
2343          * @throws NullPointerException if this element has no children
2344          *
2345          * @return the offset &gt;= 0
2346          */
2347         public int getEndOffset() {
2348             Element child =
2349                 (nchildren > 0) ? children[nchildren - 1] : children[0];
2350             return child.getEndOffset();
2351         }
2352 
2353         /**
2354          * Gets a child element.
2355          *
2356          * @param index the child index, &gt;= 0 &amp;&amp; &lt; getElementCount()
2357          * @return the child element, null if none
2358          */
2359         public Element getElement(int index) {
2360             if (index < nchildren) {
2361                 return children[index];
2362             }
2363             return null;
2364         }
2365 
2366         /**
2367          * Gets the number of children for the element.
2368          *
2369          * @return the number of children &gt;= 0
2370          */
2371         public int getElementCount()  {
2372             return nchildren;
2373         }
2374 
2375         /**
2376          * Gets the child element index closest to the given model offset.
2377          *
2378          * @param offset the offset &gt;= 0
2379          * @return the element index &gt;= 0
2380          */
2381         public int getElementIndex(int offset) {
2382             int index;
2383             int lower = 0;
2384             int upper = nchildren - 1;
2385             int mid = 0;
2386             int p0 = getStartOffset();
2387             int p1;
2388 
2389             if (nchildren == 0) {
2390                 return 0;
2391             }
2392             if (offset >= getEndOffset()) {
2393                 return nchildren - 1;
2394             }
2395 
2396             // see if the last index can be used.
2397             if ((lastIndex >= lower) && (lastIndex <= upper)) {
2398                 Element lastHit = children[lastIndex];
2399                 p0 = lastHit.getStartOffset();
2400                 p1 = lastHit.getEndOffset();
2401                 if ((offset >= p0) && (offset < p1)) {
2402                     return lastIndex;
2403                 }
2404 
2405                 // last index wasn't a hit, but it does give useful info about
2406                 // where a hit (if any) would be.
2407                 if (offset < p0) {
2408                     upper = lastIndex;
2409                 } else  {
2410                     lower = lastIndex;
2411                 }
2412             }
2413 
2414             while (lower <= upper) {
2415                 mid = lower + ((upper - lower) / 2);
2416                 Element elem = children[mid];
2417                 p0 = elem.getStartOffset();
2418                 p1 = elem.getEndOffset();
2419                 if ((offset >= p0) && (offset < p1)) {
2420                     // found the location
2421                     index = mid;
2422                     lastIndex = index;
2423                     return index;
2424                 } else if (offset < p0) {
2425                     upper = mid - 1;
2426                 } else {
2427                     lower = mid + 1;
2428                 }
2429             }
2430 
2431             // didn't find it, but we indicate the index of where it would belong
2432             if (offset < p0) {
2433                 index = mid;
2434             } else {
2435                 index = mid + 1;
2436             }
2437             lastIndex = index;
2438             return index;
2439         }
2440 
2441         /**
2442          * Checks whether the element is a leaf.
2443          *
2444          * @return true if a leaf
2445          */
2446         public boolean isLeaf() {
2447             return false;
2448         }
2449 
2450 
2451         // ------ TreeNode ----------------------------------------------
2452 
2453         /**
2454          * Returns true if the receiver allows children.
2455          * @return true if the receiver allows children, otherwise false
2456          */
2457         public boolean getAllowsChildren() {
2458             return true;
2459         }
2460 
2461 
2462         /**
2463          * Returns the children of the receiver as an
2464          * <code>Enumeration</code>.
2465          * @return the children of the receiver
2466          */
2467         public Enumeration<TreeNode> children() {
2468             if(nchildren == 0)
2469                 return null;
2470 
2471             Vector<TreeNode> tempVector = new Vector<>(nchildren);
2472 
2473             for(int counter = 0; counter < nchildren; counter++)
2474                 tempVector.addElement(children[counter]);
2475             return tempVector.elements();
2476         }
2477 
2478         // ------ members ----------------------------------------------
2479 
2480         private AbstractElement[] children;
2481         private int nchildren;
2482         private int lastIndex;
2483     }
2484 
2485     /**
2486      * Implements an element that directly represents content of
2487      * some kind.
2488      * <p>
2489      * <strong>Warning:</strong>
2490      * Serialized objects of this class will not be compatible with
2491      * future Swing releases. The current serialization support is
2492      * appropriate for short term storage or RMI between applications running
2493      * the same version of Swing.  As of 1.4, support for long term storage
2494      * of all JavaBeans&trade;
2495      * has been added to the <code>java.beans</code> package.
2496      * Please see {@link java.beans.XMLEncoder}.
2497      *
2498      * @see     Element
2499      */
2500     @SuppressWarnings("serial") // Same-version serialization only
2501     public class LeafElement extends AbstractElement {
2502 
2503         /**
2504          * Constructs an element that represents content within the
2505          * document (has no children).
2506          *
2507          * @param parent  The parent element
2508          * @param a       The element attributes
2509          * @param offs0   The start offset &gt;= 0
2510          * @param offs1   The end offset &gt;= offs0
2511          * @since 1.4
2512          */
2513         public LeafElement(Element parent, AttributeSet a, int offs0, int offs1) {
2514             super(parent, a);
2515             try {
2516                 p0 = createPosition(offs0);
2517                 p1 = createPosition(offs1);
2518             } catch (BadLocationException e) {
2519                 p0 = null;
2520                 p1 = null;
2521                 throw new StateInvariantError("Can't create Position references");
2522             }
2523         }
2524 
2525         /**
2526          * Converts the element to a string.
2527          *
2528          * @return the string
2529          */
2530         public String toString() {
2531             return "LeafElement(" + getName() + ") " + p0 + "," + p1 + "\n";
2532         }
2533 
2534         // --- Element methods ---------------------------------------------
2535 
2536         /**
2537          * Gets the starting offset in the model for the element.
2538          *
2539          * @return the offset &gt;= 0
2540          */
2541         public int getStartOffset() {
2542             return p0.getOffset();
2543         }
2544 
2545         /**
2546          * Gets the ending offset in the model for the element.
2547          *
2548          * @return the offset &gt;= 0
2549          */
2550         public int getEndOffset() {
2551             return p1.getOffset();
2552         }
2553 
2554         /**
2555          * Gets the element name.
2556          *
2557          * @return the name
2558          */
2559         public String getName() {
2560             String nm = super.getName();
2561             if (nm == null) {
2562                 nm = ContentElementName;
2563             }
2564             return nm;
2565         }
2566 
2567         /**
2568          * Gets the child element index closest to the given model offset.
2569          *
2570          * @param pos the offset &gt;= 0
2571          * @return the element index &gt;= 0
2572          */
2573         public int getElementIndex(int pos) {
2574             return -1;
2575         }
2576 
2577         /**
2578          * Gets a child element.
2579          *
2580          * @param index the child index, &gt;= 0 &amp;&amp; &lt; getElementCount()
2581          * @return the child element
2582          */
2583         public Element getElement(int index) {
2584             return null;
2585         }
2586 
2587         /**
2588          * Returns the number of child elements.
2589          *
2590          * @return the number of children &gt;= 0
2591          */
2592         public int getElementCount()  {
2593             return 0;
2594         }
2595 
2596         /**
2597          * Checks whether the element is a leaf.
2598          *
2599          * @return true if a leaf
2600          */
2601         public boolean isLeaf() {
2602             return true;
2603         }
2604 
2605         // ------ TreeNode ----------------------------------------------
2606 
2607         /**
2608          * Returns true if the receiver allows children.
2609          * @return true if the receiver allows children, otherwise false
2610          */
2611         public boolean getAllowsChildren() {
2612             return false;
2613         }
2614 
2615 
2616         /**
2617          * Returns the children of the receiver as an
2618          * <code>Enumeration</code>.
2619          * @return the children of the receiver
2620          */
2621         @Override
2622         public Enumeration<TreeNode> children() {
2623             return null;
2624         }
2625 
2626         // --- serialization ---------------------------------------------
2627 
2628         private void writeObject(ObjectOutputStream s) throws IOException {
2629             s.defaultWriteObject();
2630             s.writeInt(p0.getOffset());
2631             s.writeInt(p1.getOffset());
2632         }
2633 
2634         private void readObject(ObjectInputStream s)
2635             throws ClassNotFoundException, IOException
2636         {
2637             s.defaultReadObject();
2638 
2639             // set the range with positions that track change
2640             int off0 = s.readInt();
2641             int off1 = s.readInt();
2642             try {
2643                 p0 = createPosition(off0);
2644                 p1 = createPosition(off1);
2645             } catch (BadLocationException e) {
2646                 p0 = null;
2647                 p1 = null;
2648                 throw new IOException("Can't restore Position references");
2649             }
2650         }
2651 
2652         // ---- members -----------------------------------------------------
2653 
2654         private transient Position p0;
2655         private transient Position p1;
2656     }
2657 
2658     /**
2659      * Represents the root element of the bidirectional element structure.
2660      * The root element is the only element in the bidi element structure
2661      * which contains children.
2662      */
2663     class BidiRootElement extends BranchElement {
2664 
2665         BidiRootElement() {
2666             super( null, null );
2667         }
2668 
2669         /**
2670          * Gets the name of the element.
2671          * @return the name
2672          */
2673         public String getName() {
2674             return "bidi root";
2675         }
2676     }
2677 
2678     /**
2679      * Represents an element of the bidirectional element structure.
2680      */
2681     class BidiElement extends LeafElement {
2682 
2683         /**
2684          * Creates a new BidiElement.
2685          */
2686         BidiElement(Element parent, int start, int end, int level) {
2687             super(parent, new SimpleAttributeSet(), start, end);
2688             addAttribute(StyleConstants.BidiLevel, Integer.valueOf(level));
2689             //System.out.println("BidiElement: start = " + start
2690             //                   + " end = " + end + " level = " + level );
2691         }
2692 
2693         /**
2694          * Gets the name of the element.
2695          * @return the name
2696          */
2697         public String getName() {
2698             return BidiElementName;
2699         }
2700 
2701         int getLevel() {
2702             Integer o = (Integer) getAttribute(StyleConstants.BidiLevel);
2703             if (o != null) {
2704                 return o.intValue();
2705             }
2706             return 0;  // Level 0 is base level (non-embedded) left-to-right
2707         }
2708 
2709         boolean isLeftToRight() {
2710             return ((getLevel() % 2) == 0);
2711         }
2712     }
2713 
2714     /**
2715      * Stores document changes as the document is being
2716      * modified.  Can subsequently be used for change notification
2717      * when done with the document modification transaction.
2718      * This is used by the AbstractDocument class and its extensions
2719      * for broadcasting change information to the document listeners.
2720      */
2721     public class DefaultDocumentEvent extends CompoundEdit implements DocumentEvent {
2722 
2723         /**
2724          * Constructs a change record.
2725          *
2726          * @param offs the offset into the document of the change &gt;= 0
2727          * @param len  the length of the change &gt;= 0
2728          * @param type the type of event (DocumentEvent.EventType)
2729          * @since 1.4
2730          */
2731         public DefaultDocumentEvent(int offs, int len, DocumentEvent.EventType type) {
2732             super();
2733             offset = offs;
2734             length = len;
2735             this.type = type;
2736         }
2737 
2738         /**
2739          * Returns a string description of the change event.
2740          *
2741          * @return a string
2742          */
2743         public String toString() {
2744             return edits.toString();
2745         }
2746 
2747         // --- CompoundEdit methods --------------------------
2748 
2749         /**
2750          * Adds a document edit.  If the number of edits crosses
2751          * a threshold, this switches on a hashtable lookup for
2752          * ElementChange implementations since access of these
2753          * needs to be relatively quick.
2754          *
2755          * @param anEdit a document edit record
2756          * @return true if the edit was added
2757          */
2758         public boolean addEdit(UndoableEdit anEdit) {
2759             // if the number of changes gets too great, start using
2760             // a hashtable for to locate the change for a given element.
2761             if ((changeLookup == null) && (edits.size() > 10)) {
2762                 changeLookup = new Hashtable<Element, ElementChange>();
2763                 int n = edits.size();
2764                 for (int i = 0; i < n; i++) {
2765                     Object o = edits.elementAt(i);
2766                     if (o instanceof DocumentEvent.ElementChange) {
2767                         DocumentEvent.ElementChange ec = (DocumentEvent.ElementChange) o;
2768                         changeLookup.put(ec.getElement(), ec);
2769                     }
2770                 }
2771             }
2772 
2773             // if we have a hashtable... add the entry if it's
2774             // an ElementChange.
2775             if ((changeLookup != null) && (anEdit instanceof DocumentEvent.ElementChange)) {
2776                 DocumentEvent.ElementChange ec = (DocumentEvent.ElementChange) anEdit;
2777                 changeLookup.put(ec.getElement(), ec);
2778             }
2779             return super.addEdit(anEdit);
2780         }
2781 
2782         /**
2783          * Redoes a change.
2784          *
2785          * @exception CannotRedoException if the change cannot be redone
2786          */
2787         public void redo() throws CannotRedoException {
2788             writeLock();
2789             try {
2790                 // change the state
2791                 super.redo();
2792                 // fire a DocumentEvent to notify the view(s)
2793                 UndoRedoDocumentEvent ev = new UndoRedoDocumentEvent(this, false);
2794                 if (type == DocumentEvent.EventType.INSERT) {
2795                     fireInsertUpdate(ev);
2796                 } else if (type == DocumentEvent.EventType.REMOVE) {
2797                     fireRemoveUpdate(ev);
2798                 } else {
2799                     fireChangedUpdate(ev);
2800                 }
2801             } finally {
2802                 writeUnlock();
2803             }
2804         }
2805 
2806         /**
2807          * Undoes a change.
2808          *
2809          * @exception CannotUndoException if the change cannot be undone
2810          */
2811         public void undo() throws CannotUndoException {
2812             writeLock();
2813             try {
2814                 // change the state
2815                 super.undo();
2816                 // fire a DocumentEvent to notify the view(s)
2817                 UndoRedoDocumentEvent ev = new UndoRedoDocumentEvent(this, true);
2818                 if (type == DocumentEvent.EventType.REMOVE) {
2819                     fireInsertUpdate(ev);
2820                 } else if (type == DocumentEvent.EventType.INSERT) {
2821                     fireRemoveUpdate(ev);
2822                 } else {
2823                     fireChangedUpdate(ev);
2824                 }
2825             } finally {
2826                 writeUnlock();
2827             }
2828         }
2829 
2830         /**
2831          * DefaultDocument events are significant.  If you wish to aggregate
2832          * DefaultDocumentEvents to present them as a single edit to the user
2833          * place them into a CompoundEdit.
2834          *
2835          * @return whether the event is significant for edit undo purposes
2836          */
2837         public boolean isSignificant() {
2838             return true;
2839         }
2840 
2841 
2842         /**
2843          * Provides a localized, human readable description of this edit
2844          * suitable for use in, say, a change log.
2845          *
2846          * @return the description
2847          */
2848         public String getPresentationName() {
2849             DocumentEvent.EventType type = getType();
2850             if(type == DocumentEvent.EventType.INSERT)
2851                 return UIManager.getString("AbstractDocument.additionText");
2852             if(type == DocumentEvent.EventType.REMOVE)
2853                 return UIManager.getString("AbstractDocument.deletionText");
2854             return UIManager.getString("AbstractDocument.styleChangeText");
2855         }
2856 
2857         /**
2858          * Provides a localized, human readable description of the undoable
2859          * form of this edit, e.g. for use as an Undo menu item. Typically
2860          * derived from getDescription();
2861          *
2862          * @return the description
2863          */
2864         public String getUndoPresentationName() {
2865             return UIManager.getString("AbstractDocument.undoText") + " " +
2866                 getPresentationName();
2867         }
2868 
2869         /**
2870          * Provides a localized, human readable description of the redoable
2871          * form of this edit, e.g. for use as a Redo menu item. Typically
2872          * derived from getPresentationName();
2873          *
2874          * @return the description
2875          */
2876         public String getRedoPresentationName() {
2877             return UIManager.getString("AbstractDocument.redoText") + " " +
2878                 getPresentationName();
2879         }
2880 
2881         // --- DocumentEvent methods --------------------------
2882 
2883         /**
2884          * Returns the type of event.
2885          *
2886          * @return the event type as a DocumentEvent.EventType
2887          * @see DocumentEvent#getType
2888          */
2889         public DocumentEvent.EventType getType() {
2890             return type;
2891         }
2892 
2893         /**
2894          * Returns the offset within the document of the start of the change.
2895          *
2896          * @return the offset &gt;= 0
2897          * @see DocumentEvent#getOffset
2898          */
2899         public int getOffset() {
2900             return offset;
2901         }
2902 
2903         /**
2904          * Returns the length of the change.
2905          *
2906          * @return the length &gt;= 0
2907          * @see DocumentEvent#getLength
2908          */
2909         public int getLength() {
2910             return length;
2911         }
2912 
2913         /**
2914          * Gets the document that sourced the change event.
2915          *
2916          * @return the document
2917          * @see DocumentEvent#getDocument
2918          */
2919         public Document getDocument() {
2920             return AbstractDocument.this;
2921         }
2922 
2923         /**
2924          * Gets the changes for an element.
2925          *
2926          * @param elem the element
2927          * @return the changes
2928          */
2929         public DocumentEvent.ElementChange getChange(Element elem) {
2930             if (changeLookup != null) {
2931                 return changeLookup.get(elem);
2932             }
2933             int n = edits.size();
2934             for (int i = 0; i < n; i++) {
2935                 Object o = edits.elementAt(i);
2936                 if (o instanceof DocumentEvent.ElementChange) {
2937                     DocumentEvent.ElementChange c = (DocumentEvent.ElementChange) o;
2938                     if (elem.equals(c.getElement())) {
2939                         return c;
2940                     }
2941                 }
2942             }
2943             return null;
2944         }
2945 
2946         // --- member variables ------------------------------------
2947 
2948         private int offset;
2949         private int length;
2950         private Hashtable<Element, ElementChange> changeLookup;
2951         private DocumentEvent.EventType type;
2952 
2953     }
2954 
2955     /**
2956      * This event used when firing document changes while Undo/Redo
2957      * operations. It just wraps DefaultDocumentEvent and delegates
2958      * all calls to it except getType() which depends on operation
2959      * (Undo or Redo).
2960      */
2961     class UndoRedoDocumentEvent implements DocumentEvent {
2962         private DefaultDocumentEvent src = null;
2963         private EventType type = null;
2964 
2965         public UndoRedoDocumentEvent(DefaultDocumentEvent src, boolean isUndo) {
2966             this.src = src;
2967             if(isUndo) {
2968                 if(src.getType().equals(EventType.INSERT)) {
2969                     type = EventType.REMOVE;
2970                 } else if(src.getType().equals(EventType.REMOVE)) {
2971                     type = EventType.INSERT;
2972                 } else {
2973                     type = src.getType();
2974                 }
2975             } else {
2976                 type = src.getType();
2977             }
2978         }
2979 
2980         public DefaultDocumentEvent getSource() {
2981             return src;
2982         }
2983 
2984         // DocumentEvent methods delegated to DefaultDocumentEvent source
2985         // except getType() which depends on operation (Undo or Redo).
2986         public int getOffset() {
2987             return src.getOffset();
2988         }
2989 
2990         public int getLength() {
2991             return src.getLength();
2992         }
2993 
2994         public Document getDocument() {
2995             return src.getDocument();
2996         }
2997 
2998         public DocumentEvent.EventType getType() {
2999             return type;
3000         }
3001 
3002         public DocumentEvent.ElementChange getChange(Element elem) {
3003             return src.getChange(elem);
3004         }
3005     }
3006 
3007     /**
3008      * An implementation of ElementChange that can be added to the document
3009      * event.
3010      */
3011     public static class ElementEdit extends AbstractUndoableEdit implements DocumentEvent.ElementChange {
3012 
3013         /**
3014          * Constructs an edit record.  This does not modify the element
3015          * so it can safely be used to <em>catch up</em> a view to the
3016          * current model state for views that just attached to a model.
3017          *
3018          * @param e the element
3019          * @param index the index into the model &gt;= 0
3020          * @param removed a set of elements that were removed
3021          * @param added a set of elements that were added
3022          */
3023         public ElementEdit(Element e, int index, Element[] removed, Element[] added) {
3024             super();
3025             this.e = e;
3026             this.index = index;
3027             this.removed = removed;
3028             this.added = added;
3029         }
3030 
3031         /**
3032          * Returns the underlying element.
3033          *
3034          * @return the element
3035          */
3036         public Element getElement() {
3037             return e;
3038         }
3039 
3040         /**
3041          * Returns the index into the list of elements.
3042          *
3043          * @return the index &gt;= 0
3044          */
3045         public int getIndex() {
3046             return index;
3047         }
3048 
3049         /**
3050          * Gets a list of children that were removed.
3051          *
3052          * @return the list
3053          */
3054         public Element[] getChildrenRemoved() {
3055             return removed;
3056         }
3057 
3058         /**
3059          * Gets a list of children that were added.
3060          *
3061          * @return the list
3062          */
3063         public Element[] getChildrenAdded() {
3064             return added;
3065         }
3066 
3067         /**
3068          * Redoes a change.
3069          *
3070          * @exception CannotRedoException if the change cannot be redone
3071          */
3072         public void redo() throws CannotRedoException {
3073             super.redo();
3074 
3075             // Since this event will be reused, switch around added/removed.
3076             Element[] tmp = removed;
3077             removed = added;
3078             added = tmp;
3079 
3080             // PENDING(prinz) need MutableElement interface, canRedo() should check
3081             ((AbstractDocument.BranchElement)e).replace(index, removed.length, added);
3082         }
3083 
3084         /**
3085          * Undoes a change.
3086          *
3087          * @exception CannotUndoException if the change cannot be undone
3088          */
3089         public void undo() throws CannotUndoException {
3090             super.undo();
3091             // PENDING(prinz) need MutableElement interface, canUndo() should check
3092             ((AbstractDocument.BranchElement)e).replace(index, added.length, removed);
3093 
3094             // Since this event will be reused, switch around added/removed.
3095             Element[] tmp = removed;
3096             removed = added;
3097             added = tmp;
3098         }
3099 
3100         private Element e;
3101         private int index;
3102         private Element[] removed;
3103         private Element[] added;
3104     }
3105 
3106 
3107     private class DefaultFilterBypass extends DocumentFilter.FilterBypass {
3108         public Document getDocument() {
3109             return AbstractDocument.this;
3110         }
3111 
3112         public void remove(int offset, int length) throws
3113             BadLocationException {
3114             handleRemove(offset, length);
3115         }
3116 
3117         public void insertString(int offset, String string,
3118                                  AttributeSet attr) throws
3119                                         BadLocationException {
3120             handleInsertString(offset, string, attr);
3121         }
3122 
3123         public void replace(int offset, int length, String text,
3124                             AttributeSet attrs) throws BadLocationException {
3125             handleRemove(offset, length);
3126             handleInsertString(offset, text, attrs);
3127         }
3128     }
3129 }