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      * &quot;section&quot;.
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