1 /* AbstractDocument.java --
2 Copyright (C) 2002, 2004, 2005, 2006 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
39 package javax.swing.text;
41 import java.awt.font.TextAttribute;
42 import java.io.PrintStream;
43 import java.io.Serializable;
44 import java.text.Bidi;
45 import java.util.ArrayList;
46 import java.util.Dictionary;
47 import java.util.Enumeration;
48 import java.util.EventListener;
49 import java.util.HashMap;
50 import java.util.Hashtable;
51 import java.util.Vector;
53 import javax.swing.event.DocumentEvent;
54 import javax.swing.event.DocumentListener;
55 import javax.swing.event.EventListenerList;
56 import javax.swing.event.UndoableEditEvent;
57 import javax.swing.event.UndoableEditListener;
58 import javax.swing.text.DocumentFilter;
59 import javax.swing.tree.TreeNode;
60 import javax.swing.undo.AbstractUndoableEdit;
61 import javax.swing.undo.CompoundEdit;
62 import javax.swing.undo.UndoableEdit;
65 * An abstract base implementation for the {@link Document} interface.
66 * This class provides some common functionality for all <code>Element</code>s,
67 * most notably it implements a locking mechanism to make document modification
70 * @author original author unknown
71 * @author Roman Kennke (roman@kennke.org)
73 public abstract class AbstractDocument implements Document, Serializable
75 /** The serialization UID (compatible with JDK1.5). */
76 private static final long serialVersionUID = 6842927725919637215L;
79 * Standard error message to indicate a bad location.
81 protected static final String BAD_LOCATION = "document location failure";
84 * Standard name for unidirectional <code>Element</code>s.
86 public static final String BidiElementName = "bidi level";
89 * Standard name for content <code>Element</code>s. These are usually
90 * {@link LeafElement}s.
92 public static final String ContentElementName = "content";
95 * Standard name for paragraph <code>Element</code>s. These are usually
96 * {@link BranchElement}s.
98 public static final String ParagraphElementName = "paragraph";
101 * Standard name for section <code>Element</code>s. These are usually
102 * {@link DefaultStyledDocument.SectionElement}s.
104 public static final String SectionElementName = "section";
107 * Attribute key for storing the element name.
109 public static final String ElementNameAttribute = "$ename";
112 * Standard name for the bidi root element.
114 private static final String BidiRootName = "bidi root";
117 * Key for storing the asynchronous load priority.
119 private static final String AsyncLoadPriority = "load priority";
122 * Key for storing the I18N state.
124 private static final String I18N = "i18n";
127 * The actual content model of this <code>Document</code>.
132 * The AttributeContext for this <code>Document</code>.
134 AttributeContext context;
137 * The currently installed <code>DocumentFilter</code>.
139 DocumentFilter documentFilter;
142 * The documents properties.
144 Dictionary properties;
147 * Manages event listeners for this <code>Document</code>.
149 protected EventListenerList listenerList = new EventListenerList();
152 * Stores the current writer thread. Used for locking.
154 private Thread currentWriter = null;
157 * The number of readers. Used for locking.
159 private int numReaders = 0;
162 * The number of current writers. If this is > 1 then the same thread entered
163 * the write lock more than once.
165 private int numWriters = 0;
167 /** An instance of a DocumentFilter.FilterBypass which allows calling
168 * the insert, remove and replace method without checking for an installed
171 private DocumentFilter.FilterBypass bypass;
174 * The bidi root element.
176 private BidiRootElement bidiRoot;
179 * True when we are currently notifying any listeners. This is used
180 * to detect illegal situations in writeLock().
182 private transient boolean notifyListeners;
185 * Creates a new <code>AbstractDocument</code> with the specified
186 * {@link Content} model.
188 * @param doc the <code>Content</code> model to be used in this
189 * <code>Document<code>
194 protected AbstractDocument(Content doc)
196 this(doc, StyleContext.getDefaultStyleContext());
200 * Creates a new <code>AbstractDocument</code> with the specified
201 * {@link Content} model and {@link AttributeContext}.
203 * @param doc the <code>Content</code> model to be used in this
204 * <code>Document<code>
205 * @param ctx the <code>AttributeContext</code> to use
210 protected AbstractDocument(Content doc, AttributeContext ctx)
215 // FIXME: Fully implement bidi.
216 bidiRoot = new BidiRootElement();
218 // FIXME: This is determined using a Mauve test. Make the document
219 // actually use this.
220 putProperty(I18N, Boolean.FALSE);
222 // Add one child to the bidi root.
226 Element[] children = new Element[1];
227 children[0] = new BidiElement(bidiRoot, 0, 1, 0);
228 bidiRoot.replace(0, 0, children);
236 /** Returns the DocumentFilter.FilterBypass instance for this
237 * document and create it if it does not exist yet.
239 * @return This document's DocumentFilter.FilterBypass instance.
241 private DocumentFilter.FilterBypass getBypass()
244 bypass = new Bypass();
250 * Returns the paragraph {@link Element} that holds the specified position.
252 * @param pos the position for which to get the paragraph element
254 * @return the paragraph {@link Element} that holds the specified position
256 public abstract Element getParagraphElement(int pos);
259 * Returns the default root {@link Element} of this <code>Document</code>.
260 * Usual <code>Document</code>s only have one root element and return this.
261 * However, there may be <code>Document</code> implementations that
262 * support multiple root elements, they have to return a default root element
265 * @return the default root {@link Element} of this <code>Document</code>
267 public abstract Element getDefaultRootElement();
270 * Creates and returns a branch element with the specified
271 * <code>parent</code> and <code>attributes</code>. Note that the new
272 * <code>Element</code> is linked to the parent <code>Element</code>
273 * through {@link Element#getParentElement}, but it is not yet added
274 * to the parent <code>Element</code> as child.
276 * @param parent the parent <code>Element</code> for the new branch element
277 * @param attributes the text attributes to be installed in the new element
279 * @return the new branch <code>Element</code>
283 protected Element createBranchElement(Element parent,
284 AttributeSet attributes)
286 return new BranchElement(parent, attributes);
290 * Creates and returns a leaf element with the specified
291 * <code>parent</code> and <code>attributes</code>. Note that the new
292 * <code>Element</code> is linked to the parent <code>Element</code>
293 * through {@link Element#getParentElement}, but it is not yet added
294 * to the parent <code>Element</code> as child.
296 * @param parent the parent <code>Element</code> for the new branch element
297 * @param attributes the text attributes to be installed in the new element
299 * @return the new branch <code>Element</code>
303 protected Element createLeafElement(Element parent, AttributeSet attributes,
306 return new LeafElement(parent, attributes, start, end);
310 * Creates a {@link Position} that keeps track of the location at the
311 * specified <code>offset</code>.
313 * @param offset the location in the document to keep track by the new
314 * <code>Position</code>
316 * @return the newly created <code>Position</code>
318 * @throws BadLocationException if <code>offset</code> is not a valid
319 * location in the documents content model
321 public synchronized Position createPosition(final int offset)
322 throws BadLocationException
324 return content.createPosition(offset);
328 * Notifies all registered listeners when the document model changes.
330 * @param event the <code>DocumentEvent</code> to be fired
332 protected void fireChangedUpdate(DocumentEvent event)
334 notifyListeners = true;
337 DocumentListener[] listeners = getDocumentListeners();
338 for (int index = 0; index < listeners.length; ++index)
339 listeners[index].changedUpdate(event);
343 notifyListeners = false;
348 * Notifies all registered listeners when content is inserted in the document
351 * @param event the <code>DocumentEvent</code> to be fired
353 protected void fireInsertUpdate(DocumentEvent event)
355 notifyListeners = true;
358 DocumentListener[] listeners = getDocumentListeners();
359 for (int index = 0; index < listeners.length; ++index)
360 listeners[index].insertUpdate(event);
364 notifyListeners = false;
369 * Notifies all registered listeners when content is removed from the
372 * @param event the <code>DocumentEvent</code> to be fired
374 protected void fireRemoveUpdate(DocumentEvent event)
376 notifyListeners = true;
379 DocumentListener[] listeners = getDocumentListeners();
380 for (int index = 0; index < listeners.length; ++index)
381 listeners[index].removeUpdate(event);
385 notifyListeners = false;
390 * Notifies all registered listeners when an <code>UndoableEdit</code> has
391 * been performed on this <code>Document</code>.
393 * @param event the <code>UndoableEditEvent</code> to be fired
395 protected void fireUndoableEditUpdate(UndoableEditEvent event)
397 UndoableEditListener[] listeners = getUndoableEditListeners();
399 for (int index = 0; index < listeners.length; ++index)
400 listeners[index].undoableEditHappened(event);
404 * Returns the asynchronous loading priority. Returns <code>-1</code> if this
405 * document should not be loaded asynchronously.
407 * @return the asynchronous loading priority
409 public int getAsynchronousLoadPriority()
411 Object val = getProperty(AsyncLoadPriority);
414 prio = ((Integer) val).intValue();
419 * Returns the {@link AttributeContext} used in this <code>Document</code>.
421 * @return the {@link AttributeContext} used in this <code>Document</code>
423 protected final AttributeContext getAttributeContext()
429 * Returns the root element for bidirectional content.
431 * @return the root element for bidirectional content
433 public Element getBidiRootElement()
439 * Returns the {@link Content} model for this <code>Document</code>
441 * @return the {@link Content} model for this <code>Document</code>
446 protected final Content getContent()
452 * Returns the thread that currently modifies this <code>Document</code>
453 * if there is one, otherwise <code>null</code>. This can be used to
454 * distinguish between a method call that is part of an ongoing modification
455 * or if it is a separate modification for which a new lock must be aquired.
457 * @return the thread that currently modifies this <code>Document</code>
458 * if there is one, otherwise <code>null</code>
460 protected final synchronized Thread getCurrentWriter()
462 return currentWriter;
466 * Returns the properties of this <code>Document</code>.
468 * @return the properties of this <code>Document</code>
470 public Dictionary<Object, Object> getDocumentProperties()
472 // FIXME: make me thread-safe
473 if (properties == null)
474 properties = new Hashtable();
480 * Returns a {@link Position} which will always mark the end of the
481 * <code>Document</code>.
483 * @return a {@link Position} which will always mark the end of the
484 * <code>Document</code>
486 public final Position getEndPosition()
491 p = createPosition(content.length());
493 catch (BadLocationException ex)
495 // Shouldn't really happen.
502 * Returns the length of this <code>Document</code>'s content.
504 * @return the length of this <code>Document</code>'s content
506 public int getLength()
508 // We return Content.getLength() -1 here because there is always an
509 // implicit \n at the end of the Content which does count in Content
510 // but not in Document.
511 return content.length() - 1;
515 * Returns all registered listeners of a given listener type.
517 * @param listenerType the type of the listeners to be queried
519 * @return all registered listeners of the specified type
521 public <T extends EventListener> T[] getListeners(Class<T> listenerType)
523 return listenerList.getListeners(listenerType);
527 * Returns a property from this <code>Document</code>'s property list.
529 * @param key the key of the property to be fetched
531 * @return the property for <code>key</code> or <code>null</code> if there
532 * is no such property stored
534 public final Object getProperty(Object key)
536 // FIXME: make me thread-safe
538 if (properties != null)
539 value = properties.get(key);
545 * Returns all root elements of this <code>Document</code>. By default
546 * this just returns the single root element returned by
547 * {@link #getDefaultRootElement()}. <code>Document</code> implementations
548 * that support multiple roots must override this method and return all roots
551 * @return all root elements of this <code>Document</code>
553 public Element[] getRootElements()
555 Element[] elements = new Element[2];
556 elements[0] = getDefaultRootElement();
557 elements[1] = getBidiRootElement();
562 * Returns a {@link Position} which will always mark the beginning of the
563 * <code>Document</code>.
565 * @return a {@link Position} which will always mark the beginning of the
566 * <code>Document</code>
568 public final Position getStartPosition()
573 p = createPosition(0);
575 catch (BadLocationException ex)
577 // Shouldn't really happen.
584 * Returns a piece of this <code>Document</code>'s content.
586 * @param offset the start offset of the content
587 * @param length the length of the content
589 * @return the piece of content specified by <code>offset</code> and
590 * <code>length</code>
592 * @throws BadLocationException if <code>offset</code> or <code>offset +
593 * length</code> are invalid locations with this
594 * <code>Document</code>
596 public String getText(int offset, int length) throws BadLocationException
598 return content.getString(offset, length);
602 * Fetches a piece of this <code>Document</code>'s content and stores
603 * it in the given {@link Segment}.
605 * @param offset the start offset of the content
606 * @param length the length of the content
607 * @param segment the <code>Segment</code> to store the content in
609 * @throws BadLocationException if <code>offset</code> or <code>offset +
610 * length</code> are invalid locations with this
611 * <code>Document</code>
613 public void getText(int offset, int length, Segment segment)
614 throws BadLocationException
616 content.getChars(offset, length, segment);
620 * Inserts a String into this <code>Document</code> at the specified
621 * position and assigning the specified attributes to it.
623 * <p>If a {@link DocumentFilter} is installed in this document, the
624 * corresponding method of the filter object is called.</p>
626 * <p>The method has no effect when <code>text</code> is <code>null</code>
627 * or has a length of zero.</p>
630 * @param offset the location at which the string should be inserted
631 * @param text the content to be inserted
632 * @param attributes the text attributes to be assigned to that string
634 * @throws BadLocationException if <code>offset</code> is not a valid
635 * location in this <code>Document</code>
637 public void insertString(int offset, String text, AttributeSet attributes)
638 throws BadLocationException
640 // Bail out if we have a bogus insertion (Behavior observed in RI).
641 if (text == null || text.length() == 0)
647 if (documentFilter == null)
648 insertStringImpl(offset, text, attributes);
650 documentFilter.insertString(getBypass(), offset, text, attributes);
658 void insertStringImpl(int offset, String text, AttributeSet attributes)
659 throws BadLocationException
661 // Just return when no text to insert was given.
662 if (text == null || text.length() == 0)
664 DefaultDocumentEvent event =
665 new DefaultDocumentEvent(offset, text.length(),
666 DocumentEvent.EventType.INSERT);
668 UndoableEdit undo = content.insertString(offset, text);
672 // Check if we need bidi layout.
673 if (getProperty(I18N).equals(Boolean.FALSE))
675 Object dir = getProperty(TextAttribute.RUN_DIRECTION);
676 if (TextAttribute.RUN_DIRECTION_RTL.equals(dir))
677 putProperty(I18N, Boolean.TRUE);
680 char[] chars = text.toCharArray();
681 if (Bidi.requiresBidi(chars, 0, chars.length))
682 putProperty(I18N, Boolean.TRUE);
686 insertUpdate(event, attributes);
688 fireInsertUpdate(event);
691 fireUndoableEditUpdate(new UndoableEditEvent(this, undo));
695 * Called to indicate that text has been inserted into this
696 * <code>Document</code>. The default implementation does nothing.
697 * This method is executed within a write lock.
699 * @param chng the <code>DefaultDocumentEvent</code> describing the change
700 * @param attr the attributes of the changed content
702 protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr)
704 if (Boolean.TRUE.equals(getProperty(I18N)))
709 * Called after some content has been removed from this
710 * <code>Document</code>. The default implementation does nothing.
711 * This method is executed within a write lock.
713 * @param chng the <code>DefaultDocumentEvent</code> describing the change
715 protected void postRemoveUpdate(DefaultDocumentEvent chng)
717 if (Boolean.TRUE.equals(getProperty(I18N)))
722 * Stores a property in this <code>Document</code>'s property list.
724 * @param key the key of the property to be stored
725 * @param value the value of the property to be stored
727 public final void putProperty(Object key, Object value)
729 // FIXME: make me thread-safe
730 if (properties == null)
731 properties = new Hashtable();
734 properties.remove(key);
736 properties.put(key, value);
738 // Update bidi structure if the RUN_DIRECTION is set.
739 if (TextAttribute.RUN_DIRECTION.equals(key))
741 if (TextAttribute.RUN_DIRECTION_RTL.equals(value)
742 && Boolean.FALSE.equals(getProperty(I18N)))
743 putProperty(I18N, Boolean.TRUE);
745 if (Boolean.TRUE.equals(getProperty(I18N)))
750 DefaultDocumentEvent ev =
751 new DefaultDocumentEvent(0, getLength(),
752 DocumentEvent.EventType.INSERT);
764 * Updates the bidi element structure.
766 * @param ev the document event for the change
768 private void updateBidi(DefaultDocumentEvent ev)
770 // Determine start and end offset of the paragraphs to be scanned.
773 DocumentEvent.EventType type = ev.getType();
774 if (type == DocumentEvent.EventType.INSERT
775 || type == DocumentEvent.EventType.CHANGE)
777 int offs = ev.getOffset();
778 int endOffs = offs + ev.getLength();
779 start = getParagraphElement(offs).getStartOffset();
780 end = getParagraphElement(endOffs).getEndOffset();
782 else if (type == DocumentEvent.EventType.REMOVE)
784 Element par = getParagraphElement(ev.getOffset());
785 start = par.getStartOffset();
786 end = par.getEndOffset();
789 assert false : "Unknown event type";
791 // Determine the bidi levels for the affected range.
792 Bidi[] bidis = getBidis(start, end);
798 int lastRunStart = 0;
800 int lastRunLevel = 0;
801 ArrayList newEls = new ArrayList();
802 for (int i = 0; i < bidis.length; i++)
804 Bidi bidi = bidis[i];
805 int numRuns = bidi.getRunCount();
806 for (int r = 0; r < numRuns; r++)
808 if (r == 0 && i == 0)
812 // Try to merge with the previous element if it has the
813 // same bidi level as the first run.
814 int prevElIndex = bidiRoot.getElementIndex(start - 1);
815 removeFrom = prevElIndex;
816 Element prevEl = bidiRoot.getElement(prevElIndex);
817 AttributeSet atts = prevEl.getAttributes();
818 int prevElLevel = StyleConstants.getBidiLevel(atts);
819 if (prevElLevel == bidi.getRunLevel(r))
821 // Merge previous element with current run.
822 lastRunStart = prevEl.getStartOffset() - start;
823 lastRunEnd = bidi.getRunLimit(r);
824 lastRunLevel = bidi.getRunLevel(r);
826 else if (prevEl.getEndOffset() > start)
828 // Split previous element and replace by 2 new elements.
830 lastRunEnd = bidi.getRunLimit(r);
831 lastRunLevel = bidi.getRunLevel(r);
832 newEls.add(new BidiElement(bidiRoot,
833 prevEl.getStartOffset(),
834 start, prevElLevel));
838 // Simply start new run at start location.
840 lastRunEnd = bidi.getRunLimit(r);
841 lastRunLevel = bidi.getRunLevel(r);
847 // Simply start new run at start location.
849 lastRunEnd = bidi.getRunLimit(r);
850 lastRunLevel = bidi.getRunLevel(r);
854 if (i == bidis.length - 1 && r == numRuns - 1)
856 if (end <= getLength())
858 // Try to merge last element with next element.
859 int nextIndex = bidiRoot.getElementIndex(end);
860 Element nextEl = bidiRoot.getElement(nextIndex);
861 AttributeSet atts = nextEl.getAttributes();
862 int nextLevel = StyleConstants.getBidiLevel(atts);
863 int level = bidi.getRunLevel(r);
864 if (lastRunLevel == level && level == nextLevel)
866 // Merge runs together.
867 if (lastRunStart + start == nextEl.getStartOffset())
868 removeTo = nextIndex - 1;
871 newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
872 nextEl.getEndOffset(), level));
873 removeTo = nextIndex;
876 else if (lastRunLevel == level)
878 // Merge current and last run.
879 int endOffs = offs + bidi.getRunLimit(r);
880 newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
881 start + endOffs, level));
882 if (start + endOffs == nextEl.getStartOffset())
883 removeTo = nextIndex - 1;
886 newEls.add(new BidiElement(bidiRoot, start + endOffs,
887 nextEl.getEndOffset(),
889 removeTo = nextIndex;
892 else if (level == nextLevel)
894 // Merge current and next run.
895 newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
898 newEls.add(new BidiElement(bidiRoot, start + lastRunEnd,
899 nextEl.getEndOffset(), level));
900 removeTo = nextIndex;
904 // Split next element.
905 int endOffs = offs + bidi.getRunLimit(r);
906 newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
909 newEls.add(new BidiElement(bidiRoot, start + lastRunEnd,
910 start + endOffs, level));
911 newEls.add(new BidiElement(bidiRoot, start + endOffs,
912 nextEl.getEndOffset(),
914 removeTo = nextIndex;
919 removeTo = bidiRoot.getElementIndex(end);
920 int level = bidi.getRunLevel(r);
921 int runEnd = offs + bidi.getRunLimit(r);
923 if (level == lastRunLevel)
925 // Merge with previous.
926 lastRunEnd = offs + runEnd;
927 newEls.add(new BidiElement(bidiRoot,
928 start + lastRunStart,
929 start + runEnd, level));
933 // Create element for last run and current run.
934 newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
937 newEls.add(new BidiElement(bidiRoot,
946 int level = bidi.getRunLevel(r);
947 int runEnd = bidi.getRunLimit(r);
949 if (level == lastRunLevel)
951 // Merge with previous.
952 lastRunEnd = offs + runEnd;
956 // Create element for last run and update values for
958 newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
961 lastRunStart = lastRunEnd;
962 lastRunEnd = offs + runEnd;
963 lastRunLevel = level;
967 offs += bidi.getLength();
970 // Determine the bidi elements which are to be removed.
972 if (bidiRoot.getElementCount() > 0)
973 numRemoved = removeTo - removeFrom + 1;
974 Element[] removed = new Element[numRemoved];
975 for (int i = 0; i < numRemoved; i++)
976 removed[i] = bidiRoot.getElement(removeFrom + i);
978 Element[] added = new Element[newEls.size()];
979 added = (Element[]) newEls.toArray(added);
982 ElementEdit edit = new ElementEdit(bidiRoot, removeFrom, removed, added);
985 // Update the structure.
986 bidiRoot.replace(removeFrom, numRemoved, added);
990 * Determines the Bidi objects for the paragraphs in the specified range.
992 * @param start the start of the range
993 * @param end the end of the range
995 * @return the Bidi analysers for the paragraphs in the range
997 private Bidi[] getBidis(int start, int end)
999 // Determine the default run direction from the document property.
1000 Boolean defaultDir = null;
1001 Object o = getProperty(TextAttribute.RUN_DIRECTION);
1002 if (o instanceof Boolean)
1003 defaultDir = (Boolean) o;
1005 // Scan paragraphs and add their level arrays to the overall levels array.
1006 ArrayList bidis = new ArrayList();
1007 Segment s = new Segment();
1008 for (int i = start; i < end;)
1010 Element par = getParagraphElement(i);
1011 int pStart = par.getStartOffset();
1012 int pEnd = par.getEndOffset();
1014 // Determine the default run direction of the paragraph.
1015 Boolean dir = defaultDir;
1016 o = par.getAttributes().getAttribute(TextAttribute.RUN_DIRECTION);
1017 if (o instanceof Boolean)
1020 // Bidi over the paragraph.
1023 getText(pStart, pEnd - pStart, s);
1025 catch (BadLocationException ex)
1027 assert false : "Must not happen";
1029 int flag = Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT;
1032 if (TextAttribute.RUN_DIRECTION_LTR.equals(dir))
1033 flag = Bidi.DIRECTION_LEFT_TO_RIGHT;
1035 flag = Bidi.DIRECTION_RIGHT_TO_LEFT;
1037 Bidi bidi = new Bidi(s.array, s.offset, null, 0, s.count, flag);
1041 Bidi[] ret = new Bidi[bidis.size()];
1042 ret = (Bidi[]) bidis.toArray(ret);
1047 * Blocks until a read lock can be obtained. Must block if there is
1048 * currently a writer modifying the <code>Document</code>.
1050 public final synchronized void readLock()
1054 while (currentWriter != null)
1056 if (currentWriter == Thread.currentThread())
1062 catch (InterruptedException ex)
1064 throw new Error("Interrupted during grab read lock");
1069 * Releases the read lock. If this was the only reader on this
1070 * <code>Document</code>, writing may begin now.
1072 public final synchronized void readUnlock()
1074 // Note we could have a problem here if readUnlock was called without a
1075 // prior call to readLock but the specs simply warn users to ensure that
1076 // balance by using a finally block:
1087 // All that the JDK seems to check for is that you don't call unlock
1088 // more times than you've previously called lock, but it doesn't make
1089 // sure that the threads calling unlock were the same ones that called lock
1091 // If the current thread holds the write lock, and attempted to also obtain
1092 // a readLock, then numReaders hasn't been incremented and we don't need
1093 // to unlock it here.
1094 if (currentWriter == Thread.currentThread())
1097 // FIXME: the reference implementation throws a
1098 // javax.swing.text.StateInvariantError here
1099 if (numReaders <= 0)
1100 throw new IllegalStateException("document lock failure");
1102 // If currentWriter is not null, the application code probably had a
1103 // writeLock and then tried to obtain a readLock, in which case
1104 // numReaders wasn't incremented
1110 * Removes a piece of content from this <code>Document</code>.
1112 * <p>If a {@link DocumentFilter} is installed in this document, the
1113 * corresponding method of the filter object is called. The
1114 * <code>DocumentFilter</code> is called even if <code>length</code>
1115 * is zero. This is different from {@link #replace}.</p>
1117 * <p>Note: When <code>length</code> is zero or below the call is not
1118 * forwarded to the underlying {@link AbstractDocument.Content} instance
1119 * of this document and no exception is thrown.</p>
1121 * @param offset the start offset of the fragment to be removed
1122 * @param length the length of the fragment to be removed
1124 * @throws BadLocationException if <code>offset</code> or
1125 * <code>offset + length</code> or invalid locations within this
1128 public void remove(int offset, int length) throws BadLocationException
1133 DocumentFilter f = getDocumentFilter();
1135 removeImpl(offset, length);
1137 f.remove(getBypass(), offset, length);
1145 void removeImpl(int offset, int length) throws BadLocationException
1147 // The RI silently ignores all requests that have a negative length.
1148 // Don't ask my why, but that's how it is.
1151 if (offset < 0 || offset > getLength())
1152 throw new BadLocationException("Invalid remove position", offset);
1154 if (offset + length > getLength())
1155 throw new BadLocationException("Invalid remove length", offset);
1157 DefaultDocumentEvent event =
1158 new DefaultDocumentEvent(offset, length,
1159 DocumentEvent.EventType.REMOVE);
1161 // The order of the operations below is critical!
1162 removeUpdate(event);
1163 UndoableEdit temp = content.remove(offset, length);
1165 postRemoveUpdate(event);
1166 fireRemoveUpdate(event);
1171 * Replaces a piece of content in this <code>Document</code> with
1172 * another piece of content.
1174 * <p>If a {@link DocumentFilter} is installed in this document, the
1175 * corresponding method of the filter object is called.</p>
1177 * <p>The method has no effect if <code>length</code> is zero (and
1178 * only zero) and, at the same time, <code>text</code> is
1179 * <code>null</code> or has zero length.</p>
1181 * @param offset the start offset of the fragment to be removed
1182 * @param length the length of the fragment to be removed
1183 * @param text the text to replace the content with
1184 * @param attributes the text attributes to assign to the new content
1186 * @throws BadLocationException if <code>offset</code> or
1187 * <code>offset + length</code> or invalid locations within this
1192 public void replace(int offset, int length, String text,
1193 AttributeSet attributes)
1194 throws BadLocationException
1196 // Bail out if we have a bogus replacement (Behavior observed in RI).
1198 && (text == null || text.length() == 0))
1204 if (documentFilter == null)
1206 // It is important to call the methods which again do the checks
1207 // of the arguments and the DocumentFilter because subclasses may
1208 // have overridden these methods and provide crucial behavior
1209 // which would be skipped if we call the non-checking variants.
1210 // An example for this is PlainDocument where insertString can
1211 // provide a filtering of newlines.
1212 remove(offset, length);
1213 insertString(offset, text, attributes);
1216 documentFilter.replace(getBypass(), offset, length, text, attributes);
1224 void replaceImpl(int offset, int length, String text,
1225 AttributeSet attributes)
1226 throws BadLocationException
1228 removeImpl(offset, length);
1229 insertStringImpl(offset, text, attributes);
1233 * Adds a <code>DocumentListener</code> object to this document.
1235 * @param listener the listener to add
1237 public void addDocumentListener(DocumentListener listener)
1239 listenerList.add(DocumentListener.class, listener);
1243 * Removes a <code>DocumentListener</code> object from this document.
1245 * @param listener the listener to remove
1247 public void removeDocumentListener(DocumentListener listener)
1249 listenerList.remove(DocumentListener.class, listener);
1253 * Returns all registered <code>DocumentListener</code>s.
1255 * @return all registered <code>DocumentListener</code>s
1257 public DocumentListener[] getDocumentListeners()
1259 return (DocumentListener[]) getListeners(DocumentListener.class);
1263 * Adds an {@link UndoableEditListener} to this <code>Document</code>.
1265 * @param listener the listener to add
1267 public void addUndoableEditListener(UndoableEditListener listener)
1269 listenerList.add(UndoableEditListener.class, listener);
1273 * Removes an {@link UndoableEditListener} from this <code>Document</code>.
1275 * @param listener the listener to remove
1277 public void removeUndoableEditListener(UndoableEditListener listener)
1279 listenerList.remove(UndoableEditListener.class, listener);
1283 * Returns all registered {@link UndoableEditListener}s.
1285 * @return all registered {@link UndoableEditListener}s
1287 public UndoableEditListener[] getUndoableEditListeners()
1289 return (UndoableEditListener[]) getListeners(UndoableEditListener.class);
1293 * Called before some content gets removed from this <code>Document</code>.
1294 * The default implementation does nothing but may be overridden by
1295 * subclasses to modify the <code>Document</code> structure in response
1296 * to a remove request. The method is executed within a write lock.
1298 * @param chng the <code>DefaultDocumentEvent</code> describing the change
1300 protected void removeUpdate(DefaultDocumentEvent chng)
1302 // Do nothing here. Subclasses may wish to override this.
1306 * Called to render this <code>Document</code> visually. It obtains a read
1307 * lock, ensuring that no changes will be made to the <code>document</code>
1308 * during the rendering process. It then calls the {@link Runnable#run()}
1309 * method on <code>runnable</code>. This method <em>must not</em> attempt
1310 * to modifiy the <code>Document</code>, since a deadlock will occur if it
1311 * tries to obtain a write lock. When the {@link Runnable#run()} method
1312 * completes (either naturally or by throwing an exception), the read lock
1313 * is released. Note that there is nothing in this method related to
1314 * the actual rendering. It could be used to execute arbitrary code within
1317 * @param runnable the {@link Runnable} to execute
1319 public void render(Runnable runnable)
1333 * Sets the asynchronous loading priority for this <code>Document</code>.
1334 * A value of <code>-1</code> indicates that this <code>Document</code>
1335 * should be loaded synchronously.
1337 * @param p the asynchronous loading priority to set
1339 public void setAsynchronousLoadPriority(int p)
1341 Integer val = p >= 0 ? new Integer(p) : null;
1342 putProperty(AsyncLoadPriority, val);
1346 * Sets the properties of this <code>Document</code>.
1348 * @param p the document properties to set
1350 public void setDocumentProperties(Dictionary<Object, Object> p)
1352 // FIXME: make me thread-safe
1357 * Blocks until a write lock can be obtained. Must wait if there are
1358 * readers currently reading or another thread is currently writing.
1360 protected synchronized final void writeLock()
1364 while (numReaders > 0 || currentWriter != null)
1366 if (Thread.currentThread() == currentWriter)
1368 if (notifyListeners)
1369 throw new IllegalStateException("Mutation during notify");
1375 currentWriter = Thread.currentThread();
1378 catch (InterruptedException ex)
1380 throw new Error("Interupted during grab write lock");
1385 * Releases the write lock. This allows waiting readers or writers to
1388 protected final synchronized void writeUnlock()
1390 if (--numWriters <= 0)
1393 currentWriter = null;
1399 * Returns the currently installed {@link DocumentFilter} for this
1400 * <code>Document</code>.
1402 * @return the currently installed {@link DocumentFilter} for this
1403 * <code>Document</code>
1407 public DocumentFilter getDocumentFilter()
1409 return documentFilter;
1413 * Sets the {@link DocumentFilter} for this <code>Document</code>.
1415 * @param filter the <code>DocumentFilter</code> to set
1419 public void setDocumentFilter(DocumentFilter filter)
1421 this.documentFilter = filter;
1425 * Dumps diagnostic information to the specified <code>PrintStream</code>.
1427 * @param out the stream to write the diagnostic information to
1429 public void dump(PrintStream out)
1431 ((AbstractElement) getDefaultRootElement()).dump(out, 0);
1432 ((AbstractElement) getBidiRootElement()).dump(out, 0);
1436 * Defines a set of methods for managing text attributes for one or more
1437 * <code>Document</code>s.
1439 * Replicating {@link AttributeSet}s throughout a <code>Document</code> can
1440 * be very expensive. Implementations of this interface are intended to
1441 * provide intelligent management of <code>AttributeSet</code>s, eliminating
1442 * costly duplication.
1446 public interface AttributeContext
1449 * Returns an {@link AttributeSet} that contains the attributes
1450 * of <code>old</code> plus the new attribute specified by
1451 * <code>name</code> and <code>value</code>.
1453 * @param old the attribute set to be merged with the new attribute
1454 * @param name the name of the attribute to be added
1455 * @param value the value of the attribute to be added
1457 * @return the old attributes plus the new attribute
1459 AttributeSet addAttribute(AttributeSet old, Object name, Object value);
1462 * Returns an {@link AttributeSet} that contains the attributes
1463 * of <code>old</code> plus the new attributes in <code>attributes</code>.
1465 * @param old the set of attributes where to add the new attributes
1466 * @param attributes the attributes to be added
1468 * @return an {@link AttributeSet} that contains the attributes
1469 * of <code>old</code> plus the new attributes in
1470 * <code>attributes</code>
1472 AttributeSet addAttributes(AttributeSet old, AttributeSet attributes);
1475 * Returns an empty {@link AttributeSet}.
1477 * @return an empty {@link AttributeSet}
1479 AttributeSet getEmptySet();
1482 * Called to indicate that the attributes in <code>attributes</code> are
1485 * @param attributes the attributes are no longer used
1487 void reclaim(AttributeSet attributes);
1490 * Returns a {@link AttributeSet} that has the attribute with the specified
1491 * <code>name</code> removed from <code>old</code>.
1493 * @param old the attribute set from which an attribute is removed
1494 * @param name the name of the attribute to be removed
1496 * @return the attributes of <code>old</code> minus the attribute
1497 * specified by <code>name</code>
1499 AttributeSet removeAttribute(AttributeSet old, Object name);
1502 * Removes all attributes in <code>attributes</code> from <code>old</code>
1503 * and returns the resulting <code>AttributeSet</code>.
1505 * @param old the set of attributes from which to remove attributes
1506 * @param attributes the attributes to be removed from <code>old</code>
1508 * @return the attributes of <code>old</code> minus the attributes in
1509 * <code>attributes</code>
1511 AttributeSet removeAttributes(AttributeSet old, AttributeSet attributes);
1514 * Removes all attributes specified by <code>names</code> from
1515 * <code>old</code> and returns the resulting <code>AttributeSet</code>.
1517 * @param old the set of attributes from which to remove attributes
1518 * @param names the names of the attributes to be removed from
1521 * @return the attributes of <code>old</code> minus the attributes in
1522 * <code>attributes</code>
1524 AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names);
1528 * A sequence of data that can be edited. This is were the actual content
1529 * in <code>AbstractDocument</code>'s is stored.
1531 public interface Content
1534 * Creates a {@link Position} that keeps track of the location at
1535 * <code>offset</code>.
1537 * @return a {@link Position} that keeps track of the location at
1538 * <code>offset</code>.
1540 * @throw BadLocationException if <code>offset</code> is not a valid
1541 * location in this <code>Content</code> model
1543 Position createPosition(int offset) throws BadLocationException;
1546 * Returns the length of the content.
1548 * @return the length of the content
1553 * Inserts a string into the content model.
1555 * @param where the offset at which to insert the string
1556 * @param str the string to be inserted
1558 * @return an <code>UndoableEdit</code> or <code>null</code> if undo is
1559 * not supported by this <code>Content</code> model
1561 * @throws BadLocationException if <code>where</code> is not a valid
1562 * location in this <code>Content</code> model
1564 UndoableEdit insertString(int where, String str)
1565 throws BadLocationException;
1568 * Removes a piece of content from the content model.
1570 * @param where the offset at which to remove content
1571 * @param nitems the number of characters to be removed
1573 * @return an <code>UndoableEdit</code> or <code>null</code> if undo is
1574 * not supported by this <code>Content</code> model
1576 * @throws BadLocationException if <code>where</code> is not a valid
1577 * location in this <code>Content</code> model
1579 UndoableEdit remove(int where, int nitems) throws BadLocationException;
1582 * Returns a piece of content.
1584 * @param where the start offset of the requested fragment
1585 * @param len the length of the requested fragment
1587 * @return the requested fragment
1588 * @throws BadLocationException if <code>offset</code> or
1589 * <code>offset + len</code>is not a valid
1590 * location in this <code>Content</code> model
1592 String getString(int where, int len) throws BadLocationException;
1595 * Fetches a piece of content and stores it in <code>txt</code>.
1597 * @param where the start offset of the requested fragment
1598 * @param len the length of the requested fragment
1599 * @param txt the <code>Segment</code> where to fragment is stored into
1601 * @throws BadLocationException if <code>offset</code> or
1602 * <code>offset + len</code>is not a valid
1603 * location in this <code>Content</code> model
1605 void getChars(int where, int len, Segment txt) throws BadLocationException;
1609 * An abstract base implementation of the {@link Element} interface.
1611 public abstract class AbstractElement
1612 implements Element, MutableAttributeSet, TreeNode, Serializable
1614 /** The serialization UID (compatible with JDK1.5). */
1615 private static final long serialVersionUID = 1712240033321461704L;
1617 /** The number of characters that this Element spans. */
1620 /** The starting offset of this Element. */
1623 /** The attributes of this Element. */
1624 AttributeSet attributes;
1626 /** The parent element. */
1627 Element element_parent;
1629 /** The parent in the TreeNode interface. */
1630 TreeNode tree_parent;
1632 /** The children of this element. */
1633 Vector tree_children;
1636 * Creates a new instance of <code>AbstractElement</code> with a
1637 * specified parent <code>Element</code> and <code>AttributeSet</code>.
1639 * @param p the parent of this <code>AbstractElement</code>
1640 * @param s the attributes to be assigned to this
1641 * <code>AbstractElement</code>
1643 public AbstractElement(Element p, AttributeSet s)
1646 AttributeContext ctx = getAttributeContext();
1647 attributes = ctx.getEmptySet();
1653 * Returns the child nodes of this <code>Element</code> as an
1654 * <code>Enumeration</code> of {@link TreeNode}s.
1656 * @return the child nodes of this <code>Element</code> as an
1657 * <code>Enumeration</code> of {@link TreeNode}s
1659 public abstract Enumeration children();
1662 * Returns <code>true</code> if this <code>AbstractElement</code>
1665 * @return <code>true</code> if this <code>AbstractElement</code>
1668 public abstract boolean getAllowsChildren();
1671 * Returns the child of this <code>AbstractElement</code> at
1672 * <code>index</code>.
1674 * @param index the position in the child list of the child element to
1677 * @return the child of this <code>AbstractElement</code> at
1678 * <code>index</code>
1680 public TreeNode getChildAt(int index)
1682 return (TreeNode) tree_children.get(index);
1686 * Returns the number of children of this <code>AbstractElement</code>.
1688 * @return the number of children of this <code>AbstractElement</code>
1690 public int getChildCount()
1692 return tree_children.size();
1696 * Returns the index of a given child <code>TreeNode</code> or
1697 * <code>-1</code> if <code>node</code> is not a child of this
1698 * <code>AbstractElement</code>.
1700 * @param node the node for which the index is requested
1702 * @return the index of a given child <code>TreeNode</code> or
1703 * <code>-1</code> if <code>node</code> is not a child of this
1704 * <code>AbstractElement</code>
1706 public int getIndex(TreeNode node)
1708 return tree_children.indexOf(node);
1712 * Returns the parent <code>TreeNode</code> of this
1713 * <code>AbstractElement</code> or <code>null</code> if this element
1716 * @return the parent <code>TreeNode</code> of this
1717 * <code>AbstractElement</code> or <code>null</code> if this
1718 * element has no parent
1720 public TreeNode getParent()
1726 * Returns <code>true</code> if this <code>AbstractElement</code> is a
1727 * leaf element, <code>false</code> otherwise.
1729 * @return <code>true</code> if this <code>AbstractElement</code> is a
1730 * leaf element, <code>false</code> otherwise
1732 public abstract boolean isLeaf();
1735 * Adds an attribute to this element.
1737 * @param name the name of the attribute to be added
1738 * @param value the value of the attribute to be added
1740 public void addAttribute(Object name, Object value)
1742 attributes = getAttributeContext().addAttribute(attributes, name, value);
1746 * Adds a set of attributes to this element.
1748 * @param attrs the attributes to be added to this element
1750 public void addAttributes(AttributeSet attrs)
1752 attributes = getAttributeContext().addAttributes(attributes, attrs);
1756 * Removes an attribute from this element.
1758 * @param name the name of the attribute to be removed
1760 public void removeAttribute(Object name)
1762 attributes = getAttributeContext().removeAttribute(attributes, name);
1766 * Removes a set of attributes from this element.
1768 * @param attrs the attributes to be removed
1770 public void removeAttributes(AttributeSet attrs)
1772 attributes = getAttributeContext().removeAttributes(attributes, attrs);
1776 * Removes a set of attribute from this element.
1778 * @param names the names of the attributes to be removed
1780 public void removeAttributes(Enumeration<?> names)
1782 attributes = getAttributeContext().removeAttributes(attributes, names);
1786 * Sets the parent attribute set against which the element can resolve
1787 * attributes that are not defined in itself.
1789 * @param parent the resolve parent to set
1791 public void setResolveParent(AttributeSet parent)
1793 attributes = getAttributeContext().addAttribute(attributes,
1799 * Returns <code>true</code> if this element contains the specified
1802 * @param name the name of the attribute to check
1803 * @param value the value of the attribute to check
1805 * @return <code>true</code> if this element contains the specified
1808 public boolean containsAttribute(Object name, Object value)
1810 return attributes.containsAttribute(name, value);
1814 * Returns <code>true</code> if this element contains all of the
1815 * specified attributes.
1817 * @param attrs the attributes to check
1819 * @return <code>true</code> if this element contains all of the
1820 * specified attributes
1822 public boolean containsAttributes(AttributeSet attrs)
1824 return attributes.containsAttributes(attrs);
1828 * Returns a copy of the attributes of this element.
1830 * @return a copy of the attributes of this element
1832 public AttributeSet copyAttributes()
1834 return attributes.copyAttributes();
1838 * Returns the attribute value with the specified key. If this attribute
1839 * is not defined in this element and this element has a resolving
1840 * parent, the search goes upward to the resolve parent chain.
1842 * @param key the key of the requested attribute
1844 * @return the attribute value for <code>key</code> of <code>null</code>
1845 * if <code>key</code> is not found locally and cannot be resolved
1846 * in this element's resolve parents
1848 public Object getAttribute(Object key)
1850 Object result = attributes.getAttribute(key);
1853 AttributeSet resParent = getResolveParent();
1854 if (resParent != null)
1855 result = resParent.getAttribute(key);
1861 * Returns the number of defined attributes in this element.
1863 * @return the number of defined attributes in this element
1865 public int getAttributeCount()
1867 return attributes.getAttributeCount();
1871 * Returns the names of the attributes of this element.
1873 * @return the names of the attributes of this element
1875 public Enumeration<?> getAttributeNames()
1877 return attributes.getAttributeNames();
1881 * Returns the resolve parent of this element.
1882 * This is taken from the AttributeSet, but if this is null,
1883 * this method instead returns the Element's parent's
1886 * @return the resolve parent of this element
1888 * @see #setResolveParent(AttributeSet)
1890 public AttributeSet getResolveParent()
1892 return attributes.getResolveParent();
1896 * Returns <code>true</code> if an attribute with the specified name
1897 * is defined in this element, <code>false</code> otherwise.
1899 * @param attrName the name of the requested attributes
1901 * @return <code>true</code> if an attribute with the specified name
1902 * is defined in this element, <code>false</code> otherwise
1904 public boolean isDefined(Object attrName)
1906 return attributes.isDefined(attrName);
1910 * Returns <code>true</code> if the specified <code>AttributeSet</code>
1911 * is equal to this element's <code>AttributeSet</code>, <code>false</code>
1914 * @param attrs the attributes to compare this element to
1916 * @return <code>true</code> if the specified <code>AttributeSet</code>
1917 * is equal to this element's <code>AttributeSet</code>,
1918 * <code>false</code> otherwise
1920 public boolean isEqual(AttributeSet attrs)
1922 return attributes.isEqual(attrs);
1926 * Returns the attributes of this element.
1928 * @return the attributes of this element
1930 public AttributeSet getAttributes()
1936 * Returns the {@link Document} to which this element belongs.
1938 * @return the {@link Document} to which this element belongs
1940 public Document getDocument()
1942 return AbstractDocument.this;
1946 * Returns the child element at the specified <code>index</code>.
1948 * @param index the index of the requested child element
1950 * @return the requested element
1952 public abstract Element getElement(int index);
1955 * Returns the name of this element.
1957 * @return the name of this element
1959 public String getName()
1961 return (String) attributes.getAttribute(ElementNameAttribute);
1965 * Returns the parent element of this element.
1967 * @return the parent element of this element
1969 public Element getParentElement()
1971 return element_parent;
1975 * Returns the offset inside the document model that is after the last
1976 * character of this element.
1978 * @return the offset inside the document model that is after the last
1979 * character of this element
1981 public abstract int getEndOffset();
1984 * Returns the number of child elements of this element.
1986 * @return the number of child elements of this element
1988 public abstract int getElementCount();
1991 * Returns the index of the child element that spans the specified
1992 * offset in the document model.
1994 * @param offset the offset for which the responsible element is searched
1996 * @return the index of the child element that spans the specified
1997 * offset in the document model
1999 public abstract int getElementIndex(int offset);
2002 * Returns the start offset if this element inside the document model.
2004 * @return the start offset if this element inside the document model
2006 public abstract int getStartOffset();
2009 * Prints diagnostic output to the specified stream.
2011 * @param stream the stream to write to
2012 * @param indent the indentation level
2014 public void dump(PrintStream stream, int indent)
2016 StringBuffer b = new StringBuffer();
2017 for (int i = 0; i < indent; ++i)
2020 b.append(getName());
2021 // Dump attributes if there are any.
2022 if (getAttributeCount() > 0)
2025 Enumeration attNames = getAttributeNames();
2026 while (attNames.hasMoreElements())
2028 for (int i = 0; i < indent + 2; ++i)
2030 Object attName = attNames.nextElement();
2033 Object attribute = getAttribute(attName);
2034 b.append(attribute);
2038 if (getAttributeCount() > 0)
2040 for (int i = 0; i < indent; ++i)
2045 // Dump element content for leaf elements.
2048 for (int i = 0; i < indent + 2; ++i)
2050 int start = getStartOffset();
2051 int end = getEndOffset();
2059 b.append(getDocument().getText(start, end - start));
2061 catch (BadLocationException ex)
2063 AssertionError err = new AssertionError("BadLocationException "
2064 + "must not be thrown "
2071 stream.print(b.toString());
2073 // Dump child elements if any.
2074 int count = getElementCount();
2075 for (int i = 0; i < count; ++i)
2077 Element el = getElement(i);
2078 if (el instanceof AbstractElement)
2079 ((AbstractElement) el).dump(stream, indent + 2);
2085 * An implementation of {@link Element} to represent composite
2086 * <code>Element</code>s that contain other <code>Element</code>s.
2088 public class BranchElement extends AbstractElement
2090 /** The serialization UID (compatible with JDK1.5). */
2091 private static final long serialVersionUID = -6037216547466333183L;
2094 * The child elements of this BranchElement.
2096 private Element[] children;
2099 * The number of children in the branch element.
2101 private int numChildren;
2104 * The last found index in getElementIndex(). Used for faster searching.
2106 private int lastIndex;
2109 * Creates a new <code>BranchElement</code> with the specified
2110 * parent and attributes.
2112 * @param parent the parent element of this <code>BranchElement</code>
2113 * @param attributes the attributes to set on this
2114 * <code>BranchElement</code>
2116 public BranchElement(Element parent, AttributeSet attributes)
2118 super(parent, attributes);
2119 children = new Element[1];
2125 * Returns the children of this <code>BranchElement</code>.
2127 * @return the children of this <code>BranchElement</code>
2129 public Enumeration children()
2131 if (numChildren == 0)
2134 Vector tmp = new Vector();
2136 for (int index = 0; index < numChildren; ++index)
2137 tmp.add(children[index]);
2139 return tmp.elements();
2143 * Returns <code>true</code> since <code>BranchElements</code> allow
2146 * @return <code>true</code> since <code>BranchElements</code> allow
2149 public boolean getAllowsChildren()
2155 * Returns the child element at the specified <code>index</code>.
2157 * @param index the index of the requested child element
2159 * @return the requested element
2161 public Element getElement(int index)
2163 if (index < 0 || index >= numChildren)
2166 return children[index];
2170 * Returns the number of child elements of this element.
2172 * @return the number of child elements of this element
2174 public int getElementCount()
2180 * Returns the index of the child element that spans the specified
2181 * offset in the document model.
2183 * @param offset the offset for which the responsible element is searched
2185 * @return the index of the child element that spans the specified
2186 * offset in the document model
2188 public int getElementIndex(int offset)
2190 // Implemented using an improved linear search.
2191 // This makes use of the fact that searches are not random but often
2192 // close to the previous search. So we try to start the binary
2193 // search at the last found index.
2195 int i0 = 0; // The lower bounds.
2196 int i1 = numChildren - 1; // The upper bounds.
2197 int index = -1; // The found index.
2199 int p0 = getStartOffset();
2200 int p1; // Start and end offset local variables.
2202 if (numChildren == 0)
2204 else if (offset >= getEndOffset())
2205 index = numChildren - 1;
2209 if (lastIndex >= i0 && lastIndex <= i1)
2211 Element last = getElement(lastIndex);
2212 p0 = last.getStartOffset();
2213 p1 = last.getEndOffset();
2214 if (offset >= p0 && offset < p1)
2218 // Narrow the search bounds using the lastIndex, even
2219 // if it hasn't been a hit.
2226 // The actual search.
2228 while (i0 <= i1 && index == -1)
2230 i = i0 + (i1 - i0) / 2;
2231 Element el = getElement(i);
2232 p0 = el.getStartOffset();
2233 p1 = el.getEndOffset();
2234 if (offset >= p0 && offset < p1)
2239 else if (offset < p0)
2247 // Didn't find it. Return the boundary index.
2260 * Returns the offset inside the document model that is after the last
2261 * character of this element.
2262 * This is the end offset of the last child element. If this element
2263 * has no children, this method throws a <code>NullPointerException</code>.
2265 * @return the offset inside the document model that is after the last
2266 * character of this element
2268 * @throws NullPointerException if this branch element has no children
2270 public int getEndOffset()
2272 // This might accss one cached element or trigger an NPE for
2273 // numChildren == 0. This is checked by a Mauve test.
2274 Element child = numChildren > 0 ? children[numChildren - 1]
2276 return child.getEndOffset();
2280 * Returns the name of this element. This is {@link #ParagraphElementName}
2283 * @return the name of this element
2285 public String getName()
2287 return ParagraphElementName;
2291 * Returns the start offset of this element inside the document model.
2292 * This is the start offset of the first child element. If this element
2293 * has no children, this method throws a <code>NullPointerException</code>.
2295 * @return the start offset of this element inside the document model
2297 * @throws NullPointerException if this branch element has no children and
2298 * no startOffset value has been cached
2300 public int getStartOffset()
2302 // Do not explicitly throw an NPE here. If the first element is null
2303 // then the NPE gets thrown anyway. If it isn't, then it either
2304 // holds a real value (for numChildren > 0) or a cached value
2305 // (for numChildren == 0) as we don't fully remove elements in replace()
2306 // when removing single elements.
2307 // This is checked by a Mauve test.
2308 return children[0].getStartOffset();
2312 * Returns <code>false</code> since <code>BranchElement</code> are no
2315 * @return <code>false</code> since <code>BranchElement</code> are no
2318 public boolean isLeaf()
2324 * Returns the <code>Element</code> at the specified <code>Document</code>
2327 * @return the <code>Element</code> at the specified <code>Document</code>
2330 * @see #getElementIndex(int)
2332 public Element positionToElement(int position)
2334 // XXX: There is surely a better algorithm
2335 // as beginning from first element each time.
2336 for (int index = 0; index < numChildren; ++index)
2338 Element elem = children[index];
2340 if ((elem.getStartOffset() <= position)
2341 && (position < elem.getEndOffset()))
2349 * Replaces a set of child elements with a new set of child elemens.
2351 * @param offset the start index of the elements to be removed
2352 * @param length the number of elements to be removed
2353 * @param elements the new elements to be inserted
2355 public void replace(int offset, int length, Element[] elements)
2357 int delta = elements.length - length;
2358 int copyFrom = offset + length; // From where to copy.
2359 int copyTo = copyFrom + delta; // Where to copy to.
2360 int numMove = numChildren - copyFrom; // How many elements are moved.
2361 if (numChildren + delta > children.length)
2363 // Gotta grow the array.
2364 int newSize = Math.max(2 * children.length, numChildren + delta);
2365 Element[] target = new Element[newSize];
2366 System.arraycopy(children, 0, target, 0, offset);
2367 System.arraycopy(elements, 0, target, offset, elements.length);
2368 System.arraycopy(children, copyFrom, target, copyTo, numMove);
2373 System.arraycopy(children, copyFrom, children, copyTo, numMove);
2374 System.arraycopy(elements, 0, children, offset, elements.length);
2376 numChildren += delta;
2380 * Returns a string representation of this element.
2382 * @return a string representation of this element
2384 public String toString()
2386 return ("BranchElement(" + getName() + ") "
2387 + getStartOffset() + "," + getEndOffset() + "\n");
2392 * Stores the changes when a <code>Document</code> is beeing modified.
2394 public class DefaultDocumentEvent extends CompoundEdit
2395 implements DocumentEvent
2397 /** The serialization UID (compatible with JDK1.5). */
2398 private static final long serialVersionUID = 5230037221564563284L;
2401 * The threshold that indicates when we switch to using a Hashtable.
2403 private static final int THRESHOLD = 10;
2405 /** The starting offset of the change. */
2408 /** The length of the change. */
2411 /** The type of change. */
2412 private DocumentEvent.EventType type;
2415 * Maps <code>Element</code> to their change records. This is only
2416 * used when the changes array gets too big. We can use an
2417 * (unsync'ed) HashMap here, since changes to this are (should) always
2418 * be performed inside a write lock.
2420 private HashMap changes;
2423 * Indicates if this event has been modified or not. This is used to
2424 * determine if this event is thrown.
2426 private boolean modified;
2429 * Creates a new <code>DefaultDocumentEvent</code>.
2431 * @param offset the starting offset of the change
2432 * @param length the length of the change
2433 * @param type the type of change
2435 public DefaultDocumentEvent(int offset, int length,
2436 DocumentEvent.EventType type)
2438 this.offset = offset;
2439 this.length = length;
2445 * Adds an UndoableEdit to this <code>DocumentEvent</code>. If this
2446 * edit is an instance of {@link ElementEdit}, then this record can
2447 * later be fetched by calling {@link #getChange}.
2449 * @param edit the undoable edit to add
2451 public boolean addEdit(UndoableEdit edit)
2453 // Start using Hashtable when we pass a certain threshold. This
2454 // gives a good memory/performance compromise.
2455 if (changes == null && edits.size() > THRESHOLD)
2457 changes = new HashMap();
2458 int count = edits.size();
2459 for (int i = 0; i < count; i++)
2461 Object o = edits.elementAt(i);
2462 if (o instanceof ElementChange)
2464 ElementChange ec = (ElementChange) o;
2465 changes.put(ec.getElement(), ec);
2470 if (changes != null && edit instanceof ElementChange)
2472 ElementChange elEdit = (ElementChange) edit;
2473 changes.put(elEdit.getElement(), elEdit);
2475 return super.addEdit(edit);
2479 * Returns the document that has been modified.
2481 * @return the document that has been modified
2483 public Document getDocument()
2485 return AbstractDocument.this;
2489 * Returns the length of the modification.
2491 * @return the length of the modification
2493 public int getLength()
2499 * Returns the start offset of the modification.
2501 * @return the start offset of the modification
2503 public int getOffset()
2509 * Returns the type of the modification.
2511 * @return the type of the modification
2513 public DocumentEvent.EventType getType()
2519 * Returns the changes for an element.
2521 * @param elem the element for which the changes are requested
2523 * @return the changes for <code>elem</code> or <code>null</code> if
2524 * <code>elem</code> has not been changed
2526 public ElementChange getChange(Element elem)
2528 ElementChange change = null;
2529 if (changes != null)
2531 change = (ElementChange) changes.get(elem);
2535 int count = edits.size();
2536 for (int i = 0; i < count && change == null; i++)
2538 Object o = edits.get(i);
2539 if (o instanceof ElementChange)
2541 ElementChange ec = (ElementChange) o;
2542 if (elem.equals(ec.getElement()))
2551 * Returns a String description of the change event. This returns the
2552 * toString method of the Vector of edits.
2554 public String toString()
2556 return edits.toString();
2561 * An implementation of {@link DocumentEvent.ElementChange} to be added
2562 * to {@link DefaultDocumentEvent}s.
2564 public static class ElementEdit extends AbstractUndoableEdit
2565 implements DocumentEvent.ElementChange
2567 /** The serial version UID of ElementEdit. */
2568 private static final long serialVersionUID = -1216620962142928304L;
2571 * The changed element.
2573 private Element elem;
2576 * The index of the change.
2581 * The removed elements.
2583 private Element[] removed;
2586 * The added elements.
2588 private Element[] added;
2591 * Creates a new <code>ElementEdit</code>.
2593 * @param elem the changed element
2594 * @param index the index of the change
2595 * @param removed the removed elements
2596 * @param added the added elements
2598 public ElementEdit(Element elem, int index,
2599 Element[] removed, Element[] added)
2603 this.removed = removed;
2608 * Returns the added elements.
2610 * @return the added elements
2612 public Element[] getChildrenAdded()
2618 * Returns the removed elements.
2620 * @return the removed elements
2622 public Element[] getChildrenRemoved()
2628 * Returns the changed element.
2630 * @return the changed element
2632 public Element getElement()
2638 * Returns the index of the change.
2640 * @return the index of the change
2642 public int getIndex()
2649 * An implementation of {@link Element} that represents a leaf in the
2650 * document structure. This is used to actually store content.
2652 public class LeafElement extends AbstractElement
2654 /** The serialization UID (compatible with JDK1.5). */
2655 private static final long serialVersionUID = -8906306331347768017L;
2658 * Manages the start offset of this element.
2660 private Position startPos;
2663 * Manages the end offset of this element.
2665 private Position endPos;
2668 * Creates a new <code>LeafElement</code>.
2670 * @param parent the parent of this <code>LeafElement</code>
2671 * @param attributes the attributes to be set
2672 * @param start the start index of this element inside the document model
2673 * @param end the end index of this element inside the document model
2675 public LeafElement(Element parent, AttributeSet attributes, int start,
2678 super(parent, attributes);
2681 startPos = createPosition(start);
2682 endPos = createPosition(end);
2684 catch (BadLocationException ex)
2687 as = new AssertionError("BadLocationException thrown "
2688 + "here. start=" + start
2690 + ", length=" + getLength());
2697 * Returns <code>null</code> since <code>LeafElement</code>s cannot have
2700 * @return <code>null</code> since <code>LeafElement</code>s cannot have
2703 public Enumeration children()
2709 * Returns <code>false</code> since <code>LeafElement</code>s cannot have
2712 * @return <code>false</code> since <code>LeafElement</code>s cannot have
2715 public boolean getAllowsChildren()
2721 * Returns <code>null</code> since <code>LeafElement</code>s cannot have
2724 * @return <code>null</code> since <code>LeafElement</code>s cannot have
2727 public Element getElement(int index)
2733 * Returns <code>0</code> since <code>LeafElement</code>s cannot have
2736 * @return <code>0</code> since <code>LeafElement</code>s cannot have
2739 public int getElementCount()
2745 * Returns <code>-1</code> since <code>LeafElement</code>s cannot have
2748 * @return <code>-1</code> since <code>LeafElement</code>s cannot have
2751 public int getElementIndex(int offset)
2757 * Returns the end offset of this <code>Element</code> inside the
2760 * @return the end offset of this <code>Element</code> inside the
2763 public int getEndOffset()
2765 return endPos.getOffset();
2769 * Returns the name of this <code>Element</code>. This is
2770 * {@link #ContentElementName} in this case.
2772 * @return the name of this <code>Element</code>
2774 public String getName()
2776 String name = super.getName();
2778 name = ContentElementName;
2783 * Returns the start offset of this <code>Element</code> inside the
2786 * @return the start offset of this <code>Element</code> inside the
2789 public int getStartOffset()
2791 return startPos.getOffset();
2795 * Returns <code>true</code>.
2797 * @return <code>true</code>
2799 public boolean isLeaf()
2805 * Returns a string representation of this <code>Element</code>.
2807 * @return a string representation of this <code>Element</code>
2809 public String toString()
2811 return ("LeafElement(" + getName() + ") "
2812 + getStartOffset() + "," + getEndOffset() + "\n");
2817 * The root element for bidirectional text.
2819 private class BidiRootElement
2820 extends BranchElement
2823 * Creates a new bidi root element.
2831 * Returns the name of the element.
2833 * @return the name of the element
2835 public String getName()
2837 return BidiRootName;
2842 * A leaf element for the bidi structure.
2844 private class BidiElement
2848 * Creates a new BidiElement.
2850 * @param parent the parent element
2851 * @param start the start offset
2852 * @param end the end offset
2853 * @param level the bidi level
2855 BidiElement(Element parent, int start, int end, int level)
2857 super(parent, new SimpleAttributeSet(), start, end);
2858 addAttribute(StyleConstants.BidiLevel, new Integer(level));
2862 * Returns the name of the element.
2864 * @return the name of the element
2866 public String getName()
2868 return BidiElementName;
2872 /** A class whose methods delegate to the insert, remove and replace methods
2873 * of this document which do not check for an installed DocumentFilter.
2875 class Bypass extends DocumentFilter.FilterBypass
2878 public Document getDocument()
2880 return AbstractDocument.this;
2883 public void insertString(int offset, String string, AttributeSet attr)
2884 throws BadLocationException
2886 AbstractDocument.this.insertStringImpl(offset, string, attr);
2889 public void remove(int offset, int length)
2890 throws BadLocationException
2892 AbstractDocument.this.removeImpl(offset, length);
2895 public void replace(int offset, int length, String string,
2897 throws BadLocationException
2899 AbstractDocument.this.replaceImpl(offset, length, string, attrs);