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