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