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