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 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 Vector v = (Vector)listeningStyles.clone(); 1053 listeningStyles.removeAllElements(); 1054 List<ChangeListener> staleListeners = 1055 AbstractChangeHandler.getStaleListeners(styleChangeListener); 1056 while (styleNames.hasMoreElements()) { 1057 String name = (String)styleNames.nextElement(); 1058 Style aStyle = styles.getStyle(name); 1059 int index = v.indexOf(aStyle); 1060 listeningStyles.addElement(aStyle); 1061 if (index == -1) { 1062 for (ChangeListener l: staleListeners) { 1063 aStyle.removeChangeListener(l); 1064 } 1065 aStyle.addChangeListener(styleChangeListener); 1066 } 1067 else { 1068 v.removeElementAt(index); 1069 } 1070 } 1071 for (int counter = v.size() - 1; counter >= 0; counter--) { 1072 Style aStyle = (Style)v.elementAt(counter); 1073 aStyle.removeChangeListener(styleChangeListener); 1074 } 1075 if (listeningStyles.size() == 0) { 1076 styleChangeListener = null; 1077 } 1078 } 1079 } 1080 } 1081 1082 private void readObject(ObjectInputStream s) 1083 throws ClassNotFoundException, IOException { 1084 listeningStyles = new Vector<Style>(); 1085 s.defaultReadObject(); 1086 // Reinstall style listeners. 1087 if (styleContextChangeListener == null && 1088 listenerList.getListenerCount(DocumentListener.class) > 0) { 1089 styleContextChangeListener = createStyleContextChangeListener(); 1090 if (styleContextChangeListener != null) { 1091 StyleContext styles = (StyleContext)getAttributeContext(); 1092 styles.addChangeListener(styleContextChangeListener); 1093 } 1094 updateStylesListeningTo(); 1095 } 1096 } 1097 1098 // --- member variables ----------------------------------------------------------- 1099 1100 /** 1101 * The default size of the initial content buffer. 1102 */ 1103 public static final int BUFFER_SIZE_DEFAULT = 4096; 1104 1105 protected ElementBuffer buffer; 1106 1107 /** Styles listening to. */ 1108 private transient Vector<Style> listeningStyles; 1109 1110 /** Listens to Styles. */ 1111 private transient ChangeListener styleChangeListener; 1112 1113 /** Listens to Styles. */ 1114 private transient ChangeListener styleContextChangeListener; 1115 1116 /** Run to create a change event for the document */ 1117 private transient ChangeUpdateRunnable updateRunnable; 1118 1119 /** 1120 * Default root element for a document... maps out the 1121 * paragraphs/lines contained. 1122 * <p> 1123 * <strong>Warning:</strong> 1124 * Serialized objects of this class will not be compatible with 1125 * future Swing releases. The current serialization support is 1126 * appropriate for short term storage or RMI between applications running 1127 * the same version of Swing. As of 1.4, support for long term storage 1128 * of all JavaBeans™ 1129 * has been added to the <code>java.beans</code> package. 1130 * Please see {@link java.beans.XMLEncoder}. 1131 */ 1132 @SuppressWarnings("serial") // Same-version serialization only 1133 protected class SectionElement extends BranchElement { 1134 1135 /** 1136 * Creates a new SectionElement. 1137 */ 1138 public SectionElement() { 1139 super(null, null); 1140 } 1141 1142 /** 1143 * Gets the name of the element. 1144 * 1145 * @return the name 1146 */ 1147 public String getName() { 1148 return SectionElementName; 1149 } 1150 } 1151 1152 /** 1153 * Specification for building elements. 1154 * <p> 1155 * <strong>Warning:</strong> 1156 * Serialized objects of this class will not be compatible with 1157 * future Swing releases. The current serialization support is 1158 * appropriate for short term storage or RMI between applications running 1159 * the same version of Swing. As of 1.4, support for long term storage 1160 * of all JavaBeans™ 1161 * has been added to the <code>java.beans</code> package. 1162 * Please see {@link java.beans.XMLEncoder}. 1163 */ 1164 @SuppressWarnings("serial") // Same-version serialization only 1165 public static class ElementSpec { 1166 1167 /** 1168 * A possible value for getType. This specifies 1169 * that this record type is a start tag and 1170 * represents markup that specifies the start 1171 * of an element. 1172 */ 1173 public static final short StartTagType = 1; 1174 1175 /** 1176 * A possible value for getType. This specifies 1177 * that this record type is a end tag and 1178 * represents markup that specifies the end 1179 * of an element. 1180 */ 1181 public static final short EndTagType = 2; 1182 1183 /** 1184 * A possible value for getType. This specifies 1185 * that this record type represents content. 1186 */ 1187 public static final short ContentType = 3; 1188 1189 /** 1190 * A possible value for getDirection. This specifies 1191 * that the data associated with this record should 1192 * be joined to what precedes it. 1193 */ 1194 public static final short JoinPreviousDirection = 4; 1195 1196 /** 1197 * A possible value for getDirection. This specifies 1198 * that the data associated with this record should 1199 * be joined to what follows it. 1200 */ 1201 public static final short JoinNextDirection = 5; 1202 1203 /** 1204 * A possible value for getDirection. This specifies 1205 * that the data associated with this record should 1206 * be used to originate a new element. This would be 1207 * the normal value. 1208 */ 1209 public static final short OriginateDirection = 6; 1210 1211 /** 1212 * A possible value for getDirection. This specifies 1213 * that the data associated with this record should 1214 * be joined to the fractured element. 1215 */ 1216 public static final short JoinFractureDirection = 7; 1217 1218 1219 /** 1220 * Constructor useful for markup when the markup will not 1221 * be stored in the document. 1222 * 1223 * @param a the attributes for the element 1224 * @param type the type of the element (StartTagType, EndTagType, 1225 * ContentType) 1226 */ 1227 public ElementSpec(AttributeSet a, short type) { 1228 this(a, type, null, 0, 0); 1229 } 1230 1231 /** 1232 * Constructor for parsing inside the document when 1233 * the data has already been added, but len information 1234 * is needed. 1235 * 1236 * @param a the attributes for the element 1237 * @param type the type of the element (StartTagType, EndTagType, 1238 * ContentType) 1239 * @param len the length >= 0 1240 */ 1241 public ElementSpec(AttributeSet a, short type, int len) { 1242 this(a, type, null, 0, len); 1243 } 1244 1245 /** 1246 * Constructor for creating a spec externally for batch 1247 * input of content and markup into the document. 1248 * 1249 * @param a the attributes for the element 1250 * @param type the type of the element (StartTagType, EndTagType, 1251 * ContentType) 1252 * @param txt the text for the element 1253 * @param offs the offset into the text >= 0 1254 * @param len the length of the text >= 0 1255 */ 1256 public ElementSpec(AttributeSet a, short type, char[] txt, 1257 int offs, int len) { 1258 attr = a; 1259 this.type = type; 1260 this.data = txt; 1261 this.offs = offs; 1262 this.len = len; 1263 this.direction = OriginateDirection; 1264 } 1265 1266 /** 1267 * Sets the element type. 1268 * 1269 * @param type the type of the element (StartTagType, EndTagType, 1270 * ContentType) 1271 */ 1272 public void setType(short type) { 1273 this.type = type; 1274 } 1275 1276 /** 1277 * Gets the element type. 1278 * 1279 * @return the type of the element (StartTagType, EndTagType, 1280 * ContentType) 1281 */ 1282 public short getType() { 1283 return type; 1284 } 1285 1286 /** 1287 * Sets the direction. 1288 * 1289 * @param direction the direction (JoinPreviousDirection, 1290 * JoinNextDirection) 1291 */ 1292 public void setDirection(short direction) { 1293 this.direction = direction; 1294 } 1295 1296 /** 1297 * Gets the direction. 1298 * 1299 * @return the direction (JoinPreviousDirection, JoinNextDirection) 1300 */ 1301 public short getDirection() { 1302 return direction; 1303 } 1304 1305 /** 1306 * Gets the element attributes. 1307 * 1308 * @return the attribute set 1309 */ 1310 public AttributeSet getAttributes() { 1311 return attr; 1312 } 1313 1314 /** 1315 * Gets the array of characters. 1316 * 1317 * @return the array 1318 */ 1319 public char[] getArray() { 1320 return data; 1321 } 1322 1323 1324 /** 1325 * Gets the starting offset. 1326 * 1327 * @return the offset >= 0 1328 */ 1329 public int getOffset() { 1330 return offs; 1331 } 1332 1333 /** 1334 * Gets the length. 1335 * 1336 * @return the length >= 0 1337 */ 1338 public int getLength() { 1339 return len; 1340 } 1341 1342 /** 1343 * Converts the element to a string. 1344 * 1345 * @return the string 1346 */ 1347 public String toString() { 1348 String tlbl = "??"; 1349 String plbl = "??"; 1350 switch(type) { 1351 case StartTagType: 1352 tlbl = "StartTag"; 1353 break; 1354 case ContentType: 1355 tlbl = "Content"; 1356 break; 1357 case EndTagType: 1358 tlbl = "EndTag"; 1359 break; 1360 } 1361 switch(direction) { 1362 case JoinPreviousDirection: 1363 plbl = "JoinPrevious"; 1364 break; 1365 case JoinNextDirection: 1366 plbl = "JoinNext"; 1367 break; 1368 case OriginateDirection: 1369 plbl = "Originate"; 1370 break; 1371 case JoinFractureDirection: 1372 plbl = "Fracture"; 1373 break; 1374 } 1375 return tlbl + ":" + plbl + ":" + getLength(); 1376 } 1377 1378 private AttributeSet attr; 1379 private int len; 1380 private short type; 1381 private short direction; 1382 1383 private int offs; 1384 private char[] data; 1385 } 1386 1387 /** 1388 * Class to manage changes to the element 1389 * hierarchy. 1390 * <p> 1391 * <strong>Warning:</strong> 1392 * Serialized objects of this class will not be compatible with 1393 * future Swing releases. The current serialization support is 1394 * appropriate for short term storage or RMI between applications running 1395 * the same version of Swing. As of 1.4, support for long term storage 1396 * of all JavaBeans™ 1397 * has been added to the <code>java.beans</code> package. 1398 * Please see {@link java.beans.XMLEncoder}. 1399 */ 1400 @SuppressWarnings("serial") // Same-version serialization only 1401 public class ElementBuffer implements Serializable { 1402 1403 /** 1404 * Creates a new ElementBuffer. 1405 * 1406 * @param root the root element 1407 * @since 1.4 1408 */ 1409 public ElementBuffer(Element root) { 1410 this.root = root; 1411 changes = new Vector<ElemChanges>(); 1412 path = new Stack<ElemChanges>(); 1413 } 1414 1415 /** 1416 * Gets the root element. 1417 * 1418 * @return the root element 1419 */ 1420 public Element getRootElement() { 1421 return root; 1422 } 1423 1424 /** 1425 * Inserts new content. 1426 * 1427 * @param offset the starting offset >= 0 1428 * @param length the length >= 0 1429 * @param data the data to insert 1430 * @param de the event capturing this edit 1431 */ 1432 public void insert(int offset, int length, ElementSpec[] data, 1433 DefaultDocumentEvent de) { 1434 if (length == 0) { 1435 // Nothing was inserted, no structure change. 1436 return; 1437 } 1438 insertOp = true; 1439 beginEdits(offset, length); 1440 insertUpdate(data); 1441 endEdits(de); 1442 1443 insertOp = false; 1444 } 1445 1446 void create(int length, ElementSpec[] data, DefaultDocumentEvent de) { 1447 insertOp = true; 1448 beginEdits(offset, length); 1449 1450 // PENDING(prinz) this needs to be fixed to create a new 1451 // root element as well, but requires changes to the 1452 // DocumentEvent to inform the views that there is a new 1453 // root element. 1454 1455 // Recreate the ending fake element to have the correct offsets. 1456 Element elem = root; 1457 int index = elem.getElementIndex(0); 1458 while (! elem.isLeaf()) { 1459 Element child = elem.getElement(index); 1460 push(elem, index); 1461 elem = child; 1462 index = elem.getElementIndex(0); 1463 } 1464 ElemChanges ec = path.peek(); 1465 Element child = ec.parent.getElement(ec.index); 1466 ec.added.addElement(createLeafElement(ec.parent, 1467 child.getAttributes(), getLength(), 1468 child.getEndOffset())); 1469 ec.removed.addElement(child); 1470 while (path.size() > 1) { 1471 pop(); 1472 } 1473 1474 int n = data.length; 1475 1476 // Reset the root elements attributes. 1477 AttributeSet newAttrs = null; 1478 if (n > 0 && data[0].getType() == ElementSpec.StartTagType) { 1479 newAttrs = data[0].getAttributes(); 1480 } 1481 if (newAttrs == null) { 1482 newAttrs = SimpleAttributeSet.EMPTY; 1483 } 1484 MutableAttributeSet attr = (MutableAttributeSet)root. 1485 getAttributes(); 1486 de.addEdit(new AttributeUndoableEdit(root, newAttrs, true)); 1487 attr.removeAttributes(attr); 1488 attr.addAttributes(newAttrs); 1489 1490 // fold in the specified subtree 1491 for (int i = 1; i < n; i++) { 1492 insertElement(data[i]); 1493 } 1494 1495 // pop the remaining path 1496 while (path.size() != 0) { 1497 pop(); 1498 } 1499 1500 endEdits(de); 1501 insertOp = false; 1502 } 1503 1504 /** 1505 * Removes content. 1506 * 1507 * @param offset the starting offset >= 0 1508 * @param length the length >= 0 1509 * @param de the event capturing this edit 1510 */ 1511 public void remove(int offset, int length, DefaultDocumentEvent de) { 1512 beginEdits(offset, length); 1513 removeUpdate(); 1514 endEdits(de); 1515 } 1516 1517 /** 1518 * Changes content. 1519 * 1520 * @param offset the starting offset >= 0 1521 * @param length the length >= 0 1522 * @param de the event capturing this edit 1523 */ 1524 public void change(int offset, int length, DefaultDocumentEvent de) { 1525 beginEdits(offset, length); 1526 changeUpdate(); 1527 endEdits(de); 1528 } 1529 1530 /** 1531 * Inserts an update into the document. 1532 * 1533 * @param data the elements to insert 1534 */ 1535 protected void insertUpdate(ElementSpec[] data) { 1536 // push the path 1537 Element elem = root; 1538 int index = elem.getElementIndex(offset); 1539 while (! elem.isLeaf()) { 1540 Element child = elem.getElement(index); 1541 push(elem, (child.isLeaf() ? index : index+1)); 1542 elem = child; 1543 index = elem.getElementIndex(offset); 1544 } 1545 1546 // Build a copy of the original path. 1547 insertPath = new ElemChanges[path.size()]; 1548 path.copyInto(insertPath); 1549 1550 // Haven't created the fracture yet. 1551 createdFracture = false; 1552 1553 // Insert the first content. 1554 int i; 1555 1556 recreateLeafs = false; 1557 if(data[0].getType() == ElementSpec.ContentType) { 1558 insertFirstContent(data); 1559 pos += data[0].getLength(); 1560 i = 1; 1561 } 1562 else { 1563 fractureDeepestLeaf(data); 1564 i = 0; 1565 } 1566 1567 // fold in the specified subtree 1568 int n = data.length; 1569 for (; i < n; i++) { 1570 insertElement(data[i]); 1571 } 1572 1573 // Fracture, if we haven't yet. 1574 if(!createdFracture) 1575 fracture(-1); 1576 1577 // pop the remaining path 1578 while (path.size() != 0) { 1579 pop(); 1580 } 1581 1582 // Offset the last index if necessary. 1583 if(offsetLastIndex && offsetLastIndexOnReplace) { 1584 insertPath[insertPath.length - 1].index++; 1585 } 1586 1587 // Make sure an edit is going to be created for each of the 1588 // original path items that have a change. 1589 for(int counter = insertPath.length - 1; counter >= 0; 1590 counter--) { 1591 ElemChanges change = insertPath[counter]; 1592 if(change.parent == fracturedParent) 1593 change.added.addElement(fracturedChild); 1594 if((change.added.size() > 0 || 1595 change.removed.size() > 0) && !changes.contains(change)) { 1596 // PENDING(sky): Do I need to worry about order here? 1597 changes.addElement(change); 1598 } 1599 } 1600 1601 // An insert at 0 with an initial end implies some elements 1602 // will have no children (the bottomost leaf would have length 0) 1603 // this will find what element need to be removed and remove it. 1604 if (offset == 0 && fracturedParent != null && 1605 data[0].getType() == ElementSpec.EndTagType) { 1606 int counter = 0; 1607 while (counter < data.length && 1608 data[counter].getType() == ElementSpec.EndTagType) { 1609 counter++; 1610 } 1611 ElemChanges change = insertPath[insertPath.length - 1612 counter - 1]; 1613 change.removed.insertElementAt(change.parent.getElement 1614 (--change.index), 0); 1615 } 1616 } 1617 1618 /** 1619 * Updates the element structure in response to a removal from the 1620 * associated sequence in the document. Any elements consumed by the 1621 * span of the removal are removed. 1622 */ 1623 protected void removeUpdate() { 1624 removeElements(root, offset, offset + length); 1625 } 1626 1627 /** 1628 * Updates the element structure in response to a change in the 1629 * document. 1630 */ 1631 protected void changeUpdate() { 1632 boolean didEnd = split(offset, length); 1633 if (! didEnd) { 1634 // need to do the other end 1635 while (path.size() != 0) { 1636 pop(); 1637 } 1638 split(offset + length, 0); 1639 } 1640 while (path.size() != 0) { 1641 pop(); 1642 } 1643 } 1644 1645 boolean split(int offs, int len) { 1646 boolean splitEnd = false; 1647 // push the path 1648 Element e = root; 1649 int index = e.getElementIndex(offs); 1650 while (! e.isLeaf()) { 1651 push(e, index); 1652 e = e.getElement(index); 1653 index = e.getElementIndex(offs); 1654 } 1655 1656 ElemChanges ec = path.peek(); 1657 Element child = ec.parent.getElement(ec.index); 1658 // make sure there is something to do... if the 1659 // offset is already at a boundary then there is 1660 // nothing to do. 1661 if (child.getStartOffset() < offs && offs < child.getEndOffset()) { 1662 // we need to split, now see if the other end is within 1663 // the same parent. 1664 int index0 = ec.index; 1665 int index1 = index0; 1666 if (((offs + len) < ec.parent.getEndOffset()) && (len != 0)) { 1667 // it's a range split in the same parent 1668 index1 = ec.parent.getElementIndex(offs+len); 1669 if (index1 == index0) { 1670 // it's a three-way split 1671 ec.removed.addElement(child); 1672 e = createLeafElement(ec.parent, child.getAttributes(), 1673 child.getStartOffset(), offs); 1674 ec.added.addElement(e); 1675 e = createLeafElement(ec.parent, child.getAttributes(), 1676 offs, offs + len); 1677 ec.added.addElement(e); 1678 e = createLeafElement(ec.parent, child.getAttributes(), 1679 offs + len, child.getEndOffset()); 1680 ec.added.addElement(e); 1681 return true; 1682 } else { 1683 child = ec.parent.getElement(index1); 1684 if ((offs + len) == child.getStartOffset()) { 1685 // end is already on a boundary 1686 index1 = index0; 1687 } 1688 } 1689 splitEnd = true; 1690 } 1691 1692 // split the first location 1693 pos = offs; 1694 child = ec.parent.getElement(index0); 1695 ec.removed.addElement(child); 1696 e = createLeafElement(ec.parent, child.getAttributes(), 1697 child.getStartOffset(), pos); 1698 ec.added.addElement(e); 1699 e = createLeafElement(ec.parent, child.getAttributes(), 1700 pos, child.getEndOffset()); 1701 ec.added.addElement(e); 1702 1703 // pick up things in the middle 1704 for (int i = index0 + 1; i < index1; i++) { 1705 child = ec.parent.getElement(i); 1706 ec.removed.addElement(child); 1707 ec.added.addElement(child); 1708 } 1709 1710 if (index1 != index0) { 1711 child = ec.parent.getElement(index1); 1712 pos = offs + len; 1713 ec.removed.addElement(child); 1714 e = createLeafElement(ec.parent, child.getAttributes(), 1715 child.getStartOffset(), pos); 1716 ec.added.addElement(e); 1717 e = createLeafElement(ec.parent, child.getAttributes(), 1718 pos, child.getEndOffset()); 1719 ec.added.addElement(e); 1720 } 1721 } 1722 return splitEnd; 1723 } 1724 1725 /** 1726 * Creates the UndoableEdit record for the edits made 1727 * in the buffer. 1728 */ 1729 void endEdits(DefaultDocumentEvent de) { 1730 int n = changes.size(); 1731 for (int i = 0; i < n; i++) { 1732 ElemChanges ec = changes.elementAt(i); 1733 Element[] removed = new Element[ec.removed.size()]; 1734 ec.removed.copyInto(removed); 1735 Element[] added = new Element[ec.added.size()]; 1736 ec.added.copyInto(added); 1737 int index = ec.index; 1738 ((BranchElement) ec.parent).replace(index, removed.length, added); 1739 ElementEdit ee = new ElementEdit(ec.parent, index, removed, added); 1740 de.addEdit(ee); 1741 } 1742 1743 changes.removeAllElements(); 1744 path.removeAllElements(); 1745 1746 /* 1747 for (int i = 0; i < n; i++) { 1748 ElemChanges ec = (ElemChanges) changes.elementAt(i); 1749 System.err.print("edited: " + ec.parent + " at: " + ec.index + 1750 " removed " + ec.removed.size()); 1751 if (ec.removed.size() > 0) { 1752 int r0 = ((Element) ec.removed.firstElement()).getStartOffset(); 1753 int r1 = ((Element) ec.removed.lastElement()).getEndOffset(); 1754 System.err.print("[" + r0 + "," + r1 + "]"); 1755 } 1756 System.err.print(" added " + ec.added.size()); 1757 if (ec.added.size() > 0) { 1758 int p0 = ((Element) ec.added.firstElement()).getStartOffset(); 1759 int p1 = ((Element) ec.added.lastElement()).getEndOffset(); 1760 System.err.print("[" + p0 + "," + p1 + "]"); 1761 } 1762 System.err.println(""); 1763 } 1764 */ 1765 } 1766 1767 /** 1768 * Initialize the buffer 1769 */ 1770 void beginEdits(int offset, int length) { 1771 this.offset = offset; 1772 this.length = length; 1773 this.endOffset = offset + length; 1774 pos = offset; 1775 if (changes == null) { 1776 changes = new Vector<ElemChanges>(); 1777 } else { 1778 changes.removeAllElements(); 1779 } 1780 if (path == null) { 1781 path = new Stack<ElemChanges>(); 1782 } else { 1783 path.removeAllElements(); 1784 } 1785 fracturedParent = null; 1786 fracturedChild = null; 1787 offsetLastIndex = offsetLastIndexOnReplace = false; 1788 } 1789 1790 /** 1791 * Pushes a new element onto the stack that represents 1792 * the current path. 1793 * @param record Whether or not the push should be 1794 * recorded as an element change or not. 1795 * @param isFracture true if pushing on an element that was created 1796 * as the result of a fracture. 1797 */ 1798 void push(Element e, int index, boolean isFracture) { 1799 ElemChanges ec = new ElemChanges(e, index, isFracture); 1800 path.push(ec); 1801 } 1802 1803 void push(Element e, int index) { 1804 push(e, index, false); 1805 } 1806 1807 void pop() { 1808 ElemChanges ec = path.peek(); 1809 path.pop(); 1810 if ((ec.added.size() > 0) || (ec.removed.size() > 0)) { 1811 changes.addElement(ec); 1812 } else if (! path.isEmpty()) { 1813 Element e = ec.parent; 1814 if(e.getElementCount() == 0) { 1815 // if we pushed a branch element that didn't get 1816 // used, make sure its not marked as having been added. 1817 ec = path.peek(); 1818 ec.added.removeElement(e); 1819 } 1820 } 1821 } 1822 1823 /** 1824 * move the current offset forward by n. 1825 */ 1826 void advance(int n) { 1827 pos += n; 1828 } 1829 1830 void insertElement(ElementSpec es) { 1831 ElemChanges ec = path.peek(); 1832 switch(es.getType()) { 1833 case ElementSpec.StartTagType: 1834 switch(es.getDirection()) { 1835 case ElementSpec.JoinNextDirection: 1836 // Don't create a new element, use the existing one 1837 // at the specified location. 1838 Element parent = ec.parent.getElement(ec.index); 1839 1840 if(parent.isLeaf()) { 1841 // This happens if inserting into a leaf, followed 1842 // by a join next where next sibling is not a leaf. 1843 if((ec.index + 1) < ec.parent.getElementCount()) 1844 parent = ec.parent.getElement(ec.index + 1); 1845 else 1846 throw new StateInvariantError("Join next to leaf"); 1847 } 1848 // Not really a fracture, but need to treat it like 1849 // one so that content join next will work correctly. 1850 // We can do this because there will never be a join 1851 // next followed by a join fracture. 1852 push(parent, 0, true); 1853 break; 1854 case ElementSpec.JoinFractureDirection: 1855 if(!createdFracture) { 1856 // Should always be something on the stack! 1857 fracture(path.size() - 1); 1858 } 1859 // If parent isn't a fracture, fracture will be 1860 // fracturedChild. 1861 if(!ec.isFracture) { 1862 push(fracturedChild, 0, true); 1863 } 1864 else 1865 // Parent is a fracture, use 1st element. 1866 push(ec.parent.getElement(0), 0, true); 1867 break; 1868 default: 1869 Element belem = createBranchElement(ec.parent, 1870 es.getAttributes()); 1871 ec.added.addElement(belem); 1872 push(belem, 0); 1873 break; 1874 } 1875 break; 1876 case ElementSpec.EndTagType: 1877 pop(); 1878 break; 1879 case ElementSpec.ContentType: 1880 int len = es.getLength(); 1881 if (es.getDirection() != ElementSpec.JoinNextDirection) { 1882 Element leaf = createLeafElement(ec.parent, es.getAttributes(), 1883 pos, pos + len); 1884 ec.added.addElement(leaf); 1885 } 1886 else { 1887 // JoinNext on tail is only applicable if last element 1888 // and attributes come from that of first element. 1889 // With a little extra testing it would be possible 1890 // to NOT due this again, as more than likely fracture() 1891 // created this element. 1892 if(!ec.isFracture) { 1893 Element first = null; 1894 if(insertPath != null) { 1895 for(int counter = insertPath.length - 1; 1896 counter >= 0; counter--) { 1897 if(insertPath[counter] == ec) { 1898 if(counter != (insertPath.length - 1)) 1899 first = ec.parent.getElement(ec.index); 1900 break; 1901 } 1902 } 1903 } 1904 if(first == null) 1905 first = ec.parent.getElement(ec.index + 1); 1906 Element leaf = createLeafElement(ec.parent, first. 1907 getAttributes(), pos, first.getEndOffset()); 1908 ec.added.addElement(leaf); 1909 ec.removed.addElement(first); 1910 } 1911 else { 1912 // Parent was fractured element. 1913 Element first = ec.parent.getElement(0); 1914 Element leaf = createLeafElement(ec.parent, first. 1915 getAttributes(), pos, first.getEndOffset()); 1916 ec.added.addElement(leaf); 1917 ec.removed.addElement(first); 1918 } 1919 } 1920 pos += len; 1921 break; 1922 } 1923 } 1924 1925 /** 1926 * Remove the elements from <code>elem</code> in range 1927 * <code>rmOffs0</code>, <code>rmOffs1</code>. This uses 1928 * <code>canJoin</code> and <code>join</code> to handle joining 1929 * the endpoints of the insertion. 1930 * 1931 * @return true if elem will no longer have any elements. 1932 */ 1933 boolean removeElements(Element elem, int rmOffs0, int rmOffs1) { 1934 if (! elem.isLeaf()) { 1935 // update path for changes 1936 int index0 = elem.getElementIndex(rmOffs0); 1937 int index1 = elem.getElementIndex(rmOffs1); 1938 push(elem, index0); 1939 ElemChanges ec = path.peek(); 1940 1941 // if the range is contained by one element, 1942 // we just forward the request 1943 if (index0 == index1) { 1944 Element child0 = elem.getElement(index0); 1945 if(rmOffs0 <= child0.getStartOffset() && 1946 rmOffs1 >= child0.getEndOffset()) { 1947 // Element totally removed. 1948 ec.removed.addElement(child0); 1949 } 1950 else if(removeElements(child0, rmOffs0, rmOffs1)) { 1951 ec.removed.addElement(child0); 1952 } 1953 } else { 1954 // the removal range spans elements. If we can join 1955 // the two endpoints, do it. Otherwise we remove the 1956 // interior and forward to the endpoints. 1957 Element child0 = elem.getElement(index0); 1958 Element child1 = elem.getElement(index1); 1959 boolean containsOffs1 = (rmOffs1 < elem.getEndOffset()); 1960 if (containsOffs1 && canJoin(child0, child1)) { 1961 // remove and join 1962 for (int i = index0; i <= index1; i++) { 1963 ec.removed.addElement(elem.getElement(i)); 1964 } 1965 Element e = join(elem, child0, child1, rmOffs0, rmOffs1); 1966 ec.added.addElement(e); 1967 } else { 1968 // remove interior and forward 1969 int rmIndex0 = index0 + 1; 1970 int rmIndex1 = index1 - 1; 1971 if (child0.getStartOffset() == rmOffs0 || 1972 (index0 == 0 && 1973 child0.getStartOffset() > rmOffs0 && 1974 child0.getEndOffset() <= rmOffs1)) { 1975 // start element completely consumed 1976 child0 = null; 1977 rmIndex0 = index0; 1978 } 1979 if (!containsOffs1) { 1980 child1 = null; 1981 rmIndex1++; 1982 } 1983 else if (child1.getStartOffset() == rmOffs1) { 1984 // end element not touched 1985 child1 = null; 1986 } 1987 if (rmIndex0 <= rmIndex1) { 1988 ec.index = rmIndex0; 1989 } 1990 for (int i = rmIndex0; i <= rmIndex1; i++) { 1991 ec.removed.addElement(elem.getElement(i)); 1992 } 1993 if (child0 != null) { 1994 if(removeElements(child0, rmOffs0, rmOffs1)) { 1995 ec.removed.insertElementAt(child0, 0); 1996 ec.index = index0; 1997 } 1998 } 1999 if (child1 != null) { 2000 if(removeElements(child1, rmOffs0, rmOffs1)) { 2001 ec.removed.addElement(child1); 2002 } 2003 } 2004 } 2005 } 2006 2007 // publish changes 2008 pop(); 2009 2010 // Return true if we no longer have any children. 2011 if(elem.getElementCount() == (ec.removed.size() - 2012 ec.added.size())) { 2013 return true; 2014 } 2015 } 2016 return false; 2017 } 2018 2019 /** 2020 * Can the two given elements be coelesced together 2021 * into one element? 2022 */ 2023 boolean canJoin(Element e0, Element e1) { 2024 if ((e0 == null) || (e1 == null)) { 2025 return false; 2026 } 2027 // Don't join a leaf to a branch. 2028 boolean leaf0 = e0.isLeaf(); 2029 boolean leaf1 = e1.isLeaf(); 2030 if(leaf0 != leaf1) { 2031 return false; 2032 } 2033 if (leaf0) { 2034 // Only join leaves if the attributes match, otherwise 2035 // style information will be lost. 2036 return e0.getAttributes().isEqual(e1.getAttributes()); 2037 } 2038 // Only join non-leafs if the names are equal. This may result 2039 // in loss of style information, but this is typically acceptable 2040 // for non-leafs. 2041 String name0 = e0.getName(); 2042 String name1 = e1.getName(); 2043 if (name0 != null) { 2044 return name0.equals(name1); 2045 } 2046 if (name1 != null) { 2047 return name1.equals(name0); 2048 } 2049 // Both names null, treat as equal. 2050 return true; 2051 } 2052 2053 /** 2054 * Joins the two elements carving out a hole for the 2055 * given removed range. 2056 */ 2057 Element join(Element p, Element left, Element right, int rmOffs0, int rmOffs1) { 2058 if (left.isLeaf() && right.isLeaf()) { 2059 return createLeafElement(p, left.getAttributes(), left.getStartOffset(), 2060 right.getEndOffset()); 2061 } else if ((!left.isLeaf()) && (!right.isLeaf())) { 2062 // join two branch elements. This copies the children before 2063 // the removal range on the left element, and after the removal 2064 // range on the right element. The two elements on the edge 2065 // are joined if possible and needed. 2066 Element to = createBranchElement(p, left.getAttributes()); 2067 int ljIndex = left.getElementIndex(rmOffs0); 2068 int rjIndex = right.getElementIndex(rmOffs1); 2069 Element lj = left.getElement(ljIndex); 2070 if (lj.getStartOffset() >= rmOffs0) { 2071 lj = null; 2072 } 2073 Element rj = right.getElement(rjIndex); 2074 if (rj.getStartOffset() == rmOffs1) { 2075 rj = null; 2076 } 2077 Vector<Element> children = new Vector<Element>(); 2078 2079 // transfer the left 2080 for (int i = 0; i < ljIndex; i++) { 2081 children.addElement(clone(to, left.getElement(i))); 2082 } 2083 2084 // transfer the join/middle 2085 if (canJoin(lj, rj)) { 2086 Element e = join(to, lj, rj, rmOffs0, rmOffs1); 2087 children.addElement(e); 2088 } else { 2089 if (lj != null) { 2090 children.addElement(cloneAsNecessary(to, lj, rmOffs0, rmOffs1)); 2091 } 2092 if (rj != null) { 2093 children.addElement(cloneAsNecessary(to, rj, rmOffs0, rmOffs1)); 2094 } 2095 } 2096 2097 // transfer the right 2098 int n = right.getElementCount(); 2099 for (int i = (rj == null) ? rjIndex : rjIndex + 1; i < n; i++) { 2100 children.addElement(clone(to, right.getElement(i))); 2101 } 2102 2103 // install the children 2104 Element[] c = new Element[children.size()]; 2105 children.copyInto(c); 2106 ((BranchElement)to).replace(0, 0, c); 2107 return to; 2108 } else { 2109 throw new StateInvariantError( 2110 "No support to join leaf element with non-leaf element"); 2111 } 2112 } 2113 2114 /** 2115 * Creates a copy of this element, with a different 2116 * parent. 2117 * 2118 * @param parent the parent element 2119 * @param clonee the element to be cloned 2120 * @return the copy 2121 */ 2122 public Element clone(Element parent, Element clonee) { 2123 if (clonee.isLeaf()) { 2124 return createLeafElement(parent, clonee.getAttributes(), 2125 clonee.getStartOffset(), 2126 clonee.getEndOffset()); 2127 } 2128 Element e = createBranchElement(parent, clonee.getAttributes()); 2129 int n = clonee.getElementCount(); 2130 Element[] children = new Element[n]; 2131 for (int i = 0; i < n; i++) { 2132 children[i] = clone(e, clonee.getElement(i)); 2133 } 2134 ((BranchElement)e).replace(0, 0, children); 2135 return e; 2136 } 2137 2138 /** 2139 * Creates a copy of this element, with a different 2140 * parent. Children of this element included in the 2141 * removal range will be discarded. 2142 */ 2143 Element cloneAsNecessary(Element parent, Element clonee, int rmOffs0, int rmOffs1) { 2144 if (clonee.isLeaf()) { 2145 return createLeafElement(parent, clonee.getAttributes(), 2146 clonee.getStartOffset(), 2147 clonee.getEndOffset()); 2148 } 2149 Element e = createBranchElement(parent, clonee.getAttributes()); 2150 int n = clonee.getElementCount(); 2151 ArrayList<Element> childrenList = new ArrayList<Element>(n); 2152 for (int i = 0; i < n; i++) { 2153 Element elem = clonee.getElement(i); 2154 if (elem.getStartOffset() < rmOffs0 || elem.getEndOffset() > rmOffs1) { 2155 childrenList.add(cloneAsNecessary(e, elem, rmOffs0, rmOffs1)); 2156 } 2157 } 2158 Element[] children = new Element[childrenList.size()]; 2159 children = childrenList.toArray(children); 2160 ((BranchElement)e).replace(0, 0, children); 2161 return e; 2162 } 2163 2164 /** 2165 * Determines if a fracture needs to be performed. A fracture 2166 * can be thought of as moving the right part of a tree to a 2167 * new location, where the right part is determined by what has 2168 * been inserted. <code>depth</code> is used to indicate a 2169 * JoinToFracture is needed to an element at a depth 2170 * of <code>depth</code>. Where the root is 0, 1 is the children 2171 * of the root... 2172 * <p>This will invoke <code>fractureFrom</code> if it is determined 2173 * a fracture needs to happen. 2174 */ 2175 void fracture(int depth) { 2176 int cLength = insertPath.length; 2177 int lastIndex = -1; 2178 boolean needRecreate = recreateLeafs; 2179 ElemChanges lastChange = insertPath[cLength - 1]; 2180 // Use childAltered to determine when a child has been altered, 2181 // that is the point of insertion is less than the element count. 2182 boolean childAltered = ((lastChange.index + 1) < 2183 lastChange.parent.getElementCount()); 2184 int deepestAlteredIndex = (needRecreate) ? cLength : -1; 2185 int lastAlteredIndex = cLength - 1; 2186 2187 createdFracture = true; 2188 // Determine where to start recreating from. 2189 // Start at - 2, as first one is indicated by recreateLeafs and 2190 // childAltered. 2191 for(int counter = cLength - 2; counter >= 0; counter--) { 2192 ElemChanges change = insertPath[counter]; 2193 if(change.added.size() > 0 || counter == depth) { 2194 lastIndex = counter; 2195 if(!needRecreate && childAltered) { 2196 needRecreate = true; 2197 if(deepestAlteredIndex == -1) 2198 deepestAlteredIndex = lastAlteredIndex + 1; 2199 } 2200 } 2201 if(!childAltered && change.index < 2202 change.parent.getElementCount()) { 2203 childAltered = true; 2204 lastAlteredIndex = counter; 2205 } 2206 } 2207 if(needRecreate) { 2208 // Recreate all children to right of parent starting 2209 // at lastIndex. 2210 if(lastIndex == -1) 2211 lastIndex = cLength - 1; 2212 fractureFrom(insertPath, lastIndex, deepestAlteredIndex); 2213 } 2214 } 2215 2216 /** 2217 * Recreates the elements to the right of the insertion point. 2218 * This starts at <code>startIndex</code> in <code>changed</code>, 2219 * and calls duplicate to duplicate existing elements. 2220 * This will also duplicate the elements along the insertion 2221 * point, until a depth of <code>endFractureIndex</code> is 2222 * reached, at which point only the elements to the right of 2223 * the insertion point are duplicated. 2224 */ 2225 void fractureFrom(ElemChanges[] changed, int startIndex, 2226 int endFractureIndex) { 2227 // Recreate the element representing the inserted index. 2228 ElemChanges change = changed[startIndex]; 2229 Element child; 2230 Element newChild; 2231 int changeLength = changed.length; 2232 2233 if((startIndex + 1) == changeLength) 2234 child = change.parent.getElement(change.index); 2235 else 2236 child = change.parent.getElement(change.index - 1); 2237 if(child.isLeaf()) { 2238 newChild = createLeafElement(change.parent, 2239 child.getAttributes(), Math.max(endOffset, 2240 child.getStartOffset()), child.getEndOffset()); 2241 } 2242 else { 2243 newChild = createBranchElement(change.parent, 2244 child.getAttributes()); 2245 } 2246 fracturedParent = change.parent; 2247 fracturedChild = newChild; 2248 2249 // Recreate all the elements to the right of the 2250 // insertion point. 2251 Element parent = newChild; 2252 2253 while(++startIndex < endFractureIndex) { 2254 boolean isEnd = ((startIndex + 1) == endFractureIndex); 2255 boolean isEndLeaf = ((startIndex + 1) == changeLength); 2256 2257 // Create the newChild, a duplicate of the elment at 2258 // index. This isn't done if isEnd and offsetLastIndex are true 2259 // indicating a join previous was done. 2260 change = changed[startIndex]; 2261 2262 // Determine the child to duplicate, won't have to duplicate 2263 // if at end of fracture, or offseting index. 2264 if(isEnd) { 2265 if(offsetLastIndex || !isEndLeaf) 2266 child = null; 2267 else 2268 child = change.parent.getElement(change.index); 2269 } 2270 else { 2271 child = change.parent.getElement(change.index - 1); 2272 } 2273 // Duplicate it. 2274 if(child != null) { 2275 if(child.isLeaf()) { 2276 newChild = createLeafElement(parent, 2277 child.getAttributes(), Math.max(endOffset, 2278 child.getStartOffset()), child.getEndOffset()); 2279 } 2280 else { 2281 newChild = createBranchElement(parent, 2282 child.getAttributes()); 2283 } 2284 } 2285 else 2286 newChild = null; 2287 2288 // Recreate the remaining children (there may be none). 2289 int kidsToMove = change.parent.getElementCount() - 2290 change.index; 2291 Element[] kids; 2292 int moveStartIndex; 2293 int kidStartIndex = 1; 2294 2295 if(newChild == null) { 2296 // Last part of fracture. 2297 if(isEndLeaf) { 2298 kidsToMove--; 2299 moveStartIndex = change.index + 1; 2300 } 2301 else { 2302 moveStartIndex = change.index; 2303 } 2304 kidStartIndex = 0; 2305 kids = new Element[kidsToMove]; 2306 } 2307 else { 2308 if(!isEnd) { 2309 // Branch. 2310 kidsToMove++; 2311 moveStartIndex = change.index; 2312 } 2313 else { 2314 // Last leaf, need to recreate part of it. 2315 moveStartIndex = change.index + 1; 2316 } 2317 kids = new Element[kidsToMove]; 2318 kids[0] = newChild; 2319 } 2320 2321 for(int counter = kidStartIndex; counter < kidsToMove; 2322 counter++) { 2323 Element toMove =change.parent.getElement(moveStartIndex++); 2324 kids[counter] = recreateFracturedElement(parent, toMove); 2325 change.removed.addElement(toMove); 2326 } 2327 ((BranchElement)parent).replace(0, 0, kids); 2328 parent = newChild; 2329 } 2330 } 2331 2332 /** 2333 * Recreates <code>toDuplicate</code>. This is called when an 2334 * element needs to be created as the result of an insertion. This 2335 * will recurse and create all the children. This is similar to 2336 * <code>clone</code>, but deteremines the offsets differently. 2337 */ 2338 Element recreateFracturedElement(Element parent, Element toDuplicate) { 2339 if(toDuplicate.isLeaf()) { 2340 return createLeafElement(parent, toDuplicate.getAttributes(), 2341 Math.max(toDuplicate.getStartOffset(), 2342 endOffset), 2343 toDuplicate.getEndOffset()); 2344 } 2345 // Not a leaf 2346 Element newParent = createBranchElement(parent, toDuplicate. 2347 getAttributes()); 2348 int childCount = toDuplicate.getElementCount(); 2349 Element[] newKids = new Element[childCount]; 2350 for(int counter = 0; counter < childCount; counter++) { 2351 newKids[counter] = recreateFracturedElement(newParent, 2352 toDuplicate.getElement(counter)); 2353 } 2354 ((BranchElement)newParent).replace(0, 0, newKids); 2355 return newParent; 2356 } 2357 2358 /** 2359 * Splits the bottommost leaf in <code>path</code>. 2360 * This is called from insert when the first element is NOT content. 2361 */ 2362 void fractureDeepestLeaf(ElementSpec[] specs) { 2363 // Split the bottommost leaf. It will be recreated elsewhere. 2364 ElemChanges ec = path.peek(); 2365 Element child = ec.parent.getElement(ec.index); 2366 // Inserts at offset 0 do not need to recreate child (it would 2367 // have a length of 0!). 2368 if (offset != 0) { 2369 Element newChild = createLeafElement(ec.parent, 2370 child.getAttributes(), 2371 child.getStartOffset(), 2372 offset); 2373 2374 ec.added.addElement(newChild); 2375 } 2376 ec.removed.addElement(child); 2377 if(child.getEndOffset() != endOffset) 2378 recreateLeafs = true; 2379 else 2380 offsetLastIndex = true; 2381 } 2382 2383 /** 2384 * Inserts the first content. This needs to be separate to handle 2385 * joining. 2386 */ 2387 void insertFirstContent(ElementSpec[] specs) { 2388 ElementSpec firstSpec = specs[0]; 2389 ElemChanges ec = path.peek(); 2390 Element child = ec.parent.getElement(ec.index); 2391 int firstEndOffset = offset + firstSpec.getLength(); 2392 boolean isOnlyContent = (specs.length == 1); 2393 2394 switch(firstSpec.getDirection()) { 2395 case ElementSpec.JoinPreviousDirection: 2396 if(child.getEndOffset() != firstEndOffset && 2397 !isOnlyContent) { 2398 // Create the left split part containing new content. 2399 Element newE = createLeafElement(ec.parent, 2400 child.getAttributes(), child.getStartOffset(), 2401 firstEndOffset); 2402 ec.added.addElement(newE); 2403 ec.removed.addElement(child); 2404 // Remainder will be created later. 2405 if(child.getEndOffset() != endOffset) 2406 recreateLeafs = true; 2407 else 2408 offsetLastIndex = true; 2409 } 2410 else { 2411 offsetLastIndex = true; 2412 offsetLastIndexOnReplace = true; 2413 } 2414 // else Inserted at end, and is total length. 2415 // Update index incase something added/removed. 2416 break; 2417 case ElementSpec.JoinNextDirection: 2418 if(offset != 0) { 2419 // Recreate the first element, its offset will have 2420 // changed. 2421 Element newE = createLeafElement(ec.parent, 2422 child.getAttributes(), child.getStartOffset(), 2423 offset); 2424 ec.added.addElement(newE); 2425 // Recreate the second, merge part. We do no checking 2426 // to see if JoinNextDirection is valid here! 2427 Element nextChild = ec.parent.getElement(ec.index + 1); 2428 if(isOnlyContent) 2429 newE = createLeafElement(ec.parent, nextChild. 2430 getAttributes(), offset, nextChild.getEndOffset()); 2431 else 2432 newE = createLeafElement(ec.parent, nextChild. 2433 getAttributes(), offset, firstEndOffset); 2434 ec.added.addElement(newE); 2435 ec.removed.addElement(child); 2436 ec.removed.addElement(nextChild); 2437 } 2438 // else nothin to do. 2439 // PENDING: if !isOnlyContent could raise here! 2440 break; 2441 default: 2442 // Inserted into middle, need to recreate split left 2443 // new content, and split right. 2444 if(child.getStartOffset() != offset) { 2445 Element newE = createLeafElement(ec.parent, 2446 child.getAttributes(), child.getStartOffset(), 2447 offset); 2448 ec.added.addElement(newE); 2449 } 2450 ec.removed.addElement(child); 2451 // new content 2452 Element newE = createLeafElement(ec.parent, 2453 firstSpec.getAttributes(), 2454 offset, firstEndOffset); 2455 ec.added.addElement(newE); 2456 if(child.getEndOffset() != endOffset) { 2457 // Signals need to recreate right split later. 2458 recreateLeafs = true; 2459 } 2460 else { 2461 offsetLastIndex = true; 2462 } 2463 break; 2464 } 2465 } 2466 2467 Element root; 2468 transient int pos; // current position 2469 transient int offset; 2470 transient int length; 2471 transient int endOffset; 2472 transient Vector<ElemChanges> changes; 2473 transient Stack<ElemChanges> path; 2474 transient boolean insertOp; 2475 2476 transient boolean recreateLeafs; // For insert. 2477 2478 /** For insert, path to inserted elements. */ 2479 transient ElemChanges[] insertPath; 2480 /** Only for insert, set to true when the fracture has been created. */ 2481 transient boolean createdFracture; 2482 /** Parent that contains the fractured child. */ 2483 transient Element fracturedParent; 2484 /** Fractured child. */ 2485 transient Element fracturedChild; 2486 /** Used to indicate when fracturing that the last leaf should be 2487 * skipped. */ 2488 transient boolean offsetLastIndex; 2489 /** Used to indicate that the parent of the deepest leaf should 2490 * offset the index by 1 when adding/removing elements in an 2491 * insert. */ 2492 transient boolean offsetLastIndexOnReplace; 2493 2494 /* 2495 * Internal record used to hold element change specifications 2496 */ 2497 class ElemChanges { 2498 2499 ElemChanges(Element parent, int index, boolean isFracture) { 2500 this.parent = parent; 2501 this.index = index; 2502 this.isFracture = isFracture; 2503 added = new Vector<Element>(); 2504 removed = new Vector<Element>(); 2505 } 2506 2507 public String toString() { 2508 return "added: " + added + "\nremoved: " + removed + "\n"; 2509 } 2510 2511 Element parent; 2512 int index; 2513 Vector<Element> added; 2514 Vector<Element> removed; 2515 boolean isFracture; 2516 } 2517 2518 } 2519 2520 /** 2521 * An UndoableEdit used to remember AttributeSet changes to an 2522 * Element. 2523 */ 2524 public static class AttributeUndoableEdit extends AbstractUndoableEdit { 2525 public AttributeUndoableEdit(Element element, AttributeSet newAttributes, 2526 boolean isReplacing) { 2527 super(); 2528 this.element = element; 2529 this.newAttributes = newAttributes; 2530 this.isReplacing = isReplacing; 2531 // If not replacing, it may be more efficient to only copy the 2532 // changed values... 2533 copy = element.getAttributes().copyAttributes(); 2534 } 2535 2536 /** 2537 * Redoes a change. 2538 * 2539 * @exception CannotRedoException if the change cannot be redone 2540 */ 2541 public void redo() throws CannotRedoException { 2542 super.redo(); 2543 MutableAttributeSet as = (MutableAttributeSet)element 2544 .getAttributes(); 2545 if(isReplacing) 2546 as.removeAttributes(as); 2547 as.addAttributes(newAttributes); 2548 } 2549 2550 /** 2551 * Undoes a change. 2552 * 2553 * @exception CannotUndoException if the change cannot be undone 2554 */ 2555 public void undo() throws CannotUndoException { 2556 super.undo(); 2557 MutableAttributeSet as = (MutableAttributeSet)element.getAttributes(); 2558 as.removeAttributes(as); 2559 as.addAttributes(copy); 2560 } 2561 2562 // AttributeSet containing additional entries, must be non-mutable! 2563 protected AttributeSet newAttributes; 2564 // Copy of the AttributeSet the Element contained. 2565 protected AttributeSet copy; 2566 // true if all the attributes in the element were removed first. 2567 protected boolean isReplacing; 2568 // Efected Element. 2569 protected Element element; 2570 } 2571 2572 /** 2573 * UndoableEdit for changing the resolve parent of an Element. 2574 */ 2575 static class StyleChangeUndoableEdit extends AbstractUndoableEdit { 2576 public StyleChangeUndoableEdit(AbstractElement element, 2577 Style newStyle) { 2578 super(); 2579 this.element = element; 2580 this.newStyle = newStyle; 2581 oldStyle = element.getResolveParent(); 2582 } 2583 2584 /** 2585 * Redoes a change. 2586 * 2587 * @exception CannotRedoException if the change cannot be redone 2588 */ 2589 public void redo() throws CannotRedoException { 2590 super.redo(); 2591 element.setResolveParent(newStyle); 2592 } 2593 2594 /** 2595 * Undoes a change. 2596 * 2597 * @exception CannotUndoException if the change cannot be undone 2598 */ 2599 public void undo() throws CannotUndoException { 2600 super.undo(); 2601 element.setResolveParent(oldStyle); 2602 } 2603 2604 /** Element to change resolve parent of. */ 2605 protected AbstractElement element; 2606 /** New style. */ 2607 protected Style newStyle; 2608 /** Old style, before setting newStyle. */ 2609 protected AttributeSet oldStyle; 2610 } 2611 2612 /** 2613 * Base class for style change handlers with support for stale objects detection. 2614 */ 2615 abstract static class AbstractChangeHandler implements ChangeListener { 2616 2617 /* This has an implicit reference to the handler object. */ 2618 private class DocReference extends WeakReference<DefaultStyledDocument> { 2619 2620 DocReference(DefaultStyledDocument d, ReferenceQueue<DefaultStyledDocument> q) { 2621 super(d, q); 2622 } 2623 2624 /** 2625 * Return a reference to the style change handler object. 2626 */ 2627 ChangeListener getListener() { 2628 return AbstractChangeHandler.this; 2629 } 2630 } 2631 2632 /** Class-specific reference queues. */ 2633 private final static Map<Class, ReferenceQueue<DefaultStyledDocument>> queueMap 2634 = new HashMap<Class, ReferenceQueue<DefaultStyledDocument>>(); 2635 2636 /** A weak reference to the document object. */ 2637 private DocReference doc; 2638 2639 AbstractChangeHandler(DefaultStyledDocument d) { 2640 Class c = getClass(); 2641 ReferenceQueue<DefaultStyledDocument> q; 2642 synchronized (queueMap) { 2643 q = queueMap.get(c); 2644 if (q == null) { 2645 q = new ReferenceQueue<DefaultStyledDocument>(); 2646 queueMap.put(c, q); 2647 } 2648 } 2649 doc = new DocReference(d, q); 2650 } 2651 2652 /** 2653 * Return a list of stale change listeners. 2654 * 2655 * A change listener becomes "stale" when its document is cleaned by GC. 2656 */ 2657 static List<ChangeListener> getStaleListeners(ChangeListener l) { 2658 List<ChangeListener> staleListeners = new ArrayList<ChangeListener>(); 2659 ReferenceQueue<DefaultStyledDocument> q = queueMap.get(l.getClass()); 2660 2661 if (q != null) { 2662 DocReference r; 2663 synchronized (q) { 2664 while ((r = (DocReference) q.poll()) != null) { 2665 staleListeners.add(r.getListener()); 2666 } 2667 } 2668 } 2669 2670 return staleListeners; 2671 } 2672 2673 /** 2674 * The ChangeListener wrapper which guards against dead documents. 2675 */ 2676 public void stateChanged(ChangeEvent e) { 2677 DefaultStyledDocument d = doc.get(); 2678 if (d != null) { 2679 fireStateChanged(d, e); 2680 } 2681 } 2682 2683 /** Run the actual class-specific stateChanged() method. */ 2684 abstract void fireStateChanged(DefaultStyledDocument d, ChangeEvent e); 2685 } 2686 2687 /** 2688 * Added to all the Styles. When instances of this receive a 2689 * stateChanged method, styleChanged is invoked. 2690 */ 2691 static class StyleChangeHandler extends AbstractChangeHandler { 2692 2693 StyleChangeHandler(DefaultStyledDocument d) { 2694 super(d); 2695 } 2696 2697 void fireStateChanged(DefaultStyledDocument d, ChangeEvent e) { 2698 Object source = e.getSource(); 2699 if (source instanceof Style) { 2700 d.styleChanged((Style) source); 2701 } else { 2702 d.styleChanged(null); 2703 } 2704 } 2705 } 2706 2707 2708 /** 2709 * Added to the StyleContext. When the StyleContext changes, this invokes 2710 * <code>updateStylesListeningTo</code>. 2711 */ 2712 static class StyleContextChangeHandler extends AbstractChangeHandler { 2713 2714 StyleContextChangeHandler(DefaultStyledDocument d) { 2715 super(d); 2716 } 2717 2718 void fireStateChanged(DefaultStyledDocument d, ChangeEvent e) { 2719 d.updateStylesListeningTo(); 2720 } 2721 } 2722 2723 2724 /** 2725 * When run this creates a change event for the complete document 2726 * and fires it. 2727 */ 2728 class ChangeUpdateRunnable implements Runnable { 2729 boolean isPending = false; 2730 2731 public void run() { 2732 synchronized(this) { 2733 isPending = false; 2734 } 2735 2736 try { 2737 writeLock(); 2738 DefaultDocumentEvent dde = new DefaultDocumentEvent(0, 2739 getLength(), 2740 DocumentEvent.EventType.CHANGE); 2741 dde.end(); 2742 fireChangedUpdate(dde); 2743 } finally { 2744 writeUnlock(); 2745 } 2746 } 2747 } 2748 }