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