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