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 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                 @SuppressWarnings("unchecked")
1053                 Vector<Style> v = (Vector<Style>)listeningStyles.clone();
1054                 listeningStyles.removeAllElements();
1055                 List<ChangeListener> staleListeners =
1056                     AbstractChangeHandler.getStaleListeners(styleChangeListener);
1057                 while (styleNames.hasMoreElements()) {
1058                     String name = (String)styleNames.nextElement();
1059                     Style aStyle = styles.getStyle(name);
1060                     int index = v.indexOf(aStyle);
1061                     listeningStyles.addElement(aStyle);
1062                     if (index == -1) {
1063                         for (ChangeListener l: staleListeners) {
1064                             aStyle.removeChangeListener(l);
1065                         }
1066                         aStyle.addChangeListener(styleChangeListener);
1067                     }
1068                     else {
1069                         v.removeElementAt(index);
1070                     }
1071                 }
1072                 for (int counter = v.size() - 1; counter >= 0; counter--) {
1073                     Style aStyle = v.elementAt(counter);
1074                     aStyle.removeChangeListener(styleChangeListener);
1075                 }
1076                 if (listeningStyles.size() == 0) {
1077                     styleChangeListener = null;
1078                 }
1079             }
1080         }
1081     }
1082 
1083     private void readObject(ObjectInputStream s)
1084             throws ClassNotFoundException, IOException {
1085         listeningStyles = new Vector<>();
1086         ObjectInputStream.GetField f = s.readFields();
1087         buffer = (ElementBuffer) f.get("buffer", null);
1088         // Reinstall style listeners.
1089         if (styleContextChangeListener == null &&
1090             listenerList.getListenerCount(DocumentListener.class) > 0) {
1091             styleContextChangeListener = createStyleContextChangeListener();
1092             if (styleContextChangeListener != null) {
1093                 StyleContext styles = (StyleContext)getAttributeContext();
1094                 styles.addChangeListener(styleContextChangeListener);
1095             }
1096             updateStylesListeningTo();
1097         }
1098     }
1099 
1100     // --- member variables -----------------------------------------------------------
1101 
1102     /**
1103      * The default size of the initial content buffer.
1104      */
1105     public static final int BUFFER_SIZE_DEFAULT = 4096;
1106 
1107     protected ElementBuffer buffer;
1108 
1109     /** Styles listening to. */
1110     private transient Vector<Style> listeningStyles;
1111 
1112     /** Listens to Styles. */
1113     private transient ChangeListener styleChangeListener;
1114 
1115     /** Listens to Styles. */
1116     private transient ChangeListener styleContextChangeListener;
1117 
1118     /** Run to create a change event for the document */
1119     private transient ChangeUpdateRunnable updateRunnable;
1120 
1121     /**
1122      * Default root element for a document... maps out the
1123      * paragraphs/lines contained.
1124      * <p>
1125      * <strong>Warning:</strong>
1126      * Serialized objects of this class will not be compatible with
1127      * future Swing releases. The current serialization support is
1128      * appropriate for short term storage or RMI between applications running
1129      * the same version of Swing.  As of 1.4, support for long term storage
1130      * of all JavaBeans&trade;
1131      * has been added to the <code>java.beans</code> package.
1132      * Please see {@link java.beans.XMLEncoder}.
1133      */
1134     @SuppressWarnings("serial") // Same-version serialization only
1135     protected class SectionElement extends BranchElement {
1136 
1137         /**
1138          * Creates a new SectionElement.
1139          */
1140         public SectionElement() {
1141             super(null, null);
1142         }
1143 
1144         /**
1145          * Gets the name of the element.
1146          *
1147          * @return the name
1148          */
1149         public String getName() {
1150             return SectionElementName;
1151         }
1152     }
1153 
1154     /**
1155      * Specification for building elements.
1156      * <p>
1157      * <strong>Warning:</strong>
1158      * Serialized objects of this class will not be compatible with
1159      * future Swing releases. The current serialization support is
1160      * appropriate for short term storage or RMI between applications running
1161      * the same version of Swing.  As of 1.4, support for long term storage
1162      * of all JavaBeans&trade;
1163      * has been added to the <code>java.beans</code> package.
1164      * Please see {@link java.beans.XMLEncoder}.
1165      */
1166     @SuppressWarnings("serial") // Same-version serialization only
1167     public static class ElementSpec {
1168 
1169         /**
1170          * A possible value for getType.  This specifies
1171          * that this record type is a start tag and
1172          * represents markup that specifies the start
1173          * of an element.
1174          */
1175         public static final short StartTagType = 1;
1176 
1177         /**
1178          * A possible value for getType.  This specifies
1179          * that this record type is a end tag and
1180          * represents markup that specifies the end
1181          * of an element.
1182          */
1183         public static final short EndTagType = 2;
1184 
1185         /**
1186          * A possible value for getType.  This specifies
1187          * that this record type represents content.
1188          */
1189         public static final short ContentType = 3;
1190 
1191         /**
1192          * A possible value for getDirection.  This specifies
1193          * that the data associated with this record should
1194          * be joined to what precedes it.
1195          */
1196         public static final short JoinPreviousDirection = 4;
1197 
1198         /**
1199          * A possible value for getDirection.  This specifies
1200          * that the data associated with this record should
1201          * be joined to what follows it.
1202          */
1203         public static final short JoinNextDirection = 5;
1204 
1205         /**
1206          * A possible value for getDirection.  This specifies
1207          * that the data associated with this record should
1208          * be used to originate a new element.  This would be
1209          * the normal value.
1210          */
1211         public static final short OriginateDirection = 6;
1212 
1213         /**
1214          * A possible value for getDirection.  This specifies
1215          * that the data associated with this record should
1216          * be joined to the fractured element.
1217          */
1218         public static final short JoinFractureDirection = 7;
1219 
1220 
1221         /**
1222          * Constructor useful for markup when the markup will not
1223          * be stored in the document.
1224          *
1225          * @param a the attributes for the element
1226          * @param type the type of the element (StartTagType, EndTagType,
1227          *  ContentType)
1228          */
1229         public ElementSpec(AttributeSet a, short type) {
1230             this(a, type, null, 0, 0);
1231         }
1232 
1233         /**
1234          * Constructor for parsing inside the document when
1235          * the data has already been added, but len information
1236          * is needed.
1237          *
1238          * @param a the attributes for the element
1239          * @param type the type of the element (StartTagType, EndTagType,
1240          *  ContentType)
1241          * @param len the length &gt;= 0
1242          */
1243         public ElementSpec(AttributeSet a, short type, int len) {
1244             this(a, type, null, 0, len);
1245         }
1246 
1247         /**
1248          * Constructor for creating a spec externally for batch
1249          * input of content and markup into the document.
1250          *
1251          * @param a the attributes for the element
1252          * @param type the type of the element (StartTagType, EndTagType,
1253          *  ContentType)
1254          * @param txt the text for the element
1255          * @param offs the offset into the text &gt;= 0
1256          * @param len the length of the text &gt;= 0
1257          */
1258         public ElementSpec(AttributeSet a, short type, char[] txt,
1259                                   int offs, int len) {
1260             attr = a;
1261             this.type = type;
1262             this.data = txt;
1263             this.offs = offs;
1264             this.len = len;
1265             this.direction = OriginateDirection;
1266         }
1267 
1268         /**
1269          * Sets the element type.
1270          *
1271          * @param type the type of the element (StartTagType, EndTagType,
1272          *  ContentType)
1273          */
1274         public void setType(short type) {
1275             this.type = type;
1276         }
1277 
1278         /**
1279          * Gets the element type.
1280          *
1281          * @return  the type of the element (StartTagType, EndTagType,
1282          *  ContentType)
1283          */
1284         public short getType() {
1285             return type;
1286         }
1287 
1288         /**
1289          * Sets the direction.
1290          *
1291          * @param direction the direction (JoinPreviousDirection,
1292          *   JoinNextDirection)
1293          */
1294         public void setDirection(short direction) {
1295             this.direction = direction;
1296         }
1297 
1298         /**
1299          * Gets the direction.
1300          *
1301          * @return the direction (JoinPreviousDirection, JoinNextDirection)
1302          */
1303         public short getDirection() {
1304             return direction;
1305         }
1306 
1307         /**
1308          * Gets the element attributes.
1309          *
1310          * @return the attribute set
1311          */
1312         public AttributeSet getAttributes() {
1313             return attr;
1314         }
1315 
1316         /**
1317          * Gets the array of characters.
1318          *
1319          * @return the array
1320          */
1321         public char[] getArray() {
1322             return data;
1323         }
1324 
1325 
1326         /**
1327          * Gets the starting offset.
1328          *
1329          * @return the offset &gt;= 0
1330          */
1331         public int getOffset() {
1332             return offs;
1333         }
1334 
1335         /**
1336          * Gets the length.
1337          *
1338          * @return the length &gt;= 0
1339          */
1340         public int getLength() {
1341             return len;
1342         }
1343 
1344         /**
1345          * Converts the element to a string.
1346          *
1347          * @return the string
1348          */
1349         public String toString() {
1350             String tlbl = "??";
1351             String plbl = "??";
1352             switch(type) {
1353             case StartTagType:
1354                 tlbl = "StartTag";
1355                 break;
1356             case ContentType:
1357                 tlbl = "Content";
1358                 break;
1359             case EndTagType:
1360                 tlbl = "EndTag";
1361                 break;
1362             }
1363             switch(direction) {
1364             case JoinPreviousDirection:
1365                 plbl = "JoinPrevious";
1366                 break;
1367             case JoinNextDirection:
1368                 plbl = "JoinNext";
1369                 break;
1370             case OriginateDirection:
1371                 plbl = "Originate";
1372                 break;
1373             case JoinFractureDirection:
1374                 plbl = "Fracture";
1375                 break;
1376             }
1377             return tlbl + ":" + plbl + ":" + getLength();
1378         }
1379 
1380         private AttributeSet attr;
1381         private int len;
1382         private short type;
1383         private short direction;
1384 
1385         private int offs;
1386         private char[] data;
1387     }
1388 
1389     /**
1390      * Class to manage changes to the element
1391      * hierarchy.
1392      * <p>
1393      * <strong>Warning:</strong>
1394      * Serialized objects of this class will not be compatible with
1395      * future Swing releases. The current serialization support is
1396      * appropriate for short term storage or RMI between applications running
1397      * the same version of Swing.  As of 1.4, support for long term storage
1398      * of all JavaBeans&trade;
1399      * has been added to the <code>java.beans</code> package.
1400      * Please see {@link java.beans.XMLEncoder}.
1401      */
1402     @SuppressWarnings("serial") // Same-version serialization only
1403     public class ElementBuffer implements Serializable {
1404 
1405         /**
1406          * Creates a new ElementBuffer.
1407          *
1408          * @param root the root element
1409          * @since 1.4
1410          */
1411         public ElementBuffer(Element root) {
1412             this.root = root;
1413             changes = new Vector<ElemChanges>();
1414             path = new Stack<ElemChanges>();
1415         }
1416 
1417         /**
1418          * Gets the root element.
1419          *
1420          * @return the root element
1421          */
1422         public Element getRootElement() {
1423             return root;
1424         }
1425 
1426         /**
1427          * Inserts new content.
1428          *
1429          * @param offset the starting offset &gt;= 0
1430          * @param length the length &gt;= 0
1431          * @param data the data to insert
1432          * @param de the event capturing this edit
1433          */
1434         public void insert(int offset, int length, ElementSpec[] data,
1435                                  DefaultDocumentEvent de) {
1436             if (length == 0) {
1437                 // Nothing was inserted, no structure change.
1438                 return;
1439             }
1440             insertOp = true;
1441             beginEdits(offset, length);
1442             insertUpdate(data);
1443             endEdits(de);
1444 
1445             insertOp = false;
1446         }
1447 
1448         void create(int length, ElementSpec[] data, DefaultDocumentEvent de) {
1449             insertOp = true;
1450             beginEdits(offset, length);
1451 
1452             // PENDING(prinz) this needs to be fixed to create a new
1453             // root element as well, but requires changes to the
1454             // DocumentEvent to inform the views that there is a new
1455             // root element.
1456 
1457             // Recreate the ending fake element to have the correct offsets.
1458             Element elem = root;
1459             int index = elem.getElementIndex(0);
1460             while (! elem.isLeaf()) {
1461                 Element child = elem.getElement(index);
1462                 push(elem, index);
1463                 elem = child;
1464                 index = elem.getElementIndex(0);
1465             }
1466             ElemChanges ec = path.peek();
1467             Element child = ec.parent.getElement(ec.index);
1468             ec.added.addElement(createLeafElement(ec.parent,
1469                                 child.getAttributes(), getLength(),
1470                                 child.getEndOffset()));
1471             ec.removed.addElement(child);
1472             while (path.size() > 1) {
1473                 pop();
1474             }
1475 
1476             int n = data.length;
1477 
1478             // Reset the root elements attributes.
1479             AttributeSet newAttrs = null;
1480             if (n > 0 && data[0].getType() == ElementSpec.StartTagType) {
1481                 newAttrs = data[0].getAttributes();
1482             }
1483             if (newAttrs == null) {
1484                 newAttrs = SimpleAttributeSet.EMPTY;
1485             }
1486             MutableAttributeSet attr = (MutableAttributeSet)root.
1487                                        getAttributes();
1488             de.addEdit(new AttributeUndoableEdit(root, newAttrs, true));
1489             attr.removeAttributes(attr);
1490             attr.addAttributes(newAttrs);
1491 
1492             // fold in the specified subtree
1493             for (int i = 1; i < n; i++) {
1494                 insertElement(data[i]);
1495             }
1496 
1497             // pop the remaining path
1498             while (path.size() != 0) {
1499                 pop();
1500             }
1501 
1502             endEdits(de);
1503             insertOp = false;
1504         }
1505 
1506         /**
1507          * Removes content.
1508          *
1509          * @param offset the starting offset &gt;= 0
1510          * @param length the length &gt;= 0
1511          * @param de the event capturing this edit
1512          */
1513         public void remove(int offset, int length, DefaultDocumentEvent de) {
1514             beginEdits(offset, length);
1515             removeUpdate();
1516             endEdits(de);
1517         }
1518 
1519         /**
1520          * Changes content.
1521          *
1522          * @param offset the starting offset &gt;= 0
1523          * @param length the length &gt;= 0
1524          * @param de the event capturing this edit
1525          */
1526         public void change(int offset, int length, DefaultDocumentEvent de) {
1527             beginEdits(offset, length);
1528             changeUpdate();
1529             endEdits(de);
1530         }
1531 
1532         /**
1533          * Inserts an update into the document.
1534          *
1535          * @param data the elements to insert
1536          */
1537         protected void insertUpdate(ElementSpec[] data) {
1538             // push the path
1539             Element elem = root;
1540             int index = elem.getElementIndex(offset);
1541             while (! elem.isLeaf()) {
1542                 Element child = elem.getElement(index);
1543                 push(elem, (child.isLeaf() ? index : index+1));
1544                 elem = child;
1545                 index = elem.getElementIndex(offset);
1546             }
1547 
1548             // Build a copy of the original path.
1549             insertPath = new ElemChanges[path.size()];
1550             path.copyInto(insertPath);
1551 
1552             // Haven't created the fracture yet.
1553             createdFracture = false;
1554 
1555             // Insert the first content.
1556             int i;
1557 
1558             recreateLeafs = false;
1559             if(data[0].getType() == ElementSpec.ContentType) {
1560                 insertFirstContent(data);
1561                 pos += data[0].getLength();
1562                 i = 1;
1563             }
1564             else {
1565                 fractureDeepestLeaf(data);
1566                 i = 0;
1567             }
1568 
1569             // fold in the specified subtree
1570             int n = data.length;
1571             for (; i < n; i++) {
1572                 insertElement(data[i]);
1573             }
1574 
1575             // Fracture, if we haven't yet.
1576             if(!createdFracture)
1577                 fracture(-1);
1578 
1579             // pop the remaining path
1580             while (path.size() != 0) {
1581                 pop();
1582             }
1583 
1584             // Offset the last index if necessary.
1585             if(offsetLastIndex && offsetLastIndexOnReplace) {
1586                 insertPath[insertPath.length - 1].index++;
1587             }
1588 
1589             // Make sure an edit is going to be created for each of the
1590             // original path items that have a change.
1591             for(int counter = insertPath.length - 1; counter >= 0;
1592                 counter--) {
1593                 ElemChanges change = insertPath[counter];
1594                 if(change.parent == fracturedParent)
1595                     change.added.addElement(fracturedChild);
1596                 if((change.added.size() > 0 ||
1597                     change.removed.size() > 0) && !changes.contains(change)) {
1598                     // PENDING(sky): Do I need to worry about order here?
1599                     changes.addElement(change);
1600                 }
1601             }
1602 
1603             // An insert at 0 with an initial end implies some elements
1604             // will have no children (the bottomost leaf would have length 0)
1605             // this will find what element need to be removed and remove it.
1606             if (offset == 0 && fracturedParent != null &&
1607                 data[0].getType() == ElementSpec.EndTagType) {
1608                 int counter = 0;
1609                 while (counter < data.length &&
1610                        data[counter].getType() == ElementSpec.EndTagType) {
1611                     counter++;
1612                 }
1613                 ElemChanges change = insertPath[insertPath.length -
1614                                                counter - 1];
1615                 change.removed.insertElementAt(change.parent.getElement
1616                                                (--change.index), 0);
1617             }
1618         }
1619 
1620         /**
1621          * Updates the element structure in response to a removal from the
1622          * associated sequence in the document.  Any elements consumed by the
1623          * span of the removal are removed.
1624          */
1625         protected void removeUpdate() {
1626             removeElements(root, offset, offset + length);
1627         }
1628 
1629         /**
1630          * Updates the element structure in response to a change in the
1631          * document.
1632          */
1633         protected void changeUpdate() {
1634             boolean didEnd = split(offset, length);
1635             if (! didEnd) {
1636                 // need to do the other end
1637                 while (path.size() != 0) {
1638                     pop();
1639                 }
1640                 split(offset + length, 0);
1641             }
1642             while (path.size() != 0) {
1643                 pop();
1644             }
1645         }
1646 
1647         boolean split(int offs, int len) {
1648             boolean splitEnd = false;
1649             // push the path
1650             Element e = root;
1651             int index = e.getElementIndex(offs);
1652             while (! e.isLeaf()) {
1653                 push(e, index);
1654                 e = e.getElement(index);
1655                 index = e.getElementIndex(offs);
1656             }
1657 
1658             ElemChanges ec = path.peek();
1659             Element child = ec.parent.getElement(ec.index);
1660             // make sure there is something to do... if the
1661             // offset is already at a boundary then there is
1662             // nothing to do.
1663             if (child.getStartOffset() < offs && offs < child.getEndOffset()) {
1664                 // we need to split, now see if the other end is within
1665                 // the same parent.
1666                 int index0 = ec.index;
1667                 int index1 = index0;
1668                 if (((offs + len) < ec.parent.getEndOffset()) && (len != 0)) {
1669                     // it's a range split in the same parent
1670                     index1 = ec.parent.getElementIndex(offs+len);
1671                     if (index1 == index0) {
1672                         // it's a three-way split
1673                         ec.removed.addElement(child);
1674                         e = createLeafElement(ec.parent, child.getAttributes(),
1675                                               child.getStartOffset(), offs);
1676                         ec.added.addElement(e);
1677                         e = createLeafElement(ec.parent, child.getAttributes(),
1678                                           offs, offs + len);
1679                         ec.added.addElement(e);
1680                         e = createLeafElement(ec.parent, child.getAttributes(),
1681                                               offs + len, child.getEndOffset());
1682                         ec.added.addElement(e);
1683                         return true;
1684                     } else {
1685                         child = ec.parent.getElement(index1);
1686                         if ((offs + len) == child.getStartOffset()) {
1687                             // end is already on a boundary
1688                             index1 = index0;
1689                         }
1690                     }
1691                     splitEnd = true;
1692                 }
1693 
1694                 // split the first location
1695                 pos = offs;
1696                 child = ec.parent.getElement(index0);
1697                 ec.removed.addElement(child);
1698                 e = createLeafElement(ec.parent, child.getAttributes(),
1699                                       child.getStartOffset(), pos);
1700                 ec.added.addElement(e);
1701                 e = createLeafElement(ec.parent, child.getAttributes(),
1702                                       pos, child.getEndOffset());
1703                 ec.added.addElement(e);
1704 
1705                 // pick up things in the middle
1706                 for (int i = index0 + 1; i < index1; i++) {
1707                     child = ec.parent.getElement(i);
1708                     ec.removed.addElement(child);
1709                     ec.added.addElement(child);
1710                 }
1711 
1712                 if (index1 != index0) {
1713                     child = ec.parent.getElement(index1);
1714                     pos = offs + len;
1715                     ec.removed.addElement(child);
1716                     e = createLeafElement(ec.parent, child.getAttributes(),
1717                                           child.getStartOffset(), pos);
1718                     ec.added.addElement(e);
1719                     e = createLeafElement(ec.parent, child.getAttributes(),
1720                                           pos, child.getEndOffset());
1721                     ec.added.addElement(e);
1722                 }
1723             }
1724             return splitEnd;
1725         }
1726 
1727         /**
1728          * Creates the UndoableEdit record for the edits made
1729          * in the buffer.
1730          */
1731         void endEdits(DefaultDocumentEvent de) {
1732             int n = changes.size();
1733             for (int i = 0; i < n; i++) {
1734                 ElemChanges ec = changes.elementAt(i);
1735                 Element[] removed = new Element[ec.removed.size()];
1736                 ec.removed.copyInto(removed);
1737                 Element[] added = new Element[ec.added.size()];
1738                 ec.added.copyInto(added);
1739                 int index = ec.index;
1740                 ((BranchElement) ec.parent).replace(index, removed.length, added);
1741                 ElementEdit ee = new ElementEdit(ec.parent, index, removed, added);
1742                 de.addEdit(ee);
1743             }
1744 
1745             changes.removeAllElements();
1746             path.removeAllElements();
1747 
1748             /*
1749             for (int i = 0; i < n; i++) {
1750                 ElemChanges ec = (ElemChanges) changes.elementAt(i);
1751                 System.err.print("edited: " + ec.parent + " at: " + ec.index +
1752                     " removed " + ec.removed.size());
1753                 if (ec.removed.size() > 0) {
1754                     int r0 = ((Element) ec.removed.firstElement()).getStartOffset();
1755                     int r1 = ((Element) ec.removed.lastElement()).getEndOffset();
1756                     System.err.print("[" + r0 + "," + r1 + "]");
1757                 }
1758                 System.err.print(" added " + ec.added.size());
1759                 if (ec.added.size() > 0) {
1760                     int p0 = ((Element) ec.added.firstElement()).getStartOffset();
1761                     int p1 = ((Element) ec.added.lastElement()).getEndOffset();
1762                     System.err.print("[" + p0 + "," + p1 + "]");
1763                 }
1764                 System.err.println("");
1765             }
1766             */
1767         }
1768 
1769         /**
1770          * Initialize the buffer
1771          */
1772         void beginEdits(int offset, int length) {
1773             this.offset = offset;
1774             this.length = length;
1775             this.endOffset = offset + length;
1776             pos = offset;
1777             if (changes == null) {
1778                 changes = new Vector<ElemChanges>();
1779             } else {
1780                 changes.removeAllElements();
1781             }
1782             if (path == null) {
1783                 path = new Stack<ElemChanges>();
1784             } else {
1785                 path.removeAllElements();
1786             }
1787             fracturedParent = null;
1788             fracturedChild = null;
1789             offsetLastIndex = offsetLastIndexOnReplace = false;
1790         }
1791 
1792         /**
1793          * Pushes a new element onto the stack that represents
1794          * the current path.
1795          * @param record Whether or not the push should be
1796          *  recorded as an element change or not.
1797          * @param isFracture true if pushing on an element that was created
1798          * as the result of a fracture.
1799          */
1800         void push(Element e, int index, boolean isFracture) {
1801             ElemChanges ec = new ElemChanges(e, index, isFracture);
1802             path.push(ec);
1803         }
1804 
1805         void push(Element e, int index) {
1806             push(e, index, false);
1807         }
1808 
1809         void pop() {
1810             ElemChanges ec = path.peek();
1811             path.pop();
1812             if ((ec.added.size() > 0) || (ec.removed.size() > 0)) {
1813                 changes.addElement(ec);
1814             } else if (! path.isEmpty()) {
1815                 Element e = ec.parent;
1816                 if(e.getElementCount() == 0) {
1817                     // if we pushed a branch element that didn't get
1818                     // used, make sure its not marked as having been added.
1819                     ec = path.peek();
1820                     ec.added.removeElement(e);
1821                 }
1822             }
1823         }
1824 
1825         /**
1826          * move the current offset forward by n.
1827          */
1828         void advance(int n) {
1829             pos += n;
1830         }
1831 
1832         void insertElement(ElementSpec es) {
1833             ElemChanges ec = path.peek();
1834             switch(es.getType()) {
1835             case ElementSpec.StartTagType:
1836                 switch(es.getDirection()) {
1837                 case ElementSpec.JoinNextDirection:
1838                     // Don't create a new element, use the existing one
1839                     // at the specified location.
1840                     Element parent = ec.parent.getElement(ec.index);
1841 
1842                     if(parent.isLeaf()) {
1843                         // This happens if inserting into a leaf, followed
1844                         // by a join next where next sibling is not a leaf.
1845                         if((ec.index + 1) < ec.parent.getElementCount())
1846                             parent = ec.parent.getElement(ec.index + 1);
1847                         else
1848                             throw new StateInvariantError("Join next to leaf");
1849                     }
1850                     // Not really a fracture, but need to treat it like
1851                     // one so that content join next will work correctly.
1852                     // We can do this because there will never be a join
1853                     // next followed by a join fracture.
1854                     push(parent, 0, true);
1855                     break;
1856                 case ElementSpec.JoinFractureDirection:
1857                     if(!createdFracture) {
1858                         // Should always be something on the stack!
1859                         fracture(path.size() - 1);
1860                     }
1861                     // If parent isn't a fracture, fracture will be
1862                     // fracturedChild.
1863                     if(!ec.isFracture) {
1864                         push(fracturedChild, 0, true);
1865                     }
1866                     else
1867                         // Parent is a fracture, use 1st element.
1868                         push(ec.parent.getElement(0), 0, true);
1869                     break;
1870                 default:
1871                     Element belem = createBranchElement(ec.parent,
1872                                                         es.getAttributes());
1873                     ec.added.addElement(belem);
1874                     push(belem, 0);
1875                     break;
1876                 }
1877                 break;
1878             case ElementSpec.EndTagType:
1879                 pop();
1880                 break;
1881             case ElementSpec.ContentType:
1882               int len = es.getLength();
1883                 if (es.getDirection() != ElementSpec.JoinNextDirection) {
1884                     Element leaf = createLeafElement(ec.parent, es.getAttributes(),
1885                                                      pos, pos + len);
1886                     ec.added.addElement(leaf);
1887                 }
1888                 else {
1889                     // JoinNext on tail is only applicable if last element
1890                     // and attributes come from that of first element.
1891                     // With a little extra testing it would be possible
1892                     // to NOT due this again, as more than likely fracture()
1893                     // created this element.
1894                     if(!ec.isFracture) {
1895                         Element first = null;
1896                         if(insertPath != null) {
1897                             for(int counter = insertPath.length - 1;
1898                                 counter >= 0; counter--) {
1899                                 if(insertPath[counter] == ec) {
1900                                     if(counter != (insertPath.length - 1))
1901                                         first = ec.parent.getElement(ec.index);
1902                                     break;
1903                                 }
1904                             }
1905                         }
1906                         if(first == null)
1907                             first = ec.parent.getElement(ec.index + 1);
1908                         Element leaf = createLeafElement(ec.parent, first.
1909                                  getAttributes(), pos, first.getEndOffset());
1910                         ec.added.addElement(leaf);
1911                         ec.removed.addElement(first);
1912                     }
1913                     else {
1914                         // Parent was fractured element.
1915                         Element first = ec.parent.getElement(0);
1916                         Element leaf = createLeafElement(ec.parent, first.
1917                                  getAttributes(), pos, first.getEndOffset());
1918                         ec.added.addElement(leaf);
1919                         ec.removed.addElement(first);
1920                     }
1921                 }
1922                 pos += len;
1923                 break;
1924             }
1925         }
1926 
1927         /**
1928          * Remove the elements from <code>elem</code> in range
1929          * <code>rmOffs0</code>, <code>rmOffs1</code>. This uses
1930          * <code>canJoin</code> and <code>join</code> to handle joining
1931          * the endpoints of the insertion.
1932          *
1933          * @return true if elem will no longer have any elements.
1934          */
1935         boolean removeElements(Element elem, int rmOffs0, int rmOffs1) {
1936             if (! elem.isLeaf()) {
1937                 // update path for changes
1938                 int index0 = elem.getElementIndex(rmOffs0);
1939                 int index1 = elem.getElementIndex(rmOffs1);
1940                 push(elem, index0);
1941                 ElemChanges ec = path.peek();
1942 
1943                 // if the range is contained by one element,
1944                 // we just forward the request
1945                 if (index0 == index1) {
1946                     Element child0 = elem.getElement(index0);
1947                     if(rmOffs0 <= child0.getStartOffset() &&
1948                        rmOffs1 >= child0.getEndOffset()) {
1949                         // Element totally removed.
1950                         ec.removed.addElement(child0);
1951                     }
1952                     else if(removeElements(child0, rmOffs0, rmOffs1)) {
1953                         ec.removed.addElement(child0);
1954                     }
1955                 } else {
1956                     // the removal range spans elements.  If we can join
1957                     // the two endpoints, do it.  Otherwise we remove the
1958                     // interior and forward to the endpoints.
1959                     Element child0 = elem.getElement(index0);
1960                     Element child1 = elem.getElement(index1);
1961                     boolean containsOffs1 = (rmOffs1 < elem.getEndOffset());
1962                     if (containsOffs1 && canJoin(child0, child1)) {
1963                         // remove and join
1964                         for (int i = index0; i <= index1; i++) {
1965                             ec.removed.addElement(elem.getElement(i));
1966                         }
1967                         Element e = join(elem, child0, child1, rmOffs0, rmOffs1);
1968                         ec.added.addElement(e);
1969                     } else {
1970                         // remove interior and forward
1971                         int rmIndex0 = index0 + 1;
1972                         int rmIndex1 = index1 - 1;
1973                         if (child0.getStartOffset() == rmOffs0 ||
1974                             (index0 == 0 &&
1975                              child0.getStartOffset() > rmOffs0 &&
1976                              child0.getEndOffset() <= rmOffs1)) {
1977                             // start element completely consumed
1978                             child0 = null;
1979                             rmIndex0 = index0;
1980                         }
1981                         if (!containsOffs1) {
1982                             child1 = null;
1983                             rmIndex1++;
1984                         }
1985                         else if (child1.getStartOffset() == rmOffs1) {
1986                             // end element not touched
1987                             child1 = null;
1988                         }
1989                         if (rmIndex0 <= rmIndex1) {
1990                             ec.index = rmIndex0;
1991                         }
1992                         for (int i = rmIndex0; i <= rmIndex1; i++) {
1993                             ec.removed.addElement(elem.getElement(i));
1994                         }
1995                         if (child0 != null) {
1996                             if(removeElements(child0, rmOffs0, rmOffs1)) {
1997                                 ec.removed.insertElementAt(child0, 0);
1998                                 ec.index = index0;
1999                             }
2000                         }
2001                         if (child1 != null) {
2002                             if(removeElements(child1, rmOffs0, rmOffs1)) {
2003                                 ec.removed.addElement(child1);
2004                             }
2005                         }
2006                     }
2007                 }
2008 
2009                 // publish changes
2010                 pop();
2011 
2012                 // Return true if we no longer have any children.
2013                 if(elem.getElementCount() == (ec.removed.size() -
2014                                               ec.added.size())) {
2015                     return true;
2016                 }
2017             }
2018             return false;
2019         }
2020 
2021         /**
2022          * Can the two given elements be coelesced together
2023          * into one element?
2024          */
2025         boolean canJoin(Element e0, Element e1) {
2026             if ((e0 == null) || (e1 == null)) {
2027                 return false;
2028             }
2029             // Don't join a leaf to a branch.
2030             boolean leaf0 = e0.isLeaf();
2031             boolean leaf1 = e1.isLeaf();
2032             if(leaf0 != leaf1) {
2033                 return false;
2034             }
2035             if (leaf0) {
2036                 // Only join leaves if the attributes match, otherwise
2037                 // style information will be lost.
2038                 return e0.getAttributes().isEqual(e1.getAttributes());
2039             }
2040             // Only join non-leafs if the names are equal. This may result
2041             // in loss of style information, but this is typically acceptable
2042             // for non-leafs.
2043             String name0 = e0.getName();
2044             String name1 = e1.getName();
2045             if (name0 != null) {
2046                 return name0.equals(name1);
2047             }
2048             if (name1 != null) {
2049                 return name1.equals(name0);
2050             }
2051             // Both names null, treat as equal.
2052             return true;
2053         }
2054 
2055         /**
2056          * Joins the two elements carving out a hole for the
2057          * given removed range.
2058          */
2059         Element join(Element p, Element left, Element right, int rmOffs0, int rmOffs1) {
2060             if (left.isLeaf() && right.isLeaf()) {
2061                 return createLeafElement(p, left.getAttributes(), left.getStartOffset(),
2062                                          right.getEndOffset());
2063             } else if ((!left.isLeaf()) && (!right.isLeaf())) {
2064                 // join two branch elements.  This copies the children before
2065                 // the removal range on the left element, and after the removal
2066                 // range on the right element.  The two elements on the edge
2067                 // are joined if possible and needed.
2068                 Element to = createBranchElement(p, left.getAttributes());
2069                 int ljIndex = left.getElementIndex(rmOffs0);
2070                 int rjIndex = right.getElementIndex(rmOffs1);
2071                 Element lj = left.getElement(ljIndex);
2072                 if (lj.getStartOffset() >= rmOffs0) {
2073                     lj = null;
2074                 }
2075                 Element rj = right.getElement(rjIndex);
2076                 if (rj.getStartOffset() == rmOffs1) {
2077                     rj = null;
2078                 }
2079                 Vector<Element> children = new Vector<Element>();
2080 
2081                 // transfer the left
2082                 for (int i = 0; i < ljIndex; i++) {
2083                     children.addElement(clone(to, left.getElement(i)));
2084                 }
2085 
2086                 // transfer the join/middle
2087                 if (canJoin(lj, rj)) {
2088                     Element e = join(to, lj, rj, rmOffs0, rmOffs1);
2089                     children.addElement(e);
2090                 } else {
2091                     if (lj != null) {
2092                         children.addElement(cloneAsNecessary(to, lj, rmOffs0, rmOffs1));
2093                     }
2094                     if (rj != null) {
2095                         children.addElement(cloneAsNecessary(to, rj, rmOffs0, rmOffs1));
2096                     }
2097                 }
2098 
2099                 // transfer the right
2100                 int n = right.getElementCount();
2101                 for (int i = (rj == null) ? rjIndex : rjIndex + 1; i < n; i++) {
2102                     children.addElement(clone(to, right.getElement(i)));
2103                 }
2104 
2105                 // install the children
2106                 Element[] c = new Element[children.size()];
2107                 children.copyInto(c);
2108                 ((BranchElement)to).replace(0, 0, c);
2109                 return to;
2110             } else {
2111                 throw new StateInvariantError(
2112                     "No support to join leaf element with non-leaf element");
2113             }
2114         }
2115 
2116         /**
2117          * Creates a copy of this element, with a different
2118          * parent.
2119          *
2120          * @param parent the parent element
2121          * @param clonee the element to be cloned
2122          * @return the copy
2123          */
2124         public Element clone(Element parent, Element clonee) {
2125             if (clonee.isLeaf()) {
2126                 return createLeafElement(parent, clonee.getAttributes(),
2127                                          clonee.getStartOffset(),
2128                                          clonee.getEndOffset());
2129             }
2130             Element e = createBranchElement(parent, clonee.getAttributes());
2131             int n = clonee.getElementCount();
2132             Element[] children = new Element[n];
2133             for (int i = 0; i < n; i++) {
2134                 children[i] = clone(e, clonee.getElement(i));
2135             }
2136             ((BranchElement)e).replace(0, 0, children);
2137             return e;
2138         }
2139 
2140         /**
2141          * Creates a copy of this element, with a different
2142          * parent. Children of this element included in the
2143          * removal range will be discarded.
2144          */
2145         Element cloneAsNecessary(Element parent, Element clonee, int rmOffs0, int rmOffs1) {
2146             if (clonee.isLeaf()) {
2147                 return createLeafElement(parent, clonee.getAttributes(),
2148                                          clonee.getStartOffset(),
2149                                          clonee.getEndOffset());
2150             }
2151             Element e = createBranchElement(parent, clonee.getAttributes());
2152             int n = clonee.getElementCount();
2153             ArrayList<Element> childrenList = new ArrayList<Element>(n);
2154             for (int i = 0; i < n; i++) {
2155                 Element elem = clonee.getElement(i);
2156                 if (elem.getStartOffset() < rmOffs0 || elem.getEndOffset() > rmOffs1) {
2157                     childrenList.add(cloneAsNecessary(e, elem, rmOffs0, rmOffs1));
2158                 }
2159             }
2160             Element[] children = new Element[childrenList.size()];
2161             children = childrenList.toArray(children);
2162             ((BranchElement)e).replace(0, 0, children);
2163             return e;
2164         }
2165 
2166         /**
2167          * Determines if a fracture needs to be performed. A fracture
2168          * can be thought of as moving the right part of a tree to a
2169          * new location, where the right part is determined by what has
2170          * been inserted. <code>depth</code> is used to indicate a
2171          * JoinToFracture is needed to an element at a depth
2172          * of <code>depth</code>. Where the root is 0, 1 is the children
2173          * of the root...
2174          * <p>This will invoke <code>fractureFrom</code> if it is determined
2175          * a fracture needs to happen.
2176          */
2177         void fracture(int depth) {
2178             int cLength = insertPath.length;
2179             int lastIndex = -1;
2180             boolean needRecreate = recreateLeafs;
2181             ElemChanges lastChange = insertPath[cLength - 1];
2182             // Use childAltered to determine when a child has been altered,
2183             // that is the point of insertion is less than the element count.
2184             boolean childAltered = ((lastChange.index + 1) <
2185                                     lastChange.parent.getElementCount());
2186             int deepestAlteredIndex = (needRecreate) ? cLength : -1;
2187             int lastAlteredIndex = cLength - 1;
2188 
2189             createdFracture = true;
2190             // Determine where to start recreating from.
2191             // Start at - 2, as first one is indicated by recreateLeafs and
2192             // childAltered.
2193             for(int counter = cLength - 2; counter >= 0; counter--) {
2194                 ElemChanges change = insertPath[counter];
2195                 if(change.added.size() > 0 || counter == depth) {
2196                     lastIndex = counter;
2197                     if(!needRecreate && childAltered) {
2198                         needRecreate = true;
2199                         if(deepestAlteredIndex == -1)
2200                             deepestAlteredIndex = lastAlteredIndex + 1;
2201                     }
2202                 }
2203                 if(!childAltered && change.index <
2204                    change.parent.getElementCount()) {
2205                     childAltered = true;
2206                     lastAlteredIndex = counter;
2207                 }
2208             }
2209             if(needRecreate) {
2210                 // Recreate all children to right of parent starting
2211                 // at lastIndex.
2212                 if(lastIndex == -1)
2213                     lastIndex = cLength - 1;
2214                 fractureFrom(insertPath, lastIndex, deepestAlteredIndex);
2215             }
2216         }
2217 
2218         /**
2219          * Recreates the elements to the right of the insertion point.
2220          * This starts at <code>startIndex</code> in <code>changed</code>,
2221          * and calls duplicate to duplicate existing elements.
2222          * This will also duplicate the elements along the insertion
2223          * point, until a depth of <code>endFractureIndex</code> is
2224          * reached, at which point only the elements to the right of
2225          * the insertion point are duplicated.
2226          */
2227         void fractureFrom(ElemChanges[] changed, int startIndex,
2228                           int endFractureIndex) {
2229             // Recreate the element representing the inserted index.
2230             ElemChanges change = changed[startIndex];
2231             Element child;
2232             Element newChild;
2233             int changeLength = changed.length;
2234 
2235             if((startIndex + 1) == changeLength)
2236                 child = change.parent.getElement(change.index);
2237             else
2238                 child = change.parent.getElement(change.index - 1);
2239             if(child.isLeaf()) {
2240                 newChild = createLeafElement(change.parent,
2241                                child.getAttributes(), Math.max(endOffset,
2242                                child.getStartOffset()), child.getEndOffset());
2243             }
2244             else {
2245                 newChild = createBranchElement(change.parent,
2246                                                child.getAttributes());
2247             }
2248             fracturedParent = change.parent;
2249             fracturedChild = newChild;
2250 
2251             // Recreate all the elements to the right of the
2252             // insertion point.
2253             Element parent = newChild;
2254 
2255             while(++startIndex < endFractureIndex) {
2256                 boolean isEnd = ((startIndex + 1) == endFractureIndex);
2257                 boolean isEndLeaf = ((startIndex + 1) == changeLength);
2258 
2259                 // Create the newChild, a duplicate of the elment at
2260                 // index. This isn't done if isEnd and offsetLastIndex are true
2261                 // indicating a join previous was done.
2262                 change = changed[startIndex];
2263 
2264                 // Determine the child to duplicate, won't have to duplicate
2265                 // if at end of fracture, or offseting index.
2266                 if(isEnd) {
2267                     if(offsetLastIndex || !isEndLeaf)
2268                         child = null;
2269                     else
2270                         child = change.parent.getElement(change.index);
2271                 }
2272                 else {
2273                     child = change.parent.getElement(change.index - 1);
2274                 }
2275                 // Duplicate it.
2276                 if(child != null) {
2277                     if(child.isLeaf()) {
2278                         newChild = createLeafElement(parent,
2279                                child.getAttributes(), Math.max(endOffset,
2280                                child.getStartOffset()), child.getEndOffset());
2281                     }
2282                     else {
2283                         newChild = createBranchElement(parent,
2284                                                    child.getAttributes());
2285                     }
2286                 }
2287                 else
2288                     newChild = null;
2289 
2290                 // Recreate the remaining children (there may be none).
2291                 int kidsToMove = change.parent.getElementCount() -
2292                                  change.index;
2293                 Element[] kids;
2294                 int moveStartIndex;
2295                 int kidStartIndex = 1;
2296 
2297                 if(newChild == null) {
2298                     // Last part of fracture.
2299                     if(isEndLeaf) {
2300                         kidsToMove--;
2301                         moveStartIndex = change.index + 1;
2302                     }
2303                     else {
2304                         moveStartIndex = change.index;
2305                     }
2306                     kidStartIndex = 0;
2307                     kids = new Element[kidsToMove];
2308                 }
2309                 else {
2310                     if(!isEnd) {
2311                         // Branch.
2312                         kidsToMove++;
2313                         moveStartIndex = change.index;
2314                     }
2315                     else {
2316                         // Last leaf, need to recreate part of it.
2317                         moveStartIndex = change.index + 1;
2318                     }
2319                     kids = new Element[kidsToMove];
2320                     kids[0] = newChild;
2321                 }
2322 
2323                 for(int counter = kidStartIndex; counter < kidsToMove;
2324                     counter++) {
2325                     Element toMove =change.parent.getElement(moveStartIndex++);
2326                     kids[counter] = recreateFracturedElement(parent, toMove);
2327                     change.removed.addElement(toMove);
2328                 }
2329                 ((BranchElement)parent).replace(0, 0, kids);
2330                 parent = newChild;
2331             }
2332         }
2333 
2334         /**
2335          * Recreates <code>toDuplicate</code>. This is called when an
2336          * element needs to be created as the result of an insertion. This
2337          * will recurse and create all the children. This is similar to
2338          * <code>clone</code>, but deteremines the offsets differently.
2339          */
2340         Element recreateFracturedElement(Element parent, Element toDuplicate) {
2341             if(toDuplicate.isLeaf()) {
2342                 return createLeafElement(parent, toDuplicate.getAttributes(),
2343                                          Math.max(toDuplicate.getStartOffset(),
2344                                                   endOffset),
2345                                          toDuplicate.getEndOffset());
2346             }
2347             // Not a leaf
2348             Element newParent = createBranchElement(parent, toDuplicate.
2349                                                     getAttributes());
2350             int childCount = toDuplicate.getElementCount();
2351             Element[] newKids = new Element[childCount];
2352             for(int counter = 0; counter < childCount; counter++) {
2353                 newKids[counter] = recreateFracturedElement(newParent,
2354                                              toDuplicate.getElement(counter));
2355             }
2356             ((BranchElement)newParent).replace(0, 0, newKids);
2357             return newParent;
2358         }
2359 
2360         /**
2361          * Splits the bottommost leaf in <code>path</code>.
2362          * This is called from insert when the first element is NOT content.
2363          */
2364         void fractureDeepestLeaf(ElementSpec[] specs) {
2365             // Split the bottommost leaf. It will be recreated elsewhere.
2366             ElemChanges ec = path.peek();
2367             Element child = ec.parent.getElement(ec.index);
2368             // Inserts at offset 0 do not need to recreate child (it would
2369             // have a length of 0!).
2370             if (offset != 0) {
2371                 Element newChild = createLeafElement(ec.parent,
2372                                                  child.getAttributes(),
2373                                                  child.getStartOffset(),
2374                                                  offset);
2375 
2376                 ec.added.addElement(newChild);
2377             }
2378             ec.removed.addElement(child);
2379             if(child.getEndOffset() != endOffset)
2380                 recreateLeafs = true;
2381             else
2382                 offsetLastIndex = true;
2383         }
2384 
2385         /**
2386          * Inserts the first content. This needs to be separate to handle
2387          * joining.
2388          */
2389         void insertFirstContent(ElementSpec[] specs) {
2390             ElementSpec firstSpec = specs[0];
2391             ElemChanges ec = path.peek();
2392             Element child = ec.parent.getElement(ec.index);
2393             int firstEndOffset = offset + firstSpec.getLength();
2394             boolean isOnlyContent = (specs.length == 1);
2395 
2396             switch(firstSpec.getDirection()) {
2397             case ElementSpec.JoinPreviousDirection:
2398                 if(child.getEndOffset() != firstEndOffset &&
2399                     !isOnlyContent) {
2400                     // Create the left split part containing new content.
2401                     Element newE = createLeafElement(ec.parent,
2402                             child.getAttributes(), child.getStartOffset(),
2403                             firstEndOffset);
2404                     ec.added.addElement(newE);
2405                     ec.removed.addElement(child);
2406                     // Remainder will be created later.
2407                     if(child.getEndOffset() != endOffset)
2408                         recreateLeafs = true;
2409                     else
2410                         offsetLastIndex = true;
2411                 }
2412                 else {
2413                     offsetLastIndex = true;
2414                     offsetLastIndexOnReplace = true;
2415                 }
2416                 // else Inserted at end, and is total length.
2417                 // Update index incase something added/removed.
2418                 break;
2419             case ElementSpec.JoinNextDirection:
2420                 if(offset != 0) {
2421                     // Recreate the first element, its offset will have
2422                     // changed.
2423                     Element newE = createLeafElement(ec.parent,
2424                             child.getAttributes(), child.getStartOffset(),
2425                             offset);
2426                     ec.added.addElement(newE);
2427                     // Recreate the second, merge part. We do no checking
2428                     // to see if JoinNextDirection is valid here!
2429                     Element nextChild = ec.parent.getElement(ec.index + 1);
2430                     if(isOnlyContent)
2431                         newE = createLeafElement(ec.parent, nextChild.
2432                             getAttributes(), offset, nextChild.getEndOffset());
2433                     else
2434                         newE = createLeafElement(ec.parent, nextChild.
2435                             getAttributes(), offset, firstEndOffset);
2436                     ec.added.addElement(newE);
2437                     ec.removed.addElement(child);
2438                     ec.removed.addElement(nextChild);
2439                 }
2440                 // else nothin to do.
2441                 // PENDING: if !isOnlyContent could raise here!
2442                 break;
2443             default:
2444                 // Inserted into middle, need to recreate split left
2445                 // new content, and split right.
2446                 if(child.getStartOffset() != offset) {
2447                     Element newE = createLeafElement(ec.parent,
2448                             child.getAttributes(), child.getStartOffset(),
2449                             offset);
2450                     ec.added.addElement(newE);
2451                 }
2452                 ec.removed.addElement(child);
2453                 // new content
2454                 Element newE = createLeafElement(ec.parent,
2455                                                  firstSpec.getAttributes(),
2456                                                  offset, firstEndOffset);
2457                 ec.added.addElement(newE);
2458                 if(child.getEndOffset() != endOffset) {
2459                     // Signals need to recreate right split later.
2460                     recreateLeafs = true;
2461                 }
2462                 else {
2463                     offsetLastIndex = true;
2464                 }
2465                 break;
2466             }
2467         }
2468 
2469         Element root;
2470         transient int pos;          // current position
2471         transient int offset;
2472         transient int length;
2473         transient int endOffset;
2474         transient Vector<ElemChanges> changes;
2475         transient Stack<ElemChanges> path;
2476         transient boolean insertOp;
2477 
2478         transient boolean recreateLeafs; // For insert.
2479 
2480         /** For insert, path to inserted elements. */
2481         transient ElemChanges[] insertPath;
2482         /** Only for insert, set to true when the fracture has been created. */
2483         transient boolean createdFracture;
2484         /** Parent that contains the fractured child. */
2485         transient Element fracturedParent;
2486         /** Fractured child. */
2487         transient Element fracturedChild;
2488         /** Used to indicate when fracturing that the last leaf should be
2489          * skipped. */
2490         transient boolean offsetLastIndex;
2491         /** Used to indicate that the parent of the deepest leaf should
2492          * offset the index by 1 when adding/removing elements in an
2493          * insert. */
2494         transient boolean offsetLastIndexOnReplace;
2495 
2496         /*
2497          * Internal record used to hold element change specifications
2498          */
2499         class ElemChanges {
2500 
2501             ElemChanges(Element parent, int index, boolean isFracture) {
2502                 this.parent = parent;
2503                 this.index = index;
2504                 this.isFracture = isFracture;
2505                 added = new Vector<Element>();
2506                 removed = new Vector<Element>();
2507             }
2508 
2509             public String toString() {
2510                 return "added: " + added + "\nremoved: " + removed + "\n";
2511             }
2512 
2513             Element parent;
2514             int index;
2515             Vector<Element> added;
2516             Vector<Element> removed;
2517             boolean isFracture;
2518         }
2519 
2520     }
2521 
2522     /**
2523      * An UndoableEdit used to remember AttributeSet changes to an
2524      * Element.
2525      */
2526     public static class AttributeUndoableEdit extends AbstractUndoableEdit {
2527         public AttributeUndoableEdit(Element element, AttributeSet newAttributes,
2528                               boolean isReplacing) {
2529             super();
2530             this.element = element;
2531             this.newAttributes = newAttributes;
2532             this.isReplacing = isReplacing;
2533             // If not replacing, it may be more efficient to only copy the
2534             // changed values...
2535             copy = element.getAttributes().copyAttributes();
2536         }
2537 
2538         /**
2539          * Redoes a change.
2540          *
2541          * @exception CannotRedoException if the change cannot be redone
2542          */
2543         public void redo() throws CannotRedoException {
2544             super.redo();
2545             MutableAttributeSet as = (MutableAttributeSet)element
2546                                      .getAttributes();
2547             if(isReplacing)
2548                 as.removeAttributes(as);
2549             as.addAttributes(newAttributes);
2550         }
2551 
2552         /**
2553          * Undoes a change.
2554          *
2555          * @exception CannotUndoException if the change cannot be undone
2556          */
2557         public void undo() throws CannotUndoException {
2558             super.undo();
2559             MutableAttributeSet as = (MutableAttributeSet)element.getAttributes();
2560             as.removeAttributes(as);
2561             as.addAttributes(copy);
2562         }
2563 
2564         // AttributeSet containing additional entries, must be non-mutable!
2565         protected AttributeSet newAttributes;
2566         // Copy of the AttributeSet the Element contained.
2567         protected AttributeSet copy;
2568         // true if all the attributes in the element were removed first.
2569         protected boolean isReplacing;
2570         // Efected Element.
2571         protected Element element;
2572     }
2573 
2574     /**
2575      * UndoableEdit for changing the resolve parent of an Element.
2576      */
2577     static class StyleChangeUndoableEdit extends AbstractUndoableEdit {
2578         public StyleChangeUndoableEdit(AbstractElement element,
2579                                        Style newStyle) {
2580             super();
2581             this.element = element;
2582             this.newStyle = newStyle;
2583             oldStyle = element.getResolveParent();
2584         }
2585 
2586         /**
2587          * Redoes a change.
2588          *
2589          * @exception CannotRedoException if the change cannot be redone
2590          */
2591         public void redo() throws CannotRedoException {
2592             super.redo();
2593             element.setResolveParent(newStyle);
2594         }
2595 
2596         /**
2597          * Undoes a change.
2598          *
2599          * @exception CannotUndoException if the change cannot be undone
2600          */
2601         public void undo() throws CannotUndoException {
2602             super.undo();
2603             element.setResolveParent(oldStyle);
2604         }
2605 
2606         /** Element to change resolve parent of. */
2607         protected AbstractElement element;
2608         /** New style. */
2609         protected Style newStyle;
2610         /** Old style, before setting newStyle. */
2611         protected AttributeSet oldStyle;
2612     }
2613 
2614     /**
2615      * Base class for style change handlers with support for stale objects detection.
2616      */
2617     abstract static class AbstractChangeHandler implements ChangeListener {
2618 
2619         /* This has an implicit reference to the handler object.  */
2620         private class DocReference extends WeakReference<DefaultStyledDocument> {
2621 
2622             DocReference(DefaultStyledDocument d, ReferenceQueue<DefaultStyledDocument> q) {
2623                 super(d, q);
2624             }
2625 
2626             /**
2627              * Return a reference to the style change handler object.
2628              */
2629             ChangeListener getListener() {
2630                 return AbstractChangeHandler.this;
2631             }
2632         }
2633 
2634         /** Class-specific reference queues.  */
2635         private final static Map<Class<?>, ReferenceQueue<DefaultStyledDocument>> queueMap
2636                 = new HashMap<Class<?>, ReferenceQueue<DefaultStyledDocument>>();
2637 
2638         /** A weak reference to the document object.  */
2639         private DocReference doc;
2640 
2641         AbstractChangeHandler(DefaultStyledDocument d) {
2642             Class<?> c = getClass();
2643             ReferenceQueue<DefaultStyledDocument> q;
2644             synchronized (queueMap) {
2645                 q = queueMap.get(c);
2646                 if (q == null) {
2647                     q = new ReferenceQueue<DefaultStyledDocument>();
2648                     queueMap.put(c, q);
2649                 }
2650             }
2651             doc = new DocReference(d, q);
2652         }
2653 
2654         /**
2655          * Return a list of stale change listeners.
2656          *
2657          * A change listener becomes "stale" when its document is cleaned by GC.
2658          */
2659         static List<ChangeListener> getStaleListeners(ChangeListener l) {
2660             List<ChangeListener> staleListeners = new ArrayList<ChangeListener>();
2661             ReferenceQueue<DefaultStyledDocument> q = queueMap.get(l.getClass());
2662 
2663             if (q != null) {
2664                 DocReference r;
2665                 synchronized (q) {
2666                     while ((r = (DocReference) q.poll()) != null) {
2667                         staleListeners.add(r.getListener());
2668                     }
2669                 }
2670             }
2671 
2672             return staleListeners;
2673         }
2674 
2675         /**
2676          * The ChangeListener wrapper which guards against dead documents.
2677          */
2678         public void stateChanged(ChangeEvent e) {
2679             DefaultStyledDocument d = doc.get();
2680             if (d != null) {
2681                 fireStateChanged(d, e);
2682             }
2683         }
2684 
2685         /** Run the actual class-specific stateChanged() method.  */
2686         abstract void fireStateChanged(DefaultStyledDocument d, ChangeEvent e);
2687     }
2688 
2689     /**
2690      * Added to all the Styles. When instances of this receive a
2691      * stateChanged method, styleChanged is invoked.
2692      */
2693     static class StyleChangeHandler extends AbstractChangeHandler {
2694 
2695         StyleChangeHandler(DefaultStyledDocument d) {
2696             super(d);
2697         }
2698 
2699         void fireStateChanged(DefaultStyledDocument d, ChangeEvent e) {
2700             Object source = e.getSource();
2701             if (source instanceof Style) {
2702                 d.styleChanged((Style) source);
2703             } else {
2704                 d.styleChanged(null);
2705             }
2706         }
2707     }
2708 
2709 
2710     /**
2711      * Added to the StyleContext. When the StyleContext changes, this invokes
2712      * <code>updateStylesListeningTo</code>.
2713      */
2714     static class StyleContextChangeHandler extends AbstractChangeHandler {
2715 
2716         StyleContextChangeHandler(DefaultStyledDocument d) {
2717             super(d);
2718         }
2719 
2720         void fireStateChanged(DefaultStyledDocument d, ChangeEvent e) {
2721             d.updateStylesListeningTo();
2722         }
2723     }
2724 
2725 
2726     /**
2727      * When run this creates a change event for the complete document
2728      * and fires it.
2729      */
2730     class ChangeUpdateRunnable implements Runnable {
2731         boolean isPending = false;
2732 
2733         public void run() {
2734             synchronized(this) {
2735                 isPending = false;
2736             }
2737 
2738             try {
2739                 writeLock();
2740                 DefaultDocumentEvent dde = new DefaultDocumentEvent(0,
2741                                               getLength(),
2742                                               DocumentEvent.EventType.CHANGE);
2743                 dde.end();
2744                 fireChangedUpdate(dde);
2745             } finally {
2746                 writeUnlock();
2747             }
2748         }
2749     }
2750 }