001    /* DefaultStyledDocument.java --
002       Copyright (C) 2004, 2005 Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package javax.swing.text;
040    
041    import gnu.java.lang.CPStringBuilder;
042    
043    import java.awt.Color;
044    import java.awt.Font;
045    import java.io.Serializable;
046    import java.util.ArrayList;
047    import java.util.Enumeration;
048    import java.util.Iterator;
049    import java.util.Stack;
050    import java.util.Vector;
051    
052    import javax.swing.event.ChangeEvent;
053    import javax.swing.event.ChangeListener;
054    import javax.swing.event.DocumentEvent;
055    import javax.swing.event.UndoableEditEvent;
056    import javax.swing.undo.AbstractUndoableEdit;
057    import javax.swing.undo.UndoableEdit;
058    
059    /**
060     * The default implementation of {@link StyledDocument}. The document is
061     * modeled as an {@link Element} tree, which has a {@link SectionElement} as
062     * single root, which has one or more {@link AbstractDocument.BranchElement}s
063     * as paragraph nodes and each paragraph node having one or more
064     * {@link AbstractDocument.LeafElement}s as content nodes.
065     * 
066     * @author Michael Koch (konqueror@gmx.de)
067     * @author Roman Kennke (roman@kennke.org)
068     */
069    public class DefaultStyledDocument extends AbstractDocument implements
070        StyledDocument
071    {
072    
073      /**
074       * An {@link UndoableEdit} that can undo attribute changes to an element.
075       * 
076       * @author Roman Kennke (kennke@aicas.com)
077       */
078      public static class AttributeUndoableEdit extends AbstractUndoableEdit
079      {
080        /**
081         * A copy of the old attributes.
082         */
083        protected AttributeSet copy;
084    
085        /**
086         * The new attributes.
087         */
088        protected AttributeSet newAttributes;
089    
090        /**
091         * If the new attributes replaced the old attributes or if they only were
092         * added to them.
093         */
094        protected boolean isReplacing;
095    
096        /**
097         * The element that has changed.
098         */
099        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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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           */
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           */
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         */
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         */
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         */
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         */
579        protected void removeUpdate()
580        {
581          removeElements(root, offset, endOffset);
582        }
583    
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         */
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    
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    
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         */
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         */
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         */
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    
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         */
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         */
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         */
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         */
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    
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    
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         */
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         */
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         */
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    
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    
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    
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    
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         */
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         */
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         */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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    
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       */
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       */
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       */
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       */
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       */
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       */
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    }