KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > javax > swing > text > AbstractDocument


1 /*
2  * @(#)AbstractDocument.java 1.151 04/07/13
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7 package javax.swing.text;
8
9 import java.util.*;
10 import java.io.*;
11 import java.awt.font.TextAttribute JavaDoc;
12 import java.text.Bidi JavaDoc;
13
14 import javax.swing.UIManager JavaDoc;
15 import javax.swing.undo.*;
16 import javax.swing.event.ChangeListener JavaDoc;
17 import javax.swing.event.*;
18 import javax.swing.tree.TreeNode JavaDoc;
19
20 import sun.font.BidiUtils;
21
22 /**
23  * An implementation of the document interface to serve as a
24  * basis for implementing various kinds of documents. At this
25  * level there is very little policy, so there is a corresponding
26  * increase in difficulty of use.
27  * <p>
28  * This class implements a locking mechanism for the document. It
29  * allows multiple readers or one writer, and writers must wait until
30  * all observers of the document have been notified of a previous
31  * change before beginning another mutation to the document. The
32  * read lock is acquired and released using the <code>render</code>
33  * method. A write lock is aquired by the methods that mutate the
34  * document, and are held for the duration of the method call.
35  * Notification is done on the thread that produced the mutation,
36  * and the thread has full read access to the document for the
37  * duration of the notification, but other readers are kept out
38  * until the notification has finished. The notification is a
39  * beans event notification which does not allow any further
40  * mutations until all listeners have been notified.
41  * <p>
42  * Any models subclassed from this class and used in conjunction
43  * with a text component that has a look and feel implementation
44  * that is derived from BasicTextUI may be safely updated
45  * asynchronously, because all access to the View hierarchy
46  * is serialized by BasicTextUI if the document is of type
47  * <code>AbstractDocument</code>. The locking assumes that an
48  * independant thread will access the View hierarchy only from
49  * the DocumentListener methods, and that there will be only
50  * one event thread active at a time.
51  * <p>
52  * If concurrency support is desired, there are the following
53  * additional implications. The code path for any DocumentListener
54  * implementation and any UndoListener implementation must be threadsafe,
55  * and not access the component lock if trying to be safe from deadlocks.
56  * The <code>repaint</code> and <code>revalidate</code> methods
57  * on JComponent are safe.
58  * <p>
59  * AbstractDocument models an implied break at the end of the document.
60  * Among other things this allows you to position the caret after the last
61  * character. As a result of this, <code>getLength</code> returns one less
62  * than the length of the Content. If you create your own Content, be
63  * sure and initialize it to have an additional character. Refer to
64  * StringContent and GapContent for examples of this. Another implication
65  * of this is that Elements that model the implied end character will have
66  * an endOffset == (getLength() + 1). For example, in DefaultStyledDocument
67  * <code>getParagraphElement(getLength()).getEndOffset() == getLength() + 1
68  * </code>.
69  * <p>
70  * <strong>Warning:</strong>
71  * Serialized objects of this class will not be compatible with
72  * future Swing releases. The current serialization support is
73  * appropriate for short term storage or RMI between applications running
74  * the same version of Swing. As of 1.4, support for long term storage
75  * of all JavaBeans<sup><font size="-2">TM</font></sup>
76  * has been added to the <code>java.beans</code> package.
77  * Please see {@link java.beans.XMLEncoder}.
78  *
79  * @author Timothy Prinzing
80  * @version 1.151 07/13/04
81  */

82 public abstract class AbstractDocument implements Document JavaDoc, Serializable {
83
84     /**
85      * Constructs a new <code>AbstractDocument</code>, wrapped around some
86      * specified content storage mechanism.
87      *
88      * @param data the content
89      */

90     protected AbstractDocument(Content data) {
91     this(data, StyleContext.getDefaultStyleContext());
92     }
93
94     /**
95      * Constructs a new <code>AbstractDocument</code>, wrapped around some
96      * specified content storage mechanism.
97      *
98      * @param data the content
99      * @param context the attribute context
100      */

101     protected AbstractDocument(Content data, AttributeContext context) {
102     this.data = data;
103     this.context = context;
104         bidiRoot = new BidiRootElement();
105
106     if (defaultI18NProperty == null) {
107         // determine default setting for i18n support
108
Object JavaDoc o = java.security.AccessController.doPrivileged(
109         new java.security.PrivilegedAction JavaDoc() {
110                     public Object JavaDoc run() {
111             return System.getProperty(I18NProperty);
112             }
113                 }
114         );
115         if (o != null) {
116         defaultI18NProperty = Boolean.valueOf((String JavaDoc)o);
117         } else {
118         defaultI18NProperty = Boolean.FALSE;
119         }
120     }
121     putProperty( I18NProperty, defaultI18NProperty);
122
123         //REMIND(bcb) This creates an initial bidi element to account for
124
//the \n that exists by default in the content. Doing it this way
125
//seems to expose a little too much knowledge of the content given
126
//to us by the sub-class. Consider having the sub-class' constructor
127
//make an initial call to insertUpdate.
128
writeLock();
129         try {
130             Element JavaDoc[] p = new Element JavaDoc[1];
131             p[0] = new BidiElement( bidiRoot, 0, 1, 0 );
132             bidiRoot.replace(0,0,p);
133         } finally {
134             writeUnlock();
135         }
136     }
137
138     /**
139      * Supports managing a set of properties. Callers
140      * can use the <code>documentProperties</code> dictionary
141      * to annotate the document with document-wide properties.
142      *
143      * @return a non-<code>null</code> <code>Dictionary</code>
144      * @see #setDocumentProperties
145      */

146     public Dictionary<Object JavaDoc,Object JavaDoc> getDocumentProperties() {
147     if (documentProperties == null) {
148         documentProperties = new Hashtable(2);
149     }
150     return documentProperties;
151     }
152
153     /**
154      * Replaces the document properties dictionary for this document.
155      *
156      * @param x the new dictionary
157      * @see #getDocumentProperties
158      */

159     public void setDocumentProperties(Dictionary<Object JavaDoc,Object JavaDoc> x) {
160     documentProperties = x;
161     }
162
163     /**
164      * Notifies all listeners that have registered interest for
165      * notification on this event type. The event instance
166      * is lazily created using the parameters passed into
167      * the fire method.
168      *
169      * @param e the event
170      * @see EventListenerList
171      */

172     protected void fireInsertUpdate(DocumentEvent e) {
173         notifyingListeners = true;
174         try {
175             // Guaranteed to return a non-null array
176
Object JavaDoc[] listeners = listenerList.getListenerList();
177             // Process the listeners last to first, notifying
178
// those that are interested in this event
179
for (int i = listeners.length-2; i>=0; i-=2) {
180                 if (listeners[i]==DocumentListener.class) {
181                     // Lazily create the event:
182
// if (e == null)
183
// e = new ListSelectionEvent(this, firstIndex, lastIndex);
184
((DocumentListener)listeners[i+1]).insertUpdate(e);
185                 }
186             }
187         } finally {
188             notifyingListeners = false;
189         }
190     }
191
192     /**
193      * Notifies all listeners that have registered interest for
194      * notification on this event type. The event instance
195      * is lazily created using the parameters passed into
196      * the fire method.
197      *
198      * @param e the event
199      * @see EventListenerList
200      */

201     protected void fireChangedUpdate(DocumentEvent e) {
202         notifyingListeners = true;
203         try {
204             // Guaranteed to return a non-null array
205
Object JavaDoc[] listeners = listenerList.getListenerList();
206             // Process the listeners last to first, notifying
207
// those that are interested in this event
208
for (int i = listeners.length-2; i>=0; i-=2) {
209                 if (listeners[i]==DocumentListener.class) {
210                     // Lazily create the event:
211
// if (e == null)
212
// e = new ListSelectionEvent(this, firstIndex, lastIndex);
213
((DocumentListener)listeners[i+1]).changedUpdate(e);
214                 }
215             }
216         } finally {
217             notifyingListeners = false;
218         }
219     }
220
221     /**
222      * Notifies all listeners that have registered interest for
223      * notification on this event type. The event instance
224      * is lazily created using the parameters passed into
225      * the fire method.
226      *
227      * @param e the event
228      * @see EventListenerList
229      */

230     protected void fireRemoveUpdate(DocumentEvent e) {
231         notifyingListeners = true;
232         try {
233             // Guaranteed to return a non-null array
234
Object JavaDoc[] listeners = listenerList.getListenerList();
235             // Process the listeners last to first, notifying
236
// those that are interested in this event
237
for (int i = listeners.length-2; i>=0; i-=2) {
238                 if (listeners[i]==DocumentListener.class) {
239                     // Lazily create the event:
240
// if (e == null)
241
// e = new ListSelectionEvent(this, firstIndex, lastIndex);
242
((DocumentListener)listeners[i+1]).removeUpdate(e);
243                 }
244             }
245         } finally {
246             notifyingListeners = false;
247         }
248     }
249
250     /**
251      * Notifies all listeners that have registered interest for
252      * notification on this event type. The event instance
253      * is lazily created using the parameters passed into
254      * the fire method.
255      *
256      * @param e the event
257      * @see EventListenerList
258      */

259     protected void fireUndoableEditUpdate(UndoableEditEvent e) {
260     // Guaranteed to return a non-null array
261
Object JavaDoc[] listeners = listenerList.getListenerList();
262     // Process the listeners last to first, notifying
263
// those that are interested in this event
264
for (int i = listeners.length-2; i>=0; i-=2) {
265         if (listeners[i]==UndoableEditListener.class) {
266         // Lazily create the event:
267
// if (e == null)
268
// e = new ListSelectionEvent(this, firstIndex, lastIndex);
269
((UndoableEditListener)listeners[i+1]).undoableEditHappened(e);
270         }
271     }
272     }
273
274     /**
275      * Returns an array of all the objects currently registered
276      * as <code><em>Foo</em>Listener</code>s
277      * upon this document.
278      * <code><em>Foo</em>Listener</code>s are registered using the
279      * <code>add<em>Foo</em>Listener</code> method.
280      *
281      * <p>
282      * You can specify the <code>listenerType</code> argument
283      * with a class literal, such as
284      * <code><em>Foo</em>Listener.class</code>.
285      * For example, you can query a
286      * document <code>d</code>
287      * for its document listeners with the following code:
288      *
289      * <pre>DocumentListener[] mls = (DocumentListener[])(d.getListeners(DocumentListener.class));</pre>
290      *
291      * If no such listeners exist, this method returns an empty array.
292      *
293      * @param listenerType the type of listeners requested; this parameter
294      * should specify an interface that descends from
295      * <code>java.util.EventListener</code>
296      * @return an array of all objects registered as
297      * <code><em>Foo</em>Listener</code>s on this component,
298      * or an empty array if no such
299      * listeners have been added
300      * @exception ClassCastException if <code>listenerType</code>
301      * doesn't specify a class or interface that implements
302      * <code>java.util.EventListener</code>
303      *
304      * @see #getDocumentListeners
305      * @see #getUndoableEditListeners
306      *
307      * @since 1.3
308      */

309     public <T extends EventListener> T[] getListeners(Class JavaDoc<T> listenerType) {
310     return listenerList.getListeners(listenerType);
311     }
312
313     /**
314      * Gets the asynchronous loading priority. If less than zero,
315      * the document should not be loaded asynchronously.
316      *
317      * @return the asynchronous loading priority, or <code>-1</code>
318      * if the document should not be loaded asynchronously
319      */

320     public int getAsynchronousLoadPriority() {
321     Integer JavaDoc loadPriority = (Integer JavaDoc)
322         getProperty(AbstractDocument.AsyncLoadPriority);
323     if (loadPriority != null) {
324         return loadPriority.intValue();
325     }
326     return -1;
327     }
328
329     /**
330      * Sets the asynchronous loading priority.
331      * @param p the new asynchronous loading priority; a value
332      * less than zero indicates that the document should not be
333      * loaded asynchronously
334      */

335     public void setAsynchronousLoadPriority(int p) {
336     Integer JavaDoc loadPriority = (p >= 0) ? new Integer JavaDoc(p) : null;
337     putProperty(AbstractDocument.AsyncLoadPriority, loadPriority);
338     }
339
340     /**
341      * Sets the <code>DocumentFilter</code>. The <code>DocumentFilter</code>
342      * is passed <code>insert</code> and <code>remove</code> to conditionally
343      * allow inserting/deleting of the text. A <code>null</code> value
344      * indicates that no filtering will occur.
345      *
346      * @param filter the <code>DocumentFilter</code> used to constrain text
347      * @see #getDocumentFilter
348      * @since 1.4
349      */

350     public void setDocumentFilter(DocumentFilter JavaDoc filter) {
351         documentFilter = filter;
352     }
353
354     /**
355      * Returns the <code>DocumentFilter</code> that is responsible for
356      * filtering of insertion/removal. A <code>null</code> return value
357      * implies no filtering is to occur.
358      *
359      * @since 1.4
360      * @see #setDocumentFilter
361      * @return the DocumentFilter
362      */

363     public DocumentFilter JavaDoc getDocumentFilter() {
364         return documentFilter;
365     }
366
367     // --- Document methods -----------------------------------------
368

369     /**
370      * This allows the model to be safely rendered in the presence
371      * of currency, if the model supports being updated asynchronously.
372      * The given runnable will be executed in a way that allows it
373      * to safely read the model with no changes while the runnable
374      * is being executed. The runnable itself may <em>not</em>
375      * make any mutations.
376      * <p>
377      * This is implemented to aquire a read lock for the duration
378      * of the runnables execution. There may be multiple runnables
379      * executing at the same time, and all writers will be blocked
380      * while there are active rendering runnables. If the runnable
381      * throws an exception, its lock will be safely released.
382      * There is no protection against a runnable that never exits,
383      * which will effectively leave the document locked for it's
384      * lifetime.
385      * <p>
386      * If the given runnable attempts to make any mutations in
387      * this implementation, a deadlock will occur. There is
388      * no tracking of individual rendering threads to enable
389      * detecting this situation, but a subclass could incur
390      * the overhead of tracking them and throwing an error.
391      * <p>
392      * This method is thread safe, although most Swing methods
393      * are not. Please see
394      * <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">Threads
395      * and Swing</A> for more information.
396      *
397      * @param r the renderer to execute
398      */

399     public void render(Runnable JavaDoc r) {
400     readLock();
401     try {
402         r.run();
403     } finally {
404         readUnlock();
405     }
406     }
407
408     /**
409      * Returns the length of the data. This is the number of
410      * characters of content that represents the users data.
411      *
412      * @return the length >= 0
413      * @see Document#getLength
414      */

415     public int getLength() {
416     return data.length() - 1;
417     }
418
419     /**
420      * Adds a document listener for notification of any changes.
421      *
422      * @param listener the <code>DocumentListener</code> to add
423      * @see Document#addDocumentListener
424      */

425     public void addDocumentListener(DocumentListener listener) {
426     listenerList.add(DocumentListener.class, listener);
427     }
428
429     /**
430      * Removes a document listener.
431      *
432      * @param listener the <code>DocumentListener</code> to remove
433      * @see Document#removeDocumentListener
434      */

435     public void removeDocumentListener(DocumentListener listener) {
436     listenerList.remove(DocumentListener.class, listener);
437     }
438
439     /**
440      * Returns an array of all the document listeners
441      * registered on this document.
442      *
443      * @return all of this document's <code>DocumentListener</code>s
444      * or an empty array if no document listeners are
445      * currently registered
446      *
447      * @see #addDocumentListener
448      * @see #removeDocumentListener
449      * @since 1.4
450      */

451     public DocumentListener[] getDocumentListeners() {
452         return (DocumentListener[])listenerList.getListeners(
453                 DocumentListener.class);
454     }
455
456     /**
457      * Adds an undo listener for notification of any changes.
458      * Undo/Redo operations performed on the <code>UndoableEdit</code>
459      * will cause the appropriate DocumentEvent to be fired to keep
460      * the view(s) in sync with the model.
461      *
462      * @param listener the <code>UndoableEditListener</code> to add
463      * @see Document#addUndoableEditListener
464      */

465     public void addUndoableEditListener(UndoableEditListener listener) {
466     listenerList.add(UndoableEditListener.class, listener);
467     }
468
469     /**
470      * Removes an undo listener.
471      *
472      * @param listener the <code>UndoableEditListener</code> to remove
473      * @see Document#removeDocumentListener
474      */

475     public void removeUndoableEditListener(UndoableEditListener listener) {
476     listenerList.remove(UndoableEditListener.class, listener);
477     }
478
479     /**
480      * Returns an array of all the undoable edit listeners
481      * registered on this document.
482      *
483      * @return all of this document's <code>UndoableEditListener</code>s
484      * or an empty array if no undoable edit listeners are
485      * currently registered
486      *
487      * @see #addUndoableEditListener
488      * @see #removeUndoableEditListener
489      *
490      * @since 1.4
491      */

492     public UndoableEditListener[] getUndoableEditListeners() {
493         return (UndoableEditListener[])listenerList.getListeners(
494                 UndoableEditListener.class);
495     }
496
497     /**
498      * A convenience method for looking up a property value. It is
499      * equivalent to:
500      * <pre>
501      * getDocumentProperties().get(key);
502      * </pre>
503      *
504      * @param key the non-<code>null</code> property key
505      * @return the value of this property or <code>null</code>
506      * @see #getDocumentProperties
507      */

508     public final Object JavaDoc getProperty(Object JavaDoc key) {
509         return getDocumentProperties().get(key);
510     }
511
512
513     /**
514      * A convenience method for storing up a property value. It is
515      * equivalent to:
516      * <pre>
517      * getDocumentProperties().put(key, value);
518      * </pre>
519      * If <code>value</code> is <code>null</code> this method will
520      * remove the property.
521      *
522      * @param key the non-<code>null</code> key
523      * @param value the property value
524      * @see #getDocumentProperties
525      */

526     public final void putProperty(Object JavaDoc key, Object JavaDoc value) {
527     if (value != null) {
528         getDocumentProperties().put(key, value);
529     } else {
530             getDocumentProperties().remove(key);
531         }
532         if( key == TextAttribute.RUN_DIRECTION
533             && Boolean.TRUE.equals(getProperty(I18NProperty)) )
534         {
535             //REMIND - this needs to flip on the i18n property if run dir
536
//is rtl and the i18n property is not already on.
537
writeLock();
538             try {
539                 DefaultDocumentEvent e
540                     = new DefaultDocumentEvent(0, getLength(),
541                                                DocumentEvent.EventType.INSERT);
542                 updateBidi( e );
543             } finally {
544                 writeUnlock();
545             }
546         }
547     }
548
549     /**
550      * Removes some content from the document.
551      * Removing content causes a write lock to be held while the
552      * actual changes are taking place. Observers are notified
553      * of the change on the thread that called this method.
554      * <p>
555      * This method is thread safe, although most Swing methods
556      * are not. Please see
557      * <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">Threads
558      * and Swing</A> for more information.
559      *
560      * @param offs the starting offset >= 0
561      * @param len the number of characters to remove >= 0
562      * @exception BadLocationException the given remove position is not a valid
563      * position within the document
564      * @see Document#remove
565      */

566     public void remove(int offs, int len) throws BadLocationException JavaDoc {
567         DocumentFilter JavaDoc filter = getDocumentFilter();
568
569         writeLock();
570         try {
571             if (filter != null) {
572                 filter.remove(getFilterBypass(), offs, len);
573             }
574             else {
575                 handleRemove(offs, len);
576             }
577         } finally {
578             writeUnlock();
579         }
580     }
581
582     /**
583      * Performs the actual work of the remove. It is assumed the caller
584      * will have obtained a <code>writeLock</code> before invoking this.
585      */

586     void handleRemove(int offs, int len) throws BadLocationException JavaDoc {
587     if (len > 0) {
588             if (offs < 0 || (offs + len) > getLength()) {
589                 throw new BadLocationException JavaDoc("Invalid remove",
590                                                getLength() + 1);
591             }
592             DefaultDocumentEvent chng =
593             new DefaultDocumentEvent(offs, len, DocumentEvent.EventType.REMOVE);
594
595             boolean isComposedTextElement = false;
596             // Check whether the position of interest is the composed text
597
isComposedTextElement = Utilities.isComposedTextElement(this, offs);
598         
599             removeUpdate(chng);
600             UndoableEdit u = data.remove(offs, len);
601             if (u != null) {
602                 chng.addEdit(u);
603             }
604             postRemoveUpdate(chng);
605             // Mark the edit as done.
606
chng.end();
607             fireRemoveUpdate(chng);
608             // only fire undo if Content implementation supports it
609
// undo for the composed text is not supported for now
610
if ((u != null) && !isComposedTextElement) {
611                 fireUndoableEditUpdate(new UndoableEditEvent(this, chng));
612             }
613     }
614     }
615     
616     /**
617      * Indic, Thai, and surrogate char values require complex
618      * text layout and cursor support.
619      */

620     private static final boolean isComplex(char ch) {
621         return (ch >= '\u0900' && ch <= '\u0D7F') || // Indic
622
(ch >= '\u0E00' && ch <= '\u0E7F') || // Thai
623
(ch >= '\uD800' && ch <= '\uDFFF'); // surrogate value range
624
}
625
626     private static final boolean isComplex(char[] text, int start, int limit) {
627     for (int i = start; i < limit; ++i) {
628         if (isComplex(text[i])) {
629         return true;
630         }
631     }
632     return false;
633     }
634
635     /**
636      * Deletes the region of text from <code>offset</code> to
637      * <code>offset + length</code>, and replaces it with <code>text</code>.
638      * It is up to the implementation as to how this is implemented, some
639      * implementations may treat this as two distinct operations: a remove
640      * followed by an insert, others may treat the replace as one atomic
641      * operation.
642      *
643      * @param offset index of child element
644      * @param length length of text to delete, may be 0 indicating don't
645      * delete anything
646      * @param text text to insert, <code>null</code> indicates no text to insert
647      * @param attrs AttributeSet indicating attributes of inserted text,
648      * <code>null</code>
649      * is legal, and typically treated as an empty attributeset,
650      * but exact interpretation is left to the subclass
651      * @exception BadLocationException the given position is not a valid
652      * position within the document
653      * @since 1.4
654      */

655     public void replace(int offset, int length, String JavaDoc text,
656                         AttributeSet JavaDoc attrs) throws BadLocationException JavaDoc {
657         if (length == 0 && (text == null || text.length() == 0)) {
658             return;
659         }
660         DocumentFilter JavaDoc filter = getDocumentFilter();
661
662     writeLock();
663     try {
664             if (filter != null) {
665                 filter.replace(getFilterBypass(), offset, length, text,
666                                attrs);
667             }
668             else {
669                 if (length > 0) {
670                     remove(offset, length);
671                 }
672                 if (text != null && text.length() > 0) {
673                     insertString(offset, text, attrs);
674                 }
675             }
676         } finally {
677             writeUnlock();
678         }
679     }
680
681     /**
682      * Inserts some content into the document.
683      * Inserting content causes a write lock to be held while the
684      * actual changes are taking place, followed by notification
685      * to the observers on the thread that grabbed the write lock.
686      * <p>
687      * This method is thread safe, although most Swing methods
688      * are not. Please see
689      * <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">Threads
690      * and Swing</A> for more information.
691      *
692      * @param offs the starting offset >= 0
693      * @param str the string to insert; does nothing with null/empty strings
694      * @param a the attributes for the inserted content
695      * @exception BadLocationException the given insert position is not a valid
696      * position within the document
697      * @see Document#insertString
698      */

699     public void insertString(int offs, String JavaDoc str, AttributeSet JavaDoc a) throws BadLocationException JavaDoc {
700         if ((str == null) || (str.length() == 0)) {
701         return;
702     }
703         DocumentFilter JavaDoc filter = getDocumentFilter();
704
705     writeLock();
706     try {
707             if (filter != null) {
708                 filter.insertString(getFilterBypass(), offs, str, a);
709             }
710             else {
711                 handleInsertString(offs, str, a);
712             }
713         } finally {
714             writeUnlock();
715         }
716     }
717
718     /**
719      * Performs the actual work of inserting the text; it is assumed the
720      * caller has obtained a write lock before invoking this.
721      */

722     void handleInsertString(int offs, String JavaDoc str, AttributeSet JavaDoc a)
723                          throws BadLocationException JavaDoc {
724         if ((str == null) || (str.length() == 0)) {
725         return;
726     }
727         UndoableEdit u = data.insertString(offs, str);
728         DefaultDocumentEvent e =
729             new DefaultDocumentEvent(offs, str.length(), DocumentEvent.EventType.INSERT);
730         if (u != null) {
731             e.addEdit(u);
732         }
733         
734         // see if complex glyph layout support is needed
735
if( getProperty(I18NProperty).equals( Boolean.FALSE ) ) {
736             // if a default direction of right-to-left has been specified,
737
// we want complex layout even if the text is all left to right.
738
Object JavaDoc d = getProperty(TextAttribute.RUN_DIRECTION);
739             if ((d != null) && (d.equals(TextAttribute.RUN_DIRECTION_RTL))) {
740                 putProperty( I18NProperty, Boolean.TRUE);
741             } else {
742         char[] chars = str.toCharArray();
743         if (Bidi.requiresBidi(chars, 0, chars.length) ||
744             isComplex(chars, 0, chars.length)) {
745             //
746
putProperty( I18NProperty, Boolean.TRUE);
747                 }
748             }
749         }
750
751         insertUpdate(e, a);
752         // Mark the edit as done.
753
e.end();
754         fireInsertUpdate(e);
755         // only fire undo if Content implementation supports it
756
// undo for the composed text is not supported for now
757
if (u != null &&
758             (a == null || !a.isDefined(StyleConstants.ComposedTextAttribute))) {
759             fireUndoableEditUpdate(new UndoableEditEvent(this, e));
760         }
761     }
762
763     /**
764      * Gets a sequence of text from the document.
765      *
766      * @param offset the starting offset >= 0
767      * @param length the number of characters to retrieve >= 0
768      * @return the text
769      * @exception BadLocationException the range given includes a position
770      * that is not a valid position within the document
771      * @see Document#getText
772      */

773     public String JavaDoc getText(int offset, int length) throws BadLocationException JavaDoc {
774     if (length < 0) {
775         throw new BadLocationException JavaDoc("Length must be positive", length);
776     }
777     String JavaDoc str = data.getString(offset, length);
778     return str;
779     }
780
781     /**
782      * Fetches the text contained within the given portion
783      * of the document.
784      * <p>
785      * If the partialReturn property on the txt parameter is false, the
786      * data returned in the Segment will be the entire length requested and
787      * may or may not be a copy depending upon how the data was stored.
788      * If the partialReturn property is true, only the amount of text that
789      * can be returned without creating a copy is returned. Using partial
790      * returns will give better performance for situations where large
791      * parts of the document are being scanned. The following is an example
792      * of using the partial return to access the entire document:
793      * <p>
794      * <pre>
795      * &nbsp; int nleft = doc.getDocumentLength();
796      * &nbsp; Segment text = new Segment();
797      * &nbsp; int offs = 0;
798      * &nbsp; text.setPartialReturn(true);
799      * &nbsp; while (nleft > 0) {
800      * &nbsp; doc.getText(offs, nleft, text);
801      * &nbsp; // do something with text
802      * &nbsp; nleft -= text.count;
803      * &nbsp; offs += text.count;
804      * &nbsp; }
805      * </pre>
806      *
807      * @param offset the starting offset >= 0
808      * @param length the number of characters to retrieve >= 0
809      * @param txt the Segment object to retrieve the text into
810      * @exception BadLocationException the range given includes a position
811      * that is not a valid position within the document
812      */

813     public void getText(int offset, int length, Segment JavaDoc txt) throws BadLocationException JavaDoc {
814     if (length < 0) {
815         throw new BadLocationException JavaDoc("Length must be positive", length);
816     }
817     data.getChars(offset, length, txt);
818     }
819
820     /**
821      * Returns a position that will track change as the document
822      * is altered.
823      * <p>
824      * This method is thread safe, although most Swing methods
825      * are not. Please see
826      * <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">Threads
827      * and Swing</A> for more information.
828      *
829      * @param offs the position in the model >= 0
830      * @return the position
831      * @exception BadLocationException if the given position does not
832      * represent a valid location in the associated document
833      * @see Document#createPosition
834      */

835     public synchronized Position JavaDoc createPosition(int offs) throws BadLocationException JavaDoc {
836     return data.createPosition(offs);
837     }
838
839     /**
840      * Returns a position that represents the start of the document. The
841      * position returned can be counted on to track change and stay
842      * located at the beginning of the document.
843      *
844      * @return the position
845      */

846     public final Position JavaDoc getStartPosition() {
847     Position JavaDoc p;
848     try {
849         p = createPosition(0);
850     } catch (BadLocationException JavaDoc bl) {
851         p = null;
852     }
853     return p;
854     }
855     
856     /**
857      * Returns a position that represents the end of the document. The
858      * position returned can be counted on to track change and stay
859      * located at the end of the document.
860      *
861      * @return the position
862      */

863     public final Position JavaDoc getEndPosition() {
864     Position JavaDoc p;
865     try {
866         p = createPosition(data.length());
867     } catch (BadLocationException JavaDoc bl) {
868         p = null;
869     }
870     return p;
871     }
872
873     /**
874      * Gets all root elements defined. Typically, there
875      * will only be one so the default implementation
876      * is to return the default root element.
877      *
878      * @return the root element
879      */

880     public Element JavaDoc[] getRootElements() {
881     Element JavaDoc[] elems = new Element JavaDoc[2];
882     elems[0] = getDefaultRootElement();
883         elems[1] = getBidiRootElement();
884     return elems;
885     }
886
887     /**
888      * Returns the root element that views should be based upon
889      * unless some other mechanism for assigning views to element
890      * structures is provided.
891      *
892      * @return the root element
893      * @see Document#getDefaultRootElement
894      */

895     public abstract Element JavaDoc getDefaultRootElement();
896
897     // ---- local methods -----------------------------------------
898

899     /**
900      * Returns the <code>FilterBypass</code>. This will create one if one
901      * does not yet exist.
902      */

903     private DocumentFilter.FilterBypass JavaDoc getFilterBypass() {
904         if (filterBypass == null) {
905             filterBypass = new DefaultFilterBypass();
906         }
907         return filterBypass;
908     }
909
910     /**
911      * Returns the root element of the bidirectional structure for this
912      * document. Its children represent character runs with a given
913      * Unicode bidi level.
914      */

915     public Element JavaDoc getBidiRootElement() {
916         return bidiRoot;
917     }
918
919     /**
920      * Returns true if the text in the range <code>p0</code> to
921      * <code>p1</code> is left to right.
922      */

923     boolean isLeftToRight(int p0, int p1) {
924         if(!getProperty(I18NProperty).equals(Boolean.TRUE)) {
925         return true;
926     }
927     Element JavaDoc bidiRoot = getBidiRootElement();
928     int index = bidiRoot.getElementIndex(p0);
929     Element JavaDoc bidiElem = bidiRoot.getElement(index);
930     if(bidiElem.getEndOffset() >= p1) {
931         AttributeSet JavaDoc bidiAttrs = bidiElem.getAttributes();
932         return ((StyleConstants.getBidiLevel(bidiAttrs) % 2) == 0);
933     }
934     return true;
935     }
936
937     /**
938      * Get the paragraph element containing the given position. Sub-classes
939      * must define for themselves what exactly constitutes a paragraph. They
940      * should keep in mind however that a paragraph should at least be the
941      * unit of text over which to run the Unicode bidirectional algorithm.
942      *
943      * @param pos the starting offset >= 0
944      * @return the element */

945     public abstract Element