mirror of git://gcc.gnu.org/git/gcc.git
				
				
				
			
		
			
				
	
	
		
			2527 lines
		
	
	
		
			78 KiB
		
	
	
	
		
			Java
		
	
	
	
			
		
		
	
	
			2527 lines
		
	
	
		
			78 KiB
		
	
	
	
		
			Java
		
	
	
	
/* DefaultStyledDocument.java --
 | 
						|
   Copyright (C) 2004, 2005 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.Color;
 | 
						|
import java.awt.Font;
 | 
						|
import java.io.Serializable;
 | 
						|
import java.util.ArrayList;
 | 
						|
import java.util.Enumeration;
 | 
						|
import java.util.Iterator;
 | 
						|
import java.util.Stack;
 | 
						|
import java.util.Vector;
 | 
						|
 | 
						|
import javax.swing.event.ChangeEvent;
 | 
						|
import javax.swing.event.ChangeListener;
 | 
						|
import javax.swing.event.DocumentEvent;
 | 
						|
import javax.swing.event.UndoableEditEvent;
 | 
						|
import javax.swing.undo.AbstractUndoableEdit;
 | 
						|
import javax.swing.undo.UndoableEdit;
 | 
						|
 | 
						|
/**
 | 
						|
 * The default implementation of {@link StyledDocument}. The document is
 | 
						|
 * modeled as an {@link Element} tree, which has a {@link SectionElement} as
 | 
						|
 * single root, which has one or more {@link AbstractDocument.BranchElement}s
 | 
						|
 * as paragraph nodes and each paragraph node having one or more
 | 
						|
 * {@link AbstractDocument.LeafElement}s as content nodes.
 | 
						|
 *
 | 
						|
 * @author Michael Koch (konqueror@gmx.de)
 | 
						|
 * @author Roman Kennke (roman@kennke.org)
 | 
						|
 */
 | 
						|
public class DefaultStyledDocument extends AbstractDocument implements
 | 
						|
    StyledDocument
 | 
						|
{
 | 
						|
 | 
						|
  /**
 | 
						|
   * An {@link UndoableEdit} that can undo attribute changes to an element.
 | 
						|
   *
 | 
						|
   * @author Roman Kennke (kennke@aicas.com)
 | 
						|
   */
 | 
						|
  public static class AttributeUndoableEdit extends AbstractUndoableEdit
 | 
						|
  {
 | 
						|
    /**
 | 
						|
     * A copy of the old attributes.
 | 
						|
     */
 | 
						|
    protected AttributeSet copy;
 | 
						|
 | 
						|
    /**
 | 
						|
     * The new attributes.
 | 
						|
     */
 | 
						|
    protected AttributeSet newAttributes;
 | 
						|
 | 
						|
    /**
 | 
						|
     * If the new attributes replaced the old attributes or if they only were
 | 
						|
     * added to them.
 | 
						|
     */
 | 
						|
    protected boolean isReplacing;
 | 
						|
 | 
						|
    /**
 | 
						|
     * The element that has changed.
 | 
						|
     */
 | 
						|
    protected Element element;
 | 
						|
 | 
						|
    /**
 | 
						|
     * Creates a new <code>AttributeUndoableEdit</code>.
 | 
						|
     *
 | 
						|
     * @param el
 | 
						|
     *          the element that changes attributes
 | 
						|
     * @param newAtts
 | 
						|
     *          the new attributes
 | 
						|
     * @param replacing
 | 
						|
     *          if the new attributes replace the old or only append to them
 | 
						|
     */
 | 
						|
    public AttributeUndoableEdit(Element el, AttributeSet newAtts,
 | 
						|
                                 boolean replacing)
 | 
						|
    {
 | 
						|
      element = el;
 | 
						|
      newAttributes = newAtts;
 | 
						|
      isReplacing = replacing;
 | 
						|
      copy = el.getAttributes().copyAttributes();
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Undos the attribute change. The <code>copy</code> field is set as
 | 
						|
     * attributes on <code>element</code>.
 | 
						|
     */
 | 
						|
    public void undo()
 | 
						|
    {
 | 
						|
      super.undo();
 | 
						|
      AttributeSet atts = element.getAttributes();
 | 
						|
      if (atts instanceof MutableAttributeSet)
 | 
						|
        {
 | 
						|
          MutableAttributeSet mutable = (MutableAttributeSet) atts;
 | 
						|
          mutable.removeAttributes(atts);
 | 
						|
          mutable.addAttributes(copy);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Redos an attribute change. This adds <code>newAttributes</code> to the
 | 
						|
     * <code>element</code>'s attribute set, possibly clearing all attributes
 | 
						|
     * if <code>isReplacing</code> is true.
 | 
						|
     */
 | 
						|
    public void redo()
 | 
						|
    {
 | 
						|
      super.undo();
 | 
						|
      AttributeSet atts = element.getAttributes();
 | 
						|
      if (atts instanceof MutableAttributeSet)
 | 
						|
        {
 | 
						|
          MutableAttributeSet mutable = (MutableAttributeSet) atts;
 | 
						|
          if (isReplacing)
 | 
						|
            mutable.removeAttributes(atts);
 | 
						|
          mutable.addAttributes(newAttributes);
 | 
						|
        }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Carries specification information for new {@link Element}s that should be
 | 
						|
   * created in {@link ElementBuffer}. This allows the parsing process to be
 | 
						|
   * decoupled from the <code>Element</code> creation process.
 | 
						|
   */
 | 
						|
  public static class ElementSpec
 | 
						|
  {
 | 
						|
    /**
 | 
						|
     * This indicates a start tag. This is a possible value for {@link #getType}.
 | 
						|
     */
 | 
						|
    public static final short StartTagType = 1;
 | 
						|
 | 
						|
    /**
 | 
						|
     * This indicates an end tag. This is a possible value for {@link #getType}.
 | 
						|
     */
 | 
						|
    public static final short EndTagType = 2;
 | 
						|
 | 
						|
    /**
 | 
						|
     * This indicates a content element. This is a possible value for
 | 
						|
     * {@link #getType}.
 | 
						|
     */
 | 
						|
    public static final short ContentType = 3;
 | 
						|
 | 
						|
    /**
 | 
						|
     * This indicates that the data associated with this spec should be joined
 | 
						|
     * with what precedes it. This is a possible value for {@link #getDirection}.
 | 
						|
     */
 | 
						|
    public static final short JoinPreviousDirection = 4;
 | 
						|
 | 
						|
    /**
 | 
						|
     * This indicates that the data associated with this spec should be joined
 | 
						|
     * with what follows it. This is a possible value for {@link #getDirection}.
 | 
						|
     */
 | 
						|
    public static final short JoinNextDirection = 5;
 | 
						|
 | 
						|
    /**
 | 
						|
     * This indicates that the data associated with this spec should be used to
 | 
						|
     * create a new element. This is a possible value for {@link #getDirection}.
 | 
						|
     */
 | 
						|
    public static final short OriginateDirection = 6;
 | 
						|
 | 
						|
    /**
 | 
						|
     * This indicates that the data associated with this spec should be joined
 | 
						|
     * to the fractured element. This is a possible value for
 | 
						|
     * {@link #getDirection}.
 | 
						|
     */
 | 
						|
    public static final short JoinFractureDirection = 7;
 | 
						|
 | 
						|
    /**
 | 
						|
     * The type of the tag.
 | 
						|
     */
 | 
						|
    short type;
 | 
						|
 | 
						|
    /**
 | 
						|
     * The direction of the tag.
 | 
						|
     */
 | 
						|
    short direction;
 | 
						|
 | 
						|
    /**
 | 
						|
     * The offset of the content.
 | 
						|
     */
 | 
						|
    int offset;
 | 
						|
 | 
						|
    /**
 | 
						|
     * The length of the content.
 | 
						|
     */
 | 
						|
    int length;
 | 
						|
 | 
						|
    /**
 | 
						|
     * The actual content.
 | 
						|
     */
 | 
						|
    char[] content;
 | 
						|
 | 
						|
    /**
 | 
						|
     * The attributes for the tag.
 | 
						|
     */
 | 
						|
    AttributeSet attributes;
 | 
						|
 | 
						|
    /**
 | 
						|
     * Creates a new <code>ElementSpec</code> with no content, length or
 | 
						|
     * offset. This is most useful for start and end tags.
 | 
						|
     *
 | 
						|
     * @param a
 | 
						|
     *          the attributes for the element to be created
 | 
						|
     * @param type
 | 
						|
     *          the type of the tag
 | 
						|
     */
 | 
						|
    public ElementSpec(AttributeSet a, short type)
 | 
						|
    {
 | 
						|
      this(a, type, 0);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Creates a new <code>ElementSpec</code> that specifies the length but
 | 
						|
     * not the offset of an element. Such <code>ElementSpec</code>s are
 | 
						|
     * processed sequentially from a known starting point.
 | 
						|
     *
 | 
						|
     * @param a
 | 
						|
     *          the attributes for the element to be created
 | 
						|
     * @param type
 | 
						|
     *          the type of the tag
 | 
						|
     * @param len
 | 
						|
     *          the length of the element
 | 
						|
     */
 | 
						|
    public ElementSpec(AttributeSet a, short type, int len)
 | 
						|
    {
 | 
						|
      this(a, type, null, 0, len);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Creates a new <code>ElementSpec</code> with document content.
 | 
						|
     *
 | 
						|
     * @param a
 | 
						|
     *          the attributes for the element to be created
 | 
						|
     * @param type
 | 
						|
     *          the type of the tag
 | 
						|
     * @param txt
 | 
						|
     *          the actual content
 | 
						|
     * @param offs
 | 
						|
     *          the offset into the <code>txt</code> array
 | 
						|
     * @param len
 | 
						|
     *          the length of the element
 | 
						|
     */
 | 
						|
    public ElementSpec(AttributeSet a, short type, char[] txt, int offs, int len)
 | 
						|
    {
 | 
						|
      attributes = a;
 | 
						|
      this.type = type;
 | 
						|
      offset = offs;
 | 
						|
      length = len;
 | 
						|
      content = txt;
 | 
						|
      direction = OriginateDirection;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Sets the type of the element.
 | 
						|
     *
 | 
						|
     * @param type
 | 
						|
     *          the type of the element to be set
 | 
						|
     */
 | 
						|
    public void setType(short type)
 | 
						|
    {
 | 
						|
      this.type = type;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Returns the type of the element.
 | 
						|
     *
 | 
						|
     * @return the type of the element
 | 
						|
     */
 | 
						|
    public short getType()
 | 
						|
    {
 | 
						|
      return type;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Sets the direction of the element.
 | 
						|
     *
 | 
						|
     * @param dir
 | 
						|
     *          the direction of the element to be set
 | 
						|
     */
 | 
						|
    public void setDirection(short dir)
 | 
						|
    {
 | 
						|
      direction = dir;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Returns the direction of the element.
 | 
						|
     *
 | 
						|
     * @return the direction of the element
 | 
						|
     */
 | 
						|
    public short getDirection()
 | 
						|
    {
 | 
						|
      return direction;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Returns the attributes of the element.
 | 
						|
     *
 | 
						|
     * @return the attributes of the element
 | 
						|
     */
 | 
						|
    public AttributeSet getAttributes()
 | 
						|
    {
 | 
						|
      return attributes;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Returns the actual content of the element.
 | 
						|
     *
 | 
						|
     * @return the actual content of the element
 | 
						|
     */
 | 
						|
    public char[] getArray()
 | 
						|
    {
 | 
						|
      return content;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Returns the offset of the content.
 | 
						|
     *
 | 
						|
     * @return the offset of the content
 | 
						|
     */
 | 
						|
    public int getOffset()
 | 
						|
    {
 | 
						|
      return offset;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Returns the length of the content.
 | 
						|
     *
 | 
						|
     * @return the length of the content
 | 
						|
     */
 | 
						|
    public int getLength()
 | 
						|
    {
 | 
						|
      return length;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Returns a String representation of this <code>ElementSpec</code>
 | 
						|
     * describing the type, direction and length of this
 | 
						|
     * <code>ElementSpec</code>.
 | 
						|
     *
 | 
						|
     * @return a String representation of this <code>ElementSpec</code>
 | 
						|
     */
 | 
						|
    public String toString()
 | 
						|
    {
 | 
						|
      CPStringBuilder b = new CPStringBuilder();
 | 
						|
      switch (type)
 | 
						|
        {
 | 
						|
        case StartTagType:
 | 
						|
          b.append("StartTag");
 | 
						|
          break;
 | 
						|
        case EndTagType:
 | 
						|
          b.append("EndTag");
 | 
						|
          break;
 | 
						|
        case ContentType:
 | 
						|
          b.append("Content");
 | 
						|
          break;
 | 
						|
        default:
 | 
						|
          b.append("??");
 | 
						|
          break;
 | 
						|
        }
 | 
						|
 | 
						|
      b.append(':');
 | 
						|
 | 
						|
      switch (direction)
 | 
						|
        {
 | 
						|
        case JoinPreviousDirection:
 | 
						|
          b.append("JoinPrevious");
 | 
						|
          break;
 | 
						|
        case JoinNextDirection:
 | 
						|
          b.append("JoinNext");
 | 
						|
          break;
 | 
						|
        case OriginateDirection:
 | 
						|
          b.append("Originate");
 | 
						|
          break;
 | 
						|
        case JoinFractureDirection:
 | 
						|
          b.append("Fracture");
 | 
						|
          break;
 | 
						|
        default:
 | 
						|
          b.append("??");
 | 
						|
          break;
 | 
						|
        }
 | 
						|
 | 
						|
      b.append(':');
 | 
						|
      b.append(length);
 | 
						|
 | 
						|
      return b.toString();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Performs all <em>structural</code> changes to the <code>Element</code>
 | 
						|
   * hierarchy.  This class was implemented with much help from the document:
 | 
						|
   * http://java.sun.com/products/jfc/tsc/articles/text/element_buffer/index.html.
 | 
						|
   */
 | 
						|
  public class ElementBuffer implements Serializable
 | 
						|
  {
 | 
						|
    /**
 | 
						|
     * Instance of all editing information for an object in the Vector. This class
 | 
						|
     * is used to add information to the DocumentEvent associated with an
 | 
						|
     * insertion/removal/change as well as to store the changes that need to be
 | 
						|
     * made so they can be made all at the same (appropriate) time.
 | 
						|
     */
 | 
						|
    class Edit
 | 
						|
    {
 | 
						|
      /** The element to edit . */
 | 
						|
      Element e;
 | 
						|
 | 
						|
      /** The index of the change. */
 | 
						|
      int index;
 | 
						|
 | 
						|
      /** The removed elements. */
 | 
						|
      ArrayList removed = new ArrayList();
 | 
						|
 | 
						|
      /** The added elements. */
 | 
						|
      ArrayList added = new ArrayList();
 | 
						|
 | 
						|
      /**
 | 
						|
       * Indicates if this edit contains a fracture.
 | 
						|
       */
 | 
						|
      boolean isFracture;
 | 
						|
 | 
						|
      /**
 | 
						|
       * Creates a new Edit for the specified element at index i.
 | 
						|
       *
 | 
						|
       * @param el the element
 | 
						|
       * @param i the index
 | 
						|
       */
 | 
						|
      Edit(Element el, int i)
 | 
						|
      {
 | 
						|
        this(el, i, false);
 | 
						|
      }
 | 
						|
 | 
						|
      /**
 | 
						|
       * Creates a new Edit for the specified element at index i.
 | 
						|
       *
 | 
						|
       * @param el the element
 | 
						|
       * @param i the index
 | 
						|
       * @param frac if this is a fracture edit or not
 | 
						|
       */
 | 
						|
      Edit(Element el, int i, boolean frac)
 | 
						|
      {
 | 
						|
        e = el;
 | 
						|
        index = i;
 | 
						|
        isFracture = frac;
 | 
						|
      }
 | 
						|
 | 
						|
    }
 | 
						|
 | 
						|
    /** The serialization UID (compatible with JDK1.5). */
 | 
						|
    private static final long serialVersionUID = 1688745877691146623L;
 | 
						|
 | 
						|
    /** The root element of the hierarchy. */
 | 
						|
    private Element root;
 | 
						|
 | 
						|
    /** Holds the offset for structural changes. */
 | 
						|
    private int offset;
 | 
						|
 | 
						|
    /** Holds the end offset for structural changes. */
 | 
						|
    private int endOffset;
 | 
						|
 | 
						|
    /** Holds the length of structural changes. */
 | 
						|
    private int length;
 | 
						|
 | 
						|
    /** Holds the position of the change. */
 | 
						|
    private int pos;
 | 
						|
 | 
						|
    /**
 | 
						|
     * The parent of the fracture.
 | 
						|
     */
 | 
						|
    private Element fracturedParent;
 | 
						|
 | 
						|
    /**
 | 
						|
     * The fractured child.
 | 
						|
     */
 | 
						|
    private Element fracturedChild;
 | 
						|
 | 
						|
    /**
 | 
						|
     * Indicates if a fracture has been created.
 | 
						|
     */
 | 
						|
    private boolean createdFracture;
 | 
						|
 | 
						|
    /**
 | 
						|
     * The current position in the element tree. This is used for bulk inserts
 | 
						|
     * using ElementSpecs.
 | 
						|
     */
 | 
						|
    private Stack elementStack;
 | 
						|
 | 
						|
    private Edit[] insertPath;
 | 
						|
 | 
						|
    private boolean recreateLeafs;
 | 
						|
 | 
						|
    /**
 | 
						|
     * Vector that contains all the edits. Maybe replace by a HashMap.
 | 
						|
     */
 | 
						|
    private ArrayList edits;
 | 
						|
 | 
						|
    private boolean offsetLastIndex;
 | 
						|
    private boolean offsetLastIndexReplace;
 | 
						|
 | 
						|
    /**
 | 
						|
     * Creates a new <code>ElementBuffer</code> for the specified
 | 
						|
     * <code>root</code> element.
 | 
						|
     *
 | 
						|
     * @param root
 | 
						|
     *          the root element for this <code>ElementBuffer</code>
 | 
						|
     */
 | 
						|
    public ElementBuffer(Element root)
 | 
						|
    {
 | 
						|
      this.root = root;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Returns the root element of this <code>ElementBuffer</code>.
 | 
						|
     *
 | 
						|
     * @return the root element of this <code>ElementBuffer</code>
 | 
						|
     */
 | 
						|
    public Element getRootElement()
 | 
						|
    {
 | 
						|
      return root;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Removes the content. This method sets some internal parameters and
 | 
						|
     * delegates the work to {@link #removeUpdate}.
 | 
						|
     *
 | 
						|
     * @param offs
 | 
						|
     *          the offset from which content is remove
 | 
						|
     * @param len
 | 
						|
     *          the length of the removed content
 | 
						|
     * @param ev
 | 
						|
     *          the document event that records the changes
 | 
						|
     */
 | 
						|
    public void remove(int offs, int len, DefaultDocumentEvent ev)
 | 
						|
    {
 | 
						|
      prepareEdit(offs, len);
 | 
						|
      removeUpdate();
 | 
						|
      finishEdit(ev);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Updates the element structure of the document in response to removal of
 | 
						|
     * content. It removes the affected {@link Element}s from the document
 | 
						|
     * structure.
 | 
						|
     */
 | 
						|
    protected void removeUpdate()
 | 
						|
    {
 | 
						|
      removeElements(root, offset, endOffset);
 | 
						|
    }
 | 
						|
 | 
						|
    private boolean removeElements(Element elem, int rmOffs0, int rmOffs1)
 | 
						|
    {
 | 
						|
      boolean ret = false;
 | 
						|
      if (! elem.isLeaf())
 | 
						|
        {
 | 
						|
          // Update stack for changes.
 | 
						|
          int index0 = elem.getElementIndex(rmOffs0);
 | 
						|
          int index1 = elem.getElementIndex(rmOffs1);
 | 
						|
          elementStack.push(new Edit(elem, index0));
 | 
						|
          Edit ec = (Edit) elementStack.peek();
 | 
						|
 | 
						|
          // If the range is contained by one element,
 | 
						|
          // we just forward the request
 | 
						|
          if (index0 == index1)
 | 
						|
            {
 | 
						|
              Element child0 = elem.getElement(index0);
 | 
						|
              if(rmOffs0 <= child0.getStartOffset()
 | 
						|
                  && rmOffs1 >= child0.getEndOffset())
 | 
						|
                {
 | 
						|
                  // Element totally removed.
 | 
						|
                  ec.removed.add(child0);
 | 
						|
                }
 | 
						|
              else if (removeElements(child0, rmOffs0, rmOffs1))
 | 
						|
                {
 | 
						|
                  ec.removed.add(child0);
 | 
						|
                }
 | 
						|
            }
 | 
						|
          else
 | 
						|
            {
 | 
						|
              // The removal range spans elements.  If we can join
 | 
						|
              // the two endpoints, do it.  Otherwise we remove the
 | 
						|
              // interior and forward to the endpoints.
 | 
						|
              Element child0 = elem.getElement(index0);
 | 
						|
              Element child1 = elem.getElement(index1);
 | 
						|
              boolean containsOffs1 = (rmOffs1 < elem.getEndOffset());
 | 
						|
          if (containsOffs1 && canJoin(child0, child1))
 | 
						|
            {
 | 
						|
              // Remove and join.
 | 
						|
              for (int i = index0; i <= index1; i++)
 | 
						|
                {
 | 
						|
                  ec.removed.add(elem.getElement(i));
 | 
						|
                }
 | 
						|
              Element e = join(elem, child0, child1, rmOffs0, rmOffs1);
 | 
						|
              ec.added.add(e);
 | 
						|
            }
 | 
						|
          else
 | 
						|
            {
 | 
						|
              // Remove interior and forward.
 | 
						|
              int rmIndex0 = index0 + 1;
 | 
						|
              int rmIndex1 = index1 - 1;
 | 
						|
              if (child0.getStartOffset() == rmOffs0
 | 
						|
                  || (index0 == 0 && child0.getStartOffset() > rmOffs0
 | 
						|
                      && child0.getEndOffset() <= rmOffs1))
 | 
						|
                {
 | 
						|
                  // Start element completely consumed.
 | 
						|
                  child0 = null;
 | 
						|
                  rmIndex0 = index0;
 | 
						|
                }
 | 
						|
              if (! containsOffs1)
 | 
						|
                {
 | 
						|
                  child1 = null;
 | 
						|
                  rmIndex1++;
 | 
						|
              }
 | 
						|
              else if (child1.getStartOffset() == rmOffs1)
 | 
						|
                {
 | 
						|
                  // End element not touched.
 | 
						|
                  child1 = null;
 | 
						|
                }
 | 
						|
              if (rmIndex0 <= rmIndex1)
 | 
						|
                {
 | 
						|
                  ec.index = rmIndex0;
 | 
						|
                }
 | 
						|
              for (int i = rmIndex0; i <= rmIndex1; i++)
 | 
						|
                {
 | 
						|
                  ec.removed.add(elem.getElement(i));
 | 
						|
                }
 | 
						|
              if (child0 != null)
 | 
						|
                {
 | 
						|
                  if(removeElements(child0, rmOffs0, rmOffs1))
 | 
						|
                    {
 | 
						|
                      ec.removed.add(0, child0);
 | 
						|
                      ec.index = index0;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
              if (child1 != null)
 | 
						|
                {
 | 
						|
                  if(removeElements(child1, rmOffs0, rmOffs1))
 | 
						|
                    {
 | 
						|
                      ec.removed.add(child1);
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
            }
 | 
						|
 | 
						|
          // Perform changes.
 | 
						|
          pop();
 | 
						|
 | 
						|
          // Return true if we no longer have any children.
 | 
						|
          if(elem.getElementCount() == (ec.removed.size() - ec.added.size()))
 | 
						|
            ret = true;
 | 
						|
        }
 | 
						|
      return ret;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Creates a document in response to a call to
 | 
						|
     * {@link DefaultStyledDocument#create(ElementSpec[])}.
 | 
						|
     *
 | 
						|
     * @param len the length of the inserted text
 | 
						|
     * @param data the specs for the elements
 | 
						|
     * @param ev the document event
 | 
						|
     */
 | 
						|
    void create(int len, ElementSpec[] data, DefaultDocumentEvent ev)
 | 
						|
    {
 | 
						|
      prepareEdit(offset, len);
 | 
						|
      Element el = root;
 | 
						|
      int index = el.getElementIndex(0);
 | 
						|
      while (! el.isLeaf())
 | 
						|
        {
 | 
						|
          Element child = el.getElement(index);
 | 
						|
          Edit edit = new Edit(el, index, false);
 | 
						|
          elementStack.push(edit);
 | 
						|
          el = child;
 | 
						|
          index = el.getElementIndex(0);
 | 
						|
        }
 | 
						|
      Edit ed = (Edit) elementStack.peek();
 | 
						|
      Element child = ed.e.getElement(ed.index);
 | 
						|
      ed.added.add(createLeafElement(ed.e, child.getAttributes(), getLength(),
 | 
						|
                                     child.getEndOffset()));
 | 
						|
      ed.removed.add(child);
 | 
						|
      while (elementStack.size() > 1)
 | 
						|
        pop();
 | 
						|
      int n = data.length;
 | 
						|
 | 
						|
      // Reset root element's attributes.
 | 
						|
      AttributeSet newAtts = null;
 | 
						|
      if (n > 0 && data[0].getType() == ElementSpec.StartTagType)
 | 
						|
        newAtts = data[0].getAttributes();
 | 
						|
      if (newAtts == null)
 | 
						|
        newAtts = SimpleAttributeSet.EMPTY;
 | 
						|
      MutableAttributeSet mAtts = (MutableAttributeSet) root.getAttributes();
 | 
						|
      ev.addEdit(new AttributeUndoableEdit(root, newAtts, true));
 | 
						|
      mAtts.removeAttributes(mAtts);
 | 
						|
      mAtts.addAttributes(newAtts);
 | 
						|
 | 
						|
      // Insert the specified elements.
 | 
						|
      for (int i = 1; i < n; i++)
 | 
						|
        insertElement(data[i]);
 | 
						|
 | 
						|
      // Pop remaining stack.
 | 
						|
      while (elementStack.size() > 0)
 | 
						|
        pop();
 | 
						|
 | 
						|
      finishEdit(ev);
 | 
						|
    }
 | 
						|
 | 
						|
    private boolean canJoin(Element e0, Element e1)
 | 
						|
    {
 | 
						|
      boolean ret = false;
 | 
						|
      if ((e0 != null) && (e1 != null))
 | 
						|
        {
 | 
						|
          // Don't join a leaf to a branch.
 | 
						|
          boolean isLeaf0 = e0.isLeaf();
 | 
						|
          boolean isLeaf1 = e1.isLeaf();
 | 
						|
          if(isLeaf0 == isLeaf1)
 | 
						|
            {
 | 
						|
              if (isLeaf0)
 | 
						|
                {
 | 
						|
                  // Only join leaves if the attributes match, otherwise
 | 
						|
                  // style information will be lost.
 | 
						|
                  ret = e0.getAttributes().isEqual(e1.getAttributes());
 | 
						|
                }
 | 
						|
              else
 | 
						|
                {
 | 
						|
                  // Only join non-leafs if the names are equal. This may result
 | 
						|
                  // in loss of style information, but this is typically
 | 
						|
                  // acceptable for non-leafs.
 | 
						|
                  String name0 = e0.getName();
 | 
						|
                  String name1 = e1.getName();
 | 
						|
                  if (name0 != null)
 | 
						|
                    ret = name0.equals(name1);
 | 
						|
                  else if (name1 != null)
 | 
						|
                    ret = name1.equals(name0);
 | 
						|
                  else // Both names null.
 | 
						|
                    ret = true;
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
      return ret;
 | 
						|
    }
 | 
						|
 | 
						|
    private Element join(Element p, Element left, Element right, int rmOffs0,
 | 
						|
                         int rmOffs1)
 | 
						|
    {
 | 
						|
      Element joined = null;
 | 
						|
      if (left.isLeaf() && right.isLeaf())
 | 
						|
        {
 | 
						|
          joined = createLeafElement(p, left.getAttributes(),
 | 
						|
                                     left.getStartOffset(),
 | 
						|
                                     right.getEndOffset());
 | 
						|
        }
 | 
						|
      else if ((! left.isLeaf()) && (! right.isLeaf()))
 | 
						|
        {
 | 
						|
          // Join two branch elements.  This copies the children before
 | 
						|
          // the removal range on the left element, and after the removal
 | 
						|
          // range on the right element.  The two elements on the edge
 | 
						|
          // are joined if possible and needed.
 | 
						|
          joined = createBranchElement(p, left.getAttributes());
 | 
						|
          int ljIndex = left.getElementIndex(rmOffs0);
 | 
						|
          int rjIndex = right.getElementIndex(rmOffs1);
 | 
						|
          Element lj = left.getElement(ljIndex);
 | 
						|
          if (lj.getStartOffset() >= rmOffs0)
 | 
						|
            {
 | 
						|
              lj = null;
 | 
						|
            }
 | 
						|
          Element rj = right.getElement(rjIndex);
 | 
						|
          if (rj.getStartOffset() == rmOffs1)
 | 
						|
            {
 | 
						|
              rj = null;
 | 
						|
            }
 | 
						|
          ArrayList children = new ArrayList();
 | 
						|
          // Transfer the left.
 | 
						|
          for (int i = 0; i < ljIndex; i++)
 | 
						|
            {
 | 
						|
              children.add(clone(joined, left.getElement(i)));
 | 
						|
            }
 | 
						|
 | 
						|
          // Transfer the join/middle.
 | 
						|
          if (canJoin(lj, rj))
 | 
						|
            {
 | 
						|
              Element e = join(joined, lj, rj, rmOffs0, rmOffs1);
 | 
						|
              children.add(e);
 | 
						|
            }
 | 
						|
          else
 | 
						|
            {
 | 
						|
              if (lj != null)
 | 
						|
                {
 | 
						|
                  children.add(cloneAsNecessary(joined, lj, rmOffs0, rmOffs1));
 | 
						|
                }
 | 
						|
              if (rj != null)
 | 
						|
                {
 | 
						|
                  children.add(cloneAsNecessary(joined, rj, rmOffs0, rmOffs1));
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
          // Transfer the right.
 | 
						|
          int n = right.getElementCount();
 | 
						|
          for (int i = (rj == null) ? rjIndex : rjIndex + 1; i < n; i++)
 | 
						|
            {
 | 
						|
              children.add(clone(joined, right.getElement(i)));
 | 
						|
            }
 | 
						|
 | 
						|
          // Install the children.
 | 
						|
          Element[] c = new Element[children.size()];
 | 
						|
          c = (Element[]) children.toArray(c);
 | 
						|
          ((BranchElement) joined).replace(0, 0, c);
 | 
						|
        }
 | 
						|
      else
 | 
						|
        {
 | 
						|
          assert false : "Must not happen";
 | 
						|
        }
 | 
						|
      return joined;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Performs the actual work for {@link #change}. The elements at the
 | 
						|
     * interval boundaries are split up (if necessary) so that the interval
 | 
						|
     * boundaries are located at element boundaries.
 | 
						|
     */
 | 
						|
    protected void changeUpdate()
 | 
						|
    {
 | 
						|
      boolean didEnd = split(offset, length);
 | 
						|
      if (! didEnd)
 | 
						|
        {
 | 
						|
          // need to do the other end
 | 
						|
          while (elementStack.size() != 0)
 | 
						|
            {
 | 
						|
              pop();
 | 
						|
            }
 | 
						|
          split(offset + length, 0);
 | 
						|
        }
 | 
						|
      while (elementStack.size() != 0)
 | 
						|
        {
 | 
						|
          pop();
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Modifies the element structure so that the specified interval starts and
 | 
						|
     * ends at an element boundary. Content and paragraph elements are split and
 | 
						|
     * created as necessary. This also updates the
 | 
						|
     * <code>DefaultDocumentEvent</code> to reflect the structural changes.
 | 
						|
     * The bulk work is delegated to {@link #changeUpdate()}.
 | 
						|
     *
 | 
						|
     * @param offset
 | 
						|
     *          the start index of the interval to be changed
 | 
						|
     * @param length
 | 
						|
     *          the length of the interval to be changed
 | 
						|
     * @param ev
 | 
						|
     *          the <code>DefaultDocumentEvent</code> describing the change
 | 
						|
     */
 | 
						|
    public void change(int offset, int length, DefaultDocumentEvent ev)
 | 
						|
    {
 | 
						|
      prepareEdit(offset, length);
 | 
						|
      changeUpdate();
 | 
						|
      finishEdit(ev);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Creates and returns a deep clone of the specified <code>clonee</code>
 | 
						|
     * with the specified parent as new parent.
 | 
						|
     *
 | 
						|
     * This method can only clone direct instances of {@link BranchElement}
 | 
						|
     * or {@link LeafElement}.
 | 
						|
     *
 | 
						|
     * @param parent the new parent
 | 
						|
     * @param clonee the element to be cloned
 | 
						|
     *
 | 
						|
     * @return the cloned element with the new parent
 | 
						|
     */
 | 
						|
    public Element clone(Element parent, Element clonee)
 | 
						|
    {
 | 
						|
      Element clone = clonee;
 | 
						|
      // We can only handle AbstractElements here.
 | 
						|
      if (clonee instanceof BranchElement)
 | 
						|
        {
 | 
						|
          BranchElement branchEl = (BranchElement) clonee;
 | 
						|
          BranchElement branchClone =
 | 
						|
            new BranchElement(parent, branchEl.getAttributes());
 | 
						|
          // Also clone all of the children.
 | 
						|
          int numChildren = branchClone.getElementCount();
 | 
						|
          Element[] cloneChildren = new Element[numChildren];
 | 
						|
          for (int i = 0; i < numChildren; ++i)
 | 
						|
            {
 | 
						|
              cloneChildren[i] = clone(branchClone,
 | 
						|
                                       branchClone.getElement(i));
 | 
						|
            }
 | 
						|
          branchClone.replace(0, 0, cloneChildren);
 | 
						|
          clone = branchClone;
 | 
						|
        }
 | 
						|
      else if (clonee instanceof LeafElement)
 | 
						|
        {
 | 
						|
          clone = new LeafElement(parent, clonee.getAttributes(),
 | 
						|
                                  clonee.getStartOffset(),
 | 
						|
                                  clonee.getEndOffset());
 | 
						|
        }
 | 
						|
      return clone;
 | 
						|
    }
 | 
						|
 | 
						|
    private Element cloneAsNecessary(Element parent, Element clonee,
 | 
						|
                                     int rmOffs0, int rmOffs1)
 | 
						|
    {
 | 
						|
      Element cloned;
 | 
						|
      if (clonee.isLeaf())
 | 
						|
        {
 | 
						|
          cloned = createLeafElement(parent, clonee.getAttributes(),
 | 
						|
                                     clonee.getStartOffset(),
 | 
						|
                                     clonee.getEndOffset());
 | 
						|
        }
 | 
						|
      else
 | 
						|
        {
 | 
						|
          Element e = createBranchElement(parent, clonee.getAttributes());
 | 
						|
          int n = clonee.getElementCount();
 | 
						|
          ArrayList childrenList = new ArrayList(n);
 | 
						|
          for (int i = 0; i < n; i++)
 | 
						|
            {
 | 
						|
              Element elem = clonee.getElement(i);
 | 
						|
              if (elem.getStartOffset() < rmOffs0
 | 
						|
                  || elem.getEndOffset() > rmOffs1)
 | 
						|
                {
 | 
						|
                  childrenList.add(cloneAsNecessary(e, elem, rmOffs0,
 | 
						|
                                                    rmOffs1));
 | 
						|
                }
 | 
						|
            }
 | 
						|
          Element[] children = new Element[childrenList.size()];
 | 
						|
          children = (Element[]) childrenList.toArray(children);
 | 
						|
          ((BranchElement) e).replace(0, 0, children);
 | 
						|
          cloned = e;
 | 
						|
        }
 | 
						|
      return cloned;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Inserts new <code>Element</code> in the document at the specified
 | 
						|
     * position. Most of the work is done by {@link #insertUpdate}, after some
 | 
						|
     * fields have been prepared for it.
 | 
						|
     *
 | 
						|
     * @param offset
 | 
						|
     *          the location in the document at which the content is inserted
 | 
						|
     * @param length
 | 
						|
     *          the length of the inserted content
 | 
						|
     * @param data
 | 
						|
     *          the element specifications for the content to be inserted
 | 
						|
     * @param ev
 | 
						|
     *          the document event that is updated to reflect the structural
 | 
						|
     *          changes
 | 
						|
     */
 | 
						|
    public void insert(int offset, int length, ElementSpec[] data,
 | 
						|
                       DefaultDocumentEvent ev)
 | 
						|
    {
 | 
						|
      if (length > 0)
 | 
						|
        {
 | 
						|
          prepareEdit(offset, length);
 | 
						|
          insertUpdate(data);
 | 
						|
          finishEdit(ev);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Prepares the state of this object for performing an insert.
 | 
						|
     *
 | 
						|
     * @param offset the offset at which is inserted
 | 
						|
     * @param length the length of the inserted region
 | 
						|
     */
 | 
						|
    private void prepareEdit(int offset, int length)
 | 
						|
    {
 | 
						|
      this.offset = offset;
 | 
						|
      this.pos = offset;
 | 
						|
      this.endOffset = offset + length;
 | 
						|
      this.length = length;
 | 
						|
 | 
						|
      if (edits == null)
 | 
						|
        edits = new ArrayList();
 | 
						|
      else
 | 
						|
        edits.clear();
 | 
						|
 | 
						|
      if (elementStack == null)
 | 
						|
        elementStack = new Stack();
 | 
						|
      else
 | 
						|
        elementStack.clear();
 | 
						|
 | 
						|
      fracturedParent = null;
 | 
						|
      fracturedChild = null;
 | 
						|
      offsetLastIndex = false;
 | 
						|
      offsetLastIndexReplace = false;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Finishes an insert. This applies all changes and updates
 | 
						|
     * the DocumentEvent.
 | 
						|
     *
 | 
						|
     * @param ev the document event
 | 
						|
     */
 | 
						|
    private void finishEdit(DefaultDocumentEvent ev)
 | 
						|
    {
 | 
						|
      // This for loop applies all the changes that were made and updates the
 | 
						|
      // DocumentEvent.
 | 
						|
      for (Iterator i = edits.iterator(); i.hasNext();)
 | 
						|
        {
 | 
						|
          Edit edits = (Edit) i.next();
 | 
						|
          Element[] removed = new Element[edits.removed.size()];
 | 
						|
          removed = (Element[]) edits.removed.toArray(removed);
 | 
						|
          Element[] added = new Element[edits.added.size()];
 | 
						|
          added = (Element[]) edits.added.toArray(added);
 | 
						|
          int index = edits.index;
 | 
						|
          BranchElement parent = (BranchElement) edits.e;
 | 
						|
          parent.replace(index, removed.length, added);
 | 
						|
          ElementEdit ee = new ElementEdit(parent, index, removed, added);
 | 
						|
          ev.addEdit(ee);
 | 
						|
        }
 | 
						|
      edits.clear();
 | 
						|
      elementStack.clear();
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Inserts new content.
 | 
						|
     *
 | 
						|
     * @param data the element specifications for the elements to be inserted
 | 
						|
     */
 | 
						|
    protected void insertUpdate(ElementSpec[] data)
 | 
						|
    {
 | 
						|
      // Push the current path to the stack.
 | 
						|
      Element current = root;
 | 
						|
      int index = current.getElementIndex(offset);
 | 
						|
      while (! current.isLeaf())
 | 
						|
        {
 | 
						|
          Element child = current.getElement(index);
 | 
						|
          int editIndex = child.isLeaf() ? index : index + 1;
 | 
						|
          Edit edit = new Edit(current, editIndex);
 | 
						|
          elementStack.push(edit);
 | 
						|
          current = child;
 | 
						|
          index = current.getElementIndex(offset);
 | 
						|
        }
 | 
						|
 | 
						|
      // Create a copy of the original path.
 | 
						|
      insertPath = new Edit[elementStack.size()];
 | 
						|
      insertPath = (Edit[]) elementStack.toArray(insertPath);
 | 
						|
 | 
						|
      // No fracture yet.
 | 
						|
      createdFracture = false;
 | 
						|
 | 
						|
      // Insert first content tag.
 | 
						|
      int i = 0;
 | 
						|
      recreateLeafs = false;
 | 
						|
      int type = data[0].getType();
 | 
						|
      if (type == ElementSpec.ContentType)
 | 
						|
        {
 | 
						|
          // If the first tag is content we must treat it separately to allow
 | 
						|
          // for joining properly to previous Elements and to ensure that
 | 
						|
          // no extra LeafElements are erroneously inserted.
 | 
						|
          insertFirstContentTag(data);
 | 
						|
          pos += data[0].length;
 | 
						|
          i = 1;
 | 
						|
        }
 | 
						|
      else
 | 
						|
        {
 | 
						|
          createFracture(data);
 | 
						|
          i = 0;
 | 
						|
        }
 | 
						|
 | 
						|
      // Handle each ElementSpec individually.
 | 
						|
      for (; i < data.length; i++)
 | 
						|
        {
 | 
						|
          insertElement(data[i]);
 | 
						|
        }
 | 
						|
 | 
						|
      // Fracture if we haven't done yet.
 | 
						|
      if (! createdFracture)
 | 
						|
        fracture(-1);
 | 
						|
 | 
						|
      // Pop the remaining stack.
 | 
						|
      while (elementStack.size() != 0)
 | 
						|
        pop();
 | 
						|
 | 
						|
      // Offset last index if necessary.
 | 
						|
      if (offsetLastIndex && offsetLastIndexReplace)
 | 
						|
        insertPath[insertPath.length - 1].index++;
 | 
						|
 | 
						|
      // Make sure we havea an Edit for each path item that has a change.
 | 
						|
      for (int p = insertPath.length - 1; p >= 0; p--)
 | 
						|
        {
 | 
						|
          Edit edit = insertPath[p];
 | 
						|
          if (edit.e == fracturedParent)
 | 
						|
            edit.added.add(fracturedChild);
 | 
						|
          if ((edit.added.size() > 0 || edit.removed.size() > 0)
 | 
						|
              && ! edits.contains(edit))
 | 
						|
            edits.add(edit);
 | 
						|
        }
 | 
						|
 | 
						|
      // Remove element that would be created by an insert at 0 with
 | 
						|
      // an initial end tag.
 | 
						|
      if (offset == 0 && fracturedParent != null
 | 
						|
          && data[0].getType() == ElementSpec.EndTagType)
 | 
						|
        {
 | 
						|
          int p;
 | 
						|
          for (p = 0;
 | 
						|
               p < data.length && data[p].getType() == ElementSpec.EndTagType;
 | 
						|
               p++)
 | 
						|
            ;
 | 
						|
 | 
						|
          Edit edit = insertPath[insertPath.length - p - 1];
 | 
						|
          edit.index--;
 | 
						|
          edit.removed.add(0, edit.e.getElement(edit.index));
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    private void pop()
 | 
						|
    {
 | 
						|
      Edit edit = (Edit) elementStack.peek();
 | 
						|
      elementStack.pop();
 | 
						|
      if ((edit.added.size() > 0) || (edit.removed.size() > 0))
 | 
						|
        {
 | 
						|
          edits.add(edit);
 | 
						|
        }
 | 
						|
      else if (! elementStack.isEmpty())
 | 
						|
        {
 | 
						|
          Element e = edit.e;
 | 
						|
          if (e.getElementCount() == 0)
 | 
						|
            {
 | 
						|
              // If we pushed a branch element that didn't get
 | 
						|
              // used, make sure its not marked as having been added.
 | 
						|
              edit = (Edit) elementStack.peek();
 | 
						|
              edit.added.remove(e);
 | 
						|
          }
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    private void insertElement(ElementSpec spec)
 | 
						|
    {
 | 
						|
      if (elementStack.isEmpty())
 | 
						|
        return;
 | 
						|
 | 
						|
      Edit edit = (Edit) elementStack.peek();
 | 
						|
      switch (spec.getType())
 | 
						|
        {
 | 
						|
        case ElementSpec.StartTagType:
 | 
						|
          switch (spec.getDirection())
 | 
						|
            {
 | 
						|
            case ElementSpec.JoinFractureDirection:
 | 
						|
              // Fracture the tree and ensure the appropriate element
 | 
						|
              // is on top of the stack.
 | 
						|
              if (! createdFracture)
 | 
						|
                {
 | 
						|
                  fracture(elementStack.size() - 1);
 | 
						|
                }
 | 
						|
              if (! edit.isFracture)
 | 
						|
                {
 | 
						|
                  // If the parent isn't a fracture, then the fracture is
 | 
						|
                  // in fracturedChild.
 | 
						|
                  Edit newEdit = new Edit(fracturedChild, 0, true);
 | 
						|
                  elementStack.push(newEdit);
 | 
						|
                }
 | 
						|
              else
 | 
						|
                {
 | 
						|
                  // Otherwise use the parent's first child.
 | 
						|
                  Element el = edit.e.getElement(0);
 | 
						|
                  Edit newEdit = new Edit(el, 0, true);
 | 
						|
                  elementStack.push(newEdit);
 | 
						|
                }
 | 
						|
              break;
 | 
						|
            case ElementSpec.JoinNextDirection:
 | 
						|
              // Push the next paragraph element onto the stack so
 | 
						|
              // future insertions are added to it.
 | 
						|
              Element parent = edit.e.getElement(edit.index);
 | 
						|
              if (parent.isLeaf())
 | 
						|
                {
 | 
						|
                  if (edit.index + 1 < edit.e.getElementCount())
 | 
						|
                    parent = edit.e.getElement(edit.index + 1);
 | 
						|
                  else
 | 
						|
                    assert false; // Must not happen.
 | 
						|
                }
 | 
						|
              elementStack.push(new Edit(parent, 0, true));
 | 
						|
              break;
 | 
						|
            default:
 | 
						|
              Element branch = createBranchElement(edit.e,
 | 
						|
                                                   spec.getAttributes());
 | 
						|
              edit.added.add(branch);
 | 
						|
              elementStack.push(new Edit(branch, 0));
 | 
						|
              break;
 | 
						|
            }
 | 
						|
          break;
 | 
						|
        case ElementSpec.EndTagType:
 | 
						|
          pop();
 | 
						|
          break;
 | 
						|
        case ElementSpec.ContentType:
 | 
						|
          insertContentTag(spec, edit);
 | 
						|
          break;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Inserts the first tag into the document.
 | 
						|
     *
 | 
						|
     * @param data -
 | 
						|
     *          the data to be inserted.
 | 
						|
     */
 | 
						|
    private void insertFirstContentTag(ElementSpec[] data)
 | 
						|
    {
 | 
						|
      ElementSpec first = data[0];
 | 
						|
      Edit edit = (Edit) elementStack.peek();
 | 
						|
      Element current = edit.e.getElement(edit.index);
 | 
						|
      int firstEndOffset = offset + first.length;
 | 
						|
      boolean onlyContent = data.length == 1;
 | 
						|
      switch (first.getDirection())
 | 
						|
        {
 | 
						|
        case ElementSpec.JoinPreviousDirection:
 | 
						|
          if (current.getEndOffset() != firstEndOffset && ! onlyContent)
 | 
						|
            {
 | 
						|
              Element newEl1 = createLeafElement(edit.e,
 | 
						|
                                                 current.getAttributes(),
 | 
						|
                                                 current.getStartOffset(),
 | 
						|
                                                 firstEndOffset);
 | 
						|
              edit.added.add(newEl1);
 | 
						|
              edit.removed.add(current);
 | 
						|
              if (current.getEndOffset() != endOffset)
 | 
						|
                recreateLeafs = true;
 | 
						|
              else
 | 
						|
                offsetLastIndex = true;
 | 
						|
            }
 | 
						|
          else
 | 
						|
            {
 | 
						|
              offsetLastIndex = true;
 | 
						|
              offsetLastIndexReplace = true;
 | 
						|
            }
 | 
						|
          break;
 | 
						|
        case ElementSpec.JoinNextDirection:
 | 
						|
          if (offset != 0)
 | 
						|
            {
 | 
						|
              Element newEl1 = createLeafElement(edit.e,
 | 
						|
                                                 current.getAttributes(),
 | 
						|
                                                 current.getStartOffset(),
 | 
						|
                                                 offset);
 | 
						|
              edit.added.add(newEl1);
 | 
						|
              Element next = edit.e.getElement(edit.index + 1);
 | 
						|
              if (onlyContent)
 | 
						|
                newEl1 = createLeafElement(edit.e, next.getAttributes(),
 | 
						|
                                           offset, next.getEndOffset());
 | 
						|
              else
 | 
						|
                {
 | 
						|
                  newEl1 = createLeafElement(edit.e, next.getAttributes(),
 | 
						|
                                             offset, firstEndOffset);
 | 
						|
                }
 | 
						|
              edit.added.add(newEl1);
 | 
						|
              edit.removed.add(current);
 | 
						|
              edit.removed.add(next);
 | 
						|
            }
 | 
						|
          break;
 | 
						|
        default: // OriginateDirection.
 | 
						|
          if (current.getStartOffset() != offset)
 | 
						|
            {
 | 
						|
              Element newEl = createLeafElement(edit.e,
 | 
						|
                                                current.getAttributes(),
 | 
						|
                                                current.getStartOffset(),
 | 
						|
                                                offset);
 | 
						|
              edit.added.add(newEl);
 | 
						|
            }
 | 
						|
          edit.removed.add(current);
 | 
						|
          Element newEl1 = createLeafElement(edit.e, first.getAttributes(),
 | 
						|
                                             offset, firstEndOffset);
 | 
						|
          edit.added.add(newEl1);
 | 
						|
          if (current.getEndOffset() != endOffset)
 | 
						|
            recreateLeafs = true;
 | 
						|
          else
 | 
						|
            offsetLastIndex = true;
 | 
						|
          break;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Inserts a content element into the document structure.
 | 
						|
     *
 | 
						|
     * @param tag -
 | 
						|
     *          the element spec
 | 
						|
     */
 | 
						|
    private void insertContentTag(ElementSpec tag, Edit edit)
 | 
						|
    {
 | 
						|
      int len = tag.getLength();
 | 
						|
      int dir = tag.getDirection();
 | 
						|
      if (dir == ElementSpec.JoinNextDirection)
 | 
						|
        {
 | 
						|
          if (! edit.isFracture)
 | 
						|
            {
 | 
						|
              Element first = null;
 | 
						|
              if (insertPath != null)
 | 
						|
                {
 | 
						|
                  for (int p = insertPath.length - 1; p >= 0; p--)
 | 
						|
                    {
 | 
						|
                      if (insertPath[p] == edit)
 | 
						|
                        {
 | 
						|
                          if (p != insertPath.length - 1)
 | 
						|
                            first = edit.e.getElement(edit.index);
 | 
						|
                          break;
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }
 | 
						|
              if (first == null)
 | 
						|
                first = edit.e.getElement(edit.index + 1);
 | 
						|
              Element leaf = createLeafElement(edit.e, first.getAttributes(),
 | 
						|
                                               pos, first.getEndOffset());
 | 
						|
              edit.added.add(leaf);
 | 
						|
              edit.removed.add(first);
 | 
						|
            }
 | 
						|
          else
 | 
						|
            {
 | 
						|
              Element first = edit.e.getElement(0);
 | 
						|
              Element leaf = createLeafElement(edit.e, first.getAttributes(),
 | 
						|
                                               pos, first.getEndOffset());
 | 
						|
              edit.added.add(leaf);
 | 
						|
              edit.removed.add(first);
 | 
						|
            }
 | 
						|
        }
 | 
						|
      else
 | 
						|
        {
 | 
						|
          Element leaf = createLeafElement(edit.e, tag.getAttributes(), pos,
 | 
						|
                                           pos + len);
 | 
						|
          edit.added.add(leaf);
 | 
						|
        }
 | 
						|
 | 
						|
      pos += len;
 | 
						|
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * This method fractures bottomost leaf in the elementStack. This
 | 
						|
     * happens when the first inserted tag is not content.
 | 
						|
     *
 | 
						|
     * @param data
 | 
						|
     *          the ElementSpecs used for the entire insertion
 | 
						|
     */
 | 
						|
    private void createFracture(ElementSpec[] data)
 | 
						|
    {
 | 
						|
      Edit edit = (Edit) elementStack.peek();
 | 
						|
      Element child = edit.e.getElement(edit.index);
 | 
						|
      if (offset != 0)
 | 
						|
        {
 | 
						|
          Element newChild = createLeafElement(edit.e, child.getAttributes(),
 | 
						|
                                               child.getStartOffset(), offset);
 | 
						|
          edit.added.add(newChild);
 | 
						|
        }
 | 
						|
      edit.removed.add(child);
 | 
						|
      if (child.getEndOffset() != endOffset)
 | 
						|
        recreateLeafs = true;
 | 
						|
      else
 | 
						|
        offsetLastIndex = true;
 | 
						|
    }
 | 
						|
 | 
						|
    private void fracture(int depth)
 | 
						|
    {
 | 
						|
      int len = insertPath.length;
 | 
						|
      int lastIndex = -1;
 | 
						|
      boolean recreate = recreateLeafs;
 | 
						|
      Edit lastEdit = insertPath[len - 1];
 | 
						|
      boolean childChanged = lastEdit.index + 1 < lastEdit.e.getElementCount();
 | 
						|
      int deepestChangedIndex = recreate ? len : - 1;
 | 
						|
      int lastChangedIndex = len - 1;
 | 
						|
      createdFracture = true;
 | 
						|
      for (int i = len - 2; i >= 0; i--)
 | 
						|
        {
 | 
						|
          Edit edit = insertPath[i];
 | 
						|
          if (edit.added.size() > 0 || i == depth)
 | 
						|
            {
 | 
						|
              lastIndex = i;
 | 
						|
              if (! recreate && childChanged)
 | 
						|
                {
 | 
						|
                  recreate = true;
 | 
						|
                  if (deepestChangedIndex == -1)
 | 
						|
                    deepestChangedIndex = lastChangedIndex + 1;
 | 
						|
                }
 | 
						|
            }
 | 
						|
          if (! childChanged && edit.index < edit.e.getElementCount())
 | 
						|
            {
 | 
						|
              childChanged = true;
 | 
						|
              lastChangedIndex = i;
 | 
						|
            }
 | 
						|
        }
 | 
						|
      if (recreate)
 | 
						|
        {
 | 
						|
          if (lastIndex == -1)
 | 
						|
            lastIndex = len - 1;
 | 
						|
          recreate(lastIndex, deepestChangedIndex);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    private void recreate(int startIndex, int endIndex)
 | 
						|
    {
 | 
						|
      // Recreate the element representing the inserted index.
 | 
						|
      Edit edit = insertPath[startIndex];
 | 
						|
      Element child;
 | 
						|
      Element newChild;
 | 
						|
      int changeLength = insertPath.length;
 | 
						|
 | 
						|
      if (startIndex + 1 == changeLength)
 | 
						|
        child = edit.e.getElement(edit.index);
 | 
						|
      else
 | 
						|
        child = edit.e.getElement(edit.index - 1);
 | 
						|
 | 
						|
      if(child.isLeaf())
 | 
						|
        {
 | 
						|
          newChild = createLeafElement(edit.e, child.getAttributes(),
 | 
						|
                                   Math.max(endOffset, child.getStartOffset()),
 | 
						|
                                   child.getEndOffset());
 | 
						|
        }
 | 
						|
      else
 | 
						|
        {
 | 
						|
          newChild = createBranchElement(edit.e, child.getAttributes());
 | 
						|
        }
 | 
						|
      fracturedParent = edit.e;
 | 
						|
      fracturedChild = newChild;
 | 
						|
 | 
						|
      // Recreate all the elements to the right of the insertion point.
 | 
						|
      Element parent = newChild;
 | 
						|
      while (++startIndex < endIndex)
 | 
						|
        {
 | 
						|
          boolean isEnd = (startIndex + 1) == endIndex;
 | 
						|
          boolean isEndLeaf = (startIndex + 1) == changeLength;
 | 
						|
 | 
						|
          // Create the newChild, a duplicate of the elment at
 | 
						|
          // index. This isn't done if isEnd and offsetLastIndex are true
 | 
						|
          // indicating a join previous was done.
 | 
						|
          edit = insertPath[startIndex];
 | 
						|
 | 
						|
          // Determine the child to duplicate, won't have to duplicate
 | 
						|
          // if at end of fracture, or offseting index.
 | 
						|
          if(isEnd)
 | 
						|
            {
 | 
						|
              if(offsetLastIndex || ! isEndLeaf)
 | 
						|
                child = null;
 | 
						|
              else
 | 
						|
                child = edit.e.getElement(edit.index);
 | 
						|
            }
 | 
						|
          else
 | 
						|
            {
 | 
						|
              child = edit.e.getElement(edit.index - 1);
 | 
						|
            }
 | 
						|
 | 
						|
          // Duplicate it.
 | 
						|
          if(child != null)
 | 
						|
            {
 | 
						|
              if(child.isLeaf())
 | 
						|
                {
 | 
						|
                  newChild = createLeafElement(parent, child.getAttributes(),
 | 
						|
                                   Math.max(endOffset, child.getStartOffset()),
 | 
						|
                                   child.getEndOffset());
 | 
						|
                }
 | 
						|
              else
 | 
						|
                {
 | 
						|
                  newChild = createBranchElement(parent,
 | 
						|
                                                 child.getAttributes());
 | 
						|
                }
 | 
						|
            }
 | 
						|
          else
 | 
						|
            newChild = null;
 | 
						|
 | 
						|
        // Recreate the remaining children (there may be none).
 | 
						|
        int childrenToMove = edit.e.getElementCount() - edit.index;
 | 
						|
        Element[] children;
 | 
						|
        int moveStartIndex;
 | 
						|
        int childStartIndex = 1;
 | 
						|
 | 
						|
        if (newChild == null)
 | 
						|
          {
 | 
						|
            // Last part of fracture.
 | 
						|
            if (isEndLeaf)
 | 
						|
              {
 | 
						|
                childrenToMove--;
 | 
						|
                moveStartIndex = edit.index + 1;
 | 
						|
              }
 | 
						|
            else
 | 
						|
              {
 | 
						|
                moveStartIndex = edit.index;
 | 
						|
              }
 | 
						|
            childStartIndex = 0;
 | 
						|
            children = new Element[childrenToMove];
 | 
						|
          }
 | 
						|
        else
 | 
						|
          {
 | 
						|
            if (! isEnd)
 | 
						|
              {
 | 
						|
                // Branch.
 | 
						|
                childrenToMove++;
 | 
						|
                moveStartIndex = edit.index;
 | 
						|
            }
 | 
						|
            else
 | 
						|
              {
 | 
						|
                // Last leaf, need to recreate part of it.
 | 
						|
                moveStartIndex = edit.index + 1;
 | 
						|
              }
 | 
						|
            children = new Element[childrenToMove];
 | 
						|
            children[0] = newChild;
 | 
						|
        }
 | 
						|
 | 
						|
        for (int c = childStartIndex; c < childrenToMove; c++)
 | 
						|
          {
 | 
						|
            Element toMove = edit.e.getElement(moveStartIndex++);
 | 
						|
            children[c] = recreateFracturedElement(parent, toMove);
 | 
						|
            edit.removed.add(toMove);
 | 
						|
          }
 | 
						|
        ((BranchElement) parent).replace(0, 0, children);
 | 
						|
        parent = newChild;
 | 
						|
      }
 | 
						|
 | 
						|
    }
 | 
						|
 | 
						|
    private Element recreateFracturedElement(Element parent, Element toCopy)
 | 
						|
    {
 | 
						|
      Element recreated;
 | 
						|
      if(toCopy.isLeaf())
 | 
						|
        {
 | 
						|
          recreated = createLeafElement(parent, toCopy.getAttributes(),
 | 
						|
                                  Math.max(toCopy.getStartOffset(), endOffset),
 | 
						|
                                  toCopy.getEndOffset());
 | 
						|
        }
 | 
						|
      else
 | 
						|
        {
 | 
						|
          Element newParent = createBranchElement(parent,
 | 
						|
                                                  toCopy.getAttributes());
 | 
						|
          int childCount = toCopy.getElementCount();
 | 
						|
          Element[] newChildren = new Element[childCount];
 | 
						|
          for (int i = 0; i < childCount; i++)
 | 
						|
            {
 | 
						|
              newChildren[i] = recreateFracturedElement(newParent,
 | 
						|
                                                        toCopy.getElement(i));
 | 
						|
            }
 | 
						|
          ((BranchElement) newParent).replace(0, 0, newChildren);
 | 
						|
          recreated = newParent;
 | 
						|
        }
 | 
						|
      return recreated;
 | 
						|
    }
 | 
						|
 | 
						|
    private boolean split(int offs, int len)
 | 
						|
    {
 | 
						|
      boolean splitEnd = false;
 | 
						|
      // Push the path to the stack.
 | 
						|
      Element e = root;
 | 
						|
      int index = e.getElementIndex(offs);
 | 
						|
      while (! e.isLeaf())
 | 
						|
        {
 | 
						|
          elementStack.push(new Edit(e, index));
 | 
						|
          e = e.getElement(index);
 | 
						|
          index = e.getElementIndex(offs);
 | 
						|
        }
 | 
						|
 | 
						|
      Edit ec = (Edit) elementStack.peek();
 | 
						|
      Element child = ec.e.getElement(ec.index);
 | 
						|
      // Make sure there is something to do. If the
 | 
						|
      // offset is already at a boundary then there is
 | 
						|
      // nothing to do.
 | 
						|
      if (child.getStartOffset() < offs && offs < child.getEndOffset())
 | 
						|
        {
 | 
						|
          // We need to split, now see if the other end is within
 | 
						|
          // the same parent.
 | 
						|
          int index0 = ec.index;
 | 
						|
          int index1 = index0;
 | 
						|
          if (((offs + len) < ec.e.getEndOffset()) && (len != 0))
 | 
						|
            {
 | 
						|
              // It's a range split in the same parent.
 | 
						|
              index1 = ec.e.getElementIndex(offs+len);
 | 
						|
              if (index1 == index0)
 | 
						|
                {
 | 
						|
                  // It's a three-way split.
 | 
						|
                  ec.removed.add(child);
 | 
						|
                  e = createLeafElement(ec.e, child.getAttributes(),
 | 
						|
                                        child.getStartOffset(), offs);
 | 
						|
                  ec.added.add(e);
 | 
						|
                  e = createLeafElement(ec.e, child.getAttributes(),
 | 
						|
                                        offs, offs + len);
 | 
						|
                  ec.added.add(e);
 | 
						|
                  e = createLeafElement(ec.e, child.getAttributes(),
 | 
						|
                                        offs + len, child.getEndOffset());
 | 
						|
                  ec.added.add(e);
 | 
						|
                  return true;
 | 
						|
                }
 | 
						|
              else
 | 
						|
                {
 | 
						|
                  child = ec.e.getElement(index1);
 | 
						|
                  if ((offs + len) == child.getStartOffset())
 | 
						|
                    {
 | 
						|
                      // End is already on a boundary.
 | 
						|
                      index1 = index0;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
              splitEnd = true;
 | 
						|
            }
 | 
						|
 | 
						|
          // Split the first location.
 | 
						|
          pos = offs;
 | 
						|
          child = ec.e.getElement(index0);
 | 
						|
          ec.removed.add(child);
 | 
						|
          e = createLeafElement(ec.e, child.getAttributes(),
 | 
						|
                                child.getStartOffset(), pos);
 | 
						|
          ec.added.add(e);
 | 
						|
          e = createLeafElement(ec.e, child.getAttributes(),
 | 
						|
                                pos, child.getEndOffset());
 | 
						|
          ec.added.add(e);
 | 
						|
 | 
						|
          // Pick up things in the middle.
 | 
						|
          for (int i = index0 + 1; i < index1; i++)
 | 
						|
            {
 | 
						|
              child = ec.e.getElement(i);
 | 
						|
              ec.removed.add(child);
 | 
						|
              ec.added.add(child);
 | 
						|
            }
 | 
						|
 | 
						|
          if (index1 != index0)
 | 
						|
            {
 | 
						|
              child = ec.e.getElement(index1);
 | 
						|
              pos = offs + len;
 | 
						|
              ec.removed.add(child);
 | 
						|
              e = createLeafElement(ec.e, child.getAttributes(),
 | 
						|
                                    child.getStartOffset(), pos);
 | 
						|
              ec.added.add(e);
 | 
						|
              e = createLeafElement(ec.e, child.getAttributes(),
 | 
						|
                                    pos, child.getEndOffset());
 | 
						|
 | 
						|
              ec.added.add(e);
 | 
						|
            }
 | 
						|
        }
 | 
						|
      return splitEnd;
 | 
						|
 | 
						|
    }
 | 
						|
 | 
						|
  }
 | 
						|
 | 
						|
 | 
						|
  /**
 | 
						|
   * An element type for sections. This is a simple BranchElement with a unique
 | 
						|
   * name.
 | 
						|
   */
 | 
						|
  protected class SectionElement extends BranchElement
 | 
						|
  {
 | 
						|
    /**
 | 
						|
     * Creates a new SectionElement.
 | 
						|
     */
 | 
						|
    public SectionElement()
 | 
						|
    {
 | 
						|
      super(null, null);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Returns the name of the element. This method always returns
 | 
						|
     * "section".
 | 
						|
     *
 | 
						|
     * @return the name of the element
 | 
						|
     */
 | 
						|
    public String getName()
 | 
						|
    {
 | 
						|
      return SectionElementName;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Receives notification when any of the document's style changes and calls
 | 
						|
   * {@link DefaultStyledDocument#styleChanged(Style)}.
 | 
						|
   *
 | 
						|
   * @author Roman Kennke (kennke@aicas.com)
 | 
						|
   */
 | 
						|
  private class StyleChangeListener implements ChangeListener
 | 
						|
  {
 | 
						|
 | 
						|
    /**
 | 
						|
     * Receives notification when any of the document's style changes and calls
 | 
						|
     * {@link DefaultStyledDocument#styleChanged(Style)}.
 | 
						|
     *
 | 
						|
     * @param event
 | 
						|
     *          the change event
 | 
						|
     */
 | 
						|
    public void stateChanged(ChangeEvent event)
 | 
						|
    {
 | 
						|
      Style style = (Style) event.getSource();
 | 
						|
      styleChanged(style);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /** The serialization UID (compatible with JDK1.5). */
 | 
						|
  private static final long serialVersionUID = 940485415728614849L;
 | 
						|
 | 
						|
  /**
 | 
						|
   * The default size to use for new content buffers.
 | 
						|
   */
 | 
						|
  public static final int BUFFER_SIZE_DEFAULT = 4096;
 | 
						|
 | 
						|
  /**
 | 
						|
   * The <code>EditorBuffer</code> that is used to manage to
 | 
						|
   * <code>Element</code> hierarchy.
 | 
						|
   */
 | 
						|
  protected DefaultStyledDocument.ElementBuffer buffer;
 | 
						|
 | 
						|
  /**
 | 
						|
   * Listens for changes on this document's styles and notifies styleChanged().
 | 
						|
   */
 | 
						|
  private StyleChangeListener styleChangeListener;
 | 
						|
 | 
						|
  /**
 | 
						|
   * Creates a new <code>DefaultStyledDocument</code>.
 | 
						|
   */
 | 
						|
  public DefaultStyledDocument()
 | 
						|
  {
 | 
						|
    this(new GapContent(BUFFER_SIZE_DEFAULT), new StyleContext());
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Creates a new <code>DefaultStyledDocument</code> that uses the specified
 | 
						|
   * {@link StyleContext}.
 | 
						|
   *
 | 
						|
   * @param context
 | 
						|
   *          the <code>StyleContext</code> to use
 | 
						|
   */
 | 
						|
  public DefaultStyledDocument(StyleContext context)
 | 
						|
  {
 | 
						|
    this(new GapContent(BUFFER_SIZE_DEFAULT), context);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Creates a new <code>DefaultStyledDocument</code> that uses the specified
 | 
						|
   * {@link StyleContext} and {@link Content} buffer.
 | 
						|
   *
 | 
						|
   * @param content
 | 
						|
   *          the <code>Content</code> buffer to use
 | 
						|
   * @param context
 | 
						|
   *          the <code>StyleContext</code> to use
 | 
						|
   */
 | 
						|
  public DefaultStyledDocument(AbstractDocument.Content content,
 | 
						|
                               StyleContext context)
 | 
						|
  {
 | 
						|
    super(content, context);
 | 
						|
    buffer = new ElementBuffer(createDefaultRoot());
 | 
						|
    setLogicalStyle(0, context.getStyle(StyleContext.DEFAULT_STYLE));
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Adds a style into the style hierarchy. Unspecified style attributes can be
 | 
						|
   * resolved in the <code>parent</code> style, if one is specified. While it
 | 
						|
   * is legal to add nameless styles (<code>nm == null</code),
 | 
						|
   * you must be aware that the client application is then responsible
 | 
						|
   * for managing the style hierarchy, since unnamed styles cannot be
 | 
						|
   * looked up by their name.
 | 
						|
   *
 | 
						|
   * @param nm the name of the style or <code>null</code> if the style should
 | 
						|
   *           be unnamed
 | 
						|
   * @param parent the parent in which unspecified style attributes are
 | 
						|
   *           resolved, or <code>null</code> if that is not necessary
 | 
						|
   *
 | 
						|
   * @return the newly created <code>Style</code>
 | 
						|
   */
 | 
						|
  public Style addStyle(String nm, Style parent)
 | 
						|
  {
 | 
						|
    StyleContext context = (StyleContext) getAttributeContext();
 | 
						|
    Style newStyle = context.addStyle(nm, parent);
 | 
						|
 | 
						|
    // Register change listener.
 | 
						|
    if (styleChangeListener == null)
 | 
						|
      styleChangeListener = new StyleChangeListener();
 | 
						|
    newStyle.addChangeListener(styleChangeListener);
 | 
						|
 | 
						|
    return newStyle;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Create the default root element for this kind of <code>Document</code>.
 | 
						|
   *
 | 
						|
   * @return the default root element for this kind of <code>Document</code>
 | 
						|
   */
 | 
						|
  protected AbstractDocument.AbstractElement createDefaultRoot()
 | 
						|
  {
 | 
						|
    Element[] tmp;
 | 
						|
    SectionElement section = new SectionElement();
 | 
						|
 | 
						|
    BranchElement paragraph = new BranchElement(section, null);
 | 
						|
    tmp = new Element[1];
 | 
						|
    tmp[0] = paragraph;
 | 
						|
    section.replace(0, 0, tmp);
 | 
						|
 | 
						|
    Element leaf = new LeafElement(paragraph, null, 0, 1);
 | 
						|
    tmp = new Element[1];
 | 
						|
    tmp[0] = leaf;
 | 
						|
    paragraph.replace(0, 0, tmp);
 | 
						|
 | 
						|
    return section;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the <code>Element</code> that corresponds to the character at the
 | 
						|
   * specified position.
 | 
						|
   *
 | 
						|
   * @param position
 | 
						|
   *          the position of which we query the corresponding
 | 
						|
   *          <code>Element</code>
 | 
						|
   * @return the <code>Element</code> that corresponds to the character at the
 | 
						|
   *         specified position
 | 
						|
   */
 | 
						|
  public Element getCharacterElement(int position)
 | 
						|
  {
 | 
						|
    Element element = getDefaultRootElement();
 | 
						|
 | 
						|
    while (!element.isLeaf())
 | 
						|
      {
 | 
						|
        int index = element.getElementIndex(position);
 | 
						|
        element = element.getElement(index);
 | 
						|
      }
 | 
						|
 | 
						|
    return element;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Extracts a background color from a set of attributes.
 | 
						|
   *
 | 
						|
   * @param attributes
 | 
						|
   *          the attributes from which to get a background color
 | 
						|
   * @return the background color that correspond to the attributes
 | 
						|
   */
 | 
						|
  public Color getBackground(AttributeSet attributes)
 | 
						|
  {
 | 
						|
    StyleContext context = (StyleContext) getAttributeContext();
 | 
						|
    return context.getBackground(attributes);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the default root element.
 | 
						|
   *
 | 
						|
   * @return the default root element
 | 
						|
   */
 | 
						|
  public Element getDefaultRootElement()
 | 
						|
  {
 | 
						|
    return buffer.getRootElement();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Extracts a font from a set of attributes.
 | 
						|
   *
 | 
						|
   * @param attributes
 | 
						|
   *          the attributes from which to get a font
 | 
						|
   * @return the font that correspond to the attributes
 | 
						|
   */
 | 
						|
  public Font getFont(AttributeSet attributes)
 | 
						|
  {
 | 
						|
    StyleContext context = (StyleContext) getAttributeContext();
 | 
						|
    return context.getFont(attributes);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Extracts a foreground color from a set of attributes.
 | 
						|
   *
 | 
						|
   * @param attributes
 | 
						|
   *          the attributes from which to get a foreground color
 | 
						|
   * @return the foreground color that correspond to the attributes
 | 
						|
   */
 | 
						|
  public Color getForeground(AttributeSet attributes)
 | 
						|
  {
 | 
						|
    StyleContext context = (StyleContext) getAttributeContext();
 | 
						|
    return context.getForeground(attributes);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the logical <code>Style</code> for the specified position.
 | 
						|
   *
 | 
						|
   * @param position
 | 
						|
   *          the position from which to query to logical style
 | 
						|
   * @return the logical <code>Style</code> for the specified position
 | 
						|
   */
 | 
						|
  public Style getLogicalStyle(int position)
 | 
						|
  {
 | 
						|
    Element paragraph = getParagraphElement(position);
 | 
						|
    AttributeSet attributes = paragraph.getAttributes();
 | 
						|
    AttributeSet a = attributes.getResolveParent();
 | 
						|
    // If the resolve parent is not of type Style, we return null.
 | 
						|
    if (a instanceof Style)
 | 
						|
      return (Style) a;
 | 
						|
    return null;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the paragraph element for the specified position. If the position
 | 
						|
   * is outside the bounds of the document's root element, then the closest
 | 
						|
   * element is returned. That is the last paragraph if
 | 
						|
   * <code>position >= endIndex</code> or the first paragraph if
 | 
						|
   * <code>position < startIndex</code>.
 | 
						|
   *
 | 
						|
   * @param position
 | 
						|
   *          the position for which to query the paragraph element
 | 
						|
   * @return the paragraph element for the specified position
 | 
						|
   */
 | 
						|
  public Element getParagraphElement(int position)
 | 
						|
  {
 | 
						|
    Element e = getDefaultRootElement();
 | 
						|
    while (!e.isLeaf())
 | 
						|
      e = e.getElement(e.getElementIndex(position));
 | 
						|
 | 
						|
    if (e != null)
 | 
						|
      return e.getParentElement();
 | 
						|
    return e;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Looks up and returns a named <code>Style</code>.
 | 
						|
   *
 | 
						|
   * @param nm
 | 
						|
   *          the name of the <code>Style</code>
 | 
						|
   * @return the found <code>Style</code> of <code>null</code> if no such
 | 
						|
   *         <code>Style</code> exists
 | 
						|
   */
 | 
						|
  public Style getStyle(String nm)
 | 
						|
  {
 | 
						|
    StyleContext context = (StyleContext) getAttributeContext();
 | 
						|
    return context.getStyle(nm);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Removes a named <code>Style</code> from the style hierarchy.
 | 
						|
   *
 | 
						|
   * @param nm
 | 
						|
   *          the name of the <code>Style</code> to be removed
 | 
						|
   */
 | 
						|
  public void removeStyle(String nm)
 | 
						|
  {
 | 
						|
    StyleContext context = (StyleContext) getAttributeContext();
 | 
						|
    context.removeStyle(nm);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sets text attributes for the fragment specified by <code>offset</code>
 | 
						|
   * and <code>length</code>.
 | 
						|
   *
 | 
						|
   * @param offset
 | 
						|
   *          the start offset of the fragment
 | 
						|
   * @param length
 | 
						|
   *          the length of the fragment
 | 
						|
   * @param attributes
 | 
						|
   *          the text attributes to set
 | 
						|
   * @param replace
 | 
						|
   *          if <code>true</code>, the attributes of the current selection
 | 
						|
   *          are overridden, otherwise they are merged
 | 
						|
   */
 | 
						|
  public void setCharacterAttributes(int offset, int length,
 | 
						|
                                     AttributeSet attributes, boolean replace)
 | 
						|
  {
 | 
						|
    // Exit early if length is 0, so no DocumentEvent is created or fired.
 | 
						|
    if (length == 0)
 | 
						|
      return;
 | 
						|
    try
 | 
						|
      {
 | 
						|
        // Must obtain a write lock for this method. writeLock() and
 | 
						|
        // writeUnlock() should always be in try/finally block to make
 | 
						|
        // sure that locking happens in a balanced manner.
 | 
						|
        writeLock();
 | 
						|
        DefaultDocumentEvent ev = new DefaultDocumentEvent(offset,
 | 
						|
                                                           length,
 | 
						|
                                                           DocumentEvent.EventType.CHANGE);
 | 
						|
 | 
						|
        // Modify the element structure so that the interval begins at an
 | 
						|
        // element
 | 
						|
        // start and ends at an element end.
 | 
						|
        buffer.change(offset, length, ev);
 | 
						|
 | 
						|
        // Visit all paragraph elements within the specified interval
 | 
						|
        int end = offset + length;
 | 
						|
        Element curr;
 | 
						|
        for (int pos = offset; pos < end;)
 | 
						|
          {
 | 
						|
            // Get the CharacterElement at offset pos.
 | 
						|
            curr = getCharacterElement(pos);
 | 
						|
            if (pos == curr.getEndOffset())
 | 
						|
              break;
 | 
						|
 | 
						|
            MutableAttributeSet a = (MutableAttributeSet) curr.getAttributes();
 | 
						|
            ev.addEdit(new AttributeUndoableEdit(curr, attributes, replace));
 | 
						|
            // If replace is true, remove all the old attributes.
 | 
						|
            if (replace)
 | 
						|
              a.removeAttributes(a);
 | 
						|
            // Add all the new attributes.
 | 
						|
            a.addAttributes(attributes);
 | 
						|
            // Increment pos so we can check the next CharacterElement.
 | 
						|
            pos = curr.getEndOffset();
 | 
						|
          }
 | 
						|
        fireChangedUpdate(ev);
 | 
						|
        fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
 | 
						|
      }
 | 
						|
    finally
 | 
						|
      {
 | 
						|
        writeUnlock();
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sets the logical style for the paragraph at the specified position.
 | 
						|
   *
 | 
						|
   * @param position
 | 
						|
   *          the position at which the logical style is added
 | 
						|
   * @param style
 | 
						|
   *          the style to set for the current paragraph
 | 
						|
   */
 | 
						|
  public void setLogicalStyle(int position, Style style)
 | 
						|
  {
 | 
						|
    Element el = getParagraphElement(position);
 | 
						|
    // getParagraphElement doesn't return null but subclasses might so
 | 
						|
    // we check for null here.
 | 
						|
    if (el == null)
 | 
						|
      return;
 | 
						|
    try
 | 
						|
      {
 | 
						|
        writeLock();
 | 
						|
        if (el instanceof AbstractElement)
 | 
						|
          {
 | 
						|
            AbstractElement ael = (AbstractElement) el;
 | 
						|
            ael.setResolveParent(style);
 | 
						|
            int start = el.getStartOffset();
 | 
						|
            int end = el.getEndOffset();
 | 
						|
            DefaultDocumentEvent ev = new DefaultDocumentEvent(start,
 | 
						|
                                                               end - start,
 | 
						|
                                                               DocumentEvent.EventType.CHANGE);
 | 
						|
            fireChangedUpdate(ev);
 | 
						|
            fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
 | 
						|
          }
 | 
						|
        else
 | 
						|
          throw new AssertionError(
 | 
						|
                                   "paragraph elements are expected to be"
 | 
						|
                                       + "instances of AbstractDocument.AbstractElement");
 | 
						|
      }
 | 
						|
    finally
 | 
						|
      {
 | 
						|
        writeUnlock();
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sets text attributes for the paragraph at the specified fragment.
 | 
						|
   *
 | 
						|
   * @param offset
 | 
						|
   *          the beginning of the fragment
 | 
						|
   * @param length
 | 
						|
   *          the length of the fragment
 | 
						|
   * @param attributes
 | 
						|
   *          the text attributes to set
 | 
						|
   * @param replace
 | 
						|
   *          if <code>true</code>, the attributes of the current selection
 | 
						|
   *          are overridden, otherwise they are merged
 | 
						|
   */
 | 
						|
  public void setParagraphAttributes(int offset, int length,
 | 
						|
                                     AttributeSet attributes, boolean replace)
 | 
						|
  {
 | 
						|
    try
 | 
						|
      {
 | 
						|
        // Must obtain a write lock for this method. writeLock() and
 | 
						|
        // writeUnlock() should always be in try/finally blocks to make
 | 
						|
        // sure that locking occurs in a balanced manner.
 | 
						|
        writeLock();
 | 
						|
 | 
						|
        // Create a DocumentEvent to use for changedUpdate().
 | 
						|
        DefaultDocumentEvent ev = new DefaultDocumentEvent(offset,
 | 
						|
                                                           length,
 | 
						|
                                                           DocumentEvent.EventType.CHANGE);
 | 
						|
 | 
						|
        // Have to iterate through all the _paragraph_ elements that are
 | 
						|
        // contained or partially contained in the interval
 | 
						|
        // (offset, offset + length).
 | 
						|
        Element rootElement = getDefaultRootElement();
 | 
						|
        int startElement = rootElement.getElementIndex(offset);
 | 
						|
        int endElement = rootElement.getElementIndex(offset + length - 1);
 | 
						|
        if (endElement < startElement)
 | 
						|
          endElement = startElement;
 | 
						|
 | 
						|
        for (int i = startElement; i <= endElement; i++)
 | 
						|
          {
 | 
						|
            Element par = rootElement.getElement(i);
 | 
						|
            MutableAttributeSet a = (MutableAttributeSet) par.getAttributes();
 | 
						|
            // Add the change to the DocumentEvent.
 | 
						|
            ev.addEdit(new AttributeUndoableEdit(par, attributes, replace));
 | 
						|
            // If replace is true remove the old attributes.
 | 
						|
            if (replace)
 | 
						|
              a.removeAttributes(a);
 | 
						|
            // Add the new attributes.
 | 
						|
            a.addAttributes(attributes);
 | 
						|
          }
 | 
						|
        fireChangedUpdate(ev);
 | 
						|
        fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
 | 
						|
      }
 | 
						|
    finally
 | 
						|
      {
 | 
						|
        writeUnlock();
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Called in response to content insert actions. This is used to update the
 | 
						|
   * element structure.
 | 
						|
   *
 | 
						|
   * @param ev
 | 
						|
   *          the <code>DocumentEvent</code> describing the change
 | 
						|
   * @param attr
 | 
						|
   *          the attributes for the change
 | 
						|
   */
 | 
						|
  protected void insertUpdate(DefaultDocumentEvent ev, AttributeSet attr)
 | 
						|
  {
 | 
						|
    int offs = ev.getOffset();
 | 
						|
    int len = ev.getLength();
 | 
						|
    int endOffs = offs + len;
 | 
						|
    if (attr == null)
 | 
						|
      attr = SimpleAttributeSet.EMPTY;
 | 
						|
 | 
						|
    // Paragraph attributes are fetched from the point _after_ the insertion.
 | 
						|
    Element paragraph = getParagraphElement(endOffs);
 | 
						|
    AttributeSet pAttr = paragraph.getAttributes();
 | 
						|
    // Character attributes are fetched from the actual insertion point.
 | 
						|
    Element paragraph2 = getParagraphElement(offs);
 | 
						|
    int contIndex = paragraph2.getElementIndex(offs);
 | 
						|
    Element content = paragraph2.getElement(contIndex);
 | 
						|
    AttributeSet cAttr = content.getAttributes();
 | 
						|
 | 
						|
    boolean insertAtBoundary = content.getEndOffset() == endOffs;
 | 
						|
    try
 | 
						|
      {
 | 
						|
        Segment s = new Segment();
 | 
						|
        ArrayList buf = new ArrayList();
 | 
						|
        ElementSpec lastStartTag = null;
 | 
						|
        boolean insertAfterNewline = false;
 | 
						|
        short lastStartDir = ElementSpec.OriginateDirection;
 | 
						|
 | 
						|
        // Special handle if we are inserting after a newline.
 | 
						|
        if (offs > 0)
 | 
						|
          {
 | 
						|
            getText(offs - 1, 1, s);
 | 
						|
            if (s.array[s.offset] == '\n')
 | 
						|
              {
 | 
						|
                insertAfterNewline = true;
 | 
						|
                lastStartDir = insertAfterNewline(paragraph, paragraph2,
 | 
						|
                                                  pAttr, buf, offs,
 | 
						|
                                                  endOffs);
 | 
						|
                // Search last start tag.
 | 
						|
                for (int i = buf.size() - 1; i >= 0 && lastStartTag == null;
 | 
						|
                     i--)
 | 
						|
                  {
 | 
						|
                    ElementSpec tag = (ElementSpec) buf.get(i);
 | 
						|
                    if (tag.getType() == ElementSpec.StartTagType)
 | 
						|
                      {
 | 
						|
                        lastStartTag = tag;
 | 
						|
                      }
 | 
						|
                  }
 | 
						|
              }
 | 
						|
 | 
						|
          }
 | 
						|
 | 
						|
        // If we are not inserting after a newline, the paragraph attributes
 | 
						|
        // come from the paragraph under the insertion point.
 | 
						|
        if (! insertAfterNewline)
 | 
						|
          pAttr = paragraph2.getAttributes();
 | 
						|
 | 
						|
        // Scan text and build up the specs.
 | 
						|
        getText(offs, len, s);
 | 
						|
        int end = s.offset + s.count;
 | 
						|
        int last = s.offset;
 | 
						|
        for (int i = s.offset; i < end; i++)
 | 
						|
          {
 | 
						|
            if (s.array[i] == '\n')
 | 
						|
              {
 | 
						|
                int breakOffs = i + 1;
 | 
						|
                buf.add(new ElementSpec(attr, ElementSpec.ContentType,
 | 
						|
                                        breakOffs - last));
 | 
						|
                buf.add(new ElementSpec(null, ElementSpec.EndTagType));
 | 
						|
                lastStartTag = new ElementSpec(pAttr,
 | 
						|
                                               ElementSpec.StartTagType);
 | 
						|
                buf.add(lastStartTag);
 | 
						|
                last = breakOffs;
 | 
						|
              }
 | 
						|
          }
 | 
						|
 | 
						|
        // Need to add a tailing content tag if we didn't finish at a boundary.
 | 
						|
        if (last < end)
 | 
						|
          {
 | 
						|
            buf.add(new ElementSpec(attr, ElementSpec.ContentType,
 | 
						|
                                    end - last));
 | 
						|
          }
 | 
						|
 | 
						|
        // Now we need to fix up the directions of the specs.
 | 
						|
        ElementSpec first = (ElementSpec) buf.get(0);
 | 
						|
        int doclen = getLength();
 | 
						|
 | 
						|
        // Maybe join-previous the first tag if it is content and has
 | 
						|
        // the same attributes as the previous character run.
 | 
						|
        if (first.getType() == ElementSpec.ContentType && cAttr.isEqual(attr))
 | 
						|
          first.setDirection(ElementSpec.JoinPreviousDirection);
 | 
						|
 | 
						|
        // Join-fracture or join-next the last start tag if necessary.
 | 
						|
        if (lastStartTag != null)
 | 
						|
          {
 | 
						|
            if (insertAfterNewline)
 | 
						|
              lastStartTag.setDirection(lastStartDir);
 | 
						|
            else if (paragraph2.getEndOffset() != endOffs)
 | 
						|
              lastStartTag.setDirection(ElementSpec.JoinFractureDirection);
 | 
						|
            else
 | 
						|
              {
 | 
						|
                Element par = paragraph2.getParentElement();
 | 
						|
                int par2Index = par.getElementIndex(offs);
 | 
						|
                if (par2Index + 1 < par.getElementCount()
 | 
						|
                    && ! par.getElement(par2Index + 1).isLeaf())
 | 
						|
                  lastStartTag.setDirection(ElementSpec.JoinNextDirection);
 | 
						|
              }
 | 
						|
          }
 | 
						|
 | 
						|
        // Join-next last tag if possible.
 | 
						|
        if (insertAtBoundary && endOffs < doclen)
 | 
						|
          {
 | 
						|
            ElementSpec lastTag = (ElementSpec) buf.get(buf.size() - 1);
 | 
						|
            if (lastTag.getType() == ElementSpec.ContentType
 | 
						|
                && ((lastStartTag == null
 | 
						|
                     && (paragraph == paragraph2 || insertAfterNewline))
 | 
						|
                    || (lastStartTag != null
 | 
						|
             && lastStartTag.getDirection() != ElementSpec.OriginateDirection)))
 | 
						|
              {
 | 
						|
                int nextIndex = paragraph.getElementIndex(endOffs);
 | 
						|
                Element nextRun = paragraph.getElement(nextIndex);
 | 
						|
                if (nextRun.isLeaf() && attr.isEqual(nextRun.getAttributes()))
 | 
						|
                  lastTag.setDirection(ElementSpec.JoinNextDirection);
 | 
						|
              }
 | 
						|
          }
 | 
						|
 | 
						|
        else if (! insertAtBoundary && lastStartTag != null
 | 
						|
           && lastStartTag.getDirection() == ElementSpec.JoinFractureDirection)
 | 
						|
          {
 | 
						|
            ElementSpec lastTag = (ElementSpec) buf.get(buf.size() - 1);
 | 
						|
            if (lastTag.getType() == ElementSpec.ContentType
 | 
						|
                && lastTag.getDirection() != ElementSpec.JoinPreviousDirection
 | 
						|
                && attr.isEqual(cAttr))
 | 
						|
              {
 | 
						|
                lastTag.setDirection(ElementSpec.JoinNextDirection);
 | 
						|
              }
 | 
						|
          }
 | 
						|
 | 
						|
        ElementSpec[] specs = new ElementSpec[buf.size()];
 | 
						|
        specs = (ElementSpec[]) buf.toArray(specs);
 | 
						|
        buffer.insert(offs, len, specs, ev);
 | 
						|
      }
 | 
						|
    catch (BadLocationException ex)
 | 
						|
      {
 | 
						|
        // Ignore this. Comment out for debugging.
 | 
						|
        ex.printStackTrace();
 | 
						|
      }
 | 
						|
    super.insertUpdate(ev, attr);
 | 
						|
  }
 | 
						|
 | 
						|
  private short insertAfterNewline(Element par1, Element par2,
 | 
						|
                                   AttributeSet attr, ArrayList buf,
 | 
						|
                                   int offs, int endOffs)
 | 
						|
  {
 | 
						|
    short dir = 0;
 | 
						|
    if (par1.getParentElement() == par2.getParentElement())
 | 
						|
      {
 | 
						|
        ElementSpec tag = new ElementSpec(attr, ElementSpec.EndTagType);
 | 
						|
        buf.add(tag);
 | 
						|
        tag = new ElementSpec(attr, ElementSpec.StartTagType);
 | 
						|
        buf.add(tag);
 | 
						|
        if (par2.getEndOffset() != endOffs)
 | 
						|
          dir = ElementSpec.JoinFractureDirection;
 | 
						|
        else
 | 
						|
          {
 | 
						|
            Element par = par2.getParentElement();
 | 
						|
            if (par.getElementIndex(offs) + 1 < par.getElementCount())
 | 
						|
              dir = ElementSpec.JoinNextDirection;
 | 
						|
          }
 | 
						|
      }
 | 
						|
    else
 | 
						|
      {
 | 
						|
        // For text with more than 2 levels, find the common parent of
 | 
						|
        // par1 and par2.
 | 
						|
        ArrayList parentsLeft = new ArrayList();
 | 
						|
        ArrayList parentsRight = new ArrayList();
 | 
						|
        Element e = par2;
 | 
						|
        while (e != null)
 | 
						|
          {
 | 
						|
            parentsLeft.add(e);
 | 
						|
            e = e.getParentElement();
 | 
						|
          }
 | 
						|
        e = par1;
 | 
						|
        int leftIndex = -1;
 | 
						|
        while (e != null && (leftIndex = parentsLeft.indexOf(e)) == 1)
 | 
						|
          {
 | 
						|
            parentsRight.add(e);
 | 
						|
            e = e.getParentElement();
 | 
						|
          }
 | 
						|
 | 
						|
        if (e != null)
 | 
						|
 | 
						|
          {
 | 
						|
            // e is now the common parent.
 | 
						|
            // Insert the end tags.
 | 
						|
            for (int c = 0; c < leftIndex; c++)
 | 
						|
              {
 | 
						|
                buf.add(new ElementSpec(null, ElementSpec.EndTagType));
 | 
						|
              }
 | 
						|
            // Insert the start tags.
 | 
						|
            for (int c = parentsRight.size() - 1; c >= 0; c--)
 | 
						|
              {
 | 
						|
                Element el = (Element) parentsRight.get(c);
 | 
						|
                ElementSpec tag = new ElementSpec(el.getAttributes(),
 | 
						|
                                                  ElementSpec.StartTagType);
 | 
						|
                if (c > 0)
 | 
						|
                  tag.setDirection(ElementSpec.JoinNextDirection);
 | 
						|
                buf.add(tag);
 | 
						|
              }
 | 
						|
            if (parentsRight.size() > 0)
 | 
						|
              dir = ElementSpec.JoinNextDirection;
 | 
						|
            else
 | 
						|
              dir = ElementSpec.JoinFractureDirection;
 | 
						|
          }
 | 
						|
        else
 | 
						|
          assert false;
 | 
						|
      }
 | 
						|
    return dir;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * A helper method to set up the ElementSpec buffer for the special case of an
 | 
						|
   * insertion occurring immediately after a newline.
 | 
						|
   *
 | 
						|
   * @param specs
 | 
						|
   *          the ElementSpec buffer to initialize.
 | 
						|
   */
 | 
						|
  short handleInsertAfterNewline(Vector specs, int offset, int endOffset,
 | 
						|
                                 Element prevParagraph, Element paragraph,
 | 
						|
                                 AttributeSet a)
 | 
						|
  {
 | 
						|
    if (prevParagraph.getParentElement() == paragraph.getParentElement())
 | 
						|
      {
 | 
						|
        specs.add(new ElementSpec(a, ElementSpec.EndTagType));
 | 
						|
        specs.add(new ElementSpec(a, ElementSpec.StartTagType));
 | 
						|
        if (paragraph.getStartOffset() != endOffset)
 | 
						|
          return ElementSpec.JoinFractureDirection;
 | 
						|
        // If there is an Element after this one, use JoinNextDirection.
 | 
						|
        Element parent = paragraph.getParentElement();
 | 
						|
        if (parent.getElementCount() > (parent.getElementIndex(offset) + 1))
 | 
						|
          return ElementSpec.JoinNextDirection;
 | 
						|
      }
 | 
						|
    return ElementSpec.OriginateDirection;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Updates the document structure in response to text removal. This is
 | 
						|
   * forwarded to the {@link ElementBuffer} of this document. Any changes to the
 | 
						|
   * document structure are added to the specified document event and sent to
 | 
						|
   * registered listeners.
 | 
						|
   *
 | 
						|
   * @param ev
 | 
						|
   *          the document event that records the changes to the document
 | 
						|
   */
 | 
						|
  protected void removeUpdate(DefaultDocumentEvent ev)
 | 
						|
  {
 | 
						|
    super.removeUpdate(ev);
 | 
						|
    buffer.remove(ev.getOffset(), ev.getLength(), ev);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns an enumeration of all style names.
 | 
						|
   *
 | 
						|
   * @return an enumeration of all style names
 | 
						|
   */
 | 
						|
  public Enumeration<?> getStyleNames()
 | 
						|
  {
 | 
						|
    StyleContext context = (StyleContext) getAttributeContext();
 | 
						|
    return context.getStyleNames();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Called when any of this document's styles changes.
 | 
						|
   *
 | 
						|
   * @param style
 | 
						|
   *          the style that changed
 | 
						|
   */
 | 
						|
  protected void styleChanged(Style style)
 | 
						|
  {
 | 
						|
    // Nothing to do here. This is intended to be overridden by subclasses.
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Inserts a bulk of structured content at once.
 | 
						|
   *
 | 
						|
   * @param offset
 | 
						|
   *          the offset at which the content should be inserted
 | 
						|
   * @param data
 | 
						|
   *          the actual content spec to be inserted
 | 
						|
   */
 | 
						|
  protected void insert(int offset, ElementSpec[] data)
 | 
						|
      throws BadLocationException
 | 
						|
  {
 | 
						|
    if (data == null || data.length == 0)
 | 
						|
      return;
 | 
						|
    try
 | 
						|
      {
 | 
						|
        // writeLock() and writeUnlock() should always be in a try/finally
 | 
						|
        // block so that locking balance is guaranteed even if some
 | 
						|
        // exception is thrown.
 | 
						|
        writeLock();
 | 
						|
 | 
						|
        // First we collect the content to be inserted.
 | 
						|
        CPStringBuilder contentBuffer = new CPStringBuilder();
 | 
						|
        for (int i = 0; i < data.length; i++)
 | 
						|
          {
 | 
						|
            // Collect all inserts into one so we can get the correct
 | 
						|
            // ElementEdit
 | 
						|
            ElementSpec spec = data[i];
 | 
						|
            if (spec.getArray() != null && spec.getLength() > 0)
 | 
						|
              contentBuffer.append(spec.getArray(), spec.getOffset(),
 | 
						|
                                   spec.getLength());
 | 
						|
          }
 | 
						|
 | 
						|
        int length = contentBuffer.length();
 | 
						|
 | 
						|
        // If there was no content inserted then exit early.
 | 
						|
        if (length == 0)
 | 
						|
          return;
 | 
						|
 | 
						|
        Content c = getContent();
 | 
						|
        UndoableEdit edit = c.insertString(offset,
 | 
						|
                                           contentBuffer.toString());
 | 
						|
 | 
						|
        // Create the DocumentEvent with the ElementEdit added
 | 
						|
        DefaultDocumentEvent ev = new DefaultDocumentEvent(offset,
 | 
						|
                                                           length,
 | 
						|
                                                           DocumentEvent.EventType.INSERT);
 | 
						|
 | 
						|
        ev.addEdit(edit);
 | 
						|
 | 
						|
        // Finally we must update the document structure and fire the insert
 | 
						|
        // update event.
 | 
						|
        buffer.insert(offset, length, data, ev);
 | 
						|
 | 
						|
        super.insertUpdate(ev, null);
 | 
						|
 | 
						|
        ev.end();
 | 
						|
        fireInsertUpdate(ev);
 | 
						|
        fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
 | 
						|
      }
 | 
						|
    finally
 | 
						|
      {
 | 
						|
        writeUnlock();
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Initializes the <code>DefaultStyledDocument</code> with the specified
 | 
						|
   * data.
 | 
						|
   *
 | 
						|
   * @param data
 | 
						|
   *          the specification of the content with which the document is
 | 
						|
   *          initialized
 | 
						|
   */
 | 
						|
  protected void create(ElementSpec[] data)
 | 
						|
  {
 | 
						|
    try
 | 
						|
      {
 | 
						|
 | 
						|
        // Clear content if there is some.
 | 
						|
        int len = getLength();
 | 
						|
        if (len > 0)
 | 
						|
          remove(0, len);
 | 
						|
 | 
						|
        writeLock();
 | 
						|
 | 
						|
        // Now we insert the content.
 | 
						|
        StringBuilder b = new StringBuilder();
 | 
						|
        for (int i = 0; i < data.length; ++i)
 | 
						|
          {
 | 
						|
            ElementSpec el = data[i];
 | 
						|
            if (el.getArray() != null && el.getLength() > 0)
 | 
						|
              b.append(el.getArray(), el.getOffset(), el.getLength());
 | 
						|
          }
 | 
						|
        Content content = getContent();
 | 
						|
        UndoableEdit cEdit = content.insertString(0, b.toString());
 | 
						|
 | 
						|
        len = b.length();
 | 
						|
        DefaultDocumentEvent ev =
 | 
						|
          new DefaultDocumentEvent(0, b.length(),
 | 
						|
                                   DocumentEvent.EventType.INSERT);
 | 
						|
        ev.addEdit(cEdit);
 | 
						|
 | 
						|
        buffer.create(len, data, ev);
 | 
						|
 | 
						|
        // For the bidi update.
 | 
						|
        super.insertUpdate(ev, null);
 | 
						|
 | 
						|
        ev.end();
 | 
						|
        fireInsertUpdate(ev);
 | 
						|
        fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
 | 
						|
      }
 | 
						|
    catch (BadLocationException ex)
 | 
						|
      {
 | 
						|
        AssertionError err = new AssertionError("Unexpected bad location");
 | 
						|
        err.initCause(ex);
 | 
						|
        throw err;
 | 
						|
      }
 | 
						|
    finally
 | 
						|
      {
 | 
						|
        writeUnlock();
 | 
						|
      }
 | 
						|
  }
 | 
						|
}
 |