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