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