home *** CD-ROM | disk | FTP | other *** search
/ Chip 1998 November / Chip_1998-11_cd.bin / tema / Cafe / jfc.bin / DefaultStyledDocument.java < prev    next >
Text File  |  1998-02-26  |  44KB  |  1,489 lines

  1. /*
  2.  * @(#)DefaultStyledDocument.java    1.70 98/02/06
  3.  * 
  4.  * Copyright (c) 1997 Sun Microsystems, Inc. All Rights Reserved.
  5.  * 
  6.  * This software is the confidential and proprietary information of Sun
  7.  * Microsystems, Inc. ("Confidential Information").  You shall not
  8.  * disclose such Confidential Information and shall use it only in
  9.  * accordance with the terms of the license agreement you entered into
  10.  * with Sun.
  11.  * 
  12.  * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
  13.  * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  14.  * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  15.  * PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
  16.  * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
  17.  * THIS SOFTWARE OR ITS DERIVATIVES.
  18.  * 
  19.  */
  20. package com.sun.java.swing.text;
  21.  
  22. import java.awt.Color;
  23. import java.awt.Component;
  24. import java.awt.Font;
  25. import java.awt.FontMetrics;
  26. import java.util.Hashtable;
  27. import java.util.Stack;
  28. import java.util.Vector;
  29. import java.io.Serializable;
  30. import com.sun.java.swing.Icon;
  31. import com.sun.java.swing.event.*;
  32. import com.sun.java.swing.undo.AbstractUndoableEdit;
  33. import com.sun.java.swing.undo.CannotRedoException;
  34. import com.sun.java.swing.undo.CannotUndoException;
  35.  
  36. /**
  37.  * A document that can be marked up with character and paragraph 
  38.  * styles in a manner similar to the Rich Text Format.  The element
  39.  * structure for this document represents style crossings for
  40.  * style runs.  These style runs are mapped into a paragraph element 
  41.  * structure (which may reside in some other structure).  The 
  42.  * style runs break at paragraph boundries since logical styles are 
  43.  * assigned to paragraph boundries.
  44.  * <p>
  45.  * Warning: serialized objects of this class will not be compatible with
  46.  * future swing releases.  The current serialization support is appropriate
  47.  * for short term storage or RMI between Swing1.0 applications.  It will
  48.  * not be possible to load serialized Swing1.0 objects with future releases
  49.  * of Swing.  The JDK1.2 release of Swing will be the compatibility
  50.  * baseline for the serialized form of Swing objects.
  51.  *
  52.  * @author  Timothy Prinzing
  53.  * @version 1.70 02/06/98
  54.  * @see     Document
  55.  * @see     AbstractDocument
  56.  */
  57. public class DefaultStyledDocument extends AbstractDocument implements StyledDocument {
  58.  
  59.     /**
  60.      * Constructs a styled document.
  61.      *
  62.      * @param c  the container for the content
  63.      * @param styles resources and style definitions which may
  64.      *  be shared across documents
  65.      */
  66.     public DefaultStyledDocument(Content c, StyleContext styles) {
  67.     super(c, styles);
  68.     buffer = new ElementBuffer(createDefaultRoot());
  69.     Style defaultStyle = styles.getStyle(StyleContext.DEFAULT_STYLE);
  70.     setLogicalStyle(0, defaultStyle);
  71.     }
  72.  
  73.     /**
  74.      * Constructs a styled document with the default content
  75.      * storage implementation and a shared set of styles.
  76.      *
  77.      * @param styles the styles
  78.      */
  79.     public DefaultStyledDocument(StyleContext styles) {
  80.     this(new StringContent(BUFFER_SIZE_DEFAULT), styles);
  81.     }
  82.  
  83.     /**
  84.      * Constructs a default styled document.  This buffers
  85.      * input content by a size of <em>BUFFER_SIZE_DEFAULT</em> 
  86.      * and has a style context that is scoped by the lifetime
  87.      * of the document and is not shared with other documents.
  88.      */
  89.     public DefaultStyledDocument() {
  90.     this(new StringContent(BUFFER_SIZE_DEFAULT), new StyleContext());
  91.     }
  92.  
  93.     /**
  94.      * Gets the default root element.
  95.      *
  96.      * @return the root
  97.      * @see Document#getDefaultRootElement
  98.      */
  99.     public Element getDefaultRootElement() {
  100.     return buffer.getRootElement();
  101.     }
  102.  
  103.     /**
  104.      * Inserts new elements in bulk.  This is useful to allow
  105.      * parsing with the document in an unlocked state and
  106.      * prepare an element structure modification.  This method
  107.      * takes an array of tokens that describe how to update an
  108.      * element structure so the time within a write lock can
  109.      * be greatly reduced in an asynchronous update situation.     
  110.      * <p>
  111.      * This method is thread safe, although most Swing methods
  112.      * are not. Please see 
  113.      * <A HREF="http://java.sun.com/products/jfc/swingdoc/threads.html">Threads
  114.      * and Swing</A> for more information.     
  115.      *
  116.      * @param offset the starting offset
  117.      * @param data the element data
  118.      * @exception BadLocationException for an invalid starting offset
  119.      */
  120.     protected void insert(int offset, ElementSpec[] data) throws BadLocationException {
  121.     try {
  122.         writeLock();
  123.  
  124.         // install the content
  125.         Content c = getContent();
  126.         int n = data.length;
  127.         int pos = offset;
  128.         for (int i = 0; i < n; i++) {
  129.         ElementSpec es = data[i];
  130.         if (es.getLength() > 0) {
  131.             c.insertString(pos, new String(es.getArray(), es.getOffset(), 
  132.                            es.getLength()));
  133.             pos += es.getLength();
  134.         }
  135.         }
  136.  
  137.         // build the element structure
  138.         int length = pos - offset;
  139.         DefaultDocumentEvent evnt = 
  140.           new DefaultDocumentEvent(offset, length, DocumentEvent.EventType.CHANGE);
  141.         buffer.insert(offset, length, data, evnt);
  142.         evnt.end();
  143.         fireInsertUpdate(evnt);
  144.         fireUndoableEditUpdate(new UndoableEditEvent(this, evnt));
  145.     } finally {
  146.         writeUnlock();
  147.     }
  148.     }
  149.  
  150.     /**
  151.      * Adds a new style into the logical style hierarchy.  Style attributes
  152.      * resolve from bottom up so an attribute specified in a child
  153.      * will override an attribute specified in the parent.
  154.      *
  155.      * @param nm   the name of the style (must be unique within the
  156.      *   collection of named styles).  The name may be null if the style 
  157.      *   is unnamed, but the caller is responsible
  158.      *   for managing the reference returned as an unnamed style can't
  159.      *   be fetched by name.  An unnamed style may be useful for things
  160.      *   like character attribute overrides such as found in a style 
  161.      *   run.
  162.      * @param parent the parent style.  This may be null if unspecified
  163.      *   attributes need not be resolved in some other style.
  164.      * @return the style
  165.      */
  166.     public Style addStyle(String nm, Style parent) {
  167.     StyleContext styles = (StyleContext) getAttributeContext();
  168.     return styles.addStyle(nm, parent);
  169.     }
  170.  
  171.     /**
  172.      * Removes a named style previously added to the document.  
  173.      *
  174.      * @param nm  the name of the style to remove
  175.      */
  176.     public void removeStyle(String nm) {
  177.     StyleContext styles = (StyleContext) getAttributeContext();
  178.     styles.removeStyle(nm);
  179.     }
  180.  
  181.     /**
  182.      * Fetches a named style previously added.
  183.      *
  184.      * @param nm  the name of the style
  185.      * @return the style
  186.      */
  187.     public Style getStyle(String nm) {
  188.     StyleContext styles = (StyleContext) getAttributeContext();
  189.     return styles.getStyle(nm);
  190.     }
  191.  
  192.     /**
  193.      * Sets the logical style to use for the paragraph at the
  194.      * given position.  If attributes aren't explicitly set 
  195.      * for character and paragraph attributes they will resolve 
  196.      * through the logical style assigned to the paragraph, which
  197.      * in turn may resolve through some hierarchy completely 
  198.      * independent of the element hierarchy in the document.
  199.      * <p>
  200.      * This method is thread safe, although most Swing methods
  201.      * are not. Please see 
  202.      * <A HREF="http://java.sun.com/products/jfc/swingdoc/threads.html">Threads
  203.      * and Swing</A> for more information.     
  204.      *
  205.      * @param pos the offset from the start of the document
  206.      * @param s  the logical style to assign to the paragraph
  207.      */
  208.     public void setLogicalStyle(int pos, Style s) {
  209.     Element paragraph = getParagraphElement(pos);
  210.     if ((paragraph != null) && (paragraph instanceof AbstractElement)) {
  211.         try {
  212.         writeLock();
  213.         StyleChangeUndoableEdit edit = new StyleChangeUndoableEdit((AbstractElement)paragraph, s);
  214.         ((AbstractElement)paragraph).setResolveParent(s);
  215.         int p0 = paragraph.getStartOffset();
  216.         int p1 = paragraph.getEndOffset();
  217.         DefaultDocumentEvent e = 
  218.           new DefaultDocumentEvent(p0, p1 - p0, DocumentEvent.EventType.CHANGE);
  219.         e.addEdit(edit);
  220.         e.end();
  221.         fireChangedUpdate(e);
  222.         fireUndoableEditUpdate(new UndoableEditEvent(this, e));
  223.         } finally {
  224.         writeUnlock();
  225.         }
  226.     }
  227.     }
  228.  
  229.     /** 
  230.      * Fetches the logical style assigned to the paragraph 
  231.      * represented by the given position.
  232.      *
  233.      * @param p the location to translate to a paragraph
  234.      *  and determine the logical style assigned.  This
  235.      *  is an offset from the start of the document.
  236.      * @return the style
  237.      */
  238.     public Style getLogicalStyle(int p) {
  239.     Style s = null;
  240.     Element paragraph = getParagraphElement(p);
  241.     if (paragraph != null) {
  242.         AttributeSet a = paragraph.getAttributes();
  243.         s = (Style) a.getResolveParent();
  244.     }
  245.     return s;
  246.     }
  247.  
  248.     /**
  249.      * Sets attributes for some part of the document.
  250.      * A write lock is held by this operation while changes
  251.      * are being made, and a DocumentEvent is sent to the listeners 
  252.      * after the change has been successfully completed.
  253.      * <p>
  254.      * This method is thread safe, although most Swing methods
  255.      * are not. Please see 
  256.      * <A HREF="http://java.sun.com/products/jfc/swingdoc/threads.html">Threads
  257.      * and Swing</A> for more information.     
  258.      *
  259.      * @param offset the offset in the document
  260.      * @param length the length
  261.      * @param s the attributes
  262.      * @param replace true if the previous attributes should be replaced
  263.      *  before setting the new attributes
  264.      */
  265.     public void setCharacterAttributes(int offset, int length, AttributeSet s, boolean replace) {
  266.     try {
  267.         writeLock();
  268.         DefaultDocumentEvent changes = 
  269.         new DefaultDocumentEvent(offset, length, DocumentEvent.EventType.CHANGE);
  270.  
  271.         // split elements that need it
  272.         buffer.change(offset, length, changes);
  273.  
  274.         AttributeSet sCopy = s.copyAttributes();
  275.  
  276.         // PENDING(prinz) - this isn't a very efficient way to iterate
  277.         int lastEnd = Integer.MAX_VALUE;
  278.         for (int pos = offset; pos < (offset + length); pos = lastEnd) {
  279.         Element run = getCharacterElement(pos);
  280.         lastEnd = run.getEndOffset();
  281.         MutableAttributeSet attr = (MutableAttributeSet) run.getAttributes();
  282.         changes.addEdit(new AttributeUndoableEdit(run, sCopy, replace));
  283.         if (replace) {
  284.             attr.removeAttributes(attr);
  285.         }
  286.         attr.addAttributes(s);
  287.         }
  288.         changes.end();
  289.         fireChangedUpdate(changes);
  290.         fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
  291.     } finally {
  292.         writeUnlock();
  293.     }
  294.  
  295.     }
  296.  
  297.     /**
  298.      * Sets attributes for a paragraph.
  299.      * <p>
  300.      * This method is thread safe, although most Swing methods
  301.      * are not. Please see 
  302.      * <A HREF="http://java.sun.com/products/jfc/swingdoc/threads.html">Threads
  303.      * and Swing</A> for more information.     
  304.      *
  305.      * @param offset the offset into the paragraph
  306.      * @param length the number of characters affected
  307.      * @param s the attributes
  308.      * @param replace whether to replace existing attributes, or merge them
  309.      */
  310.     public void setParagraphAttributes(int offset, int length, AttributeSet s, 
  311.                        boolean replace) {
  312.     try {
  313.         writeLock();
  314.         DefaultDocumentEvent changes = 
  315.         new DefaultDocumentEvent(offset, length, DocumentEvent.EventType.CHANGE);
  316.  
  317.         AttributeSet sCopy = s.copyAttributes();
  318.  
  319.         // PENDING(prinz) - this assumes a particular element structure
  320.         Element section = getDefaultRootElement();
  321.         int index0 = section.getElementIndex(offset);
  322.         int index1 = section.getElementIndex(offset + ((length > 0) ? length - 1 : 0));
  323.         for (int i = index0; i <= index1; i++) {
  324.         Element paragraph = section.getElement(i);
  325.         MutableAttributeSet attr = (MutableAttributeSet) paragraph.getAttributes();
  326.         changes.addEdit(new AttributeUndoableEdit(paragraph, sCopy, replace));
  327.         if (replace) {
  328.             attr.removeAttributes(attr);
  329.         }
  330.         attr.addAttributes(s);
  331.         }
  332.         changes.end();
  333.         fireChangedUpdate(changes);
  334.         fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
  335.     } finally {
  336.         writeUnlock();
  337.     }
  338.     }
  339.  
  340.     /**
  341.      * Gets a paragraph element.
  342.      *
  343.      * @param pos the starting offset
  344.      * @return the element
  345.      */
  346.     public Element getParagraphElement(int pos) {
  347.     Element section = getDefaultRootElement();
  348.     int index = section.getElementIndex(pos);
  349.     Element paragraph = section.getElement(index);
  350.     return paragraph;
  351.     }
  352.  
  353.     /**
  354.      * Gets a character element based on a position.
  355.      *
  356.      * @param pos the position in the document
  357.      * @return the element
  358.      */
  359.     public Element getCharacterElement(int pos) {
  360.     Element e = null;
  361.     for (e = getDefaultRootElement(); ! e.isLeaf(); ) {
  362.         int index = e.getElementIndex(pos);
  363.         e = e.getElement(index);
  364.     }
  365.     return e;
  366.     }
  367.  
  368.     // --- local methods -------------------------------------------------
  369.  
  370.     /**
  371.      * Updates document structure as a result of text insertion.  This
  372.      * will happen within a write lock.  This implementation simply
  373.      * parses the inserted content for line breaks and builds up a set
  374.      * of instructions for the element buffer.
  375.      *
  376.      * @param chng a description of the document change
  377.      * @param attr the attributes
  378.      */
  379.     protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) {
  380.     int offset = chng.getOffset();
  381.     int length = chng.getLength();
  382.     if (attr == null) {
  383.         attr = SimpleAttributeSet.EMPTY;
  384.     }
  385.  
  386.     Element paragraph = getParagraphElement(offset);
  387.     Element run = paragraph.getElement(paragraph.getElementIndex(offset));
  388.     AttributeSet pattr = paragraph.getAttributes();
  389.     AttributeSet cattr = run.getAttributes();
  390.  
  391.     try {
  392.         boolean breakAtStart = false;
  393.         boolean breakAtEnd = false;
  394.         Segment s = new Segment();
  395.         Vector parseBuffer = new Vector();
  396.         getText(offset, length, s);
  397.         char[] txt = s.array;
  398.         int n = s.offset + s.count;
  399.         int lastOffset = s.offset;
  400.         for (int i = s.offset; i < n; i++) {
  401.         if (txt[i] == '\n') {
  402.             int breakOffset = i + 1;
  403.             parseBuffer.addElement(
  404.                         new ElementSpec(attr, ElementSpec.ContentType,
  405.                            breakOffset - lastOffset));
  406.             parseBuffer.addElement(
  407.                         new ElementSpec(null, ElementSpec.EndTagType));
  408.             parseBuffer.addElement(
  409.                         new ElementSpec(pattr, ElementSpec.StartTagType));
  410.             lastOffset = breakOffset;
  411.         }
  412.         }
  413.         if (lastOffset < n) {
  414.         parseBuffer.addElement(
  415.                     new ElementSpec(attr, ElementSpec.ContentType,
  416.                        n - lastOffset));
  417.         } else {
  418.         breakAtEnd = true;
  419.         }
  420.         if (offset > 0) {
  421.         getText(offset - 1, 1, s);
  422.         if (s.array[s.offset] == '\n') {
  423.             breakAtStart = true;
  424.             ElementSpec spec = new ElementSpec(pattr, ElementSpec.StartTagType);
  425.             parseBuffer.insertElementAt(spec, 0);
  426.             spec = new ElementSpec(pattr, ElementSpec.EndTagType);
  427.             parseBuffer.insertElementAt(spec, 0);
  428.         }
  429.         }
  430.         ElementSpec first = (ElementSpec) parseBuffer.firstElement();
  431.         if ((breakAtStart == false) && cattr.isEqual(attr) && (offset > 0)) {
  432.         first.setDirection(ElementSpec.JoinPreviousDirection);
  433.         }
  434.         if (((parseBuffer.size() > 1) || 
  435.          (first.getDirection() != ElementSpec.JoinPreviousDirection)) && 
  436.         (breakAtEnd == false)) {
  437.  
  438.         ElementSpec last = (ElementSpec) parseBuffer.lastElement();
  439.         if (run.getEndOffset() <= (offset + length)) {
  440.             cattr = getCharacterElement(offset+length).getAttributes();
  441.         }
  442.         if (cattr.isEqual(attr)) {
  443.             last.setDirection(ElementSpec.JoinNextDirection);
  444.         }
  445.         }
  446.  
  447.         ElementSpec[] spec = new ElementSpec[parseBuffer.size()];
  448.         parseBuffer.copyInto(spec);
  449.         buffer.insert(offset, length, spec, chng);
  450.     } catch (BadLocationException bl) {
  451.     }
  452.     }
  453.  
  454.     /**
  455.      * Updates document structure as a result of text removal.
  456.      *
  457.      * @param chng a description of the document change
  458.      */
  459.     protected void removeUpdate(DefaultDocumentEvent chng) {
  460.     buffer.remove(chng.getOffset(), chng.getLength(), chng);
  461.     }
  462.  
  463.     /**
  464.      * Creates the root element to be used to represent the
  465.      * default document structure.
  466.      *
  467.      * @return the element base
  468.      */
  469.     protected AbstractElement createDefaultRoot() {
  470.     // grabs a write-lock for this initialization and
  471.     // abandon it during initialization so in normal
  472.     // operation we can detect an illegitimate attempt
  473.     // to mutate attributes.
  474.     writeLock();
  475.     BranchElement section = new SectionElement();
  476.     BranchElement paragraph = new BranchElement(section, null);
  477.  
  478.     LeafElement brk = new LeafElement(paragraph, null, 0, 1);
  479.     Element[] buff = new Element[1];
  480.     buff[0] = brk;
  481.     paragraph.replace(0, 0, buff);
  482.  
  483.     buff[0] = paragraph;
  484.     section.replace(0, 0, buff);
  485.     writeUnlock();
  486.     return section;
  487.     }
  488.  
  489.     /**
  490.      * Gets the foreground color from an attribute set.
  491.      *
  492.      * @param attr the attribute set
  493.      * @return the color
  494.      */
  495.     public Color getForeground(AttributeSet attr) {
  496.     return StyleConstants.getForeground(attr);
  497.     }
  498.  
  499.     /**
  500.      * Gets the background color from an attribute set.
  501.      *
  502.      * @param attr the attribute set
  503.      * @return the color
  504.      */
  505.     public Color getBackground(AttributeSet attr) {
  506.     throw new Error("not implemented");
  507.     }
  508.  
  509.     /**
  510.      * Gets the font from an attribute set.
  511.      *
  512.      * @param attr the attribute set
  513.      * @return the font
  514.      */
  515.     public Font getFont(AttributeSet attr) {
  516.     StyleContext styles = (StyleContext) getAttributeContext();
  517.     return styles.getFont(attr);
  518.     }
  519.  
  520.     // --- member variables -----------------------------------------------------------
  521.  
  522.     /**
  523.      * The default size of the initial content buffer.
  524.      */
  525.     public static final int BUFFER_SIZE_DEFAULT = 4096;
  526.  
  527.     private ElementBuffer buffer;
  528.  
  529.     /**
  530.      * Default root element for a document... maps out the 
  531.      * paragraphs/lines contained.
  532.      * <p>
  533.      * Warning: serialized objects of this class will not be compatible with
  534.      * future swing releases.  The current serialization support is appropriate
  535.      * for short term storage or RMI between Swing1.0 applications.  It will
  536.      * not be possible to load serialized Swing1.0 objects with future releases
  537.      * of Swing.  The JDK1.2 release of Swing will be the compatibility
  538.      * baseline for the serialized form of Swing objects.
  539.      */
  540.     protected class SectionElement extends BranchElement {
  541.  
  542.         /**
  543.          * Creates a new SectionElement.
  544.          */
  545.     public SectionElement() {
  546.         super(null, null);
  547.     }
  548.  
  549.         /**
  550.          * Gets the name of the element.
  551.          *
  552.          * @return the name
  553.          */
  554.         public String getName() {
  555.         return SectionElementName;
  556.     }
  557.     }
  558.  
  559.     /**
  560.      * Specification for building elements.
  561.      * <p>
  562.      * Warning: serialized objects of this class will not be compatible with
  563.      * future swing releases.  The current serialization support is appropriate
  564.      * for short term storage or RMI between Swing1.0 applications.  It will
  565.      * not be possible to load serialized Swing1.0 objects with future releases
  566.      * of Swing.  The JDK1.2 release of Swing will be the compatibility
  567.      * baseline for the serialized form of Swing objects.
  568.      */
  569.     public static class ElementSpec {
  570.  
  571.     /**
  572.      * A possible value for getType.  This specifies
  573.      * that this record type is a start tag and
  574.      * represents markup that specifies the start
  575.      * of an element.
  576.      */
  577.     public static final short StartTagType = 1;
  578.     
  579.     /**
  580.      * A possible value for getType.  This specifies
  581.      * that this record type is a end tag and
  582.      * represents markup that specifies the end
  583.      * of an element.
  584.      */
  585.     public static final short EndTagType = 2;
  586.  
  587.     /**
  588.      * A possible value for getType.  This specifies
  589.      * that this record type represents content.
  590.      */
  591.     public static final short ContentType = 3;
  592.     
  593.     /**
  594.      * A possible value for getDirection.  This specifies
  595.      * that the data associated with this record should
  596.      * be joined to what precedes it.
  597.      */
  598.     public static final short JoinPreviousDirection = 4;
  599.     
  600.     /**
  601.      * A possible value for getDirection.  This specifies
  602.      * that the data associated with this record should
  603.      * be joined to what follows it.
  604.      */
  605.     public static final short JoinNextDirection = 5;
  606.     
  607.     /**
  608.      * A possible value for getDirection.  This specifies
  609.      * that the data associated with this record should
  610.      * be used to originate a new element.  This would be
  611.      * the normal value.
  612.      */
  613.     public static final short OriginateDirection = 6;
  614.  
  615.     
  616.     /**
  617.      * Constructor useful for markup when the markup will not
  618.      * be stored in the document.
  619.          *
  620.          * @param a the attributes for the element
  621.          * @param type the type of the element
  622.      */
  623.     public ElementSpec(AttributeSet a, short type) {
  624.         this(a, type, null, 0, 0);
  625.     }
  626.  
  627.     /**
  628.      * Constructor for parsing inside the document when
  629.      * the data has already been added, but len information
  630.      * is needed.
  631.          *
  632.          * @param a the attributes for the element
  633.          * @param type the type of the element
  634.          * @param len the length
  635.      */
  636.     public ElementSpec(AttributeSet a, short type, int len) {
  637.         this(a, type, null, 0, len);
  638.     }
  639.  
  640.     /**
  641.      * Constructor for creating a spec externally for batch
  642.      * input of content and markup into the document.
  643.          *
  644.          * @param a the attributes for the element
  645.          * @param type the element type
  646.          * @param txt the text for the element
  647.          * @param offs the offset into the text
  648.          * @param len the length of the text
  649.      */
  650.         public ElementSpec(AttributeSet a, short type, char[] txt, 
  651.                   int offs, int len) {
  652.         attr = a;
  653.         this.type = type;
  654.         this.data = txt;
  655.         this.offs = offs;
  656.         this.len = len;
  657.         this.direction = OriginateDirection;
  658.     }
  659.  
  660.         /**
  661.          * Sets the element type.
  662.          *
  663.          * @param type the type
  664.          */
  665.     public void setType(short type) {
  666.         this.type = type;
  667.     }
  668.  
  669.         /**
  670.          * Gets the element type.
  671.          *
  672.          * @return the type
  673.          */
  674.     public short getType() {
  675.         return type;
  676.     }
  677.  
  678.         /**
  679.          * Sets the direction.
  680.          *
  681.          * @param direction the direction
  682.          */
  683.     public void setDirection(short direction) {
  684.         this.direction = direction;
  685.     }
  686.  
  687.         /**
  688.          * Gets the direction.
  689.          *
  690.          * @return the direction
  691.          */
  692.     public short getDirection() {
  693.         return direction;
  694.     }
  695.  
  696.         /**
  697.          * Gets the element attributes.
  698.          *
  699.          * @return the attribute set
  700.          */
  701.     public AttributeSet getAttributes() {
  702.         return attr;
  703.     }
  704.  
  705.         /**
  706.          * Gets the array of characters.
  707.          *
  708.          * @return the array
  709.          */
  710.     public char[] getArray() {
  711.         return data;
  712.     }
  713.  
  714.  
  715.         /**
  716.          * Gets the starting offset.
  717.          *
  718.          * @return the offset
  719.          */
  720.     public int getOffset() {
  721.         return 0;
  722.     }
  723.  
  724.         /**
  725.          * Gets the length.
  726.          *
  727.          * @return the length
  728.          */
  729.     public int getLength() {
  730.         return len;
  731.     }
  732.  
  733.         /**
  734.          * Converts the element to a string.
  735.          *
  736.          * @return the string
  737.          */
  738.         public String toString() {
  739.         String tlbl = "??";
  740.         String plbl = "??";
  741.         switch(type) {
  742.         case StartTagType:
  743.         tlbl = "StartTag";
  744.         break;
  745.         case ContentType:
  746.         tlbl = "Content";
  747.         break;
  748.         case EndTagType:
  749.         tlbl = "EndTag";
  750.         break;
  751.         }
  752.         switch(direction) {
  753.         case JoinPreviousDirection:
  754.         plbl = "JoinPrevious";
  755.         break;
  756.         case JoinNextDirection:
  757.         plbl = "JoinNext";
  758.         break;
  759.         case OriginateDirection:
  760.         plbl = "Originate";
  761.         break;
  762.         }
  763.         return tlbl + ":" + plbl + ":" + getLength();
  764.     }
  765.         
  766.     private AttributeSet attr;
  767.     private int len;
  768.     private short type;
  769.     private short direction;
  770.  
  771.     private int offs;
  772.     private char[] data;
  773.     }
  774.  
  775.     /**
  776.      * Class to manage changes to the element
  777.      * hierarchy.
  778.      * <p>
  779.      * Warning: serialized objects of this class will not be compatible with
  780.      * future swing releases.  The current serialization support is appropriate
  781.      * for short term storage or RMI between Swing1.0 applications.  It will
  782.      * not be possible to load serialized Swing1.0 objects with future releases
  783.      * of Swing.  The JDK1.2 release of Swing will be the compatibility
  784.      * baseline for the serialized form of Swing objects.
  785.      */
  786.     public class ElementBuffer implements Serializable {
  787.  
  788.         /**
  789.          * Creates a new ElementBuffer.
  790.          *
  791.          * @param root the root element
  792.          */
  793.     public ElementBuffer(Element root) {
  794.         this.root = root;
  795.         changes = new Vector();
  796.         path = new Stack();
  797.         endJoin = new Vector();
  798.     }
  799.  
  800.         /**
  801.          * Gets the root element.
  802.          *
  803.          * @return the root element
  804.          */
  805.         public Element getRootElement() {
  806.         return root;
  807.     }
  808.  
  809.         /**
  810.          * Inserts new content.
  811.          *
  812.          * @param offset the starting offset
  813.          * @param length the length
  814.          * @param data the data to insert
  815.          * @param de the event capturing this edit
  816.          */
  817.     public final void insert(int offset, int length, ElementSpec[] data,
  818.                  DefaultDocumentEvent de) {
  819.         insertOp = true;
  820.         beginEdits(offset, length);
  821.         insertUpdate(data);
  822.         endEdits(de);
  823.         insertOp = false;
  824.     }
  825.  
  826.         /**
  827.          * Removes content.
  828.          *
  829.          * @param offset the starting offset
  830.          * @param length the length
  831.          * @param de the event capturing this edit
  832.          */
  833.     public final void remove(int offset, int length, DefaultDocumentEvent de) {
  834.         beginEdits(offset, length);
  835.         removeUpdate();
  836.         endEdits(de);
  837.     }
  838.  
  839.         /**
  840.          * Changes content.
  841.          *
  842.          * @param offset the starting offset
  843.          * @param length the length
  844.          * @param de the event capturing this edit
  845.          */
  846.         public final void change(int offset, int length, DefaultDocumentEvent de) {
  847.         beginEdits(offset, length);
  848.         changeUpdate();
  849.         endEdits(de);
  850.     }
  851.  
  852.         /**
  853.          * Inserts an update into the document.
  854.          *
  855.          * @param data the elements to insert
  856.          */
  857.     protected void insertUpdate(ElementSpec[] data) {
  858.         // push the path
  859.         Element elem = root;
  860.         int index = elem.getElementIndex(offset);
  861.         while (! elem.isLeaf()) {
  862.         Element child = elem.getElement(index);
  863.         push(elem, (child.isLeaf() ? index : index+1));
  864.         elem = child;
  865.         index = elem.getElementIndex(offset);
  866.         }
  867.  
  868.         // open a hole to inject new elements (if needed)
  869.         open(data);
  870.  
  871.         // fold in the specified subtree
  872.         int n = data.length;
  873.         for (int i = 0; i < n; i++) {
  874.         insertElement(data[i]);
  875.         }
  876.  
  877.         // close up the hole in the tree
  878.         close();
  879.  
  880.         // pop the remaining path
  881.         while (path.size() != 0) {
  882.         pop();
  883.         }
  884.     }
  885.  
  886.     /**
  887.      * Updates the element structure in response to a removal from the
  888.      * associated sequence in the document.  Any elements consumed by the
  889.      * span of the removal are removed.  
  890.      */
  891.     protected void removeUpdate() {
  892.         removeElements(root, offset, offset + length);
  893.     }
  894.  
  895.         /**
  896.          * Updates the element structure in response to a change in the
  897.          * document.
  898.          */
  899.         protected void changeUpdate() {
  900.         boolean didEnd = split(offset, length);
  901.         if (! didEnd) {
  902.         // need to do the other end
  903.         while (path.size() != 0) {
  904.             pop();
  905.         }
  906.         split(offset + length, 0);
  907.         }
  908.         while (path.size() != 0) {
  909.         pop();
  910.         }
  911.     }
  912.  
  913.     boolean split(int offs, int len) {
  914.         boolean splitEnd = false;
  915.         // push the path
  916.         Element e = root;
  917.         int index = e.getElementIndex(offs);
  918.         while (! e.isLeaf()) {
  919.         push(e, index);
  920.         e = e.getElement(index);
  921.         index = e.getElementIndex(offs);
  922.         }
  923.  
  924.         ElemChanges ec = (ElemChanges) path.peek();
  925.         Element child = ec.parent.getElement(ec.index);
  926.         // make sure there is something to do... if the
  927.         // offset is already at a boundry then there is 
  928.         // nothing to do.
  929.         if (child.getStartOffset() != offs) {
  930.         // we need to split, now see if the other end is within
  931.         // the same parent.
  932.         int index0 = ec.index;
  933.         int index1 = index0;
  934.         if (((offs + len) < ec.parent.getEndOffset()) && (len != 0)) {
  935.             // it's a range split in the same parent
  936.             index1 = ec.parent.getElementIndex(offs+len);
  937.             if (index1 == index0) {
  938.             // it's a three-way split
  939.             ec.removed.addElement(child);
  940.             e = createLeafElement(ec.parent, child.getAttributes(),
  941.                           child.getStartOffset(), offs);
  942.             ec.added.addElement(e);
  943.             e = createLeafElement(ec.parent, child.getAttributes(),
  944.                       offs, offs + len);
  945.             ec.added.addElement(e);
  946.             e = createLeafElement(ec.parent, child.getAttributes(),
  947.                           offs + len, child.getEndOffset());
  948.             ec.added.addElement(e);
  949.             return true;
  950.             } else {
  951.             child = ec.parent.getElement(index1);
  952.             if ((offs + len) == child.getStartOffset()) {
  953.                 // end is already on a boundry
  954.                 index1 = index0;
  955.             }
  956.             }
  957.             splitEnd = true;
  958.         }
  959.  
  960.         // split the first location
  961.         pos = offs;
  962.         child = ec.parent.getElement(index0);
  963.         ec.removed.addElement(child);
  964.         e = createLeafElement(ec.parent, child.getAttributes(),
  965.                       child.getStartOffset(), pos);
  966.         ec.added.addElement(e);
  967.         e = createLeafElement(ec.parent, child.getAttributes(),
  968.                       pos, child.getEndOffset());
  969.         ec.added.addElement(e);
  970.  
  971.         // pick up things in the middle
  972.         for (int i = index0 + 1; i < index1; i++) {
  973.             child = ec.parent.getElement(i);
  974.             ec.removed.addElement(child);
  975.             ec.added.addElement(child);
  976.         }
  977.  
  978.         if (index1 != index0) {
  979.             child = ec.parent.getElement(index1);
  980.             pos = offs + len;
  981.             ec.removed.addElement(child);
  982.             e = createLeafElement(ec.parent, child.getAttributes(),
  983.                       child.getStartOffset(), pos);
  984.             ec.added.addElement(e);
  985.             e = createLeafElement(ec.parent, child.getAttributes(),
  986.                       pos, child.getEndOffset());
  987.             ec.added.addElement(e);
  988.         }
  989.         }
  990.         return splitEnd;
  991.     }
  992.  
  993.     /**
  994.      * Creates the UndoableEdit record for the edits made
  995.      * in the buffer.
  996.      */
  997.     void endEdits(DefaultDocumentEvent de) {
  998.         int n = changes.size();
  999.         for (int i = 0; i < n; i++) {
  1000.         ElemChanges ec = (ElemChanges) changes.elementAt(i);
  1001.         Element[] removed = new Element[ec.removed.size()];
  1002.         ec.removed.copyInto(removed);
  1003.         Element[] added = new Element[ec.added.size()];
  1004.         ec.added.copyInto(added);
  1005.         int index = ec.index;
  1006.         ((BranchElement) ec.parent).replace(index, removed.length, added);
  1007.         ElementEdit ee = new ElementEdit((BranchElement) ec.parent, 
  1008.                          index, removed, added);
  1009.         de.addEdit(ee);
  1010.         }
  1011.         
  1012.         /*
  1013.         for (int i = 0; i < n; i++) {
  1014.         ElemChanges ec = (ElemChanges) changes.elementAt(i);
  1015.         System.err.print("edited: " + ec.parent + " at: " + ec.index +
  1016.             " removed " + ec.removed.size());
  1017.         if (ec.removed.size() > 0) {
  1018.             int r0 = ((Element) ec.removed.firstElement()).getStartOffset();
  1019.             int r1 = ((Element) ec.removed.lastElement()).getEndOffset();
  1020.             System.err.print("[" + r0 + "," + r1 + "]");
  1021.         }
  1022.         System.err.print(" added " + ec.added.size());
  1023.         if (ec.added.size() > 0) {
  1024.             int p0 = ((Element) ec.added.firstElement()).getStartOffset();
  1025.             int p1 = ((Element) ec.added.lastElement()).getEndOffset();
  1026.             System.err.print("[" + p0 + "," + p1 + "]");
  1027.         }
  1028.         System.err.println("");
  1029.         }
  1030.         */
  1031.     }
  1032.  
  1033.     /**
  1034.      * Initialize the buffer
  1035.      */
  1036.     void beginEdits(int offset, int length) {
  1037.         this.offset = offset;
  1038.         this.length = length;
  1039.         pos = offset;
  1040.         if (changes == null) {
  1041.         changes = new Vector();
  1042.         } else {
  1043.         changes.removeAllElements();
  1044.         }
  1045.         if (path == null) {
  1046.         path = new Stack();
  1047.         } else {
  1048.         path.removeAllElements();
  1049.         }
  1050.         if (endJoin == null) {
  1051.         endJoin = new Vector();
  1052.         } else {
  1053.         endJoin.removeAllElements();
  1054.         }
  1055.     }
  1056.  
  1057.     /**
  1058.      * Pushes a new element onto the stack that represents
  1059.      * the current path.
  1060.      * @param record Whether or not the push should be
  1061.      *  recorded as an element change or not.
  1062.      */
  1063.     void push(Element e, int index) {
  1064.         ElemChanges old = (ElemChanges) ((path.size() != 0) ? path.peek() : null);
  1065.         ElemChanges ec = new ElemChanges(e, index);
  1066.         path.push(ec);
  1067.     }
  1068.  
  1069.     void pop() {
  1070.         ElemChanges ec = (ElemChanges) path.peek();
  1071.         path.pop();
  1072.         if ((ec.added.size() > 0) || (ec.removed.size() > 0)) {
  1073.         changes.addElement(ec);
  1074.         } else if (! path.isEmpty()) {
  1075.         // if we pushed a branch element that didn't get
  1076.         // used, make sure its not marked as having been added.
  1077.         Element e = ec.parent;
  1078.         ec = (ElemChanges) path.peek();
  1079.         ec.added.removeElement(e);
  1080.         }
  1081.     }
  1082.  
  1083.     /**
  1084.      * move the current offset forward by n.
  1085.      */
  1086.     void advance(int n) {
  1087.         pos += n;
  1088.     }
  1089.  
  1090.     void insertElement(ElementSpec es) {
  1091.         ElemChanges ec = (ElemChanges) path.peek();
  1092.         switch(es.getType()) {
  1093.         case ElementSpec.StartTagType:
  1094.         Element belem = createBranchElement(ec.parent, es.getAttributes());
  1095.         ec.added.addElement(belem);
  1096.         push(belem, 0);
  1097.         break;
  1098.         case ElementSpec.EndTagType:
  1099.         pop();
  1100.         break;
  1101.         case ElementSpec.ContentType:
  1102.           int len = es.getLength();
  1103.         if (es.getDirection() != ElementSpec.JoinPreviousDirection) {
  1104.             Element leaf = createLeafElement(ec.parent, es.getAttributes(), 
  1105.                              pos, pos + len);
  1106.             ec.added.addElement(leaf);
  1107.         }
  1108.         pos += len;
  1109.         break;
  1110.         }
  1111.     }
  1112.         
  1113.     void removeElements(Element elem, int rmOffs0, int rmOffs1) {
  1114.         if (! elem.isLeaf()) {
  1115.         // update path for changes
  1116.         int index0 = elem.getElementIndex(rmOffs0);
  1117.         int index1 = elem.getElementIndex(rmOffs1);
  1118.         push(elem, index0);
  1119.  
  1120.         // if the range is contained by one element,
  1121.         // we just forward the request
  1122.         if (index0 == index1) {
  1123.             removeElements(elem.getElement(index0), rmOffs0, rmOffs1);
  1124.         } else {
  1125.             // the removal range spans elements.  If we can join
  1126.             // the two endpoints, do it.  Otherwise we remove the
  1127.             // interior and forward to the endpoints.
  1128.             Element child0 = elem.getElement(index0);
  1129.             Element child1 = elem.getElement(index1);
  1130.             ElemChanges ec = (ElemChanges) path.peek();
  1131.             if (canJoin(child0, child1)) {
  1132.             // remove and join
  1133.             for (int i = index0; i <= index1; i++) {
  1134.                 ec.removed.addElement(elem.getElement(i));
  1135.             }
  1136.             Element e = join(elem, child0, child1, rmOffs0, rmOffs1);
  1137.             ec.added.addElement(e);
  1138.             } else {
  1139.             // remove interior and forward
  1140.             int rmIndex0 = index0 + 1;
  1141.             int rmIndex1 = index1 - 1;
  1142.             if (child0.getStartOffset() == rmOffs0) {
  1143.                 // start element completely consumed
  1144.                 child0 = null;
  1145.                 rmIndex0 = index0;
  1146.             }
  1147.             if (child1.getStartOffset() == rmOffs1) {
  1148.                 // end element not touched
  1149.                 child1 = null;
  1150.             }
  1151.             if (rmIndex0 <= rmIndex1) {
  1152.                 ec.index = rmIndex0;
  1153.             }
  1154.             for (int i = rmIndex0; i <= rmIndex1; i++) {
  1155.                 ec.removed.addElement(elem.getElement(i));
  1156.             }
  1157.             if (child0 != null) {
  1158.                 removeElements(child0, rmOffs0, rmOffs1);
  1159.             }
  1160.             if (child1 != null) {
  1161.                 removeElements(child1, rmOffs0, rmOffs1);
  1162.             }
  1163.             }
  1164.         }
  1165.  
  1166.         // publish changes
  1167.         pop();
  1168.         }
  1169.     }
  1170.  
  1171.     /**
  1172.      * Can the two given elements be coelesced together
  1173.      * into one element?
  1174.      */
  1175.     boolean canJoin(Element e0, Element e1) {
  1176.         if ((e0 == null) || (e1 == null)) {
  1177.         return false;
  1178.         }
  1179.         if (e0.getName().equals(ParagraphElementName) &&
  1180.         e1.getName().equals(ParagraphElementName)) {
  1181.         return true;
  1182.         }
  1183.         return e0.getAttributes().isEqual(e1.getAttributes());
  1184.     }
  1185.  
  1186.     /** 
  1187.      * Joins the two elements carving out a hole for the
  1188.      * given removed range.
  1189.      */
  1190.     Element join(Element p, Element left, Element right, int rmOffs0, int rmOffs1) {
  1191.         if (left.isLeaf() && right.isLeaf()) {
  1192.         return createLeafElement(p, left.getAttributes(), left.getStartOffset(),
  1193.                      right.getEndOffset());
  1194.         } else if ((!left.isLeaf()) && (!right.isLeaf())) {
  1195.         // join two branch elements.  This copies the children before
  1196.         // the removal range on the left element, and after the removal
  1197.         // range on the right element.  The two elements on the edge
  1198.         // are joined if possible and needed.
  1199.         Element to = createBranchElement(p, left.getAttributes());
  1200.         int ljIndex = left.getElementIndex(rmOffs0);
  1201.         int rjIndex = right.getElementIndex(rmOffs1);
  1202.         Element lj = left.getElement(ljIndex);
  1203.         if (lj.getStartOffset() == rmOffs0) {
  1204.             lj = null; 
  1205.         }
  1206.         Element rj = right.getElement(rjIndex);
  1207.         if (rj.getStartOffset() == rmOffs1) {
  1208.             rj = null;
  1209.         }
  1210.         Vector children = new Vector();
  1211.  
  1212.         // transfer the left
  1213.         for (int i = 0; i < ljIndex; i++) {
  1214.             children.addElement(clone(to, left.getElement(i)));
  1215.         }
  1216.  
  1217.         // transfer the join/middle
  1218.         if (canJoin(lj, rj)) {
  1219.             Element e = join(to, lj, rj, rmOffs0, rmOffs1);
  1220.             children.addElement(e);
  1221.         } else {
  1222.             if (lj != null) {
  1223.             children.addElement(clone(to, lj));
  1224.             }
  1225.             if (rj != null) {
  1226.             children.addElement(clone(to, rj));
  1227.             }
  1228.         }
  1229.  
  1230.         // transfer the right
  1231.         int n = right.getElementCount();
  1232.         for (int i = (rj == null) ? rjIndex : rjIndex + 1; i < n; i++) {
  1233.             children.addElement(clone(to, right.getElement(i)));
  1234.         }
  1235.  
  1236.         // install the children
  1237.         Element[] c = new Element[children.size()];
  1238.         children.copyInto(c);
  1239.         ((BranchElement)to).replace(0, 0, c);
  1240.         return to;
  1241.         } else {
  1242.         throw new StateInvariantError(
  1243.             "No support to join leaf element with non-leaf element");
  1244.         }
  1245.     }
  1246.  
  1247.     /**
  1248.      * Creates a copy of this element, with a different 
  1249.      * parent.
  1250.          *
  1251.          * @param parent the parent element
  1252.          * @param clonee the element to be cloned
  1253.          * @return the copy
  1254.      */
  1255.         public Element clone(Element parent, Element clonee) {
  1256.         if (clonee.isLeaf()) {
  1257.         return createLeafElement(parent, clonee.getAttributes(), 
  1258.                      clonee.getStartOffset(), 
  1259.                      clonee.getEndOffset());
  1260.         }
  1261.         Element e = createBranchElement(parent, clonee.getAttributes());
  1262.         int n = clonee.getElementCount();
  1263.         Element[] children = new Element[n];
  1264.         for (int i = 0; i < n; i++) {
  1265.         children[i] = clone(e, clonee.getElement(i));
  1266.         }
  1267.         ((BranchElement)e).replace(0, 0, children);
  1268.         return e;
  1269.     }
  1270.  
  1271.     /**
  1272.      * open the tree to fold in this data
  1273.      */
  1274.     void open(ElementSpec[] data) {
  1275.         int firstOffs = (data[0].getDirection() == ElementSpec.JoinPreviousDirection) ?
  1276.         offset + data[0].getLength() : offset;
  1277.         int lastOffs = offset + length;
  1278.         // FIXME - should actually join when end is a JoinNextDirection
  1279.  
  1280.         ElemChanges ec = (ElemChanges) path.peek();
  1281.         Element child = ec.parent.getElement(ec.index);
  1282.         boolean hasPop = false;
  1283.         for (int i = 0; i < data.length; i++) {
  1284.         if (data[i].getType() == ElementSpec.EndTagType) {
  1285.             hasPop = true;
  1286.             break;
  1287.         }
  1288.         }
  1289.         if (hasPop || (firstOffs != lastOffs)) {
  1290.         // split the entry point
  1291.         ec.removed.addElement(child);
  1292.         if (firstOffs != child.getStartOffset()) {
  1293.             // put the area before the insertion back into
  1294.             // the tree (ie the part before the split).
  1295.             Element shortened = 
  1296.             createLeafElement(ec.parent, child.getAttributes(),
  1297.                       child.getStartOffset(), firstOffs);
  1298.             ec.added.addElement(shortened);
  1299.         }
  1300.         if (child.getEndOffset() > lastOffs) {
  1301.             // pick up the content after the hole to be returned
  1302.             // back to the tree.
  1303.             int len = child.getEndOffset() - lastOffs;
  1304.             ElementSpec spec = 
  1305.             new ElementSpec(child.getAttributes(), 
  1306.                            ElementSpec.ContentType, len);
  1307.             endJoin.addElement(spec);
  1308.         }
  1309.         if (hasPop) {
  1310.             // pick up the remaining content to be placed into a new
  1311.             // parent.
  1312.             int n = ec.parent.getElementCount();
  1313.             for (int i = ec.index + 1; i < n; i++) {
  1314.             child = ec.parent.getElement(i);
  1315.             ec.removed.addElement(child);
  1316.             int len = child.getEndOffset() - child.getStartOffset();
  1317.             ElementSpec spec = 
  1318.                 new ElementSpec(child.getAttributes(), 
  1319.                            ElementSpec.ContentType, len);
  1320.             endJoin.addElement(spec);
  1321.             }
  1322.         }
  1323.         if (hasPop && (endJoin.size() == 0)) {
  1324.             // join into next paragraph, pull the next paragraph
  1325.             // from the tree.
  1326.             ec = (ElemChanges) path.elementAt(path.size() - 2);
  1327.             Element e = ec.parent.getElement(ec.index);
  1328.             if (e != null) {
  1329.             ec.removed.addElement(e);
  1330.             int n = e.getElementCount();
  1331.             for (int i = 0; i < n; i++) {
  1332.                 child = e.getElement(i);
  1333.                 int len = child.getEndOffset() - child.getStartOffset();
  1334.                 ElementSpec spec = 
  1335.                 new ElementSpec(child.getAttributes(), 
  1336.                         ElementSpec.ContentType, len);
  1337.                 endJoin.addElement(spec);
  1338.             }
  1339.             }
  1340.         }
  1341.         }
  1342.     }
  1343.  
  1344.     /**
  1345.      * finish mending the right side of the subtree
  1346.      * inserted into the element hierarchy.
  1347.      */
  1348.     void close() {
  1349.         ElemChanges ec = (ElemChanges) path.peek();
  1350.         int n = endJoin.size();
  1351.         for (int i = 0; i < n; i++) {
  1352.         ElementSpec spec = (ElementSpec) endJoin.elementAt(i);
  1353.         int p1 = pos + spec.getLength();
  1354.         Element e = createLeafElement(ec.parent, spec.getAttributes(), pos, p1);
  1355.         ec.added.addElement(e);
  1356.         pos = p1;
  1357.         }
  1358.     }
  1359.  
  1360.     Element root;
  1361.     transient int pos;          // current position
  1362.     transient int offset;
  1363.     transient int length;
  1364.     transient Vector endJoin;  // Vector<ElementSpec>
  1365.     transient Vector changes;  // Vector<ElemChanges>
  1366.     transient Stack path;      // Stack<ElemChanges>
  1367.     transient boolean insertOp;
  1368.  
  1369.     /*
  1370.      * Internal record used to hold element change specifications
  1371.      */
  1372.     class ElemChanges {
  1373.         
  1374.         ElemChanges(Element parent, int index) {
  1375.         this.parent = parent;
  1376.         this.index = index;
  1377.         added = new Vector();
  1378.         removed = new Vector();
  1379.         }
  1380.     
  1381.             public String toString() {
  1382.         return "added: " + added + "\nremoved: " + removed + "\n";
  1383.         }
  1384.         
  1385.         Element parent;
  1386.         int index;
  1387.         Vector added;
  1388.         Vector removed;
  1389.     }    
  1390.  
  1391.     }
  1392.  
  1393.     /**
  1394.      * An UndoableEdit used to remember AttributeSet changes to an
  1395.      * Element.
  1396.      */
  1397.     static class AttributeUndoableEdit extends AbstractUndoableEdit {
  1398.     AttributeUndoableEdit(Element element, AttributeSet newAttributes,
  1399.                   boolean isReplacing) {
  1400.         super();
  1401.         this.element = element;
  1402.         this.newAttributes = newAttributes;
  1403.         this.isReplacing = isReplacing;
  1404.         // If not replacing, it may be more efficient to only copy the
  1405.         // changed values...
  1406.         copy = element.getAttributes().copyAttributes();
  1407.     }
  1408.  
  1409.     /**
  1410.      * Redoes a change.
  1411.      *
  1412.      * @exception CannotRedoException if the change cannot be redone
  1413.      */
  1414.         public void redo() throws CannotRedoException {
  1415.         super.redo();
  1416.         MutableAttributeSet as = (MutableAttributeSet)element
  1417.                              .getAttributes();
  1418.         if(isReplacing)
  1419.         as.removeAttributes(as);
  1420.         as.addAttributes(newAttributes);
  1421.     }
  1422.  
  1423.     /**
  1424.      * Undoes a change.
  1425.      *
  1426.      * @exception CannotUndoException if the change cannot be undone
  1427.      */
  1428.         public void undo() throws CannotUndoException {
  1429.         super.undo();
  1430.         MutableAttributeSet as = (MutableAttributeSet)element.getAttributes();
  1431.         as.removeAttributes(newAttributes);
  1432.         as.addAttributes(copy);
  1433.     }
  1434.  
  1435.     // AttributeSet containing additional entries, must be non-mutable!
  1436.     protected AttributeSet newAttributes;
  1437.     // Copy of the AttributeSet the Element contained.
  1438.     protected AttributeSet copy;
  1439.     // true if all the attributes in the element were removed first.
  1440.     protected boolean isReplacing;
  1441.     // Efected Element.
  1442.     protected Element element;
  1443.     }
  1444.  
  1445.     /**
  1446.      * UndoableEdit for changing the resolve parent of an Element.
  1447.      */
  1448.     static class StyleChangeUndoableEdit extends AbstractUndoableEdit {
  1449.     public StyleChangeUndoableEdit(AbstractElement element,
  1450.                        Style newStyle) {
  1451.         super();
  1452.         this.element = element;
  1453.         this.newStyle = newStyle;
  1454.         oldStyle = element.getResolveParent();
  1455.     }
  1456.  
  1457.     /**
  1458.      * Redoes a change.
  1459.      *
  1460.      * @exception CannotRedoException if the change cannot be redone
  1461.      */
  1462.         public void redo() throws CannotRedoException {
  1463.         super.redo();
  1464.         element.setResolveParent(newStyle);
  1465.     }
  1466.  
  1467.     /**
  1468.      * Undoes a change.
  1469.      *
  1470.      * @exception CannotUndoException if the change cannot be undone
  1471.      */
  1472.         public void undo() throws CannotUndoException {
  1473.         super.undo();
  1474.         element.setResolveParent(oldStyle);
  1475.     }
  1476.  
  1477.     /** Element to change resolve parent of. */
  1478.     protected AbstractElement element;
  1479.     /** New style. */
  1480.     protected Style newStyle;
  1481.     /** Old style, before setting newStyle. */
  1482.     protected AttributeSet oldStyle;
  1483.     }
  1484. }
  1485.  
  1486.  
  1487.  
  1488.  
  1489.