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

    1   /*
    2    * Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved.
    3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
    4    *
    5    * This code is free software; you can redistribute it and/or modify it
    6    * under the terms of the GNU General Public License version 2 only, as
    7    * published by the Free Software Foundation.  Oracle designates this
    8    * particular file as subject to the "Classpath" exception as provided
    9    * by Oracle in the LICENSE file that accompanied this code.
   10    *
   11    * This code is distributed in the hope that it will be useful, but WITHOUT
   12    * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   13    * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   14    * version 2 for more details (a copy is included in the LICENSE file that
   15    * accompanied this code).
   16    *
   17    * You should have received a copy of the GNU General Public License version
   18    * 2 along with this work; if not, write to the Free Software Foundation,
   19    * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   20    *
   21    * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
   22    * or visit www.oracle.com if you need additional information or have any
   23    * questions.
   24    */
   25   package javax.swing.text;
   26   
   27   import java.awt.Color;
   28   import java.awt.Font;
   29   import java.awt.font.TextAttribute;
   30   import java.lang.ref.ReferenceQueue;
   31   import java.lang.ref.WeakReference;
   32   import java.util.Enumeration;
   33   import java.util.HashMap;
   34   import java.util.List;
   35   import java.util.Map;
   36   import java.util.Stack;
   37   import java.util.Vector;
   38   import java.util.ArrayList;
   39   import java.io.IOException;
   40   import java.io.ObjectInputStream;
   41   import java.io.Serializable;
   42   import javax.swing.event;
   43   import javax.swing.undo.AbstractUndoableEdit;
   44   import javax.swing.undo.CannotRedoException;
   45   import javax.swing.undo.CannotUndoException;
   46   import javax.swing.undo.UndoableEdit;
   47   import javax.swing.SwingUtilities;
   48   import static sun.swing.SwingUtilities2.IMPLIED_CR;
   49   
   50   /**
   51    * A document that can be marked up with character and paragraph
   52    * styles in a manner similar to the Rich Text Format.  The element
   53    * structure for this document represents style crossings for
   54    * style runs.  These style runs are mapped into a paragraph element
   55    * structure (which may reside in some other structure).  The
   56    * style runs break at paragraph boundaries since logical styles are
   57    * assigned to paragraph boundaries.
   58    * <p>
   59    * <strong>Warning:</strong>
   60    * Serialized objects of this class will not be compatible with
   61    * future Swing releases. The current serialization support is
   62    * appropriate for short term storage or RMI between applications running
   63    * the same version of Swing.  As of 1.4, support for long term storage
   64    * of all JavaBeans<sup><font size="-2">TM</font></sup>
   65    * has been added to the <code>java.beans</code> package.
   66    * Please see {@link java.beans.XMLEncoder}.
   67    *
   68    * @author  Timothy Prinzing
   69    * @see     Document
   70    * @see     AbstractDocument
   71    */
   72   public class DefaultStyledDocument extends AbstractDocument implements StyledDocument {
   73   
   74       /**
   75        * Constructs a styled document.
   76        *
   77        * @param c  the container for the content
   78        * @param styles resources and style definitions which may
   79        *  be shared across documents
   80        */
   81       public DefaultStyledDocument(Content c, StyleContext styles) {
   82           super(c, styles);
   83           listeningStyles = new Vector<Style>();
   84           buffer = new ElementBuffer(createDefaultRoot());
   85           Style defaultStyle = styles.getStyle(StyleContext.DEFAULT_STYLE);
   86           setLogicalStyle(0, defaultStyle);
   87       }
   88   
   89       /**
   90        * Constructs a styled document with the default content
   91        * storage implementation and a shared set of styles.
   92        *
   93        * @param styles the styles
   94        */
   95       public DefaultStyledDocument(StyleContext styles) {
   96           this(new GapContent(BUFFER_SIZE_DEFAULT), styles);
   97       }
   98   
   99       /**
  100        * Constructs a default styled document.  This buffers
  101        * input content by a size of <em>BUFFER_SIZE_DEFAULT</em>
  102        * and has a style context that is scoped by the lifetime
  103        * of the document and is not shared with other documents.
  104        */
  105       public DefaultStyledDocument() {
  106           this(new GapContent(BUFFER_SIZE_DEFAULT), new StyleContext());
  107       }
  108   
  109       /**
  110        * Gets the default root element.
  111        *
  112        * @return the root
  113        * @see Document#getDefaultRootElement
  114        */
  115       public Element getDefaultRootElement() {
  116           return buffer.getRootElement();
  117       }
  118   
  119       /**
  120        * Initialize the document to reflect the given element
  121        * structure (i.e. the structure reported by the
  122        * <code>getDefaultRootElement</code> method.  If the
  123        * document contained any data it will first be removed.
  124        */
  125       protected void create(ElementSpec[] data) {
  126           try {
  127               if (getLength() != 0) {
  128                   remove(0, getLength());
  129               }
  130               writeLock();
  131   
  132               // install the content
  133               Content c = getContent();
  134               int n = data.length;
  135               StringBuilder sb = new StringBuilder();
  136               for (int i = 0; i < n; i++) {
  137                   ElementSpec es = data[i];
  138                   if (es.getLength() > 0) {
  139                       sb.append(es.getArray(), es.getOffset(),  es.getLength());
  140                   }
  141               }
  142               UndoableEdit cEdit = c.insertString(0, sb.toString());
  143   
  144               // build the event and element structure
  145               int length = sb.length();
  146               DefaultDocumentEvent evnt =
  147                   new DefaultDocumentEvent(0, length, DocumentEvent.EventType.INSERT);
  148               evnt.addEdit(cEdit);
  149               buffer.create(length, data, evnt);
  150   
  151               // update bidi (possibly)
  152               super.insertUpdate(evnt, null);
  153   
  154               // notify the listeners
  155               evnt.end();
  156               fireInsertUpdate(evnt);
  157               fireUndoableEditUpdate(new UndoableEditEvent(this, evnt));
  158           } catch (BadLocationException ble) {
  159               throw new StateInvariantError("problem initializing");
  160           } finally {
  161               writeUnlock();
  162           }
  163   
  164       }
  165   
  166       /**
  167        * Inserts new elements in bulk.  This is useful to allow
  168        * parsing with the document in an unlocked state and
  169        * prepare an element structure modification.  This method
  170        * takes an array of tokens that describe how to update an
  171        * element structure so the time within a write lock can
  172        * be greatly reduced in an asynchronous update situation.
  173        * <p>
  174        * This method is thread safe, although most Swing methods
  175        * are not. Please see
  176        * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
  177        * to Use Threads</A> for more information.
  178        *
  179        * @param offset the starting offset >= 0
  180        * @param data the element data
  181        * @exception BadLocationException for an invalid starting offset
  182        */
  183       protected void insert(int offset, ElementSpec[] data) throws BadLocationException {
  184           if (data == null || data.length == 0) {
  185               return;
  186           }
  187   
  188           try {
  189               writeLock();
  190   
  191               // install the content
  192               Content c = getContent();
  193               int n = data.length;
  194               StringBuilder sb = new StringBuilder();
  195               for (int i = 0; i < n; i++) {
  196                   ElementSpec es = data[i];
  197                   if (es.getLength() > 0) {
  198                       sb.append(es.getArray(), es.getOffset(),  es.getLength());
  199                   }
  200               }
  201               if (sb.length() == 0) {
  202                   // Nothing to insert, bail.
  203                   return;
  204               }
  205               UndoableEdit cEdit = c.insertString(offset, sb.toString());
  206   
  207               // create event and build the element structure
  208               int length = sb.length();
  209               DefaultDocumentEvent evnt =
  210                   new DefaultDocumentEvent(offset, length, DocumentEvent.EventType.INSERT);
  211               evnt.addEdit(cEdit);
  212               buffer.insert(offset, length, data, evnt);
  213   
  214               // update bidi (possibly)
  215               super.insertUpdate(evnt, null);
  216   
  217               // notify the listeners
  218               evnt.end();
  219               fireInsertUpdate(evnt);
  220               fireUndoableEditUpdate(new UndoableEditEvent(this, evnt));
  221           } finally {
  222               writeUnlock();
  223           }
  224       }
  225   
  226       /**
  227        * Removes an element from this document.
  228        *
  229        * <p>The element is removed from its parent element, as well as
  230        * the text in the range identified by the element.  If the
  231        * element isn't associated with the document, {@code
  232        * IllegalArgumentException} is thrown.</p>
  233        *
  234        * <p>As empty branch elements are not allowed in the document, if the
  235        * element is the sole child, its parent element is removed as well,
  236        * recursively.  This means that when replacing all the children of a
  237        * particular element, new children should be added <em>before</em>
  238        * removing old children.
  239        *
  240        * <p>Element removal results in two events being fired, the
  241        * {@code DocumentEvent} for changes in element structure and {@code
  242        * UndoableEditEvent} for changes in document content.</p>
  243        *
  244        * <p>If the element contains end-of-content mark (the last {@code
  245        * "\n"} character in document), this character is not removed;
  246        * instead, preceding leaf element is extended to cover the
  247        * character.  If the last leaf already ends with {@code "\n",} it is
  248        * included in content removal.</p>
  249        *
  250        * <p>If the element is {@code null,} {@code NullPointerException} is
  251        * thrown.  If the element structure would become invalid after the removal,
  252        * for example if the element is the document root element, {@code
  253        * IllegalArgumentException} is thrown.  If the current element structure is
  254        * invalid, {@code IllegalStateException} is thrown.</p>
  255        *
  256        * @param  elem                      the element to remove
  257        * @throws NullPointerException      if the element is {@code null}
  258        * @throws IllegalArgumentException  if the element could not be removed
  259        * @throws IllegalStateException     if the element structure is invalid
  260        *
  261        * @since  1.7
  262        */
  263       public void removeElement(Element elem) {
  264           try {
  265               writeLock();
  266               removeElementImpl(elem);
  267           } finally {
  268               writeUnlock();
  269           }
  270       }
  271   
  272       private void removeElementImpl(Element elem) {
  273           if (elem.getDocument() != this) {
  274               throw new IllegalArgumentException("element doesn't belong to document");
  275           }
  276           BranchElement parent = (BranchElement) elem.getParentElement();
  277           if (parent == null) {
  278               throw new IllegalArgumentException("can't remove the root element");
  279           }
  280   
  281           int startOffset = elem.getStartOffset();
  282           int removeFrom = startOffset;
  283           int endOffset = elem.getEndOffset();
  284           int removeTo = endOffset;
  285           int lastEndOffset = getLength() + 1;
  286           Content content = getContent();
  287           boolean atEnd = false;
  288           boolean isComposedText = Utilities.isComposedTextElement(elem);
  289   
  290           if (endOffset >= lastEndOffset) {
  291               // element includes the last "\n" character, needs special handling
  292               if (startOffset <= 0) {
  293                   throw new IllegalArgumentException("can't remove the whole content");
  294               }
  295               removeTo = lastEndOffset - 1; // last "\n" must not be removed
  296               try {
  297                   if (content.getString(startOffset - 1, 1).charAt(0) == '\n') {
  298                       removeFrom--; // preceding leaf ends with "\n", remove it
  299                   }
  300               } catch (BadLocationException ble) { // can't happen
  301                   throw new IllegalStateException(ble);
  302               }
  303               atEnd = true;
  304           }
  305           int length = removeTo - removeFrom;
  306   
  307           DefaultDocumentEvent dde = new DefaultDocumentEvent(removeFrom,
  308                   length, DefaultDocumentEvent.EventType.REMOVE);
  309           UndoableEdit ue = null;
  310           // do not leave empty branch elements
  311           while (parent.getElementCount() == 1) {
  312               elem = parent;
  313               parent = (BranchElement) parent.getParentElement();
  314               if (parent == null) { // shouldn't happen
  315                   throw new IllegalStateException("invalid element structure");
  316               }
  317           }
  318           Element[] removed = { elem };
  319           Element[] added = {};
  320           int index = parent.getElementIndex(startOffset);
  321           parent.replace(index, 1, added);
  322           dde.addEdit(new ElementEdit(parent, index, removed, added));
  323           if (length > 0) {
  324               try {
  325                   ue = content.remove(removeFrom, length);
  326                   if (ue != null) {
  327                       dde.addEdit(ue);
  328                   }
  329               } catch (BadLocationException ble) {
  330                   // can only happen if the element structure is severely broken
  331                   throw new IllegalStateException(ble);
  332               }
  333               lastEndOffset -= length;
  334           }
  335   
  336           if (atEnd) {
  337               // preceding leaf element should be extended to cover orphaned "\n"
  338               Element prevLeaf = parent.getElement(parent.getElementCount() - 1);
  339               while ((prevLeaf != null) && !prevLeaf.isLeaf()) {
  340                   prevLeaf = prevLeaf.getElement(prevLeaf.getElementCount() - 1);
  341               }
  342               if (prevLeaf == null) { // shouldn't happen
  343                   throw new IllegalStateException("invalid element structure");
  344               }
  345               int prevStartOffset = prevLeaf.getStartOffset();
  346               BranchElement prevParent = (BranchElement) prevLeaf.getParentElement();
  347               int prevIndex = prevParent.getElementIndex(prevStartOffset);
  348               Element newElem;
  349               newElem = createLeafElement(prevParent, prevLeaf.getAttributes(),
  350                                               prevStartOffset, lastEndOffset);
  351               Element[] prevRemoved = { prevLeaf };
  352               Element[] prevAdded = { newElem };
  353               prevParent.replace(prevIndex, 1, prevAdded);
  354               dde.addEdit(new ElementEdit(prevParent, prevIndex,
  355                                                       prevRemoved, prevAdded));
  356           }
  357   
  358           postRemoveUpdate(dde);
  359           dde.end();
  360           fireRemoveUpdate(dde);
  361           if (! (isComposedText && (ue != null))) {
  362               // do not fire UndoabeEdit event for composed text edit (unsupported)
  363               fireUndoableEditUpdate(new UndoableEditEvent(this, dde));
  364           }
  365       }
  366   
  367       /**
  368        * Adds a new style into the logical style hierarchy.  Style attributes
  369        * resolve from bottom up so an attribute specified in a child
  370        * will override an attribute specified in the parent.
  371        *
  372        * @param nm   the name of the style (must be unique within the
  373        *   collection of named styles).  The name may be null if the style
  374        *   is unnamed, but the caller is responsible
  375        *   for managing the reference returned as an unnamed style can't
  376        *   be fetched by name.  An unnamed style may be useful for things
  377        *   like character attribute overrides such as found in a style
  378        *   run.
  379        * @param parent the parent style.  This may be null if unspecified
  380        *   attributes need not be resolved in some other style.
  381        * @return the style
  382        */
  383       public Style addStyle(String nm, Style parent) {
  384           StyleContext styles = (StyleContext) getAttributeContext();
  385           return styles.addStyle(nm, parent);
  386       }
  387   
  388       /**
  389        * Removes a named style previously added to the document.
  390        *
  391        * @param nm  the name of the style to remove
  392        */
  393       public void removeStyle(String nm) {
  394           StyleContext styles = (StyleContext) getAttributeContext();
  395           styles.removeStyle(nm);
  396       }
  397   
  398       /**
  399        * Fetches a named style previously added.
  400        *
  401        * @param nm  the name of the style
  402        * @return the style
  403        */
  404       public Style getStyle(String nm) {
  405           StyleContext styles = (StyleContext) getAttributeContext();
  406           return styles.getStyle(nm);
  407       }
  408   
  409   
  410       /**
  411        * Fetches the list of of style names.
  412        *
  413        * @return all the style names
  414        */
  415       public Enumeration<?> getStyleNames() {
  416           return ((StyleContext) getAttributeContext()).getStyleNames();
  417       }
  418   
  419       /**
  420        * Sets the logical style to use for the paragraph at the
  421        * given position.  If attributes aren't explicitly set
  422        * for character and paragraph attributes they will resolve
  423        * through the logical style assigned to the paragraph, which
  424        * in turn may resolve through some hierarchy completely
  425        * independent of the element hierarchy in the document.
  426        * <p>
  427        * This method is thread safe, although most Swing methods
  428        * are not. Please see
  429        * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
  430        * to Use Threads</A> for more information.
  431        *
  432        * @param pos the offset from the start of the document >= 0
  433        * @param s  the logical style to assign to the paragraph, null if none
  434        */
  435       public void setLogicalStyle(int pos, Style s) {
  436           Element paragraph = getParagraphElement(pos);
  437           if ((paragraph != null) && (paragraph instanceof AbstractElement)) {
  438               try {
  439                   writeLock();
  440                   StyleChangeUndoableEdit edit = new StyleChangeUndoableEdit((AbstractElement)paragraph, s);
  441                   ((AbstractElement)paragraph).setResolveParent(s);
  442                   int p0 = paragraph.getStartOffset();
  443                   int p1 = paragraph.getEndOffset();
  444                   DefaultDocumentEvent e =
  445                     new DefaultDocumentEvent(p0, p1 - p0, DocumentEvent.EventType.CHANGE);
  446                   e.addEdit(edit);
  447                   e.end();
  448                   fireChangedUpdate(e);
  449                   fireUndoableEditUpdate(new UndoableEditEvent(this, e));
  450               } finally {
  451                   writeUnlock();
  452               }
  453           }
  454       }
  455   
  456       /**
  457        * Fetches the logical style assigned to the paragraph
  458        * represented by the given position.
  459        *
  460        * @param p the location to translate to a paragraph
  461        *  and determine the logical style assigned >= 0.  This
  462        *  is an offset from the start of the document.
  463        * @return the style, null if none
  464        */
  465       public Style getLogicalStyle(int p) {
  466           Style s = null;
  467           Element paragraph = getParagraphElement(p);
  468           if (paragraph != null) {
  469               AttributeSet a = paragraph.getAttributes();
  470               AttributeSet parent = a.getResolveParent();
  471               if (parent instanceof Style) {
  472                   s = (Style) parent;
  473               }
  474           }
  475           return s;
  476       }
  477   
  478       /**
  479        * Sets attributes for some part of the document.
  480        * A write lock is held by this operation while changes
  481        * are being made, and a DocumentEvent is sent to the listeners
  482        * after the change has been successfully completed.
  483        * <p>
  484        * This method is thread safe, although most Swing methods
  485        * are not. Please see
  486        * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
  487        * to Use Threads</A> for more information.
  488        *
  489        * @param offset the offset in the document >= 0
  490        * @param length the length >= 0
  491        * @param s the attributes
  492        * @param replace true if the previous attributes should be replaced
  493        *  before setting the new attributes
  494        */
  495       public void setCharacterAttributes(int offset, int length, AttributeSet s, boolean replace) {
  496           if (length == 0) {
  497               return;
  498           }
  499           try {
  500               writeLock();
  501               DefaultDocumentEvent changes =
  502                   new DefaultDocumentEvent(offset, length, DocumentEvent.EventType.CHANGE);
  503   
  504               // split elements that need it
  505               buffer.change(offset, length, changes);
  506   
  507               AttributeSet sCopy = s.copyAttributes();
  508   
  509               // PENDING(prinz) - this isn't a very efficient way to iterate
  510               int lastEnd;
  511               for (int pos = offset; pos < (offset + length); pos = lastEnd) {
  512                   Element run = getCharacterElement(pos);
  513                   lastEnd = run.getEndOffset();
  514                   if (pos == lastEnd) {
  515                       // offset + length beyond length of document, bail.
  516                       break;
  517                   }
  518                   MutableAttributeSet attr = (MutableAttributeSet) run.getAttributes();
  519                   changes.addEdit(new AttributeUndoableEdit(run, sCopy, replace));
  520                   if (replace) {
  521                       attr.removeAttributes(attr);
  522                   }
  523                   attr.addAttributes(s);
  524               }
  525               changes.end();
  526               fireChangedUpdate(changes);
  527               fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
  528           } finally {
  529               writeUnlock();
  530           }
  531   
  532       }
  533   
  534       /**
  535        * Sets attributes for a paragraph.
  536        * <p>
  537        * This method is thread safe, although most Swing methods
  538        * are not. Please see
  539        * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
  540        * to Use Threads</A> for more information.
  541        *
  542        * @param offset the offset into the paragraph >= 0
  543        * @param length the number of characters affected >= 0
  544        * @param s the attributes
  545        * @param replace whether to replace existing attributes, or merge them
  546        */
  547       public void setParagraphAttributes(int offset, int length, AttributeSet s,
  548                                          boolean replace) {
  549           try {
  550               writeLock();
  551               DefaultDocumentEvent changes =
  552                   new DefaultDocumentEvent(offset, length, DocumentEvent.EventType.CHANGE);
  553   
  554               AttributeSet sCopy = s.copyAttributes();
  555   
  556               // PENDING(prinz) - this assumes a particular element structure
  557               Element section = getDefaultRootElement();
  558               int index0 = section.getElementIndex(offset);
  559               int index1 = section.getElementIndex(offset + ((length > 0) ? length - 1 : 0));
  560               boolean isI18N = Boolean.TRUE.equals(getProperty(I18NProperty));
  561               boolean hasRuns = false;
  562               for (int i = index0; i <= index1; i++) {
  563                   Element paragraph = section.getElement(i);
  564                   MutableAttributeSet attr = (MutableAttributeSet) paragraph.getAttributes();
  565                   changes.addEdit(new AttributeUndoableEdit(paragraph, sCopy, replace));
  566                   if (replace) {
  567                       attr.removeAttributes(attr);
  568                   }
  569                   attr.addAttributes(s);
  570                   if (isI18N && !hasRuns) {
  571                       hasRuns = (attr.getAttribute(TextAttribute.RUN_DIRECTION) != null);
  572                   }
  573               }
  574   
  575               if (hasRuns) {
  576                   updateBidi( changes );
  577               }
  578   
  579               changes.end();
  580               fireChangedUpdate(changes);
  581               fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
  582           } finally {
  583               writeUnlock();
  584           }
  585       }
  586   
  587       /**
  588        * Gets the paragraph element at the offset <code>pos</code>.
  589        * A paragraph consists of at least one child Element, which is usually
  590        * a leaf.
  591        *
  592        * @param pos the starting offset >= 0
  593        * @return the element
  594        */
  595       public Element getParagraphElement(int pos) {
  596           Element e;
  597           for (e = getDefaultRootElement(); ! e.isLeaf(); ) {
  598               int index = e.getElementIndex(pos);
  599               e = e.getElement(index);
  600           }
  601           if(e != null)
  602               return e.getParentElement();
  603           return e;
  604       }
  605   
  606       /**
  607        * Gets a character element based on a position.
  608        *
  609        * @param pos the position in the document >= 0
  610        * @return the element
  611        */
  612       public Element getCharacterElement(int pos) {
  613           Element e;
  614           for (e = getDefaultRootElement(); ! e.isLeaf(); ) {
  615               int index = e.getElementIndex(pos);
  616               e = e.getElement(index);
  617           }
  618           return e;
  619       }
  620   
  621       // --- local methods -------------------------------------------------
  622   
  623       /**
  624        * Updates document structure as a result of text insertion.  This
  625        * will happen within a write lock.  This implementation simply
  626        * parses the inserted content for line breaks and builds up a set
  627        * of instructions for the element buffer.
  628        *
  629        * @param chng a description of the document change
  630        * @param attr the attributes
  631        */
  632       protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) {
  633           int offset = chng.getOffset();
  634           int length = chng.getLength();
  635           if (attr == null) {
  636               attr = SimpleAttributeSet.EMPTY;
  637           }
  638   
  639           // Paragraph attributes should come from point after insertion.
  640           // You really only notice this when inserting at a paragraph
  641           // boundary.
  642           Element paragraph = getParagraphElement(offset + length);
  643           AttributeSet pattr = paragraph.getAttributes();
  644           // Character attributes should come from actual insertion point.
  645           Element pParagraph = getParagraphElement(offset);
  646           Element run = pParagraph.getElement(pParagraph.getElementIndex
  647                                               (offset));
  648           int endOffset = offset + length;
  649           boolean insertingAtBoundry = (run.getEndOffset() == endOffset);
  650           AttributeSet cattr = run.getAttributes();
  651   
  652           try {
  653               Segment s = new Segment();
  654               Vector<ElementSpec> parseBuffer = new Vector<ElementSpec>();
  655               ElementSpec lastStartSpec = null;
  656               boolean insertingAfterNewline = false;
  657               short lastStartDirection = ElementSpec.OriginateDirection;
  658               // Check if the previous character was a newline.
  659               if (offset > 0) {
  660                   getText(offset - 1, 1, s);
  661                   if (s.array[s.offset] == '\n') {
  662                       // Inserting after a newline.
  663                       insertingAfterNewline = true;
  664                       lastStartDirection = createSpecsForInsertAfterNewline
  665                                     (paragraph, pParagraph, pattr, parseBuffer,
  666                                      offset, endOffset);
  667                       for(int counter = parseBuffer.size() - 1; counter >= 0;
  668                           counter--) {
  669                           ElementSpec spec = parseBuffer.elementAt(counter);
  670                           if(spec.getType() == ElementSpec.StartTagType) {
  671                               lastStartSpec = spec;
  672                               break;
  673                           }
  674                       }
  675                   }
  676               }
  677               // If not inserting after a new line, pull the attributes for
  678               // new paragraphs from the paragraph under the insertion point.
  679               if(!insertingAfterNewline)
  680                   pattr = pParagraph.getAttributes();
  681   
  682               getText(offset, length, s);
  683               char[] txt = s.array;
  684               int n = s.offset + s.count;
  685               int lastOffset = s.offset;
  686   
  687               for (int i = s.offset; i < n; i++) {
  688                   if (txt[i] == '\n') {
  689                       int breakOffset = i + 1;
  690                       parseBuffer.addElement(
  691                           new ElementSpec(attr, ElementSpec.ContentType,
  692                                                  breakOffset - lastOffset));
  693                       parseBuffer.addElement(
  694                           new ElementSpec(null, ElementSpec.EndTagType));
  695                       lastStartSpec = new ElementSpec(pattr, ElementSpec.
  696                                                      StartTagType);
  697                       parseBuffer.addElement(lastStartSpec);
  698                       lastOffset = breakOffset;
  699                   }
  700               }
  701               if (lastOffset < n) {
  702                   parseBuffer.addElement(
  703                       new ElementSpec(attr, ElementSpec.ContentType,
  704                                              n - lastOffset));
  705               }
  706   
  707               ElementSpec first = parseBuffer.firstElement();
  708   
  709               int docLength = getLength();
  710   
  711               // Check for join previous of first content.
  712               if(first.getType() == ElementSpec.ContentType &&
  713                  cattr.isEqual(attr)) {
  714                   first.setDirection(ElementSpec.JoinPreviousDirection);
  715               }
  716   
  717               // Do a join fracture/next for last start spec if necessary.
  718               if(lastStartSpec != null) {
  719                   if(insertingAfterNewline) {
  720                       lastStartSpec.setDirection(lastStartDirection);
  721                   }
  722                   // Join to the fracture if NOT inserting at the end
  723                   // (fracture only happens when not inserting at end of
  724                   // paragraph).
  725                   else if(pParagraph.getEndOffset() != endOffset) {
  726                       lastStartSpec.setDirection(ElementSpec.
  727                                                  JoinFractureDirection);
  728                   }
  729                   // Join to next if parent of pParagraph has another
  730                   // element after pParagraph, and it isn't a leaf.
  731                   else {
  732                       Element parent = pParagraph.getParentElement();
  733                       int pParagraphIndex = parent.getElementIndex(offset);
  734                       if((pParagraphIndex + 1) < parent.getElementCount() &&
  735                          !parent.getElement(pParagraphIndex + 1).isLeaf()) {
  736                           lastStartSpec.setDirection(ElementSpec.
  737                                                      JoinNextDirection);
  738                       }
  739                   }
  740               }
  741   
  742               // Do a JoinNext for last spec if it is content, it doesn't
  743               // already have a direction set, no new paragraphs have been
  744               // inserted or a new paragraph has been inserted and its join
  745               // direction isn't originate, and the element at endOffset
  746               // is a leaf.
  747               if(insertingAtBoundry && endOffset < docLength) {
  748                   ElementSpec last = parseBuffer.lastElement();
  749                   if(last.getType() == ElementSpec.ContentType &&
  750                      last.getDirection() != ElementSpec.JoinPreviousDirection &&
  751                      ((lastStartSpec == null && (paragraph == pParagraph ||
  752                                                  insertingAfterNewline)) ||
  753                       (lastStartSpec != null && lastStartSpec.getDirection() !=
  754                        ElementSpec.OriginateDirection))) {
  755                       Element nextRun = paragraph.getElement(paragraph.
  756                                              getElementIndex(endOffset));
  757                       // Don't try joining to a branch!
  758                       if(nextRun.isLeaf() &&
  759                          attr.isEqual(nextRun.getAttributes())) {
  760                           last.setDirection(ElementSpec.JoinNextDirection);
  761                       }
  762                   }
  763               }
  764               // If not inserting at boundary and there is going to be a
  765               // fracture, then can join next on last content if cattr
  766               // matches the new attributes.
  767               else if(!insertingAtBoundry && lastStartSpec != null &&
  768                       lastStartSpec.getDirection() ==
  769                       ElementSpec.JoinFractureDirection) {
  770                   ElementSpec last = parseBuffer.lastElement();
  771                   if(last.getType() == ElementSpec.ContentType &&
  772                      last.getDirection() != ElementSpec.JoinPreviousDirection &&
  773                      attr.isEqual(cattr)) {
  774                       last.setDirection(ElementSpec.JoinNextDirection);
  775                   }
  776               }
  777   
  778               // Check for the composed text element. If it is, merge the character attributes
  779               // into this element as well.
  780               if (Utilities.isComposedTextAttributeDefined(attr)) {
  781                   MutableAttributeSet mattr = (MutableAttributeSet) attr;
  782                   mattr.addAttributes(cattr);
  783                   mattr.addAttribute(AbstractDocument.ElementNameAttribute,
  784                           AbstractDocument.ContentElementName);
  785   
  786                   // Assure that the composed text element is named properly
  787                   // and doesn't have the CR attribute defined.
  788                   mattr.addAttribute(StyleConstants.NameAttribute,
  789                           AbstractDocument.ContentElementName);
  790                   if (mattr.isDefined(IMPLIED_CR)) {
  791                       mattr.removeAttribute(IMPLIED_CR);
  792                   }
  793               }
  794   
  795               ElementSpec[] spec = new ElementSpec[parseBuffer.size()];
  796               parseBuffer.copyInto(spec);
  797               buffer.insert(offset, length, spec, chng);
  798           } catch (BadLocationException bl) {
  799           }
  800   
  801           super.insertUpdate( chng, attr );
  802       }
  803   
  804       /**
  805        * This is called by insertUpdate when inserting after a new line.
  806        * It generates, in <code>parseBuffer</code>, ElementSpecs that will
  807        * position the stack in <code>paragraph</code>.<p>
  808        * It returns the direction the last StartSpec should have (this don't
  809        * necessarily create the last start spec).
  810        */
  811       short createSpecsForInsertAfterNewline(Element paragraph,
  812               Element pParagraph, AttributeSet pattr, Vector<ElementSpec> parseBuffer,
  813                                                    int offset, int endOffset) {
  814           // Need to find the common parent of pParagraph and paragraph.
  815           if(paragraph.getParentElement() == pParagraph.getParentElement()) {
  816               // The simple (and common) case that pParagraph and
  817               // paragraph have the same parent.
  818               ElementSpec spec = new ElementSpec(pattr, ElementSpec.EndTagType);
  819               parseBuffer.addElement(spec);
  820               spec = new ElementSpec(pattr, ElementSpec.StartTagType);
  821               parseBuffer.addElement(spec);
  822               if(pParagraph.getEndOffset() != endOffset)
  823                   return ElementSpec.JoinFractureDirection;
  824   
  825               Element parent = pParagraph.getParentElement();
  826               if((parent.getElementIndex(offset) + 1) < parent.getElementCount())
  827                   return ElementSpec.JoinNextDirection;
  828           }
  829           else {
  830               // Will only happen for text with more than 2 levels.
  831               // Find the common parent of a paragraph and pParagraph
  832               Vector<Element> leftParents = new Vector<Element>();
  833               Vector<Element> rightParents = new Vector<Element>();
  834               Element e = pParagraph;
  835               while(e != null) {
  836                   leftParents.addElement(e);
  837                   e = e.getParentElement();
  838               }
  839               e = paragraph;
  840               int leftIndex = -1;
  841               while(e != null && (leftIndex = leftParents.indexOf(e)) == -1) {
  842                   rightParents.addElement(e);
  843                   e = e.getParentElement();
  844               }
  845               if(e != null) {
  846                   // e identifies the common parent.
  847                   // Build the ends.
  848                   for(int counter = 0; counter < leftIndex;
  849                       counter++) {
  850                       parseBuffer.addElement(new ElementSpec
  851                                                 (null, ElementSpec.EndTagType));
  852                   }
  853                   // And the starts.
  854                   ElementSpec spec;
  855                   for(int counter = rightParents.size() - 1;
  856                       counter >= 0; counter--) {
  857                       spec = new ElementSpec(rightParents.elementAt(counter).getAttributes(),
  858                                      ElementSpec.StartTagType);
  859                       if(counter > 0)
  860                           spec.setDirection(ElementSpec.JoinNextDirection);
  861                       parseBuffer.addElement(spec);
  862                   }
  863                   // If there are right parents, then we generated starts
  864                   // down the right subtree and there will be an element to
  865                   // join to.
  866                   if(rightParents.size() > 0)
  867                       return ElementSpec.JoinNextDirection;
  868                   // No right subtree, e.getElement(endOffset) is a
  869                   // leaf. There will be a facture.
  870                   return ElementSpec.JoinFractureDirection;
  871               }
  872               // else: Could throw an exception here, but should never get here!
  873           }
  874           return ElementSpec.OriginateDirection;
  875       }
  876   
  877       /**
  878        * Updates document structure as a result of text removal.
  879        *
  880        * @param chng a description of the document change
  881        */
  882       protected void removeUpdate(DefaultDocumentEvent chng) {
  883           super.removeUpdate(chng);
  884           buffer.remove(chng.getOffset(), chng.getLength(), chng);
  885       }
  886   
  887       /**
  888        * Creates the root element to be used to represent the
  889        * default document structure.
  890        *
  891        * @return the element base
  892        */
  893       protected AbstractElement createDefaultRoot() {
  894           // grabs a write-lock for this initialization and
  895           // abandon it during initialization so in normal
  896           // operation we can detect an illegitimate attempt
  897           // to mutate attributes.
  898           writeLock();
  899           BranchElement section = new SectionElement();
  900           BranchElement paragraph = new BranchElement(section, null);
  901   
  902           LeafElement brk = new LeafElement(paragraph, null, 0, 1);
  903           Element[] buff = new Element[1];
  904           buff[0] = brk;
  905           paragraph.replace(0, 0, buff);
  906   
  907           buff[0] = paragraph;
  908           section.replace(0, 0, buff);
  909           writeUnlock();
  910           return section;
  911       }
  912   
  913       /**
  914        * Gets the foreground color from an attribute set.
  915        *
  916        * @param attr the attribute set
  917        * @return the color
  918        */
  919       public Color getForeground(AttributeSet attr) {
  920           StyleContext styles = (StyleContext) getAttributeContext();
  921           return styles.getForeground(attr);
  922       }
  923   
  924       /**
  925        * Gets the background color from an attribute set.
  926        *
  927        * @param attr the attribute set
  928        * @return the color
  929        */
  930       public Color getBackground(AttributeSet attr) {
  931           StyleContext styles = (StyleContext) getAttributeContext();
  932           return styles.getBackground(attr);
  933       }
  934   
  935       /**
  936        * Gets the font from an attribute set.
  937        *
  938        * @param attr the attribute set
  939        * @return the font
  940        */
  941       public Font getFont(AttributeSet attr) {
  942           StyleContext styles = (StyleContext) getAttributeContext();
  943           return styles.getFont(attr);
  944       }
  945   
  946       /**
  947        * Called when any of this document's styles have changed.
  948        * Subclasses may wish to be intelligent about what gets damaged.
  949        *
  950        * @param style The Style that has changed.
  951        */
  952       protected void styleChanged(Style style) {
  953           // Only propagate change updated if have content
  954           if (getLength() != 0) {
  955               // lazily create a ChangeUpdateRunnable
  956               if (updateRunnable == null) {
  957                   updateRunnable = new ChangeUpdateRunnable();
  958               }
  959   
  960               // We may get a whole batch of these at once, so only
  961               // queue the runnable if it is not already pending
  962               synchronized(updateRunnable) {
  963                   if (!updateRunnable.isPending) {
  964                       SwingUtilities.invokeLater(updateRunnable);
  965                       updateRunnable.isPending = true;
  966                   }
  967               }
  968           }
  969       }
  970   
  971       /**
  972        * Adds a document listener for notification of any changes.
  973        *
  974        * @param listener the listener
  975        * @see Document#addDocumentListener
  976        */
  977       public void addDocumentListener(DocumentListener listener) {
  978           synchronized(listeningStyles) {
  979               int oldDLCount = listenerList.getListenerCount
  980                                             (DocumentListener.class);
  981               super.addDocumentListener(listener);
  982               if (oldDLCount == 0) {
  983                   if (styleContextChangeListener == null) {
  984                       styleContextChangeListener =
  985                                         createStyleContextChangeListener();
  986                   }
  987                   if (styleContextChangeListener != null) {
  988                       StyleContext styles = (StyleContext)getAttributeContext();
  989                       List<ChangeListener> staleListeners =
  990                           AbstractChangeHandler.getStaleListeners(styleContextChangeListener);
  991                       for (ChangeListener l: staleListeners) {
  992                           styles.removeChangeListener(l);
  993                       }
  994                       styles.addChangeListener(styleContextChangeListener);
  995                   }
  996                   updateStylesListeningTo();
  997               }
  998           }
  999       }
 1000   
 1001       /**
 1002        * Removes a document listener.
 1003        *
 1004        * @param listener the listener
 1005        * @see Document#removeDocumentListener
 1006        */
 1007       public void removeDocumentListener(DocumentListener listener) {
 1008           synchronized(listeningStyles) {
 1009               super.removeDocumentListener(listener);
 1010               if (listenerList.getListenerCount(DocumentListener.class) == 0) {
 1011                   for (int counter = listeningStyles.size() - 1; counter >= 0;
 1012                        counter--) {
 1013                       listeningStyles.elementAt(counter).
 1014                                       removeChangeListener(styleChangeListener);
 1015                   }
 1016                   listeningStyles.removeAllElements();
 1017                   if (styleContextChangeListener != null) {
 1018                       StyleContext styles = (StyleContext)getAttributeContext();
 1019                       styles.removeChangeListener(styleContextChangeListener);
 1020                   }
 1021               }
 1022           }
 1023       }
 1024   
 1025       /**
 1026        * Returns a new instance of StyleChangeHandler.
 1027        */
 1028       ChangeListener createStyleChangeListener() {
 1029           return new StyleChangeHandler(this);
 1030       }
 1031   
 1032       /**
 1033        * Returns a new instance of StyleContextChangeHandler.
 1034        */
 1035       ChangeListener createStyleContextChangeListener() {
 1036           return new StyleContextChangeHandler(this);
 1037       }
 1038   
 1039       /**
 1040        * Adds a ChangeListener to new styles, and removes ChangeListener from
 1041        * old styles.
 1042        */
 1043       void updateStylesListeningTo() {
 1044           synchronized(listeningStyles) {
 1045               StyleContext styles = (StyleContext)getAttributeContext();
 1046               if (styleChangeListener == null) {
 1047                   styleChangeListener = createStyleChangeListener();
 1048               }
 1049               if (styleChangeListener != null && styles != null) {
 1050                   Enumeration styleNames = styles.getStyleNames();
 1051                   Vector v = (Vector)listeningStyles.clone();
 1052                   listeningStyles.removeAllElements();
 1053                   List<ChangeListener> staleListeners =
 1054                       AbstractChangeHandler.getStaleListeners(styleChangeListener);
 1055                   while (styleNames.hasMoreElements()) {
 1056                       String name = (String)styleNames.nextElement();
 1057                       Style aStyle = styles.getStyle(name);
 1058                       int index = v.indexOf(aStyle);
 1059                       listeningStyles.addElement(aStyle);
 1060                       if (index == -1) {
 1061                           for (ChangeListener l: staleListeners) {
 1062                               aStyle.removeChangeListener(l);
 1063                           }
 1064                           aStyle.addChangeListener(styleChangeListener);
 1065                       }
 1066                       else {
 1067                           v.removeElementAt(index);
 1068                       }
 1069                   }
 1070                   for (int counter = v.size() - 1; counter >= 0; counter--) {
 1071                       Style aStyle = (Style)v.elementAt(counter);
 1072                       aStyle.removeChangeListener(styleChangeListener);
 1073                   }
 1074                   if (listeningStyles.size() == 0) {
 1075                       styleChangeListener = null;
 1076                   }
 1077               }
 1078           }
 1079       }
 1080   
 1081       private void readObject(ObjectInputStream s)
 1082               throws ClassNotFoundException, IOException {
 1083           listeningStyles = new Vector<Style>();
 1084           s.defaultReadObject();
 1085           // Reinstall style listeners.
 1086           if (styleContextChangeListener == null &&
 1087               listenerList.getListenerCount(DocumentListener.class) > 0) {
 1088               styleContextChangeListener = createStyleContextChangeListener();
 1089               if (styleContextChangeListener != null) {
 1090                   StyleContext styles = (StyleContext)getAttributeContext();
 1091                   styles.addChangeListener(styleContextChangeListener);
 1092               }
 1093               updateStylesListeningTo();
 1094           }
 1095       }
 1096   
 1097       // --- member variables -----------------------------------------------------------
 1098   
 1099       /**
 1100        * The default size of the initial content buffer.
 1101        */
 1102       public static final int BUFFER_SIZE_DEFAULT = 4096;
 1103   
 1104       protected ElementBuffer buffer;
 1105   
 1106       /** Styles listening to. */
 1107       private transient Vector<Style> listeningStyles;
 1108   
 1109       /** Listens to Styles. */
 1110       private transient ChangeListener styleChangeListener;
 1111   
 1112       /** Listens to Styles. */
 1113       private transient ChangeListener styleContextChangeListener;
 1114   
 1115       /** Run to create a change event for the document */
 1116       private transient ChangeUpdateRunnable updateRunnable;
 1117   
 1118       /**
 1119        * Default root element for a document... maps out the
 1120        * paragraphs/lines contained.
 1121        * <p>
 1122        * <strong>Warning:</strong>
 1123        * Serialized objects of this class will not be compatible with
 1124        * future Swing releases. The current serialization support is
 1125        * appropriate for short term storage or RMI between applications running
 1126        * the same version of Swing.  As of 1.4, support for long term storage
 1127        * of all JavaBeans<sup><font size="-2">TM</font></sup>
 1128        * has been added to the <code>java.beans</code> package.
 1129        * Please see {@link java.beans.XMLEncoder}.
 1130        */
 1131       protected class SectionElement extends BranchElement {
 1132   
 1133           /**
 1134            * Creates a new SectionElement.
 1135            */
 1136           public SectionElement() {
 1137               super(null, null);
 1138           }
 1139   
 1140           /**
 1141            * Gets the name of the element.
 1142            *
 1143            * @return the name
 1144            */
 1145           public String getName() {
 1146               return SectionElementName;
 1147           }
 1148       }
 1149   
 1150       /**
 1151        * Specification for building elements.
 1152        * <p>
 1153        * <strong>Warning:</strong>
 1154        * Serialized objects of this class will not be compatible with
 1155        * future Swing releases. The current serialization support is
 1156        * appropriate for short term storage or RMI between applications running
 1157        * the same version of Swing.  As of 1.4, support for long term storage
 1158        * of all JavaBeans<sup><font size="-2">TM</font></sup>
 1159        * has been added to the <code>java.beans</code> package.
 1160        * Please see {@link java.beans.XMLEncoder}.
 1161        */
 1162       public static class ElementSpec {
 1163   
 1164           /**
 1165            * A possible value for getType.  This specifies
 1166            * that this record type is a start tag and
 1167            * represents markup that specifies the start
 1168            * of an element.
 1169            */
 1170           public static final short StartTagType = 1;
 1171   
 1172           /**
 1173            * A possible value for getType.  This specifies
 1174            * that this record type is a end tag and
 1175            * represents markup that specifies the end
 1176            * of an element.
 1177            */
 1178           public static final short EndTagType = 2;
 1179   
 1180           /**
 1181            * A possible value for getType.  This specifies
 1182            * that this record type represents content.
 1183            */
 1184           public static final short ContentType = 3;
 1185   
 1186           /**
 1187            * A possible value for getDirection.  This specifies
 1188            * that the data associated with this record should
 1189            * be joined to what precedes it.
 1190            */
 1191           public static final short JoinPreviousDirection = 4;
 1192   
 1193           /**
 1194            * A possible value for getDirection.  This specifies
 1195            * that the data associated with this record should
 1196            * be joined to what follows it.
 1197            */
 1198           public static final short JoinNextDirection = 5;
 1199   
 1200           /**
 1201            * A possible value for getDirection.  This specifies
 1202            * that the data associated with this record should
 1203            * be used to originate a new element.  This would be
 1204            * the normal value.
 1205            */
 1206           public static final short OriginateDirection = 6;
 1207   
 1208           /**
 1209            * A possible value for getDirection.  This specifies
 1210            * that the data associated with this record should
 1211            * be joined to the fractured element.
 1212            */
 1213           public static final short JoinFractureDirection = 7;
 1214   
 1215   
 1216           /**
 1217            * Constructor useful for markup when the markup will not
 1218            * be stored in the document.
 1219            *
 1220            * @param a the attributes for the element
 1221            * @param type the type of the element (StartTagType, EndTagType,
 1222            *  ContentType)
 1223            */
 1224           public ElementSpec(AttributeSet a, short type) {
 1225               this(a, type, null, 0, 0);
 1226           }
 1227   
 1228           /**
 1229            * Constructor for parsing inside the document when
 1230            * the data has already been added, but len information
 1231            * is needed.
 1232            *
 1233            * @param a the attributes for the element
 1234            * @param type the type of the element (StartTagType, EndTagType,
 1235            *  ContentType)
 1236            * @param len the length >= 0
 1237            */
 1238           public ElementSpec(AttributeSet a, short type, int len) {
 1239               this(a, type, null, 0, len);
 1240           }
 1241   
 1242           /**
 1243            * Constructor for creating a spec externally for batch
 1244            * input of content and markup into the document.
 1245            *
 1246            * @param a the attributes for the element
 1247            * @param type the type of the element (StartTagType, EndTagType,
 1248            *  ContentType)
 1249            * @param txt the text for the element
 1250            * @param offs the offset into the text >= 0
 1251            * @param len the length of the text >= 0
 1252            */
 1253           public ElementSpec(AttributeSet a, short type, char[] txt,
 1254                                     int offs, int len) {
 1255               attr = a;
 1256               this.type = type;
 1257               this.data = txt;
 1258               this.offs = offs;
 1259               this.len = len;
 1260               this.direction = OriginateDirection;
 1261           }
 1262   
 1263           /**
 1264            * Sets the element type.
 1265            *
 1266            * @param type the type of the element (StartTagType, EndTagType,
 1267            *  ContentType)
 1268            */
 1269           public void setType(short type) {
 1270               this.type = type;
 1271           }
 1272   
 1273           /**
 1274            * Gets the element type.
 1275            *
 1276            * @return  the type of the element (StartTagType, EndTagType,
 1277            *  ContentType)
 1278            */
 1279           public short getType() {
 1280               return type;
 1281           }
 1282   
 1283           /**
 1284            * Sets the direction.
 1285            *
 1286            * @param direction the direction (JoinPreviousDirection,
 1287            *   JoinNextDirection)
 1288            */
 1289           public void setDirection(short direction) {
 1290               this.direction = direction;
 1291           }
 1292   
 1293           /**
 1294            * Gets the direction.
 1295            *
 1296            * @return the direction (JoinPreviousDirection, JoinNextDirection)
 1297            */
 1298           public short getDirection() {
 1299               return direction;
 1300           }
 1301   
 1302           /**
 1303            * Gets the element attributes.
 1304            *
 1305            * @return the attribute set
 1306            */
 1307           public AttributeSet getAttributes() {
 1308               return attr;
 1309           }
 1310   
 1311           /**
 1312            * Gets the array of characters.
 1313            *
 1314            * @return the array
 1315            */
 1316           public char[] getArray() {
 1317               return data;
 1318           }
 1319   
 1320   
 1321           /**
 1322            * Gets the starting offset.
 1323            *
 1324            * @return the offset >= 0
 1325            */
 1326           public int getOffset() {
 1327               return offs;
 1328           }
 1329   
 1330           /**
 1331            * Gets the length.
 1332            *
 1333            * @return the length >= 0
 1334            */
 1335           public int getLength() {
 1336               return len;
 1337           }
 1338   
 1339           /**
 1340            * Converts the element to a string.
 1341            *
 1342            * @return the string
 1343            */
 1344           public String toString() {
 1345               String tlbl = "??";
 1346               String plbl = "??";
 1347               switch(type) {
 1348               case StartTagType:
 1349                   tlbl = "StartTag";
 1350                   break;
 1351               case ContentType:
 1352                   tlbl = "Content";
 1353                   break;
 1354               case EndTagType:
 1355                   tlbl = "EndTag";
 1356                   break;
 1357               }
 1358               switch(direction) {
 1359               case JoinPreviousDirection:
 1360                   plbl = "JoinPrevious";
 1361                   break;
 1362               case JoinNextDirection:
 1363                   plbl = "JoinNext";
 1364                   break;
 1365               case OriginateDirection:
 1366                   plbl = "Originate";
 1367                   break;
 1368               case JoinFractureDirection:
 1369                   plbl = "Fracture";
 1370                   break;
 1371               }
 1372               return tlbl + ":" + plbl + ":" + getLength();
 1373           }
 1374   
 1375           private AttributeSet attr;
 1376           private int len;
 1377           private short type;
 1378           private short direction;
 1379   
 1380           private int offs;
 1381           private char[] data;
 1382       }
 1383   
 1384       /**
 1385        * Class to manage changes to the element
 1386        * hierarchy.
 1387        * <p>
 1388        * <strong>Warning:</strong>
 1389        * Serialized objects of this class will not be compatible with
 1390        * future Swing releases. The current serialization support is
 1391        * appropriate for short term storage or RMI between applications running
 1392        * the same version of Swing.  As of 1.4, support for long term storage
 1393        * of all JavaBeans<sup><font size="-2">TM</font></sup>
 1394        * has been added to the <code>java.beans</code> package.
 1395        * Please see {@link java.beans.XMLEncoder}.
 1396        */
 1397       public class ElementBuffer implements Serializable {
 1398   
 1399           /**
 1400            * Creates a new ElementBuffer.
 1401            *
 1402            * @param root the root element
 1403            * @since 1.4
 1404            */
 1405           public ElementBuffer(Element root) {
 1406               this.root = root;
 1407               changes = new Vector<ElemChanges>();
 1408               path = new Stack<ElemChanges>();
 1409           }
 1410   
 1411           /**
 1412            * Gets the root element.
 1413            *
 1414            * @return the root element
 1415            */
 1416           public Element getRootElement() {
 1417               return root;
 1418           }
 1419   
 1420           /**
 1421            * Inserts new content.
 1422            *
 1423            * @param offset the starting offset >= 0
 1424            * @param length the length >= 0
 1425            * @param data the data to insert
 1426            * @param de the event capturing this edit
 1427            */
 1428           public void insert(int offset, int length, ElementSpec[] data,
 1429                                    DefaultDocumentEvent de) {
 1430               if (length == 0) {
 1431                   // Nothing was inserted, no structure change.
 1432                   return;
 1433               }
 1434               insertOp = true;
 1435               beginEdits(offset, length);
 1436               insertUpdate(data);
 1437               endEdits(de);
 1438   
 1439               insertOp = false;
 1440           }
 1441   
 1442           void create(int length, ElementSpec[] data, DefaultDocumentEvent de) {
 1443               insertOp = true;
 1444               beginEdits(offset, length);
 1445   
 1446               // PENDING(prinz) this needs to be fixed to create a new
 1447               // root element as well, but requires changes to the
 1448               // DocumentEvent to inform the views that there is a new
 1449               // root element.
 1450   
 1451               // Recreate the ending fake element to have the correct offsets.
 1452               Element elem = root;
 1453               int index = elem.getElementIndex(0);
 1454               while (! elem.isLeaf()) {
 1455                   Element child = elem.getElement(index);
 1456                   push(elem, index);
 1457                   elem = child;
 1458                   index = elem.getElementIndex(0);
 1459               }
 1460               ElemChanges ec = path.peek();
 1461               Element child = ec.parent.getElement(ec.index);
 1462               ec.added.addElement(createLeafElement(ec.parent,
 1463                                   child.getAttributes(), getLength(),
 1464                                   child.getEndOffset()));
 1465               ec.removed.addElement(child);
 1466               while (path.size() > 1) {
 1467                   pop();
 1468               }
 1469   
 1470               int n = data.length;
 1471   
 1472               // Reset the root elements attributes.
 1473               AttributeSet newAttrs = null;
 1474               if (n > 0 && data[0].getType() == ElementSpec.StartTagType) {
 1475                   newAttrs = data[0].getAttributes();
 1476               }
 1477               if (newAttrs == null) {
 1478                   newAttrs = SimpleAttributeSet.EMPTY;
 1479               }
 1480               MutableAttributeSet attr = (MutableAttributeSet)root.
 1481                                          getAttributes();
 1482               de.addEdit(new AttributeUndoableEdit(root, newAttrs, true));
 1483               attr.removeAttributes(attr);
 1484               attr.addAttributes(newAttrs);
 1485   
 1486               // fold in the specified subtree
 1487               for (int i = 1; i < n; i++) {
 1488                   insertElement(data[i]);
 1489               }
 1490   
 1491               // pop the remaining path
 1492               while (path.size() != 0) {
 1493                   pop();
 1494               }
 1495   
 1496               endEdits(de);
 1497               insertOp = false;
 1498           }
 1499   
 1500           /**
 1501            * Removes content.
 1502            *
 1503            * @param offset the starting offset >= 0
 1504            * @param length the length >= 0
 1505            * @param de the event capturing this edit
 1506            */
 1507           public void remove(int offset, int length, DefaultDocumentEvent de) {
 1508               beginEdits(offset, length);
 1509               removeUpdate();
 1510               endEdits(de);
 1511           }
 1512   
 1513           /**
 1514            * Changes content.
 1515            *
 1516            * @param offset the starting offset >= 0
 1517            * @param length the length >= 0
 1518            * @param de the event capturing this edit
 1519            */
 1520           public void change(int offset, int length, DefaultDocumentEvent de) {
 1521               beginEdits(offset, length);
 1522               changeUpdate();
 1523               endEdits(de);
 1524           }
 1525   
 1526           /**
 1527            * Inserts an update into the document.
 1528            *
 1529            * @param data the elements to insert
 1530            */
 1531           protected void insertUpdate(ElementSpec[] data) {
 1532               // push the path
 1533               Element elem = root;
 1534               int index = elem.getElementIndex(offset);
 1535               while (! elem.isLeaf()) {
 1536                   Element child = elem.getElement(index);
 1537                   push(elem, (child.isLeaf() ? index : index+1));
 1538                   elem = child;
 1539                   index = elem.getElementIndex(offset);
 1540               }
 1541   
 1542               // Build a copy of the original path.
 1543               insertPath = new ElemChanges[path.size()];
 1544               path.copyInto(insertPath);
 1545   
 1546               // Haven't created the fracture yet.
 1547               createdFracture = false;
 1548   
 1549               // Insert the first content.
 1550               int i;
 1551   
 1552               recreateLeafs = false;
 1553               if(data[0].getType() == ElementSpec.ContentType) {
 1554                   insertFirstContent(data);
 1555                   pos += data[0].getLength();
 1556                   i = 1;
 1557               }
 1558               else {
 1559                   fractureDeepestLeaf(data);
 1560                   i = 0;
 1561               }
 1562   
 1563               // fold in the specified subtree
 1564               int n = data.length;
 1565               for (; i < n; i++) {
 1566                   insertElement(data[i]);
 1567               }
 1568   
 1569               // Fracture, if we haven't yet.
 1570               if(!createdFracture)
 1571                   fracture(-1);
 1572   
 1573               // pop the remaining path
 1574               while (path.size() != 0) {
 1575                   pop();
 1576               }
 1577   
 1578               // Offset the last index if necessary.
 1579               if(offsetLastIndex && offsetLastIndexOnReplace) {
 1580                   insertPath[insertPath.length - 1].index++;
 1581               }
 1582   
 1583               // Make sure an edit is going to be created for each of the
 1584               // original path items that have a change.
 1585               for(int counter = insertPath.length - 1; counter >= 0;
 1586                   counter--) {
 1587                   ElemChanges change = insertPath[counter];
 1588                   if(change.parent == fracturedParent)
 1589                       change.added.addElement(fracturedChild);
 1590                   if((change.added.size() > 0 ||
 1591                       change.removed.size() > 0) && !changes.contains(change)) {
 1592                       // PENDING(sky): Do I need to worry about order here?
 1593                       changes.addElement(change);
 1594                   }
 1595               }
 1596   
 1597               // An insert at 0 with an initial end implies some elements
 1598               // will have no children (the bottomost leaf would have length 0)
 1599               // this will find what element need to be removed and remove it.
 1600               if (offset == 0 && fracturedParent != null &&
 1601                   data[0].getType() == ElementSpec.EndTagType) {
 1602                   int counter = 0;
 1603                   while (counter < data.length &&
 1604                          data[counter].getType() == ElementSpec.EndTagType) {
 1605                       counter++;
 1606                   }
 1607                   ElemChanges change = insertPath[insertPath.length -
 1608                                                  counter - 1];
 1609                   change.removed.insertElementAt(change.parent.getElement
 1610                                                  (--change.index), 0);
 1611               }
 1612           }
 1613   
 1614           /**
 1615            * Updates the element structure in response to a removal from the
 1616            * associated sequence in the document.  Any elements consumed by the
 1617            * span of the removal are removed.
 1618            */
 1619           protected void removeUpdate() {
 1620               removeElements(root, offset, offset + length);
 1621           }
 1622   
 1623           /**
 1624            * Updates the element structure in response to a change in the
 1625            * document.
 1626            */
 1627           protected void changeUpdate() {
 1628               boolean didEnd = split(offset, length);
 1629               if (! didEnd) {
 1630                   // need to do the other end
 1631                   while (path.size() != 0) {
 1632                       pop();
 1633                   }
 1634                   split(offset + length, 0);
 1635               }
 1636               while (path.size() != 0) {
 1637                   pop();
 1638               }
 1639           }
 1640   
 1641           boolean split(int offs, int len) {
 1642               boolean splitEnd = false;
 1643               // push the path
 1644               Element e = root;
 1645               int index = e.getElementIndex(offs);
 1646               while (! e.isLeaf()) {
 1647                   push(e, index);
 1648                   e = e.getElement(index);
 1649                   index = e.getElementIndex(offs);
 1650               }
 1651   
 1652               ElemChanges ec = path.peek();
 1653               Element child = ec.parent.getElement(ec.index);
 1654               // make sure there is something to do... if the
 1655               // offset is already at a boundary then there is
 1656               // nothing to do.
 1657               if (child.getStartOffset() < offs && offs < child.getEndOffset()) {
 1658                   // we need to split, now see if the other end is within
 1659                   // the same parent.
 1660                   int index0 = ec.index;
 1661                   int index1 = index0;
 1662                   if (((offs + len) < ec.parent.getEndOffset()) && (len != 0)) {
 1663                       // it's a range split in the same parent
 1664                       index1 = ec.parent.getElementIndex(offs+len);
 1665                       if (index1 == index0) {
 1666                           // it's a three-way split
 1667                           ec.removed.addElement(child);
 1668                           e = createLeafElement(ec.parent, child.getAttributes(),
 1669                                                 child.getStartOffset(), offs);
 1670                           ec.added.addElement(e);
 1671                           e = createLeafElement(ec.parent, child.getAttributes(),
 1672                                             offs, offs + len);
 1673                           ec.added.addElement(e);
 1674                           e = createLeafElement(ec.parent, child.getAttributes(),
 1675                                                 offs + len, child.getEndOffset());
 1676                           ec.added.addElement(e);
 1677                           return true;
 1678                       } else {
 1679                           child = ec.parent.getElement(index1);
 1680                           if ((offs + len) == child.getStartOffset()) {
 1681                               // end is already on a boundary
 1682                               index1 = index0;
 1683                           }
 1684                       }
 1685                       splitEnd = true;
 1686                   }
 1687   
 1688                   // split the first location
 1689                   pos = offs;
 1690                   child = ec.parent.getElement(index0);
 1691                   ec.removed.addElement(child);
 1692                   e = createLeafElement(ec.parent, child.getAttributes(),
 1693                                         child.getStartOffset(), pos);
 1694                   ec.added.addElement(e);
 1695                   e = createLeafElement(ec.parent, child.getAttributes(),
 1696                                         pos, child.getEndOffset());
 1697                   ec.added.addElement(e);
 1698   
 1699                   // pick up things in the middle
 1700                   for (int i = index0 + 1; i < index1; i++) {
 1701                       child = ec.parent.getElement(i);
 1702                       ec.removed.addElement(child);
 1703                       ec.added.addElement(child);
 1704                   }
 1705   
 1706                   if (index1 != index0) {
 1707                       child = ec.parent.getElement(index1);
 1708                       pos = offs + len;
 1709                       ec.removed.addElement(child);
 1710                       e = createLeafElement(ec.parent, child.getAttributes(),
 1711                                             child.getStartOffset(), pos);
 1712                       ec.added.addElement(e);
 1713                       e = createLeafElement(ec.parent, child.getAttributes(),
 1714                                             pos, child.getEndOffset());
 1715                       ec.added.addElement(e);
 1716                   }
 1717               }
 1718               return splitEnd;
 1719           }
 1720   
 1721           /**
 1722            * Creates the UndoableEdit record for the edits made
 1723            * in the buffer.
 1724            */
 1725           void endEdits(DefaultDocumentEvent de) {
 1726               int n = changes.size();
 1727               for (int i = 0; i < n; i++) {
 1728                   ElemChanges ec = changes.elementAt(i);
 1729                   Element[] removed = new Element[ec.removed.size()];
 1730                   ec.removed.copyInto(removed);
 1731                   Element[] added = new Element[ec.added.size()];
 1732                   ec.added.copyInto(added);
 1733                   int index = ec.index;
 1734                   ((BranchElement) ec.parent).replace(index, removed.length, added);
 1735                   ElementEdit ee = new ElementEdit(ec.parent, index, removed, added);
 1736                   de.addEdit(ee);
 1737               }
 1738   
 1739               changes.removeAllElements();
 1740               path.removeAllElements();
 1741   
 1742               /*
 1743               for (int i = 0; i < n; i++) {
 1744                   ElemChanges ec = (ElemChanges) changes.elementAt(i);
 1745                   System.err.print("edited: " + ec.parent + " at: " + ec.index +
 1746                       " removed " + ec.removed.size());
 1747                   if (ec.removed.size() > 0) {
 1748                       int r0 = ((Element) ec.removed.firstElement()).getStartOffset();
 1749                       int r1 = ((Element) ec.removed.lastElement()).getEndOffset();
 1750                       System.err.print("[" + r0 + "," + r1 + "]");
 1751                   }
 1752                   System.err.print(" added " + ec.added.size());
 1753                   if (ec.added.size() > 0) {
 1754                       int p0 = ((Element) ec.added.firstElement()).getStartOffset();
 1755                       int p1 = ((Element) ec.added.lastElement()).getEndOffset();
 1756                       System.err.print("[" + p0 + "," + p1 + "]");
 1757                   }
 1758                   System.err.println("");
 1759               }
 1760               */
 1761           }
 1762   
 1763           /**
 1764            * Initialize the buffer
 1765            */
 1766           void beginEdits(int offset, int length) {
 1767               this.offset = offset;
 1768               this.length = length;
 1769               this.endOffset = offset + length;
 1770               pos = offset;
 1771               if (changes == null) {
 1772                   changes = new Vector<ElemChanges>();
 1773               } else {
 1774                   changes.removeAllElements();
 1775               }
 1776               if (path == null) {
 1777                   path = new Stack<ElemChanges>();
 1778               } else {
 1779                   path.removeAllElements();
 1780               }
 1781               fracturedParent = null;
 1782               fracturedChild = null;
 1783               offsetLastIndex = offsetLastIndexOnReplace = false;
 1784           }
 1785   
 1786           /**
 1787            * Pushes a new element onto the stack that represents
 1788            * the current path.
 1789            * @param record Whether or not the push should be
 1790            *  recorded as an element change or not.
 1791            * @param isFracture true if pushing on an element that was created
 1792            * as the result of a fracture.
 1793            */
 1794           void push(Element e, int index, boolean isFracture) {
 1795               ElemChanges ec = new ElemChanges(e, index, isFracture);
 1796               path.push(ec);
 1797           }
 1798   
 1799           void push(Element e, int index) {
 1800               push(e, index, false);
 1801           }
 1802   
 1803           void pop() {
 1804               ElemChanges ec = path.peek();
 1805               path.pop();
 1806               if ((ec.added.size() > 0) || (ec.removed.size() > 0)) {
 1807                   changes.addElement(ec);
 1808               } else if (! path.isEmpty()) {
 1809                   Element e = ec.parent;
 1810                   if(e.getElementCount() == 0) {
 1811                       // if we pushed a branch element that didn't get
 1812                       // used, make sure its not marked as having been added.
 1813                       ec = path.peek();
 1814                       ec.added.removeElement(e);
 1815                   }
 1816               }
 1817           }
 1818   
 1819           /**
 1820            * move the current offset forward by n.
 1821            */
 1822           void advance(int n) {
 1823               pos += n;
 1824           }
 1825   
 1826           void insertElement(ElementSpec es) {
 1827               ElemChanges ec = path.peek();
 1828               switch(es.getType()) {
 1829               case ElementSpec.StartTagType:
 1830                   switch(es.getDirection()) {
 1831                   case ElementSpec.JoinNextDirection:
 1832                       // Don't create a new element, use the existing one
 1833                       // at the specified location.
 1834                       Element parent = ec.parent.getElement(ec.index);
 1835   
 1836                       if(parent.isLeaf()) {
 1837                           // This happens if inserting into a leaf, followed
 1838                           // by a join next where next sibling is not a leaf.
 1839                           if((ec.index + 1) < ec.parent.getElementCount())
 1840                               parent = ec.parent.getElement(ec.index + 1);
 1841                           else
 1842                               throw new StateInvariantError("Join next to leaf");
 1843                       }
 1844                       // Not really a fracture, but need to treat it like
 1845                       // one so that content join next will work correctly.
 1846                       // We can do this because there will never be a join
 1847                       // next followed by a join fracture.
 1848                       push(parent, 0, true);
 1849                       break;
 1850                   case ElementSpec.JoinFractureDirection:
 1851                       if(!createdFracture) {
 1852                           // Should always be something on the stack!
 1853                           fracture(path.size() - 1);
 1854                       }
 1855                       // If parent isn't a fracture, fracture will be
 1856                       // fracturedChild.
 1857                       if(!ec.isFracture) {
 1858                           push(fracturedChild, 0, true);
 1859                       }
 1860                       else
 1861                           // Parent is a fracture, use 1st element.
 1862                           push(ec.parent.getElement(0), 0, true);
 1863                       break;
 1864                   default:
 1865                       Element belem = createBranchElement(ec.parent,
 1866                                                           es.getAttributes());
 1867                       ec.added.addElement(belem);
 1868                       push(belem, 0);
 1869                       break;
 1870                   }
 1871                   break;
 1872               case ElementSpec.EndTagType:
 1873                   pop();
 1874                   break;
 1875               case ElementSpec.ContentType:
 1876                 int len = es.getLength();
 1877                   if (es.getDirection() != ElementSpec.JoinNextDirection) {
 1878                       Element leaf = createLeafElement(ec.parent, es.getAttributes(),
 1879                                                        pos, pos + len);
 1880                       ec.added.addElement(leaf);
 1881                   }
 1882                   else {
 1883                       // JoinNext on tail is only applicable if last element
 1884                       // and attributes come from that of first element.
 1885                       // With a little extra testing it would be possible
 1886                       // to NOT due this again, as more than likely fracture()
 1887                       // created this element.
 1888                       if(!ec.isFracture) {
 1889                           Element first = null;
 1890                           if(insertPath != null) {
 1891                               for(int counter = insertPath.length - 1;
 1892                                   counter >= 0; counter--) {
 1893                                   if(insertPath[counter] == ec) {
 1894                                       if(counter != (insertPath.length - 1))
 1895                                           first = ec.parent.getElement(ec.index);
 1896                                       break;
 1897                                   }
 1898                               }
 1899                           }
 1900                           if(first == null)
 1901                               first = ec.parent.getElement(ec.index + 1);
 1902                           Element leaf = createLeafElement(ec.parent, first.
 1903                                    getAttributes(), pos, first.getEndOffset());
 1904                           ec.added.addElement(leaf);
 1905                           ec.removed.addElement(first);
 1906                       }
 1907                       else {
 1908                           // Parent was fractured element.
 1909                           Element first = ec.parent.getElement(0);
 1910                           Element leaf = createLeafElement(ec.parent, first.
 1911                                    getAttributes(), pos, first.getEndOffset());
 1912                           ec.added.addElement(leaf);
 1913                           ec.removed.addElement(first);
 1914                       }
 1915                   }
 1916                   pos += len;
 1917                   break;
 1918               }
 1919           }
 1920   
 1921           /**
 1922            * Remove the elements from <code>elem</code> in range
 1923            * <code>rmOffs0</code>, <code>rmOffs1</code>. This uses
 1924            * <code>canJoin</code> and <code>join</code> to handle joining
 1925            * the endpoints of the insertion.
 1926            *
 1927            * @return true if elem will no longer have any elements.
 1928            */
 1929           boolean removeElements(Element elem, int rmOffs0, int rmOffs1) {
 1930               if (! elem.isLeaf()) {
 1931                   // update path for changes
 1932                   int index0 = elem.getElementIndex(rmOffs0);
 1933                   int index1 = elem.getElementIndex(rmOffs1);
 1934                   push(elem, index0);
 1935                   ElemChanges ec = path.peek();
 1936   
 1937                   // if the range is contained by one element,
 1938                   // we just forward the request
 1939                   if (index0 == index1) {
 1940                       Element child0 = elem.getElement(index0);
 1941                       if(rmOffs0 <= child0.getStartOffset() &&
 1942                          rmOffs1 >= child0.getEndOffset()) {
 1943                           // Element totally removed.
 1944                           ec.removed.addElement(child0);
 1945                       }
 1946                       else if(removeElements(child0, rmOffs0, rmOffs1)) {
 1947                           ec.removed.addElement(child0);
 1948                       }
 1949                   } else {
 1950                       // the removal range spans elements.  If we can join
 1951                       // the two endpoints, do it.  Otherwise we remove the
 1952                       // interior and forward to the endpoints.
 1953                       Element child0 = elem.getElement(index0);
 1954                       Element child1 = elem.getElement(index1);
 1955                       boolean containsOffs1 = (rmOffs1 < elem.getEndOffset());
 1956                       if (containsOffs1 && canJoin(child0, child1)) {
 1957                           // remove and join
 1958                           for (int i = index0; i <= index1; i++) {
 1959                               ec.removed.addElement(elem.getElement(i));
 1960                           }
 1961                           Element e = join(elem, child0, child1, rmOffs0, rmOffs1);
 1962                           ec.added.addElement(e);
 1963                       } else {
 1964                           // remove interior and forward
 1965                           int rmIndex0 = index0 + 1;
 1966                           int rmIndex1 = index1 - 1;
 1967                           if (child0.getStartOffset() == rmOffs0 ||
 1968                               (index0 == 0 &&
 1969                                child0.getStartOffset() > rmOffs0 &&
 1970                                child0.getEndOffset() <= rmOffs1)) {
 1971                               // start element completely consumed
 1972                               child0 = null;
 1973                               rmIndex0 = index0;
 1974                           }
 1975                           if (!containsOffs1) {
 1976                               child1 = null;
 1977                               rmIndex1++;
 1978                           }
 1979                           else if (child1.getStartOffset() == rmOffs1) {
 1980                               // end element not touched
 1981                               child1 = null;
 1982                           }
 1983                           if (rmIndex0 <= rmIndex1) {
 1984                               ec.index = rmIndex0;
 1985                           }
 1986                           for (int i = rmIndex0; i <= rmIndex1; i++) {
 1987                               ec.removed.addElement(elem.getElement(i));
 1988                           }
 1989                           if (child0 != null) {
 1990                               if(removeElements(child0, rmOffs0, rmOffs1)) {
 1991                                   ec.removed.insertElementAt(child0, 0);
 1992                                   ec.index = index0;
 1993                               }
 1994                           }
 1995                           if (child1 != null) {
 1996                               if(removeElements(child1, rmOffs0, rmOffs1)) {
 1997                                   ec.removed.addElement(child1);
 1998                               }
 1999                           }
 2000                       }
 2001                   }
 2002   
 2003                   // publish changes
 2004                   pop();
 2005   
 2006                   // Return true if we no longer have any children.
 2007                   if(elem.getElementCount() == (ec.removed.size() -
 2008                                                 ec.added.size())) {
 2009                       return true;
 2010                   }
 2011               }
 2012               return false;
 2013           }
 2014   
 2015           /**
 2016            * Can the two given elements be coelesced together
 2017            * into one element?
 2018            */
 2019           boolean canJoin(Element e0, Element e1) {
 2020               if ((e0 == null) || (e1 == null)) {
 2021                   return false;
 2022               }
 2023               // Don't join a leaf to a branch.
 2024               boolean leaf0 = e0.isLeaf();
 2025               boolean leaf1 = e1.isLeaf();
 2026               if(leaf0 != leaf1) {
 2027                   return false;
 2028               }
 2029               if (leaf0) {
 2030                   // Only join leaves if the attributes match, otherwise
 2031                   // style information will be lost.
 2032                   return e0.getAttributes().isEqual(e1.getAttributes());
 2033               }
 2034               // Only join non-leafs if the names are equal. This may result
 2035               // in loss of style information, but this is typically acceptable
 2036               // for non-leafs.
 2037               String name0 = e0.getName();
 2038               String name1 = e1.getName();
 2039               if (name0 != null) {
 2040                   return name0.equals(name1);
 2041               }
 2042               if (name1 != null) {
 2043                   return name1.equals(name0);
 2044               }
 2045               // Both names null, treat as equal.
 2046               return true;
 2047           }
 2048   
 2049           /**
 2050            * Joins the two elements carving out a hole for the
 2051            * given removed range.
 2052            */
 2053           Element join(Element p, Element left, Element right, int rmOffs0, int rmOffs1) {
 2054               if (left.isLeaf() && right.isLeaf()) {
 2055                   return createLeafElement(p, left.getAttributes(), left.getStartOffset(),
 2056                                            right.getEndOffset());
 2057               } else if ((!left.isLeaf()) && (!right.isLeaf())) {
 2058                   // join two branch elements.  This copies the children before
 2059                   // the removal range on the left element, and after the removal
 2060                   // range on the right element.  The two elements on the edge
 2061                   // are joined if possible and needed.
 2062                   Element to = createBranchElement(p, left.getAttributes());
 2063                   int ljIndex = left.getElementIndex(rmOffs0);
 2064                   int rjIndex = right.getElementIndex(rmOffs1);
 2065                   Element lj = left.getElement(ljIndex);
 2066                   if (lj.getStartOffset() >= rmOffs0) {
 2067                       lj = null;
 2068                   }
 2069                   Element rj = right.getElement(rjIndex);
 2070                   if (rj.getStartOffset() == rmOffs1) {
 2071                       rj = null;
 2072                   }
 2073                   Vector<Element> children = new Vector<Element>();
 2074   
 2075                   // transfer the left
 2076                   for (int i = 0; i < ljIndex; i++) {
 2077                       children.addElement(clone(to, left.getElement(i)));
 2078                   }
 2079   
 2080                   // transfer the join/middle
 2081                   if (canJoin(lj, rj)) {
 2082                       Element e = join(to, lj, rj, rmOffs0, rmOffs1);
 2083                       children.addElement(e);
 2084                   } else {
 2085                       if (lj != null) {
 2086                           children.addElement(cloneAsNecessary(to, lj, rmOffs0, rmOffs1));
 2087                       }
 2088                       if (rj != null) {
 2089                           children.addElement(cloneAsNecessary(to, rj, rmOffs0, rmOffs1));
 2090                       }
 2091                   }
 2092   
 2093                   // transfer the right
 2094                   int n = right.getElementCount();
 2095                   for (int i = (rj == null) ? rjIndex : rjIndex + 1; i < n; i++) {
 2096                       children.addElement(clone(to, right.getElement(i)));
 2097                   }
 2098   
 2099                   // install the children
 2100                   Element[] c = new Element[children.size()];
 2101                   children.copyInto(c);
 2102                   ((BranchElement)to).replace(0, 0, c);
 2103                   return to;
 2104               } else {
 2105                   throw new StateInvariantError(
 2106                       "No support to join leaf element with non-leaf element");
 2107               }
 2108           }
 2109   
 2110           /**
 2111            * Creates a copy of this element, with a different
 2112            * parent.
 2113            *
 2114            * @param parent the parent element
 2115            * @param clonee the element to be cloned
 2116            * @return the copy
 2117            */
 2118           public Element clone(Element parent, Element clonee) {
 2119               if (clonee.isLeaf()) {
 2120                   return createLeafElement(parent, clonee.getAttributes(),
 2121                                            clonee.getStartOffset(),
 2122                                            clonee.getEndOffset());
 2123               }
 2124               Element e = createBranchElement(parent, clonee.getAttributes());
 2125               int n = clonee.getElementCount();
 2126               Element[] children = new Element[n];
 2127               for (int i = 0; i < n; i++) {
 2128                   children[i] = clone(e, clonee.getElement(i));
 2129               }
 2130               ((BranchElement)e).replace(0, 0, children);
 2131               return e;
 2132           }
 2133   
 2134           /**
 2135            * Creates a copy of this element, with a different
 2136            * parent. Children of this element included in the
 2137            * removal range will be discarded.
 2138            */
 2139           Element cloneAsNecessary(Element parent, Element clonee, int rmOffs0, int rmOffs1) {
 2140               if (clonee.isLeaf()) {
 2141                   return createLeafElement(parent, clonee.getAttributes(),
 2142                                            clonee.getStartOffset(),
 2143                                            clonee.getEndOffset());
 2144               }
 2145               Element e = createBranchElement(parent, clonee.getAttributes());
 2146               int n = clonee.getElementCount();
 2147               ArrayList<Element> childrenList = new ArrayList<Element>(n);
 2148               for (int i = 0; i < n; i++) {
 2149                   Element elem = clonee.getElement(i);
 2150                   if (elem.getStartOffset() < rmOffs0 || elem.getEndOffset() > rmOffs1) {
 2151                       childrenList.add(cloneAsNecessary(e, elem, rmOffs0, rmOffs1));
 2152                   }
 2153               }
 2154               Element[] children = new Element[childrenList.size()];
 2155               children = childrenList.toArray(children);
 2156               ((BranchElement)e).replace(0, 0, children);
 2157               return e;
 2158           }
 2159   
 2160           /**
 2161            * Determines if a fracture needs to be performed. A fracture
 2162            * can be thought of as moving the right part of a tree to a
 2163            * new location, where the right part is determined by what has
 2164            * been inserted. <code>depth</code> is used to indicate a
 2165            * JoinToFracture is needed to an element at a depth
 2166            * of <code>depth</code>. Where the root is 0, 1 is the children
 2167            * of the root...
 2168            * <p>This will invoke <code>fractureFrom</code> if it is determined
 2169            * a fracture needs to happen.
 2170            */
 2171           void fracture(int depth) {
 2172               int cLength = insertPath.length;
 2173               int lastIndex = -1;
 2174               boolean needRecreate = recreateLeafs;
 2175               ElemChanges lastChange = insertPath[cLength - 1];
 2176               // Use childAltered to determine when a child has been altered,
 2177               // that is the point of insertion is less than the element count.
 2178               boolean childAltered = ((lastChange.index + 1) <
 2179                                       lastChange.parent.getElementCount());
 2180               int deepestAlteredIndex = (needRecreate) ? cLength : -1;
 2181               int lastAlteredIndex = cLength - 1;
 2182   
 2183               createdFracture = true;
 2184               // Determine where to start recreating from.
 2185               // Start at - 2, as first one is indicated by recreateLeafs and
 2186               // childAltered.
 2187               for(int counter = cLength - 2; counter >= 0; counter--) {
 2188                   ElemChanges change = insertPath[counter];
 2189                   if(change.added.size() > 0 || counter == depth) {
 2190                       lastIndex = counter;
 2191                       if(!needRecreate && childAltered) {
 2192                           needRecreate = true;
 2193                           if(deepestAlteredIndex == -1)
 2194                               deepestAlteredIndex = lastAlteredIndex + 1;
 2195                       }
 2196                   }
 2197                   if(!childAltered && change.index <
 2198                      change.parent.getElementCount()) {
 2199                       childAltered = true;
 2200                       lastAlteredIndex = counter;
 2201                   }
 2202               }
 2203               if(needRecreate) {
 2204                   // Recreate all children to right of parent starting
 2205                   // at lastIndex.
 2206                   if(lastIndex == -1)
 2207                       lastIndex = cLength - 1;
 2208                   fractureFrom(insertPath, lastIndex, deepestAlteredIndex);
 2209               }
 2210           }
 2211   
 2212           /**
 2213            * Recreates the elements to the right of the insertion point.
 2214            * This starts at <code>startIndex</code> in <code>changed</code>,
 2215            * and calls duplicate to duplicate existing elements.
 2216            * This will also duplicate the elements along the insertion
 2217            * point, until a depth of <code>endFractureIndex</code> is
 2218            * reached, at which point only the elements to the right of
 2219            * the insertion point are duplicated.
 2220            */
 2221           void fractureFrom(ElemChanges[] changed, int startIndex,
 2222                             int endFractureIndex) {
 2223               // Recreate the element representing the inserted index.
 2224               ElemChanges change = changed[startIndex];
 2225               Element child;
 2226               Element newChild;
 2227               int changeLength = changed.length;
 2228   
 2229               if((startIndex + 1) == changeLength)
 2230                   child = change.parent.getElement(change.index);
 2231               else
 2232                   child = change.parent.getElement(change.index - 1);
 2233               if(child.isLeaf()) {
 2234                   newChild = createLeafElement(change.parent,
 2235                                  child.getAttributes(), Math.max(endOffset,
 2236                                  child.getStartOffset()), child.getEndOffset());
 2237               }
 2238               else {
 2239                   newChild = createBranchElement(change.parent,
 2240                                                  child.getAttributes());
 2241               }
 2242               fracturedParent = change.parent;
 2243               fracturedChild = newChild;
 2244   
 2245               // Recreate all the elements to the right of the
 2246               // insertion point.
 2247               Element parent = newChild;
 2248   
 2249               while(++startIndex < endFractureIndex) {
 2250                   boolean isEnd = ((startIndex + 1) == endFractureIndex);
 2251                   boolean isEndLeaf = ((startIndex + 1) == changeLength);
 2252   
 2253                   // Create the newChild, a duplicate of the elment at
 2254                   // index. This isn't done if isEnd and offsetLastIndex are true
 2255                   // indicating a join previous was done.
 2256                   change = changed[startIndex];
 2257   
 2258                   // Determine the child to duplicate, won't have to duplicate
 2259                   // if at end of fracture, or offseting index.
 2260                   if(isEnd) {
 2261                       if(offsetLastIndex || !isEndLeaf)
 2262                           child = null;
 2263                       else
 2264                           child = change.parent.getElement(change.index);
 2265                   }
 2266                   else {
 2267                       child = change.parent.getElement(change.index - 1);
 2268                   }
 2269                   // Duplicate it.
 2270                   if(child != null) {
 2271                       if(child.isLeaf()) {
 2272                           newChild = createLeafElement(parent,
 2273                                  child.getAttributes(), Math.max(endOffset,
 2274                                  child.getStartOffset()), child.getEndOffset());
 2275                       }
 2276                       else {
 2277                           newChild = createBranchElement(parent,
 2278                                                      child.getAttributes());
 2279                       }
 2280                   }
 2281                   else
 2282                       newChild = null;
 2283   
 2284                   // Recreate the remaining children (there may be none).
 2285                   int kidsToMove = change.parent.getElementCount() -
 2286                                    change.index;
 2287                   Element[] kids;
 2288                   int moveStartIndex;
 2289                   int kidStartIndex = 1;
 2290   
 2291                   if(newChild == null) {
 2292                       // Last part of fracture.
 2293                       if(isEndLeaf) {
 2294                           kidsToMove--;
 2295                           moveStartIndex = change.index + 1;
 2296                       }
 2297                       else {
 2298                           moveStartIndex = change.index;
 2299                       }
 2300                       kidStartIndex = 0;
 2301                       kids = new Element[kidsToMove];
 2302                   }
 2303                   else {
 2304                       if(!isEnd) {
 2305                           // Branch.
 2306                           kidsToMove++;
 2307                           moveStartIndex = change.index;
 2308                       }
 2309                       else {
 2310                           // Last leaf, need to recreate part of it.
 2311                           moveStartIndex = change.index + 1;
 2312                       }
 2313                       kids = new Element[kidsToMove];
 2314                       kids[0] = newChild;
 2315                   }
 2316   
 2317                   for(int counter = kidStartIndex; counter < kidsToMove;
 2318                       counter++) {
 2319                       Element toMove =change.parent.getElement(moveStartIndex++);
 2320                       kids[counter] = recreateFracturedElement(parent, toMove);
 2321                       change.removed.addElement(toMove);
 2322                   }
 2323                   ((BranchElement)parent).replace(0, 0, kids);
 2324                   parent = newChild;
 2325               }
 2326           }
 2327   
 2328           /**
 2329            * Recreates <code>toDuplicate</code>. This is called when an
 2330            * element needs to be created as the result of an insertion. This
 2331            * will recurse and create all the children. This is similiar to
 2332            * <code>clone</code>, but deteremines the offsets differently.
 2333            */
 2334           Element recreateFracturedElement(Element parent, Element toDuplicate) {
 2335               if(toDuplicate.isLeaf()) {
 2336                   return createLeafElement(parent, toDuplicate.getAttributes(),
 2337                                            Math.max(toDuplicate.getStartOffset(),
 2338                                                     endOffset),
 2339                                            toDuplicate.getEndOffset());
 2340               }
 2341               // Not a leaf
 2342               Element newParent = createBranchElement(parent, toDuplicate.
 2343                                                       getAttributes());
 2344               int childCount = toDuplicate.getElementCount();
 2345               Element[] newKids = new Element[childCount];
 2346               for(int counter = 0; counter < childCount; counter++) {
 2347                   newKids[counter] = recreateFracturedElement(newParent,
 2348                                                toDuplicate.getElement(counter));
 2349               }
 2350               ((BranchElement)newParent).replace(0, 0, newKids);
 2351               return newParent;
 2352           }
 2353   
 2354           /**
 2355            * Splits the bottommost leaf in <code>path</code>.
 2356            * This is called from insert when the first element is NOT content.
 2357            */
 2358           void fractureDeepestLeaf(ElementSpec[] specs) {
 2359               // Split the bottommost leaf. It will be recreated elsewhere.
 2360               ElemChanges ec = path.peek();
 2361               Element child = ec.parent.getElement(ec.index);
 2362               // Inserts at offset 0 do not need to recreate child (it would
 2363               // have a length of 0!).
 2364               if (offset != 0) {
 2365                   Element newChild = createLeafElement(ec.parent,
 2366                                                    child.getAttributes(),
 2367                                                    child.getStartOffset(),
 2368                                                    offset);
 2369   
 2370                   ec.added.addElement(newChild);
 2371               }
 2372               ec.removed.addElement(child);
 2373               if(child.getEndOffset() != endOffset)
 2374                   recreateLeafs = true;
 2375               else
 2376                   offsetLastIndex = true;
 2377           }
 2378   
 2379           /**
 2380            * Inserts the first content. This needs to be separate to handle
 2381            * joining.
 2382            */
 2383           void insertFirstContent(ElementSpec[] specs) {
 2384               ElementSpec firstSpec = specs[0];
 2385               ElemChanges ec = path.peek();
 2386               Element child = ec.parent.getElement(ec.index);
 2387               int firstEndOffset = offset + firstSpec.getLength();
 2388               boolean isOnlyContent = (specs.length == 1);
 2389   
 2390               switch(firstSpec.getDirection()) {
 2391               case ElementSpec.JoinPreviousDirection:
 2392                   if(child.getEndOffset() != firstEndOffset &&
 2393                       !isOnlyContent) {
 2394                       // Create the left split part containing new content.
 2395                       Element newE = createLeafElement(ec.parent,
 2396                               child.getAttributes(), child.getStartOffset(),
 2397                               firstEndOffset);
 2398                       ec.added.addElement(newE);
 2399                       ec.removed.addElement(child);
 2400                       // Remainder will be created later.
 2401                       if(child.getEndOffset() != endOffset)
 2402                           recreateLeafs = true;
 2403                       else
 2404                           offsetLastIndex = true;
 2405                   }
 2406                   else {
 2407                       offsetLastIndex = true;
 2408                       offsetLastIndexOnReplace = true;
 2409                   }
 2410                   // else Inserted at end, and is total length.
 2411                   // Update index incase something added/removed.
 2412                   break;
 2413               case ElementSpec.JoinNextDirection:
 2414                   if(offset != 0) {
 2415                       // Recreate the first element, its offset will have
 2416                       // changed.
 2417                       Element newE = createLeafElement(ec.parent,
 2418                               child.getAttributes(), child.getStartOffset(),
 2419                               offset);
 2420                       ec.added.addElement(newE);
 2421                       // Recreate the second, merge part. We do no checking
 2422                       // to see if JoinNextDirection is valid here!
 2423                       Element nextChild = ec.parent.getElement(ec.index + 1);
 2424                       if(isOnlyContent)
 2425                           newE = createLeafElement(ec.parent, nextChild.
 2426                               getAttributes(), offset, nextChild.getEndOffset());
 2427                       else
 2428                           newE = createLeafElement(ec.parent, nextChild.
 2429                               getAttributes(), offset, firstEndOffset);
 2430                       ec.added.addElement(newE);
 2431                       ec.removed.addElement(child);
 2432                       ec.removed.addElement(nextChild);
 2433                   }
 2434                   // else nothin to do.
 2435                   // PENDING: if !isOnlyContent could raise here!
 2436                   break;
 2437               default:
 2438                   // Inserted into middle, need to recreate split left
 2439                   // new content, and split right.
 2440                   if(child.getStartOffset() != offset) {
 2441                       Element newE = createLeafElement(ec.parent,
 2442                               child.getAttributes(), child.getStartOffset(),
 2443                               offset);
 2444                       ec.added.addElement(newE);
 2445                   }
 2446                   ec.removed.addElement(child);
 2447                   // new content
 2448                   Element newE = createLeafElement(ec.parent,
 2449                                                    firstSpec.getAttributes(),
 2450                                                    offset, firstEndOffset);
 2451                   ec.added.addElement(newE);
 2452                   if(child.getEndOffset() != endOffset) {
 2453                       // Signals need to recreate right split later.
 2454                       recreateLeafs = true;
 2455                   }
 2456                   else {
 2457                       offsetLastIndex = true;
 2458                   }
 2459                   break;
 2460               }
 2461           }
 2462   
 2463           Element root;
 2464           transient int pos;          // current position
 2465           transient int offset;
 2466           transient int length;
 2467           transient int endOffset;
 2468           transient Vector<ElemChanges> changes;
 2469           transient Stack<ElemChanges> path;
 2470           transient boolean insertOp;
 2471   
 2472           transient boolean recreateLeafs; // For insert.
 2473   
 2474           /** For insert, path to inserted elements. */
 2475           transient ElemChanges[] insertPath;
 2476           /** Only for insert, set to true when the fracture has been created. */
 2477           transient boolean createdFracture;
 2478           /** Parent that contains the fractured child. */
 2479           transient Element fracturedParent;
 2480           /** Fractured child. */
 2481           transient Element fracturedChild;
 2482           /** Used to indicate when fracturing that the last leaf should be
 2483            * skipped. */
 2484           transient boolean offsetLastIndex;
 2485           /** Used to indicate that the parent of the deepest leaf should
 2486            * offset the index by 1 when adding/removing elements in an
 2487            * insert. */
 2488           transient boolean offsetLastIndexOnReplace;
 2489   
 2490           /*
 2491            * Internal record used to hold element change specifications
 2492            */
 2493           class ElemChanges {
 2494   
 2495               ElemChanges(Element parent, int index, boolean isFracture) {
 2496                   this.parent = parent;
 2497                   this.index = index;
 2498                   this.isFracture = isFracture;
 2499                   added = new Vector<Element>();
 2500                   removed = new Vector<Element>();
 2501               }
 2502   
 2503               public String toString() {
 2504                   return "added: " + added + "\nremoved: " + removed + "\n";
 2505               }
 2506   
 2507               Element parent;
 2508               int index;
 2509               Vector<Element> added;
 2510               Vector<Element> removed;
 2511               boolean isFracture;
 2512           }
 2513   
 2514       }
 2515   
 2516       /**
 2517        * An UndoableEdit used to remember AttributeSet changes to an
 2518        * Element.
 2519        */
 2520       public static class AttributeUndoableEdit extends AbstractUndoableEdit {
 2521           public AttributeUndoableEdit(Element element, AttributeSet newAttributes,
 2522                                 boolean isReplacing) {
 2523               super();
 2524               this.element = element;
 2525               this.newAttributes = newAttributes;
 2526               this.isReplacing = isReplacing;
 2527               // If not replacing, it may be more efficient to only copy the
 2528               // changed values...
 2529               copy = element.getAttributes().copyAttributes();
 2530           }
 2531   
 2532           /**
 2533            * Redoes a change.
 2534            *
 2535            * @exception CannotRedoException if the change cannot be redone
 2536            */
 2537           public void redo() throws CannotRedoException {
 2538               super.redo();
 2539               MutableAttributeSet as = (MutableAttributeSet)element
 2540                                        .getAttributes();
 2541               if(isReplacing)
 2542                   as.removeAttributes(as);
 2543               as.addAttributes(newAttributes);
 2544           }
 2545   
 2546           /**
 2547            * Undoes a change.
 2548            *
 2549            * @exception CannotUndoException if the change cannot be undone
 2550            */
 2551           public void undo() throws CannotUndoException {
 2552               super.undo();
 2553               MutableAttributeSet as = (MutableAttributeSet)element.getAttributes();
 2554               as.removeAttributes(as);
 2555               as.addAttributes(copy);
 2556           }
 2557   
 2558           // AttributeSet containing additional entries, must be non-mutable!
 2559           protected AttributeSet newAttributes;
 2560           // Copy of the AttributeSet the Element contained.
 2561           protected AttributeSet copy;
 2562           // true if all the attributes in the element were removed first.
 2563           protected boolean isReplacing;
 2564           // Efected Element.
 2565           protected Element element;
 2566       }
 2567   
 2568       /**
 2569        * UndoableEdit for changing the resolve parent of an Element.
 2570        */
 2571       static class StyleChangeUndoableEdit extends AbstractUndoableEdit {
 2572           public StyleChangeUndoableEdit(AbstractElement element,
 2573                                          Style newStyle) {
 2574               super();
 2575               this.element = element;
 2576               this.newStyle = newStyle;
 2577               oldStyle = element.getResolveParent();
 2578           }
 2579   
 2580           /**
 2581            * Redoes a change.
 2582            *
 2583            * @exception CannotRedoException if the change cannot be redone
 2584            */
 2585           public void redo() throws CannotRedoException {
 2586               super.redo();
 2587               element.setResolveParent(newStyle);
 2588           }
 2589   
 2590           /**
 2591            * Undoes a change.
 2592            *
 2593            * @exception CannotUndoException if the change cannot be undone
 2594            */
 2595           public void undo() throws CannotUndoException {
 2596               super.undo();
 2597               element.setResolveParent(oldStyle);
 2598           }
 2599   
 2600           /** Element to change resolve parent of. */
 2601           protected AbstractElement element;
 2602           /** New style. */
 2603           protected Style newStyle;
 2604           /** Old style, before setting newStyle. */
 2605           protected AttributeSet oldStyle;
 2606       }
 2607   
 2608       /**
 2609        * Base class for style change handlers with support for stale objects detection.
 2610        */
 2611       abstract static class AbstractChangeHandler implements ChangeListener {
 2612   
 2613           /* This has an implicit reference to the handler object.  */
 2614           private class DocReference extends WeakReference<DefaultStyledDocument> {
 2615   
 2616               DocReference(DefaultStyledDocument d, ReferenceQueue<DefaultStyledDocument> q) {
 2617                   super(d, q);
 2618               }
 2619   
 2620               /**
 2621                * Return a reference to the style change handler object.
 2622                */
 2623               ChangeListener getListener() {
 2624                   return AbstractChangeHandler.this;
 2625               }
 2626           }
 2627   
 2628           /** Class-specific reference queues.  */
 2629           private final static Map<Class, ReferenceQueue<DefaultStyledDocument>> queueMap
 2630                   = new HashMap<Class, ReferenceQueue<DefaultStyledDocument>>();
 2631   
 2632           /** A weak reference to the document object.  */
 2633           private DocReference doc;
 2634   
 2635           AbstractChangeHandler(DefaultStyledDocument d) {
 2636               Class c = getClass();
 2637               ReferenceQueue<DefaultStyledDocument> q;
 2638               synchronized (queueMap) {
 2639                   q = queueMap.get(c);
 2640                   if (q == null) {
 2641                       q = new ReferenceQueue<DefaultStyledDocument>();
 2642                       queueMap.put(c, q);
 2643                   }
 2644               }
 2645               doc = new DocReference(d, q);
 2646           }
 2647   
 2648           /**
 2649            * Return a list of stale change listeners.
 2650            *
 2651            * A change listener becomes "stale" when its document is cleaned by GC.
 2652            */
 2653           static List<ChangeListener> getStaleListeners(ChangeListener l) {
 2654               List<ChangeListener> staleListeners = new ArrayList<ChangeListener>();
 2655               ReferenceQueue<DefaultStyledDocument> q = queueMap.get(l.getClass());
 2656   
 2657               if (q != null) {
 2658                   DocReference r;
 2659                   synchronized (q) {
 2660                       while ((r = (DocReference) q.poll()) != null) {
 2661                           staleListeners.add(r.getListener());
 2662                       }
 2663                   }
 2664               }
 2665   
 2666               return staleListeners;
 2667           }
 2668   
 2669           /**
 2670            * The ChangeListener wrapper which guards against dead documents.
 2671            */
 2672           public void stateChanged(ChangeEvent e) {
 2673               DefaultStyledDocument d = doc.get();
 2674               if (d != null) {
 2675                   fireStateChanged(d, e);
 2676               }
 2677           }
 2678   
 2679           /** Run the actual class-specific stateChanged() method.  */
 2680           abstract void fireStateChanged(DefaultStyledDocument d, ChangeEvent e);
 2681       }
 2682   
 2683       /**
 2684        * Added to all the Styles. When instances of this receive a
 2685        * stateChanged method, styleChanged is invoked.
 2686        */
 2687       static class StyleChangeHandler extends AbstractChangeHandler {
 2688   
 2689           StyleChangeHandler(DefaultStyledDocument d) {
 2690               super(d);
 2691           }
 2692   
 2693           void fireStateChanged(DefaultStyledDocument d, ChangeEvent e) {
 2694               Object source = e.getSource();
 2695               if (source instanceof Style) {
 2696                   d.styleChanged((Style) source);
 2697               } else {
 2698                   d.styleChanged(null);
 2699               }
 2700           }
 2701       }
 2702   
 2703   
 2704       /**
 2705        * Added to the StyleContext. When the StyleContext changes, this invokes
 2706        * <code>updateStylesListeningTo</code>.
 2707        */
 2708       static class StyleContextChangeHandler extends AbstractChangeHandler {
 2709   
 2710           StyleContextChangeHandler(DefaultStyledDocument d) {
 2711               super(d);
 2712           }
 2713   
 2714           void fireStateChanged(DefaultStyledDocument d, ChangeEvent e) {
 2715               d.updateStylesListeningTo();
 2716           }
 2717       }
 2718   
 2719   
 2720       /**
 2721        * When run this creates a change event for the complete document
 2722        * and fires it.
 2723        */
 2724       class ChangeUpdateRunnable implements Runnable {
 2725           boolean isPending = false;
 2726   
 2727           public void run() {
 2728               synchronized(this) {
 2729                   isPending = false;
 2730               }
 2731   
 2732               try {
 2733                   writeLock();
 2734                   DefaultDocumentEvent dde = new DefaultDocumentEvent(0,
 2735                                                 getLength(),
 2736                                                 DocumentEvent.EventType.CHANGE);
 2737                   dde.end();
 2738                   fireChangedUpdate(dde);
 2739               } finally {
 2740                   writeUnlock();
 2741               }
 2742           }
 2743       }
 2744   }

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