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