blob: 4939a7615c341975d4bf15d8bc8475174ecd90a6 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1997-2007 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25package javax.swing.text;
26
27import java.awt.Color;
28import java.awt.Component;
29import java.awt.Font;
30import java.awt.FontMetrics;
31import java.awt.font.TextAttribute;
32import java.lang.ref.ReferenceQueue;
33import java.lang.ref.WeakReference;
34import java.util.Enumeration;
35import java.util.HashMap;
36import java.util.Hashtable;
37import java.util.List;
38import java.util.Map;
39import java.util.Stack;
40import java.util.Vector;
41import java.util.ArrayList;
42import java.io.IOException;
43import java.io.ObjectInputStream;
44import java.io.ObjectOutputStream;
45import java.io.Serializable;
46import javax.swing.Icon;
47import javax.swing.event.*;
48import javax.swing.undo.AbstractUndoableEdit;
49import javax.swing.undo.CannotRedoException;
50import javax.swing.undo.CannotUndoException;
51import javax.swing.undo.UndoableEdit;
52import javax.swing.SwingUtilities;
53
54/**
55 * A document that can be marked up with character and paragraph
56 * styles in a manner similar to the Rich Text Format. The element
57 * structure for this document represents style crossings for
58 * style runs. These style runs are mapped into a paragraph element
59 * structure (which may reside in some other structure). The
60 * style runs break at paragraph boundaries since logical styles are
61 * assigned to paragraph boundaries.
62 * <p>
63 * <strong>Warning:</strong>
64 * Serialized objects of this class will not be compatible with
65 * future Swing releases. The current serialization support is
66 * appropriate for short term storage or RMI between applications running
67 * the same version of Swing. As of 1.4, support for long term storage
68 * of all JavaBeans<sup><font size="-2">TM</font></sup>
69 * has been added to the <code>java.beans</code> package.
70 * Please see {@link java.beans.XMLEncoder}.
71 *
72 * @author Timothy Prinzing
73 * @see Document
74 * @see AbstractDocument
75 */
76public class DefaultStyledDocument extends AbstractDocument implements StyledDocument {
77
78 /**
79 * Constructs a styled document.
80 *
81 * @param c the container for the content
82 * @param styles resources and style definitions which may
83 * be shared across documents
84 */
85 public DefaultStyledDocument(Content c, StyleContext styles) {
86 super(c, styles);
87 listeningStyles = new Vector();
88 buffer = new ElementBuffer(createDefaultRoot());
89 Style defaultStyle = styles.getStyle(StyleContext.DEFAULT_STYLE);
90 setLogicalStyle(0, defaultStyle);
91 }
92
93 /**
94 * Constructs a styled document with the default content
95 * storage implementation and a shared set of styles.
96 *
97 * @param styles the styles
98 */
99 public DefaultStyledDocument(StyleContext styles) {
100 this(new GapContent(BUFFER_SIZE_DEFAULT), styles);
101 }
102
103 /**
104 * Constructs a default styled document. This buffers
105 * input content by a size of <em>BUFFER_SIZE_DEFAULT</em>
106 * and has a style context that is scoped by the lifetime
107 * of the document and is not shared with other documents.
108 */
109 public DefaultStyledDocument() {
110 this(new GapContent(BUFFER_SIZE_DEFAULT), new StyleContext());
111 }
112
113 /**
114 * Gets the default root element.
115 *
116 * @return the root
117 * @see Document#getDefaultRootElement
118 */
119 public Element getDefaultRootElement() {
120 return buffer.getRootElement();
121 }
122
123 /**
124 * Initialize the document to reflect the given element
125 * structure (i.e. the structure reported by the
126 * <code>getDefaultRootElement</code> method. If the
127 * document contained any data it will first be removed.
128 */
129 protected void create(ElementSpec[] data) {
130 try {
131 if (getLength() != 0) {
132 remove(0, getLength());
133 }
134 writeLock();
135
136 // install the content
137 Content c = getContent();
138 int n = data.length;
139 StringBuffer sb = new StringBuffer();
140 for (int i = 0; i < n; i++) {
141 ElementSpec es = data[i];
142 if (es.getLength() > 0) {
143 sb.append(es.getArray(), es.getOffset(), es.getLength());
144 }
145 }
146 UndoableEdit cEdit = c.insertString(0, sb.toString());
147
148 // build the event and element structure
149 int length = sb.length();
150 DefaultDocumentEvent evnt =
151 new DefaultDocumentEvent(0, length, DocumentEvent.EventType.INSERT);
152 evnt.addEdit(cEdit);
153 buffer.create(length, data, evnt);
154
155 // update bidi (possibly)
156 super.insertUpdate(evnt, null);
157
158 // notify the listeners
159 evnt.end();
160 fireInsertUpdate(evnt);
161 fireUndoableEditUpdate(new UndoableEditEvent(this, evnt));
162 } catch (BadLocationException ble) {
163 throw new StateInvariantError("problem initializing");
164 } finally {
165 writeUnlock();
166 }
167
168 }
169
170 /**
171 * Inserts new elements in bulk. This is useful to allow
172 * parsing with the document in an unlocked state and
173 * prepare an element structure modification. This method
174 * takes an array of tokens that describe how to update an
175 * element structure so the time within a write lock can
176 * be greatly reduced in an asynchronous update situation.
177 * <p>
178 * This method is thread safe, although most Swing methods
179 * are not. Please see
180 * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
181 * to Use Threads</A> for more information.
182 *
183 * @param offset the starting offset >= 0
184 * @param data the element data
185 * @exception BadLocationException for an invalid starting offset
186 */
187 protected void insert(int offset, ElementSpec[] data) throws BadLocationException {
188 if (data == null || data.length == 0) {
189 return;
190 }
191
192 try {
193 writeLock();
194
195 // install the content
196 Content c = getContent();
197 int n = data.length;
198 StringBuffer sb = new StringBuffer();
199 for (int i = 0; i < n; i++) {
200 ElementSpec es = data[i];
201 if (es.getLength() > 0) {
202 sb.append(es.getArray(), es.getOffset(), es.getLength());
203 }
204 }
205 if (sb.length() == 0) {
206 // Nothing to insert, bail.
207 return;
208 }
209 UndoableEdit cEdit = c.insertString(offset, sb.toString());
210
211 // create event and build the element structure
212 int length = sb.length();
213 DefaultDocumentEvent evnt =
214 new DefaultDocumentEvent(offset, length, DocumentEvent.EventType.INSERT);
215 evnt.addEdit(cEdit);
216 buffer.insert(offset, length, data, evnt);
217
218 // update bidi (possibly)
219 super.insertUpdate(evnt, null);
220
221 // notify the listeners
222 evnt.end();
223 fireInsertUpdate(evnt);
224 fireUndoableEditUpdate(new UndoableEditEvent(this, evnt));
225 } finally {
226 writeUnlock();
227 }
228 }
229
230 /**
231 * Removes an element from this document.
232 *
233 * <p>The element is removed from its parent element, as well as
234 * the text in the range identified by the element. If the
235 * element isn't associated with the document, {@code
236 * IllegalArgumentException} is thrown.</p>
237 *
238 * <p>As empty branch elements are not allowed in the document, if the
239 * element is the sole child, its parent element is removed as well,
240 * recursively. This means that when replacing all the children of a
241 * particular element, new children should be added <em>before</em>
242 * removing old children.
243 *
244 * <p>Element removal results in two events being fired, the
245 * {@code DocumentEvent} for changes in element structure and {@code
246 * UndoableEditEvent} for changes in document content.</p>
247 *
248 * <p>If the element contains end-of-content mark (the last {@code
249 * "\n"} character in document), this character is not removed;
250 * instead, preceding leaf element is extended to cover the
251 * character. If the last leaf already ends with {@code "\n",} it is
252 * included in content removal.</p>
253 *
254 * <p>If the element is {@code null,} {@code NullPointerException} is
255 * thrown. If the element structure would become invalid after the removal,
256 * for example if the element is the document root element, {@code
257 * IllegalArgumentException} is thrown. If the current element structure is
258 * invalid, {@code IllegalStateException} is thrown.</p>
259 *
260 * @param elem the element to remove
261 * @throws NullPointerException if the element is {@code null}
262 * @throws IllegalArgumentException if the element could not be removed
263 * @throws IllegalStateException if the element structure is invalid
264 *
265 * @since 1.7
266 */
267 public void removeElement(Element elem) {
268 try {
269 writeLock();
270 removeElementImpl(elem);
271 } finally {
272 writeUnlock();
273 }
274 }
275
276 private void removeElementImpl(Element elem) {
277 if (elem.getDocument() != this) {
278 throw new IllegalArgumentException("element doesn't belong to document");
279 }
280 BranchElement parent = (BranchElement) elem.getParentElement();
281 if (parent == null) {
282 throw new IllegalArgumentException("can't remove the root element");
283 }
284
285 int startOffset = elem.getStartOffset();
286 int removeFrom = startOffset;
287 int endOffset = elem.getEndOffset();
288 int removeTo = endOffset;
289 int lastEndOffset = getLength() + 1;
290 Content content = getContent();
291 boolean atEnd = false;
292 boolean isComposedText = Utilities.isComposedTextElement(elem);
293
294 if (endOffset >= lastEndOffset) {
295 // element includes the last "\n" character, needs special handling
296 if (startOffset <= 0) {
297 throw new IllegalArgumentException("can't remove the whole content");
298 }
299 removeTo = lastEndOffset - 1; // last "\n" must not be removed
300 try {
301 if (content.getString(startOffset - 1, 1).charAt(0) == '\n') {
302 removeFrom--; // preceding leaf ends with "\n", remove it
303 }
304 } catch (BadLocationException ble) { // can't happen
305 throw new IllegalStateException(ble);
306 }
307 atEnd = true;
308 }
309 int length = removeTo - removeFrom;
310
311 DefaultDocumentEvent dde = new DefaultDocumentEvent(removeFrom,
312 length, DefaultDocumentEvent.EventType.REMOVE);
313 UndoableEdit ue = null;
314 // do not leave empty branch elements
315 while (parent.getElementCount() == 1) {
316 elem = parent;
317 parent = (BranchElement) parent.getParentElement();
318 if (parent == null) { // shouldn't happen
319 throw new IllegalStateException("invalid element structure");
320 }
321 }
322 Element[] removed = { elem };
323 Element[] added = {};
324 int index = parent.getElementIndex(startOffset);
325 parent.replace(index, 1, added);
326 dde.addEdit(new ElementEdit(parent, index, removed, added));
327 if (length > 0) {
328 try {
329 ue = content.remove(removeFrom, length);
330 if (ue != null) {
331 dde.addEdit(ue);
332 }
333 } catch (BadLocationException ble) {
334 // can only happen if the element structure is severely broken
335 throw new IllegalStateException(ble);
336 }
337 lastEndOffset -= length;
338 }
339
340 if (atEnd) {
341 // preceding leaf element should be extended to cover orphaned "\n"
342 Element prevLeaf = parent.getElement(parent.getElementCount() - 1);
343 while ((prevLeaf != null) && !prevLeaf.isLeaf()) {
344 prevLeaf = prevLeaf.getElement(prevLeaf.getElementCount() - 1);
345 }
346 if (prevLeaf == null) { // shouldn't happen
347 throw new IllegalStateException("invalid element structure");
348 }
349 int prevStartOffset = prevLeaf.getStartOffset();
350 BranchElement prevParent = (BranchElement) prevLeaf.getParentElement();
351 int prevIndex = prevParent.getElementIndex(prevStartOffset);
352 Element newElem = null;
353 newElem = createLeafElement(prevParent, prevLeaf.getAttributes(),
354 prevStartOffset, lastEndOffset);
355 Element[] prevRemoved = { prevLeaf };
356 Element[] prevAdded = { newElem };
357 prevParent.replace(prevIndex, 1, prevAdded);
358 dde.addEdit(new ElementEdit(prevParent, prevIndex,
359 prevRemoved, prevAdded));
360 }
361
362 postRemoveUpdate(dde);
363 dde.end();
364 fireRemoveUpdate(dde);
365 if (! (isComposedText && (ue != null))) {
366 // do not fire UndoabeEdit event for composed text edit (unsupported)
367 fireUndoableEditUpdate(new UndoableEditEvent(this, dde));
368 }
369 }
370
371 /**
372 * Adds a new style into the logical style hierarchy. Style attributes
373 * resolve from bottom up so an attribute specified in a child
374 * will override an attribute specified in the parent.
375 *
376 * @param nm the name of the style (must be unique within the
377 * collection of named styles). The name may be null if the style
378 * is unnamed, but the caller is responsible
379 * for managing the reference returned as an unnamed style can't
380 * be fetched by name. An unnamed style may be useful for things
381 * like character attribute overrides such as found in a style
382 * run.
383 * @param parent the parent style. This may be null if unspecified
384 * attributes need not be resolved in some other style.
385 * @return the style
386 */
387 public Style addStyle(String nm, Style parent) {
388 StyleContext styles = (StyleContext) getAttributeContext();
389 return styles.addStyle(nm, parent);
390 }
391
392 /**
393 * Removes a named style previously added to the document.
394 *
395 * @param nm the name of the style to remove
396 */
397 public void removeStyle(String nm) {
398 StyleContext styles = (StyleContext) getAttributeContext();
399 styles.removeStyle(nm);
400 }
401
402 /**
403 * Fetches a named style previously added.
404 *
405 * @param nm the name of the style
406 * @return the style
407 */
408 public Style getStyle(String nm) {
409 StyleContext styles = (StyleContext) getAttributeContext();
410 return styles.getStyle(nm);
411 }
412
413
414 /**
415 * Fetches the list of of style names.
416 *
417 * @return all the style names
418 */
419 public Enumeration<?> getStyleNames() {
420 return ((StyleContext) getAttributeContext()).getStyleNames();
421 }
422
423 /**
424 * Sets the logical style to use for the paragraph at the
425 * given position. If attributes aren't explicitly set
426 * for character and paragraph attributes they will resolve
427 * through the logical style assigned to the paragraph, which
428 * in turn may resolve through some hierarchy completely
429 * independent of the element hierarchy in the document.
430 * <p>
431 * This method is thread safe, although most Swing methods
432 * are not. Please see
433 * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
434 * to Use Threads</A> for more information.
435 *
436 * @param pos the offset from the start of the document >= 0
437 * @param s the logical style to assign to the paragraph, null if none
438 */
439 public void setLogicalStyle(int pos, Style s) {
440 Element paragraph = getParagraphElement(pos);
441 if ((paragraph != null) && (paragraph instanceof AbstractElement)) {
442 try {
443 writeLock();
444 StyleChangeUndoableEdit edit = new StyleChangeUndoableEdit((AbstractElement)paragraph, s);
445 ((AbstractElement)paragraph).setResolveParent(s);
446 int p0 = paragraph.getStartOffset();
447 int p1 = paragraph.getEndOffset();
448 DefaultDocumentEvent e =
449 new DefaultDocumentEvent(p0, p1 - p0, DocumentEvent.EventType.CHANGE);
450 e.addEdit(edit);
451 e.end();
452 fireChangedUpdate(e);
453 fireUndoableEditUpdate(new UndoableEditEvent(this, e));
454 } finally {
455 writeUnlock();
456 }
457 }
458 }
459
460 /**
461 * Fetches the logical style assigned to the paragraph
462 * represented by the given position.
463 *
464 * @param p the location to translate to a paragraph
465 * and determine the logical style assigned >= 0. This
466 * is an offset from the start of the document.
467 * @return the style, null if none
468 */
469 public Style getLogicalStyle(int p) {
470 Style s = null;
471 Element paragraph = getParagraphElement(p);
472 if (paragraph != null) {
473 AttributeSet a = paragraph.getAttributes();
474 AttributeSet parent = a.getResolveParent();
475 if (parent instanceof Style) {
476 s = (Style) parent;
477 }
478 }
479 return s;
480 }
481
482 /**
483 * Sets attributes for some part of the document.
484 * A write lock is held by this operation while changes
485 * are being made, and a DocumentEvent is sent to the listeners
486 * after the change has been successfully completed.
487 * <p>
488 * This method is thread safe, although most Swing methods
489 * are not. Please see
490 * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
491 * to Use Threads</A> for more information.
492 *
493 * @param offset the offset in the document >= 0
494 * @param length the length >= 0
495 * @param s the attributes
496 * @param replace true if the previous attributes should be replaced
497 * before setting the new attributes
498 */
499 public void setCharacterAttributes(int offset, int length, AttributeSet s, boolean replace) {
500 if (length == 0) {
501 return;
502 }
503 try {
504 writeLock();
505 DefaultDocumentEvent changes =
506 new DefaultDocumentEvent(offset, length, DocumentEvent.EventType.CHANGE);
507
508 // split elements that need it
509 buffer.change(offset, length, changes);
510
511 AttributeSet sCopy = s.copyAttributes();
512
513 // PENDING(prinz) - this isn't a very efficient way to iterate
514 int lastEnd = Integer.MAX_VALUE;
515 for (int pos = offset; pos < (offset + length); pos = lastEnd) {
516 Element run = getCharacterElement(pos);
517 lastEnd = run.getEndOffset();
518 if (pos == lastEnd) {
519 // offset + length beyond length of document, bail.
520 break;
521 }
522 MutableAttributeSet attr = (MutableAttributeSet) run.getAttributes();
523 changes.addEdit(new AttributeUndoableEdit(run, sCopy, replace));
524 if (replace) {
525 attr.removeAttributes(attr);
526 }
527 attr.addAttributes(s);
528 }
529 changes.end();
530 fireChangedUpdate(changes);
531 fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
532 } finally {
533 writeUnlock();
534 }
535
536 }
537
538 /**
539 * Sets attributes for a paragraph.
540 * <p>
541 * This method is thread safe, although most Swing methods
542 * are not. Please see
543 * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
544 * to Use Threads</A> for more information.
545 *
546 * @param offset the offset into the paragraph >= 0
547 * @param length the number of characters affected >= 0
548 * @param s the attributes
549 * @param replace whether to replace existing attributes, or merge them
550 */
551 public void setParagraphAttributes(int offset, int length, AttributeSet s,
552 boolean replace) {
553 try {
554 writeLock();
555 DefaultDocumentEvent changes =
556 new DefaultDocumentEvent(offset, length, DocumentEvent.EventType.CHANGE);
557
558 AttributeSet sCopy = s.copyAttributes();
559
560 // PENDING(prinz) - this assumes a particular element structure
561 Element section = getDefaultRootElement();
562 int index0 = section.getElementIndex(offset);
563 int index1 = section.getElementIndex(offset + ((length > 0) ? length - 1 : 0));
564 boolean isI18N = Boolean.TRUE.equals(getProperty(I18NProperty));
565 boolean hasRuns = false;
566 for (int i = index0; i <= index1; i++) {
567 Element paragraph = section.getElement(i);
568 MutableAttributeSet attr = (MutableAttributeSet) paragraph.getAttributes();
569 changes.addEdit(new AttributeUndoableEdit(paragraph, sCopy, replace));
570 if (replace) {
571 attr.removeAttributes(attr);
572 }
573 attr.addAttributes(s);
574 if (isI18N && !hasRuns) {
575 hasRuns = (attr.getAttribute(TextAttribute.RUN_DIRECTION) != null);
576 }
577 }
578
579 if (hasRuns) {
580 updateBidi( changes );
581 }
582
583 changes.end();
584 fireChangedUpdate(changes);
585 fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
586 } finally {
587 writeUnlock();
588 }
589 }
590
591 /**
592 * Gets the paragraph element at the offset <code>pos</code>.
593 * A paragraph consists of at least one child Element, which is usually
594 * a leaf.
595 *
596 * @param pos the starting offset >= 0
597 * @return the element
598 */
599 public Element getParagraphElement(int pos) {
600 Element e = null;
601 for (e = getDefaultRootElement(); ! e.isLeaf(); ) {
602 int index = e.getElementIndex(pos);
603 e = e.getElement(index);
604 }
605 if(e != null)
606 return e.getParentElement();
607 return e;
608 }
609
610 /**
611 * Gets a character element based on a position.
612 *
613 * @param pos the position in the document >= 0
614 * @return the element
615 */
616 public Element getCharacterElement(int pos) {
617 Element e = null;
618 for (e = getDefaultRootElement(); ! e.isLeaf(); ) {
619 int index = e.getElementIndex(pos);
620 e = e.getElement(index);
621 }
622 return e;
623 }
624
625 // --- local methods -------------------------------------------------
626
627 /**
628 * Updates document structure as a result of text insertion. This
629 * will happen within a write lock. This implementation simply
630 * parses the inserted content for line breaks and builds up a set
631 * of instructions for the element buffer.
632 *
633 * @param chng a description of the document change
634 * @param attr the attributes
635 */
636 protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) {
637 int offset = chng.getOffset();
638 int length = chng.getLength();
639 if (attr == null) {
640 attr = SimpleAttributeSet.EMPTY;
641 }
642
643 // Paragraph attributes should come from point after insertion.
644 // You really only notice this when inserting at a paragraph
645 // boundary.
646 Element paragraph = getParagraphElement(offset + length);
647 AttributeSet pattr = paragraph.getAttributes();
648 // Character attributes should come from actual insertion point.
649 Element pParagraph = getParagraphElement(offset);
650 Element run = pParagraph.getElement(pParagraph.getElementIndex
651 (offset));
652 int endOffset = offset + length;
653 boolean insertingAtBoundry = (run.getEndOffset() == endOffset);
654 AttributeSet cattr = run.getAttributes();
655
656 try {
657 Segment s = new Segment();
658 Vector parseBuffer = new Vector();
659 ElementSpec lastStartSpec = null;
660 boolean insertingAfterNewline = false;
661 short lastStartDirection = ElementSpec.OriginateDirection;
662 // Check if the previous character was a newline.
663 if (offset > 0) {
664 getText(offset - 1, 1, s);
665 if (s.array[s.offset] == '\n') {
666 // Inserting after a newline.
667 insertingAfterNewline = true;
668 lastStartDirection = createSpecsForInsertAfterNewline
669 (paragraph, pParagraph, pattr, parseBuffer,
670 offset, endOffset);
671 for(int counter = parseBuffer.size() - 1; counter >= 0;
672 counter--) {
673 ElementSpec spec = (ElementSpec)parseBuffer.
674 elementAt(counter);
675 if(spec.getType() == ElementSpec.StartTagType) {
676 lastStartSpec = spec;
677 break;
678 }
679 }
680 }
681 }
682 // If not inserting after a new line, pull the attributes for
683 // new paragraphs from the paragraph under the insertion point.
684 if(!insertingAfterNewline)
685 pattr = pParagraph.getAttributes();
686
687 getText(offset, length, s);
688 char[] txt = s.array;
689 int n = s.offset + s.count;
690 int lastOffset = s.offset;
691
692 for (int i = s.offset; i < n; i++) {
693 if (txt[i] == '\n') {
694 int breakOffset = i + 1;
695 parseBuffer.addElement(
696 new ElementSpec(attr, ElementSpec.ContentType,
697 breakOffset - lastOffset));
698 parseBuffer.addElement(
699 new ElementSpec(null, ElementSpec.EndTagType));
700 lastStartSpec = new ElementSpec(pattr, ElementSpec.
701 StartTagType);
702 parseBuffer.addElement(lastStartSpec);
703 lastOffset = breakOffset;
704 }
705 }
706 if (lastOffset < n) {
707 parseBuffer.addElement(
708 new ElementSpec(attr, ElementSpec.ContentType,
709 n - lastOffset));
710 }
711
712 ElementSpec first = (ElementSpec) parseBuffer.firstElement();
713
714 int docLength = getLength();
715
716 // Check for join previous of first content.
717 if(first.getType() == ElementSpec.ContentType &&
718 cattr.isEqual(attr)) {
719 first.setDirection(ElementSpec.JoinPreviousDirection);
720 }
721
722 // Do a join fracture/next for last start spec if necessary.
723 if(lastStartSpec != null) {
724 if(insertingAfterNewline) {
725 lastStartSpec.setDirection(lastStartDirection);
726 }
727 // Join to the fracture if NOT inserting at the end
728 // (fracture only happens when not inserting at end of
729 // paragraph).
730 else if(pParagraph.getEndOffset() != endOffset) {
731 lastStartSpec.setDirection(ElementSpec.
732 JoinFractureDirection);
733 }
734 // Join to next if parent of pParagraph has another
735 // element after pParagraph, and it isn't a leaf.
736 else {
737 Element parent = pParagraph.getParentElement();
738 int pParagraphIndex = parent.getElementIndex(offset);
739 if((pParagraphIndex + 1) < parent.getElementCount() &&
740 !parent.getElement(pParagraphIndex + 1).isLeaf()) {
741 lastStartSpec.setDirection(ElementSpec.
742 JoinNextDirection);
743 }
744 }
745 }
746
747 // Do a JoinNext for last spec if it is content, it doesn't
748 // already have a direction set, no new paragraphs have been
749 // inserted or a new paragraph has been inserted and its join
750 // direction isn't originate, and the element at endOffset
751 // is a leaf.
752 if(insertingAtBoundry && endOffset < docLength) {
753 ElementSpec last = (ElementSpec) parseBuffer.lastElement();
754 if(last.getType() == ElementSpec.ContentType &&
755 last.getDirection() != ElementSpec.JoinPreviousDirection &&
756 ((lastStartSpec == null && (paragraph == pParagraph ||
757 insertingAfterNewline)) ||
758 (lastStartSpec != null && lastStartSpec.getDirection() !=
759 ElementSpec.OriginateDirection))) {
760 Element nextRun = paragraph.getElement(paragraph.
761 getElementIndex(endOffset));
762 // Don't try joining to a branch!
763 if(nextRun.isLeaf() &&
764 attr.isEqual(nextRun.getAttributes())) {
765 last.setDirection(ElementSpec.JoinNextDirection);
766 }
767 }
768 }
769 // If not inserting at boundary and there is going to be a
770 // fracture, then can join next on last content if cattr
771 // matches the new attributes.
772 else if(!insertingAtBoundry && lastStartSpec != null &&
773 lastStartSpec.getDirection() ==
774 ElementSpec.JoinFractureDirection) {
775 ElementSpec last = (ElementSpec) parseBuffer.lastElement();
776 if(last.getType() == ElementSpec.ContentType &&
777 last.getDirection() != ElementSpec.JoinPreviousDirection &&
778 attr.isEqual(cattr)) {
779 last.setDirection(ElementSpec.JoinNextDirection);
780 }
781 }
782
783 // Check for the composed text element. If it is, merge the character attributes
784 // into this element as well.
785 if (Utilities.isComposedTextAttributeDefined(attr)) {
786 ((MutableAttributeSet)attr).addAttributes(cattr);
787 ((MutableAttributeSet)attr).addAttribute(AbstractDocument.ElementNameAttribute,
788 AbstractDocument.ContentElementName);
789 }
790
791 ElementSpec[] spec = new ElementSpec[parseBuffer.size()];
792 parseBuffer.copyInto(spec);
793 buffer.insert(offset, length, spec, chng);
794 } catch (BadLocationException bl) {
795 }
796
797 super.insertUpdate( chng, attr );
798 }
799
800 /**
801 * This is called by insertUpdate when inserting after a new line.
802 * It generates, in <code>parseBuffer</code>, ElementSpecs that will
803 * position the stack in <code>paragraph</code>.<p>
804 * It returns the direction the last StartSpec should have (this don't
805 * necessarily create the last start spec).
806 */
807 short createSpecsForInsertAfterNewline(Element paragraph,
808 Element pParagraph, AttributeSet pattr, Vector parseBuffer,
809 int offset, int endOffset) {
810 // Need to find the common parent of pParagraph and paragraph.
811 if(paragraph.getParentElement() == pParagraph.getParentElement()) {
812 // The simple (and common) case that pParagraph and
813 // paragraph have the same parent.
814 ElementSpec spec = new ElementSpec(pattr, ElementSpec.EndTagType);
815 parseBuffer.addElement(spec);
816 spec = new ElementSpec(pattr, ElementSpec.StartTagType);
817 parseBuffer.addElement(spec);
818 if(pParagraph.getEndOffset() != endOffset)
819 return ElementSpec.JoinFractureDirection;
820
821 Element parent = pParagraph.getParentElement();
822 if((parent.getElementIndex(offset) + 1) < parent.getElementCount())
823 return ElementSpec.JoinNextDirection;
824 }
825 else {
826 // Will only happen for text with more than 2 levels.
827 // Find the common parent of a paragraph and pParagraph
828 Vector leftParents = new Vector();
829 Vector rightParents = new Vector();
830 Element e = pParagraph;
831 while(e != null) {
832 leftParents.addElement(e);
833 e = e.getParentElement();
834 }
835 e = paragraph;
836 int leftIndex = -1;
837 while(e != null && (leftIndex = leftParents.indexOf(e)) == -1) {
838 rightParents.addElement(e);
839 e = e.getParentElement();
840 }
841 if(e != null) {
842 // e identifies the common parent.
843 // Build the ends.
844 for(int counter = 0; counter < leftIndex;
845 counter++) {
846 parseBuffer.addElement(new ElementSpec
847 (null, ElementSpec.EndTagType));
848 }
849 // And the starts.
850 ElementSpec spec = null;
851 for(int counter = rightParents.size() - 1;
852 counter >= 0; counter--) {
853 spec = new ElementSpec(((Element)rightParents.
854 elementAt(counter)).getAttributes(),
855 ElementSpec.StartTagType);
856 if(counter > 0)
857 spec.setDirection(ElementSpec.JoinNextDirection);
858 parseBuffer.addElement(spec);
859 }
860 // If there are right parents, then we generated starts
861 // down the right subtree and there will be an element to
862 // join to.
863 if(rightParents.size() > 0)
864 return ElementSpec.JoinNextDirection;
865 // No right subtree, e.getElement(endOffset) is a
866 // leaf. There will be a facture.
867 return ElementSpec.JoinFractureDirection;
868 }
869 // else: Could throw an exception here, but should never get here!
870 }
871 return ElementSpec.OriginateDirection;
872 }
873
874 /**
875 * Updates document structure as a result of text removal.
876 *
877 * @param chng a description of the document change
878 */
879 protected void removeUpdate(DefaultDocumentEvent chng) {
880 super.removeUpdate(chng);
881 buffer.remove(chng.getOffset(), chng.getLength(), chng);
882 }
883
884 /**
885 * Creates the root element to be used to represent the
886 * default document structure.
887 *
888 * @return the element base
889 */
890 protected AbstractElement createDefaultRoot() {
891 // grabs a write-lock for this initialization and
892 // abandon it during initialization so in normal
893 // operation we can detect an illegitimate attempt
894 // to mutate attributes.
895 writeLock();
896 BranchElement section = new SectionElement();
897 BranchElement paragraph = new BranchElement(section, null);
898
899 LeafElement brk = new LeafElement(paragraph, null, 0, 1);
900 Element[] buff = new Element[1];
901 buff[0] = brk;
902 paragraph.replace(0, 0, buff);
903
904 buff[0] = paragraph;
905 section.replace(0, 0, buff);
906 writeUnlock();
907 return section;
908 }
909
910 /**
911 * Gets the foreground color from an attribute set.
912 *
913 * @param attr the attribute set
914 * @return the color
915 */
916 public Color getForeground(AttributeSet attr) {
917 StyleContext styles = (StyleContext) getAttributeContext();
918 return styles.getForeground(attr);
919 }
920
921 /**
922 * Gets the background color from an attribute set.
923 *
924 * @param attr the attribute set
925 * @return the color
926 */
927 public Color getBackground(AttributeSet attr) {
928 StyleContext styles = (StyleContext) getAttributeContext();
929 return styles.getBackground(attr);
930 }
931
932 /**
933 * Gets the font from an attribute set.
934 *
935 * @param attr the attribute set
936 * @return the font
937 */
938 public Font getFont(AttributeSet attr) {
939 StyleContext styles = (StyleContext) getAttributeContext();
940 return styles.getFont(attr);
941 }
942
943 /**
944 * Called when any of this document's styles have changed.
945 * Subclasses may wish to be intelligent about what gets damaged.
946 *
947 * @param style The Style that has changed.
948 */
949 protected void styleChanged(Style style) {
950 // Only propagate change updated if have content
951 if (getLength() != 0) {
952 // lazily create a ChangeUpdateRunnable
953 if (updateRunnable == null) {
954 updateRunnable = new ChangeUpdateRunnable();
955 }
956
957 // We may get a whole batch of these at once, so only
958 // queue the runnable if it is not already pending
959 synchronized(updateRunnable) {
960 if (!updateRunnable.isPending) {
961 SwingUtilities.invokeLater(updateRunnable);
962 updateRunnable.isPending = true;
963 }
964 }
965 }
966 }
967
968 /**
969 * Adds a document listener for notification of any changes.
970 *
971 * @param listener the listener
972 * @see Document#addDocumentListener
973 */
974 public void addDocumentListener(DocumentListener listener) {
975 synchronized(listeningStyles) {
976 int oldDLCount = listenerList.getListenerCount
977 (DocumentListener.class);
978 super.addDocumentListener(listener);
979 if (oldDLCount == 0) {
980 if (styleContextChangeListener == null) {
981 styleContextChangeListener =
982 createStyleContextChangeListener();
983 }
984 if (styleContextChangeListener != null) {
985 StyleContext styles = (StyleContext)getAttributeContext();
986 List<ChangeListener> staleListeners =
987 AbstractChangeHandler.getStaleListeners(styleContextChangeListener);
988 for (ChangeListener l: staleListeners) {
989 styles.removeChangeListener(l);
990 }
991 styles.addChangeListener(styleContextChangeListener);
992 }
993 updateStylesListeningTo();
994 }
995 }
996 }
997
998 /**
999 * Removes a document listener.
1000 *
1001 * @param listener the listener
1002 * @see Document#removeDocumentListener
1003 */
1004 public void removeDocumentListener(DocumentListener listener) {
1005 synchronized(listeningStyles) {
1006 super.removeDocumentListener(listener);
1007 if (listenerList.getListenerCount(DocumentListener.class) == 0) {
1008 for (int counter = listeningStyles.size() - 1; counter >= 0;
1009 counter--) {
1010 ((Style)listeningStyles.elementAt(counter)).
1011 removeChangeListener(styleChangeListener);
1012 }
1013 listeningStyles.removeAllElements();
1014 if (styleContextChangeListener != null) {
1015 StyleContext styles = (StyleContext)getAttributeContext();
1016 styles.removeChangeListener(styleContextChangeListener);
1017 }
1018 }
1019 }
1020 }
1021
1022 /**
1023 * Returns a new instance of StyleChangeHandler.
1024 */
1025 ChangeListener createStyleChangeListener() {
1026 return new StyleChangeHandler(this);
1027 }
1028
1029 /**
1030 * Returns a new instance of StyleContextChangeHandler.
1031 */
1032 ChangeListener createStyleContextChangeListener() {
1033 return new StyleContextChangeHandler(this);
1034 }
1035
1036 /**
1037 * Adds a ChangeListener to new styles, and removes ChangeListener from
1038 * old styles.
1039 */
1040 void updateStylesListeningTo() {
1041 synchronized(listeningStyles) {
1042 StyleContext styles = (StyleContext)getAttributeContext();
1043 if (styleChangeListener == null) {
1044 styleChangeListener = createStyleChangeListener();
1045 }
1046 if (styleChangeListener != null && styles != null) {
1047 Enumeration styleNames = styles.getStyleNames();
1048 Vector v = (Vector)listeningStyles.clone();
1049 listeningStyles.removeAllElements();
1050 List<ChangeListener> staleListeners =
1051 AbstractChangeHandler.getStaleListeners(styleChangeListener);
1052 while (styleNames.hasMoreElements()) {
1053 String name = (String)styleNames.nextElement();
1054 Style aStyle = styles.getStyle(name);
1055 int index = v.indexOf(aStyle);
1056 listeningStyles.addElement(aStyle);
1057 if (index == -1) {
1058 for (ChangeListener l: staleListeners) {
1059 aStyle.removeChangeListener(l);
1060 }
1061 aStyle.addChangeListener(styleChangeListener);
1062 }
1063 else {
1064 v.removeElementAt(index);
1065 }
1066 }
1067 for (int counter = v.size() - 1; counter >= 0; counter--) {
1068 Style aStyle = (Style)v.elementAt(counter);
1069 aStyle.removeChangeListener(styleChangeListener);
1070 }
1071 if (listeningStyles.size() == 0) {
1072 styleChangeListener = null;
1073 }
1074 }
1075 }
1076 }
1077
1078 private void readObject(ObjectInputStream s)
1079 throws ClassNotFoundException, IOException {
1080 listeningStyles = new Vector();
1081 s.defaultReadObject();
1082 // Reinstall style listeners.
1083 if (styleContextChangeListener == null &&
1084 listenerList.getListenerCount(DocumentListener.class) > 0) {
1085 styleContextChangeListener = createStyleContextChangeListener();
1086 if (styleContextChangeListener != null) {
1087 StyleContext styles = (StyleContext)getAttributeContext();
1088 styles.addChangeListener(styleContextChangeListener);
1089 }
1090 updateStylesListeningTo();
1091 }
1092 }
1093
1094 // --- member variables -----------------------------------------------------------
1095
1096 /**
1097 * The default size of the initial content buffer.
1098 */
1099 public static final int BUFFER_SIZE_DEFAULT = 4096;
1100
1101 protected ElementBuffer buffer;
1102
1103 /** Styles listening to. */
1104 private transient Vector listeningStyles;
1105
1106 /** Listens to Styles. */
1107 private transient ChangeListener styleChangeListener;
1108
1109 /** Listens to Styles. */
1110 private transient ChangeListener styleContextChangeListener;
1111
1112 /** Run to create a change event for the document */
1113 private transient ChangeUpdateRunnable updateRunnable;
1114
1115 /**
1116 * Default root element for a document... maps out the
1117 * paragraphs/lines contained.
1118 * <p>
1119 * <strong>Warning:</strong>
1120 * Serialized objects of this class will not be compatible with
1121 * future Swing releases. The current serialization support is
1122 * appropriate for short term storage or RMI between applications running
1123 * the same version of Swing. As of 1.4, support for long term storage
1124 * of all JavaBeans<sup><font size="-2">TM</font></sup>
1125 * has been added to the <code>java.beans</code> package.
1126 * Please see {@link java.beans.XMLEncoder}.
1127 */
1128 protected class SectionElement extends BranchElement {
1129
1130 /**
1131 * Creates a new SectionElement.
1132 */
1133 public SectionElement() {
1134 super(null, null);
1135 }
1136
1137 /**
1138 * Gets the name of the element.
1139 *
1140 * @return the name
1141 */
1142 public String getName() {
1143 return SectionElementName;
1144 }
1145 }
1146
1147 /**
1148 * Specification for building elements.
1149 * <p>
1150 * <strong>Warning:</strong>
1151 * Serialized objects of this class will not be compatible with
1152 * future Swing releases. The current serialization support is
1153 * appropriate for short term storage or RMI between applications running
1154 * the same version of Swing. As of 1.4, support for long term storage
1155 * of all JavaBeans<sup><font size="-2">TM</font></sup>
1156 * has been added to the <code>java.beans</code> package.
1157 * Please see {@link java.beans.XMLEncoder}.
1158 */
1159 public static class ElementSpec {
1160
1161 /**
1162 * A possible value for getType. This specifies
1163 * that this record type is a start tag and
1164 * represents markup that specifies the start
1165 * of an element.
1166 */
1167 public static final short StartTagType = 1;
1168
1169 /**
1170 * A possible value for getType. This specifies
1171 * that this record type is a end tag and
1172 * represents markup that specifies the end
1173 * of an element.
1174 */
1175 public static final short EndTagType = 2;
1176
1177 /**
1178 * A possible value for getType. This specifies
1179 * that this record type represents content.
1180 */
1181 public static final short ContentType = 3;
1182
1183 /**
1184 * A possible value for getDirection. This specifies
1185 * that the data associated with this record should
1186 * be joined to what precedes it.
1187 */
1188 public static final short JoinPreviousDirection = 4;
1189
1190 /**
1191 * A possible value for getDirection. This specifies
1192 * that the data associated with this record should
1193 * be joined to what follows it.
1194 */
1195 public static final short JoinNextDirection = 5;
1196
1197 /**
1198 * A possible value for getDirection. This specifies
1199 * that the data associated with this record should
1200 * be used to originate a new element. This would be
1201 * the normal value.
1202 */
1203 public static final short OriginateDirection = 6;
1204
1205 /**
1206 * A possible value for getDirection. This specifies
1207 * that the data associated with this record should
1208 * be joined to the fractured element.
1209 */
1210 public static final short JoinFractureDirection = 7;
1211
1212
1213 /**
1214 * Constructor useful for markup when the markup will not
1215 * be stored in the document.
1216 *
1217 * @param a the attributes for the element
1218 * @param type the type of the element (StartTagType, EndTagType,
1219 * ContentType)
1220 */
1221 public ElementSpec(AttributeSet a, short type) {
1222 this(a, type, null, 0, 0);
1223 }
1224
1225 /**
1226 * Constructor for parsing inside the document when
1227 * the data has already been added, but len information
1228 * is needed.
1229 *
1230 * @param a the attributes for the element
1231 * @param type the type of the element (StartTagType, EndTagType,
1232 * ContentType)
1233 * @param len the length >= 0
1234 */
1235 public ElementSpec(AttributeSet a, short type, int len) {
1236 this(a, type, null, 0, len);
1237 }
1238
1239 /**
1240 * Constructor for creating a spec externally for batch
1241 * input of content and markup into the document.
1242 *
1243 * @param a the attributes for the element
1244 * @param type the type of the element (StartTagType, EndTagType,
1245 * ContentType)
1246 * @param txt the text for the element
1247 * @param offs the offset into the text >= 0
1248 * @param len the length of the text >= 0
1249 */
1250 public ElementSpec(AttributeSet a, short type, char[] txt,
1251 int offs, int len) {
1252 attr = a;
1253 this.type = type;
1254 this.data = txt;
1255 this.offs = offs;
1256 this.len = len;
1257 this.direction = OriginateDirection;
1258 }
1259
1260 /**
1261 * Sets the element type.
1262 *
1263 * @param type the type of the element (StartTagType, EndTagType,
1264 * ContentType)
1265 */
1266 public void setType(short type) {
1267 this.type = type;
1268 }
1269
1270 /**
1271 * Gets the element type.
1272 *
1273 * @return the type of the element (StartTagType, EndTagType,
1274 * ContentType)
1275 */
1276 public short getType() {
1277 return type;
1278 }
1279
1280 /**
1281 * Sets the direction.
1282 *
1283 * @param direction the direction (JoinPreviousDirection,
1284 * JoinNextDirection)
1285 */
1286 public void setDirection(short direction) {
1287 this.direction = direction;
1288 }
1289
1290 /**
1291 * Gets the direction.
1292 *
1293 * @return the direction (JoinPreviousDirection, JoinNextDirection)
1294 */
1295 public short getDirection() {
1296 return direction;
1297 }
1298
1299 /**
1300 * Gets the element attributes.
1301 *
1302 * @return the attribute set
1303 */
1304 public AttributeSet getAttributes() {
1305 return attr;
1306 }
1307
1308 /**
1309 * Gets the array of characters.
1310 *
1311 * @return the array
1312 */
1313 public char[] getArray() {
1314 return data;
1315 }
1316
1317
1318 /**
1319 * Gets the starting offset.
1320 *
1321 * @return the offset >= 0
1322 */
1323 public int getOffset() {
1324 return offs;
1325 }
1326
1327 /**
1328 * Gets the length.
1329 *
1330 * @return the length >= 0
1331 */
1332 public int getLength() {
1333 return len;
1334 }
1335
1336 /**
1337 * Converts the element to a string.
1338 *
1339 * @return the string
1340 */
1341 public String toString() {
1342 String tlbl = "??";
1343 String plbl = "??";
1344 switch(type) {
1345 case StartTagType:
1346 tlbl = "StartTag";
1347 break;
1348 case ContentType:
1349 tlbl = "Content";
1350 break;
1351 case EndTagType:
1352 tlbl = "EndTag";
1353 break;
1354 }
1355 switch(direction) {
1356 case JoinPreviousDirection:
1357 plbl = "JoinPrevious";
1358 break;
1359 case JoinNextDirection:
1360 plbl = "JoinNext";
1361 break;
1362 case OriginateDirection:
1363 plbl = "Originate";
1364 break;
1365 case JoinFractureDirection:
1366 plbl = "Fracture";
1367 break;
1368 }
1369 return tlbl + ":" + plbl + ":" + getLength();
1370 }
1371
1372 private AttributeSet attr;
1373 private int len;
1374 private short type;
1375 private short direction;
1376
1377 private int offs;
1378 private char[] data;
1379 }
1380
1381 /**
1382 * Class to manage changes to the element
1383 * hierarchy.
1384 * <p>
1385 * <strong>Warning:</strong>
1386 * Serialized objects of this class will not be compatible with
1387 * future Swing releases. The current serialization support is
1388 * appropriate for short term storage or RMI between applications running
1389 * the same version of Swing. As of 1.4, support for long term storage
1390 * of all JavaBeans<sup><font size="-2">TM</font></sup>
1391 * has been added to the <code>java.beans</code> package.
1392 * Please see {@link java.beans.XMLEncoder}.
1393 */
1394 public class ElementBuffer implements Serializable {
1395
1396 /**
1397 * Creates a new ElementBuffer.
1398 *
1399 * @param root the root element
1400 * @since 1.4
1401 */
1402 public ElementBuffer(Element root) {
1403 this.root = root;
1404 changes = new Vector();
1405 path = new Stack();
1406 }
1407
1408 /**
1409 * Gets the root element.
1410 *
1411 * @return the root element
1412 */
1413 public Element getRootElement() {
1414 return root;
1415 }
1416
1417 /**
1418 * Inserts new content.
1419 *
1420 * @param offset the starting offset >= 0
1421 * @param length the length >= 0
1422 * @param data the data to insert
1423 * @param de the event capturing this edit
1424 */
1425 public void insert(int offset, int length, ElementSpec[] data,
1426 DefaultDocumentEvent de) {
1427 if (length == 0) {
1428 // Nothing was inserted, no structure change.
1429 return;
1430 }
1431 insertOp = true;
1432 beginEdits(offset, length);
1433 insertUpdate(data);
1434 endEdits(de);
1435
1436 insertOp = false;
1437 }
1438
1439 void create(int length, ElementSpec[] data, DefaultDocumentEvent de) {
1440 insertOp = true;
1441 beginEdits(offset, length);
1442
1443 // PENDING(prinz) this needs to be fixed to create a new
1444 // root element as well, but requires changes to the
1445 // DocumentEvent to inform the views that there is a new
1446 // root element.
1447
1448 // Recreate the ending fake element to have the correct offsets.
1449 Element elem = root;
1450 int index = elem.getElementIndex(0);
1451 while (! elem.isLeaf()) {
1452 Element child = elem.getElement(index);
1453 push(elem, index);
1454 elem = child;
1455 index = elem.getElementIndex(0);
1456 }
1457 ElemChanges ec = (ElemChanges) path.peek();
1458 Element child = ec.parent.getElement(ec.index);
1459 ec.added.addElement(createLeafElement(ec.parent,
1460 child.getAttributes(), getLength(),
1461 child.getEndOffset()));
1462 ec.removed.addElement(child);
1463 while (path.size() > 1) {
1464 pop();
1465 }
1466
1467 int n = data.length;
1468
1469 // Reset the root elements attributes.
1470 AttributeSet newAttrs = null;
1471 if (n > 0 && data[0].getType() == ElementSpec.StartTagType) {
1472 newAttrs = data[0].getAttributes();
1473 }
1474 if (newAttrs == null) {
1475 newAttrs = SimpleAttributeSet.EMPTY;
1476 }
1477 MutableAttributeSet attr = (MutableAttributeSet)root.
1478 getAttributes();
1479 de.addEdit(new AttributeUndoableEdit(root, newAttrs, true));
1480 attr.removeAttributes(attr);
1481 attr.addAttributes(newAttrs);
1482
1483 // fold in the specified subtree
1484 for (int i = 1; i < n; i++) {
1485 insertElement(data[i]);
1486 }
1487
1488 // pop the remaining path
1489 while (path.size() != 0) {
1490 pop();
1491 }
1492
1493 endEdits(de);
1494 insertOp = false;
1495 }
1496
1497 /**
1498 * Removes content.
1499 *
1500 * @param offset the starting offset >= 0
1501 * @param length the length >= 0
1502 * @param de the event capturing this edit
1503 */
1504 public void remove(int offset, int length, DefaultDocumentEvent de) {
1505 beginEdits(offset, length);
1506 removeUpdate();
1507 endEdits(de);
1508 }
1509
1510 /**
1511 * Changes content.
1512 *
1513 * @param offset the starting offset >= 0
1514 * @param length the length >= 0
1515 * @param de the event capturing this edit
1516 */
1517 public void change(int offset, int length, DefaultDocumentEvent de) {
1518 beginEdits(offset, length);
1519 changeUpdate();
1520 endEdits(de);
1521 }
1522
1523 /**
1524 * Inserts an update into the document.
1525 *
1526 * @param data the elements to insert
1527 */
1528 protected void insertUpdate(ElementSpec[] data) {
1529 // push the path
1530 Element elem = root;
1531 int index = elem.getElementIndex(offset);
1532 while (! elem.isLeaf()) {
1533 Element child = elem.getElement(index);
1534 push(elem, (child.isLeaf() ? index : index+1));
1535 elem = child;
1536 index = elem.getElementIndex(offset);
1537 }
1538
1539 // Build a copy of the original path.
1540 insertPath = new ElemChanges[path.size()];
1541 path.copyInto(insertPath);
1542
1543 // Haven't created the fracture yet.
1544 createdFracture = false;
1545
1546 // Insert the first content.
1547 int i;
1548
1549 recreateLeafs = false;
1550 if(data[0].getType() == ElementSpec.ContentType) {
1551 insertFirstContent(data);
1552 pos += data[0].getLength();
1553 i = 1;
1554 }
1555 else {
1556 fractureDeepestLeaf(data);
1557 i = 0;
1558 }
1559
1560 // fold in the specified subtree
1561 int n = data.length;
1562 for (; i < n; i++) {
1563 insertElement(data[i]);
1564 }
1565
1566 // Fracture, if we haven't yet.
1567 if(!createdFracture)
1568 fracture(-1);
1569
1570 // pop the remaining path
1571 while (path.size() != 0) {
1572 pop();
1573 }
1574
1575 // Offset the last index if necessary.
1576 if(offsetLastIndex && offsetLastIndexOnReplace) {
1577 insertPath[insertPath.length - 1].index++;
1578 }
1579
1580 // Make sure an edit is going to be created for each of the
1581 // original path items that have a change.
1582 for(int counter = insertPath.length - 1; counter >= 0;
1583 counter--) {
1584 ElemChanges change = insertPath[counter];
1585 if(change.parent == fracturedParent)
1586 change.added.addElement(fracturedChild);
1587 if((change.added.size() > 0 ||
1588 change.removed.size() > 0) && !changes.contains(change)) {
1589 // PENDING(sky): Do I need to worry about order here?
1590 changes.addElement(change);
1591 }
1592 }
1593
1594 // An insert at 0 with an initial end implies some elements
1595 // will have no children (the bottomost leaf would have length 0)
1596 // this will find what element need to be removed and remove it.
1597 if (offset == 0 && fracturedParent != null &&
1598 data[0].getType() == ElementSpec.EndTagType) {
1599 int counter = 0;
1600 while (counter < data.length &&
1601 data[counter].getType() == ElementSpec.EndTagType) {
1602 counter++;
1603 }
1604 ElemChanges change = insertPath[insertPath.length -
1605 counter - 1];
1606 change.removed.insertElementAt(change.parent.getElement
1607 (--change.index), 0);
1608 }
1609 }
1610
1611 /**
1612 * Updates the element structure in response to a removal from the
1613 * associated sequence in the document. Any elements consumed by the
1614 * span of the removal are removed.
1615 */
1616 protected void removeUpdate() {
1617 removeElements(root, offset, offset + length);
1618 }
1619
1620 /**
1621 * Updates the element structure in response to a change in the
1622 * document.
1623 */
1624 protected void changeUpdate() {
1625 boolean didEnd = split(offset, length);
1626 if (! didEnd) {
1627 // need to do the other end
1628 while (path.size() != 0) {
1629 pop();
1630 }
1631 split(offset + length, 0);
1632 }
1633 while (path.size() != 0) {
1634 pop();
1635 }
1636 }
1637
1638 boolean split(int offs, int len) {
1639 boolean splitEnd = false;
1640 // push the path
1641 Element e = root;
1642 int index = e.getElementIndex(offs);
1643 while (! e.isLeaf()) {
1644 push(e, index);
1645 e = e.getElement(index);
1646 index = e.getElementIndex(offs);
1647 }
1648
1649 ElemChanges ec = (ElemChanges) path.peek();
1650 Element child = ec.parent.getElement(ec.index);
1651 // make sure there is something to do... if the
1652 // offset is already at a boundary then there is
1653 // nothing to do.
1654 if (child.getStartOffset() < offs && offs < child.getEndOffset()) {
1655 // we need to split, now see if the other end is within
1656 // the same parent.
1657 int index0 = ec.index;
1658 int index1 = index0;
1659 if (((offs + len) < ec.parent.getEndOffset()) && (len != 0)) {
1660 // it's a range split in the same parent
1661 index1 = ec.parent.getElementIndex(offs+len);
1662 if (index1 == index0) {
1663 // it's a three-way split
1664 ec.removed.addElement(child);
1665 e = createLeafElement(ec.parent, child.getAttributes(),
1666 child.getStartOffset(), offs);
1667 ec.added.addElement(e);
1668 e = createLeafElement(ec.parent, child.getAttributes(),
1669 offs, offs + len);
1670 ec.added.addElement(e);
1671 e = createLeafElement(ec.parent, child.getAttributes(),
1672 offs + len, child.getEndOffset());
1673 ec.added.addElement(e);
1674 return true;
1675 } else {
1676 child = ec.parent.getElement(index1);
1677 if ((offs + len) == child.getStartOffset()) {
1678 // end is already on a boundary
1679 index1 = index0;
1680 }
1681 }
1682 splitEnd = true;
1683 }
1684
1685 // split the first location
1686 pos = offs;
1687 child = ec.parent.getElement(index0);
1688 ec.removed.addElement(child);
1689 e = createLeafElement(ec.parent, child.getAttributes(),
1690 child.getStartOffset(), pos);
1691 ec.added.addElement(e);
1692 e = createLeafElement(ec.parent, child.getAttributes(),
1693 pos, child.getEndOffset());
1694 ec.added.addElement(e);
1695
1696 // pick up things in the middle
1697 for (int i = index0 + 1; i < index1; i++) {
1698 child = ec.parent.getElement(i);
1699 ec.removed.addElement(child);
1700 ec.added.addElement(child);
1701 }
1702
1703 if (index1 != index0) {
1704 child = ec.parent.getElement(index1);
1705 pos = offs + len;
1706 ec.removed.addElement(child);
1707 e = createLeafElement(ec.parent, child.getAttributes(),
1708 child.getStartOffset(), pos);
1709 ec.added.addElement(e);
1710 e = createLeafElement(ec.parent, child.getAttributes(),
1711 pos, child.getEndOffset());
1712 ec.added.addElement(e);
1713 }
1714 }
1715 return splitEnd;
1716 }
1717
1718 /**
1719 * Creates the UndoableEdit record for the edits made
1720 * in the buffer.
1721 */
1722 void endEdits(DefaultDocumentEvent de) {
1723 int n = changes.size();
1724 for (int i = 0; i < n; i++) {
1725 ElemChanges ec = (ElemChanges) changes.elementAt(i);
1726 Element[] removed = new Element[ec.removed.size()];
1727 ec.removed.copyInto(removed);
1728 Element[] added = new Element[ec.added.size()];
1729 ec.added.copyInto(added);
1730 int index = ec.index;
1731 ((BranchElement) ec.parent).replace(index, removed.length, added);
1732 ElementEdit ee = new ElementEdit((BranchElement) ec.parent,
1733 index, removed, added);
1734 de.addEdit(ee);
1735 }
1736
1737 changes.removeAllElements();
1738 path.removeAllElements();
1739
1740 /*
1741 for (int i = 0; i < n; i++) {
1742 ElemChanges ec = (ElemChanges) changes.elementAt(i);
1743 System.err.print("edited: " + ec.parent + " at: " + ec.index +
1744 " removed " + ec.removed.size());
1745 if (ec.removed.size() > 0) {
1746 int r0 = ((Element) ec.removed.firstElement()).getStartOffset();
1747 int r1 = ((Element) ec.removed.lastElement()).getEndOffset();
1748 System.err.print("[" + r0 + "," + r1 + "]");
1749 }
1750 System.err.print(" added " + ec.added.size());
1751 if (ec.added.size() > 0) {
1752 int p0 = ((Element) ec.added.firstElement()).getStartOffset();
1753 int p1 = ((Element) ec.added.lastElement()).getEndOffset();
1754 System.err.print("[" + p0 + "," + p1 + "]");
1755 }
1756 System.err.println("");
1757 }
1758 */
1759 }
1760
1761 /**
1762 * Initialize the buffer
1763 */
1764 void beginEdits(int offset, int length) {
1765 this.offset = offset;
1766 this.length = length;
1767 this.endOffset = offset + length;
1768 pos = offset;
1769 if (changes == null) {
1770 changes = new Vector();
1771 } else {
1772 changes.removeAllElements();
1773 }
1774 if (path == null) {
1775 path = new Stack();
1776 } else {
1777 path.removeAllElements();
1778 }
1779 fracturedParent = null;
1780 fracturedChild = null;
1781 offsetLastIndex = offsetLastIndexOnReplace = false;
1782 }
1783
1784 /**
1785 * Pushes a new element onto the stack that represents
1786 * the current path.
1787 * @param record Whether or not the push should be
1788 * recorded as an element change or not.
1789 * @param isFracture true if pushing on an element that was created
1790 * as the result of a fracture.
1791 */
1792 void push(Element e, int index, boolean isFracture) {
1793 ElemChanges ec = new ElemChanges(e, index, isFracture);
1794 path.push(ec);
1795 }
1796
1797 void push(Element e, int index) {
1798 push(e, index, false);
1799 }
1800
1801 void pop() {
1802 ElemChanges ec = (ElemChanges) path.peek();
1803 path.pop();
1804 if ((ec.added.size() > 0) || (ec.removed.size() > 0)) {
1805 changes.addElement(ec);
1806 } else if (! path.isEmpty()) {
1807 Element e = ec.parent;
1808 if(e.getElementCount() == 0) {
1809 // if we pushed a branch element that didn't get
1810 // used, make sure its not marked as having been added.
1811 ec = (ElemChanges) path.peek();
1812 ec.added.removeElement(e);
1813 }
1814 }
1815 }
1816
1817 /**
1818 * move the current offset forward by n.
1819 */
1820 void advance(int n) {
1821 pos += n;
1822 }
1823
1824 void insertElement(ElementSpec es) {
1825 ElemChanges ec = (ElemChanges) path.peek();
1826 switch(es.getType()) {
1827 case ElementSpec.StartTagType:
1828 switch(es.getDirection()) {
1829 case ElementSpec.JoinNextDirection:
1830 // Don't create a new element, use the existing one
1831 // at the specified location.
1832 Element parent = ec.parent.getElement(ec.index);
1833
1834 if(parent.isLeaf()) {
1835 // This happens if inserting into a leaf, followed
1836 // by a join next where next sibling is not a leaf.
1837 if((ec.index + 1) < ec.parent.getElementCount())
1838 parent = ec.parent.getElement(ec.index + 1);
1839 else
1840 throw new StateInvariantError("Join next to leaf");
1841 }
1842 // Not really a fracture, but need to treat it like
1843 // one so that content join next will work correctly.
1844 // We can do this because there will never be a join
1845 // next followed by a join fracture.
1846 push(parent, 0, true);
1847 break;
1848 case ElementSpec.JoinFractureDirection:
1849 if(!createdFracture) {
1850 // Should always be something on the stack!
1851 fracture(path.size() - 1);
1852 }
1853 // If parent isn't a fracture, fracture will be
1854 // fracturedChild.
1855 if(!ec.isFracture) {
1856 push(fracturedChild, 0, true);
1857 }
1858 else
1859 // Parent is a fracture, use 1st element.
1860 push(ec.parent.getElement(0), 0, true);
1861 break;
1862 default:
1863 Element belem = createBranchElement(ec.parent,
1864 es.getAttributes());
1865 ec.added.addElement(belem);
1866 push(belem, 0);
1867 break;
1868 }
1869 break;
1870 case ElementSpec.EndTagType:
1871 pop();
1872 break;
1873 case ElementSpec.ContentType:
1874 int len = es.getLength();
1875 if (es.getDirection() != ElementSpec.JoinNextDirection) {
1876 Element leaf = createLeafElement(ec.parent, es.getAttributes(),
1877 pos, pos + len);
1878 ec.added.addElement(leaf);
1879 }
1880 else {
1881 // JoinNext on tail is only applicable if last element
1882 // and attributes come from that of first element.
1883 // With a little extra testing it would be possible
1884 // to NOT due this again, as more than likely fracture()
1885 // created this element.
1886 if(!ec.isFracture) {
1887 Element first = null;
1888 if(insertPath != null) {
1889 for(int counter = insertPath.length - 1;
1890 counter >= 0; counter--) {
1891 if(insertPath[counter] == ec) {
1892 if(counter != (insertPath.length - 1))
1893 first = ec.parent.getElement(ec.index);
1894 break;
1895 }
1896 }
1897 }
1898 if(first == null)
1899 first = ec.parent.getElement(ec.index + 1);
1900 Element leaf = createLeafElement(ec.parent, first.
1901 getAttributes(), pos, first.getEndOffset());
1902 ec.added.addElement(leaf);
1903 ec.removed.addElement(first);
1904 }
1905 else {
1906 // Parent was fractured element.
1907 Element first = ec.parent.getElement(0);
1908 Element leaf = createLeafElement(ec.parent, first.
1909 getAttributes(), pos, first.getEndOffset());
1910 ec.added.addElement(leaf);
1911 ec.removed.addElement(first);
1912 }
1913 }
1914 pos += len;
1915 break;
1916 }
1917 }
1918
1919 /**
1920 * Remove the elements from <code>elem</code> in range
1921 * <code>rmOffs0</code>, <code>rmOffs1</code>. This uses
1922 * <code>canJoin</code> and <code>join</code> to handle joining
1923 * the endpoints of the insertion.
1924 *
1925 * @return true if elem will no longer have any elements.
1926 */
1927 boolean removeElements(Element elem, int rmOffs0, int rmOffs1) {
1928 if (! elem.isLeaf()) {
1929 // update path for changes
1930 int index0 = elem.getElementIndex(rmOffs0);
1931 int index1 = elem.getElementIndex(rmOffs1);
1932 push(elem, index0);
1933 ElemChanges ec = (ElemChanges)path.peek();
1934
1935 // if the range is contained by one element,
1936 // we just forward the request
1937 if (index0 == index1) {
1938 Element child0 = elem.getElement(index0);
1939 if(rmOffs0 <= child0.getStartOffset() &&
1940 rmOffs1 >= child0.getEndOffset()) {
1941 // Element totally removed.
1942 ec.removed.addElement(child0);
1943 }
1944 else if(removeElements(child0, rmOffs0, rmOffs1)) {
1945 ec.removed.addElement(child0);
1946 }
1947 } else {
1948 // the removal range spans elements. If we can join
1949 // the two endpoints, do it. Otherwise we remove the
1950 // interior and forward to the endpoints.
1951 Element child0 = elem.getElement(index0);
1952 Element child1 = elem.getElement(index1);
1953 boolean containsOffs1 = (rmOffs1 < elem.getEndOffset());
1954 if (containsOffs1 && canJoin(child0, child1)) {
1955 // remove and join
1956 for (int i = index0; i <= index1; i++) {
1957 ec.removed.addElement(elem.getElement(i));
1958 }
1959 Element e = join(elem, child0, child1, rmOffs0, rmOffs1);
1960 ec.added.addElement(e);
1961 } else {
1962 // remove interior and forward
1963 int rmIndex0 = index0 + 1;
1964 int rmIndex1 = index1 - 1;
1965 if (child0.getStartOffset() == rmOffs0 ||
1966 (index0 == 0 &&
1967 child0.getStartOffset() > rmOffs0 &&
1968 child0.getEndOffset() <= rmOffs1)) {
1969 // start element completely consumed
1970 child0 = null;
1971 rmIndex0 = index0;
1972 }
1973 if (!containsOffs1) {
1974 child1 = null;
1975 rmIndex1++;
1976 }
1977 else if (child1.getStartOffset() == rmOffs1) {
1978 // end element not touched
1979 child1 = null;
1980 }
1981 if (rmIndex0 <= rmIndex1) {
1982 ec.index = rmIndex0;
1983 }
1984 for (int i = rmIndex0; i <= rmIndex1; i++) {
1985 ec.removed.addElement(elem.getElement(i));
1986 }
1987 if (child0 != null) {
1988 if(removeElements(child0, rmOffs0, rmOffs1)) {
1989 ec.removed.insertElementAt(child0, 0);
1990 ec.index = index0;
1991 }
1992 }
1993 if (child1 != null) {
1994 if(removeElements(child1, rmOffs0, rmOffs1)) {
1995 ec.removed.addElement(child1);
1996 }
1997 }
1998 }
1999 }
2000
2001 // publish changes
2002 pop();
2003
2004 // Return true if we no longer have any children.
2005 if(elem.getElementCount() == (ec.removed.size() -
2006 ec.added.size())) {
2007 return true;
2008 }
2009 }
2010 return false;
2011 }
2012
2013 /**
2014 * Can the two given elements be coelesced together
2015 * into one element?
2016 */
2017 boolean canJoin(Element e0, Element e1) {
2018 if ((e0 == null) || (e1 == null)) {
2019 return false;
2020 }
2021 // Don't join a leaf to a branch.
2022 boolean leaf0 = e0.isLeaf();
2023 boolean leaf1 = e1.isLeaf();
2024 if(leaf0 != leaf1) {
2025 return false;
2026 }
2027 if (leaf0) {
2028 // Only join leaves if the attributes match, otherwise
2029 // style information will be lost.
2030 return e0.getAttributes().isEqual(e1.getAttributes());
2031 }
2032 // Only join non-leafs if the names are equal. This may result
2033 // in loss of style information, but this is typically acceptable
2034 // for non-leafs.
2035 String name0 = e0.getName();
2036 String name1 = e1.getName();
2037 if (name0 != null) {
2038 return name0.equals(name1);
2039 }
2040 if (name1 != null) {
2041 return name1.equals(name0);
2042 }
2043 // Both names null, treat as equal.
2044 return true;
2045 }
2046
2047 /**
2048 * Joins the two elements carving out a hole for the
2049 * given removed range.
2050 */
2051 Element join(Element p, Element left, Element right, int rmOffs0, int rmOffs1) {
2052 if (left.isLeaf() && right.isLeaf()) {
2053 return createLeafElement(p, left.getAttributes(), left.getStartOffset(),
2054 right.getEndOffset());
2055 } else if ((!left.isLeaf()) && (!right.isLeaf())) {
2056 // join two branch elements. This copies the children before
2057 // the removal range on the left element, and after the removal
2058 // range on the right element. The two elements on the edge
2059 // are joined if possible and needed.
2060 Element to = createBranchElement(p, left.getAttributes());
2061 int ljIndex = left.getElementIndex(rmOffs0);
2062 int rjIndex = right.getElementIndex(rmOffs1);
2063 Element lj = left.getElement(ljIndex);
2064 if (lj.getStartOffset() >= rmOffs0) {
2065 lj = null;
2066 }
2067 Element rj = right.getElement(rjIndex);
2068 if (rj.getStartOffset() == rmOffs1) {
2069 rj = null;
2070 }
2071 Vector children = new Vector();
2072
2073 // transfer the left
2074 for (int i = 0; i < ljIndex; i++) {
2075 children.addElement(clone(to, left.getElement(i)));
2076 }
2077
2078 // transfer the join/middle
2079 if (canJoin(lj, rj)) {
2080 Element e = join(to, lj, rj, rmOffs0, rmOffs1);
2081 children.addElement(e);
2082 } else {
2083 if (lj != null) {
2084 children.addElement(cloneAsNecessary(to, lj, rmOffs0, rmOffs1));
2085 }
2086 if (rj != null) {
2087 children.addElement(cloneAsNecessary(to, rj, rmOffs0, rmOffs1));
2088 }
2089 }
2090
2091 // transfer the right
2092 int n = right.getElementCount();
2093 for (int i = (rj == null) ? rjIndex : rjIndex + 1; i < n; i++) {
2094 children.addElement(clone(to, right.getElement(i)));
2095 }
2096
2097 // install the children
2098 Element[] c = new Element[children.size()];
2099 children.copyInto(c);
2100 ((BranchElement)to).replace(0, 0, c);
2101 return to;
2102 } else {
2103 throw new StateInvariantError(
2104 "No support to join leaf element with non-leaf element");
2105 }
2106 }
2107
2108 /**
2109 * Creates a copy of this element, with a different
2110 * parent.
2111 *
2112 * @param parent the parent element
2113 * @param clonee the element to be cloned
2114 * @return the copy
2115 */
2116 public Element clone(Element parent, Element clonee) {
2117 if (clonee.isLeaf()) {
2118 return createLeafElement(parent, clonee.getAttributes(),
2119 clonee.getStartOffset(),
2120 clonee.getEndOffset());
2121 }
2122 Element e = createBranchElement(parent, clonee.getAttributes());
2123 int n = clonee.getElementCount();
2124 Element[] children = new Element[n];
2125 for (int i = 0; i < n; i++) {
2126 children[i] = clone(e, clonee.getElement(i));
2127 }
2128 ((BranchElement)e).replace(0, 0, children);
2129 return e;
2130 }
2131
2132 /**
2133 * Creates a copy of this element, with a different
2134 * parent. Children of this element included in the
2135 * removal range will be discarded.
2136 */
2137 Element cloneAsNecessary(Element parent, Element clonee, int rmOffs0, int rmOffs1) {
2138 if (clonee.isLeaf()) {
2139 return createLeafElement(parent, clonee.getAttributes(),
2140 clonee.getStartOffset(),
2141 clonee.getEndOffset());
2142 }
2143 Element e = createBranchElement(parent, clonee.getAttributes());
2144 int n = clonee.getElementCount();
2145 ArrayList childrenList = new ArrayList(n);
2146 for (int i = 0; i < n; i++) {
2147 Element elem = clonee.getElement(i);
2148 if (elem.getStartOffset() < rmOffs0 || elem.getEndOffset() > rmOffs1) {
2149 childrenList.add(cloneAsNecessary(e, elem, rmOffs0, rmOffs1));
2150 }
2151 }
2152 Element[] children = new Element[childrenList.size()];
2153 children = (Element[])childrenList.toArray(children);
2154 ((BranchElement)e).replace(0, 0, children);
2155 return e;
2156 }
2157
2158 /**
2159 * Determines if a fracture needs to be performed. A fracture
2160 * can be thought of as moving the right part of a tree to a
2161 * new location, where the right part is determined by what has
2162 * been inserted. <code>depth</code> is used to indicate a
2163 * JoinToFracture is needed to an element at a depth
2164 * of <code>depth</code>. Where the root is 0, 1 is the children
2165 * of the root...
2166 * <p>This will invoke <code>fractureFrom</code> if it is determined
2167 * a fracture needs to happen.
2168 */
2169 void fracture(int depth) {
2170 int cLength = insertPath.length;
2171 int lastIndex = -1;
2172 boolean needRecreate = recreateLeafs;
2173 ElemChanges lastChange = insertPath[cLength - 1];
2174 // Use childAltered to determine when a child has been altered,
2175 // that is the point of insertion is less than the element count.
2176 boolean childAltered = ((lastChange.index + 1) <
2177 lastChange.parent.getElementCount());
2178 int deepestAlteredIndex = (needRecreate) ? cLength : -1;
2179 int lastAlteredIndex = cLength - 1;
2180
2181 createdFracture = true;
2182 // Determine where to start recreating from.
2183 // Start at - 2, as first one is indicated by recreateLeafs and
2184 // childAltered.
2185 for(int counter = cLength - 2; counter >= 0; counter--) {
2186 ElemChanges change = insertPath[counter];
2187 if(change.added.size() > 0 || counter == depth) {
2188 lastIndex = counter;
2189 if(!needRecreate && childAltered) {
2190 needRecreate = true;
2191 if(deepestAlteredIndex == -1)
2192 deepestAlteredIndex = lastAlteredIndex + 1;
2193 }
2194 }
2195 if(!childAltered && change.index <
2196 change.parent.getElementCount()) {
2197 childAltered = true;
2198 lastAlteredIndex = counter;
2199 }
2200 }
2201 if(needRecreate) {
2202 // Recreate all children to right of parent starting
2203 // at lastIndex.
2204 if(lastIndex == -1)
2205 lastIndex = cLength - 1;
2206 fractureFrom(insertPath, lastIndex, deepestAlteredIndex);
2207 }
2208 }
2209
2210 /**
2211 * Recreates the elements to the right of the insertion point.
2212 * This starts at <code>startIndex</code> in <code>changed</code>,
2213 * and calls duplicate to duplicate existing elements.
2214 * This will also duplicate the elements along the insertion
2215 * point, until a depth of <code>endFractureIndex</code> is
2216 * reached, at which point only the elements to the right of
2217 * the insertion point are duplicated.
2218 */
2219 void fractureFrom(ElemChanges[] changed, int startIndex,
2220 int endFractureIndex) {
2221 // Recreate the element representing the inserted index.
2222 ElemChanges change = changed[startIndex];
2223 Element child;
2224 Element newChild;
2225 int changeLength = changed.length;
2226
2227 if((startIndex + 1) == changeLength)
2228 child = change.parent.getElement(change.index);
2229 else
2230 child = change.parent.getElement(change.index - 1);
2231 if(child.isLeaf()) {
2232 newChild = createLeafElement(change.parent,
2233 child.getAttributes(), Math.max(endOffset,
2234 child.getStartOffset()), child.getEndOffset());
2235 }
2236 else {
2237 newChild = createBranchElement(change.parent,
2238 child.getAttributes());
2239 }
2240 fracturedParent = change.parent;
2241 fracturedChild = newChild;
2242
2243 // Recreate all the elements to the right of the
2244 // insertion point.
2245 Element parent = newChild;
2246
2247 while(++startIndex < endFractureIndex) {
2248 boolean isEnd = ((startIndex + 1) == endFractureIndex);
2249 boolean isEndLeaf = ((startIndex + 1) == changeLength);
2250
2251 // Create the newChild, a duplicate of the elment at
2252 // index. This isn't done if isEnd and offsetLastIndex are true
2253 // indicating a join previous was done.
2254 change = changed[startIndex];
2255
2256 // Determine the child to duplicate, won't have to duplicate
2257 // if at end of fracture, or offseting index.
2258 if(isEnd) {
2259 if(offsetLastIndex || !isEndLeaf)
2260 child = null;
2261 else
2262 child = change.parent.getElement(change.index);
2263 }
2264 else {
2265 child = change.parent.getElement(change.index - 1);
2266 }
2267 // Duplicate it.
2268 if(child != null) {
2269 if(child.isLeaf()) {
2270 newChild = createLeafElement(parent,
2271 child.getAttributes(), Math.max(endOffset,
2272 child.getStartOffset()), child.getEndOffset());
2273 }
2274 else {
2275 newChild = createBranchElement(parent,
2276 child.getAttributes());
2277 }
2278 }
2279 else
2280 newChild = null;
2281
2282 // Recreate the remaining children (there may be none).
2283 int kidsToMove = change.parent.getElementCount() -
2284 change.index;
2285 Element[] kids;
2286 int moveStartIndex;
2287 int kidStartIndex = 1;
2288
2289 if(newChild == null) {
2290 // Last part of fracture.
2291 if(isEndLeaf) {
2292 kidsToMove--;
2293 moveStartIndex = change.index + 1;
2294 }
2295 else {
2296 moveStartIndex = change.index;
2297 }
2298 kidStartIndex = 0;
2299 kids = new Element[kidsToMove];
2300 }
2301 else {
2302 if(!isEnd) {
2303 // Branch.
2304 kidsToMove++;
2305 moveStartIndex = change.index;
2306 }
2307 else {
2308 // Last leaf, need to recreate part of it.
2309 moveStartIndex = change.index + 1;
2310 }
2311 kids = new Element[kidsToMove];
2312 kids[0] = newChild;
2313 }
2314
2315 for(int counter = kidStartIndex; counter < kidsToMove;
2316 counter++) {
2317 Element toMove =change.parent.getElement(moveStartIndex++);
2318 kids[counter] = recreateFracturedElement(parent, toMove);
2319 change.removed.addElement(toMove);
2320 }
2321 ((BranchElement)parent).replace(0, 0, kids);
2322 parent = newChild;
2323 }
2324 }
2325
2326 /**
2327 * Recreates <code>toDuplicate</code>. This is called when an
2328 * element needs to be created as the result of an insertion. This
2329 * will recurse and create all the children. This is similiar to
2330 * <code>clone</code>, but deteremines the offsets differently.
2331 */
2332 Element recreateFracturedElement(Element parent, Element toDuplicate) {
2333 if(toDuplicate.isLeaf()) {
2334 return createLeafElement(parent, toDuplicate.getAttributes(),
2335 Math.max(toDuplicate.getStartOffset(),
2336 endOffset),
2337 toDuplicate.getEndOffset());
2338 }
2339 // Not a leaf
2340 Element newParent = createBranchElement(parent, toDuplicate.
2341 getAttributes());
2342 int childCount = toDuplicate.getElementCount();
2343 Element[] newKids = new Element[childCount];
2344 for(int counter = 0; counter < childCount; counter++) {
2345 newKids[counter] = recreateFracturedElement(newParent,
2346 toDuplicate.getElement(counter));
2347 }
2348 ((BranchElement)newParent).replace(0, 0, newKids);
2349 return newParent;
2350 }
2351
2352 /**
2353 * Splits the bottommost leaf in <code>path</code>.
2354 * This is called from insert when the first element is NOT content.
2355 */
2356 void fractureDeepestLeaf(ElementSpec[] specs) {
2357 // Split the bottommost leaf. It will be recreated elsewhere.
2358 ElemChanges ec = (ElemChanges) path.peek();
2359 Element child = ec.parent.getElement(ec.index);
2360 // Inserts at offset 0 do not need to recreate child (it would
2361 // have a length of 0!).
2362 if (offset != 0) {
2363 Element newChild = createLeafElement(ec.parent,
2364 child.getAttributes(),
2365 child.getStartOffset(),
2366 offset);
2367
2368 ec.added.addElement(newChild);
2369 }
2370 ec.removed.addElement(child);
2371 if(child.getEndOffset() != endOffset)
2372 recreateLeafs = true;
2373 else
2374 offsetLastIndex = true;
2375 }
2376
2377 /**
2378 * Inserts the first content. This needs to be separate to handle
2379 * joining.
2380 */
2381 void insertFirstContent(ElementSpec[] specs) {
2382 ElementSpec firstSpec = specs[0];
2383 ElemChanges ec = (ElemChanges) path.peek();
2384 Element child = ec.parent.getElement(ec.index);
2385 int firstEndOffset = offset + firstSpec.getLength();
2386 boolean isOnlyContent = (specs.length == 1);
2387
2388 switch(firstSpec.getDirection()) {
2389 case ElementSpec.JoinPreviousDirection:
2390 if(child.getEndOffset() != firstEndOffset &&
2391 !isOnlyContent) {
2392 // Create the left split part containing new content.
2393 Element newE = createLeafElement(ec.parent,
2394 child.getAttributes(), child.getStartOffset(),
2395 firstEndOffset);
2396 ec.added.addElement(newE);
2397 ec.removed.addElement(child);
2398 // Remainder will be created later.
2399 if(child.getEndOffset() != endOffset)
2400 recreateLeafs = true;
2401 else
2402 offsetLastIndex = true;
2403 }
2404 else {
2405 offsetLastIndex = true;
2406 offsetLastIndexOnReplace = true;
2407 }
2408 // else Inserted at end, and is total length.
2409 // Update index incase something added/removed.
2410 break;
2411 case ElementSpec.JoinNextDirection:
2412 if(offset != 0) {
2413 // Recreate the first element, its offset will have
2414 // changed.
2415 Element newE = createLeafElement(ec.parent,
2416 child.getAttributes(), child.getStartOffset(),
2417 offset);
2418 ec.added.addElement(newE);
2419 // Recreate the second, merge part. We do no checking
2420 // to see if JoinNextDirection is valid here!
2421 Element nextChild = ec.parent.getElement(ec.index + 1);
2422 if(isOnlyContent)
2423 newE = createLeafElement(ec.parent, nextChild.
2424 getAttributes(), offset, nextChild.getEndOffset());
2425 else
2426 newE = createLeafElement(ec.parent, nextChild.
2427 getAttributes(), offset, firstEndOffset);
2428 ec.added.addElement(newE);
2429 ec.removed.addElement(child);
2430 ec.removed.addElement(nextChild);
2431 }
2432 // else nothin to do.
2433 // PENDING: if !isOnlyContent could raise here!
2434 break;
2435 default:
2436 // Inserted into middle, need to recreate split left
2437 // new content, and split right.
2438 if(child.getStartOffset() != offset) {
2439 Element newE = createLeafElement(ec.parent,
2440 child.getAttributes(), child.getStartOffset(),
2441 offset);
2442 ec.added.addElement(newE);
2443 }
2444 ec.removed.addElement(child);
2445 // new content
2446 Element newE = createLeafElement(ec.parent,
2447 firstSpec.getAttributes(),
2448 offset, firstEndOffset);
2449 ec.added.addElement(newE);
2450 if(child.getEndOffset() != endOffset) {
2451 // Signals need to recreate right split later.
2452 recreateLeafs = true;
2453 }
2454 else {
2455 offsetLastIndex = true;
2456 }
2457 break;
2458 }
2459 }
2460
2461 Element root;
2462 transient int pos; // current position
2463 transient int offset;
2464 transient int length;
2465 transient int endOffset;
2466 transient Vector changes; // Vector<ElemChanges>
2467 transient Stack path; // Stack<ElemChanges>
2468 transient boolean insertOp;
2469
2470 transient boolean recreateLeafs; // For insert.
2471
2472 /** For insert, path to inserted elements. */
2473 transient ElemChanges[] insertPath;
2474 /** Only for insert, set to true when the fracture has been created. */
2475 transient boolean createdFracture;
2476 /** Parent that contains the fractured child. */
2477 transient Element fracturedParent;
2478 /** Fractured child. */
2479 transient Element fracturedChild;
2480 /** Used to indicate when fracturing that the last leaf should be
2481 * skipped. */
2482 transient boolean offsetLastIndex;
2483 /** Used to indicate that the parent of the deepest leaf should
2484 * offset the index by 1 when adding/removing elements in an
2485 * insert. */
2486 transient boolean offsetLastIndexOnReplace;
2487
2488 /*
2489 * Internal record used to hold element change specifications
2490 */
2491 class ElemChanges {
2492
2493 ElemChanges(Element parent, int index, boolean isFracture) {
2494 this.parent = parent;
2495 this.index = index;
2496 this.isFracture = isFracture;
2497 added = new Vector();
2498 removed = new Vector();
2499 }
2500
2501 public String toString() {
2502 return "added: " + added + "\nremoved: " + removed + "\n";
2503 }
2504
2505 Element parent;
2506 int index;
2507 Vector added;
2508 Vector removed;
2509 boolean isFracture;
2510 }
2511
2512 }
2513
2514 /**
2515 * An UndoableEdit used to remember AttributeSet changes to an
2516 * Element.
2517 */
2518 public static class AttributeUndoableEdit extends AbstractUndoableEdit {
2519 public AttributeUndoableEdit(Element element, AttributeSet newAttributes,
2520 boolean isReplacing) {
2521 super();
2522 this.element = element;
2523 this.newAttributes = newAttributes;
2524 this.isReplacing = isReplacing;
2525 // If not replacing, it may be more efficient to only copy the
2526 // changed values...
2527 copy = element.getAttributes().copyAttributes();
2528 }
2529
2530 /**
2531 * Redoes a change.
2532 *
2533 * @exception CannotRedoException if the change cannot be redone
2534 */
2535 public void redo() throws CannotRedoException {
2536 super.redo();
2537 MutableAttributeSet as = (MutableAttributeSet)element
2538 .getAttributes();
2539 if(isReplacing)
2540 as.removeAttributes(as);
2541 as.addAttributes(newAttributes);
2542 }
2543
2544 /**
2545 * Undoes a change.
2546 *
2547 * @exception CannotUndoException if the change cannot be undone
2548 */
2549 public void undo() throws CannotUndoException {
2550 super.undo();
2551 MutableAttributeSet as = (MutableAttributeSet)element.getAttributes();
2552 as.removeAttributes(as);
2553 as.addAttributes(copy);
2554 }
2555
2556 // AttributeSet containing additional entries, must be non-mutable!
2557 protected AttributeSet newAttributes;
2558 // Copy of the AttributeSet the Element contained.
2559 protected AttributeSet copy;
2560 // true if all the attributes in the element were removed first.
2561 protected boolean isReplacing;
2562 // Efected Element.
2563 protected Element element;
2564 }
2565
2566 /**
2567 * UndoableEdit for changing the resolve parent of an Element.
2568 */
2569 static class StyleChangeUndoableEdit extends AbstractUndoableEdit {
2570 public StyleChangeUndoableEdit(AbstractElement element,
2571 Style newStyle) {
2572 super();
2573 this.element = element;
2574 this.newStyle = newStyle;
2575 oldStyle = element.getResolveParent();
2576 }
2577
2578 /**
2579 * Redoes a change.
2580 *
2581 * @exception CannotRedoException if the change cannot be redone
2582 */
2583 public void redo() throws CannotRedoException {
2584 super.redo();
2585 element.setResolveParent(newStyle);
2586 }
2587
2588 /**
2589 * Undoes a change.
2590 *
2591 * @exception CannotUndoException if the change cannot be undone
2592 */
2593 public void undo() throws CannotUndoException {
2594 super.undo();
2595 element.setResolveParent(oldStyle);
2596 }
2597
2598 /** Element to change resolve parent of. */
2599 protected AbstractElement element;
2600 /** New style. */
2601 protected Style newStyle;
2602 /** Old style, before setting newStyle. */
2603 protected AttributeSet oldStyle;
2604 }
2605
2606 /**
2607 * Base class for style change handlers with support for stale objects detection.
2608 */
2609 abstract static class AbstractChangeHandler implements ChangeListener {
2610
2611 /* This has an implicit reference to the handler object. */
2612 private class DocReference extends WeakReference<DefaultStyledDocument> {
2613
2614 DocReference(DefaultStyledDocument d, ReferenceQueue q) {
2615 super(d, q);
2616 }
2617
2618 /**
2619 * Return a reference to the style change handler object.
2620 */
2621 ChangeListener getListener() {
2622 return AbstractChangeHandler.this;
2623 }
2624 }
2625
2626 /** Class-specific reference queues. */
2627 private final static Map<Class, ReferenceQueue> queueMap
2628 = new HashMap<Class, ReferenceQueue>();
2629
2630 /** A weak reference to the document object. */
2631 private DocReference doc;
2632
2633 AbstractChangeHandler(DefaultStyledDocument d) {
2634 Class c = getClass();
2635 ReferenceQueue q;
2636 synchronized (queueMap) {
2637 q = queueMap.get(c);
2638 if (q == null) {
2639 q = new ReferenceQueue();
2640 queueMap.put(c, q);
2641 }
2642 }
2643 doc = new DocReference(d, q);
2644 }
2645
2646 /**
2647 * Return a list of stale change listeners.
2648 *
2649 * A change listener becomes "stale" when its document is cleaned by GC.
2650 */
2651 static List<ChangeListener> getStaleListeners(ChangeListener l) {
2652 List<ChangeListener> staleListeners = new ArrayList<ChangeListener>();
2653 ReferenceQueue q = queueMap.get(l.getClass());
2654
2655 if (q != null) {
2656 DocReference r;
2657 synchronized (q) {
2658 while ((r = (DocReference) q.poll()) != null) {
2659 staleListeners.add(r.getListener());
2660 }
2661 }
2662 }
2663
2664 return staleListeners;
2665 }
2666
2667 /**
2668 * The ChangeListener wrapper which guards against dead documents.
2669 */
2670 public void stateChanged(ChangeEvent e) {
2671 DefaultStyledDocument d = doc.get();
2672 if (d != null) {
2673 fireStateChanged(d, e);
2674 }
2675 }
2676
2677 /** Run the actual class-specific stateChanged() method. */
2678 abstract void fireStateChanged(DefaultStyledDocument d, ChangeEvent e);
2679 }
2680
2681 /**
2682 * Added to all the Styles. When instances of this receive a
2683 * stateChanged method, styleChanged is invoked.
2684 */
2685 static class StyleChangeHandler extends AbstractChangeHandler {
2686
2687 StyleChangeHandler(DefaultStyledDocument d) {
2688 super(d);
2689 }
2690
2691 void fireStateChanged(DefaultStyledDocument d, ChangeEvent e) {
2692 Object source = e.getSource();
2693 if (source instanceof Style) {
2694 d.styleChanged((Style) source);
2695 } else {
2696 d.styleChanged(null);
2697 }
2698 }
2699 }
2700
2701
2702 /**
2703 * Added to the StyleContext. When the StyleContext changes, this invokes
2704 * <code>updateStylesListeningTo</code>.
2705 */
2706 static class StyleContextChangeHandler extends AbstractChangeHandler {
2707
2708 StyleContextChangeHandler(DefaultStyledDocument d) {
2709 super(d);
2710 }
2711
2712 void fireStateChanged(DefaultStyledDocument d, ChangeEvent e) {
2713 d.updateStylesListeningTo();
2714 }
2715 }
2716
2717
2718 /**
2719 * When run this creates a change event for the complete document
2720 * and fires it.
2721 */
2722 class ChangeUpdateRunnable implements Runnable {
2723 boolean isPending = false;
2724
2725 public void run() {
2726 synchronized(this) {
2727 isPending = false;
2728 }
2729
2730 try {
2731 writeLock();
2732 DefaultDocumentEvent dde = new DefaultDocumentEvent(0,
2733 getLength(),
2734 DocumentEvent.EventType.CHANGE);
2735 dde.end();
2736 fireChangedUpdate(dde);
2737 } finally {
2738 writeUnlock();
2739 }
2740 }
2741 }
2742}