1 /*
2 * Copyright 1997-2006 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25 package javax.swing.text;
26
27 import java.util;
28 import java.io;
29 import java.awt.font.TextAttribute;
30 import java.text.Bidi;
31
32 import javax.swing.UIManager;
33 import javax.swing.undo;
34 import javax.swing.event.ChangeListener;
35 import javax.swing.event;
36 import javax.swing.tree.TreeNode;
37
38 import sun.font.BidiUtils;
39 import sun.swing.SwingUtilities2;
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 aquired 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<sup><font size="-2">TM</font></sup>
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 public abstract class AbstractDocument implements Document, Serializable {
101
102 /**
103 * Constructs a new <code>AbstractDocument</code>, wrapped around some
104 * specified content storage mechanism.
105 *
106 * @param data the content
107 */
108 protected AbstractDocument(Content data) {
109 this(data, StyleContext.getDefaultStyleContext());
110 }
111
112 /**
113 * Constructs a new <code>AbstractDocument</code>, wrapped around some
114 * specified content storage mechanism.
115 *
116 * @param data the content
117 * @param context the attribute context
118 */
119 protected AbstractDocument(Content data, AttributeContext context) {
120 this.data = data;
121 this.context = context;
122 bidiRoot = new BidiRootElement();
123
124 if (defaultI18NProperty == null) {
125 // determine default setting for i18n support
126 Object o = java.security.AccessController.doPrivileged(
127 new java.security.PrivilegedAction() {
128 public Object run() {
129 return System.getProperty(I18NProperty);
130 }
131 }
132 );
133 if (o != null) {
134 defaultI18NProperty = Boolean.valueOf((String)o);
135 } else {
136 defaultI18NProperty = Boolean.FALSE;
137 }
138 }
139 putProperty( I18NProperty, defaultI18NProperty);
140
141 //REMIND(bcb) This creates an initial bidi element to account for
142 //the \n that exists by default in the content. Doing it this way
143 //seems to expose a little too much knowledge of the content given
144 //to us by the sub-class. Consider having the sub-class' constructor
145 //make an initial call to insertUpdate.
146 writeLock();
147 try {
148 Element[] p = new Element[1];
149 p[0] = new BidiElement( bidiRoot, 0, 1, 0 );
150 bidiRoot.replace(0,0,p);
151 } finally {
152 writeUnlock();
153 }
154 }
155
156 /**
157 * Supports managing a set of properties. Callers
158 * can use the <code>documentProperties</code> dictionary
159 * to annotate the document with document-wide properties.
160 *
161 * @return a non-<code>null</code> <code>Dictionary</code>
162 * @see #setDocumentProperties
163 */
164 public Dictionary<Object,Object> getDocumentProperties() {
165 if (documentProperties == null) {
166 documentProperties = new Hashtable(2);
167 }
168 return documentProperties;
169 }
170
171 /**
172 * Replaces the document properties dictionary for this document.
173 *
174 * @param x the new dictionary
175 * @see #getDocumentProperties
176 */
177 public void setDocumentProperties(Dictionary<Object,Object> x) {
178 documentProperties = x;
179 }
180
181 /**
182 * Notifies all listeners that have registered interest for
183 * notification on this event type. The event instance
184 * is lazily created using the parameters passed into
185 * the fire method.
186 *
187 * @param e the event
188 * @see EventListenerList
189 */
190 protected void fireInsertUpdate(DocumentEvent e) {
191 notifyingListeners = true;
192 try {
193 // Guaranteed to return a non-null array
194 Object[] listeners = listenerList.getListenerList();
195 // Process the listeners last to first, notifying
196 // those that are interested in this event
197 for (int i = listeners.length-2; i>=0; i-=2) {
198 if (listeners[i]==DocumentListener.class) {
199 // Lazily create the event:
200 // if (e == null)
201 // e = new ListSelectionEvent(this, firstIndex, lastIndex);
202 ((DocumentListener)listeners[i+1]).insertUpdate(e);
203 }
204 }
205 } finally {
206 notifyingListeners = false;
207 }
208 }
209
210 /**
211 * Notifies all listeners that have registered interest for
212 * notification on this event type. The event instance
213 * is lazily created using the parameters passed into
214 * the fire method.
215 *
216 * @param e the event
217 * @see EventListenerList
218 */
219 protected void fireChangedUpdate(DocumentEvent e) {
220 notifyingListeners = true;
221 try {
222 // Guaranteed to return a non-null array
223 Object[] listeners = listenerList.getListenerList();
224 // Process the listeners last to first, notifying
225 // those that are interested in this event
226 for (int i = listeners.length-2; i>=0; i-=2) {
227 if (listeners[i]==DocumentListener.class) {
228 // Lazily create the event:
229 // if (e == null)
230 // e = new ListSelectionEvent(this, firstIndex, lastIndex);
231 ((DocumentListener)listeners[i+1]).changedUpdate(e);
232 }
233 }
234 } finally {
235 notifyingListeners = false;
236 }
237 }
238
239 /**
240 * Notifies all listeners that have registered interest for
241 * notification on this event type. The event instance
242 * is lazily created using the parameters passed into
243 * the fire method.
244 *
245 * @param e the event
246 * @see EventListenerList
247 */
248 protected void fireRemoveUpdate(DocumentEvent e) {
249 notifyingListeners = true;
250 try {
251 // Guaranteed to return a non-null array
252 Object[] listeners = listenerList.getListenerList();
253 // Process the listeners last to first, notifying
254 // those that are interested in this event
255 for (int i = listeners.length-2; i>=0; i-=2) {
256 if (listeners[i]==DocumentListener.class) {
257 // Lazily create the event:
258 // if (e == null)
259 // e = new ListSelectionEvent(this, firstIndex, lastIndex);
260 ((DocumentListener)listeners[i+1]).removeUpdate(e);
261 }
262 }
263 } finally {
264 notifyingListeners = false;
265 }
266 }
267
268 /**
269 * Notifies all listeners that have registered interest for
270 * notification on this event type. The event instance
271 * is lazily created using the parameters passed into
272 * the fire method.
273 *
274 * @param e the event
275 * @see EventListenerList
276 */
277 protected void fireUndoableEditUpdate(UndoableEditEvent e) {
278 // Guaranteed to return a non-null array
279 Object[] listeners = listenerList.getListenerList();
280 // Process the listeners last to first, notifying
281 // those that are interested in this event
282 for (int i = listeners.length-2; i>=0; i-=2) {
283 if (listeners[i]==UndoableEditListener.class) {
284 // Lazily create the event:
285 // if (e == null)
286 // e = new ListSelectionEvent(this, firstIndex, lastIndex);
287 ((UndoableEditListener)listeners[i+1]).undoableEditHappened(e);
288 }
289 }
290 }
291
292 /**
293 * Returns an array of all the objects currently registered
294 * as <code><em>Foo</em>Listener</code>s
295 * upon this document.
296 * <code><em>Foo</em>Listener</code>s are registered using the
297 * <code>add<em>Foo</em>Listener</code> method.
298 *
299 * <p>
300 * You can specify the <code>listenerType</code> argument
301 * with a class literal, such as
302 * <code><em>Foo</em>Listener.class</code>.
303 * For example, you can query a
304 * document <code>d</code>
305 * for its document listeners with the following code:
306 *
307 * <pre>DocumentListener[] mls = (DocumentListener[])(d.getListeners(DocumentListener.class));</pre>
308 *
309 * If no such listeners exist, this method returns an empty array.
310 *
311 * @param listenerType the type of listeners requested; this parameter
312 * should specify an interface that descends from
313 * <code>java.util.EventListener</code>
314 * @return an array of all objects registered as
315 * <code><em>Foo</em>Listener</code>s on this component,
316 * or an empty array if no such
317 * listeners have been added
318 * @exception ClassCastException if <code>listenerType</code>
319 * doesn't specify a class or interface that implements
320 * <code>java.util.EventListener</code>
321 *
322 * @see #getDocumentListeners
323 * @see #getUndoableEditListeners
324 *
325 * @since 1.3
326 */
327 public <T extends EventListener> T[] getListeners(Class<T> listenerType) {
328 return listenerList.getListeners(listenerType);
329 }
330
331 /**
332 * Gets the asynchronous loading priority. If less than zero,
333 * the document should not be loaded asynchronously.
334 *
335 * @return the asynchronous loading priority, or <code>-1</code>
336 * if the document should not be loaded asynchronously
337 */
338 public int getAsynchronousLoadPriority() {
339 Integer loadPriority = (Integer)
340 getProperty(AbstractDocument.AsyncLoadPriority);
341 if (loadPriority != null) {
342 return loadPriority.intValue();
343 }
344 return -1;
345 }
346
347 /**
348 * Sets the asynchronous loading priority.
349 * @param p the new asynchronous loading priority; a value
350 * less than zero indicates that the document should not be
351 * loaded asynchronously
352 */
353 public void setAsynchronousLoadPriority(int p) {
354 Integer loadPriority = (p >= 0) ? Integer.valueOf(p) : null;
355 putProperty(AbstractDocument.AsyncLoadPriority, loadPriority);
356 }
357
358 /**
359 * Sets the <code>DocumentFilter</code>. The <code>DocumentFilter</code>
360 * is passed <code>insert</code> and <code>remove</code> to conditionally
361 * allow inserting/deleting of the text. A <code>null</code> value
362 * indicates that no filtering will occur.
363 *
364 * @param filter the <code>DocumentFilter</code> used to constrain text
365 * @see #getDocumentFilter
366 * @since 1.4
367 */
368 public void setDocumentFilter(DocumentFilter filter) {
369 documentFilter = filter;
370 }
371
372 /**
373 * Returns the <code>DocumentFilter</code> that is responsible for
374 * filtering of insertion/removal. A <code>null</code> return value
375 * implies no filtering is to occur.
376 *
377 * @since 1.4
378 * @see #setDocumentFilter
379 * @return the DocumentFilter
380 */
381 public DocumentFilter getDocumentFilter() {
382 return documentFilter;
383 }
384
385 // --- Document methods -----------------------------------------
386
387 /**
388 * This allows the model to be safely rendered in the presence
389 * of currency, if the model supports being updated asynchronously.
390 * The given runnable will be executed in a way that allows it
391 * to safely read the model with no changes while the runnable
392 * is being executed. The runnable itself may <em>not</em>
393 * make any mutations.
394 * <p>
395 * This is implemented to aquire a read lock for the duration
396 * of the runnables execution. There may be multiple runnables
397 * executing at the same time, and all writers will be blocked
398 * while there are active rendering runnables. If the runnable
399 * throws an exception, its lock will be safely released.
400 * There is no protection against a runnable that never exits,
401 * which will effectively leave the document locked for it's
402 * lifetime.
403 * <p>
404 * If the given runnable attempts to make any mutations in
405 * this implementation, a deadlock will occur. There is
406 * no tracking of individual rendering threads to enable
407 * detecting this situation, but a subclass could incur
408 * the overhead of tracking them and throwing an error.
409 * <p>
410 * This method is thread safe, although most Swing methods
411 * are not. Please see
412 * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
413 * to Use Threads</A> for more information.
414 *
415 * @param r the renderer to execute
416 */
417 public void render(Runnable r) {
418 readLock();
419 try {
420 r.run();
421 } finally {
422 readUnlock();
423 }
424 }
425
426 /**
427 * Returns the length of the data. This is the number of
428 * characters of content that represents the users data.
429 *
430 * @return the length >= 0
431 * @see Document#getLength
432 */
433 public int getLength() {
434 return data.length() - 1;
435 }
436
437 /**
438 * Adds a document listener for notification of any changes.
439 *
440 * @param listener the <code>DocumentListener</code> to add
441 * @see Document#addDocumentListener
442 */
443 public void addDocumentListener(DocumentListener listener) {
444 listenerList.add(DocumentListener.class, listener);
445 }
446
447 /**
448 * Removes a document listener.
449 *
450 * @param listener the <code>DocumentListener</code> to remove
451 * @see Document#removeDocumentListener
452 */
453 public void removeDocumentListener(DocumentListener listener) {
454 listenerList.remove(DocumentListener.class, listener);
455 }
456
457 /**
458 * Returns an array of all the document listeners
459 * registered on this document.
460 *
461 * @return all of this document's <code>DocumentListener</code>s
462 * or an empty array if no document listeners are
463 * currently registered
464 *
465 * @see #addDocumentListener
466 * @see #removeDocumentListener
467 * @since 1.4
468 */
469 public DocumentListener[] getDocumentListeners() {
470 return (DocumentListener[])listenerList.getListeners(
471 DocumentListener.class);
472 }
473
474 /**
475 * Adds an undo listener for notification of any changes.
476 * Undo/Redo operations performed on the <code>UndoableEdit</code>
477 * will cause the appropriate DocumentEvent to be fired to keep
478 * the view(s) in sync with the model.
479 *
480 * @param listener the <code>UndoableEditListener</code> to add
481 * @see Document#addUndoableEditListener
482 */
483 public void addUndoableEditListener(UndoableEditListener listener) {
484 listenerList.add(UndoableEditListener.class, listener);
485 }
486
487 /**
488 * Removes an undo listener.
489 *
490 * @param listener the <code>UndoableEditListener</code> to remove
491 * @see Document#removeDocumentListener
492 */
493 public void removeUndoableEditListener(UndoableEditListener listener) {
494 listenerList.remove(UndoableEditListener.class, listener);
495 }
496
497 /**
498 * Returns an array of all the undoable edit listeners
499 * registered on this document.
500 *
501 * @return all of this document's <code>UndoableEditListener</code>s
502 * or an empty array if no undoable edit listeners are
503 * currently registered
504 *
505 * @see #addUndoableEditListener
506 * @see #removeUndoableEditListener
507 *
508 * @since 1.4
509 */
510 public UndoableEditListener[] getUndoableEditListeners() {
511 return (UndoableEditListener[])listenerList.getListeners(
512 UndoableEditListener.class);
513 }
514
515 /**
516 * A convenience method for looking up a property value. It is
517 * equivalent to:
518 * <pre>
519 * getDocumentProperties().get(key);
520 * </pre>
521 *
522 * @param key the non-<code>null</code> property key
523 * @return the value of this property or <code>null</code>
524 * @see #getDocumentProperties
525 */
526 public final Object getProperty(Object key) {
527 return getDocumentProperties().get(key);
528 }
529
530
531 /**
532 * A convenience method for storing up a property value. It is
533 * equivalent to:
534 * <pre>
535 * getDocumentProperties().put(key, value);
536 * </pre>
537 * If <code>value</code> is <code>null</code> this method will
538 * remove the property.
539 *
540 * @param key the non-<code>null</code> key
541 * @param value the property value
542 * @see #getDocumentProperties
543 */
544 public final void putProperty(Object key, Object value) {
545 if (value != null) {
546 getDocumentProperties().put(key, value);
547 } else {
548 getDocumentProperties().remove(key);
549 }
550 if( key == TextAttribute.RUN_DIRECTION
551 && Boolean.TRUE.equals(getProperty(I18NProperty)) )
552 {
553 //REMIND - this needs to flip on the i18n property if run dir
554 //is rtl and the i18n property is not already on.
555 writeLock();
556 try {
557 DefaultDocumentEvent e
558 = new DefaultDocumentEvent(0, getLength(),
559 DocumentEvent.EventType.INSERT);
560 updateBidi( e );
561 } finally {
562 writeUnlock();
563 }
564 }
565 }
566
567 /**
568 * Removes some content from the document.
569 * Removing content causes a write lock to be held while the
570 * actual changes are taking place. Observers are notified
571 * of the change on the thread that called this method.
572 * <p>
573 * This method is thread safe, although most Swing methods
574 * are not. Please see
575 * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
576 * to Use Threads</A> for more information.
577 *
578 * @param offs the starting offset >= 0
579 * @param len the number of characters to remove >= 0
580 * @exception BadLocationException the given remove position is not a valid
581 * position within the document
582 * @see Document#remove
583 */
584 public void remove(int offs, int len) throws BadLocationException {
585 DocumentFilter filter = getDocumentFilter();
586
587 writeLock();
588 try {
589 if (filter != null) {
590 filter.remove(getFilterBypass(), offs, len);
591 }
592 else {
593 handleRemove(offs, len);
594 }
595 } finally {
596 writeUnlock();
597 }
598 }
599
600 /**
601 * Performs the actual work of the remove. It is assumed the caller
602 * will have obtained a <code>writeLock</code> before invoking this.
603 */
604 void handleRemove(int offs, int len) throws BadLocationException {
605 if (len > 0) {
606 if (offs < 0 || (offs + len) > getLength()) {
607 throw new BadLocationException("Invalid remove",
608 getLength() + 1);
609 }
610 DefaultDocumentEvent chng =
611 new DefaultDocumentEvent(offs, len, DocumentEvent.EventType.REMOVE);
612
613 boolean isComposedTextElement = false;
614 // Check whether the position of interest is the composed text
615 isComposedTextElement = Utilities.isComposedTextElement(this, offs);
616
617 removeUpdate(chng);
618 UndoableEdit u = data.remove(offs, len);
619 if (u != null) {
620 chng.addEdit(u);
621 }
622 postRemoveUpdate(chng);
623 // Mark the edit as done.
624 chng.end();
625 fireRemoveUpdate(chng);
626 // only fire undo if Content implementation supports it
627 // undo for the composed text is not supported for now
628 if ((u != null) && !isComposedTextElement) {
629 fireUndoableEditUpdate(new UndoableEditEvent(this, chng));
630 }
631 }
632 }
633
634 /**
635 * Deletes the region of text from <code>offset</code> to
636 * <code>offset + length</code>, and replaces it with <code>text</code>.
637 * It is up to the implementation as to how this is implemented, some
638 * implementations may treat this as two distinct operations: a remove
639 * followed by an insert, others may treat the replace as one atomic
640 * operation.
641 *
642 * @param offset index of child element
643 * @param length length of text to delete, may be 0 indicating don't
644 * delete anything
645 * @param text text to insert, <code>null</code> indicates no text to insert
646 * @param attrs AttributeSet indicating attributes of inserted text,
647 * <code>null</code>
648 * is legal, and typically treated as an empty attributeset,
649 * but exact interpretation is left to the subclass
650 * @exception BadLocationException the given position is not a valid
651 * position within the document
652 * @since 1.4
653 */
654 public void replace(int offset, int length, String text,
655 AttributeSet attrs) throws BadLocationException {
656 if (length == 0 && (text == null || text.length() == 0)) {
657 return;
658 }
659 DocumentFilter filter = getDocumentFilter();
660
661 writeLock();
662 try {
663 if (filter != null) {
664 filter.replace(getFilterBypass(), offset, length, text,
665 attrs);
666 }
667 else {
668 if (length > 0) {
669 remove(offset, length);
670 }
671 if (text != null && text.length() > 0) {
672 insertString(offset, text, attrs);
673 }
674 }
675 } finally {
676 writeUnlock();
677 }
678 }
679
680 /**
681 * Inserts some content into the document.
682 * Inserting content causes a write lock to be held while the
683 * actual changes are taking place, followed by notification
684 * to the observers on the thread that grabbed the write lock.
685 * <p>
686 * This method is thread safe, although most Swing methods
687 * are not. Please see
688 * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
689 * to Use Threads</A> for more information.
690 *
691 * @param offs the starting offset >= 0
692 * @param str the string to insert; does nothing with null/empty strings
693 * @param a the attributes for the inserted content
694 * @exception BadLocationException the given insert position is not a valid
695 * position within the document
696 * @see Document#insertString
697 */
698 public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
699 if ((str == null) || (str.length() == 0)) {
700 return;
701 }
702 DocumentFilter filter = getDocumentFilter();
703
704 writeLock();
705 try {
706 if (filter != null) {
707 filter.insertString(getFilterBypass(), offs, str, a);
708 }
709 else {
710 handleInsertString(offs, str, a);
711 }
712 } finally {
713 writeUnlock();
714 }
715 }
716
717 /**
718 * Performs the actual work of inserting the text; it is assumed the
719 * caller has obtained a write lock before invoking this.
720 */
721 void handleInsertString(int offs, String str, AttributeSet a)
722 throws BadLocationException {
723 if ((str == null) || (str.length() == 0)) {
724 return;
725 }
726 UndoableEdit u = data.insertString(offs, str);
727 DefaultDocumentEvent e =
728 new DefaultDocumentEvent(offs, str.length(), DocumentEvent.EventType.INSERT);
729 if (u != null) {
730 e.addEdit(u);
731 }
732
733 // see if complex glyph layout support is needed
734 if( getProperty(I18NProperty).equals( Boolean.FALSE ) ) {
735 // if a default direction of right-to-left has been specified,
736 // we want complex layout even if the text is all left to right.
737 Object d = getProperty(TextAttribute.RUN_DIRECTION);
738 if ((d != null) && (d.equals(TextAttribute.RUN_DIRECTION_RTL))) {
739 putProperty( I18NProperty, Boolean.TRUE);
740 } else {
741 char[] chars = str.toCharArray();
742 if (SwingUtilities2.isComplexLayout(chars, 0, chars.length)) {
743 putProperty( I18NProperty, Boolean.TRUE);
744 }
745 }
746 }
747
748 insertUpdate(e, a);
749 // Mark the edit as done.
750 e.end();
751 fireInsertUpdate(e);
752 // only fire undo if Content implementation supports it
753 // undo for the composed text is not supported for now
754 if (u != null &&
755 (a == null || !a.isDefined(StyleConstants.ComposedTextAttribute))) {
756 fireUndoableEditUpdate(new UndoableEditEvent(this, e));
757 }
758 }
759
760 /**
761 * Gets a sequence of text from the document.
762 *
763 * @param offset the starting offset >= 0
764 * @param length the number of characters to retrieve >= 0
765 * @return the text
766 * @exception BadLocationException the range given includes a position
767 * that is not a valid position within the document
768 * @see Document#getText
769 */
770 public String getText(int offset, int length) throws BadLocationException {
771 if (length < 0) {
772 throw new BadLocationException("Length must be positive", length);
773 }
774 String str = data.getString(offset, length);
775 return str;
776 }
777
778 /**
779 * Fetches the text contained within the given portion
780 * of the document.
781 * <p>
782 * If the partialReturn property on the txt parameter is false, the
783 * data returned in the Segment will be the entire length requested and
784 * may or may not be a copy depending upon how the data was stored.
785 * If the partialReturn property is true, only the amount of text that
786 * can be returned without creating a copy is returned. Using partial
787 * returns will give better performance for situations where large
788 * parts of the document are being scanned. The following is an example
789 * of using the partial return to access the entire document:
790 * <p>
791 * <pre>
792 * int nleft = doc.getDocumentLength();
793 * Segment text = new Segment();
794 * int offs = 0;
795 * text.setPartialReturn(true);
796 * while (nleft > 0) {
797 * doc.getText(offs, nleft, text);
798 * // do something with text
799 * nleft -= text.count;
800 * offs += text.count;
801 * }
802 * </pre>
803 *
804 * @param offset the starting offset >= 0
805 * @param length the number of characters to retrieve >= 0
806 * @param txt the Segment object to retrieve the text into
807 * @exception BadLocationException the range given includes a position
808 * that is not a valid position within the document
809 */
810 public void getText(int offset, int length, Segment txt) throws BadLocationException {
811 if (length < 0) {
812 throw new BadLocationException("Length must be positive", length);
813 }
814 data.getChars(offset, length, txt);
815 }
816
817 /**
818 * Returns a position that will track change as the document
819 * is altered.
820 * <p>
821 * This method is thread safe, although most Swing methods
822 * are not. Please see
823 * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
824 * to Use Threads</A> for more information.
825 *
826 * @param offs the position in the model >= 0
827 * @return the position
828 * @exception BadLocationException if the given position does not
829 * represent a valid location in the associated document
830 * @see Document#createPosition
831 */
832 public synchronized Position createPosition(int offs) throws BadLocationException {
833 return data.createPosition(offs);
834 }
835
836 /**
837 * Returns a position that represents the start of the document. The
838 * position returned can be counted on to track change and stay
839 * located at the beginning of the document.
840 *
841 * @return the position
842 */
843 public final Position getStartPosition() {
844 Position p;
845 try {
846 p = createPosition(0);
847 } catch (BadLocationException bl) {
848 p = null;
849 }
850 return p;
851 }
852
853 /**
854 * Returns a position that represents the end of the document. The
855 * position returned can be counted on to track change and stay
856 * located at the end of the document.
857 *
858 * @return the position
859 */
860 public final Position getEndPosition() {
861 Position p;
862 try {
863 p = createPosition(data.length());
864 } catch (BadLocationException bl) {
865 p = null;
866 }
867 return p;
868 }
869
870 /**
871 * Gets all root elements defined. Typically, there
872 * will only be one so the default implementation
873 * is to return the default root element.
874 *
875 * @return the root element
876 */
877 public Element[] getRootElements() {
878 Element[] elems = new Element[2];
879 elems[0] = getDefaultRootElement();
880 elems[1] = getBidiRootElement();
881 return elems;
882 }
883
884 /**
885 * Returns the root element that views should be based upon
886 * unless some other mechanism for assigning views to element
887 * structures is provided.
888 *
889 * @return the root element
890 * @see Document#getDefaultRootElement
891 */
892 public abstract Element getDefaultRootElement();
893
894 // ---- local methods -----------------------------------------
895
896 /**
897 * Returns the <code>FilterBypass</code>. This will create one if one
898 * does not yet exist.
899 */
900 private DocumentFilter.FilterBypass getFilterBypass() {
901 if (filterBypass == null) {
902 filterBypass = new DefaultFilterBypass();
903 }
904 return filterBypass;
905 }
906
907 /**
908 * Returns the root element of the bidirectional structure for this
909 * document. Its children represent character runs with a given
910 * Unicode bidi level.
911 */
912 public Element getBidiRootElement() {
913 return bidiRoot;
914 }
915
916 /**
917 * Returns true if the text in the range <code>p0</code> to
918 * <code>p1</code> is left to right.
919 */
920 boolean isLeftToRight(int p0, int p1) {
921 if(!getProperty(I18NProperty).equals(Boolean.TRUE)) {
922 return true;
923 }
924 Element bidiRoot = getBidiRootElement();
925 int index = bidiRoot.getElementIndex(p0);
926 Element bidiElem = bidiRoot.getElement(index);
927 if(bidiElem.getEndOffset() >= p1) {
928 AttributeSet bidiAttrs = bidiElem.getAttributes();
929 return ((StyleConstants.getBidiLevel(bidiAttrs) % 2) == 0);
930 }
931 return true;
932 }
933
934 /**
935 * Get the paragraph element containing the given position. Sub-classes
936 * must define for themselves what exactly constitutes a paragraph. They
937 * should keep in mind however that a paragraph should at least be the
938 * unit of text over which to run the Unicode bidirectional algorithm.
939 *
940 * @param pos the starting offset >= 0
941 * @return the element */
942 public abstract Element getParagraphElement(int pos);
943
944
945 /**
946 * Fetches the context for managing attributes. This
947 * method effectively establishes the strategy used
948 * for compressing AttributeSet information.
949 *
950 * @return the context
951 */
952 protected final AttributeContext getAttributeContext() {
953 return context;
954 }
955
956 /**
957 * Updates document structure as a result of text insertion. This
958 * will happen within a write lock. If a subclass of
959 * this class reimplements this method, it should delegate to the
960 * superclass as well.
961 *
962 * @param chng a description of the change
963 * @param attr the attributes for the change
964 */
965 protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) {
966 if( getProperty(I18NProperty).equals( Boolean.TRUE ) )
967 updateBidi( chng );
968
969 // Check if a multi byte is encountered in the inserted text.
970 if (chng.type == DocumentEvent.EventType.INSERT &&
971 chng.getLength() > 0 &&
972 !Boolean.TRUE.equals(getProperty(MultiByteProperty))) {
973 Segment segment = SegmentCache.getSharedSegment();
974 try {
975 getText(chng.getOffset(), chng.getLength(), segment);
976 segment.first();
977 do {
978 if ((int)segment.current() > 255) {
979 putProperty(MultiByteProperty, Boolean.TRUE);
980 break;
981 }
982 } while (segment.next() != Segment.DONE);
983 } catch (BadLocationException ble) {
984 // Should never happen
985 }
986 SegmentCache.releaseSharedSegment(segment);
987 }
988 }
989
990 /**
991 * Updates any document structure as a result of text removal. This
992 * method is called before the text is actually removed from the Content.
993 * This will happen within a write lock. If a subclass
994 * of this class reimplements this method, it should delegate to the
995 * superclass as well.
996 *
997 * @param chng a description of the change
998 */
999 protected void removeUpdate(DefaultDocumentEvent chng) {
1000 }
1001
1002 /**
1003 * Updates any document structure as a result of text removal. This
1004 * method is called after the text has been removed from the Content.
1005 * This will happen within a write lock. If a subclass
1006 * of this class reimplements this method, it should delegate to the
1007 * superclass as well.
1008 *
1009 * @param chng a description of the change
1010 */
1011 protected void postRemoveUpdate(DefaultDocumentEvent chng) {
1012 if( getProperty(I18NProperty).equals( Boolean.TRUE ) )
1013 updateBidi( chng );
1014 }
1015
1016
1017 /**
1018 * Update the bidi element structure as a result of the given change
1019 * to the document. The given change will be updated to reflect the
1020 * changes made to the bidi structure.
1021 *
1022 * This method assumes that every offset in the model is contained in
1023 * exactly one paragraph. This method also assumes that it is called
1024 * after the change is made to the default element structure.
1025 */
1026 void updateBidi( DefaultDocumentEvent chng ) {
1027
1028 // Calculate the range of paragraphs affected by the change.
1029 int firstPStart;
1030 int lastPEnd;
1031 if( chng.type == DocumentEvent.EventType.INSERT
1032 || chng.type == DocumentEvent.EventType.CHANGE )
1033 {
1034 int chngStart = chng.getOffset();
1035 int chngEnd = chngStart + chng.getLength();
1036 firstPStart = getParagraphElement(chngStart).getStartOffset();
1037 lastPEnd = getParagraphElement(chngEnd).getEndOffset();
1038 } else if( chng.type == DocumentEvent.EventType.REMOVE ) {
1039 Element paragraph = getParagraphElement( chng.getOffset() );
1040 firstPStart = paragraph.getStartOffset();
1041 lastPEnd = paragraph.getEndOffset();
1042 } else {
1043 throw new Error("Internal error: unknown event type.");
1044 }
1045 //System.out.println("updateBidi: firstPStart = " + firstPStart + " lastPEnd = " + lastPEnd );
1046
1047
1048 // Calculate the bidi levels for the affected range of paragraphs. The
1049 // levels array will contain a bidi level for each character in the
1050 // affected text.
1051 byte levels[] = calculateBidiLevels( firstPStart, lastPEnd );
1052
1053
1054 Vector newElements = new Vector();
1055
1056 // Calculate the first span of characters in the affected range with
1057 // the same bidi level. If this level is the same as the level of the
1058 // previous bidi element (the existing bidi element containing
1059 // firstPStart-1), then merge in the previous element. If not, but
1060 // the previous element overlaps the affected range, truncate the
1061 // previous element at firstPStart.
1062 int firstSpanStart = firstPStart;
1063 int removeFromIndex = 0;
1064 if( firstSpanStart > 0 ) {
1065 int prevElemIndex = bidiRoot.getElementIndex(firstPStart-1);
1066 removeFromIndex = prevElemIndex;
1067 Element prevElem = bidiRoot.getElement(prevElemIndex);
1068 int prevLevel=StyleConstants.getBidiLevel(prevElem.getAttributes());
1069 //System.out.println("createbidiElements: prevElem= " + prevElem + " prevLevel= " + prevLevel + "level[0] = " + levels[0]);
1070 if( prevLevel==levels[0] ) {
1071 firstSpanStart = prevElem.getStartOffset();
1072 } else if( prevElem.getEndOffset() > firstPStart ) {
1073 newElements.addElement(new BidiElement(bidiRoot,
1074 prevElem.getStartOffset(),
1075 firstPStart, prevLevel));
1076 } else {
1077 removeFromIndex++;
1078 }
1079 }
1080
1081 int firstSpanEnd = 0;
1082 while((firstSpanEnd<levels.length) && (levels[firstSpanEnd]==levels[0]))
1083 firstSpanEnd++;
1084
1085
1086 // Calculate the last span of characters in the affected range with
1087 // the same bidi level. If this level is the same as the level of the
1088 // next bidi element (the existing bidi element containing lastPEnd),
1089 // then merge in the next element. If not, but the next element
1090 // overlaps the affected range, adjust the next element to start at
1091 // lastPEnd.
1092 int lastSpanEnd = lastPEnd;
1093 Element newNextElem = null;
1094 int removeToIndex = bidiRoot.getElementCount() - 1;
1095 if( lastSpanEnd <= getLength() ) {
1096 int nextElemIndex = bidiRoot.getElementIndex( lastPEnd );
1097 removeToIndex = nextElemIndex;
1098 Element nextElem = bidiRoot.getElement( nextElemIndex );
1099 int nextLevel = StyleConstants.getBidiLevel(nextElem.getAttributes());
1100 if( nextLevel == levels[levels.length-1] ) {
1101 lastSpanEnd = nextElem.getEndOffset();
1102 } else if( nextElem.getStartOffset() < lastPEnd ) {
1103 newNextElem = new BidiElement(bidiRoot, lastPEnd,
1104 nextElem.getEndOffset(),
1105 nextLevel);
1106 } else {
1107 removeToIndex--;
1108 }
1109 }
1110
1111 int lastSpanStart = levels.length;
1112 while( (lastSpanStart>firstSpanEnd)
1113 && (levels[lastSpanStart-1]==levels[levels.length-1]) )
1114 lastSpanStart--;
1115
1116
1117 // If the first and last spans are contiguous and have the same level,
1118 // merge them and create a single new element for the entire span.
1119 // Otherwise, create elements for the first and last spans as well as
1120 // any spans in between.
1121 if((firstSpanEnd==lastSpanStart)&&(levels[0]==levels[levels.length-1])){
1122 newElements.addElement(new BidiElement(bidiRoot, firstSpanStart,
1123 lastSpanEnd, levels[0]));
1124 } else {
1125 // Create an element for the first span.
1126 newElements.addElement(new BidiElement(bidiRoot, firstSpanStart,
1127 firstSpanEnd+firstPStart,
1128 levels[0]));
1129 // Create elements for the spans in between the first and last
1130 for( int i=firstSpanEnd; i<lastSpanStart; ) {
1131 //System.out.println("executed line 872");
1132 int j;
1133 for( j=i; (j<levels.length) && (levels[j] == levels[i]); j++ );
1134 newElements.addElement(new BidiElement(bidiRoot, firstPStart+i,
1135 firstPStart+j,
1136 (int)levels[i]));
1137 i=j;
1138 }
1139 // Create an element for the last span.
1140 newElements.addElement(new BidiElement(bidiRoot,
1141 lastSpanStart+firstPStart,
1142 lastSpanEnd,
1143 levels[levels.length-1]));
1144 }
1145
1146 if( newNextElem != null )
1147 newElements.addElement( newNextElem );
1148
1149
1150 // Calculate the set of existing bidi elements which must be
1151 // removed.
1152 int removedElemCount = 0;
1153 if( bidiRoot.getElementCount() > 0 ) {
1154 removedElemCount = removeToIndex - removeFromIndex + 1;
1155 }
1156 Element[] removedElems = new Element[removedElemCount];
1157 for( int i=0; i<removedElemCount; i++ ) {
1158 removedElems[i] = bidiRoot.getElement(removeFromIndex+i);
1159 }
1160
1161 Element[] addedElems = new Element[ newElements.size() ];
1162 newElements.copyInto( addedElems );
1163
1164 // Update the change record.
1165 ElementEdit ee = new ElementEdit( bidiRoot, removeFromIndex,
1166 removedElems, addedElems );
1167 chng.addEdit( ee );
1168
1169 // Update the bidi element structure.
1170 bidiRoot.replace( removeFromIndex, removedElems.length, addedElems );
1171 }
1172
1173
1174 /**
1175 * Calculate the levels array for a range of paragraphs.
1176 */
1177 private byte[] calculateBidiLevels( int firstPStart, int lastPEnd ) {
1178
1179 byte levels[] = new byte[ lastPEnd - firstPStart ];
1180 int levelsEnd = 0;
1181 Boolean defaultDirection = null;
1182 Object d = getProperty(TextAttribute.RUN_DIRECTION);
1183 if (d instanceof Boolean) {
1184 defaultDirection = (Boolean) d;
1185 }
1186
1187 // For each paragraph in the given range of paragraphs, get its
1188 // levels array and add it to the levels array for the entire span.
1189 for(int o=firstPStart; o<lastPEnd; ) {
1190 Element p = getParagraphElement( o );
1191 int pStart = p.getStartOffset();
1192 int pEnd = p.getEndOffset();
1193
1194 // default run direction for the paragraph. This will be
1195 // null if there is no direction override specified (i.e.
1196 // the direction will be determined from the content).
1197 Boolean direction = defaultDirection;
1198 d = p.getAttributes().getAttribute(TextAttribute.RUN_DIRECTION);
1199 if (d instanceof Boolean) {
1200 direction = (Boolean) d;
1201 }
1202
1203 //System.out.println("updateBidi: paragraph start = " + pStart + " paragraph end = " + pEnd);
1204
1205 // Create a Bidi over this paragraph then get the level
1206 // array.
1207 Segment seg = SegmentCache.getSharedSegment();
1208 try {
1209 getText(pStart, pEnd-pStart, seg);
1210 } catch (BadLocationException e ) {
1211 throw new Error("Internal error: " + e.toString());
1212 }
1213 // REMIND(bcb) we should really be using a Segment here.
1214 Bidi bidiAnalyzer;
1215 int bidiflag = Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT;
1216 if (direction != null) {
1217 if (TextAttribute.RUN_DIRECTION_LTR.equals(direction)) {
1218 bidiflag = Bidi.DIRECTION_LEFT_TO_RIGHT;
1219 } else {
1220 bidiflag = Bidi.DIRECTION_RIGHT_TO_LEFT;
1221 }
1222 }
1223 bidiAnalyzer = new Bidi(seg.array, seg.offset, null, 0, seg.count,
1224 bidiflag);
1225 BidiUtils.getLevels(bidiAnalyzer, levels, levelsEnd);
1226 levelsEnd += bidiAnalyzer.getLength();
1227
1228 o = p.getEndOffset();
1229 SegmentCache.releaseSharedSegment(seg);
1230 }
1231
1232 // REMIND(bcb) remove this code when debugging is done.
1233 if( levelsEnd != levels.length )
1234 throw new Error("levelsEnd assertion failed.");
1235
1236 return levels;
1237 }
1238
1239 /**
1240 * Gives a diagnostic dump.
1241 *
1242 * @param out the output stream
1243 */
1244 public void dump(PrintStream out) {
1245 Element root = getDefaultRootElement();
1246 if (root instanceof AbstractElement) {
1247 ((AbstractElement)root).dump(out, 0);
1248 }
1249 bidiRoot.dump(out,0);
1250 }
1251
1252 /**
1253 * Gets the content for the document.
1254 *
1255 * @return the content
1256 */
1257 protected final Content getContent() {
1258 return data;
1259 }
1260
1261 /**
1262 * Creates a document leaf element.
1263 * Hook through which elements are created to represent the
1264 * document structure. Because this implementation keeps
1265 * structure and content separate, elements grow automatically
1266 * when content is extended so splits of existing elements
1267 * follow. The document itself gets to decide how to generate
1268 * elements to give flexibility in the type of elements used.
1269 *
1270 * @param parent the parent element
1271 * @param a the attributes for the element
1272 * @param p0 the beginning of the range >= 0
1273 * @param p1 the end of the range >= p0
1274 * @return the new element
1275 */
1276 protected Element createLeafElement(Element parent, AttributeSet a, int p0, int p1) {
1277 return new LeafElement(parent, a, p0, p1);
1278 }
1279
1280 /**
1281 * Creates a document branch element, that can contain other elements.
1282 *
1283 * @param parent the parent element
1284 * @param a the attributes
1285 * @return the element
1286 */
1287 protected Element createBranchElement(Element parent, AttributeSet a) {
1288 return new BranchElement(parent, a);
1289 }
1290
1291 // --- Document locking ----------------------------------
1292
1293 /**
1294 * Fetches the current writing thread if there is one.
1295 * This can be used to distinguish whether a method is
1296 * being called as part of an existing modification or
1297 * if a lock needs to be acquired and a new transaction
1298 * started.
1299 *
1300 * @return the thread actively modifying the document
1301 * or <code>null</code> if there are no modifications in progress
1302 */
1303 protected synchronized final Thread getCurrentWriter() {
1304 return currWriter;
1305 }
1306
1307 /**
1308 * Acquires a lock to begin mutating the document this lock
1309 * protects. There can be no writing, notification of changes, or
1310 * reading going on in order to gain the lock. Additionally a thread is
1311 * allowed to gain more than one <code>writeLock</code>,
1312 * as long as it doesn't attempt to gain additional <code>writeLock</code>s
1313 * from within document notification. Attempting to gain a
1314 * <code>writeLock</code> from within a DocumentListener notification will
1315 * result in an <code>IllegalStateException</code>. The ability
1316 * to obtain more than one <code>writeLock</code> per thread allows
1317 * subclasses to gain a writeLock, perform a number of operations, then
1318 * release the lock.
1319 * <p>
1320 * Calls to <code>writeLock</code>
1321 * must be balanced with calls to <code>writeUnlock</code>, else the
1322 * <code>Document</code> will be left in a locked state so that no
1323 * reading or writing can be done.
1324 *
1325 * @exception IllegalStateException thrown on illegal lock
1326 * attempt. If the document is implemented properly, this can
1327 * only happen if a document listener attempts to mutate the
1328 * document. This situation violates the bean event model
1329 * where order of delivery is not guaranteed and all listeners
1330 * should be notified before further mutations are allowed.
1331 */
1332 protected synchronized final void writeLock() {
1333 try {
1334 while ((numReaders > 0) || (currWriter != null)) {
1335 if (Thread.currentThread() == currWriter) {
1336 if (notifyingListeners) {
1337 // Assuming one doesn't do something wrong in a
1338 // subclass this should only happen if a
1339 // DocumentListener tries to mutate the document.
1340 throw new IllegalStateException(
1341 "Attempt to mutate in notification");
1342 }
1343 numWriters++;
1344 return;
1345 }
1346 wait();
1347 }
1348 currWriter = Thread.currentThread();
1349 numWriters = 1;
1350 } catch (InterruptedException e) {
1351 throw new Error("Interrupted attempt to aquire write lock");
1352 }
1353 }
1354
1355 /**
1356 * Releases a write lock previously obtained via <code>writeLock</code>.
1357 * After decrementing the lock count if there are no oustanding locks
1358 * this will allow a new writer, or readers.
1359 *
1360 * @see #writeLock
1361 */
1362 protected synchronized final void writeUnlock() {
1363 if (--numWriters <= 0) {
1364 numWriters = 0;
1365 currWriter = null;
1366 notifyAll();
1367 }
1368 }
1369
1370 /**
1371 * Acquires a lock to begin reading some state from the
1372 * document. There can be multiple readers at the same time.
1373 * Writing blocks the readers until notification of the change
1374 * to the listeners has been completed. This method should
1375 * be used very carefully to avoid unintended compromise
1376 * of the document. It should always be balanced with a
1377 * <code>readUnlock</code>.
1378 *
1379 * @see #readUnlock
1380 */
1381 public synchronized final void readLock() {
1382 try {
1383 while (currWriter != null) {
1384 if (currWriter == Thread.currentThread()) {
1385 // writer has full read access.... may try to acquire
1386 // lock in notification
1387 return;
1388 }
1389 wait();
1390 }
1391 numReaders += 1;
1392 } catch (InterruptedException e) {
1393 throw new Error("Interrupted attempt to aquire read lock");
1394 }
1395 }
1396
1397 /**
1398 * Does a read unlock. This signals that one
1399 * of the readers is done. If there are no more readers
1400 * then writing can begin again. This should be balanced
1401 * with a readLock, and should occur in a finally statement
1402 * so that the balance is guaranteed. The following is an
1403 * example.
1404 * <pre><code>
1405 * readLock();
1406 * try {
1407 * // do something
1408 * } finally {
1409 * readUnlock();
1410 * }
1411 * </code></pre>
1412 *
1413 * @see #readLock
1414 */
1415 public synchronized final void readUnlock() {
1416 if (currWriter == Thread.currentThread()) {
1417 // writer has full read access.... may try to acquire
1418 // lock in notification
1419 return;
1420 }
1421 if (numReaders <= 0) {
1422 throw new StateInvariantError(BAD_LOCK_STATE);
1423 }
1424 numReaders -= 1;
1425 notify();
1426 }
1427
1428 // --- serialization ---------------------------------------------
1429
1430 private void readObject(ObjectInputStream s)
1431 throws ClassNotFoundException, IOException
1432 {
1433 s.defaultReadObject();
1434 listenerList = new EventListenerList();
1435
1436 // Restore bidi structure
1437 //REMIND(bcb) This creates an initial bidi element to account for
1438 //the \n that exists by default in the content.
1439 bidiRoot = new BidiRootElement();
1440 try {
1441 writeLock();
1442 Element[] p = new Element[1];
1443 p[0] = new BidiElement( bidiRoot, 0, 1, 0 );
1444 bidiRoot.replace(0,0,p);
1445 } finally {
1446 writeUnlock();
1447 }
1448 // At this point bidi root is only partially correct. To fully
1449 // restore it we need access to getDefaultRootElement. But, this
1450 // is created by the subclass and at this point will be null. We
1451 // thus use registerValidation.
1452 s.registerValidation(new ObjectInputValidation() {
1453 public void validateObject() {
1454 try {
1455 writeLock();
1456 DefaultDocumentEvent e = new DefaultDocumentEvent
1457 (0, getLength(),
1458 DocumentEvent.EventType.INSERT);
1459 updateBidi( e );
1460 }
1461 finally {
1462 writeUnlock();
1463 }
1464 }
1465 }, 0);
1466 }
1467
1468 // ----- member variables ------------------------------------------
1469
1470 private transient int numReaders;
1471 private transient Thread currWriter;
1472 /**
1473 * The number of writers, all obtained from <code>currWriter</code>.
1474 */
1475 private transient int numWriters;
1476 /**
1477 * True will notifying listeners.
1478 */
1479 private transient boolean notifyingListeners;
1480
1481 private static Boolean defaultI18NProperty;
1482
1483 /**
1484 * Storage for document-wide properties.
1485 */
1486 private Dictionary<Object,Object> documentProperties = null;
1487
1488 /**
1489 * The event listener list for the document.
1490 */
1491 protected EventListenerList listenerList = new EventListenerList();
1492
1493 /**
1494 * Where the text is actually stored, and a set of marks
1495 * that track change as the document is edited are managed.
1496 */
1497 private Content data;
1498
1499 /**
1500 * Factory for the attributes. This is the strategy for
1501 * attribute compression and control of the lifetime of
1502 * a set of attributes as a collection. This may be shared
1503 * with other documents.
1504 */
1505 private AttributeContext context;
1506
1507 /**
1508 * The root of the bidirectional structure for this document. Its children
1509 * represent character runs with the same Unicode bidi level.
1510 */
1511 private transient BranchElement bidiRoot;
1512
1513 /**
1514 * Filter for inserting/removing of text.
1515 */
1516 private DocumentFilter documentFilter;
1517
1518 /**
1519 * Used by DocumentFilter to do actual insert/remove.
1520 */
1521 private transient DocumentFilter.FilterBypass filterBypass;
1522
1523 private static final String BAD_LOCK_STATE = "document lock failure";
1524
1525 /**
1526 * Error message to indicate a bad location.
1527 */
1528 protected static final String BAD_LOCATION = "document location failure";
1529
1530 /**
1531 * Name of elements used to represent paragraphs
1532 */
1533 public static final String ParagraphElementName = "paragraph";
1534
1535 /**
1536 * Name of elements used to represent content
1537 */
1538 public static final String ContentElementName = "content";
1539
1540 /**
1541 * Name of elements used to hold sections (lines/paragraphs).
1542 */
1543 public static final String SectionElementName = "section";
1544
1545 /**
1546 * Name of elements used to hold a unidirectional run
1547 */
1548 public static final String BidiElementName = "bidi level";
1549
1550 /**
1551 * Name of the attribute used to specify element
1552 * names.
1553 */
1554 public static final String ElementNameAttribute = "$ename";
1555
1556 /**
1557 * Document property that indicates whether internationalization
1558 * functions such as text reordering or reshaping should be
1559 * performed. This property should not be publicly exposed,
1560 * since it is used for implementation convenience only. As a
1561 * side effect, copies of this property may be in its subclasses
1562 * that live in different packages (e.g. HTMLDocument as of now),
1563 * so those copies should also be taken care of when this property
1564 * needs to be modified.
1565 */
1566 static final String I18NProperty = "i18n";
1567
1568 /**
1569 * Document property that indicates if a character has been inserted
1570 * into the document that is more than one byte long. GlyphView uses
1571 * this to determine if it should use BreakIterator.
1572 */
1573 static final Object MultiByteProperty = "multiByte";
1574
1575 /**
1576 * Document property that indicates asynchronous loading is
1577 * desired, with the thread priority given as the value.
1578 */
1579 static final String AsyncLoadPriority = "load priority";
1580
1581 /**
1582 * Interface to describe a sequence of character content that
1583 * can be edited. Implementations may or may not support a
1584 * history mechanism which will be reflected by whether or not
1585 * mutations return an UndoableEdit implementation.
1586 * @see AbstractDocument
1587 */
1588 public interface Content {
1589
1590 /**
1591 * Creates a position within the content that will
1592 * track change as the content is mutated.
1593 *
1594 * @param offset the offset in the content >= 0
1595 * @return a Position
1596 * @exception BadLocationException for an invalid offset
1597 */
1598 public Position createPosition(int offset) throws BadLocationException;
1599
1600 /**
1601 * Current length of the sequence of character content.
1602 *
1603 * @return the length >= 0
1604 */
1605 public int length();
1606
1607 /**
1608 * Inserts a string of characters into the sequence.
1609 *
1610 * @param where offset into the sequence to make the insertion >= 0
1611 * @param str string to insert
1612 * @return if the implementation supports a history mechanism,
1613 * a reference to an <code>Edit</code> implementation will be returned,
1614 * otherwise returns <code>null</code>
1615 * @exception BadLocationException thrown if the area covered by
1616 * the arguments is not contained in the character sequence
1617 */
1618 public UndoableEdit insertString(int where, String str) throws BadLocationException;
1619
1620 /**
1621 * Removes some portion of the sequence.
1622 *
1623 * @param where The offset into the sequence to make the
1624 * insertion >= 0.
1625 * @param nitems The number of items in the sequence to remove >= 0.
1626 * @return If the implementation supports a history mechansim,
1627 * a reference to an Edit implementation will be returned,
1628 * otherwise null.
1629 * @exception BadLocationException Thrown if the area covered by
1630 * the arguments is not contained in the character sequence.
1631 */
1632 public UndoableEdit remove(int where, int nitems) throws BadLocationException;
1633
1634 /**
1635 * Fetches a string of characters contained in the sequence.
1636 *
1637 * @param where Offset into the sequence to fetch >= 0.
1638 * @param len number of characters to copy >= 0.
1639 * @return the string
1640 * @exception BadLocationException Thrown if the area covered by
1641 * the arguments is not contained in the character sequence.
1642 */
1643 public String getString(int where, int len) throws BadLocationException;
1644
1645 /**
1646 * Gets a sequence of characters and copies them into a Segment.
1647 *
1648 * @param where the starting offset >= 0
1649 * @param len the number of characters >= 0
1650 * @param txt the target location to copy into
1651 * @exception BadLocationException Thrown if the area covered by
1652 * the arguments is not contained in the character sequence.
1653 */
1654 public void getChars(int where, int len, Segment txt) throws BadLocationException;
1655 }
1656
1657 /**
1658 * An interface that can be used to allow MutableAttributeSet
1659 * implementations to use pluggable attribute compression
1660 * techniques. Each mutation of the attribute set can be
1661 * used to exchange a previous AttributeSet instance with
1662 * another, preserving the possibility of the AttributeSet
1663 * remaining immutable. An implementation is provided by
1664 * the StyleContext class.
1665 *
1666 * The Element implementations provided by this class use
1667 * this interface to provide their MutableAttributeSet
1668 * implementations, so that different AttributeSet compression
1669 * techniques can be employed. The method
1670 * <code>getAttributeContext</code> should be implemented to
1671 * return the object responsible for implementing the desired
1672 * compression technique.
1673 *
1674 * @see StyleContext
1675 */
1676 public interface AttributeContext {
1677
1678 /**
1679 * Adds an attribute to the given set, and returns
1680 * the new representative set.
1681 *
1682 * @param old the old attribute set
1683 * @param name the non-null attribute name
1684 * @param value the attribute value
1685 * @return the updated attribute set
1686 * @see MutableAttributeSet#addAttribute
1687 */
1688 public AttributeSet addAttribute(AttributeSet old, Object name, Object value);
1689
1690 /**
1691 * Adds a set of attributes to the element.
1692 *
1693 * @param old the old attribute set
1694 * @param attr the attributes to add
1695 * @return the updated attribute set
1696 * @see MutableAttributeSet#addAttribute
1697 */
1698 public AttributeSet addAttributes(AttributeSet old, AttributeSet attr);
1699
1700 /**
1701 * Removes an attribute from the set.
1702 *
1703 * @param old the old attribute set
1704 * @param name the non-null attribute name
1705 * @return the updated attribute set
1706 * @see MutableAttributeSet#removeAttribute
1707 */
1708 public AttributeSet removeAttribute(AttributeSet old, Object name);
1709
1710 /**
1711 * Removes a set of attributes for the element.
1712 *
1713 * @param old the old attribute set
1714 * @param names the attribute names
1715 * @return the updated attribute set
1716 * @see MutableAttributeSet#removeAttributes
1717 */
1718 public AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names);
1719
1720 /**
1721 * Removes a set of attributes for the element.
1722 *
1723 * @param old the old attribute set
1724 * @param attrs the attributes
1725 * @return the updated attribute set
1726 * @see MutableAttributeSet#removeAttributes
1727 */
1728 public AttributeSet removeAttributes(AttributeSet old, AttributeSet attrs);
1729
1730 /**
1731 * Fetches an empty AttributeSet.
1732 *
1733 * @return the attribute set
1734 */
1735 public AttributeSet getEmptySet();
1736
1737 /**
1738 * Reclaims an attribute set.
1739 * This is a way for a MutableAttributeSet to mark that it no
1740 * longer need a particular immutable set. This is only necessary
1741 * in 1.1 where there are no weak references. A 1.1 implementation
1742 * would call this in its finalize method.
1743 *
1744 * @param a the attribute set to reclaim
1745 */
1746 public void reclaim(AttributeSet a);
1747 }
1748
1749 /**
1750 * Implements the abstract part of an element. By default elements
1751 * support attributes by having a field that represents the immutable
1752 * part of the current attribute set for the element. The element itself
1753 * implements MutableAttributeSet which can be used to modify the set
1754 * by fetching a new immutable set. The immutable sets are provided
1755 * by the AttributeContext associated with the document.
1756 * <p>
1757 * <strong>Warning:</strong>
1758 * Serialized objects of this class will not be compatible with
1759 * future Swing releases. The current serialization support is
1760 * appropriate for short term storage or RMI between applications running
1761 * the same version of Swing. As of 1.4, support for long term storage
1762 * of all JavaBeans<sup><font size="-2">TM</font></sup>
1763 * has been added to the <code>java.beans</code> package.
1764 * Please see {@link java.beans.XMLEncoder}.
1765 */
1766 public abstract class AbstractElement implements Element, MutableAttributeSet, Serializable, TreeNode {
1767
1768 /**
1769 * Creates a new AbstractElement.
1770 *
1771 * @param parent the parent element
1772 * @param a the attributes for the element
1773 * @since 1.4
1774 */
1775 public AbstractElement(Element parent, AttributeSet a) {
1776 this.parent = parent;
1777 attributes = getAttributeContext().getEmptySet();
1778 if (a != null) {
1779 addAttributes(a);
1780 }
1781 }
1782
1783 private final void indent(PrintWriter out, int n) {
1784 for (int i = 0; i < n; i++) {
1785 out.print(" ");
1786 }
1787 }
1788
1789 /**
1790 * Dumps a debugging representation of the element hierarchy.
1791 *
1792 * @param psOut the output stream
1793 * @param indentAmount the indentation level >= 0
1794 */
1795 public void dump(PrintStream psOut, int indentAmount) {
1796 PrintWriter out;
1797 try {
1798 out = new PrintWriter(new OutputStreamWriter(psOut,"JavaEsc"),
1799 true);
1800 } catch (UnsupportedEncodingException e){
1801 out = new PrintWriter(psOut,true);
1802 }
1803 indent(out, indentAmount);
1804 if (getName() == null) {
1805 out.print("<??");
1806 } else {
1807 out.print("<" + getName());
1808 }
1809 if (getAttributeCount() > 0) {
1810 out.println("");
1811 // dump the attributes
1812 Enumeration names = attributes.getAttributeNames();
1813 while (names.hasMoreElements()) {
1814 Object name = names.nextElement();
1815 indent(out, indentAmount + 1);
1816 out.println(name + "=" + getAttribute(name));
1817 }
1818 indent(out, indentAmount);
1819 }
1820 out.println(">");
1821
1822 if (isLeaf()) {
1823 indent(out, indentAmount+1);
1824 out.print("[" + getStartOffset() + "," + getEndOffset() + "]");
1825 Content c = getContent();
1826 try {
1827 String contentStr = c.getString(getStartOffset(),
1828 getEndOffset() - getStartOffset())/*.trim()*/;
1829 if (contentStr.length() > 40) {
1830 contentStr = contentStr.substring(0, 40) + "...";
1831 }
1832 out.println("["+contentStr+"]");
1833 } catch (BadLocationException e) {
1834 ;
1835 }
1836
1837 } else {
1838 int n = getElementCount();
1839 for (int i = 0; i < n; i++) {
1840 AbstractElement e = (AbstractElement) getElement(i);
1841 e.dump(psOut, indentAmount+1);
1842 }
1843 }
1844 }
1845
1846 // --- AttributeSet ----------------------------
1847 // delegated to the immutable field "attributes"
1848
1849 /**
1850 * Gets the number of attributes that are defined.
1851 *
1852 * @return the number of attributes >= 0
1853 * @see AttributeSet#getAttributeCount
1854 */
1855 public int getAttributeCount() {
1856 return attributes.getAttributeCount();
1857 }
1858
1859 /**
1860 * Checks whether a given attribute is defined.
1861 *
1862 * @param attrName the non-null attribute name
1863 * @return true if the attribute is defined
1864 * @see AttributeSet#isDefined
1865 */
1866 public boolean isDefined(Object attrName) {
1867 return attributes.isDefined(attrName);
1868 }
1869
1870 /**
1871 * Checks whether two attribute sets are equal.
1872 *
1873 * @param attr the attribute set to check against
1874 * @return true if the same
1875 * @see AttributeSet#isEqual
1876 */
1877 public boolean isEqual(AttributeSet attr) {
1878 return attributes.isEqual(attr);
1879 }
1880
1881 /**
1882 * Copies a set of attributes.
1883 *
1884 * @return the copy
1885 * @see AttributeSet#copyAttributes
1886 */
1887 public AttributeSet copyAttributes() {
1888 return attributes.copyAttributes();
1889 }
1890
1891 /**
1892 * Gets the value of an attribute.
1893 *
1894 * @param attrName the non-null attribute name
1895 * @return the attribute value
1896 * @see AttributeSet#getAttribute
1897 */
1898 public Object getAttribute(Object attrName) {
1899 Object value = attributes.getAttribute(attrName);
1900 if (value == null) {
1901 // The delegate nor it's resolvers had a match,
1902 // so we'll try to resolve through the parent
1903 // element.
1904 AttributeSet a = (parent != null) ? parent.getAttributes() : null;
1905 if (a != null) {
1906 value = a.getAttribute(attrName);
1907 }
1908 }
1909 return value;
1910 }
1911
1912 /**
1913 * Gets the names of all attributes.
1914 *
1915 * @return the attribute names as an enumeration
1916 * @see AttributeSet#getAttributeNames
1917 */
1918 public Enumeration<?> getAttributeNames() {
1919 return attributes.getAttributeNames();
1920 }
1921
1922 /**
1923 * Checks whether a given attribute name/value is defined.
1924 *
1925 * @param name the non-null attribute name
1926 * @param value the attribute value
1927 * @return true if the name/value is defined
1928 * @see AttributeSet#containsAttribute
1929 */
1930 public boolean containsAttribute(Object name, Object value) {
1931 return attributes.containsAttribute(name, value);
1932 }
1933
1934
1935 /**
1936 * Checks whether the element contains all the attributes.
1937 *
1938 * @param attrs the attributes to check
1939 * @return true if the element contains all the attributes
1940 * @see AttributeSet#containsAttributes
1941 */
1942 public boolean containsAttributes(AttributeSet attrs) {
1943 return attributes.containsAttributes(attrs);
1944 }
1945
1946 /**
1947 * Gets the resolving parent.
1948 * If not overridden, the resolving parent defaults to
1949 * the parent element.
1950 *
1951 * @return the attributes from the parent, <code>null</code> if none
1952 * @see AttributeSet#getResolveParent
1953 */
1954 public AttributeSet getResolveParent() {
1955 AttributeSet a = attributes.getResolveParent();
1956 if ((a == null) && (parent != null)) {
1957 a = parent.getAttributes();
1958 }
1959 return a;
1960 }
1961
1962 // --- MutableAttributeSet ----------------------------------
1963 // should fetch a new immutable record for the field
1964 // "attributes".
1965
1966 /**
1967 * Adds an attribute to the element.
1968 *
1969 * @param name the non-null attribute name
1970 * @param value the attribute value
1971 * @see MutableAttributeSet#addAttribute
1972 */
1973 public void addAttribute(Object name, Object value) {
1974 checkForIllegalCast();
1975 AttributeContext context = getAttributeContext();
1976 attributes = context.addAttribute(attributes, name, value);
1977 }
1978
1979 /**
1980 * Adds a set of attributes to the element.
1981 *
1982 * @param attr the attributes to add
1983 * @see MutableAttributeSet#addAttribute
1984 */
1985 public void addAttributes(AttributeSet attr) {
1986 checkForIllegalCast();
1987 AttributeContext context = getAttributeContext();
1988 attributes = context.addAttributes(attributes, attr);
1989 }
1990
1991 /**
1992 * Removes an attribute from the set.
1993 *
1994 * @param name the non-null attribute name
1995 * @see MutableAttributeSet#removeAttribute
1996 */
1997 public void removeAttribute(Object name) {
1998 checkForIllegalCast();
1999 AttributeContext context = getAttributeContext();
2000 attributes = context.removeAttribute(attributes, name);
2001 }
2002
2003 /**
2004 * Removes a set of attributes for the element.
2005 *
2006 * @param names the attribute names
2007 * @see MutableAttributeSet#removeAttributes
2008 */
2009 public void removeAttributes(Enumeration<?> names) {
2010 checkForIllegalCast();
2011 AttributeContext context = getAttributeContext();
2012 attributes = context.removeAttributes(attributes, names);
2013 }
2014
2015 /**
2016 * Removes a set of attributes for the element.
2017 *
2018 * @param attrs the attributes
2019 * @see MutableAttributeSet#removeAttributes
2020 */
2021 public void removeAttributes(AttributeSet attrs) {
2022 checkForIllegalCast();
2023 AttributeContext context = getAttributeContext();
2024 if (attrs == this) {
2025 attributes = context.getEmptySet();
2026 } else {
2027 attributes = context.removeAttributes(attributes, attrs);
2028 }
2029 }
2030
2031 /**
2032 * Sets the resolving parent.
2033 *
2034 * @param parent the parent, null if none
2035 * @see MutableAttributeSet#setResolveParent
2036 */
2037 public void setResolveParent(AttributeSet parent) {
2038 checkForIllegalCast();
2039 AttributeContext context = getAttributeContext();
2040 if (parent != null) {
2041 attributes =
2042 context.addAttribute(attributes, StyleConstants.ResolveAttribute,
2043 parent);
2044 } else {
2045 attributes =
2046 context.removeAttribute(attributes, StyleConstants.ResolveAttribute);
2047 }
2048 }
2049
2050 private final void checkForIllegalCast() {
2051 Thread t = getCurrentWriter();
2052 if ((t == null) || (t != Thread.currentThread())) {
2053 throw new StateInvariantError("Illegal cast to MutableAttributeSet");
2054 }
2055 }
2056
2057 // --- Element methods -------------------------------------
2058
2059 /**
2060 * Retrieves the underlying model.
2061 *
2062 * @return the model
2063 */
2064 public Document getDocument() {
2065 return AbstractDocument.this;
2066 }
2067
2068 /**
2069 * Gets the parent of the element.
2070 *
2071 * @return the parent
2072 */
2073 public Element getParentElement() {
2074 return parent;
2075 }
2076
2077 /**
2078 * Gets the attributes for the element.
2079 *
2080 * @return the attribute set
2081 */
2082 public AttributeSet getAttributes() {
2083 return this;
2084 }
2085
2086 /**
2087 * Gets the name of the element.
2088 *
2089 * @return the name, null if none
2090 */
2091 public String getName() {
2092 if (attributes.isDefined(ElementNameAttribute)) {
2093 return (String) attributes.getAttribute(ElementNameAttribute);
2094 }
2095 return null;
2096 }
2097
2098 /**
2099 * Gets the starting offset in the model for the element.
2100 *
2101 * @return the offset >= 0
2102 */
2103 public abstract int getStartOffset();
2104
2105 /**
2106 * Gets the ending offset in the model for the element.
2107 *
2108 * @return the offset >= 0
2109 */
2110 public abstract int getEndOffset();
2111
2112 /**
2113 * Gets a child element.
2114 *
2115 * @param index the child index, >= 0 && < getElementCount()
2116 * @return the child element
2117 */
2118 public abstract Element getElement(int index);
2119
2120 /**
2121 * Gets the number of children for the element.
2122 *
2123 * @return the number of children >= 0
2124 */
2125 public abstract int getElementCount();
2126
2127 /**
2128 * Gets the child element index closest to the given model offset.
2129 *
2130 * @param offset the offset >= 0
2131 * @return the element index >= 0
2132 */
2133 public abstract int getElementIndex(int offset);
2134
2135 /**
2136 * Checks whether the element is a leaf.
2137 *
2138 * @return true if a leaf
2139 */
2140 public abstract boolean isLeaf();
2141
2142 // --- TreeNode methods -------------------------------------
2143
2144 /**
2145 * Returns the child <code>TreeNode</code> at index
2146 * <code>childIndex</code>.
2147 */
2148 public TreeNode getChildAt(int childIndex) {
2149 return (TreeNode)getElement(childIndex);
2150 }
2151
2152 /**
2153 * Returns the number of children <code>TreeNode</code>'s
2154 * receiver contains.
2155 * @return the number of children <code>TreeNodews</code>'s
2156 * receiver contains
2157 */
2158 public int getChildCount() {
2159 return getElementCount();
2160 }
2161
2162 /**
2163 * Returns the parent <code>TreeNode</code> of the receiver.
2164 * @return the parent <code>TreeNode</code> of the receiver
2165 */
2166 public TreeNode getParent() {
2167 return (TreeNode)getParentElement();
2168 }
2169
2170 /**
2171 * Returns the index of <code>node</code> in the receivers children.
2172 * If the receiver does not contain <code>node</code>, -1 will be
2173 * returned.
2174 * @param node the location of interest
2175 * @return the index of <code>node</code> in the receiver's
2176 * children, or -1 if absent
2177 */
2178 public int getIndex(TreeNode node) {
2179 for(int counter = getChildCount() - 1; counter >= 0; counter--)
2180 if(getChildAt(counter) == node)
2181 return counter;
2182 return -1;
2183 }
2184
2185 /**
2186 * Returns true if the receiver allows children.
2187 * @return true if the receiver allows children, otherwise false
2188 */
2189 public abstract boolean getAllowsChildren();
2190
2191
2192 /**
2193 * Returns the children of the receiver as an
2194 * <code>Enumeration</code>.
2195 * @return the children of the receiver as an <code>Enumeration</code>
2196 */
2197 public abstract Enumeration children();
2198
2199
2200 // --- serialization ---------------------------------------------
2201
2202 private void writeObject(ObjectOutputStream s) throws IOException {
2203 s.defaultWriteObject();
2204 StyleContext.writeAttributeSet(s, attributes);
2205 }
2206
2207 private void readObject(ObjectInputStream s)
2208 throws ClassNotFoundException, IOException
2209 {
2210 s.defaultReadObject();
2211 MutableAttributeSet attr = new SimpleAttributeSet();
2212 StyleContext.readAttributeSet(s, attr);
2213 AttributeContext context = getAttributeContext();
2214 attributes = context.addAttributes(SimpleAttributeSet.EMPTY, attr);
2215 }
2216
2217 // ---- variables -----------------------------------------------------
2218
2219 private Element parent;
2220 private transient AttributeSet attributes;
2221
2222 }
2223
2224 /**
2225 * Implements a composite element that contains other elements.
2226 * <p>
2227 * <strong>Warning:</strong>
2228 * Serialized objects of this class will not be compatible with
2229 * future Swing releases. The current serialization support is
2230 * appropriate for short term storage or RMI between applications running
2231 * the same version of Swing. As of 1.4, support for long term storage
2232 * of all JavaBeans<sup><font size="-2">TM</font></sup>
2233 * has been added to the <code>java.beans</code> package.
2234 * Please see {@link java.beans.XMLEncoder}.
2235 */
2236 public class BranchElement extends AbstractElement {
2237
2238 /**
2239 * Constructs a composite element that initially contains
2240 * no children.
2241 *
2242 * @param parent The parent element
2243 * @param a the attributes for the element
2244 * @since 1.4
2245 */
2246 public BranchElement(Element parent, AttributeSet a) {
2247 super(parent, a);
2248 children = new AbstractElement[1];
2249 nchildren = 0;
2250 lastIndex = -1;
2251 }
2252
2253 /**
2254 * Gets the child element that contains
2255 * the given model position.
2256 *
2257 * @param pos the position >= 0
2258 * @return the element, null if none
2259 */
2260 public Element positionToElement(int pos) {
2261 int index = getElementIndex(pos);
2262 Element child = children[index];
2263 int p0 = child.getStartOffset();
2264 int p1 = child.getEndOffset();
2265 if ((pos >= p0) && (pos < p1)) {
2266 return child;
2267 }
2268 return null;
2269 }
2270
2271 /**
2272 * Replaces content with a new set of elements.
2273 *
2274 * @param offset the starting offset >= 0
2275 * @param length the length to replace >= 0
2276 * @param elems the new elements
2277 */
2278 public void replace(int offset, int length, Element[] elems) {
2279 int delta = elems.length - length;
2280 int src = offset + length;
2281 int nmove = nchildren - src;
2282 int dest = src + delta;
2283 if ((nchildren + delta) >= children.length) {
2284 // need to grow the array
2285 int newLength = Math.max(2*children.length, nchildren + delta);
2286 AbstractElement[] newChildren = new AbstractElement[newLength];
2287 System.arraycopy(children, 0, newChildren, 0, offset);
2288 System.arraycopy(elems, 0, newChildren, offset, elems.length);
2289 System.arraycopy(children, src, newChildren, dest, nmove);
2290 children = newChildren;
2291 } else {
2292 // patch the existing array
2293 System.arraycopy(children, src, children, dest, nmove);
2294 System.arraycopy(elems, 0, children, offset, elems.length);
2295 }
2296 nchildren = nchildren + delta;
2297 }
2298
2299 /**
2300 * Converts the element to a string.
2301 *
2302 * @return the string
2303 */
2304 public String toString() {
2305 return "BranchElement(" + getName() + ") " + getStartOffset() + "," +
2306 getEndOffset() + "\n";
2307 }
2308
2309 // --- Element methods -----------------------------------
2310
2311 /**
2312 * Gets the element name.
2313 *
2314 * @return the element name
2315 */
2316 public String getName() {
2317 String nm = super.getName();
2318 if (nm == null) {
2319 nm = ParagraphElementName;
2320 }
2321 return nm;
2322 }
2323
2324 /**
2325 * Gets the starting offset in the model for the element.
2326 *
2327 * @return the offset >= 0
2328 */
2329 public int getStartOffset() {
2330 return children[0].getStartOffset();
2331 }
2332
2333 /**
2334 * Gets the ending offset in the model for the element.
2335 * @throws NullPointerException if this element has no children
2336 *
2337 * @return the offset >= 0
2338 */
2339 public int getEndOffset() {
2340 Element child =
2341 (nchildren > 0) ? children[nchildren - 1] : children[0];
2342 return child.getEndOffset();
2343 }
2344
2345 /**
2346 * Gets a child element.
2347 *
2348 * @param index the child index, >= 0 && < getElementCount()
2349 * @return the child element, null if none
2350 */
2351 public Element getElement(int index) {
2352 if (index < nchildren) {
2353 return children[index];
2354 }
2355 return null;
2356 }
2357
2358 /**
2359 * Gets the number of children for the element.
2360 *
2361 * @return the number of children >= 0
2362 */
2363 public int getElementCount() {
2364 return nchildren;
2365 }
2366
2367 /**
2368 * Gets the child element index closest to the given model offset.
2369 *
2370 * @param offset the offset >= 0
2371 * @return the element index >= 0
2372 */
2373 public int getElementIndex(int offset) {
2374 int index;
2375 int lower = 0;
2376 int upper = nchildren - 1;
2377 int mid = 0;
2378 int p0 = getStartOffset();
2379 int p1;
2380
2381 if (nchildren == 0) {
2382 return 0;
2383 }
2384 if (offset >= getEndOffset()) {
2385 return nchildren - 1;
2386 }
2387
2388 // see if the last index can be used.
2389 if ((lastIndex >= lower) && (lastIndex <= upper)) {
2390 Element lastHit = children[lastIndex];
2391 p0 = lastHit.getStartOffset();
2392 p1 = lastHit.getEndOffset();
2393 if ((offset >= p0) && (offset < p1)) {
2394 return lastIndex;
2395 }
2396
2397 // last index wasn't a hit, but it does give useful info about
2398 // where a hit (if any) would be.
2399 if (offset < p0) {
2400 upper = lastIndex;
2401 } else {
2402 lower = lastIndex;
2403 }
2404 }
2405
2406 while (lower <= upper) {
2407 mid = lower + ((upper - lower) / 2);
2408 Element elem = children[mid];
2409 p0 = elem.getStartOffset();
2410 p1 = elem.getEndOffset();
2411 if ((offset >= p0) && (offset < p1)) {
2412 // found the location
2413 index = mid;
2414 lastIndex = index;
2415 return index;
2416 } else if (offset < p0) {
2417 upper = mid - 1;
2418 } else {
2419 lower = mid + 1;
2420 }
2421 }
2422
2423 // didn't find it, but we indicate the index of where it would belong
2424 if (offset < p0) {
2425 in