2744 lines · 104.7 KB · Java
RawDownload
/* * Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation.  Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */package javax.swing.text; import java.awt.Color;import java.awt.Font;import java.awt.font.TextAttribute;import java.lang.ref.ReferenceQueue;import java.lang.ref.WeakReference;import java.util.Enumeration;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.Stack;import java.util.Vector;import java.util.ArrayList;import java.io.IOException;import java.io.ObjectInputStream;import java.io.Serializable;import javax.swing.event.*;import javax.swing.undo.AbstractUndoableEdit;import javax.swing.undo.CannotRedoException;import javax.swing.undo.CannotUndoException;import javax.swing.undo.UndoableEdit;import javax.swing.SwingUtilities;import static sun.swing.SwingUtilities2.IMPLIED_CR; /** * A document that can be marked up with character and paragraph * styles in a manner similar to the Rich Text Format.  The element * structure for this document represents style crossings for * style runs.  These style runs are mapped into a paragraph element * structure (which may reside in some other structure).  The * style runs break at paragraph boundaries since logical styles are * assigned to paragraph boundaries. * <p> * <strong>Warning:</strong> * Serialized objects of this class will not be compatible with * future Swing releases. The current serialization support is * appropriate for short term storage or RMI between applications running * the same version of Swing.  As of 1.4, support for long term storage * of all JavaBeans<sup><font size="-2">TM</font></sup> * has been added to the <code>java.beans</code> package. * Please see {@link java.beans.XMLEncoder}. * * @author  Timothy Prinzing * @see     Document * @see     AbstractDocument */public class DefaultStyledDocument extends AbstractDocument implements StyledDocument {     /**     * Constructs a styled document.     *     * @param c  the container for the content     * @param styles resources and style definitions which may     *  be shared across documents     */    public DefaultStyledDocument(Content c, StyleContext styles) {        super(c, styles);        listeningStyles = new Vector<Style>();        buffer = new ElementBuffer(createDefaultRoot());        Style defaultStyle = styles.getStyle(StyleContext.DEFAULT_STYLE);        setLogicalStyle(0, defaultStyle);    }     /**     * Constructs a styled document with the default content     * storage implementation and a shared set of styles.     *     * @param styles the styles     */    public DefaultStyledDocument(StyleContext styles) {        this(new GapContent(BUFFER_SIZE_DEFAULT), styles);    }     /**     * Constructs a default styled document.  This buffers     * input content by a size of <em>BUFFER_SIZE_DEFAULT</em>     * and has a style context that is scoped by the lifetime     * of the document and is not shared with other documents.     */    public DefaultStyledDocument() {        this(new GapContent(BUFFER_SIZE_DEFAULT), new StyleContext());    }     /**     * Gets the default root element.     *     * @return the root     * @see Document#getDefaultRootElement     */    public Element getDefaultRootElement() {        return buffer.getRootElement();    }     /**     * Initialize the document to reflect the given element     * structure (i.e. the structure reported by the     * <code>getDefaultRootElement</code> method.  If the     * document contained any data it will first be removed.     */    protected void create(ElementSpec[] data) {        try {            if (getLength() != 0) {                remove(0, getLength());            }            writeLock();             // install the content            Content c = getContent();            int n = data.length;            StringBuilder sb = new StringBuilder();            for (int i = 0; i < n; i++) {                ElementSpec es = data[i];                if (es.getLength() > 0) {                    sb.append(es.getArray(), es.getOffset(),  es.getLength());                }            }            UndoableEdit cEdit = c.insertString(0, sb.toString());             // build the event and element structure            int length = sb.length();            DefaultDocumentEvent evnt =                new DefaultDocumentEvent(0, length, DocumentEvent.EventType.INSERT);            evnt.addEdit(cEdit);            buffer.create(length, data, evnt);             // update bidi (possibly)            super.insertUpdate(evnt, null);             // notify the listeners            evnt.end();            fireInsertUpdate(evnt);            fireUndoableEditUpdate(new UndoableEditEvent(this, evnt));        } catch (BadLocationException ble) {            throw new StateInvariantError("problem initializing");        } finally {            writeUnlock();        }     }     /**     * Inserts new elements in bulk.  This is useful to allow     * parsing with the document in an unlocked state and     * prepare an element structure modification.  This method     * takes an array of tokens that describe how to update an     * element structure so the time within a write lock can     * be greatly reduced in an asynchronous update situation.     * <p>     * This method is thread safe, although most Swing methods     * are not. Please see     * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How     * to Use Threads</A> for more information.     *     * @param offset the starting offset >= 0     * @param data the element data     * @exception BadLocationException for an invalid starting offset     */    protected void insert(int offset, ElementSpec[] data) throws BadLocationException {        if (data == null || data.length == 0) {            return;        }         try {            writeLock();             // install the content            Content c = getContent();            int n = data.length;            StringBuilder sb = new StringBuilder();            for (int i = 0; i < n; i++) {                ElementSpec es = data[i];                if (es.getLength() > 0) {                    sb.append(es.getArray(), es.getOffset(),  es.getLength());                }            }            if (sb.length() == 0) {                // Nothing to insert, bail.                return;            }            UndoableEdit cEdit = c.insertString(offset, sb.toString());             // create event and build the element structure            int length = sb.length();            DefaultDocumentEvent evnt =                new DefaultDocumentEvent(offset, length, DocumentEvent.EventType.INSERT);            evnt.addEdit(cEdit);            buffer.insert(offset, length, data, evnt);             // update bidi (possibly)            super.insertUpdate(evnt, null);             // notify the listeners            evnt.end();            fireInsertUpdate(evnt);            fireUndoableEditUpdate(new UndoableEditEvent(this, evnt));        } finally {            writeUnlock();        }    }     /**     * Removes an element from this document.     *     * <p>The element is removed from its parent element, as well as     * the text in the range identified by the element.  If the     * element isn't associated with the document, {@code     * IllegalArgumentException} is thrown.</p>     *     * <p>As empty branch elements are not allowed in the document, if the     * element is the sole child, its parent element is removed as well,     * recursively.  This means that when replacing all the children of a     * particular element, new children should be added <em>before</em>     * removing old children.     *     * <p>Element removal results in two events being fired, the     * {@code DocumentEvent} for changes in element structure and {@code     * UndoableEditEvent} for changes in document content.</p>     *     * <p>If the element contains end-of-content mark (the last {@code     * "\n"} character in document), this character is not removed;     * instead, preceding leaf element is extended to cover the     * character.  If the last leaf already ends with {@code "\n",} it is     * included in content removal.</p>     *     * <p>If the element is {@code null,} {@code NullPointerException} is     * thrown.  If the element structure would become invalid after the removal,     * for example if the element is the document root element, {@code     * IllegalArgumentException} is thrown.  If the current element structure is     * invalid, {@code IllegalStateException} is thrown.</p>     *     * @param  elem                      the element to remove     * @throws NullPointerException      if the element is {@code null}     * @throws IllegalArgumentException  if the element could not be removed     * @throws IllegalStateException     if the element structure is invalid     *     * @since  1.7     */    public void removeElement(Element elem) {        try {            writeLock();            removeElementImpl(elem);        } finally {            writeUnlock();        }    }     private void removeElementImpl(Element elem) {        if (elem.getDocument() != this) {            throw new IllegalArgumentException("element doesn't belong to document");        }        BranchElement parent = (BranchElement) elem.getParentElement();        if (parent == null) {            throw new IllegalArgumentException("can't remove the root element");        }         int startOffset = elem.getStartOffset();        int removeFrom = startOffset;        int endOffset = elem.getEndOffset();        int removeTo = endOffset;        int lastEndOffset = getLength() + 1;        Content content = getContent();        boolean atEnd = false;        boolean isComposedText = Utilities.isComposedTextElement(elem);         if (endOffset >= lastEndOffset) {            // element includes the last "\n" character, needs special handling            if (startOffset <= 0) {                throw new IllegalArgumentException("can't remove the whole content");            }            removeTo = lastEndOffset - 1; // last "\n" must not be removed            try {                if (content.getString(startOffset - 1, 1).charAt(0) == '\n') {                    removeFrom--; // preceding leaf ends with "\n", remove it                }            } catch (BadLocationException ble) { // can't happen                throw new IllegalStateException(ble);            }            atEnd = true;        }        int length = removeTo - removeFrom;         DefaultDocumentEvent dde = new DefaultDocumentEvent(removeFrom,                length, DefaultDocumentEvent.EventType.REMOVE);        UndoableEdit ue = null;        // do not leave empty branch elements        while (parent.getElementCount() == 1) {            elem = parent;            parent = (BranchElement) parent.getParentElement();            if (parent == null) { // shouldn't happen                throw new IllegalStateException("invalid element structure");            }        }        Element[] removed = { elem };        Element[] added = {};        int index = parent.getElementIndex(startOffset);        parent.replace(index, 1, added);        dde.addEdit(new ElementEdit(parent, index, removed, added));        if (length > 0) {            try {                ue = content.remove(removeFrom, length);                if (ue != null) {                    dde.addEdit(ue);                }            } catch (BadLocationException ble) {                // can only happen if the element structure is severely broken                throw new IllegalStateException(ble);            }            lastEndOffset -= length;        }         if (atEnd) {            // preceding leaf element should be extended to cover orphaned "\n"            Element prevLeaf = parent.getElement(parent.getElementCount() - 1);            while ((prevLeaf != null) && !prevLeaf.isLeaf()) {                prevLeaf = prevLeaf.getElement(prevLeaf.getElementCount() - 1);            }            if (prevLeaf == null) { // shouldn't happen                throw new IllegalStateException("invalid element structure");            }            int prevStartOffset = prevLeaf.getStartOffset();            BranchElement prevParent = (BranchElement) prevLeaf.getParentElement();            int prevIndex = prevParent.getElementIndex(prevStartOffset);            Element newElem;            newElem = createLeafElement(prevParent, prevLeaf.getAttributes(),                                            prevStartOffset, lastEndOffset);            Element[] prevRemoved = { prevLeaf };            Element[] prevAdded = { newElem };            prevParent.replace(prevIndex, 1, prevAdded);            dde.addEdit(new ElementEdit(prevParent, prevIndex,                                                    prevRemoved, prevAdded));        }         postRemoveUpdate(dde);        dde.end();        fireRemoveUpdate(dde);        if (! (isComposedText && (ue != null))) {            // do not fire UndoabeEdit event for composed text edit (unsupported)            fireUndoableEditUpdate(new UndoableEditEvent(this, dde));        }    }     /**     * Adds a new style into the logical style hierarchy.  Style attributes     * resolve from bottom up so an attribute specified in a child     * will override an attribute specified in the parent.     *     * @param nm   the name of the style (must be unique within the     *   collection of named styles).  The name may be null if the style     *   is unnamed, but the caller is responsible     *   for managing the reference returned as an unnamed style can't     *   be fetched by name.  An unnamed style may be useful for things     *   like character attribute overrides such as found in a style     *   run.     * @param parent the parent style.  This may be null if unspecified     *   attributes need not be resolved in some other style.     * @return the style     */    public Style addStyle(String nm, Style parent) {        StyleContext styles = (StyleContext) getAttributeContext();        return styles.addStyle(nm, parent);    }     /**     * Removes a named style previously added to the document.     *     * @param nm  the name of the style to remove     */    public void removeStyle(String nm) {        StyleContext styles = (StyleContext) getAttributeContext();        styles.removeStyle(nm);    }     /**     * Fetches a named style previously added.     *     * @param nm  the name of the style     * @return the style     */    public Style getStyle(String nm) {        StyleContext styles = (StyleContext) getAttributeContext();        return styles.getStyle(nm);    }      /**     * Fetches the list of of style names.     *     * @return all the style names     */    public Enumeration<?> getStyleNames() {        return ((StyleContext) getAttributeContext()).getStyleNames();    }     /**     * Sets the logical style to use for the paragraph at the     * given position.  If attributes aren't explicitly set     * for character and paragraph attributes they will resolve     * through the logical style assigned to the paragraph, which     * in turn may resolve through some hierarchy completely     * independent of the element hierarchy in the document.     * <p>     * This method is thread safe, although most Swing methods     * are not. Please see     * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How     * to Use Threads</A> for more information.     *     * @param pos the offset from the start of the document >= 0     * @param s  the logical style to assign to the paragraph, null if none     */    public void setLogicalStyle(int pos, Style s) {        Element paragraph = getParagraphElement(pos);        if ((paragraph != null) && (paragraph instanceof AbstractElement)) {            try {                writeLock();                StyleChangeUndoableEdit edit = new StyleChangeUndoableEdit((AbstractElement)paragraph, s);                ((AbstractElement)paragraph).setResolveParent(s);                int p0 = paragraph.getStartOffset();                int p1 = paragraph.getEndOffset();                DefaultDocumentEvent e =                  new DefaultDocumentEvent(p0, p1 - p0, DocumentEvent.EventType.CHANGE);                e.addEdit(edit);                e.end();                fireChangedUpdate(e);                fireUndoableEditUpdate(new UndoableEditEvent(this, e));            } finally {                writeUnlock();            }        }    }     /**     * Fetches the logical style assigned to the paragraph     * represented by the given position.     *     * @param p the location to translate to a paragraph     *  and determine the logical style assigned >= 0.  This     *  is an offset from the start of the document.     * @return the style, null if none     */    public Style getLogicalStyle(int p) {        Style s = null;        Element paragraph = getParagraphElement(p);        if (paragraph != null) {            AttributeSet a = paragraph.getAttributes();            AttributeSet parent = a.getResolveParent();            if (parent instanceof Style) {                s = (Style) parent;            }        }        return s;    }     /**     * Sets attributes for some part of the document.     * A write lock is held by this operation while changes     * are being made, and a DocumentEvent is sent to the listeners     * after the change has been successfully completed.     * <p>     * This method is thread safe, although most Swing methods     * are not. Please see     * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How     * to Use Threads</A> for more information.     *     * @param offset the offset in the document >= 0     * @param length the length >= 0     * @param s the attributes     * @param replace true if the previous attributes should be replaced     *  before setting the new attributes     */    public void setCharacterAttributes(int offset, int length, AttributeSet s, boolean replace) {        if (length == 0) {            return;        }        try {            writeLock();

Lines 1–500 of 2744.