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