alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Java example source code file (AbstractDocument.java)

This example Java source code file (AbstractDocument.java) is included in the alvinalexander.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Learn more about this Java project at its project page.

Java - Java tags/keywords

abstractelement, attributecontext, attributeset, badlocationexception, bidielement, boolean, defaultdocumentevent, element, enumeration, font, gui, ioexception, jtree, object, position, string, swing, undo, undoredodocumentevent, util

The AbstractDocument.java Java example source code

/*
 * Copyright (c) 1997, 2013, 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.util.*;
import java.io.*;
import java.awt.font.TextAttribute;
import java.text.Bidi;

import javax.swing.UIManager;
import javax.swing.undo.*;
import javax.swing.event.*;
import javax.swing.tree.TreeNode;

import sun.font.BidiUtils;
import sun.swing.SwingUtilities2;

/**
 * An implementation of the document interface to serve as a
 * basis for implementing various kinds of documents.  At this
 * level there is very little policy, so there is a corresponding
 * increase in difficulty of use.
 * <p>
 * This class implements a locking mechanism for the document.  It
 * allows multiple readers or one writer, and writers must wait until
 * all observers of the document have been notified of a previous
 * change before beginning another mutation to the document.  The
 * read lock is acquired and released using the <code>render
 * method.  A write lock is acquired by the methods that mutate the
 * document, and are held for the duration of the method call.
 * Notification is done on the thread that produced the mutation,
 * and the thread has full read access to the document for the
 * duration of the notification, but other readers are kept out
 * until the notification has finished.  The notification is a
 * beans event notification which does not allow any further
 * mutations until all listeners have been notified.
 * <p>
 * Any models subclassed from this class and used in conjunction
 * with a text component that has a look and feel implementation
 * that is derived from BasicTextUI may be safely updated
 * asynchronously, because all access to the View hierarchy
 * is serialized by BasicTextUI if the document is of type
 * <code>AbstractDocument.  The locking assumes that an
 * independent thread will access the View hierarchy only from
 * the DocumentListener methods, and that there will be only
 * one event thread active at a time.
 * <p>
 * If concurrency support is desired, there are the following
 * additional implications.  The code path for any DocumentListener
 * implementation and any UndoListener implementation must be threadsafe,
 * and not access the component lock if trying to be safe from deadlocks.
 * The <code>repaint and revalidate methods
 * on JComponent are safe.
 * <p>
 * AbstractDocument models an implied break at the end of the document.
 * Among other things this allows you to position the caret after the last
 * character. As a result of this, <code>getLength returns one less
 * than the length of the Content. If you create your own Content, be
 * sure and initialize it to have an additional character. Refer to
 * StringContent and GapContent for examples of this. Another implication
 * of this is that Elements that model the implied end character will have
 * an endOffset == (getLength() + 1). For example, in DefaultStyledDocument
 * <code>getParagraphElement(getLength()).getEndOffset() == getLength() + 1
 * </code>.
 * <p>
 * <strong>Warning:
 * 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™
 * has been added to the <code>java.beans package.
 * Please see {@link java.beans.XMLEncoder}.
 *
 * @author  Timothy Prinzing
 */
public abstract class AbstractDocument implements Document, Serializable {

    /**
     * Constructs a new <code>AbstractDocument, wrapped around some
     * specified content storage mechanism.
     *
     * @param data the content
     */
    protected AbstractDocument(Content data) {
        this(data, StyleContext.getDefaultStyleContext());
    }

    /**
     * Constructs a new <code>AbstractDocument, wrapped around some
     * specified content storage mechanism.
     *
     * @param data the content
     * @param context the attribute context
     */
    protected AbstractDocument(Content data, AttributeContext context) {
        this.data = data;
        this.context = context;
        bidiRoot = new BidiRootElement();

        if (defaultI18NProperty == null) {
            // determine default setting for i18n support
            String o = java.security.AccessController.doPrivileged(
                new java.security.PrivilegedAction<String>() {
                    public String run() {
                        return System.getProperty(I18NProperty);
                    }
                }
            );
            if (o != null) {
                defaultI18NProperty = Boolean.valueOf(o);
            } else {
                defaultI18NProperty = Boolean.FALSE;
            }
        }
        putProperty( I18NProperty, defaultI18NProperty);

        //REMIND(bcb) This creates an initial bidi element to account for
        //the \n that exists by default in the content.  Doing it this way
        //seems to expose a little too much knowledge of the content given
        //to us by the sub-class.  Consider having the sub-class' constructor
        //make an initial call to insertUpdate.
        writeLock();
        try {
            Element[] p = new Element[1];
            p[0] = new BidiElement( bidiRoot, 0, 1, 0 );
            bidiRoot.replace(0,0,p);
        } finally {
            writeUnlock();
        }
    }

    /**
     * Supports managing a set of properties. Callers
     * can use the <code>documentProperties dictionary
     * to annotate the document with document-wide properties.
     *
     * @return a non-<code>null Dictionary
     * @see #setDocumentProperties
     */
    public Dictionary<Object,Object> getDocumentProperties() {
        if (documentProperties == null) {
            documentProperties = new Hashtable<Object, Object>(2);
        }
        return documentProperties;
    }

    /**
     * Replaces the document properties dictionary for this document.
     *
     * @param x the new dictionary
     * @see #getDocumentProperties
     */
    public void setDocumentProperties(Dictionary<Object,Object> x) {
        documentProperties = x;
    }

    /**
     * Notifies all listeners that have registered interest for
     * notification on this event type.  The event instance
     * is lazily created using the parameters passed into
     * the fire method.
     *
     * @param e the event
     * @see EventListenerList
     */
    protected void fireInsertUpdate(DocumentEvent e) {
        notifyingListeners = true;
        try {
            // Guaranteed to return a non-null array
            Object[] listeners = listenerList.getListenerList();
            // Process the listeners last to first, notifying
            // those that are interested in this event
            for (int i = listeners.length-2; i>=0; i-=2) {
                if (listeners[i]==DocumentListener.class) {
                    // Lazily create the event:
                    // if (e == null)
                    // e = new ListSelectionEvent(this, firstIndex, lastIndex);
                    ((DocumentListener)listeners[i+1]).insertUpdate(e);
                }
            }
        } finally {
            notifyingListeners = false;
        }
    }

    /**
     * Notifies all listeners that have registered interest for
     * notification on this event type.  The event instance
     * is lazily created using the parameters passed into
     * the fire method.
     *
     * @param e the event
     * @see EventListenerList
     */
    protected void fireChangedUpdate(DocumentEvent e) {
        notifyingListeners = true;
        try {
            // Guaranteed to return a non-null array
            Object[] listeners = listenerList.getListenerList();
            // Process the listeners last to first, notifying
            // those that are interested in this event
            for (int i = listeners.length-2; i>=0; i-=2) {
                if (listeners[i]==DocumentListener.class) {
                    // Lazily create the event:
                    // if (e == null)
                    // e = new ListSelectionEvent(this, firstIndex, lastIndex);
                    ((DocumentListener)listeners[i+1]).changedUpdate(e);
                }
            }
        } finally {
            notifyingListeners = false;
        }
    }

    /**
     * Notifies all listeners that have registered interest for
     * notification on this event type.  The event instance
     * is lazily created using the parameters passed into
     * the fire method.
     *
     * @param e the event
     * @see EventListenerList
     */
    protected void fireRemoveUpdate(DocumentEvent e) {
        notifyingListeners = true;
        try {
            // Guaranteed to return a non-null array
            Object[] listeners = listenerList.getListenerList();
            // Process the listeners last to first, notifying
            // those that are interested in this event
            for (int i = listeners.length-2; i>=0; i-=2) {
                if (listeners[i]==DocumentListener.class) {
                    // Lazily create the event:
                    // if (e == null)
                    // e = new ListSelectionEvent(this, firstIndex, lastIndex);
                    ((DocumentListener)listeners[i+1]).removeUpdate(e);
                }
            }
        } finally {
            notifyingListeners = false;
        }
    }

    /**
     * Notifies all listeners that have registered interest for
     * notification on this event type.  The event instance
     * is lazily created using the parameters passed into
     * the fire method.
     *
     * @param e the event
     * @see EventListenerList
     */
    protected void fireUndoableEditUpdate(UndoableEditEvent e) {
        // Guaranteed to return a non-null array
        Object[] listeners = listenerList.getListenerList();
        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length-2; i>=0; i-=2) {
            if (listeners[i]==UndoableEditListener.class) {
                // Lazily create the event:
                // if (e == null)
                // e = new ListSelectionEvent(this, firstIndex, lastIndex);
                ((UndoableEditListener)listeners[i+1]).undoableEditHappened(e);
            }
        }
    }

    /**
     * Returns an array of all the objects currently registered
     * as <code>FooListeners
     * upon this document.
     * <code>FooListeners are registered using the
     * <code>addFooListener method.
     *
     * <p>
     * You can specify the <code>listenerType argument
     * with a class literal, such as
     * <code>FooListener.class.
     * For example, you can query a
     * document <code>d
     * for its document listeners with the following code:
     *
     * <pre>DocumentListener[] mls = (DocumentListener[])(d.getListeners(DocumentListener.class));
* * If no such listeners exist, this method returns an empty array. * * @param listenerType the type of listeners requested; this parameter * should specify an interface that descends from * <code>java.util.EventListener * @return an array of all objects registered as * <code>FooListeners on this component, * or an empty array if no such * listeners have been added * @exception ClassCastException if <code>listenerType * doesn't specify a class or interface that implements * <code>java.util.EventListener * * @see #getDocumentListeners * @see #getUndoableEditListeners * * @since 1.3 */ public <T extends EventListener> T[] getListeners(Class listenerType) { return listenerList.getListeners(listenerType); } /** * Gets the asynchronous loading priority. If less than zero, * the document should not be loaded asynchronously. * * @return the asynchronous loading priority, or <code>-1 * if the document should not be loaded asynchronously */ public int getAsynchronousLoadPriority() { Integer loadPriority = (Integer) getProperty(AbstractDocument.AsyncLoadPriority); if (loadPriority != null) { return loadPriority.intValue(); } return -1; } /** * Sets the asynchronous loading priority. * @param p the new asynchronous loading priority; a value * less than zero indicates that the document should not be * loaded asynchronously */ public void setAsynchronousLoadPriority(int p) { Integer loadPriority = (p >= 0) ? Integer.valueOf(p) : null; putProperty(AbstractDocument.AsyncLoadPriority, loadPriority); } /** * Sets the <code>DocumentFilter. The DocumentFilter * is passed <code>insert and remove to conditionally * allow inserting/deleting of the text. A <code>null value * indicates that no filtering will occur. * * @param filter the <code>DocumentFilter used to constrain text * @see #getDocumentFilter * @since 1.4 */ public void setDocumentFilter(DocumentFilter filter) { documentFilter = filter; } /** * Returns the <code>DocumentFilter that is responsible for * filtering of insertion/removal. A <code>null return value * implies no filtering is to occur. * * @since 1.4 * @see #setDocumentFilter * @return the DocumentFilter */ public DocumentFilter getDocumentFilter() { return documentFilter; } // --- Document methods ----------------------------------------- /** * This allows the model to be safely rendered in the presence * of currency, if the model supports being updated asynchronously. * The given runnable will be executed in a way that allows it * to safely read the model with no changes while the runnable * is being executed. The runnable itself may <em>not * make any mutations. * <p> * This is implemented to acquire a read lock for the duration * of the runnables execution. There may be multiple runnables * executing at the same time, and all writers will be blocked * while there are active rendering runnables. If the runnable * throws an exception, its lock will be safely released. * There is no protection against a runnable that never exits, * which will effectively leave the document locked for it's * lifetime. * <p> * If the given runnable attempts to make any mutations in * this implementation, a deadlock will occur. There is * no tracking of individual rendering threads to enable * detecting this situation, but a subclass could incur * the overhead of tracking them and throwing an error. * <p> * This method is thread safe, although most Swing methods * are not. Please see * <A HREF="http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency * in Swing</A> for more information. * * @param r the renderer to execute */ public void render(Runnable r) { readLock(); try { r.run(); } finally { readUnlock(); } } /** * Returns the length of the data. This is the number of * characters of content that represents the users data. * * @return the length >= 0 * @see Document#getLength */ public int getLength() { return data.length() - 1; } /** * Adds a document listener for notification of any changes. * * @param listener the <code>DocumentListener to add * @see Document#addDocumentListener */ public void addDocumentListener(DocumentListener listener) { listenerList.add(DocumentListener.class, listener); } /** * Removes a document listener. * * @param listener the <code>DocumentListener to remove * @see Document#removeDocumentListener */ public void removeDocumentListener(DocumentListener listener) { listenerList.remove(DocumentListener.class, listener); } /** * Returns an array of all the document listeners * registered on this document. * * @return all of this document's <code>DocumentListeners * or an empty array if no document listeners are * currently registered * * @see #addDocumentListener * @see #removeDocumentListener * @since 1.4 */ public DocumentListener[] getDocumentListeners() { return listenerList.getListeners(DocumentListener.class); } /** * Adds an undo listener for notification of any changes. * Undo/Redo operations performed on the <code>UndoableEdit * will cause the appropriate DocumentEvent to be fired to keep * the view(s) in sync with the model. * * @param listener the <code>UndoableEditListener to add * @see Document#addUndoableEditListener */ public void addUndoableEditListener(UndoableEditListener listener) { listenerList.add(UndoableEditListener.class, listener); } /** * Removes an undo listener. * * @param listener the <code>UndoableEditListener to remove * @see Document#removeDocumentListener */ public void removeUndoableEditListener(UndoableEditListener listener) { listenerList.remove(UndoableEditListener.class, listener); } /** * Returns an array of all the undoable edit listeners * registered on this document. * * @return all of this document's <code>UndoableEditListeners * or an empty array if no undoable edit listeners are * currently registered * * @see #addUndoableEditListener * @see #removeUndoableEditListener * * @since 1.4 */ public UndoableEditListener[] getUndoableEditListeners() { return listenerList.getListeners(UndoableEditListener.class); } /** * A convenience method for looking up a property value. It is * equivalent to: * <pre> * getDocumentProperties().get(key); * </pre> * * @param key the non-<code>null property key * @return the value of this property or <code>null * @see #getDocumentProperties */ public final Object getProperty(Object key) { return getDocumentProperties().get(key); } /** * A convenience method for storing up a property value. It is * equivalent to: * <pre> * getDocumentProperties().put(key, value); * </pre> * If <code>value is null this method will * remove the property. * * @param key the non-<code>null key * @param value the property value * @see #getDocumentProperties */ public final void putProperty(Object key, Object value) { if (value != null) { getDocumentProperties().put(key, value); } else { getDocumentProperties().remove(key); } if( key == TextAttribute.RUN_DIRECTION && Boolean.TRUE.equals(getProperty(I18NProperty)) ) { //REMIND - this needs to flip on the i18n property if run dir //is rtl and the i18n property is not already on. writeLock(); try { DefaultDocumentEvent e = new DefaultDocumentEvent(0, getLength(), DocumentEvent.EventType.INSERT); updateBidi( e ); } finally { writeUnlock(); } } } /** * Removes some content from the document. * Removing content causes a write lock to be held while the * actual changes are taking place. Observers are notified * of the change on the thread that called this method. * <p> * This method is thread safe, although most Swing methods * are not. Please see * <A HREF="http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency * in Swing</A> for more information. * * @param offs the starting offset >= 0 * @param len the number of characters to remove >= 0 * @exception BadLocationException the given remove position is not a valid * position within the document * @see Document#remove */ public void remove(int offs, int len) throws BadLocationException { DocumentFilter filter = getDocumentFilter(); writeLock(); try { if (filter != null) { filter.remove(getFilterBypass(), offs, len); } else { handleRemove(offs, len); } } finally { writeUnlock(); } } /** * Performs the actual work of the remove. It is assumed the caller * will have obtained a <code>writeLock before invoking this. */ void handleRemove(int offs, int len) throws BadLocationException { if (len > 0) { if (offs < 0 || (offs + len) > getLength()) { throw new BadLocationException("Invalid remove", getLength() + 1); } DefaultDocumentEvent chng = new DefaultDocumentEvent(offs, len, DocumentEvent.EventType.REMOVE); boolean isComposedTextElement; // Check whether the position of interest is the composed text isComposedTextElement = Utilities.isComposedTextElement(this, offs); removeUpdate(chng); UndoableEdit u = data.remove(offs, len); if (u != null) { chng.addEdit(u); } postRemoveUpdate(chng); // Mark the edit as done. chng.end(); fireRemoveUpdate(chng); // only fire undo if Content implementation supports it // undo for the composed text is not supported for now if ((u != null) && !isComposedTextElement) { fireUndoableEditUpdate(new UndoableEditEvent(this, chng)); } } } /** * Deletes the region of text from <code>offset to * <code>offset + length, and replaces it with text. * It is up to the implementation as to how this is implemented, some * implementations may treat this as two distinct operations: a remove * followed by an insert, others may treat the replace as one atomic * operation. * * @param offset index of child element * @param length length of text to delete, may be 0 indicating don't * delete anything * @param text text to insert, <code>null indicates no text to insert * @param attrs AttributeSet indicating attributes of inserted text, * <code>null * is legal, and typically treated as an empty attributeset, * but exact interpretation is left to the subclass * @exception BadLocationException the given position is not a valid * position within the document * @since 1.4 */ public void replace(int offset, int length, String text, AttributeSet attrs) throws BadLocationException { if (length == 0 && (text == null || text.length() == 0)) { return; } DocumentFilter filter = getDocumentFilter(); writeLock(); try { if (filter != null) { filter.replace(getFilterBypass(), offset, length, text, attrs); } else { if (length > 0) { remove(offset, length); } if (text != null && text.length() > 0) { insertString(offset, text, attrs); } } } finally { writeUnlock(); } } /** * Inserts some content into the document. * Inserting content causes a write lock to be held while the * actual changes are taking place, followed by notification * to the observers on the thread that grabbed the write lock. * <p> * This method is thread safe, although most Swing methods * are not. Please see * <A HREF="http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency * in Swing</A> for more information. * * @param offs the starting offset >= 0 * @param str the string to insert; does nothing with null/empty strings * @param a the attributes for the inserted content * @exception BadLocationException the given insert position is not a valid * position within the document * @see Document#insertString */ public void insertString(int offs, String str, AttributeSet a) throws BadLocationException { if ((str == null) || (str.length() == 0)) { return; } DocumentFilter filter = getDocumentFilter(); writeLock(); try { if (filter != null) { filter.insertString(getFilterBypass(), offs, str, a); } else { handleInsertString(offs, str, a); } } finally { writeUnlock(); } } /** * Performs the actual work of inserting the text; it is assumed the * caller has obtained a write lock before invoking this. */ private void handleInsertString(int offs, String str, AttributeSet a) throws BadLocationException { if ((str == null) || (str.length() == 0)) { return; } UndoableEdit u = data.insertString(offs, str); DefaultDocumentEvent e = new DefaultDocumentEvent(offs, str.length(), DocumentEvent.EventType.INSERT); if (u != null) { e.addEdit(u); } // see if complex glyph layout support is needed if( getProperty(I18NProperty).equals( Boolean.FALSE ) ) { // if a default direction of right-to-left has been specified, // we want complex layout even if the text is all left to right. Object d = getProperty(TextAttribute.RUN_DIRECTION); if ((d != null) && (d.equals(TextAttribute.RUN_DIRECTION_RTL))) { putProperty( I18NProperty, Boolean.TRUE); } else { char[] chars = str.toCharArray(); if (SwingUtilities2.isComplexLayout(chars, 0, chars.length)) { putProperty( I18NProperty, Boolean.TRUE); } } } insertUpdate(e, a); // Mark the edit as done. e.end(); fireInsertUpdate(e); // only fire undo if Content implementation supports it // undo for the composed text is not supported for now if (u != null && (a == null || !a.isDefined(StyleConstants.ComposedTextAttribute))) { fireUndoableEditUpdate(new UndoableEditEvent(this, e)); } } /** * Gets a sequence of text from the document. * * @param offset the starting offset >= 0 * @param length the number of characters to retrieve >= 0 * @return the text * @exception BadLocationException the range given includes a position * that is not a valid position within the document * @see Document#getText */ public String getText(int offset, int length) throws BadLocationException { if (length < 0) { throw new BadLocationException("Length must be positive", length); } String str = data.getString(offset, length); return str; } /** * Fetches the text contained within the given portion * of the document. * <p> * If the partialReturn property on the txt parameter is false, the * data returned in the Segment will be the entire length requested and * may or may not be a copy depending upon how the data was stored. * If the partialReturn property is true, only the amount of text that * can be returned without creating a copy is returned. Using partial * returns will give better performance for situations where large * parts of the document are being scanned. The following is an example * of using the partial return to access the entire document: * * <pre> *   int nleft = doc.getDocumentLength(); *   Segment text = new Segment(); *   int offs = 0; *   text.setPartialReturn(true); *   while (nleft > 0) { *   doc.getText(offs, nleft, text); *   // do something with text *   nleft -= text.count; *   offs += text.count; *   } * </pre> * * @param offset the starting offset >= 0 * @param length the number of characters to retrieve >= 0 * @param txt the Segment object to retrieve the text into * @exception BadLocationException the range given includes a position * that is not a valid position within the document */ public void getText(int offset, int length, Segment txt) throws BadLocationException { if (length < 0) { throw new BadLocationException("Length must be positive", length); } data.getChars(offset, length, txt); } /** * Returns a position that will track change as the document * is altered. * <p> * This method is thread safe, although most Swing methods * are not. Please see * <A HREF="http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency * in Swing</A> for more information. * * @param offs the position in the model >= 0 * @return the position * @exception BadLocationException if the given position does not * represent a valid location in the associated document * @see Document#createPosition */ public synchronized Position createPosition(int offs) throws BadLocationException { return data.createPosition(offs); } /** * Returns a position that represents the start of the document. The * position returned can be counted on to track change and stay * located at the beginning of the document. * * @return the position */ public final Position getStartPosition() { Position p; try { p = createPosition(0); } catch (BadLocationException bl) { p = null; } return p; } /** * Returns a position that represents the end of the document. The * position returned can be counted on to track change and stay * located at the end of the document. * * @return the position */ public final Position getEndPosition() { Position p; try { p = createPosition(data.length()); } catch (BadLocationException bl) { p = null; } return p; } /** * Gets all root elements defined. Typically, there * will only be one so the default implementation * is to return the default root element. * * @return the root element */ public Element[] getRootElements() { Element[] elems = new Element[2]; elems[0] = getDefaultRootElement(); elems[1] = getBidiRootElement(); return elems; } /** * Returns the root element that views should be based upon * unless some other mechanism for assigning views to element * structures is provided. * * @return the root element * @see Document#getDefaultRootElement */ public abstract Element getDefaultRootElement(); // ---- local methods ----------------------------------------- /** * Returns the <code>FilterBypass. This will create one if one * does not yet exist. */ private DocumentFilter.FilterBypass getFilterBypass() { if (filterBypass == null) { filterBypass = new DefaultFilterBypass(); } return filterBypass; } /** * Returns the root element of the bidirectional structure for this * document. Its children represent character runs with a given * Unicode bidi level. */ public Element getBidiRootElement() { return bidiRoot; } /** * Returns true if the text in the range <code>p0 to * <code>p1 is left to right. */ static boolean isLeftToRight(Document doc, int p0, int p1) { if (Boolean.TRUE.equals(doc.getProperty(I18NProperty))) { if (doc instanceof AbstractDocument) { AbstractDocument adoc = (AbstractDocument) doc; Element bidiRoot = adoc.getBidiRootElement(); int index = bidiRoot.getElementIndex(p0); Element bidiElem = bidiRoot.getElement(index); if (bidiElem.getEndOffset() >= p1) { AttributeSet bidiAttrs = bidiElem.getAttributes(); return ((StyleConstants.getBidiLevel(bidiAttrs) % 2) == 0); } } } return true; } /** * Get the paragraph element containing the given position. Sub-classes * must define for themselves what exactly constitutes a paragraph. They * should keep in mind however that a paragraph should at least be the * unit of text over which to run the Unicode bidirectional algorithm. * * @param pos the starting offset >= 0 * @return the element */ public abstract Element getParagraphElement(int pos); /** * Fetches the context for managing attributes. This * method effectively establishes the strategy used * for compressing AttributeSet information. * * @return the context */ protected final AttributeContext getAttributeContext() { return context; } /** * Updates document structure as a result of text insertion. This * will happen within a write lock. If a subclass of * this class reimplements this method, it should delegate to the * superclass as well. * * @param chng a description of the change * @param attr the attributes for the change */ protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) { if( getProperty(I18NProperty).equals( Boolean.TRUE ) ) updateBidi( chng ); // Check if a multi byte is encountered in the inserted text. if (chng.type == DocumentEvent.EventType.INSERT && chng.getLength() > 0 && !Boolean.TRUE.equals(getProperty(MultiByteProperty))) { Segment segment = SegmentCache.getSharedSegment(); try { getText(chng.getOffset(), chng.getLength(), segment); segment.first(); do { if ((int)segment.current() > 255) { putProperty(MultiByteProperty, Boolean.TRUE); break; } } while (segment.next() != Segment.DONE); } catch (BadLocationException ble) { // Should never happen } SegmentCache.releaseSharedSegment(segment); } } /** * Updates any document structure as a result of text removal. This * method is called before the text is actually removed from the Content. * This will happen within a write lock. If a subclass * of this class reimplements this method, it should delegate to the * superclass as well. * * @param chng a description of the change */ protected void removeUpdate(DefaultDocumentEvent chng) { } /** * Updates any document structure as a result of text removal. This * method is called after the text has been removed from the Content. * This will happen within a write lock. If a subclass * of this class reimplements this method, it should delegate to the * superclass as well. * * @param chng a description of the change */ protected void postRemoveUpdate(DefaultDocumentEvent chng) { if( getProperty(I18NProperty).equals( Boolean.TRUE ) ) updateBidi( chng ); } /** * Update the bidi element structure as a result of the given change * to the document. The given change will be updated to reflect the * changes made to the bidi structure. * * This method assumes that every offset in the model is contained in * exactly one paragraph. This method also assumes that it is called * after the change is made to the default element structure. */ void updateBidi( DefaultDocumentEvent chng ) { // Calculate the range of paragraphs affected by the change. int firstPStart; int lastPEnd; if( chng.type == DocumentEvent.EventType.INSERT || chng.type == DocumentEvent.EventType.CHANGE ) { int chngStart = chng.getOffset(); int chngEnd = chngStart + chng.getLength(); firstPStart = getParagraphElement(chngStart).getStartOffset(); lastPEnd = getParagraphElement(chngEnd).getEndOffset(); } else if( chng.type == DocumentEvent.EventType.REMOVE ) { Element paragraph = getParagraphElement( chng.getOffset() ); firstPStart = paragraph.getStartOffset(); lastPEnd = paragraph.getEndOffset(); } else { throw new Error("Internal error: unknown event type."); } //System.out.println("updateBidi: firstPStart = " + firstPStart + " lastPEnd = " + lastPEnd ); // Calculate the bidi levels for the affected range of paragraphs. The // levels array will contain a bidi level for each character in the // affected text. byte levels[] = calculateBidiLevels( firstPStart, lastPEnd ); Vector<Element> newElements = new Vector(); // Calculate the first span of characters in the affected range with // the same bidi level. If this level is the same as the level of the // previous bidi element (the existing bidi element containing // firstPStart-1), then merge in the previous element. If not, but // the previous element overlaps the affected range, truncate the // previous element at firstPStart. int firstSpanStart = firstPStart; int removeFromIndex = 0; if( firstSpanStart > 0 ) { int prevElemIndex = bidiRoot.getElementIndex(firstPStart-1); removeFromIndex = prevElemIndex; Element prevElem = bidiRoot.getElement(prevElemIndex); int prevLevel=StyleConstants.getBidiLevel(prevElem.getAttributes()); //System.out.println("createbidiElements: prevElem= " + prevElem + " prevLevel= " + prevLevel + "level[0] = " + levels[0]); if( prevLevel==levels[0] ) { firstSpanStart = prevElem.getStartOffset(); } else if( prevElem.getEndOffset() > firstPStart ) { newElements.addElement(new BidiElement(bidiRoot, prevElem.getStartOffset(), firstPStart, prevLevel)); } else { removeFromIndex++; } } int firstSpanEnd = 0; while((firstSpanEnd<levels.length) && (levels[firstSpanEnd]==levels[0])) firstSpanEnd++; // Calculate the last span of characters in the affected range with // the same bidi level. If this level is the same as the level of the // next bidi element (the existing bidi element containing lastPEnd), // then merge in the next element. If not, but the next element // overlaps the affected range, adjust the next element to start at // lastPEnd. int lastSpanEnd = lastPEnd; Element newNextElem = null; int removeToIndex = bidiRoot.getElementCount() - 1; if( lastSpanEnd <= getLength() ) { int nextElemIndex = bidiRoot.getElementIndex( lastPEnd ); removeToIndex = nextElemIndex; Element nextElem = bidiRoot.getElement( nextElemIndex ); int nextLevel = StyleConstants.getBidiLevel(nextElem.getAttributes()); if( nextLevel == levels[levels.length-1] ) { lastSpanEnd = nextElem.getEndOffset(); } else if( nextElem.getStartOffset() < lastPEnd ) { newNextElem = new BidiElement(bidiRoot, lastPEnd, nextElem.getEndOffset(), nextLevel); } else { removeToIndex--; } } int lastSpanStart = levels.length; while( (lastSpanStart>firstSpanEnd) && (levels[lastSpanStart-1]==levels[levels.length-1]) ) lastSpanStart--; // If the first and last spans are contiguous and have the same level, // merge them and create a single new element for the entire span. // Otherwise, create elements for the first and last spans as well as // any spans in between. if((firstSpanEnd==lastSpanStart)&&(levels[0]==levels[levels.length-1])){ newElements.addElement(new BidiElement(bidiRoot, firstSpanStart, lastSpanEnd, levels[0])); } else { // Create an element for the first span. newElements.addElement(new BidiElement(bidiRoot, firstSpanStart, firstSpanEnd+firstPStart, levels[0])); // Create elements for the spans in between the first and last for( int i=firstSpanEnd; i<lastSpanStart; ) { //System.out.println("executed line 872"); int j; for( j=i; (j<levels.length) && (levels[j] == levels[i]); j++ ); newElements.addElement(new BidiElement(bidiRoot, firstPStart+i, firstPStart+j, (int)levels[i])); i=j; } // Create an element for the last span. newElements.addElement(new BidiElement(bidiRoot, lastSpanStart+firstPStart, lastSpanEnd, levels[levels.length-1])); } if( newNextElem != null ) newElements.addElement( newNextElem ); // Calculate the set of existing bidi elements which must be // removed. int removedElemCount = 0; if( bidiRoot.getElementCount() > 0 ) { removedElemCount = removeToIndex - removeFromIndex + 1; } Element[] removedElems = new Element[removedElemCount]; for( int i=0; i<removedElemCount; i++ ) { removedElems[i] = bidiRoot.getElement(removeFromIndex+i); } Element[] addedElems = new Element[ newElements.size() ]; newElements.copyInto( addedElems ); // Update the change record. ElementEdit ee = new ElementEdit( bidiRoot, removeFromIndex, removedElems, addedElems ); chng.addEdit( ee ); // Update the bidi element structure. bidiRoot.replace( removeFromIndex, removedElems.length, addedElems ); } /** * Calculate the levels array for a range of paragraphs. */ private byte[] calculateBidiLevels( int firstPStart, int lastPEnd ) { byte levels[] = new byte[ lastPEnd - firstPStart ]; int levelsEnd = 0; Boolean defaultDirection = null; Object d = getProperty(TextAttribute.RUN_DIRECTION); if (d instanceof Boolean) { defaultDirection = (Boolean) d; } // For each paragraph in the given range of paragraphs, get its // levels array and add it to the levels array for the entire span. for(int o=firstPStart; o<lastPEnd; ) { Element p = getParagraphElement( o ); int pStart = p.getStartOffset(); int pEnd = p.getEndOffset(); // default run direction for the paragraph. This will be // null if there is no direction override specified (i.e. // the direction will be determined from the content). Boolean direction = defaultDirection; d = p.getAttributes().getAttribute(TextAttribute.RUN_DIRECTION); if (d instanceof Boolean) { direction = (Boolean) d; } //System.out.println("updateBidi: paragraph start = " + pStart + " paragraph end = " + pEnd); // Create a Bidi over this paragraph then get the level // array. Segment seg = SegmentCache.getSharedSegment(); try { getText(pStart, pEnd-pStart, seg); } catch (BadLocationException e ) { throw new Error("Internal error: " + e.toString()); } // REMIND(bcb) we should really be using a Segment here. Bidi bidiAnalyzer; int bidiflag = Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT; if (direction != null) { if (TextAttribute.RUN_DIRECTION_LTR.equals(direction)) { bidiflag = Bidi.DIRECTION_LEFT_TO_RIGHT; } else { bidiflag = Bidi.DIRECTION_RIGHT_TO_LEFT; } } bidiAnalyzer = new Bidi(seg.array, seg.offset, null, 0, seg.count, bidiflag); BidiUtils.getLevels(bidiAnalyzer, levels, levelsEnd); levelsEnd += bidiAnalyzer.getLength(); o = p.getEndOffset(); SegmentCache.releaseSharedSegment(seg); } // REMIND(bcb) remove this code when debugging is done. if( levelsEnd != levels.length ) throw new Error("levelsEnd assertion failed."); return levels; } /** * Gives a diagnostic dump. * * @param out the output stream */ public void dump(PrintStream out) { Element root = getDefaultRootElement(); if (root instanceof AbstractElement) { ((AbstractElement)root).dump(out, 0); } bidiRoot.dump(out,0); } /** * Gets the content for the document. * * @return the content */ protected final Content getContent() { return data; } /** * Creates a document leaf element. * Hook through which elements are created to represent the * document structure. Because this implementation keeps * structure and content separate, elements grow automatically * when content is extended so splits of existing elements * follow. The document itself gets to decide how to generate * elements to give flexibility in the type of elements used. * * @param parent the parent element * @param a the attributes for the element * @param p0 the beginning of the range >= 0 * @param p1 the end of the range >= p0 * @return the new element */ protected Element createLeafElement(Element parent, AttributeSet a, int p0, int p1) { return new LeafElement(parent, a, p0, p1); } /** * Creates a document branch element, that can contain other elements. * * @param parent the parent element * @param a the attributes * @return the element */ protected Element createBranchElement(Element parent, AttributeSet a) { return new BranchElement(parent, a); } // --- Document locking ---------------------------------- /** * Fetches the current writing thread if there is one. * This can be used to distinguish whether a method is * being called as part of an existing modification or * if a lock needs to be acquired and a new transaction * started. * * @return the thread actively modifying the document * or <code>null if there are no modifications in progress */ protected synchronized final Thread getCurrentWriter() { return currWriter; } /** * Acquires a lock to begin mutating the document this lock * protects. There can be no writing, notification of changes, or * reading going on in order to gain the lock. Additionally a thread is * allowed to gain more than one <code>writeLock, * as long as it doesn't attempt to gain additional <code>writeLocks * from within document notification. Attempting to gain a * <code>writeLock from within a DocumentListener notification will * result in an <code>IllegalStateException. The ability * to obtain more than one <code>writeLock per thread allows * subclasses to gain a writeLock, perform a number of operations, then * release the lock. * <p> * Calls to <code>writeLock * must be balanced with calls to <code>writeUnlock, else the * <code>Document will be left in a locked state so that no * reading or writing can be done. * * @exception IllegalStateException thrown on illegal lock * attempt. If the document is implemented properly, this can * only happen if a document listener attempts to mutate the * document. This situation violates the bean event model * where order of delivery is not guaranteed and all listeners * should be notified before further mutations are allowed. */ protected synchronized final void writeLock() { try { while ((numReaders > 0) || (currWriter != null)) { if (Thread.currentThread() == currWriter) { if (notifyingListeners) { // Assuming one doesn't do something wrong in a // subclass this should only happen if a // DocumentListener tries to mutate the document. throw new IllegalStateException( "Attempt to mutate in notification"); } numWriters++; return; } wait(); } currWriter = Thread.currentThread(); numWriters = 1; } catch (InterruptedException e) { throw new Error("Interrupted attempt to acquire write lock"); } } /** * Releases a write lock previously obtained via <code>writeLock. * After decrementing the lock count if there are no outstanding locks * this will allow a new writer, or readers. * * @see #writeLock */ protected synchronized final void writeUnlock() { if (--numWriters <= 0) { numWriters = 0; currWriter = null; notifyAll(); } } /** * Acquires a lock to begin reading some state from the * document. There can be multiple readers at the same time. * Writing blocks the readers until notification of the change * to the listeners has been completed. This method should * be used very carefully to avoid unintended compromise * of the document. It should always be balanced with a * <code>readUnlock. * * @see #readUnlock */ public synchronized final void readLock() { try { while (currWriter != null) { if (currWriter == Thread.currentThread()) { // writer has full read access.... may try to acquire // lock in notification return; } wait(); } numReaders += 1; } catch (InterruptedException e) { throw new Error("Interrupted attempt to acquire read lock"); } } /** * Does a read unlock. This signals that one * of the readers is done. If there are no more readers * then writing can begin again. This should be balanced * with a readLock, and should occur in a finally statement * so that the balance is guaranteed. The following is an * example. * <pre> *   readLock(); *   try { *   // do something *   } finally { *   readUnlock(); *   } * </code> * * @see #readLock */ public synchronized final void readUnlock() { if (currWriter == Thread.currentThread()) { // writer has full read access.... may try to acquire // lock in notification return; } if (numReaders <= 0) { throw new StateInvariantError(BAD_LOCK_STATE); } numReaders -= 1; notify(); } // --- serialization --------------------------------------------- private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException { s.defaultReadObject(); listenerList = new EventListenerList(); // Restore bidi structure //REMIND(bcb) This creates an initial bidi element to account for //the \n that exists by default in the content. bidiRoot = new BidiRootElement(); try { writeLock(); Element[] p = new Element[1]; p[0] = new BidiElement( bidiRoot, 0, 1, 0 ); bidiRoot.replace(0,0,p); } finally { writeUnlock(); } // At this point bidi root is only partially correct. To fully // restore it we need access to getDefaultRootElement. But, this // is created by the subclass and at this point will be null. We // thus use registerValidation. s.registerValidation(new ObjectInputValidation() { public void validateObject() { try { writeLock(); DefaultDocumentEvent e = new DefaultDocumentEvent (0, getLength(), DocumentEvent.EventType.INSERT); updateBidi( e ); } finally { writeUnlock(); } } }, 0); } // ----- member variables ------------------------------------------ private transient int numReaders; private transient Thread currWriter; /** * The number of writers, all obtained from <code>currWriter. */ private transient int numWriters; /** * True will notifying listeners. */ private transient boolean notifyingListeners; private static Boolean defaultI18NProperty; /** * Storage for document-wide properties. */ private Dictionary<Object,Object> documentProperties = null; /** * The event listener list for the document. */ protected EventListenerList listenerList = new EventListenerList(); /** * Where the text is actually stored, and a set of marks * that track change as the document is edited are managed. */ private Content data; /** * Factory for the attributes. This is the strategy for * attribute compression and control of the lifetime of * a set of attributes as a collection. This may be shared * with other documents. */ private AttributeContext context; /** * The root of the bidirectional structure for this document. Its children * represent character runs with the same Unicode bidi level. */ private transient BranchElement bidiRoot; /** * Filter for inserting/removing of text. */ private DocumentFilter documentFilter; /** * Used by DocumentFilter to do actual insert/remove. */ private transient DocumentFilter.FilterBypass filterBypass; private static final String BAD_LOCK_STATE = "document lock failure"; /** * Error message to indicate a bad location. */ protected static final String BAD_LOCATION = "document location failure"; /** * Name of elements used to represent paragraphs */ public static final String ParagraphElementName = "paragraph"; /** * Name of elements used to represent content */ public static final String ContentElementName = "content"; /** * Name of elements used to hold sections (lines/paragraphs). */ public static final String SectionElementName = "section"; /** * Name of elements used to hold a unidirectional run */ public static final String BidiElementName = "bidi level"; /** * Name of the attribute used to specify element * names. */ public static final String ElementNameAttribute = "$ename"; /** * Document property that indicates whether internationalization * functions such as text reordering or reshaping should be * performed. This property should not be publicly exposed, * since it is used for implementation convenience only. As a * side effect, copies of this property may be in its subclasses * that live in different packages (e.g. HTMLDocument as of now), * so those copies should also be taken care of when this property * needs to be modified. */ static final String I18NProperty = "i18n"; /** * Document property that indicates if a character has been inserted * into the document that is more than one byte long. GlyphView uses * this to determine if it should use BreakIterator. */ static final Object MultiByteProperty = "multiByte"; /** * Document property that indicates asynchronous loading is * desired, with the thread priority given as the value. */ static final String AsyncLoadPriority = "load priority"; /** * Interface to describe a sequence of character content that * can be edited. Implementations may or may not support a * history mechanism which will be reflected by whether or not * mutations return an UndoableEdit implementation. * @see AbstractDocument */ public interface Content { /** * Creates a position within the content that will * track change as the content is mutated. * * @param offset the offset in the content >= 0 * @return a Position * @exception BadLocationException for an invalid offset */ public Position createPosition(int offset) throws BadLocationException; /** * Current length of the sequence of character content. * * @return the length >= 0 */ public int length(); /** * Inserts a string of characters into the sequence. * * @param where offset into the sequence to make the insertion >= 0 * @param str string to insert * @return if the implementation supports a history mechanism, * a reference to an <code>Edit implementation will be returned, * otherwise returns <code>null * @exception BadLocationException thrown if the area covered by * the arguments is not contained in the character sequence */ public UndoableEdit insertString(int where, String str) throws BadLocationException; /** * Removes some portion of the sequence. * * @param where The offset into the sequence to make the * insertion >= 0. * @param nitems The number of items in the sequence to remove >= 0. * @return If the implementation supports a history mechanism, * a reference to an Edit implementation will be returned, * otherwise null. * @exception BadLocationException Thrown if the area covered by * the arguments is not contained in the character sequence. */ public UndoableEdit remove(int where, int nitems) throws BadLocationException; /** * Fetches a string of characters contained in the sequence. * * @param where Offset into the sequence to fetch >= 0. * @param len number of characters to copy >= 0. * @return the string * @exception BadLocationException Thrown if the area covered by * the arguments is not contained in the character sequence. */ public String getString(int where, int len) throws BadLocationException; /** * Gets a sequence of characters and copies them into a Segment. * * @param where the starting offset >= 0 * @param len the number of characters >= 0 * @param txt the target location to copy into * @exception BadLocationException Thrown if the area covered by * the arguments is not contained in the character sequence. */ public void getChars(int where, int len, Segment txt) throws BadLocationException; } /** * An interface that can be used to allow MutableAttributeSet * implementations to use pluggable attribute compression * techniques. Each mutation of the attribute set can be * used to exchange a previous AttributeSet instance with * another, preserving the possibility of the AttributeSet * remaining immutable. An implementation is provided by * the StyleContext class. * * The Element implementations provided by this class use * this interface to provide their MutableAttributeSet * implementations, so that different AttributeSet compression * techniques can be employed. The method * <code>getAttributeContext should be implemented to * return the object responsible for implementing the desired * compression technique. * * @see StyleContext */ public interface AttributeContext { /** * Adds an attribute to the given set, and returns * the new representative set. * * @param old the old attribute set * @param name the non-null attribute name * @param value the attribute value * @return the updated attribute set * @see MutableAttributeSet#addAttribute */ public AttributeSet addAttribute(AttributeSet old, Object name, Object value); /** * Adds a set of attributes to the element. * * @param old the old attribute set * @param attr the attributes to add * @return the updated attribute set * @see MutableAttributeSet#addAttribute */ public AttributeSet addAttributes(AttributeSet old, AttributeSet attr); /** * Removes an attribute from the set. * * @param old the old attribute set * @param name the non-null attribute name * @return the updated attribute set * @see MutableAttributeSet#removeAttribute */ public AttributeSet removeAttribute(AttributeSet old, Object name); /** * Removes a set of attributes for the element. * * @param old the old attribute set * @param names the attribute names * @return the updated attribute set * @see MutableAttributeSet#removeAttributes */ public AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names); /** * Removes a set of attributes for the element. * * @param old the old attribute set * @param attrs the attributes * @return the updated attribute set * @see MutableAttributeSet#removeAttributes */ public AttributeSet removeAttributes(AttributeSet old, AttributeSet attrs); /** * Fetches an empty AttributeSet. * * @return the attribute set */ public AttributeSet getEmptySet(); /** * Reclaims an attribute set. * This is a way for a MutableAttributeSet to mark that it no * longer need a particular immutable set. This is only necessary * in 1.1 where there are no weak references. A 1.1 implementation * would call this in its finalize method. * * @param a the attribute set to reclaim */ public void reclaim(AttributeSet a); } /** * Implements the abstract part of an element. By default elements * support attributes by having a field that represents the immutable * part of the current attribute set for the element. The element itself * implements MutableAttributeSet which can be used to modify the set * by fetching a new immutable set. The immutable sets are provided * by the AttributeContext associated with the document. * <p> * <strong>Warning: * 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™ * has been added to the <code>java.beans package. * Please see {@link java.beans.XMLEncoder}. */ public abstract class AbstractElement implements Element, MutableAttributeSet, Serializable, TreeNode { /** * Creates a new AbstractElement. * * @param parent the parent element * @param a the attributes for the element * @since 1.4 */ public AbstractElement(Element parent, AttributeSet a) { this.parent = parent; attributes = getAttributeContext().getEmptySet(); if (a != null) { addAttributes(a); } } private final void indent(PrintWriter out, int n) { for (int i = 0; i < n; i++) { out.print(" "); } } /** * Dumps a debugging representation of the element hierarchy. * * @param psOut the output stream * @param indentAmount the indentation level >= 0 */ public void dump(PrintStream psOut, int indentAmount) { PrintWriter out; try { out = new PrintWriter(new OutputStreamWriter(psOut,"JavaEsc"), true); } catch (UnsupportedEncodingException e){ out = new PrintWriter(psOut,true); } indent(out, indentAmount); if (getName() == null) { out.print("<??"); } else { out.print("<" + getName()); } if (getAttributeCount() > 0) { out.println(""); // dump the attributes Enumeration names = attributes.getAttributeNames(); while (names.hasMoreElements()) { Object name = names.nextElement(); indent(out, indentAmount + 1); out.println(name + "=" + getAttribute(name)); } indent(out, indentAmount); } out.println(">"); if (isLeaf()) { indent(out, indentAmount+1); out.print("[" + getStartOffset() + "," + getEndOffset() + "]"); Content c = getContent(); try { String contentStr = c.getString(getStartOffset(), getEndOffset() - getStartOffset())/*.trim()*/; if (contentStr.length() > 40) { contentStr = contentStr.substring(0, 40) + "..."; } out.println("["+contentStr+"]"); } catch (BadLocationException e) { } } else { int n = getElementCount(); for (int i = 0; i < n; i++) { AbstractElement e = (AbstractElement) getElement(i); e.dump(psOut, indentAmount+1); } } } // --- AttributeSet ---------------------------- // delegated to the immutable field "attributes" /** * Gets the number of attributes that are defined. * * @return the number of attributes >= 0 * @see AttributeSet#getAttributeCount */ public int getAttributeCount() { return attributes.getAttributeCount(); } /** * Checks whether a given attribute is defined. * * @param attrName the non-null attribute name * @return true if the attribute is defined * @see AttributeSet#isDefined */ public boolean isDefined(Object attrName) { return attributes.isDefined(attrName); } /** * Checks whether two attribute sets are equal. * * @param attr the attribute set to check against * @return true if the same * @see AttributeSet#isEqual */ public boolean isEqual(AttributeSet attr) { return attributes.isEqual(attr); } /** * Copies a set of attributes. * * @return the copy * @see AttributeSet#copyAttributes */ public AttributeSet copyAttributes() { return attributes.copyAttributes(); } /** * Gets the value of an attribute. * * @param attrName the non-null attribute name * @return the attribute value * @see AttributeSet#getAttribute */ public Object getAttribute(Object attrName) { Object value = attributes.getAttribute(attrName); if (value == null) { // The delegate nor it's resolvers had a match, // so we'll try to resolve through the parent // element. AttributeSet a = (parent != null) ? parent.getAttributes() : null; if (a != null) { value = a.getAttribute(attrName); } } return value; } /** * Gets the names of all attributes. * * @return the attribute names as an enumeration * @see AttributeSet#getAttributeNames */ public Enumeration<?> getAttributeNames() { return attributes.getAttributeNames(); } /** * Checks whether a given attribute name/value is defined. * * @param name the non-null attribute name * @param value the attribute value * @return true if the name/value is defined * @see AttributeSet#containsAttribute */ public boolean containsAttribute(Object name, Object value) { return attributes.containsAttribute(name, value); } /** * Checks whether the element contains all the attributes. * * @param attrs the attributes to check * @return true if the element contains all the attributes * @see AttributeSet#containsAttributes */ public boolean containsAttributes(AttributeSet attrs) { return attributes.containsAttributes(attrs); } /** * Gets the resolving parent. * If not overridden, the resolving parent defaults to * the parent element. * * @return the attributes from the parent, <code>null if none * @see AttributeSet#getResolveParent */ public AttributeSet getResolveParent() { AttributeSet a = attributes.getResolveParent(); if ((a == null) && (parent != null)) { a = parent.getAttributes(); } return a; } // --- MutableAttributeSet ---------------------------------- // should fetch a new immutable record for the field // "attributes". /** * Adds an attribute to the element. * * @param name the non-null attribute name * @param value the attribute value * @see MutableAttributeSet#addAttribute */ public void addAttribute(Object name, Object value) { checkForIllegalCast(); AttributeContext context = getAttributeContext(); attributes = context.addAttribute(attributes, name, value); } /** * Adds a set of attributes to the element. * * @param attr the attributes to add * @see MutableAttributeSet#addAttribute */ public void addAttributes(AttributeSet attr) { checkForIllegalCast(); AttributeContext context = getAttributeContext(); attributes = context.addAttributes(attributes, attr); } /** * Removes an attribute from the set. * * @param name the non-null attribute name * @see MutableAttributeSet#removeAttribute */ public void removeAttribute(Object name) { checkForIllegalCast(); AttributeContext context = getAttributeContext(); attributes = context.removeAttribute(attributes, name); } /** * Removes a set of attributes for the element. * * @param names the attribute names * @see MutableAttributeSet#removeAttributes */ public void removeAttributes(Enumeration<?> names) { checkForIllegalCast(); AttributeContext context = getAttributeContext(); attributes = context.removeAttributes(attributes, names); } /** * Removes a set of attributes for the element. * * @param attrs the attributes * @see MutableAttributeSet#removeAttributes */ public void removeAttributes(AttributeSet attrs) { checkForIllegalCast(); AttributeContext context = getAttributeContext(); if (attrs == this) { attributes = context.getEmptySet(); } else { attributes = context.removeAttributes(attributes, attrs); } } /** * Sets the resolving parent. * * @param parent the parent, null if none * @see MutableAttributeSet#setResolveParent */ public void setResolveParent(AttributeSet parent) { checkForIllegalCast(); AttributeContext context = getAttributeContext(); if (parent != null) { attributes = context.addAttribute(attributes, StyleConstants.ResolveAttribute, parent); } else { attributes = context.removeAttribute(attributes, StyleConstants.ResolveAttribute); } } private final void checkForIllegalCast() { Thread t = getCurrentWriter(); if ((t == null) || (t != Thread.currentThread())) { throw new StateInvariantError("Illegal cast to MutableAttributeSet"); } } // --- Element methods ------------------------------------- /** * Retrieves the underlying model. * * @return the model */ public Document getDocument() { return AbstractDocument.this; } /** * Gets the parent of the element. * * @return the parent */ public Element getParentElement() { return parent; } /** * Gets the attributes for the element. * * @return the attribute set */ public AttributeSet getAttributes() { return this; } /** * Gets the name of the element. * * @return the name, null if none */ public String getName() { if (attributes.isDefined(ElementNameAttribute)) { return (String) attributes.getAttribute(ElementNameAttribute); } return null; } /** * Gets the starting offset in the model for the element. * * @return the offset >= 0 */ public abstract int getStartOffset(); /** * Gets the ending offset in the model for the element. * * @return the offset >= 0 */ public abstract int getEndOffset(); /** * Gets a child element. * * @param index the child index, >= 0 && < getElementCount() * @return the child element */ public abstract Element getElement(int index); /** * Gets the number of children for the element. * * @return the number of children >= 0 */ public abstract int getElementCount(); /** * Gets the child element index closest to the given model offset. * * @param offset the offset >= 0 * @return the element index >= 0 */ public abstract int getElementIndex(int offset); /** * Checks whether the element is a leaf. * * @return true if a leaf */ public abstract boolean isLeaf(); // --- TreeNode methods ------------------------------------- /** * Returns the child <code>TreeNode at index * <code>childIndex. */ public TreeNode getChildAt(int childIndex) { return (TreeNode)getElement(childIndex); } /** * Returns the number of children <code>TreeNode's * receiver contains. * @return the number of children <code>TreeNodews's * receiver contains */ public int getChildCount() { return getElementCount(); } /** * Returns the parent <code>TreeNode of the receiver. * @return the parent <code>TreeNode of the receiver */ public TreeNode getParent() { return (TreeNode)getParentElement(); } /** * Returns the index of <code>node in the receivers children. * If the receiver does not contain <code>node, -1 will be * returned. * @param node the location of interest * @return the index of <code>node in the receiver's * children, or -1 if absent */ public int getIndex(TreeNode node) { for(int counter = getChildCount() - 1; counter >= 0; counter--) if(getChildAt(counter) == node) return counter; return -1; } /** * Returns true if the receiver allows children. * @return true if the receiver allows children, otherwise false */ public abstract boolean getAllowsChildren(); /** * Returns the children of the receiver as an * <code>Enumeration. * @return the children of the receiver as an <code>Enumeration */ public abstract Enumeration children(); // --- serialization --------------------------------------------- private void writeObject(ObjectOutputStream s) throws IOException { s.defaultWriteObject(); StyleContext.writeAttributeSet(s, attributes); } private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException { s.defaultReadObject(); MutableAttributeSet attr = new SimpleAttributeSet(); StyleContext.readAttributeSet(s, attr); AttributeContext context = getAttributeContext(); attributes = context.addAttributes(SimpleAttributeSet.EMPTY, attr); } // ---- variables ----------------------------------------------------- private Element parent; private transient AttributeSet attributes; } /** * Implements a composite element that contains other elements. * <p> * <strong>Warning: * 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™ * has been added to the <code>java.beans package. * Please see {@link java.beans.XMLEncoder}. */ public class BranchElement extends AbstractElement { /** * Constructs a composite element that initially contains * no children. * * @param parent The parent element * @param a the attributes for the element * @since 1.4 */ public BranchElement(Element parent, AttributeSet a) { super(parent, a); children = new AbstractElement[1]; nchildren = 0; lastIndex = -1; } /** * Gets the child element that contains * the given model position. * * @param pos the position >= 0 * @return the element, null if none */ public Element positionToElement(int pos) { int index = getElementIndex(pos); Element child = children[index]; int p0 = child.getStartOffset(); int p1 = child.getEndOffset(); if ((pos >= p0) && (pos < p1)) { return child; } return null; } /** * Replaces content with a new set of elements. * * @param offset the starting offset >= 0 * @param length the length to replace >= 0 * @param elems the new elements */ public void replace(int offset, int length, Element[] elems) { int delta = elems.length - length; int src = offset + length; int nmove = nchildren - src; int dest = src + delta; if ((nchildren + delta) >= children.length) { // need to grow the array int newLength = Math.max(2*children.length, nchildren + delta); AbstractElement[] newChildren = new AbstractElement[newLength]; System.arraycopy(children, 0, newChildren, 0, offset); System.arraycopy(elems, 0, newChildren, offset, elems.length); System.arraycopy(children, src, newChildren, dest, nmove); children = newChildren; } else { // patch the existing array System.arraycopy(children, src, children, dest, nmove); System.arraycopy(elems, 0, children, offset, elems.length); } nchildren = nchildren + delta; } /** * Converts the element to a string. * * @return the string */ public String toString() { return "BranchElement(" + getName() + ") " + getStartOffset() + "," + getEndOffset() + "\n"; } // --- Element methods ----------------------------------- /** * Gets the element name. * * @return the element name */ public String getName() { String nm = super.getName(); if (nm == null) { nm = ParagraphElementName; } return nm; } /** * Gets the starting offset in the model for the element. * * @return the offset >= 0 */ public int getStartOffset() { return children[0].getStartOffset(); } /** * Gets the ending offset in the model for the element. * @throws NullPointerException if this element has no children * * @return the offset >= 0 */ public int getEndOffset() { Element child = (nchildren > 0) ? children[nchildren - 1] : children[0]; return child.getEndOffset(); } /** * Gets a child element. * * @param index the child index, >= 0 && < getElementCount() * @return the child element, null if none */ public Element getElement(int index) { if (index < nchildren) { return children[index]; } return null; } /** * Gets the number of children for the element. * * @return the number of children >= 0 */ public int getElementCount() { return nchildren; } /** * Gets the child element index closest to the given model offset. * * @param offset the offset >= 0 * @return the element index >= 0 */ public int getElementIndex(int offset) { int index; int lower = 0; int upper = nchildren - 1; int mid = 0; int p0 = getStartOffset(); int p1; if (nchildren == 0) { return 0; } if (offset >= getEndOffset()) { return nchildren - 1; } // see if the last index can be used. if ((lastIndex >= lower) && (lastIndex <= upper)) { Element lastHit = children[lastIndex]; p0 = lastHit.getStartOffset(); p1 = lastHit.getEndOffset(); if ((offset >= p0) && (offset < p1)) { return lastIndex; } // last index wasn't a hit, but it does give useful info about // where a hit (if any) would be. if (offset < p0) { upper = lastIndex; } else { lower = lastIndex; } } while (lower <= upper) { mid = lower + ((upper - lower) / 2); Element elem = children[mid]; p0 = elem.getStartOffset(); p1 = elem.getEndOffset(); if ((offset >= p0) && (offset < p1)) { // found the location index = mid; lastIndex = index; return index; } else if (offset < p0) { upper = mid - 1; } else { lower = mid + 1; } } // didn't find it, but we indicate the index of where it would belong if (offset < p0) { index = mid; } else { index = mid + 1; } lastIndex = index; return index; } /** * Checks whether the element is a leaf. * * @return true if a leaf */ public boolean isLeaf() { return false; } // ------ TreeNode ---------------------------------------------- /** * Returns true if the receiver allows children. * @return true if the receiver allows children, otherwise false */ public boolean getAllowsChildren() { return true; } /** * Returns the children of the receiver as an * <code>Enumeration. * @return the children of the receiver */ public Enumeration children() { if(nchildren == 0) return null; Vector<AbstractElement> tempVector = new Vector(nchildren); for(int counter = 0; counter < nchildren; counter++) tempVector.addElement(children[counter]); return tempVector.elements(); } // ------ members ---------------------------------------------- private AbstractElement[] children; private int nchildren; private int lastIndex; } /** * Implements an element that directly represents content of * some kind. * <p> * <strong>Warning: * 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™ * has been added to the <code>java.beans package. * Please see {@link java.beans.XMLEncoder}. * * @see Element */ public class LeafElement extends AbstractElement { /** * Constructs an element that represents content within the * document (has no children). * * @param parent The parent element * @param a The element attributes * @param offs0 The start offset >= 0 * @param offs1 The end offset >= offs0 * @since 1.4 */ public LeafElement(Element parent, AttributeSet a, int offs0, int offs1) { super(parent, a); try { p0 = createPosition(offs0); p1 = createPosition(offs1); } catch (BadLocationException e) { p0 = null; p1 = null; throw new StateInvariantError("Can't create Position references"); } } /** * Converts the element to a string. * * @return the string */ public String toString() { return "LeafElement(" + getName() + ") " + p0 + "," + p1 + "\n"; } // --- Element methods --------------------------------------------- /** * Gets the starting offset in the model for the element. * * @return the offset >= 0 */ public int getStartOffset() { return p0.getOffset(); } /** * Gets the ending offset in the model for the element. * * @return the offset >= 0 */ public int getEndOffset() { return p1.getOffset(); } /** * Gets the element name. * * @return the name */ public String getName() { String nm = super.getName(); if (nm == null) { nm = ContentElementName; } return nm; } /** * Gets the child element index closest to the given model offset. * * @param pos the offset >= 0 * @return the element index >= 0 */ public int getElementIndex(int pos) { return -1; } /** * Gets a child element. * * @param index the child index, >= 0 && < getElementCount() * @return the child element */ public Element getElement(int index) { return null; } /** * Returns the number of child elements. * * @return the number of children >= 0 */ public int getElementCount() { return 0; } /** * Checks whether the element is a leaf. * * @return true if a leaf */ public boolean isLeaf() { return true; } // ------ TreeNode ---------------------------------------------- /** * Returns true if the receiver allows children. * @return true if the receiver allows children, otherwise false */ public boolean getAllowsChildren() { return false; } /** * Returns the children of the receiver as an * <code>Enumeration. * @return the children of the receiver */ public Enumeration children() { return null; } // --- serialization --------------------------------------------- private void writeObject(ObjectOutputStream s) throws IOException { s.defaultWriteObject(); s.writeInt(p0.getOffset()); s.writeInt(p1.getOffset()); } private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException { s.defaultReadObject(); // set the range with positions that track change int off0 = s.readInt(); int off1 = s.readInt(); try { p0 = createPosition(off0); p1 = createPosition(off1); } catch (BadLocationException e) { p0 = null; p1 = null; throw new IOException("Can't restore Position references"); } } // ---- members ----------------------------------------------------- private transient Position p0; private transient Position p1; } /** * Represents the root element of the bidirectional element structure. * The root element is the only element in the bidi element structure * which contains children. */ class BidiRootElement extends BranchElement { BidiRootElement() { super( null, null ); } /** * Gets the name of the element. * @return the name */ public String getName() { return "bidi root"; } } /** * Represents an element of the bidirectional element structure. */ class BidiElement extends LeafElement { /** * Creates a new BidiElement. */ BidiElement(Element parent, int start, int end, int level) { super(parent, new SimpleAttributeSet(), start, end); addAttribute(StyleConstants.BidiLevel, Integer.valueOf(level)); //System.out.println("BidiElement: start = " + start // + " end = " + end + " level = " + level ); } /** * Gets the name of the element. * @return the name */ public String getName() { return BidiElementName; } int getLevel() { Integer o = (Integer) getAttribute(StyleConstants.BidiLevel); if (o != null) { return o.intValue(); } return 0; // Level 0 is base level (non-embedded) left-to-right } boolean isLeftToRight() { return ((getLevel() % 2) == 0); } } /** * Stores document changes as the document is being * modified. Can subsequently be used for change notification * when done with the document modification transaction. * This is used by the AbstractDocument class and its extensions * for broadcasting change information to the document listeners. */ public class DefaultDocumentEvent extends CompoundEdit implements DocumentEvent { /** * Constructs a change record. * * @param offs the offset into the document of the change >= 0 * @param len the length of the change >= 0 * @param type the type of event (DocumentEvent.EventType) * @since 1.4 */ public DefaultDocumentEvent(int offs, int len, DocumentEvent.EventType type) { super(); offset = offs; length = len; this.type = type; } /** * Returns a string description of the change event. * * @return a string */ public String toString() { return edits.toString(); } // --- CompoundEdit methods -------------------------- /** * Adds a document edit. If the number of edits crosses * a threshold, this switches on a hashtable lookup for * ElementChange implementations since access of these * needs to be relatively quick. * * @param anEdit a document edit record * @return true if the edit was added */ public boolean addEdit(UndoableEdit anEdit) { // if the number of changes gets too great, start using // a hashtable for to locate the change for a given element. if ((changeLookup == null) && (edits.size() > 10)) { changeLookup = new Hashtable<Element, ElementChange>(); int n = edits.size(); for (int i = 0; i < n; i++) { Object o = edits.elementAt(i); if (o instanceof DocumentEvent.ElementChange) { DocumentEvent.ElementChange ec = (DocumentEvent.ElementChange) o; changeLookup.put(ec.getElement(), ec); } } } // if we have a hashtable... add the entry if it's // an ElementChange. if ((changeLookup != null) && (anEdit instanceof DocumentEvent.ElementChange)) { DocumentEvent.ElementChange ec = (DocumentEvent.ElementChange) anEdit; changeLookup.put(ec.getElement(), ec); } return super.addEdit(anEdit); } /** * Redoes a change. * * @exception CannotRedoException if the change cannot be redone */ public void redo() throws CannotRedoException { writeLock(); try { // change the state super.redo(); // fire a DocumentEvent to notify the view(s) UndoRedoDocumentEvent ev = new UndoRedoDocumentEvent(this, false); if (type == DocumentEvent.EventType.INSERT) { fireInsertUpdate(ev); } else if (type == DocumentEvent.EventType.REMOVE) { fireRemoveUpdate(ev); } else { fireChangedUpdate(ev); } } finally { writeUnlock(); } } /** * Undoes a change. * * @exception CannotUndoException if the change cannot be undone */ public void undo() throws CannotUndoException { writeLock(); try { // change the state super.undo(); // fire a DocumentEvent to notify the view(s) UndoRedoDocumentEvent ev = new UndoRedoDocumentEvent(this, true); if (type == DocumentEvent.EventType.REMOVE) { fireInsertUpdate(ev); } else if (type == DocumentEvent.EventType.INSERT) { fireRemoveUpdate(ev); } else { fireChangedUpdate(ev); } } finally { writeUnlock(); } } /** * DefaultDocument events are significant. If you wish to aggregate * DefaultDocumentEvents to present them as a single edit to the user * place them into a CompoundEdit. * * @return whether the event is significant for edit undo purposes */ public boolean isSignificant() { return true; } /** * Provides a localized, human readable description of this edit * suitable for use in, say, a change log. * * @return the description */ public String getPresentationName() { DocumentEvent.EventType type = getType(); if(type == DocumentEvent.EventType.INSERT) return UIManager.getString("AbstractDocument.additionText"); if(type == DocumentEvent.EventType.REMOVE) return UIManager.getString("AbstractDocument.deletionText"); return UIManager.getString("AbstractDocument.styleChangeText"); } /** * Provides a localized, human readable description of the undoable * form of this edit, e.g. for use as an Undo menu item. Typically * derived from getDescription(); * * @return the description */ public String getUndoPresentationName() { return UIManager.getString("AbstractDocument.undoText") + " " + getPresentationName(); } /** * Provides a localized, human readable description of the redoable * form of this edit, e.g. for use as a Redo menu item. Typically * derived from getPresentationName(); * * @return the description */ public String getRedoPresentationName() { return UIManager.getString("AbstractDocument.redoText") + " " + getPresentationName(); } // --- DocumentEvent methods -------------------------- /** * Returns the type of event. * * @return the event type as a DocumentEvent.EventType * @see DocumentEvent#getType */ public DocumentEvent.EventType getType() { return type; } /** * Returns the offset within the document of the start of the change. * * @return the offset >= 0 * @see DocumentEvent#getOffset */ public int getOffset() { return offset; } /** * Returns the length of the change. * * @return the length >= 0 * @see DocumentEvent#getLength */ public int getLength() { return length; } /** * Gets the document that sourced the change event. * * @return the document * @see DocumentEvent#getDocument */ public Document getDocument() { return AbstractDocument.this; } /** * Gets the changes for an element. * * @param elem the element * @return the changes */ public DocumentEvent.ElementChange getChange(Element elem) { if (changeLookup != null) { return changeLookup.get(elem); } int n = edits.size(); for (int i = 0; i < n; i++) { Object o = edits.elementAt(i); if (o instanceof DocumentEvent.ElementChange) { DocumentEvent.ElementChange c = (DocumentEvent.ElementChange) o; if (elem.equals(c.getElement())) { return c; } } } return null; } // --- member variables ------------------------------------ private int offset; private int length; private Hashtable<Element, ElementChange> changeLookup; private DocumentEvent.EventType type; } /** * This event used when firing document changes while Undo/Redo * operations. It just wraps DefaultDocumentEvent and delegates * all calls to it except getType() which depends on operation * (Undo or Redo). */ class UndoRedoDocumentEvent implements DocumentEvent { private DefaultDocumentEvent src = null; private EventType type = null; public UndoRedoDocumentEvent(DefaultDocumentEvent src, boolean isUndo) { this.src = src; if(isUndo) { if(src.getType().equals(EventType.INSERT)) { type = EventType.REMOVE; } else if(src.getType().equals(EventType.REMOVE)) { type = EventType.INSERT; } else { type = src.getType(); } } else { type = src.getType(); } } public DefaultDocumentEvent getSource() { return src; } // DocumentEvent methods delegated to DefaultDocumentEvent source // except getType() which depends on operation (Undo or Redo). public int getOffset() { return src.getOffset(); } public int getLength() { return src.getLength(); } public Document getDocument() { return src.getDocument(); } public DocumentEvent.EventType getType() { return type; } public DocumentEvent.ElementChange getChange(Element elem) { return src.getChange(elem); } } /** * An implementation of ElementChange that can be added to the document * event. */ public static class ElementEdit extends AbstractUndoableEdit implements DocumentEvent.ElementChange { /** * Constructs an edit record. This does not modify the element * so it can safely be used to <em>catch up a view to the * current model state for views that just attached to a model. * * @param e the element * @param index the index into the model >= 0 * @param removed a set of elements that were removed * @param added a set of elements that were added */ public ElementEdit(Element e, int index, Element[] removed, Element[] added) { super(); this.e = e; this.index = index; this.removed = removed; this.added = added; } /** * Returns the underlying element. * * @return the element */ public Element getElement() { return e; } /** * Returns the index into the list of elements. * * @return the index >= 0 */ public int getIndex() { return index; } /** * Gets a list of children that were removed. * * @return the list */ public Element[] getChildrenRemoved() { return removed; } /** * Gets a list of children that were added. * * @return the list */ public Element[] getChildrenAdded() { return added; } /** * Redoes a change. * * @exception CannotRedoException if the change cannot be redone */ public void redo() throws CannotRedoException { super.redo(); // Since this event will be reused, switch around added/removed. Element[] tmp = removed; removed = added; added = tmp; // PENDING(prinz) need MutableElement interface, canRedo() should check ((AbstractDocument.BranchElement)e).replace(index, removed.length, added); } /** * Undoes a change. * * @exception CannotUndoException if the change cannot be undone */ public void undo() throws CannotUndoException { super.undo(); // PENDING(prinz) need MutableElement interface, canUndo() should check ((AbstractDocument.BranchElement)e).replace(index, added.length, removed); // Since this event will be reused, switch around added/removed. Element[] tmp = removed; removed = added; added = tmp; } private Element e; private int index; private Element[] removed; private Element[] added; } private class DefaultFilterBypass extends DocumentFilter.FilterBypass { public Document getDocument() { return AbstractDocument.this; } public void remove(int offset, int length) throws BadLocationException { handleRemove(offset, length); } public void insertString(int offset, String string, AttributeSet attr) throws BadLocationException { handleInsertString(offset, string, attr); } public void replace(int offset, int length, String text, AttributeSet attrs) throws BadLocationException { handleRemove(offset, length); handleInsertString(offset, text, attrs); } } }

Other Java examples (source code examples)

Here is a short list of links related to this Java AbstractDocument.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.