001    /* AbstractDocument.java --
002       Copyright (C) 2002, 2004, 2005, 2006  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.font.TextAttribute;
042    import java.io.PrintStream;
043    import java.io.Serializable;
044    import java.text.Bidi;
045    import java.util.ArrayList;
046    import java.util.Dictionary;
047    import java.util.Enumeration;
048    import java.util.EventListener;
049    import java.util.HashMap;
050    import java.util.Hashtable;
051    import java.util.Vector;
052    
053    import javax.swing.event.DocumentEvent;
054    import javax.swing.event.DocumentListener;
055    import javax.swing.event.EventListenerList;
056    import javax.swing.event.UndoableEditEvent;
057    import javax.swing.event.UndoableEditListener;
058    import javax.swing.text.DocumentFilter;
059    import javax.swing.tree.TreeNode;
060    import javax.swing.undo.AbstractUndoableEdit;
061    import javax.swing.undo.CompoundEdit;
062    import javax.swing.undo.UndoableEdit;
063    
064    /**
065     * An abstract base implementation for the {@link Document} interface.
066     * This class provides some common functionality for all <code>Element</code>s,
067     * most notably it implements a locking mechanism to make document modification
068     * thread-safe.
069     *
070     * @author original author unknown
071     * @author Roman Kennke (roman@kennke.org)
072     */
073    public abstract class AbstractDocument implements Document, Serializable
074    {
075      /** The serialization UID (compatible with JDK1.5). */
076      private static final long serialVersionUID = 6842927725919637215L;
077    
078      /**
079       * Standard error message to indicate a bad location.
080       */
081      protected static final String BAD_LOCATION = "document location failure";
082    
083      /**
084       * Standard name for unidirectional <code>Element</code>s.
085       */
086      public static final String BidiElementName = "bidi level";
087    
088      /**
089       * Standard name for content <code>Element</code>s. These are usually
090       * {@link LeafElement}s.
091       */
092      public static final String ContentElementName = "content";
093    
094      /**
095       * Standard name for paragraph <code>Element</code>s. These are usually
096       * {@link BranchElement}s.
097       */
098      public static final String ParagraphElementName = "paragraph";
099    
100      /**
101       * Standard name for section <code>Element</code>s. These are usually
102       * {@link DefaultStyledDocument.SectionElement}s.
103       */
104      public static final String SectionElementName = "section";
105    
106      /**
107       * Attribute key for storing the element name.
108       */
109      public static final String ElementNameAttribute = "$ename";
110    
111      /**
112       * Standard name for the bidi root element.
113       */
114      private static final String BidiRootName = "bidi root";
115    
116      /**
117       * Key for storing the asynchronous load priority.
118       */
119      private static final String AsyncLoadPriority = "load priority";
120    
121      /**
122       * Key for storing the I18N state.
123       */
124      private static final String I18N = "i18n";
125    
126      /**
127       * The actual content model of this <code>Document</code>.
128       */
129      Content content;
130    
131      /**
132       * The AttributeContext for this <code>Document</code>.
133       */
134      AttributeContext context;
135    
136      /**
137       * The currently installed <code>DocumentFilter</code>.
138       */
139      DocumentFilter documentFilter;
140    
141      /**
142       * The documents properties.
143       */
144      Dictionary properties;
145    
146      /**
147       * Manages event listeners for this <code>Document</code>.
148       */
149      protected EventListenerList listenerList = new EventListenerList();
150      
151      /**
152       * Stores the current writer thread.  Used for locking.
153       */ 
154      private Thread currentWriter = null;
155      
156      /**
157       * The number of readers.  Used for locking.
158       */
159      private int numReaders = 0;
160      
161      /**
162       * The number of current writers. If this is > 1 then the same thread entered
163       * the write lock more than once.
164       */
165      private int numWriters = 0;  
166    
167      /** An instance of a DocumentFilter.FilterBypass which allows calling
168       * the insert, remove and replace method without checking for an installed
169       * document filter.
170       */
171      private DocumentFilter.FilterBypass bypass;
172    
173      /**
174       * The bidi root element.
175       */
176      private BidiRootElement bidiRoot;
177    
178      /**
179       * True when we are currently notifying any listeners. This is used
180       * to detect illegal situations in writeLock().
181       */
182      private transient boolean notifyListeners;
183    
184      /**
185       * Creates a new <code>AbstractDocument</code> with the specified
186       * {@link Content} model.
187       *
188       * @param doc the <code>Content</code> model to be used in this
189       *        <code>Document<code>
190       *
191       * @see GapContent
192       * @see StringContent
193       */
194      protected AbstractDocument(Content doc)
195      {
196        this(doc, StyleContext.getDefaultStyleContext());
197      }
198    
199      /**
200       * Creates a new <code>AbstractDocument</code> with the specified
201       * {@link Content} model and {@link AttributeContext}.
202       *
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
206       *
207       * @see GapContent
208       * @see StringContent
209       */
210      protected AbstractDocument(Content doc, AttributeContext ctx)
211      {
212        content = doc;
213        context = ctx;
214    
215        // FIXME: Fully implement bidi.
216        bidiRoot = new BidiRootElement();
217    
218        // FIXME: This is determined using a Mauve test. Make the document
219        // actually use this.
220        putProperty(I18N, Boolean.FALSE);
221    
222        // Add one child to the bidi root.
223        writeLock();
224        try
225          {
226            Element[] children = new Element[1];
227            children[0] = new BidiElement(bidiRoot, 0, 1, 0);
228            bidiRoot.replace(0, 0, children);
229          }
230        finally
231          {
232            writeUnlock();
233          }
234      }
235      
236      /** Returns the DocumentFilter.FilterBypass instance for this
237       * document and create it if it does not exist yet.
238       *  
239       * @return This document's DocumentFilter.FilterBypass instance.
240       */
241      private DocumentFilter.FilterBypass getBypass()
242      {
243        if (bypass == null)
244          bypass = new Bypass();
245        
246        return bypass;
247      }
248    
249      /**
250       * Returns the paragraph {@link Element} that holds the specified position.
251       *
252       * @param pos the position for which to get the paragraph element
253       *
254       * @return the paragraph {@link Element} that holds the specified position
255       */
256      public abstract Element getParagraphElement(int pos);
257    
258      /**
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
263       * here.
264       *
265       * @return the default root {@link Element} of this <code>Document</code>
266       */
267      public abstract Element getDefaultRootElement();
268    
269      /**
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.
275       *
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
278       *
279       * @return the new branch <code>Element</code>
280       *
281       * @see BranchElement
282       */
283      protected Element createBranchElement(Element parent,
284                                            AttributeSet attributes)
285      {
286        return new BranchElement(parent, attributes);
287      }
288    
289      /**
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.
295       *
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
298       *
299       * @return the new branch <code>Element</code>
300       *
301       * @see LeafElement
302       */
303      protected Element createLeafElement(Element parent, AttributeSet attributes,
304                                          int start, int end)
305      {
306        return new LeafElement(parent, attributes, start, end);
307      }
308    
309      /**
310       * Creates a {@link Position} that keeps track of the location at the
311       * specified <code>offset</code>.
312       *
313       * @param offset the location in the document to keep track by the new
314       *        <code>Position</code>
315       *
316       * @return the newly created <code>Position</code>
317       *
318       * @throws BadLocationException if <code>offset</code> is not a valid
319       *         location in the documents content model
320       */
321      public synchronized Position createPosition(final int offset)
322        throws BadLocationException
323      {
324        return content.createPosition(offset);
325      }
326    
327      /**
328       * Notifies all registered listeners when the document model changes.
329       *
330       * @param event the <code>DocumentEvent</code> to be fired
331       */
332      protected void fireChangedUpdate(DocumentEvent event)
333      {
334        notifyListeners = true;
335        try
336          {
337            DocumentListener[] listeners = getDocumentListeners();
338            for (int index = 0; index < listeners.length; ++index)
339              listeners[index].changedUpdate(event);
340          }
341        finally
342          {
343            notifyListeners = false;
344          }
345      }
346    
347      /**
348       * Notifies all registered listeners when content is inserted in the document
349       * model.
350       *
351       * @param event the <code>DocumentEvent</code> to be fired
352       */
353      protected void fireInsertUpdate(DocumentEvent event)
354      {
355        notifyListeners = true;
356        try
357          {
358            DocumentListener[] listeners = getDocumentListeners();
359            for (int index = 0; index < listeners.length; ++index)
360              listeners[index].insertUpdate(event);
361          }
362        finally
363          {
364            notifyListeners = false;
365          }
366      }
367    
368      /**
369       * Notifies all registered listeners when content is removed from the
370       * document model.
371       *
372       * @param event the <code>DocumentEvent</code> to be fired
373       */
374      protected void fireRemoveUpdate(DocumentEvent event)
375      {
376        notifyListeners = true;
377        try
378          {
379            DocumentListener[] listeners = getDocumentListeners();
380            for (int index = 0; index < listeners.length; ++index)
381              listeners[index].removeUpdate(event);
382          }
383        finally
384          {
385            notifyListeners = false;
386          }
387      }
388    
389      /**
390       * Notifies all registered listeners when an <code>UndoableEdit</code> has
391       * been performed on this <code>Document</code>.
392       *
393       * @param event the <code>UndoableEditEvent</code> to be fired
394       */
395      protected void fireUndoableEditUpdate(UndoableEditEvent event)
396      {
397        UndoableEditListener[] listeners = getUndoableEditListeners();
398    
399        for (int index = 0; index < listeners.length; ++index)
400          listeners[index].undoableEditHappened(event);
401      }
402    
403      /**
404       * Returns the asynchronous loading priority. Returns <code>-1</code> if this
405       * document should not be loaded asynchronously.
406       *
407       * @return the asynchronous loading priority
408       */
409      public int getAsynchronousLoadPriority()
410      {
411        Object val = getProperty(AsyncLoadPriority);
412        int prio = -1;
413        if (val != null)
414          prio = ((Integer) val).intValue(); 
415        return prio;
416      }
417    
418      /**
419       * Returns the {@link AttributeContext} used in this <code>Document</code>.
420       *
421       * @return the {@link AttributeContext} used in this <code>Document</code>
422       */
423      protected final AttributeContext getAttributeContext()
424      {
425        return context;
426      }
427    
428      /**
429       * Returns the root element for bidirectional content.
430       *
431       * @return the root element for bidirectional content
432       */
433      public Element getBidiRootElement()
434      {
435        return bidiRoot;
436      }
437    
438      /**
439       * Returns the {@link Content} model for this <code>Document</code>
440       *
441       * @return the {@link Content} model for this <code>Document</code>
442       *
443       * @see GapContent
444       * @see StringContent
445       */
446      protected final Content getContent()
447      {
448        return content;
449      }
450    
451      /**
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.
456       *
457       * @return the thread that currently modifies this <code>Document</code>
458       *         if there is one, otherwise <code>null</code>
459       */
460      protected final synchronized Thread getCurrentWriter()
461      {
462        return currentWriter;
463      }
464    
465      /**
466       * Returns the properties of this <code>Document</code>.
467       *
468       * @return the properties of this <code>Document</code>
469       */
470      public Dictionary<Object, Object> getDocumentProperties()
471      {
472        // FIXME: make me thread-safe
473        if (properties == null)
474          properties = new Hashtable();
475    
476        return properties;
477      }
478    
479      /**
480       * Returns a {@link Position} which will always mark the end of the
481       * <code>Document</code>.
482       *
483       * @return a {@link Position} which will always mark the end of the
484       *         <code>Document</code>
485       */
486      public final Position getEndPosition()
487      {
488        Position p;
489        try
490          {
491            p = createPosition(content.length());
492          }
493        catch (BadLocationException ex)
494          {
495            // Shouldn't really happen.
496            p = null;
497          }
498        return p;
499      }
500    
501      /**
502       * Returns the length of this <code>Document</code>'s content.
503       *
504       * @return the length of this <code>Document</code>'s content
505       */
506      public int getLength()
507      {
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;
512      }
513    
514      /**
515       * Returns all registered listeners of a given listener type.
516       *
517       * @param listenerType the type of the listeners to be queried
518       *
519       * @return all registered listeners of the specified type
520       */
521      public <T extends EventListener> T[] getListeners(Class<T> listenerType)
522      {
523        return listenerList.getListeners(listenerType);
524      }
525    
526      /**
527       * Returns a property from this <code>Document</code>'s property list.
528       *
529       * @param key the key of the property to be fetched
530       *
531       * @return the property for <code>key</code> or <code>null</code> if there
532       *         is no such property stored
533       */
534      public final Object getProperty(Object key)
535      {
536        // FIXME: make me thread-safe
537        Object value = null;
538        if (properties != null)
539          value = properties.get(key);
540    
541        return value;
542      }
543    
544      /**
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
549       * here.
550       *
551       * @return all root elements of this <code>Document</code>
552       */
553      public Element[] getRootElements()
554      {
555        Element[] elements = new Element[2];
556        elements[0] = getDefaultRootElement();
557        elements[1] = getBidiRootElement();
558        return elements;
559      }
560    
561      /**
562       * Returns a {@link Position} which will always mark the beginning of the
563       * <code>Document</code>.
564       *
565       * @return a {@link Position} which will always mark the beginning of the
566       *         <code>Document</code>
567       */
568      public final Position getStartPosition()
569      {
570        Position p;
571        try
572          {
573            p = createPosition(0);
574          }
575        catch (BadLocationException ex)
576          {
577            // Shouldn't really happen.
578            p = null;
579          }
580        return p;
581      }
582    
583      /**
584       * Returns a piece of this <code>Document</code>'s content.
585       *
586       * @param offset the start offset of the content
587       * @param length the length of the content
588       *
589       * @return the piece of content specified by <code>offset</code> and
590       *         <code>length</code>
591       *
592       * @throws BadLocationException if <code>offset</code> or <code>offset +
593       *         length</code> are invalid locations with this
594       *         <code>Document</code>
595       */
596      public String getText(int offset, int length) throws BadLocationException
597      {
598        return content.getString(offset, length);
599      }
600    
601      /**
602       * Fetches a piece of this <code>Document</code>'s content and stores
603       * it in the given {@link Segment}.
604       *
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
608       *
609       * @throws BadLocationException if <code>offset</code> or <code>offset +
610       *         length</code> are invalid locations with this
611       *         <code>Document</code>
612       */
613      public void getText(int offset, int length, Segment segment)
614        throws BadLocationException
615      {
616        content.getChars(offset, length, segment);
617      }
618    
619      /**
620       * Inserts a String into this <code>Document</code> at the specified
621       * position and assigning the specified attributes to it.
622       * 
623       * <p>If a {@link DocumentFilter} is installed in this document, the
624       * corresponding method of the filter object is called.</p>
625       * 
626       * <p>The method has no effect when <code>text</code> is <code>null</code>
627       * or has a length of zero.</p>
628       * 
629       *
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
633       *
634       * @throws BadLocationException if <code>offset</code> is not a valid
635       *         location in this <code>Document</code>
636       */
637      public void insertString(int offset, String text, AttributeSet attributes)
638        throws BadLocationException
639      {
640        // Bail out if we have a bogus insertion (Behavior observed in RI).
641        if (text == null || text.length() == 0)
642          return;
643    
644        writeLock();
645        try
646          {
647            if (documentFilter == null)
648              insertStringImpl(offset, text, attributes);
649            else
650              documentFilter.insertString(getBypass(), offset, text, attributes);
651          }
652        finally
653          {
654            writeUnlock();
655          }
656      }
657    
658      void insertStringImpl(int offset, String text, AttributeSet attributes)
659        throws BadLocationException
660      {
661        // Just return when no text to insert was given.
662        if (text == null || text.length() == 0)
663          return;
664        DefaultDocumentEvent event =
665          new DefaultDocumentEvent(offset, text.length(),
666                                   DocumentEvent.EventType.INSERT);
667    
668        UndoableEdit undo = content.insertString(offset, text);
669        if (undo != null)
670          event.addEdit(undo);
671    
672        // Check if we need bidi layout.
673        if (getProperty(I18N).equals(Boolean.FALSE))
674          {
675            Object dir = getProperty(TextAttribute.RUN_DIRECTION);
676            if (TextAttribute.RUN_DIRECTION_RTL.equals(dir))
677              putProperty(I18N, Boolean.TRUE);
678            else
679              {
680                char[] chars = text.toCharArray();
681                if (Bidi.requiresBidi(chars, 0, chars.length))
682                  putProperty(I18N, Boolean.TRUE);
683              }
684          }
685    
686        insertUpdate(event, attributes);
687    
688        fireInsertUpdate(event);
689    
690        if (undo != null)
691          fireUndoableEditUpdate(new UndoableEditEvent(this, undo));
692      }
693    
694      /**
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.
698       *
699       * @param chng the <code>DefaultDocumentEvent</code> describing the change
700       * @param attr the attributes of the changed content
701       */
702      protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr)
703      {
704        if (Boolean.TRUE.equals(getProperty(I18N)))
705          updateBidi(chng);
706      }
707    
708      /**
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.
712       *
713       * @param chng the <code>DefaultDocumentEvent</code> describing the change
714       */
715      protected void postRemoveUpdate(DefaultDocumentEvent chng)
716      {
717        if (Boolean.TRUE.equals(getProperty(I18N)))
718          updateBidi(chng);
719      }
720    
721      /**
722       * Stores a property in this <code>Document</code>'s property list.
723       *
724       * @param key the key of the property to be stored
725       * @param value the value of the property to be stored
726       */
727      public final void putProperty(Object key, Object value)
728      {
729        // FIXME: make me thread-safe
730        if (properties == null)
731          properties = new Hashtable();
732    
733        if (value == null)
734          properties.remove(key);
735        else
736          properties.put(key, value);
737    
738        // Update bidi structure if the RUN_DIRECTION is set.
739        if (TextAttribute.RUN_DIRECTION.equals(key))
740          {
741            if (TextAttribute.RUN_DIRECTION_RTL.equals(value)
742                && Boolean.FALSE.equals(getProperty(I18N)))
743              putProperty(I18N, Boolean.TRUE);
744    
745            if (Boolean.TRUE.equals(getProperty(I18N)))
746              {
747                writeLock();
748                try
749                  {
750                    DefaultDocumentEvent ev =
751                      new DefaultDocumentEvent(0, getLength(),
752                                               DocumentEvent.EventType.INSERT);
753                    updateBidi(ev);
754                  }
755                finally
756                  {
757                    writeUnlock();
758                  }
759              }
760          }
761      }
762    
763      /**
764       * Updates the bidi element structure.
765       *
766       * @param ev the document event for the change
767       */
768      private void updateBidi(DefaultDocumentEvent ev)
769      {
770        // Determine start and end offset of the paragraphs to be scanned.
771        int start = 0;
772        int end = 0;
773        DocumentEvent.EventType type = ev.getType();
774        if (type == DocumentEvent.EventType.INSERT
775            || type == DocumentEvent.EventType.CHANGE)
776          {
777            int offs = ev.getOffset();
778            int endOffs = offs + ev.getLength();
779            start = getParagraphElement(offs).getStartOffset();
780            end = getParagraphElement(endOffs).getEndOffset();
781          }
782        else if (type == DocumentEvent.EventType.REMOVE)
783          {
784            Element par = getParagraphElement(ev.getOffset());
785            start = par.getStartOffset();
786            end = par.getEndOffset();
787          }
788        else
789          assert false : "Unknown event type";
790    
791        // Determine the bidi levels for the affected range.
792        Bidi[] bidis = getBidis(start, end);
793    
794        int removeFrom = 0;
795        int removeTo = 0;
796    
797        int offs = 0;
798        int lastRunStart = 0;
799        int lastRunEnd = 0;
800        int lastRunLevel = 0;
801        ArrayList newEls = new ArrayList();
802        for (int i = 0; i < bidis.length; i++)
803          {
804            Bidi bidi = bidis[i];
805            int numRuns = bidi.getRunCount();
806            for (int r = 0; r < numRuns; r++)
807              {
808                if (r == 0 && i == 0)
809                  {
810                    if (start > 0)
811                      {
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))
820                          {
821                            // Merge previous element with current run.
822                            lastRunStart = prevEl.getStartOffset() - start;
823                            lastRunEnd = bidi.getRunLimit(r);
824                            lastRunLevel  = bidi.getRunLevel(r);
825                          }
826                        else if (prevEl.getEndOffset() > start)
827                          {
828                            // Split previous element and replace by 2 new elements.
829                            lastRunStart = 0;
830                            lastRunEnd = bidi.getRunLimit(r);
831                            lastRunLevel = bidi.getRunLevel(r);
832                            newEls.add(new BidiElement(bidiRoot,
833                                                       prevEl.getStartOffset(),
834                                                       start, prevElLevel));
835                          }
836                        else
837                          {
838                            // Simply start new run at start location.
839                            lastRunStart = 0;
840                            lastRunEnd = bidi.getRunLimit(r);
841                            lastRunLevel = bidi.getRunLevel(r);
842                            removeFrom++;
843                          }
844                      }
845                    else
846                      {
847                        // Simply start new run at start location.
848                        lastRunStart = 0;
849                        lastRunEnd = bidi.getRunLimit(r);
850                        lastRunLevel = bidi.getRunLevel(r);
851                        removeFrom = 0;
852                      }
853                  }
854                if (i == bidis.length - 1 && r == numRuns - 1)
855                  {
856                    if (end <= getLength())
857                      {
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)
865                          {
866                            // Merge runs together.
867                            if (lastRunStart + start == nextEl.getStartOffset())
868                              removeTo = nextIndex - 1;
869                            else
870                              {
871                                newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
872                                                           nextEl.getEndOffset(), level));
873                                removeTo = nextIndex;
874                              }
875                          }
876                        else if (lastRunLevel == level)
877                          {
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;
884                            else
885                              {
886                                newEls.add(new BidiElement(bidiRoot, start + endOffs,
887                                                           nextEl.getEndOffset(),
888                                                           nextLevel));
889                                removeTo = nextIndex;
890                              }
891                          }
892                        else if (level == nextLevel)
893                          {
894                            // Merge current and next run.
895                            newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
896                                                       start + lastRunEnd,
897                                                       lastRunLevel));
898                            newEls.add(new BidiElement(bidiRoot, start + lastRunEnd,
899                                                       nextEl.getEndOffset(), level));
900                            removeTo = nextIndex;
901                          }
902                        else
903                          {
904                            // Split next element.
905                            int endOffs = offs + bidi.getRunLimit(r);
906                            newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
907                                                       start + lastRunEnd,
908                                                       lastRunLevel));
909                            newEls.add(new BidiElement(bidiRoot, start + lastRunEnd,
910                                                       start + endOffs, level));
911                            newEls.add(new BidiElement(bidiRoot, start + endOffs,
912                                                       nextEl.getEndOffset(),
913                                                       nextLevel));
914                            removeTo = nextIndex;
915                          }
916                      }
917                    else
918                      {
919                        removeTo = bidiRoot.getElementIndex(end);
920                        int level = bidi.getRunLevel(r);
921                        int runEnd = offs + bidi.getRunLimit(r);
922    
923                        if (level == lastRunLevel)
924                          {
925                            // Merge with previous.
926                            lastRunEnd = offs + runEnd;
927                            newEls.add(new BidiElement(bidiRoot,
928                                                      start + lastRunStart,
929                                                      start + runEnd, level));
930                          }
931                        else
932                          {
933                            // Create element for last run and current run.
934                            newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
935                                                       start + lastRunEnd,
936                                                       lastRunLevel));
937                            newEls.add(new BidiElement(bidiRoot,
938                                                       start + lastRunEnd,
939                                                       start + runEnd,
940                                                       level));
941                           }
942                      }
943                  }
944                else
945                  {
946                    int level = bidi.getRunLevel(r);
947                    int runEnd = bidi.getRunLimit(r);
948    
949                    if (level == lastRunLevel)
950                      {
951                        // Merge with previous.
952                        lastRunEnd = offs + runEnd;
953                      }
954                    else
955                      {
956                        // Create element for last run and update values for
957                        // current run.
958                        newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
959                                                   start + lastRunEnd,
960                                                   lastRunLevel));
961                        lastRunStart = lastRunEnd;
962                        lastRunEnd = offs + runEnd;
963                        lastRunLevel = level;
964                      }
965                  }
966              }
967            offs += bidi.getLength();
968          }
969    
970        // Determine the bidi elements which are to be removed.
971        int numRemoved = 0;
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);
977    
978        Element[] added = new Element[newEls.size()];
979        added = (Element[]) newEls.toArray(added);
980    
981        // Update the event.
982        ElementEdit edit = new ElementEdit(bidiRoot, removeFrom, removed, added);
983        ev.addEdit(edit);
984    
985        // Update the structure.
986        bidiRoot.replace(removeFrom, numRemoved, added);
987      }
988    
989      /**
990       * Determines the Bidi objects for the paragraphs in the specified range.
991       *
992       * @param start the start of the range
993       * @param end the end of the range
994       *
995       * @return the Bidi analysers for the paragraphs in the range
996       */
997      private Bidi[] getBidis(int start, int end)
998      {
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;
1004    
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;)
1009          {
1010            Element par = getParagraphElement(i);
1011            int pStart = par.getStartOffset();
1012            int pEnd = par.getEndOffset();
1013    
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)
1018              dir = (Boolean) o;
1019    
1020            // Bidi over the paragraph.
1021            try
1022              {
1023                getText(pStart, pEnd - pStart, s);
1024              }
1025            catch (BadLocationException ex)
1026              {
1027                assert false : "Must not happen";
1028              }
1029            int flag = Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT;
1030            if (dir != null)
1031              {
1032                if (TextAttribute.RUN_DIRECTION_LTR.equals(dir))
1033                  flag = Bidi.DIRECTION_LEFT_TO_RIGHT;
1034                else
1035                  flag = Bidi.DIRECTION_RIGHT_TO_LEFT;
1036              }
1037            Bidi bidi = new Bidi(s.array, s.offset, null, 0, s.count, flag);
1038            bidis.add(bidi);
1039            i = pEnd;
1040          }
1041        Bidi[] ret = new Bidi[bidis.size()];
1042        ret = (Bidi[]) bidis.toArray(ret);
1043        return ret;
1044      }
1045    
1046      /**
1047       * Blocks until a read lock can be obtained.  Must block if there is
1048       * currently a writer modifying the <code>Document</code>.
1049       */
1050      public final synchronized void readLock()
1051      {
1052        try
1053          {
1054            while (currentWriter != null)
1055              {
1056                if (currentWriter == Thread.currentThread())
1057                  return;
1058                wait();
1059              }
1060            numReaders++;
1061          }
1062        catch (InterruptedException ex)
1063          {
1064            throw new Error("Interrupted during grab read lock");
1065          }
1066      }
1067    
1068      /**
1069       * Releases the read lock. If this was the only reader on this
1070       * <code>Document</code>, writing may begin now.
1071       */
1072      public final synchronized void readUnlock()
1073      {
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:
1077        // readLock()
1078        // try
1079        // { 
1080        //   doSomethingHere 
1081        // }
1082        // finally
1083        // {
1084        //   readUnlock();
1085        // }
1086        
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
1090    
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())
1095          return;
1096    
1097        // FIXME: the reference implementation throws a 
1098        // javax.swing.text.StateInvariantError here
1099        if (numReaders <= 0)
1100          throw new IllegalStateException("document lock failure");
1101        
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
1105        numReaders--;
1106        notify();
1107      }
1108    
1109      /**
1110       * Removes a piece of content from this <code>Document</code>.
1111       * 
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>
1116       * 
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>
1120       * 
1121       * @param offset the start offset of the fragment to be removed
1122       * @param length the length of the fragment to be removed
1123       *
1124       * @throws BadLocationException if <code>offset</code> or
1125       *         <code>offset + length</code> or invalid locations within this
1126       *         document
1127       */
1128      public void remove(int offset, int length) throws BadLocationException
1129      {
1130        writeLock();
1131        try
1132          {
1133            DocumentFilter f = getDocumentFilter();
1134            if (f == null)
1135              removeImpl(offset, length);
1136            else
1137              f.remove(getBypass(), offset, length);
1138          }
1139        finally
1140          {
1141            writeUnlock();
1142          }
1143      }
1144    
1145      void removeImpl(int offset, int length) throws BadLocationException
1146      {
1147        // The RI silently ignores all requests that have a negative length.
1148        // Don't ask my why, but that's how it is.
1149        if (length > 0)
1150          {
1151            if (offset < 0 || offset > getLength())
1152              throw new BadLocationException("Invalid remove position", offset);
1153    
1154            if (offset + length > getLength())
1155              throw new BadLocationException("Invalid remove length", offset);
1156    
1157            DefaultDocumentEvent event =
1158              new DefaultDocumentEvent(offset, length,
1159                                       DocumentEvent.EventType.REMOVE);
1160        
1161            // The order of the operations below is critical!        
1162            removeUpdate(event);
1163            UndoableEdit temp = content.remove(offset, length);
1164    
1165            postRemoveUpdate(event);
1166            fireRemoveUpdate(event);
1167          }
1168      }
1169    
1170      /**
1171       * Replaces a piece of content in this <code>Document</code> with
1172       * another piece of content.
1173       * 
1174       * <p>If a {@link DocumentFilter} is installed in this document, the
1175       * corresponding method of the filter object is called.</p>
1176       * 
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>
1180       *
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
1185       *
1186       * @throws BadLocationException if <code>offset</code> or
1187       *         <code>offset + length</code> or invalid locations within this
1188       *         document
1189       *
1190       * @since 1.4
1191       */
1192      public void replace(int offset, int length, String text,
1193                          AttributeSet attributes)
1194        throws BadLocationException
1195      {
1196        // Bail out if we have a bogus replacement (Behavior observed in RI).
1197        if (length == 0 
1198            && (text == null || text.length() == 0))
1199          return;
1200    
1201        writeLock();
1202        try
1203          {
1204            if (documentFilter == null)
1205              {
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);
1214              }
1215            else
1216              documentFilter.replace(getBypass(), offset, length, text, attributes);
1217          }
1218        finally
1219          {
1220            writeUnlock();
1221          }
1222      }
1223      
1224      void replaceImpl(int offset, int length, String text,
1225                          AttributeSet attributes)
1226        throws BadLocationException
1227      {
1228        removeImpl(offset, length);
1229        insertStringImpl(offset, text, attributes);
1230      }
1231    
1232      /**
1233       * Adds a <code>DocumentListener</code> object to this document.
1234       *
1235       * @param listener the listener to add
1236       */
1237      public void addDocumentListener(DocumentListener listener)
1238      {
1239        listenerList.add(DocumentListener.class, listener);
1240      }
1241    
1242      /**
1243       * Removes a <code>DocumentListener</code> object from this document.
1244       *
1245       * @param listener the listener to remove
1246       */
1247      public void removeDocumentListener(DocumentListener listener)
1248      {
1249        listenerList.remove(DocumentListener.class, listener);
1250      }
1251    
1252      /**
1253       * Returns all registered <code>DocumentListener</code>s.
1254       *
1255       * @return all registered <code>DocumentListener</code>s
1256       */
1257      public DocumentListener[] getDocumentListeners()
1258      {
1259        return (DocumentListener[]) getListeners(DocumentListener.class);
1260      }
1261    
1262      /**
1263       * Adds an {@link UndoableEditListener} to this <code>Document</code>.
1264       *
1265       * @param listener the listener to add
1266       */
1267      public void addUndoableEditListener(UndoableEditListener listener)
1268      {
1269        listenerList.add(UndoableEditListener.class, listener);
1270      }
1271    
1272      /**
1273       * Removes an {@link UndoableEditListener} from this <code>Document</code>.
1274       *
1275       * @param listener the listener to remove
1276       */
1277      public void removeUndoableEditListener(UndoableEditListener listener)
1278      {
1279        listenerList.remove(UndoableEditListener.class, listener);
1280      }
1281    
1282      /**
1283       * Returns all registered {@link UndoableEditListener}s.
1284       *
1285       * @return all registered {@link UndoableEditListener}s
1286       */
1287      public UndoableEditListener[] getUndoableEditListeners()
1288      {
1289        return (UndoableEditListener[]) getListeners(UndoableEditListener.class);
1290      }
1291    
1292      /**
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.
1297       *
1298       * @param chng the <code>DefaultDocumentEvent</code> describing the change
1299       */
1300      protected void removeUpdate(DefaultDocumentEvent chng)
1301      {
1302        // Do nothing here. Subclasses may wish to override this.
1303      }
1304    
1305      /**
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
1315       * a read lock.
1316       *
1317       * @param runnable the {@link Runnable} to execute
1318       */
1319      public void render(Runnable runnable)
1320      {
1321        readLock();
1322        try
1323        {
1324          runnable.run();
1325        }
1326        finally
1327        {
1328          readUnlock();
1329        }
1330      }
1331    
1332      /**
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.
1336       *
1337       * @param p the asynchronous loading priority to set
1338       */
1339      public void setAsynchronousLoadPriority(int p)
1340      {
1341        Integer val = p >= 0 ? new Integer(p) : null;
1342        putProperty(AsyncLoadPriority, val);
1343      }
1344    
1345      /**
1346       * Sets the properties of this <code>Document</code>.
1347       *
1348       * @param p the document properties to set
1349       */
1350      public void setDocumentProperties(Dictionary<Object, Object> p)
1351      {
1352        // FIXME: make me thread-safe
1353        properties = p;
1354      }
1355    
1356      /**
1357       * Blocks until a write lock can be obtained.  Must wait if there are 
1358       * readers currently reading or another thread is currently writing.
1359       */
1360      protected synchronized final void writeLock()
1361      {
1362        try
1363          {
1364            while (numReaders > 0 || currentWriter != null)
1365              {
1366                if (Thread.currentThread() == currentWriter)
1367                  {
1368                    if (notifyListeners)
1369                      throw new IllegalStateException("Mutation during notify");
1370                    numWriters++;
1371                    return;
1372                  }
1373                wait();
1374              }
1375            currentWriter = Thread.currentThread();
1376            numWriters = 1;
1377          }
1378        catch (InterruptedException ex)
1379          {
1380            throw new Error("Interupted during grab write lock");
1381          }
1382      }
1383    
1384      /**
1385       * Releases the write lock. This allows waiting readers or writers to
1386       * obtain the lock.
1387       */
1388      protected final synchronized void writeUnlock()
1389      {
1390        if (--numWriters <= 0)
1391          {
1392            numWriters = 0;
1393            currentWriter = null;
1394            notifyAll();
1395          }
1396      }
1397    
1398      /**
1399       * Returns the currently installed {@link DocumentFilter} for this
1400       * <code>Document</code>.
1401       *
1402       * @return the currently installed {@link DocumentFilter} for this
1403       *         <code>Document</code>
1404       *
1405       * @since 1.4
1406       */
1407      public DocumentFilter getDocumentFilter()
1408      {
1409        return documentFilter;
1410      }
1411    
1412      /**
1413       * Sets the {@link DocumentFilter} for this <code>Document</code>.
1414       *
1415       * @param filter the <code>DocumentFilter</code> to set
1416       *
1417       * @since 1.4
1418       */
1419      public void setDocumentFilter(DocumentFilter filter)
1420      {
1421        this.documentFilter = filter;
1422      }
1423    
1424      /**
1425       * Dumps diagnostic information to the specified <code>PrintStream</code>.
1426       *
1427       * @param out the stream to write the diagnostic information to
1428       */
1429      public void dump(PrintStream out)
1430      {
1431        ((AbstractElement) getDefaultRootElement()).dump(out, 0);
1432        ((AbstractElement) getBidiRootElement()).dump(out, 0);
1433      }
1434    
1435      /**
1436       * Defines a set of methods for managing text attributes for one or more
1437       * <code>Document</code>s.
1438       *
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.
1443       *
1444       * @see StyleContext
1445       */
1446      public interface AttributeContext
1447      {
1448        /**
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>.
1452         *
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
1456         *
1457         * @return the old attributes plus the new attribute
1458         */
1459        AttributeSet addAttribute(AttributeSet old, Object name, Object value);
1460    
1461        /**
1462         * Returns an {@link AttributeSet} that contains the attributes
1463         * of <code>old</code> plus the new attributes in <code>attributes</code>.
1464         *
1465         * @param old the set of attributes where to add the new attributes
1466         * @param attributes the attributes to be added
1467         *
1468         * @return an {@link AttributeSet} that contains the attributes
1469         *         of <code>old</code> plus the new attributes in
1470         *         <code>attributes</code>
1471         */
1472        AttributeSet addAttributes(AttributeSet old, AttributeSet attributes);
1473    
1474        /**
1475         * Returns an empty {@link AttributeSet}.
1476         *
1477         * @return  an empty {@link AttributeSet}
1478         */
1479        AttributeSet getEmptySet();
1480    
1481        /**
1482         * Called to indicate that the attributes in <code>attributes</code> are
1483         * no longer used.
1484         *
1485         * @param attributes the attributes are no longer used
1486         */
1487        void reclaim(AttributeSet attributes);
1488    
1489        /**
1490         * Returns a {@link AttributeSet} that has the attribute with the specified
1491         * <code>name</code> removed from <code>old</code>.
1492         *
1493         * @param old the attribute set from which an attribute is removed
1494         * @param name the name of the attribute to be removed
1495         *
1496         * @return the attributes of <code>old</code> minus the attribute
1497         *         specified by <code>name</code>
1498         */
1499        AttributeSet removeAttribute(AttributeSet old, Object name);
1500    
1501        /**
1502         * Removes all attributes in <code>attributes</code> from <code>old</code>
1503         * and returns the resulting <code>AttributeSet</code>.
1504         *
1505         * @param old the set of attributes from which to remove attributes
1506         * @param attributes the attributes to be removed from <code>old</code>
1507         *
1508         * @return the attributes of <code>old</code> minus the attributes in
1509         *         <code>attributes</code>
1510         */
1511        AttributeSet removeAttributes(AttributeSet old, AttributeSet attributes);
1512    
1513        /**
1514         * Removes all attributes specified by <code>names</code> from
1515         * <code>old</code> and returns the resulting <code>AttributeSet</code>.
1516         *
1517         * @param old the set of attributes from which to remove attributes
1518         * @param names the names of the attributes to be removed from
1519         *        <code>old</code>
1520         *
1521         * @return the attributes of <code>old</code> minus the attributes in
1522         *         <code>attributes</code>
1523         */
1524        AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names);
1525      }
1526    
1527      /**
1528       * A sequence of data that can be edited. This is were the actual content
1529       * in <code>AbstractDocument</code>'s is stored.
1530       */
1531      public interface Content
1532      {
1533        /**
1534         * Creates a {@link Position} that keeps track of the location at
1535         * <code>offset</code>.
1536         *
1537         * @return a {@link Position} that keeps track of the location at
1538         *         <code>offset</code>.
1539         *
1540         * @throw BadLocationException if <code>offset</code> is not a valid
1541         *        location in this <code>Content</code> model
1542         */
1543        Position createPosition(int offset) throws BadLocationException;
1544    
1545        /**
1546         * Returns the length of the content.
1547         *
1548         * @return the length of the content
1549         */
1550        int length();
1551    
1552        /**
1553         * Inserts a string into the content model.
1554         *
1555         * @param where the offset at which to insert the string
1556         * @param str the string to be inserted
1557         *
1558         * @return an <code>UndoableEdit</code> or <code>null</code> if undo is
1559         *         not supported by this <code>Content</code> model
1560         *
1561         * @throws BadLocationException if <code>where</code> is not a valid
1562         *         location in this <code>Content</code> model
1563         */
1564        UndoableEdit insertString(int where, String str)
1565          throws BadLocationException;
1566    
1567        /**
1568         * Removes a piece of content from the content model.
1569         *
1570         * @param where the offset at which to remove content
1571         * @param nitems the number of characters to be removed
1572         *
1573         * @return an <code>UndoableEdit</code> or <code>null</code> if undo is
1574         *         not supported by this <code>Content</code> model
1575         *
1576         * @throws BadLocationException if <code>where</code> is not a valid
1577         *         location in this <code>Content</code> model
1578         */
1579        UndoableEdit remove(int where, int nitems) throws BadLocationException;
1580    
1581        /**
1582         * Returns a piece of content.
1583         *
1584         * @param where the start offset of the requested fragment
1585         * @param len the length of the requested fragment
1586         *
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
1591         */
1592        String getString(int where, int len) throws BadLocationException;
1593    
1594        /**
1595         * Fetches a piece of content and stores it in <code>txt</code>.
1596         *
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
1600         *
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
1604         */
1605        void getChars(int where, int len, Segment txt) throws BadLocationException;
1606      }
1607    
1608      /**
1609       * An abstract base implementation of the {@link Element} interface.
1610       */
1611      public abstract class AbstractElement
1612        implements Element, MutableAttributeSet, TreeNode, Serializable
1613      {
1614        /** The serialization UID (compatible with JDK1.5). */
1615        private static final long serialVersionUID = 1712240033321461704L;
1616    
1617        /** The number of characters that this Element spans. */
1618        int count;
1619    
1620        /** The starting offset of this Element. */
1621        int offset;
1622    
1623        /** The attributes of this Element. */
1624        AttributeSet attributes;
1625    
1626        /** The parent element. */
1627        Element element_parent;
1628    
1629        /** The parent in the TreeNode interface. */
1630        TreeNode tree_parent;
1631    
1632        /** The children of this element. */
1633        Vector tree_children;
1634    
1635        /**
1636         * Creates a new instance of <code>AbstractElement</code> with a
1637         * specified parent <code>Element</code> and <code>AttributeSet</code>.
1638         *
1639         * @param p the parent of this <code>AbstractElement</code>
1640         * @param s the attributes to be assigned to this
1641         *        <code>AbstractElement</code>
1642         */
1643        public AbstractElement(Element p, AttributeSet s)
1644        {
1645          element_parent = p;
1646          AttributeContext ctx = getAttributeContext();
1647          attributes = ctx.getEmptySet();
1648          if (s != null)
1649            addAttributes(s);
1650        }
1651    
1652        /**
1653         * Returns the child nodes of this <code>Element</code> as an
1654         * <code>Enumeration</code> of {@link TreeNode}s.
1655         *
1656         * @return the child nodes of this <code>Element</code> as an
1657         *         <code>Enumeration</code> of {@link TreeNode}s
1658         */
1659        public abstract Enumeration children();
1660    
1661        /**
1662         * Returns <code>true</code> if this <code>AbstractElement</code>
1663         * allows children.
1664         *
1665         * @return <code>true</code> if this <code>AbstractElement</code>
1666         *         allows children
1667         */
1668        public abstract boolean getAllowsChildren();
1669    
1670        /**
1671         * Returns the child of this <code>AbstractElement</code> at
1672         * <code>index</code>.
1673         *
1674         * @param index the position in the child list of the child element to
1675         *        be returned
1676         *
1677         * @return the child of this <code>AbstractElement</code> at
1678         *         <code>index</code>
1679         */
1680        public TreeNode getChildAt(int index)
1681        {
1682          return (TreeNode) tree_children.get(index);
1683        }
1684    
1685        /**
1686         * Returns the number of children of this <code>AbstractElement</code>.
1687         *
1688         * @return the number of children of this <code>AbstractElement</code>
1689         */
1690        public int getChildCount()
1691        {
1692          return tree_children.size();
1693        }
1694    
1695        /**
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>.
1699         *
1700         * @param node the node for which the index is requested
1701         *
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>
1705         */
1706        public int getIndex(TreeNode node)
1707        {
1708          return tree_children.indexOf(node);
1709        }
1710    
1711        /**
1712         * Returns the parent <code>TreeNode</code> of this
1713         * <code>AbstractElement</code> or <code>null</code> if this element
1714         * has no parent.
1715         *
1716         * @return the parent <code>TreeNode</code> of this
1717         *         <code>AbstractElement</code> or <code>null</code> if this
1718         *         element has no parent
1719         */
1720        public TreeNode getParent()
1721        {
1722          return tree_parent;
1723        }
1724    
1725        /**
1726         * Returns <code>true</code> if this <code>AbstractElement</code> is a
1727         * leaf element, <code>false</code> otherwise.
1728         *
1729         * @return <code>true</code> if this <code>AbstractElement</code> is a
1730         *         leaf element, <code>false</code> otherwise
1731         */
1732        public abstract boolean isLeaf();
1733    
1734        /**
1735         * Adds an attribute to this element.
1736         *
1737         * @param name the name of the attribute to be added
1738         * @param value the value of the attribute to be added
1739         */
1740        public void addAttribute(Object name, Object value)
1741        {
1742          attributes = getAttributeContext().addAttribute(attributes, name, value);
1743        }
1744    
1745        /**
1746         * Adds a set of attributes to this element.
1747         *
1748         * @param attrs the attributes to be added to this element
1749         */
1750        public void addAttributes(AttributeSet attrs)
1751        {
1752          attributes = getAttributeContext().addAttributes(attributes, attrs);
1753        }
1754    
1755        /**
1756         * Removes an attribute from this element.
1757         *
1758         * @param name the name of the attribute to be removed
1759         */
1760        public void removeAttribute(Object name)
1761        {
1762          attributes = getAttributeContext().removeAttribute(attributes, name);
1763        }
1764    
1765        /**
1766         * Removes a set of attributes from this element.
1767         *
1768         * @param attrs the attributes to be removed
1769         */
1770        public void removeAttributes(AttributeSet attrs)
1771        {
1772          attributes = getAttributeContext().removeAttributes(attributes, attrs);
1773        }
1774    
1775        /**
1776         * Removes a set of attribute from this element.
1777         *
1778         * @param names the names of the attributes to be removed
1779         */
1780        public void removeAttributes(Enumeration<?> names)
1781        {
1782          attributes = getAttributeContext().removeAttributes(attributes, names);
1783        }
1784    
1785        /**
1786         * Sets the parent attribute set against which the element can resolve
1787         * attributes that are not defined in itself.
1788         *
1789         * @param parent the resolve parent to set
1790         */
1791        public void setResolveParent(AttributeSet parent)
1792        {
1793          attributes = getAttributeContext().addAttribute(attributes,
1794                                                          ResolveAttribute,
1795                                                          parent);
1796        }
1797    
1798        /**
1799         * Returns <code>true</code> if this element contains the specified
1800         * attribute.
1801         *
1802         * @param name the name of the attribute to check
1803         * @param value the value of the attribute to check
1804         *
1805         * @return <code>true</code> if this element contains the specified
1806         *         attribute
1807         */
1808        public boolean containsAttribute(Object name, Object value)
1809        {
1810          return attributes.containsAttribute(name, value);
1811        }
1812    
1813        /**
1814         * Returns <code>true</code> if this element contains all of the
1815         * specified attributes.
1816         *
1817         * @param attrs the attributes to check
1818         *
1819         * @return <code>true</code> if this element contains all of the
1820         *         specified attributes
1821         */
1822        public boolean containsAttributes(AttributeSet attrs)
1823        {
1824          return attributes.containsAttributes(attrs);
1825        }
1826    
1827        /**
1828         * Returns a copy of the attributes of this element.
1829         *
1830         * @return a copy of the attributes of this element
1831         */
1832        public AttributeSet copyAttributes()
1833        {
1834          return attributes.copyAttributes();
1835        }
1836    
1837        /**
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.
1841         *
1842         * @param key the key of the requested attribute
1843         *
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
1847         */
1848        public Object getAttribute(Object key)
1849        {
1850          Object result = attributes.getAttribute(key);
1851          if (result == null)
1852            {
1853              AttributeSet resParent = getResolveParent();
1854              if (resParent != null)
1855                result = resParent.getAttribute(key);
1856            }
1857          return result;
1858        }
1859    
1860        /**
1861         * Returns the number of defined attributes in this element.
1862         *
1863         * @return the number of defined attributes in this element
1864         */
1865        public int getAttributeCount()
1866        {
1867          return attributes.getAttributeCount();
1868        }
1869    
1870        /**
1871         * Returns the names of the attributes of this element.
1872         *
1873         * @return the names of the attributes of this element
1874         */
1875        public Enumeration<?> getAttributeNames()
1876        {
1877          return attributes.getAttributeNames();
1878        }
1879    
1880        /**
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 
1884         * AttributeSet
1885         *
1886         * @return the resolve parent of this element
1887         *
1888         * @see #setResolveParent(AttributeSet)
1889         */
1890        public AttributeSet getResolveParent()
1891        {
1892          return attributes.getResolveParent();
1893        }
1894    
1895        /**
1896         * Returns <code>true</code> if an attribute with the specified name
1897         * is defined in this element, <code>false</code> otherwise.
1898         *
1899         * @param attrName the name of the requested attributes
1900         *
1901         * @return <code>true</code> if an attribute with the specified name
1902         *         is defined in this element, <code>false</code> otherwise
1903         */
1904        public boolean isDefined(Object attrName)
1905        {
1906          return attributes.isDefined(attrName);
1907        }
1908    
1909        /**
1910         * Returns <code>true</code> if the specified <code>AttributeSet</code>
1911         * is equal to this element's <code>AttributeSet</code>, <code>false</code>
1912         * otherwise.
1913         *
1914         * @param attrs the attributes to compare this element to
1915         *
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
1919         */
1920        public boolean isEqual(AttributeSet attrs) 
1921        {
1922          return attributes.isEqual(attrs);
1923        }
1924    
1925        /**
1926         * Returns the attributes of this element.
1927         *
1928         * @return the attributes of this element
1929         */
1930        public AttributeSet getAttributes()
1931        {
1932          return this;
1933        }
1934    
1935        /**
1936         * Returns the {@link Document} to which this element belongs.
1937         *
1938         * @return the {@link Document} to which this element belongs
1939         */
1940        public Document getDocument()
1941        {
1942          return AbstractDocument.this;
1943        }
1944    
1945        /**
1946         * Returns the child element at the specified <code>index</code>.
1947         *
1948         * @param index the index of the requested child element
1949         *
1950         * @return the requested element
1951         */
1952        public abstract Element getElement(int index);
1953    
1954        /**
1955         * Returns the name of this element.
1956         *
1957         * @return the name of this element
1958         */
1959        public String getName()
1960        {
1961          return (String) attributes.getAttribute(ElementNameAttribute);
1962        }
1963    
1964        /**
1965         * Returns the parent element of this element.
1966         *
1967         * @return the parent element of this element
1968         */
1969        public Element getParentElement()
1970        {
1971          return element_parent;
1972        }
1973    
1974        /**
1975         * Returns the offset inside the document model that is after the last
1976         * character of this element.
1977         *
1978         * @return the offset inside the document model that is after the last
1979         *         character of this element
1980         */
1981        public abstract int getEndOffset();
1982    
1983        /**
1984         * Returns the number of child elements of this element.
1985         *
1986         * @return the number of child elements of this element
1987         */
1988        public abstract int getElementCount();
1989    
1990        /**
1991         * Returns the index of the child element that spans the specified
1992         * offset in the document model.
1993         *
1994         * @param offset the offset for which the responsible element is searched
1995         *
1996         * @return the index of the child element that spans the specified
1997         *         offset in the document model
1998         */
1999        public abstract int getElementIndex(int offset);
2000    
2001        /**
2002         * Returns the start offset if this element inside the document model.
2003         *
2004         * @return the start offset if this element inside the document model
2005         */
2006        public abstract int getStartOffset();
2007    
2008        /**
2009         * Prints diagnostic output to the specified stream.
2010         *
2011         * @param stream the stream to write to
2012         * @param indent the indentation level
2013         */
2014        public void dump(PrintStream stream, int indent)
2015        {
2016          StringBuffer b = new StringBuffer();
2017          for (int i = 0; i < indent; ++i)
2018            b.append(' ');
2019          b.append('<');
2020          b.append(getName());
2021          // Dump attributes if there are any.
2022          if (getAttributeCount() > 0)
2023            {
2024              b.append('\n');
2025              Enumeration attNames = getAttributeNames();
2026              while (attNames.hasMoreElements())
2027                {
2028                  for (int i = 0; i < indent + 2; ++i)
2029                    b.append(' ');
2030                  Object attName = attNames.nextElement();
2031                  b.append(attName);
2032                  b.append('=');
2033                  Object attribute = getAttribute(attName);
2034                  b.append(attribute);
2035                  b.append('\n');
2036                }
2037            }
2038          if (getAttributeCount() > 0)
2039            {
2040              for (int i = 0; i < indent; ++i)
2041                b.append(' ');
2042            }
2043          b.append(">\n");
2044    
2045          // Dump element content for leaf elements.
2046          if (isLeaf())
2047            {
2048              for (int i = 0; i < indent + 2; ++i)
2049                b.append(' ');
2050              int start = getStartOffset();
2051              int end = getEndOffset();
2052              b.append('[');
2053              b.append(start);
2054              b.append(',');
2055              b.append(end);
2056              b.append("][");
2057              try
2058                {
2059                  b.append(getDocument().getText(start, end - start));
2060                }
2061              catch (BadLocationException ex)
2062                {
2063                  AssertionError err = new AssertionError("BadLocationException "
2064                                                          + "must not be thrown "
2065                                                          + "here.");
2066                  err.initCause(ex);
2067                  throw err;
2068                }
2069              b.append("]\n");
2070            }
2071          stream.print(b.toString());
2072    
2073          // Dump child elements if any.
2074          int count = getElementCount();
2075          for (int i = 0; i < count; ++i)
2076            {
2077              Element el = getElement(i);
2078              if (el instanceof AbstractElement)
2079                ((AbstractElement) el).dump(stream, indent + 2);
2080            }
2081        }
2082      }
2083    
2084      /**
2085       * An implementation of {@link Element} to represent composite
2086       * <code>Element</code>s that contain other <code>Element</code>s.
2087       */
2088      public class BranchElement extends AbstractElement
2089      {
2090        /** The serialization UID (compatible with JDK1.5). */
2091        private static final long serialVersionUID = -6037216547466333183L;
2092    
2093        /**
2094         * The child elements of this BranchElement.
2095         */
2096        private Element[] children;
2097    
2098        /**
2099         * The number of children in the branch element.
2100         */
2101        private int numChildren;
2102    
2103        /**
2104         * The last found index in getElementIndex(). Used for faster searching.
2105         */
2106        private int lastIndex;
2107    
2108        /**
2109         * Creates a new <code>BranchElement</code> with the specified
2110         * parent and attributes.
2111         *
2112         * @param parent the parent element of this <code>BranchElement</code>
2113         * @param attributes the attributes to set on this
2114         *        <code>BranchElement</code>
2115         */
2116        public BranchElement(Element parent, AttributeSet attributes)
2117        {
2118          super(parent, attributes);
2119          children = new Element[1];
2120          numChildren = 0;
2121          lastIndex = -1;
2122        }
2123    
2124        /**
2125         * Returns the children of this <code>BranchElement</code>.
2126         *
2127         * @return the children of this <code>BranchElement</code>
2128         */
2129        public Enumeration children()
2130        {
2131          if (numChildren == 0)
2132            return null;
2133    
2134          Vector tmp = new Vector();
2135    
2136          for (int index = 0; index < numChildren; ++index)
2137            tmp.add(children[index]);
2138          
2139          return tmp.elements();
2140        }
2141    
2142        /**
2143         * Returns <code>true</code> since <code>BranchElements</code> allow
2144         * child elements.
2145         *
2146         * @return <code>true</code> since <code>BranchElements</code> allow
2147         *         child elements
2148         */
2149        public boolean getAllowsChildren()
2150        {
2151          return true;
2152        }
2153    
2154        /**
2155         * Returns the child element at the specified <code>index</code>.
2156         *
2157         * @param index the index of the requested child element
2158         *
2159         * @return the requested element
2160         */
2161        public Element getElement(int index)
2162        {
2163          if (index < 0 || index >= numChildren)
2164            return null;
2165    
2166          return children[index];
2167        }
2168    
2169        /**
2170         * Returns the number of child elements of this element.
2171         *
2172         * @return the number of child elements of this element
2173         */
2174        public int getElementCount()
2175        {
2176          return numChildren;
2177        }
2178    
2179        /**
2180         * Returns the index of the child element that spans the specified
2181         * offset in the document model.
2182         *
2183         * @param offset the offset for which the responsible element is searched
2184         *
2185         * @return the index of the child element that spans the specified
2186         *         offset in the document model
2187         */
2188        public int getElementIndex(int offset)
2189        {
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.
2194    
2195          int i0 = 0; // The lower bounds.
2196          int i1 = numChildren - 1; // The upper bounds.
2197          int index = -1; // The found index.
2198    
2199          int p0 = getStartOffset();
2200          int p1; // Start and end offset local variables.
2201    
2202          if (numChildren == 0)
2203            index = 0;
2204          else if (offset >= getEndOffset())
2205            index = numChildren - 1;
2206          else
2207            {
2208              // Try lastIndex.
2209              if (lastIndex >= i0 && lastIndex <= i1)
2210                {
2211                  Element last = getElement(lastIndex);
2212                  p0 = last.getStartOffset();
2213                  p1 = last.getEndOffset();
2214                  if (offset >= p0 && offset < p1)
2215                    index = lastIndex;
2216                  else
2217                    {
2218                      // Narrow the search bounds using the lastIndex, even
2219                      // if it hasn't been a hit.
2220                      if (offset < p0)
2221                        i1 = lastIndex;
2222                      else
2223                        i0 = lastIndex;
2224                    }
2225                }
2226              // The actual search.
2227              int i = 0;
2228              while (i0 <= i1 && index == -1)
2229                {
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)
2235                    {
2236                      // Found it!
2237                      index = i;
2238                    }
2239                  else if (offset < p0)
2240                    i1 = i - 1;
2241                  else
2242                    i0 = i + 1;
2243                }
2244    
2245              if (index == -1)
2246                {
2247                  // Didn't find it. Return the boundary index.
2248                  if (offset < p0)
2249                    index = i;
2250                  else
2251                    index = i + 1;
2252                }
2253    
2254              lastIndex = index;
2255            }
2256          return index;
2257        }
2258    
2259        /**
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>.
2264         *
2265         * @return the offset inside the document model that is after the last
2266         *         character of this element
2267         *
2268         * @throws NullPointerException if this branch element has no children
2269         */
2270        public int getEndOffset()
2271        {
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]
2275                                          : children[0];
2276          return child.getEndOffset();
2277        }
2278    
2279        /**
2280         * Returns the name of this element. This is {@link #ParagraphElementName}
2281         * in this case.
2282         *
2283         * @return the name of this element
2284         */
2285        public String getName()
2286        {
2287          return ParagraphElementName;
2288        }
2289    
2290        /**
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>.
2294         *
2295         * @return the start offset of this element inside the document model
2296         *
2297         * @throws NullPointerException if this branch element has no children and
2298         *         no startOffset value has been cached
2299         */
2300        public int getStartOffset()
2301        {
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();
2309        }
2310    
2311        /**
2312         * Returns <code>false</code> since <code>BranchElement</code> are no
2313         * leafes.
2314         *
2315         * @return <code>false</code> since <code>BranchElement</code> are no
2316         *         leafes
2317         */
2318        public boolean isLeaf()
2319        {
2320          return false;
2321        }
2322    
2323        /**
2324         * Returns the <code>Element</code> at the specified <code>Document</code>
2325         * offset.
2326         *
2327         * @return the <code>Element</code> at the specified <code>Document</code>
2328         *         offset
2329         *
2330         * @see #getElementIndex(int)
2331         */
2332        public Element positionToElement(int position)
2333        {
2334          // XXX: There is surely a better algorithm
2335          // as beginning from first element each time.
2336          for (int index = 0; index < numChildren; ++index)
2337            {
2338              Element elem = children[index];
2339    
2340              if ((elem.getStartOffset() <= position)
2341                  && (position < elem.getEndOffset()))
2342                return elem;
2343            }
2344    
2345          return null;
2346        }
2347    
2348        /**
2349         * Replaces a set of child elements with a new set of child elemens.
2350         *
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
2354         */
2355        public void replace(int offset, int length, Element[] elements)
2356        {
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)
2362            {
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);
2369              children = target;
2370            }
2371          else
2372            {
2373              System.arraycopy(children, copyFrom, children, copyTo, numMove);
2374              System.arraycopy(elements, 0, children, offset, elements.length);
2375            }
2376          numChildren += delta;
2377        }
2378    
2379        /**
2380         * Returns a string representation of this element.
2381         *
2382         * @return a string representation of this element
2383         */
2384        public String toString()
2385        {
2386          return ("BranchElement(" + getName() + ") "
2387                  + getStartOffset() + "," + getEndOffset() + "\n");
2388        }
2389      }
2390    
2391      /**
2392       * Stores the changes when a <code>Document</code> is beeing modified.
2393       */
2394      public class DefaultDocumentEvent extends CompoundEdit
2395        implements DocumentEvent
2396      {
2397        /** The serialization UID (compatible with JDK1.5). */
2398        private static final long serialVersionUID = 5230037221564563284L;
2399    
2400        /**
2401         * The threshold that indicates when we switch to using a Hashtable.
2402         */
2403        private static final int THRESHOLD = 10;
2404        
2405        /** The starting offset of the change. */
2406        private int offset;
2407    
2408        /** The length of the change. */
2409        private int length;
2410    
2411        /** The type of change. */
2412        private DocumentEvent.EventType type;
2413    
2414        /**
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. 
2419         */
2420        private HashMap changes;
2421    
2422        /**
2423         * Indicates if this event has been modified or not. This is used to
2424         * determine if this event is thrown.
2425         */
2426        private boolean modified;
2427    
2428        /**
2429         * Creates a new <code>DefaultDocumentEvent</code>.
2430         *
2431         * @param offset the starting offset of the change
2432         * @param length the length of the change
2433         * @param type the type of change
2434         */
2435        public DefaultDocumentEvent(int offset, int length,
2436                                    DocumentEvent.EventType type)
2437        {
2438          this.offset = offset;
2439          this.length = length;
2440          this.type = type;
2441          modified = false;
2442        }
2443    
2444        /**
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}.
2448         *
2449         * @param edit the undoable edit to add
2450         */
2451        public boolean addEdit(UndoableEdit edit)
2452        {
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)
2456            {
2457              changes = new HashMap();
2458              int count = edits.size();
2459              for (int i = 0; i < count; i++)
2460                {
2461                  Object o = edits.elementAt(i);
2462                  if (o instanceof ElementChange)
2463                    {
2464                      ElementChange ec = (ElementChange) o;
2465                      changes.put(ec.getElement(), ec);
2466                    }
2467                }
2468            }
2469    
2470          if (changes != null && edit instanceof ElementChange)
2471            {
2472              ElementChange elEdit = (ElementChange) edit;
2473              changes.put(elEdit.getElement(), elEdit);
2474            }
2475          return super.addEdit(edit);
2476        }
2477    
2478        /**
2479         * Returns the document that has been modified.
2480         *
2481         * @return the document that has been modified
2482         */
2483        public Document getDocument()
2484        {
2485          return AbstractDocument.this;
2486        }
2487    
2488        /**
2489         * Returns the length of the modification.
2490         *
2491         * @return the length of the modification
2492         */
2493        public int getLength()
2494        {
2495          return length;
2496        }
2497    
2498        /**
2499         * Returns the start offset of the modification.
2500         *
2501         * @return the start offset of the modification
2502         */
2503        public int getOffset()
2504        {
2505          return offset;
2506        }
2507    
2508        /**
2509         * Returns the type of the modification.
2510         *
2511         * @return the type of the modification
2512         */
2513        public DocumentEvent.EventType getType()
2514        {
2515          return type;
2516        }
2517    
2518        /**
2519         * Returns the changes for an element.
2520         *
2521         * @param elem the element for which the changes are requested
2522         *
2523         * @return the changes for <code>elem</code> or <code>null</code> if
2524         *         <code>elem</code> has not been changed
2525         */
2526        public ElementChange getChange(Element elem)
2527        {
2528          ElementChange change = null;
2529          if (changes != null)
2530            {
2531              change = (ElementChange) changes.get(elem);
2532            }
2533          else
2534            {
2535              int count = edits.size();
2536              for (int i = 0; i < count && change == null; i++)
2537                {
2538                  Object o = edits.get(i);
2539                  if (o instanceof ElementChange)
2540                    {
2541                      ElementChange ec = (ElementChange) o;
2542                      if (elem.equals(ec.getElement()))
2543                        change = ec;
2544                    }
2545                }
2546            }
2547          return change;
2548        }
2549        
2550        /**
2551         * Returns a String description of the change event.  This returns the
2552         * toString method of the Vector of edits.
2553         */
2554        public String toString()
2555        {
2556          return edits.toString();
2557        }
2558      }
2559      
2560      /**
2561       * An implementation of {@link DocumentEvent.ElementChange} to be added
2562       * to {@link DefaultDocumentEvent}s.
2563       */
2564      public static class ElementEdit extends AbstractUndoableEdit
2565        implements DocumentEvent.ElementChange
2566      {
2567        /** The serial version UID of ElementEdit. */
2568        private static final long serialVersionUID = -1216620962142928304L;
2569    
2570        /**
2571         * The changed element.
2572         */
2573        private Element elem;
2574    
2575        /**
2576         * The index of the change.
2577         */
2578        private int index;
2579    
2580        /**
2581         * The removed elements.
2582         */
2583        private Element[] removed;
2584    
2585        /**
2586         * The added elements.
2587         */
2588        private Element[] added;
2589        
2590        /**
2591         * Creates a new <code>ElementEdit</code>.
2592         *
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
2597         */
2598        public ElementEdit(Element elem, int index,
2599                           Element[] removed, Element[] added)
2600        {
2601          this.elem = elem;
2602          this.index = index;
2603          this.removed = removed;
2604          this.added = added;
2605        }
2606    
2607        /**
2608         * Returns the added elements.
2609         *
2610         * @return the added elements
2611         */
2612        public Element[] getChildrenAdded()
2613        {
2614          return added;
2615        }
2616    
2617        /**
2618         * Returns the removed elements.
2619         *
2620         * @return the removed elements
2621         */
2622        public Element[] getChildrenRemoved()
2623        {
2624          return removed;
2625        }
2626    
2627        /**
2628         * Returns the changed element.
2629         *
2630         * @return the changed element
2631         */
2632        public Element getElement()
2633        {
2634          return elem;
2635        }
2636    
2637        /**
2638         * Returns the index of the change.
2639         *
2640         * @return the index of the change
2641         */
2642        public int getIndex()
2643        {
2644          return index;
2645        }
2646      }
2647    
2648      /**
2649       * An implementation of {@link Element} that represents a leaf in the
2650       * document structure. This is used to actually store content.
2651       */
2652      public class LeafElement extends AbstractElement
2653      {
2654        /** The serialization UID (compatible with JDK1.5). */
2655        private static final long serialVersionUID = -8906306331347768017L;
2656    
2657        /**
2658         * Manages the start offset of this element.
2659         */
2660        private Position startPos;
2661    
2662        /**
2663         * Manages the end offset of this element.
2664         */
2665        private Position endPos;
2666    
2667        /**
2668         * Creates a new <code>LeafElement</code>.
2669         *
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
2674         */
2675        public LeafElement(Element parent, AttributeSet attributes, int start,
2676                           int end)
2677        {
2678          super(parent, attributes);
2679          try
2680            {
2681              startPos = createPosition(start);
2682              endPos = createPosition(end);
2683            }
2684          catch (BadLocationException ex)
2685            {
2686              AssertionError as;
2687              as = new AssertionError("BadLocationException thrown "
2688                                      + "here. start=" + start
2689                                      + ", end=" + end
2690                                      + ", length=" + getLength());
2691              as.initCause(ex);
2692              throw as;
2693            }
2694        }
2695    
2696        /**
2697         * Returns <code>null</code> since <code>LeafElement</code>s cannot have
2698         * children.
2699         *
2700         * @return <code>null</code> since <code>LeafElement</code>s cannot have
2701         *         children
2702         */
2703        public Enumeration children()
2704        {
2705          return null;
2706        }
2707    
2708        /**
2709         * Returns <code>false</code> since <code>LeafElement</code>s cannot have
2710         * children.
2711         *
2712         * @return <code>false</code> since <code>LeafElement</code>s cannot have
2713         *         children
2714         */
2715        public boolean getAllowsChildren()
2716        {
2717          return false;
2718        }
2719    
2720        /**
2721         * Returns <code>null</code> since <code>LeafElement</code>s cannot have
2722         * children.
2723         *
2724         * @return <code>null</code> since <code>LeafElement</code>s cannot have
2725         *         children
2726         */
2727        public Element getElement(int index)
2728        {
2729          return null;
2730        }
2731    
2732        /**
2733         * Returns <code>0</code> since <code>LeafElement</code>s cannot have
2734         * children.
2735         *
2736         * @return <code>0</code> since <code>LeafElement</code>s cannot have
2737         *         children
2738         */
2739        public int getElementCount()
2740        {
2741          return 0;
2742        }
2743    
2744        /**
2745         * Returns <code>-1</code> since <code>LeafElement</code>s cannot have
2746         * children.
2747         *
2748         * @return <code>-1</code> since <code>LeafElement</code>s cannot have
2749         *         children
2750         */
2751        public int getElementIndex(int offset)
2752        {
2753          return -1;
2754        }
2755    
2756        /**
2757         * Returns the end offset of this <code>Element</code> inside the
2758         * document.
2759         *
2760         * @return the end offset of this <code>Element</code> inside the
2761         *         document
2762         */
2763        public int getEndOffset()
2764        {
2765          return endPos.getOffset();
2766        }
2767    
2768        /**
2769         * Returns the name of this <code>Element</code>. This is
2770         * {@link #ContentElementName} in this case.
2771         *
2772         * @return the name of this <code>Element</code>
2773         */
2774        public String getName()
2775        {
2776          String name = super.getName();
2777          if (name == null)
2778            name = ContentElementName;
2779          return name;
2780        }
2781    
2782        /**
2783         * Returns the start offset of this <code>Element</code> inside the
2784         * document.
2785         *
2786         * @return the start offset of this <code>Element</code> inside the
2787         *         document
2788         */
2789        public int getStartOffset()
2790        {
2791          return startPos.getOffset();
2792        }
2793    
2794        /**
2795         * Returns <code>true</code>.
2796         *
2797         * @return <code>true</code>
2798         */
2799        public boolean isLeaf()
2800        {
2801          return true;
2802        }
2803    
2804        /**
2805         * Returns a string representation of this <code>Element</code>.
2806         *
2807         * @return a string representation of this <code>Element</code>
2808         */
2809        public String toString()
2810        {
2811          return ("LeafElement(" + getName() + ") "
2812                  + getStartOffset() + "," + getEndOffset() + "\n");
2813        }
2814      }
2815    
2816      /**
2817       * The root element for bidirectional text.
2818       */
2819      private class BidiRootElement
2820        extends BranchElement
2821      {
2822        /**
2823         * Creates a new bidi root element.
2824         */
2825        BidiRootElement()
2826        {
2827          super(null, null);
2828        }
2829    
2830        /**
2831         * Returns the name of the element.
2832         *
2833         * @return the name of the element
2834         */
2835        public String getName()
2836        {
2837          return BidiRootName;
2838        }
2839      }
2840    
2841      /**
2842       * A leaf element for the bidi structure.
2843       */
2844      private class BidiElement
2845        extends LeafElement
2846      {
2847        /**
2848         * Creates a new BidiElement.
2849         *
2850         * @param parent the parent element
2851         * @param start the start offset
2852         * @param end the end offset
2853         * @param level the bidi level
2854         */
2855        BidiElement(Element parent, int start, int end, int level)
2856        {
2857          super(parent, new SimpleAttributeSet(), start, end);
2858          addAttribute(StyleConstants.BidiLevel, new Integer(level));
2859        }
2860    
2861        /**
2862         * Returns the name of the element.
2863         *
2864         * @return the name of the element
2865         */
2866        public String getName()
2867        {
2868          return BidiElementName;
2869        }
2870      }
2871    
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.
2874       */
2875      class Bypass extends DocumentFilter.FilterBypass
2876      {
2877    
2878        public Document getDocument()
2879        {
2880          return AbstractDocument.this;
2881        }
2882    
2883        public void insertString(int offset, String string, AttributeSet attr)
2884        throws BadLocationException
2885        {
2886          AbstractDocument.this.insertStringImpl(offset, string, attr);
2887        }
2888    
2889        public void remove(int offset, int length)
2890        throws BadLocationException
2891        {
2892          AbstractDocument.this.removeImpl(offset, length);
2893        }
2894    
2895        public void replace(int offset, int length, String string,
2896                            AttributeSet attrs)
2897        throws BadLocationException
2898        {
2899          AbstractDocument.this.replaceImpl(offset, length, string, attrs);
2900        }
2901        
2902      }
2903      
2904    }