Save This Page
Home » openjdk-7 » javax » swing » text » [javadoc | source]
    1   /*
    2    * Copyright 1997-2006 Sun Microsystems, Inc.  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.  Sun designates this
    8    * particular file as subject to the "Classpath" exception as provided
    9    * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
   22    * CA 95054 USA or visit www.sun.com if you need additional information or
   23    * have any 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               Object o = java.security.AccessController.doPrivileged(
  127                   new java.security.PrivilegedAction() {
  128                       public Object run() {
  129                           return System.getProperty(I18NProperty);
  130                       }
  131                   }
  132               );
  133               if (o != null) {
  134                   defaultI18NProperty = Boolean.valueOf((String)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(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 (DocumentListener[])listenerList.getListeners(
  471                   DocumentListener.class);
  472       }
  473   
  474       /**
  475        * Adds an undo listener for notification of any changes.
  476        * Undo/Redo operations performed on the <code>UndoableEdit</code>
  477        * will cause the appropriate DocumentEvent to be fired to keep
  478        * the view(s) in sync with the model.
  479        *
  480        * @param listener the <code>UndoableEditListener</code> to add
  481        * @see Document#addUndoableEditListener
  482        */
  483       public void addUndoableEditListener(UndoableEditListener listener) {
  484           listenerList.add(UndoableEditListener.class, listener);
  485       }
  486   
  487       /**
  488        * Removes an undo listener.
  489        *
  490        * @param listener the <code>UndoableEditListener</code> to remove
  491        * @see Document#removeDocumentListener
  492        */
  493       public void removeUndoableEditListener(UndoableEditListener listener) {
  494           listenerList.remove(UndoableEditListener.class, listener);
  495       }
  496   
  497       /**
  498        * Returns an array of all the undoable edit listeners
  499        * registered on this document.
  500        *
  501        * @return all of this document's <code>UndoableEditListener</code>s
  502        *         or an empty array if no undoable edit listeners are
  503        *         currently registered
  504        *
  505        * @see #addUndoableEditListener
  506        * @see #removeUndoableEditListener
  507        *
  508        * @since 1.4
  509        */
  510       public UndoableEditListener[] getUndoableEditListeners() {
  511           return (UndoableEditListener[])listenerList.getListeners(
  512                   UndoableEditListener.class);
  513       }
  514   
  515       /**
  516        * A convenience method for looking up a property value. It is
  517        * equivalent to:
  518        * <pre>
  519        * getDocumentProperties().get(key);
  520        * </pre>
  521        *
  522        * @param key the non-<code>null</code> property key
  523        * @return the value of this property or <code>null</code>
  524        * @see #getDocumentProperties
  525        */
  526       public final Object getProperty(Object key) {
  527           return getDocumentProperties().get(key);
  528       }
  529   
  530   
  531       /**
  532        * A convenience method for storing up a property value.  It is
  533        * equivalent to:
  534        * <pre>
  535        * getDocumentProperties().put(key, value);
  536        * </pre>
  537        * If <code>value</code> is <code>null</code> this method will
  538        * remove the property.
  539        *
  540        * @param key the non-<code>null</code> key
  541        * @param value the property value
  542        * @see #getDocumentProperties
  543        */
  544       public final void putProperty(Object key, Object value) {
  545           if (value != null) {
  546               getDocumentProperties().put(key, value);
  547           } else {
  548               getDocumentProperties().remove(key);
  549           }
  550           if( key == TextAttribute.RUN_DIRECTION
  551               && Boolean.TRUE.equals(getProperty(I18NProperty)) )
  552           {
  553               //REMIND - this needs to flip on the i18n property if run dir
  554               //is rtl and the i18n property is not already on.
  555               writeLock();
  556               try {
  557                   DefaultDocumentEvent e
  558                       = new DefaultDocumentEvent(0, getLength(),
  559                                                  DocumentEvent.EventType.INSERT);
  560                   updateBidi( e );
  561               } finally {
  562                   writeUnlock();
  563               }
  564           }
  565       }
  566   
  567       /**
  568        * Removes some content from the document.
  569        * Removing content causes a write lock to be held while the
  570        * actual changes are taking place.  Observers are notified
  571        * of the change on the thread that called this method.
  572        * <p>
  573        * This method is thread safe, although most Swing methods
  574        * are not. Please see
  575        * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
  576        * to Use Threads</A> for more information.
  577        *
  578        * @param offs the starting offset >= 0
  579        * @param len the number of characters to remove >= 0
  580        * @exception BadLocationException  the given remove position is not a valid
  581        *   position within the document
  582        * @see Document#remove
  583        */
  584       public void remove(int offs, int len) throws BadLocationException {
  585           DocumentFilter filter = getDocumentFilter();
  586   
  587           writeLock();
  588           try {
  589               if (filter != null) {
  590                   filter.remove(getFilterBypass(), offs, len);
  591               }
  592               else {
  593                   handleRemove(offs, len);
  594               }
  595           } finally {
  596               writeUnlock();
  597           }
  598       }
  599   
  600       /**
  601        * Performs the actual work of the remove. It is assumed the caller
  602        * will have obtained a <code>writeLock</code> before invoking this.
  603        */
  604       void handleRemove(int offs, int len) throws BadLocationException {
  605           if (len > 0) {
  606               if (offs < 0 || (offs + len) > getLength()) {
  607                   throw new BadLocationException("Invalid remove",
  608                                                  getLength() + 1);
  609               }
  610               DefaultDocumentEvent chng =
  611                       new DefaultDocumentEvent(offs, len, DocumentEvent.EventType.REMOVE);
  612   
  613               boolean isComposedTextElement = false;
  614               // Check whether the position of interest is the composed text
  615               isComposedTextElement = Utilities.isComposedTextElement(this, offs);
  616   
  617               removeUpdate(chng);
  618               UndoableEdit u = data.remove(offs, len);
  619               if (u != null) {
  620                   chng.addEdit(u);
  621               }
  622               postRemoveUpdate(chng);
  623               // Mark the edit as done.
  624               chng.end();
  625               fireRemoveUpdate(chng);
  626               // only fire undo if Content implementation supports it
  627               // undo for the composed text is not supported for now
  628               if ((u != null) && !isComposedTextElement) {
  629                   fireUndoableEditUpdate(new UndoableEditEvent(this, chng));
  630               }
  631           }
  632       }
  633   
  634       /**
  635        * Deletes the region of text from <code>offset</code> to
  636        * <code>offset + length</code>, and replaces it with <code>text</code>.
  637        * It is up to the implementation as to how this is implemented, some
  638        * implementations may treat this as two distinct operations: a remove
  639        * followed by an insert, others may treat the replace as one atomic
  640        * operation.
  641        *
  642        * @param offset index of child element
  643        * @param length length of text to delete, may be 0 indicating don't
  644        *               delete anything
  645        * @param text text to insert, <code>null</code> indicates no text to insert
  646        * @param attrs AttributeSet indicating attributes of inserted text,
  647        *              <code>null</code>
  648        *              is legal, and typically treated as an empty attributeset,
  649        *              but exact interpretation is left to the subclass
  650        * @exception BadLocationException the given position is not a valid
  651        *            position within the document
  652        * @since 1.4
  653        */
  654       public void replace(int offset, int length, String text,
  655                           AttributeSet attrs) throws BadLocationException {
  656           if (length == 0 && (text == null || text.length() == 0)) {
  657               return;
  658           }
  659           DocumentFilter filter = getDocumentFilter();
  660   
  661           writeLock();
  662           try {
  663               if (filter != null) {
  664                   filter.replace(getFilterBypass(), offset, length, text,
  665                                  attrs);
  666               }
  667               else {
  668                   if (length > 0) {
  669                       remove(offset, length);
  670                   }
  671                   if (text != null && text.length() > 0) {
  672                       insertString(offset, text, attrs);
  673                   }
  674               }
  675           } finally {
  676               writeUnlock();
  677           }
  678       }
  679   
  680       /**
  681        * Inserts some content into the document.
  682        * Inserting content causes a write lock to be held while the
  683        * actual changes are taking place, followed by notification
  684        * to the observers on the thread that grabbed the write lock.
  685        * <p>
  686        * This method is thread safe, although most Swing methods
  687        * are not. Please see
  688        * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
  689        * to Use Threads</A> for more information.
  690        *
  691        * @param offs the starting offset >= 0
  692        * @param str the string to insert; does nothing with null/empty strings
  693        * @param a the attributes for the inserted content
  694        * @exception BadLocationException  the given insert position is not a valid
  695        *   position within the document
  696        * @see Document#insertString
  697        */
  698       public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
  699           if ((str == null) || (str.length() == 0)) {
  700               return;
  701           }
  702           DocumentFilter filter = getDocumentFilter();
  703   
  704           writeLock();
  705           try {
  706               if (filter != null) {
  707                   filter.insertString(getFilterBypass(), offs, str, a);
  708               }
  709               else {
  710                   handleInsertString(offs, str, a);
  711               }
  712           } finally {
  713               writeUnlock();
  714           }
  715       }
  716   
  717       /**
  718        * Performs the actual work of inserting the text; it is assumed the
  719        * caller has obtained a write lock before invoking this.
  720        */
  721       void handleInsertString(int offs, String str, AttributeSet a)
  722                            throws BadLocationException {
  723           if ((str == null) || (str.length() == 0)) {
  724               return;
  725           }
  726           UndoableEdit u = data.insertString(offs, str);
  727           DefaultDocumentEvent e =
  728               new DefaultDocumentEvent(offs, str.length(), DocumentEvent.EventType.INSERT);
  729           if (u != null) {
  730               e.addEdit(u);
  731           }
  732   
  733           // see if complex glyph layout support is needed
  734           if( getProperty(I18NProperty).equals( Boolean.FALSE ) ) {
  735               // if a default direction of right-to-left has been specified,
  736               // we want complex layout even if the text is all left to right.
  737               Object d = getProperty(TextAttribute.RUN_DIRECTION);
  738               if ((d != null) && (d.equals(TextAttribute.RUN_DIRECTION_RTL))) {
  739                   putProperty( I18NProperty, Boolean.TRUE);
  740               } else {
  741                   char[] chars = str.toCharArray();
  742                   if (SwingUtilities2.isComplexLayout(chars, 0, chars.length)) {
  743                       putProperty( I18NProperty, Boolean.TRUE);
  744                   }
  745               }
  746           }
  747   
  748           insertUpdate(e, a);
  749           // Mark the edit as done.
  750           e.end();
  751           fireInsertUpdate(e);
  752           // only fire undo if Content implementation supports it
  753           // undo for the composed text is not supported for now
  754           if (u != null &&
  755               (a == null || !a.isDefined(StyleConstants.ComposedTextAttribute))) {
  756               fireUndoableEditUpdate(new UndoableEditEvent(this, e));
  757           }
  758       }
  759   
  760       /**
  761        * Gets a sequence of text from the document.
  762        *
  763        * @param offset the starting offset >= 0
  764        * @param length the number of characters to retrieve >= 0
  765        * @return the text
  766        * @exception BadLocationException  the range given includes a position
  767        *   that is not a valid position within the document
  768        * @see Document#getText
  769        */
  770       public String getText(int offset, int length) throws BadLocationException {
  771           if (length < 0) {
  772               throw new BadLocationException("Length must be positive", length);
  773           }
  774           String str = data.getString(offset, length);
  775           return str;
  776       }
  777   
  778       /**
  779        * Fetches the text contained within the given portion
  780        * of the document.
  781        * <p>
  782        * If the partialReturn property on the txt parameter is false, the
  783        * data returned in the Segment will be the entire length requested and
  784        * may or may not be a copy depending upon how the data was stored.
  785        * If the partialReturn property is true, only the amount of text that
  786        * can be returned without creating a copy is returned.  Using partial
  787        * returns will give better performance for situations where large
  788        * parts of the document are being scanned.  The following is an example
  789        * of using the partial return to access the entire document:
  790        * <p>
  791        * <pre>
  792        * &nbsp; int nleft = doc.getDocumentLength();
  793        * &nbsp; Segment text = new Segment();
  794        * &nbsp; int offs = 0;
  795        * &nbsp; text.setPartialReturn(true);
  796        * &nbsp; while (nleft > 0) {
  797        * &nbsp;     doc.getText(offs, nleft, text);
  798        * &nbsp;     // do something with text
  799        * &nbsp;     nleft -= text.count;
  800        * &nbsp;     offs += text.count;
  801        * &nbsp; }
  802        * </pre>
  803        *
  804        * @param offset the starting offset >= 0
  805        * @param length the number of characters to retrieve >= 0
  806        * @param txt the Segment object to retrieve the text into
  807        * @exception BadLocationException  the range given includes a position
  808        *   that is not a valid position within the document
  809        */
  810       public void getText(int offset, int length, Segment txt) throws BadLocationException {
  811           if (length < 0) {
  812               throw new BadLocationException("Length must be positive", length);
  813           }
  814           data.getChars(offset, length, txt);
  815       }
  816   
  817       /**
  818        * Returns a position that will track change as the document
  819        * is altered.
  820        * <p>
  821        * This method is thread safe, although most Swing methods
  822        * are not. Please see
  823        * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
  824        * to Use Threads</A> for more information.
  825        *
  826        * @param offs the position in the model >= 0
  827        * @return the position
  828        * @exception BadLocationException  if the given position does not
  829        *   represent a valid location in the associated document
  830        * @see Document#createPosition
  831        */
  832       public synchronized Position createPosition(int offs) throws BadLocationException {
  833           return data.createPosition(offs);
  834       }
  835   
  836       /**
  837        * Returns a position that represents the start of the document.  The
  838        * position returned can be counted on to track change and stay
  839        * located at the beginning of the document.
  840        *
  841        * @return the position
  842        */
  843       public final Position getStartPosition() {
  844           Position p;
  845           try {
  846               p = createPosition(0);
  847           } catch (BadLocationException bl) {
  848               p = null;
  849           }
  850           return p;
  851       }
  852   
  853       /**
  854        * Returns a position that represents the end of the document.  The
  855        * position returned can be counted on to track change and stay
  856        * located at the end of the document.
  857        *
  858        * @return the position
  859        */
  860       public final Position getEndPosition() {
  861           Position p;
  862           try {
  863               p = createPosition(data.length());
  864           } catch (BadLocationException bl) {
  865               p = null;
  866           }
  867           return p;
  868       }
  869   
  870       /**
  871        * Gets all root elements defined.  Typically, there
  872        * will only be one so the default implementation
  873        * is to return the default root element.
  874        *
  875        * @return the root element
  876        */
  877       public Element[] getRootElements() {
  878           Element[] elems = new Element[2];
  879           elems[0] = getDefaultRootElement();
  880           elems[1] = getBidiRootElement();
  881           return elems;
  882       }
  883   
  884       /**
  885        * Returns the root element that views should be based upon
  886        * unless some other mechanism for assigning views to element
  887        * structures is provided.
  888        *
  889        * @return the root element
  890        * @see Document#getDefaultRootElement
  891        */
  892       public abstract Element getDefaultRootElement();
  893   
  894       // ---- local methods -----------------------------------------
  895   
  896       /**
  897        * Returns the <code>FilterBypass</code>. This will create one if one
  898        * does not yet exist.
  899        */
  900       private DocumentFilter.FilterBypass getFilterBypass() {
  901           if (filterBypass == null) {
  902               filterBypass = new DefaultFilterBypass();
  903           }
  904           return filterBypass;
  905       }
  906   
  907       /**
  908        * Returns the root element of the bidirectional structure for this
  909        * document.  Its children represent character runs with a given
  910        * Unicode bidi level.
  911        */
  912       public Element getBidiRootElement() {
  913           return bidiRoot;
  914       }
  915   
  916       /**
  917        * Returns true if the text in the range <code>p0</code> to
  918        * <code>p1</code> is left to right.
  919        */
  920       boolean isLeftToRight(int p0, int p1) {
  921           if(!getProperty(I18NProperty).equals(Boolean.TRUE)) {
  922               return true;
  923           }
  924           Element bidiRoot = getBidiRootElement();
  925           int index = bidiRoot.getElementIndex(p0);
  926           Element bidiElem = bidiRoot.getElement(index);
  927           if(bidiElem.getEndOffset() >= p1) {
  928               AttributeSet bidiAttrs = bidiElem.getAttributes();
  929               return ((StyleConstants.getBidiLevel(bidiAttrs) % 2) == 0);
  930           }
  931           return true;
  932       }
  933   
  934       /**
  935        * Get the paragraph element containing the given position.  Sub-classes
  936        * must define for themselves what exactly constitutes a paragraph.  They
  937        * should keep in mind however that a paragraph should at least be the
  938        * unit of text over which to run the Unicode bidirectional algorithm.
  939        *
  940        * @param pos the starting offset >= 0
  941        * @return the element */
  942       public abstract Element getParagraphElement(int pos);
  943   
  944   
  945       /**
  946        * Fetches the context for managing attributes.  This
  947        * method effectively establishes the strategy used
  948        * for compressing AttributeSet information.
  949        *
  950        * @return the context
  951        */
  952       protected final AttributeContext getAttributeContext() {
  953           return context;
  954       }
  955   
  956       /**
  957        * Updates document structure as a result of text insertion.  This
  958        * will happen within a write lock.  If a subclass of
  959        * this class reimplements this method, it should delegate to the
  960        * superclass as well.
  961        *
  962        * @param chng a description of the change
  963        * @param attr the attributes for the change
  964        */
  965       protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) {
  966           if( getProperty(I18NProperty).equals( Boolean.TRUE ) )
  967               updateBidi( chng );
  968   
  969           // Check if a multi byte is encountered in the inserted text.
  970           if (chng.type == DocumentEvent.EventType.INSERT &&
  971                           chng.getLength() > 0 &&
  972                           !Boolean.TRUE.equals(getProperty(MultiByteProperty))) {
  973               Segment segment = SegmentCache.getSharedSegment();
  974               try {
  975                   getText(chng.getOffset(), chng.getLength(), segment);
  976                   segment.first();
  977                   do {
  978                       if ((int)segment.current() > 255) {
  979                           putProperty(MultiByteProperty, Boolean.TRUE);
  980                           break;
  981                       }
  982                   } while (segment.next() != Segment.DONE);
  983               } catch (BadLocationException ble) {
  984                   // Should never happen
  985               }
  986               SegmentCache.releaseSharedSegment(segment);
  987           }
  988       }
  989   
  990       /**
  991        * Updates any document structure as a result of text removal.  This
  992        * method is called before the text is actually removed from the Content.
  993        * This will happen within a write lock. If a subclass
  994        * of this class reimplements this method, it should delegate to the
  995        * superclass as well.
  996        *
  997        * @param chng a description of the change
  998        */
  999       protected void removeUpdate(DefaultDocumentEvent chng) {
 1000       }
 1001   
 1002       /**
 1003        * Updates any document structure as a result of text removal.  This
 1004        * method is called after the text has been removed from the Content.
 1005        * This will happen within a write lock. If a subclass
 1006        * of this class reimplements this method, it should delegate to the
 1007        * superclass as well.
 1008        *
 1009        * @param chng a description of the change
 1010        */
 1011       protected void postRemoveUpdate(DefaultDocumentEvent chng) {
 1012           if( getProperty(I18NProperty).equals( Boolean.TRUE ) )
 1013               updateBidi( chng );
 1014       }
 1015   
 1016   
 1017       /**
 1018        * Update the bidi element structure as a result of the given change
 1019        * to the document.  The given change will be updated to reflect the
 1020        * changes made to the bidi structure.
 1021        *
 1022        * This method assumes that every offset in the model is contained in
 1023        * exactly one paragraph.  This method also assumes that it is called
 1024        * after the change is made to the default element structure.
 1025        */
 1026       void updateBidi( DefaultDocumentEvent chng ) {
 1027   
 1028           // Calculate the range of paragraphs affected by the change.
 1029           int firstPStart;
 1030           int lastPEnd;
 1031           if( chng.type == DocumentEvent.EventType.INSERT
 1032               || chng.type == DocumentEvent.EventType.CHANGE )
 1033           {
 1034               int chngStart = chng.getOffset();
 1035               int chngEnd =  chngStart + chng.getLength();
 1036               firstPStart = getParagraphElement(chngStart).getStartOffset();
 1037               lastPEnd = getParagraphElement(chngEnd).getEndOffset();
 1038           } else if( chng.type == DocumentEvent.EventType.REMOVE ) {
 1039               Element paragraph = getParagraphElement( chng.getOffset() );
 1040               firstPStart = paragraph.getStartOffset();
 1041               lastPEnd = paragraph.getEndOffset();
 1042           } else {
 1043               throw new Error("Internal error: unknown event type.");
 1044           }
 1045           //System.out.println("updateBidi: firstPStart = " + firstPStart + " lastPEnd = " + lastPEnd );
 1046   
 1047   
 1048           // Calculate the bidi levels for the affected range of paragraphs.  The
 1049           // levels array will contain a bidi level for each character in the
 1050           // affected text.
 1051           byte levels[] = calculateBidiLevels( firstPStart, lastPEnd );
 1052   
 1053   
 1054           Vector newElements = new Vector();
 1055   
 1056           // Calculate the first span of characters in the affected range with
 1057           // the same bidi level.  If this level is the same as the level of the
 1058           // previous bidi element (the existing bidi element containing
 1059           // firstPStart-1), then merge in the previous element.  If not, but
 1060           // the previous element overlaps the affected range, truncate the
 1061           // previous element at firstPStart.
 1062           int firstSpanStart = firstPStart;
 1063           int removeFromIndex = 0;
 1064           if( firstSpanStart > 0 ) {
 1065               int prevElemIndex = bidiRoot.getElementIndex(firstPStart-1);
 1066               removeFromIndex = prevElemIndex;
 1067               Element prevElem = bidiRoot.getElement(prevElemIndex);
 1068               int prevLevel=StyleConstants.getBidiLevel(prevElem.getAttributes());
 1069               //System.out.println("createbidiElements: prevElem= " + prevElem  + " prevLevel= " + prevLevel + "level[0] = " + levels[0]);
 1070               if( prevLevel==levels[0] ) {
 1071                   firstSpanStart = prevElem.getStartOffset();
 1072               } else if( prevElem.getEndOffset() > firstPStart ) {
 1073                   newElements.addElement(new BidiElement(bidiRoot,
 1074                                                          prevElem.getStartOffset(),
 1075                                                          firstPStart, prevLevel));
 1076               } else {
 1077                   removeFromIndex++;
 1078               }
 1079           }
 1080   
 1081           int firstSpanEnd = 0;
 1082           while((firstSpanEnd<levels.length) && (levels[firstSpanEnd]==levels[0]))
 1083               firstSpanEnd++;
 1084   
 1085   
 1086           // Calculate the last span of characters in the affected range with
 1087           // the same bidi level.  If this level is the same as the level of the
 1088           // next bidi element (the existing bidi element containing lastPEnd),
 1089           // then merge in the next element.  If not, but the next element
 1090           // overlaps the affected range, adjust the next element to start at
 1091           // lastPEnd.
 1092           int lastSpanEnd = lastPEnd;
 1093           Element newNextElem = null;
 1094           int removeToIndex = bidiRoot.getElementCount() - 1;
 1095           if( lastSpanEnd <= getLength() ) {
 1096               int nextElemIndex = bidiRoot.getElementIndex( lastPEnd );
 1097               removeToIndex = nextElemIndex;
 1098               Element nextElem = bidiRoot.getElement( nextElemIndex );
 1099               int nextLevel = StyleConstants.getBidiLevel(nextElem.getAttributes());
 1100               if( nextLevel == levels[levels.length-1] ) {
 1101                   lastSpanEnd = nextElem.getEndOffset();
 1102               } else if( nextElem.getStartOffset() < lastPEnd ) {
 1103                   newNextElem = new BidiElement(bidiRoot, lastPEnd,
 1104                                                 nextElem.getEndOffset(),
 1105                                                 nextLevel);
 1106               } else {
 1107                   removeToIndex--;
 1108               }
 1109           }
 1110   
 1111           int lastSpanStart = levels.length;
 1112           while( (lastSpanStart>firstSpanEnd)
 1113                  && (levels[lastSpanStart-1]==levels[levels.length-1]) )
 1114               lastSpanStart--;
 1115   
 1116   
 1117           // If the first and last spans are contiguous and have the same level,
 1118           // merge them and create a single new element for the entire span.
 1119           // Otherwise, create elements for the first and last spans as well as
 1120           // any spans in between.
 1121           if((firstSpanEnd==lastSpanStart)&&(levels[0]==levels[levels.length-1])){
 1122               newElements.addElement(new BidiElement(bidiRoot, firstSpanStart,
 1123                                                      lastSpanEnd, levels[0]));
 1124           } else {
 1125               // Create an element for the first span.
 1126               newElements.addElement(new BidiElement(bidiRoot, firstSpanStart,
 1127                                                      firstSpanEnd+firstPStart,
 1128                                                      levels[0]));
 1129               // Create elements for the spans in between the first and last
 1130               for( int i=firstSpanEnd; i<lastSpanStart; ) {
 1131                   //System.out.println("executed line 872");
 1132                   int j;
 1133                   for( j=i;  (j<levels.length) && (levels[j] == levels[i]); j++ );
 1134                   newElements.addElement(new BidiElement(bidiRoot, firstPStart+i,
 1135                                                          firstPStart+j,
 1136                                                          (int)levels[i]));
 1137                   i=j;
 1138               }
 1139               // Create an element for the last span.
 1140               newElements.addElement(new BidiElement(bidiRoot,
 1141                                                      lastSpanStart+firstPStart,
 1142                                                      lastSpanEnd,
 1143                                                      levels[levels.length-1]));
 1144           }
 1145   
 1146           if( newNextElem != null )
 1147               newElements.addElement( newNextElem );
 1148   
 1149   
 1150           // Calculate the set of existing bidi elements which must be
 1151           // removed.
 1152           int removedElemCount = 0;
 1153           if( bidiRoot.getElementCount() > 0 ) {
 1154               removedElemCount = removeToIndex - removeFromIndex + 1;
 1155           }
 1156           Element[] removedElems = new Element[removedElemCount];
 1157           for( int i=0; i<removedElemCount; i++ ) {
 1158               removedElems[i] = bidiRoot.getElement(removeFromIndex+i);
 1159           }
 1160   
 1161           Element[] addedElems = new Element[ newElements.size() ];
 1162           newElements.copyInto( addedElems );
 1163   
 1164           // Update the change record.
 1165           ElementEdit ee = new ElementEdit( bidiRoot, removeFromIndex,
 1166                                             removedElems, addedElems );
 1167           chng.addEdit( ee );
 1168   
 1169           // Update the bidi element structure.
 1170           bidiRoot.replace( removeFromIndex, removedElems.length, addedElems );
 1171       }
 1172   
 1173   
 1174       /**
 1175        * Calculate the levels array for a range of paragraphs.
 1176        */
 1177       private byte[] calculateBidiLevels( int firstPStart, int lastPEnd ) {
 1178   
 1179           byte levels[] = new byte[ lastPEnd - firstPStart ];
 1180           int  levelsEnd = 0;
 1181           Boolean defaultDirection = null;
 1182           Object d = getProperty(TextAttribute.RUN_DIRECTION);
 1183           if (d instanceof Boolean) {
 1184               defaultDirection = (Boolean) d;
 1185           }
 1186   
 1187           // For each paragraph in the given range of paragraphs, get its
 1188           // levels array and add it to the levels array for the entire span.
 1189           for(int o=firstPStart; o<lastPEnd; ) {
 1190               Element p = getParagraphElement( o );
 1191               int pStart = p.getStartOffset();
 1192               int pEnd = p.getEndOffset();
 1193   
 1194               // default run direction for the paragraph.  This will be
 1195               // null if there is no direction override specified (i.e.
 1196               // the direction will be determined from the content).
 1197               Boolean direction = defaultDirection;
 1198               d = p.getAttributes().getAttribute(TextAttribute.RUN_DIRECTION);
 1199               if (d instanceof Boolean) {
 1200                   direction = (Boolean) d;
 1201               }
 1202   
 1203               //System.out.println("updateBidi: paragraph start = " + pStart + " paragraph end = " + pEnd);
 1204   
 1205               // Create a Bidi over this paragraph then get the level
 1206               // array.
 1207               Segment seg = SegmentCache.getSharedSegment();
 1208               try {
 1209                   getText(pStart, pEnd-pStart, seg);
 1210               } catch (BadLocationException e ) {
 1211                   throw new Error("Internal error: " + e.toString());
 1212               }
 1213               // REMIND(bcb) we should really be using a Segment here.
 1214               Bidi bidiAnalyzer;
 1215               int bidiflag = Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT;
 1216               if (direction != null) {
 1217                   if (TextAttribute.RUN_DIRECTION_LTR.equals(direction)) {
 1218                       bidiflag = Bidi.DIRECTION_LEFT_TO_RIGHT;
 1219                   } else {
 1220                       bidiflag = Bidi.DIRECTION_RIGHT_TO_LEFT;
 1221                   }
 1222               }
 1223               bidiAnalyzer = new Bidi(seg.array, seg.offset, null, 0, seg.count,
 1224                       bidiflag);
 1225               BidiUtils.getLevels(bidiAnalyzer, levels, levelsEnd);
 1226               levelsEnd += bidiAnalyzer.getLength();
 1227   
 1228               o =  p.getEndOffset();
 1229               SegmentCache.releaseSharedSegment(seg);
 1230           }
 1231   
 1232           // REMIND(bcb) remove this code when debugging is done.
 1233           if( levelsEnd != levels.length )
 1234               throw new Error("levelsEnd assertion failed.");
 1235   
 1236           return levels;
 1237       }
 1238   
 1239       /**
 1240        * Gives a diagnostic dump.
 1241        *
 1242        * @param out the output stream
 1243        */
 1244       public void dump(PrintStream out) {
 1245           Element root = getDefaultRootElement();
 1246           if (root instanceof AbstractElement) {
 1247               ((AbstractElement)root).dump(out, 0);
 1248           }
 1249           bidiRoot.dump(out,0);
 1250       }
 1251   
 1252       /**
 1253        * Gets the content for the document.
 1254        *
 1255        * @return the content
 1256        */
 1257       protected final Content getContent() {
 1258           return data;
 1259       }
 1260   
 1261       /**
 1262        * Creates a document leaf element.
 1263        * Hook through which elements are created to represent the
 1264        * document structure.  Because this implementation keeps
 1265        * structure and content separate, elements grow automatically
 1266        * when content is extended so splits of existing elements
 1267        * follow.  The document itself gets to decide how to generate
 1268        * elements to give flexibility in the type of elements used.
 1269        *
 1270        * @param parent the parent element
 1271        * @param a the attributes for the element
 1272        * @param p0 the beginning of the range >= 0
 1273        * @param p1 the end of the range >= p0
 1274        * @return the new element
 1275        */
 1276       protected Element createLeafElement(Element parent, AttributeSet a, int p0, int p1) {
 1277           return new LeafElement(parent, a, p0, p1);
 1278       }
 1279   
 1280       /**
 1281        * Creates a document branch element, that can contain other elements.
 1282        *
 1283        * @param parent the parent element
 1284        * @param a the attributes
 1285        * @return the element
 1286        */
 1287       protected Element createBranchElement(Element parent, AttributeSet a) {
 1288           return new BranchElement(parent, a);
 1289       }
 1290   
 1291       // --- Document locking ----------------------------------
 1292   
 1293       /**
 1294        * Fetches the current writing thread if there is one.
 1295        * This can be used to distinguish whether a method is
 1296        * being called as part of an existing modification or
 1297        * if a lock needs to be acquired and a new transaction
 1298        * started.
 1299        *
 1300        * @return the thread actively modifying the document
 1301        *  or <code>null</code> if there are no modifications in progress
 1302        */
 1303       protected synchronized final Thread getCurrentWriter() {
 1304           return currWriter;
 1305       }
 1306   
 1307       /**
 1308        * Acquires a lock to begin mutating the document this lock
 1309        * protects.  There can be no writing, notification of changes, or
 1310        * reading going on in order to gain the lock.  Additionally a thread is
 1311        * allowed to gain more than one <code>writeLock</code>,
 1312        * as long as it doesn't attempt to gain additional <code>writeLock</code>s
 1313        * from within document notification.  Attempting to gain a
 1314        * <code>writeLock</code> from within a DocumentListener notification will
 1315        * result in an <code>IllegalStateException</code>.  The ability
 1316        * to obtain more than one <code>writeLock</code> per thread allows
 1317        * subclasses to gain a writeLock, perform a number of operations, then
 1318        * release the lock.
 1319        * <p>
 1320        * Calls to <code>writeLock</code>
 1321        * must be balanced with calls to <code>writeUnlock</code>, else the
 1322        * <code>Document</code> will be left in a locked state so that no
 1323        * reading or writing can be done.
 1324        *
 1325        * @exception IllegalStateException thrown on illegal lock
 1326        *  attempt.  If the document is implemented properly, this can
 1327        *  only happen if a document listener attempts to mutate the
 1328        *  document.  This situation violates the bean event model
 1329        *  where order of delivery is not guaranteed and all listeners
 1330        *  should be notified before further mutations are allowed.
 1331        */
 1332       protected synchronized final void writeLock() {
 1333           try {
 1334               while ((numReaders > 0) || (currWriter != null)) {
 1335                   if (Thread.currentThread() == currWriter) {
 1336                       if (notifyingListeners) {
 1337                           // Assuming one doesn't do something wrong in a
 1338                           // subclass this should only happen if a
 1339                           // DocumentListener tries to mutate the document.
 1340                           throw new IllegalStateException(
 1341                                         "Attempt to mutate in notification");
 1342                       }
 1343                       numWriters++;
 1344                       return;
 1345                   }
 1346                   wait();
 1347               }
 1348               currWriter = Thread.currentThread();
 1349               numWriters = 1;
 1350           } catch (InterruptedException e) {
 1351               throw new Error("Interrupted attempt to aquire write lock");
 1352           }
 1353       }
 1354   
 1355       /**
 1356        * Releases a write lock previously obtained via <code>writeLock</code>.
 1357        * After decrementing the lock count if there are no oustanding locks
 1358        * this will allow a new writer, or readers.
 1359        *
 1360        * @see #writeLock
 1361        */
 1362       protected synchronized final void writeUnlock() {
 1363           if (--numWriters <= 0) {
 1364               numWriters = 0;
 1365               currWriter = null;
 1366               notifyAll();
 1367           }
 1368       }
 1369   
 1370       /**
 1371        * Acquires a lock to begin reading some state from the
 1372        * document.  There can be multiple readers at the same time.
 1373        * Writing blocks the readers until notification of the change
 1374        * to the listeners has been completed.  This method should
 1375        * be used very carefully to avoid unintended compromise
 1376        * of the document.  It should always be balanced with a
 1377        * <code>readUnlock</code>.
 1378        *
 1379        * @see #readUnlock
 1380        */
 1381       public synchronized final void readLock() {
 1382           try {
 1383               while (currWriter != null) {
 1384                   if (currWriter == Thread.currentThread()) {
 1385                       // writer has full read access.... may try to acquire
 1386                       // lock in notification
 1387                       return;
 1388                   }
 1389                   wait();
 1390               }
 1391               numReaders += 1;
 1392           } catch (InterruptedException e) {
 1393               throw new Error("Interrupted attempt to aquire read lock");
 1394           }
 1395       }
 1396   
 1397       /**
 1398        * Does a read unlock.  This signals that one
 1399        * of the readers is done.  If there are no more readers
 1400        * then writing can begin again.  This should be balanced
 1401        * with a readLock, and should occur in a finally statement
 1402        * so that the balance is guaranteed.  The following is an
 1403        * example.
 1404        * <pre><code>
 1405        * &nbsp;   readLock();
 1406        * &nbsp;   try {
 1407        * &nbsp;       // do something
 1408        * &nbsp;   } finally {
 1409        * &nbsp;       readUnlock();
 1410        * &nbsp;   }
 1411        * </code></pre>
 1412        *
 1413        * @see #readLock
 1414        */
 1415       public synchronized final void readUnlock() {
 1416           if (currWriter == Thread.currentThread()) {
 1417               // writer has full read access.... may try to acquire
 1418               // lock in notification
 1419               return;
 1420           }
 1421           if (numReaders <= 0) {
 1422               throw new StateInvariantError(BAD_LOCK_STATE);
 1423           }
 1424           numReaders -= 1;
 1425           notify();
 1426       }
 1427   
 1428       // --- serialization ---------------------------------------------
 1429   
 1430       private void readObject(ObjectInputStream s)
 1431         throws ClassNotFoundException, IOException
 1432       {
 1433           s.defaultReadObject();
 1434           listenerList = new EventListenerList();
 1435   
 1436           // Restore bidi structure
 1437           //REMIND(bcb) This creates an initial bidi element to account for
 1438           //the \n that exists by default in the content.
 1439           bidiRoot = new BidiRootElement();
 1440           try {
 1441               writeLock();
 1442               Element[] p = new Element[1];
 1443               p[0] = new BidiElement( bidiRoot, 0, 1, 0 );
 1444               bidiRoot.replace(0,0,p);
 1445           } finally {
 1446               writeUnlock();
 1447           }
 1448           // At this point bidi root is only partially correct. To fully
 1449           // restore it we need access to getDefaultRootElement. But, this
 1450           // is created by the subclass and at this point will be null. We
 1451           // thus use registerValidation.
 1452           s.registerValidation(new ObjectInputValidation() {
 1453               public void validateObject() {
 1454                   try {
 1455                       writeLock();
 1456                       DefaultDocumentEvent e = new DefaultDocumentEvent
 1457                                      (0, getLength(),
 1458                                       DocumentEvent.EventType.INSERT);
 1459                       updateBidi( e );
 1460                   }
 1461                   finally {
 1462                       writeUnlock();
 1463                   }
 1464               }
 1465           }, 0);
 1466       }
 1467   
 1468       // ----- member variables ------------------------------------------
 1469   
 1470       private transient int numReaders;
 1471       private transient Thread currWriter;
 1472       /**
 1473        * The number of writers, all obtained from <code>currWriter</code>.
 1474        */
 1475       private transient int numWriters;
 1476       /**
 1477        * True will notifying listeners.
 1478        */
 1479       private transient boolean notifyingListeners;
 1480   
 1481       private static Boolean defaultI18NProperty;
 1482   
 1483       /**
 1484        * Storage for document-wide properties.
 1485        */
 1486       private Dictionary<Object,Object> documentProperties = null;
 1487   
 1488       /**
 1489        * The event listener list for the document.
 1490        */
 1491       protected EventListenerList listenerList = new EventListenerList();
 1492   
 1493       /**
 1494        * Where the text is actually stored, and a set of marks
 1495        * that track change as the document is edited are managed.
 1496        */
 1497       private Content data;
 1498   
 1499       /**
 1500        * Factory for the attributes.  This is the strategy for
 1501        * attribute compression and control of the lifetime of
 1502        * a set of attributes as a collection.  This may be shared
 1503        * with other documents.
 1504        */
 1505       private AttributeContext context;
 1506   
 1507       /**
 1508        * The root of the bidirectional structure for this document.  Its children
 1509        * represent character runs with the same Unicode bidi level.
 1510        */
 1511       private transient BranchElement bidiRoot;
 1512   
 1513       /**
 1514        * Filter for inserting/removing of text.
 1515        */
 1516       private DocumentFilter documentFilter;
 1517   
 1518       /**
 1519        * Used by DocumentFilter to do actual insert/remove.
 1520        */
 1521       private transient DocumentFilter.FilterBypass filterBypass;
 1522   
 1523       private static final String BAD_LOCK_STATE = "document lock failure";
 1524   
 1525       /**
 1526        * Error message to indicate a bad location.
 1527        */
 1528       protected static final String BAD_LOCATION = "document location failure";
 1529   
 1530       /**
 1531        * Name of elements used to represent paragraphs
 1532        */
 1533       public static final String ParagraphElementName = "paragraph";
 1534   
 1535       /**
 1536        * Name of elements used to represent content
 1537        */
 1538       public static final String ContentElementName = "content";
 1539   
 1540       /**
 1541        * Name of elements used to hold sections (lines/paragraphs).
 1542        */
 1543       public static final String SectionElementName = "section";
 1544   
 1545       /**
 1546        * Name of elements used to hold a unidirectional run
 1547        */
 1548       public static final String BidiElementName = "bidi level";
 1549   
 1550       /**
 1551        * Name of the attribute used to specify element
 1552        * names.
 1553        */
 1554       public static final String ElementNameAttribute = "$ename";
 1555   
 1556       /**
 1557        * Document property that indicates whether internationalization
 1558        * functions such as text reordering or reshaping should be
 1559        * performed. This property should not be publicly exposed,
 1560        * since it is used for implementation convenience only.  As a
 1561        * side effect, copies of this property may be in its subclasses
 1562        * that live in different packages (e.g. HTMLDocument as of now),
 1563        * so those copies should also be taken care of when this property
 1564        * needs to be modified.
 1565        */
 1566       static final String I18NProperty = "i18n";
 1567   
 1568       /**
 1569        * Document property that indicates if a character has been inserted
 1570        * into the document that is more than one byte long.  GlyphView uses
 1571        * this to determine if it should use BreakIterator.
 1572        */
 1573       static final Object MultiByteProperty = "multiByte";
 1574   
 1575       /**
 1576        * Document property that indicates asynchronous loading is
 1577        * desired, with the thread priority given as the value.
 1578        */
 1579       static final String AsyncLoadPriority = "load priority";
 1580   
 1581       /**
 1582        * Interface to describe a sequence of character content that
 1583        * can be edited.  Implementations may or may not support a
 1584        * history mechanism which will be reflected by whether or not
 1585        * mutations return an UndoableEdit implementation.
 1586        * @see AbstractDocument
 1587        */
 1588       public interface Content {
 1589   
 1590           /**
 1591            * Creates a position within the content that will
 1592            * track change as the content is mutated.
 1593            *
 1594            * @param offset the offset in the content >= 0
 1595            * @return a Position
 1596            * @exception BadLocationException for an invalid offset
 1597            */
 1598           public Position createPosition(int offset) throws BadLocationException;
 1599   
 1600           /**
 1601            * Current length of the sequence of character content.
 1602            *
 1603            * @return the length >= 0
 1604            */
 1605           public int length();
 1606   
 1607           /**
 1608            * Inserts a string of characters into the sequence.
 1609            *
 1610            * @param where   offset into the sequence to make the insertion >= 0
 1611            * @param str     string to insert
 1612            * @return  if the implementation supports a history mechanism,
 1613            *    a reference to an <code>Edit</code> implementation will be returned,
 1614            *    otherwise returns <code>null</code>
 1615            * @exception BadLocationException  thrown if the area covered by
 1616            *   the arguments is not contained in the character sequence
 1617            */
 1618           public UndoableEdit insertString(int where, String str) throws BadLocationException;
 1619   
 1620           /**
 1621            * Removes some portion of the sequence.
 1622            *
 1623            * @param where   The offset into the sequence to make the
 1624            *   insertion >= 0.
 1625            * @param nitems  The number of items in the sequence to remove >= 0.
 1626            * @return  If the implementation supports a history mechansim,
 1627            *    a reference to an Edit implementation will be returned,
 1628            *    otherwise null.
 1629            * @exception BadLocationException  Thrown if the area covered by
 1630            *   the arguments is not contained in the character sequence.
 1631            */
 1632           public UndoableEdit remove(int where, int nitems) throws BadLocationException;
 1633   
 1634           /**
 1635            * Fetches a string of characters contained in the sequence.
 1636            *
 1637            * @param where   Offset into the sequence to fetch >= 0.
 1638            * @param len     number of characters to copy >= 0.
 1639            * @return the string
 1640            * @exception BadLocationException  Thrown if the area covered by
 1641            *   the arguments is not contained in the character sequence.
 1642            */
 1643           public String getString(int where, int len) throws BadLocationException;
 1644   
 1645           /**
 1646            * Gets a sequence of characters and copies them into a Segment.
 1647            *
 1648            * @param where the starting offset >= 0
 1649            * @param len the number of characters >= 0
 1650            * @param txt the target location to copy into
 1651            * @exception BadLocationException  Thrown if the area covered by
 1652            *   the arguments is not contained in the character sequence.
 1653            */
 1654           public void getChars(int where, int len, Segment txt) throws BadLocationException;
 1655       }
 1656   
 1657       /**
 1658        * An interface that can be used to allow MutableAttributeSet
 1659        * implementations to use pluggable attribute compression
 1660        * techniques.  Each mutation of the attribute set can be
 1661        * used to exchange a previous AttributeSet instance with
 1662        * another, preserving the possibility of the AttributeSet
 1663        * remaining immutable.  An implementation is provided by
 1664        * the StyleContext class.
 1665        *
 1666        * The Element implementations provided by this class use
 1667        * this interface to provide their MutableAttributeSet
 1668        * implementations, so that different AttributeSet compression
 1669        * techniques can be employed.  The method
 1670        * <code>getAttributeContext</code> should be implemented to
 1671        * return the object responsible for implementing the desired
 1672        * compression technique.
 1673        *
 1674        * @see StyleContext
 1675        */
 1676       public interface AttributeContext {
 1677   
 1678           /**
 1679            * Adds an attribute to the given set, and returns
 1680            * the new representative set.
 1681            *
 1682            * @param old the old attribute set
 1683            * @param name the non-null attribute name
 1684            * @param value the attribute value
 1685            * @return the updated attribute set
 1686            * @see MutableAttributeSet#addAttribute
 1687            */
 1688           public AttributeSet addAttribute(AttributeSet old, Object name, Object value);
 1689   
 1690           /**
 1691            * Adds a set of attributes to the element.
 1692            *
 1693            * @param old the old attribute set
 1694            * @param attr the attributes to add
 1695            * @return the updated attribute set
 1696            * @see MutableAttributeSet#addAttribute
 1697            */
 1698           public AttributeSet addAttributes(AttributeSet old, AttributeSet attr);
 1699   
 1700           /**
 1701            * Removes an attribute from the set.
 1702            *
 1703            * @param old the old attribute set
 1704            * @param name the non-null attribute name
 1705            * @return the updated attribute set
 1706            * @see MutableAttributeSet#removeAttribute
 1707            */
 1708           public AttributeSet removeAttribute(AttributeSet old, Object name);
 1709   
 1710           /**
 1711            * Removes a set of attributes for the element.
 1712            *
 1713            * @param old the old attribute set
 1714            * @param names the attribute names
 1715            * @return the updated attribute set
 1716            * @see MutableAttributeSet#removeAttributes
 1717            */
 1718           public AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names);
 1719   
 1720           /**
 1721            * Removes a set of attributes for the element.
 1722            *
 1723            * @param old the old attribute set
 1724            * @param attrs the attributes
 1725            * @return the updated attribute set
 1726            * @see MutableAttributeSet#removeAttributes
 1727            */
 1728           public AttributeSet removeAttributes(AttributeSet old, AttributeSet attrs);
 1729   
 1730           /**
 1731            * Fetches an empty AttributeSet.
 1732            *
 1733            * @return the attribute set
 1734            */
 1735           public AttributeSet getEmptySet();
 1736   
 1737           /**
 1738            * Reclaims an attribute set.
 1739            * This is a way for a MutableAttributeSet to mark that it no
 1740            * longer need a particular immutable set.  This is only necessary
 1741            * in 1.1 where there are no weak references.  A 1.1 implementation
 1742            * would call this in its finalize method.
 1743            *
 1744            * @param a the attribute set to reclaim
 1745            */
 1746           public void reclaim(AttributeSet a);
 1747       }
 1748   
 1749       /**
 1750        * Implements the abstract part of an element.  By default elements
 1751        * support attributes by having a field that represents the immutable
 1752        * part of the current attribute set for the element.  The element itself
 1753        * implements MutableAttributeSet which can be used to modify the set
 1754        * by fetching a new immutable set.  The immutable sets are provided
 1755        * by the AttributeContext associated with the document.
 1756        * <p>
 1757        * <strong>Warning:</strong>
 1758        * Serialized objects of this class will not be compatible with
 1759        * future Swing releases. The current serialization support is
 1760        * appropriate for short term storage or RMI between applications running
 1761        * the same version of Swing.  As of 1.4, support for long term storage
 1762        * of all JavaBeans<sup><font size="-2">TM</font></sup>
 1763        * has been added to the <code>java.beans</code> package.
 1764        * Please see {@link java.beans.XMLEncoder}.
 1765        */
 1766       public abstract class AbstractElement implements Element, MutableAttributeSet, Serializable, TreeNode {
 1767   
 1768           /**
 1769            * Creates a new AbstractElement.
 1770            *
 1771            * @param parent the parent element
 1772            * @param a the attributes for the element
 1773            * @since 1.4
 1774            */
 1775           public AbstractElement(Element parent, AttributeSet a) {
 1776               this.parent = parent;
 1777               attributes = getAttributeContext().getEmptySet();
 1778               if (a != null) {
 1779                   addAttributes(a);
 1780               }
 1781           }
 1782   
 1783           private final void indent(PrintWriter out, int n) {
 1784               for (int i = 0; i < n; i++) {
 1785                   out.print("  ");
 1786               }
 1787           }
 1788   
 1789           /**
 1790            * Dumps a debugging representation of the element hierarchy.
 1791            *
 1792            * @param psOut the output stream
 1793            * @param indentAmount the indentation level >= 0
 1794            */
 1795           public void dump(PrintStream psOut, int indentAmount) {
 1796               PrintWriter out;
 1797               try {
 1798                   out = new PrintWriter(new OutputStreamWriter(psOut,"JavaEsc"),
 1799                                         true);
 1800               } catch (UnsupportedEncodingException e){
 1801                   out = new PrintWriter(psOut,true);
 1802               }
 1803               indent(out, indentAmount);
 1804               if (getName() == null) {
 1805                   out.print("<??");
 1806               } else {
 1807                   out.print("<" + getName());
 1808               }
 1809               if (getAttributeCount() > 0) {
 1810                   out.println("");
 1811                   // dump the attributes
 1812                   Enumeration names = attributes.getAttributeNames();
 1813                   while (names.hasMoreElements()) {
 1814                       Object name = names.nextElement();
 1815                       indent(out, indentAmount + 1);
 1816                       out.println(name + "=" + getAttribute(name));
 1817                   }
 1818                   indent(out, indentAmount);
 1819               }
 1820               out.println(">");
 1821   
 1822               if (isLeaf()) {
 1823                   indent(out, indentAmount+1);
 1824                   out.print("[" + getStartOffset() + "," + getEndOffset() + "]");
 1825                   Content c = getContent();
 1826                   try {
 1827                       String contentStr = c.getString(getStartOffset(),
 1828                                                       getEndOffset() - getStartOffset())/*.trim()*/;
 1829                       if (contentStr.length() > 40) {
 1830                           contentStr = contentStr.substring(0, 40) + "...";
 1831                       }
 1832                       out.println("["+contentStr+"]");
 1833                   } catch (BadLocationException e) {
 1834                           ;
 1835                   }
 1836   
 1837               } else {
 1838                   int n = getElementCount();
 1839                   for (int i = 0; i < n; i++) {
 1840                       AbstractElement e = (AbstractElement) getElement(i);
 1841                       e.dump(psOut, indentAmount+1);
 1842                   }
 1843               }
 1844           }
 1845   
 1846           // --- AttributeSet ----------------------------
 1847           // delegated to the immutable field "attributes"
 1848   
 1849           /**
 1850            * Gets the number of attributes that are defined.
 1851            *
 1852            * @return the number of attributes >= 0
 1853            * @see AttributeSet#getAttributeCount
 1854            */
 1855           public int getAttributeCount() {
 1856               return attributes.getAttributeCount();
 1857           }
 1858   
 1859           /**
 1860            * Checks whether a given attribute is defined.
 1861            *
 1862            * @param attrName the non-null attribute name
 1863            * @return true if the attribute is defined
 1864            * @see AttributeSet#isDefined
 1865            */
 1866           public boolean isDefined(Object attrName) {
 1867               return attributes.isDefined(attrName);
 1868           }
 1869   
 1870           /**
 1871            * Checks whether two attribute sets are equal.
 1872            *
 1873            * @param attr the attribute set to check against
 1874            * @return true if the same
 1875            * @see AttributeSet#isEqual
 1876            */
 1877           public boolean isEqual(AttributeSet attr) {
 1878               return attributes.isEqual(attr);
 1879           }
 1880   
 1881           /**
 1882            * Copies a set of attributes.
 1883            *
 1884            * @return the copy
 1885            * @see AttributeSet#copyAttributes
 1886            */
 1887           public AttributeSet copyAttributes() {
 1888               return attributes.copyAttributes();
 1889           }
 1890   
 1891           /**
 1892            * Gets the value of an attribute.
 1893            *
 1894            * @param attrName the non-null attribute name
 1895            * @return the attribute value
 1896            * @see AttributeSet#getAttribute
 1897            */
 1898           public Object getAttribute(Object attrName) {
 1899               Object value = attributes.getAttribute(attrName);
 1900               if (value == null) {
 1901                   // The delegate nor it's resolvers had a match,
 1902                   // so we'll try to resolve through the parent
 1903                   // element.
 1904                   AttributeSet a = (parent != null) ? parent.getAttributes() : null;
 1905                   if (a != null) {
 1906                       value = a.getAttribute(attrName);
 1907                   }
 1908               }
 1909               return value;
 1910           }
 1911   
 1912           /**
 1913            * Gets the names of all attributes.
 1914            *
 1915            * @return the attribute names as an enumeration
 1916            * @see AttributeSet#getAttributeNames
 1917            */
 1918           public Enumeration<?> getAttributeNames() {
 1919               return attributes.getAttributeNames();
 1920           }
 1921   
 1922           /**
 1923            * Checks whether a given attribute name/value is defined.
 1924            *
 1925            * @param name the non-null attribute name
 1926            * @param value the attribute value
 1927            * @return true if the name/value is defined
 1928            * @see AttributeSet#containsAttribute
 1929            */
 1930           public boolean containsAttribute(Object name, Object value) {
 1931               return attributes.containsAttribute(name, value);
 1932           }
 1933   
 1934   
 1935           /**
 1936            * Checks whether the element contains all the attributes.
 1937            *
 1938            * @param attrs the attributes to check
 1939            * @return true if the element contains all the attributes
 1940            * @see AttributeSet#containsAttributes
 1941            */
 1942           public boolean containsAttributes(AttributeSet attrs) {
 1943               return attributes.containsAttributes(attrs);
 1944           }
 1945   
 1946           /**
 1947            * Gets the resolving parent.
 1948            * If not overridden, the resolving parent defaults to
 1949            * the parent element.
 1950            *
 1951            * @return the attributes from the parent, <code>null</code> if none
 1952            * @see AttributeSet#getResolveParent
 1953            */
 1954           public AttributeSet getResolveParent() {
 1955               AttributeSet a = attributes.getResolveParent();
 1956               if ((a == null) && (parent != null)) {
 1957                   a = parent.getAttributes();
 1958               }
 1959               return a;
 1960           }
 1961   
 1962           // --- MutableAttributeSet ----------------------------------
 1963           // should fetch a new immutable record for the field
 1964           // "attributes".
 1965   
 1966           /**
 1967            * Adds an attribute to the element.
 1968            *
 1969            * @param name the non-null attribute name
 1970            * @param value the attribute value
 1971            * @see MutableAttributeSet#addAttribute
 1972            */
 1973           public void addAttribute(Object name, Object value) {
 1974               checkForIllegalCast();
 1975               AttributeContext context = getAttributeContext();
 1976               attributes = context.addAttribute(attributes, name, value);
 1977           }
 1978   
 1979           /**
 1980            * Adds a set of attributes to the element.
 1981            *
 1982            * @param attr the attributes to add
 1983            * @see MutableAttributeSet#addAttribute
 1984            */
 1985           public void addAttributes(AttributeSet attr) {
 1986               checkForIllegalCast();
 1987               AttributeContext context = getAttributeContext();
 1988               attributes = context.addAttributes(attributes, attr);
 1989           }
 1990   
 1991           /**
 1992            * Removes an attribute from the set.
 1993            *
 1994            * @param name the non-null attribute name
 1995            * @see MutableAttributeSet#removeAttribute
 1996            */
 1997           public void removeAttribute(Object name) {
 1998               checkForIllegalCast();
 1999               AttributeContext context = getAttributeContext();
 2000               attributes = context.removeAttribute(attributes, name);
 2001           }
 2002   
 2003           /**
 2004            * Removes a set of attributes for the element.
 2005            *
 2006            * @param names the attribute names
 2007            * @see MutableAttributeSet#removeAttributes
 2008            */
 2009           public void removeAttributes(Enumeration<?> names) {
 2010               checkForIllegalCast();
 2011               AttributeContext context = getAttributeContext();
 2012               attributes = context.removeAttributes(attributes, names);
 2013           }
 2014   
 2015           /**
 2016            * Removes a set of attributes for the element.
 2017            *
 2018            * @param attrs the attributes
 2019            * @see MutableAttributeSet#removeAttributes
 2020            */
 2021           public void removeAttributes(AttributeSet attrs) {
 2022               checkForIllegalCast();
 2023               AttributeContext context = getAttributeContext();
 2024               if (attrs == this) {
 2025                   attributes = context.getEmptySet();
 2026               } else {
 2027                   attributes = context.removeAttributes(attributes, attrs);
 2028               }
 2029           }
 2030   
 2031           /**
 2032            * Sets the resolving parent.
 2033            *
 2034            * @param parent the parent, null if none
 2035            * @see MutableAttributeSet#setResolveParent
 2036            */
 2037           public void setResolveParent(AttributeSet parent) {
 2038               checkForIllegalCast();
 2039               AttributeContext context = getAttributeContext();
 2040               if (parent != null) {
 2041                   attributes =
 2042                       context.addAttribute(attributes, StyleConstants.ResolveAttribute,
 2043                                            parent);
 2044               } else {
 2045                   attributes =
 2046                       context.removeAttribute(attributes, StyleConstants.ResolveAttribute);
 2047               }
 2048           }
 2049   
 2050           private final void checkForIllegalCast() {
 2051               Thread t = getCurrentWriter();
 2052               if ((t == null) || (t != Thread.currentThread())) {
 2053                   throw new StateInvariantError("Illegal cast to MutableAttributeSet");
 2054               }
 2055           }
 2056   
 2057           // --- Element methods -------------------------------------
 2058   
 2059           /**
 2060            * Retrieves the underlying model.
 2061            *
 2062            * @return the model
 2063            */
 2064           public Document getDocument() {
 2065               return AbstractDocument.this;
 2066           }
 2067   
 2068           /**
 2069            * Gets the parent of the element.
 2070            *
 2071            * @return the parent
 2072            */
 2073           public Element getParentElement() {
 2074               return parent;
 2075           }
 2076   
 2077           /**
 2078            * Gets the attributes for the element.
 2079            *
 2080            * @return the attribute set
 2081            */
 2082           public AttributeSet getAttributes() {
 2083               return this;
 2084           }
 2085   
 2086           /**
 2087            * Gets the name of the element.
 2088            *
 2089            * @return the name, null if none
 2090            */
 2091           public String getName() {
 2092               if (attributes.isDefined(ElementNameAttribute)) {
 2093                   return (String) attributes.getAttribute(ElementNameAttribute);
 2094               }
 2095               return null;
 2096           }
 2097   
 2098           /**
 2099            * Gets the starting offset in the model for the element.
 2100            *
 2101            * @return the offset >= 0
 2102            */
 2103           public abstract int getStartOffset();
 2104   
 2105           /**
 2106            * Gets the ending offset in the model for the element.
 2107            *
 2108            * @return the offset >= 0
 2109            */
 2110           public abstract int getEndOffset();
 2111   
 2112           /**
 2113            * Gets a child element.
 2114            *
 2115            * @param index the child index, >= 0 && < getElementCount()
 2116            * @return the child element
 2117            */
 2118           public abstract Element getElement(int index);
 2119   
 2120           /**
 2121            * Gets the number of children for the element.
 2122            *
 2123            * @return the number of children >= 0
 2124            */
 2125           public abstract int getElementCount();
 2126   
 2127           /**
 2128            * Gets the child element index closest to the given model offset.
 2129            *
 2130            * @param offset the offset >= 0
 2131            * @return the element index >= 0
 2132            */
 2133           public abstract int getElementIndex(int offset);
 2134   
 2135           /**
 2136            * Checks whether the element is a leaf.
 2137            *
 2138            * @return true if a leaf
 2139            */
 2140           public abstract boolean isLeaf();
 2141   
 2142           // --- TreeNode methods -------------------------------------
 2143   
 2144           /**
 2145            * Returns the child <code>TreeNode</code> at index
 2146            * <code>childIndex</code>.
 2147            */
 2148           public TreeNode getChildAt(int childIndex) {
 2149               return (TreeNode)getElement(childIndex);
 2150           }
 2151   
 2152           /**
 2153            * Returns the number of children <code>TreeNode</code>'s
 2154            * receiver contains.
 2155            * @return the number of children <code>TreeNodews</code>'s
 2156            * receiver contains
 2157            */
 2158           public int getChildCount() {
 2159               return getElementCount();
 2160           }
 2161   
 2162           /**
 2163            * Returns the parent <code>TreeNode</code> of the receiver.
 2164            * @return the parent <code>TreeNode</code> of the receiver
 2165            */
 2166           public TreeNode getParent() {
 2167               return (TreeNode)getParentElement();
 2168           }
 2169   
 2170           /**
 2171            * Returns the index of <code>node</code> in the receivers children.
 2172            * If the receiver does not contain <code>node</code>, -1 will be
 2173            * returned.
 2174            * @param node the location of interest
 2175            * @return the index of <code>node</code> in the receiver's
 2176            * children, or -1 if absent
 2177            */
 2178           public int getIndex(TreeNode node) {
 2179               for(int counter = getChildCount() - 1; counter >= 0; counter--)
 2180                   if(getChildAt(counter) == node)
 2181                       return counter;
 2182               return -1;
 2183           }
 2184   
 2185           /**
 2186            * Returns true if the receiver allows children.
 2187            * @return true if the receiver allows children, otherwise false
 2188            */
 2189           public abstract boolean getAllowsChildren();
 2190   
 2191   
 2192           /**
 2193            * Returns the children of the receiver as an
 2194            * <code>Enumeration</code>.
 2195            * @return the children of the receiver as an <code>Enumeration</code>
 2196            */
 2197           public abstract Enumeration children();
 2198   
 2199   
 2200           // --- serialization ---------------------------------------------
 2201   
 2202           private void writeObject(ObjectOutputStream s) throws IOException {
 2203               s.defaultWriteObject();
 2204               StyleContext.writeAttributeSet(s, attributes);
 2205           }
 2206   
 2207           private void readObject(ObjectInputStream s)
 2208               throws ClassNotFoundException, IOException
 2209           {
 2210               s.defaultReadObject();
 2211               MutableAttributeSet attr = new SimpleAttributeSet();
 2212               StyleContext.readAttributeSet(s, attr);
 2213               AttributeContext context = getAttributeContext();
 2214               attributes = context.addAttributes(SimpleAttributeSet.EMPTY, attr);
 2215           }
 2216   
 2217           // ---- variables -----------------------------------------------------
 2218   
 2219           private Element parent;
 2220           private transient AttributeSet attributes;
 2221   
 2222       }
 2223   
 2224       /**
 2225        * Implements a composite element that contains other elements.
 2226        * <p>
 2227        * <strong>Warning:</strong>
 2228        * Serialized objects of this class will not be compatible with
 2229        * future Swing releases. The current serialization support is
 2230        * appropriate for short term storage or RMI between applications running
 2231        * the same version of Swing.  As of 1.4, support for long term storage
 2232        * of all JavaBeans<sup><font size="-2">TM</font></sup>
 2233        * has been added to the <code>java.beans</code> package.
 2234        * Please see {@link java.beans.XMLEncoder}.
 2235        */
 2236       public class BranchElement extends AbstractElement {
 2237   
 2238           /**
 2239            * Constructs a composite element that initially contains
 2240            * no children.
 2241            *
 2242            * @param parent  The parent element
 2243            * @param a the attributes for the element
 2244            * @since 1.4
 2245            */
 2246           public BranchElement(Element parent, AttributeSet a) {
 2247               super(parent, a);
 2248               children = new AbstractElement[1];
 2249               nchildren = 0;
 2250               lastIndex = -1;
 2251           }
 2252   
 2253           /**
 2254            * Gets the child element that contains
 2255            * the given model position.
 2256            *
 2257            * @param pos the position >= 0
 2258            * @return the element, null if none
 2259            */
 2260           public Element positionToElement(int pos) {
 2261               int index = getElementIndex(pos);
 2262               Element child = children[index];
 2263               int p0 = child.getStartOffset();
 2264               int p1 = child.getEndOffset();
 2265               if ((pos >= p0) && (pos < p1)) {
 2266                   return child;
 2267               }
 2268               return null;
 2269           }
 2270   
 2271           /**
 2272            * Replaces content with a new set of elements.
 2273            *
 2274            * @param offset the starting offset >= 0
 2275            * @param length the length to replace >= 0
 2276            * @param elems the new elements
 2277            */
 2278           public void replace(int offset, int length, Element[] elems) {
 2279               int delta = elems.length - length;
 2280               int src = offset + length;
 2281               int nmove = nchildren - src;
 2282               int dest = src + delta;
 2283               if ((nchildren + delta) >= children.length) {
 2284                   // need to grow the array
 2285                   int newLength = Math.max(2*children.length, nchildren + delta);
 2286                   AbstractElement[] newChildren = new AbstractElement[newLength];
 2287                   System.arraycopy(children, 0, newChildren, 0, offset);
 2288                   System.arraycopy(elems, 0, newChildren, offset, elems.length);
 2289                   System.arraycopy(children, src, newChildren, dest, nmove);
 2290                   children = newChildren;
 2291               } else {
 2292                   // patch the existing array
 2293                   System.arraycopy(children, src, children, dest, nmove);
 2294                   System.arraycopy(elems, 0, children, offset, elems.length);
 2295               }
 2296               nchildren = nchildren + delta;
 2297           }
 2298   
 2299           /**
 2300            * Converts the element to a string.
 2301            *
 2302            * @return the string
 2303            */
 2304           public String toString() {
 2305               return "BranchElement(" + getName() + ") " + getStartOffset() + "," +
 2306                   getEndOffset() + "\n";
 2307           }
 2308   
 2309           // --- Element methods -----------------------------------
 2310   
 2311           /**
 2312            * Gets the element name.
 2313            *
 2314            * @return the element name
 2315            */
 2316           public String getName() {
 2317               String nm = super.getName();
 2318               if (nm == null) {
 2319                   nm = ParagraphElementName;
 2320               }
 2321               return nm;
 2322           }
 2323   
 2324           /**
 2325            * Gets the starting offset in the model for the element.
 2326            *
 2327            * @return the offset >= 0
 2328            */
 2329           public int getStartOffset() {
 2330               return children[0].getStartOffset();
 2331           }
 2332   
 2333           /**
 2334            * Gets the ending offset in the model for the element.
 2335            * @throws NullPointerException if this element has no children
 2336            *
 2337            * @return the offset >= 0
 2338            */
 2339           public int getEndOffset() {
 2340               Element child =
 2341                   (nchildren > 0) ? children[nchildren - 1] : children[0];
 2342               return child.getEndOffset();
 2343           }
 2344   
 2345           /**
 2346            * Gets a child element.
 2347            *
 2348            * @param index the child index, >= 0 && < getElementCount()
 2349            * @return the child element, null if none
 2350            */
 2351           public Element getElement(int index) {
 2352               if (index < nchildren) {
 2353                   return children[index];
 2354               }
 2355               return null;
 2356           }
 2357   
 2358           /**
 2359            * Gets the number of children for the element.
 2360            *
 2361            * @return the number of children >= 0
 2362            */
 2363           public int getElementCount()  {
 2364               return nchildren;
 2365           }
 2366   
 2367           /**
 2368            * Gets the child element index closest to the given model offset.
 2369            *
 2370            * @param offset the offset >= 0
 2371            * @return the element index >= 0
 2372            */
 2373           public int getElementIndex(int offset) {
 2374               int index;
 2375               int lower = 0;
 2376               int upper = nchildren - 1;
 2377               int mid = 0;
 2378               int p0 = getStartOffset();
 2379               int p1;
 2380   
 2381               if (nchildren == 0) {
 2382                   return 0;
 2383               }
 2384               if (offset >= getEndOffset()) {
 2385                   return nchildren - 1;
 2386               }
 2387   
 2388               // see if the last index can be used.
 2389               if ((lastIndex >= lower) && (lastIndex <= upper)) {
 2390                   Element lastHit = children[lastIndex];
 2391                   p0 = lastHit.getStartOffset();
 2392                   p1 = lastHit.getEndOffset();
 2393                   if ((offset >= p0) && (offset < p1)) {
 2394                       return lastIndex;
 2395                   }
 2396   
 2397                   // last index wasn't a hit, but it does give useful info about
 2398                   // where a hit (if any) would be.
 2399                   if (offset < p0) {
 2400                       upper = lastIndex;
 2401                   } else  {
 2402                       lower = lastIndex;
 2403                   }
 2404               }
 2405   
 2406               while (lower <= upper) {
 2407                   mid = lower + ((upper - lower) / 2);
 2408                   Element elem = children[mid];
 2409                   p0 = elem.getStartOffset();
 2410                   p1 = elem.getEndOffset();
 2411                   if ((offset >= p0) && (offset < p1)) {
 2412                       // found the location
 2413                       index = mid;
 2414                       lastIndex = index;
 2415                       return index;
 2416                   } else if (offset < p0) {
 2417                       upper = mid - 1;
 2418                   } else {
 2419                       lower = mid + 1;
 2420                   }
 2421               }
 2422   
 2423               // didn't find it, but we indicate the index of where it would belong
 2424               if (offset < p0) {
 2425                   in