Home » openjdk-7 » javax » swing » text » [javadoc | source]

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

Home » openjdk-7 » javax » swing » text » [javadoc | source]