Source Code Cross Referenced for DefaultStyledDocument.java in  » 6.0-JDK-Core » swing » javax » swing » text » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Home
Java Source Code / Java Documentation
1.6.0 JDK Core
2.6.0 JDK Modules
3.6.0 JDK Modules com.sun
4.6.0 JDK Modules com.sun.java
5.6.0 JDK Modules sun
6.6.0 JDK Platform
7.Ajax
8.Apache Harmony Java SE
9.Aspect oriented
10.Authentication Authorization
11.Blogger System
12.Build
13.Byte Code
14.Cache
15.Chart
16.Chat
17.Code Analyzer
18.Collaboration
19.Content Management System
20.Database Client
21.Database DBMS
22.Database JDBC Connection Pool
23.Database ORM
24.Development
25.EJB Server
26.ERP CRM Financial
27.ESB
28.Forum
29.Game
30.GIS
31.Graphic 3D
32.Graphic Library
33.Groupware
34.HTML Parser
35.IDE
36.IDE Eclipse
37.IDE Netbeans
38.Installer
39.Internationalization Localization
40.Inversion of Control
41.Issue Tracking
42.J2EE
43.J2ME
44.JBoss
45.JMS
46.JMX
47.Library
48.Mail Clients
49.Music
50.Net
51.Parser
52.PDF
53.Portal
54.Profiler
55.Project Management
56.Report
57.RSS RDF
58.Rule Engine
59.Science
60.Scripting
61.Search Engine
62.Security
63.Sevlet Container
64.Source Control
65.Swing Library
66.Template Engine
67.Test Coverage
68.Testing
69.UML
70.Web Crawler
71.Web Framework
72.Web Mail
73.Web Server
74.Web Services
75.Web Services apache cxf 2.2.6
76.Web Services AXIS2
77.Wiki Engine
78.Workflow Engines
79.XML
80.XML UI
Java Source Code / Java Documentation » 6.0 JDK Core » swing » javax.swing.text 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


0001        /*
0002         * Copyright 1997-2007 Sun Microsystems, Inc.  All Rights Reserved.
0003         * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004         *
0005         * This code is free software; you can redistribute it and/or modify it
0006         * under the terms of the GNU General Public License version 2 only, as
0007         * published by the Free Software Foundation.  Sun designates this
0008         * particular file as subject to the "Classpath" exception as provided
0009         * by Sun in the LICENSE file that accompanied this code.
0010         *
0011         * This code is distributed in the hope that it will be useful, but WITHOUT
0012         * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013         * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
0014         * version 2 for more details (a copy is included in the LICENSE file that
0015         * accompanied this code).
0016         *
0017         * You should have received a copy of the GNU General Public License version
0018         * 2 along with this work; if not, write to the Free Software Foundation,
0019         * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020         *
0021         * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022         * CA 95054 USA or visit www.sun.com if you need additional information or
0023         * have any questions.
0024         */
0025        package javax.swing.text;
0026
0027        import java.awt.Color;
0028        import java.awt.Component;
0029        import java.awt.Font;
0030        import java.awt.FontMetrics;
0031        import java.awt.font.TextAttribute;
0032        import java.lang.ref.ReferenceQueue;
0033        import java.lang.ref.WeakReference;
0034        import java.util.Enumeration;
0035        import java.util.HashMap;
0036        import java.util.Hashtable;
0037        import java.util.List;
0038        import java.util.Map;
0039        import java.util.Stack;
0040        import java.util.Vector;
0041        import java.util.ArrayList;
0042        import java.io.IOException;
0043        import java.io.ObjectInputStream;
0044        import java.io.ObjectOutputStream;
0045        import java.io.Serializable;
0046        import javax.swing.Icon;
0047        import javax.swing.event.*;
0048        import javax.swing.undo.AbstractUndoableEdit;
0049        import javax.swing.undo.CannotRedoException;
0050        import javax.swing.undo.CannotUndoException;
0051        import javax.swing.undo.UndoableEdit;
0052        import javax.swing.SwingUtilities;
0053
0054        /**
0055         * A document that can be marked up with character and paragraph 
0056         * styles in a manner similar to the Rich Text Format.  The element
0057         * structure for this document represents style crossings for
0058         * style runs.  These style runs are mapped into a paragraph element 
0059         * structure (which may reside in some other structure).  The 
0060         * style runs break at paragraph boundaries since logical styles are 
0061         * assigned to paragraph boundaries.
0062         * <p>
0063         * <strong>Warning:</strong>
0064         * Serialized objects of this class will not be compatible with
0065         * future Swing releases. The current serialization support is
0066         * appropriate for short term storage or RMI between applications running
0067         * the same version of Swing.  As of 1.4, support for long term storage
0068         * of all JavaBeans<sup><font size="-2">TM</font></sup>
0069         * has been added to the <code>java.beans</code> package.
0070         * Please see {@link java.beans.XMLEncoder}.
0071         *
0072         * @author  Timothy Prinzing
0073         * @version 1.133 05/05/07
0074         * @see     Document
0075         * @see     AbstractDocument
0076         */
0077        public class DefaultStyledDocument extends AbstractDocument implements 
0078                StyledDocument {
0079
0080            /**
0081             * Constructs a styled document.
0082             *
0083             * @param c  the container for the content
0084             * @param styles resources and style definitions which may
0085             *  be shared across documents
0086             */
0087            public DefaultStyledDocument(Content c, StyleContext styles) {
0088                super (c, styles);
0089                listeningStyles = new Vector();
0090                buffer = new ElementBuffer(createDefaultRoot());
0091                Style defaultStyle = styles
0092                        .getStyle(StyleContext.DEFAULT_STYLE);
0093                setLogicalStyle(0, defaultStyle);
0094            }
0095
0096            /**
0097             * Constructs a styled document with the default content
0098             * storage implementation and a shared set of styles.
0099             *
0100             * @param styles the styles
0101             */
0102            public DefaultStyledDocument(StyleContext styles) {
0103                this (new GapContent(BUFFER_SIZE_DEFAULT), styles);
0104            }
0105
0106            /**
0107             * Constructs a default styled document.  This buffers
0108             * input content by a size of <em>BUFFER_SIZE_DEFAULT</em> 
0109             * and has a style context that is scoped by the lifetime
0110             * of the document and is not shared with other documents.
0111             */
0112            public DefaultStyledDocument() {
0113                this (new GapContent(BUFFER_SIZE_DEFAULT), new StyleContext());
0114            }
0115
0116            /**
0117             * Gets the default root element.
0118             *
0119             * @return the root
0120             * @see Document#getDefaultRootElement
0121             */
0122            public Element getDefaultRootElement() {
0123                return buffer.getRootElement();
0124            }
0125
0126            /**
0127             * Initialize the document to reflect the given element
0128             * structure (i.e. the structure reported by the
0129             * <code>getDefaultRootElement</code> method.  If the
0130             * document contained any data it will first be removed.
0131             */
0132            protected void create(ElementSpec[] data) {
0133                try {
0134                    if (getLength() != 0) {
0135                        remove(0, getLength());
0136                    }
0137                    writeLock();
0138
0139                    // install the content
0140                    Content c = getContent();
0141                    int n = data.length;
0142                    StringBuffer sb = new StringBuffer();
0143                    for (int i = 0; i < n; i++) {
0144                        ElementSpec es = data[i];
0145                        if (es.getLength() > 0) {
0146                            sb.append(es.getArray(), es.getOffset(), es
0147                                    .getLength());
0148                        }
0149                    }
0150                    UndoableEdit cEdit = c.insertString(0, sb.toString());
0151
0152                    // build the event and element structure
0153                    int length = sb.length();
0154                    DefaultDocumentEvent evnt = new DefaultDocumentEvent(0,
0155                            length, DocumentEvent.EventType.INSERT);
0156                    evnt.addEdit(cEdit);
0157                    buffer.create(length, data, evnt);
0158
0159                    // update bidi (possibly)
0160                    super .insertUpdate(evnt, null);
0161
0162                    // notify the listeners
0163                    evnt.end();
0164                    fireInsertUpdate(evnt);
0165                    fireUndoableEditUpdate(new UndoableEditEvent(this , evnt));
0166                } catch (BadLocationException ble) {
0167                    throw new StateInvariantError("problem initializing");
0168                } finally {
0169                    writeUnlock();
0170                }
0171
0172            }
0173
0174            /**
0175             * Inserts new elements in bulk.  This is useful to allow
0176             * parsing with the document in an unlocked state and
0177             * prepare an element structure modification.  This method
0178             * takes an array of tokens that describe how to update an
0179             * element structure so the time within a write lock can
0180             * be greatly reduced in an asynchronous update situation.     
0181             * <p>
0182             * This method is thread safe, although most Swing methods
0183             * are not. Please see 
0184             * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
0185             * to Use Threads</A> for more information.     
0186             *
0187             * @param offset the starting offset >= 0
0188             * @param data the element data
0189             * @exception BadLocationException for an invalid starting offset
0190             */
0191            protected void insert(int offset, ElementSpec[] data)
0192                    throws BadLocationException {
0193                if (data == null || data.length == 0) {
0194                    return;
0195                }
0196
0197                try {
0198                    writeLock();
0199
0200                    // install the content
0201                    Content c = getContent();
0202                    int n = data.length;
0203                    StringBuffer sb = new StringBuffer();
0204                    for (int i = 0; i < n; i++) {
0205                        ElementSpec es = data[i];
0206                        if (es.getLength() > 0) {
0207                            sb.append(es.getArray(), es.getOffset(), es
0208                                    .getLength());
0209                        }
0210                    }
0211                    if (sb.length() == 0) {
0212                        // Nothing to insert, bail.
0213                        return;
0214                    }
0215                    UndoableEdit cEdit = c.insertString(offset, sb.toString());
0216
0217                    // create event and build the element structure
0218                    int length = sb.length();
0219                    DefaultDocumentEvent evnt = new DefaultDocumentEvent(
0220                            offset, length, DocumentEvent.EventType.INSERT);
0221                    evnt.addEdit(cEdit);
0222                    buffer.insert(offset, length, data, evnt);
0223
0224                    // update bidi (possibly)
0225                    super .insertUpdate(evnt, null);
0226
0227                    // notify the listeners
0228                    evnt.end();
0229                    fireInsertUpdate(evnt);
0230                    fireUndoableEditUpdate(new UndoableEditEvent(this , evnt));
0231                } finally {
0232                    writeUnlock();
0233                }
0234            }
0235
0236            /**
0237             * Removes an element from this document.
0238             *
0239             * <p>The element is removed from its parent element, as well as
0240             * the text in the range identified by the element.  If the
0241             * element isn't associated with the document, {@code
0242             * IllegalArgumentException} is thrown.</p>
0243             * 
0244             * <p>As empty branch elements are not allowed in the document, if the
0245             * element is the sole child, its parent element is removed as well,
0246             * recursively.  This means that when replacing all the children of a
0247             * particular element, new children should be added <em>before</em>
0248             * removing old children.
0249             *
0250             * <p>Element removal results in two events being fired, the
0251             * {@code DocumentEvent} for changes in element structure and {@code
0252             * UndoableEditEvent} for changes in document content.</p>
0253             *
0254             * <p>If the element contains end-of-content mark (the last {@code
0255             * "\n"} character in document), this character is not removed;
0256             * instead, preceding leaf element is extended to cover the
0257             * character.  If the last leaf already ends with {@code "\n",} it is
0258             * included in content removal.</p>
0259             *
0260             * <p>If the element is {@code null,} {@code NullPointerException} is
0261             * thrown.  If the element structure would become invalid after the removal,
0262             * for example if the element is the document root element, {@code
0263             * IllegalArgumentException} is thrown.  If the current element structure is
0264             * invalid, {@code IllegalStateException} is thrown.</p>
0265             *
0266             * @param  elem                      the element to remove
0267             * @throws NullPointerException      if the element is {@code null}
0268             * @throws IllegalArgumentException  if the element could not be removed
0269             * @throws IllegalStateException     if the element structure is invalid
0270             *
0271             * @since  1.7
0272             */
0273            public void removeElement(Element elem) {
0274                try {
0275                    writeLock();
0276                    removeElementImpl(elem);
0277                } finally {
0278                    writeUnlock();
0279                }
0280            }
0281
0282            private void removeElementImpl(Element elem) {
0283                if (elem.getDocument() != this ) {
0284                    throw new IllegalArgumentException(
0285                            "element doesn't belong to document");
0286                }
0287                BranchElement parent = (BranchElement) elem.getParentElement();
0288                if (parent == null) {
0289                    throw new IllegalArgumentException(
0290                            "can't remove the root element");
0291                }
0292
0293                int startOffset = elem.getStartOffset();
0294                int removeFrom = startOffset;
0295                int endOffset = elem.getEndOffset();
0296                int removeTo = endOffset;
0297                int lastEndOffset = getLength() + 1;
0298                Content content = getContent();
0299                boolean atEnd = false;
0300                boolean isComposedText = Utilities.isComposedTextElement(elem);
0301
0302                if (endOffset >= lastEndOffset) {
0303                    // element includes the last "\n" character, needs special handling
0304                    if (startOffset <= 0) {
0305                        throw new IllegalArgumentException(
0306                                "can't remove the whole content");
0307                    }
0308                    removeTo = lastEndOffset - 1; // last "\n" must not be removed
0309                    try {
0310                        if (content.getString(startOffset - 1, 1).charAt(0) == '\n') {
0311                            removeFrom--; // preceding leaf ends with "\n", remove it
0312                        }
0313                    } catch (BadLocationException ble) { // can't happen
0314                        throw new IllegalStateException(ble);
0315                    }
0316                    atEnd = true;
0317                }
0318                int length = removeTo - removeFrom;
0319
0320                DefaultDocumentEvent dde = new DefaultDocumentEvent(removeFrom,
0321                        length, DefaultDocumentEvent.EventType.REMOVE);
0322                UndoableEdit ue = null;
0323                // do not leave empty branch elements
0324                while (parent.getElementCount() == 1) {
0325                    elem = parent;
0326                    parent = (BranchElement) parent.getParentElement();
0327                    if (parent == null) { // shouldn't happen
0328                        throw new IllegalStateException(
0329                                "invalid element structure");
0330                    }
0331                }
0332                Element[] removed = { elem };
0333                Element[] added = {};
0334                int index = parent.getElementIndex(startOffset);
0335                parent.replace(index, 1, added);
0336                dde.addEdit(new ElementEdit(parent, index, removed, added));
0337                if (length > 0) {
0338                    try {
0339                        ue = content.remove(removeFrom, length);
0340                        if (ue != null) {
0341                            dde.addEdit(ue);
0342                        }
0343                    } catch (BadLocationException ble) {
0344                        // can only happen if the element structure is severely broken
0345                        throw new IllegalStateException(ble);
0346                    }
0347                    lastEndOffset -= length;
0348                }
0349
0350                if (atEnd) {
0351                    // preceding leaf element should be extended to cover orphaned "\n"
0352                    Element prevLeaf = parent.getElement(parent
0353                            .getElementCount() - 1);
0354                    while ((prevLeaf != null) && !prevLeaf.isLeaf()) {
0355                        prevLeaf = prevLeaf.getElement(prevLeaf
0356                                .getElementCount() - 1);
0357                    }
0358                    if (prevLeaf == null) { // shouldn't happen
0359                        throw new IllegalStateException(
0360                                "invalid element structure");
0361                    }
0362                    int prevStartOffset = prevLeaf.getStartOffset();
0363                    BranchElement prevParent = (BranchElement) prevLeaf
0364                            .getParentElement();
0365                    int prevIndex = prevParent.getElementIndex(prevStartOffset);
0366                    Element newElem = null;
0367                    newElem = createLeafElement(prevParent, prevLeaf
0368                            .getAttributes(), prevStartOffset, lastEndOffset);
0369                    Element[] prevRemoved = { prevLeaf };
0370                    Element[] prevAdded = { newElem };
0371                    prevParent.replace(prevIndex, 1, prevAdded);
0372                    dde.addEdit(new ElementEdit(prevParent, prevIndex,
0373                            prevRemoved, prevAdded));
0374                }
0375
0376                postRemoveUpdate(dde);
0377                dde.end();
0378                fireRemoveUpdate(dde);
0379                if (!(isComposedText && (ue != null))) {
0380                    // do not fire UndoabeEdit event for composed text edit (unsupported)
0381                    fireUndoableEditUpdate(new UndoableEditEvent(this , dde));
0382                }
0383            }
0384
0385            /**
0386             * Adds a new style into the logical style hierarchy.  Style attributes
0387             * resolve from bottom up so an attribute specified in a child
0388             * will override an attribute specified in the parent.
0389             *
0390             * @param nm   the name of the style (must be unique within the
0391             *   collection of named styles).  The name may be null if the style 
0392             *   is unnamed, but the caller is responsible
0393             *   for managing the reference returned as an unnamed style can't
0394             *   be fetched by name.  An unnamed style may be useful for things
0395             *   like character attribute overrides such as found in a style 
0396             *   run.
0397             * @param parent the parent style.  This may be null if unspecified
0398             *   attributes need not be resolved in some other style.
0399             * @return the style
0400             */
0401            public Style addStyle(String nm, Style parent) {
0402                StyleContext styles = (StyleContext) getAttributeContext();
0403                return styles.addStyle(nm, parent);
0404            }
0405
0406            /**
0407             * Removes a named style previously added to the document.  
0408             *
0409             * @param nm  the name of the style to remove
0410             */
0411            public void removeStyle(String nm) {
0412                StyleContext styles = (StyleContext) getAttributeContext();
0413                styles.removeStyle(nm);
0414            }
0415
0416            /**
0417             * Fetches a named style previously added.
0418             *
0419             * @param nm  the name of the style
0420             * @return the style
0421             */
0422            public Style getStyle(String nm) {
0423                StyleContext styles = (StyleContext) getAttributeContext();
0424                return styles.getStyle(nm);
0425            }
0426
0427            /**
0428             * Fetches the list of of style names.
0429             *
0430             * @return all the style names
0431             */
0432            public Enumeration<?> getStyleNames() {
0433                return ((StyleContext) getAttributeContext()).getStyleNames();
0434            }
0435
0436            /**
0437             * Sets the logical style to use for the paragraph at the
0438             * given position.  If attributes aren't explicitly set 
0439             * for character and paragraph attributes they will resolve 
0440             * through the logical style assigned to the paragraph, which
0441             * in turn may resolve through some hierarchy completely 
0442             * independent of the element hierarchy in the document.
0443             * <p>
0444             * This method is thread safe, although most Swing methods
0445             * are not. Please see 
0446             * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
0447             * to Use Threads</A> for more information.     
0448             *
0449             * @param pos the offset from the start of the document >= 0
0450             * @param s  the logical style to assign to the paragraph, null if none
0451             */
0452            public void setLogicalStyle(int pos, Style s) {
0453                Element paragraph = getParagraphElement(pos);
0454                if ((paragraph != null)
0455                        && (paragraph instanceof  AbstractElement)) {
0456                    try {
0457                        writeLock();
0458                        StyleChangeUndoableEdit edit = new StyleChangeUndoableEdit(
0459                                (AbstractElement) paragraph, s);
0460                        ((AbstractElement) paragraph).setResolveParent(s);
0461                        int p0 = paragraph.getStartOffset();
0462                        int p1 = paragraph.getEndOffset();
0463                        DefaultDocumentEvent e = new DefaultDocumentEvent(p0,
0464                                p1 - p0, DocumentEvent.EventType.CHANGE);
0465                        e.addEdit(edit);
0466                        e.end();
0467                        fireChangedUpdate(e);
0468                        fireUndoableEditUpdate(new UndoableEditEvent(this , e));
0469                    } finally {
0470                        writeUnlock();
0471                    }
0472                }
0473            }
0474
0475            /** 
0476             * Fetches the logical style assigned to the paragraph 
0477             * represented by the given position.
0478             *
0479             * @param p the location to translate to a paragraph
0480             *  and determine the logical style assigned >= 0.  This
0481             *  is an offset from the start of the document.
0482             * @return the style, null if none
0483             */
0484            public Style getLogicalStyle(int p) {
0485                Style s = null;
0486                Element paragraph = getParagraphElement(p);
0487                if (paragraph != null) {
0488                    AttributeSet a = paragraph.getAttributes();
0489                    AttributeSet parent = a.getResolveParent();
0490                    if (parent instanceof  Style) {
0491                        s = (Style) parent;
0492                    }
0493                }
0494                return s;
0495            }
0496
0497            /**
0498             * Sets attributes for some part of the document.
0499             * A write lock is held by this operation while changes
0500             * are being made, and a DocumentEvent is sent to the listeners 
0501             * after the change has been successfully completed.
0502             * <p>
0503             * This method is thread safe, although most Swing methods
0504             * are not. Please see 
0505             * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
0506             * to Use Threads</A> for more information.     
0507             *
0508             * @param offset the offset in the document >= 0
0509             * @param length the length >= 0
0510             * @param s the attributes
0511             * @param replace true if the previous attributes should be replaced
0512             *  before setting the new attributes
0513             */
0514            public void setCharacterAttributes(int offset, int length,
0515                    AttributeSet s, boolean replace) {
0516                if (length == 0) {
0517                    return;
0518                }
0519                try {
0520                    writeLock();
0521                    DefaultDocumentEvent changes = new DefaultDocumentEvent(
0522                            offset, length, DocumentEvent.EventType.CHANGE);
0523
0524                    // split elements that need it
0525                    buffer.change(offset, length, changes);
0526
0527                    AttributeSet sCopy = s.copyAttributes();
0528
0529                    // PENDING(prinz) - this isn't a very efficient way to iterate
0530                    int lastEnd = Integer.MAX_VALUE;
0531                    for (int pos = offset; pos < (offset + length); pos = lastEnd) {
0532                        Element run = getCharacterElement(pos);
0533                        lastEnd = run.getEndOffset();
0534                        if (pos == lastEnd) {
0535                            // offset + length beyond length of document, bail.
0536                            break;
0537                        }
0538                        MutableAttributeSet attr = (MutableAttributeSet) run
0539                                .getAttributes();
0540                        changes.addEdit(new AttributeUndoableEdit(run, sCopy,
0541                                replace));
0542                        if (replace) {
0543                            attr.removeAttributes(attr);
0544                        }
0545                        attr.addAttributes(s);
0546                    }
0547                    changes.end();
0548                    fireChangedUpdate(changes);
0549                    fireUndoableEditUpdate(new UndoableEditEvent(this , changes));
0550                } finally {
0551                    writeUnlock();
0552                }
0553
0554            }
0555
0556            /**
0557             * Sets attributes for a paragraph.
0558             * <p>
0559             * This method is thread safe, although most Swing methods
0560             * are not. Please see 
0561             * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
0562             * to Use Threads</A> for more information.     
0563             *
0564             * @param offset the offset into the paragraph >= 0
0565             * @param length the number of characters affected >= 0
0566             * @param s the attributes
0567             * @param replace whether to replace existing attributes, or merge them
0568             */
0569            public void setParagraphAttributes(int offset, int length,
0570                    AttributeSet s, boolean replace) {
0571                try {
0572                    writeLock();
0573                    DefaultDocumentEvent changes = new DefaultDocumentEvent(
0574                            offset, length, DocumentEvent.EventType.CHANGE);
0575
0576                    AttributeSet sCopy = s.copyAttributes();
0577
0578                    // PENDING(prinz) - this assumes a particular element structure
0579                    Element section = getDefaultRootElement();
0580                    int index0 = section.getElementIndex(offset);
0581                    int index1 = section.getElementIndex(offset
0582                            + ((length > 0) ? length - 1 : 0));
0583                    boolean isI18N = Boolean.TRUE
0584                            .equals(getProperty(I18NProperty));
0585                    boolean hasRuns = false;
0586                    for (int i = index0; i <= index1; i++) {
0587                        Element paragraph = section.getElement(i);
0588                        MutableAttributeSet attr = (MutableAttributeSet) paragraph
0589                                .getAttributes();
0590                        changes.addEdit(new AttributeUndoableEdit(paragraph,
0591                                sCopy, replace));
0592                        if (replace) {
0593                            attr.removeAttributes(attr);
0594                        }
0595                        attr.addAttributes(s);
0596                        if (isI18N && !hasRuns) {
0597                            hasRuns = (attr
0598                                    .getAttribute(TextAttribute.RUN_DIRECTION) != null);
0599                        }
0600                    }
0601
0602                    if (hasRuns) {
0603                        updateBidi(changes);
0604                    }
0605
0606                    changes.end();
0607                    fireChangedUpdate(changes);
0608                    fireUndoableEditUpdate(new UndoableEditEvent(this , changes));
0609                } finally {
0610                    writeUnlock();
0611                }
0612            }
0613
0614            /**
0615             * Gets the paragraph element at the offset <code>pos</code>.
0616             * A paragraph consists of at least one child Element, which is usually
0617             * a leaf.
0618             *
0619             * @param pos the starting offset >= 0
0620             * @return the element
0621             */
0622            public Element getParagraphElement(int pos) {
0623                Element e = null;
0624                for (e = getDefaultRootElement(); !e.isLeaf();) {
0625                    int index = e.getElementIndex(pos);
0626                    e = e.getElement(index);
0627                }
0628                if (e != null)
0629                    return e.getParentElement();
0630                return e;
0631            }
0632
0633            /**
0634             * Gets a character element based on a position.
0635             *
0636             * @param pos the position in the document >= 0
0637             * @return the element
0638             */
0639            public Element getCharacterElement(int pos) {
0640                Element e = null;
0641                for (e = getDefaultRootElement(); !e.isLeaf();) {
0642                    int index = e.getElementIndex(pos);
0643                    e = e.getElement(index);
0644                }
0645                return e;
0646            }
0647
0648            // --- local methods -------------------------------------------------
0649
0650            /**
0651             * Updates document structure as a result of text insertion.  This
0652             * will happen within a write lock.  This implementation simply
0653             * parses the inserted content for line breaks and builds up a set
0654             * of instructions for the element buffer.
0655             *
0656             * @param chng a description of the document change
0657             * @param attr the attributes
0658             */
0659            protected void insertUpdate(DefaultDocumentEvent chng,
0660                    AttributeSet attr) {
0661                int offset = chng.getOffset();
0662                int length = chng.getLength();
0663                if (attr == null) {
0664                    attr = SimpleAttributeSet.EMPTY;
0665                }
0666
0667                // Paragraph attributes should come from point after insertion.
0668                // You really only notice this when inserting at a paragraph
0669                // boundary.
0670                Element paragraph = getParagraphElement(offset + length);
0671                AttributeSet pattr = paragraph.getAttributes();
0672                // Character attributes should come from actual insertion point.
0673                Element pParagraph = getParagraphElement(offset);
0674                Element run = pParagraph.getElement(pParagraph
0675                        .getElementIndex(offset));
0676                int endOffset = offset + length;
0677                boolean insertingAtBoundry = (run.getEndOffset() == endOffset);
0678                AttributeSet cattr = run.getAttributes();
0679
0680                try {
0681                    Segment s = new Segment();
0682                    Vector parseBuffer = new Vector();
0683                    ElementSpec lastStartSpec = null;
0684                    boolean insertingAfterNewline = false;
0685                    short lastStartDirection = ElementSpec.OriginateDirection;
0686                    // Check if the previous character was a newline.
0687                    if (offset > 0) {
0688                        getText(offset - 1, 1, s);
0689                        if (s.array[s.offset] == '\n') {
0690                            // Inserting after a newline.
0691                            insertingAfterNewline = true;
0692                            lastStartDirection = createSpecsForInsertAfterNewline(
0693                                    paragraph, pParagraph, pattr, parseBuffer,
0694                                    offset, endOffset);
0695                            for (int counter = parseBuffer.size() - 1; counter >= 0; counter--) {
0696                                ElementSpec spec = (ElementSpec) parseBuffer
0697                                        .elementAt(counter);
0698                                if (spec.getType() == ElementSpec.StartTagType) {
0699                                    lastStartSpec = spec;
0700                                    break;
0701                                }
0702                            }
0703                        }
0704                    }
0705                    // If not inserting after a new line, pull the attributes for
0706                    // new paragraphs from the paragraph under the insertion point.
0707                    if (!insertingAfterNewline)
0708                        pattr = pParagraph.getAttributes();
0709
0710                    getText(offset, length, s);
0711                    char[] txt = s.array;
0712                    int n = s.offset + s.count;
0713                    int lastOffset = s.offset;
0714
0715                    for (int i = s.offset; i < n; i++) {
0716                        if (txt[i] == '\n') {
0717                            int breakOffset = i + 1;
0718                            parseBuffer.addElement(new ElementSpec(attr,
0719                                    ElementSpec.ContentType, breakOffset
0720                                            - lastOffset));
0721                            parseBuffer.addElement(new ElementSpec(null,
0722                                    ElementSpec.EndTagType));
0723                            lastStartSpec = new ElementSpec(pattr,
0724                                    ElementSpec.StartTagType);
0725                            parseBuffer.addElement(lastStartSpec);
0726                            lastOffset = breakOffset;
0727                        }
0728                    }
0729                    if (lastOffset < n) {
0730                        parseBuffer.addElement(new ElementSpec(attr,
0731                                ElementSpec.ContentType, n - lastOffset));
0732                    }
0733
0734                    ElementSpec first = (ElementSpec) parseBuffer
0735                            .firstElement();
0736
0737                    int docLength = getLength();
0738
0739                    // Check for join previous of first content.
0740                    if (first.getType() == ElementSpec.ContentType
0741                            && cattr.isEqual(attr)) {
0742                        first.setDirection(ElementSpec.JoinPreviousDirection);
0743                    }
0744
0745                    // Do a join fracture/next for last start spec if necessary.
0746                    if (lastStartSpec != null) {
0747                        if (insertingAfterNewline) {
0748                            lastStartSpec.setDirection(lastStartDirection);
0749                        }
0750                        // Join to the fracture if NOT inserting at the end
0751                        // (fracture only happens when not inserting at end of
0752                        // paragraph).
0753                        else if (pParagraph.getEndOffset() != endOffset) {
0754                            lastStartSpec
0755                                    .setDirection(ElementSpec.JoinFractureDirection);
0756                        }
0757                        // Join to next if parent of pParagraph has another
0758                        // element after pParagraph, and it isn't a leaf.
0759                        else {
0760                            Element parent = pParagraph.getParentElement();
0761                            int pParagraphIndex = parent
0762                                    .getElementIndex(offset);
0763                            if ((pParagraphIndex + 1) < parent
0764                                    .getElementCount()
0765                                    && !parent.getElement(pParagraphIndex + 1)
0766                                            .isLeaf()) {
0767                                lastStartSpec
0768                                        .setDirection(ElementSpec.JoinNextDirection);
0769                            }
0770                        }
0771                    }
0772
0773                    // Do a JoinNext for last spec if it is content, it doesn't
0774                    // already have a direction set, no new paragraphs have been
0775                    // inserted or a new paragraph has been inserted and its join
0776                    // direction isn't originate, and the element at endOffset 
0777                    // is a leaf.
0778                    if (insertingAtBoundry && endOffset < docLength) {
0779                        ElementSpec last = (ElementSpec) parseBuffer
0780                                .lastElement();
0781                        if (last.getType() == ElementSpec.ContentType
0782                                && last.getDirection() != ElementSpec.JoinPreviousDirection
0783                                && ((lastStartSpec == null && (paragraph == pParagraph || insertingAfterNewline)) || (lastStartSpec != null && lastStartSpec
0784                                        .getDirection() != ElementSpec.OriginateDirection))) {
0785                            Element nextRun = paragraph.getElement(paragraph
0786                                    .getElementIndex(endOffset));
0787                            // Don't try joining to a branch!
0788                            if (nextRun.isLeaf()
0789                                    && attr.isEqual(nextRun.getAttributes())) {
0790                                last
0791                                        .setDirection(ElementSpec.JoinNextDirection);
0792                            }
0793                        }
0794                    }
0795                    // If not inserting at boundary and there is going to be a
0796                    // fracture, then can join next on last content if cattr
0797                    // matches the new attributes.
0798                    else if (!insertingAtBoundry
0799                            && lastStartSpec != null
0800                            && lastStartSpec.getDirection() == ElementSpec.JoinFractureDirection) {
0801                        ElementSpec last = (ElementSpec) parseBuffer
0802                                .lastElement();
0803                        if (last.getType() == ElementSpec.ContentType
0804                                && last.getDirection() != ElementSpec.JoinPreviousDirection
0805                                && attr.isEqual(cattr)) {
0806                            last.setDirection(ElementSpec.JoinNextDirection);
0807                        }
0808                    }
0809
0810                    // Check for the composed text element. If it is, merge the character attributes
0811                    // into this element as well.
0812                    if (Utilities.isComposedTextAttributeDefined(attr)) {
0813                        ((MutableAttributeSet) attr).addAttributes(cattr);
0814                        ((MutableAttributeSet) attr).addAttribute(
0815                                AbstractDocument.ElementNameAttribute,
0816                                AbstractDocument.ContentElementName);
0817                    }
0818
0819                    ElementSpec[] spec = new ElementSpec[parseBuffer.size()];
0820                    parseBuffer.copyInto(spec);
0821                    buffer.insert(offset, length, spec, chng);
0822                } catch (BadLocationException bl) {
0823                }
0824
0825                super .insertUpdate(chng, attr);
0826            }
0827
0828            /**
0829             * This is called by insertUpdate when inserting after a new line.
0830             * It generates, in <code>parseBuffer</code>, ElementSpecs that will
0831             * position the stack in <code>paragraph</code>.<p>
0832             * It returns the direction the last StartSpec should have (this don't
0833             * necessarily create the last start spec).
0834             */
0835            short createSpecsForInsertAfterNewline(Element paragraph,
0836                    Element pParagraph, AttributeSet pattr, Vector parseBuffer,
0837                    int offset, int endOffset) {
0838                // Need to find the common parent of pParagraph and paragraph.
0839                if (paragraph.getParentElement() == pParagraph
0840                        .getParentElement()) {
0841                    // The simple (and common) case that pParagraph and
0842                    // paragraph have the same parent.
0843                    ElementSpec spec = new ElementSpec(pattr,
0844                            ElementSpec.EndTagType);
0845                    parseBuffer.addElement(spec);
0846                    spec = new ElementSpec(pattr, ElementSpec.StartTagType);
0847                    parseBuffer.addElement(spec);
0848                    if (pParagraph.getEndOffset() != endOffset)
0849                        return ElementSpec.JoinFractureDirection;
0850
0851                    Element parent = pParagraph.getParentElement();
0852                    if ((parent.getElementIndex(offset) + 1) < parent
0853                            .getElementCount())
0854                        return ElementSpec.JoinNextDirection;
0855                } else {
0856                    // Will only happen for text with more than 2 levels.
0857                    // Find the common parent of a paragraph and pParagraph
0858                    Vector leftParents = new Vector();
0859                    Vector rightParents = new Vector();
0860                    Element e = pParagraph;
0861                    while (e != null) {
0862                        leftParents.addElement(e);
0863                        e = e.getParentElement();
0864                    }
0865                    e = paragraph;
0866                    int leftIndex = -1;
0867                    while (e != null
0868                            && (leftIndex = leftParents.indexOf(e)) == -1) {
0869                        rightParents.addElement(e);
0870                        e = e.getParentElement();
0871                    }
0872                    if (e != null) {
0873                        // e identifies the common parent.
0874                        // Build the ends.
0875                        for (int counter = 0; counter < leftIndex; counter++) {
0876                            parseBuffer.addElement(new ElementSpec(null,
0877                                    ElementSpec.EndTagType));
0878                        }
0879                        // And the starts.
0880                        ElementSpec spec = null;
0881                        for (int counter = rightParents.size() - 1; counter >= 0; counter--) {
0882                            spec = new ElementSpec(((Element) rightParents
0883                                    .elementAt(counter)).getAttributes(),
0884                                    ElementSpec.StartTagType);
0885                            if (counter > 0)
0886                                spec
0887                                        .setDirection(ElementSpec.JoinNextDirection);
0888                            parseBuffer.addElement(spec);
0889                        }
0890                        // If there are right parents, then we generated starts
0891                        // down the right subtree and there will be an element to
0892                        // join to.
0893                        if (rightParents.size() > 0)
0894                            return ElementSpec.JoinNextDirection;
0895                        // No right subtree, e.getElement(endOffset) is a
0896                        // leaf. There will be a facture.
0897                        return ElementSpec.JoinFractureDirection;
0898                    }
0899                    // else: Could throw an exception here, but should never get here!
0900                }
0901                return ElementSpec.OriginateDirection;
0902            }
0903
0904            /**
0905             * Updates document structure as a result of text removal.
0906             *
0907             * @param chng a description of the document change
0908             */
0909            protected void removeUpdate(DefaultDocumentEvent chng) {
0910                super .removeUpdate(chng);
0911                buffer.remove(chng.getOffset(), chng.getLength(), chng);
0912            }
0913
0914            /**
0915             * Creates the root element to be used to represent the
0916             * default document structure.
0917             *
0918             * @return the element base
0919             */
0920            protected AbstractElement createDefaultRoot() {
0921                // grabs a write-lock for this initialization and
0922                // abandon it during initialization so in normal
0923                // operation we can detect an illegitimate attempt
0924                // to mutate attributes.
0925                writeLock();
0926                BranchElement section = new SectionElement();
0927                BranchElement paragraph = new BranchElement(section, null);
0928
0929                LeafElement brk = new LeafElement(paragraph, null, 0, 1);
0930                Element[] buff = new Element[1];
0931                buff[0] = brk;
0932                paragraph.replace(0, 0, buff);
0933
0934                buff[0] = paragraph;
0935                section.replace(0, 0, buff);
0936                writeUnlock();
0937                return section;
0938            }
0939
0940            /**
0941             * Gets the foreground color from an attribute set.
0942             *
0943             * @param attr the attribute set
0944             * @return the color
0945             */
0946            public Color getForeground(AttributeSet attr) {
0947                StyleContext styles = (StyleContext) getAttributeContext();
0948                return styles.getForeground(attr);
0949            }
0950
0951            /**
0952             * Gets the background color from an attribute set.
0953             *
0954             * @param attr the attribute set
0955             * @return the color
0956             */
0957            public Color getBackground(AttributeSet attr) {
0958                StyleContext styles = (StyleContext) getAttributeContext();
0959                return styles.getBackground(attr);
0960            }
0961
0962            /**
0963             * Gets the font from an attribute set.
0964             *
0965             * @param attr the attribute set
0966             * @return the font
0967             */
0968            public Font getFont(AttributeSet attr) {
0969                StyleContext styles = (StyleContext) getAttributeContext();
0970                return styles.getFont(attr);
0971            }
0972
0973            /**
0974             * Called when any of this document's styles have changed.
0975             * Subclasses may wish to be intelligent about what gets damaged.
0976             *
0977             * @param style The Style that has changed.
0978             */
0979            protected void styleChanged(Style style) {
0980                // Only propagate change updated if have content
0981                if (getLength() != 0) {
0982                    // lazily create a ChangeUpdateRunnable
0983                    if (updateRunnable == null) {
0984                        updateRunnable = new ChangeUpdateRunnable();
0985                    }
0986
0987                    // We may get a whole batch of these at once, so only
0988                    // queue the runnable if it is not already pending
0989                    synchronized (updateRunnable) {
0990                        if (!updateRunnable.isPending) {
0991                            SwingUtilities.invokeLater(updateRunnable);
0992                            updateRunnable.isPending = true;
0993                        }
0994                    }
0995                }
0996            }
0997
0998            /**
0999             * Adds a document listener for notification of any changes.
1000             *
1001             * @param listener the listener
1002             * @see Document#addDocumentListener
1003             */
1004            public void addDocumentListener(DocumentListener listener) {
1005                synchronized (listeningStyles) {
1006                    int oldDLCount = listenerList
1007                            .getListenerCount(DocumentListener.class);
1008                    super .addDocumentListener(listener);
1009                    if (oldDLCount == 0) {
1010                        if (styleContextChangeListener == null) {
1011                            styleContextChangeListener = createStyleContextChangeListener();
1012                        }
1013                        if (styleContextChangeListener != null) {
1014                            StyleContext styles = (StyleContext) getAttributeContext();
1015                            List<ChangeListener> staleListeners = AbstractChangeHandler
1016                                    .getStaleListeners(styleContextChangeListener);
1017                            for (ChangeListener l : staleListeners) {
1018                                styles.removeChangeListener(l);
1019                            }
1020                            styles
1021                                    .addChangeListener(styleContextChangeListener);
1022                        }
1023                        updateStylesListeningTo();
1024                    }
1025                }
1026            }
1027
1028            /**
1029             * Removes a document listener.
1030             *
1031             * @param listener the listener
1032             * @see Document#removeDocumentListener
1033             */
1034            public void removeDocumentListener(DocumentListener listener) {
1035                synchronized (listeningStyles) {
1036                    super .removeDocumentListener(listener);
1037                    if (listenerList.getListenerCount(DocumentListener.class) == 0) {
1038                        for (int counter = listeningStyles.size() - 1; counter >= 0; counter--) {
1039                            ((Style) listeningStyles.elementAt(counter))
1040                                    .removeChangeListener(styleChangeListener);
1041                        }
1042                        listeningStyles.removeAllElements();
1043                        if (styleContextChangeListener != null) {
1044                            StyleContext styles = (StyleContext) getAttributeContext();
1045                            styles
1046                                    .removeChangeListener(styleContextChangeListener);
1047                        }
1048                    }
1049                }
1050            }
1051
1052            /**
1053             * Returns a new instance of StyleChangeHandler.
1054             */
1055            ChangeListener createStyleChangeListener() {
1056                return new StyleChangeHandler(this );
1057            }
1058
1059            /**
1060             * Returns a new instance of StyleContextChangeHandler.
1061             */
1062            ChangeListener createStyleContextChangeListener() {
1063                return new StyleContextChangeHandler(this );
1064            }
1065
1066            /**
1067             * Adds a ChangeListener to new styles, and removes ChangeListener from
1068             * old styles.
1069             */
1070            void updateStylesListeningTo() {
1071                synchronized (listeningStyles) {
1072                    StyleContext styles = (StyleContext) getAttributeContext();
1073                    if (styleChangeListener == null) {
1074                        styleChangeListener = createStyleChangeListener();
1075                    }
1076                    if (styleChangeListener != null && styles != null) {
1077                        Enumeration styleNames = styles.getStyleNames();
1078                        Vector v = (Vector) listeningStyles.clone();
1079                        listeningStyles.removeAllElements();
1080                        List<ChangeListener> staleListeners = AbstractChangeHandler
1081                                .getStaleListeners(styleChangeListener);
1082                        while (styleNames.hasMoreElements()) {
1083                            String name = (String) styleNames.nextElement();
1084                            Style aStyle = styles.getStyle(name);
1085                            int index = v.indexOf(aStyle);
1086                            listeningStyles.addElement(aStyle);
1087                            if (index == -1) {
1088                                for (ChangeListener l : staleListeners) {
1089                                    aStyle.removeChangeListener(l);
1090                                }
1091                                aStyle.addChangeListener(styleChangeListener);
1092                            } else {
1093                                v.removeElementAt(index);
1094                            }
1095                        }
1096                        for (int counter = v.size() - 1; counter >= 0; counter--) {
1097                            Style aStyle = (Style) v.elementAt(counter);
1098                            aStyle.removeChangeListener(styleChangeListener);
1099                        }
1100                        if (listeningStyles.size() == 0) {
1101                            styleChangeListener = null;
1102                        }
1103                    }
1104                }
1105            }
1106
1107            private void readObject(ObjectInputStream s)
1108                    throws ClassNotFoundException, IOException {
1109                listeningStyles = new Vector();
1110                s.defaultReadObject();
1111                // Reinstall style listeners.
1112                if (styleContextChangeListener == null
1113                        && listenerList
1114                                .getListenerCount(DocumentListener.class) > 0) {
1115                    styleContextChangeListener = createStyleContextChangeListener();
1116                    if (styleContextChangeListener != null) {
1117                        StyleContext styles = (StyleContext) getAttributeContext();
1118                        styles.addChangeListener(styleContextChangeListener);
1119                    }
1120                    updateStylesListeningTo();
1121                }
1122            }
1123
1124            // --- member variables -----------------------------------------------------------
1125
1126            /**
1127             * The default size of the initial content buffer.
1128             */
1129            public static final int BUFFER_SIZE_DEFAULT = 4096;
1130
1131            protected ElementBuffer buffer;
1132
1133            /** Styles listening to. */
1134            private transient Vector listeningStyles;
1135
1136            /** Listens to Styles. */
1137            private transient ChangeListener styleChangeListener;
1138
1139            /** Listens to Styles. */
1140            private transient ChangeListener styleContextChangeListener;
1141
1142            /** Run to create a change event for the document */
1143            private transient ChangeUpdateRunnable updateRunnable;
1144
1145            /**
1146             * Default root element for a document... maps out the 
1147             * paragraphs/lines contained.
1148             * <p>
1149             * <strong>Warning:</strong>
1150             * Serialized objects of this class will not be compatible with
1151             * future Swing releases. The current serialization support is
1152             * appropriate for short term storage or RMI between applications running
1153             * the same version of Swing.  As of 1.4, support for long term storage
1154             * of all JavaBeans<sup><font size="-2">TM</font></sup>
1155             * has been added to the <code>java.beans</code> package.
1156             * Please see {@link java.beans.XMLEncoder}.
1157             */
1158            protected class SectionElement extends BranchElement {
1159
1160                /**
1161                 * Creates a new SectionElement.
1162                 */
1163                public SectionElement() {
1164                    super (null, null);
1165                }
1166
1167                /**
1168                 * Gets the name of the element.
1169                 *
1170                 * @return the name
1171                 */
1172                public String getName() {
1173                    return SectionElementName;
1174                }
1175            }
1176
1177            /**
1178             * Specification for building elements.
1179             * <p>
1180             * <strong>Warning:</strong>
1181             * Serialized objects of this class will not be compatible with
1182             * future Swing releases. The current serialization support is
1183             * appropriate for short term storage or RMI between applications running
1184             * the same version of Swing.  As of 1.4, support for long term storage
1185             * of all JavaBeans<sup><font size="-2">TM</font></sup>
1186             * has been added to the <code>java.beans</code> package.
1187             * Please see {@link java.beans.XMLEncoder}.
1188             */
1189            public static class ElementSpec {
1190
1191                /**
1192                 * A possible value for getType.  This specifies
1193                 * that this record type is a start tag and
1194                 * represents markup that specifies the start
1195                 * of an element.
1196                 */
1197                public static final short StartTagType = 1;
1198
1199                /**
1200                 * A possible value for getType.  This specifies
1201                 * that this record type is a end tag and
1202                 * represents markup that specifies the end
1203                 * of an element.
1204                 */
1205                public static final short EndTagType = 2;
1206
1207                /**
1208                 * A possible value for getType.  This specifies
1209                 * that this record type represents content.
1210                 */
1211                public static final short ContentType = 3;
1212
1213                /**
1214                 * A possible value for getDirection.  This specifies
1215                 * that the data associated with this record should
1216                 * be joined to what precedes it.
1217                 */
1218                public static final short JoinPreviousDirection = 4;
1219
1220                /**
1221                 * A possible value for getDirection.  This specifies
1222                 * that the data associated with this record should
1223                 * be joined to what follows it.
1224                 */
1225                public static final short JoinNextDirection = 5;
1226
1227                /**
1228                 * A possible value for getDirection.  This specifies
1229                 * that the data associated with this record should
1230                 * be used to originate a new element.  This would be
1231                 * the normal value.
1232                 */
1233                public static final short OriginateDirection = 6;
1234
1235                /**
1236                 * A possible value for getDirection.  This specifies
1237                 * that the data associated with this record should
1238                 * be joined to the fractured element.
1239                 */
1240                public static final short JoinFractureDirection = 7;
1241
1242                /**
1243                 * Constructor useful for markup when the markup will not
1244                 * be stored in the document.
1245                 *
1246                 * @param a the attributes for the element
1247                 * @param type the type of the element (StartTagType, EndTagType,
1248                 *  ContentType)
1249                 */
1250                public ElementSpec(AttributeSet a, short type) {
1251                    this (a, type, null, 0, 0);
1252                }
1253
1254                /**
1255                 * Constructor for parsing inside the document when
1256                 * the data has already been added, but len information
1257                 * is needed.
1258                 *
1259                 * @param a the attributes for the element
1260                 * @param type the type of the element (StartTagType, EndTagType,
1261                 *  ContentType)
1262                 * @param len the length >= 0
1263                 */
1264                public ElementSpec(AttributeSet a, short type, int len) {
1265                    this (a, type, null, 0, len);
1266                }
1267
1268                /**
1269                 * Constructor for creating a spec externally for batch
1270                 * input of content and markup into the document.
1271                 *
1272                 * @param a the attributes for the element
1273                 * @param type the type of the element (StartTagType, EndTagType,
1274                 *  ContentType)
1275                 * @param txt the text for the element
1276                 * @param offs the offset into the text >= 0
1277                 * @param len the length of the text >= 0
1278                 */
1279                public ElementSpec(AttributeSet a, short type, char[] txt,
1280                        int offs, int len) {
1281                    attr = a;
1282                    this .type = type;
1283                    this .data = txt;
1284                    this .offs = offs;
1285                    this .len = len;
1286                    this .direction = OriginateDirection;
1287                }
1288
1289                /**
1290                 * Sets the element type.
1291                 *
1292                 * @param type the type of the element (StartTagType, EndTagType,
1293                 *  ContentType)
1294                 */
1295                public void setType(short type) {
1296                    this .type = type;
1297                }
1298
1299                /**
1300                 * Gets the element type.
1301                 *
1302                 * @return  the type of the element (StartTagType, EndTagType,
1303                 *  ContentType)
1304                 */
1305                public short getType() {
1306                    return type;
1307                }
1308
1309                /**
1310                 * Sets the direction.
1311                 *
1312                 * @param direction the direction (JoinPreviousDirection,
1313                 *   JoinNextDirection)
1314                 */
1315                public void setDirection(short direction) {
1316                    this .direction = direction;
1317                }
1318
1319                /**
1320                 * Gets the direction.
1321                 *
1322                 * @return the direction (JoinPreviousDirection, JoinNextDirection)
1323                 */
1324                public short getDirection() {
1325                    return direction;
1326                }
1327
1328                /**
1329                 * Gets the element attributes.
1330                 *
1331                 * @return the attribute set
1332                 */
1333                public AttributeSet getAttributes() {
1334                    return attr;
1335                }
1336
1337                /**
1338                 * Gets the array of characters.
1339                 *
1340                 * @return the array
1341                 */
1342                public char[] getArray() {
1343                    return data;
1344                }
1345
1346                /**
1347                 * Gets the starting offset.
1348                 *
1349                 * @return the offset >= 0
1350                 */
1351                public int getOffset() {
1352                    return offs;
1353                }
1354
1355                /**
1356                 * Gets the length.
1357                 *
1358                 * @return the length >= 0
1359                 */
1360                public int getLength() {
1361                    return len;
1362                }
1363
1364                /**
1365                 * Converts the element to a string.
1366                 *
1367                 * @return the string
1368                 */
1369                public String toString() {
1370                    String tlbl = "??";
1371                    String plbl = "??";
1372                    switch (type) {
1373                    case StartTagType:
1374                        tlbl = "StartTag";
1375                        break;
1376                    case ContentType:
1377                        tlbl = "Content";
1378                        break;
1379                    case EndTagType:
1380                        tlbl = "EndTag";
1381                        break;
1382                    }
1383                    switch (direction) {
1384                    case JoinPreviousDirection:
1385                        plbl = "JoinPrevious";
1386                        break;
1387                    case JoinNextDirection:
1388                        plbl = "JoinNext";
1389                        break;
1390                    case OriginateDirection:
1391                        plbl = "Originate";
1392                        break;
1393                    case JoinFractureDirection:
1394                        plbl = "Fracture";
1395                        break;
1396                    }
1397                    return tlbl + ":" + plbl + ":" + getLength();
1398                }
1399
1400                private AttributeSet attr;
1401                private int len;
1402                private short type;
1403                private short direction;
1404
1405                private int offs;
1406                private char[] data;
1407            }
1408
1409            /**
1410             * Class to manage changes to the element
1411             * hierarchy.
1412             * <p>
1413             * <strong>Warning:</strong>
1414             * Serialized objects of this class will not be compatible with
1415             * future Swing releases. The current serialization support is
1416             * appropriate for short term storage or RMI between applications running
1417             * the same version of Swing.  As of 1.4, support for long term storage
1418             * of all JavaBeans<sup><font size="-2">TM</font></sup>
1419             * has been added to the <code>java.beans</code> package.
1420             * Please see {@link java.beans.XMLEncoder}.
1421             */
1422            public class ElementBuffer implements  Serializable {
1423
1424                /**
1425                 * Creates a new ElementBuffer.
1426                 *
1427                 * @param root the root element
1428                 * @since 1.4
1429                 */
1430                public ElementBuffer(Element root) {
1431                    this .root = root;
1432                    changes = new Vector();
1433                    path = new Stack();
1434                }
1435
1436                /**
1437                 * Gets the root element.
1438                 *
1439                 * @return the root element
1440                 */
1441                public Element getRootElement() {
1442                    return root;
1443                }
1444
1445                /**
1446                 * Inserts new content.
1447                 *
1448                 * @param offset the starting offset >= 0
1449                 * @param length the length >= 0
1450                 * @param data the data to insert
1451                 * @param de the event capturing this edit
1452                 */
1453                public void insert(int offset, int length, ElementSpec[] data,
1454                        DefaultDocumentEvent de) {
1455                    if (length == 0) {
1456                        // Nothing was inserted, no structure change.
1457                        return;
1458                    }
1459                    insertOp = true;
1460                    beginEdits(offset, length);
1461                    insertUpdate(data);
1462                    endEdits(de);
1463
1464                    insertOp = false;
1465                }
1466
1467                void create(int length, ElementSpec[] data,
1468                        DefaultDocumentEvent de) {
1469                    insertOp = true;
1470                    beginEdits(offset, length);
1471
1472                    // PENDING(prinz) this needs to be fixed to create a new
1473                    // root element as well, but requires changes to the
1474                    // DocumentEvent to inform the views that there is a new
1475                    // root element.
1476
1477                    // Recreate the ending fake element to have the correct offsets.
1478                    Element elem = root;
1479                    int index = elem.getElementIndex(0);
1480                    while (!elem.isLeaf()) {
1481                        Element child = elem.getElement(index);
1482                        push(elem, index);
1483                        elem = child;
1484                        index = elem.getElementIndex(0);
1485                    }
1486                    ElemChanges ec = (ElemChanges) path.peek();
1487                    Element child = ec.parent.getElement(ec.index);
1488                    ec.added
1489                            .addElement(createLeafElement(ec.parent, child
1490                                    .getAttributes(), getLength(), child
1491                                    .getEndOffset()));
1492                    ec.removed.addElement(child);
1493                    while (path.size() > 1) {
1494                        pop();
1495                    }
1496
1497                    int n = data.length;
1498
1499                    // Reset the root elements attributes.
1500                    AttributeSet newAttrs = null;
1501                    if (n > 0 && data[0].getType() == ElementSpec.StartTagType) {
1502                        newAttrs = data[0].getAttributes();
1503                    }
1504                    if (newAttrs == null) {
1505                        newAttrs = SimpleAttributeSet.EMPTY;
1506                    }
1507                    MutableAttributeSet attr = (MutableAttributeSet) root
1508                            .getAttributes();
1509                    de.addEdit(new AttributeUndoableEdit(root, newAttrs, true));
1510                    attr.removeAttributes(attr);
1511                    attr.addAttributes(newAttrs);
1512
1513                    // fold in the specified subtree
1514                    for (int i = 1; i < n; i++) {
1515                        insertElement(data[i]);
1516                    }
1517
1518                    // pop the remaining path
1519                    while (path.size() != 0) {
1520                        pop();
1521                    }
1522
1523                    endEdits(de);
1524                    insertOp = false;
1525                }
1526
1527                /**
1528                 * Removes content.
1529                 *
1530                 * @param offset the starting offset >= 0
1531                 * @param length the length >= 0
1532                 * @param de the event capturing this edit
1533                 */
1534                public void remove(int offset, int length,
1535                        DefaultDocumentEvent de) {
1536                    beginEdits(offset, length);
1537                    removeUpdate();
1538                    endEdits(de);
1539                }
1540
1541                /**
1542                 * Changes content.
1543                 *
1544                 * @param offset the starting offset >= 0
1545                 * @param length the length >= 0
1546                 * @param de the event capturing this edit
1547                 */
1548                public void change(int offset, int length,
1549                        DefaultDocumentEvent de) {
1550                    beginEdits(offset, length);
1551                    changeUpdate();
1552                    endEdits(de);
1553                }
1554
1555                /**
1556                 * Inserts an update into the document.
1557                 *
1558                 * @param data the elements to insert
1559                 */
1560                protected void insertUpdate(ElementSpec[] data) {
1561                    // push the path
1562                    Element elem = root;
1563                    int index = elem.getElementIndex(offset);
1564                    while (!elem.isLeaf()) {
1565                        Element child = elem.getElement(index);
1566                        push(elem, (child.isLeaf() ? index : index + 1));
1567                        elem = child;
1568                        index = elem.getElementIndex(offset);
1569                    }
1570
1571                    // Build a copy of the original path.
1572                    insertPath = new ElemChanges[path.size()];
1573                    path.copyInto(insertPath);
1574
1575                    // Haven't created the fracture yet.
1576                    createdFracture = false;
1577
1578                    // Insert the first content.
1579                    int i;
1580
1581                    recreateLeafs = false;
1582                    if (data[0].getType() == ElementSpec.ContentType) {
1583                        insertFirstContent(data);
1584                        pos += data[0].getLength();
1585                        i = 1;
1586                    } else {
1587                        fractureDeepestLeaf(data);
1588                        i = 0;
1589                    }
1590
1591                    // fold in the specified subtree
1592                    int n = data.length;
1593                    for (; i < n; i++) {
1594                        insertElement(data[i]);
1595                    }
1596
1597                    // Fracture, if we haven't yet.
1598                    if (!createdFracture)
1599                        fracture(-1);
1600
1601                    // pop the remaining path
1602                    while (path.size() != 0) {
1603                        pop();
1604                    }
1605
1606                    // Offset the last index if necessary.
1607                    if (offsetLastIndex && offsetLastIndexOnReplace) {
1608                        insertPath[insertPath.length - 1].index++;
1609                    }
1610
1611                    // Make sure an edit is going to be created for each of the
1612                    // original path items that have a change.
1613                    for (int counter = insertPath.length - 1; counter >= 0; counter--) {
1614                        ElemChanges change = insertPath[counter];
1615                        if (change.parent == fracturedParent)
1616                            change.added.addElement(fracturedChild);
1617                        if ((change.added.size() > 0 || change.removed.size() > 0)
1618                                && !changes.contains(change)) {
1619                            // PENDING(sky): Do I need to worry about order here?
1620                            changes.addElement(change);
1621                        }
1622                    }
1623
1624                    // An insert at 0 with an initial end implies some elements
1625                    // will have no children (the bottomost leaf would have length 0)
1626                    // this will find what element need to be removed and remove it.
1627                    if (offset == 0 && fracturedParent != null
1628                            && data[0].getType() == ElementSpec.EndTagType) {
1629                        int counter = 0;
1630                        while (counter < data.length
1631                                && data[counter].getType() == ElementSpec.EndTagType) {
1632                            counter++;
1633                        }
1634                        ElemChanges change = insertPath[insertPath.length
1635                                - counter - 1];
1636                        change.removed.insertElementAt(change.parent
1637                                .getElement(--change.index), 0);
1638                    }
1639                }
1640
1641                /**
1642                 * Updates the element structure in response to a removal from the
1643                 * associated sequence in the document.  Any elements consumed by the
1644                 * span of the removal are removed.  
1645                 */
1646                protected void removeUpdate() {
1647                    removeElements(root, offset, offset + length);
1648                }
1649
1650                /**
1651                 * Updates the element structure in response to a change in the
1652                 * document.
1653                 */
1654                protected void changeUpdate() {
1655                    boolean didEnd = split(offset, length);
1656                    if (!didEnd) {
1657                        // need to do the other end
1658                        while (path.size() != 0) {
1659                            pop();
1660                        }
1661                        split(offset + length, 0);
1662                    }
1663                    while (path.size() != 0) {
1664                        pop();
1665                    }
1666                }
1667
1668                boolean split(int offs, int len) {
1669                    boolean splitEnd = false;
1670                    // push the path
1671                    Element e = root;
1672                    int index = e.getElementIndex(offs);
1673                    while (!e.isLeaf()) {
1674                        push(e, index);
1675                        e = e.getElement(index);
1676                        index = e.getElementIndex(offs);
1677                    }
1678
1679                    ElemChanges ec = (ElemChanges) path.peek();
1680                    Element child = ec.parent.getElement(ec.index);
1681                    // make sure there is something to do... if the
1682                    // offset is already at a boundary then there is 
1683                    // nothing to do.
1684                    if (child.getStartOffset() < offs
1685                            && offs < child.getEndOffset()) {
1686                        // we need to split, now see if the other end is within
1687                        // the same parent.
1688                        int index0 = ec.index;
1689                        int index1 = index0;
1690                        if (((offs + len) < ec.parent.getEndOffset())
1691                                && (len != 0)) {
1692                            // it's a range split in the same parent
1693                            index1 = ec.parent.getElementIndex(offs + len);
1694                            if (index1 == index0) {
1695                                // it's a three-way split
1696                                ec.removed.addElement(child);
1697                                e = createLeafElement(ec.parent, child
1698                                        .getAttributes(), child
1699                                        .getStartOffset(), offs);
1700                                ec.added.addElement(e);
1701                                e = createLeafElement(ec.parent, child
1702                                        .getAttributes(), offs, offs + len);
1703                                ec.added.addElement(e);
1704                                e = createLeafElement(ec.parent, child
1705                                        .getAttributes(), offs + len, child
1706                                        .getEndOffset());
1707                                ec.added.addElement(e);
1708                                return true;
1709                            } else {
1710                                child = ec.parent.getElement(index1);
1711                                if ((offs + len) == child.getStartOffset()) {
1712                                    // end is already on a boundary
1713                                    index1 = index0;
1714                                }
1715                            }
1716                            splitEnd = true;
1717                        }
1718
1719                        // split the first location
1720                        pos = offs;
1721                        child = ec.parent.getElement(index0);
1722                        ec.removed.addElement(child);
1723                        e = createLeafElement(ec.parent, child.getAttributes(),
1724                                child.getStartOffset(), pos);
1725                        ec.added.addElement(e);
1726                        e = createLeafElement(ec.parent, child.getAttributes(),
1727                                pos, child.getEndOffset());
1728                        ec.added.addElement(e);
1729
1730                        // pick up things in the middle
1731                        for (int i = index0 + 1; i < index1; i++) {
1732                            child = ec.parent.getElement(i);
1733                            ec.removed.addElement(child);
1734                            ec.added.addElement(child);
1735                        }
1736
1737                        if (index1 != index0) {
1738                            child = ec.parent.getElement(index1);
1739                            pos = offs + len;
1740                            ec.removed.addElement(child);
1741                            e = createLeafElement(ec.parent, child
1742                                    .getAttributes(), child.getStartOffset(),
1743                                    pos);
1744                            ec.added.addElement(e);
1745                            e = createLeafElement(ec.parent, child
1746                                    .getAttributes(), pos, child.getEndOffset());
1747                            ec.added.addElement(e);
1748                        }
1749                    }
1750                    return splitEnd;
1751                }
1752
1753                /**
1754                 * Creates the UndoableEdit record for the edits made
1755                 * in the buffer.
1756                 */
1757                void endEdits(DefaultDocumentEvent de) {
1758                    int n = changes.size();
1759                    for (int i = 0; i < n; i++) {
1760                        ElemChanges ec = (ElemChanges) changes.elementAt(i);
1761                        Element[] removed = new Element[ec.removed.size()];
1762                        ec.removed.copyInto(removed);
1763                        Element[] added = new Element[ec.added.size()];
1764                        ec.added.copyInto(added);
1765                        int index = ec.index;
1766                        ((BranchElement) ec.parent).replace(index,
1767                                removed.length, added);
1768                        ElementEdit ee = new ElementEdit(
1769                                (BranchElement) ec.parent, index, removed,
1770                                added);
1771                        de.addEdit(ee);
1772                    }
1773
1774                    changes.removeAllElements();
1775                    path.removeAllElements();
1776
1777                    /*
1778                    for (int i = 0; i < n; i++) {
1779                    ElemChanges ec = (ElemChanges) changes.elementAt(i);
1780                    System.err.print("edited: " + ec.parent + " at: " + ec.index +
1781                        " removed " + ec.removed.size());
1782                    if (ec.removed.size() > 0) {
1783                        int r0 = ((Element) ec.removed.firstElement()).getStartOffset();
1784                        int r1 = ((Element) ec.removed.lastElement()).getEndOffset();
1785                        System.err.print("[" + r0 + "," + r1 + "]");
1786                    }
1787                    System.err.print(" added " + ec.added.size());
1788                    if (ec.added.size() > 0) {
1789                        int p0 = ((Element) ec.added.firstElement()).getStartOffset();
1790                        int p1 = ((Element) ec.added.lastElement()).getEndOffset();
1791                        System.err.print("[" + p0 + "," + p1 + "]");
1792                    }
1793                    System.err.println("");
1794                    }
1795                     */
1796                }
1797
1798                /**
1799                 * Initialize the buffer
1800                 */
1801                void beginEdits(int offset, int length) {
1802                    this .offset = offset;
1803                    this .length = length;
1804                    this .endOffset = offset + length;
1805                    pos = offset;
1806                    if (changes == null) {
1807                        changes = new Vector();
1808                    } else {
1809                        changes.removeAllElements();
1810                    }
1811                    if (path == null) {
1812                        path = new Stack();
1813                    } else {
1814                        path.removeAllElements();
1815                    }
1816                    fracturedParent = null;
1817                    fracturedChild = null;
1818                    offsetLastIndex = offsetLastIndexOnReplace = false;
1819                }
1820
1821                /**
1822                 * Pushes a new element onto the stack that represents
1823                 * the current path.
1824                 * @param record Whether or not the push should be
1825                 *  recorded as an element change or not.
1826                 * @param isFracture true if pushing on an element that was created
1827                 * as the result of a fracture.
1828                 */
1829                void push(Element e, int index, boolean isFracture) {
1830                    ElemChanges ec = new ElemChanges(e, index, isFracture);
1831                    path.push(ec);
1832                }
1833
1834                void push(Element e, int index) {
1835                    push(e, index, false);
1836                }
1837
1838                void pop() {
1839                    ElemChanges ec = (ElemChanges) path.peek();
1840                    path.pop();
1841                    if ((ec.added.size() > 0) || (ec.removed.size() > 0)) {
1842                        changes.addElement(ec);
1843                    } else if (!path.isEmpty()) {
1844                        Element e = ec.parent;
1845                        if (e.getElementCount() == 0) {
1846                            // if we pushed a branch element that didn't get
1847                            // used, make sure its not marked as having been added.
1848                            ec = (ElemChanges) path.peek();
1849                            ec.added.removeElement(e);
1850                        }
1851                    }
1852                }
1853
1854                /**
1855                 * move the current offset forward by n.
1856                 */
1857                void advance(int n) {
1858                    pos += n;
1859                }
1860
1861                void insertElement(ElementSpec es) {
1862                    ElemChanges ec = (ElemChanges) path.peek();
1863                    switch (es.getType()) {
1864                    case ElementSpec.StartTagType:
1865                        switch (es.getDirection()) {
1866                        case ElementSpec.JoinNextDirection:
1867                            // Don't create a new element, use the existing one
1868                            // at the specified location.
1869                            Element parent = ec.parent.getElement(ec.index);
1870
1871                            if (parent.isLeaf()) {
1872                                // This happens if inserting into a leaf, followed
1873                                // by a join next where next sibling is not a leaf.
1874                                if ((ec.index + 1) < ec.parent
1875                                        .getElementCount())
1876                                    parent = ec.parent.getElement(ec.index + 1);
1877                                else
1878                                    throw new StateInvariantError(
1879                                            "Join next to leaf");
1880                            }
1881                            // Not really a fracture, but need to treat it like
1882                            // one so that content join next will work correctly.
1883                            // We can do this because there will never be a join
1884                            // next followed by a join fracture.
1885                            push(parent, 0, true);
1886                            break;
1887                        case ElementSpec.JoinFractureDirection:
1888                            if (!createdFracture) {
1889                                // Should always be something on the stack!
1890                                fracture(path.size() - 1);
1891                            }
1892                            // If parent isn't a fracture, fracture will be
1893                            // fracturedChild.
1894                            if (!ec.isFracture) {
1895                                push(fracturedChild, 0, true);
1896                            } else
1897                                // Parent is a fracture, use 1st element.
1898                                push(ec.parent.getElement(0), 0, true);
1899                            break;
1900                        default:
1901                            Element belem = createBranchElement(ec.parent, es
1902                                    .getAttributes());
1903                            ec.added.addElement(belem);
1904                            push(belem, 0);
1905                            break;
1906                        }
1907                        break;
1908                    case ElementSpec.EndTagType:
1909                        pop();
1910                        break;
1911                    case ElementSpec.ContentType:
1912                        int len = es.getLength();
1913                        if (es.getDirection() != ElementSpec.JoinNextDirection) {
1914                            Element leaf = createLeafElement(ec.parent, es
1915                                    .getAttributes(), pos, pos + len);
1916                            ec.added.addElement(leaf);
1917                        } else {
1918                            // JoinNext on tail is only applicable if last element
1919                            // and attributes come from that of first element.
1920                            // With a little extra testing it would be possible
1921                            // to NOT due this again, as more than likely fracture()
1922                            // created this element.
1923                            if (!ec.isFracture) {
1924                                Element first = null;
1925                                if (insertPath != null) {
1926                                    for (int counter = insertPath.length - 1; counter >= 0; counter--) {
1927                                        if (insertPath[counter] == ec) {
1928                                            if (counter != (insertPath.length - 1))
1929                                                first = ec.parent
1930                                                        .getElement(ec.index);
1931                                            break;
1932                                        }
1933                                    }
1934                                }
1935                                if (first == null)
1936                                    first = ec.parent.getElement(ec.index + 1);
1937                                Element leaf = createLeafElement(ec.parent,
1938                                        first.getAttributes(), pos, first
1939                                                .getEndOffset());
1940                                ec.added.addElement(leaf);
1941                                ec.removed.addElement(first);
1942                            } else {
1943                                // Parent was fractured element.
1944                                Element first = ec.parent.getElement(0);
1945                                Element leaf = createLeafElement(ec.parent,
1946                                        first.getAttributes(), pos, first
1947                                                .getEndOffset());
1948                                ec.added.addElement(leaf);
1949                                ec.removed.addElement(first);
1950                            }
1951                        }
1952                        pos += len;
1953                        break;
1954                    }
1955                }
1956
1957                /**
1958                 * Remove the elements from <code>elem</code> in range
1959                 * <code>rmOffs0</code>, <code>rmOffs1</code>. This uses
1960                 * <code>canJoin</code> and <code>join</code> to handle joining
1961                 * the endpoints of the insertion.
1962                 *
1963                 * @return true if elem will no longer have any elements.
1964                 */
1965                boolean removeElements(Element elem, int rmOffs0, int rmOffs1) {
1966                    if (!elem.isLeaf()) {
1967                        // update path for changes
1968                        int index0 = elem.getElementIndex(rmOffs0);
1969                        int index1 = elem.getElementIndex(rmOffs1);
1970                        push(elem, index0);
1971                        ElemChanges ec = (ElemChanges) path.peek();
1972
1973                        // if the range is contained by one element,
1974                        // we just forward the request
1975                        if (index0 == index1) {
1976                            Element child0 = elem.getElement(index0);
1977                            if (rmOffs0 <= child0.getStartOffset()
1978                                    && rmOffs1 >= child0.getEndOffset()) {
1979                                // Element totally removed.
1980                                ec.removed.addElement(child0);
1981                            } else if (removeElements(child0, rmOffs0, rmOffs1)) {
1982                                ec.removed.addElement(child0);
1983                            }
1984                        } else {
1985                            // the removal range spans elements.  If we can join
1986                            // the two endpoints, do it.  Otherwise we remove the
1987                            // interior and forward to the endpoints.
1988                            Element child0 = elem.getElement(index0);
1989                            Element child1 = elem.getElement(index1);
1990                            boolean containsOffs1 = (rmOffs1 < elem
1991                                    .getEndOffset());
1992                            if (containsOffs1 && canJoin(child0, child1)) {
1993                                // remove and join
1994                                for (int i = index0; i <= index1; i++) {
1995                                    ec.removed.addElement(elem.getElement(i));
1996                                }
1997                                Element e = join(elem, child0, child1, rmOffs0,
1998                                        rmOffs1);
1999                                ec.added.addElement(e);
2000                            } else {
2001                                // remove interior and forward
2002                                int rmIndex0 = index0 + 1;
2003                                int rmIndex1 = index1 - 1;
2004                                if (child0.getStartOffset() == rmOffs0
2005                                        || (index0 == 0
2006                                                && child0.getStartOffset() > rmOffs0 && child0
2007                                                .getEndOffset() <= rmOffs1)) {
2008                                    // start element completely consumed
2009                                    child0 = null;
2010                                    rmIndex0 = index0;
2011                                }
2012                                if (!containsOffs1) {
2013                                    child1 = null;
2014                                    rmIndex1++;
2015                                } else if (child1.getStartOffset() == rmOffs1) {
2016                                    // end element not touched
2017                                    child1 = null;
2018                                }
2019                                if (rmIndex0 <= rmIndex1) {
2020                                    ec.index = rmIndex0;
2021                                }
2022                                for (int i = rmIndex0; i <= rmIndex1; i++) {
2023                                    ec.removed.addElement(elem.getElement(i));
2024                                }
2025                                if (child0 != null) {
2026                                    if (removeElements(child0, rmOffs0, rmOffs1)) {
2027                                        ec.removed.insertElementAt(child0, 0);
2028                                        ec.index = index0;
2029                                    }
2030                                }
2031                                if (child1 != null) {
2032                                    if (removeElements(child1, rmOffs0, rmOffs1)) {
2033                                        ec.removed.addElement(child1);
2034                                    }
2035                                }
2036                            }
2037                        }
2038
2039                        // publish changes
2040                        pop();
2041
2042                        // Return true if we no longer have any children.
2043                        if (elem.getElementCount() == (ec.removed.size() - ec.added
2044                                .size())) {
2045                            return true;
2046                        }
2047                    }
2048                    return false;
2049                }
2050
2051                /**
2052                 * Can the two given elements be coelesced together
2053                 * into one element?
2054                 */
2055                boolean canJoin(Element e0, Element e1) {
2056                    if ((e0 == null) || (e1 == null)) {
2057                        return false;
2058                    }
2059                    // Don't join a leaf to a branch.
2060                    boolean leaf0 = e0.isLeaf();
2061                    boolean leaf1 = e1.isLeaf();
2062                    if (leaf0 != leaf1) {
2063                        return false;
2064                    }
2065                    if (leaf0) {
2066                        // Only join leaves if the attributes match, otherwise
2067                        // style information will be lost.
2068                        return e0.getAttributes().isEqual(e1.getAttributes());
2069                    }
2070                    // Only join non-leafs if the names are equal. This may result
2071                    // in loss of style information, but this is typically acceptable
2072                    // for non-leafs.
2073                    String name0 = e0.getName();
2074                    String name1 = e1.getName();
2075                    if (name0 != null) {
2076                        return name0.equals(name1);
2077                    }
2078                    if (name1 != null) {
2079                        return name1.equals(name0);
2080                    }
2081                    // Both names null, treat as equal.
2082                    return true;
2083                }
2084
2085                /** 
2086                 * Joins the two elements carving out a hole for the
2087                 * given removed range.
2088                 */
2089                Element join(Element p, Element left, Element right,
2090                        int rmOffs0, int rmOffs1) {
2091                    if (left.isLeaf() && right.isLeaf()) {
2092                        return createLeafElement(p, left.getAttributes(), left
2093                                .getStartOffset(), right.getEndOffset());
2094                    } else if ((!left.isLeaf()) && (!right.isLeaf())) {
2095                        // join two branch elements.  This copies the children before
2096                        // the removal range on the left element, and after the removal
2097                        // range on the right element.  The two elements on the edge
2098                        // are joined if possible and needed.
2099                        Element to = createBranchElement(p, left
2100                                .getAttributes());
2101                        int ljIndex = left.getElementIndex(rmOffs0);
2102                        int rjIndex = right.getElementIndex(rmOffs1);
2103                        Element lj = left.getElement(ljIndex);
2104                        if (lj.getStartOffset() >= rmOffs0) {
2105                            lj = null;
2106                        }
2107                        Element rj = right.getElement(rjIndex);
2108                        if (rj.getStartOffset() == rmOffs1) {
2109                            rj = null;
2110                        }
2111                        Vector children = new Vector();
2112
2113                        // transfer the left
2114                        for (int i = 0; i < ljIndex; i++) {
2115                            children.addElement(clone(to, left.getElement(i)));
2116                        }
2117
2118                        // transfer the join/middle
2119                        if (canJoin(lj, rj)) {
2120                            Element e = join(to, lj, rj, rmOffs0, rmOffs1);
2121                            children.addElement(e);
2122                        } else {
2123                            if (lj != null) {
2124                                children.addElement(cloneAsNecessary(to, lj,
2125                                        rmOffs0, rmOffs1));
2126                            }
2127                            if (rj != null) {
2128                                children.addElement(cloneAsNecessary(to, rj,
2129                                        rmOffs0, rmOffs1));
2130                            }
2131                        }
2132
2133                        // transfer the right
2134                        int n = right.getElementCount();
2135                        for (int i = (rj == null) ? rjIndex : rjIndex + 1; i < n; i++) {
2136                            children.addElement(clone(to, right.getElement(i)));
2137                        }
2138
2139                        // install the children
2140                        Element[] c = new Element[children.size()];
2141                        children.copyInto(c);
2142                        ((BranchElement) to).replace(0, 0, c);
2143                        return to;
2144                    } else {
2145                        throw new StateInvariantError(
2146                                "No support to join leaf element with non-leaf element");
2147                    }
2148                }
2149
2150                /**
2151                 * Creates a copy of this element, with a different 
2152                 * parent.
2153                 *
2154                 * @param parent the parent element
2155                 * @param clonee the element to be cloned
2156                 * @return the copy
2157                 */
2158                public Element clone(Element parent, Element clonee) {
2159                    if (clonee.isLeaf()) {
2160                        return createLeafElement(parent,
2161                                clonee.getAttributes(),
2162                                clonee.getStartOffset(), clonee.getEndOffset());
2163                    }
2164                    Element e = createBranchElement(parent, clonee
2165                            .getAttributes());
2166                    int n = clonee.getElementCount();
2167                    Element[] children = new Element[n];
2168                    for (int i = 0; i < n; i++) {
2169                        children[i] = clone(e, clonee.getElement(i));
2170                    }
2171                    ((BranchElement) e).replace(0, 0, children);
2172                    return e;
2173                }
2174
2175                /**
2176                 * Creates a copy of this element, with a different 
2177                 * parent. Children of this element included in the
2178                 * removal range will be discarded.
2179                 */
2180                Element cloneAsNecessary(Element parent, Element clonee,
2181                        int rmOffs0, int rmOffs1) {
2182                    if (clonee.isLeaf()) {
2183                        return createLeafElement(parent,
2184                                clonee.getAttributes(),
2185                                clonee.getStartOffset(), clonee.getEndOffset());
2186                    }
2187                    Element e = createBranchElement(parent, clonee
2188                            .getAttributes());
2189                    int n = clonee.getElementCount();
2190                    ArrayList childrenList = new ArrayList(n);
2191                    for (int i = 0; i < n; i++) {
2192                        Element elem = clonee.getElement(i);
2193                        if (elem.getStartOffset() < rmOffs0
2194                                || elem.getEndOffset() > rmOffs1) {
2195                            childrenList.add(cloneAsNecessary(e, elem, rmOffs0,
2196                                    rmOffs1));
2197                        }
2198                    }
2199                    Element[] children = new Element[childrenList.size()];
2200                    children = (Element[]) childrenList.toArray(children);
2201                    ((BranchElement) e).replace(0, 0, children);
2202                    return e;
2203                }
2204
2205                /**
2206                 * Determines if a fracture needs to be performed. A fracture
2207                 * can be thought of as moving the right part of a tree to a
2208                 * new location, where the right part is determined by what has
2209                 * been inserted. <code>depth</code> is used to indicate a
2210                 * JoinToFracture is needed to an element at a depth
2211                 * of <code>depth</code>. Where the root is 0, 1 is the children
2212                 * of the root...
2213                 * <p>This will invoke <code>fractureFrom</code> if it is determined
2214                 * a fracture needs to happen.
2215                 */
2216                void fracture(int depth) {
2217                    int cLength = insertPath.length;
2218                    int lastIndex = -1;
2219                    boolean needRecreate = recreateLeafs;
2220                    ElemChanges lastChange = insertPath[cLength - 1];
2221                    // Use childAltered to determine when a child has been altered,
2222                    // that is the point of insertion is less than the element count.
2223                    boolean childAltered = ((lastChange.index + 1) < lastChange.parent
2224                            .getElementCount());
2225                    int deepestAlteredIndex = (needRecreate) ? cLength : -1;
2226                    int lastAlteredIndex = cLength - 1;
2227
2228                    createdFracture = true;
2229                    // Determine where to start recreating from.
2230                    // Start at - 2, as first one is indicated by recreateLeafs and
2231                    // childAltered.
2232                    for (int counter = cLength - 2; counter >= 0; counter--) {
2233                        ElemChanges change = insertPath[counter];
2234                        if (change.added.size() > 0 || counter == depth) {
2235                            lastIndex = counter;
2236                            if (!needRecreate && childAltered) {
2237                                needRecreate = true;
2238                                if (deepestAlteredIndex == -1)
2239                                    deepestAlteredIndex = lastAlteredIndex + 1;
2240                            }
2241                        }
2242                        if (!childAltered
2243                                && change.index < change.parent
2244                                        .getElementCount()) {
2245                            childAltered = true;
2246                            lastAlteredIndex = counter;
2247                        }
2248                    }
2249                    if (needRecreate) {
2250                        // Recreate all children to right of parent starting
2251                        // at lastIndex.
2252                        if (lastIndex == -1)
2253                            lastIndex = cLength - 1;
2254                        fractureFrom(insertPath, lastIndex, deepestAlteredIndex);
2255                    }
2256                }
2257
2258                /**
2259                 * Recreates the elements to the right of the insertion point.
2260                 * This starts at <code>startIndex</code> in <code>changed</code>,
2261                 * and calls duplicate to duplicate existing elements.
2262                 * This will also duplicate the elements along the insertion
2263                 * point, until a depth of <code>endFractureIndex</code> is
2264                 * reached, at which point only the elements to the right of
2265                 * the insertion point are duplicated.
2266                 */
2267                void fractureFrom(ElemChanges[] changed, int startIndex,
2268                        int endFractureIndex) {
2269                    // Recreate the element representing the inserted index.
2270                    ElemChanges change = changed[startIndex];
2271                    Element child;
2272                    Element newChild;
2273                    int changeLength = changed.length;
2274
2275                    if ((startIndex + 1) == changeLength)
2276                        child = change.parent.getElement(change.index);
2277                    else
2278                        child = change.parent.getElement(change.index - 1);
2279                    if (child.isLeaf()) {
2280                        newChild = createLeafElement(change.parent, child
2281                                .getAttributes(), Math.max(endOffset, child
2282                                .getStartOffset()), child.getEndOffset());
2283                    } else {
2284                        newChild = createBranchElement(change.parent, child
2285                                .getAttributes());
2286                    }
2287                    fracturedParent = change.parent;
2288                    fracturedChild = newChild;
2289
2290                    // Recreate all the elements to the right of the
2291                    // insertion point.
2292                    Element parent = newChild;
2293
2294                    while (++startIndex < endFractureIndex) {
2295                        boolean isEnd = ((startIndex + 1) == endFractureIndex);
2296                        boolean isEndLeaf = ((startIndex + 1) == changeLength);
2297
2298                        // Create the newChild, a duplicate of the elment at
2299                        // index. This isn't done if isEnd and offsetLastIndex are true
2300                        // indicating a join previous was done.
2301                        change = changed[startIndex];
2302
2303                        // Determine the child to duplicate, won't have to duplicate
2304                        // if at end of fracture, or offseting index.
2305                        if (isEnd) {
2306                            if (offsetLastIndex || !isEndLeaf)
2307                                child = null;
2308                            else
2309                                child = change.parent.getElement(change.index);
2310                        } else {
2311                            child = change.parent.getElement(change.index - 1);
2312                        }
2313                        // Duplicate it.
2314                        if (child != null) {
2315                            if (child.isLeaf()) {
2316                                newChild = createLeafElement(parent, child
2317                                        .getAttributes(), Math.max(endOffset,
2318                                        child.getStartOffset()), child
2319                                        .getEndOffset());
2320                            } else {
2321                                newChild = createBranchElement(parent, child
2322                                        .getAttributes());
2323                            }
2324                        } else
2325                            newChild = null;
2326
2327                        // Recreate the remaining children (there may be none).
2328                        int kidsToMove = change.parent.getElementCount()
2329                                - change.index;
2330                        Element[] kids;
2331                        int moveStartIndex;
2332                        int kidStartIndex = 1;
2333
2334                        if (newChild == null) {
2335                            // Last part of fracture.
2336                            if (isEndLeaf) {
2337                                kidsToMove--;
2338                                moveStartIndex = change.index + 1;
2339                            } else {
2340                                moveStartIndex = change.index;
2341                            }
2342                            kidStartIndex = 0;
2343                            kids = new Element[kidsToMove];
2344                        } else {
2345                            if (!isEnd) {
2346                                // Branch.
2347                                kidsToMove++;
2348                                moveStartIndex = change.index;
2349                            } else {
2350                                // Last leaf, need to recreate part of it.
2351                                moveStartIndex = change.index + 1;
2352                            }
2353                            kids = new Element[kidsToMove];
2354                            kids[0] = newChild;
2355                        }
2356
2357                        for (int counter = kidStartIndex; counter < kidsToMove; counter++) {
2358                            Element toMove = change.parent
2359                                    .getElement(moveStartIndex++);
2360                            kids[counter] = recreateFracturedElement(parent,
2361                                    toMove);
2362                            change.removed.addElement(toMove);
2363                        }
2364                        ((BranchElement) parent).replace(0, 0, kids);
2365                        parent = newChild;
2366                    }
2367                }
2368
2369                /**
2370                 * Recreates <code>toDuplicate</code>. This is called when an
2371                 * element needs to be created as the result of an insertion. This
2372                 * will recurse and create all the children. This is similiar to
2373                 * <code>clone</code>, but deteremines the offsets differently.
2374                 */
2375                Element recreateFracturedElement(Element parent,
2376                        Element toDuplicate) {
2377                    if (toDuplicate.isLeaf()) {
2378                        return createLeafElement(parent, toDuplicate
2379                                .getAttributes(), Math.max(toDuplicate
2380                                .getStartOffset(), endOffset), toDuplicate
2381                                .getEndOffset());
2382                    }
2383                    // Not a leaf
2384                    Element newParent = createBranchElement(parent, toDuplicate
2385                            .getAttributes());
2386                    int childCount = toDuplicate.getElementCount();
2387                    Element[] newKids = new Element[childCount];
2388                    for (int counter = 0; counter < childCount; counter++) {
2389                        newKids[counter] = recreateFracturedElement(newParent,
2390                                toDuplicate.getElement(counter));
2391                    }
2392                    ((BranchElement) newParent).replace(0, 0, newKids);
2393                    return newParent;
2394                }
2395
2396                /**
2397                 * Splits the bottommost leaf in <code>path</code>.
2398                 * This is called from insert when the first element is NOT content.
2399                 */
2400                void fractureDeepestLeaf(ElementSpec[] specs) {
2401                    // Split the bottommost leaf. It will be recreated elsewhere.
2402                    ElemChanges ec = (ElemChanges) path.peek();
2403                    Element child = ec.parent.getElement(ec.index);
2404                    // Inserts at offset 0 do not need to recreate child (it would
2405                    // have a length of 0!).
2406                    if (offset != 0) {
2407                        Element newChild = createLeafElement(ec.parent, child
2408                                .getAttributes(), child.getStartOffset(),
2409                                offset);
2410
2411                        ec.added.addElement(newChild);
2412                    }
2413                    ec.removed.addElement(child);
2414                    if (child.getEndOffset() != endOffset)
2415                        recreateLeafs = true;
2416                    else
2417                        offsetLastIndex = true;
2418                }
2419
2420                /**
2421                 * Inserts the first content. This needs to be separate to handle
2422                 * joining.
2423                 */
2424                void insertFirstContent(ElementSpec[] specs) {
2425                    ElementSpec firstSpec = specs[0];
2426                    ElemChanges ec = (ElemChanges) path.peek();
2427                    Element child = ec.parent.getElement(ec.index);
2428                    int firstEndOffset = offset + firstSpec.getLength();
2429                    boolean isOnlyContent = (specs.length == 1);
2430
2431                    switch (firstSpec.getDirection()) {
2432                    case ElementSpec.JoinPreviousDirection:
2433                        if (child.getEndOffset() != firstEndOffset
2434                                && !isOnlyContent) {
2435                            // Create the left split part containing new content.
2436                            Element newE = createLeafElement(ec.parent, child
2437                                    .getAttributes(), child.getStartOffset(),
2438                                    firstEndOffset);
2439                            ec.added.addElement(newE);
2440                            ec.removed.addElement(child);
2441                            // Remainder will be created later.
2442                            if (child.getEndOffset() != endOffset)
2443                                recreateLeafs = true;
2444                            else
2445                                offsetLastIndex = true;
2446                        } else {
2447                            offsetLastIndex = true;
2448                            offsetLastIndexOnReplace = true;
2449                        }
2450                        // else Inserted at end, and is total length.
2451                        // Update index incase something added/removed.
2452                        break;
2453                    case ElementSpec.JoinNextDirection:
2454                        if (offset != 0) {
2455                            // Recreate the first element, its offset will have
2456                            // changed.
2457                            Element newE = createLeafElement(ec.parent, child
2458                                    .getAttributes(), child.getStartOffset(),
2459                                    offset);
2460                            ec.added.addElement(newE);
2461                            // Recreate the second, merge part. We do no checking
2462                            // to see if JoinNextDirection is valid here!
2463                            Element nextChild = ec.parent
2464                                    .getElement(ec.index + 1);
2465                            if (isOnlyContent)
2466                                newE = createLeafElement(ec.parent, nextChild
2467                                        .getAttributes(), offset, nextChild
2468                                        .getEndOffset());
2469                            else
2470                                newE = createLeafElement(ec.parent, nextChild
2471                                        .getAttributes(), offset,
2472                                        firstEndOffset);
2473                            ec.added.addElement(newE);
2474                            ec.removed.addElement(child);
2475                            ec.removed.addElement(nextChild);
2476                        }
2477                        // else nothin to do.
2478                        // PENDING: if !isOnlyContent could raise here!
2479                        break;
2480                    default:
2481                        // Inserted into middle, need to recreate split left
2482                        // new content, and split right.
2483                        if (child.getStartOffset() != offset) {
2484                            Element newE = createLeafElement(ec.parent, child
2485                                    .getAttributes(), child.getStartOffset(),
2486                                    offset);
2487                            ec.added.addElement(newE);
2488                        }
2489                        ec.removed.addElement(child);
2490                        // new content
2491                        Element newE = createLeafElement(ec.parent, firstSpec
2492                                .getAttributes(), offset, firstEndOffset);
2493                        ec.added.addElement(newE);
2494                        if (child.getEndOffset() != endOffset) {
2495                            // Signals need to recreate right split later.
2496                            recreateLeafs = true;
2497                        } else {
2498                            offsetLastIndex = true;
2499                        }
2500                        break;
2501                    }
2502                }
2503
2504                Element root;
2505                transient int pos; // current position
2506                transient int offset;
2507                transient int length;
2508                transient int endOffset;
2509                transient Vector changes; // Vector<ElemChanges>
2510                transient Stack path; // Stack<ElemChanges>
2511                transient boolean insertOp;
2512
2513                transient boolean recreateLeafs; // For insert.
2514
2515                /** For insert, path to inserted elements. */
2516                transient ElemChanges[] insertPath;
2517                /** Only for insert, set to true when the fracture has been created. */
2518                transient boolean createdFracture;
2519                /** Parent that contains the fractured child. */
2520                transient Element fracturedParent;
2521                /** Fractured child. */
2522                transient Element fracturedChild;
2523                /** Used to indicate when fracturing that the last leaf should be
2524                 * skipped. */
2525                transient boolean offsetLastIndex;
2526                /** Used to indicate that the parent of the deepest leaf should
2527                 * offset the index by 1 when adding/removing elements in an
2528                 * insert. */
2529                transient boolean offsetLastIndexOnReplace;
2530
2531                /*
2532                 * Internal record used to hold element change specifications
2533                 */
2534                class ElemChanges {
2535
2536                    ElemChanges(Element parent, int index, boolean isFracture) {
2537                        this .parent = parent;
2538                        this .index = index;
2539                        this .isFracture = isFracture;
2540                        added = new Vector();
2541                        removed = new Vector();
2542                    }
2543
2544                    public String toString() {
2545                        return "added: " + added + "\nremoved: " + removed
2546                                + "\n";
2547                    }
2548
2549                    Element parent;
2550                    int index;
2551                    Vector added;
2552                    Vector removed;
2553                    boolean isFracture;
2554                }
2555
2556            }
2557
2558            /**
2559             * An UndoableEdit used to remember AttributeSet changes to an
2560             * Element.
2561             */
2562            public static class AttributeUndoableEdit extends
2563                    AbstractUndoableEdit {
2564                public AttributeUndoableEdit(Element element,
2565                        AttributeSet newAttributes, boolean isReplacing) {
2566                    super ();
2567                    this .element = element;
2568                    this .newAttributes = newAttributes;
2569                    this .isReplacing = isReplacing;
2570                    // If not replacing, it may be more efficient to only copy the
2571                    // changed values...
2572                    copy = element.getAttributes().copyAttributes();
2573                }
2574
2575                /**
2576                 * Redoes a change.
2577                 *
2578                 * @exception CannotRedoException if the change cannot be redone
2579                 */
2580                public void redo() throws CannotRedoException {
2581                    super .redo();
2582                    MutableAttributeSet as = (MutableAttributeSet) element
2583                            .getAttributes();
2584                    if (isReplacing)
2585                        as.removeAttributes(as);
2586                    as.addAttributes(newAttributes);
2587                }
2588
2589                /**
2590                 * Undoes a change.
2591                 *
2592                 * @exception CannotUndoException if the change cannot be undone
2593                 */
2594                public void undo() throws CannotUndoException {
2595                    super .undo();
2596                    MutableAttributeSet as = (MutableAttributeSet) element
2597                            .getAttributes();
2598                    as.removeAttributes(as);
2599                    as.addAttributes(copy);
2600                }
2601
2602                // AttributeSet containing additional entries, must be non-mutable!
2603                protected AttributeSet newAttributes;
2604                // Copy of the AttributeSet the Element contained.
2605                protected AttributeSet copy;
2606                // true if all the attributes in the element were removed first.
2607                protected boolean isReplacing;
2608                // Efected Element.
2609                protected Element element;
2610            }
2611
2612            /**
2613             * UndoableEdit for changing the resolve parent of an Element.
2614             */
2615            static class StyleChangeUndoableEdit extends AbstractUndoableEdit {
2616                public StyleChangeUndoableEdit(AbstractElement element,
2617                        Style newStyle) {
2618                    super ();
2619                    this .element = element;
2620                    this .newStyle = newStyle;
2621                    oldStyle = element.getResolveParent();
2622                }
2623
2624                /**
2625                 * Redoes a change.
2626                 *
2627                 * @exception CannotRedoException if the change cannot be redone
2628                 */
2629                public void redo() throws CannotRedoException {
2630                    super .redo();
2631                    element.setResolveParent(newStyle);
2632                }
2633
2634                /**
2635                 * Undoes a change.
2636                 *
2637                 * @exception CannotUndoException if the change cannot be undone
2638                 */
2639                public void undo() throws CannotUndoException {
2640                    super .undo();
2641                    element.setResolveParent(oldStyle);
2642                }
2643
2644                /** Element to change resolve parent of. */
2645                protected AbstractElement element;
2646                /** New style. */
2647                protected Style newStyle;
2648                /** Old style, before setting newStyle. */
2649                protected AttributeSet oldStyle;
2650            }
2651
2652            /**
2653             * Base class for style change handlers with support for stale objects detection.
2654             */
2655            abstract static class AbstractChangeHandler implements 
2656                    ChangeListener {
2657
2658                /* This has an implicit reference to the handler object.  */
2659                private class DocReference extends
2660                        WeakReference<DefaultStyledDocument> {
2661
2662                    DocReference(DefaultStyledDocument d, ReferenceQueue q) {
2663                        super (d, q);
2664                    }
2665
2666                    /**
2667                     * Return a reference to the style change handler object.
2668                     */
2669                    ChangeListener getListener() {
2670                        return AbstractChangeHandler.this ;
2671                    }
2672                }
2673
2674                /** Class-specific reference queues.  */
2675                private final static Map<Class, ReferenceQueue> queueMap = new HashMap<Class, ReferenceQueue>();
2676
2677                /** A weak reference to the document object.  */
2678                private DocReference doc;
2679
2680                AbstractChangeHandler(DefaultStyledDocument d) {
2681                    Class c = getClass();
2682                    ReferenceQueue q;
2683                    synchronized (queueMap) {
2684                        q = queueMap.get(c);
2685                        if (q == null) {
2686                            q = new ReferenceQueue();
2687                            queueMap.put(c, q);
2688                        }
2689                    }
2690                    doc = new DocReference(d, q);
2691                }
2692
2693                /**
2694                 * Return a list of stale change listeners.
2695                 *
2696                 * A change listener becomes "stale" when its document is cleaned by GC.
2697                 */
2698                static List<ChangeListener> getStaleListeners(ChangeListener l) {
2699                    List<ChangeListener> staleListeners = new ArrayList<ChangeListener>();
2700                    ReferenceQueue q = queueMap.get(l.getClass());
2701
2702                    if (q != null) {
2703                        DocReference r;
2704                        synchronized (q) {
2705                            while ((r = (DocReference) q.poll()) != null) {
2706                                staleListeners.add(r.getListener());
2707                            }
2708                        }
2709                    }
2710
2711                    return staleListeners;
2712                }
2713
2714                /**
2715                 * The ChangeListener wrapper which guards against dead documents.
2716                 */
2717                public void stateChanged(ChangeEvent e) {
2718                    DefaultStyledDocument d = doc.get();
2719                    if (d != null) {
2720                        fireStateChanged(d, e);
2721                    }
2722                }
2723
2724                /** Run the actual class-specific stateChanged() method.  */
2725                abstract void fireStateChanged(DefaultStyledDocument d,
2726                        ChangeEvent e);
2727            }
2728
2729            /**
2730             * Added to all the Styles. When instances of this receive a
2731             * stateChanged method, styleChanged is invoked.
2732             */
2733            static class StyleChangeHandler extends AbstractChangeHandler {
2734
2735                StyleChangeHandler(DefaultStyledDocument d) {
2736                    super (d);
2737                }
2738
2739                void fireStateChanged(DefaultStyledDocument d, ChangeEvent e) {
2740                    Object source = e.getSource();
2741                    if (source instanceof  Style) {
2742                        d.styleChanged((Style) source);
2743                    } else {
2744                        d.styleChanged(null);
2745                    }
2746                }
2747            }
2748
2749            /**
2750             * Added to the StyleContext. When the StyleContext changes, this invokes
2751             * <code>updateStylesListeningTo</code>.
2752             */
2753            static class StyleContextChangeHandler extends
2754                    AbstractChangeHandler {
2755
2756                StyleContextChangeHandler(DefaultStyledDocument d) {
2757                    super (d);
2758                }
2759
2760                void fireStateChanged(DefaultStyledDocument d, ChangeEvent e) {
2761                    d.updateStylesListeningTo();
2762                }
2763            }
2764
2765            /**
2766             * When run this creates a change event for the complete document
2767             * and fires it.
2768             */
2769            class ChangeUpdateRunnable implements  Runnable {
2770                boolean isPending = false;
2771
2772                public void run() {
2773                    synchronized (this ) {
2774                        isPending = false;
2775                    }
2776
2777                    try {
2778                        writeLock();
2779                        DefaultDocumentEvent dde = new DefaultDocumentEvent(0,
2780                                getLength(), DocumentEvent.EventType.CHANGE);
2781                        dde.end();
2782                        fireChangedUpdate(dde);
2783                    } finally {
2784                        writeUnlock();
2785                    }
2786                }
2787            }
2788        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.