0001 /*
0002 * Copyright 1997-2007 Sun Microsystems, Inc. All Rights Reserved.
0003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004 *
0005 * This code is free software; you can redistribute it and/or modify it
0006 * under the terms of the GNU General Public License version 2 only, as
0007 * published by the Free Software Foundation. Sun designates this
0008 * particular file as subject to the "Classpath" exception as provided
0009 * by Sun in the LICENSE file that accompanied this code.
0010 *
0011 * This code is distributed in the hope that it will be useful, but WITHOUT
0012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014 * version 2 for more details (a copy is included in the LICENSE file that
0015 * accompanied this code).
0016 *
0017 * You should have received a copy of the GNU General Public License version
0018 * 2 along with this work; if not, write to the Free Software Foundation,
0019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020 *
0021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022 * CA 95054 USA or visit www.sun.com if you need additional information or
0023 * have any questions.
0024 */
0025 package javax.swing.text;
0026
0027 import java.awt.Color;
0028 import java.awt.Component;
0029 import java.awt.Font;
0030 import java.awt.FontMetrics;
0031 import java.awt.font.TextAttribute;
0032 import java.lang.ref.ReferenceQueue;
0033 import java.lang.ref.WeakReference;
0034 import java.util.Enumeration;
0035 import java.util.HashMap;
0036 import java.util.Hashtable;
0037 import java.util.List;
0038 import java.util.Map;
0039 import java.util.Stack;
0040 import java.util.Vector;
0041 import java.util.ArrayList;
0042 import java.io.IOException;
0043 import java.io.ObjectInputStream;
0044 import java.io.ObjectOutputStream;
0045 import java.io.Serializable;
0046 import javax.swing.Icon;
0047 import javax.swing.event.*;
0048 import javax.swing.undo.AbstractUndoableEdit;
0049 import javax.swing.undo.CannotRedoException;
0050 import javax.swing.undo.CannotUndoException;
0051 import javax.swing.undo.UndoableEdit;
0052 import javax.swing.SwingUtilities;
0053
0054 /**
0055 * A document that can be marked up with character and paragraph
0056 * styles in a manner similar to the Rich Text Format. The element
0057 * structure for this document represents style crossings for
0058 * style runs. These style runs are mapped into a paragraph element
0059 * structure (which may reside in some other structure). The
0060 * style runs break at paragraph boundaries since logical styles are
0061 * assigned to paragraph boundaries.
0062 * <p>
0063 * <strong>Warning:</strong>
0064 * Serialized objects of this class will not be compatible with
0065 * future Swing releases. The current serialization support is
0066 * appropriate for short term storage or RMI between applications running
0067 * the same version of Swing. As of 1.4, support for long term storage
0068 * of all JavaBeans<sup><font size="-2">TM</font></sup>
0069 * has been added to the <code>java.beans</code> package.
0070 * Please see {@link java.beans.XMLEncoder}.
0071 *
0072 * @author Timothy Prinzing
0073 * @version 1.133 05/05/07
0074 * @see Document
0075 * @see AbstractDocument
0076 */
0077 public class DefaultStyledDocument extends AbstractDocument implements
0078 StyledDocument {
0079
0080 /**
0081 * Constructs a styled document.
0082 *
0083 * @param c the container for the content
0084 * @param styles resources and style definitions which may
0085 * be shared across documents
0086 */
0087 public DefaultStyledDocument(Content c, StyleContext styles) {
0088 super (c, styles);
0089 listeningStyles = new Vector();
0090 buffer = new ElementBuffer(createDefaultRoot());
0091 Style defaultStyle = styles
0092 .getStyle(StyleContext.DEFAULT_STYLE);
0093 setLogicalStyle(0, defaultStyle);
0094 }
0095
0096 /**
0097 * Constructs a styled document with the default content
0098 * storage implementation and a shared set of styles.
0099 *
0100 * @param styles the styles
0101 */
0102 public DefaultStyledDocument(StyleContext styles) {
0103 this (new GapContent(BUFFER_SIZE_DEFAULT), styles);
0104 }
0105
0106 /**
0107 * Constructs a default styled document. This buffers
0108 * input content by a size of <em>BUFFER_SIZE_DEFAULT</em>
0109 * and has a style context that is scoped by the lifetime
0110 * of the document and is not shared with other documents.
0111 */
0112 public DefaultStyledDocument() {
0113 this (new GapContent(BUFFER_SIZE_DEFAULT), new StyleContext());
0114 }
0115
0116 /**
0117 * Gets the default root element.
0118 *
0119 * @return the root
0120 * @see Document#getDefaultRootElement
0121 */
0122 public Element getDefaultRootElement() {
0123 return buffer.getRootElement();
0124 }
0125
0126 /**
0127 * Initialize the document to reflect the given element
0128 * structure (i.e. the structure reported by the
0129 * <code>getDefaultRootElement</code> method. If the
0130 * document contained any data it will first be removed.
0131 */
0132 protected void create(ElementSpec[] data) {
0133 try {
0134 if (getLength() != 0) {
0135 remove(0, getLength());
0136 }
0137 writeLock();
0138
0139 // install the content
0140 Content c = getContent();
0141 int n = data.length;
0142 StringBuffer sb = new StringBuffer();
0143 for (int i = 0; i < n; i++) {
0144 ElementSpec es = data[i];
0145 if (es.getLength() > 0) {
0146 sb.append(es.getArray(), es.getOffset(), es
0147 .getLength());
0148 }
0149 }
0150 UndoableEdit cEdit = c.insertString(0, sb.toString());
0151
0152 // build the event and element structure
0153 int length = sb.length();
0154 DefaultDocumentEvent evnt = new DefaultDocumentEvent(0,
0155 length, DocumentEvent.EventType.INSERT);
0156 evnt.addEdit(cEdit);
0157 buffer.create(length, data, evnt);
0158
0159 // update bidi (possibly)
0160 super .insertUpdate(evnt, null);
0161
0162 // notify the listeners
0163 evnt.end();
0164 fireInsertUpdate(evnt);
0165 fireUndoableEditUpdate(new UndoableEditEvent(this , evnt));
0166 } catch (BadLocationException ble) {
0167 throw new StateInvariantError("problem initializing");
0168 } finally {
0169 writeUnlock();
0170 }
0171
0172 }
0173
0174 /**
0175 * Inserts new elements in bulk. This is useful to allow
0176 * parsing with the document in an unlocked state and
0177 * prepare an element structure modification. This method
0178 * takes an array of tokens that describe how to update an
0179 * element structure so the time within a write lock can
0180 * be greatly reduced in an asynchronous update situation.
0181 * <p>
0182 * This method is thread safe, although most Swing methods
0183 * are not. Please see
0184 * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
0185 * to Use Threads</A> for more information.
0186 *
0187 * @param offset the starting offset >= 0
0188 * @param data the element data
0189 * @exception BadLocationException for an invalid starting offset
0190 */
0191 protected void insert(int offset, ElementSpec[] data)
0192 throws BadLocationException {
0193 if (data == null || data.length == 0) {
0194 return;
0195 }
0196
0197 try {
0198 writeLock();
0199
0200 // install the content
0201 Content c = getContent();
0202 int n = data.length;
0203 StringBuffer sb = new StringBuffer();
0204 for (int i = 0; i < n; i++) {
0205 ElementSpec es = data[i];
0206 if (es.getLength() > 0) {
0207 sb.append(es.getArray(), es.getOffset(), es
0208 .getLength());
0209 }
0210 }
0211 if (sb.length() == 0) {
0212 // Nothing to insert, bail.
0213 return;
0214 }
0215 UndoableEdit cEdit = c.insertString(offset, sb.toString());
0216
0217 // create event and build the element structure
0218 int length = sb.length();
0219 DefaultDocumentEvent evnt = new DefaultDocumentEvent(
0220 offset, length, DocumentEvent.EventType.INSERT);
0221 evnt.addEdit(cEdit);
0222 buffer.insert(offset, length, data, evnt);
0223
0224 // update bidi (possibly)
0225 super .insertUpdate(evnt, null);
0226
0227 // notify the listeners
0228 evnt.end();
0229 fireInsertUpdate(evnt);
0230 fireUndoableEditUpdate(new UndoableEditEvent(this , evnt));
0231 } finally {
0232 writeUnlock();
0233 }
0234 }
0235
0236 /**
0237 * Removes an element from this document.
0238 *
0239 * <p>The element is removed from its parent element, as well as
0240 * the text in the range identified by the element. If the
0241 * element isn't associated with the document, {@code
0242 * IllegalArgumentException} is thrown.</p>
0243 *
0244 * <p>As empty branch elements are not allowed in the document, if the
0245 * element is the sole child, its parent element is removed as well,
0246 * recursively. This means that when replacing all the children of a
0247 * particular element, new children should be added <em>before</em>
0248 * removing old children.
0249 *
0250 * <p>Element removal results in two events being fired, the
0251 * {@code DocumentEvent} for changes in element structure and {@code
0252 * UndoableEditEvent} for changes in document content.</p>
0253 *
0254 * <p>If the element contains end-of-content mark (the last {@code
0255 * "\n"} character in document), this character is not removed;
0256 * instead, preceding leaf element is extended to cover the
0257 * character. If the last leaf already ends with {@code "\n",} it is
0258 * included in content removal.</p>
0259 *
0260 * <p>If the element is {@code null,} {@code NullPointerException} is
0261 * thrown. If the element structure would become invalid after the removal,
0262 * for example if the element is the document root element, {@code
0263 * IllegalArgumentException} is thrown. If the current element structure is
0264 * invalid, {@code IllegalStateException} is thrown.</p>
0265 *
0266 * @param elem the element to remove
0267 * @throws NullPointerException if the element is {@code null}
0268 * @throws IllegalArgumentException if the element could not be removed
0269 * @throws IllegalStateException if the element structure is invalid
0270 *
0271 * @since 1.7
0272 */
0273 public void removeElement(Element elem) {
0274 try {
0275 writeLock();
0276 removeElementImpl(elem);
0277 } finally {
0278 writeUnlock();
0279 }
0280 }
0281
0282 private void removeElementImpl(Element elem) {
0283 if (elem.getDocument() != this ) {
0284 throw new IllegalArgumentException(
0285 "element doesn't belong to document");
0286 }
0287 BranchElement parent = (BranchElement) elem.getParentElement();
0288 if (parent == null) {
0289 throw new IllegalArgumentException(
0290 "can't remove the root element");
0291 }
0292
0293 int startOffset = elem.getStartOffset();
0294 int removeFrom = startOffset;
0295 int endOffset = elem.getEndOffset();
0296 int removeTo = endOffset;
0297 int lastEndOffset = getLength() + 1;
0298 Content content = getContent();
0299 boolean atEnd = false;
0300 boolean isComposedText = Utilities.isComposedTextElement(elem);
0301
0302 if (endOffset >= lastEndOffset) {
0303 // element includes the last "\n" character, needs special handling
0304 if (startOffset <= 0) {
0305 throw new IllegalArgumentException(
0306 "can't remove the whole content");
0307 }
0308 removeTo = lastEndOffset - 1; // last "\n" must not be removed
0309 try {
0310 if (content.getString(startOffset - 1, 1).charAt(0) == '\n') {
0311 removeFrom--; // preceding leaf ends with "\n", remove it
0312 }
0313 } catch (BadLocationException ble) { // can't happen
0314 throw new IllegalStateException(ble);
0315 }
0316 atEnd = true;
0317 }
0318 int length = removeTo - removeFrom;
0319
0320 DefaultDocumentEvent dde = new DefaultDocumentEvent(removeFrom,
0321 length, DefaultDocumentEvent.EventType.REMOVE);
0322 UndoableEdit ue = null;
0323 // do not leave empty branch elements
0324 while (parent.getElementCount() == 1) {
0325 elem = parent;
0326 parent = (BranchElement) parent.getParentElement();
0327 if (parent == null) { // shouldn't happen
0328 throw new IllegalStateException(
0329 "invalid element structure");
0330 }
0331 }
0332 Element[] removed = { elem };
0333 Element[] added = {};
0334 int index = parent.getElementIndex(startOffset);
0335 parent.replace(index, 1, added);
0336 dde.addEdit(new ElementEdit(parent, index, removed, added));
0337 if (length > 0) {
0338 try {
0339 ue = content.remove(removeFrom, length);
0340 if (ue != null) {
0341 dde.addEdit(ue);
0342 }
0343 } catch (BadLocationException ble) {
0344 // can only happen if the element structure is severely broken
0345 throw new IllegalStateException(ble);
0346 }
0347 lastEndOffset -= length;
0348 }
0349
0350 if (atEnd) {
0351 // preceding leaf element should be extended to cover orphaned "\n"
0352 Element prevLeaf = parent.getElement(parent
0353 .getElementCount() - 1);
0354 while ((prevLeaf != null) && !prevLeaf.isLeaf()) {
0355 prevLeaf = prevLeaf.getElement(prevLeaf
0356 .getElementCount() - 1);
0357 }
0358 if (prevLeaf == null) { // shouldn't happen
0359 throw new IllegalStateException(
0360 "invalid element structure");
0361 }
0362 int prevStartOffset = prevLeaf.getStartOffset();
0363 BranchElement prevParent = (BranchElement) prevLeaf
0364 .getParentElement();
0365 int prevIndex = prevParent.getElementIndex(prevStartOffset);
0366 Element newElem = null;
0367 newElem = createLeafElement(prevParent, prevLeaf
0368 .getAttributes(), prevStartOffset, lastEndOffset);
0369 Element[] prevRemoved = { prevLeaf };
0370 Element[] prevAdded = { newElem };
0371 prevParent.replace(prevIndex, 1, prevAdded);
0372 dde.addEdit(new ElementEdit(prevParent, prevIndex,
0373 prevRemoved, prevAdded));
0374 }
0375
0376 postRemoveUpdate(dde);
0377 dde.end();
0378 fireRemoveUpdate(dde);
0379 if (!(isComposedText && (ue != null))) {
0380 // do not fire UndoabeEdit event for composed text edit (unsupported)
0381 fireUndoableEditUpdate(new UndoableEditEvent(this , dde));
0382 }
0383 }
0384
0385 /**
0386 * Adds a new style into the logical style hierarchy. Style attributes
0387 * resolve from bottom up so an attribute specified in a child
0388 * will override an attribute specified in the parent.
0389 *
0390 * @param nm the name of the style (must be unique within the
0391 * collection of named styles). The name may be null if the style
0392 * is unnamed, but the caller is responsible
0393 * for managing the reference returned as an unnamed style can't
0394 * be fetched by name. An unnamed style may be useful for things
0395 * like character attribute overrides such as found in a style
0396 * run.
0397 * @param parent the parent style. This may be null if unspecified
0398 * attributes need not be resolved in some other style.
0399 * @return the style
0400 */
0401 public Style addStyle(String nm, Style parent) {
0402 StyleContext styles = (StyleContext) getAttributeContext();
0403 return styles.addStyle(nm, parent);
0404 }
0405
0406 /**
0407 * Removes a named style previously added to the document.
0408 *
0409 * @param nm the name of the style to remove
0410 */
0411 public void removeStyle(String nm) {
0412 StyleContext styles = (StyleContext) getAttributeContext();
0413 styles.removeStyle(nm);
0414 }
0415
0416 /**
0417 * Fetches a named style previously added.
0418 *
0419 * @param nm the name of the style
0420 * @return the style
0421 */
0422 public Style getStyle(String nm) {
0423 StyleContext styles = (StyleContext) getAttributeContext();
0424 return styles.getStyle(nm);
0425 }
0426
0427 /**
0428 * Fetches the list of of style names.
0429 *
0430 * @return all the style names
0431 */
0432 public Enumeration<?> getStyleNames() {
0433 return ((StyleContext) getAttributeContext()).getStyleNames();
0434 }
0435
0436 /**
0437 * Sets the logical style to use for the paragraph at the
0438 * given position. If attributes aren't explicitly set
0439 * for character and paragraph attributes they will resolve
0440 * through the logical style assigned to the paragraph, which
0441 * in turn may resolve through some hierarchy completely
0442 * independent of the element hierarchy in the document.
0443 * <p>
0444 * This method is thread safe, although most Swing methods
0445 * are not. Please see
0446 * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
0447 * to Use Threads</A> for more information.
0448 *
0449 * @param pos the offset from the start of the document >= 0
0450 * @param s the logical style to assign to the paragraph, null if none
0451 */
0452 public void setLogicalStyle(int pos, Style s) {
0453 Element paragraph = getParagraphElement(pos);
0454 if ((paragraph != null)
0455 && (paragraph instanceof AbstractElement)) {
0456 try {
0457 writeLock();
0458 StyleChangeUndoableEdit edit = new StyleChangeUndoableEdit(
0459 (AbstractElement) paragraph, s);
0460 ((AbstractElement) paragraph).setResolveParent(s);
0461 int p0 = paragraph.getStartOffset();
0462 int p1 = paragraph.getEndOffset();
0463 DefaultDocumentEvent e = new DefaultDocumentEvent(p0,
0464 p1 - p0, DocumentEvent.EventType.CHANGE);
0465 e.addEdit(edit);
0466 e.end();
0467 fireChangedUpdate(e);
0468 fireUndoableEditUpdate(new UndoableEditEvent(this , e));
0469 } finally {
0470 writeUnlock();
0471 }
0472 }
0473 }
0474
0475 /**
0476 * Fetches the logical style assigned to the paragraph
0477 * represented by the given position.
0478 *
0479 * @param p the location to translate to a paragraph
0480 * and determine the logical style assigned >= 0. This
0481 * is an offset from the start of the document.
0482 * @return the style, null if none
0483 */
0484 public Style getLogicalStyle(int p) {
0485 Style s = null;
0486 Element paragraph = getParagraphElement(p);
0487 if (paragraph != null) {
0488 AttributeSet a = paragraph.getAttributes();
0489 AttributeSet parent = a.getResolveParent();
0490 if (parent instanceof Style) {
0491 s = (Style) parent;
0492 }
0493 }
0494 return s;
0495 }
0496
0497 /**
0498 * Sets attributes for some part of the document.
0499 * A write lock is held by this operation while changes
0500 * are being made, and a DocumentEvent is sent to the listeners
0501 * after the change has been successfully completed.
0502 * <p>
0503 * This method is thread safe, although most Swing methods
0504 * are not. Please see
0505 * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
0506 * to Use Threads</A> for more information.
0507 *
0508 * @param offset the offset in the document >= 0
0509 * @param length the length >= 0
0510 * @param s the attributes
0511 * @param replace true if the previous attributes should be replaced
0512 * before setting the new attributes
0513 */
0514 public void setCharacterAttributes(int offset, int length,
0515 AttributeSet s, boolean replace) {
0516 if (length == 0) {
0517 return;
0518 }
0519 try {
0520 writeLock();
0521 DefaultDocumentEvent changes = new DefaultDocumentEvent(
0522 offset, length, DocumentEvent.EventType.CHANGE);
0523
0524 // split elements that need it
0525 buffer.change(offset, length, changes);
0526
0527 AttributeSet sCopy = s.copyAttributes();
0528
0529 // PENDING(prinz) - this isn't a very efficient way to iterate
0530 int lastEnd = Integer.MAX_VALUE;
0531 for (int pos = offset; pos < (offset + length); pos = lastEnd) {
0532 Element run = getCharacterElement(pos);
0533 lastEnd = run.getEndOffset();
0534 if (pos == lastEnd) {
0535 // offset + length beyond length of document, bail.
0536 break;
0537 }
0538 MutableAttributeSet attr = (MutableAttributeSet) run
0539 .getAttributes();
0540 changes.addEdit(new AttributeUndoableEdit(run, sCopy,
0541 replace));
0542 if (replace) {
0543 attr.removeAttributes(attr);
0544 }
0545 attr.addAttributes(s);
0546 }
0547 changes.end();
0548 fireChangedUpdate(changes);
0549 fireUndoableEditUpdate(new UndoableEditEvent(this , changes));
0550 } finally {
0551 writeUnlock();
0552 }
0553
0554 }
0555
0556 /**
0557 * Sets attributes for a paragraph.
0558 * <p>
0559 * This method is thread safe, although most Swing methods
0560 * are not. Please see
0561 * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
0562 * to Use Threads</A> for more information.
0563 *
0564 * @param offset the offset into the paragraph >= 0
0565 * @param length the number of characters affected >= 0
0566 * @param s the attributes
0567 * @param replace whether to replace existing attributes, or merge them
0568 */
0569 public void setParagraphAttributes(int offset, int length,
0570 AttributeSet s, boolean replace) {
0571 try {
0572 writeLock();
0573 DefaultDocumentEvent changes = new DefaultDocumentEvent(
0574 offset, length, DocumentEvent.EventType.CHANGE);
0575
0576 AttributeSet sCopy = s.copyAttributes();
0577
0578 // PENDING(prinz) - this assumes a particular element structure
0579 Element section = getDefaultRootElement();
0580 int index0 = section.getElementIndex(offset);
0581 int index1 = section.getElementIndex(offset
0582 + ((length > 0) ? length - 1 : 0));
0583 boolean isI18N = Boolean.TRUE
0584 .equals(getProperty(I18NProperty));
0585 boolean hasRuns = false;
0586 for (int i = index0; i <= index1; i++) {
0587 Element paragraph = section.getElement(i);
0588 MutableAttributeSet attr = (MutableAttributeSet) paragraph
0589 .getAttributes();
0590 changes.addEdit(new AttributeUndoableEdit(paragraph,
0591 sCopy, replace));
0592 if (replace) {
0593 attr.removeAttributes(attr);
0594 }
0595 attr.addAttributes(s);
0596 if (isI18N && !hasRuns) {
0597 hasRuns = (attr
0598 .getAttribute(TextAttribute.RUN_DIRECTION) != null);
0599 }
0600 }
0601
0602 if (hasRuns) {
0603 updateBidi(changes);
0604 }
0605
0606 changes.end();
0607 fireChangedUpdate(changes);
0608 fireUndoableEditUpdate(new UndoableEditEvent(this , changes));
0609 } finally {
0610 writeUnlock();
0611 }
0612 }
0613
0614 /**
0615 * Gets the paragraph element at the offset <code>pos</code>.
0616 * A paragraph consists of at least one child Element, which is usually
0617 * a leaf.
0618 *
0619 * @param pos the starting offset >= 0
0620 * @return the element
0621 */
0622 public Element getParagraphElement(int pos) {
0623 Element e = null;
0624 for (e = getDefaultRootElement(); !e.isLeaf();) {
0625 int index = e.getElementIndex(pos);
0626 e = e.getElement(index);
0627 }
0628 if (e != null)
0629 return e.getParentElement();
0630 return e;
0631 }
0632
0633 /**
0634 * Gets a character element based on a position.
0635 *
0636 * @param pos the position in the document >= 0
0637 * @return the element
0638 */
0639 public Element getCharacterElement(int pos) {
0640 Element e = null;
0641 for (e = getDefaultRootElement(); !e.isLeaf();) {
0642 int index = e.getElementIndex(pos);
0643 e = e.getElement(index);
0644 }
0645 return e;
0646 }
0647
0648 // --- local methods -------------------------------------------------
0649
0650 /**
0651 * Updates document structure as a result of text insertion. This
0652 * will happen within a write lock. This implementation simply
0653 * parses the inserted content for line breaks and builds up a set
0654 * of instructions for the element buffer.
0655 *
0656 * @param chng a description of the document change
0657 * @param attr the attributes
0658 */
0659 protected void insertUpdate(DefaultDocumentEvent chng,
0660 AttributeSet attr) {
0661 int offset = chng.getOffset();
0662 int length = chng.getLength();
0663 if (attr == null) {
0664 attr = SimpleAttributeSet.EMPTY;
0665 }
0666
0667 // Paragraph attributes should come from point after insertion.
0668 // You really only notice this when inserting at a paragraph
0669 // boundary.
0670 Element paragraph = getParagraphElement(offset + length);
0671 AttributeSet pattr = paragraph.getAttributes();
0672 // Character attributes should come from actual insertion point.
0673 Element pParagraph = getParagraphElement(offset);
0674 Element run = pParagraph.getElement(pParagraph
0675 .getElementIndex(offset));
0676 int endOffset = offset + length;
0677 boolean insertingAtBoundry = (run.getEndOffset() == endOffset);
0678 AttributeSet cattr = run.getAttributes();
0679
0680 try {
0681 Segment s = new Segment();
0682 Vector parseBuffer = new Vector();
0683 ElementSpec lastStartSpec = null;
0684 boolean insertingAfterNewline = false;
0685 short lastStartDirection = ElementSpec.OriginateDirection;
0686 // Check if the previous character was a newline.
0687 if (offset > 0) {
0688 getText(offset - 1, 1, s);
0689 if (s.array[s.offset] == '\n') {
0690 // Inserting after a newline.
0691 insertingAfterNewline = true;
0692 lastStartDirection = createSpecsForInsertAfterNewline(
0693 paragraph, pParagraph, pattr, parseBuffer,
0694 offset, endOffset);
0695 for (int counter = parseBuffer.size() - 1; counter >= 0; counter--) {
0696 ElementSpec spec = (ElementSpec) parseBuffer
0697 .elementAt(counter);
0698 if (spec.getType() == ElementSpec.StartTagType) {
0699 lastStartSpec = spec;
0700 break;
0701 }
0702 }
0703 }
0704 }
0705 // If not inserting after a new line, pull the attributes for
0706 // new paragraphs from the paragraph under the insertion point.
0707 if (!insertingAfterNewline)
0708 pattr = pParagraph.getAttributes();
0709
0710 getText(offset, length, s);
0711 char[] txt = s.array;
0712 int n = s.offset + s.count;
0713 int lastOffset = s.offset;
0714
0715 for (int i = s.offset; i < n; i++) {
0716 if (txt[i] == '\n') {
0717 int breakOffset = i + 1;
0718 parseBuffer.addElement(new ElementSpec(attr,
0719 ElementSpec.ContentType, breakOffset
0720 - lastOffset));
0721 parseBuffer.addElement(new ElementSpec(null,
0722 ElementSpec.EndTagType));
0723 lastStartSpec = new ElementSpec(pattr,
0724 ElementSpec.StartTagType);
0725 parseBuffer.addElement(lastStartSpec);
0726 lastOffset = breakOffset;
0727 }
0728 }
0729 if (lastOffset < n) {
0730 parseBuffer.addElement(new ElementSpec(attr,
0731 ElementSpec.ContentType, n - lastOffset));
0732 }
0733
0734 ElementSpec first = (ElementSpec) parseBuffer
0735 .firstElement();
0736
0737 int docLength = getLength();
0738
0739 // Check for join previous of first content.
0740 if (first.getType() == ElementSpec.ContentType
0741 && cattr.isEqual(attr)) {
0742 first.setDirection(ElementSpec.JoinPreviousDirection);
0743 }
0744
0745 // Do a join fracture/next for last start spec if necessary.
0746 if (lastStartSpec != null) {
0747 if (insertingAfterNewline) {
0748 lastStartSpec.setDirection(lastStartDirection);
0749 }
0750 // Join to the fracture if NOT inserting at the end
0751 // (fracture only happens when not inserting at end of
0752 // paragraph).
0753 else if (pParagraph.getEndOffset() != endOffset) {
0754 lastStartSpec
0755 .setDirection(ElementSpec.JoinFractureDirection);
0756 }
0757 // Join to next if parent of pParagraph has another
0758 // element after pParagraph, and it isn't a leaf.
0759 else {
0760 Element parent = pParagraph.getParentElement();
0761 int pParagraphIndex = parent
0762 .getElementIndex(offset);
0763 if ((pParagraphIndex + 1) < parent
0764 .getElementCount()
0765 && !parent.getElement(pParagraphIndex + 1)
0766 .isLeaf()) {
0767 lastStartSpec
0768 .setDirection(ElementSpec.JoinNextDirection);
0769 }
0770 }
0771 }
0772
0773 // Do a JoinNext for last spec if it is content, it doesn't
0774 // already have a direction set, no new paragraphs have been
0775 // inserted or a new paragraph has been inserted and its join
0776 // direction isn't originate, and the element at endOffset
0777 // is a leaf.
0778 if (insertingAtBoundry && endOffset < docLength) {
0779 ElementSpec last = (ElementSpec) parseBuffer
0780 .lastElement();
0781 if (last.getType() == ElementSpec.ContentType
0782 && last.getDirection() != ElementSpec.JoinPreviousDirection
0783 && ((lastStartSpec == null && (paragraph == pParagraph || insertingAfterNewline)) || (lastStartSpec != null && lastStartSpec
0784 .getDirection() != ElementSpec.OriginateDirection))) {
0785 Element nextRun = paragraph.getElement(paragraph
0786 .getElementIndex(endOffset));
0787 // Don't try joining to a branch!
0788 if (nextRun.isLeaf()
0789 && attr.isEqual(nextRun.getAttributes())) {
0790 last
0791 .setDirection(ElementSpec.JoinNextDirection);
0792 }
0793 }
0794 }
0795 // If not inserting at boundary and there is going to be a
0796 // fracture, then can join next on last content if cattr
0797 // matches the new attributes.
0798 else if (!insertingAtBoundry
0799 && lastStartSpec != null
0800 && lastStartSpec.getDirection() == ElementSpec.JoinFractureDirection) {
0801 ElementSpec last = (ElementSpec) parseBuffer
0802 .lastElement();
0803 if (last.getType() == ElementSpec.ContentType
0804 && last.getDirection() != ElementSpec.JoinPreviousDirection
0805 && attr.isEqual(cattr)) {
0806 last.setDirection(ElementSpec.JoinNextDirection);
0807 }
0808 }
0809
0810 // Check for the composed text element. If it is, merge the character attributes
0811 // into this element as well.
0812 if (Utilities.isComposedTextAttributeDefined(attr)) {
0813 ((MutableAttributeSet) attr).addAttributes(cattr);
0814 ((MutableAttributeSet) attr).addAttribute(
0815 AbstractDocument.ElementNameAttribute,
0816 AbstractDocument.ContentElementName);
0817 }
0818
0819 ElementSpec[] spec = new ElementSpec[parseBuffer.size()];
0820 parseBuffer.copyInto(spec);
0821 buffer.insert(offset, length, spec, chng);
0822 } catch (BadLocationException bl) {
0823 }
0824
0825 super .insertUpdate(chng, attr);
0826 }
0827
0828 /**
0829 * This is called by insertUpdate when inserting after a new line.
0830 * It generates, in <code>parseBuffer</code>, ElementSpecs that will
0831 * position the stack in <code>paragraph</code>.<p>
0832 * It returns the direction the last StartSpec should have (this don't
0833 * necessarily create the last start spec).
0834 */
0835 short createSpecsForInsertAfterNewline(Element paragraph,
0836 Element pParagraph, AttributeSet pattr, Vector parseBuffer,
0837 int offset, int endOffset) {
0838 // Need to find the common parent of pParagraph and paragraph.
0839 if (paragraph.getParentElement() == pParagraph
0840 .getParentElement()) {
0841 // The simple (and common) case that pParagraph and
0842 // paragraph have the same parent.
0843 ElementSpec spec = new ElementSpec(pattr,
0844 ElementSpec.EndTagType);
0845 parseBuffer.addElement(spec);
0846 spec = new ElementSpec(pattr, ElementSpec.StartTagType);
0847 parseBuffer.addElement(spec);
0848 if (pParagraph.getEndOffset() != endOffset)
0849 return ElementSpec.JoinFractureDirection;
0850
0851 Element parent = pParagraph.getParentElement();
0852 if ((parent.getElementIndex(offset) + 1) < parent
0853 .getElementCount())
0854 return ElementSpec.JoinNextDirection;
0855 } else {
0856 // Will only happen for text with more than 2 levels.
0857 // Find the common parent of a paragraph and pParagraph
0858 Vector leftParents = new Vector();
0859 Vector rightParents = new Vector();
0860 Element e = pParagraph;
0861 while (e != null) {
0862 leftParents.addElement(e);
0863 e = e.getParentElement();
0864 }
0865 e = paragraph;
0866 int leftIndex = -1;
0867 while (e != null
0868 && (leftIndex = leftParents.indexOf(e)) == -1) {
0869 rightParents.addElement(e);
0870 e = e.getParentElement();
0871 }
0872 if (e != null) {
0873 // e identifies the common parent.
0874 // Build the ends.
0875 for (int counter = 0; counter < leftIndex; counter++) {
0876 parseBuffer.addElement(new ElementSpec(null,
0877 ElementSpec.EndTagType));
0878 }
0879 // And the starts.
0880 ElementSpec spec = null;
0881 for (int counter = rightParents.size() - 1; counter >= 0; counter--) {
0882 spec = new ElementSpec(((Element) rightParents
0883 .elementAt(counter)).getAttributes(),
0884 ElementSpec.StartTagType);
0885 if (counter > 0)
0886 spec
0887 .setDirection(ElementSpec.JoinNextDirection);
0888 parseBuffer.addElement(spec);
0889 }
0890 // If there are right parents, then we generated starts
0891 // down the right subtree and there will be an element to
0892 // join to.
0893 if (rightParents.size() > 0)
0894 return ElementSpec.JoinNextDirection;
0895 // No right subtree, e.getElement(endOffset) is a
0896 // leaf. There will be a facture.
0897 return ElementSpec.JoinFractureDirection;
0898 }
0899 // else: Could throw an exception here, but should never get here!
0900 }
0901 return ElementSpec.OriginateDirection;
0902 }
0903
0904 /**
0905 * Updates document structure as a result of text removal.
0906 *
0907 * @param chng a description of the document change
0908 */
0909 protected void removeUpdate(DefaultDocumentEvent chng) {
0910 super .removeUpdate(chng);
0911 buffer.remove(chng.getOffset(), chng.getLength(), chng);
0912 }
0913
0914 /**
0915 * Creates the root element to be used to represent the
0916 * default document structure.
0917 *
0918 * @return the element base
0919 */
0920 protected AbstractElement createDefaultRoot() {
0921 // grabs a write-lock for this initialization and
0922 // abandon it during initialization so in normal
0923 // operation we can detect an illegitimate attempt
0924 // to mutate attributes.
0925 writeLock();
0926 BranchElement section = new SectionElement();
0927 BranchElement paragraph = new BranchElement(section, null);
0928
0929 LeafElement brk = new LeafElement(paragraph, null, 0, 1);
0930 Element[] buff = new Element[1];
0931 buff[0] = brk;
0932 paragraph.replace(0, 0, buff);
0933
0934 buff[0] = paragraph;
0935 section.replace(0, 0, buff);
0936 writeUnlock();
0937 return section;
0938 }
0939
0940 /**
0941 * Gets the foreground color from an attribute set.
0942 *
0943 * @param attr the attribute set
0944 * @return the color
0945 */
0946 public Color getForeground(AttributeSet attr) {
0947 StyleContext styles = (StyleContext) getAttributeContext();
0948 return styles.getForeground(attr);
0949 }
0950
0951 /**
0952 * Gets the background color from an attribute set.
0953 *
0954 * @param attr the attribute set
0955 * @return the color
0956 */
0957 public Color getBackground(AttributeSet attr) {
0958 StyleContext styles = (StyleContext) getAttributeContext();
0959 return styles.getBackground(attr);
0960 }
0961
0962 /**
0963 * Gets the font from an attribute set.
0964 *
0965 * @param attr the attribute set
0966 * @return the font
0967 */
0968 public Font getFont(AttributeSet attr) {
0969 StyleContext styles = (StyleContext) getAttributeContext();
0970 return styles.getFont(attr);
0971 }
0972
0973 /**
0974 * Called when any of this document's styles have changed.
0975 * Subclasses may wish to be intelligent about what gets damaged.
0976 *
0977 * @param style The Style that has changed.
0978 */
0979 protected void styleChanged(Style style) {
0980 // Only propagate change updated if have content
0981 if (getLength() != 0) {
0982 // lazily create a ChangeUpdateRunnable
0983 if (updateRunnable == null) {
0984 updateRunnable = new ChangeUpdateRunnable();
0985 }
0986
0987 // We may get a whole batch of these at once, so only
0988 // queue the runnable if it is not already pending
0989 synchronized (updateRunnable) {
0990 if (!updateRunnable.isPending) {
0991 SwingUtilities.invokeLater(updateRunnable);
0992 updateRunnable.isPending = true;
0993 }
0994 }
0995 }
0996 }
0997
0998 /**
0999 * Adds a document listener for notification of any changes.
1000 *
1001 * @param listener the listener
1002 * @see Document#addDocumentListener
1003 */
1004 public void addDocumentListener(DocumentListener listener) {
1005 synchronized (listeningStyles) {
1006 int oldDLCount = listenerList
1007 .getListenerCount(DocumentListener.class);
1008 super .addDocumentListener(listener);
1009 if (oldDLCount == 0) {
1010 if (styleContextChangeListener == null) {
1011 styleContextChangeListener = createStyleContextChangeListener();
1012 }
1013 if (styleContextChangeListener != null) {
1014 StyleContext styles = (StyleContext) getAttributeContext();
1015 List<ChangeListener> staleListeners = AbstractChangeHandler
1016 .getStaleListeners(styleContextChangeListener);
1017 for (ChangeListener l : staleListeners) {
1018 styles.removeChangeListener(l);
1019 }
1020 styles
1021 .addChangeListener(styleContextChangeListener);
1022 }
1023 updateStylesListeningTo();
1024 }
1025 }
1026 }
1027
1028 /**
1029 * Removes a document listener.
1030 *
1031 * @param listener the listener
1032 * @see Document#removeDocumentListener
1033 */
1034 public void removeDocumentListener(DocumentListener listener) {
1035 synchronized (listeningStyles) {
1036 super .removeDocumentListener(listener);
1037 if (listenerList.getListenerCount(DocumentListener.class) == 0) {
1038 for (int counter = listeningStyles.size() - 1; counter >= 0; counter--) {
1039 ((Style) listeningStyles.elementAt(counter))
1040 .removeChangeListener(styleChangeListener);
1041 }
1042 listeningStyles.removeAllElements();
1043 if (styleContextChangeListener != null) {
1044 StyleContext styles = (StyleContext) getAttributeContext();
1045 styles
1046 .removeChangeListener(styleContextChangeListener);
1047 }
1048 }
1049 }
1050 }
1051
1052 /**
1053 * Returns a new instance of StyleChangeHandler.
1054 */
1055 ChangeListener createStyleChangeListener() {
1056 return new StyleChangeHandler(this );
1057 }
1058
1059 /**
1060 * Returns a new instance of StyleContextChangeHandler.
1061 */
1062 ChangeListener createStyleContextChangeListener() {
1063 return new StyleContextChangeHandler(this );
1064 }
1065
1066 /**
1067 * Adds a ChangeListener to new styles, and removes ChangeListener from
1068 * old styles.
1069 */
1070 void updateStylesListeningTo() {
1071 synchronized (listeningStyles) {
1072 StyleContext styles = (StyleContext) getAttributeContext();
1073 if (styleChangeListener == null) {
1074 styleChangeListener = createStyleChangeListener();
1075 }
1076 if (styleChangeListener != null && styles != null) {
1077 Enumeration styleNames = styles.getStyleNames();
1078 Vector v = (Vector) listeningStyles.clone();
1079 listeningStyles.removeAllElements();
1080 List<ChangeListener> staleListeners = AbstractChangeHandler
1081 .getStaleListeners(styleChangeListener);
1082 while (styleNames.hasMoreElements()) {
1083 String name = (String) styleNames.nextElement();
1084 Style aStyle = styles.getStyle(name);
1085 int index = v.indexOf(aStyle);
1086 listeningStyles.addElement(aStyle);
1087 if (index == -1) {
1088 for (ChangeListener l : staleListeners) {
1089 aStyle.removeChangeListener(l);
1090 }
1091 aStyle.addChangeListener(styleChangeListener);
1092 } else {
1093 v.removeElementAt(index);
1094 }
1095 }
1096 for (int counter = v.size() - 1; counter >= 0; counter--) {
1097 Style aStyle = (Style) v.elementAt(counter);
1098 aStyle.removeChangeListener(styleChangeListener);
1099 }
1100 if (listeningStyles.size() == 0) {
1101 styleChangeListener = null;
1102 }
1103 }
1104 }
1105 }
1106
1107 private void readObject(ObjectInputStream s)
1108 throws ClassNotFoundException, IOException {
1109 listeningStyles = new Vector();
1110 s.defaultReadObject();
1111 // Reinstall style listeners.
1112 if (styleContextChangeListener == null
1113 && listenerList
1114 .getListenerCount(DocumentListener.class) > 0) {
1115 styleContextChangeListener = createStyleContextChangeListener();
1116 if (styleContextChangeListener != null) {
1117 StyleContext styles = (StyleContext) getAttributeContext();
1118 styles.addChangeListener(styleContextChangeListener);
1119 }
1120 updateStylesListeningTo();
1121 }
1122 }
1123
1124 // --- member variables -----------------------------------------------------------
1125
1126 /**
1127 * The default size of the initial content buffer.
1128 */
1129 public static final int BUFFER_SIZE_DEFAULT = 4096;
1130
1131 protected ElementBuffer buffer;
1132
1133 /** Styles listening to. */
1134 private transient Vector listeningStyles;
1135
1136 /** Listens to Styles. */
1137 private transient ChangeListener styleChangeListener;
1138
1139 /** Listens to Styles. */
1140 private transient ChangeListener styleContextChangeListener;
1141
1142 /** Run to create a change event for the document */
1143 private transient ChangeUpdateRunnable updateRunnable;
1144
1145 /**
1146 * Default root element for a document... maps out the
1147 * paragraphs/lines contained.
1148 * <p>
1149 * <strong>Warning:</strong>
1150 * Serialized objects of this class will not be compatible with
1151 * future Swing releases. The current serialization support is
1152 * appropriate for short term storage or RMI between applications running
1153 * the same version of Swing. As of 1.4, support for long term storage
1154 * of all JavaBeans<sup><font size="-2">TM</font></sup>
1155 * has been added to the <code>java.beans</code> package.
1156 * Please see {@link java.beans.XMLEncoder}.
1157 */
1158 protected class SectionElement extends BranchElement {
1159
1160 /**
1161 * Creates a new SectionElement.
1162 */
1163 public SectionElement() {
1164 super (null, null);
1165 }
1166
1167 /**
1168 * Gets the name of the element.
1169 *
1170 * @return the name
1171 */
1172 public String getName() {
1173 return SectionElementName;
1174 }
1175 }
1176
1177 /**
1178 * Specification for building elements.
1179 * <p>
1180 * <strong>Warning:</strong>
1181 * Serialized objects of this class will not be compatible with
1182 * future Swing releases. The current serialization support is
1183 * appropriate for short term storage or RMI between applications running
1184 * the same version of Swing. As of 1.4, support for long term storage
1185 * of all JavaBeans<sup><font size="-2">TM</font></sup>
1186 * has been added to the <code>java.beans</code> package.
1187 * Please see {@link java.beans.XMLEncoder}.
1188 */
1189 public static class ElementSpec {
1190
1191 /**
1192 * A possible value for getType. This specifies
1193 * that this record type is a start tag and
1194 * represents markup that specifies the start
1195 * of an element.
1196 */
1197 public static final short StartTagType = 1;
1198
1199 /**
1200 * A possible value for getType. This specifies
1201 * that this record type is a end tag and
1202 * represents markup that specifies the end
1203 * of an element.
1204 */
1205 public static final short EndTagType = 2;
1206
1207 /**
1208 * A possible value for getType. This specifies
1209 * that this record type represents content.
1210 */
1211 public static final short ContentType = 3;
1212
1213 /**
1214 * A possible value for getDirection. This specifies
1215 * that the data associated with this record should
1216 * be joined to what precedes it.
1217 */
1218 public static final short JoinPreviousDirection = 4;
1219
1220 /**
1221 * A possible value for getDirection. This specifies
1222 * that the data associated with this record should
1223 * be joined to what follows it.
1224 */
1225 public static final short JoinNextDirection = 5;
1226
1227 /**
1228 * A possible value for getDirection. This specifies
1229 * that the data associated with this record should
1230 * be used to originate a new element. This would be
1231 * the normal value.
1232 */
1233 public static final short OriginateDirection = 6;
1234
1235 /**
1236 * A possible value for getDirection. This specifies
1237 * that the data associated with this record should
1238 * be joined to the fractured element.
1239 */
1240 public static final short JoinFractureDirection = 7;
1241
1242 /**
1243 * Constructor useful for markup when the markup will not
1244 * be stored in the document.
1245 *
1246 * @param a the attributes for the element
1247 * @param type the type of the element (StartTagType, EndTagType,
1248 * ContentType)
1249 */
1250 public ElementSpec(AttributeSet a, short type) {
1251 this (a, type, null, 0, 0);
1252 }
1253
1254 /**
1255 * Constructor for parsing inside the document when
1256 * the data has already been added, but len information
1257 * is needed.
1258 *
1259 * @param a the attributes for the element
1260 * @param type the type of the element (StartTagType, EndTagType,
1261 * ContentType)
1262 * @param len the length >= 0
1263 */
1264 public ElementSpec(AttributeSet a, short type, int len) {
1265 this (a, type, null, 0, len);
1266 }
1267
1268 /**
1269 * Constructor for creating a spec externally for batch
1270 * input of content and markup into the document.
1271 *
1272 * @param a the attributes for the element
1273 * @param type the type of the element (StartTagType, EndTagType,
1274 * ContentType)
1275 * @param txt the text for the element
1276 * @param offs the offset into the text >= 0
1277 * @param len the length of the text >= 0
1278 */
1279 public ElementSpec(AttributeSet a, short type, char[] txt,
1280 int offs, int len) {
1281 attr = a;
1282 this .type = type;
1283 this .data = txt;
1284 this .offs = offs;
1285 this .len = len;
1286 this .direction = OriginateDirection;
1287 }
1288
1289 /**
1290 * Sets the element type.
1291 *
1292 * @param type the type of the element (StartTagType, EndTagType,
1293 * ContentType)
1294 */
1295 public void setType(short type) {
1296 this .type = type;
1297 }
1298
1299 /**
1300 * Gets the element type.
1301 *
1302 * @return the type of the element (StartTagType, EndTagType,
1303 * ContentType)
1304 */
1305 public short getType() {
1306 return type;
1307 }
1308
1309 /**
1310 * Sets the direction.
1311 *
1312 * @param direction the direction (JoinPreviousDirection,
1313 * JoinNextDirection)
1314 */
1315 public void setDirection(short direction) {
1316 this .direction = direction;
1317 }
1318
1319 /**
1320 * Gets the direction.
1321 *
1322 * @return the direction (JoinPreviousDirection, JoinNextDirection)
1323 */
1324 public short getDirection() {
1325 return direction;
1326 }
1327
1328 /**
1329 * Gets the element attributes.
1330 *
1331 * @return the attribute set
1332 */
1333 public AttributeSet getAttributes() {
1334 return attr;
1335 }
1336
1337 /**
1338 * Gets the array of characters.
1339 *
1340 * @return the array
1341 */
1342 public char[] getArray() {
1343 return data;
1344 }
1345
1346 /**
1347 * Gets the starting offset.
1348 *
1349 * @return the offset >= 0
1350 */
1351 public int getOffset() {
1352 return offs;
1353 }
1354
1355 /**
1356 * Gets the length.
1357 *
1358 * @return the length >= 0
1359 */
1360 public int getLength() {
1361 return len;
1362 }
1363
1364 /**
1365 * Converts the element to a string.
1366 *
1367 * @return the string
1368 */
1369 public String toString() {
1370 String tlbl = "??";
1371 String plbl = "??";
1372 switch (type) {
1373 case StartTagType:
1374 tlbl = "StartTag";
1375 break;
1376 case ContentType:
1377 tlbl = "Content";
1378 break;
1379 case EndTagType:
1380 tlbl = "EndTag";
1381 break;
1382 }
1383 switch (direction) {
1384 case JoinPreviousDirection:
1385 plbl = "JoinPrevious";
1386 break;
1387 case JoinNextDirection:
1388 plbl = "JoinNext";
1389 break;
1390 case OriginateDirection:
1391 plbl = "Originate";
1392 break;
1393 case JoinFractureDirection:
1394 plbl = "Fracture";
1395 break;
1396 }
1397 return tlbl + ":" + plbl + ":" + getLength();
1398 }
1399
1400 private AttributeSet attr;
1401 private int len;
1402 private short type;
1403 private short direction;
1404
1405 private int offs;
1406 private char[] data;
1407 }
1408
1409 /**
1410 * Class to manage changes to the element
1411 * hierarchy.
1412 * <p>
1413 * <strong>Warning:</strong>
1414 * Serialized objects of this class will not be compatible with
1415 * future Swing releases. The current serialization support is
1416 * appropriate for short term storage or RMI between applications running
1417 * the same version of Swing. As of 1.4, support for long term storage
1418 * of all JavaBeans<sup><font size="-2">TM</font></sup>
1419 * has been added to the <code>java.beans</code> package.
1420 * Please see {@link java.beans.XMLEncoder}.
1421 */
1422 public class ElementBuffer implements Serializable {
1423
1424 /**
1425 * Creates a new ElementBuffer.
1426 *
1427 * @param root the root element
1428 * @since 1.4
1429 */
1430 public ElementBuffer(Element root) {
1431 this .root = root;
1432 changes = new Vector();
1433 path = new Stack();
1434 }
1435
1436 /**
1437 * Gets the root element.
1438 *
1439 * @return the root element
1440 */
1441 public Element getRootElement() {
1442 return root;
1443 }
1444
1445 /**
1446 * Inserts new content.
1447 *
1448 * @param offset the starting offset >= 0
1449 * @param length the length >= 0
1450 * @param data the data to insert
1451 * @param de the event capturing this edit
1452 */
1453 public void insert(int offset, int length, ElementSpec[] data,
1454 DefaultDocumentEvent de) {
1455 if (length == 0) {
1456 // Nothing was inserted, no structure change.
1457 return;
1458 }
1459 insertOp = true;
1460 beginEdits(offset, length);
1461 insertUpdate(data);
1462 endEdits(de);
1463
1464 insertOp = false;
1465 }
1466
1467 void create(int length, ElementSpec[] data,
1468 DefaultDocumentEvent de) {
1469 insertOp = true;
1470 beginEdits(offset, length);
1471
1472 // PENDING(prinz) this needs to be fixed to create a new
1473 // root element as well, but requires changes to the
1474 // DocumentEvent to inform the views that there is a new
1475 // root element.
1476
1477 // Recreate the ending fake element to have the correct offsets.
1478 Element elem = root;
1479 int index = elem.getElementIndex(0);
1480 while (!elem.isLeaf()) {
1481 Element child = elem.getElement(index);
1482 push(elem, index);
1483 elem = child;
1484 index = elem.getElementIndex(0);
1485 }
1486 ElemChanges ec = (ElemChanges) path.peek();
1487 Element child = ec.parent.getElement(ec.index);
1488 ec.added
1489 .addElement(createLeafElement(ec.parent, child
1490 .getAttributes(), getLength(), child
1491 .getEndOffset()));
1492 ec.removed.addElement(child);
1493 while (path.size() > 1) {
1494 pop();
1495 }
1496
1497 int n = data.length;
1498
1499 // Reset the root elements attributes.
1500 AttributeSet newAttrs = null;
1501 if (n > 0 && data[0].getType() == ElementSpec.StartTagType) {
1502 newAttrs = data[0].getAttributes();
1503 }
1504 if (newAttrs == null) {
1505 newAttrs = SimpleAttributeSet.EMPTY;
1506 }
1507 MutableAttributeSet attr = (MutableAttributeSet) root
1508 .getAttributes();
1509 de.addEdit(new AttributeUndoableEdit(root, newAttrs, true));
1510 attr.removeAttributes(attr);
1511 attr.addAttributes(newAttrs);
1512
1513 // fold in the specified subtree
1514 for (int i = 1; i < n; i++) {
1515 insertElement(data[i]);
1516 }
1517
1518 // pop the remaining path
1519 while (path.size() != 0) {
1520 pop();
1521 }
1522
1523 endEdits(de);
1524 insertOp = false;
1525 }
1526
1527 /**
1528 * Removes content.
1529 *
1530 * @param offset the starting offset >= 0
1531 * @param length the length >= 0
1532 * @param de the event capturing this edit
1533 */
1534 public void remove(int offset, int length,
1535 DefaultDocumentEvent de) {
1536 beginEdits(offset, length);
1537 removeUpdate();
1538 endEdits(de);
1539 }
1540
1541 /**
1542 * Changes content.
1543 *
1544 * @param offset the starting offset >= 0
1545 * @param length the length >= 0
1546 * @param de the event capturing this edit
1547 */
1548 public void change(int offset, int length,
1549 DefaultDocumentEvent de) {
1550 beginEdits(offset, length);
1551 changeUpdate();
1552 endEdits(de);
1553 }
1554
1555 /**
1556 * Inserts an update into the document.
1557 *
1558 * @param data the elements to insert
1559 */
1560 protected void insertUpdate(ElementSpec[] data) {
1561 // push the path
1562 Element elem = root;
1563 int index = elem.getElementIndex(offset);
1564 while (!elem.isLeaf()) {
1565 Element child = elem.getElement(index);
1566 push(elem, (child.isLeaf() ? index : index + 1));
1567 elem = child;
1568 index = elem.getElementIndex(offset);
1569 }
1570
1571 // Build a copy of the original path.
1572 insertPath = new ElemChanges[path.size()];
1573 path.copyInto(insertPath);
1574
1575 // Haven't created the fracture yet.
1576 createdFracture = false;
1577
1578 // Insert the first content.
1579 int i;
1580
1581 recreateLeafs = false;
1582 if (data[0].getType() == ElementSpec.ContentType) {
1583 insertFirstContent(data);
1584 pos += data[0].getLength();
1585 i = 1;
1586 } else {
1587 fractureDeepestLeaf(data);
1588 i = 0;
1589 }
1590
1591 // fold in the specified subtree
1592 int n = data.length;
1593 for (; i < n; i++) {
1594 insertElement(data[i]);
1595 }
1596
1597 // Fracture, if we haven't yet.
1598 if (!createdFracture)
1599 fracture(-1);
1600
1601 // pop the remaining path
1602 while (path.size() != 0) {
1603 pop();
1604 }
1605
1606 // Offset the last index if necessary.
1607 if (offsetLastIndex && offsetLastIndexOnReplace) {
1608 insertPath[insertPath.length - 1].index++;
1609 }
1610
1611 // Make sure an edit is going to be created for each of the
1612 // original path items that have a change.
1613 for (int counter = insertPath.length - 1; counter >= 0; counter--) {
1614 ElemChanges change = insertPath[counter];
1615 if (change.parent == fracturedParent)
1616 change.added.addElement(fracturedChild);
1617 if ((change.added.size() > 0 || change.removed.size() > 0)
1618 && !changes.contains(change)) {
1619 // PENDING(sky): Do I need to worry about order here?
1620 changes.addElement(change);
1621 }
1622 }
1623
1624 // An insert at 0 with an initial end implies some elements
1625 // will have no children (the bottomost leaf would have length 0)
1626 // this will find what element need to be removed and remove it.
1627 if (offset == 0 && fracturedParent != null
1628 && data[0].getType() == ElementSpec.EndTagType) {
1629 int counter = 0;
1630 while (counter < data.length
1631 && data[counter].getType() == ElementSpec.EndTagType) {
1632 counter++;
1633 }
1634 ElemChanges change = insertPath[insertPath.length
1635 - counter - 1];
1636 change.removed.insertElementAt(change.parent
1637 .getElement(--change.index), 0);
1638 }
1639 }
1640
1641 /**
1642 * Updates the element structure in response to a removal from the
1643 * associated sequence in the document. Any elements consumed by the
1644 * span of the removal are removed.
1645 */
1646 protected void removeUpdate() {
1647 removeElements(root, offset, offset + length);
1648 }
1649
1650 /**
1651 * Updates the element structure in response to a change in the
1652 * document.
1653 */
1654 protected void changeUpdate() {
1655 boolean didEnd = split(offset, length);
1656 if (!didEnd) {
1657 // need to do the other end
1658 while (path.size() != 0) {
1659 pop();
1660 }
1661 split(offset + length, 0);
1662 }
1663 while (path.size() != 0) {
1664 pop();
1665 }
1666 }
1667
1668 boolean split(int offs, int len) {
1669 boolean splitEnd = false;
1670 // push the path
1671 Element e = root;
1672 int index = e.getElementIndex(offs);
1673 while (!e.isLeaf()) {
1674 push(e, index);
1675 e = e.getElement(index);
1676 index = e.getElementIndex(offs);
1677 }
1678
1679 ElemChanges ec = (ElemChanges) path.peek();
1680 Element child = ec.parent.getElement(ec.index);
1681 // make sure there is something to do... if the
1682 // offset is already at a boundary then there is
1683 // nothing to do.
1684 if (child.getStartOffset() < offs
1685 && offs < child.getEndOffset()) {
1686 // we need to split, now see if the other end is within
1687 // the same parent.
1688 int index0 = ec.index;
1689 int index1 = index0;
1690 if (((offs + len) < ec.parent.getEndOffset())
1691 && (len != 0)) {
1692 // it's a range split in the same parent
1693 index1 = ec.parent.getElementIndex(offs + len);
1694 if (index1 == index0) {
1695 // it's a three-way split
1696 ec.removed.addElement(child);
1697 e = createLeafElement(ec.parent, child
1698 .getAttributes(), child
1699 .getStartOffset(), offs);
1700 ec.added.addElement(e);
1701 e = createLeafElement(ec.parent, child
1702 .getAttributes(), offs, offs + len);
1703 ec.added.addElement(e);
1704 e = createLeafElement(ec.parent, child
1705 .getAttributes(), offs + len, child
1706 .getEndOffset());
1707 ec.added.addElement(e);
1708 return true;
1709 } else {
1710 child = ec.parent.getElement(index1);
1711 if ((offs + len) == child.getStartOffset()) {
1712 // end is already on a boundary
1713 index1 = index0;
1714 }
1715 }
1716 splitEnd = true;
1717 }
1718
1719 // split the first location
1720 pos = offs;
1721 child = ec.parent.getElement(index0);
1722 ec.removed.addElement(child);
1723 e = createLeafElement(ec.parent, child.getAttributes(),
1724 child.getStartOffset(), pos);
1725 ec.added.addElement(e);
1726 e = createLeafElement(ec.parent, child.getAttributes(),
1727 pos, child.getEndOffset());
1728 ec.added.addElement(e);
1729
1730 // pick up things in the middle
1731 for (int i = index0 + 1; i < index1; i++) {
1732 child = ec.parent.getElement(i);
1733 ec.removed.addElement(child);
1734 ec.added.addElement(child);
1735 }
1736
1737 if (index1 != index0) {
1738 child = ec.parent.getElement(index1);
1739 pos = offs + len;
1740 ec.removed.addElement(child);
1741 e = createLeafElement(ec.parent, child
1742 .getAttributes(), child.getStartOffset(),
1743 pos);
1744 ec.added.addElement(e);
1745 e = createLeafElement(ec.parent, child
1746 .getAttributes(), pos, child.getEndOffset());
1747 ec.added.addElement(e);
1748 }
1749 }
1750 return splitEnd;
1751 }
1752
1753 /**
1754 * Creates the UndoableEdit record for the edits made
1755 * in the buffer.
1756 */
1757 void endEdits(DefaultDocumentEvent de) {
1758 int n = changes.size();
1759 for (int i = 0; i < n; i++) {
1760 ElemChanges ec = (ElemChanges) changes.elementAt(i);
1761 Element[] removed = new Element[ec.removed.size()];
1762 ec.removed.copyInto(removed);
1763 Element[] added = new Element[ec.added.size()];
1764 ec.added.copyInto(added);
1765 int index = ec.index;
1766 ((BranchElement) ec.parent).replace(index,
1767 removed.length, added);
1768 ElementEdit ee = new ElementEdit(
1769 (BranchElement) ec.parent, index, removed,
1770 added);
1771 de.addEdit(ee);
1772 }
1773
1774 changes.removeAllElements();
1775 path.removeAllElements();
1776
1777 /*
1778 for (int i = 0; i < n; i++) {
1779 ElemChanges ec = (ElemChanges) changes.elementAt(i);
1780 System.err.print("edited: " + ec.parent + " at: " + ec.index +
1781 " removed " + ec.removed.size());
1782 if (ec.removed.size() > 0) {
1783 int r0 = ((Element) ec.removed.firstElement()).getStartOffset();
1784 int r1 = ((Element) ec.removed.lastElement()).getEndOffset();
1785 System.err.print("[" + r0 + "," + r1 + "]");
1786 }
1787 System.err.print(" added " + ec.added.size());
1788 if (ec.added.size() > 0) {
1789 int p0 = ((Element) ec.added.firstElement()).getStartOffset();
1790 int p1 = ((Element) ec.added.lastElement()).getEndOffset();
1791 System.err.print("[" + p0 + "," + p1 + "]");
1792 }
1793 System.err.println("");
1794 }
1795 */
1796 }
1797
1798 /**
1799 * Initialize the buffer
1800 */
1801 void beginEdits(int offset, int length) {
1802 this .offset = offset;
1803 this .length = length;
1804 this .endOffset = offset + length;
1805 pos = offset;
1806 if (changes == null) {
1807 changes = new Vector();
1808 } else {
1809 changes.removeAllElements();
1810 }
1811 if (path == null) {
1812 path = new Stack();
1813 } else {
1814 path.removeAllElements();
1815 }
1816 fracturedParent = null;
1817 fracturedChild = null;
1818 offsetLastIndex = offsetLastIndexOnReplace = false;
1819 }
1820
1821 /**
1822 * Pushes a new element onto the stack that represents
1823 * the current path.
1824 * @param record Whether or not the push should be
1825 * recorded as an element change or not.
1826 * @param isFracture true if pushing on an element that was created
1827 * as the result of a fracture.
1828 */
1829 void push(Element e, int index, boolean isFracture) {
1830 ElemChanges ec = new ElemChanges(e, index, isFracture);
1831 path.push(ec);
1832 }
1833
1834 void push(Element e, int index) {
1835 push(e, index, false);
1836 }
1837
1838 void pop() {
1839 ElemChanges ec = (ElemChanges) path.peek();
1840 path.pop();
1841 if ((ec.added.size() > 0) || (ec.removed.size() > 0)) {
1842 changes.addElement(ec);
1843 } else if (!path.isEmpty()) {
1844 Element e = ec.parent;
1845 if (e.getElementCount() == 0) {
1846 // if we pushed a branch element that didn't get
1847 // used, make sure its not marked as having been added.
1848 ec = (ElemChanges) path.peek();
1849 ec.added.removeElement(e);
1850 }
1851 }
1852 }
1853
1854 /**
1855 * move the current offset forward by n.
1856 */
1857 void advance(int n) {
1858 pos += n;
1859 }
1860
1861 void insertElement(ElementSpec es) {
1862 ElemChanges ec = (ElemChanges) path.peek();
1863 switch (es.getType()) {
1864 case ElementSpec.StartTagType:
1865 switch (es.getDirection()) {
1866 case ElementSpec.JoinNextDirection:
1867 // Don't create a new element, use the existing one
1868 // at the specified location.
1869 Element parent = ec.parent.getElement(ec.index);
1870
1871 if (parent.isLeaf()) {
1872 // This happens if inserting into a leaf, followed
1873 // by a join next where next sibling is not a leaf.
1874 if ((ec.index + 1) < ec.parent
1875 .getElementCount())
1876 parent = ec.parent.getElement(ec.index + 1);
1877 else
1878 throw new StateInvariantError(
1879 "Join next to leaf");
1880 }
1881 // Not really a fracture, but need to treat it like
1882 // one so that content join next will work correctly.
1883 // We can do this because there will never be a join
1884 // next followed by a join fracture.
1885 push(parent, 0, true);
1886 break;
1887 case ElementSpec.JoinFractureDirection:
1888 if (!createdFracture) {
1889 // Should always be something on the stack!
1890 fracture(path.size() - 1);
1891 }
1892 // If parent isn't a fracture, fracture will be
1893 // fracturedChild.
1894 if (!ec.isFracture) {
1895 push(fracturedChild, 0, true);
1896 } else
1897 // Parent is a fracture, use 1st element.
1898 push(ec.parent.getElement(0), 0, true);
1899 break;
1900 default:
1901 Element belem = createBranchElement(ec.parent, es
1902 .getAttributes());
1903 ec.added.addElement(belem);
1904 push(belem, 0);
1905 break;
1906 }
1907 break;
1908 case ElementSpec.EndTagType:
1909 pop();
1910 break;
1911 case ElementSpec.ContentType:
1912 int len = es.getLength();
1913 if (es.getDirection() != ElementSpec.JoinNextDirection) {
1914 Element leaf = createLeafElement(ec.parent, es
1915 .getAttributes(), pos, pos + len);
1916 ec.added.addElement(leaf);
1917 } else {
1918 // JoinNext on tail is only applicable if last element
1919 // and attributes come from that of first element.
1920 // With a little extra testing it would be possible
1921 // to NOT due this again, as more than likely fracture()
1922 // created this element.
1923 if (!ec.isFracture) {
1924 Element first = null;
1925 if (insertPath != null) {
1926 for (int counter = insertPath.length - 1; counter >= 0; counter--) {
1927 if (insertPath[counter] == ec) {
1928 if (counter != (insertPath.length - 1))
1929 first = ec.parent
1930 .getElement(ec.index);
1931 break;
1932 }
1933 }
1934 }
1935 if (first == null)
1936 first = ec.parent.getElement(ec.index + 1);
1937 Element leaf = createLeafElement(ec.parent,
1938 first.getAttributes(), pos, first
1939 .getEndOffset());
1940 ec.added.addElement(leaf);
1941 ec.removed.addElement(first);
1942 } else {
1943 // Parent was fractured element.
1944 Element first = ec.parent.getElement(0);
1945 Element leaf = createLeafElement(ec.parent,
1946 first.getAttributes(), pos, first
1947 .getEndOffset());
1948 ec.added.addElement(leaf);
1949 ec.removed.addElement(first);
1950 }
1951 }
1952 pos += len;
1953 break;
1954 }
1955 }
1956
1957 /**
1958 * Remove the elements from <code>elem</code> in range
1959 * <code>rmOffs0</code>, <code>rmOffs1</code>. This uses
1960 * <code>canJoin</code> and <code>join</code> to handle joining
1961 * the endpoints of the insertion.
1962 *
1963 * @return true if elem will no longer have any elements.
1964 */
1965 boolean removeElements(Element elem, int rmOffs0, int rmOffs1) {
1966 if (!elem.isLeaf()) {
1967 // update path for changes
1968 int index0 = elem.getElementIndex(rmOffs0);
1969 int index1 = elem.getElementIndex(rmOffs1);
1970 push(elem, index0);
1971 ElemChanges ec = (ElemChanges) path.peek();
1972
1973 // if the range is contained by one element,
1974 // we just forward the request
1975 if (index0 == index1) {
1976 Element child0 = elem.getElement(index0);
1977 if (rmOffs0 <= child0.getStartOffset()
1978 && rmOffs1 >= child0.getEndOffset()) {
1979 // Element totally removed.
1980 ec.removed.addElement(child0);
1981 } else if (removeElements(child0, rmOffs0, rmOffs1)) {
1982 ec.removed.addElement(child0);
1983 }
1984 } else {
1985 // the removal range spans elements. If we can join
1986 // the two endpoints, do it. Otherwise we remove the
1987 // interior and forward to the endpoints.
1988 Element child0 = elem.getElement(index0);
1989 Element child1 = elem.getElement(index1);
1990 boolean containsOffs1 = (rmOffs1 < elem
1991 .getEndOffset());
1992 if (containsOffs1 && canJoin(child0, child1)) {
1993 // remove and join
1994 for (int i = index0; i <= index1; i++) {
1995 ec.removed.addElement(elem.getElement(i));
1996 }
1997 Element e = join(elem, child0, child1, rmOffs0,
1998 rmOffs1);
1999 ec.added.addElement(e);
2000 } else {
2001 // remove interior and forward
2002 int rmIndex0 = index0 + 1;
2003 int rmIndex1 = index1 - 1;
2004 if (child0.getStartOffset() == rmOffs0
2005 || (index0 == 0
2006 && child0.getStartOffset() > rmOffs0 && child0
2007 .getEndOffset() <= rmOffs1)) {
2008 // start element completely consumed
2009 child0 = null;
2010 rmIndex0 = index0;
2011 }
2012 if (!containsOffs1) {
2013 child1 = null;
2014 rmIndex1++;
2015 } else if (child1.getStartOffset() == rmOffs1) {
2016 // end element not touched
2017 child1 = null;
2018 }
2019 if (rmIndex0 <= rmIndex1) {
2020 ec.index = rmIndex0;
2021 }
2022 for (int i = rmIndex0; i <= rmIndex1; i++) {
2023 ec.removed.addElement(elem.getElement(i));
2024 }
2025 if (child0 != null) {
2026 if (removeElements(child0, rmOffs0, rmOffs1)) {
2027 ec.removed.insertElementAt(child0, 0);
2028 ec.index = index0;
2029 }
2030 }
2031 if (child1 != null) {
2032 if (removeElements(child1, rmOffs0, rmOffs1)) {
2033 ec.removed.addElement(child1);
2034 }
2035 }
2036 }
2037 }
2038
2039 // publish changes
2040 pop();
2041
2042 // Return true if we no longer have any children.
2043 if (elem.getElementCount() == (ec.removed.size() - ec.added
2044 .size())) {
2045 return true;
2046 }
2047 }
2048 return false;
2049 }
2050
2051 /**
2052 * Can the two given elements be coelesced together
2053 * into one element?
2054 */
2055 boolean canJoin(Element e0, Element e1) {
2056 if ((e0 == null) || (e1 == null)) {
2057 return false;
2058 }
2059 // Don't join a leaf to a branch.
2060 boolean leaf0 = e0.isLeaf();
2061 boolean leaf1 = e1.isLeaf();
2062 if (leaf0 != leaf1) {
2063 return false;
2064 }
2065 if (leaf0) {
2066 // Only join leaves if the attributes match, otherwise
2067 // style information will be lost.
2068 return e0.getAttributes().isEqual(e1.getAttributes());
2069 }
2070 // Only join non-leafs if the names are equal. This may result
2071 // in loss of style information, but this is typically acceptable
2072 // for non-leafs.
2073 String name0 = e0.getName();
2074 String name1 = e1.getName();
2075 if (name0 != null) {
2076 return name0.equals(name1);
2077 }
2078 if (name1 != null) {
2079 return name1.equals(name0);
2080 }
2081 // Both names null, treat as equal.
2082 return true;
2083 }
2084
2085 /**
2086 * Joins the two elements carving out a hole for the
2087 * given removed range.
2088 */
2089 Element join(Element p, Element left, Element right,
2090 int rmOffs0, int rmOffs1) {
2091 if (left.isLeaf() && right.isLeaf()) {
2092 return createLeafElement(p, left.getAttributes(), left
2093 .getStartOffset(), right.getEndOffset());
2094 } else if ((!left.isLeaf()) && (!right.isLeaf())) {
2095 // join two branch elements. This copies the children before
2096 // the removal range on the left element, and after the removal
2097 // range on the right element. The two elements on the edge
2098 // are joined if possible and needed.
2099 Element to = createBranchElement(p, left
2100 .getAttributes());
2101 int ljIndex = left.getElementIndex(rmOffs0);
2102 int rjIndex = right.getElementIndex(rmOffs1);
2103 Element lj = left.getElement(ljIndex);
2104 if (lj.getStartOffset() >= rmOffs0) {
2105 lj = null;
2106 }
2107 Element rj = right.getElement(rjIndex);
2108 if (rj.getStartOffset() == rmOffs1) {
2109 rj = null;
2110 }
2111 Vector children = new Vector();
2112
2113 // transfer the left
2114 for (int i = 0; i < ljIndex; i++) {
2115 children.addElement(clone(to, left.getElement(i)));
2116 }
2117
2118 // transfer the join/middle
2119 if (canJoin(lj, rj)) {
2120 Element e = join(to, lj, rj, rmOffs0, rmOffs1);
2121 children.addElement(e);
2122 } else {
2123 if (lj != null) {
2124 children.addElement(cloneAsNecessary(to, lj,
2125 rmOffs0, rmOffs1));
2126 }
2127 if (rj != null) {
2128 children.addElement(cloneAsNecessary(to, rj,
2129 rmOffs0, rmOffs1));
2130 }
2131 }
2132
2133 // transfer the right
2134 int n = right.getElementCount();
2135 for (int i = (rj == null) ? rjIndex : rjIndex + 1; i < n; i++) {
2136 children.addElement(clone(to, right.getElement(i)));
2137 }
2138
2139 // install the children
2140 Element[] c = new Element[children.size()];
2141 children.copyInto(c);
2142 ((BranchElement) to).replace(0, 0, c);
2143 return to;
2144 } else {
2145 throw new StateInvariantError(
2146 "No support to join leaf element with non-leaf element");
2147 }
2148 }
2149
2150 /**
2151 * Creates a copy of this element, with a different
2152 * parent.
2153 *
2154 * @param parent the parent element
2155 * @param clonee the element to be cloned
2156 * @return the copy
2157 */
2158 public Element clone(Element parent, Element clonee) {
2159 if (clonee.isLeaf()) {
2160 return createLeafElement(parent,
2161 clonee.getAttributes(),
2162 clonee.getStartOffset(), clonee.getEndOffset());
2163 }
2164 Element e = createBranchElement(parent, clonee
2165 .getAttributes());
2166 int n = clonee.getElementCount();
2167 Element[] children = new Element[n];
2168 for (int i = 0; i < n; i++) {
2169 children[i] = clone(e, clonee.getElement(i));
2170 }
2171 ((BranchElement) e).replace(0, 0, children);
2172 return e;
2173 }
2174
2175 /**
2176 * Creates a copy of this element, with a different
2177 * parent. Children of this element included in the
2178 * removal range will be discarded.
2179 */
2180 Element cloneAsNecessary(Element parent, Element clonee,
2181 int rmOffs0, int rmOffs1) {
2182 if (clonee.isLeaf()) {
2183 return createLeafElement(parent,
2184 clonee.getAttributes(),
2185 clonee.getStartOffset(), clonee.getEndOffset());
2186 }
2187 Element e = createBranchElement(parent, clonee
2188 .getAttributes());
2189 int n = clonee.getElementCount();
2190 ArrayList childrenList = new ArrayList(n);
2191 for (int i = 0; i < n; i++) {
2192 Element elem = clonee.getElement(i);
2193 if (elem.getStartOffset() < rmOffs0
2194 || elem.getEndOffset() > rmOffs1) {
2195 childrenList.add(cloneAsNecessary(e, elem, rmOffs0,
2196 rmOffs1));
2197 }
2198 }
2199 Element[] children = new Element[childrenList.size()];
2200 children = (Element[]) childrenList.toArray(children);
2201 ((BranchElement) e).replace(0, 0, children);
2202 return e;
2203 }
2204
2205 /**
2206 * Determines if a fracture needs to be performed. A fracture
2207 * can be thought of as moving the right part of a tree to a
2208 * new location, where the right part is determined by what has
2209 * been inserted. <code>depth</code> is used to indicate a
2210 * JoinToFracture is needed to an element at a depth
2211 * of <code>depth</code>. Where the root is 0, 1 is the children
2212 * of the root...
2213 * <p>This will invoke <code>fractureFrom</code> if it is determined
2214 * a fracture needs to happen.
2215 */
2216 void fracture(int depth) {
2217 int cLength = insertPath.length;
2218 int lastIndex = -1;
2219 boolean needRecreate = recreateLeafs;
2220 ElemChanges lastChange = insertPath[cLength - 1];
2221 // Use childAltered to determine when a child has been altered,
2222 // that is the point of insertion is less than the element count.
2223 boolean childAltered = ((lastChange.index + 1) < lastChange.parent
2224 .getElementCount());
2225 int deepestAlteredIndex = (needRecreate) ? cLength : -1;
2226 int lastAlteredIndex = cLength - 1;
2227
2228 createdFracture = true;
2229 // Determine where to start recreating from.
2230 // Start at - 2, as first one is indicated by recreateLeafs and
2231 // childAltered.
2232 for (int counter = cLength - 2; counter >= 0; counter--) {
2233 ElemChanges change = insertPath[counter];
2234 if (change.added.size() > 0 || counter == depth) {
2235 lastIndex = counter;
2236 if (!needRecreate && childAltered) {
2237 needRecreate = true;
2238 if (deepestAlteredIndex == -1)
2239 deepestAlteredIndex = lastAlteredIndex + 1;
2240 }
2241 }
2242 if (!childAltered
2243 && change.index < change.parent
2244 .getElementCount()) {
2245 childAltered = true;
2246 lastAlteredIndex = counter;
2247 }
2248 }
2249 if (needRecreate) {
2250 // Recreate all children to right of parent starting
2251 // at lastIndex.
2252 if (lastIndex == -1)
2253 lastIndex = cLength - 1;
2254 fractureFrom(insertPath, lastIndex, deepestAlteredIndex);
2255 }
2256 }
2257
2258 /**
2259 * Recreates the elements to the right of the insertion point.
2260 * This starts at <code>startIndex</code> in <code>changed</code>,
2261 * and calls duplicate to duplicate existing elements.
2262 * This will also duplicate the elements along the insertion
2263 * point, until a depth of <code>endFractureIndex</code> is
2264 * reached, at which point only the elements to the right of
2265 * the insertion point are duplicated.
2266 */
2267 void fractureFrom(ElemChanges[] changed, int startIndex,
2268 int endFractureIndex) {
2269 // Recreate the element representing the inserted index.
2270 ElemChanges change = changed[startIndex];
2271 Element child;
2272 Element newChild;
2273 int changeLength = changed.length;
2274
2275 if ((startIndex + 1) == changeLength)
2276 child = change.parent.getElement(change.index);
2277 else
2278 child = change.parent.getElement(change.index - 1);
2279 if (child.isLeaf()) {
2280 newChild = createLeafElement(change.parent, child
2281 .getAttributes(), Math.max(endOffset, child
2282 .getStartOffset()), child.getEndOffset());
2283 } else {
2284 newChild = createBranchElement(change.parent, child
2285 .getAttributes());
2286 }
2287 fracturedParent = change.parent;
2288 fracturedChild = newChild;
2289
2290 // Recreate all the elements to the right of the
2291 // insertion point.
2292 Element parent = newChild;
2293
2294 while (++startIndex < endFractureIndex) {
2295 boolean isEnd = ((startIndex + 1) == endFractureIndex);
2296 boolean isEndLeaf = ((startIndex + 1) == changeLength);
2297
2298 // Create the newChild, a duplicate of the elment at
2299 // index. This isn't done if isEnd and offsetLastIndex are true
2300 // indicating a join previous was done.
2301 change = changed[startIndex];
2302
2303 // Determine the child to duplicate, won't have to duplicate
2304 // if at end of fracture, or offseting index.
2305 if (isEnd) {
2306 if (offsetLastIndex || !isEndLeaf)
2307 child = null;
2308 else
2309 child = change.parent.getElement(change.index);
2310 } else {
2311 child = change.parent.getElement(change.index - 1);
2312 }
2313 // Duplicate it.
2314 if (child != null) {
2315 if (child.isLeaf()) {
2316 newChild = createLeafElement(parent, child
2317 .getAttributes(), Math.max(endOffset,
2318 child.getStartOffset()), child
2319 .getEndOffset());
2320 } else {
2321 newChild = createBranchElement(parent, child
2322 .getAttributes());
2323 }
2324 } else
2325 newChild = null;
2326
2327 // Recreate the remaining children (there may be none).
2328 int kidsToMove = change.parent.getElementCount()
2329 - change.index;
2330 Element[] kids;
2331 int moveStartIndex;
2332 int kidStartIndex = 1;
2333
2334 if (newChild == null) {
2335 // Last part of fracture.
2336 if (isEndLeaf) {
2337 kidsToMove--;
2338 moveStartIndex = change.index + 1;
2339 } else {
2340 moveStartIndex = change.index;
2341 }
2342 kidStartIndex = 0;
2343 kids = new Element[kidsToMove];
2344 } else {
2345 if (!isEnd) {
2346 // Branch.
2347 kidsToMove++;
2348 moveStartIndex = change.index;
2349 } else {
2350 // Last leaf, need to recreate part of it.
2351 moveStartIndex = change.index + 1;
2352 }
2353 kids = new Element[kidsToMove];
2354 kids[0] = newChild;
2355 }
2356
2357 for (int counter = kidStartIndex; counter < kidsToMove; counter++) {
2358 Element toMove = change.parent
2359 .getElement(moveStartIndex++);
2360 kids[counter] = recreateFracturedElement(parent,
2361 toMove);
2362 change.removed.addElement(toMove);
2363 }
2364 ((BranchElement) parent).replace(0, 0, kids);
2365 parent = newChild;
2366 }
2367 }
2368
2369 /**
2370 * Recreates <code>toDuplicate</code>. This is called when an
2371 * element needs to be created as the result of an insertion. This
2372 * will recurse and create all the children. This is similiar to
2373 * <code>clone</code>, but deteremines the offsets differently.
2374 */
2375 Element recreateFracturedElement(Element parent,
2376 Element toDuplicate) {
2377 if (toDuplicate.isLeaf()) {
2378 return createLeafElement(parent, toDuplicate
2379 .getAttributes(), Math.max(toDuplicate
2380 .getStartOffset(), endOffset), toDuplicate
2381 .getEndOffset());
2382 }
2383 // Not a leaf
2384 Element newParent = createBranchElement(parent, toDuplicate
2385 .getAttributes());
2386 int childCount = toDuplicate.getElementCount();
2387 Element[] newKids = new Element[childCount];
2388 for (int counter = 0; counter < childCount; counter++) {
2389 newKids[counter] = recreateFracturedElement(newParent,
2390 toDuplicate.getElement(counter));
2391 }
2392 ((BranchElement) newParent).replace(0, 0, newKids);
2393 return newParent;
2394 }
2395
2396 /**
2397 * Splits the bottommost leaf in <code>path</code>.
2398 * This is called from insert when the first element is NOT content.
2399 */
2400 void fractureDeepestLeaf(ElementSpec[] specs) {
2401 // Split the bottommost leaf. It will be recreated elsewhere.
2402 ElemChanges ec = (ElemChanges) path.peek();
2403 Element child = ec.parent.getElement(ec.index);
2404 // Inserts at offset 0 do not need to recreate child (it would
2405 // have a length of 0!).
2406 if (offset != 0) {
2407 Element newChild = createLeafElement(ec.parent, child
2408 .getAttributes(), child.getStartOffset(),
2409 offset);
2410
2411 ec.added.addElement(newChild);
2412 }
2413 ec.removed.addElement(child);
2414 if (child.getEndOffset() != endOffset)
2415 recreateLeafs = true;
2416 else
2417 offsetLastIndex = true;
2418 }
2419
2420 /**
2421 * Inserts the first content. This needs to be separate to handle
2422 * joining.
2423 */
2424 void insertFirstContent(ElementSpec[] specs) {
2425 ElementSpec firstSpec = specs[0];
2426 ElemChanges ec = (ElemChanges) path.peek();
2427 Element child = ec.parent.getElement(ec.index);
2428 int firstEndOffset = offset + firstSpec.getLength();
2429 boolean isOnlyContent = (specs.length == 1);
2430
2431 switch (firstSpec.getDirection()) {
2432 case ElementSpec.JoinPreviousDirection:
2433 if (child.getEndOffset() != firstEndOffset
2434 && !isOnlyContent) {
2435 // Create the left split part containing new content.
2436 Element newE = createLeafElement(ec.parent, child
2437 .getAttributes(), child.getStartOffset(),
2438 firstEndOffset);
2439 ec.added.addElement(newE);
2440 ec.removed.addElement(child);
2441 // Remainder will be created later.
2442 if (child.getEndOffset() != endOffset)
2443 recreateLeafs = true;
2444 else
2445 offsetLastIndex = true;
2446 } else {
2447 offsetLastIndex = true;
2448 offsetLastIndexOnReplace = true;
2449 }
2450 // else Inserted at end, and is total length.
2451 // Update index incase something added/removed.
2452 break;
2453 case ElementSpec.JoinNextDirection:
2454 if (offset != 0) {
2455 // Recreate the first element, its offset will have
2456 // changed.
2457 Element newE = createLeafElement(ec.parent, child
2458 .getAttributes(), child.getStartOffset(),
2459 offset);
2460 ec.added.addElement(newE);
2461 // Recreate the second, merge part. We do no checking
2462 // to see if JoinNextDirection is valid here!
2463 Element nextChild = ec.parent
2464 .getElement(ec.index + 1);
2465 if (isOnlyContent)
2466 newE = createLeafElement(ec.parent, nextChild
2467 .getAttributes(), offset, nextChild
2468 .getEndOffset());
2469 else
2470 newE = createLeafElement(ec.parent, nextChild
2471 .getAttributes(), offset,
2472 firstEndOffset);
2473 ec.added.addElement(newE);
2474 ec.removed.addElement(child);
2475 ec.removed.addElement(nextChild);
2476 }
2477 // else nothin to do.
2478 // PENDING: if !isOnlyContent could raise here!
2479 break;
2480 default:
2481 // Inserted into middle, need to recreate split left
2482 // new content, and split right.
2483 if (child.getStartOffset() != offset) {
2484 Element newE = createLeafElement(ec.parent, child
2485 .getAttributes(), child.getStartOffset(),
2486 offset);
2487 ec.added.addElement(newE);
2488 }
2489 ec.removed.addElement(child);
2490 // new content
2491 Element newE = createLeafElement(ec.parent, firstSpec
2492 .getAttributes(), offset, firstEndOffset);
2493 ec.added.addElement(newE);
2494 if (child.getEndOffset() != endOffset) {
2495 // Signals need to recreate right split later.
2496 recreateLeafs = true;
2497 } else {
2498 offsetLastIndex = true;
2499 }
2500 break;
2501 }
2502 }
2503
2504 Element root;
2505 transient int pos; // current position
2506 transient int offset;
2507 transient int length;
2508 transient int endOffset;
2509 transient Vector changes; // Vector<ElemChanges>
2510 transient Stack path; // Stack<ElemChanges>
2511 transient boolean insertOp;
2512
2513 transient boolean recreateLeafs; // For insert.
2514
2515 /** For insert, path to inserted elements. */
2516 transient ElemChanges[] insertPath;
2517 /** Only for insert, set to true when the fracture has been created. */
2518 transient boolean createdFracture;
2519 /** Parent that contains the fractured child. */
2520 transient Element fracturedParent;
2521 /** Fractured child. */
2522 transient Element fracturedChild;
2523 /** Used to indicate when fracturing that the last leaf should be
2524 * skipped. */
2525 transient boolean offsetLastIndex;
2526 /** Used to indicate that the parent of the deepest leaf should
2527 * offset the index by 1 when adding/removing elements in an
2528 * insert. */
2529 transient boolean offsetLastIndexOnReplace;
2530
2531 /*
2532 * Internal record used to hold element change specifications
2533 */
2534 class ElemChanges {
2535
2536 ElemChanges(Element parent, int index, boolean isFracture) {
2537 this .parent = parent;
2538 this .index = index;
2539 this .isFracture = isFracture;
2540 added = new Vector();
2541 removed = new Vector();
2542 }
2543
2544 public String toString() {
2545 return "added: " + added + "\nremoved: " + removed
2546 + "\n";
2547 }
2548
2549 Element parent;
2550 int index;
2551 Vector added;
2552 Vector removed;
2553 boolean isFracture;
2554 }
2555
2556 }
2557
2558 /**
2559 * An UndoableEdit used to remember AttributeSet changes to an
2560 * Element.
2561 */
2562 public static class AttributeUndoableEdit extends
2563 AbstractUndoableEdit {
2564 public AttributeUndoableEdit(Element element,
2565 AttributeSet newAttributes, boolean isReplacing) {
2566 super ();
2567 this .element = element;
2568 this .newAttributes = newAttributes;
2569 this .isReplacing = isReplacing;
2570 // If not replacing, it may be more efficient to only copy the
2571 // changed values...
2572 copy = element.getAttributes().copyAttributes();
2573 }
2574
2575 /**
2576 * Redoes a change.
2577 *
2578 * @exception CannotRedoException if the change cannot be redone
2579 */
2580 public void redo() throws CannotRedoException {
2581 super .redo();
2582 MutableAttributeSet as = (MutableAttributeSet) element
2583 .getAttributes();
2584 if (isReplacing)
2585 as.removeAttributes(as);
2586 as.addAttributes(newAttributes);
2587 }
2588
2589 /**
2590 * Undoes a change.
2591 *
2592 * @exception CannotUndoException if the change cannot be undone
2593 */
2594 public void undo() throws CannotUndoException {
2595 super .undo();
2596 MutableAttributeSet as = (MutableAttributeSet) element
2597 .getAttributes();
2598 as.removeAttributes(as);
2599 as.addAttributes(copy);
2600 }
2601
2602 // AttributeSet containing additional entries, must be non-mutable!
2603 protected AttributeSet newAttributes;
2604 // Copy of the AttributeSet the Element contained.
2605 protected AttributeSet copy;
2606 // true if all the attributes in the element were removed first.
2607 protected boolean isReplacing;
2608 // Efected Element.
2609 protected Element element;
2610 }
2611
2612 /**
2613 * UndoableEdit for changing the resolve parent of an Element.
2614 */
2615 static class StyleChangeUndoableEdit extends AbstractUndoableEdit {
2616 public StyleChangeUndoableEdit(AbstractElement element,
2617 Style newStyle) {
2618 super ();
2619 this .element = element;
2620 this .newStyle = newStyle;
2621 oldStyle = element.getResolveParent();
2622 }
2623
2624 /**
2625 * Redoes a change.
2626 *
2627 * @exception CannotRedoException if the change cannot be redone
2628 */
2629 public void redo() throws CannotRedoException {
2630 super .redo();
2631 element.setResolveParent(newStyle);
2632 }
2633
2634 /**
2635 * Undoes a change.
2636 *
2637 * @exception CannotUndoException if the change cannot be undone
2638 */
2639 public void undo() throws CannotUndoException {
2640 super .undo();
2641 element.setResolveParent(oldStyle);
2642 }
2643
2644 /** Element to change resolve parent of. */
2645 protected AbstractElement element;
2646 /** New style. */
2647 protected Style newStyle;
2648 /** Old style, before setting newStyle. */
2649 protected AttributeSet oldStyle;
2650 }
2651
2652 /**
2653 * Base class for style change handlers with support for stale objects detection.
2654 */
2655 abstract static class AbstractChangeHandler implements
2656 ChangeListener {
2657
2658 /* This has an implicit reference to the handler object. */
2659 private class DocReference extends
2660 WeakReference<DefaultStyledDocument> {
2661
2662 DocReference(DefaultStyledDocument d, ReferenceQueue q) {
2663 super (d, q);
2664 }
2665
2666 /**
2667 * Return a reference to the style change handler object.
2668 */
2669 ChangeListener getListener() {
2670 return AbstractChangeHandler.this ;
2671 }
2672 }
2673
2674 /** Class-specific reference queues. */
2675 private final static Map<Class, ReferenceQueue> queueMap = new HashMap<Class, ReferenceQueue>();
2676
2677 /** A weak reference to the document object. */
2678 private DocReference doc;
2679
2680 AbstractChangeHandler(DefaultStyledDocument d) {
2681 Class c = getClass();
2682 ReferenceQueue q;
2683 synchronized (queueMap) {
2684 q = queueMap.get(c);
2685 if (q == null) {
2686 q = new ReferenceQueue();
2687 queueMap.put(c, q);
2688 }
2689 }
2690 doc = new DocReference(d, q);
2691 }
2692
2693 /**
2694 * Return a list of stale change listeners.
2695 *
2696 * A change listener becomes "stale" when its document is cleaned by GC.
2697 */
2698 static List<ChangeListener> getStaleListeners(ChangeListener l) {
2699 List<ChangeListener> staleListeners = new ArrayList<ChangeListener>();
2700 ReferenceQueue q = queueMap.get(l.getClass());
2701
2702 if (q != null) {
2703 DocReference r;
2704 synchronized (q) {
2705 while ((r = (DocReference) q.poll()) != null) {
2706 staleListeners.add(r.getListener());
2707 }
2708 }
2709 }
2710
2711 return staleListeners;
2712 }
2713
2714 /**
2715 * The ChangeListener wrapper which guards against dead documents.
2716 */
2717 public void stateChanged(ChangeEvent e) {
2718 DefaultStyledDocument d = doc.get();
2719 if (d != null) {
2720 fireStateChanged(d, e);
2721 }
2722 }
2723
2724 /** Run the actual class-specific stateChanged() method. */
2725 abstract void fireStateChanged(DefaultStyledDocument d,
2726 ChangeEvent e);
2727 }
2728
2729 /**
2730 * Added to all the Styles. When instances of this receive a
2731 * stateChanged method, styleChanged is invoked.
2732 */
2733 static class StyleChangeHandler extends AbstractChangeHandler {
2734
2735 StyleChangeHandler(DefaultStyledDocument d) {
2736 super (d);
2737 }
2738
2739 void fireStateChanged(DefaultStyledDocument d, ChangeEvent e) {
2740 Object source = e.getSource();
2741 if (source instanceof Style) {
2742 d.styleChanged((Style) source);
2743 } else {
2744 d.styleChanged(null);
2745 }
2746 }
2747 }
2748
2749 /**
2750 * Added to the StyleContext. When the StyleContext changes, this invokes
2751 * <code>updateStylesListeningTo</code>.
2752 */
2753 static class StyleContextChangeHandler extends
2754 AbstractChangeHandler {
2755
2756 StyleContextChangeHandler(DefaultStyledDocument d) {
2757 super (d);
2758 }
2759
2760 void fireStateChanged(DefaultStyledDocument d, ChangeEvent e) {
2761 d.updateStylesListeningTo();
2762 }
2763 }
2764
2765 /**
2766 * When run this creates a change event for the complete document
2767 * and fires it.
2768 */
2769 class ChangeUpdateRunnable implements Runnable {
2770 boolean isPending = false;
2771
2772 public void run() {
2773 synchronized (this ) {
2774 isPending = false;
2775 }
2776
2777 try {
2778 writeLock();
2779 DefaultDocumentEvent dde = new DefaultDocumentEvent(0,
2780 getLength(), DocumentEvent.EventType.CHANGE);
2781 dde.end();
2782 fireChangedUpdate(dde);
2783 } finally {
2784 writeUnlock();
2785 }
2786 }
2787 }
2788 }
|