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