1 /* DefaultStyledDocument.java -- 2 Copyright (C) 2004, 2005 Free Software Foundation, Inc. 3 4 This file is part of GNU Classpath. 5 6 GNU Classpath is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation; either version 2, or (at your option) 9 any later version. 10 11 GNU Classpath is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with GNU Classpath; see the file COPYING. If not, write to the 18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19 02110-1301 USA. 20 21 Linking this library statically or dynamically with other modules is 22 making a combined work based on this library. Thus, the terms and 23 conditions of the GNU General Public License cover the whole 24 combination. 25 26 As a special exception, the copyright holders of this library give you 27 permission to link this library with independent modules to produce an 28 executable, regardless of the license terms of these independent 29 modules, and to copy and distribute the resulting executable under 30 terms of your choice, provided that you also meet, for each linked 31 independent module, the terms and conditions of the license of that 32 module. An independent module is a module which is not derived from 33 or based on this library. If you modify this library, you may extend 34 this exception to your version of the library, but you are not 35 obligated to do so. If you do not wish to do so, delete this 36 exception statement from your version. */ 37 38 39 package javax.swing.text; 40 41 import gnu.java.lang.CPStringBuilder; 42 43 import java.awt.Color; 44 import java.awt.Font; 45 import java.io.Serializable; 46 import java.util.ArrayList; 47 import java.util.Enumeration; 48 import java.util.Iterator; 49 import java.util.Stack; 50 import java.util.Vector; 51 52 import javax.swing.event.ChangeEvent; 53 import javax.swing.event.ChangeListener; 54 import javax.swing.event.DocumentEvent; 55 import javax.swing.event.UndoableEditEvent; 56 import javax.swing.undo.AbstractUndoableEdit; 57 import javax.swing.undo.UndoableEdit; 58 59 /** 60 * The default implementation of {@link StyledDocument}. The document is 61 * modeled as an {@link Element} tree, which has a {@link SectionElement} as 62 * single root, which has one or more {@link AbstractDocument.BranchElement}s 63 * as paragraph nodes and each paragraph node having one or more 64 * {@link AbstractDocument.LeafElement}s as content nodes. 65 * 66 * @author Michael Koch (konqueror@gmx.de) 67 * @author Roman Kennke (roman@kennke.org) 68 */ 69 public class DefaultStyledDocument extends AbstractDocument implements 70 StyledDocument 71 { 72 73 /** 74 * An {@link UndoableEdit} that can undo attribute changes to an element. 75 * 76 * @author Roman Kennke (kennke@aicas.com) 77 */ 78 public static class AttributeUndoableEdit extends AbstractUndoableEdit 79 { 80 /** 81 * A copy of the old attributes. 82 */ 83 protected AttributeSet copy; 84 85 /** 86 * The new attributes. 87 */ 88 protected AttributeSet newAttributes; 89 90 /** 91 * If the new attributes replaced the old attributes or if they only were 92 * added to them. 93 */ 94 protected boolean isReplacing; 95 96 /** 97 * The element that has changed. 98 */ 99 protected Element element; 100 101 /** 102 * Creates a new <code>AttributeUndoableEdit</code>. 103 * 104 * @param el 105 * the element that changes attributes 106 * @param newAtts 107 * the new attributes 108 * @param replacing 109 * if the new attributes replace the old or only append to them 110 */ AttributeUndoableEdit(Element el, AttributeSet newAtts, boolean replacing)111 public AttributeUndoableEdit(Element el, AttributeSet newAtts, 112 boolean replacing) 113 { 114 element = el; 115 newAttributes = newAtts; 116 isReplacing = replacing; 117 copy = el.getAttributes().copyAttributes(); 118 } 119 120 /** 121 * Undos the attribute change. The <code>copy</code> field is set as 122 * attributes on <code>element</code>. 123 */ undo()124 public void undo() 125 { 126 super.undo(); 127 AttributeSet atts = element.getAttributes(); 128 if (atts instanceof MutableAttributeSet) 129 { 130 MutableAttributeSet mutable = (MutableAttributeSet) atts; 131 mutable.removeAttributes(atts); 132 mutable.addAttributes(copy); 133 } 134 } 135 136 /** 137 * Redos an attribute change. This adds <code>newAttributes</code> to the 138 * <code>element</code>'s attribute set, possibly clearing all attributes 139 * if <code>isReplacing</code> is true. 140 */ redo()141 public void redo() 142 { 143 super.undo(); 144 AttributeSet atts = element.getAttributes(); 145 if (atts instanceof MutableAttributeSet) 146 { 147 MutableAttributeSet mutable = (MutableAttributeSet) atts; 148 if (isReplacing) 149 mutable.removeAttributes(atts); 150 mutable.addAttributes(newAttributes); 151 } 152 } 153 } 154 155 /** 156 * Carries specification information for new {@link Element}s that should be 157 * created in {@link ElementBuffer}. This allows the parsing process to be 158 * decoupled from the <code>Element</code> creation process. 159 */ 160 public static class ElementSpec 161 { 162 /** 163 * This indicates a start tag. This is a possible value for {@link #getType}. 164 */ 165 public static final short StartTagType = 1; 166 167 /** 168 * This indicates an end tag. This is a possible value for {@link #getType}. 169 */ 170 public static final short EndTagType = 2; 171 172 /** 173 * This indicates a content element. This is a possible value for 174 * {@link #getType}. 175 */ 176 public static final short ContentType = 3; 177 178 /** 179 * This indicates that the data associated with this spec should be joined 180 * with what precedes it. This is a possible value for {@link #getDirection}. 181 */ 182 public static final short JoinPreviousDirection = 4; 183 184 /** 185 * This indicates that the data associated with this spec should be joined 186 * with what follows it. This is a possible value for {@link #getDirection}. 187 */ 188 public static final short JoinNextDirection = 5; 189 190 /** 191 * This indicates that the data associated with this spec should be used to 192 * create a new element. This is a possible value for {@link #getDirection}. 193 */ 194 public static final short OriginateDirection = 6; 195 196 /** 197 * This indicates that the data associated with this spec should be joined 198 * to the fractured element. This is a possible value for 199 * {@link #getDirection}. 200 */ 201 public static final short JoinFractureDirection = 7; 202 203 /** 204 * The type of the tag. 205 */ 206 short type; 207 208 /** 209 * The direction of the tag. 210 */ 211 short direction; 212 213 /** 214 * The offset of the content. 215 */ 216 int offset; 217 218 /** 219 * The length of the content. 220 */ 221 int length; 222 223 /** 224 * The actual content. 225 */ 226 char[] content; 227 228 /** 229 * The attributes for the tag. 230 */ 231 AttributeSet attributes; 232 233 /** 234 * Creates a new <code>ElementSpec</code> with no content, length or 235 * offset. This is most useful for start and end tags. 236 * 237 * @param a 238 * the attributes for the element to be created 239 * @param type 240 * the type of the tag 241 */ ElementSpec(AttributeSet a, short type)242 public ElementSpec(AttributeSet a, short type) 243 { 244 this(a, type, 0); 245 } 246 247 /** 248 * Creates a new <code>ElementSpec</code> that specifies the length but 249 * not the offset of an element. Such <code>ElementSpec</code>s are 250 * processed sequentially from a known starting point. 251 * 252 * @param a 253 * the attributes for the element to be created 254 * @param type 255 * the type of the tag 256 * @param len 257 * the length of the element 258 */ ElementSpec(AttributeSet a, short type, int len)259 public ElementSpec(AttributeSet a, short type, int len) 260 { 261 this(a, type, null, 0, len); 262 } 263 264 /** 265 * Creates a new <code>ElementSpec</code> with document content. 266 * 267 * @param a 268 * the attributes for the element to be created 269 * @param type 270 * the type of the tag 271 * @param txt 272 * the actual content 273 * @param offs 274 * the offset into the <code>txt</code> array 275 * @param len 276 * the length of the element 277 */ ElementSpec(AttributeSet a, short type, char[] txt, int offs, int len)278 public ElementSpec(AttributeSet a, short type, char[] txt, int offs, int len) 279 { 280 attributes = a; 281 this.type = type; 282 offset = offs; 283 length = len; 284 content = txt; 285 direction = OriginateDirection; 286 } 287 288 /** 289 * Sets the type of the element. 290 * 291 * @param type 292 * the type of the element to be set 293 */ setType(short type)294 public void setType(short type) 295 { 296 this.type = type; 297 } 298 299 /** 300 * Returns the type of the element. 301 * 302 * @return the type of the element 303 */ getType()304 public short getType() 305 { 306 return type; 307 } 308 309 /** 310 * Sets the direction of the element. 311 * 312 * @param dir 313 * the direction of the element to be set 314 */ setDirection(short dir)315 public void setDirection(short dir) 316 { 317 direction = dir; 318 } 319 320 /** 321 * Returns the direction of the element. 322 * 323 * @return the direction of the element 324 */ getDirection()325 public short getDirection() 326 { 327 return direction; 328 } 329 330 /** 331 * Returns the attributes of the element. 332 * 333 * @return the attributes of the element 334 */ getAttributes()335 public AttributeSet getAttributes() 336 { 337 return attributes; 338 } 339 340 /** 341 * Returns the actual content of the element. 342 * 343 * @return the actual content of the element 344 */ getArray()345 public char[] getArray() 346 { 347 return content; 348 } 349 350 /** 351 * Returns the offset of the content. 352 * 353 * @return the offset of the content 354 */ getOffset()355 public int getOffset() 356 { 357 return offset; 358 } 359 360 /** 361 * Returns the length of the content. 362 * 363 * @return the length of the content 364 */ getLength()365 public int getLength() 366 { 367 return length; 368 } 369 370 /** 371 * Returns a String representation of this <code>ElementSpec</code> 372 * describing the type, direction and length of this 373 * <code>ElementSpec</code>. 374 * 375 * @return a String representation of this <code>ElementSpec</code> 376 */ toString()377 public String toString() 378 { 379 CPStringBuilder b = new CPStringBuilder(); 380 switch (type) 381 { 382 case StartTagType: 383 b.append("StartTag"); 384 break; 385 case EndTagType: 386 b.append("EndTag"); 387 break; 388 case ContentType: 389 b.append("Content"); 390 break; 391 default: 392 b.append("??"); 393 break; 394 } 395 396 b.append(':'); 397 398 switch (direction) 399 { 400 case JoinPreviousDirection: 401 b.append("JoinPrevious"); 402 break; 403 case JoinNextDirection: 404 b.append("JoinNext"); 405 break; 406 case OriginateDirection: 407 b.append("Originate"); 408 break; 409 case JoinFractureDirection: 410 b.append("Fracture"); 411 break; 412 default: 413 b.append("??"); 414 break; 415 } 416 417 b.append(':'); 418 b.append(length); 419 420 return b.toString(); 421 } 422 } 423 424 /** 425 * Performs all <em>structural</code> changes to the <code>Element</code> 426 * hierarchy. This class was implemented with much help from the document: 427 * http://java.sun.com/products/jfc/tsc/articles/text/element_buffer/index.html. 428 */ 429 public class ElementBuffer implements Serializable 430 { 431 /** 432 * Instance of all editing information for an object in the Vector. This class 433 * is used to add information to the DocumentEvent associated with an 434 * insertion/removal/change as well as to store the changes that need to be 435 * made so they can be made all at the same (appropriate) time. 436 */ 437 class Edit 438 { 439 /** The element to edit . */ 440 Element e; 441 442 /** The index of the change. */ 443 int index; 444 445 /** The removed elements. */ 446 ArrayList removed = new ArrayList(); 447 448 /** The added elements. */ 449 ArrayList added = new ArrayList(); 450 451 /** 452 * Indicates if this edit contains a fracture. 453 */ 454 boolean isFracture; 455 456 /** 457 * Creates a new Edit for the specified element at index i. 458 * 459 * @param el the element 460 * @param i the index 461 */ Edit(Element el, int i)462 Edit(Element el, int i) 463 { 464 this(el, i, false); 465 } 466 467 /** 468 * Creates a new Edit for the specified element at index i. 469 * 470 * @param el the element 471 * @param i the index 472 * @param frac if this is a fracture edit or not 473 */ Edit(Element el, int i, boolean frac)474 Edit(Element el, int i, boolean frac) 475 { 476 e = el; 477 index = i; 478 isFracture = frac; 479 } 480 481 } 482 483 /** The serialization UID (compatible with JDK1.5). */ 484 private static final long serialVersionUID = 1688745877691146623L; 485 486 /** The root element of the hierarchy. */ 487 private Element root; 488 489 /** Holds the offset for structural changes. */ 490 private int offset; 491 492 /** Holds the end offset for structural changes. */ 493 private int endOffset; 494 495 /** Holds the length of structural changes. */ 496 private int length; 497 498 /** Holds the position of the change. */ 499 private int pos; 500 501 /** 502 * The parent of the fracture. 503 */ 504 private Element fracturedParent; 505 506 /** 507 * The fractured child. 508 */ 509 private Element fracturedChild; 510 511 /** 512 * Indicates if a fracture has been created. 513 */ 514 private boolean createdFracture; 515 516 /** 517 * The current position in the element tree. This is used for bulk inserts 518 * using ElementSpecs. 519 */ 520 private Stack elementStack; 521 522 private Edit[] insertPath; 523 524 private boolean recreateLeafs; 525 526 /** 527 * Vector that contains all the edits. Maybe replace by a HashMap. 528 */ 529 private ArrayList edits; 530 531 private boolean offsetLastIndex; 532 private boolean offsetLastIndexReplace; 533 534 /** 535 * Creates a new <code>ElementBuffer</code> for the specified 536 * <code>root</code> element. 537 * 538 * @param root 539 * the root element for this <code>ElementBuffer</code> 540 */ ElementBuffer(Element root)541 public ElementBuffer(Element root) 542 { 543 this.root = root; 544 } 545 546 /** 547 * Returns the root element of this <code>ElementBuffer</code>. 548 * 549 * @return the root element of this <code>ElementBuffer</code> 550 */ getRootElement()551 public Element getRootElement() 552 { 553 return root; 554 } 555 556 /** 557 * Removes the content. This method sets some internal parameters and 558 * delegates the work to {@link #removeUpdate}. 559 * 560 * @param offs 561 * the offset from which content is remove 562 * @param len 563 * the length of the removed content 564 * @param ev 565 * the document event that records the changes 566 */ remove(int offs, int len, DefaultDocumentEvent ev)567 public void remove(int offs, int len, DefaultDocumentEvent ev) 568 { 569 prepareEdit(offs, len); 570 removeUpdate(); 571 finishEdit(ev); 572 } 573 574 /** 575 * Updates the element structure of the document in response to removal of 576 * content. It removes the affected {@link Element}s from the document 577 * structure. 578 */ removeUpdate()579 protected void removeUpdate() 580 { 581 removeElements(root, offset, endOffset); 582 } 583 removeElements(Element elem, int rmOffs0, int rmOffs1)584 private boolean removeElements(Element elem, int rmOffs0, int rmOffs1) 585 { 586 boolean ret = false; 587 if (! elem.isLeaf()) 588 { 589 // Update stack for changes. 590 int index0 = elem.getElementIndex(rmOffs0); 591 int index1 = elem.getElementIndex(rmOffs1); 592 elementStack.push(new Edit(elem, index0)); 593 Edit ec = (Edit) elementStack.peek(); 594 595 // If the range is contained by one element, 596 // we just forward the request 597 if (index0 == index1) 598 { 599 Element child0 = elem.getElement(index0); 600 if(rmOffs0 <= child0.getStartOffset() 601 && rmOffs1 >= child0.getEndOffset()) 602 { 603 // Element totally removed. 604 ec.removed.add(child0); 605 } 606 else if (removeElements(child0, rmOffs0, rmOffs1)) 607 { 608 ec.removed.add(child0); 609 } 610 } 611 else 612 { 613 // The removal range spans elements. If we can join 614 // the two endpoints, do it. Otherwise we remove the 615 // interior and forward to the endpoints. 616 Element child0 = elem.getElement(index0); 617 Element child1 = elem.getElement(index1); 618 boolean containsOffs1 = (rmOffs1 < elem.getEndOffset()); 619 if (containsOffs1 && canJoin(child0, child1)) 620 { 621 // Remove and join. 622 for (int i = index0; i <= index1; i++) 623 { 624 ec.removed.add(elem.getElement(i)); 625 } 626 Element e = join(elem, child0, child1, rmOffs0, rmOffs1); 627 ec.added.add(e); 628 } 629 else 630 { 631 // Remove interior and forward. 632 int rmIndex0 = index0 + 1; 633 int rmIndex1 = index1 - 1; 634 if (child0.getStartOffset() == rmOffs0 635 || (index0 == 0 && child0.getStartOffset() > rmOffs0 636 && child0.getEndOffset() <= rmOffs1)) 637 { 638 // Start element completely consumed. 639 child0 = null; 640 rmIndex0 = index0; 641 } 642 if (! containsOffs1) 643 { 644 child1 = null; 645 rmIndex1++; 646 } 647 else if (child1.getStartOffset() == rmOffs1) 648 { 649 // End element not touched. 650 child1 = null; 651 } 652 if (rmIndex0 <= rmIndex1) 653 { 654 ec.index = rmIndex0; 655 } 656 for (int i = rmIndex0; i <= rmIndex1; i++) 657 { 658 ec.removed.add(elem.getElement(i)); 659 } 660 if (child0 != null) 661 { 662 if(removeElements(child0, rmOffs0, rmOffs1)) 663 { 664 ec.removed.add(0, child0); 665 ec.index = index0; 666 } 667 } 668 if (child1 != null) 669 { 670 if(removeElements(child1, rmOffs0, rmOffs1)) 671 { 672 ec.removed.add(child1); 673 } 674 } 675 } 676 } 677 678 // Perform changes. 679 pop(); 680 681 // Return true if we no longer have any children. 682 if(elem.getElementCount() == (ec.removed.size() - ec.added.size())) 683 ret = true; 684 } 685 return ret; 686 } 687 688 /** 689 * Creates a document in response to a call to 690 * {@link DefaultStyledDocument#create(ElementSpec[])}. 691 * 692 * @param len the length of the inserted text 693 * @param data the specs for the elements 694 * @param ev the document event 695 */ create(int len, ElementSpec[] data, DefaultDocumentEvent ev)696 void create(int len, ElementSpec[] data, DefaultDocumentEvent ev) 697 { 698 prepareEdit(offset, len); 699 Element el = root; 700 int index = el.getElementIndex(0); 701 while (! el.isLeaf()) 702 { 703 Element child = el.getElement(index); 704 Edit edit = new Edit(el, index, false); 705 elementStack.push(edit); 706 el = child; 707 index = el.getElementIndex(0); 708 } 709 Edit ed = (Edit) elementStack.peek(); 710 Element child = ed.e.getElement(ed.index); 711 ed.added.add(createLeafElement(ed.e, child.getAttributes(), getLength(), 712 child.getEndOffset())); 713 ed.removed.add(child); 714 while (elementStack.size() > 1) 715 pop(); 716 int n = data.length; 717 718 // Reset root element's attributes. 719 AttributeSet newAtts = null; 720 if (n > 0 && data[0].getType() == ElementSpec.StartTagType) 721 newAtts = data[0].getAttributes(); 722 if (newAtts == null) 723 newAtts = SimpleAttributeSet.EMPTY; 724 MutableAttributeSet mAtts = (MutableAttributeSet) root.getAttributes(); 725 ev.addEdit(new AttributeUndoableEdit(root, newAtts, true)); 726 mAtts.removeAttributes(mAtts); 727 mAtts.addAttributes(newAtts); 728 729 // Insert the specified elements. 730 for (int i = 1; i < n; i++) 731 insertElement(data[i]); 732 733 // Pop remaining stack. 734 while (elementStack.size() > 0) 735 pop(); 736 737 finishEdit(ev); 738 } 739 canJoin(Element e0, Element e1)740 private boolean canJoin(Element e0, Element e1) 741 { 742 boolean ret = false; 743 if ((e0 != null) && (e1 != null)) 744 { 745 // Don't join a leaf to a branch. 746 boolean isLeaf0 = e0.isLeaf(); 747 boolean isLeaf1 = e1.isLeaf(); 748 if(isLeaf0 == isLeaf1) 749 { 750 if (isLeaf0) 751 { 752 // Only join leaves if the attributes match, otherwise 753 // style information will be lost. 754 ret = e0.getAttributes().isEqual(e1.getAttributes()); 755 } 756 else 757 { 758 // Only join non-leafs if the names are equal. This may result 759 // in loss of style information, but this is typically 760 // acceptable for non-leafs. 761 String name0 = e0.getName(); 762 String name1 = e1.getName(); 763 if (name0 != null) 764 ret = name0.equals(name1); 765 else if (name1 != null) 766 ret = name1.equals(name0); 767 else // Both names null. 768 ret = true; 769 } 770 } 771 } 772 return ret; 773 } 774 join(Element p, Element left, Element right, int rmOffs0, int rmOffs1)775 private Element join(Element p, Element left, Element right, int rmOffs0, 776 int rmOffs1) 777 { 778 Element joined = null; 779 if (left.isLeaf() && right.isLeaf()) 780 { 781 joined = createLeafElement(p, left.getAttributes(), 782 left.getStartOffset(), 783 right.getEndOffset()); 784 } 785 else if ((! left.isLeaf()) && (! right.isLeaf())) 786 { 787 // Join two branch elements. This copies the children before 788 // the removal range on the left element, and after the removal 789 // range on the right element. The two elements on the edge 790 // are joined if possible and needed. 791 joined = createBranchElement(p, left.getAttributes()); 792 int ljIndex = left.getElementIndex(rmOffs0); 793 int rjIndex = right.getElementIndex(rmOffs1); 794 Element lj = left.getElement(ljIndex); 795 if (lj.getStartOffset() >= rmOffs0) 796 { 797 lj = null; 798 } 799 Element rj = right.getElement(rjIndex); 800 if (rj.getStartOffset() == rmOffs1) 801 { 802 rj = null; 803 } 804 ArrayList children = new ArrayList(); 805 // Transfer the left. 806 for (int i = 0; i < ljIndex; i++) 807 { 808 children.add(clone(joined, left.getElement(i))); 809 } 810 811 // Transfer the join/middle. 812 if (canJoin(lj, rj)) 813 { 814 Element e = join(joined, lj, rj, rmOffs0, rmOffs1); 815 children.add(e); 816 } 817 else 818 { 819 if (lj != null) 820 { 821 children.add(cloneAsNecessary(joined, lj, rmOffs0, rmOffs1)); 822 } 823 if (rj != null) 824 { 825 children.add(cloneAsNecessary(joined, rj, rmOffs0, rmOffs1)); 826 } 827 } 828 829 // Transfer the right. 830 int n = right.getElementCount(); 831 for (int i = (rj == null) ? rjIndex : rjIndex + 1; i < n; i++) 832 { 833 children.add(clone(joined, right.getElement(i))); 834 } 835 836 // Install the children. 837 Element[] c = new Element[children.size()]; 838 c = (Element[]) children.toArray(c); 839 ((BranchElement) joined).replace(0, 0, c); 840 } 841 else 842 { 843 assert false : "Must not happen"; 844 } 845 return joined; 846 } 847 848 /** 849 * Performs the actual work for {@link #change}. The elements at the 850 * interval boundaries are split up (if necessary) so that the interval 851 * boundaries are located at element boundaries. 852 */ changeUpdate()853 protected void changeUpdate() 854 { 855 boolean didEnd = split(offset, length); 856 if (! didEnd) 857 { 858 // need to do the other end 859 while (elementStack.size() != 0) 860 { 861 pop(); 862 } 863 split(offset + length, 0); 864 } 865 while (elementStack.size() != 0) 866 { 867 pop(); 868 } 869 } 870 871 /** 872 * Modifies the element structure so that the specified interval starts and 873 * ends at an element boundary. Content and paragraph elements are split and 874 * created as necessary. This also updates the 875 * <code>DefaultDocumentEvent</code> to reflect the structural changes. 876 * The bulk work is delegated to {@link #changeUpdate()}. 877 * 878 * @param offset 879 * the start index of the interval to be changed 880 * @param length 881 * the length of the interval to be changed 882 * @param ev 883 * the <code>DefaultDocumentEvent</code> describing the change 884 */ change(int offset, int length, DefaultDocumentEvent ev)885 public void change(int offset, int length, DefaultDocumentEvent ev) 886 { 887 prepareEdit(offset, length); 888 changeUpdate(); 889 finishEdit(ev); 890 } 891 892 /** 893 * Creates and returns a deep clone of the specified <code>clonee</code> 894 * with the specified parent as new parent. 895 * 896 * This method can only clone direct instances of {@link BranchElement} 897 * or {@link LeafElement}. 898 * 899 * @param parent the new parent 900 * @param clonee the element to be cloned 901 * 902 * @return the cloned element with the new parent 903 */ clone(Element parent, Element clonee)904 public Element clone(Element parent, Element clonee) 905 { 906 Element clone = clonee; 907 // We can only handle AbstractElements here. 908 if (clonee instanceof BranchElement) 909 { 910 BranchElement branchEl = (BranchElement) clonee; 911 BranchElement branchClone = 912 new BranchElement(parent, branchEl.getAttributes()); 913 // Also clone all of the children. 914 int numChildren = branchClone.getElementCount(); 915 Element[] cloneChildren = new Element[numChildren]; 916 for (int i = 0; i < numChildren; ++i) 917 { 918 cloneChildren[i] = clone(branchClone, 919 branchClone.getElement(i)); 920 } 921 branchClone.replace(0, 0, cloneChildren); 922 clone = branchClone; 923 } 924 else if (clonee instanceof LeafElement) 925 { 926 clone = new LeafElement(parent, clonee.getAttributes(), 927 clonee.getStartOffset(), 928 clonee.getEndOffset()); 929 } 930 return clone; 931 } 932 cloneAsNecessary(Element parent, Element clonee, int rmOffs0, int rmOffs1)933 private Element cloneAsNecessary(Element parent, Element clonee, 934 int rmOffs0, int rmOffs1) 935 { 936 Element cloned; 937 if (clonee.isLeaf()) 938 { 939 cloned = createLeafElement(parent, clonee.getAttributes(), 940 clonee.getStartOffset(), 941 clonee.getEndOffset()); 942 } 943 else 944 { 945 Element e = createBranchElement(parent, clonee.getAttributes()); 946 int n = clonee.getElementCount(); 947 ArrayList childrenList = new ArrayList(n); 948 for (int i = 0; i < n; i++) 949 { 950 Element elem = clonee.getElement(i); 951 if (elem.getStartOffset() < rmOffs0 952 || elem.getEndOffset() > rmOffs1) 953 { 954 childrenList.add(cloneAsNecessary(e, elem, rmOffs0, 955 rmOffs1)); 956 } 957 } 958 Element[] children = new Element[childrenList.size()]; 959 children = (Element[]) childrenList.toArray(children); 960 ((BranchElement) e).replace(0, 0, children); 961 cloned = e; 962 } 963 return cloned; 964 } 965 966 /** 967 * Inserts new <code>Element</code> in the document at the specified 968 * position. Most of the work is done by {@link #insertUpdate}, after some 969 * fields have been prepared for it. 970 * 971 * @param offset 972 * the location in the document at which the content is inserted 973 * @param length 974 * the length of the inserted content 975 * @param data 976 * the element specifications for the content to be inserted 977 * @param ev 978 * the document event that is updated to reflect the structural 979 * changes 980 */ insert(int offset, int length, ElementSpec[] data, DefaultDocumentEvent ev)981 public void insert(int offset, int length, ElementSpec[] data, 982 DefaultDocumentEvent ev) 983 { 984 if (length > 0) 985 { 986 prepareEdit(offset, length); 987 insertUpdate(data); 988 finishEdit(ev); 989 } 990 } 991 992 /** 993 * Prepares the state of this object for performing an insert. 994 * 995 * @param offset the offset at which is inserted 996 * @param length the length of the inserted region 997 */ prepareEdit(int offset, int length)998 private void prepareEdit(int offset, int length) 999 { 1000 this.offset = offset; 1001 this.pos = offset; 1002 this.endOffset = offset + length; 1003 this.length = length; 1004 1005 if (edits == null) 1006 edits = new ArrayList(); 1007 else 1008 edits.clear(); 1009 1010 if (elementStack == null) 1011 elementStack = new Stack(); 1012 else 1013 elementStack.clear(); 1014 1015 fracturedParent = null; 1016 fracturedChild = null; 1017 offsetLastIndex = false; 1018 offsetLastIndexReplace = false; 1019 } 1020 1021 /** 1022 * Finishes an insert. This applies all changes and updates 1023 * the DocumentEvent. 1024 * 1025 * @param ev the document event 1026 */ finishEdit(DefaultDocumentEvent ev)1027 private void finishEdit(DefaultDocumentEvent ev) 1028 { 1029 // This for loop applies all the changes that were made and updates the 1030 // DocumentEvent. 1031 for (Iterator i = edits.iterator(); i.hasNext();) 1032 { 1033 Edit edits = (Edit) i.next(); 1034 Element[] removed = new Element[edits.removed.size()]; 1035 removed = (Element[]) edits.removed.toArray(removed); 1036 Element[] added = new Element[edits.added.size()]; 1037 added = (Element[]) edits.added.toArray(added); 1038 int index = edits.index; 1039 BranchElement parent = (BranchElement) edits.e; 1040 parent.replace(index, removed.length, added); 1041 ElementEdit ee = new ElementEdit(parent, index, removed, added); 1042 ev.addEdit(ee); 1043 } 1044 edits.clear(); 1045 elementStack.clear(); 1046 } 1047 1048 /** 1049 * Inserts new content. 1050 * 1051 * @param data the element specifications for the elements to be inserted 1052 */ insertUpdate(ElementSpec[] data)1053 protected void insertUpdate(ElementSpec[] data) 1054 { 1055 // Push the current path to the stack. 1056 Element current = root; 1057 int index = current.getElementIndex(offset); 1058 while (! current.isLeaf()) 1059 { 1060 Element child = current.getElement(index); 1061 int editIndex = child.isLeaf() ? index : index + 1; 1062 Edit edit = new Edit(current, editIndex); 1063 elementStack.push(edit); 1064 current = child; 1065 index = current.getElementIndex(offset); 1066 } 1067 1068 // Create a copy of the original path. 1069 insertPath = new Edit[elementStack.size()]; 1070 insertPath = (Edit[]) elementStack.toArray(insertPath); 1071 1072 // No fracture yet. 1073 createdFracture = false; 1074 1075 // Insert first content tag. 1076 int i = 0; 1077 recreateLeafs = false; 1078 int type = data[0].getType(); 1079 if (type == ElementSpec.ContentType) 1080 { 1081 // If the first tag is content we must treat it separately to allow 1082 // for joining properly to previous Elements and to ensure that 1083 // no extra LeafElements are erroneously inserted. 1084 insertFirstContentTag(data); 1085 pos += data[0].length; 1086 i = 1; 1087 } 1088 else 1089 { 1090 createFracture(data); 1091 i = 0; 1092 } 1093 1094 // Handle each ElementSpec individually. 1095 for (; i < data.length; i++) 1096 { 1097 insertElement(data[i]); 1098 } 1099 1100 // Fracture if we haven't done yet. 1101 if (! createdFracture) 1102 fracture(-1); 1103 1104 // Pop the remaining stack. 1105 while (elementStack.size() != 0) 1106 pop(); 1107 1108 // Offset last index if necessary. 1109 if (offsetLastIndex && offsetLastIndexReplace) 1110 insertPath[insertPath.length - 1].index++; 1111 1112 // Make sure we havea an Edit for each path item that has a change. 1113 for (int p = insertPath.length - 1; p >= 0; p--) 1114 { 1115 Edit edit = insertPath[p]; 1116 if (edit.e == fracturedParent) 1117 edit.added.add(fracturedChild); 1118 if ((edit.added.size() > 0 || edit.removed.size() > 0) 1119 && ! edits.contains(edit)) 1120 edits.add(edit); 1121 } 1122 1123 // Remove element that would be created by an insert at 0 with 1124 // an initial end tag. 1125 if (offset == 0 && fracturedParent != null 1126 && data[0].getType() == ElementSpec.EndTagType) 1127 { 1128 int p; 1129 for (p = 0; 1130 p < data.length && data[p].getType() == ElementSpec.EndTagType; 1131 p++) 1132 ; 1133 1134 Edit edit = insertPath[insertPath.length - p - 1]; 1135 edit.index--; 1136 edit.removed.add(0, edit.e.getElement(edit.index)); 1137 } 1138 } 1139 pop()1140 private void pop() 1141 { 1142 Edit edit = (Edit) elementStack.peek(); 1143 elementStack.pop(); 1144 if ((edit.added.size() > 0) || (edit.removed.size() > 0)) 1145 { 1146 edits.add(edit); 1147 } 1148 else if (! elementStack.isEmpty()) 1149 { 1150 Element e = edit.e; 1151 if (e.getElementCount() == 0) 1152 { 1153 // If we pushed a branch element that didn't get 1154 // used, make sure its not marked as having been added. 1155 edit = (Edit) elementStack.peek(); 1156 edit.added.remove(e); 1157 } 1158 } 1159 } 1160 insertElement(ElementSpec spec)1161 private void insertElement(ElementSpec spec) 1162 { 1163 if (elementStack.isEmpty()) 1164 return; 1165 1166 Edit edit = (Edit) elementStack.peek(); 1167 switch (spec.getType()) 1168 { 1169 case ElementSpec.StartTagType: 1170 switch (spec.getDirection()) 1171 { 1172 case ElementSpec.JoinFractureDirection: 1173 // Fracture the tree and ensure the appropriate element 1174 // is on top of the stack. 1175 if (! createdFracture) 1176 { 1177 fracture(elementStack.size() - 1); 1178 } 1179 if (! edit.isFracture) 1180 { 1181 // If the parent isn't a fracture, then the fracture is 1182 // in fracturedChild. 1183 Edit newEdit = new Edit(fracturedChild, 0, true); 1184 elementStack.push(newEdit); 1185 } 1186 else 1187 { 1188 // Otherwise use the parent's first child. 1189 Element el = edit.e.getElement(0); 1190 Edit newEdit = new Edit(el, 0, true); 1191 elementStack.push(newEdit); 1192 } 1193 break; 1194 case ElementSpec.JoinNextDirection: 1195 // Push the next paragraph element onto the stack so 1196 // future insertions are added to it. 1197 Element parent = edit.e.getElement(edit.index); 1198 if (parent.isLeaf()) 1199 { 1200 if (edit.index + 1 < edit.e.getElementCount()) 1201 parent = edit.e.getElement(edit.index + 1); 1202 else 1203 assert false; // Must not happen. 1204 } 1205 elementStack.push(new Edit(parent, 0, true)); 1206 break; 1207 default: 1208 Element branch = createBranchElement(edit.e, 1209 spec.getAttributes()); 1210 edit.added.add(branch); 1211 elementStack.push(new Edit(branch, 0)); 1212 break; 1213 } 1214 break; 1215 case ElementSpec.EndTagType: 1216 pop(); 1217 break; 1218 case ElementSpec.ContentType: 1219 insertContentTag(spec, edit); 1220 break; 1221 } 1222 } 1223 1224 /** 1225 * Inserts the first tag into the document. 1226 * 1227 * @param data - 1228 * the data to be inserted. 1229 */ insertFirstContentTag(ElementSpec[] data)1230 private void insertFirstContentTag(ElementSpec[] data) 1231 { 1232 ElementSpec first = data[0]; 1233 Edit edit = (Edit) elementStack.peek(); 1234 Element current = edit.e.getElement(edit.index); 1235 int firstEndOffset = offset + first.length; 1236 boolean onlyContent = data.length == 1; 1237 switch (first.getDirection()) 1238 { 1239 case ElementSpec.JoinPreviousDirection: 1240 if (current.getEndOffset() != firstEndOffset && ! onlyContent) 1241 { 1242 Element newEl1 = createLeafElement(edit.e, 1243 current.getAttributes(), 1244 current.getStartOffset(), 1245 firstEndOffset); 1246 edit.added.add(newEl1); 1247 edit.removed.add(current); 1248 if (current.getEndOffset() != endOffset) 1249 recreateLeafs = true; 1250 else 1251 offsetLastIndex = true; 1252 } 1253 else 1254 { 1255 offsetLastIndex = true; 1256 offsetLastIndexReplace = true; 1257 } 1258 break; 1259 case ElementSpec.JoinNextDirection: 1260 if (offset != 0) 1261 { 1262 Element newEl1 = createLeafElement(edit.e, 1263 current.getAttributes(), 1264 current.getStartOffset(), 1265 offset); 1266 edit.added.add(newEl1); 1267 Element next = edit.e.getElement(edit.index + 1); 1268 if (onlyContent) 1269 newEl1 = createLeafElement(edit.e, next.getAttributes(), 1270 offset, next.getEndOffset()); 1271 else 1272 { 1273 newEl1 = createLeafElement(edit.e, next.getAttributes(), 1274 offset, firstEndOffset); 1275 } 1276 edit.added.add(newEl1); 1277 edit.removed.add(current); 1278 edit.removed.add(next); 1279 } 1280 break; 1281 default: // OriginateDirection. 1282 if (current.getStartOffset() != offset) 1283 { 1284 Element newEl = createLeafElement(edit.e, 1285 current.getAttributes(), 1286 current.getStartOffset(), 1287 offset); 1288 edit.added.add(newEl); 1289 } 1290 edit.removed.add(current); 1291 Element newEl1 = createLeafElement(edit.e, first.getAttributes(), 1292 offset, firstEndOffset); 1293 edit.added.add(newEl1); 1294 if (current.getEndOffset() != endOffset) 1295 recreateLeafs = true; 1296 else 1297 offsetLastIndex = true; 1298 break; 1299 } 1300 } 1301 1302 /** 1303 * Inserts a content element into the document structure. 1304 * 1305 * @param tag - 1306 * the element spec 1307 */ insertContentTag(ElementSpec tag, Edit edit)1308 private void insertContentTag(ElementSpec tag, Edit edit) 1309 { 1310 int len = tag.getLength(); 1311 int dir = tag.getDirection(); 1312 if (dir == ElementSpec.JoinNextDirection) 1313 { 1314 if (! edit.isFracture) 1315 { 1316 Element first = null; 1317 if (insertPath != null) 1318 { 1319 for (int p = insertPath.length - 1; p >= 0; p--) 1320 { 1321 if (insertPath[p] == edit) 1322 { 1323 if (p != insertPath.length - 1) 1324 first = edit.e.getElement(edit.index); 1325 break; 1326 } 1327 } 1328 } 1329 if (first == null) 1330 first = edit.e.getElement(edit.index + 1); 1331 Element leaf = createLeafElement(edit.e, first.getAttributes(), 1332 pos, first.getEndOffset()); 1333 edit.added.add(leaf); 1334 edit.removed.add(first); 1335 } 1336 else 1337 { 1338 Element first = edit.e.getElement(0); 1339 Element leaf = createLeafElement(edit.e, first.getAttributes(), 1340 pos, first.getEndOffset()); 1341 edit.added.add(leaf); 1342 edit.removed.add(first); 1343 } 1344 } 1345 else 1346 { 1347 Element leaf = createLeafElement(edit.e, tag.getAttributes(), pos, 1348 pos + len); 1349 edit.added.add(leaf); 1350 } 1351 1352 pos += len; 1353 1354 } 1355 1356 /** 1357 * This method fractures bottomost leaf in the elementStack. This 1358 * happens when the first inserted tag is not content. 1359 * 1360 * @param data 1361 * the ElementSpecs used for the entire insertion 1362 */ createFracture(ElementSpec[] data)1363 private void createFracture(ElementSpec[] data) 1364 { 1365 Edit edit = (Edit) elementStack.peek(); 1366 Element child = edit.e.getElement(edit.index); 1367 if (offset != 0) 1368 { 1369 Element newChild = createLeafElement(edit.e, child.getAttributes(), 1370 child.getStartOffset(), offset); 1371 edit.added.add(newChild); 1372 } 1373 edit.removed.add(child); 1374 if (child.getEndOffset() != endOffset) 1375 recreateLeafs = true; 1376 else 1377 offsetLastIndex = true; 1378 } 1379 fracture(int depth)1380 private void fracture(int depth) 1381 { 1382 int len = insertPath.length; 1383 int lastIndex = -1; 1384 boolean recreate = recreateLeafs; 1385 Edit lastEdit = insertPath[len - 1]; 1386 boolean childChanged = lastEdit.index + 1 < lastEdit.e.getElementCount(); 1387 int deepestChangedIndex = recreate ? len : - 1; 1388 int lastChangedIndex = len - 1; 1389 createdFracture = true; 1390 for (int i = len - 2; i >= 0; i--) 1391 { 1392 Edit edit = insertPath[i]; 1393 if (edit.added.size() > 0 || i == depth) 1394 { 1395 lastIndex = i; 1396 if (! recreate && childChanged) 1397 { 1398 recreate = true; 1399 if (deepestChangedIndex == -1) 1400 deepestChangedIndex = lastChangedIndex + 1; 1401 } 1402 } 1403 if (! childChanged && edit.index < edit.e.getElementCount()) 1404 { 1405 childChanged = true; 1406 lastChangedIndex = i; 1407 } 1408 } 1409 if (recreate) 1410 { 1411 if (lastIndex == -1) 1412 lastIndex = len - 1; 1413 recreate(lastIndex, deepestChangedIndex); 1414 } 1415 } 1416 recreate(int startIndex, int endIndex)1417 private void recreate(int startIndex, int endIndex) 1418 { 1419 // Recreate the element representing the inserted index. 1420 Edit edit = insertPath[startIndex]; 1421 Element child; 1422 Element newChild; 1423 int changeLength = insertPath.length; 1424 1425 if (startIndex + 1 == changeLength) 1426 child = edit.e.getElement(edit.index); 1427 else 1428 child = edit.e.getElement(edit.index - 1); 1429 1430 if(child.isLeaf()) 1431 { 1432 newChild = createLeafElement(edit.e, child.getAttributes(), 1433 Math.max(endOffset, child.getStartOffset()), 1434 child.getEndOffset()); 1435 } 1436 else 1437 { 1438 newChild = createBranchElement(edit.e, child.getAttributes()); 1439 } 1440 fracturedParent = edit.e; 1441 fracturedChild = newChild; 1442 1443 // Recreate all the elements to the right of the insertion point. 1444 Element parent = newChild; 1445 while (++startIndex < endIndex) 1446 { 1447 boolean isEnd = (startIndex + 1) == endIndex; 1448 boolean isEndLeaf = (startIndex + 1) == changeLength; 1449 1450 // Create the newChild, a duplicate of the elment at 1451 // index. This isn't done if isEnd and offsetLastIndex are true 1452 // indicating a join previous was done. 1453 edit = insertPath[startIndex]; 1454 1455 // Determine the child to duplicate, won't have to duplicate 1456 // if at end of fracture, or offseting index. 1457 if(isEnd) 1458 { 1459 if(offsetLastIndex || ! isEndLeaf) 1460 child = null; 1461 else 1462 child = edit.e.getElement(edit.index); 1463 } 1464 else 1465 { 1466 child = edit.e.getElement(edit.index - 1); 1467 } 1468 1469 // Duplicate it. 1470 if(child != null) 1471 { 1472 if(child.isLeaf()) 1473 { 1474 newChild = createLeafElement(parent, child.getAttributes(), 1475 Math.max(endOffset, child.getStartOffset()), 1476 child.getEndOffset()); 1477 } 1478 else 1479 { 1480 newChild = createBranchElement(parent, 1481 child.getAttributes()); 1482 } 1483 } 1484 else 1485 newChild = null; 1486 1487 // Recreate the remaining children (there may be none). 1488 int childrenToMove = edit.e.getElementCount() - edit.index; 1489 Element[] children; 1490 int moveStartIndex; 1491 int childStartIndex = 1; 1492 1493 if (newChild == null) 1494 { 1495 // Last part of fracture. 1496 if (isEndLeaf) 1497 { 1498 childrenToMove--; 1499 moveStartIndex = edit.index + 1; 1500 } 1501 else 1502 { 1503 moveStartIndex = edit.index; 1504 } 1505 childStartIndex = 0; 1506 children = new Element[childrenToMove]; 1507 } 1508 else 1509 { 1510 if (! isEnd) 1511 { 1512 // Branch. 1513 childrenToMove++; 1514 moveStartIndex = edit.index; 1515 } 1516 else 1517 { 1518 // Last leaf, need to recreate part of it. 1519 moveStartIndex = edit.index + 1; 1520 } 1521 children = new Element[childrenToMove]; 1522 children[0] = newChild; 1523 } 1524 1525 for (int c = childStartIndex; c < childrenToMove; c++) 1526 { 1527 Element toMove = edit.e.getElement(moveStartIndex++); 1528 children[c] = recreateFracturedElement(parent, toMove); 1529 edit.removed.add(toMove); 1530 } 1531 ((BranchElement) parent).replace(0, 0, children); 1532 parent = newChild; 1533 } 1534 1535 } 1536 recreateFracturedElement(Element parent, Element toCopy)1537 private Element recreateFracturedElement(Element parent, Element toCopy) 1538 { 1539 Element recreated; 1540 if(toCopy.isLeaf()) 1541 { 1542 recreated = createLeafElement(parent, toCopy.getAttributes(), 1543 Math.max(toCopy.getStartOffset(), endOffset), 1544 toCopy.getEndOffset()); 1545 } 1546 else 1547 { 1548 Element newParent = createBranchElement(parent, 1549 toCopy.getAttributes()); 1550 int childCount = toCopy.getElementCount(); 1551 Element[] newChildren = new Element[childCount]; 1552 for (int i = 0; i < childCount; i++) 1553 { 1554 newChildren[i] = recreateFracturedElement(newParent, 1555 toCopy.getElement(i)); 1556 } 1557 ((BranchElement) newParent).replace(0, 0, newChildren); 1558 recreated = newParent; 1559 } 1560 return recreated; 1561 } 1562 split(int offs, int len)1563 private boolean split(int offs, int len) 1564 { 1565 boolean splitEnd = false; 1566 // Push the path to the stack. 1567 Element e = root; 1568 int index = e.getElementIndex(offs); 1569 while (! e.isLeaf()) 1570 { 1571 elementStack.push(new Edit(e, index)); 1572 e = e.getElement(index); 1573 index = e.getElementIndex(offs); 1574 } 1575 1576 Edit ec = (Edit) elementStack.peek(); 1577 Element child = ec.e.getElement(ec.index); 1578 // Make sure there is something to do. If the 1579 // offset is already at a boundary then there is 1580 // nothing to do. 1581 if (child.getStartOffset() < offs && offs < child.getEndOffset()) 1582 { 1583 // We need to split, now see if the other end is within 1584 // the same parent. 1585 int index0 = ec.index; 1586 int index1 = index0; 1587 if (((offs + len) < ec.e.getEndOffset()) && (len != 0)) 1588 { 1589 // It's a range split in the same parent. 1590 index1 = ec.e.getElementIndex(offs+len); 1591 if (index1 == index0) 1592 { 1593 // It's a three-way split. 1594 ec.removed.add(child); 1595 e = createLeafElement(ec.e, child.getAttributes(), 1596 child.getStartOffset(), offs); 1597 ec.added.add(e); 1598 e = createLeafElement(ec.e, child.getAttributes(), 1599 offs, offs + len); 1600 ec.added.add(e); 1601 e = createLeafElement(ec.e, child.getAttributes(), 1602 offs + len, child.getEndOffset()); 1603 ec.added.add(e); 1604 return true; 1605 } 1606 else 1607 { 1608 child = ec.e.getElement(index1); 1609 if ((offs + len) == child.getStartOffset()) 1610 { 1611 // End is already on a boundary. 1612 index1 = index0; 1613 } 1614 } 1615 splitEnd = true; 1616 } 1617 1618 // Split the first location. 1619 pos = offs; 1620 child = ec.e.getElement(index0); 1621 ec.removed.add(child); 1622 e = createLeafElement(ec.e, child.getAttributes(), 1623 child.getStartOffset(), pos); 1624 ec.added.add(e); 1625 e = createLeafElement(ec.e, child.getAttributes(), 1626 pos, child.getEndOffset()); 1627 ec.added.add(e); 1628 1629 // Pick up things in the middle. 1630 for (int i = index0 + 1; i < index1; i++) 1631 { 1632 child = ec.e.getElement(i); 1633 ec.removed.add(child); 1634 ec.added.add(child); 1635 } 1636 1637 if (index1 != index0) 1638 { 1639 child = ec.e.getElement(index1); 1640 pos = offs + len; 1641 ec.removed.add(child); 1642 e = createLeafElement(ec.e, child.getAttributes(), 1643 child.getStartOffset(), pos); 1644 ec.added.add(e); 1645 e = createLeafElement(ec.e, child.getAttributes(), 1646 pos, child.getEndOffset()); 1647 1648 ec.added.add(e); 1649 } 1650 } 1651 return splitEnd; 1652 1653 } 1654 1655 } 1656 1657 1658 /** 1659 * An element type for sections. This is a simple BranchElement with a unique 1660 * name. 1661 */ 1662 protected class SectionElement extends BranchElement 1663 { 1664 /** 1665 * Creates a new SectionElement. 1666 */ SectionElement()1667 public SectionElement() 1668 { 1669 super(null, null); 1670 } 1671 1672 /** 1673 * Returns the name of the element. This method always returns 1674 * "section". 1675 * 1676 * @return the name of the element 1677 */ getName()1678 public String getName() 1679 { 1680 return SectionElementName; 1681 } 1682 } 1683 1684 /** 1685 * Receives notification when any of the document's style changes and calls 1686 * {@link DefaultStyledDocument#styleChanged(Style)}. 1687 * 1688 * @author Roman Kennke (kennke@aicas.com) 1689 */ 1690 private class StyleChangeListener implements ChangeListener 1691 { 1692 1693 /** 1694 * Receives notification when any of the document's style changes and calls 1695 * {@link DefaultStyledDocument#styleChanged(Style)}. 1696 * 1697 * @param event 1698 * the change event 1699 */ stateChanged(ChangeEvent event)1700 public void stateChanged(ChangeEvent event) 1701 { 1702 Style style = (Style) event.getSource(); 1703 styleChanged(style); 1704 } 1705 } 1706 1707 /** The serialization UID (compatible with JDK1.5). */ 1708 private static final long serialVersionUID = 940485415728614849L; 1709 1710 /** 1711 * The default size to use for new content buffers. 1712 */ 1713 public static final int BUFFER_SIZE_DEFAULT = 4096; 1714 1715 /** 1716 * The <code>EditorBuffer</code> that is used to manage to 1717 * <code>Element</code> hierarchy. 1718 */ 1719 protected DefaultStyledDocument.ElementBuffer buffer; 1720 1721 /** 1722 * Listens for changes on this document's styles and notifies styleChanged(). 1723 */ 1724 private StyleChangeListener styleChangeListener; 1725 1726 /** 1727 * Creates a new <code>DefaultStyledDocument</code>. 1728 */ DefaultStyledDocument()1729 public DefaultStyledDocument() 1730 { 1731 this(new GapContent(BUFFER_SIZE_DEFAULT), new StyleContext()); 1732 } 1733 1734 /** 1735 * Creates a new <code>DefaultStyledDocument</code> that uses the specified 1736 * {@link StyleContext}. 1737 * 1738 * @param context 1739 * the <code>StyleContext</code> to use 1740 */ DefaultStyledDocument(StyleContext context)1741 public DefaultStyledDocument(StyleContext context) 1742 { 1743 this(new GapContent(BUFFER_SIZE_DEFAULT), context); 1744 } 1745 1746 /** 1747 * Creates a new <code>DefaultStyledDocument</code> that uses the specified 1748 * {@link StyleContext} and {@link Content} buffer. 1749 * 1750 * @param content 1751 * the <code>Content</code> buffer to use 1752 * @param context 1753 * the <code>StyleContext</code> to use 1754 */ DefaultStyledDocument(AbstractDocument.Content content, StyleContext context)1755 public DefaultStyledDocument(AbstractDocument.Content content, 1756 StyleContext context) 1757 { 1758 super(content, context); 1759 buffer = new ElementBuffer(createDefaultRoot()); 1760 setLogicalStyle(0, context.getStyle(StyleContext.DEFAULT_STYLE)); 1761 } 1762 1763 /** 1764 * Adds a style into the style hierarchy. Unspecified style attributes can be 1765 * resolved in the <code>parent</code> style, if one is specified. While it 1766 * is legal to add nameless styles (<code>nm == null</code), 1767 * you must be aware that the client application is then responsible 1768 * for managing the style hierarchy, since unnamed styles cannot be 1769 * looked up by their name. 1770 * 1771 * @param nm the name of the style or <code>null</code> if the style should 1772 * be unnamed 1773 * @param parent the parent in which unspecified style attributes are 1774 * resolved, or <code>null</code> if that is not necessary 1775 * 1776 * @return the newly created <code>Style</code> 1777 */ addStyle(String nm, Style parent)1778 public Style addStyle(String nm, Style parent) 1779 { 1780 StyleContext context = (StyleContext) getAttributeContext(); 1781 Style newStyle = context.addStyle(nm, parent); 1782 1783 // Register change listener. 1784 if (styleChangeListener == null) 1785 styleChangeListener = new StyleChangeListener(); 1786 newStyle.addChangeListener(styleChangeListener); 1787 1788 return newStyle; 1789 } 1790 1791 /** 1792 * Create the default root element for this kind of <code>Document</code>. 1793 * 1794 * @return the default root element for this kind of <code>Document</code> 1795 */ createDefaultRoot()1796 protected AbstractDocument.AbstractElement createDefaultRoot() 1797 { 1798 Element[] tmp; 1799 SectionElement section = new SectionElement(); 1800 1801 BranchElement paragraph = new BranchElement(section, null); 1802 tmp = new Element[1]; 1803 tmp[0] = paragraph; 1804 section.replace(0, 0, tmp); 1805 1806 Element leaf = new LeafElement(paragraph, null, 0, 1); 1807 tmp = new Element[1]; 1808 tmp[0] = leaf; 1809 paragraph.replace(0, 0, tmp); 1810 1811 return section; 1812 } 1813 1814 /** 1815 * Returns the <code>Element</code> that corresponds to the character at the 1816 * specified position. 1817 * 1818 * @param position 1819 * the position of which we query the corresponding 1820 * <code>Element</code> 1821 * @return the <code>Element</code> that corresponds to the character at the 1822 * specified position 1823 */ getCharacterElement(int position)1824 public Element getCharacterElement(int position) 1825 { 1826 Element element = getDefaultRootElement(); 1827 1828 while (!element.isLeaf()) 1829 { 1830 int index = element.getElementIndex(position); 1831 element = element.getElement(index); 1832 } 1833 1834 return element; 1835 } 1836 1837 /** 1838 * Extracts a background color from a set of attributes. 1839 * 1840 * @param attributes 1841 * the attributes from which to get a background color 1842 * @return the background color that correspond to the attributes 1843 */ getBackground(AttributeSet attributes)1844 public Color getBackground(AttributeSet attributes) 1845 { 1846 StyleContext context = (StyleContext) getAttributeContext(); 1847 return context.getBackground(attributes); 1848 } 1849 1850 /** 1851 * Returns the default root element. 1852 * 1853 * @return the default root element 1854 */ getDefaultRootElement()1855 public Element getDefaultRootElement() 1856 { 1857 return buffer.getRootElement(); 1858 } 1859 1860 /** 1861 * Extracts a font from a set of attributes. 1862 * 1863 * @param attributes 1864 * the attributes from which to get a font 1865 * @return the font that correspond to the attributes 1866 */ getFont(AttributeSet attributes)1867 public Font getFont(AttributeSet attributes) 1868 { 1869 StyleContext context = (StyleContext) getAttributeContext(); 1870 return context.getFont(attributes); 1871 } 1872 1873 /** 1874 * Extracts a foreground color from a set of attributes. 1875 * 1876 * @param attributes 1877 * the attributes from which to get a foreground color 1878 * @return the foreground color that correspond to the attributes 1879 */ getForeground(AttributeSet attributes)1880 public Color getForeground(AttributeSet attributes) 1881 { 1882 StyleContext context = (StyleContext) getAttributeContext(); 1883 return context.getForeground(attributes); 1884 } 1885 1886 /** 1887 * Returns the logical <code>Style</code> for the specified position. 1888 * 1889 * @param position 1890 * the position from which to query to logical style 1891 * @return the logical <code>Style</code> for the specified position 1892 */ getLogicalStyle(int position)1893 public Style getLogicalStyle(int position) 1894 { 1895 Element paragraph = getParagraphElement(position); 1896 AttributeSet attributes = paragraph.getAttributes(); 1897 AttributeSet a = attributes.getResolveParent(); 1898 // If the resolve parent is not of type Style, we return null. 1899 if (a instanceof Style) 1900 return (Style) a; 1901 return null; 1902 } 1903 1904 /** 1905 * Returns the paragraph element for the specified position. If the position 1906 * is outside the bounds of the document's root element, then the closest 1907 * element is returned. That is the last paragraph if 1908 * <code>position >= endIndex</code> or the first paragraph if 1909 * <code>position < startIndex</code>. 1910 * 1911 * @param position 1912 * the position for which to query the paragraph element 1913 * @return the paragraph element for the specified position 1914 */ getParagraphElement(int position)1915 public Element getParagraphElement(int position) 1916 { 1917 Element e = getDefaultRootElement(); 1918 while (!e.isLeaf()) 1919 e = e.getElement(e.getElementIndex(position)); 1920 1921 if (e != null) 1922 return e.getParentElement(); 1923 return e; 1924 } 1925 1926 /** 1927 * Looks up and returns a named <code>Style</code>. 1928 * 1929 * @param nm 1930 * the name of the <code>Style</code> 1931 * @return the found <code>Style</code> of <code>null</code> if no such 1932 * <code>Style</code> exists 1933 */ getStyle(String nm)1934 public Style getStyle(String nm) 1935 { 1936 StyleContext context = (StyleContext) getAttributeContext(); 1937 return context.getStyle(nm); 1938 } 1939 1940 /** 1941 * Removes a named <code>Style</code> from the style hierarchy. 1942 * 1943 * @param nm 1944 * the name of the <code>Style</code> to be removed 1945 */ removeStyle(String nm)1946 public void removeStyle(String nm) 1947 { 1948 StyleContext context = (StyleContext) getAttributeContext(); 1949 context.removeStyle(nm); 1950 } 1951 1952 /** 1953 * Sets text attributes for the fragment specified by <code>offset</code> 1954 * and <code>length</code>. 1955 * 1956 * @param offset 1957 * the start offset of the fragment 1958 * @param length 1959 * the length of the fragment 1960 * @param attributes 1961 * the text attributes to set 1962 * @param replace 1963 * if <code>true</code>, the attributes of the current selection 1964 * are overridden, otherwise they are merged 1965 */ setCharacterAttributes(int offset, int length, AttributeSet attributes, boolean replace)1966 public void setCharacterAttributes(int offset, int length, 1967 AttributeSet attributes, boolean replace) 1968 { 1969 // Exit early if length is 0, so no DocumentEvent is created or fired. 1970 if (length == 0) 1971 return; 1972 try 1973 { 1974 // Must obtain a write lock for this method. writeLock() and 1975 // writeUnlock() should always be in try/finally block to make 1976 // sure that locking happens in a balanced manner. 1977 writeLock(); 1978 DefaultDocumentEvent ev = new DefaultDocumentEvent(offset, 1979 length, 1980 DocumentEvent.EventType.CHANGE); 1981 1982 // Modify the element structure so that the interval begins at an 1983 // element 1984 // start and ends at an element end. 1985 buffer.change(offset, length, ev); 1986 1987 // Visit all paragraph elements within the specified interval 1988 int end = offset + length; 1989 Element curr; 1990 for (int pos = offset; pos < end;) 1991 { 1992 // Get the CharacterElement at offset pos. 1993 curr = getCharacterElement(pos); 1994 if (pos == curr.getEndOffset()) 1995 break; 1996 1997 MutableAttributeSet a = (MutableAttributeSet) curr.getAttributes(); 1998 ev.addEdit(new AttributeUndoableEdit(curr, attributes, replace)); 1999 // If replace is true, remove all the old attributes. 2000 if (replace) 2001 a.removeAttributes(a); 2002 // Add all the new attributes. 2003 a.addAttributes(attributes); 2004 // Increment pos so we can check the next CharacterElement. 2005 pos = curr.getEndOffset(); 2006 } 2007 fireChangedUpdate(ev); 2008 fireUndoableEditUpdate(new UndoableEditEvent(this, ev)); 2009 } 2010 finally 2011 { 2012 writeUnlock(); 2013 } 2014 } 2015 2016 /** 2017 * Sets the logical style for the paragraph at the specified position. 2018 * 2019 * @param position 2020 * the position at which the logical style is added 2021 * @param style 2022 * the style to set for the current paragraph 2023 */ setLogicalStyle(int position, Style style)2024 public void setLogicalStyle(int position, Style style) 2025 { 2026 Element el = getParagraphElement(position); 2027 // getParagraphElement doesn't return null but subclasses might so 2028 // we check for null here. 2029 if (el == null) 2030 return; 2031 try 2032 { 2033 writeLock(); 2034 if (el instanceof AbstractElement) 2035 { 2036 AbstractElement ael = (AbstractElement) el; 2037 ael.setResolveParent(style); 2038 int start = el.getStartOffset(); 2039 int end = el.getEndOffset(); 2040 DefaultDocumentEvent ev = new DefaultDocumentEvent(start, 2041 end - start, 2042 DocumentEvent.EventType.CHANGE); 2043 fireChangedUpdate(ev); 2044 fireUndoableEditUpdate(new UndoableEditEvent(this, ev)); 2045 } 2046 else 2047 throw new AssertionError( 2048 "paragraph elements are expected to be" 2049 + "instances of AbstractDocument.AbstractElement"); 2050 } 2051 finally 2052 { 2053 writeUnlock(); 2054 } 2055 } 2056 2057 /** 2058 * Sets text attributes for the paragraph at the specified fragment. 2059 * 2060 * @param offset 2061 * the beginning of the fragment 2062 * @param length 2063 * the length of the fragment 2064 * @param attributes 2065 * the text attributes to set 2066 * @param replace 2067 * if <code>true</code>, the attributes of the current selection 2068 * are overridden, otherwise they are merged 2069 */ setParagraphAttributes(int offset, int length, AttributeSet attributes, boolean replace)2070 public void setParagraphAttributes(int offset, int length, 2071 AttributeSet attributes, boolean replace) 2072 { 2073 try 2074 { 2075 // Must obtain a write lock for this method. writeLock() and 2076 // writeUnlock() should always be in try/finally blocks to make 2077 // sure that locking occurs in a balanced manner. 2078 writeLock(); 2079 2080 // Create a DocumentEvent to use for changedUpdate(). 2081 DefaultDocumentEvent ev = new DefaultDocumentEvent(offset, 2082 length, 2083 DocumentEvent.EventType.CHANGE); 2084 2085 // Have to iterate through all the _paragraph_ elements that are 2086 // contained or partially contained in the interval 2087 // (offset, offset + length). 2088 Element rootElement = getDefaultRootElement(); 2089 int startElement = rootElement.getElementIndex(offset); 2090 int endElement = rootElement.getElementIndex(offset + length - 1); 2091 if (endElement < startElement) 2092 endElement = startElement; 2093 2094 for (int i = startElement; i <= endElement; i++) 2095 { 2096 Element par = rootElement.getElement(i); 2097 MutableAttributeSet a = (MutableAttributeSet) par.getAttributes(); 2098 // Add the change to the DocumentEvent. 2099 ev.addEdit(new AttributeUndoableEdit(par, attributes, replace)); 2100 // If replace is true remove the old attributes. 2101 if (replace) 2102 a.removeAttributes(a); 2103 // Add the new attributes. 2104 a.addAttributes(attributes); 2105 } 2106 fireChangedUpdate(ev); 2107 fireUndoableEditUpdate(new UndoableEditEvent(this, ev)); 2108 } 2109 finally 2110 { 2111 writeUnlock(); 2112 } 2113 } 2114 2115 /** 2116 * Called in response to content insert actions. This is used to update the 2117 * element structure. 2118 * 2119 * @param ev 2120 * the <code>DocumentEvent</code> describing the change 2121 * @param attr 2122 * the attributes for the change 2123 */ insertUpdate(DefaultDocumentEvent ev, AttributeSet attr)2124 protected void insertUpdate(DefaultDocumentEvent ev, AttributeSet attr) 2125 { 2126 int offs = ev.getOffset(); 2127 int len = ev.getLength(); 2128 int endOffs = offs + len; 2129 if (attr == null) 2130 attr = SimpleAttributeSet.EMPTY; 2131 2132 // Paragraph attributes are fetched from the point _after_ the insertion. 2133 Element paragraph = getParagraphElement(endOffs); 2134 AttributeSet pAttr = paragraph.getAttributes(); 2135 // Character attributes are fetched from the actual insertion point. 2136 Element paragraph2 = getParagraphElement(offs); 2137 int contIndex = paragraph2.getElementIndex(offs); 2138 Element content = paragraph2.getElement(contIndex); 2139 AttributeSet cAttr = content.getAttributes(); 2140 2141 boolean insertAtBoundary = content.getEndOffset() == endOffs; 2142 try 2143 { 2144 Segment s = new Segment(); 2145 ArrayList buf = new ArrayList(); 2146 ElementSpec lastStartTag = null; 2147 boolean insertAfterNewline = false; 2148 short lastStartDir = ElementSpec.OriginateDirection; 2149 2150 // Special handle if we are inserting after a newline. 2151 if (offs > 0) 2152 { 2153 getText(offs - 1, 1, s); 2154 if (s.array[s.offset] == '\n') 2155 { 2156 insertAfterNewline = true; 2157 lastStartDir = insertAfterNewline(paragraph, paragraph2, 2158 pAttr, buf, offs, 2159 endOffs); 2160 // Search last start tag. 2161 for (int i = buf.size() - 1; i >= 0 && lastStartTag == null; 2162 i--) 2163 { 2164 ElementSpec tag = (ElementSpec) buf.get(i); 2165 if (tag.getType() == ElementSpec.StartTagType) 2166 { 2167 lastStartTag = tag; 2168 } 2169 } 2170 } 2171 2172 } 2173 2174 // If we are not inserting after a newline, the paragraph attributes 2175 // come from the paragraph under the insertion point. 2176 if (! insertAfterNewline) 2177 pAttr = paragraph2.getAttributes(); 2178 2179 // Scan text and build up the specs. 2180 getText(offs, len, s); 2181 int end = s.offset + s.count; 2182 int last = s.offset; 2183 for (int i = s.offset; i < end; i++) 2184 { 2185 if (s.array[i] == '\n') 2186 { 2187 int breakOffs = i + 1; 2188 buf.add(new ElementSpec(attr, ElementSpec.ContentType, 2189 breakOffs - last)); 2190 buf.add(new ElementSpec(null, ElementSpec.EndTagType)); 2191 lastStartTag = new ElementSpec(pAttr, 2192 ElementSpec.StartTagType); 2193 buf.add(lastStartTag); 2194 last = breakOffs; 2195 } 2196 } 2197 2198 // Need to add a tailing content tag if we didn't finish at a boundary. 2199 if (last < end) 2200 { 2201 buf.add(new ElementSpec(attr, ElementSpec.ContentType, 2202 end - last)); 2203 } 2204 2205 // Now we need to fix up the directions of the specs. 2206 ElementSpec first = (ElementSpec) buf.get(0); 2207 int doclen = getLength(); 2208 2209 // Maybe join-previous the first tag if it is content and has 2210 // the same attributes as the previous character run. 2211 if (first.getType() == ElementSpec.ContentType && cAttr.isEqual(attr)) 2212 first.setDirection(ElementSpec.JoinPreviousDirection); 2213 2214 // Join-fracture or join-next the last start tag if necessary. 2215 if (lastStartTag != null) 2216 { 2217 if (insertAfterNewline) 2218 lastStartTag.setDirection(lastStartDir); 2219 else if (paragraph2.getEndOffset() != endOffs) 2220 lastStartTag.setDirection(ElementSpec.JoinFractureDirection); 2221 else 2222 { 2223 Element par = paragraph2.getParentElement(); 2224 int par2Index = par.getElementIndex(offs); 2225 if (par2Index + 1 < par.getElementCount() 2226 && ! par.getElement(par2Index + 1).isLeaf()) 2227 lastStartTag.setDirection(ElementSpec.JoinNextDirection); 2228 } 2229 } 2230 2231 // Join-next last tag if possible. 2232 if (insertAtBoundary && endOffs < doclen) 2233 { 2234 ElementSpec lastTag = (ElementSpec) buf.get(buf.size() - 1); 2235 if (lastTag.getType() == ElementSpec.ContentType 2236 && ((lastStartTag == null 2237 && (paragraph == paragraph2 || insertAfterNewline)) 2238 || (lastStartTag != null 2239 && lastStartTag.getDirection() != ElementSpec.OriginateDirection))) 2240 { 2241 int nextIndex = paragraph.getElementIndex(endOffs); 2242 Element nextRun = paragraph.getElement(nextIndex); 2243 if (nextRun.isLeaf() && attr.isEqual(nextRun.getAttributes())) 2244 lastTag.setDirection(ElementSpec.JoinNextDirection); 2245 } 2246 } 2247 2248 else if (! insertAtBoundary && lastStartTag != null 2249 && lastStartTag.getDirection() == ElementSpec.JoinFractureDirection) 2250 { 2251 ElementSpec lastTag = (ElementSpec) buf.get(buf.size() - 1); 2252 if (lastTag.getType() == ElementSpec.ContentType 2253 && lastTag.getDirection() != ElementSpec.JoinPreviousDirection 2254 && attr.isEqual(cAttr)) 2255 { 2256 lastTag.setDirection(ElementSpec.JoinNextDirection); 2257 } 2258 } 2259 2260 ElementSpec[] specs = new ElementSpec[buf.size()]; 2261 specs = (ElementSpec[]) buf.toArray(specs); 2262 buffer.insert(offs, len, specs, ev); 2263 } 2264 catch (BadLocationException ex) 2265 { 2266 // Ignore this. Comment out for debugging. 2267 ex.printStackTrace(); 2268 } 2269 super.insertUpdate(ev, attr); 2270 } 2271 insertAfterNewline(Element par1, Element par2, AttributeSet attr, ArrayList buf, int offs, int endOffs)2272 private short insertAfterNewline(Element par1, Element par2, 2273 AttributeSet attr, ArrayList buf, 2274 int offs, int endOffs) 2275 { 2276 short dir = 0; 2277 if (par1.getParentElement() == par2.getParentElement()) 2278 { 2279 ElementSpec tag = new ElementSpec(attr, ElementSpec.EndTagType); 2280 buf.add(tag); 2281 tag = new ElementSpec(attr, ElementSpec.StartTagType); 2282 buf.add(tag); 2283 if (par2.getEndOffset() != endOffs) 2284 dir = ElementSpec.JoinFractureDirection; 2285 else 2286 { 2287 Element par = par2.getParentElement(); 2288 if (par.getElementIndex(offs) + 1 < par.getElementCount()) 2289 dir = ElementSpec.JoinNextDirection; 2290 } 2291 } 2292 else 2293 { 2294 // For text with more than 2 levels, find the common parent of 2295 // par1 and par2. 2296 ArrayList parentsLeft = new ArrayList(); 2297 ArrayList parentsRight = new ArrayList(); 2298 Element e = par2; 2299 while (e != null) 2300 { 2301 parentsLeft.add(e); 2302 e = e.getParentElement(); 2303 } 2304 e = par1; 2305 int leftIndex = -1; 2306 while (e != null && (leftIndex = parentsLeft.indexOf(e)) == 1) 2307 { 2308 parentsRight.add(e); 2309 e = e.getParentElement(); 2310 } 2311 2312 if (e != null) 2313 2314 { 2315 // e is now the common parent. 2316 // Insert the end tags. 2317 for (int c = 0; c < leftIndex; c++) 2318 { 2319 buf.add(new ElementSpec(null, ElementSpec.EndTagType)); 2320 } 2321 // Insert the start tags. 2322 for (int c = parentsRight.size() - 1; c >= 0; c--) 2323 { 2324 Element el = (Element) parentsRight.get(c); 2325 ElementSpec tag = new ElementSpec(el.getAttributes(), 2326 ElementSpec.StartTagType); 2327 if (c > 0) 2328 tag.setDirection(ElementSpec.JoinNextDirection); 2329 buf.add(tag); 2330 } 2331 if (parentsRight.size() > 0) 2332 dir = ElementSpec.JoinNextDirection; 2333 else 2334 dir = ElementSpec.JoinFractureDirection; 2335 } 2336 else 2337 assert false; 2338 } 2339 return dir; 2340 } 2341 2342 /** 2343 * A helper method to set up the ElementSpec buffer for the special case of an 2344 * insertion occurring immediately after a newline. 2345 * 2346 * @param specs 2347 * the ElementSpec buffer to initialize. 2348 */ handleInsertAfterNewline(Vector specs, int offset, int endOffset, Element prevParagraph, Element paragraph, AttributeSet a)2349 short handleInsertAfterNewline(Vector specs, int offset, int endOffset, 2350 Element prevParagraph, Element paragraph, 2351 AttributeSet a) 2352 { 2353 if (prevParagraph.getParentElement() == paragraph.getParentElement()) 2354 { 2355 specs.add(new ElementSpec(a, ElementSpec.EndTagType)); 2356 specs.add(new ElementSpec(a, ElementSpec.StartTagType)); 2357 if (paragraph.getStartOffset() != endOffset) 2358 return ElementSpec.JoinFractureDirection; 2359 // If there is an Element after this one, use JoinNextDirection. 2360 Element parent = paragraph.getParentElement(); 2361 if (parent.getElementCount() > (parent.getElementIndex(offset) + 1)) 2362 return ElementSpec.JoinNextDirection; 2363 } 2364 return ElementSpec.OriginateDirection; 2365 } 2366 2367 /** 2368 * Updates the document structure in response to text removal. This is 2369 * forwarded to the {@link ElementBuffer} of this document. Any changes to the 2370 * document structure are added to the specified document event and sent to 2371 * registered listeners. 2372 * 2373 * @param ev 2374 * the document event that records the changes to the document 2375 */ removeUpdate(DefaultDocumentEvent ev)2376 protected void removeUpdate(DefaultDocumentEvent ev) 2377 { 2378 super.removeUpdate(ev); 2379 buffer.remove(ev.getOffset(), ev.getLength(), ev); 2380 } 2381 2382 /** 2383 * Returns an enumeration of all style names. 2384 * 2385 * @return an enumeration of all style names 2386 */ getStyleNames()2387 public Enumeration<?> getStyleNames() 2388 { 2389 StyleContext context = (StyleContext) getAttributeContext(); 2390 return context.getStyleNames(); 2391 } 2392 2393 /** 2394 * Called when any of this document's styles changes. 2395 * 2396 * @param style 2397 * the style that changed 2398 */ styleChanged(Style style)2399 protected void styleChanged(Style style) 2400 { 2401 // Nothing to do here. This is intended to be overridden by subclasses. 2402 } 2403 2404 /** 2405 * Inserts a bulk of structured content at once. 2406 * 2407 * @param offset 2408 * the offset at which the content should be inserted 2409 * @param data 2410 * the actual content spec to be inserted 2411 */ insert(int offset, ElementSpec[] data)2412 protected void insert(int offset, ElementSpec[] data) 2413 throws BadLocationException 2414 { 2415 if (data == null || data.length == 0) 2416 return; 2417 try 2418 { 2419 // writeLock() and writeUnlock() should always be in a try/finally 2420 // block so that locking balance is guaranteed even if some 2421 // exception is thrown. 2422 writeLock(); 2423 2424 // First we collect the content to be inserted. 2425 CPStringBuilder contentBuffer = new CPStringBuilder(); 2426 for (int i = 0; i < data.length; i++) 2427 { 2428 // Collect all inserts into one so we can get the correct 2429 // ElementEdit 2430 ElementSpec spec = data[i]; 2431 if (spec.getArray() != null && spec.getLength() > 0) 2432 contentBuffer.append(spec.getArray(), spec.getOffset(), 2433 spec.getLength()); 2434 } 2435 2436 int length = contentBuffer.length(); 2437 2438 // If there was no content inserted then exit early. 2439 if (length == 0) 2440 return; 2441 2442 Content c = getContent(); 2443 UndoableEdit edit = c.insertString(offset, 2444 contentBuffer.toString()); 2445 2446 // Create the DocumentEvent with the ElementEdit added 2447 DefaultDocumentEvent ev = new DefaultDocumentEvent(offset, 2448 length, 2449 DocumentEvent.EventType.INSERT); 2450 2451 ev.addEdit(edit); 2452 2453 // Finally we must update the document structure and fire the insert 2454 // update event. 2455 buffer.insert(offset, length, data, ev); 2456 2457 super.insertUpdate(ev, null); 2458 2459 ev.end(); 2460 fireInsertUpdate(ev); 2461 fireUndoableEditUpdate(new UndoableEditEvent(this, ev)); 2462 } 2463 finally 2464 { 2465 writeUnlock(); 2466 } 2467 } 2468 2469 /** 2470 * Initializes the <code>DefaultStyledDocument</code> with the specified 2471 * data. 2472 * 2473 * @param data 2474 * the specification of the content with which the document is 2475 * initialized 2476 */ create(ElementSpec[] data)2477 protected void create(ElementSpec[] data) 2478 { 2479 try 2480 { 2481 2482 // Clear content if there is some. 2483 int len = getLength(); 2484 if (len > 0) 2485 remove(0, len); 2486 2487 writeLock(); 2488 2489 // Now we insert the content. 2490 StringBuilder b = new StringBuilder(); 2491 for (int i = 0; i < data.length; ++i) 2492 { 2493 ElementSpec el = data[i]; 2494 if (el.getArray() != null && el.getLength() > 0) 2495 b.append(el.getArray(), el.getOffset(), el.getLength()); 2496 } 2497 Content content = getContent(); 2498 UndoableEdit cEdit = content.insertString(0, b.toString()); 2499 2500 len = b.length(); 2501 DefaultDocumentEvent ev = 2502 new DefaultDocumentEvent(0, b.length(), 2503 DocumentEvent.EventType.INSERT); 2504 ev.addEdit(cEdit); 2505 2506 buffer.create(len, data, ev); 2507 2508 // For the bidi update. 2509 super.insertUpdate(ev, null); 2510 2511 ev.end(); 2512 fireInsertUpdate(ev); 2513 fireUndoableEditUpdate(new UndoableEditEvent(this, ev)); 2514 } 2515 catch (BadLocationException ex) 2516 { 2517 AssertionError err = new AssertionError("Unexpected bad location"); 2518 err.initCause(ex); 2519 throw err; 2520 } 2521 finally 2522 { 2523 writeUnlock(); 2524 } 2525 } 2526 } 2527