mirror of git://gcc.gnu.org/git/gcc.git
				
				
				
			
		
			
				
	
	
		
			2907 lines
		
	
	
		
			86 KiB
		
	
	
	
		
			Java
		
	
	
	
			
		
		
	
	
			2907 lines
		
	
	
		
			86 KiB
		
	
	
	
		
			Java
		
	
	
	
/* 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);
 | 
						|
    }
 | 
						|
 | 
						|
  }
 | 
						|
 | 
						|
}
 |