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