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