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