1/*
2 * Copyright (c) 1997, 2016, 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        if (offs > getLength()) {
706            throw new BadLocationException("Invalid insert", getLength());
707        }
708        DocumentFilter filter = getDocumentFilter();
709
710        writeLock();
711
712        try {
713            if (filter != null) {
714                filter.insertString(getFilterBypass(), offs, str, a);
715            } else {
716                handleInsertString(offs, str, a);
717            }
718        } finally {
719            writeUnlock();
720        }
721    }
722
723    /**
724     * Performs the actual work of inserting the text; it is assumed the
725     * caller has obtained a write lock before invoking this.
726     */
727    private void handleInsertString(int offs, String str, AttributeSet a)
728            throws BadLocationException {
729        if ((str == null) || (str.length() == 0)) {
730            return;
731        }
732        UndoableEdit u = data.insertString(offs, str);
733        DefaultDocumentEvent e =
734            new DefaultDocumentEvent(offs, str.length(), DocumentEvent.EventType.INSERT);
735        if (u != null) {
736            e.addEdit(u);
737        }
738
739        // see if complex glyph layout support is needed
740        if( getProperty(I18NProperty).equals( Boolean.FALSE ) ) {
741            // if a default direction of right-to-left has been specified,
742            // we want complex layout even if the text is all left to right.
743            Object d = getProperty(TextAttribute.RUN_DIRECTION);
744            if ((d != null) && (d.equals(TextAttribute.RUN_DIRECTION_RTL))) {
745                putProperty( I18NProperty, Boolean.TRUE);
746            } else {
747                char[] chars = str.toCharArray();
748                if (SwingUtilities2.isComplexLayout(chars, 0, chars.length)) {
749                    putProperty( I18NProperty, Boolean.TRUE);
750                }
751            }
752        }
753
754        insertUpdate(e, a);
755        // Mark the edit as done.
756        e.end();
757        fireInsertUpdate(e);
758        // only fire undo if Content implementation supports it
759        // undo for the composed text is not supported for now
760        if (u != null && (a == null || !a.isDefined(StyleConstants.ComposedTextAttribute))) {
761            fireUndoableEditUpdate(new UndoableEditEvent(this, e));
762        }
763    }
764
765    /**
766     * Gets a sequence of text from the document.
767     *
768     * @param offset the starting offset &gt;= 0
769     * @param length the number of characters to retrieve &gt;= 0
770     * @return the text
771     * @exception BadLocationException  the range given includes a position
772     *   that is not a valid position within the document
773     * @see Document#getText
774     */
775    public String getText(int offset, int length) throws BadLocationException {
776        if (length < 0) {
777            throw new BadLocationException("Length must be positive", length);
778        }
779        String str = data.getString(offset, length);
780        return str;
781    }
782
783    /**
784     * Fetches the text contained within the given portion
785     * of the document.
786     * <p>
787     * If the partialReturn property on the txt parameter is false, the
788     * data returned in the Segment will be the entire length requested and
789     * may or may not be a copy depending upon how the data was stored.
790     * If the partialReturn property is true, only the amount of text that
791     * can be returned without creating a copy is returned.  Using partial
792     * returns will give better performance for situations where large
793     * parts of the document are being scanned.  The following is an example
794     * of using the partial return to access the entire document:
795     *
796     * <pre>
797     * &nbsp; int nleft = doc.getDocumentLength();
798     * &nbsp; Segment text = new Segment();
799     * &nbsp; int offs = 0;
800     * &nbsp; text.setPartialReturn(true);
801     * &nbsp; while (nleft &gt; 0) {
802     * &nbsp;     doc.getText(offs, nleft, text);
803     * &nbsp;     // do something with text
804     * &nbsp;     nleft -= text.count;
805     * &nbsp;     offs += text.count;
806     * &nbsp; }
807     * </pre>
808     *
809     * @param offset the starting offset &gt;= 0
810     * @param length the number of characters to retrieve &gt;= 0
811     * @param txt the Segment object to retrieve the text into
812     * @exception BadLocationException  the range given includes a position
813     *   that is not a valid position within the document
814     */
815    public void getText(int offset, int length, Segment txt) throws BadLocationException {
816        if (length < 0) {
817            throw new BadLocationException("Length must be positive", length);
818        }
819        data.getChars(offset, length, txt);
820    }
821
822    /**
823     * Returns a position that will track change as the document
824     * is altered.
825     * <p>
826     * This method is thread safe, although most Swing methods
827     * are not. Please see
828     * <A HREF="http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency
829     * in Swing</A> for more information.
830     *
831     * @param offs the position in the model &gt;= 0
832     * @return the position
833     * @exception BadLocationException  if the given position does not
834     *   represent a valid location in the associated document
835     * @see Document#createPosition
836     */
837    public synchronized Position createPosition(int offs) throws BadLocationException {
838        return data.createPosition(offs);
839    }
840
841    /**
842     * Returns a position that represents the start of the document.  The
843     * position returned can be counted on to track change and stay
844     * located at the beginning of the document.
845     *
846     * @return the position
847     */
848    public final Position getStartPosition() {
849        Position p;
850        try {
851            p = createPosition(0);
852        } catch (BadLocationException bl) {
853            p = null;
854        }
855        return p;
856    }
857
858    /**
859     * Returns a position that represents the end of the document.  The
860     * position returned can be counted on to track change and stay
861     * located at the end of the document.
862     *
863     * @return the position
864     */
865    public final Position getEndPosition() {
866        Position p;
867        try {
868            p = createPosition(data.length());
869        } catch (BadLocationException bl) {
870            p = null;
871        }
872        return p;
873    }
874
875    /**
876     * Gets all root elements defined.  Typically, there
877     * will only be one so the default implementation
878     * is to return the default root element.
879     *
880     * @return the root element
881     */
882    public Element[] getRootElements() {
883        Element[] elems = new Element[2];
884        elems[0] = getDefaultRootElement();
885        elems[1] = getBidiRootElement();
886        return elems;
887    }
888
889    /**
890     * Returns the root element that views should be based upon
891     * unless some other mechanism for assigning views to element
892     * structures is provided.
893     *
894     * @return the root element
895     * @see Document#getDefaultRootElement
896     */
897    public abstract Element getDefaultRootElement();
898
899    // ---- local methods -----------------------------------------
900
901    /**
902     * Returns the <code>FilterBypass</code>. This will create one if one
903     * does not yet exist.
904     */
905    private DocumentFilter.FilterBypass getFilterBypass() {
906        if (filterBypass == null) {
907            filterBypass = new DefaultFilterBypass();
908        }
909        return filterBypass;
910    }
911
912    /**
913     * Returns the root element of the bidirectional structure for this
914     * document.  Its children represent character runs with a given
915     * Unicode bidi level.
916     * @return the root element of the bidirectional structure for this
917     * document
918     */
919    public Element getBidiRootElement() {
920        return bidiRoot;
921    }
922
923    /**
924     * Returns true if the text in the range <code>p0</code> to
925     * <code>p1</code> is left to right.
926     */
927    static boolean isLeftToRight(Document doc, int p0, int p1) {
928        if (Boolean.TRUE.equals(doc.getProperty(I18NProperty))) {
929            if (doc instanceof AbstractDocument) {
930                AbstractDocument adoc = (AbstractDocument) doc;
931                Element bidiRoot = adoc.getBidiRootElement();
932                int index = bidiRoot.getElementIndex(p0);
933                Element bidiElem = bidiRoot.getElement(index);
934                if (bidiElem.getEndOffset() >= p1) {
935                    AttributeSet bidiAttrs = bidiElem.getAttributes();
936                    return ((StyleConstants.getBidiLevel(bidiAttrs) % 2) == 0);
937                }
938            }
939        }
940        return true;
941    }
942
943    /**
944     * Get the paragraph element containing the given position.  Sub-classes
945     * must define for themselves what exactly constitutes a paragraph.  They
946     * should keep in mind however that a paragraph should at least be the
947     * unit of text over which to run the Unicode bidirectional algorithm.
948     *
949     * @param pos the starting offset &gt;= 0
950     * @return the element */
951    public abstract Element getParagraphElement(int pos);
952
953
954    /**
955     * Fetches the context for managing attributes.  This
956     * method effectively establishes the strategy used
957     * for compressing AttributeSet information.
958     *
959     * @return the context
960     */
961    protected final AttributeContext getAttributeContext() {
962        return context;
963    }
964
965    /**
966     * Updates document structure as a result of text insertion.  This
967     * will happen within a write lock.  If a subclass of
968     * this class reimplements this method, it should delegate to the
969     * superclass as well.
970     *
971     * @param chng a description of the change
972     * @param attr the attributes for the change
973     */
974    protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) {
975        if( getProperty(I18NProperty).equals( Boolean.TRUE ) )
976            updateBidi( chng );
977
978        // Check if a multi byte is encountered in the inserted text.
979        if (chng.type == DocumentEvent.EventType.INSERT &&
980                        chng.getLength() > 0 &&
981                        !Boolean.TRUE.equals(getProperty(MultiByteProperty))) {
982            Segment segment = SegmentCache.getSharedSegment();
983            try {
984                getText(chng.getOffset(), chng.getLength(), segment);
985                segment.first();
986                do {
987                    if ((int)segment.current() > 255) {
988                        putProperty(MultiByteProperty, Boolean.TRUE);
989                        break;
990                    }
991                } while (segment.next() != Segment.DONE);
992            } catch (BadLocationException ble) {
993                // Should never happen
994            }
995            SegmentCache.releaseSharedSegment(segment);
996        }
997    }
998
999    /**
1000     * Updates any document structure as a result of text removal.  This
1001     * method is called before the text is actually removed from the Content.
1002     * This will happen within a write lock. If a subclass
1003     * of this class reimplements this method, it should delegate to the
1004     * superclass as well.
1005     *
1006     * @param chng a description of the change
1007     */
1008    protected void removeUpdate(DefaultDocumentEvent chng) {
1009    }
1010
1011    /**
1012     * Updates any document structure as a result of text removal.  This
1013     * method is called after the text has been removed from the Content.
1014     * This will happen within a write lock. If a subclass
1015     * of this class reimplements this method, it should delegate to the
1016     * superclass as well.
1017     *
1018     * @param chng a description of the change
1019     */
1020    protected void postRemoveUpdate(DefaultDocumentEvent chng) {
1021        if( getProperty(I18NProperty).equals( Boolean.TRUE ) )
1022            updateBidi( chng );
1023    }
1024
1025
1026    /**
1027     * Update the bidi element structure as a result of the given change
1028     * to the document.  The given change will be updated to reflect the
1029     * changes made to the bidi structure.
1030     *
1031     * This method assumes that every offset in the model is contained in
1032     * exactly one paragraph.  This method also assumes that it is called
1033     * after the change is made to the default element structure.
1034     */
1035    void updateBidi( DefaultDocumentEvent chng ) {
1036
1037        // Calculate the range of paragraphs affected by the change.
1038        int firstPStart;
1039        int lastPEnd;
1040        if( chng.type == DocumentEvent.EventType.INSERT
1041            || chng.type == DocumentEvent.EventType.CHANGE )
1042        {
1043            int chngStart = chng.getOffset();
1044            int chngEnd =  chngStart + chng.getLength();
1045            firstPStart = getParagraphElement(chngStart).getStartOffset();
1046            lastPEnd = getParagraphElement(chngEnd).getEndOffset();
1047        } else if( chng.type == DocumentEvent.EventType.REMOVE ) {
1048            Element paragraph = getParagraphElement( chng.getOffset() );
1049            firstPStart = paragraph.getStartOffset();
1050            lastPEnd = paragraph.getEndOffset();
1051        } else {
1052            throw new Error("Internal error: unknown event type.");
1053        }
1054        //System.out.println("updateBidi: firstPStart = " + firstPStart + " lastPEnd = " + lastPEnd );
1055
1056
1057        // Calculate the bidi levels for the affected range of paragraphs.  The
1058        // levels array will contain a bidi level for each character in the
1059        // affected text.
1060        byte levels[] = calculateBidiLevels( firstPStart, lastPEnd );
1061
1062
1063        Vector<Element> newElements = new Vector<Element>();
1064
1065        // Calculate the first span of characters in the affected range with
1066        // the same bidi level.  If this level is the same as the level of the
1067        // previous bidi element (the existing bidi element containing
1068        // firstPStart-1), then merge in the previous element.  If not, but
1069        // the previous element overlaps the affected range, truncate the
1070        // previous element at firstPStart.
1071        int firstSpanStart = firstPStart;
1072        int removeFromIndex = 0;
1073        if( firstSpanStart > 0 ) {
1074            int prevElemIndex = bidiRoot.getElementIndex(firstPStart-1);
1075            removeFromIndex = prevElemIndex;
1076            Element prevElem = bidiRoot.getElement(prevElemIndex);
1077            int prevLevel=StyleConstants.getBidiLevel(prevElem.getAttributes());
1078            //System.out.println("createbidiElements: prevElem= " + prevElem  + " prevLevel= " + prevLevel + "level[0] = " + levels[0]);
1079            if( prevLevel==levels[0] ) {
1080                firstSpanStart = prevElem.getStartOffset();
1081            } else if( prevElem.getEndOffset() > firstPStart ) {
1082                newElements.addElement(new BidiElement(bidiRoot,
1083                                                       prevElem.getStartOffset(),
1084                                                       firstPStart, prevLevel));
1085            } else {
1086                removeFromIndex++;
1087            }
1088        }
1089
1090        int firstSpanEnd = 0;
1091        while((firstSpanEnd<levels.length) && (levels[firstSpanEnd]==levels[0]))
1092            firstSpanEnd++;
1093
1094
1095        // Calculate the last span of characters in the affected range with
1096        // the same bidi level.  If this level is the same as the level of the
1097        // next bidi element (the existing bidi element containing lastPEnd),
1098        // then merge in the next element.  If not, but the next element
1099        // overlaps the affected range, adjust the next element to start at
1100        // lastPEnd.
1101        int lastSpanEnd = lastPEnd;
1102        Element newNextElem = null;
1103        int removeToIndex = bidiRoot.getElementCount() - 1;
1104        if( lastSpanEnd <= getLength() ) {
1105            int nextElemIndex = bidiRoot.getElementIndex( lastPEnd );
1106            removeToIndex = nextElemIndex;
1107            Element nextElem = bidiRoot.getElement( nextElemIndex );
1108            int nextLevel = StyleConstants.getBidiLevel(nextElem.getAttributes());
1109            if( nextLevel == levels[levels.length-1] ) {
1110                lastSpanEnd = nextElem.getEndOffset();
1111            } else if( nextElem.getStartOffset() < lastPEnd ) {
1112                newNextElem = new BidiElement(bidiRoot, lastPEnd,
1113                                              nextElem.getEndOffset(),
1114                                              nextLevel);
1115            } else {
1116                removeToIndex--;
1117            }
1118        }
1119
1120        int lastSpanStart = levels.length;
1121        while( (lastSpanStart>firstSpanEnd)
1122               && (levels[lastSpanStart-1]==levels[levels.length-1]) )
1123            lastSpanStart--;
1124
1125
1126        // If the first and last spans are contiguous and have the same level,
1127        // merge them and create a single new element for the entire span.
1128        // Otherwise, create elements for the first and last spans as well as
1129        // any spans in between.
1130        if((firstSpanEnd==lastSpanStart)&&(levels[0]==levels[levels.length-1])){
1131            newElements.addElement(new BidiElement(bidiRoot, firstSpanStart,
1132                                                   lastSpanEnd, levels[0]));
1133        } else {
1134            // Create an element for the first span.
1135            newElements.addElement(new BidiElement(bidiRoot, firstSpanStart,
1136                                                   firstSpanEnd+firstPStart,
1137                                                   levels[0]));
1138            // Create elements for the spans in between the first and last
1139            for( int i=firstSpanEnd; i<lastSpanStart; ) {
1140                //System.out.println("executed line 872");
1141                int j;
1142                for( j=i;  (j<levels.length) && (levels[j] == levels[i]); j++ );
1143                newElements.addElement(new BidiElement(bidiRoot, firstPStart+i,
1144                                                       firstPStart+j,
1145                                                       (int)levels[i]));
1146                i=j;
1147            }
1148            // Create an element for the last span.
1149            newElements.addElement(new BidiElement(bidiRoot,
1150                                                   lastSpanStart+firstPStart,
1151                                                   lastSpanEnd,
1152                                                   levels[levels.length-1]));
1153        }
1154
1155        if( newNextElem != null )
1156            newElements.addElement( newNextElem );
1157
1158
1159        // Calculate the set of existing bidi elements which must be
1160        // removed.
1161        int removedElemCount = 0;
1162        if( bidiRoot.getElementCount() > 0 ) {
1163            removedElemCount = removeToIndex - removeFromIndex + 1;
1164        }
1165        Element[] removedElems = new Element[removedElemCount];
1166        for( int i=0; i<removedElemCount; i++ ) {
1167            removedElems[i] = bidiRoot.getElement(removeFromIndex+i);
1168        }
1169
1170        Element[] addedElems = new Element[ newElements.size() ];
1171        newElements.copyInto( addedElems );
1172
1173        // Update the change record.
1174        ElementEdit ee = new ElementEdit( bidiRoot, removeFromIndex,
1175                                          removedElems, addedElems );
1176        chng.addEdit( ee );
1177
1178        // Update the bidi element structure.
1179        bidiRoot.replace( removeFromIndex, removedElems.length, addedElems );
1180    }
1181
1182
1183    /**
1184     * Calculate the levels array for a range of paragraphs.
1185     */
1186    private byte[] calculateBidiLevels( int firstPStart, int lastPEnd ) {
1187
1188        byte levels[] = new byte[ lastPEnd - firstPStart ];
1189        int  levelsEnd = 0;
1190        Boolean defaultDirection = null;
1191        Object d = getProperty(TextAttribute.RUN_DIRECTION);
1192        if (d instanceof Boolean) {
1193            defaultDirection = (Boolean) d;
1194        }
1195
1196        // For each paragraph in the given range of paragraphs, get its
1197        // levels array and add it to the levels array for the entire span.
1198        for(int o=firstPStart; o<lastPEnd; ) {
1199            Element p = getParagraphElement( o );
1200            int pStart = p.getStartOffset();
1201            int pEnd = p.getEndOffset();
1202
1203            // default run direction for the paragraph.  This will be
1204            // null if there is no direction override specified (i.e.
1205            // the direction will be determined from the content).
1206            Boolean direction = defaultDirection;
1207            d = p.getAttributes().getAttribute(TextAttribute.RUN_DIRECTION);
1208            if (d instanceof Boolean) {
1209                direction = (Boolean) d;
1210            }
1211
1212            //System.out.println("updateBidi: paragraph start = " + pStart + " paragraph end = " + pEnd);
1213
1214            // Create a Bidi over this paragraph then get the level
1215            // array.
1216            Segment seg = SegmentCache.getSharedSegment();
1217            try {
1218                getText(pStart, pEnd-pStart, seg);
1219            } catch (BadLocationException e ) {
1220                throw new Error("Internal error: " + e.toString());
1221            }
1222            // REMIND(bcb) we should really be using a Segment here.
1223            Bidi bidiAnalyzer;
1224            int bidiflag = Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT;
1225            if (direction != null) {
1226                if (TextAttribute.RUN_DIRECTION_LTR.equals(direction)) {
1227                    bidiflag = Bidi.DIRECTION_LEFT_TO_RIGHT;
1228                } else {
1229                    bidiflag = Bidi.DIRECTION_RIGHT_TO_LEFT;
1230                }
1231            }
1232            bidiAnalyzer = new Bidi(seg.array, seg.offset, null, 0, seg.count,
1233                    bidiflag);
1234            BidiUtils.getLevels(bidiAnalyzer, levels, levelsEnd);
1235            levelsEnd += bidiAnalyzer.getLength();
1236
1237            o =  p.getEndOffset();
1238            SegmentCache.releaseSharedSegment(seg);
1239        }
1240
1241        // REMIND(bcb) remove this code when debugging is done.
1242        if( levelsEnd != levels.length )
1243            throw new Error("levelsEnd assertion failed.");
1244
1245        return levels;
1246    }
1247
1248    /**
1249     * Gives a diagnostic dump.
1250     *
1251     * @param out the output stream
1252     */
1253    public void dump(PrintStream out) {
1254        Element root = getDefaultRootElement();
1255        if (root instanceof AbstractElement) {
1256            ((AbstractElement)root).dump(out, 0);
1257        }
1258        bidiRoot.dump(out,0);
1259    }
1260
1261    /**
1262     * Gets the content for the document.
1263     *
1264     * @return the content
1265     */
1266    protected final Content getContent() {
1267        return data;
1268    }
1269
1270    /**
1271     * Creates a document leaf element.
1272     * Hook through which elements are created to represent the
1273     * document structure.  Because this implementation keeps
1274     * structure and content separate, elements grow automatically
1275     * when content is extended so splits of existing elements
1276     * follow.  The document itself gets to decide how to generate
1277     * elements to give flexibility in the type of elements used.
1278     *
1279     * @param parent the parent element
1280     * @param a the attributes for the element
1281     * @param p0 the beginning of the range &gt;= 0
1282     * @param p1 the end of the range &gt;= p0
1283     * @return the new element
1284     */
1285    protected Element createLeafElement(Element parent, AttributeSet a, int p0, int p1) {
1286        return new LeafElement(parent, a, p0, p1);
1287    }
1288
1289    /**
1290     * Creates a document branch element, that can contain other elements.
1291     *
1292     * @param parent the parent element
1293     * @param a the attributes
1294     * @return the element
1295     */
1296    protected Element createBranchElement(Element parent, AttributeSet a) {
1297        return new BranchElement(parent, a);
1298    }
1299
1300    // --- Document locking ----------------------------------
1301
1302    /**
1303     * Fetches the current writing thread if there is one.
1304     * This can be used to distinguish whether a method is
1305     * being called as part of an existing modification or
1306     * if a lock needs to be acquired and a new transaction
1307     * started.
1308     *
1309     * @return the thread actively modifying the document
1310     *  or <code>null</code> if there are no modifications in progress
1311     */
1312    protected final synchronized Thread getCurrentWriter() {
1313        return currWriter;
1314    }
1315
1316    /**
1317     * Acquires a lock to begin mutating the document this lock
1318     * protects.  There can be no writing, notification of changes, or
1319     * reading going on in order to gain the lock.  Additionally a thread is
1320     * allowed to gain more than one <code>writeLock</code>,
1321     * as long as it doesn't attempt to gain additional <code>writeLock</code>s
1322     * from within document notification.  Attempting to gain a
1323     * <code>writeLock</code> from within a DocumentListener notification will
1324     * result in an <code>IllegalStateException</code>.  The ability
1325     * to obtain more than one <code>writeLock</code> per thread allows
1326     * subclasses to gain a writeLock, perform a number of operations, then
1327     * release the lock.
1328     * <p>
1329     * Calls to <code>writeLock</code>
1330     * must be balanced with calls to <code>writeUnlock</code>, else the
1331     * <code>Document</code> will be left in a locked state so that no
1332     * reading or writing can be done.
1333     *
1334     * @exception IllegalStateException thrown on illegal lock
1335     *  attempt.  If the document is implemented properly, this can
1336     *  only happen if a document listener attempts to mutate the
1337     *  document.  This situation violates the bean event model
1338     *  where order of delivery is not guaranteed and all listeners
1339     *  should be notified before further mutations are allowed.
1340     */
1341    protected final synchronized void writeLock() {
1342        try {
1343            while ((numReaders > 0) || (currWriter != null)) {
1344                if (Thread.currentThread() == currWriter) {
1345                    if (notifyingListeners) {
1346                        // Assuming one doesn't do something wrong in a
1347                        // subclass this should only happen if a
1348                        // DocumentListener tries to mutate the document.
1349                        throw new IllegalStateException(
1350                                      "Attempt to mutate in notification");
1351                    }
1352                    numWriters++;
1353                    return;
1354                }
1355                wait();
1356            }
1357            currWriter = Thread.currentThread();
1358            numWriters = 1;
1359        } catch (InterruptedException e) {
1360            throw new Error("Interrupted attempt to acquire write lock");
1361        }
1362    }
1363
1364    /**
1365     * Releases a write lock previously obtained via <code>writeLock</code>.
1366     * After decrementing the lock count if there are no outstanding locks
1367     * this will allow a new writer, or readers.
1368     *
1369     * @see #writeLock
1370     */
1371    protected final synchronized void writeUnlock() {
1372        if (--numWriters <= 0) {
1373            numWriters = 0;
1374            currWriter = null;
1375            notifyAll();
1376        }
1377    }
1378
1379    /**
1380     * Acquires a lock to begin reading some state from the
1381     * document.  There can be multiple readers at the same time.
1382     * Writing blocks the readers until notification of the change
1383     * to the listeners has been completed.  This method should
1384     * be used very carefully to avoid unintended compromise
1385     * of the document.  It should always be balanced with a
1386     * <code>readUnlock</code>.
1387     *
1388     * @see #readUnlock
1389     */
1390    public final synchronized void readLock() {
1391        try {
1392            while (currWriter != null) {
1393                if (currWriter == Thread.currentThread()) {
1394                    // writer has full read access.... may try to acquire
1395                    // lock in notification
1396                    return;
1397                }
1398                wait();
1399            }
1400            numReaders += 1;
1401        } catch (InterruptedException e) {
1402            throw new Error("Interrupted attempt to acquire read lock");
1403        }
1404    }
1405
1406    /**
1407     * Does a read unlock.  This signals that one
1408     * of the readers is done.  If there are no more readers
1409     * then writing can begin again.  This should be balanced
1410     * with a readLock, and should occur in a finally statement
1411     * so that the balance is guaranteed.  The following is an
1412     * example.
1413     * <pre><code>
1414     * &nbsp;   readLock();
1415     * &nbsp;   try {
1416     * &nbsp;       // do something
1417     * &nbsp;   } finally {
1418     * &nbsp;       readUnlock();
1419     * &nbsp;   }
1420     * </code></pre>
1421     *
1422     * @see #readLock
1423     */
1424    public final synchronized void readUnlock() {
1425        if (currWriter == Thread.currentThread()) {
1426            // writer has full read access.... may try to acquire
1427            // lock in notification
1428            return;
1429        }
1430        if (numReaders <= 0) {
1431            throw new StateInvariantError(BAD_LOCK_STATE);
1432        }
1433        numReaders -= 1;
1434        notify();
1435    }
1436
1437    // --- serialization ---------------------------------------------
1438
1439    @SuppressWarnings("unchecked")
1440    private void readObject(ObjectInputStream s)
1441      throws ClassNotFoundException, IOException
1442    {
1443        ObjectInputStream.GetField f = s.readFields();
1444
1445        documentProperties =
1446            (Dictionary<Object, Object>) f.get("documentProperties", null);
1447        listenerList = new EventListenerList();
1448        data = (Content) f.get("data", null);
1449        context = (AttributeContext) f.get("context", null);
1450        documentFilter = (DocumentFilter) f.get("documentFilter", null);
1451
1452        // Restore bidi structure
1453        //REMIND(bcb) This creates an initial bidi element to account for
1454        //the \n that exists by default in the content.
1455        bidiRoot = new BidiRootElement();
1456        try {
1457            writeLock();
1458            Element[] p = new Element[1];
1459            p[0] = new BidiElement( bidiRoot, 0, 1, 0 );
1460            bidiRoot.replace(0,0,p);
1461        } finally {
1462            writeUnlock();
1463        }
1464        // At this point bidi root is only partially correct. To fully
1465        // restore it we need access to getDefaultRootElement. But, this
1466        // is created by the subclass and at this point will be null. We
1467        // thus use registerValidation.
1468        s.registerValidation(new ObjectInputValidation() {
1469            public void validateObject() {
1470                try {
1471                    writeLock();
1472                    DefaultDocumentEvent e = new DefaultDocumentEvent
1473                                   (0, getLength(),
1474                                    DocumentEvent.EventType.INSERT);
1475                    updateBidi( e );
1476                }
1477                finally {
1478                    writeUnlock();
1479                }
1480            }
1481        }, 0);
1482    }
1483
1484    // ----- member variables ------------------------------------------
1485
1486    private transient int numReaders;
1487    private transient Thread currWriter;
1488    /**
1489     * The number of writers, all obtained from <code>currWriter</code>.
1490     */
1491    private transient int numWriters;
1492    /**
1493     * True will notifying listeners.
1494     */
1495    private transient boolean notifyingListeners;
1496
1497    private static Boolean defaultI18NProperty;
1498
1499    /**
1500     * Storage for document-wide properties.
1501     */
1502    private Dictionary<Object,Object> documentProperties = null;
1503
1504    /**
1505     * The event listener list for the document.
1506     */
1507    protected EventListenerList listenerList = new EventListenerList();
1508
1509    /**
1510     * Where the text is actually stored, and a set of marks
1511     * that track change as the document is edited are managed.
1512     */
1513    private Content data;
1514
1515    /**
1516     * Factory for the attributes.  This is the strategy for
1517     * attribute compression and control of the lifetime of
1518     * a set of attributes as a collection.  This may be shared
1519     * with other documents.
1520     */
1521    private AttributeContext context;
1522
1523    /**
1524     * The root of the bidirectional structure for this document.  Its children
1525     * represent character runs with the same Unicode bidi level.
1526     */
1527    private transient BranchElement bidiRoot;
1528
1529    /**
1530     * Filter for inserting/removing of text.
1531     */
1532    private DocumentFilter documentFilter;
1533
1534    /**
1535     * Used by DocumentFilter to do actual insert/remove.
1536     */
1537    private transient DocumentFilter.FilterBypass filterBypass;
1538
1539    private static final String BAD_LOCK_STATE = "document lock failure";
1540
1541    /**
1542     * Error message to indicate a bad location.
1543     */
1544    protected static final String BAD_LOCATION = "document location failure";
1545
1546    /**
1547     * Name of elements used to represent paragraphs
1548     */
1549    public static final String ParagraphElementName = "paragraph";
1550
1551    /**
1552     * Name of elements used to represent content
1553     */
1554    public static final String ContentElementName = "content";
1555
1556    /**
1557     * Name of elements used to hold sections (lines/paragraphs).
1558     */
1559    public static final String SectionElementName = "section";
1560
1561    /**
1562     * Name of elements used to hold a unidirectional run
1563     */
1564    public static final String BidiElementName = "bidi level";
1565
1566    /**
1567     * Name of the attribute used to specify element
1568     * names.
1569     */
1570    public static final String ElementNameAttribute = "$ename";
1571
1572    /**
1573     * Document property that indicates whether internationalization
1574     * functions such as text reordering or reshaping should be
1575     * performed. This property should not be publicly exposed,
1576     * since it is used for implementation convenience only.  As a
1577     * side effect, copies of this property may be in its subclasses
1578     * that live in different packages (e.g. HTMLDocument as of now),
1579     * so those copies should also be taken care of when this property
1580     * needs to be modified.
1581     */
1582    static final String I18NProperty = "i18n";
1583
1584    /**
1585     * Document property that indicates if a character has been inserted
1586     * into the document that is more than one byte long.  GlyphView uses
1587     * this to determine if it should use BreakIterator.
1588     */
1589    static final Object MultiByteProperty = "multiByte";
1590
1591    /**
1592     * Document property that indicates asynchronous loading is
1593     * desired, with the thread priority given as the value.
1594     */
1595    static final String AsyncLoadPriority = "load priority";
1596
1597    /**
1598     * Interface to describe a sequence of character content that
1599     * can be edited.  Implementations may or may not support a
1600     * history mechanism which will be reflected by whether or not
1601     * mutations return an UndoableEdit implementation.
1602     * @see AbstractDocument
1603     */
1604    public interface Content {
1605
1606        /**
1607         * Creates a position within the content that will
1608         * track change as the content is mutated.
1609         *
1610         * @param offset the offset in the content &gt;= 0
1611         * @return a Position
1612         * @exception BadLocationException for an invalid offset
1613         */
1614        public Position createPosition(int offset) throws BadLocationException;
1615
1616        /**
1617         * Current length of the sequence of character content.
1618         *
1619         * @return the length &gt;= 0
1620         */
1621        public int length();
1622
1623        /**
1624         * Inserts a string of characters into the sequence.
1625         *
1626         * @param where   offset into the sequence to make the insertion &gt;= 0
1627         * @param str     string to insert
1628         * @return  if the implementation supports a history mechanism,
1629         *    a reference to an <code>Edit</code> implementation will be returned,
1630         *    otherwise returns <code>null</code>
1631         * @exception BadLocationException  thrown if the area covered by
1632         *   the arguments is not contained in the character sequence
1633         */
1634        public UndoableEdit insertString(int where, String str) throws BadLocationException;
1635
1636        /**
1637         * Removes some portion of the sequence.
1638         *
1639         * @param where   The offset into the sequence to make the
1640         *   insertion &gt;= 0.
1641         * @param nitems  The number of items in the sequence to remove &gt;= 0.
1642         * @return  If the implementation supports a history mechanism,
1643         *    a reference to an Edit implementation will be returned,
1644         *    otherwise null.
1645         * @exception BadLocationException  Thrown if the area covered by
1646         *   the arguments is not contained in the character sequence.
1647         */
1648        public UndoableEdit remove(int where, int nitems) throws BadLocationException;
1649
1650        /**
1651         * Fetches a string of characters contained in the sequence.
1652         *
1653         * @param where   Offset into the sequence to fetch &gt;= 0.
1654         * @param len     number of characters to copy &gt;= 0.
1655         * @return the string
1656         * @exception BadLocationException  Thrown if the area covered by
1657         *   the arguments is not contained in the character sequence.
1658         */
1659        public String getString(int where, int len) throws BadLocationException;
1660
1661        /**
1662         * Gets a sequence of characters and copies them into a Segment.
1663         *
1664         * @param where the starting offset &gt;= 0
1665         * @param len the number of characters &gt;= 0
1666         * @param txt the target location to copy into
1667         * @exception BadLocationException  Thrown if the area covered by
1668         *   the arguments is not contained in the character sequence.
1669         */
1670        public void getChars(int where, int len, Segment txt) throws BadLocationException;
1671    }
1672
1673    /**
1674     * An interface that can be used to allow MutableAttributeSet
1675     * implementations to use pluggable attribute compression
1676     * techniques.  Each mutation of the attribute set can be
1677     * used to exchange a previous AttributeSet instance with
1678     * another, preserving the possibility of the AttributeSet
1679     * remaining immutable.  An implementation is provided by
1680     * the StyleContext class.
1681     *
1682     * The Element implementations provided by this class use
1683     * this interface to provide their MutableAttributeSet
1684     * implementations, so that different AttributeSet compression
1685     * techniques can be employed.  The method
1686     * <code>getAttributeContext</code> should be implemented to
1687     * return the object responsible for implementing the desired
1688     * compression technique.
1689     *
1690     * @see StyleContext
1691     */
1692    public interface AttributeContext {
1693
1694        /**
1695         * Adds an attribute to the given set, and returns
1696         * the new representative set.
1697         *
1698         * @param old the old attribute set
1699         * @param name the non-null attribute name
1700         * @param value the attribute value
1701         * @return the updated attribute set
1702         * @see MutableAttributeSet#addAttribute
1703         */
1704        public AttributeSet addAttribute(AttributeSet old, Object name, Object value);
1705
1706        /**
1707         * Adds a set of attributes to the element.
1708         *
1709         * @param old the old attribute set
1710         * @param attr the attributes to add
1711         * @return the updated attribute set
1712         * @see MutableAttributeSet#addAttribute
1713         */
1714        public AttributeSet addAttributes(AttributeSet old, AttributeSet attr);
1715
1716        /**
1717         * Removes an attribute from the set.
1718         *
1719         * @param old the old attribute set
1720         * @param name the non-null attribute name
1721         * @return the updated attribute set
1722         * @see MutableAttributeSet#removeAttribute
1723         */
1724        public AttributeSet removeAttribute(AttributeSet old, Object name);
1725
1726        /**
1727         * Removes a set of attributes for the element.
1728         *
1729         * @param old the old attribute set
1730         * @param names the attribute names
1731         * @return the updated attribute set
1732         * @see MutableAttributeSet#removeAttributes
1733         */
1734        public AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names);
1735
1736        /**
1737         * Removes a set of attributes for the element.
1738         *
1739         * @param old the old attribute set
1740         * @param attrs the attributes
1741         * @return the updated attribute set
1742         * @see MutableAttributeSet#removeAttributes
1743         */
1744        public AttributeSet removeAttributes(AttributeSet old, AttributeSet attrs);
1745
1746        /**
1747         * Fetches an empty AttributeSet.
1748         *
1749         * @return the attribute set
1750         */
1751        public AttributeSet getEmptySet();
1752
1753        /**
1754         * Reclaims an attribute set.
1755         * This is a way for a MutableAttributeSet to mark that it no
1756         * longer need a particular immutable set.  This is only necessary
1757         * in 1.1 where there are no weak references.  A 1.1 implementation
1758         * would call this in its finalize method.
1759         *
1760         * @param a the attribute set to reclaim
1761         */
1762        public void reclaim(AttributeSet a);
1763    }
1764
1765    /**
1766     * Implements the abstract part of an element.  By default elements
1767     * support attributes by having a field that represents the immutable
1768     * part of the current attribute set for the element.  The element itself
1769     * implements MutableAttributeSet which can be used to modify the set
1770     * by fetching a new immutable set.  The immutable sets are provided
1771     * by the AttributeContext associated with the document.
1772     * <p>
1773     * <strong>Warning:</strong>
1774     * Serialized objects of this class will not be compatible with
1775     * future Swing releases. The current serialization support is
1776     * appropriate for short term storage or RMI between applications running
1777     * the same version of Swing.  As of 1.4, support for long term storage
1778     * of all JavaBeans&trade;
1779     * has been added to the <code>java.beans</code> package.
1780     * Please see {@link java.beans.XMLEncoder}.
1781     */
1782    @SuppressWarnings("serial") // Same-version serialization only
1783    public abstract class AbstractElement implements Element, MutableAttributeSet, Serializable, TreeNode {
1784
1785        /**
1786         * Creates a new AbstractElement.
1787         *
1788         * @param parent the parent element
1789         * @param a the attributes for the element
1790         * @since 1.4
1791         */
1792        public AbstractElement(Element parent, AttributeSet a) {
1793            this.parent = parent;
1794            attributes = getAttributeContext().getEmptySet();
1795            if (a != null) {
1796                addAttributes(a);
1797            }
1798        }
1799
1800        private final void indent(PrintWriter out, int n) {
1801            for (int i = 0; i < n; i++) {
1802                out.print("  ");
1803            }
1804        }
1805
1806        /**
1807         * Dumps a debugging representation of the element hierarchy.
1808         *
1809         * @param psOut the output stream
1810         * @param indentAmount the indentation level &gt;= 0
1811         */
1812        public void dump(PrintStream psOut, int indentAmount) {
1813            PrintWriter out;
1814            try {
1815                out = new PrintWriter(new OutputStreamWriter(psOut,"JavaEsc"),
1816                                      true);
1817            } catch (UnsupportedEncodingException e){
1818                out = new PrintWriter(psOut,true);
1819            }
1820            indent(out, indentAmount);
1821            if (getName() == null) {
1822                out.print("<??");
1823            } else {
1824                out.print("<" + getName());
1825            }
1826            if (getAttributeCount() > 0) {
1827                out.println("");
1828                // dump the attributes
1829                Enumeration<?> names = attributes.getAttributeNames();
1830                while (names.hasMoreElements()) {
1831                    Object name = names.nextElement();
1832                    indent(out, indentAmount + 1);
1833                    out.println(name + "=" + getAttribute(name));
1834                }
1835                indent(out, indentAmount);
1836            }
1837            out.println(">");
1838
1839            if (isLeaf()) {
1840                indent(out, indentAmount+1);
1841                out.print("[" + getStartOffset() + "," + getEndOffset() + "]");
1842                Content c = getContent();
1843                try {
1844                    String contentStr = c.getString(getStartOffset(),
1845                                                    getEndOffset() - getStartOffset())/*.trim()*/;
1846                    if (contentStr.length() > 40) {
1847                        contentStr = contentStr.substring(0, 40) + "...";
1848                    }
1849                    out.println("["+contentStr+"]");
1850                } catch (BadLocationException e) {
1851                }
1852
1853            } else {
1854                int n = getElementCount();
1855                for (int i = 0; i < n; i++) {
1856                    AbstractElement e = (AbstractElement) getElement(i);
1857                    e.dump(psOut, indentAmount+1);
1858                }
1859            }
1860        }
1861
1862        // --- AttributeSet ----------------------------
1863        // delegated to the immutable field "attributes"
1864
1865        /**
1866         * Gets the number of attributes that are defined.
1867         *
1868         * @return the number of attributes &gt;= 0
1869         * @see AttributeSet#getAttributeCount
1870         */
1871        public int getAttributeCount() {
1872            return attributes.getAttributeCount();
1873        }
1874
1875        /**
1876         * Checks whether a given attribute is defined.
1877         *
1878         * @param attrName the non-null attribute name
1879         * @return true if the attribute is defined
1880         * @see AttributeSet#isDefined
1881         */
1882        public boolean isDefined(Object attrName) {
1883            return attributes.isDefined(attrName);
1884        }
1885
1886        /**
1887         * Checks whether two attribute sets are equal.
1888         *
1889         * @param attr the attribute set to check against
1890         * @return true if the same
1891         * @see AttributeSet#isEqual
1892         */
1893        public boolean isEqual(AttributeSet attr) {
1894            return attributes.isEqual(attr);
1895        }
1896
1897        /**
1898         * Copies a set of attributes.
1899         *
1900         * @return the copy
1901         * @see AttributeSet#copyAttributes
1902         */
1903        public AttributeSet copyAttributes() {
1904            return attributes.copyAttributes();
1905        }
1906
1907        /**
1908         * Gets the value of an attribute.
1909         *
1910         * @param attrName the non-null attribute name
1911         * @return the attribute value
1912         * @see AttributeSet#getAttribute
1913         */
1914        public Object getAttribute(Object attrName) {
1915            Object value = attributes.getAttribute(attrName);
1916            if (value == null) {
1917                // The delegate nor it's resolvers had a match,
1918                // so we'll try to resolve through the parent
1919                // element.
1920                AttributeSet a = (parent != null) ? parent.getAttributes() : null;
1921                if (a != null) {
1922                    value = a.getAttribute(attrName);
1923                }
1924            }
1925            return value;
1926        }
1927
1928        /**
1929         * Gets the names of all attributes.
1930         *
1931         * @return the attribute names as an enumeration
1932         * @see AttributeSet#getAttributeNames
1933         */
1934        public Enumeration<?> getAttributeNames() {
1935            return attributes.getAttributeNames();
1936        }
1937
1938        /**
1939         * Checks whether a given attribute name/value is defined.
1940         *
1941         * @param name the non-null attribute name
1942         * @param value the attribute value
1943         * @return true if the name/value is defined
1944         * @see AttributeSet#containsAttribute
1945         */
1946        public boolean containsAttribute(Object name, Object value) {
1947            return attributes.containsAttribute(name, value);
1948        }
1949
1950
1951        /**
1952         * Checks whether the element contains all the attributes.
1953         *
1954         * @param attrs the attributes to check
1955         * @return true if the element contains all the attributes
1956         * @see AttributeSet#containsAttributes
1957         */
1958        public boolean containsAttributes(AttributeSet attrs) {
1959            return attributes.containsAttributes(attrs);
1960        }
1961
1962        /**
1963         * Gets the resolving parent.
1964         * If not overridden, the resolving parent defaults to
1965         * the parent element.
1966         *
1967         * @return the attributes from the parent, <code>null</code> if none
1968         * @see AttributeSet#getResolveParent
1969         */
1970        public AttributeSet getResolveParent() {
1971            AttributeSet a = attributes.getResolveParent();
1972            if ((a == null) && (parent != null)) {
1973                a = parent.getAttributes();
1974            }
1975            return a;
1976        }
1977
1978        // --- MutableAttributeSet ----------------------------------
1979        // should fetch a new immutable record for the field
1980        // "attributes".
1981
1982        /**
1983         * Adds an attribute to the element.
1984         *
1985         * @param name the non-null attribute name
1986         * @param value the attribute value
1987         * @see MutableAttributeSet#addAttribute
1988         */
1989        public void addAttribute(Object name, Object value) {
1990            checkForIllegalCast();
1991            AttributeContext context = getAttributeContext();
1992            attributes = context.addAttribute(attributes, name, value);
1993        }
1994
1995        /**
1996         * Adds a set of attributes to the element.
1997         *
1998         * @param attr the attributes to add
1999         * @see MutableAttributeSet#addAttribute
2000         */
2001        public void addAttributes(AttributeSet attr) {
2002            checkForIllegalCast();
2003            AttributeContext context = getAttributeContext();
2004            attributes = context.addAttributes(attributes, attr);
2005        }
2006
2007        /**
2008         * Removes an attribute from the set.
2009         *
2010         * @param name the non-null attribute name
2011         * @see MutableAttributeSet#removeAttribute
2012         */
2013        public void removeAttribute(Object name) {
2014            checkForIllegalCast();
2015            AttributeContext context = getAttributeContext();
2016            attributes = context.removeAttribute(attributes, name);
2017        }
2018
2019        /**
2020         * Removes a set of attributes for the element.
2021         *
2022         * @param names the attribute names
2023         * @see MutableAttributeSet#removeAttributes
2024         */
2025        public void removeAttributes(Enumeration<?> names) {
2026            checkForIllegalCast();
2027            AttributeContext context = getAttributeContext();
2028            attributes = context.removeAttributes(attributes, names);
2029        }
2030
2031        /**
2032         * Removes a set of attributes for the element.
2033         *
2034         * @param attrs the attributes
2035         * @see MutableAttributeSet#removeAttributes
2036         */
2037        public void removeAttributes(AttributeSet attrs) {
2038            checkForIllegalCast();
2039            AttributeContext context = getAttributeContext();
2040            if (attrs == this) {
2041                attributes = context.getEmptySet();
2042            } else {
2043                attributes = context.removeAttributes(attributes, attrs);
2044            }
2045        }
2046
2047        /**
2048         * Sets the resolving parent.
2049         *
2050         * @param parent the parent, null if none
2051         * @see MutableAttributeSet#setResolveParent
2052         */
2053        public void setResolveParent(AttributeSet parent) {
2054            checkForIllegalCast();
2055            AttributeContext context = getAttributeContext();
2056            if (parent != null) {
2057                attributes =
2058                    context.addAttribute(attributes, StyleConstants.ResolveAttribute,
2059                                         parent);
2060            } else {
2061                attributes =
2062                    context.removeAttribute(attributes, StyleConstants.ResolveAttribute);
2063            }
2064        }
2065
2066        private final void checkForIllegalCast() {
2067            Thread t = getCurrentWriter();
2068            if ((t == null) || (t != Thread.currentThread())) {
2069                throw new StateInvariantError("Illegal cast to MutableAttributeSet");
2070            }
2071        }
2072
2073        // --- Element methods -------------------------------------
2074
2075        /**
2076         * Retrieves the underlying model.
2077         *
2078         * @return the model
2079         */
2080        public Document getDocument() {
2081            return AbstractDocument.this;
2082        }
2083
2084        /**
2085         * Gets the parent of the element.
2086         *
2087         * @return the parent
2088         */
2089        public Element getParentElement() {
2090            return parent;
2091        }
2092
2093        /**
2094         * Gets the attributes for the element.
2095         *
2096         * @return the attribute set
2097         */
2098        public AttributeSet getAttributes() {
2099            return this;
2100        }
2101
2102        /**
2103         * Gets the name of the element.
2104         *
2105         * @return the name, null if none
2106         */
2107        public String getName() {
2108            if (attributes.isDefined(ElementNameAttribute)) {
2109                return (String) attributes.getAttribute(ElementNameAttribute);
2110            }
2111            return null;
2112        }
2113
2114        /**
2115         * Gets the starting offset in the model for the element.
2116         *
2117         * @return the offset &gt;= 0
2118         */
2119        public abstract int getStartOffset();
2120
2121        /**
2122         * Gets the ending offset in the model for the element.
2123         *
2124         * @return the offset &gt;= 0
2125         */
2126        public abstract int getEndOffset();
2127
2128        /**
2129         * Gets a child element.
2130         *
2131         * @param index the child index, &gt;= 0 &amp;&amp; &lt; getElementCount()
2132         * @return the child element
2133         */
2134        public abstract Element getElement(int index);
2135
2136        /**
2137         * Gets the number of children for the element.
2138         *
2139         * @return the number of children &gt;= 0
2140         */
2141        public abstract int getElementCount();
2142
2143        /**
2144         * Gets the child element index closest to the given model offset.
2145         *
2146         * @param offset the offset &gt;= 0
2147         * @return the element index &gt;= 0
2148         */
2149        public abstract int getElementIndex(int offset);
2150
2151        /**
2152         * Checks whether the element is a leaf.
2153         *
2154         * @return true if a leaf
2155         */
2156        public abstract boolean isLeaf();
2157
2158        // --- TreeNode methods -------------------------------------
2159
2160        /**
2161         * Returns the child <code>TreeNode</code> at index
2162         * <code>childIndex</code>.
2163         */
2164        public TreeNode getChildAt(int childIndex) {
2165            return (TreeNode)getElement(childIndex);
2166        }
2167
2168        /**
2169         * Returns the number of children <code>TreeNode</code>'s
2170         * receiver contains.
2171         * @return the number of children <code>TreeNodews</code>'s
2172         * receiver contains
2173         */
2174        public int getChildCount() {
2175            return getElementCount();
2176        }
2177
2178        /**
2179         * Returns the parent <code>TreeNode</code> of the receiver.
2180         * @return the parent <code>TreeNode</code> of the receiver
2181         */
2182        public TreeNode getParent() {
2183            return (TreeNode)getParentElement();
2184        }
2185
2186        /**
2187         * Returns the index of <code>node</code> in the receivers children.
2188         * If the receiver does not contain <code>node</code>, -1 will be
2189         * returned.
2190         * @param node the location of interest
2191         * @return the index of <code>node</code> in the receiver's
2192         * children, or -1 if absent
2193         */
2194        public int getIndex(TreeNode node) {
2195            for(int counter = getChildCount() - 1; counter >= 0; counter--)
2196                if(getChildAt(counter) == node)
2197                    return counter;
2198            return -1;
2199        }
2200
2201        /**
2202         * Returns true if the receiver allows children.
2203         * @return true if the receiver allows children, otherwise false
2204         */
2205        public abstract boolean getAllowsChildren();
2206
2207
2208        /**
2209         * Returns the children of the receiver as an
2210         * <code>Enumeration</code>.
2211         * @return the children of the receiver as an <code>Enumeration</code>
2212         */
2213        public abstract Enumeration<TreeNode> children();
2214
2215
2216        // --- serialization ---------------------------------------------
2217
2218        private void writeObject(ObjectOutputStream s) throws IOException {
2219            s.defaultWriteObject();
2220            StyleContext.writeAttributeSet(s, attributes);
2221        }
2222
2223        private void readObject(ObjectInputStream s)
2224            throws ClassNotFoundException, IOException
2225        {
2226            s.defaultReadObject();
2227            MutableAttributeSet attr = new SimpleAttributeSet();
2228            StyleContext.readAttributeSet(s, attr);
2229            AttributeContext context = getAttributeContext();
2230            attributes = context.addAttributes(SimpleAttributeSet.EMPTY, attr);
2231        }
2232
2233        // ---- variables -----------------------------------------------------
2234
2235        private Element parent;
2236        private transient AttributeSet attributes;
2237
2238    }
2239
2240    /**
2241     * Implements a composite element that contains other elements.
2242     * <p>
2243     * <strong>Warning:</strong>
2244     * Serialized objects of this class will not be compatible with
2245     * future Swing releases. The current serialization support is
2246     * appropriate for short term storage or RMI between applications running
2247     * the same version of Swing.  As of 1.4, support for long term storage
2248     * of all JavaBeans&trade;
2249     * has been added to the <code>java.beans</code> package.
2250     * Please see {@link java.beans.XMLEncoder}.
2251     */
2252    @SuppressWarnings("serial") // Same-version serialization only
2253    public class BranchElement extends AbstractElement {
2254
2255        /**
2256         * Constructs a composite element that initially contains
2257         * no children.
2258         *
2259         * @param parent  The parent element
2260         * @param a the attributes for the element
2261         * @since 1.4
2262         */
2263        public BranchElement(Element parent, AttributeSet a) {
2264            super(parent, a);
2265            children = new AbstractElement[1];
2266            nchildren = 0;
2267            lastIndex = -1;
2268        }
2269
2270        /**
2271         * Gets the child element that contains
2272         * the given model position.
2273         *
2274         * @param pos the position &gt;= 0
2275         * @return the element, null if none
2276         */
2277        public Element positionToElement(int pos) {
2278            int index = getElementIndex(pos);
2279            Element child = children[index];
2280            int p0 = child.getStartOffset();
2281            int p1 = child.getEndOffset();
2282            if ((pos >= p0) && (pos < p1)) {
2283                return child;
2284            }
2285            return null;
2286        }
2287
2288        /**
2289         * Replaces content with a new set of elements.
2290         *
2291         * @param offset the starting offset &gt;= 0
2292         * @param length the length to replace &gt;= 0
2293         * @param elems the new elements
2294         */
2295        public void replace(int offset, int length, Element[] elems) {
2296            int delta = elems.length - length;
2297            int src = offset + length;
2298            int nmove = nchildren - src;
2299            int dest = src + delta;
2300            if ((nchildren + delta) >= children.length) {
2301                // need to grow the array
2302                int newLength = Math.max(2*children.length, nchildren + delta);
2303                AbstractElement[] newChildren = new AbstractElement[newLength];
2304                System.arraycopy(children, 0, newChildren, 0, offset);
2305                System.arraycopy(elems, 0, newChildren, offset, elems.length);
2306                System.arraycopy(children, src, newChildren, dest, nmove);
2307                children = newChildren;
2308            } else {
2309                // patch the existing array
2310                System.arraycopy(children, src, children, dest, nmove);
2311                System.arraycopy(elems, 0, children, offset, elems.length);
2312            }
2313            nchildren = nchildren + delta;
2314        }
2315
2316        /**
2317         * Converts the element to a string.
2318         *
2319         * @return the string
2320         */
2321        public String toString() {
2322            return "BranchElement(" + getName() + ") " + getStartOffset() + "," +
2323                getEndOffset() + "\n";
2324        }
2325
2326        // --- Element methods -----------------------------------
2327
2328        /**
2329         * Gets the element name.
2330         *
2331         * @return the element name
2332         */
2333        public String getName() {
2334            String nm = super.getName();
2335            if (nm == null) {
2336                nm = ParagraphElementName;
2337            }
2338            return nm;
2339        }
2340
2341        /**
2342         * Gets the starting offset in the model for the element.
2343         *
2344         * @return the offset &gt;= 0
2345         */
2346        public int getStartOffset() {
2347            return children[0].getStartOffset();
2348        }
2349
2350        /**
2351         * Gets the ending offset in the model for the element.
2352         * @throws NullPointerException if this element has no children
2353         *
2354         * @return the offset &gt;= 0
2355         */
2356        public int getEndOffset() {
2357            Element child =
2358                (nchildren > 0) ? children[nchildren - 1] : children[0];
2359            return child.getEndOffset();
2360        }
2361
2362        /**
2363         * Gets a child element.
2364         *
2365         * @param index the child index, &gt;= 0 &amp;&amp; &lt; getElementCount()
2366         * @return the child element, null if none
2367         */
2368        public Element getElement(int index) {
2369            if (index < nchildren) {
2370                return children[index];
2371            }
2372            return null;
2373        }
2374
2375        /**
2376         * Gets the number of children for the element.
2377         *
2378         * @return the number of children &gt;= 0
2379         */
2380        public int getElementCount()  {
2381            return nchildren;
2382        }
2383
2384        /**
2385         * Gets the child element index closest to the given model offset.
2386         *
2387         * @param offset the offset &gt;= 0
2388         * @return the element index &gt;= 0
2389         */
2390        public int getElementIndex(int offset) {
2391            int index;
2392            int lower = 0;
2393            int upper = nchildren - 1;
2394            int mid = 0;
2395            int p0 = getStartOffset();
2396            int p1;
2397
2398            if (nchildren == 0) {
2399                return 0;
2400            }
2401            if (offset >= getEndOffset()) {
2402                return nchildren - 1;
2403            }
2404
2405            // see if the last index can be used.
2406            if ((lastIndex >= lower) && (lastIndex <= upper)) {
2407                Element lastHit = children[lastIndex];
2408                p0 = lastHit.getStartOffset();
2409                p1 = lastHit.getEndOffset();
2410                if ((offset >= p0) && (offset < p1)) {
2411                    return lastIndex;
2412                }
2413
2414                // last index wasn't a hit, but it does give useful info about
2415                // where a hit (if any) would be.
2416                if (offset < p0) {
2417                    upper = lastIndex;
2418                } else  {
2419                    lower = lastIndex;
2420                }
2421            }
2422
2423            while (lower <= upper) {
2424                mid = lower + ((upper - lower) / 2);
2425                Element elem = children[mid];
2426                p0 = elem.getStartOffset();
2427                p1 = elem.getEndOffset();
2428                if ((offset >= p0) && (offset < p1)) {
2429                    // found the location
2430                    index = mid;
2431                    lastIndex = index;
2432                    return index;
2433                } else if (offset < p0) {
2434                    upper = mid - 1;
2435                } else {
2436                    lower = mid + 1;
2437                }
2438            }
2439
2440            // didn't find it, but we indicate the index of where it would belong
2441            if (offset < p0) {
2442                index = mid;
2443            } else {
2444                index = mid + 1;
2445            }
2446            lastIndex = index;
2447            return index;
2448        }
2449
2450        /**
2451         * Checks whether the element is a leaf.
2452         *
2453         * @return true if a leaf
2454         */
2455        public boolean isLeaf() {
2456            return false;
2457        }
2458
2459
2460        // ------ TreeNode ----------------------------------------------
2461
2462        /**
2463         * Returns true if the receiver allows children.
2464         * @return true if the receiver allows children, otherwise false
2465         */
2466        public boolean getAllowsChildren() {
2467            return true;
2468        }
2469
2470
2471        /**
2472         * Returns the children of the receiver as an
2473         * <code>Enumeration</code>.
2474         * @return the children of the receiver
2475         */
2476        public Enumeration<TreeNode> children() {
2477            if(nchildren == 0)
2478                return null;
2479
2480            Vector<TreeNode> tempVector = new Vector<>(nchildren);
2481
2482            for(int counter = 0; counter < nchildren; counter++)
2483                tempVector.addElement(children[counter]);
2484            return tempVector.elements();
2485        }
2486
2487        // ------ members ----------------------------------------------
2488
2489        private AbstractElement[] children;
2490        private int nchildren;
2491        private int lastIndex;
2492    }
2493
2494    /**
2495     * Implements an element that directly represents content of
2496     * some kind.
2497     * <p>
2498     * <strong>Warning:</strong>
2499     * Serialized objects of this class will not be compatible with
2500     * future Swing releases. The current serialization support is
2501     * appropriate for short term storage or RMI between applications running
2502     * the same version of Swing.  As of 1.4, support for long term storage
2503     * of all JavaBeans&trade;
2504     * has been added to the <code>java.beans</code> package.
2505     * Please see {@link java.beans.XMLEncoder}.
2506     *
2507     * @see     Element
2508     */
2509    @SuppressWarnings("serial") // Same-version serialization only
2510    public class LeafElement extends AbstractElement {
2511
2512        /**
2513         * Constructs an element that represents content within the
2514         * document (has no children).
2515         *
2516         * @param parent  The parent element
2517         * @param a       The element attributes
2518         * @param offs0   The start offset &gt;= 0
2519         * @param offs1   The end offset &gt;= offs0
2520         * @since 1.4
2521         */
2522        public LeafElement(Element parent, AttributeSet a, int offs0, int offs1) {
2523            super(parent, a);
2524            try {
2525                p0 = createPosition(offs0);
2526                p1 = createPosition(offs1);
2527            } catch (BadLocationException e) {
2528                p0 = null;
2529                p1 = null;
2530                throw new StateInvariantError("Can't create Position references");
2531            }
2532        }
2533
2534        /**
2535         * Converts the element to a string.
2536         *
2537         * @return the string
2538         */
2539        public String toString() {
2540            return "LeafElement(" + getName() + ") " + p0 + "," + p1 + "\n";
2541        }
2542
2543        // --- Element methods ---------------------------------------------
2544
2545        /**
2546         * Gets the starting offset in the model for the element.
2547         *
2548         * @return the offset &gt;= 0
2549         */
2550        public int getStartOffset() {
2551            return p0.getOffset();
2552        }
2553
2554        /**
2555         * Gets the ending offset in the model for the element.
2556         *
2557         * @return the offset &gt;= 0
2558         */
2559        public int getEndOffset() {
2560            return p1.getOffset();
2561        }
2562
2563        /**
2564         * Gets the element name.
2565         *
2566         * @return the name
2567         */
2568        public String getName() {
2569            String nm = super.getName();
2570            if (nm == null) {
2571                nm = ContentElementName;
2572            }
2573            return nm;
2574        }
2575
2576        /**
2577         * Gets the child element index closest to the given model offset.
2578         *
2579         * @param pos the offset &gt;= 0
2580         * @return the element index &gt;= 0
2581         */
2582        public int getElementIndex(int pos) {
2583            return -1;
2584        }
2585
2586        /**
2587         * Gets a child element.
2588         *
2589         * @param index the child index, &gt;= 0 &amp;&amp; &lt; getElementCount()
2590         * @return the child element
2591         */
2592        public Element getElement(int index) {
2593            return null;
2594        }
2595
2596        /**
2597         * Returns the number of child elements.
2598         *
2599         * @return the number of children &gt;= 0
2600         */
2601        public int getElementCount()  {
2602            return 0;
2603        }
2604
2605        /**
2606         * Checks whether the element is a leaf.
2607         *
2608         * @return true if a leaf
2609         */
2610        public boolean isLeaf() {
2611            return true;
2612        }
2613
2614        // ------ TreeNode ----------------------------------------------
2615
2616        /**
2617         * Returns true if the receiver allows children.
2618         * @return true if the receiver allows children, otherwise false
2619         */
2620        public boolean getAllowsChildren() {
2621            return false;
2622        }
2623
2624
2625        /**
2626         * Returns the children of the receiver as an
2627         * <code>Enumeration</code>.
2628         * @return the children of the receiver
2629         */
2630        @Override
2631        public Enumeration<TreeNode> children() {
2632            return null;
2633        }
2634
2635        // --- serialization ---------------------------------------------
2636
2637        private void writeObject(ObjectOutputStream s) throws IOException {
2638            s.defaultWriteObject();
2639            s.writeInt(p0.getOffset());
2640            s.writeInt(p1.getOffset());
2641        }
2642
2643        private void readObject(ObjectInputStream s)
2644            throws ClassNotFoundException, IOException
2645        {
2646            s.defaultReadObject();
2647
2648            // set the range with positions that track change
2649            int off0 = s.readInt();
2650            int off1 = s.readInt();
2651            try {
2652                p0 = createPosition(off0);
2653                p1 = createPosition(off1);
2654            } catch (BadLocationException e) {
2655                p0 = null;
2656                p1 = null;
2657                throw new IOException("Can't restore Position references");
2658            }
2659        }
2660
2661        // ---- members -----------------------------------------------------
2662
2663        private transient Position p0;
2664        private transient Position p1;
2665    }
2666
2667    /**
2668     * Represents the root element of the bidirectional element structure.
2669     * The root element is the only element in the bidi element structure
2670     * which contains children.
2671     */
2672    class BidiRootElement extends BranchElement {
2673
2674        BidiRootElement() {
2675            super( null, null );
2676        }
2677
2678        /**
2679         * Gets the name of the element.
2680         * @return the name
2681         */
2682        public String getName() {
2683            return "bidi root";
2684        }
2685    }
2686
2687    /**
2688     * Represents an element of the bidirectional element structure.
2689     */
2690    class BidiElement extends LeafElement {
2691
2692        /**
2693         * Creates a new BidiElement.
2694         */
2695        BidiElement(Element parent, int start, int end, int level) {
2696            super(parent, new SimpleAttributeSet(), start, end);
2697            addAttribute(StyleConstants.BidiLevel, Integer.valueOf(level));
2698            //System.out.println("BidiElement: start = " + start
2699            //                   + " end = " + end + " level = " + level );
2700        }
2701
2702        /**
2703         * Gets the name of the element.
2704         * @return the name
2705         */
2706        public String getName() {
2707            return BidiElementName;
2708        }
2709
2710        int getLevel() {
2711            Integer o = (Integer) getAttribute(StyleConstants.BidiLevel);
2712            if (o != null) {
2713                return o.intValue();
2714            }
2715            return 0;  // Level 0 is base level (non-embedded) left-to-right
2716        }
2717
2718        boolean isLeftToRight() {
2719            return ((getLevel() % 2) == 0);
2720        }
2721    }
2722
2723    /**
2724     * Stores document changes as the document is being
2725     * modified.  Can subsequently be used for change notification
2726     * when done with the document modification transaction.
2727     * This is used by the AbstractDocument class and its extensions
2728     * for broadcasting change information to the document listeners.
2729     */
2730    public class DefaultDocumentEvent extends CompoundEdit implements DocumentEvent {
2731
2732        /**
2733         * Constructs a change record.
2734         *
2735         * @param offs the offset into the document of the change &gt;= 0
2736         * @param len  the length of the change &gt;= 0
2737         * @param type the type of event (DocumentEvent.EventType)
2738         * @since 1.4
2739         */
2740        public DefaultDocumentEvent(int offs, int len, DocumentEvent.EventType type) {
2741            super();
2742            offset = offs;
2743            length = len;
2744            this.type = type;
2745        }
2746
2747        /**
2748         * Returns a string description of the change event.
2749         *
2750         * @return a string
2751         */
2752        public String toString() {
2753            return edits.toString();
2754        }
2755
2756        // --- CompoundEdit methods --------------------------
2757
2758        /**
2759         * Adds a document edit.  If the number of edits crosses
2760         * a threshold, this switches on a hashtable lookup for
2761         * ElementChange implementations since access of these
2762         * needs to be relatively quick.
2763         *
2764         * @param anEdit a document edit record
2765         * @return true if the edit was added
2766         */
2767        public boolean addEdit(UndoableEdit anEdit) {
2768            // if the number of changes gets too great, start using
2769            // a hashtable for to locate the change for a given element.
2770            if ((changeLookup == null) && (edits.size() > 10)) {
2771                changeLookup = new Hashtable<Element, ElementChange>();
2772                int n = edits.size();
2773                for (int i = 0; i < n; i++) {
2774                    Object o = edits.elementAt(i);
2775                    if (o instanceof DocumentEvent.ElementChange) {
2776                        DocumentEvent.ElementChange ec = (DocumentEvent.ElementChange) o;
2777                        changeLookup.put(ec.getElement(), ec);
2778                    }
2779                }
2780            }
2781
2782            // if we have a hashtable... add the entry if it's
2783            // an ElementChange.
2784            if ((changeLookup != null) && (anEdit instanceof DocumentEvent.ElementChange)) {
2785                DocumentEvent.ElementChange ec = (DocumentEvent.ElementChange) anEdit;
2786                changeLookup.put(ec.getElement(), ec);
2787            }
2788            return super.addEdit(anEdit);
2789        }
2790
2791        /**
2792         * Redoes a change.
2793         *
2794         * @exception CannotRedoException if the change cannot be redone
2795         */
2796        public void redo() throws CannotRedoException {
2797            writeLock();
2798            try {
2799                // change the state
2800                super.redo();
2801                // fire a DocumentEvent to notify the view(s)
2802                UndoRedoDocumentEvent ev = new UndoRedoDocumentEvent(this, false);
2803                if (type == DocumentEvent.EventType.INSERT) {
2804                    fireInsertUpdate(ev);
2805                } else if (type == DocumentEvent.EventType.REMOVE) {
2806                    fireRemoveUpdate(ev);
2807                } else {
2808                    fireChangedUpdate(ev);
2809                }
2810            } finally {
2811                writeUnlock();
2812            }
2813        }
2814
2815        /**
2816         * Undoes a change.
2817         *
2818         * @exception CannotUndoException if the change cannot be undone
2819         */
2820        public void undo() throws CannotUndoException {
2821            writeLock();
2822            try {
2823                // change the state
2824                super.undo();
2825                // fire a DocumentEvent to notify the view(s)
2826                UndoRedoDocumentEvent ev = new UndoRedoDocumentEvent(this, true);
2827                if (type == DocumentEvent.EventType.REMOVE) {
2828                    fireInsertUpdate(ev);
2829                } else if (type == DocumentEvent.EventType.INSERT) {
2830                    fireRemoveUpdate(ev);
2831                } else {
2832                    fireChangedUpdate(ev);
2833                }
2834            } finally {
2835                writeUnlock();
2836            }
2837        }
2838
2839        /**
2840         * DefaultDocument events are significant.  If you wish to aggregate
2841         * DefaultDocumentEvents to present them as a single edit to the user
2842         * place them into a CompoundEdit.
2843         *
2844         * @return whether the event is significant for edit undo purposes
2845         */
2846        public boolean isSignificant() {
2847            return true;
2848        }
2849
2850
2851        /**
2852         * Provides a localized, human readable description of this edit
2853         * suitable for use in, say, a change log.
2854         *
2855         * @return the description
2856         */
2857        public String getPresentationName() {
2858            DocumentEvent.EventType type = getType();
2859            if(type == DocumentEvent.EventType.INSERT)
2860                return UIManager.getString("AbstractDocument.additionText");
2861            if(type == DocumentEvent.EventType.REMOVE)
2862                return UIManager.getString("AbstractDocument.deletionText");
2863            return UIManager.getString("AbstractDocument.styleChangeText");
2864        }
2865
2866        /**
2867         * Provides a localized, human readable description of the undoable
2868         * form of this edit, e.g. for use as an Undo menu item. Typically
2869         * derived from getDescription();
2870         *
2871         * @return the description
2872         */
2873        public String getUndoPresentationName() {
2874            return UIManager.getString("AbstractDocument.undoText") + " " +
2875                getPresentationName();
2876        }
2877
2878        /**
2879         * Provides a localized, human readable description of the redoable
2880         * form of this edit, e.g. for use as a Redo menu item. Typically
2881         * derived from getPresentationName();
2882         *
2883         * @return the description
2884         */
2885        public String getRedoPresentationName() {
2886            return UIManager.getString("AbstractDocument.redoText") + " " +
2887                getPresentationName();
2888        }
2889
2890        // --- DocumentEvent methods --------------------------
2891
2892        /**
2893         * Returns the type of event.
2894         *
2895         * @return the event type as a DocumentEvent.EventType
2896         * @see DocumentEvent#getType
2897         */
2898        public DocumentEvent.EventType getType() {
2899            return type;
2900        }
2901
2902        /**
2903         * Returns the offset within the document of the start of the change.
2904         *
2905         * @return the offset &gt;= 0
2906         * @see DocumentEvent#getOffset
2907         */
2908        public int getOffset() {
2909            return offset;
2910        }
2911
2912        /**
2913         * Returns the length of the change.
2914         *
2915         * @return the length &gt;= 0
2916         * @see DocumentEvent#getLength
2917         */
2918        public int getLength() {
2919            return length;
2920        }
2921
2922        /**
2923         * Gets the document that sourced the change event.
2924         *
2925         * @return the document
2926         * @see DocumentEvent#getDocument
2927         */
2928        public Document getDocument() {
2929            return AbstractDocument.this;
2930        }
2931
2932        /**
2933         * Gets the changes for an element.
2934         *
2935         * @param elem the element
2936         * @return the changes
2937         */
2938        public DocumentEvent.ElementChange getChange(Element elem) {
2939            if (changeLookup != null) {
2940                return changeLookup.get(elem);
2941            }
2942            int n = edits.size();
2943            for (int i = 0; i < n; i++) {
2944                Object o = edits.elementAt(i);
2945                if (o instanceof DocumentEvent.ElementChange) {
2946                    DocumentEvent.ElementChange c = (DocumentEvent.ElementChange) o;
2947                    if (elem.equals(c.getElement())) {
2948                        return c;
2949                    }
2950                }
2951            }
2952            return null;
2953        }
2954
2955        // --- member variables ------------------------------------
2956
2957        private int offset;
2958        private int length;
2959        private Hashtable<Element, ElementChange> changeLookup;
2960        private DocumentEvent.EventType type;
2961
2962    }
2963
2964    static class DefaultDocumentEventUndoableWrapper implements
2965            UndoableEdit, UndoableEditLockSupport
2966    {
2967        final DefaultDocumentEvent dde;
2968        public DefaultDocumentEventUndoableWrapper(DefaultDocumentEvent dde) {
2969            this.dde = dde;
2970        }
2971
2972        @Override
2973        public void undo() throws CannotUndoException {
2974            dde.undo();
2975        }
2976
2977        @Override
2978        public boolean canUndo() {
2979            return dde.canUndo();
2980        }
2981
2982        @Override
2983        public void redo() throws CannotRedoException {
2984            dde.redo();
2985        }
2986
2987        @Override
2988        public boolean canRedo() {
2989            return dde.canRedo();
2990        }
2991
2992        @Override
2993        public void die() {
2994            dde.die();
2995        }
2996
2997        @Override
2998        public boolean addEdit(UndoableEdit anEdit) {
2999            return dde.addEdit(anEdit);
3000        }
3001
3002        @Override
3003        public boolean replaceEdit(UndoableEdit anEdit) {
3004            return dde.replaceEdit(anEdit);
3005        }
3006
3007        @Override
3008        public boolean isSignificant() {
3009            return dde.isSignificant();
3010        }
3011
3012        @Override
3013        public String getPresentationName() {
3014            return dde.getPresentationName();
3015        }
3016
3017        @Override
3018        public String getUndoPresentationName() {
3019            return dde.getUndoPresentationName();
3020        }
3021
3022        @Override
3023        public String getRedoPresentationName() {
3024            return dde.getRedoPresentationName();
3025        }
3026
3027        /**
3028         * {@inheritDoc}
3029         * @since 9
3030         */
3031        @Override
3032        public void lockEdit() {
3033            ((AbstractDocument)dde.getDocument()).writeLock();
3034        }
3035
3036        /**
3037         * {@inheritDoc}
3038         * @since 9
3039         */
3040        @Override
3041        public void unlockEdit() {
3042            ((AbstractDocument)dde.getDocument()).writeUnlock();
3043        }
3044    }
3045
3046    /**
3047     * This event used when firing document changes while Undo/Redo
3048     * operations. It just wraps DefaultDocumentEvent and delegates
3049     * all calls to it except getType() which depends on operation
3050     * (Undo or Redo).
3051     */
3052    class UndoRedoDocumentEvent implements DocumentEvent {
3053        private DefaultDocumentEvent src = null;
3054        private EventType type = null;
3055
3056        public UndoRedoDocumentEvent(DefaultDocumentEvent src, boolean isUndo) {
3057            this.src = src;
3058            if(isUndo) {
3059                if(src.getType().equals(EventType.INSERT)) {
3060                    type = EventType.REMOVE;
3061                } else if(src.getType().equals(EventType.REMOVE)) {
3062                    type = EventType.INSERT;
3063                } else {
3064                    type = src.getType();
3065                }
3066            } else {
3067                type = src.getType();
3068            }
3069        }
3070
3071        public DefaultDocumentEvent getSource() {
3072            return src;
3073        }
3074
3075        // DocumentEvent methods delegated to DefaultDocumentEvent source
3076        // except getType() which depends on operation (Undo or Redo).
3077        public int getOffset() {
3078            return src.getOffset();
3079        }
3080
3081        public int getLength() {
3082            return src.getLength();
3083        }
3084
3085        public Document getDocument() {
3086            return src.getDocument();
3087        }
3088
3089        public DocumentEvent.EventType getType() {
3090            return type;
3091        }
3092
3093        public DocumentEvent.ElementChange getChange(Element elem) {
3094            return src.getChange(elem);
3095        }
3096    }
3097
3098    /**
3099     * An implementation of ElementChange that can be added to the document
3100     * event.
3101     */
3102    public static class ElementEdit extends AbstractUndoableEdit implements DocumentEvent.ElementChange {
3103
3104        /**
3105         * Constructs an edit record.  This does not modify the element
3106         * so it can safely be used to <em>catch up</em> a view to the
3107         * current model state for views that just attached to a model.
3108         *
3109         * @param e the element
3110         * @param index the index into the model &gt;= 0
3111         * @param removed a set of elements that were removed
3112         * @param added a set of elements that were added
3113         */
3114        public ElementEdit(Element e, int index, Element[] removed, Element[] added) {
3115            super();
3116            this.e = e;
3117            this.index = index;
3118            this.removed = removed;
3119            this.added = added;
3120        }
3121
3122        /**
3123         * Returns the underlying element.
3124         *
3125         * @return the element
3126         */
3127        public Element getElement() {
3128            return e;
3129        }
3130
3131        /**
3132         * Returns the index into the list of elements.
3133         *
3134         * @return the index &gt;= 0
3135         */
3136        public int getIndex() {
3137            return index;
3138        }
3139
3140        /**
3141         * Gets a list of children that were removed.
3142         *
3143         * @return the list
3144         */
3145        public Element[] getChildrenRemoved() {
3146            return removed;
3147        }
3148
3149        /**
3150         * Gets a list of children that were added.
3151         *
3152         * @return the list
3153         */
3154        public Element[] getChildrenAdded() {
3155            return added;
3156        }
3157
3158        /**
3159         * Redoes a change.
3160         *
3161         * @exception CannotRedoException if the change cannot be redone
3162         */
3163        public void redo() throws CannotRedoException {
3164            super.redo();
3165
3166            // Since this event will be reused, switch around added/removed.
3167            Element[] tmp = removed;
3168            removed = added;
3169            added = tmp;
3170
3171            // PENDING(prinz) need MutableElement interface, canRedo() should check
3172            ((AbstractDocument.BranchElement)e).replace(index, removed.length, added);
3173        }
3174
3175        /**
3176         * Undoes a change.
3177         *
3178         * @exception CannotUndoException if the change cannot be undone
3179         */
3180        public void undo() throws CannotUndoException {
3181            super.undo();
3182            // PENDING(prinz) need MutableElement interface, canUndo() should check
3183            ((AbstractDocument.BranchElement)e).replace(index, added.length, removed);
3184
3185            // Since this event will be reused, switch around added/removed.
3186            Element[] tmp = removed;
3187            removed = added;
3188            added = tmp;
3189        }
3190
3191        private Element e;
3192        private int index;
3193        private Element[] removed;
3194        private Element[] added;
3195    }
3196
3197
3198    private class DefaultFilterBypass extends DocumentFilter.FilterBypass {
3199        public Document getDocument() {
3200            return AbstractDocument.this;
3201        }
3202
3203        public void remove(int offset, int length) throws
3204            BadLocationException {
3205            handleRemove(offset, length);
3206        }
3207
3208        public void insertString(int offset, String string,
3209                                 AttributeSet attr) throws
3210                                        BadLocationException {
3211            handleInsertString(offset, string, attr);
3212        }
3213
3214        public void replace(int offset, int length, String text,
3215                            AttributeSet attrs) throws BadLocationException {
3216            handleRemove(offset, length);
3217            handleInsertString(offset, text, attrs);
3218        }
3219    }
3220}
3221