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