History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/javax/swing/text/AbstractDocument.java (1 of 18352 files). Show all

Unified · Split

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