AbstractDocument.java revision 13360:5ed9c2c9abe6
1/*
2 * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25package javax.swing.text;
26
27import java.util.*;
28import java.io.*;
29import java.awt.font.TextAttribute;
30import java.text.Bidi;
31
32import javax.swing.UIManager;
33import javax.swing.undo.*;
34import javax.swing.event.*;
35import javax.swing.tree.TreeNode;
36
37import sun.font.BidiUtils;
38import sun.swing.SwingUtilities2;
39import sun.swing.text.UndoableEditLockSupport;
40
41/**
42 * An implementation of the document interface to serve as a
43 * basis for implementing various kinds of documents.  At this
44 * level there is very little policy, so there is a corresponding
45 * increase in difficulty of use.
46 * <p>
47 * This class implements a locking mechanism for the document.  It
48 * allows multiple readers or one writer, and writers must wait until
49 * all observers of the document have been notified of a previous
50 * change before beginning another mutation to the document.  The
51 * read lock is acquired and released using the <code>render</code>
52 * method.  A write lock is acquired by the methods that mutate the
53 * document, and are held for the duration of the method call.
54 * Notification is done on the thread that produced the mutation,
55 * and the thread has full read access to the document for the
56 * duration of the notification, but other readers are kept out
57 * until the notification has finished.  The notification is a
58 * beans event notification which does not allow any further
59 * mutations until all listeners have been notified.
60 * <p>
61 * Any models subclassed from this class and used in conjunction
62 * with a text component that has a look and feel implementation
63 * that is derived from BasicTextUI may be safely updated
64 * asynchronously, because all access to the View hierarchy
65 * is serialized by BasicTextUI if the document is of type
66 * <code>AbstractDocument</code>.  The locking assumes that an
67 * independent thread will access the View hierarchy only from
68 * the DocumentListener methods, and that there will be only
69 * one event thread active at a time.
70 * <p>
71 * If concurrency support is desired, there are the following
72 * additional implications.  The code path for any DocumentListener
73 * implementation and any UndoListener implementation must be threadsafe,
74 * and not access the component lock if trying to be safe from deadlocks.
75 * The <code>repaint</code> and <code>revalidate</code> methods
76 * on JComponent are safe.
77 * <p>
78 * AbstractDocument models an implied break at the end of the document.
79 * Among other things this allows you to position the caret after the last
80 * character. As a result of this, <code>getLength</code> returns one less
81 * than the length of the Content. If you create your own Content, be
82 * sure and initialize it to have an additional character. Refer to
83 * StringContent and GapContent for examples of this. Another implication
84 * of this is that Elements that model the implied end character will have
85 * an endOffset == (getLength() + 1). For example, in DefaultStyledDocument
86 * <code>getParagraphElement(getLength()).getEndOffset() == getLength() + 1
87 * </code>.
88 * <p>
89 * <strong>Warning:</strong>
90 * Serialized objects of this class will not be compatible with
91 * future Swing releases. The current serialization support is
92 * appropriate for short term storage or RMI between applications running
93 * the same version of Swing.  As of 1.4, support for long term storage
94 * of all JavaBeans&trade;
95 * has been added to the <code>java.beans</code> package.
96 * Please see {@link java.beans.XMLEncoder}.
97 *
98 * @author  Timothy Prinzing
99 */
100@SuppressWarnings("serial") // Same-version serialization only
101public abstract class AbstractDocument implements Document, Serializable {
102
103    /**
104     * Constructs a new <code>AbstractDocument</code>, wrapped around some
105     * specified content storage mechanism.
106     *
107     * @param data the content
108     */
109    protected AbstractDocument(Content data) {
110        this(data, StyleContext.getDefaultStyleContext());
111    }
112
113    /**
114     * Constructs a new <code>AbstractDocument</code>, wrapped around some
115     * specified content storage mechanism.
116     *
117     * @param data the content
118     * @param context the attribute context
119     */
120    protected AbstractDocument(Content data, AttributeContext context) {
121        this.data = data;
122        this.context = context;
123        bidiRoot = new BidiRootElement();
124
125        if (defaultI18NProperty == null) {
126            // determine default setting for i18n support
127            String o = java.security.AccessController.doPrivileged(
128                new java.security.PrivilegedAction<String>() {
129                    public String run() {
130                        return System.getProperty(I18NProperty);
131                    }
132                }
133            );
134            if (o != null) {
135                defaultI18NProperty = Boolean.valueOf(o);
136            } else {
137                defaultI18NProperty = Boolean.FALSE;
138            }
139        }
140        putProperty( I18NProperty, defaultI18NProperty);
141
142        //REMIND(bcb) This creates an initial bidi element to account for
143        //the \n that exists by default in the content.  Doing it this way
144        //seems to expose a little too much knowledge of the content given
145        //to us by the sub-class.  Consider having the sub-class' constructor
146        //make an initial call to insertUpdate.
147        writeLock();
148        try {
149            Element[] p = new Element[1];
150            p[0] = new BidiElement( bidiRoot, 0, 1, 0 );
151            bidiRoot.replace(0,0,p);
152        } finally {
153            writeUnlock();
154        }
155    }
156
157    /**
158     * Supports managing a set of properties. Callers
159     * can use the <code>documentProperties</code> dictionary
160     * to annotate the document with document-wide properties.
161     *
162     * @return a non-<code>null</code> <code>Dictionary</code>
163     * @see #setDocumentProperties
164     */
165    public Dictionary<Object,Object> getDocumentProperties() {
166        if (documentProperties == null) {
167            documentProperties = new Hashtable<Object, Object>(2);
168        }
169        return documentProperties;
170    }
171
172    /**
173     * Replaces the document properties dictionary for this document.
174     *
175     * @param x the new dictionary
176     * @see #getDocumentProperties
177     */
178    public void setDocumentProperties(Dictionary<Object,Object> x) {
179        documentProperties = x;
180    }
181
182    /**
183     * Notifies all listeners that have registered interest for
184     * notification on this event type.  The event instance
185     * is lazily created using the parameters passed into
186     * the fire method.
187     *
188     * @param e the event
189     * @see EventListenerList
190     */
191    protected void fireInsertUpdate(DocumentEvent e) {
192        notifyingListeners = true;
193        try {
194            // Guaranteed to return a non-null array
195            Object[] listeners = listenerList.getListenerList();
196            // Process the listeners last to first, notifying
197            // those that are interested in this event
198            for (int i = listeners.length-2; i>=0; i-=2) {
199                if (listeners[i]==DocumentListener.class) {
200                    // Lazily create the event:
201                    // if (e == null)
202                    // e = new ListSelectionEvent(this, firstIndex, lastIndex);
203                    ((DocumentListener)listeners[i+1]).insertUpdate(e);
204                }
205            }
206        } finally {
207            notifyingListeners = false;
208        }
209    }
210
211    /**
212     * Notifies all listeners that have registered interest for
213     * notification on this event type.  The event instance
214     * is lazily created using the parameters passed into
215     * the fire method.
216     *
217     * @param e the event
218     * @see EventListenerList
219     */
220    protected void fireChangedUpdate(DocumentEvent e) {
221        notifyingListeners = true;
222        try {
223            // Guaranteed to return a non-null array
224            Object[] listeners = listenerList.getListenerList();
225            // Process the listeners last to first, notifying
226            // those that are interested in this event
227            for (int i = listeners.length-2; i>=0; i-=2) {
228                if (listeners[i]==DocumentListener.class) {
229                    // Lazily create the event:
230                    // if (e == null)
231                    // e = new ListSelectionEvent(this, firstIndex, lastIndex);
232                    ((DocumentListener)listeners[i+1]).changedUpdate(e);
233                }
234            }
235        } finally {
236            notifyingListeners = false;
237        }
238    }
239
240    /**
241     * Notifies all listeners that have registered interest for
242     * notification on this event type.  The event instance
243     * is lazily created using the parameters passed into
244     * the fire method.
245     *
246     * @param e the event
247     * @see EventListenerList
248     */
249    protected void fireRemoveUpdate(DocumentEvent e) {
250        notifyingListeners = true;
251        try {
252            // Guaranteed to return a non-null array
253            Object[] listeners = listenerList.getListenerList();
254            // Process the listeners last to first, notifying
255            // those that are interested in this event
256            for (int i = listeners.length-2; i>=0; i-=2) {
257                if (listeners[i]==DocumentListener.class) {
258                    // Lazily create the event:
259                    // if (e == null)
260                    // e = new ListSelectionEvent(this, firstIndex, lastIndex);
261                    ((DocumentListener)listeners[i+1]).removeUpdate(e);
262                }
263            }
264        } finally {
265            notifyingListeners = false;
266        }
267    }
268
269    /**
270     * Notifies all listeners that have registered interest for
271     * notification on this event type.  The event instance
272     * is lazily created using the parameters passed into
273     * the fire method.
274     *
275     * @param e the event
276     * @see EventListenerList
277     */
278    protected void fireUndoableEditUpdate(UndoableEditEvent e) {
279        if (e.getEdit() instanceof DefaultDocumentEvent) {
280            e = new UndoableEditEvent(e.getSource(),
281                    new DefaultDocumentEventUndoableWrapper(
282                            (DefaultDocumentEvent)e.getEdit()));
283        }
284        // Guaranteed to return a non-null array
285        Object[] listeners = listenerList.getListenerList();
286        // Process the listeners last to first, notifying
287        // those that are interested in this event
288        for (int i = listeners.length-2; i>=0; i-=2) {
289            if (listeners[i]==UndoableEditListener.class) {
290                // Lazily create the event:
291                // if (e == null)
292                // e = new ListSelectionEvent(this, firstIndex, lastIndex);
293                ((UndoableEditListener)listeners[i+1]).undoableEditHappened(e);
294            }
295        }
296    }
297
298    /**
299     * Returns an array of all the objects currently registered
300     * as <code><em>Foo</em>Listener</code>s
301     * upon this document.
302     * <code><em>Foo</em>Listener</code>s are registered using the
303     * <code>add<em>Foo</em>Listener</code> method.
304     *
305     * <p>
306     * You can specify the <code>listenerType</code> argument
307     * with a class literal, such as
308     * <code><em>Foo</em>Listener.class</code>.
309     * For example, you can query a
310     * document <code>d</code>
311     * for its document listeners with the following code:
312     *
313     * <pre>DocumentListener[] mls = (DocumentListener[])(d.getListeners(DocumentListener.class));</pre>
314     *
315     * If no such listeners exist, this method returns an empty array.
316     *
317     * @param <T> the listener type
318     * @param listenerType the type of listeners requested
319     * @return an array of all objects registered as
320     *          <code><em>Foo</em>Listener</code>s on this component,
321     *          or an empty array if no such
322     *          listeners have been added
323     * @exception ClassCastException if <code>listenerType</code>
324     *          doesn't specify a class or interface that implements
325     *          <code>java.util.EventListener</code>
326     *
327     * @see #getDocumentListeners
328     * @see #getUndoableEditListeners
329     *
330     * @since 1.3
331     */
332    public <T extends EventListener> T[] getListeners(Class<T> listenerType) {
333        return listenerList.getListeners(listenerType);
334    }
335
336    /**
337     * Gets the asynchronous loading priority.  If less than zero,
338     * the document should not be loaded asynchronously.
339     *
340     * @return the asynchronous loading priority, or <code>-1</code>
341     *   if the document should not be loaded asynchronously
342     */
343    public int getAsynchronousLoadPriority() {
344        Integer loadPriority = (Integer)
345            getProperty(AbstractDocument.AsyncLoadPriority);
346        if (loadPriority != null) {
347            return loadPriority.intValue();
348        }
349        return -1;
350    }
351
352    /**
353     * Sets the asynchronous loading priority.
354     * @param p the new asynchronous loading priority; a value
355     *   less than zero indicates that the document should not be
356     *   loaded asynchronously
357     */
358    public void setAsynchronousLoadPriority(int p) {
359        Integer loadPriority = (p >= 0) ? Integer.valueOf(p) : null;
360        putProperty(AbstractDocument.AsyncLoadPriority, loadPriority);
361    }
362
363    /**
364     * Sets the <code>DocumentFilter</code>. The <code>DocumentFilter</code>
365     * is passed <code>insert</code> and <code>remove</code> to conditionally
366     * allow inserting/deleting of the text.  A <code>null</code> value
367     * indicates that no filtering will occur.
368     *
369     * @param filter the <code>DocumentFilter</code> used to constrain text
370     * @see #getDocumentFilter
371     * @since 1.4
372     */
373    public void setDocumentFilter(DocumentFilter filter) {
374        documentFilter = filter;
375    }
376
377    /**
378     * Returns the <code>DocumentFilter</code> that is responsible for
379     * filtering of insertion/removal. A <code>null</code> return value
380     * implies no filtering is to occur.
381     *
382     * @since 1.4
383     * @see #setDocumentFilter
384     * @return the DocumentFilter
385     */
386    public DocumentFilter getDocumentFilter() {
387        return documentFilter;
388    }
389
390    // --- Document methods -----------------------------------------
391
392    /**
393     * This allows the model to be safely rendered in the presence
394     * of currency, if the model supports being updated asynchronously.
395     * The given runnable will be executed in a way that allows it
396     * to safely read the model with no changes while the runnable
397     * is being executed.  The runnable itself may <em>not</em>
398     * make any mutations.
399     * <p>
400     * This is implemented to acquire a read lock for the duration
401     * of the runnables execution.  There may be multiple runnables
402     * executing at the same time, and all writers will be blocked
403     * while there are active rendering runnables.  If the runnable
404     * throws an exception, its lock will be safely released.
405     * There is no protection against a runnable that never exits,
406     * which will effectively leave the document locked for it's
407     * lifetime.
408     * <p>
409     * If the given runnable attempts to make any mutations in
410     * this implementation, a deadlock will occur.  There is
411     * no tracking of individual rendering threads to enable
412     * detecting this situation, but a subclass could incur
413     * the overhead of tracking them and throwing an error.
414     * <p>
415     * This method is thread safe, although most Swing methods
416     * are not. Please see
417     * <A HREF="http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency
418     * in Swing</A> for more information.
419     *
420     * @param r the renderer to execute
421     */
422    public void render(Runnable r) {
423        readLock();
424        try {
425            r.run();
426        } finally {
427            readUnlock();
428        }
429    }
430
431    /**
432     * Returns the length of the data.  This is the number of
433     * characters of content that represents the users data.
434     *
435     * @return the length &gt;= 0
436     * @see Document#getLength
437     */
438    public int getLength() {
439        return data.length() - 1;
440    }
441
442    /**
443     * Adds a document listener for notification of any changes.
444     *
445     * @param listener the <code>DocumentListener</code> to add
446     * @see Document#addDocumentListener
447     */
448    public void addDocumentListener(DocumentListener listener) {
449        listenerList.add(DocumentListener.class, listener);
450    }
451
452    /**
453     * Removes a document listener.
454     *
455     * @param listener the <code>DocumentListener</code> to remove
456     * @see Document#removeDocumentListener
457     */
458    public void removeDocumentListener(DocumentListener listener) {
459        listenerList.remove(DocumentListener.class, listener);
460    }
461
462    /**
463     * Returns an array of all the document listeners
464     * registered on this document.
465     *
466     * @return all of this document's <code>DocumentListener</code>s
467     *         or an empty array if no document listeners are
468     *         currently registered
469     *
470     * @see #addDocumentListener
471     * @see #removeDocumentListener
472     * @since 1.4
473     */
474    public DocumentListener[] getDocumentListeners() {
475        return listenerList.getListeners(DocumentListener.class);
476    }
477
478    /**
479     * Adds an undo listener for notification of any changes.
480     * Undo/Redo operations performed on the <code>UndoableEdit</code>
481     * will cause the appropriate DocumentEvent to be fired to keep
482     * the view(s) in sync with the model.
483     *
484     * @param listener the <code>UndoableEditListener</code> to add
485     * @see Document#addUndoableEditListener
486     */
487    public void addUndoableEditListener(UndoableEditListener listener) {
488        listenerList.add(UndoableEditListener.class, listener);
489    }
490
491    /**
492     * Removes an undo listener.
493     *
494     * @param listener the <code>UndoableEditListener</code> to remove
495     * @see Document#removeDocumentListener
496     */
497    public void removeUndoableEditListener(UndoableEditListener listener) {
498        listenerList.remove(UndoableEditListener.class, listener);
499    }
500
501    /**
502     * Returns an array of all the undoable edit listeners
503     * registered on this document.
504     *
505     * @return all of this document's <code>UndoableEditListener</code>s
506     *         or an empty array if no undoable edit listeners are
507     *         currently registered
508     *
509     * @see #addUndoableEditListener
510     * @see #removeUndoableEditListener
511     *
512     * @since 1.4
513     */
514    public UndoableEditListener[] getUndoableEditListeners() {
515        return listenerList.getListeners(UndoableEditListener.class);
516    }
517
518    /**
519     * A convenience method for looking up a property value. It is
520     * equivalent to:
521     * <pre>
522     * getDocumentProperties().get(key);
523     * </pre>
524     *
525     * @param key the non-<code>null</code> property key
526     * @return the value of this property or <code>null</code>
527     * @see #getDocumentProperties
528     */
529    public final Object getProperty(Object key) {
530        return getDocumentProperties().get(key);
531    }
532
533
534    /**
535     * A convenience method for storing up a property value.  It is
536     * equivalent to:
537     * <pre>
538     * getDocumentProperties().put(key, value);
539     * </pre>
540     * If <code>value</code> is <code>null</code> this method will
541     * remove the property.
542     *
543     * @param key the non-<code>null</code> key
544     * @param value the property value
545     * @see #getDocumentProperties
546     */
547    public final void putProperty(Object key, Object value) {
548        if (value != null) {
549            getDocumentProperties().put(key, value);
550        } else {
551            getDocumentProperties().remove(key);
552        }
553        if( key == TextAttribute.RUN_DIRECTION
554            && Boolean.TRUE.equals(getProperty(I18NProperty)) )
555        {
556            //REMIND - this needs to flip on the i18n property if run dir
557            //is rtl and the i18n property is not already on.
558            writeLock();
559            try {
560                DefaultDocumentEvent e
561                    = new DefaultDocumentEvent(0, getLength(),
562                                               DocumentEvent.EventType.INSERT);
563                updateBidi( e );
564            } finally {
565                writeUnlock();
566            }
567        }
568    }
569
570    /**
571     * Removes some content from the document.
572     * Removing content causes a write lock to be held while the
573     * actual changes are taking place.  Observers are notified
574     * of the change on the thread that called this method.
575     * <p>
576     * This method is thread safe, although most Swing methods
577     * are not. Please see
578     * <A HREF="http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency
579     * in Swing</A> for more information.
580     *
581     * @param offs the starting offset &gt;= 0
582     * @param len the number of characters to remove &gt;= 0
583     * @exception BadLocationException  the given remove position is not a valid
584     *   position within the document
585     * @see Document#remove
586     */
587    public void remove(int offs, int len) throws BadLocationException {
588        DocumentFilter filter = getDocumentFilter();
589
590        writeLock();
591        try {
592            if (filter != null) {
593                filter.remove(getFilterBypass(), offs, len);
594            }
595            else {
596                handleRemove(offs, len);
597            }
598        } finally {
599            writeUnlock();
600        }
601    }
602
603    /**
604     * Performs the actual work of the remove. It is assumed the caller
605     * will have obtained a <code>writeLock</code> before invoking this.
606     */
607    void handleRemove(int offs, int len) throws BadLocationException {
608        if (len > 0) {
609            if (offs < 0 || (offs + len) > getLength()) {
610                throw new BadLocationException("Invalid remove",
611                                               getLength() + 1);
612            }
613            DefaultDocumentEvent chng =
614                    new DefaultDocumentEvent(offs, len, DocumentEvent.EventType.REMOVE);
615
616            boolean isComposedTextElement;
617            // Check whether the position of interest is the composed text
618            isComposedTextElement = Utilities.isComposedTextElement(this, offs);
619
620            removeUpdate(chng);
621            UndoableEdit u = data.remove(offs, len);
622            if (u != null) {
623                chng.addEdit(u);
624            }
625            postRemoveUpdate(chng);
626            // Mark the edit as done.
627            chng.end();
628            fireRemoveUpdate(chng);
629            // only fire undo if Content implementation supports it
630            // undo for the composed text is not supported for now
631            if ((u != null) && !isComposedTextElement) {
632                fireUndoableEditUpdate(new UndoableEditEvent(this, chng));
633            }
634        }
635    }
636
637    /**
638     * Deletes the region of text from <code>offset</code> to
639     * <code>offset + length</code>, and replaces it with <code>text</code>.
640     * It is up to the implementation as to how this is implemented, some
641     * implementations may treat this as two distinct operations: a remove
642     * followed by an insert, others may treat the replace as one atomic
643     * operation.
644     *
645     * @param offset index of child element
646     * @param length length of text to delete, may be 0 indicating don't
647     *               delete anything
648     * @param text text to insert, <code>null</code> indicates no text to insert
649     * @param attrs AttributeSet indicating attributes of inserted text,
650     *              <code>null</code>
651     *              is legal, and typically treated as an empty attributeset,
652     *              but exact interpretation is left to the subclass
653     * @exception BadLocationException the given position is not a valid
654     *            position within the document
655     * @since 1.4
656     */
657    public void replace(int offset, int length, String text,
658                        AttributeSet attrs) throws BadLocationException {
659        if (length == 0 && (text == null || text.length() == 0)) {
660            return;
661        }
662        DocumentFilter filter = getDocumentFilter();
663
664        writeLock();
665        try {
666            if (filter != null) {
667                filter.replace(getFilterBypass(), offset, length, text,
668                               attrs);
669            }
670            else {
671                if (length > 0) {
672                    remove(offset, length);
673                }
674                if (text != null && text.length() > 0) {
675                    insertString(offset, text, attrs);
676                }
677            }
678        } finally {
679            writeUnlock();
680        }
681    }
682
683    /**
684     * Inserts some content into the document.
685     * Inserting content causes a write lock to be held while the
686     * actual changes are taking place, followed by notification
687     * to the observers on the thread that grabbed the write lock.
688     * <p>
689     * This method is thread safe, although most Swing methods
690     * are not. Please see
691     * <A HREF="http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency
692     * in Swing</A> for more information.
693     *
694     * @param offs the starting offset &gt;= 0
695     * @param str the string to insert; does nothing with null/empty strings
696     * @param a the attributes for the inserted content
697     * @exception BadLocationException  the given insert position is not a valid
698     *   position within the document
699     * @see Document#insertString
700     */
701    public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
702        if ((str == null) || (str.length() == 0)) {
703            return;
704        }
705        DocumentFilter filter = getDocumentFilter();
706
707        writeLock();
708
709        try {
710            if (filter != null) {
711                filter.insertString(getFilterBypass(), offs, str, a);
712            } else {
713                handleInsertString(offs, str, a);
714            }
715        } finally {
716            writeUnlock();
717        }
718    }
719
720    /**
721     * Performs the actual work of inserting the text; it is assumed the
722     * caller has obtained a write lock before invoking this.
723     */
724    private void handleInsertString(int offs, String str, AttributeSet a)
725            throws BadLocationException {
726        if ((str == null) || (str.length() == 0)) {
727            return;
728        }
729        UndoableEdit u = data.insertString(offs, str);
730        DefaultDocumentEvent e =
731            new DefaultDocumentEvent(offs, str.length(), DocumentEvent.EventType.INSERT);
732        if (u != null) {
733            e.addEdit(u);
734        }
735
736        // see if complex glyph layout support is needed
737        if( getProperty(I18NProperty).equals( Boolean.FALSE ) ) {
738            // if a default direction of right-to-left has been specified,
739            // we want complex layout even if the text is all left to right.
740            Object d = getProperty(TextAttribute.RUN_DIRECTION);
741            if ((d != null) && (d.equals(TextAttribute.RUN_DIRECTION_RTL))) {
742                putProperty( I18NProperty, Boolean.TRUE);
743            } else {
744                char[] chars = str.toCharArray();
745                if (SwingUtilities2.isComplexLayout(chars, 0, chars.length)) {
746                    putProperty( I18NProperty, Boolean.TRUE);
747                }
748            }
749        }
750
751        insertUpdate(e, a);
752        // Mark the edit as done.
753        e.end();
754        fireInsertUpdate(e);
755        // only fire undo if Content implementation supports it
756        // undo for the composed text is not supported for now
757        if (u != null && (a == null || !a.isDefined(StyleConstants.ComposedTextAttribute))) {
758            fireUndoableEditUpdate(new UndoableEditEvent(this, e));
759        }
760    }
761
762    /**
763     * Gets a sequence of text from the document.
764     *
765     * @param offset the starting offset &gt;= 0
766     * @param length the number of characters to retrieve &gt;= 0
767     * @return the text
768     * @exception BadLocationException  the range given includes a position
769     *   that is not a valid position within the document
770     * @see Document#getText
771     */
772    public String getText(int offset, int length) throws BadLocationException {
773        if (length < 0) {
774            throw new BadLocationException("Length must be positive", length);
775        }
776        String str = data.getString(offset, length);
777        return str;
778    }
779
780    /**
781     * Fetches the text contained within the given portion
782     * of the document.
783     * <p>
784     * If the partialReturn property on the txt parameter is false, the
785     * data returned in the Segment will be the entire length requested and
786     * may or may not be a copy depending upon how the data was stored.
787     * If the partialReturn property is true, only the amount of text that
788     * can be returned without creating a copy is returned.  Using partial
789     * returns will give better performance for situations where large
790     * parts of the document are being scanned.  The following is an example
791     * of using the partial return to access the entire document:
792     *
793     * <pre>
794     * &nbsp; int nleft = doc.getDocumentLength();
795     * &nbsp; Segment text = new Segment();
796     * &nbsp; int offs = 0;
797     * &nbsp; text.setPartialReturn(true);
798     * &nbsp; while (nleft &gt; 0) {
799     * &nbsp;     doc.getText(offs, nleft, text);
800     * &nbsp;     // do something with text
801     * &nbsp;     nleft -= text.count;
802     * &nbsp;     offs += text.count;
803     * &nbsp; }
804     * </pre>
805     *
806     * @param offset the starting offset &gt;= 0
807     * @param length the number of characters to retrieve &gt;= 0
808     * @param txt the Segment object to retrieve the text into
809     * @exception BadLocationException  the range given includes a position
810     *   that is not a valid position within the document
811     */
812    public void getText(int offset, int length, Segment txt) throws BadLocationException {
813        if (length < 0) {
814            throw new BadLocationException("Length must be positive", length);
815        }
816        data.getChars(offset, length, txt);
817    }
818
819    /**
820     * Returns a position that will track change as the document
821     * is altered.
822     * <p>
823     * This method is thread safe, although most Swing methods
824     * are not. Please see
825     * <A HREF="http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency
826     * in Swing</A> for more information.
827     *
828     * @param offs the position in the model &gt;= 0
829     * @return the position
830     * @exception BadLocationException  if the given position does not
831     *   represent a valid location in the associated document
832     * @see Document#createPosition
833     */
834    public synchronized Position createPosition(int offs) throws BadLocationException {
835        return data.createPosition(offs);
836    }
837
838    /**
839     * Returns a position that represents the start of the document.  The
840     * position returned can be counted on to track change and stay
841     * located at the beginning of the document.
842     *
843     * @return the position
844     */
845    public final Position getStartPosition() {
846        Position p;
847        try {
848            p = createPosition(0);
849        } catch (BadLocationException bl) {
850            p = null;
851        }
852        return p;
853    }
854
855    /**
856     * Returns a position that represents the end of the document.  The
857     * position returned can be counted on to track change and stay
858     * located at the end of the document.
859     *
860     * @return the position
861     */
862    public final Position getEndPosition() {
863        Position p;
864        try {
865            p = createPosition(data.length());
866        } catch (BadLocationException bl) {
867            p = null;
868        }
869        return p;
870    }
871
872    /**
873     * Gets all root elements defined.  Typically, there
874     * will only be one so the default implementation
875     * is to return the default root element.
876     *
877     * @return the root element
878     */
879    public Element[] getRootElements() {
880        Element[] elems = new Element[2];
881        elems[0] = getDefaultRootElement();
882        elems[1] = getBidiRootElement();
883        return elems;
884    }
885
886    /**
887     * Returns the root element that views should be based upon
888     * unless some other mechanism for assigning views to element
889     * structures is provided.
890     *
891     * @return the root element
892     * @see Document#getDefaultRootElement
893     */
894    public abstract Element getDefaultRootElement();
895
896    // ---- local methods -----------------------------------------
897
898    /**
899     * Returns the <code>FilterBypass</code>. This will create one if one
900     * does not yet exist.
901     */
902    private DocumentFilter.FilterBypass getFilterBypass() {
903        if (filterBypass == null) {
904            filterBypass = new DefaultFilterBypass();
905        }
906        return filterBypass;
907    }
908
909    /**
910     * Returns the root element of the bidirectional structure for this
911     * document.  Its children represent character runs with a given
912     * Unicode bidi level.
913     * @return the root element of the bidirectional structure for this
914     * document
915     */
916    public Element getBidiRootElement() {
917        return bidiRoot;
918    }
919
920    /**
921     * Returns true if the text in the range <code>p0</code> to
922     * <code>p1</code> is left to right.
923     */
924    static boolean isLeftToRight(Document doc, int p0, int p1) {
925        if (Boolean.TRUE.equals(doc.getProperty(I18NProperty))) {
926            if (doc instanceof AbstractDocument) {
927                AbstractDocument adoc = (AbstractDocument) doc;
928                Element bidiRoot = adoc.getBidiRootElement();
929                int index = bidiRoot.getElementIndex(p0);
930                Element bidiElem = bidiRoot.getElement(index);
931                if (bidiElem.getEndOffset() >= p1) {
932                    AttributeSet bidiAttrs = bidiElem.getAttributes();
933                    return ((StyleConstants.getBidiLevel(bidiAttrs) % 2) == 0);
934                }
935            }
936        }
937        return true;
938    }
939
940    /**
941     * Get the paragraph element containing the given position.  Sub-classes
942     * must define for themselves what exactly constitutes a paragraph.  They
943     * should keep in mind however that a paragraph should at least be the
944     * unit of text over which to run the Unicode bidirectional algorithm.
945     *
946     * @param pos the starting offset &gt;= 0
947     * @return the element */
948    public abstract Element getParagraphElement(int pos);
949
950
951    /**
952     * Fetches the context for managing attributes.  This
953     * method effectively establishes the strategy used
954     * for compressing AttributeSet information.
955     *
956     * @return the context
957     */
958    protected final AttributeContext getAttributeContext() {
959        return context;
960    }
961
962    /**
963     * Updates document structure as a result of text insertion.  This
964     * will happen within a write lock.  If a subclass of
965     * this class reimplements this method, it should delegate to the
966     * superclass as well.
967     *
968     * @param chng a description of the change
969     * @param attr the attributes for the change
970     */
971    protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) {
972        if( getProperty(I18NProperty).equals( Boolean.TRUE ) )
973            updateBidi( chng );
974
975        // Check if a multi byte is encountered in the inserted text.
976        if (chng.type == DocumentEvent.EventType.INSERT &&
977                        chng.getLength() > 0 &&
978                        !Boolean.TRUE.equals(getProperty(MultiByteProperty))) {
979            Segment segment = SegmentCache.getSharedSegment();
980            try {
981                getText(chng.getOffset(), chng.getLength(), segment);
982                segment.first();
983                do {
984                    if ((int)segment.current() > 255) {
985                        putProperty(MultiByteProperty, Boolean.TRUE);
986                        break;
987                    }
988                } while (segment.next() != Segment.DONE);
989            } catch (BadLocationException ble) {
990                // Should never happen
991            }
992            SegmentCache.releaseSharedSegment(segment);
993        }
994    }
995
996    /**
997     * Updates any document structure as a result of text removal.  This
998     * method is called before the text is actually removed from the Content.
999     * This will happen within a write lock. If a subclass
1000     * of this class reimplements this method, it should delegate to the
1001     * superclass as well.
1002     *
1003     * @param chng a description of the change
1004     */
1005    protected void removeUpdate(DefaultDocumentEvent chng) {
1006    }
1007
1008    /**
1009     * Updates any document structure as a result of text removal.  This
1010     * method is called after the text has been removed from the Content.
1011     * This will happen within a write lock. If a subclass
1012     * of this class reimplements this method, it should delegate to the
1013     * superclass as well.
1014     *
1015     * @param chng a description of the change
1016     */
1017    protected void postRemoveUpdate(DefaultDocumentEvent chng) {
1018        if( getProperty(I18NProperty).equals( Boolean.TRUE ) )
1019            updateBidi( chng );
1020    }
1021
1022
1023    /**
1024     * Update the bidi element structure as a result of the given change
1025     * to the document.  The given change will be updated to reflect the
1026     * changes made to the bidi structure.
1027     *
1028     * This method assumes that every offset in the model is contained in
1029     * exactly one paragraph.  This method also assumes that it is called
1030     * after the change is made to the default element structure.
1031     */
1032    void updateBidi( DefaultDocumentEvent chng ) {
1033
1034        // Calculate the range of paragraphs affected by the change.
1035        int firstPStart;
1036        int lastPEnd;
1037        if( chng.type == DocumentEvent.EventType.INSERT
1038            || chng.type == DocumentEvent.EventType.CHANGE )
1039        {
1040            int chngStart = chng.getOffset();
1041            int chngEnd =  chngStart + chng.getLength();
1042            firstPStart = getParagraphElement(chngStart).getStartOffset();
1043            lastPEnd = getParagraphElement(chngEnd).getEndOffset();
1044        } else if( chng.type == DocumentEvent.EventType.REMOVE ) {
1045            Element paragraph = getParagraphElement( chng.getOffset() );
1046            firstPStart = paragraph.getStartOffset();
1047            lastPEnd = paragraph.getEndOffset();
1048        } else {
1049            throw new Error("Internal error: unknown event type.");
1050        }
1051        //System.out.println("updateBidi: firstPStart = " + firstPStart + " lastPEnd = " + lastPEnd );
1052
1053
1054        // Calculate the bidi levels for the affected range of paragraphs.  The
1055        // levels array will contain a bidi level for each character in the
1056        // affected text.
1057        byte levels[] = calculateBidiLevels( firstPStart, lastPEnd );
1058
1059
1060        Vector<Element> newElements = new Vector<Element>();
1061
1062        // Calculate the first span of characters in the affected range with
1063        // the same bidi level.  If this level is the same as the level of the
1064        // previous bidi element (the existing bidi element containing
1065        // firstPStart-1), then merge in the previous element.  If not, but
1066        // the previous element overlaps the affected range, truncate the
1067        // previous element at firstPStart.
1068        int firstSpanStart = firstPStart;
1069        int removeFromIndex = 0;
1070        if( firstSpanStart > 0 ) {
1071            int prevElemIndex = bidiRoot.getElementIndex(firstPStart-1);
1072            removeFromIndex = prevElemIndex;
1073            Element prevElem = bidiRoot.getElement(prevElemIndex);
1074            int prevLevel=StyleConstants.getBidiLevel(prevElem.getAttributes());
1075            //System.out.println("createbidiElements: prevElem= " + prevElem  + " prevLevel= " + prevLevel + "level[0] = " + levels[0]);
1076            if( prevLevel==levels[0] ) {
1077                firstSpanStart = prevElem.getStartOffset();
1078            } else if( prevElem.getEndOffset() > firstPStart ) {
1079                newElements.addElement(new BidiElement(bidiRoot,
1080                                                       prevElem.getStartOffset(),
1081                                                       firstPStart, prevLevel));
1082            } else {
1083                removeFromIndex++;
1084            }
1085        }
1086
1087        int firstSpanEnd = 0;
1088        while((firstSpanEnd<levels.length) && (levels[firstSpanEnd]==levels[0]))
1089            firstSpanEnd++;
1090
1091
1092        // Calculate the last span of characters in the affected range with
1093        // the same bidi level.  If this level is the same as the level of the
1094        // next bidi element (the existing bidi element containing lastPEnd),
1095        // then merge in the next element.  If not, but the next element
1096        // overlaps the affected range, adjust the next element to start at
1097        // lastPEnd.
1098        int lastSpanEnd = lastPEnd;
1099        Element newNextElem = null;
1100        int removeToIndex = bidiRoot.getElementCount() - 1;
1101        if( lastSpanEnd <= getLength() ) {
1102            int nextElemIndex = bidiRoot.getElementIndex( lastPEnd );
1103            removeToIndex = nextElemIndex;
1104            Element nextElem = bidiRoot.getElement( nextElemIndex );
1105            int nextLevel = StyleConstants.getBidiLevel(nextElem.getAttributes());
1106            if( nextLevel == levels[levels.length-1] ) {
1107                lastSpanEnd = nextElem.getEndOffset();
1108            } else if( nextElem.getStartOffset() < lastPEnd ) {
1109                newNextElem = new BidiElement(bidiRoot, lastPEnd,
1110                                              nextElem.getEndOffset(),
1111                                              nextLevel);
1112            } else {
1113                removeToIndex--;
1114            }
1115        }
1116
1117        int lastSpanStart = levels.length;
1118        while( (lastSpanStart>firstSpanEnd)
1119               && (levels[lastSpanStart-1]==levels[levels.length-1]) )
1120            lastSpanStart--;
1121
1122
1123        // If the first and last spans are contiguous and have the same level,
1124        // merge them and create a single new element for the entire span.
1125        // Otherwise, create elements for the first and last spans as well as
1126        // any spans in between.
1127        if((firstSpanEnd==lastSpanStart)&&(levels[0]==levels[levels.length-1])){
1128            newElements.addElement(new BidiElement(bidiRoot, firstSpanStart,
1129                                                   lastSpanEnd, levels[0]));
1130        } else {
1131            // Create an element for the first span.
1132            newElements.addElement(new BidiElement(bidiRoot, firstSpanStart,
1133                                                   firstSpanEnd+firstPStart,
1134                                                   levels[0]));
1135            // Create elements for the spans in between the first and last
1136            for( int i=firstSpanEnd; i<lastSpanStart; ) {
1137                //System.out.println("executed line 872");
1138                int j;
1139                for( j=i;  (j<levels.length) && (levels[j] == levels[i]); j++ );
1140                newElements.addElement(new BidiElement(bidiRoot, firstPStart+i,
1141                                                       firstPStart+j,
1142                                                       (int)levels[i]));
1143                i=j;
1144            }
1145            // Create an element for the last span.
1146            newElements.addElement(new BidiElement(bidiRoot,
1147                                                   lastSpanStart+firstPStart,
1148                                                   lastSpanEnd,
1149                                                   levels[levels.length-1]));
1150        }
1151
1152        if( newNextElem != null )
1153            newElements.addElement( newNextElem );
1154
1155
1156        // Calculate the set of existing bidi elements which must be
1157        // removed.
1158        int removedElemCount = 0;
1159        if( bidiRoot.getElementCount() > 0 ) {
1160            removedElemCount = removeToIndex - removeFromIndex + 1;
1161        }
1162        Element[] removedElems = new Element[removedElemCount];
1163        for( int i=0; i<removedElemCount; i++ ) {
1164            removedElems[i] = bidiRoot.getElement(removeFromIndex+i);
1165        }
1166
1167        Element[] addedElems = new Element[ newElements.size() ];
1168        newElements.copyInto( addedElems );
1169
1170        // Update the change record.
1171        ElementEdit ee = new ElementEdit( bidiRoot, removeFromIndex,
1172                                          removedElems, addedElems );
1173        chng.addEdit( ee );
1174
1175        // Update the bidi element structure.
1176        bidiRoot.replace( removeFromIndex, removedElems.length, addedElems );
1177    }
1178
1179
1180    /**
1181     * Calculate the levels array for a range of paragraphs.
1182     */
1183    private byte[] calculateBidiLevels( int firstPStart, int lastPEnd ) {
1184
1185        byte levels[] = new byte[ lastPEnd - firstPStart ];
1186        int  levelsEnd = 0;
1187        Boolean defaultDirection = null;
1188        Object d = getProperty(TextAttribute.RUN_DIRECTION);
1189        if (d instanceof Boolean) {
1190            defaultDirection = (Boolean) d;
1191        }
1192
1193        // For each paragraph in the given range of paragraphs, get its
1194        // levels array and add it to the levels array for the entire span.
1195        for(int o=firstPStart; o<lastPEnd; ) {
1196            Element p = getParagraphElement( o );
1197            int pStart = p.getStartOffset();
1198            int pEnd = p.getEndOffset();
1199
1200            // default run direction for the paragraph.  This will be
1201            // null if there is no direction override specified (i.e.
1202            // the direction will be determined from the content).
1203            Boolean direction = defaultDirection;
1204            d = p.getAttributes().getAttribute(TextAttribute.RUN_DIRECTION);
1205            if (d instanceof Boolean) {
1206                direction = (Boolean) d;
1207            }
1208
1209            //System.out.println("updateBidi: paragraph start = " + pStart + " paragraph end = " + pEnd);
1210
1211            // Create a Bidi over this paragraph then get the level
1212            // array.
1213            Segment seg = SegmentCache.getSharedSegment();
1214            try {
1215                getText(pStart, pEnd-pStart, seg);
1216            } catch (BadLocationException e ) {
1217                throw new Error("Internal error: " + e.toString());
1218            }
1219            // REMIND(bcb) we should really be using a Segment here.
1220            Bidi bidiAnalyzer;
1221            int bidiflag = Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT;
1222            if (direction != null) {
1223                if (TextAttribute.RUN_DIRECTION_LTR.equals(direction)) {
1224                    bidiflag = Bidi.DIRECTION_LEFT_TO_RIGHT;
1225                } else {
1226                    bidiflag = Bidi.DIRECTION_RIGHT_TO_LEFT;
1227                }
1228            }
1229            bidiAnalyzer = new Bidi(seg.array, seg.offset, null, 0, seg.count,
1230                    bidiflag);
1231            BidiUtils.getLevels(bidiAnalyzer, levels, levelsEnd);
1232            levelsEnd += bidiAnalyzer.getLength();
1233
1234            o =  p.getEndOffset();
1235            SegmentCache.releaseSharedSegment(seg);
1236        }
1237
1238        // REMIND(bcb) remove this code when debugging is done.
1239        if( levelsEnd != levels.length )
1240            throw new Error("levelsEnd assertion failed.");
1241
1242        return levels;
1243    }
1244
1245    /**
1246     * Gives a diagnostic dump.
1247     *
1248     * @param out the output stream
1249     */
1250    public void dump(PrintStream out) {
1251        Element root = getDefaultRootElement();
1252        if (root instanceof AbstractElement) {
1253            ((AbstractElement)root).dump(out, 0);
1254        }
1255        bidiRoot.dump(out,0);
1256    }
1257
1258    /**
1259     * Gets the content for the document.
1260     *
1261     * @return the content
1262     */
1263    protected final Content getContent() {
1264        return data;
1265    }
1266
1267    /**
1268     * Creates a document leaf element.
1269     * Hook through which elements are created to represent the
1270     * document structure.  Because this implementation keeps
1271     * structure and content separate, elements grow automatically
1272     * when content is extended so splits of existing elements
1273     * follow.  The document itself gets to decide how to generate
1274     * elements to give flexibility in the type of elements used.
1275     *
1276     * @param parent the parent element
1277     * @param a the attributes for the element
1278     * @param p0 the beginning of the range &gt;= 0
1279     * @param p1 the end of the range &gt;= p0
1280     * @return the new element
1281     */
1282    protected Element createLeafElement(Element parent, AttributeSet a, int p0, int p1) {
1283        return new LeafElement(parent, a, p0, p1);
1284    }
1285
1286    /**
1287     * Creates a document branch element, that can contain other elements.
1288     *
1289     * @param parent the parent element
1290     * @param a the attributes
1291     * @return the element
1292     */
1293    protected Element createBranchElement(Element parent, AttributeSet a) {
1294        return new BranchElement(parent, a);
1295    }
1296
1297    // --- Document locking ----------------------------------
1298
1299    /**
1300     * Fetches the current writing thread if there is one.
1301     * This can be used to distinguish whether a method is
1302     * being called as part of an existing modification or
1303     * if a lock needs to be acquired and a new transaction
1304     * started.
1305     *
1306     * @return the thread actively modifying the document
1307     *  or <code>null</code> if there are no modifications in progress
1308     */
1309    protected final synchronized Thread getCurrentWriter() {
1310        return currWriter;
1311    }
1312
1313    /**
1314     * Acquires a lock to begin mutating the document this lock
1315     * protects.  There can be no writing, notification of changes, or
1316     * reading going on in order to gain the lock.  Additionally a thread is
1317     * allowed to gain more than one <code>writeLock</code>,
1318     * as long as it doesn't attempt to gain additional <code>writeLock</code>s
1319     * from within document notification.  Attempting to gain a
1320     * <code>writeLock</code> from within a DocumentListener notification will
1321     * result in an <code>IllegalStateException</code>.  The ability
1322     * to obtain more than one <code>writeLock</code> per thread allows
1323     * subclasses to gain a writeLock, perform a number of operations, then
1324     * release the lock.
1325     * <p>
1326     * Calls to <code>writeLock</code>
1327     * must be balanced with calls to <code>writeUnlock</code>, else the
1328     * <code>Document</code> will be left in a locked state so that no
1329     * reading or writing can be done.
1330     *
1331     * @exception IllegalStateException thrown on illegal lock
1332     *  attempt.  If the document is implemented properly, this can
1333     *  only happen if a document listener attempts to mutate the
1334     *  document.  This situation violates the bean event model
1335     *  where order of delivery is not guaranteed and all listeners
1336     *  should be notified before further mutations are allowed.
1337     */
1338    protected final synchronized void writeLock() {
1339        try {
1340            while ((numReaders > 0) || (currWriter != null)) {
1341                if (Thread.currentThread() == currWriter) {
1342                    if (notifyingListeners) {
1343                        // Assuming one doesn't do something wrong in a
1344                        // subclass this should only happen if a
1345                        // DocumentListener tries to mutate the document.
1346                        throw new IllegalStateException(
1347                                      "Attempt to mutate in notification");
1348                    }
1349                    numWriters++;
1350                    return;
1351                }
1352                wait();
1353            }
1354            currWriter = Thread.currentThread();
1355            numWriters = 1;
1356        } catch (InterruptedException e) {
1357            throw new Error("Interrupted attempt to acquire write lock");
1358        }
1359    }
1360
1361    /**
1362     * Releases a write lock previously obtained via <code>writeLock</code>.
1363     * After decrementing the lock count if there are no outstanding locks
1364     * this will allow a new writer, or readers.
1365     *
1366     * @see #writeLock
1367     */
1368    protected final synchronized void writeUnlock() {
1369        if (--numWriters <= 0) {
1370            numWriters = 0;
1371            currWriter = null;
1372            notifyAll();
1373        }
1374    }
1375
1376    /**
1377     * Acquires a lock to begin reading some state from the
1378     * document.  There can be multiple readers at the same time.
1379     * Writing blocks the readers until notification of the change
1380     * to the listeners has been completed.  This method should
1381     * be used very carefully to avoid unintended compromise
1382     * of the document.  It should always be balanced with a
1383     * <code>readUnlock</code>.
1384     *
1385     * @see #readUnlock
1386     */
1387    public final synchronized void readLock() {
1388        try {
1389            while (currWriter != null) {
1390                if (currWriter == Thread.currentThread()) {
1391                    // writer has full read access.... may try to acquire
1392                    // lock in notification
1393                    return;
1394                }
1395                wait();
1396            }
1397            numReaders += 1;
1398        } catch (InterruptedException e) {
1399            throw new Error("Interrupted attempt to acquire read lock");
1400        }
1401    }
1402
1403    /**
1404     * Does a read unlock.  This signals that one
1405     * of the readers is done.  If there are no more readers
1406     * then writing can begin again.  This should be balanced
1407     * with a readLock, and should occur in a finally statement
1408     * so that the balance is guaranteed.  The following is an
1409     * example.
1410     * <pre><code>
1411     * &nbsp;   readLock();
1412     * &nbsp;   try {
1413     * &nbsp;       // do something
1414     * &nbsp;   } finally {
1415     * &nbsp;       readUnlock();
1416     * &nbsp;   }
1417     * </code></pre>
1418     *
1419     * @see #readLock
1420     */
1421    public final synchronized void readUnlock() {
1422        if (currWriter == Thread.currentThread()) {
1423            // writer has full read access.... may try to acquire
1424            // lock in notification
1425            return;
1426        }
1427        if (numReaders <= 0) {
1428            throw new StateInvariantError(BAD_LOCK_STATE);
1429        }
1430        numReaders -= 1;
1431        notify();
1432    }
1433
1434    // --- serialization ---------------------------------------------
1435
1436    @SuppressWarnings("unchecked")
1437    private void readObject(ObjectInputStream s)
1438      throws ClassNotFoundException, IOException
1439    {
1440        ObjectInputStream.GetField f = s.readFields();
1441
1442        documentProperties =
1443            (Dictionary<Object, Object>) f.get("documentProperties", null);
1444        listenerList = new EventListenerList();
1445        data = (Content) f.get("data", null);
1446        context = (AttributeContext) f.get("context", null);
1447        documentFilter = (DocumentFilter) f.get("documentFilter", null);
1448
1449        // Restore bidi structure
1450        //REMIND(bcb) This creates an initial bidi element to account for
1451        //the \n that exists by default in the content.
1452        bidiRoot = new BidiRootElement();
1453        try {
1454            writeLock();
1455            Element[] p = new Element[1];
1456            p[0] = new BidiElement( bidiRoot, 0, 1, 0 );
1457            bidiRoot.replace(0,0,p);
1458        } finally {
1459            writeUnlock();
1460        }
1461        // At this point bidi root is only partially correct. To fully
1462        // restore it we need access to getDefaultRootElement. But, this
1463        // is created by the subclass and at this point will be null. We
1464        // thus use registerValidation.
1465        s.registerValidation(new ObjectInputValidation() {
1466            public void validateObject() {
1467                try {
1468                    writeLock();
1469                    DefaultDocumentEvent e = new DefaultDocumentEvent
1470                                   (0, getLength(),
1471                                    DocumentEvent.EventType.INSERT);
1472                    updateBidi( e );
1473                }
1474                finally {
1475                    writeUnlock();
1476                }
1477            }
1478        }, 0);
1479    }
1480
1481    // ----- member variables ------------------------------------------
1482
1483    private transient int numReaders;
1484    private transient Thread currWriter;
1485    /**
1486     * The number of writers, all obtained from <code>currWriter</code>.
1487     */
1488    private transient int numWriters;
1489    /**
1490     * True will notifying listeners.
1491     */
1492    private transient boolean notifyingListeners;
1493
1494    private static Boolean defaultI18NProperty;
1495
1496    /**
1497     * Storage for document-wide properties.
1498     */
1499    private Dictionary<Object,Object> documentProperties = null;
1500
1501    /**
1502     * The event listener list for the document.
1503     */
1504    protected EventListenerList listenerList = new EventListenerList();
1505
1506    /**
1507     * Where the text is actually stored, and a set of marks
1508     * that track change as the document is edited are managed.
1509     */
1510    private Content data;
1511
1512    /**
1513     * Factory for the attributes.  This is the strategy for
1514     * attribute compression and control of the lifetime of
1515     * a set of attributes as a collection.  This may be shared
1516     * with other documents.
1517     */
1518    private AttributeContext context;
1519
1520    /**
1521     * The root of the bidirectional structure for this document.  Its children
1522     * represent character runs with the same Unicode bidi level.
1523     */
1524    private transient BranchElement bidiRoot;
1525
1526    /**
1527     * Filter for inserting/removing of text.
1528     */
1529    private DocumentFilter documentFilter;
1530
1531    /**
1532     * Used by DocumentFilter to do actual insert/remove.
1533     */
1534    private transient DocumentFilter.FilterBypass filterBypass;
1535
1536    private static final String BAD_LOCK_STATE = "document lock failure";
1537
1538    /**
1539     * Error message to indicate a bad location.
1540     */
1541    protected static final String BAD_LOCATION = "document location failure";
1542
1543    /**
1544     * Name of elements used to represent paragraphs
1545     */
1546    public static final String ParagraphElementName = "paragraph";
1547
1548    /**
1549     * Name of elements used to represent content
1550     */
1551    public static final String ContentElementName = "content";
1552
1553    /**
1554     * Name of elements used to hold sections (lines/paragraphs).
1555     */
1556    public static final String SectionElementName = "section";
1557
1558    /**
1559     * Name of elements used to hold a unidirectional run
1560     */
1561    public static final String BidiElementName = "bidi level";
1562
1563    /**
1564     * Name of the attribute used to specify element
1565     * names.
1566     */
1567    public static final String ElementNameAttribute = "$ename";
1568
1569    /**
1570     * Document property that indicates whether internationalization
1571     * functions such as text reordering or reshaping should be
1572     * performed. This property should not be publicly exposed,
1573     * since it is used for implementation convenience only.  As a
1574     * side effect, copies of this property may be in its subclasses
1575     * that live in different packages (e.g. HTMLDocument as of now),
1576     * so those copies should also be taken care of when this property
1577     * needs to be modified.
1578     */
1579    static final String I18NProperty = "i18n";
1580
1581    /**
1582     * Document property that indicates if a character has been inserted
1583     * into the document that is more than one byte long.  GlyphView uses
1584     * this to determine if it should use BreakIterator.
1585     */
1586    static final Object MultiByteProperty = "multiByte";
1587
1588    /**
1589     * Document property that indicates asynchronous loading is
1590     * desired, with the thread priority given as the value.
1591     */
1592    static final String AsyncLoadPriority = "load priority";
1593
1594    /**
1595     * Interface to describe a sequence of character content that
1596     * can be edited.  Implementations may or may not support a
1597     * history mechanism which will be reflected by whether or not
1598     * mutations return an UndoableEdit implementation.
1599     * @see AbstractDocument
1600     */
1601    public interface Content {
1602
1603        /**
1604         * Creates a position within the content that will
1605         * track change as the content is mutated.
1606         *
1607         * @param offset the offset in the content &gt;= 0
1608         * @return a Position
1609         * @exception BadLocationException for an invalid offset
1610         */
1611        public Position createPosition(int offset) throws BadLocationException;
1612
1613        /**
1614         * Current length of the sequence of character content.
1615         *
1616         * @return the length &gt;= 0
1617         */
1618        public int length();
1619
1620        /**
1621         * Inserts a string of characters into the sequence.
1622         *
1623         * @param where   offset into the sequence to make the insertion &gt;= 0
1624         * @param str     string to insert
1625         * @return  if the implementation supports a history mechanism,
1626         *    a reference to an <code>Edit</code> implementation will be returned,
1627         *    otherwise returns <code>null</code>
1628         * @exception BadLocationException  thrown if the area covered by
1629         *   the arguments is not contained in the character sequence
1630         */
1631        public UndoableEdit insertString(int where, String str) throws BadLocationException;
1632
1633        /**
1634         * Removes some portion of the sequence.
1635         *
1636         * @param where   The offset into the sequence to make the
1637         *   insertion &gt;= 0.
1638         * @param nitems  The number of items in the sequence to remove &gt;= 0.
1639         * @return  If the implementation supports a history mechanism,
1640         *    a reference to an Edit implementation will be returned,
1641         *    otherwise null.
1642         * @exception BadLocationException  Thrown if the area covered by
1643         *   the arguments is not contained in the character sequence.
1644         */
1645        public UndoableEdit remove(int where, int nitems) throws BadLocationException;
1646
1647        /**
1648         * Fetches a string of characters contained in the sequence.
1649         *
1650         * @param where   Offset into the sequence to fetch &gt;= 0.
1651         * @param len     number of characters to copy &gt;= 0.
1652         * @return the string
1653         * @exception BadLocationException  Thrown if the area covered by
1654         *   the arguments is not contained in the character sequence.
1655         */
1656        public String getString(int where, int len) throws BadLocationException;
1657
1658        /**
1659         * Gets a sequence of characters and copies them into a Segment.
1660         *
1661         * @param where the starting offset &gt;= 0
1662         * @param len the number of characters &gt;= 0
1663         * @param txt the target location to copy into
1664         * @exception BadLocationException  Thrown if the area covered by
1665         *   the arguments is not contained in the character sequence.
1666         */
1667        public void getChars(int where, int len, Segment txt) throws BadLocationException;
1668    }
1669
1670    /**
1671     * An interface that can be used to allow MutableAttributeSet
1672     * implementations to use pluggable attribute compression
1673     * techniques.  Each mutation of the attribute set can be
1674     * used to exchange a previous AttributeSet instance with
1675     * another, preserving the possibility of the AttributeSet
1676     * remaining immutable.  An implementation is provided by
1677     * the StyleContext class.
1678     *
1679     * The Element implementations provided by this class use
1680     * this interface to provide their MutableAttributeSet
1681     * implementations, so that different AttributeSet compression
1682     * techniques can be employed.  The method
1683     * <code>getAttributeContext</code> should be implemented to
1684     * return the object responsible for implementing the desired
1685     * compression technique.
1686     *
1687     * @see StyleContext
1688     */
1689    public interface AttributeContext {
1690
1691        /**
1692         * Adds an attribute to the given set, and returns
1693         * the new representative set.
1694         *
1695         * @param old the old attribute set
1696         * @param name the non-null attribute name
1697         * @param value the attribute value
1698         * @return the updated attribute set
1699         * @see MutableAttributeSet#addAttribute
1700         */
1701        public AttributeSet addAttribute(AttributeSet old, Object name, Object value);
1702
1703        /**
1704         * Adds a set of attributes to the element.
1705         *
1706         * @param old the old attribute set
1707         * @param attr the attributes to add
1708         * @return the updated attribute set
1709         * @see MutableAttributeSet#addAttribute
1710         */
1711        public AttributeSet addAttributes(AttributeSet old, AttributeSet attr);
1712
1713        /**
1714         * Removes an attribute from the set.
1715         *
1716         * @param old the old attribute set
1717         * @param name the non-null attribute name
1718         * @return the updated attribute set
1719         * @see MutableAttributeSet#removeAttribute
1720         */
1721        public AttributeSet removeAttribute(AttributeSet old, Object name);
1722
1723        /**
1724         * Removes a set of attributes for the element.
1725         *
1726         * @param old the old attribute set
1727         * @param names the attribute names
1728         * @return the updated attribute set
1729         * @see MutableAttributeSet#removeAttributes
1730         */
1731        public AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names);
1732
1733        /**
1734         * Removes a set of attributes for the element.
1735         *
1736         * @param old the old attribute set
1737         * @param attrs the attributes
1738         * @return the updated attribute set
1739         * @see MutableAttributeSet#removeAttributes
1740         */
1741        public AttributeSet removeAttributes(AttributeSet old, AttributeSet attrs);
1742
1743        /**
1744         * Fetches an empty AttributeSet.
1745         *
1746         * @return the attribute set
1747         */
1748        public AttributeSet getEmptySet();
1749
1750        /**
1751         * Reclaims an attribute set.
1752         * This is a way for a MutableAttributeSet to mark that it no
1753         * longer need a particular immutable set.  This is only necessary
1754         * in 1.1 where there are no weak references.  A 1.1 implementation
1755         * would call this in its finalize method.
1756         *
1757         * @param a the attribute set to reclaim
1758         */
1759        public void reclaim(AttributeSet a);
1760    }
1761
1762    /**
1763     * Implements the abstract part of an element.  By default elements
1764     * support attributes by having a field that represents the immutable
1765     * part of the current attribute set for the element.  The element itself
1766     * implements MutableAttributeSet which can be used to modify the set
1767     * by fetching a new immutable set.  The immutable sets are provided
1768     * by the AttributeContext associated with the document.
1769     * <p>
1770     * <strong>Warning:</strong>
1771     * Serialized objects of this class will not be compatible with
1772     * future Swing releases. The current serialization support is
1773     * appropriate for short term storage or RMI between applications running
1774     * the same version of Swing.  As of 1.4, support for long term storage
1775     * of all JavaBeans&trade;
1776     * has been added to the <code>java.beans</code> package.
1777     * Please see {@link java.beans.XMLEncoder}.
1778     */
1779    @SuppressWarnings("serial") // Same-version serialization only
1780    public abstract class AbstractElement implements Element, MutableAttributeSet, Serializable, TreeNode {
1781
1782        /**
1783         * Creates a new AbstractElement.
1784         *
1785         * @param parent the parent element
1786         * @param a the attributes for the element
1787         * @since 1.4
1788         */
1789        public AbstractElement(Element parent, AttributeSet a) {
1790            this.parent = parent;
1791            attributes = getAttributeContext().getEmptySet();
1792            if (a != null) {
1793                addAttributes(a);
1794            }
1795        }
1796
1797        private final void indent(PrintWriter out, int n) {
1798            for (int i = 0; i < n; i++) {
1799                out.print("  ");
1800            }
1801        }
1802
1803        /**
1804         * Dumps a debugging representation of the element hierarchy.
1805         *
1806         * @param psOut the output stream
1807         * @param indentAmount the indentation level &gt;= 0
1808         */
1809        public void dump(PrintStream psOut, int indentAmount) {
1810            PrintWriter out;
1811            try {
1812                out = new PrintWriter(new OutputStreamWriter(psOut,"JavaEsc"),
1813                                      true);
1814            } catch (UnsupportedEncodingException e){
1815                out = new PrintWriter(psOut,true);
1816            }
1817            indent(out, indentAmount);
1818            if (getName() == null) {
1819                out.print("<??");
1820            } else {
1821                out.print("<" + getName());
1822            }
1823            if (getAttributeCount() > 0) {
1824                out.println("");
1825                // dump the attributes
1826                Enumeration<?> names = attributes.getAttributeNames();
1827                while (names.hasMoreElements()) {
1828                    Object name = names.nextElement();
1829                    indent(out, indentAmount + 1);
1830                    out.println(name + "=" + getAttribute(name));
1831                }
1832                indent(out, indentAmount);
1833            }
1834            out.println(">");
1835
1836            if (isLeaf()) {
1837                indent(out, indentAmount+1);
1838                out.print("[" + getStartOffset() + "," + getEndOffset() + "]");
1839                Content c = getContent();
1840                try {
1841                    String contentStr = c.getString(getStartOffset(),
1842                                                    getEndOffset() - getStartOffset())/*.trim()*/;
1843                    if (contentStr.length() > 40) {
1844                        contentStr = contentStr.substring(0, 40) + "...";
1845                    }
1846                    out.println("["+contentStr+"]");
1847                } catch (BadLocationException e) {
1848                }
1849
1850            } else {
1851                int n = getElementCount();
1852                for (int i = 0; i < n; i++) {
1853                    AbstractElement e = (AbstractElement) getElement(i);
1854                    e.dump(psOut, indentAmount+1);
1855                }
1856            }
1857        }
1858
1859        // --- AttributeSet ----------------------------
1860        // delegated to the immutable field "attributes"
1861
1862        /**
1863         * Gets the number of attributes that are defined.
1864         *
1865         * @return the number of attributes &gt;= 0
1866         * @see AttributeSet#getAttributeCount
1867         */
1868        public int getAttributeCount() {
1869            return attributes.getAttributeCount();
1870        }
1871
1872        /**
1873         * Checks whether a given attribute is defined.
1874         *
1875         * @param attrName the non-null attribute name
1876         * @return true if the attribute is defined
1877         * @see AttributeSet#isDefined
1878         */
1879        public boolean isDefined(Object attrName) {
1880            return attributes.isDefined(attrName);
1881        }
1882
1883        /**
1884         * Checks whether two attribute sets are equal.
1885         *
1886         * @param attr the attribute set to check against
1887         * @return true if the same
1888         * @see AttributeSet#isEqual
1889         */
1890        public boolean isEqual(AttributeSet attr) {
1891            return attributes.isEqual(attr);
1892        }
1893
1894        /**
1895         * Copies a set of attributes.
1896         *
1897         * @return the copy
1898         * @see AttributeSet#copyAttributes
1899         */
1900        public AttributeSet copyAttributes() {
1901            return attributes.copyAttributes();
1902        }
1903
1904        /**
1905         * Gets the value of an attribute.
1906         *
1907         * @param attrName the non-null attribute name
1908         * @return the attribute value
1909         * @see AttributeSet#getAttribute
1910         */
1911        public Object getAttribute(Object attrName) {
1912            Object value = attributes.getAttribute(attrName);
1913            if (value == null) {
1914                // The delegate nor it's resolvers had a match,
1915                // so we'll try to resolve through the parent
1916                // element.
1917                AttributeSet a = (parent != null) ? parent.getAttributes() : null;
1918                if (a != null) {
1919                    value = a.getAttribute(attrName);
1920                }
1921            }
1922            return value;
1923        }
1924
1925        /**
1926         * Gets the names of all attributes.
1927         *
1928         * @return the attribute names as an enumeration
1929         * @see AttributeSet#getAttributeNames
1930         */
1931        public Enumeration<?> getAttributeNames() {
1932            return attributes.getAttributeNames();
1933        }
1934
1935        /**
1936         * Checks whether a given attribute name/value is defined.
1937         *
1938         * @param name the non-null attribute name
1939         * @param value the attribute value
1940         * @return true if the name/value is defined
1941         * @see AttributeSet#containsAttribute
1942         */
1943        public boolean containsAttribute(Object name, Object value) {
1944            return attributes.containsAttribute(name, value);
1945        }
1946
1947
1948        /**
1949         * Checks whether the element contains all the attributes.
1950         *
1951         * @param attrs the attributes to check
1952         * @return true if the element contains all the attributes
1953         * @see AttributeSet#containsAttributes
1954         */
1955        public boolean containsAttributes(AttributeSet attrs) {
1956            return attributes.containsAttributes(attrs);
1957        }
1958
1959        /**
1960         * Gets the resolving parent.
1961         * If not overridden, the resolving parent defaults to
1962         * the parent element.
1963         *
1964         * @return the attributes from the parent, <code>null</code> if none
1965         * @see AttributeSet#getResolveParent
1966         */
1967        public AttributeSet getResolveParent() {
1968            AttributeSet a = attributes.getResolveParent();
1969            if ((a == null) && (parent != null)) {
1970                a = parent.getAttributes();
1971            }
1972            return a;
1973        }
1974
1975        // --- MutableAttributeSet ----------------------------------
1976        // should fetch a new immutable record for the field
1977        // "attributes".
1978
1979        /**
1980         * Adds an attribute to the element.
1981         *
1982         * @param name the non-null attribute name
1983         * @param value the attribute value
1984         * @see MutableAttributeSet#addAttribute
1985         */
1986        public void addAttribute(Object name, Object value) {
1987            checkForIllegalCast();
1988            AttributeContext context = getAttributeContext();
1989            attributes = context.addAttribute(attributes, name, value);
1990        }
1991
1992        /**
1993         * Adds a set of attributes to the element.
1994         *
1995         * @param attr the attributes to add
1996         * @see MutableAttributeSet#addAttribute
1997         */
1998        public void addAttributes(AttributeSet attr) {
1999            checkForIllegalCast();
2000            AttributeContext context = getAttributeContext();
2001            attributes = context.addAttributes(attributes, attr);
2002        }
2003
2004        /**
2005         * Removes an attribute from the set.
2006         *
2007         * @param name the non-null attribute name
2008         * @see MutableAttributeSet#removeAttribute
2009         */
2010        public void removeAttribute(Object name) {
2011            checkForIllegalCast();
2012            AttributeContext context = getAttributeContext();
2013            attributes = context.removeAttribute(attributes, name);
2014        }
2015
2016        /**
2017         * Removes a set of attributes for the element.
2018         *
2019         * @param names the attribute names
2020         * @see MutableAttributeSet#removeAttributes
2021         */
2022        public void removeAttributes(Enumeration<?> names) {
2023            checkForIllegalCast();
2024            AttributeContext context = getAttributeContext();
2025            attributes = context.removeAttributes(attributes, names);
2026        }
2027
2028        /**
2029         * Removes a set of attributes for the element.
2030         *
2031         * @param attrs the attributes
2032         * @see MutableAttributeSet#removeAttributes
2033         */
2034        public void removeAttributes(AttributeSet attrs) {
2035            checkForIllegalCast();
2036            AttributeContext context = getAttributeContext();
2037            if (attrs == this) {
2038                attributes = context.getEmptySet();
2039            } else {
2040                attributes = context.removeAttributes(attributes, attrs);
2041            }
2042        }
2043
2044        /**
2045         * Sets the resolving parent.
2046         *
2047         * @param parent the parent, null if none
2048         * @see MutableAttributeSet#setResolveParent
2049         */
2050        public void setResolveParent(AttributeSet parent) {
2051            checkForIllegalCast();
2052            AttributeContext context = getAttributeContext();
2053            if (parent != null) {
2054                attributes =
2055                    context.addAttribute(attributes, StyleConstants.ResolveAttribute,
2056                                         parent);
2057            } else {
2058                attributes =
2059                    context.removeAttribute(attributes, StyleConstants.ResolveAttribute);
2060            }
2061        }
2062
2063        private final void checkForIllegalCast() {
2064            Thread t = getCurrentWriter();
2065            if ((t == null) || (t != Thread.currentThread())) {
2066                throw new StateInvariantError("Illegal cast to MutableAttributeSet");
2067            }
2068        }
2069
2070        // --- Element methods -------------------------------------
2071
2072        /**
2073         * Retrieves the underlying model.
2074         *
2075         * @return the model
2076         */
2077        public Document getDocument() {
2078            return AbstractDocument.this;
2079        }
2080
2081        /**
2082         * Gets the parent of the element.
2083         *
2084         * @return the parent
2085         */
2086        public Element getParentElement() {
2087            return parent;
2088        }
2089
2090        /**
2091         * Gets the attributes for the element.
2092         *
2093         * @return the attribute set
2094         */
2095        public AttributeSet getAttributes() {
2096            return this;
2097        }
2098
2099        /**
2100         * Gets the name of the element.
2101         *
2102         * @return the name, null if none
2103         */
2104        public String getName() {
2105            if (attributes.isDefined(ElementNameAttribute)) {
2106                return (String) attributes.getAttribute(ElementNameAttribute);
2107            }
2108            return null;
2109        }
2110
2111        /**
2112         * Gets the starting offset in the model for the element.
2113         *
2114         * @return the offset &gt;= 0
2115         */
2116        public abstract int getStartOffset();
2117
2118        /**
2119         * Gets the ending offset in the model for the element.
2120         *
2121         * @return the offset &gt;= 0
2122         */
2123        public abstract int getEndOffset();
2124
2125        /**
2126         * Gets a child element.
2127         *
2128         * @param index the child index, &gt;= 0 &amp;&amp; &lt; getElementCount()
2129         * @return the child element
2130         */
2131        public abstract Element getElement(int index);
2132
2133        /**
2134         * Gets the number of children for the element.
2135         *
2136         * @return the number of children &gt;= 0
2137         */
2138        public abstract int getElementCount();
2139
2140        /**
2141         * Gets the child element index closest to the given model offset.
2142         *
2143         * @param offset the offset &gt;= 0
2144         * @return the element index &gt;= 0
2145         */
2146        public abstract int getElementIndex(int offset);
2147
2148        /**
2149         * Checks whether the element is a leaf.
2150         *
2151         * @return true if a leaf
2152         */
2153        public abstract boolean isLeaf();
2154
2155        // --- TreeNode methods -------------------------------------
2156
2157        /**
2158         * Returns the child <code>TreeNode</code> at index
2159         * <code>childIndex</code>.
2160         */
2161        public TreeNode getChildAt(int childIndex) {
2162            return (TreeNode)getElement(childIndex);
2163        }
2164
2165        /**
2166         * Returns the number of children <code>TreeNode</code>'s
2167         * receiver contains.
2168         * @return the number of children <code>TreeNodews</code>'s
2169         * receiver contains
2170         */
2171        public int getChildCount() {
2172            return getElementCount();
2173        }
2174
2175        /**
2176         * Returns the parent <code>TreeNode</code> of the receiver.
2177         * @return the parent <code>TreeNode</code> of the receiver
2178         */
2179        public TreeNode getParent() {
2180            return (TreeNode)getParentElement();
2181        }
2182
2183        /**
2184         * Returns the index of <code>node</code> in the receivers children.
2185         * If the receiver does not contain <code>node</code>, -1 will be
2186         * returned.
2187         * @param node the location of interest
2188         * @return the index of <code>node</code> in the receiver's
2189         * children, or -1 if absent
2190         */
2191        public int getIndex(TreeNode node) {
2192            for(int counter = getChildCount() - 1; counter >= 0; counter--)
2193                if(getChildAt(counter) == node)
2194                    return counter;
2195            return -1;
2196        }
2197
2198        /**
2199         * Returns true if the receiver allows children.
2200         * @return true if the receiver allows children, otherwise false
2201         */
2202        public abstract boolean getAllowsChildren();
2203
2204
2205        /**
2206         * Returns the children of the receiver as an
2207         * <code>Enumeration</code>.
2208         * @return the children of the receiver as an <code>Enumeration</code>
2209         */
2210        public abstract Enumeration<TreeNode> children();
2211
2212
2213        // --- serialization ---------------------------------------------
2214
2215        private void writeObject(ObjectOutputStream s) throws IOException {
2216            s.defaultWriteObject();
2217            StyleContext.writeAttributeSet(s, attributes);
2218        }
2219
2220        private void readObject(ObjectInputStream s)
2221            throws ClassNotFoundException, IOException
2222        {
2223            s.defaultReadObject();
2224            MutableAttributeSet attr = new SimpleAttributeSet();
2225            StyleContext.readAttributeSet(s, attr);
2226            AttributeContext context = getAttributeContext();
2227            attributes = context.addAttributes(SimpleAttributeSet.EMPTY, attr);
2228        }
2229
2230        // ---- variables -----------------------------------------------------
2231
2232        private Element parent;
2233        private transient AttributeSet attributes;
2234
2235    }
2236
2237    /**
2238     * Implements a composite element that contains other elements.
2239     * <p>
2240     * <strong>Warning:</strong>
2241     * Serialized objects of this class will not be compatible with
2242     * future Swing releases. The current serialization support is
2243     * appropriate for short term storage or RMI between applications running
2244     * the same version of Swing.  As of 1.4, support for long term storage
2245     * of all JavaBeans&trade;
2246     * has been added to the <code>java.beans</code> package.
2247     * Please see {@link java.beans.XMLEncoder}.
2248     */
2249    @SuppressWarnings("serial") // Same-version serialization only
2250    public class BranchElement extends AbstractElement {
2251
2252        /**
2253         * Constructs a composite element that initially contains
2254         * no children.
2255         *
2256         * @param parent  The parent element
2257         * @param a the attributes for the element
2258         * @since 1.4
2259         */
2260        public BranchElement(Element parent, AttributeSet a) {
2261            super(parent, a);
2262            children = new AbstractElement[1];
2263            nchildren = 0;
2264            lastIndex = -1;
2265        }
2266
2267        /**
2268         * Gets the child element that contains
2269         * the given model position.
2270         *
2271         * @param pos the position &gt;= 0
2272         * @return the element, null if none
2273         */
2274        public Element positionToElement(int pos) {
2275            int index = getElementIndex(pos);
2276            Element child = children[index];
2277            int p0 = child.getStartOffset();
2278            int p1 = child.getEndOffset();
2279            if ((pos >= p0) && (pos < p1)) {
2280                return child;
2281            }
2282            return null;
2283        }
2284
2285        /**
2286         * Replaces content with a new set of elements.
2287         *
2288         * @param offset the starting offset &gt;= 0
2289         * @param length the length to replace &gt;= 0
2290         * @param elems the new elements
2291         */
2292        public void replace(int offset, int length, Element[] elems) {
2293            int delta = elems.length - length;
2294            int src = offset + length;
2295            int nmove = nchildren - src;
2296            int dest = src + delta;
2297            if ((nchildren + delta) >= children.length) {
2298                // need to grow the array
2299                int newLength = Math.max(2*children.length, nchildren + delta);
2300                AbstractElement[] newChildren = new AbstractElement[newLength];
2301                System.arraycopy(children, 0, newChildren, 0, offset);
2302                System.arraycopy(elems, 0, newChildren, offset, elems.length);
2303                System.arraycopy(children, src, newChildren, dest, nmove);
2304                children = newChildren;
2305            } else {
2306                // patch the existing array
2307                System.arraycopy(children, src, children, dest, nmove);
2308                System.arraycopy(elems, 0, children, offset, elems.length);
2309            }
2310            nchildren = nchildren + delta;
2311        }
2312
2313        /**
2314         * Converts the element to a string.
2315         *
2316         * @return the string
2317         */
2318        public String toString() {
2319            return "BranchElement(" + getName() + ") " + getStartOffset() + "," +
2320                getEndOffset() + "\n";
2321        }
2322
2323        // --- Element methods -----------------------------------
2324
2325        /**
2326         * Gets the element name.
2327         *
2328         * @return the element name
2329         */
2330        public String getName() {
2331            String nm = super.getName();
2332            if (nm == null) {
2333                nm = ParagraphElementName;
2334            }
2335            return nm;
2336        }
2337
2338        /**
2339         * Gets the starting offset in the model for the element.
2340         *
2341         * @return the offset &gt;= 0
2342         */
2343        public int getStartOffset() {
2344            return children[0].getStartOffset();
2345        }
2346
2347        /**
2348         * Gets the ending offset in the model for the element.
2349         * @throws NullPointerException if this element has no children
2350         *
2351         * @return the offset &gt;= 0
2352         */
2353        public int getEndOffset() {
2354            Element child =
2355                (nchildren > 0) ? children[nchildren - 1] : children[0];
2356            return child.getEndOffset();
2357        }
2358
2359        /**
2360         * Gets a child element.
2361         *
2362         * @param index the child index, &gt;= 0 &amp;&amp; &lt; getElementCount()
2363         * @return the child element, null if none
2364         */
2365        public Element getElement(int index) {
2366            if (index < nchildren) {
2367                return children[index];
2368            }
2369            return null;
2370        }
2371
2372        /**
2373         * Gets the number of children for the element.
2374         *
2375         * @return the number of children &gt;= 0
2376         */
2377        public int getElementCount()  {
2378            return nchildren;
2379        }
2380
2381        /**
2382         * Gets the child element index closest to the given model offset.
2383         *
2384         * @param offset the offset &gt;= 0
2385         * @return the element index &gt;= 0
2386         */
2387        public int getElementIndex(int offset) {
2388            int index;
2389            int lower = 0;
2390            int upper = nchildren - 1;
2391            int mid = 0;
2392            int p0 = getStartOffset();
2393            int p1;
2394
2395            if (nchildren == 0) {
2396                return 0;
2397            }
2398            if (offset >= getEndOffset()) {
2399                return nchildren - 1;
2400            }
2401
2402            // see if the last index can be used.
2403            if ((lastIndex >= lower) && (lastIndex <= upper)) {
2404                Element lastHit = children[lastIndex];
2405                p0 = lastHit.getStartOffset();
2406                p1 = lastHit.getEndOffset();
2407                if ((offset >= p0) && (offset < p1)) {
2408                    return lastIndex;
2409                }
2410
2411                // last index wasn't a hit, but it does give useful info about
2412                // where a hit (if any) would be.
2413                if (offset < p0) {
2414                    upper = lastIndex;
2415                } else  {
2416                    lower = lastIndex;
2417                }
2418            }
2419
2420            while (lower <= upper) {
2421                mid = lower + ((upper - lower) / 2);
2422                Element elem = children[mid];
2423                p0 = elem.getStartOffset();
2424                p1 = elem.getEndOffset();
2425                if ((offset >= p0) && (offset < p1)) {
2426                    // found the location
2427                    index = mid;
2428                    lastIndex = index;
2429                    return index;
2430                } else if (offset < p0) {
2431                    upper = mid - 1;
2432                } else {
2433                    lower = mid + 1;
2434                }
2435            }
2436
2437            // didn't find it, but we indicate the index of where it would belong
2438            if (offset < p0) {
2439                index = mid;
2440            } else {
2441                index = mid + 1;
2442            }
2443            lastIndex = index;
2444            return index;
2445        }
2446
2447        /**
2448         * Checks whether the element is a leaf.
2449         *
2450         * @return true if a leaf
2451         */
2452        public boolean isLeaf() {
2453            return false;
2454        }
2455
2456
2457        // ------ TreeNode ----------------------------------------------
2458
2459        /**
2460         * Returns true if the receiver allows children.
2461         * @return true if the receiver allows children, otherwise false
2462         */
2463        public boolean getAllowsChildren() {
2464            return true;
2465        }
2466
2467
2468        /**
2469         * Returns the children of the receiver as an
2470         * <code>Enumeration</code>.
2471         * @return the children of the receiver
2472         */
2473        public Enumeration<TreeNode> children() {
2474            if(nchildren == 0)
2475                return null;
2476
2477            Vector<TreeNode> tempVector = new Vector<>(nchildren);
2478
2479            for(int counter = 0; counter < nchildren; counter++)
2480                tempVector.addElement(children[counter]);
2481            return tempVector.elements();
2482        }
2483
2484        // ------ members ----------------------------------------------
2485
2486        private AbstractElement[] children;
2487        private int nchildren;
2488        private int lastIndex;
2489    }
2490
2491    /**
2492     * Implements an element that directly represents content of
2493     * some kind.
2494     * <p>
2495     * <strong>Warning:</strong>
2496     * Serialized objects of this class will not be compatible with
2497     * future Swing releases. The current serialization support is
2498     * appropriate for short term storage or RMI between applications running
2499     * the same version of Swing.  As of 1.4, support for long term storage
2500     * of all JavaBeans&trade;
2501     * has been added to the <code>java.beans</code> package.
2502     * Please see {@link java.beans.XMLEncoder}.
2503     *
2504     * @see     Element
2505     */
2506    @SuppressWarnings("serial") // Same-version serialization only
2507    public class LeafElement extends AbstractElement {
2508
2509        /**
2510         * Constructs an element that represents content within the
2511         * document (has no children).
2512         *
2513         * @param parent  The parent element
2514         * @param a       The element attributes
2515         * @param offs0   The start offset &gt;= 0
2516         * @param offs1   The end offset &gt;= offs0
2517         * @since 1.4
2518         */
2519        public LeafElement(Element parent, AttributeSet a, int offs0, int offs1) {
2520            super(parent, a);
2521            try {
2522                p0 = createPosition(offs0);
2523                p1 = createPosition(offs1);
2524            } catch (BadLocationException e) {
2525                p0 = null;
2526                p1 = null;
2527                throw new StateInvariantError("Can't create Position references");
2528            }
2529        }
2530
2531        /**
2532         * Converts the element to a string.
2533         *
2534         * @return the string
2535         */
2536        public String toString() {
2537            return "LeafElement(" + getName() + ") " + p0 + "," + p1 + "\n";
2538        }
2539
2540        // --- Element methods ---------------------------------------------
2541
2542        /**
2543         * Gets the starting offset in the model for the element.
2544         *
2545         * @return the offset &gt;= 0
2546         */
2547        public int getStartOffset() {
2548            return p0.getOffset();
2549        }
2550
2551        /**
2552         * Gets the ending offset in the model for the element.
2553         *
2554         * @return the offset &gt;= 0
2555         */
2556        public int getEndOffset() {
2557            return p1.getOffset();
2558        }
2559
2560        /**
2561         * Gets the element name.
2562         *
2563         * @return the name
2564         */
2565        public String getName() {
2566            String nm = super.getName();
2567            if (nm == null) {
2568                nm = ContentElementName;
2569            }
2570            return nm;
2571        }
2572
2573        /**
2574         * Gets the child element index closest to the given model offset.
2575         *
2576         * @param pos the offset &gt;= 0
2577         * @return the element index &gt;= 0
2578         */
2579        public int getElementIndex(int pos) {
2580            return -1;
2581        }
2582
2583        /**
2584         * Gets a child element.
2585         *
2586         * @param index the child index, &gt;= 0 &amp;&amp; &lt; getElementCount()
2587         * @return the child element
2588         */
2589        public Element getElement(int index) {
2590            return null;
2591        }
2592
2593        /**
2594         * Returns the number of child elements.
2595         *
2596         * @return the number of children &gt;= 0
2597         */
2598        public int getElementCount()  {
2599            return 0;
2600        }
2601
2602        /**
2603         * Checks whether the element is a leaf.
2604         *
2605         * @return true if a leaf
2606         */
2607        public boolean isLeaf() {
2608            return true;
2609        }
2610
2611        // ------ TreeNode ----------------------------------------------
2612
2613        /**
2614         * Returns true if the receiver allows children.
2615         * @return true if the receiver allows children, otherwise false
2616         */
2617        public boolean getAllowsChildren() {
2618            return false;
2619        }
2620
2621
2622        /**
2623         * Returns the children of the receiver as an
2624         * <code>Enumeration</code>.
2625         * @return the children of the receiver
2626         */
2627        @Override
2628        public Enumeration<TreeNode> children() {
2629            return null;
2630        }
2631
2632        // --- serialization ---------------------------------------------
2633
2634        private void writeObject(ObjectOutputStream s) throws IOException {
2635            s.defaultWriteObject();
2636            s.writeInt(p0.getOffset());
2637            s.writeInt(p1.getOffset());
2638        }
2639
2640        private void readObject(ObjectInputStream s)
2641            throws ClassNotFoundException, IOException
2642        {
2643            s.defaultReadObject();
2644
2645            // set the range with positions that track change
2646            int off0 = s.readInt();
2647            int off1 = s.readInt();
2648            try {
2649                p0 = createPosition(off0);
2650                p1 = createPosition(off1);
2651            } catch (BadLocationException e) {
2652                p0 = null;
2653                p1 = null;
2654                throw new IOException("Can't restore Position references");
2655            }
2656        }
2657
2658        // ---- members -----------------------------------------------------
2659
2660        private transient Position p0;
2661        private transient Position p1;
2662    }
2663
2664    /**
2665     * Represents the root element of the bidirectional element structure.
2666     * The root element is the only element in the bidi element structure
2667     * which contains children.
2668     */
2669    class BidiRootElement extends BranchElement {
2670
2671        BidiRootElement() {
2672            super( null, null );
2673        }
2674
2675        /**
2676         * Gets the name of the element.
2677         * @return the name
2678         */
2679        public String getName() {
2680            return "bidi root";
2681        }
2682    }
2683
2684    /**
2685     * Represents an element of the bidirectional element structure.
2686     */
2687    class BidiElement extends LeafElement {
2688
2689        /**
2690         * Creates a new BidiElement.
2691         */
2692        BidiElement(Element parent, int start, int end, int level) {
2693            super(parent, new SimpleAttributeSet(), start, end);
2694            addAttribute(StyleConstants.BidiLevel, Integer.valueOf(level));
2695            //System.out.println("BidiElement: start = " + start
2696            //                   + " end = " + end + " level = " + level );
2697        }
2698
2699        /**
2700         * Gets the name of the element.
2701         * @return the name
2702         */
2703        public String getName() {
2704            return BidiElementName;
2705        }
2706
2707        int getLevel() {
2708            Integer o = (Integer) getAttribute(StyleConstants.BidiLevel);
2709            if (o != null) {
2710                return o.intValue();
2711            }
2712            return 0;  // Level 0 is base level (non-embedded) left-to-right
2713        }
2714
2715        boolean isLeftToRight() {
2716            return ((getLevel() % 2) == 0);
2717        }
2718    }
2719
2720    /**
2721     * Stores document changes as the document is being
2722     * modified.  Can subsequently be used for change notification
2723     * when done with the document modification transaction.
2724     * This is used by the AbstractDocument class and its extensions
2725     * for broadcasting change information to the document listeners.
2726     */
2727    public class DefaultDocumentEvent extends CompoundEdit implements DocumentEvent {
2728
2729        /**
2730         * Constructs a change record.
2731         *
2732         * @param offs the offset into the document of the change &gt;= 0
2733         * @param len  the length of the change &gt;= 0
2734         * @param type the type of event (DocumentEvent.EventType)
2735         * @since 1.4
2736         */
2737        public DefaultDocumentEvent(int offs, int len, DocumentEvent.EventType type) {
2738            super();
2739            offset = offs;
2740            length = len;
2741            this.type = type;
2742        }
2743
2744        /**
2745         * Returns a string description of the change event.
2746         *
2747         * @return a string
2748         */
2749        public String toString() {
2750            return edits.toString();
2751        }
2752
2753        // --- CompoundEdit methods --------------------------
2754
2755        /**
2756         * Adds a document edit.  If the number of edits crosses
2757         * a threshold, this switches on a hashtable lookup for
2758         * ElementChange implementations since access of these
2759         * needs to be relatively quick.
2760         *
2761         * @param anEdit a document edit record
2762         * @return true if the edit was added
2763         */
2764        public boolean addEdit(UndoableEdit anEdit) {
2765            // if the number of changes gets too great, start using
2766            // a hashtable for to locate the change for a given element.
2767            if ((changeLookup == null) && (edits.size() > 10)) {
2768                changeLookup = new Hashtable<Element, ElementChange>();
2769                int n = edits.size();
2770                for (int i = 0; i < n; i++) {
2771                    Object o = edits.elementAt(i);
2772                    if (o instanceof DocumentEvent.ElementChange) {
2773                        DocumentEvent.ElementChange ec = (DocumentEvent.ElementChange) o;
2774                        changeLookup.put(ec.getElement(), ec);
2775                    }
2776                }
2777            }
2778
2779            // if we have a hashtable... add the entry if it's
2780            // an ElementChange.
2781            if ((changeLookup != null) && (anEdit instanceof DocumentEvent.ElementChange)) {
2782                DocumentEvent.ElementChange ec = (DocumentEvent.ElementChange) anEdit;
2783                changeLookup.put(ec.getElement(), ec);
2784            }
2785            return super.addEdit(anEdit);
2786        }
2787
2788        /**
2789         * Redoes a change.
2790         *
2791         * @exception CannotRedoException if the change cannot be redone
2792         */
2793        public void redo() throws CannotRedoException {
2794            writeLock();
2795            try {
2796                // change the state
2797                super.redo();
2798                // fire a DocumentEvent to notify the view(s)
2799                UndoRedoDocumentEvent ev = new UndoRedoDocumentEvent(this, false);
2800                if (type == DocumentEvent.EventType.INSERT) {
2801                    fireInsertUpdate(ev);
2802                } else if (type == DocumentEvent.EventType.REMOVE) {
2803                    fireRemoveUpdate(ev);
2804                } else {
2805                    fireChangedUpdate(ev);
2806                }
2807            } finally {
2808                writeUnlock();
2809            }
2810        }
2811
2812        /**
2813         * Undoes a change.
2814         *
2815         * @exception CannotUndoException if the change cannot be undone
2816         */
2817        public void undo() throws CannotUndoException {
2818            writeLock();
2819            try {
2820                // change the state
2821                super.undo();
2822                // fire a DocumentEvent to notify the view(s)
2823                UndoRedoDocumentEvent ev = new UndoRedoDocumentEvent(this, true);
2824                if (type == DocumentEvent.EventType.REMOVE) {
2825                    fireInsertUpdate(ev);
2826                } else if (type == DocumentEvent.EventType.INSERT) {
2827                    fireRemoveUpdate(ev);
2828                } else {
2829                    fireChangedUpdate(ev);
2830                }
2831            } finally {
2832                writeUnlock();
2833            }
2834        }
2835
2836        /**
2837         * DefaultDocument events are significant.  If you wish to aggregate
2838         * DefaultDocumentEvents to present them as a single edit to the user
2839         * place them into a CompoundEdit.
2840         *
2841         * @return whether the event is significant for edit undo purposes
2842         */
2843        public boolean isSignificant() {
2844            return true;
2845        }
2846
2847
2848        /**
2849         * Provides a localized, human readable description of this edit
2850         * suitable for use in, say, a change log.
2851         *
2852         * @return the description
2853         */
2854        public String getPresentationName() {
2855            DocumentEvent.EventType type = getType();
2856            if(type == DocumentEvent.EventType.INSERT)
2857                return UIManager.getString("AbstractDocument.additionText");
2858            if(type == DocumentEvent.EventType.REMOVE)
2859                return UIManager.getString("AbstractDocument.deletionText");
2860            return UIManager.getString("AbstractDocument.styleChangeText");
2861        }
2862
2863        /**
2864         * Provides a localized, human readable description of the undoable
2865         * form of this edit, e.g. for use as an Undo menu item. Typically
2866         * derived from getDescription();
2867         *
2868         * @return the description
2869         */
2870        public String getUndoPresentationName() {
2871            return UIManager.getString("AbstractDocument.undoText") + " " +
2872                getPresentationName();
2873        }
2874
2875        /**
2876         * Provides a localized, human readable description of the redoable
2877         * form of this edit, e.g. for use as a Redo menu item. Typically
2878         * derived from getPresentationName();
2879         *
2880         * @return the description
2881         */
2882        public String getRedoPresentationName() {
2883            return UIManager.getString("AbstractDocument.redoText") + " " +
2884                getPresentationName();
2885        }
2886
2887        // --- DocumentEvent methods --------------------------
2888
2889        /**
2890         * Returns the type of event.
2891         *
2892         * @return the event type as a DocumentEvent.EventType
2893         * @see DocumentEvent#getType
2894         */
2895        public DocumentEvent.EventType getType() {
2896            return type;
2897        }
2898
2899        /**
2900         * Returns the offset within the document of the start of the change.
2901         *
2902         * @return the offset &gt;= 0
2903         * @see DocumentEvent#getOffset
2904         */
2905        public int getOffset() {
2906            return offset;
2907        }
2908
2909        /**
2910         * Returns the length of the change.
2911         *
2912         * @return the length &gt;= 0
2913         * @see DocumentEvent#getLength
2914         */
2915        public int getLength() {
2916            return length;
2917        }
2918
2919        /**
2920         * Gets the document that sourced the change event.
2921         *
2922         * @return the document
2923         * @see DocumentEvent#getDocument
2924         */
2925        public Document getDocument() {
2926            return AbstractDocument.this;
2927        }
2928
2929        /**
2930         * Gets the changes for an element.
2931         *
2932         * @param elem the element
2933         * @return the changes
2934         */
2935        public DocumentEvent.ElementChange getChange(Element elem) {
2936            if (changeLookup != null) {
2937                return changeLookup.get(elem);
2938            }
2939            int n = edits.size();
2940            for (int i = 0; i < n; i++) {
2941                Object o = edits.elementAt(i);
2942                if (o instanceof DocumentEvent.ElementChange) {
2943                    DocumentEvent.ElementChange c = (DocumentEvent.ElementChange) o;
2944                    if (elem.equals(c.getElement())) {
2945                        return c;
2946                    }
2947                }
2948            }
2949            return null;
2950        }
2951
2952        // --- member variables ------------------------------------
2953
2954        private int offset;
2955        private int length;
2956        private Hashtable<Element, ElementChange> changeLookup;
2957        private DocumentEvent.EventType type;
2958
2959    }
2960
2961    static class DefaultDocumentEventUndoableWrapper implements
2962            UndoableEdit, UndoableEditLockSupport
2963    {
2964        final DefaultDocumentEvent dde;
2965        public DefaultDocumentEventUndoableWrapper(DefaultDocumentEvent dde) {
2966            this.dde = dde;
2967        }
2968
2969        @Override
2970        public void undo() throws CannotUndoException {
2971            dde.undo();
2972        }
2973
2974        @Override
2975        public boolean canUndo() {
2976            return dde.canUndo();
2977        }
2978
2979        @Override
2980        public void redo() throws CannotRedoException {
2981            dde.redo();
2982        }
2983
2984        @Override
2985        public boolean canRedo() {
2986            return dde.canRedo();
2987        }
2988
2989        @Override
2990        public void die() {
2991            dde.die();
2992        }
2993
2994        @Override
2995        public boolean addEdit(UndoableEdit anEdit) {
2996            return dde.addEdit(anEdit);
2997        }
2998
2999        @Override
3000        public boolean replaceEdit(UndoableEdit anEdit) {
3001            return dde.replaceEdit(anEdit);
3002        }
3003
3004        @Override
3005        public boolean isSignificant() {
3006            return dde.isSignificant();
3007        }
3008
3009        @Override
3010        public String getPresentationName() {
3011            return dde.getPresentationName();
3012        }
3013
3014        @Override
3015        public String getUndoPresentationName() {
3016            return dde.getUndoPresentationName();
3017        }
3018
3019        @Override
3020        public String getRedoPresentationName() {
3021            return dde.getRedoPresentationName();
3022        }
3023
3024        /**
3025         * {@inheritDoc}
3026         * @since 1.9
3027         */
3028        @Override
3029        public void lockEdit() {
3030            ((AbstractDocument)dde.getDocument()).writeLock();
3031        }
3032
3033        /**
3034         * {@inheritDoc}
3035         * @since 1.9
3036         */
3037        @Override
3038        public void unlockEdit() {
3039            ((AbstractDocument)dde.getDocument()).writeUnlock();
3040        }
3041    }
3042
3043    /**
3044     * This event used when firing document changes while Undo/Redo
3045     * operations. It just wraps DefaultDocumentEvent and delegates
3046     * all calls to it except getType() which depends on operation
3047     * (Undo or Redo).
3048     */
3049    class UndoRedoDocumentEvent implements DocumentEvent {
3050        private DefaultDocumentEvent src = null;
3051        private EventType type = null;
3052
3053        public UndoRedoDocumentEvent(DefaultDocumentEvent src, boolean isUndo) {
3054            this.src = src;
3055            if(isUndo) {
3056                if(src.getType().equals(EventType.INSERT)) {
3057                    type = EventType.REMOVE;
3058                } else if(src.getType().equals(EventType.REMOVE)) {
3059                    type = EventType.INSERT;
3060                } else {
3061                    type = src.getType();
3062                }
3063            } else {
3064                type = src.getType();
3065            }
3066        }
3067
3068        public DefaultDocumentEvent getSource() {
3069            return src;
3070        }
3071
3072        // DocumentEvent methods delegated to DefaultDocumentEvent source
3073        // except getType() which depends on operation (Undo or Redo).
3074        public int getOffset() {
3075            return src.getOffset();
3076        }
3077
3078        public int getLength() {
3079            return src.getLength();
3080        }
3081
3082        public Document getDocument() {
3083            return src.getDocument();
3084        }
3085
3086        public DocumentEvent.EventType getType() {
3087            return type;
3088        }
3089
3090        public DocumentEvent.ElementChange getChange(Element elem) {
3091            return src.getChange(elem);
3092        }
3093    }
3094
3095    /**
3096     * An implementation of ElementChange that can be added to the document
3097     * event.
3098     */
3099    public static class ElementEdit extends AbstractUndoableEdit implements DocumentEvent.ElementChange {
3100
3101        /**
3102         * Constructs an edit record.  This does not modify the element
3103         * so it can safely be used to <em>catch up</em> a view to the
3104         * current model state for views that just attached to a model.
3105         *
3106         * @param e the element
3107         * @param index the index into the model &gt;= 0
3108         * @param removed a set of elements that were removed
3109         * @param added a set of elements that were added
3110         */
3111        public ElementEdit(Element e, int index, Element[] removed, Element[] added) {
3112            super();
3113            this.e = e;
3114            this.index = index;
3115            this.removed = removed;
3116            this.added = added;
3117        }
3118
3119        /**
3120         * Returns the underlying element.
3121         *
3122         * @return the element
3123         */
3124        public Element getElement() {
3125            return e;
3126        }
3127
3128        /**
3129         * Returns the index into the list of elements.
3130         *
3131         * @return the index &gt;= 0
3132         */
3133        public int getIndex() {
3134            return index;
3135        }
3136
3137        /**
3138         * Gets a list of children that were removed.
3139         *
3140         * @return the list
3141         */
3142        public Element[] getChildrenRemoved() {
3143            return removed;
3144        }
3145
3146        /**
3147         * Gets a list of children that were added.
3148         *
3149         * @return the list
3150         */
3151        public Element[] getChildrenAdded() {
3152            return added;
3153        }
3154
3155        /**
3156         * Redoes a change.
3157         *
3158         * @exception CannotRedoException if the change cannot be redone
3159         */
3160        public void redo() throws CannotRedoException {
3161            super.redo();
3162
3163            // Since this event will be reused, switch around added/removed.
3164            Element[] tmp = removed;
3165            removed = added;
3166            added = tmp;
3167
3168            // PENDING(prinz) need MutableElement interface, canRedo() should check
3169            ((AbstractDocument.BranchElement)e).replace(index, removed.length, added);
3170        }
3171
3172        /**
3173         * Undoes a change.
3174         *
3175         * @exception CannotUndoException if the change cannot be undone
3176         */
3177        public void undo() throws CannotUndoException {
3178            super.undo();
3179            // PENDING(prinz) need MutableElement interface, canUndo() should check
3180            ((AbstractDocument.BranchElement)e).replace(index, added.length, removed);
3181
3182            // Since this event will be reused, switch around added/removed.
3183            Element[] tmp = removed;
3184            removed = added;
3185            added = tmp;
3186        }
3187
3188        private Element e;
3189        private int index;
3190        private Element[] removed;
3191        private Element[] added;
3192    }
3193
3194
3195    private class DefaultFilterBypass extends DocumentFilter.FilterBypass {
3196        public Document getDocument() {
3197            return AbstractDocument.this;
3198        }
3199
3200        public void remove(int offset, int length) throws
3201            BadLocationException {
3202            handleRemove(offset, length);
3203        }
3204
3205        public void insertString(int offset, String string,
3206                                 AttributeSet attr) throws
3207                                        BadLocationException {
3208            handleInsertString(offset, string, attr);
3209        }
3210
3211        public void replace(int offset, int length, String text,
3212                            AttributeSet attrs) throws BadLocationException {
3213            handleRemove(offset, length);
3214            handleInsertString(offset, text, attrs);
3215        }
3216    }
3217}
3218