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