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