KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > javax > swing > text > DefaultStyledDocument


1 /*
2  * @(#)DefaultStyledDocument.java 1.124 04/05/05
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7 package javax.swing.text;
8
9 import java.awt.Color JavaDoc;
10 import java.awt.Component JavaDoc;
11 import java.awt.Font JavaDoc;
12 import java.awt.FontMetrics JavaDoc;
13 import java.awt.font.TextAttribute JavaDoc;
14 import java.util.Enumeration JavaDoc;
15 import java.util.Hashtable JavaDoc;
16 import java.util.Stack JavaDoc;
17 import java.util.Vector JavaDoc;
18 import java.util.ArrayList JavaDoc;
19 import java.io.IOException JavaDoc;
20 import java.io.ObjectInputStream JavaDoc;
21 import java.io.ObjectOutputStream JavaDoc;
22 import java.io.Serializable JavaDoc;
23 import javax.swing.Icon JavaDoc;
24 import javax.swing.event.*;
25 import javax.swing.undo.AbstractUndoableEdit JavaDoc;
26 import javax.swing.undo.CannotRedoException JavaDoc;
27 import javax.swing.undo.CannotUndoException JavaDoc;
28 import javax.swing.undo.UndoableEdit JavaDoc;
29 import javax.swing.SwingUtilities JavaDoc;
30
31 /**
32  * A document that can be marked up with character and paragraph
33  * styles in a manner similar to the Rich Text Format. The element
34  * structure for this document represents style crossings for
35  * style runs. These style runs are mapped into a paragraph element
36  * structure (which may reside in some other structure). The
37  * style runs break at paragraph boundaries since logical styles are
38  * assigned to paragraph boundaries.
39  * <p>
40  * <strong>Warning:</strong>
41  * Serialized objects of this class will not be compatible with
42  * future Swing releases. The current serialization support is
43  * appropriate for short term storage or RMI between applications running
44  * the same version of Swing. As of 1.4, support for long term storage
45  * of all JavaBeans<sup><font size="-2">TM</font></sup>
46  * has been added to the <code>java.beans</code> package.
47  * Please see {@link java.beans.XMLEncoder}.
48  *
49  * @author Timothy Prinzing
50  * @version 1.124 05/05/04
51  * @see Document
52  * @see AbstractDocument
53  */

54 public class DefaultStyledDocument extends AbstractDocument JavaDoc implements StyledDocument JavaDoc {
55
56     /**
57      * Constructs a styled document.
58      *
59      * @param c the container for the content
60      * @param styles resources and style definitions which may
61      * be shared across documents
62      */

63     public DefaultStyledDocument(Content c, StyleContext JavaDoc styles) {
64     super(c, styles);
65     listeningStyles = new Vector JavaDoc();
66     buffer = new ElementBuffer(createDefaultRoot());
67     Style JavaDoc defaultStyle = styles.getStyle(StyleContext.DEFAULT_STYLE);
68     setLogicalStyle(0, defaultStyle);
69     }
70
71     /**
72      * Constructs a styled document with the default content
73      * storage implementation and a shared set of styles.
74      *
75      * @param styles the styles
76      */

77     public DefaultStyledDocument(StyleContext JavaDoc styles) {
78     this(new GapContent JavaDoc(BUFFER_SIZE_DEFAULT), styles);
79     }
80
81     /**
82      * Constructs a default styled document. This buffers
83      * input content by a size of <em>BUFFER_SIZE_DEFAULT</em>
84      * and has a style context that is scoped by the lifetime
85      * of the document and is not shared with other documents.
86      */

87     public DefaultStyledDocument() {
88     this(new GapContent JavaDoc(BUFFER_SIZE_DEFAULT), new StyleContext JavaDoc());
89     }
90
91     /**
92      * Gets the default root element.
93      *
94      * @return the root
95      * @see Document#getDefaultRootElement
96      */

97     public Element JavaDoc getDefaultRootElement() {
98     return buffer.getRootElement();
99     }
100
101     /**
102      * Initialize the document to reflect the given element
103      * structure (i.e. the structure reported by the
104      * <code>getDefaultRootElement</code> method. If the
105      * document contained any data it will first be removed.
106      */

107     protected void create(ElementSpec[] data) {
108     try {
109         if (getLength() != 0) {
110         remove(0, getLength());
111         }
112         writeLock();
113
114         // install the content
115
Content c = getContent();
116         int n = data.length;
117         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
118         for (int i = 0; i < n; i++) {
119         ElementSpec es = data[i];
120         if (es.getLength() > 0) {
121             sb.append(es.getArray(), es.getOffset(), es.getLength());
122         }
123         }
124         UndoableEdit JavaDoc cEdit = c.insertString(0, sb.toString());
125
126         // build the event and element structure
127
int length = sb.length();
128         DefaultDocumentEvent evnt =
129         new DefaultDocumentEvent(0, length, DocumentEvent.EventType.INSERT);
130         evnt.addEdit(cEdit);
131         buffer.create(length, data, evnt);
132
133         // update bidi (possibly)
134
super.insertUpdate(evnt, null);
135
136         // notify the listeners
137
evnt.end();
138         fireInsertUpdate(evnt);
139         fireUndoableEditUpdate(new UndoableEditEvent(this, evnt));
140     } catch (BadLocationException JavaDoc ble) {
141         throw new StateInvariantError JavaDoc("problem initializing");
142     } finally {
143         writeUnlock();
144     }
145     
146     }
147
148     /**
149      * Inserts new elements in bulk. This is useful to allow
150      * parsing with the document in an unlocked state and
151      * prepare an element structure modification. This method
152      * takes an array of tokens that describe how to update an
153      * element structure so the time within a write lock can
154      * be greatly reduced in an asynchronous update situation.
155      * <p>
156      * This method is thread safe, although most Swing methods
157      * are not. Please see
158      * <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">Threads
159      * and Swing</A> for more information.
160      *
161      * @param offset the starting offset >= 0
162      * @param data the element data
163      * @exception BadLocationException for an invalid starting offset
164      */

165     protected void insert(int offset, ElementSpec[] data) throws BadLocationException JavaDoc {
166     if (data == null || data.length == 0) {
167         return;
168     }
169
170     try {
171         writeLock();
172
173         // install the content
174
Content c = getContent();
175         int n = data.length;
176         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
177         for (int i = 0; i < n; i++) {
178         ElementSpec es = data[i];
179         if (es.getLength() > 0) {
180             sb.append(es.getArray(), es.getOffset(), es.getLength());
181         }
182         }
183         if (sb.length() == 0) {
184         // Nothing to insert, bail.
185
return;
186         }
187         UndoableEdit JavaDoc cEdit = c.insertString(offset, sb.toString());
188
189         // create event and build the element structure
190
int length = sb.length();
191         DefaultDocumentEvent evnt =
192         new DefaultDocumentEvent(offset, length, DocumentEvent.EventType.INSERT);
193         evnt.addEdit(cEdit);
194         buffer.insert(offset, length, data, evnt);
195         
196         // update bidi (possibly)
197
super.insertUpdate(evnt, null);
198
199         // notify the listeners
200
evnt.end();
201         fireInsertUpdate(evnt);
202         fireUndoableEditUpdate(new UndoableEditEvent(this, evnt));
203     } finally {
204         writeUnlock();
205     }
206     }
207
208     /**
209      * Adds a new style into the logical style hierarchy. Style attributes
210      * resolve from bottom up so an attribute specified in a child
211      * will override an attribute specified in the parent.
212      *
213      * @param nm the name of the style (must be unique within the
214      * collection of named styles). The name may be null if the style
215      * is unnamed, but the caller is responsible
216      * for managing the reference returned as an unnamed style can't
217      * be fetched by name. An unnamed style may be useful for things
218      * like character attribute overrides such as found in a style
219      * run.
220      * @param parent the parent style. This may be null if unspecified
221      * attributes need not be resolved in some other style.
222      * @return the style
223      */

224     public Style JavaDoc addStyle(String JavaDoc nm, Style JavaDoc parent) {
225     StyleContext JavaDoc styles = (StyleContext JavaDoc) getAttributeContext();
226     return styles.addStyle(nm, parent);
227     }
228
229     /**
230      * Removes a named style previously added to the document.
231      *
232      * @param nm the name of the style to remove
233      */

234     public void removeStyle(String JavaDoc nm) {
235     StyleContext JavaDoc styles = (StyleContext JavaDoc) getAttributeContext();
236     styles.removeStyle(nm);
237     }
238
239     /**
240      * Fetches a named style previously added.
241      *
242      * @param nm the name of the style
243      * @return the style
244      */

245     public Style JavaDoc getStyle(String JavaDoc nm) {
246     StyleContext JavaDoc styles = (StyleContext JavaDoc) getAttributeContext();
247     return styles.getStyle(nm);
248     }
249
250
251     /**
252      * Fetches the list of of style names.
253      *
254      * @return all the style names
255      */

256     public Enumeration JavaDoc<?> getStyleNames() {
257     return ((StyleContext JavaDoc) getAttributeContext()).getStyleNames();
258     }
259
260     /**
261      * Sets the logical style to use for the paragraph at the
262      * given position. If attributes aren't explicitly set
263      * for character and paragraph attributes they will resolve
264      * through the logical style assigned to the paragraph, which
265      * in turn may resolve through some hierarchy completely
266      * independent of the element hierarchy in the document.
267      * <p>
268      * This method is thread safe, although most Swing methods
269      * are not. Please see
270      * <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">Threads
271      * and Swing</A> for more information.
272      *
273      * @param pos the offset from the start of the document >= 0
274      * @param s the logical style to assign to the paragraph, null if none
275      */

276     public void setLogicalStyle(int pos, Style JavaDoc s) {
277     Element JavaDoc paragraph = getParagraphElement(pos);
278     if ((paragraph != null) && (paragraph instanceof AbstractElement)) {
279         try {
280         writeLock();
281         StyleChangeUndoableEdit edit = new StyleChangeUndoableEdit((AbstractElement)paragraph, s);
282         ((AbstractElement)paragraph).setResolveParent(s);
283         int p0 = paragraph.getStartOffset();
284         int p1 = paragraph.getEndOffset();
285         DefaultDocumentEvent e =
286           new DefaultDocumentEvent(p0, p1 - p0, DocumentEvent.EventType.CHANGE);
287         e.addEdit(edit);
288         e.end();
289         fireChangedUpdate(e);
290         fireUndoableEditUpdate(new UndoableEditEvent(this, e));
291         } finally {
292         writeUnlock();
293         }
294     }
295     }
296
297     /**
298      * Fetches the logical style assigned to the paragraph
299      * represented by the given position.
300      *
301      * @param p the location to translate to a paragraph
302      * and determine the logical style assigned >= 0. This
303      * is an offset from the start of the document.
304      * @return the style, null if none
305      */

306     public Style JavaDoc getLogicalStyle(int p) {
307     Style JavaDoc s = null;
308     Element JavaDoc paragraph = getParagraphElement(p);
309     if (paragraph != null) {
310         AttributeSet JavaDoc a = paragraph.getAttributes();
311         AttributeSet JavaDoc parent = a.getResolveParent();
312         if (parent instanceof Style JavaDoc) {
313         s = (Style JavaDoc) parent;
314         }
315     }
316     return s;
317     }
318
319     /**
320      * Sets attributes for some part of the document.
321      * A write lock is held by this operation while changes
322      * are being made, and a DocumentEvent is sent to the listeners
323      * after the change has been successfully completed.
324      * <p>
325      * This method is thread safe, although most Swing methods
326      * are not. Please see
327      * <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">Threads
328      * and Swing</A> for more information.
329      *
330      * @param offset the offset in the document >= 0
331      * @param length the length >= 0
332      * @param s the attributes
333      * @param replace true if the previous attributes should be replaced
334      * before setting the new attributes
335      */

336     public void setCharacterAttributes(int offset, int length, AttributeSet JavaDoc s, boolean replace) {
337         if (length == 0) {
338             return;
339         }
340     try {
341         writeLock();
342         DefaultDocumentEvent changes =
343         new DefaultDocumentEvent(offset, length, DocumentEvent.EventType.CHANGE);
344
345         // split elements that need it
346
buffer.change(offset, length, changes);
347
348         AttributeSet JavaDoc sCopy = s.copyAttributes();
349
350         // PENDING(prinz) - this isn't a very efficient way to iterate
351
int lastEnd = Integer.MAX_VALUE;
352         for (int pos = offset; pos < (offset + length); pos = lastEnd) {
353         Element JavaDoc run = getCharacterElement(pos);
354         lastEnd = run.getEndOffset();
355                 if (pos == lastEnd) {
356                     // offset + length beyond length of document, bail.
357
break;
358                 }
359         MutableAttributeSet JavaDoc attr = (MutableAttributeSet JavaDoc) run.getAttributes();
360         changes.addEdit(new AttributeUndoableEdit(run, sCopy, replace));
361         if (replace) {
362             attr.removeAttributes(attr);
363         }
364         attr.addAttributes(s);
365         }
366         changes.end();
367         fireChangedUpdate(changes);
368         fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
369     } finally {
370         writeUnlock();
371     }
372
373     }
374
375     /**
376      * Sets attributes for a paragraph.
377      * <p>
378      * This method is thread safe, although most Swing methods
379      * are not. Please see
380      * <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">Threads
381      * and Swing</A> for more information.
382      *
383      * @param offset the offset into the paragraph >= 0
384      * @param length the number of characters affected >= 0
385      * @param s the attributes
386      * @param replace whether to replace existing attributes, or merge them
387      */

388     public void setParagraphAttributes(int offset, int length, AttributeSet JavaDoc s,
389                        boolean replace) {
390     try {
391         writeLock();
392         DefaultDocumentEvent changes =
393         new DefaultDocumentEvent(offset, length, DocumentEvent.EventType.CHANGE);
394
395         AttributeSet JavaDoc sCopy = s.copyAttributes();
396
397         // PENDING(prinz) - this assumes a particular element structure
398
Element JavaDoc section = getDefaultRootElement();
399         int index0 = section.getElementIndex(offset);
400         int index1 = section.getElementIndex(offset + ((length > 0) ? length - 1 : 0));
401             boolean isI18N = Boolean.TRUE.equals(getProperty(I18NProperty));
402             boolean hasRuns = false;
403         for (int i = index0; i <= index1; i++) {
404         Element JavaDoc paragraph = section.getElement(i);
405         MutableAttributeSet JavaDoc attr = (MutableAttributeSet JavaDoc) paragraph.getAttributes();
406         changes.addEdit(new AttributeUndoableEdit(paragraph, sCopy, replace));
407         if (replace) {
408             attr.removeAttributes(attr);
409         }
410         attr.addAttributes(s);
411                 if (isI18N && !hasRuns) {
412                     hasRuns = (attr.getAttribute(TextAttribute.RUN_DIRECTION) != null);
413                 }
414         }
415
416             if (hasRuns) {
417                 updateBidi( changes );
418             }
419
420         changes.end();
421         fireChangedUpdate(changes);
422         fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
423     } finally {
424         writeUnlock();
425     }
426     }
427
428     /**
429      * Gets the paragraph element at the offset <code>pos</code>.
430      * A paragraph consists of at least one child Element, which is usually
431      * a leaf.
432      *
433      * @param pos the starting offset >= 0
434      * @return the element
435      */

436     public Element JavaDoc getParagraphElement(int pos) {
437     Element JavaDoc e = null;
438     for (e = getDefaultRootElement(); ! e.isLeaf(); ) {
439         int index = e.getElementIndex(pos);
440         e = e.getElement(index);
441     }
442     if(e != null)
443         return e.getParentElement();
444     return e;
445     }
446
447     /**
448      * Gets a character element based on a position.
449      *
450      * @param pos the position in the document >= 0
451      * @return the element
452      */

453     public Element JavaDoc getCharacterElement(int pos) {
454     Element JavaDoc e = null;
455     for (e = getDefaultRootElement(); ! e.isLeaf(); ) {
456         int index = e.getElementIndex(pos);
457         e = e.getElement(index);
458     }
459     return e;
460     }
461
462     // --- local methods -------------------------------------------------
463

464     /**
465      * Updates document structure as a result of text insertion. This
466      * will happen within a write lock. This implementation simply
467      * parses the inserted content for line breaks and builds up a set
468      * of instructions for the element buffer.
469      *
470      * @param chng a description of the document change
471      * @param attr the attributes
472      */

473     protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet JavaDoc attr) {
474     int offset = chng.getOffset();
475     int length = chng.getLength();
476     if (attr == null) {
477         attr = SimpleAttributeSet.EMPTY;
478     }
479
480     // Paragraph attributes should come from point after insertion.
481
// You really only notice this when inserting at a paragraph
482
// boundary.
483
Element JavaDoc paragraph = getParagraphElement(offset + length);
484     AttributeSet JavaDoc pattr = paragraph.getAttributes();
485     // Character attributes should come from actual insertion point.
486
Element JavaDoc pParagraph = getParagraphElement(offset);
487     Element JavaDoc run = pParagraph.getElement(pParagraph.getElementIndex
488                         (offset));
489     int endOffset = offset + length;
490     boolean insertingAtBoundry = (run.getEndOffset() == endOffset);
491     AttributeSet JavaDoc cattr = run.getAttributes();
492
493     try {
494         Segment JavaDoc s = new Segment JavaDoc();
495         Vector JavaDoc parseBuffer = new Vector JavaDoc();
496         ElementSpec lastStartSpec = null;
497         boolean insertingAfterNewline = false;
498         short lastStartDirection = ElementSpec.OriginateDirection;
499         // Check if the previous character was a newline.
500
if (offset > 0) {
501         getText(offset - 1, 1, s);
502         if (s.array[s.offset] == '\n') {
503             // Inserting after a newline.
504
insertingAfterNewline = true;
505             lastStartDirection = createSpecsForInsertAfterNewline
506                       (paragraph, pParagraph, pattr, parseBuffer,
507                    offset, endOffset);
508             for(int counter = parseBuffer.size() - 1; counter >= 0;
509             counter--) {
510             ElementSpec spec = (ElementSpec)parseBuffer.
511                                 elementAt(counter);
512             if(spec.getType() == ElementSpec.StartTagType) {
513                 lastStartSpec = spec;
514                 break;
515             }
516             }
517         }
518         }
519         // If not inserting after a new line, pull the attributes for
520
// new paragraphs from the paragraph under the insertion point.
521
if(!insertingAfterNewline)
522         pattr = pParagraph.getAttributes();
523
524         getText(offset, length, s);
525         char[] txt = s.array;
526         int n = s.offset + s.count;
527         int lastOffset = s.offset;
528
529         for (int i = s.offset; i < n; i++) {
530         if (txt[i] == '\n') {
531             int breakOffset = i + 1;
532             parseBuffer.addElement(
533                         new ElementSpec(attr, ElementSpec.ContentType,
534                            breakOffset - lastOffset));
535             parseBuffer.addElement(
536                         new ElementSpec(null, ElementSpec.EndTagType));
537             lastStartSpec = new ElementSpec(pattr, ElementSpec.
538                            StartTagType);
539             parseBuffer.addElement(lastStartSpec);
540             lastOffset = breakOffset;
541         }
542         }
543         if (lastOffset < n) {
544         parseBuffer.addElement(
545                     new ElementSpec(attr, ElementSpec.ContentType,
546                        n - lastOffset));
547         }
548
549         ElementSpec first = (ElementSpec) parseBuffer.firstElement();
550
551         int docLength = getLength();
552
553         // Check for join previous of first content.
554
if(first.getType() == ElementSpec.ContentType &&
555            cattr.isEqual(attr)) {
556         first.setDirection(ElementSpec.JoinPreviousDirection);
557         }
558
559         // Do a join fracture/next for last start spec if necessary.
560
if(lastStartSpec != null) {
561         if(insertingAfterNewline) {
562             lastStartSpec.setDirection(lastStartDirection);
563         }
564         // Join to the fracture if NOT inserting at the end
565
// (fracture only happens when not inserting at end of
566
// paragraph).
567
else if(pParagraph.getEndOffset() != endOffset) {
568             lastStartSpec.setDirection(ElementSpec.
569                            JoinFractureDirection);
570         }
571         // Join to next if parent of pParagraph has another
572
// element after pParagraph, and it isn't a leaf.
573
else {
574             Element JavaDoc parent = pParagraph.getParentElement();
575             int pParagraphIndex = parent.getElementIndex(offset);
576             if((pParagraphIndex + 1) < parent.getElementCount() &&
577                !parent.getElement(pParagraphIndex + 1).isLeaf()) {
578             lastStartSpec.setDirection(ElementSpec.
579                            JoinNextDirection);
580             }
581         }
582         }
583
584         // Do a JoinNext for last spec if it is content, it doesn't
585
// already have a direction set, no new paragraphs have been
586
// inserted or a new paragraph has been inserted and its join
587
// direction isn't originate, and the element at endOffset
588
// is a leaf.
589
if(insertingAtBoundry && endOffset < docLength) {
590         ElementSpec last = (ElementSpec) parseBuffer.lastElement();
591         if(last.getType() == ElementSpec.ContentType &&
592            last.getDirection() != ElementSpec.JoinPreviousDirection &&
593            ((lastStartSpec == null && (paragraph == pParagraph ||
594                            insertingAfterNewline)) ||
595             (lastStartSpec != null && lastStartSpec.getDirection() !=
596              ElementSpec.OriginateDirection))) {
597             Element JavaDoc nextRun = paragraph.getElement(paragraph.
598                        getElementIndex(endOffset));
599             // Don't try joining to a branch!
600
if(nextRun.isLeaf() &&
601                attr.isEqual(nextRun.getAttributes())) {
602             last.setDirection(ElementSpec.JoinNextDirection);
603             }
604         }
605         }
606         // If not inserting at boundary and there is going to be a
607
// fracture, then can join next on last content if cattr
608
// matches the new attributes.
609
else if(!insertingAtBoundry && lastStartSpec != null &&
610             lastStartSpec.getDirection() ==
611             ElementSpec.JoinFractureDirection) {
612         ElementSpec last = (ElementSpec) parseBuffer.lastElement();
613         if(last.getType() == ElementSpec.ContentType &&
614            last.getDirection() != ElementSpec.JoinPreviousDirection &&
615            attr.isEqual(cattr)) {
616             last.setDirection(ElementSpec.JoinNextDirection);
617         }
618         }
619
620         // Check for the composed text element. If it is, merge the character attributes
621
// into this element as well.
622
if (Utilities.isComposedTextAttributeDefined(attr)) {
623             ((MutableAttributeSet JavaDoc)attr).addAttributes(cattr);
624             ((MutableAttributeSet JavaDoc)attr).addAttribute(AbstractDocument.ElementNameAttribute,
625                                                  AbstractDocument.ContentElementName);
626         }
627
628         ElementSpec[] spec = new ElementSpec[parseBuffer.size()];
629         parseBuffer.copyInto(spec);
630         buffer.insert(offset, length, spec, chng);
631     } catch (BadLocationException JavaDoc bl) {
632     }
633
634         super.insertUpdate( chng, attr );
635     }
636
637     /**
638      * This is called by insertUpdate when inserting after a new line.
639      * It generates, in <code>parseBuffer</code>, ElementSpecs that will
640      * position the stack in <code>paragraph</code>.<p>
641      * It returns the direction the last StartSpec should have (this don't
642      * necessarily create the last start spec).
643      */

644     short createSpecsForInsertAfterNewline(Element JavaDoc paragraph,
645                 Element JavaDoc pParagraph, AttributeSet JavaDoc pattr, Vector JavaDoc parseBuffer,
646                          int offset, int endOffset) {
647     // Need to find the common parent of pParagraph and paragraph.
648
if(paragraph.getParentElement() == pParagraph.getParentElement()) {
649         // The simple (and common) case that pParagraph and
650
// paragraph have the same parent.
651
ElementSpec spec = new ElementSpec(pattr, ElementSpec.EndTagType);
652         parseBuffer.addElement(spec);
653         spec = new ElementSpec(pattr, ElementSpec.StartTagType);
654         parseBuffer.addElement(spec);
655         if(pParagraph.getEndOffset() != endOffset)
656         return ElementSpec.JoinFractureDirection;
657
658         Element JavaDoc parent = pParagraph.getParentElement();
659         if((parent.getElementIndex(offset) + 1) < parent.getElementCount())
660         return ElementSpec.JoinNextDirection;
661     }
662     else {
663         // Will only happen for text with more than 2 levels.
664
// Find the common parent of a paragraph and pParagraph
665
Vector JavaDoc leftParents = new Vector JavaDoc();
666         Vector JavaDoc rightParents = new Vector JavaDoc();
667         Element JavaDoc e = pParagraph;
668         while(e != null) {
669         leftParents.addElement(e);
670         e = e.getParentElement();
671         }
672         e = paragraph;
673         int leftIndex = -1;
674         while(e != null && (leftIndex = leftParents.indexOf(e)) == -1) {
675         rightParents.addElement(e);
676         e = e.getParentElement();
677         }
678         if(e != null) {
679         // e identifies the common parent.
680
// Build the ends.
681
for(int counter = 0; counter < leftIndex;
682             counter++) {
683             parseBuffer.addElement(new ElementSpec
684                           (null, ElementSpec.EndTagType));
685         }
686         // And the starts.
687
ElementSpec spec = null;
688         for(int counter = rightParents.size() - 1;
689             counter >= 0; counter--) {
690             spec = new ElementSpec(((Element JavaDoc)rightParents.
691                    elementAt(counter)).getAttributes(),
692                    ElementSpec.StartTagType);
693             if(counter > 0)
694             spec.setDirection(ElementSpec.JoinNextDirection);
695             parseBuffer.addElement(spec);
696         }
697         // If there are right parents, then we generated starts
698
// down the right subtree and there will be an element to
699
// join to.
700
if(rightParents.size() > 0)
701             return ElementSpec.JoinNextDirection;
702         // No right subtree, e.getElement(endOffset) is a
703
// leaf. There will be a facture.
704
return ElementSpec.JoinFractureDirection;
705         }
706         // else: Could throw an exception here, but should never get here!
707
}
708     return ElementSpec.OriginateDirection;
709     }
710
711     /**
712      * Updates document structure as a result of text removal.
713      *
714      * @param chng a description of the document change
715      */

716     protected void removeUpdate(DefaultDocumentEvent chng) {
717         super.removeUpdate(chng);
718     buffer.remove(chng.getOffset(), chng.getLength(), chng);
719     }
720
721     /**
722      * Creates the root element to be used to represent the
723      * default document structure.
724      *
725      * @return the element base
726      */

727     protected AbstractElement createDefaultRoot() {
728     // grabs a write-lock for this initialization and
729
// abandon it during initialization so in normal
730
// operation we can detect an illegitimate attempt
731
// to mutate attributes.
732
writeLock();
733     BranchElement section = new SectionElement();
734     BranchElement paragraph = new BranchElement(section, null);
735
736     LeafElement brk = new LeafElement(paragraph, null, 0, 1);
737     Element JavaDoc[] buff = new Element JavaDoc[1];
738     buff[0] = brk;
739     paragraph.replace(0, 0, buff);
740
741     buff[0] = paragraph;
742     section.replace(0, 0, buff);
743     writeUnlock();
744     return section;
745     }
746
747     /**
748      * Gets the foreground color from an attribute set.
749      *
750      * @param attr the attribute set
751      * @return the color
752      */

753     public Color JavaDoc getForeground(AttributeSet JavaDoc attr) {
754     StyleContext JavaDoc styles = (StyleContext JavaDoc) getAttributeContext();
755     return styles.getForeground(attr);
756     }
757
758     /**
759      * Gets the background color from an attribute set.
760      *
761      * @param attr the attribute set
762      * @return the color
763      */

764     public Color JavaDoc getBackground(AttributeSet JavaDoc attr) {
765     StyleContext JavaDoc styles = (StyleContext JavaDoc) getAttributeContext();
766     return styles.getBackground(attr);
767     }
768
769     /**
770      * Gets the font from an attribute set.
771      *
772      * @param attr the attribute set
773      * @return the font
774      */

775     public Font JavaDoc getFont(AttributeSet JavaDoc attr) {
776     StyleContext JavaDoc styles = (StyleContext JavaDoc) getAttributeContext();
777     return styles.getFont(attr);
778     }
779
780     /**
781      * Called when any of this document's styles have changed.
782      * Subclasses may wish to be intelligent about what gets damaged.
783      *
784      * @param style The Style that has changed.
785      */

786     protected void styleChanged(Style JavaDoc style) {
787         // Only propagate change updated if have content
788
if (getLength() != 0) {
789             // lazily create a ChangeUpdateRunnable
790
if (updateRunnable == null) {
791                 updateRunnable = new ChangeUpdateRunnable();
792             }
793             
794             // We may get a whole batch of these at once, so only
795
// queue the runnable if it is not already pending
796
synchronized(updateRunnable) {
797                 if (!updateRunnable.isPending) {
798                     SwingUtilities.invokeLater(updateRunnable);
799                     updateRunnable.isPending = true;
800                 }
801             }
802         }
803     }
804
805     /**
806      * Adds a document listener for notification of any changes.
807      *
808      * @param listener the listener
809      * @see Document#addDocumentListener
810      */

811     public void addDocumentListener(DocumentListener listener) {
812     synchronized(listeningStyles) {
813         int oldDLCount = listenerList.getListenerCount
814                                   (DocumentListener.class);
815         super.addDocumentListener(listener);
816         if (oldDLCount == 0) {
817         if (styleContextChangeListener == null) {
818             styleContextChangeListener =
819                           createStyleContextChangeListener();
820         }
821         if (styleContextChangeListener != null) {
822             StyleContext JavaDoc styles = (StyleContext JavaDoc)getAttributeContext();
823             styles.addChangeListener(styleContextChangeListener);
824         }
825         updateStylesListeningTo();
826         }
827     }
828     }
829
830     /**
831      * Removes a document listener.
832      *
833      * @param listener the listener
834      * @see Document#removeDocumentListener
835      */

836     public void removeDocumentListener(DocumentListener listener) {
837     synchronized(listeningStyles) {
838         super.removeDocumentListener(listener);
839         if (listenerList.getListenerCount(DocumentListener.class) == 0) {
840         for (int counter = listeningStyles.size() - 1; counter >= 0;
841              counter--) {
842             ((Style JavaDoc)listeningStyles.elementAt(counter)).
843                         removeChangeListener(styleChangeListener);
844         }
845         listeningStyles.removeAllElements();
846         if (styleContextChangeListener != null) {
847             StyleContext JavaDoc styles = (StyleContext JavaDoc)getAttributeContext();
848             styles.removeChangeListener(styleContextChangeListener);
849         }
850         }
851     }
852     }
853
854     /**
855      * Returns a new instance of StyleChangeHandler.
856      */

857     ChangeListener createStyleChangeListener() {
858     return new StyleChangeHandler();
859     }
860
861     /**
862      * Returns a new instance of StyleContextChangeHandler.
863      */

864     ChangeListener createStyleContextChangeListener() {
865     return new StyleContextChangeHandler();
866     }
867
868     /**
869      * Adds a ChangeListener to new styles, and removes ChangeListener from
870      * old styles.
871      */

872     void updateStylesListeningTo() {
873     synchronized(listeningStyles) {
874         StyleContext JavaDoc styles = (StyleContext JavaDoc)getAttributeContext();
875         if (styleChangeListener == null) {
876         styleChangeListener = createStyleChangeListener();
877         }
878         if (styleChangeListener != null && styles != null) {
879         Enumeration JavaDoc styleNames = styles.getStyleNames();
880         Vector JavaDoc v = (Vector JavaDoc)listeningStyles.clone();
881         listeningStyles.removeAllElements();
882         while (styleNames.hasMoreElements()) {
883             String JavaDoc name = (String JavaDoc)styleNames.nextElement();
884             Style JavaDoc aStyle = styles.getStyle(name);
885             int index = v.indexOf(aStyle);
886             listeningStyles.addElement(aStyle);
887             if (index == -1) {
888             aStyle.addChangeListener(styleChangeListener);
889             }
890             else {
891             v.removeElementAt(index);
892             }
893         }
894         for (int counter = v.size() - 1; counter >= 0; counter--) {
895             Style JavaDoc aStyle = (Style JavaDoc)v.elementAt(counter);
896             aStyle.removeChangeListener(styleChangeListener);
897         }
898         if (listeningStyles.size() == 0) {
899             styleChangeListener = null;
900         }
901         }
902     }
903     }
904
905     private void readObject(ObjectInputStream JavaDoc s)
906             throws ClassNotFoundException JavaDoc, IOException JavaDoc {
907     listeningStyles = new Vector JavaDoc();
908     s.defaultReadObject();
909     // Reinstall style listeners.
910
if (styleContextChangeListener == null &&
911         listenerList.getListenerCount(DocumentListener.class) > 0) {
912         styleContextChangeListener = createStyleContextChangeListener();
913         if (styleContextChangeListener != null) {
914         StyleContext JavaDoc styles = (StyleContext JavaDoc)getAttributeContext();
915         styles.addChangeListener(styleContextChangeListener);
916         }
917         updateStylesListeningTo();
918     }
919     }
920
921     // --- member variables -----------------------------------------------------------
922

923     /**
924      * The default size of the initial content buffer.
925      */

926     public static final int BUFFER_SIZE_DEFAULT = 4096;
927
928     protected ElementBuffer buffer;
929
930     /** Styles listening to. */
931     private transient Vector JavaDoc listeningStyles;
932
933     /** Listens to Styles. */
934     private transient ChangeListener styleChangeListener;
935
936     /** Listens to Styles. */
937     private transient ChangeListener styleContextChangeListener;
938     
939     /** Run to create a change event for the document */
940     private transient ChangeUpdateRunnable updateRunnable;
941
942     /**
943      * Default root element for a document... maps out the
944      * paragraphs/lines contained.
945      * <p>
946      * <strong>Warning:</strong>
947      * Serialized objects of this class will not be compatible with
948      * future Swing releases. The current serialization support is
949      * appropriate for short term storage or RMI between applications running
950      * the same version of Swing. As of 1.4, support for long term storage
951      * of all JavaBeans<sup><font size="-2">TM</font></sup>
952      * has been added to the <code>java.beans</code> package.
953      * Please see {@link java.beans.XMLEncoder}.
954      */

955     protected class SectionElement extends BranchElement {
956
957         /**
958          * Creates a new SectionElement.
959          */

960     public SectionElement() {
961         super(null, null);
962     }
963
964         /**
965          * Gets the name of the element.
966          *
967          * @return the name
968          */

969         public String JavaDoc getName() {
970         return SectionElementName;
971     }
972     }
973
974     /**
975      * Specification for building elements.
976      * <p>
977      * <strong>Warning:</strong>
978      * Serialized objects of this class will not be compatible with
979      * future Swing releases. The current serialization support is
980      * appropriate for short term storage or RMI between applications running
981      * the same version of Swing. As of 1.4, support for long term storage
982      * of all JavaBeans<sup><font size="-2">TM</font></sup>
983      * has been added to the <code>java.beans</code> package.
984      * Please see {@link java.beans.XMLEncoder}.
985      */

986     public static class ElementSpec {
987
988     /**
989      * A possible value for getType. This specifies
990      * that this record type is a start tag and
991      * represents markup that specifies the start
992      * of an element.
993      */

994     public static final short StartTagType = 1;
995     
996     /**
997      * A possible value for getType. This specifies
998      * that this record type is a end tag and
999      * represents markup that specifies the end
1000     * of an element.
1001     */

1002    public static final short EndTagType = 2;
1003
1004    /**
1005     * A possible value for getType. This specifies
1006     * that this record type represents content.
1007     */

1008    public static final short ContentType = 3;
1009    
1010    /**
1011     * A possible value for getDirection. This specifies
1012     * that the data associated with this record should
1013     * be joined to what precedes it.
1014     */

1015    public static final short JoinPreviousDirection = 4;
1016    
1017    /**
1018     * A possible value for getDirection. This specifies
1019     * that the data associated with this record should
1020     * be joined to what follows it.
1021     */

1022    public static final short JoinNextDirection = 5;
1023    
1024    /**
1025     * A possible value for getDirection. This specifies
1026     * that the data associated with this record should
1027     * be used to originate a new element. This would be
1028     * the normal value.
1029     */

1030    public static final short OriginateDirection = 6;
1031
1032    /**
1033     * A possible value for getDirection. This specifies
1034     * that the data associated with this record should
1035     * be joined to the fractured element.
1036     */

1037    public static final short JoinFractureDirection = 7;
1038
1039    
1040    /**
1041     * Constructor useful for markup when the markup will not
1042     * be stored in the document.
1043         *
1044         * @param a the attributes for the element
1045         * @param type the type of the element (StartTagType, EndTagType,
1046         * ContentType)
1047     */

1048    public ElementSpec(AttributeSet JavaDoc a, short type) {
1049        this(a, type, null, 0, 0);
1050    }
1051
1052    /**
1053     * Constructor for parsing inside the document when
1054     * the data has already been added, but len information
1055     * is needed.
1056         *
1057         * @param a the attributes for the element
1058         * @param type the type of the element (StartTagType, EndTagType,
1059         * ContentType)
1060         * @param len the length >= 0
1061     */

1062    public ElementSpec(AttributeSet JavaDoc a, short type, int len) {
1063        this(a, type, null, 0, len);
1064    }
1065
1066    /**
1067     * Constructor for creating a spec externally for batch
1068     * input of content and markup into the document.
1069         *
1070         * @param a the attributes for the element
1071         * @param type the type of the element (StartTagType, EndTagType,
1072         * ContentType)
1073         * @param txt the text for the element
1074         * @param offs the offset into the text >= 0
1075         * @param len the length of the text >= 0
1076     */

1077        public ElementSpec(AttributeSet JavaDoc a, short type, char[] txt,
1078                  int offs, int len) {
1079        attr = a;
1080        this.type = type;
1081        this.data = txt;
1082        this.offs = offs;
1083        this.len = len;
1084        this.direction = OriginateDirection;
1085    }
1086
1087        /**
1088         * Sets the element type.
1089         *
1090         * @param type the type of the element (StartTagType, EndTagType,
1091         * ContentType)
1092         */

1093    public void setType(short type) {
1094        this.type = type;
1095    }
1096
1097        /**
1098         * Gets the element type.
1099         *
1100         * @return the type of the element (StartTagType, EndTagType,
1101         * ContentType)
1102         */

1103    public short getType() {
1104        return type;
1105    }
1106
1107        /**
1108         * Sets the direction.
1109         *
1110         * @param direction the direction (JoinPreviousDirection,
1111         * JoinNextDirection)
1112         */

1113    public void setDirection(short direction) {
1114        this.direction = direction;
1115    }
1116
1117        /**
1118         * Gets the direction.
1119         *
1120         * @return the direction (JoinPreviousDirection, JoinNextDirection)
1121         */

1122    public short getDirection() {
1123        return direction;
1124    }
1125
1126        /**
1127         * Gets the element attributes.
1128         *
1129         * @return the attribute set
1130         */

1131    public AttributeSet JavaDoc getAttributes() {
1132        return attr;
1133    }
1134
1135        /**
1136         * Gets the array of characters.
1137         *
1138         * @return the array
1139         */

1140    public char[] getArray() {
1141        return data;
1142    }
1143
1144
1145        /**
1146         * Gets the starting offset.
1147         *
1148         * @return the offset >= 0
1149         */

1150    public int getOffset() {
1151        return offs;
1152    }
1153
1154        /**
1155         * Gets the length.
1156         *
1157         * @return the length >= 0
1158         */

1159    public int getLength() {
1160        return len;
1161    }
1162
1163        /**
1164         * Converts the element to a string.
1165         *
1166         * @return the string
1167         */

1168        public String JavaDoc toString() {
1169        String JavaDoc tlbl = "??";
1170        String JavaDoc plbl = "??";
1171        switch(type) {
1172        case StartTagType:
1173        tlbl = "StartTag";
1174        break;
1175        case ContentType:
1176        tlbl = "Content";
1177        break;
1178        case EndTagType:
1179        tlbl = "EndTag";
1180        break;
1181        }
1182        switch(direction) {
1183        case JoinPreviousDirection:
1184        plbl = "JoinPrevious";
1185        break;
1186        case JoinNextDirection:
1187        plbl = "JoinNext";
1188        break;
1189        case OriginateDirection:
1190        plbl = "Originate";
1191        break;
1192        case JoinFractureDirection:
1193        plbl = "Fracture";
1194        break;
1195        }
1196        return tlbl + ":" + plbl + ":" + getLength();
1197    }
1198        
1199    private AttributeSet JavaDoc attr;
1200    private int len;
1201    private short type;
1202    private short direction;
1203
1204    private int offs;
1205    private char[] data;
1206    }
1207
1208    /**
1209     * Class to manage changes to the element
1210     * hierarchy.
1211     * <p>
1212     * <strong>Warning:</strong>
1213     * Serialized objects of this class will not be compatible with
1214     * future Swing releases. The current serialization support is
1215     * appropriate for short term storage or RMI between applications running
1216     * the same version of Swing. As of 1.4, support for long term storage
1217     * of all JavaBeans<sup><font size="-2">TM</font></sup>
1218     * has been added to the <code>java.beans</code> package.
1219     * Please see {@link java.beans.XMLEncoder}.
1220     */

1221    public class ElementBuffer implements Serializable JavaDoc {
1222
1223        /**
1224         * Creates a new ElementBuffer.
1225         *
1226         * @param root the root element
1227         */

1228    public ElementBuffer(Element JavaDoc root) {
1229        this.root = root;
1230        changes = new Vector JavaDoc();
1231        path = new Stack JavaDoc();
1232    }
1233
1234        /**
1235         * Gets the root element.
1236         *
1237         * @return the root element
1238         */

1239        public Element JavaDoc getRootElement() {
1240        return root;
1241    }
1242
1243        /**
1244         * Inserts new content.
1245         *
1246         * @param offset the starting offset >= 0
1247         * @param length the length >= 0
1248         * @param data the data to insert
1249         * @param de the event capturing this edit
1250         */

1251    public void insert(int offset, int length, ElementSpec[] data,
1252                 DefaultDocumentEvent de) {
1253        if (length == 0) {
1254        // Nothing was inserted, no structure change.
1255
return;
1256        }
1257        insertOp = true;
1258        beginEdits(offset, length);
1259        insertUpdate(data);
1260        endEdits(de);
1261
1262        insertOp = false;
1263    }
1264
1265    void create(int length, ElementSpec[] data, DefaultDocumentEvent de) {
1266        insertOp = true;
1267        beginEdits(offset, length);
1268
1269        // PENDING(prinz) this needs to be fixed to create a new
1270
// root element as well, but requires changes to the
1271
// DocumentEvent to inform the views that there is a new
1272
// root element.
1273

1274        // Recreate the ending fake element to have the correct offsets.
1275
Element JavaDoc elem = root;
1276        int index = elem.getElementIndex(0);
1277        while (! elem.isLeaf()) {
1278        Element JavaDoc child = elem.getElement(index);
1279        push(elem, index);
1280        elem = child;
1281        index = elem.getElementIndex(0);
1282        }
1283        ElemChanges ec = (ElemChanges) path.peek();
1284        Element JavaDoc child = ec.parent.getElement(ec.index);
1285        ec.added.addElement(createLeafElement(ec.parent,
1286                child.getAttributes(), getLength(),
1287                child.getEndOffset()));
1288        ec.removed.addElement(child);
1289        while (path.size() > 1) {
1290        pop();
1291        }
1292
1293        int n = data.length;
1294
1295        // Reset the root elements attributes.
1296
AttributeSet JavaDoc newAttrs = null;
1297        if (n > 0 && data[0].getType() == ElementSpec.StartTagType) {
1298        newAttrs = data[0].getAttributes();
1299        }
1300        if (newAttrs == null) {
1301        newAttrs = SimpleAttributeSet.EMPTY;
1302        }
1303        MutableAttributeSet JavaDoc attr = (MutableAttributeSet JavaDoc)root.
1304                               getAttributes();
1305        de.addEdit(new AttributeUndoableEdit(root, newAttrs, true));
1306        attr.removeAttributes(attr);
1307        attr.addAttributes(newAttrs);
1308
1309        // fold in the specified subtree
1310
for (int i = 1; i < n; i++) {
1311        insertElement(data[i]);
1312        }
1313
1314        // pop the remaining path
1315
while (path.size() != 0) {
1316        pop();
1317        }
1318
1319        endEdits(de);
1320        insertOp = false;
1321    }
1322
1323        /**
1324         * Removes content.
1325         *
1326         * @param offset the starting offset >= 0
1327         * @param length the length >= 0
1328         * @param de the event capturing this edit
1329         */

1330    public void remove(int offset, int length, DefaultDocumentEvent de) {
1331        beginEdits(offset, length);
1332        removeUpdate();
1333        endEdits(de);
1334    }
1335
1336        /**
1337         * Changes content.
1338         *
1339         * @param offset the starting offset >= 0
1340         * @param length the length >= 0
1341         * @param de the event capturing this edit
1342         */

1343        public void change(int offset, int length, DefaultDocumentEvent de) {
1344        beginEdits(offset, length);
1345        changeUpdate();
1346        endEdits(de);
1347    }
1348
1349        /**
1350         * Inserts an update into the document.
1351         *
1352         * @param data the elements to insert
1353         */

1354    protected void insertUpdate(ElementSpec[] data) {
1355        // push the path
1356
Element JavaDoc elem = root;
1357        int index = elem.getElementIndex(offset);
1358        while (! elem.isLeaf()) {
1359        Element JavaDoc child = elem.getElement(index);
1360        push(elem, (child.isLeaf() ? index : index+1));
1361        elem = child;
1362        index = elem.getElementIndex(offset);
1363        }
1364
1365        // Build a copy of the original path.
1366
insertPath = new ElemChanges[path.size()];
1367        path.copyInto(insertPath);
1368
1369        // Haven't created the fracture yet.
1370
createdFracture = false;
1371
1372        // Insert the first content.
1373
int i;
1374
1375        recreateLeafs = false;
1376        if(data[0].getType() == ElementSpec.ContentType) {
1377        insertFirstContent(data);
1378        pos += data[0].getLength();
1379        i = 1;
1380        }
1381        else {
1382        fractureDeepestLeaf(data);
1383        i = 0;
1384        }
1385
1386        // fold in the specified subtree
1387
int n = data.length;
1388        for (; i < n; i++) {
1389        insertElement(data[i]);
1390        }
1391
1392        // Fracture, if we haven't yet.
1393
if(!createdFracture)
1394        fracture(-1);
1395
1396        // pop the remaining path
1397
while (path.size() != 0) {
1398        pop();
1399        }
1400
1401        // Offset the last index if necessary.
1402
if(offsetLastIndex && offsetLastIndexOnReplace) {
1403        insertPath[insertPath.length - 1].index++;
1404        }
1405
1406        // Make sure an edit is going to be created for each of the
1407
// original path items that have a change.
1408
for(int counter = insertPath.length - 1; counter >= 0;
1409        counter--) {
1410        ElemChanges change = insertPath[counter];
1411        if(change.parent == fracturedParent)
1412            change.added.addElement(fracturedChild);
1413        if((change.added.size() > 0 ||
1414            change.removed.size() > 0) && !changes.contains(change)) {
1415            // PENDING(sky): Do I need to worry about order here?
1416
changes.addElement(change);
1417        }
1418        }
1419
1420        // An insert at 0 with an initial end implies some elements
1421
// will have no children (the bottomost leaf would have length 0)
1422
// this will find what element need to be removed and remove it.
1423
if (offset == 0 && fracturedParent != null &&
1424        data[0].getType() == ElementSpec.EndTagType) {
1425        int counter = 0;
1426        while (counter < data.length &&
1427               data[counter].getType() == ElementSpec.EndTagType) {
1428            counter++;
1429        }
1430        ElemChanges change = insertPath[insertPath.length -
1431                           counter - 1];
1432        change.removed.insertElementAt(change.parent.getElement
1433                           (--change.index), 0);
1434        }
1435    }
1436
1437    /**
1438     * Updates the element structure in response to a removal from the
1439     * associated sequence in the document. Any elements consumed by the
1440     * span of the removal are removed.
1441     */

1442    protected void removeUpdate() {
1443        removeElements(root, offset, offset + length);
1444    }
1445
1446        /**
1447         * Updates the element structure in response to a change in the
1448         * document.
1449         */

1450        protected void changeUpdate() {
1451        boolean didEnd = split(offset, length);
1452        if (! didEnd) {
1453        // need to do the other end
1454
while (path.size() != 0) {
1455            pop();
1456        }
1457        split(offset + length, 0);
1458        }
1459        while (path.size() != 0) {
1460        pop();
1461        }
1462    }
1463
1464    boolean split(int offs, int len) {
1465        boolean splitEnd = false;
1466        // push the path
1467
Element JavaDoc e = root;
1468        int index = e.getElementIndex(offs);
1469        while (! e.isLeaf()) {
1470        push(e, index);
1471        e = e.getElement(index);
1472        index = e.getElementIndex(offs);
1473        }
1474
1475        ElemChanges ec = (ElemChanges) path.peek();
1476        Element JavaDoc child = ec.parent.getElement(ec.index);
1477        // make sure there is something to do... if the
1478
// offset is already at a boundary then there is
1479
// nothing to do.
1480
if (child.getStartOffset() < offs && offs < child.getEndOffset()) {
1481        // we need to split, now see if the other end is within
1482
// the same parent.
1483
int index0 = ec.index;
1484        int index1 = index0;
1485        if (((offs + len) < ec.parent.getEndOffset()) && (len != 0)) {
1486            // it's a range split in the same parent
1487
index1 = ec.parent.getElementIndex(offs+len);
1488            if (index1 == index0) {
1489            // it's a three-way split
1490
ec.removed.addElement(child);
1491            e = createLeafElement(ec.parent, child.getAttributes(),
1492                          child.getStartOffset(), offs);
1493            ec.added.addElement(e);
1494            e = createLeafElement(ec.parent, child.getAttributes(),
1495                      offs, offs + len);
1496            ec.added.addElement(e);
1497            e = createLeafElement(ec.parent, child.getAttributes(),
1498                          offs + len, child.getEndOffset());
1499            ec.added.addElement(e);
1500            return true;
1501            } else {
1502            child = ec.parent.getElement(index1);
1503            if ((offs + len) == child.getStartOffset()) {
1504                // end is already on a boundary
1505
index1 = index0;
1506            }
1507            }
1508            splitEnd = true;
1509        }
1510
1511        // split the first location
1512
pos = offs;
1513        child = ec.parent.getElement(index0);
1514        ec.removed.addElement(child);
1515        e = createLeafElement(ec.parent, child.getAttributes(),
1516                      child.getStartOffset(), pos);
1517        ec.added.addElement(e);
1518        e = createLeafElement(ec.parent, child.getAttributes(),
1519                      pos, child.getEndOffset());
1520        ec.added.addElement(e);
1521
1522        // pick up things in the middle
1523
for (int i = index0 + 1; i < index1; i++) {
1524            child = ec.parent.getElement(i);
1525            ec.removed.addElement(child);
1526            ec.added.addElement(child);
1527        }
1528
1529        if (index1 != index0) {
1530            child = ec.parent.getElement(index1);
1531            pos = offs + len;
1532            ec.removed.addElement(child);
1533            e = createLeafElement(ec.parent, child.getAttributes(),
1534                      child.getStartOffset(), pos);
1535            ec.added.addElement(e);
1536            e = createLeafElement(ec.parent, child.getAttributes(),
1537                      pos, child.getEndOffset());
1538            ec.added.addElement(e);
1539        }
1540        }
1541        return splitEnd;
1542    }
1543
1544    /**
1545     * Creates the UndoableEdit record for the edits made
1546     * in the buffer.
1547     */

1548    void endEdits(DefaultDocumentEvent de) {
1549        int n = changes.size();
1550        for (int i = 0; i < n; i++) {
1551        ElemChanges ec = (ElemChanges) changes.elementAt(i);
1552        Element JavaDoc[] removed = new Element JavaDoc[ec.removed.size()];
1553        ec.removed.copyInto(removed);
1554        Element JavaDoc[] added = new Element JavaDoc[ec.added.size()];
1555        ec.added.copyInto(added);
1556        int index = ec.index;
1557        ((BranchElement) ec.parent).replace(index, removed.length, added);
1558        ElementEdit ee = new ElementEdit((BranchElement) ec.parent,
1559                         index, removed, added);
1560        de.addEdit(ee);
1561        }
1562        
1563        changes.removeAllElements();
1564        path.removeAllElements();
1565
1566        /*
1567        for (int i = 0; i < n; i++) {
1568        ElemChanges ec = (ElemChanges) changes.elementAt(i);
1569        System.err.print("edited: " + ec.parent + " at: " + ec.index +
1570            " removed " + ec.removed.size());
1571        if (ec.removed.size() > 0) {
1572            int r0 = ((Element) ec.removed.firstElement()).getStartOffset();
1573            int r1 = ((Element) ec.removed.lastElement()).getEndOffset();
1574            System.err.print("[" + r0 + "," + r1 + "]");
1575        }
1576        System.err.print(" added " + ec.added.size());
1577        if (ec.added.size() > 0) {
1578            int p0 = ((Element) ec.added.firstElement()).getStartOffset();
1579            int p1 = ((Element) ec.added.lastElement()).getEndOffset();
1580            System.err.print("[" + p0 + "," + p1 + "]");
1581        }
1582        System.err.println("");
1583        }
1584        */

1585    }
1586
1587    /**
1588     * Initialize the buffer
1589     */

1590    void beginEdits(int offset, int length) {
1591        this.offset = offset;
1592        this.length = length;
1593        this.endOffset = offset + length;
1594        pos = offset;
1595        if (changes == null) {
1596        changes = new Vector JavaDoc();
1597        } else {
1598        changes.removeAllElements();
1599        }
1600        if (path == null) {
1601        path = new Stack JavaDoc();
1602        } else {
1603        path.removeAllElements();
1604        }
1605        fracturedParent = null;
1606        fracturedChild = null;
1607        offsetLastIndex = offsetLastIndexOnReplace = false;
1608    }
1609
1610    /**
1611     * Pushes a new element onto the stack that represents
1612     * the current path.
1613     * @param record Whether or not the push should be
1614     * recorded as an element change or not.
1615     * @param isFracture true if pushing on an element that was created
1616     * as the result of a fracture.
1617     */

1618    void push(Element JavaDoc e, int index, boolean isFracture) {
1619        ElemChanges ec = new ElemChanges(e, index, isFracture);
1620        path.push(ec);
1621    }
1622
1623    void push(Element JavaDoc e, int index) {
1624        push(e, index, false);
1625    }
1626
1627    void pop() {
1628        ElemChanges ec = (ElemChanges) path.peek();
1629        path.pop();
1630        if ((ec.added.size() > 0) || (ec.removed.size() > 0)) {
1631        changes.addElement(ec);
1632        } else if (! path.isEmpty()) {
1633        Element JavaDoc e = ec.parent;
1634        if(e.getElementCount() == 0) {
1635            // if we pushed a branch element that didn't get
1636
// used, make sure its not marked as having been added.
1637
ec = (ElemChanges) path.peek();
1638            ec.added.removeElement(e);
1639        }
1640        }
1641    }
1642
1643    /**
1644     * move the current offset forward by n.
1645     */

1646    void advance(int n) {
1647        pos += n;
1648    }
1649
1650    void insertElement(ElementSpec es) {
1651        ElemChanges ec = (ElemChanges) path.peek();
1652        switch(es.getType()) {
1653        case ElementSpec.StartTagType:
1654        switch(es.getDirection()) {
1655        case ElementSpec.JoinNextDirection:
1656            // Don't create a new element, use the existing one
1657
// at the specified location.
1658
Element JavaDoc parent = ec.parent.getElement(ec.index);
1659
1660            if(parent.isLeaf()) {
1661            // This happens if inserting into a leaf, followed
1662
// by a join next where next sibling is not a leaf.
1663
if((ec.index + 1) < ec.parent.getElementCount())
1664                parent = ec.parent.getElement(ec.index + 1);
1665            else
1666                throw new StateInvariantError JavaDoc("Join next to leaf");
1667            }
1668            // Not really a fracture, but need to treat it like
1669
// one so that content join next will work correctly.
1670
// We can do this because there will never be a join
1671
// next followed by a join fracture.
1672
push(parent, 0, true);
1673            break;
1674        case ElementSpec.JoinFractureDirection:
1675            if(!createdFracture) {
1676            // Should always be something on the stack!
1677
fracture(path.size() - 1);
1678            }
1679            // If parent isn't a fracture, fracture will be
1680
// fracturedChild.
1681
if(!ec.isFracture) {
1682            push(fracturedChild, 0, true);
1683            }
1684            else
1685            // Parent is a fracture, use 1st element.
1686
push(ec.parent.getElement(0), 0, true);
1687            break;
1688        default:
1689            Element JavaDoc belem = createBranchElement(ec.parent,
1690                            es.getAttributes());
1691            ec.added.addElement(belem);
1692            push(belem, 0);
1693            break;
1694        }
1695        break;
1696        case ElementSpec.EndTagType:
1697        pop();
1698        break;
1699        case ElementSpec.ContentType:
1700          int len = es.getLength();
1701        if (es.getDirection() != ElementSpec.JoinNextDirection) {
1702            Element JavaDoc leaf = createLeafElement(ec.parent, es.getAttributes(),
1703                             pos, pos + len);
1704            ec.added.addElement(leaf);
1705        }
1706        else {
1707            // JoinNext on tail is only applicable if last element
1708
// and attributes come from that of first element.
1709
// With a little extra testing it would be possible
1710
// to NOT due this again, as more than likely fracture()
1711
// created this element.
1712
if(!ec.isFracture) {
1713            Element JavaDoc first = null;
1714            if(insertPath != null) {
1715                for(int counter = insertPath.length - 1;
1716                counter >= 0; counter--) {
1717                if(insertPath[counter] == ec) {
1718                    if(counter != (insertPath.length - 1))
1719                    first = ec.parent.getElement(ec.index);
1720                    break;
1721                }
1722                }
1723            }
1724            if(first == null)
1725                first = ec.parent.getElement(ec.index + 1);
1726            Element JavaDoc leaf = createLeafElement(ec.parent, first.
1727                     getAttributes(), pos, first.getEndOffset());
1728            ec.added.addElement(leaf);
1729            ec.removed.addElement(first);
1730            }
1731            else {
1732            // Parent was fractured element.
1733
Element JavaDoc first = ec.parent.getElement(0);
1734            Element JavaDoc leaf = createLeafElement(ec.parent, first.
1735                     getAttributes(), pos, first.getEndOffset());
1736            ec.added.addElement(leaf);
1737            ec.removed.addElement(first);
1738            }
1739        }
1740        pos += len;
1741        break;
1742        }
1743    }
1744        
1745    /**
1746     * Remove the elements from <code>elem</code> in range
1747     * <code>rmOffs0</code>, <code>rmOffs1</code>. This uses
1748     * <code>canJoin</code> and <code>join</code> to handle joining
1749     * the endpoints of the insertion.
1750     *
1751     * @return true if elem will no longer have any elements.
1752     */

1753    boolean removeElements(Element JavaDoc elem, int rmOffs0, int rmOffs1) {
1754        if (! elem.isLeaf()) {
1755        // update path for changes
1756
int index0 = elem.getElementIndex(rmOffs0);
1757        int index1 = elem.getElementIndex(rmOffs1);
1758        push(elem, index0);
1759        ElemChanges ec = (ElemChanges)path.peek();
1760
1761        // if the range is contained by one element,
1762
// we just forward the request
1763
if (index0 == index1) {
1764            Element JavaDoc child0 = elem.getElement(index0);
1765            if(rmOffs0 <= child0.getStartOffset() &&
1766               rmOffs1 >= child0.getEndOffset()) {
1767            // Element totally removed.
1768
ec.removed.addElement(child0);
1769            }
1770            else if(removeElements(child0, rmOffs0, rmOffs1)) {
1771            ec.removed.addElement(child0);
1772            }
1773        } else {
1774            // the removal range spans elements. If we can join
1775
// the two endpoints, do it. Otherwise we remove the
1776
// interior and forward to the endpoints.
1777
Element JavaDoc child0 = elem.getElement(index0);
1778            Element JavaDoc child1 = elem.getElement(index1);
1779            boolean containsOffs1 = (rmOffs1 < elem.getEndOffset());
1780            if (containsOffs1 && canJoin(child0, child1)) {
1781            // remove and join
1782
for (int i = index0; i <= index1; i++) {
1783                ec.removed.addElement(elem.getElement(i));
1784            }
1785            Element JavaDoc e = join(elem, child0, child1, rmOffs0, rmOffs1);
1786            ec.added.addElement(e);
1787            } else {
1788            // remove interior and forward
1789
int rmIndex0 = index0 + 1;
1790            int rmIndex1 = index1 - 1;
1791            if (child0.getStartOffset() == rmOffs0 ||
1792                (index0 == 0 &&
1793                 child0.getStartOffset() > rmOffs0 &&
1794                 child0.getEndOffset() <= rmOffs1)) {
1795                // start element completely consumed
1796
child0 = null;
1797                rmIndex0 = index0;
1798            }
1799            if (!containsOffs1) {
1800                child1 = null;
1801                rmIndex1++;
1802            }
1803            else if (child1.getStartOffset() == rmOffs1) {
1804                // end element not touched
1805
child1 = null;
1806            }
1807            if (rmIndex0 <= rmIndex1) {
1808                ec.index = rmIndex0;
1809            }
1810            for (int i = rmIndex0; i <= rmIndex1; i++) {
1811                ec.removed.addElement(elem.getElement(i));
1812            }
1813            if (child0 != null) {
1814                if(removeElements(child0, rmOffs0, rmOffs1)) {
1815                ec.removed.insertElementAt(child0, 0);
1816                ec.index = index0;
1817                }
1818            }
1819            if (child1 != null) {
1820                if(removeElements(child1, rmOffs0, rmOffs1)) {
1821                ec.removed.addElement(child1);
1822                }
1823            }
1824            }
1825        }
1826
1827        // publish changes
1828
pop();
1829
1830        // Return true if we no longer have any children.
1831
if(elem.getElementCount() == (ec.removed.size() -
1832                          ec.added.size())) {
1833            return true;
1834        }
1835        }
1836        return false;
1837    }
1838
1839    /**
1840     * Can the two given elements be coelesced together
1841     * into one element?
1842     */

1843    boolean canJoin(Element JavaDoc e0, Element JavaDoc e1) {
1844        if ((e0 == null) || (e1 == null)) {
1845        return false;
1846        }
1847        // Don't join a leaf to a branch.
1848
boolean leaf0 = e0.isLeaf();
1849            boolean leaf1 = e1.isLeaf();
1850        if(leaf0 != leaf1) {
1851        return false;
1852            }
1853            if (leaf0) {
1854                // Only join leaves if the attributes match, otherwise
1855
// style information will be lost.
1856
return e0.getAttributes().isEqual(e1.getAttributes());
1857            }
1858            // Only join non-leafs if the names are equal. This may result
1859
// in loss of style information, but this is typically acceptable
1860
// for non-leafs.
1861
String JavaDoc name0 = e0.getName();
1862            String JavaDoc name1 = e1.getName();
1863            if (name0 != null) {
1864                return name0.equals(name1);
1865            }
1866            if (name1 != null) {
1867                return name1.equals(name0);
1868            }
1869            // Both names null, treat as equal.
1870
return true;
1871    }
1872
1873    /**
1874     * Joins the two elements carving out a hole for the
1875     * given removed range.
1876     */

1877    Element JavaDoc join(Element JavaDoc p, Element JavaDoc left, Element JavaDoc right, int rmOffs0, int rmOffs1) {
1878        if (left.isLeaf() && right.isLeaf()) {
1879        return createLeafElement(p, left.getAttributes(), left.getStartOffset(),
1880                     right.getEndOffset());
1881        } else if ((!left.isLeaf()) && (!right.isLeaf())) {
1882        // join two branch elements. This copies the children before
1883
// the removal range on the left element, and after the removal
1884
// range on the right element. The two elements on the edge
1885
// are joined if possible and needed.
1886
Element JavaDoc to = createBranchElement(p, left.getAttributes());
1887        int ljIndex = left.getElementIndex(rmOffs0);
1888        int rjIndex = right.getElementIndex(rmOffs1);
1889        Element JavaDoc lj = left.getElement(ljIndex);
1890        if (lj.getStartOffset() >= rmOffs0) {
1891            lj = null;
1892        }
1893        Element JavaDoc rj = right.getElement(rjIndex);
1894        if (rj.getStartOffset() == rmOffs1) {
1895            rj = null;
1896        }
1897        Vector JavaDoc children = new Vector JavaDoc();
1898
1899        // transfer the left
1900
for (int i = 0; i < ljIndex; i++) {
1901            children.addElement(clone(to, left.getElement(i)));
1902        }
1903
1904        // transfer the join/middle
1905
if (canJoin(lj, rj)) {
1906            Element JavaDoc e = join(to, lj, rj, rmOffs0, rmOffs1);
1907            children.addElement(e);
1908        } else {
1909            if (lj != null) {
1910            children.addElement(cloneAsNecessary(to, lj, rmOffs0, rmOffs1));
1911            }
1912            if (rj != null) {
1913            children.addElement(cloneAsNecessary(to, rj, rmOffs0, rmOffs1));
1914            }
1915        }
1916
1917        // transfer the right
1918
int n = right.getElementCount();
1919        for (int i = (rj == null) ? rjIndex : rjIndex + 1; i < n; i++) {
1920            children.addElement(clone(to, right.getElement(i)));
1921        }
1922
1923        // install the children
1924
Element JavaDoc[] c = new Element JavaDoc[children.size()];
1925        children.copyInto(c);
1926        ((BranchElement)to).replace(0, 0, c);
1927        return to;
1928        } else {
1929        throw new StateInvariantError JavaDoc(
1930            "No support to join leaf element with non-leaf element");
1931        }
1932    }
1933
1934    /**
1935     * Creates a copy of this element, with a different
1936     * parent.
1937         *
1938         * @param parent the parent element
1939         * @param clonee the element to be cloned
1940         * @return the copy
1941     */

1942        public Element JavaDoc clone(Element JavaDoc parent, Element JavaDoc clonee) {
1943        if (clonee.isLeaf()) {
1944        return createLeafElement(parent, clonee.getAttributes(),
1945                     clonee.getStartOffset(),
1946                     clonee.getEndOffset());
1947        }
1948        Element JavaDoc e = createBranchElement(parent, clonee.getAttributes());
1949        int n = clonee.getElementCount();
1950        Element JavaDoc[] children = new Element JavaDoc[n];
1951        for (int i = 0; i < n; i++) {
1952        children[i] = clone(e, clonee.getElement(i));
1953        }
1954        ((BranchElement)e).replace(0, 0, children);
1955        return e;
1956    }
1957
1958        /**
1959         * Creates a copy of this element, with a different
1960         * parent. Children of this element included in the
1961         * removal range will be discarded.
1962         */

1963        Element JavaDoc cloneAsNecessary(Element JavaDoc parent, Element JavaDoc clonee, int rmOffs0, int rmOffs1) {
1964            if (clonee.isLeaf()) {
1965                return createLeafElement(parent, clonee.getAttributes(),
1966                                         clonee.getStartOffset(),
1967                                         clonee.getEndOffset());
1968            }
1969            Element JavaDoc e = createBranchElement(parent, clonee.getAttributes());
1970            int n = clonee.getElementCount();
1971            ArrayList JavaDoc childrenList = new ArrayList JavaDoc(n);
1972            for (int i = 0; i < n; i++) {
1973                Element JavaDoc elem = clonee.getElement(i);
1974                if (elem.getStartOffset() < rmOffs0 || elem.getEndOffset() > rmOffs1) {
1975                    childrenList.add(cloneAsNecessary(e, elem, rmOffs0, rmOffs1));
1976                }
1977            }
1978            Element JavaDoc[] children = new Element JavaDoc[childrenList.size()];
1979            children = (Element JavaDoc[])childrenList.toArray(children);
1980            ((BranchElement)e).replace(0, 0, children);
1981            return e;
1982        }
1983
1984    /**
1985     * Determines if a fracture needs to be performed. A fracture
1986     * can be thought of as moving the right part of a tree to a
1987     * new location, where the right part is determined by what has
1988     * been inserted. <code>depth</code> is used to indicate a
1989     * JoinToFracture is needed to an element at a depth
1990     * of <code>depth</code>. Where the root is 0, 1 is the children
1991     * of the root...
1992     * <p>This will invoke <code>fractureFrom</code> if it is determined
1993     * a fracture needs to happen.
1994     */

1995    void fracture(int depth) {
1996        int cLength = insertPath.length;
1997        int lastIndex = -1;
1998        boolean needRecreate = recreateLeafs;
1999        ElemChanges lastChange = insertPath[cLength - 1];
2000        // Use childAltered to determine when a child has been altered,
2001
// that is the point of insertion is less than the element count.
2002
boolean childAltered = ((lastChange.index + 1) <
2003                    lastChange.parent.getElementCount());
2004        int deepestAlteredIndex = (needRecreate) ? cLength : -1;
2005        int lastAlteredIndex = cLength - 1;
2006
2007        createdFracture = true;
2008        // Determine where to start recreating from.
2009
// Start at - 2, as first one is indicated by recreateLeafs and
2010
// childAltered.
2011
for(int counter = cLength - 2; counter >= 0; counter--) {
2012        ElemChanges change = insertPath[counter];
2013        if(change.added.size() > 0 || counter == depth) {
2014            lastIndex = counter;
2015            if(!needRecreate && childAltered) {
2016            needRecreate = true;
2017            if(deepestAlteredIndex == -1)
2018                deepestAlteredIndex = lastAlteredIndex + 1;
2019            }
2020        }
2021        if(!childAltered && change.index <
2022           change.parent.getElementCount()) {
2023            childAltered = true;
2024            lastAlteredIndex = counter;
2025        }
2026        }
2027        if(needRecreate) {
2028        // Recreate all children to right of parent starting
2029
// at lastIndex.
2030
if(lastIndex == -1)
2031            lastIndex = cLength - 1;
2032        fractureFrom(insertPath, lastIndex, deepestAlteredIndex);
2033        }
2034    }
2035
2036    /**
2037     * Recreates the elements to the right of the insertion point.
2038     * This starts at <code>startIndex</code> in <code>changed</code>,
2039     * and calls duplicate to duplicate existing elements.
2040     * This will also duplicate the elements along the insertion
2041     * point, until a depth of <code>endFractureIndex</code> is
2042     * reached, at which point only the elements to the right of
2043     * the insertion point are duplicated.
2044     */

2045    void fractureFrom(ElemChanges[] changed, int startIndex,
2046              int endFractureIndex) {
2047        // Recreate the element representing the inserted index.
2048
ElemChanges change = changed[startIndex];
2049        Element JavaDoc child;
2050        Element JavaDoc newChild;
2051        int changeLength = changed.length;
2052
2053        if((startIndex + 1) == changeLength)
2054        child = change.parent.getElement(change.index);
2055        else
2056        child = change.parent.getElement(change.index - 1);
2057        if(child.isLeaf()) {
2058        newChild = createLeafElement(change.parent,
2059                   child.getAttributes(), Math.max(endOffset,
2060                   child.getStartOffset()), child.getEndOffset());
2061        }
2062        else {
2063        newChild = createBranchElement(change.parent,
2064                           child.getAttributes());
2065        }
2066        fracturedParent = change.parent;
2067        fracturedChild = newChild;
2068
2069        // Recreate all the elements to the right of the
2070
// insertion point.
2071
Element JavaDoc parent = newChild;
2072
2073        while(++startIndex < endFractureIndex) {
2074        boolean isEnd = ((startIndex + 1) == endFractureIndex);
2075        boolean isEndLeaf = ((startIndex + 1) == changeLength);
2076
2077        // Create the newChild, a duplicate of the elment at
2078
// index. This isn't done if isEnd and offsetLastIndex are true
2079
// indicating a join previous was done.
2080
change = changed[startIndex];
2081
2082        // Determine the child to duplicate, won't have to duplicate
2083
// if at end of fracture, or offseting index.
2084
if(isEnd) {
2085            if(offsetLastIndex || !isEndLeaf)
2086            child = null;
2087            else
2088            child = change.parent.getElement(change.index);
2089        }
2090        else {
2091            child = change.parent.getElement(change.index - 1);
2092        }
2093        // Duplicate it.
2094
if(child != null) {
2095            if(child.isLeaf()) {
2096            newChild = createLeafElement(parent,
2097                   child.getAttributes(), Math.max(endOffset,
2098                   child.getStartOffset()), child.getEndOffset());
2099            }
2100            else {
2101            newChild = createBranchElement(parent,
2102                           child.getAttributes());
2103            }
2104        }
2105        else
2106            newChild = null;
2107
2108        // Recreate the remaining children (there may be none).
2109
int kidsToMove = change.parent.getElementCount() -
2110                         change.index;
2111        Element JavaDoc[] kids;
2112        int moveStartIndex;
2113        int kidStartIndex = 1;
2114
2115        if(newChild == null) {
2116            // Last part of fracture.
2117
if(isEndLeaf) {
2118            kidsToMove--;
2119            moveStartIndex = change.index + 1;
2120            }
2121            else {
2122            moveStartIndex = change.index;
2123            }
2124            kidStartIndex = 0;
2125            kids = new Element JavaDoc[kidsToMove];
2126        }
2127        else {
2128            if(!isEnd) {
2129            // Branch.
2130
kidsToMove++;
2131            moveStartIndex = change.index;
2132            }
2133            else {
2134            // Last leaf, need to recreate part of it.
2135
moveStartIndex = change.index + 1;
2136            }
2137            kids = new Element JavaDoc[kidsToMove];
2138            kids[0] = newChild;
2139        }
2140
2141        for(int counter = kidStartIndex; counter < kidsToMove;
2142            counter++) {
2143            Element JavaDoc toMove =change.parent.getElement(moveStartIndex++);
2144            kids[counter] = recreateFracturedElement(parent, toMove);
2145            change.removed.addElement(toMove);
2146        }
2147        ((BranchElement)parent).replace(0, 0, kids);
2148        parent = newChild;
2149        }
2150    }
2151
2152    /**
2153     * Recreates <code>toDuplicate</code>. This is called when an
2154     * element needs to be created as the result of an insertion. This
2155     * will recurse and create all the children. This is similiar to
2156     * <code>clone</code>, but deteremines the offsets differently.
2157     */

2158    Element JavaDoc recreateFracturedElement(Element JavaDoc parent, Element JavaDoc toDuplicate) {
2159        if(toDuplicate.isLeaf()) {
2160        return createLeafElement(parent, toDuplicate.getAttributes(),
2161                     Math.max(toDuplicate.getStartOffset(),
2162                          endOffset),
2163                     toDuplicate.getEndOffset());
2164        }
2165        // Not a leaf
2166
Element JavaDoc newParent = createBranchElement(parent, toDuplicate.
2167                            getAttributes());
2168        int childCount = toDuplicate.getElementCount();
2169        Element JavaDoc[] newKids = new Element JavaDoc[childCount];
2170        for(int counter = 0; counter < childCount; counter++) {
2171        newKids[counter] = recreateFracturedElement(newParent,
2172                         toDuplicate.getElement(counter));
2173        }
2174        ((BranchElement)newParent).replace(0, 0, newKids);
2175        return newParent;
2176    }
2177
2178    /**
2179     * Splits the bottommost leaf in <code>path</code>.
2180     * This is called from insert when the first element is NOT content.
2181     */

2182    void fractureDeepestLeaf(ElementSpec[] specs) {
2183        // Split the bottommost leaf. It will be recreated elsewhere.
2184
ElemChanges ec = (ElemChanges) path.peek();
2185        Element JavaDoc child = ec.parent.getElement(ec.index);
2186        // Inserts at offset 0 do not need to recreate child (it would
2187
// have a length of 0!).
2188
if (offset != 0) {
2189        Element JavaDoc newChild = createLeafElement(ec.parent,
2190                         child.getAttributes(),
2191                         child.getStartOffset(),
2192                         offset);
2193
2194        ec.added.addElement(newChild);
2195        }
2196        ec.removed.addElement(child);
2197        if(child.getEndOffset() != endOffset)
2198        recreateLeafs = true;
2199        else
2200        offsetLastIndex = true;
2201    }
2202
2203    /**
2204     * Inserts the first content. This needs to be separate to handle
2205     * joining.
2206     */

2207    void insertFirstContent(ElementSpec[] specs) {
2208        ElementSpec firstSpec = specs[0];
2209        ElemChanges ec = (ElemChanges) path.peek();
2210        Element JavaDoc child = ec.parent.getElement(ec.index);
2211        int firstEndOffset = offset + firstSpec.getLength();
2212        boolean isOnlyContent = (specs.length == 1);
2213
2214        switch(firstSpec.getDirection()) {
2215        case ElementSpec.JoinPreviousDirection:
2216        if(child.getEndOffset() != firstEndOffset &&
2217            !isOnlyContent) {
2218            // Create the left split part containing new content.
2219
Element JavaDoc newE = createLeafElement(ec.parent,
2220                child.getAttributes(), child.getStartOffset(),
2221                firstEndOffset);
2222            ec.added.addElement(newE);
2223            ec.removed.addElement(child);
2224            // Remainder will be created later.
2225
if(child.getEndOffset() != endOffset)
2226            recreateLeafs = true;
2227            else
2228            offsetLastIndex = true;
2229        }
2230        else {
2231            offsetLastIndex = true;
2232            offsetLastIndexOnReplace = true;
2233        }
2234        // else Inserted at end, and is total length.
2235
// Update index incase something added/removed.
2236
break;
2237        case ElementSpec.JoinNextDirection:
2238        if(offset != 0) {
2239            // Recreate the first element, its offset will have
2240
// changed.
2241
Element JavaDoc newE = createLeafElement(ec.parent,
2242                child.getAttributes(), child.getStartOffset(),
2243                offset);
2244            ec.added.addElement(newE);
2245            // Recreate the second, merge part. We do no checking
2246
// to see if JoinNextDirection is valid here!
2247
Element JavaDoc nextChild = ec.parent.getElement(ec.index + 1);
2248            if(isOnlyContent)
2249            newE = createLeafElement(ec.parent, nextChild.
2250                getAttributes(), offset, nextChild.getEndOffset());
2251            else
2252            newE = createLeafElement(ec.parent, nextChild.
2253                getAttributes(), offset, firstEndOffset);
2254            ec.added.addElement(newE);
2255            ec.removed.addElement(child);
2256            ec.removed.addElement(nextChild);
2257        }
2258        // else nothin to do.
2259
// PENDING: if !isOnlyContent could raise here!
2260
break;
2261        default:
2262        // Inserted into middle, need to recreate split left
2263
// new content, and split right.
2264
if(child.getStartOffset() != offset) {
2265            Element JavaDoc newE = createLeafElement(ec.parent,
2266                child.getAttributes(), child.getStartOffset(),
2267                offset);
2268            ec.added.addElement(newE);
2269        }
2270        ec.removed.addElement(child);
2271        // new content
2272
Element JavaDoc newE = createLeafElement(ec.parent,
2273                         firstSpec.getAttributes(),
2274                         offset, firstEndOffset);
2275        ec.added.addElement(newE);
2276        if(child.getEndOffset() != endOffset) {
2277            // Signals need to recreate right split later.
2278
recreateLeafs = true;
2279        }
2280        else {
2281            offsetLastIndex = true;
2282        }
2283        break;
2284        }
2285    }
2286
2287    Element JavaDoc root;
2288    transient int pos; // current position
2289
transient int offset;
2290    transient int length;
2291    transient int endOffset;
2292    transient Vector JavaDoc changes; // Vector<ElemChanges>
2293
transient Stack JavaDoc path; // Stack<ElemChanges>
2294
transient boolean insertOp;
2295
2296    transient boolean recreateLeafs; // For insert.
2297

2298    /** For insert, path to inserted elements. */
2299    transient ElemChanges[] insertPath;
2300    /** Only for insert, set to true when the fracture has been created. */
2301    transient boolean createdFracture;
2302    /** Parent that contains the fractured child. */
2303    transient Element JavaDoc fracturedParent;
2304    /** Fractured child. */
2305    transient Element JavaDoc fracturedChild;
2306    /** Used to indicate when fracturing that the last leaf should be
2307     * skipped. */

2308    transient boolean offsetLastIndex;
2309    /** Used to indicate that the parent of the deepest leaf should
2310     * offset the index by 1 when adding/removing elements in an
2311     * insert. */

2312    transient boolean offsetLastIndexOnReplace;
2313
2314    /*
2315     * Internal record used to hold element change specifications
2316     */

2317    class ElemChanges {
2318        
2319        ElemChanges(Element JavaDoc parent, int index, boolean isFracture) {
2320        this.parent = parent;
2321        this.index = index;
2322        this.isFracture = isFracture;
2323        added = new Vector JavaDoc();
2324        removed = new Vector JavaDoc();
2325        }
2326
2327            public String JavaDoc toString() {
2328        return "added: " + added + "\nremoved: " + removed + "\n";
2329        }
2330        
2331        Element JavaDoc parent;
2332        int index;
2333        Vector JavaDoc added;
2334        Vector JavaDoc removed;
2335        boolean isFracture;
2336    }
2337
2338    }
2339
2340    /**
2341     * An UndoableEdit used to remember AttributeSet changes to an
2342     * Element.
2343     */

2344    public static class AttributeUndoableEdit extends AbstractUndoableEdit JavaDoc {
2345    public AttributeUndoableEdit(Element JavaDoc element, AttributeSet JavaDoc newAttributes,
2346                  boolean isReplacing) {
2347        super();
2348        this.element = element;
2349        this.newAttributes = newAttributes;
2350        this.isReplacing = isReplacing;
2351        // If not replacing, it may be more efficient to only copy the
2352
// changed values...
2353
copy = element.getAttributes().copyAttributes();
2354    }
2355
2356    /**
2357     * Redoes a change.
2358     *
2359     * @exception CannotRedoException if the change cannot be redone
2360     */

2361        public void redo() throws CannotRedoException JavaDoc {
2362        super.redo();
2363        MutableAttributeSet JavaDoc as = (MutableAttributeSet JavaDoc)element
2364                             .getAttributes();
2365        if(isReplacing)
2366        as.removeAttributes(as);
2367        as.addAttributes(newAttributes);
2368    }
2369
2370    /**
2371     * Undoes a change.
2372     *
2373     * @exception CannotUndoException if the change cannot be undone
2374     */

2375        public void undo() throws CannotUndoException JavaDoc {
2376        super.undo();
2377        MutableAttributeSet JavaDoc as = (MutableAttributeSet JavaDoc)element.getAttributes();
2378        as.removeAttributes(as);
2379        as.addAttributes(copy);
2380    }
2381
2382    // AttributeSet containing additional entries, must be non-mutable!
2383
protected AttributeSet JavaDoc newAttributes;
2384    // Copy of the AttributeSet the Element contained.
2385
protected AttributeSet JavaDoc copy;
2386    // true if all the attributes in the element were removed first.
2387
protected boolean isReplacing;
2388    // Efected Element.
2389
protected Element JavaDoc element;
2390    }
2391
2392    /**
2393     * UndoableEdit for changing the resolve parent of an Element.
2394     */

2395    static class StyleChangeUndoableEdit extends AbstractUndoableEdit JavaDoc {
2396    public StyleChangeUndoableEdit(AbstractElement element,
2397                       Style JavaDoc newStyle) {
2398        super();
2399        this.element = element;
2400        this.newStyle = newStyle;
2401        oldStyle = element.getResolveParent();
2402    }
2403
2404    /**
2405     * Redoes a change.
2406     *
2407     * @exception CannotRedoException if the change cannot be redone
2408     */

2409        public void redo() throws CannotRedoException JavaDoc {
2410        super.redo();
2411        element.setResolveParent(newStyle);
2412    }
2413
2414    /**
2415     * Undoes a change.
2416     *
2417     * @exception CannotUndoException if the change cannot be undone
2418     */

2419        public void undo() throws CannotUndoException JavaDoc {
2420        super.undo();
2421        element.setResolveParent(oldStyle);
2422    }
2423
2424    /** Element to change resolve parent of. */
2425    protected AbstractElement element;
2426    /** New style. */
2427    protected Style JavaDoc newStyle;
2428    /** Old style, before setting newStyle. */
2429    protected AttributeSet JavaDoc oldStyle;
2430    }
2431
2432
2433    /**
2434     * Added to all the Styles. When instances of this receive a
2435     * stateChanged method, styleChanged is invoked.
2436     */

2437    class StyleChangeHandler implements ChangeListener {
2438        public void stateChanged(ChangeEvent e) {
2439        Object JavaDoc source = e.getSource();
2440
2441        if (source instanceof Style JavaDoc) {
2442        styleChanged((Style JavaDoc)source);
2443        }
2444        else {
2445        styleChanged(null);
2446        }
2447    }
2448    }
2449
2450
2451    /**
2452     * Added to the StyleContext. When the StyleContext changes, this invokes
2453     * <code>updateStylesListeningTo</code>.
2454     */

2455    class StyleContextChangeHandler implements ChangeListener {
2456        public void stateChanged(ChangeEvent e) {
2457        updateStylesListeningTo();
2458    }
2459    }
2460
2461
2462    /**
2463     * When run this creates a change event for the complete document
2464     * and fires it.
2465     */

2466    class ChangeUpdateRunnable implements Runnable JavaDoc {
2467        boolean isPending = false;
2468        
2469    public void run() {
2470            synchronized(this) {
2471                isPending = false;
2472            }
2473            
2474        try {
2475        writeLock();
2476        DefaultDocumentEvent dde = new DefaultDocumentEvent(0,
2477                          getLength(),
2478                          DocumentEvent.EventType.CHANGE);
2479        dde.end();
2480        fireChangedUpdate(dde);
2481        } finally {
2482        writeUnlock();
2483        }
2484    }
2485    }
2486}
2487
2488
2489
2490
2491
Popular Tags