summaryrefslogtreecommitdiff
path: root/libjava/classpath/javax/swing/text/AbstractDocument.java
diff options
context:
space:
mode:
Diffstat (limited to 'libjava/classpath/javax/swing/text/AbstractDocument.java')
-rw-r--r--libjava/classpath/javax/swing/text/AbstractDocument.java2906
1 files changed, 2906 insertions, 0 deletions
diff --git a/libjava/classpath/javax/swing/text/AbstractDocument.java b/libjava/classpath/javax/swing/text/AbstractDocument.java
new file mode 100644
index 000000000..25915bb5a
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/AbstractDocument.java
@@ -0,0 +1,2906 @@
+/* AbstractDocument.java --
+ Copyright (C) 2002, 2004, 2005, 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath 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 for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text;
+
+import gnu.java.lang.CPStringBuilder;
+
+import java.awt.font.TextAttribute;
+import java.io.PrintStream;
+import java.io.Serializable;
+import java.text.Bidi;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.event.EventListenerList;
+import javax.swing.event.UndoableEditEvent;
+import javax.swing.event.UndoableEditListener;
+import javax.swing.text.DocumentFilter;
+import javax.swing.tree.TreeNode;
+import javax.swing.undo.AbstractUndoableEdit;
+import javax.swing.undo.CompoundEdit;
+import javax.swing.undo.UndoableEdit;
+
+/**
+ * An abstract base implementation for the {@link Document} interface.
+ * This class provides some common functionality for all <code>Element</code>s,
+ * most notably it implements a locking mechanism to make document modification
+ * thread-safe.
+ *
+ * @author original author unknown
+ * @author Roman Kennke (roman@kennke.org)
+ */
+public abstract class AbstractDocument implements Document, Serializable
+{
+ /** The serialization UID (compatible with JDK1.5). */
+ private static final long serialVersionUID = 6842927725919637215L;
+
+ /**
+ * Standard error message to indicate a bad location.
+ */
+ protected static final String BAD_LOCATION = "document location failure";
+
+ /**
+ * Standard name for unidirectional <code>Element</code>s.
+ */
+ public static final String BidiElementName = "bidi level";
+
+ /**
+ * Standard name for content <code>Element</code>s. These are usually
+ * {@link LeafElement}s.
+ */
+ public static final String ContentElementName = "content";
+
+ /**
+ * Standard name for paragraph <code>Element</code>s. These are usually
+ * {@link BranchElement}s.
+ */
+ public static final String ParagraphElementName = "paragraph";
+
+ /**
+ * Standard name for section <code>Element</code>s. These are usually
+ * {@link DefaultStyledDocument.SectionElement}s.
+ */
+ public static final String SectionElementName = "section";
+
+ /**
+ * Attribute key for storing the element name.
+ */
+ public static final String ElementNameAttribute = "$ename";
+
+ /**
+ * Standard name for the bidi root element.
+ */
+ private static final String BidiRootName = "bidi root";
+
+ /**
+ * Key for storing the asynchronous load priority.
+ */
+ private static final String AsyncLoadPriority = "load priority";
+
+ /**
+ * Key for storing the I18N state.
+ */
+ private static final String I18N = "i18n";
+
+ /**
+ * The actual content model of this <code>Document</code>.
+ */
+ Content content;
+
+ /**
+ * The AttributeContext for this <code>Document</code>.
+ */
+ AttributeContext context;
+
+ /**
+ * The currently installed <code>DocumentFilter</code>.
+ */
+ DocumentFilter documentFilter;
+
+ /**
+ * The documents properties.
+ */
+ Dictionary properties;
+
+ /**
+ * Manages event listeners for this <code>Document</code>.
+ */
+ protected EventListenerList listenerList = new EventListenerList();
+
+ /**
+ * Stores the current writer thread. Used for locking.
+ */
+ private Thread currentWriter = null;
+
+ /**
+ * The number of readers. Used for locking.
+ */
+ private int numReaders = 0;
+
+ /**
+ * The number of current writers. If this is > 1 then the same thread entered
+ * the write lock more than once.
+ */
+ private int numWriters = 0;
+
+ /** An instance of a DocumentFilter.FilterBypass which allows calling
+ * the insert, remove and replace method without checking for an installed
+ * document filter.
+ */
+ private DocumentFilter.FilterBypass bypass;
+
+ /**
+ * The bidi root element.
+ */
+ private BidiRootElement bidiRoot;
+
+ /**
+ * True when we are currently notifying any listeners. This is used
+ * to detect illegal situations in writeLock().
+ */
+ private transient boolean notifyListeners;
+
+ /**
+ * Creates a new <code>AbstractDocument</code> with the specified
+ * {@link Content} model.
+ *
+ * @param doc the <code>Content</code> model to be used in this
+ * <code>Document<code>
+ *
+ * @see GapContent
+ * @see StringContent
+ */
+ protected AbstractDocument(Content doc)
+ {
+ this(doc, StyleContext.getDefaultStyleContext());
+ }
+
+ /**
+ * Creates a new <code>AbstractDocument</code> with the specified
+ * {@link Content} model and {@link AttributeContext}.
+ *
+ * @param doc the <code>Content</code> model to be used in this
+ * <code>Document<code>
+ * @param ctx the <code>AttributeContext</code> to use
+ *
+ * @see GapContent
+ * @see StringContent
+ */
+ protected AbstractDocument(Content doc, AttributeContext ctx)
+ {
+ content = doc;
+ context = ctx;
+
+ // FIXME: Fully implement bidi.
+ bidiRoot = new BidiRootElement();
+
+ // FIXME: This is determined using a Mauve test. Make the document
+ // actually use this.
+ putProperty(I18N, Boolean.FALSE);
+
+ // Add one child to the bidi root.
+ writeLock();
+ try
+ {
+ Element[] children = new Element[1];
+ children[0] = new BidiElement(bidiRoot, 0, 1, 0);
+ bidiRoot.replace(0, 0, children);
+ }
+ finally
+ {
+ writeUnlock();
+ }
+ }
+
+ /** Returns the DocumentFilter.FilterBypass instance for this
+ * document and create it if it does not exist yet.
+ *
+ * @return This document's DocumentFilter.FilterBypass instance.
+ */
+ private DocumentFilter.FilterBypass getBypass()
+ {
+ if (bypass == null)
+ bypass = new Bypass();
+
+ return bypass;
+ }
+
+ /**
+ * Returns the paragraph {@link Element} that holds the specified position.
+ *
+ * @param pos the position for which to get the paragraph element
+ *
+ * @return the paragraph {@link Element} that holds the specified position
+ */
+ public abstract Element getParagraphElement(int pos);
+
+ /**
+ * Returns the default root {@link Element} of this <code>Document</code>.
+ * Usual <code>Document</code>s only have one root element and return this.
+ * However, there may be <code>Document</code> implementations that
+ * support multiple root elements, they have to return a default root element
+ * here.
+ *
+ * @return the default root {@link Element} of this <code>Document</code>
+ */
+ public abstract Element getDefaultRootElement();
+
+ /**
+ * Creates and returns a branch element with the specified
+ * <code>parent</code> and <code>attributes</code>. Note that the new
+ * <code>Element</code> is linked to the parent <code>Element</code>
+ * through {@link Element#getParentElement}, but it is not yet added
+ * to the parent <code>Element</code> as child.
+ *
+ * @param parent the parent <code>Element</code> for the new branch element
+ * @param attributes the text attributes to be installed in the new element
+ *
+ * @return the new branch <code>Element</code>
+ *
+ * @see BranchElement
+ */
+ protected Element createBranchElement(Element parent,
+ AttributeSet attributes)
+ {
+ return new BranchElement(parent, attributes);
+ }
+
+ /**
+ * Creates and returns a leaf element with the specified
+ * <code>parent</code> and <code>attributes</code>. Note that the new
+ * <code>Element</code> is linked to the parent <code>Element</code>
+ * through {@link Element#getParentElement}, but it is not yet added
+ * to the parent <code>Element</code> as child.
+ *
+ * @param parent the parent <code>Element</code> for the new branch element
+ * @param attributes the text attributes to be installed in the new element
+ *
+ * @return the new branch <code>Element</code>
+ *
+ * @see LeafElement
+ */
+ protected Element createLeafElement(Element parent, AttributeSet attributes,
+ int start, int end)
+ {
+ return new LeafElement(parent, attributes, start, end);
+ }
+
+ /**
+ * Creates a {@link Position} that keeps track of the location at the
+ * specified <code>offset</code>.
+ *
+ * @param offset the location in the document to keep track by the new
+ * <code>Position</code>
+ *
+ * @return the newly created <code>Position</code>
+ *
+ * @throws BadLocationException if <code>offset</code> is not a valid
+ * location in the documents content model
+ */
+ public synchronized Position createPosition(final int offset)
+ throws BadLocationException
+ {
+ return content.createPosition(offset);
+ }
+
+ /**
+ * Notifies all registered listeners when the document model changes.
+ *
+ * @param event the <code>DocumentEvent</code> to be fired
+ */
+ protected void fireChangedUpdate(DocumentEvent event)
+ {
+ notifyListeners = true;
+ try
+ {
+ DocumentListener[] listeners = getDocumentListeners();
+ for (int index = 0; index < listeners.length; ++index)
+ listeners[index].changedUpdate(event);
+ }
+ finally
+ {
+ notifyListeners = false;
+ }
+ }
+
+ /**
+ * Notifies all registered listeners when content is inserted in the document
+ * model.
+ *
+ * @param event the <code>DocumentEvent</code> to be fired
+ */
+ protected void fireInsertUpdate(DocumentEvent event)
+ {
+ notifyListeners = true;
+ try
+ {
+ DocumentListener[] listeners = getDocumentListeners();
+ for (int index = 0; index < listeners.length; ++index)
+ listeners[index].insertUpdate(event);
+ }
+ finally
+ {
+ notifyListeners = false;
+ }
+ }
+
+ /**
+ * Notifies all registered listeners when content is removed from the
+ * document model.
+ *
+ * @param event the <code>DocumentEvent</code> to be fired
+ */
+ protected void fireRemoveUpdate(DocumentEvent event)
+ {
+ notifyListeners = true;
+ try
+ {
+ DocumentListener[] listeners = getDocumentListeners();
+ for (int index = 0; index < listeners.length; ++index)
+ listeners[index].removeUpdate(event);
+ }
+ finally
+ {
+ notifyListeners = false;
+ }
+ }
+
+ /**
+ * Notifies all registered listeners when an <code>UndoableEdit</code> has
+ * been performed on this <code>Document</code>.
+ *
+ * @param event the <code>UndoableEditEvent</code> to be fired
+ */
+ protected void fireUndoableEditUpdate(UndoableEditEvent event)
+ {
+ UndoableEditListener[] listeners = getUndoableEditListeners();
+
+ for (int index = 0; index < listeners.length; ++index)
+ listeners[index].undoableEditHappened(event);
+ }
+
+ /**
+ * Returns the asynchronous loading priority. Returns <code>-1</code> if this
+ * document should not be loaded asynchronously.
+ *
+ * @return the asynchronous loading priority
+ */
+ public int getAsynchronousLoadPriority()
+ {
+ Object val = getProperty(AsyncLoadPriority);
+ int prio = -1;
+ if (val != null)
+ prio = ((Integer) val).intValue();
+ return prio;
+ }
+
+ /**
+ * Returns the {@link AttributeContext} used in this <code>Document</code>.
+ *
+ * @return the {@link AttributeContext} used in this <code>Document</code>
+ */
+ protected final AttributeContext getAttributeContext()
+ {
+ return context;
+ }
+
+ /**
+ * Returns the root element for bidirectional content.
+ *
+ * @return the root element for bidirectional content
+ */
+ public Element getBidiRootElement()
+ {
+ return bidiRoot;
+ }
+
+ /**
+ * Returns the {@link Content} model for this <code>Document</code>
+ *
+ * @return the {@link Content} model for this <code>Document</code>
+ *
+ * @see GapContent
+ * @see StringContent
+ */
+ protected final Content getContent()
+ {
+ return content;
+ }
+
+ /**
+ * Returns the thread that currently modifies this <code>Document</code>
+ * if there is one, otherwise <code>null</code>. This can be used to
+ * distinguish between a method call that is part of an ongoing modification
+ * or if it is a separate modification for which a new lock must be aquired.
+ *
+ * @return the thread that currently modifies this <code>Document</code>
+ * if there is one, otherwise <code>null</code>
+ */
+ protected final synchronized Thread getCurrentWriter()
+ {
+ return currentWriter;
+ }
+
+ /**
+ * Returns the properties of this <code>Document</code>.
+ *
+ * @return the properties of this <code>Document</code>
+ */
+ public Dictionary<Object, Object> getDocumentProperties()
+ {
+ // FIXME: make me thread-safe
+ if (properties == null)
+ properties = new Hashtable();
+
+ return properties;
+ }
+
+ /**
+ * Returns a {@link Position} which will always mark the end of the
+ * <code>Document</code>.
+ *
+ * @return a {@link Position} which will always mark the end of the
+ * <code>Document</code>
+ */
+ public final Position getEndPosition()
+ {
+ Position p;
+ try
+ {
+ p = createPosition(content.length());
+ }
+ catch (BadLocationException ex)
+ {
+ // Shouldn't really happen.
+ p = null;
+ }
+ return p;
+ }
+
+ /**
+ * Returns the length of this <code>Document</code>'s content.
+ *
+ * @return the length of this <code>Document</code>'s content
+ */
+ public int getLength()
+ {
+ // We return Content.getLength() -1 here because there is always an
+ // implicit \n at the end of the Content which does count in Content
+ // but not in Document.
+ return content.length() - 1;
+ }
+
+ /**
+ * Returns all registered listeners of a given listener type.
+ *
+ * @param listenerType the type of the listeners to be queried
+ *
+ * @return all registered listeners of the specified type
+ */
+ public <T extends EventListener> T[] getListeners(Class<T> listenerType)
+ {
+ return listenerList.getListeners(listenerType);
+ }
+
+ /**
+ * Returns a property from this <code>Document</code>'s property list.
+ *
+ * @param key the key of the property to be fetched
+ *
+ * @return the property for <code>key</code> or <code>null</code> if there
+ * is no such property stored
+ */
+ public final Object getProperty(Object key)
+ {
+ // FIXME: make me thread-safe
+ Object value = null;
+ if (properties != null)
+ value = properties.get(key);
+
+ return value;
+ }
+
+ /**
+ * Returns all root elements of this <code>Document</code>. By default
+ * this just returns the single root element returned by
+ * {@link #getDefaultRootElement()}. <code>Document</code> implementations
+ * that support multiple roots must override this method and return all roots
+ * here.
+ *
+ * @return all root elements of this <code>Document</code>
+ */
+ public Element[] getRootElements()
+ {
+ Element[] elements = new Element[2];
+ elements[0] = getDefaultRootElement();
+ elements[1] = getBidiRootElement();
+ return elements;
+ }
+
+ /**
+ * Returns a {@link Position} which will always mark the beginning of the
+ * <code>Document</code>.
+ *
+ * @return a {@link Position} which will always mark the beginning of the
+ * <code>Document</code>
+ */
+ public final Position getStartPosition()
+ {
+ Position p;
+ try
+ {
+ p = createPosition(0);
+ }
+ catch (BadLocationException ex)
+ {
+ // Shouldn't really happen.
+ p = null;
+ }
+ return p;
+ }
+
+ /**
+ * Returns a piece of this <code>Document</code>'s content.
+ *
+ * @param offset the start offset of the content
+ * @param length the length of the content
+ *
+ * @return the piece of content specified by <code>offset</code> and
+ * <code>length</code>
+ *
+ * @throws BadLocationException if <code>offset</code> or <code>offset +
+ * length</code> are invalid locations with this
+ * <code>Document</code>
+ */
+ public String getText(int offset, int length) throws BadLocationException
+ {
+ return content.getString(offset, length);
+ }
+
+ /**
+ * Fetches a piece of this <code>Document</code>'s content and stores
+ * it in the given {@link Segment}.
+ *
+ * @param offset the start offset of the content
+ * @param length the length of the content
+ * @param segment the <code>Segment</code> to store the content in
+ *
+ * @throws BadLocationException if <code>offset</code> or <code>offset +
+ * length</code> are invalid locations with this
+ * <code>Document</code>
+ */
+ public void getText(int offset, int length, Segment segment)
+ throws BadLocationException
+ {
+ content.getChars(offset, length, segment);
+ }
+
+ /**
+ * Inserts a String into this <code>Document</code> at the specified
+ * position and assigning the specified attributes to it.
+ *
+ * <p>If a {@link DocumentFilter} is installed in this document, the
+ * corresponding method of the filter object is called.</p>
+ *
+ * <p>The method has no effect when <code>text</code> is <code>null</code>
+ * or has a length of zero.</p>
+ *
+ *
+ * @param offset the location at which the string should be inserted
+ * @param text the content to be inserted
+ * @param attributes the text attributes to be assigned to that string
+ *
+ * @throws BadLocationException if <code>offset</code> is not a valid
+ * location in this <code>Document</code>
+ */
+ public void insertString(int offset, String text, AttributeSet attributes)
+ throws BadLocationException
+ {
+ // Bail out if we have a bogus insertion (Behavior observed in RI).
+ if (text == null || text.length() == 0)
+ return;
+
+ writeLock();
+ try
+ {
+ if (documentFilter == null)
+ insertStringImpl(offset, text, attributes);
+ else
+ documentFilter.insertString(getBypass(), offset, text, attributes);
+ }
+ finally
+ {
+ writeUnlock();
+ }
+ }
+
+ void insertStringImpl(int offset, String text, AttributeSet attributes)
+ throws BadLocationException
+ {
+ // Just return when no text to insert was given.
+ if (text == null || text.length() == 0)
+ return;
+ DefaultDocumentEvent event =
+ new DefaultDocumentEvent(offset, text.length(),
+ DocumentEvent.EventType.INSERT);
+
+ UndoableEdit undo = content.insertString(offset, text);
+ if (undo != null)
+ event.addEdit(undo);
+
+ // Check if we need bidi layout.
+ if (getProperty(I18N).equals(Boolean.FALSE))
+ {
+ Object dir = getProperty(TextAttribute.RUN_DIRECTION);
+ if (TextAttribute.RUN_DIRECTION_RTL.equals(dir))
+ putProperty(I18N, Boolean.TRUE);
+ else
+ {
+ char[] chars = text.toCharArray();
+ if (Bidi.requiresBidi(chars, 0, chars.length))
+ putProperty(I18N, Boolean.TRUE);
+ }
+ }
+
+ insertUpdate(event, attributes);
+
+ fireInsertUpdate(event);
+
+ if (undo != null)
+ fireUndoableEditUpdate(new UndoableEditEvent(this, undo));
+ }
+
+ /**
+ * Called to indicate that text has been inserted into this
+ * <code>Document</code>. The default implementation does nothing.
+ * This method is executed within a write lock.
+ *
+ * @param chng the <code>DefaultDocumentEvent</code> describing the change
+ * @param attr the attributes of the changed content
+ */
+ protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr)
+ {
+ if (Boolean.TRUE.equals(getProperty(I18N)))
+ updateBidi(chng);
+ }
+
+ /**
+ * Called after some content has been removed from this
+ * <code>Document</code>. The default implementation does nothing.
+ * This method is executed within a write lock.
+ *
+ * @param chng the <code>DefaultDocumentEvent</code> describing the change
+ */
+ protected void postRemoveUpdate(DefaultDocumentEvent chng)
+ {
+ if (Boolean.TRUE.equals(getProperty(I18N)))
+ updateBidi(chng);
+ }
+
+ /**
+ * Stores a property in this <code>Document</code>'s property list.
+ *
+ * @param key the key of the property to be stored
+ * @param value the value of the property to be stored
+ */
+ public final void putProperty(Object key, Object value)
+ {
+ // FIXME: make me thread-safe
+ if (properties == null)
+ properties = new Hashtable();
+
+ if (value == null)
+ properties.remove(key);
+ else
+ properties.put(key, value);
+
+ // Update bidi structure if the RUN_DIRECTION is set.
+ if (TextAttribute.RUN_DIRECTION.equals(key))
+ {
+ if (TextAttribute.RUN_DIRECTION_RTL.equals(value)
+ && Boolean.FALSE.equals(getProperty(I18N)))
+ putProperty(I18N, Boolean.TRUE);
+
+ if (Boolean.TRUE.equals(getProperty(I18N)))
+ {
+ writeLock();
+ try
+ {
+ DefaultDocumentEvent ev =
+ new DefaultDocumentEvent(0, getLength(),
+ DocumentEvent.EventType.INSERT);
+ updateBidi(ev);
+ }
+ finally
+ {
+ writeUnlock();
+ }
+ }
+ }
+ }
+
+ /**
+ * Updates the bidi element structure.
+ *
+ * @param ev the document event for the change
+ */
+ private void updateBidi(DefaultDocumentEvent ev)
+ {
+ // Determine start and end offset of the paragraphs to be scanned.
+ int start = 0;
+ int end = 0;
+ DocumentEvent.EventType type = ev.getType();
+ if (type == DocumentEvent.EventType.INSERT
+ || type == DocumentEvent.EventType.CHANGE)
+ {
+ int offs = ev.getOffset();
+ int endOffs = offs + ev.getLength();
+ start = getParagraphElement(offs).getStartOffset();
+ end = getParagraphElement(endOffs).getEndOffset();
+ }
+ else if (type == DocumentEvent.EventType.REMOVE)
+ {
+ Element par = getParagraphElement(ev.getOffset());
+ start = par.getStartOffset();
+ end = par.getEndOffset();
+ }
+ else
+ assert false : "Unknown event type";
+
+ // Determine the bidi levels for the affected range.
+ Bidi[] bidis = getBidis(start, end);
+
+ int removeFrom = 0;
+ int removeTo = 0;
+
+ int offs = 0;
+ int lastRunStart = 0;
+ int lastRunEnd = 0;
+ int lastRunLevel = 0;
+ ArrayList newEls = new ArrayList();
+ for (int i = 0; i < bidis.length; i++)
+ {
+ Bidi bidi = bidis[i];
+ int numRuns = bidi.getRunCount();
+ for (int r = 0; r < numRuns; r++)
+ {
+ if (r == 0 && i == 0)
+ {
+ if (start > 0)
+ {
+ // Try to merge with the previous element if it has the
+ // same bidi level as the first run.
+ int prevElIndex = bidiRoot.getElementIndex(start - 1);
+ removeFrom = prevElIndex;
+ Element prevEl = bidiRoot.getElement(prevElIndex);
+ AttributeSet atts = prevEl.getAttributes();
+ int prevElLevel = StyleConstants.getBidiLevel(atts);
+ if (prevElLevel == bidi.getRunLevel(r))
+ {
+ // Merge previous element with current run.
+ lastRunStart = prevEl.getStartOffset() - start;
+ lastRunEnd = bidi.getRunLimit(r);
+ lastRunLevel = bidi.getRunLevel(r);
+ }
+ else if (prevEl.getEndOffset() > start)
+ {
+ // Split previous element and replace by 2 new elements.
+ lastRunStart = 0;
+ lastRunEnd = bidi.getRunLimit(r);
+ lastRunLevel = bidi.getRunLevel(r);
+ newEls.add(new BidiElement(bidiRoot,
+ prevEl.getStartOffset(),
+ start, prevElLevel));
+ }
+ else
+ {
+ // Simply start new run at start location.
+ lastRunStart = 0;
+ lastRunEnd = bidi.getRunLimit(r);
+ lastRunLevel = bidi.getRunLevel(r);
+ removeFrom++;
+ }
+ }
+ else
+ {
+ // Simply start new run at start location.
+ lastRunStart = 0;
+ lastRunEnd = bidi.getRunLimit(r);
+ lastRunLevel = bidi.getRunLevel(r);
+ removeFrom = 0;
+ }
+ }
+ if (i == bidis.length - 1 && r == numRuns - 1)
+ {
+ if (end <= getLength())
+ {
+ // Try to merge last element with next element.
+ int nextIndex = bidiRoot.getElementIndex(end);
+ Element nextEl = bidiRoot.getElement(nextIndex);
+ AttributeSet atts = nextEl.getAttributes();
+ int nextLevel = StyleConstants.getBidiLevel(atts);
+ int level = bidi.getRunLevel(r);
+ if (lastRunLevel == level && level == nextLevel)
+ {
+ // Merge runs together.
+ if (lastRunStart + start == nextEl.getStartOffset())
+ removeTo = nextIndex - 1;
+ else
+ {
+ newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
+ nextEl.getEndOffset(), level));
+ removeTo = nextIndex;
+ }
+ }
+ else if (lastRunLevel == level)
+ {
+ // Merge current and last run.
+ int endOffs = offs + bidi.getRunLimit(r);
+ newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
+ start + endOffs, level));
+ if (start + endOffs == nextEl.getStartOffset())
+ removeTo = nextIndex - 1;
+ else
+ {
+ newEls.add(new BidiElement(bidiRoot, start + endOffs,
+ nextEl.getEndOffset(),
+ nextLevel));
+ removeTo = nextIndex;
+ }
+ }
+ else if (level == nextLevel)
+ {
+ // Merge current and next run.
+ newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
+ start + lastRunEnd,
+ lastRunLevel));
+ newEls.add(new BidiElement(bidiRoot, start + lastRunEnd,
+ nextEl.getEndOffset(), level));
+ removeTo = nextIndex;
+ }
+ else
+ {
+ // Split next element.
+ int endOffs = offs + bidi.getRunLimit(r);
+ newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
+ start + lastRunEnd,
+ lastRunLevel));
+ newEls.add(new BidiElement(bidiRoot, start + lastRunEnd,
+ start + endOffs, level));
+ newEls.add(new BidiElement(bidiRoot, start + endOffs,
+ nextEl.getEndOffset(),
+ nextLevel));
+ removeTo = nextIndex;
+ }
+ }
+ else
+ {
+ removeTo = bidiRoot.getElementIndex(end);
+ int level = bidi.getRunLevel(r);
+ int runEnd = offs + bidi.getRunLimit(r);
+
+ if (level == lastRunLevel)
+ {
+ // Merge with previous.
+ lastRunEnd = offs + runEnd;
+ newEls.add(new BidiElement(bidiRoot,
+ start + lastRunStart,
+ start + runEnd, level));
+ }
+ else
+ {
+ // Create element for last run and current run.
+ newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
+ start + lastRunEnd,
+ lastRunLevel));
+ newEls.add(new BidiElement(bidiRoot,
+ start + lastRunEnd,
+ start + runEnd,
+ level));
+ }
+ }
+ }
+ else
+ {
+ int level = bidi.getRunLevel(r);
+ int runEnd = bidi.getRunLimit(r);
+
+ if (level == lastRunLevel)
+ {
+ // Merge with previous.
+ lastRunEnd = offs + runEnd;
+ }
+ else
+ {
+ // Create element for last run and update values for
+ // current run.
+ newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
+ start + lastRunEnd,
+ lastRunLevel));
+ lastRunStart = lastRunEnd;
+ lastRunEnd = offs + runEnd;
+ lastRunLevel = level;
+ }
+ }
+ }
+ offs += bidi.getLength();
+ }
+
+ // Determine the bidi elements which are to be removed.
+ int numRemoved = 0;
+ if (bidiRoot.getElementCount() > 0)
+ numRemoved = removeTo - removeFrom + 1;
+ Element[] removed = new Element[numRemoved];
+ for (int i = 0; i < numRemoved; i++)
+ removed[i] = bidiRoot.getElement(removeFrom + i);
+
+ Element[] added = new Element[newEls.size()];
+ added = (Element[]) newEls.toArray(added);
+
+ // Update the event.
+ ElementEdit edit = new ElementEdit(bidiRoot, removeFrom, removed, added);
+ ev.addEdit(edit);
+
+ // Update the structure.
+ bidiRoot.replace(removeFrom, numRemoved, added);
+ }
+
+ /**
+ * Determines the Bidi objects for the paragraphs in the specified range.
+ *
+ * @param start the start of the range
+ * @param end the end of the range
+ *
+ * @return the Bidi analysers for the paragraphs in the range
+ */
+ private Bidi[] getBidis(int start, int end)
+ {
+ // Determine the default run direction from the document property.
+ Boolean defaultDir = null;
+ Object o = getProperty(TextAttribute.RUN_DIRECTION);
+ if (o instanceof Boolean)
+ defaultDir = (Boolean) o;
+
+ // Scan paragraphs and add their level arrays to the overall levels array.
+ ArrayList bidis = new ArrayList();
+ Segment s = new Segment();
+ for (int i = start; i < end;)
+ {
+ Element par = getParagraphElement(i);
+ int pStart = par.getStartOffset();
+ int pEnd = par.getEndOffset();
+
+ // Determine the default run direction of the paragraph.
+ Boolean dir = defaultDir;
+ o = par.getAttributes().getAttribute(TextAttribute.RUN_DIRECTION);
+ if (o instanceof Boolean)
+ dir = (Boolean) o;
+
+ // Bidi over the paragraph.
+ try
+ {
+ getText(pStart, pEnd - pStart, s);
+ }
+ catch (BadLocationException ex)
+ {
+ assert false : "Must not happen";
+ }
+ int flag = Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT;
+ if (dir != null)
+ {
+ if (TextAttribute.RUN_DIRECTION_LTR.equals(dir))
+ flag = Bidi.DIRECTION_LEFT_TO_RIGHT;
+ else
+ flag = Bidi.DIRECTION_RIGHT_TO_LEFT;
+ }
+ Bidi bidi = new Bidi(s.array, s.offset, null, 0, s.count, flag);
+ bidis.add(bidi);
+ i = pEnd;
+ }
+ Bidi[] ret = new Bidi[bidis.size()];
+ ret = (Bidi[]) bidis.toArray(ret);
+ return ret;
+ }
+
+ /**
+ * Blocks until a read lock can be obtained. Must block if there is
+ * currently a writer modifying the <code>Document</code>.
+ */
+ public final synchronized void readLock()
+ {
+ try
+ {
+ while (currentWriter != null)
+ {
+ if (currentWriter == Thread.currentThread())
+ return;
+ wait();
+ }
+ numReaders++;
+ }
+ catch (InterruptedException ex)
+ {
+ throw new Error("Interrupted during grab read lock");
+ }
+ }
+
+ /**
+ * Releases the read lock. If this was the only reader on this
+ * <code>Document</code>, writing may begin now.
+ */
+ public final synchronized void readUnlock()
+ {
+ // Note we could have a problem here if readUnlock was called without a
+ // prior call to readLock but the specs simply warn users to ensure that
+ // balance by using a finally block:
+ // readLock()
+ // try
+ // {
+ // doSomethingHere
+ // }
+ // finally
+ // {
+ // readUnlock();
+ // }
+
+ // All that the JDK seems to check for is that you don't call unlock
+ // more times than you've previously called lock, but it doesn't make
+ // sure that the threads calling unlock were the same ones that called lock
+
+ // If the current thread holds the write lock, and attempted to also obtain
+ // a readLock, then numReaders hasn't been incremented and we don't need
+ // to unlock it here.
+ if (currentWriter == Thread.currentThread())
+ return;
+
+ // FIXME: the reference implementation throws a
+ // javax.swing.text.StateInvariantError here
+ if (numReaders <= 0)
+ throw new IllegalStateException("document lock failure");
+
+ // If currentWriter is not null, the application code probably had a
+ // writeLock and then tried to obtain a readLock, in which case
+ // numReaders wasn't incremented
+ numReaders--;
+ notify();
+ }
+
+ /**
+ * Removes a piece of content from this <code>Document</code>.
+ *
+ * <p>If a {@link DocumentFilter} is installed in this document, the
+ * corresponding method of the filter object is called. The
+ * <code>DocumentFilter</code> is called even if <code>length</code>
+ * is zero. This is different from {@link #replace}.</p>
+ *
+ * <p>Note: When <code>length</code> is zero or below the call is not
+ * forwarded to the underlying {@link AbstractDocument.Content} instance
+ * of this document and no exception is thrown.</p>
+ *
+ * @param offset the start offset of the fragment to be removed
+ * @param length the length of the fragment to be removed
+ *
+ * @throws BadLocationException if <code>offset</code> or
+ * <code>offset + length</code> or invalid locations within this
+ * document
+ */
+ public void remove(int offset, int length) throws BadLocationException
+ {
+ writeLock();
+ try
+ {
+ DocumentFilter f = getDocumentFilter();
+ if (f == null)
+ removeImpl(offset, length);
+ else
+ f.remove(getBypass(), offset, length);
+ }
+ finally
+ {
+ writeUnlock();
+ }
+ }
+
+ void removeImpl(int offset, int length) throws BadLocationException
+ {
+ // The RI silently ignores all requests that have a negative length.
+ // Don't ask my why, but that's how it is.
+ if (length > 0)
+ {
+ if (offset < 0 || offset > getLength())
+ throw new BadLocationException("Invalid remove position", offset);
+
+ if (offset + length > getLength())
+ throw new BadLocationException("Invalid remove length", offset);
+
+ DefaultDocumentEvent event =
+ new DefaultDocumentEvent(offset, length,
+ DocumentEvent.EventType.REMOVE);
+
+ // The order of the operations below is critical!
+ removeUpdate(event);
+ UndoableEdit temp = content.remove(offset, length);
+
+ postRemoveUpdate(event);
+ fireRemoveUpdate(event);
+ }
+ }
+
+ /**
+ * Replaces a piece of content in this <code>Document</code> with
+ * another piece of content.
+ *
+ * <p>If a {@link DocumentFilter} is installed in this document, the
+ * corresponding method of the filter object is called.</p>
+ *
+ * <p>The method has no effect if <code>length</code> is zero (and
+ * only zero) and, at the same time, <code>text</code> is
+ * <code>null</code> or has zero length.</p>
+ *
+ * @param offset the start offset of the fragment to be removed
+ * @param length the length of the fragment to be removed
+ * @param text the text to replace the content with
+ * @param attributes the text attributes to assign to the new content
+ *
+ * @throws BadLocationException if <code>offset</code> or
+ * <code>offset + length</code> or invalid locations within this
+ * document
+ *
+ * @since 1.4
+ */
+ public void replace(int offset, int length, String text,
+ AttributeSet attributes)
+ throws BadLocationException
+ {
+ // Bail out if we have a bogus replacement (Behavior observed in RI).
+ if (length == 0
+ && (text == null || text.length() == 0))
+ return;
+
+ writeLock();
+ try
+ {
+ if (documentFilter == null)
+ {
+ // It is important to call the methods which again do the checks
+ // of the arguments and the DocumentFilter because subclasses may
+ // have overridden these methods and provide crucial behavior
+ // which would be skipped if we call the non-checking variants.
+ // An example for this is PlainDocument where insertString can
+ // provide a filtering of newlines.
+ remove(offset, length);
+ insertString(offset, text, attributes);
+ }
+ else
+ documentFilter.replace(getBypass(), offset, length, text, attributes);
+ }
+ finally
+ {
+ writeUnlock();
+ }
+ }
+
+ void replaceImpl(int offset, int length, String text,
+ AttributeSet attributes)
+ throws BadLocationException
+ {
+ removeImpl(offset, length);
+ insertStringImpl(offset, text, attributes);
+ }
+
+ /**
+ * Adds a <code>DocumentListener</code> object to this document.
+ *
+ * @param listener the listener to add
+ */
+ public void addDocumentListener(DocumentListener listener)
+ {
+ listenerList.add(DocumentListener.class, listener);
+ }
+
+ /**
+ * Removes a <code>DocumentListener</code> object from this document.
+ *
+ * @param listener the listener to remove
+ */
+ public void removeDocumentListener(DocumentListener listener)
+ {
+ listenerList.remove(DocumentListener.class, listener);
+ }
+
+ /**
+ * Returns all registered <code>DocumentListener</code>s.
+ *
+ * @return all registered <code>DocumentListener</code>s
+ */
+ public DocumentListener[] getDocumentListeners()
+ {
+ return (DocumentListener[]) getListeners(DocumentListener.class);
+ }
+
+ /**
+ * Adds an {@link UndoableEditListener} to this <code>Document</code>.
+ *
+ * @param listener the listener to add
+ */
+ public void addUndoableEditListener(UndoableEditListener listener)
+ {
+ listenerList.add(UndoableEditListener.class, listener);
+ }
+
+ /**
+ * Removes an {@link UndoableEditListener} from this <code>Document</code>.
+ *
+ * @param listener the listener to remove
+ */
+ public void removeUndoableEditListener(UndoableEditListener listener)
+ {
+ listenerList.remove(UndoableEditListener.class, listener);
+ }
+
+ /**
+ * Returns all registered {@link UndoableEditListener}s.
+ *
+ * @return all registered {@link UndoableEditListener}s
+ */
+ public UndoableEditListener[] getUndoableEditListeners()
+ {
+ return (UndoableEditListener[]) getListeners(UndoableEditListener.class);
+ }
+
+ /**
+ * Called before some content gets removed from this <code>Document</code>.
+ * The default implementation does nothing but may be overridden by
+ * subclasses to modify the <code>Document</code> structure in response
+ * to a remove request. The method is executed within a write lock.
+ *
+ * @param chng the <code>DefaultDocumentEvent</code> describing the change
+ */
+ protected void removeUpdate(DefaultDocumentEvent chng)
+ {
+ // Do nothing here. Subclasses may wish to override this.
+ }
+
+ /**
+ * Called to render this <code>Document</code> visually. It obtains a read
+ * lock, ensuring that no changes will be made to the <code>document</code>
+ * during the rendering process. It then calls the {@link Runnable#run()}
+ * method on <code>runnable</code>. This method <em>must not</em> attempt
+ * to modifiy the <code>Document</code>, since a deadlock will occur if it
+ * tries to obtain a write lock. When the {@link Runnable#run()} method
+ * completes (either naturally or by throwing an exception), the read lock
+ * is released. Note that there is nothing in this method related to
+ * the actual rendering. It could be used to execute arbitrary code within
+ * a read lock.
+ *
+ * @param runnable the {@link Runnable} to execute
+ */
+ public void render(Runnable runnable)
+ {
+ readLock();
+ try
+ {
+ runnable.run();
+ }
+ finally
+ {
+ readUnlock();
+ }
+ }
+
+ /**
+ * Sets the asynchronous loading priority for this <code>Document</code>.
+ * A value of <code>-1</code> indicates that this <code>Document</code>
+ * should be loaded synchronously.
+ *
+ * @param p the asynchronous loading priority to set
+ */
+ public void setAsynchronousLoadPriority(int p)
+ {
+ Integer val = p >= 0 ? new Integer(p) : null;
+ putProperty(AsyncLoadPriority, val);
+ }
+
+ /**
+ * Sets the properties of this <code>Document</code>.
+ *
+ * @param p the document properties to set
+ */
+ public void setDocumentProperties(Dictionary<Object, Object> p)
+ {
+ // FIXME: make me thread-safe
+ properties = p;
+ }
+
+ /**
+ * Blocks until a write lock can be obtained. Must wait if there are
+ * readers currently reading or another thread is currently writing.
+ */
+ protected synchronized final void writeLock()
+ {
+ try
+ {
+ while (numReaders > 0 || currentWriter != null)
+ {
+ if (Thread.currentThread() == currentWriter)
+ {
+ if (notifyListeners)
+ throw new IllegalStateException("Mutation during notify");
+ numWriters++;
+ return;
+ }
+ wait();
+ }
+ currentWriter = Thread.currentThread();
+ numWriters = 1;
+ }
+ catch (InterruptedException ex)
+ {
+ throw new Error("Interupted during grab write lock");
+ }
+ }
+
+ /**
+ * Releases the write lock. This allows waiting readers or writers to
+ * obtain the lock.
+ */
+ protected final synchronized void writeUnlock()
+ {
+ if (--numWriters <= 0)
+ {
+ numWriters = 0;
+ currentWriter = null;
+ notifyAll();
+ }
+ }
+
+ /**
+ * Returns the currently installed {@link DocumentFilter} for this
+ * <code>Document</code>.
+ *
+ * @return the currently installed {@link DocumentFilter} for this
+ * <code>Document</code>
+ *
+ * @since 1.4
+ */
+ public DocumentFilter getDocumentFilter()
+ {
+ return documentFilter;
+ }
+
+ /**
+ * Sets the {@link DocumentFilter} for this <code>Document</code>.
+ *
+ * @param filter the <code>DocumentFilter</code> to set
+ *
+ * @since 1.4
+ */
+ public void setDocumentFilter(DocumentFilter filter)
+ {
+ this.documentFilter = filter;
+ }
+
+ /**
+ * Dumps diagnostic information to the specified <code>PrintStream</code>.
+ *
+ * @param out the stream to write the diagnostic information to
+ */
+ public void dump(PrintStream out)
+ {
+ ((AbstractElement) getDefaultRootElement()).dump(out, 0);
+ ((AbstractElement) getBidiRootElement()).dump(out, 0);
+ }
+
+ /**
+ * Defines a set of methods for managing text attributes for one or more
+ * <code>Document</code>s.
+ *
+ * Replicating {@link AttributeSet}s throughout a <code>Document</code> can
+ * be very expensive. Implementations of this interface are intended to
+ * provide intelligent management of <code>AttributeSet</code>s, eliminating
+ * costly duplication.
+ *
+ * @see StyleContext
+ */
+ public interface AttributeContext
+ {
+ /**
+ * Returns an {@link AttributeSet} that contains the attributes
+ * of <code>old</code> plus the new attribute specified by
+ * <code>name</code> and <code>value</code>.
+ *
+ * @param old the attribute set to be merged with the new attribute
+ * @param name the name of the attribute to be added
+ * @param value the value of the attribute to be added
+ *
+ * @return the old attributes plus the new attribute
+ */
+ AttributeSet addAttribute(AttributeSet old, Object name, Object value);
+
+ /**
+ * Returns an {@link AttributeSet} that contains the attributes
+ * of <code>old</code> plus the new attributes in <code>attributes</code>.
+ *
+ * @param old the set of attributes where to add the new attributes
+ * @param attributes the attributes to be added
+ *
+ * @return an {@link AttributeSet} that contains the attributes
+ * of <code>old</code> plus the new attributes in
+ * <code>attributes</code>
+ */
+ AttributeSet addAttributes(AttributeSet old, AttributeSet attributes);
+
+ /**
+ * Returns an empty {@link AttributeSet}.
+ *
+ * @return an empty {@link AttributeSet}
+ */
+ AttributeSet getEmptySet();
+
+ /**
+ * Called to indicate that the attributes in <code>attributes</code> are
+ * no longer used.
+ *
+ * @param attributes the attributes are no longer used
+ */
+ void reclaim(AttributeSet attributes);
+
+ /**
+ * Returns a {@link AttributeSet} that has the attribute with the specified
+ * <code>name</code> removed from <code>old</code>.
+ *
+ * @param old the attribute set from which an attribute is removed
+ * @param name the name of the attribute to be removed
+ *
+ * @return the attributes of <code>old</code> minus the attribute
+ * specified by <code>name</code>
+ */
+ AttributeSet removeAttribute(AttributeSet old, Object name);
+
+ /**
+ * Removes all attributes in <code>attributes</code> from <code>old</code>
+ * and returns the resulting <code>AttributeSet</code>.
+ *
+ * @param old the set of attributes from which to remove attributes
+ * @param attributes the attributes to be removed from <code>old</code>
+ *
+ * @return the attributes of <code>old</code> minus the attributes in
+ * <code>attributes</code>
+ */
+ AttributeSet removeAttributes(AttributeSet old, AttributeSet attributes);
+
+ /**
+ * Removes all attributes specified by <code>names</code> from
+ * <code>old</code> and returns the resulting <code>AttributeSet</code>.
+ *
+ * @param old the set of attributes from which to remove attributes
+ * @param names the names of the attributes to be removed from
+ * <code>old</code>
+ *
+ * @return the attributes of <code>old</code> minus the attributes in
+ * <code>attributes</code>
+ */
+ AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names);
+ }
+
+ /**
+ * A sequence of data that can be edited. This is were the actual content
+ * in <code>AbstractDocument</code>'s is stored.
+ */
+ public interface Content
+ {
+ /**
+ * Creates a {@link Position} that keeps track of the location at
+ * <code>offset</code>.
+ *
+ * @return a {@link Position} that keeps track of the location at
+ * <code>offset</code>.
+ *
+ * @throw BadLocationException if <code>offset</code> is not a valid
+ * location in this <code>Content</code> model
+ */
+ Position createPosition(int offset) throws BadLocationException;
+
+ /**
+ * Returns the length of the content.
+ *
+ * @return the length of the content
+ */
+ int length();
+
+ /**
+ * Inserts a string into the content model.
+ *
+ * @param where the offset at which to insert the string
+ * @param str the string to be inserted
+ *
+ * @return an <code>UndoableEdit</code> or <code>null</code> if undo is
+ * not supported by this <code>Content</code> model
+ *
+ * @throws BadLocationException if <code>where</code> is not a valid
+ * location in this <code>Content</code> model
+ */
+ UndoableEdit insertString(int where, String str)
+ throws BadLocationException;
+
+ /**
+ * Removes a piece of content from the content model.
+ *
+ * @param where the offset at which to remove content
+ * @param nitems the number of characters to be removed
+ *
+ * @return an <code>UndoableEdit</code> or <code>null</code> if undo is
+ * not supported by this <code>Content</code> model
+ *
+ * @throws BadLocationException if <code>where</code> is not a valid
+ * location in this <code>Content</code> model
+ */
+ UndoableEdit remove(int where, int nitems) throws BadLocationException;
+
+ /**
+ * Returns a piece of content.
+ *
+ * @param where the start offset of the requested fragment
+ * @param len the length of the requested fragment
+ *
+ * @return the requested fragment
+ * @throws BadLocationException if <code>offset</code> or
+ * <code>offset + len</code>is not a valid
+ * location in this <code>Content</code> model
+ */
+ String getString(int where, int len) throws BadLocationException;
+
+ /**
+ * Fetches a piece of content and stores it in <code>txt</code>.
+ *
+ * @param where the start offset of the requested fragment
+ * @param len the length of the requested fragment
+ * @param txt the <code>Segment</code> where to fragment is stored into
+ *
+ * @throws BadLocationException if <code>offset</code> or
+ * <code>offset + len</code>is not a valid
+ * location in this <code>Content</code> model
+ */
+ void getChars(int where, int len, Segment txt) throws BadLocationException;
+ }
+
+ /**
+ * An abstract base implementation of the {@link Element} interface.
+ */
+ public abstract class AbstractElement
+ implements Element, MutableAttributeSet, TreeNode, Serializable
+ {
+ /** The serialization UID (compatible with JDK1.5). */
+ private static final long serialVersionUID = 1712240033321461704L;
+
+ /** The number of characters that this Element spans. */
+ int count;
+
+ /** The starting offset of this Element. */
+ int offset;
+
+ /** The attributes of this Element. */
+ AttributeSet attributes;
+
+ /** The parent element. */
+ Element element_parent;
+
+ /** The parent in the TreeNode interface. */
+ TreeNode tree_parent;
+
+ /** The children of this element. */
+ Vector tree_children;
+
+ /**
+ * Creates a new instance of <code>AbstractElement</code> with a
+ * specified parent <code>Element</code> and <code>AttributeSet</code>.
+ *
+ * @param p the parent of this <code>AbstractElement</code>
+ * @param s the attributes to be assigned to this
+ * <code>AbstractElement</code>
+ */
+ public AbstractElement(Element p, AttributeSet s)
+ {
+ element_parent = p;
+ AttributeContext ctx = getAttributeContext();
+ attributes = ctx.getEmptySet();
+ if (s != null)
+ addAttributes(s);
+ }
+
+ /**
+ * Returns the child nodes of this <code>Element</code> as an
+ * <code>Enumeration</code> of {@link TreeNode}s.
+ *
+ * @return the child nodes of this <code>Element</code> as an
+ * <code>Enumeration</code> of {@link TreeNode}s
+ */
+ public abstract Enumeration children();
+
+ /**
+ * Returns <code>true</code> if this <code>AbstractElement</code>
+ * allows children.
+ *
+ * @return <code>true</code> if this <code>AbstractElement</code>
+ * allows children
+ */
+ public abstract boolean getAllowsChildren();
+
+ /**
+ * Returns the child of this <code>AbstractElement</code> at
+ * <code>index</code>.
+ *
+ * @param index the position in the child list of the child element to
+ * be returned
+ *
+ * @return the child of this <code>AbstractElement</code> at
+ * <code>index</code>
+ */
+ public TreeNode getChildAt(int index)
+ {
+ return (TreeNode) tree_children.get(index);
+ }
+
+ /**
+ * Returns the number of children of this <code>AbstractElement</code>.
+ *
+ * @return the number of children of this <code>AbstractElement</code>
+ */
+ public int getChildCount()
+ {
+ return tree_children.size();
+ }
+
+ /**
+ * Returns the index of a given child <code>TreeNode</code> or
+ * <code>-1</code> if <code>node</code> is not a child of this
+ * <code>AbstractElement</code>.
+ *
+ * @param node the node for which the index is requested
+ *
+ * @return the index of a given child <code>TreeNode</code> or
+ * <code>-1</code> if <code>node</code> is not a child of this
+ * <code>AbstractElement</code>
+ */
+ public int getIndex(TreeNode node)
+ {
+ return tree_children.indexOf(node);
+ }
+
+ /**
+ * Returns the parent <code>TreeNode</code> of this
+ * <code>AbstractElement</code> or <code>null</code> if this element
+ * has no parent.
+ *
+ * @return the parent <code>TreeNode</code> of this
+ * <code>AbstractElement</code> or <code>null</code> if this
+ * element has no parent
+ */
+ public TreeNode getParent()
+ {
+ return tree_parent;
+ }
+
+ /**
+ * Returns <code>true</code> if this <code>AbstractElement</code> is a
+ * leaf element, <code>false</code> otherwise.
+ *
+ * @return <code>true</code> if this <code>AbstractElement</code> is a
+ * leaf element, <code>false</code> otherwise
+ */
+ public abstract boolean isLeaf();
+
+ /**
+ * Adds an attribute to this element.
+ *
+ * @param name the name of the attribute to be added
+ * @param value the value of the attribute to be added
+ */
+ public void addAttribute(Object name, Object value)
+ {
+ attributes = getAttributeContext().addAttribute(attributes, name, value);
+ }
+
+ /**
+ * Adds a set of attributes to this element.
+ *
+ * @param attrs the attributes to be added to this element
+ */
+ public void addAttributes(AttributeSet attrs)
+ {
+ attributes = getAttributeContext().addAttributes(attributes, attrs);
+ }
+
+ /**
+ * Removes an attribute from this element.
+ *
+ * @param name the name of the attribute to be removed
+ */
+ public void removeAttribute(Object name)
+ {
+ attributes = getAttributeContext().removeAttribute(attributes, name);
+ }
+
+ /**
+ * Removes a set of attributes from this element.
+ *
+ * @param attrs the attributes to be removed
+ */
+ public void removeAttributes(AttributeSet attrs)
+ {
+ attributes = getAttributeContext().removeAttributes(attributes, attrs);
+ }
+
+ /**
+ * Removes a set of attribute from this element.
+ *
+ * @param names the names of the attributes to be removed
+ */
+ public void removeAttributes(Enumeration<?> names)
+ {
+ attributes = getAttributeContext().removeAttributes(attributes, names);
+ }
+
+ /**
+ * Sets the parent attribute set against which the element can resolve
+ * attributes that are not defined in itself.
+ *
+ * @param parent the resolve parent to set
+ */
+ public void setResolveParent(AttributeSet parent)
+ {
+ attributes = getAttributeContext().addAttribute(attributes,
+ ResolveAttribute,
+ parent);
+ }
+
+ /**
+ * Returns <code>true</code> if this element contains the specified
+ * attribute.
+ *
+ * @param name the name of the attribute to check
+ * @param value the value of the attribute to check
+ *
+ * @return <code>true</code> if this element contains the specified
+ * attribute
+ */
+ public boolean containsAttribute(Object name, Object value)
+ {
+ return attributes.containsAttribute(name, value);
+ }
+
+ /**
+ * Returns <code>true</code> if this element contains all of the
+ * specified attributes.
+ *
+ * @param attrs the attributes to check
+ *
+ * @return <code>true</code> if this element contains all of the
+ * specified attributes
+ */
+ public boolean containsAttributes(AttributeSet attrs)
+ {
+ return attributes.containsAttributes(attrs);
+ }
+
+ /**
+ * Returns a copy of the attributes of this element.
+ *
+ * @return a copy of the attributes of this element
+ */
+ public AttributeSet copyAttributes()
+ {
+ return attributes.copyAttributes();
+ }
+
+ /**
+ * Returns the attribute value with the specified key. If this attribute
+ * is not defined in this element and this element has a resolving
+ * parent, the search goes upward to the resolve parent chain.
+ *
+ * @param key the key of the requested attribute
+ *
+ * @return the attribute value for <code>key</code> of <code>null</code>
+ * if <code>key</code> is not found locally and cannot be resolved
+ * in this element's resolve parents
+ */
+ public Object getAttribute(Object key)
+ {
+ Object result = attributes.getAttribute(key);
+ if (result == null)
+ {
+ AttributeSet resParent = getResolveParent();
+ if (resParent != null)
+ result = resParent.getAttribute(key);
+ }
+ return result;
+ }
+
+ /**
+ * Returns the number of defined attributes in this element.
+ *
+ * @return the number of defined attributes in this element
+ */
+ public int getAttributeCount()
+ {
+ return attributes.getAttributeCount();
+ }
+
+ /**
+ * Returns the names of the attributes of this element.
+ *
+ * @return the names of the attributes of this element
+ */
+ public Enumeration<?> getAttributeNames()
+ {
+ return attributes.getAttributeNames();
+ }
+
+ /**
+ * Returns the resolve parent of this element.
+ * This is taken from the AttributeSet, but if this is null,
+ * this method instead returns the Element's parent's
+ * AttributeSet
+ *
+ * @return the resolve parent of this element
+ *
+ * @see #setResolveParent(AttributeSet)
+ */
+ public AttributeSet getResolveParent()
+ {
+ return attributes.getResolveParent();
+ }
+
+ /**
+ * Returns <code>true</code> if an attribute with the specified name
+ * is defined in this element, <code>false</code> otherwise.
+ *
+ * @param attrName the name of the requested attributes
+ *
+ * @return <code>true</code> if an attribute with the specified name
+ * is defined in this element, <code>false</code> otherwise
+ */
+ public boolean isDefined(Object attrName)
+ {
+ return attributes.isDefined(attrName);
+ }
+
+ /**
+ * Returns <code>true</code> if the specified <code>AttributeSet</code>
+ * is equal to this element's <code>AttributeSet</code>, <code>false</code>
+ * otherwise.
+ *
+ * @param attrs the attributes to compare this element to
+ *
+ * @return <code>true</code> if the specified <code>AttributeSet</code>
+ * is equal to this element's <code>AttributeSet</code>,
+ * <code>false</code> otherwise
+ */
+ public boolean isEqual(AttributeSet attrs)
+ {
+ return attributes.isEqual(attrs);
+ }
+
+ /**
+ * Returns the attributes of this element.
+ *
+ * @return the attributes of this element
+ */
+ public AttributeSet getAttributes()
+ {
+ return this;
+ }
+
+ /**
+ * Returns the {@link Document} to which this element belongs.
+ *
+ * @return the {@link Document} to which this element belongs
+ */
+ public Document getDocument()
+ {
+ return AbstractDocument.this;
+ }
+
+ /**
+ * Returns the child element at the specified <code>index</code>.
+ *
+ * @param index the index of the requested child element
+ *
+ * @return the requested element
+ */
+ public abstract Element getElement(int index);
+
+ /**
+ * Returns the name of this element.
+ *
+ * @return the name of this element
+ */
+ public String getName()
+ {
+ return (String) attributes.getAttribute(ElementNameAttribute);
+ }
+
+ /**
+ * Returns the parent element of this element.
+ *
+ * @return the parent element of this element
+ */
+ public Element getParentElement()
+ {
+ return element_parent;
+ }
+
+ /**
+ * Returns the offset inside the document model that is after the last
+ * character of this element.
+ *
+ * @return the offset inside the document model that is after the last
+ * character of this element
+ */
+ public abstract int getEndOffset();
+
+ /**
+ * Returns the number of child elements of this element.
+ *
+ * @return the number of child elements of this element
+ */
+ public abstract int getElementCount();
+
+ /**
+ * Returns the index of the child element that spans the specified
+ * offset in the document model.
+ *
+ * @param offset the offset for which the responsible element is searched
+ *
+ * @return the index of the child element that spans the specified
+ * offset in the document model
+ */
+ public abstract int getElementIndex(int offset);
+
+ /**
+ * Returns the start offset if this element inside the document model.
+ *
+ * @return the start offset if this element inside the document model
+ */
+ public abstract int getStartOffset();
+
+ /**
+ * Prints diagnostic output to the specified stream.
+ *
+ * @param stream the stream to write to
+ * @param indent the indentation level
+ */
+ public void dump(PrintStream stream, int indent)
+ {
+ CPStringBuilder b = new CPStringBuilder();
+ for (int i = 0; i < indent; ++i)
+ b.append(' ');
+ b.append('<');
+ b.append(getName());
+ // Dump attributes if there are any.
+ if (getAttributeCount() > 0)
+ {
+ b.append('\n');
+ Enumeration attNames = getAttributeNames();
+ while (attNames.hasMoreElements())
+ {
+ for (int i = 0; i < indent + 2; ++i)
+ b.append(' ');
+ Object attName = attNames.nextElement();
+ b.append(attName);
+ b.append('=');
+ Object attribute = getAttribute(attName);
+ b.append(attribute);
+ b.append('\n');
+ }
+ }
+ if (getAttributeCount() > 0)
+ {
+ for (int i = 0; i < indent; ++i)
+ b.append(' ');
+ }
+ b.append(">\n");
+
+ // Dump element content for leaf elements.
+ if (isLeaf())
+ {
+ for (int i = 0; i < indent + 2; ++i)
+ b.append(' ');
+ int start = getStartOffset();
+ int end = getEndOffset();
+ b.append('[');
+ b.append(start);
+ b.append(',');
+ b.append(end);
+ b.append("][");
+ try
+ {
+ b.append(getDocument().getText(start, end - start));
+ }
+ catch (BadLocationException ex)
+ {
+ AssertionError err = new AssertionError("BadLocationException "
+ + "must not be thrown "
+ + "here.");
+ err.initCause(ex);
+ throw err;
+ }
+ b.append("]\n");
+ }
+ stream.print(b.toString());
+
+ // Dump child elements if any.
+ int count = getElementCount();
+ for (int i = 0; i < count; ++i)
+ {
+ Element el = getElement(i);
+ if (el instanceof AbstractElement)
+ ((AbstractElement) el).dump(stream, indent + 2);
+ }
+ }
+ }
+
+ /**
+ * An implementation of {@link Element} to represent composite
+ * <code>Element</code>s that contain other <code>Element</code>s.
+ */
+ public class BranchElement extends AbstractElement
+ {
+ /** The serialization UID (compatible with JDK1.5). */
+ private static final long serialVersionUID = -6037216547466333183L;
+
+ /**
+ * The child elements of this BranchElement.
+ */
+ private Element[] children;
+
+ /**
+ * The number of children in the branch element.
+ */
+ private int numChildren;
+
+ /**
+ * The last found index in getElementIndex(). Used for faster searching.
+ */
+ private int lastIndex;
+
+ /**
+ * Creates a new <code>BranchElement</code> with the specified
+ * parent and attributes.
+ *
+ * @param parent the parent element of this <code>BranchElement</code>
+ * @param attributes the attributes to set on this
+ * <code>BranchElement</code>
+ */
+ public BranchElement(Element parent, AttributeSet attributes)
+ {
+ super(parent, attributes);
+ children = new Element[1];
+ numChildren = 0;
+ lastIndex = -1;
+ }
+
+ /**
+ * Returns the children of this <code>BranchElement</code>.
+ *
+ * @return the children of this <code>BranchElement</code>
+ */
+ public Enumeration children()
+ {
+ if (numChildren == 0)
+ return null;
+
+ Vector tmp = new Vector();
+
+ for (int index = 0; index < numChildren; ++index)
+ tmp.add(children[index]);
+
+ return tmp.elements();
+ }
+
+ /**
+ * Returns <code>true</code> since <code>BranchElements</code> allow
+ * child elements.
+ *
+ * @return <code>true</code> since <code>BranchElements</code> allow
+ * child elements
+ */
+ public boolean getAllowsChildren()
+ {
+ return true;
+ }
+
+ /**
+ * Returns the child element at the specified <code>index</code>.
+ *
+ * @param index the index of the requested child element
+ *
+ * @return the requested element
+ */
+ public Element getElement(int index)
+ {
+ if (index < 0 || index >= numChildren)
+ return null;
+
+ return children[index];
+ }
+
+ /**
+ * Returns the number of child elements of this element.
+ *
+ * @return the number of child elements of this element
+ */
+ public int getElementCount()
+ {
+ return numChildren;
+ }
+
+ /**
+ * Returns the index of the child element that spans the specified
+ * offset in the document model.
+ *
+ * @param offset the offset for which the responsible element is searched
+ *
+ * @return the index of the child element that spans the specified
+ * offset in the document model
+ */
+ public int getElementIndex(int offset)
+ {
+ // Implemented using an improved linear search.
+ // This makes use of the fact that searches are not random but often
+ // close to the previous search. So we try to start the binary
+ // search at the last found index.
+
+ int i0 = 0; // The lower bounds.
+ int i1 = numChildren - 1; // The upper bounds.
+ int index = -1; // The found index.
+
+ int p0 = getStartOffset();
+ int p1; // Start and end offset local variables.
+
+ if (numChildren == 0)
+ index = 0;
+ else if (offset >= getEndOffset())
+ index = numChildren - 1;
+ else
+ {
+ // Try lastIndex.
+ if (lastIndex >= i0 && lastIndex <= i1)
+ {
+ Element last = getElement(lastIndex);
+ p0 = last.getStartOffset();
+ p1 = last.getEndOffset();
+ if (offset >= p0 && offset < p1)
+ index = lastIndex;
+ else
+ {
+ // Narrow the search bounds using the lastIndex, even
+ // if it hasn't been a hit.
+ if (offset < p0)
+ i1 = lastIndex;
+ else
+ i0 = lastIndex;
+ }
+ }
+ // The actual search.
+ int i = 0;
+ while (i0 <= i1 && index == -1)
+ {
+ i = i0 + (i1 - i0) / 2;
+ Element el = getElement(i);
+ p0 = el.getStartOffset();
+ p1 = el.getEndOffset();
+ if (offset >= p0 && offset < p1)
+ {
+ // Found it!
+ index = i;
+ }
+ else if (offset < p0)
+ i1 = i - 1;
+ else
+ i0 = i + 1;
+ }
+
+ if (index == -1)
+ {
+ // Didn't find it. Return the boundary index.
+ if (offset < p0)
+ index = i;
+ else
+ index = i + 1;
+ }
+
+ lastIndex = index;
+ }
+ return index;
+ }
+
+ /**
+ * Returns the offset inside the document model that is after the last
+ * character of this element.
+ * This is the end offset of the last child element. If this element
+ * has no children, this method throws a <code>NullPointerException</code>.
+ *
+ * @return the offset inside the document model that is after the last
+ * character of this element
+ *
+ * @throws NullPointerException if this branch element has no children
+ */
+ public int getEndOffset()
+ {
+ // This might accss one cached element or trigger an NPE for
+ // numChildren == 0. This is checked by a Mauve test.
+ Element child = numChildren > 0 ? children[numChildren - 1]
+ : children[0];
+ return child.getEndOffset();
+ }
+
+ /**
+ * Returns the name of this element. This is {@link #ParagraphElementName}
+ * in this case.
+ *
+ * @return the name of this element
+ */
+ public String getName()
+ {
+ return ParagraphElementName;
+ }
+
+ /**
+ * Returns the start offset of this element inside the document model.
+ * This is the start offset of the first child element. If this element
+ * has no children, this method throws a <code>NullPointerException</code>.
+ *
+ * @return the start offset of this element inside the document model
+ *
+ * @throws NullPointerException if this branch element has no children and
+ * no startOffset value has been cached
+ */
+ public int getStartOffset()
+ {
+ // Do not explicitly throw an NPE here. If the first element is null
+ // then the NPE gets thrown anyway. If it isn't, then it either
+ // holds a real value (for numChildren > 0) or a cached value
+ // (for numChildren == 0) as we don't fully remove elements in replace()
+ // when removing single elements.
+ // This is checked by a Mauve test.
+ return children[0].getStartOffset();
+ }
+
+ /**
+ * Returns <code>false</code> since <code>BranchElement</code> are no
+ * leafes.
+ *
+ * @return <code>false</code> since <code>BranchElement</code> are no
+ * leafes
+ */
+ public boolean isLeaf()
+ {
+ return false;
+ }
+
+ /**
+ * Returns the <code>Element</code> at the specified <code>Document</code>
+ * offset.
+ *
+ * @return the <code>Element</code> at the specified <code>Document</code>
+ * offset
+ *
+ * @see #getElementIndex(int)
+ */
+ public Element positionToElement(int position)
+ {
+ // XXX: There is surely a better algorithm
+ // as beginning from first element each time.
+ for (int index = 0; index < numChildren; ++index)
+ {
+ Element elem = children[index];
+
+ if ((elem.getStartOffset() <= position)
+ && (position < elem.getEndOffset()))
+ return elem;
+ }
+
+ return null;
+ }
+
+ /**
+ * Replaces a set of child elements with a new set of child elemens.
+ *
+ * @param offset the start index of the elements to be removed
+ * @param length the number of elements to be removed
+ * @param elements the new elements to be inserted
+ */
+ public void replace(int offset, int length, Element[] elements)
+ {
+ int delta = elements.length - length;
+ int copyFrom = offset + length; // From where to copy.
+ int copyTo = copyFrom + delta; // Where to copy to.
+ int numMove = numChildren - copyFrom; // How many elements are moved.
+ if (numChildren + delta > children.length)
+ {
+ // Gotta grow the array.
+ int newSize = Math.max(2 * children.length, numChildren + delta);
+ Element[] target = new Element[newSize];
+ System.arraycopy(children, 0, target, 0, offset);
+ System.arraycopy(elements, 0, target, offset, elements.length);
+ System.arraycopy(children, copyFrom, target, copyTo, numMove);
+ children = target;
+ }
+ else
+ {
+ System.arraycopy(children, copyFrom, children, copyTo, numMove);
+ System.arraycopy(elements, 0, children, offset, elements.length);
+ }
+ numChildren += delta;
+ }
+
+ /**
+ * Returns a string representation of this element.
+ *
+ * @return a string representation of this element
+ */
+ public String toString()
+ {
+ return ("BranchElement(" + getName() + ") "
+ + getStartOffset() + "," + getEndOffset() + "\n");
+ }
+ }
+
+ /**
+ * Stores the changes when a <code>Document</code> is beeing modified.
+ */
+ public class DefaultDocumentEvent extends CompoundEdit
+ implements DocumentEvent
+ {
+ /** The serialization UID (compatible with JDK1.5). */
+ private static final long serialVersionUID = 5230037221564563284L;
+
+ /**
+ * The threshold that indicates when we switch to using a Hashtable.
+ */
+ private static final int THRESHOLD = 10;
+
+ /** The starting offset of the change. */
+ private int offset;
+
+ /** The length of the change. */
+ private int length;
+
+ /** The type of change. */
+ private DocumentEvent.EventType type;
+
+ /**
+ * Maps <code>Element</code> to their change records. This is only
+ * used when the changes array gets too big. We can use an
+ * (unsync'ed) HashMap here, since changes to this are (should) always
+ * be performed inside a write lock.
+ */
+ private HashMap changes;
+
+ /**
+ * Indicates if this event has been modified or not. This is used to
+ * determine if this event is thrown.
+ */
+ private boolean modified;
+
+ /**
+ * Creates a new <code>DefaultDocumentEvent</code>.
+ *
+ * @param offset the starting offset of the change
+ * @param length the length of the change
+ * @param type the type of change
+ */
+ public DefaultDocumentEvent(int offset, int length,
+ DocumentEvent.EventType type)
+ {
+ this.offset = offset;
+ this.length = length;
+ this.type = type;
+ modified = false;
+ }
+
+ /**
+ * Adds an UndoableEdit to this <code>DocumentEvent</code>. If this
+ * edit is an instance of {@link ElementEdit}, then this record can
+ * later be fetched by calling {@link #getChange}.
+ *
+ * @param edit the undoable edit to add
+ */
+ public boolean addEdit(UndoableEdit edit)
+ {
+ // Start using Hashtable when we pass a certain threshold. This
+ // gives a good memory/performance compromise.
+ if (changes == null && edits.size() > THRESHOLD)
+ {
+ changes = new HashMap();
+ int count = edits.size();
+ for (int i = 0; i < count; i++)
+ {
+ Object o = edits.elementAt(i);
+ if (o instanceof ElementChange)
+ {
+ ElementChange ec = (ElementChange) o;
+ changes.put(ec.getElement(), ec);
+ }
+ }
+ }
+
+ if (changes != null && edit instanceof ElementChange)
+ {
+ ElementChange elEdit = (ElementChange) edit;
+ changes.put(elEdit.getElement(), elEdit);
+ }
+ return super.addEdit(edit);
+ }
+
+ /**
+ * Returns the document that has been modified.
+ *
+ * @return the document that has been modified
+ */
+ public Document getDocument()
+ {
+ return AbstractDocument.this;
+ }
+
+ /**
+ * Returns the length of the modification.
+ *
+ * @return the length of the modification
+ */
+ public int getLength()
+ {
+ return length;
+ }
+
+ /**
+ * Returns the start offset of the modification.
+ *
+ * @return the start offset of the modification
+ */
+ public int getOffset()
+ {
+ return offset;
+ }
+
+ /**
+ * Returns the type of the modification.
+ *
+ * @return the type of the modification
+ */
+ public DocumentEvent.EventType getType()
+ {
+ return type;
+ }
+
+ /**
+ * Returns the changes for an element.
+ *
+ * @param elem the element for which the changes are requested
+ *
+ * @return the changes for <code>elem</code> or <code>null</code> if
+ * <code>elem</code> has not been changed
+ */
+ public ElementChange getChange(Element elem)
+ {
+ ElementChange change = null;
+ if (changes != null)
+ {
+ change = (ElementChange) changes.get(elem);
+ }
+ else
+ {
+ int count = edits.size();
+ for (int i = 0; i < count && change == null; i++)
+ {
+ Object o = edits.get(i);
+ if (o instanceof ElementChange)
+ {
+ ElementChange ec = (ElementChange) o;
+ if (elem.equals(ec.getElement()))
+ change = ec;
+ }
+ }
+ }
+ return change;
+ }
+
+ /**
+ * Returns a String description of the change event. This returns the
+ * toString method of the Vector of edits.
+ */
+ public String toString()
+ {
+ return edits.toString();
+ }
+ }
+
+ /**
+ * An implementation of {@link DocumentEvent.ElementChange} to be added
+ * to {@link DefaultDocumentEvent}s.
+ */
+ public static class ElementEdit extends AbstractUndoableEdit
+ implements DocumentEvent.ElementChange
+ {
+ /** The serial version UID of ElementEdit. */
+ private static final long serialVersionUID = -1216620962142928304L;
+
+ /**
+ * The changed element.
+ */
+ private Element elem;
+
+ /**
+ * The index of the change.
+ */
+ private int index;
+
+ /**
+ * The removed elements.
+ */
+ private Element[] removed;
+
+ /**
+ * The added elements.
+ */
+ private Element[] added;
+
+ /**
+ * Creates a new <code>ElementEdit</code>.
+ *
+ * @param elem the changed element
+ * @param index the index of the change
+ * @param removed the removed elements
+ * @param added the added elements
+ */
+ public ElementEdit(Element elem, int index,
+ Element[] removed, Element[] added)
+ {
+ this.elem = elem;
+ this.index = index;
+ this.removed = removed;
+ this.added = added;
+ }
+
+ /**
+ * Returns the added elements.
+ *
+ * @return the added elements
+ */
+ public Element[] getChildrenAdded()
+ {
+ return added;
+ }
+
+ /**
+ * Returns the removed elements.
+ *
+ * @return the removed elements
+ */
+ public Element[] getChildrenRemoved()
+ {
+ return removed;
+ }
+
+ /**
+ * Returns the changed element.
+ *
+ * @return the changed element
+ */
+ public Element getElement()
+ {
+ return elem;
+ }
+
+ /**
+ * Returns the index of the change.
+ *
+ * @return the index of the change
+ */
+ public int getIndex()
+ {
+ return index;
+ }
+ }
+
+ /**
+ * An implementation of {@link Element} that represents a leaf in the
+ * document structure. This is used to actually store content.
+ */
+ public class LeafElement extends AbstractElement
+ {
+ /** The serialization UID (compatible with JDK1.5). */
+ private static final long serialVersionUID = -8906306331347768017L;
+
+ /**
+ * Manages the start offset of this element.
+ */
+ private Position startPos;
+
+ /**
+ * Manages the end offset of this element.
+ */
+ private Position endPos;
+
+ /**
+ * Creates a new <code>LeafElement</code>.
+ *
+ * @param parent the parent of this <code>LeafElement</code>
+ * @param attributes the attributes to be set
+ * @param start the start index of this element inside the document model
+ * @param end the end index of this element inside the document model
+ */
+ public LeafElement(Element parent, AttributeSet attributes, int start,
+ int end)
+ {
+ super(parent, attributes);
+ try
+ {
+ startPos = createPosition(start);
+ endPos = createPosition(end);
+ }
+ catch (BadLocationException ex)
+ {
+ AssertionError as;
+ as = new AssertionError("BadLocationException thrown "
+ + "here. start=" + start
+ + ", end=" + end
+ + ", length=" + getLength());
+ as.initCause(ex);
+ throw as;
+ }
+ }
+
+ /**
+ * Returns <code>null</code> since <code>LeafElement</code>s cannot have
+ * children.
+ *
+ * @return <code>null</code> since <code>LeafElement</code>s cannot have
+ * children
+ */
+ public Enumeration children()
+ {
+ return null;
+ }
+
+ /**
+ * Returns <code>false</code> since <code>LeafElement</code>s cannot have
+ * children.
+ *
+ * @return <code>false</code> since <code>LeafElement</code>s cannot have
+ * children
+ */
+ public boolean getAllowsChildren()
+ {
+ return false;
+ }
+
+ /**
+ * Returns <code>null</code> since <code>LeafElement</code>s cannot have
+ * children.
+ *
+ * @return <code>null</code> since <code>LeafElement</code>s cannot have
+ * children
+ */
+ public Element getElement(int index)
+ {
+ return null;
+ }
+
+ /**
+ * Returns <code>0</code> since <code>LeafElement</code>s cannot have
+ * children.
+ *
+ * @return <code>0</code> since <code>LeafElement</code>s cannot have
+ * children
+ */
+ public int getElementCount()
+ {
+ return 0;
+ }
+
+ /**
+ * Returns <code>-1</code> since <code>LeafElement</code>s cannot have
+ * children.
+ *
+ * @return <code>-1</code> since <code>LeafElement</code>s cannot have
+ * children
+ */
+ public int getElementIndex(int offset)
+ {
+ return -1;
+ }
+
+ /**
+ * Returns the end offset of this <code>Element</code> inside the
+ * document.
+ *
+ * @return the end offset of this <code>Element</code> inside the
+ * document
+ */
+ public int getEndOffset()
+ {
+ return endPos.getOffset();
+ }
+
+ /**
+ * Returns the name of this <code>Element</code>. This is
+ * {@link #ContentElementName} in this case.
+ *
+ * @return the name of this <code>Element</code>
+ */
+ public String getName()
+ {
+ String name = super.getName();
+ if (name == null)
+ name = ContentElementName;
+ return name;
+ }
+
+ /**
+ * Returns the start offset of this <code>Element</code> inside the
+ * document.
+ *
+ * @return the start offset of this <code>Element</code> inside the
+ * document
+ */
+ public int getStartOffset()
+ {
+ return startPos.getOffset();
+ }
+
+ /**
+ * Returns <code>true</code>.
+ *
+ * @return <code>true</code>
+ */
+ public boolean isLeaf()
+ {
+ return true;
+ }
+
+ /**
+ * Returns a string representation of this <code>Element</code>.
+ *
+ * @return a string representation of this <code>Element</code>
+ */
+ public String toString()
+ {
+ return ("LeafElement(" + getName() + ") "
+ + getStartOffset() + "," + getEndOffset() + "\n");
+ }
+ }
+
+ /**
+ * The root element for bidirectional text.
+ */
+ private class BidiRootElement
+ extends BranchElement
+ {
+ /**
+ * Creates a new bidi root element.
+ */
+ BidiRootElement()
+ {
+ super(null, null);
+ }
+
+ /**
+ * Returns the name of the element.
+ *
+ * @return the name of the element
+ */
+ public String getName()
+ {
+ return BidiRootName;
+ }
+ }
+
+ /**
+ * A leaf element for the bidi structure.
+ */
+ private class BidiElement
+ extends LeafElement
+ {
+ /**
+ * Creates a new BidiElement.
+ *
+ * @param parent the parent element
+ * @param start the start offset
+ * @param end the end offset
+ * @param level the bidi level
+ */
+ BidiElement(Element parent, int start, int end, int level)
+ {
+ super(parent, new SimpleAttributeSet(), start, end);
+ addAttribute(StyleConstants.BidiLevel, new Integer(level));
+ }
+
+ /**
+ * Returns the name of the element.
+ *
+ * @return the name of the element
+ */
+ public String getName()
+ {
+ return BidiElementName;
+ }
+ }
+
+ /** A class whose methods delegate to the insert, remove and replace methods
+ * of this document which do not check for an installed DocumentFilter.
+ */
+ class Bypass extends DocumentFilter.FilterBypass
+ {
+
+ public Document getDocument()
+ {
+ return AbstractDocument.this;
+ }
+
+ public void insertString(int offset, String string, AttributeSet attr)
+ throws BadLocationException
+ {
+ AbstractDocument.this.insertStringImpl(offset, string, attr);
+ }
+
+ public void remove(int offset, int length)
+ throws BadLocationException
+ {
+ AbstractDocument.this.removeImpl(offset, length);
+ }
+
+ public void replace(int offset, int length, String string,
+ AttributeSet attrs)
+ throws BadLocationException
+ {
+ AbstractDocument.this.replaceImpl(offset, length, string, attrs);
+ }
+
+ }
+
+}