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