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