0001 /*
0002 * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved.
0003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004 *
0005 * This code is free software; you can redistribute it and/or modify it
0006 * under the terms of the GNU General Public License version 2 only, as
0007 * published by the Free Software Foundation. Sun designates this
0008 * particular file as subject to the "Classpath" exception as provided
0009 * by Sun in the LICENSE file that accompanied this code.
0010 *
0011 * This code is distributed in the hope that it will be useful, but WITHOUT
0012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014 * version 2 for more details (a copy is included in the LICENSE file that
0015 * accompanied this code).
0016 *
0017 * You should have received a copy of the GNU General Public License version
0018 * 2 along with this work; if not, write to the Free Software Foundation,
0019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020 *
0021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022 * CA 95054 USA or visit www.sun.com if you need additional information or
0023 * have any questions.
0024 */
0025 package javax.swing.text;
0026
0027 import java.util.*;
0028 import java.io.*;
0029 import java.awt.font.TextAttribute;
0030 import java.text.Bidi;
0031
0032 import javax.swing.UIManager;
0033 import javax.swing.undo.*;
0034 import javax.swing.event.ChangeListener;
0035 import javax.swing.event.*;
0036 import javax.swing.tree.TreeNode;
0037
0038 import sun.font.BidiUtils;
0039 import sun.swing.SwingUtilities2;
0040
0041 /**
0042 * An implementation of the document interface to serve as a
0043 * basis for implementing various kinds of documents. At this
0044 * level there is very little policy, so there is a corresponding
0045 * increase in difficulty of use.
0046 * <p>
0047 * This class implements a locking mechanism for the document. It
0048 * allows multiple readers or one writer, and writers must wait until
0049 * all observers of the document have been notified of a previous
0050 * change before beginning another mutation to the document. The
0051 * read lock is acquired and released using the <code>render</code>
0052 * method. A write lock is aquired by the methods that mutate the
0053 * document, and are held for the duration of the method call.
0054 * Notification is done on the thread that produced the mutation,
0055 * and the thread has full read access to the document for the
0056 * duration of the notification, but other readers are kept out
0057 * until the notification has finished. The notification is a
0058 * beans event notification which does not allow any further
0059 * mutations until all listeners have been notified.
0060 * <p>
0061 * Any models subclassed from this class and used in conjunction
0062 * with a text component that has a look and feel implementation
0063 * that is derived from BasicTextUI may be safely updated
0064 * asynchronously, because all access to the View hierarchy
0065 * is serialized by BasicTextUI if the document is of type
0066 * <code>AbstractDocument</code>. The locking assumes that an
0067 * independent thread will access the View hierarchy only from
0068 * the DocumentListener methods, and that there will be only
0069 * one event thread active at a time.
0070 * <p>
0071 * If concurrency support is desired, there are the following
0072 * additional implications. The code path for any DocumentListener
0073 * implementation and any UndoListener implementation must be threadsafe,
0074 * and not access the component lock if trying to be safe from deadlocks.
0075 * The <code>repaint</code> and <code>revalidate</code> methods
0076 * on JComponent are safe.
0077 * <p>
0078 * AbstractDocument models an implied break at the end of the document.
0079 * Among other things this allows you to position the caret after the last
0080 * character. As a result of this, <code>getLength</code> returns one less
0081 * than the length of the Content. If you create your own Content, be
0082 * sure and initialize it to have an additional character. Refer to
0083 * StringContent and GapContent for examples of this. Another implication
0084 * of this is that Elements that model the implied end character will have
0085 * an endOffset == (getLength() + 1). For example, in DefaultStyledDocument
0086 * <code>getParagraphElement(getLength()).getEndOffset() == getLength() + 1
0087 * </code>.
0088 * <p>
0089 * <strong>Warning:</strong>
0090 * Serialized objects of this class will not be compatible with
0091 * future Swing releases. The current serialization support is
0092 * appropriate for short term storage or RMI between applications running
0093 * the same version of Swing. As of 1.4, support for long term storage
0094 * of all JavaBeans<sup><font size="-2">TM</font></sup>
0095 * has been added to the <code>java.beans</code> package.
0096 * Please see {@link java.beans.XMLEncoder}.
0097 *
0098 * @author Timothy Prinzing
0099 * @version 1.163 05/05/07
0100 */
0101 public abstract class AbstractDocument implements Document,
0102 Serializable {
0103
0104 /**
0105 * Constructs a new <code>AbstractDocument</code>, wrapped around some
0106 * specified content storage mechanism.
0107 *
0108 * @param data the content
0109 */
0110 protected AbstractDocument(Content data) {
0111 this (data, StyleContext.getDefaultStyleContext());
0112 }
0113
0114 /**
0115 * Constructs a new <code>AbstractDocument</code>, wrapped around some
0116 * specified content storage mechanism.
0117 *
0118 * @param data the content
0119 * @param context the attribute context
0120 */
0121 protected AbstractDocument(Content data, AttributeContext context) {
0122 this .data = data;
0123 this .context = context;
0124 bidiRoot = new BidiRootElement();
0125
0126 if (defaultI18NProperty == null) {
0127 // determine default setting for i18n support
0128 Object o = java.security.AccessController
0129 .doPrivileged(new java.security.PrivilegedAction() {
0130 public Object run() {
0131 return System.getProperty(I18NProperty);
0132 }
0133 });
0134 if (o != null) {
0135 defaultI18NProperty = Boolean.valueOf((String) o);
0136 } else {
0137 defaultI18NProperty = Boolean.FALSE;
0138 }
0139 }
0140 putProperty(I18NProperty, defaultI18NProperty);
0141
0142 //REMIND(bcb) This creates an initial bidi element to account for
0143 //the \n that exists by default in the content. Doing it this way
0144 //seems to expose a little too much knowledge of the content given
0145 //to us by the sub-class. Consider having the sub-class' constructor
0146 //make an initial call to insertUpdate.
0147 writeLock();
0148 try {
0149 Element[] p = new Element[1];
0150 p[0] = new BidiElement(bidiRoot, 0, 1, 0);
0151 bidiRoot.replace(0, 0, p);
0152 } finally {
0153 writeUnlock();
0154 }
0155 }
0156
0157 /**
0158 * Supports managing a set of properties. Callers
0159 * can use the <code>documentProperties</code> dictionary
0160 * to annotate the document with document-wide properties.
0161 *
0162 * @return a non-<code>null</code> <code>Dictionary</code>
0163 * @see #setDocumentProperties
0164 */
0165 public Dictionary<Object, Object> getDocumentProperties() {
0166 if (documentProperties == null) {
0167 documentProperties = new Hashtable(2);
0168 }
0169 return documentProperties;
0170 }
0171
0172 /**
0173 * Replaces the document properties dictionary for this document.
0174 *
0175 * @param x the new dictionary
0176 * @see #getDocumentProperties
0177 */
0178 public void setDocumentProperties(Dictionary<Object, Object> x) {
0179 documentProperties = x;
0180 }
0181
0182 /**
0183 * Notifies all listeners that have registered interest for
0184 * notification on this event type. The event instance
0185 * is lazily created using the parameters passed into
0186 * the fire method.
0187 *
0188 * @param e the event
0189 * @see EventListenerList
0190 */
0191 protected void fireInsertUpdate(DocumentEvent e) {
0192 notifyingListeners = true;
0193 try {
0194 // Guaranteed to return a non-null array
0195 Object[] listeners = listenerList.getListenerList();
0196 // Process the listeners last to first, notifying
0197 // those that are interested in this event
0198 for (int i = listeners.length - 2; i >= 0; i -= 2) {
0199 if (listeners[i] == DocumentListener.class) {
0200 // Lazily create the event:
0201 // if (e == null)
0202 // e = new ListSelectionEvent(this, firstIndex, lastIndex);
0203 ((DocumentListener) listeners[i + 1])
0204 .insertUpdate(e);
0205 }
0206 }
0207 } finally {
0208 notifyingListeners = false;
0209 }
0210 }
0211
0212 /**
0213 * Notifies all listeners that have registered interest for
0214 * notification on this event type. The event instance
0215 * is lazily created using the parameters passed into
0216 * the fire method.
0217 *
0218 * @param e the event
0219 * @see EventListenerList
0220 */
0221 protected void fireChangedUpdate(DocumentEvent e) {
0222 notifyingListeners = true;
0223 try {
0224 // Guaranteed to return a non-null array
0225 Object[] listeners = listenerList.getListenerList();
0226 // Process the listeners last to first, notifying
0227 // those that are interested in this event
0228 for (int i = listeners.length - 2; i >= 0; i -= 2) {
0229 if (listeners[i] == DocumentListener.class) {
0230 // Lazily create the event:
0231 // if (e == null)
0232 // e = new ListSelectionEvent(this, firstIndex, lastIndex);
0233 ((DocumentListener) listeners[i + 1])
0234 .changedUpdate(e);
0235 }
0236 }
0237 } finally {
0238 notifyingListeners = false;
0239 }
0240 }
0241
0242 /**
0243 * Notifies all listeners that have registered interest for
0244 * notification on this event type. The event instance
0245 * is lazily created using the parameters passed into
0246 * the fire method.
0247 *
0248 * @param e the event
0249 * @see EventListenerList
0250 */
0251 protected void fireRemoveUpdate(DocumentEvent e) {
0252 notifyingListeners = true;
0253 try {
0254 // Guaranteed to return a non-null array
0255 Object[] listeners = listenerList.getListenerList();
0256 // Process the listeners last to first, notifying
0257 // those that are interested in this event
0258 for (int i = listeners.length - 2; i >= 0; i -= 2) {
0259 if (listeners[i] == DocumentListener.class) {
0260 // Lazily create the event:
0261 // if (e == null)
0262 // e = new ListSelectionEvent(this, firstIndex, lastIndex);
0263 ((DocumentListener) listeners[i + 1])
0264 .removeUpdate(e);
0265 }
0266 }
0267 } finally {
0268 notifyingListeners = false;
0269 }
0270 }
0271
0272 /**
0273 * Notifies all listeners that have registered interest for
0274 * notification on this event type. The event instance
0275 * is lazily created using the parameters passed into
0276 * the fire method.
0277 *
0278 * @param e the event
0279 * @see EventListenerList
0280 */
0281 protected void fireUndoableEditUpdate(UndoableEditEvent e) {
0282 // Guaranteed to return a non-null array
0283 Object[] listeners = listenerList.getListenerList();
0284 // Process the listeners last to first, notifying
0285 // those that are interested in this event
0286 for (int i = listeners.length - 2; i >= 0; i -= 2) {
0287 if (listeners[i] == UndoableEditListener.class) {
0288 // Lazily create the event:
0289 // if (e == null)
0290 // e = new ListSelectionEvent(this, firstIndex, lastIndex);
0291 ((UndoableEditListener) listeners[i + 1])
0292 .undoableEditHappened(e);
0293 }
0294 }
0295 }
0296
0297 /**
0298 * Returns an array of all the objects currently registered
0299 * as <code><em>Foo</em>Listener</code>s
0300 * upon this document.
0301 * <code><em>Foo</em>Listener</code>s are registered using the
0302 * <code>add<em>Foo</em>Listener</code> method.
0303 *
0304 * <p>
0305 * You can specify the <code>listenerType</code> argument
0306 * with a class literal, such as
0307 * <code><em>Foo</em>Listener.class</code>.
0308 * For example, you can query a
0309 * document <code>d</code>
0310 * for its document listeners with the following code:
0311 *
0312 * <pre>DocumentListener[] mls = (DocumentListener[])(d.getListeners(DocumentListener.class));</pre>
0313 *
0314 * If no such listeners exist, this method returns an empty array.
0315 *
0316 * @param listenerType the type of listeners requested; this parameter
0317 * should specify an interface that descends from
0318 * <code>java.util.EventListener</code>
0319 * @return an array of all objects registered as
0320 * <code><em>Foo</em>Listener</code>s on this component,
0321 * or an empty array if no such
0322 * listeners have been added
0323 * @exception ClassCastException if <code>listenerType</code>
0324 * doesn't specify a class or interface that implements
0325 * <code>java.util.EventListener</code>
0326 *
0327 * @see #getDocumentListeners
0328 * @see #getUndoableEditListeners
0329 *
0330 * @since 1.3
0331 */
0332 public <T extends EventListener> T[] getListeners(
0333 Class<T> listenerType) {
0334 return listenerList.getListeners(listenerType);
0335 }
0336
0337 /**
0338 * Gets the asynchronous loading priority. If less than zero,
0339 * the document should not be loaded asynchronously.
0340 *
0341 * @return the asynchronous loading priority, or <code>-1</code>
0342 * if the document should not be loaded asynchronously
0343 */
0344 public int getAsynchronousLoadPriority() {
0345 Integer loadPriority = (Integer) getProperty(AbstractDocument.AsyncLoadPriority);
0346 if (loadPriority != null) {
0347 return loadPriority.intValue();
0348 }
0349 return -1;
0350 }
0351
0352 /**
0353 * Sets the asynchronous loading priority.
0354 * @param p the new asynchronous loading priority; a value
0355 * less than zero indicates that the document should not be
0356 * loaded asynchronously
0357 */
0358 public void setAsynchronousLoadPriority(int p) {
0359 Integer loadPriority = (p >= 0) ? new Integer(p) : null;
0360 putProperty(AbstractDocument.AsyncLoadPriority, loadPriority);
0361 }
0362
0363 /**
0364 * Sets the <code>DocumentFilter</code>. The <code>DocumentFilter</code>
0365 * is passed <code>insert</code> and <code>remove</code> to conditionally
0366 * allow inserting/deleting of the text. A <code>null</code> value
0367 * indicates that no filtering will occur.
0368 *
0369 * @param filter the <code>DocumentFilter</code> used to constrain text
0370 * @see #getDocumentFilter
0371 * @since 1.4
0372 */
0373 public void setDocumentFilter(DocumentFilter filter) {
0374 documentFilter = filter;
0375 }
0376
0377 /**
0378 * Returns the <code>DocumentFilter</code> that is responsible for
0379 * filtering of insertion/removal. A <code>null</code> return value
0380 * implies no filtering is to occur.
0381 *
0382 * @since 1.4
0383 * @see #setDocumentFilter
0384 * @return the DocumentFilter
0385 */
0386 public DocumentFilter getDocumentFilter() {
0387 return documentFilter;
0388 }
0389
0390 // --- Document methods -----------------------------------------
0391
0392 /**
0393 * This allows the model to be safely rendered in the presence
0394 * of currency, if the model supports being updated asynchronously.
0395 * The given runnable will be executed in a way that allows it
0396 * to safely read the model with no changes while the runnable
0397 * is being executed. The runnable itself may <em>not</em>
0398 * make any mutations.
0399 * <p>
0400 * This is implemented to aquire a read lock for the duration
0401 * of the runnables execution. There may be multiple runnables
0402 * executing at the same time, and all writers will be blocked
0403 * while there are active rendering runnables. If the runnable
0404 * throws an exception, its lock will be safely released.
0405 * There is no protection against a runnable that never exits,
0406 * which will effectively leave the document locked for it's
0407 * lifetime.
0408 * <p>
0409 * If the given runnable attempts to make any mutations in
0410 * this implementation, a deadlock will occur. There is
0411 * no tracking of individual rendering threads to enable
0412 * detecting this situation, but a subclass could incur
0413 * the overhead of tracking them and throwing an error.
0414 * <p>
0415 * This method is thread safe, although most Swing methods
0416 * are not. Please see
0417 * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
0418 * to Use Threads</A> for more information.
0419 *
0420 * @param r the renderer to execute
0421 */
0422 public void render(Runnable r) {
0423 readLock();
0424 try {
0425 r.run();
0426 } finally {
0427 readUnlock();
0428 }
0429 }
0430
0431 /**
0432 * Returns the length of the data. This is the number of
0433 * characters of content that represents the users data.
0434 *
0435 * @return the length >= 0
0436 * @see Document#getLength
0437 */
0438 public int getLength() {
0439 return data.length() - 1;
0440 }
0441
0442 /**
0443 * Adds a document listener for notification of any changes.
0444 *
0445 * @param listener the <code>DocumentListener</code> to add
0446 * @see Document#addDocumentListener
0447 */
0448 public void addDocumentListener(DocumentListener listener) {
0449 listenerList.add(DocumentListener.class, listener);
0450 }
0451
0452 /**
0453 * Removes a document listener.
0454 *
0455 * @param listener the <code>DocumentListener</code> to remove
0456 * @see Document#removeDocumentListener
0457 */
0458 public void removeDocumentListener(DocumentListener listener) {
0459 listenerList.remove(DocumentListener.class, listener);
0460 }
0461
0462 /**
0463 * Returns an array of all the document listeners
0464 * registered on this document.
0465 *
0466 * @return all of this document's <code>DocumentListener</code>s
0467 * or an empty array if no document listeners are
0468 * currently registered
0469 *
0470 * @see #addDocumentListener
0471 * @see #removeDocumentListener
0472 * @since 1.4
0473 */
0474 public DocumentListener[] getDocumentListeners() {
0475 return (DocumentListener[]) listenerList
0476 .getListeners(DocumentListener.class);
0477 }
0478
0479 /**
0480 * Adds an undo listener for notification of any changes.
0481 * Undo/Redo operations performed on the <code>UndoableEdit</code>
0482 * will cause the appropriate DocumentEvent to be fired to keep
0483 * the view(s) in sync with the model.
0484 *
0485 * @param listener the <code>UndoableEditListener</code> to add
0486 * @see Document#addUndoableEditListener
0487 */
0488 public void addUndoableEditListener(UndoableEditListener listener) {
0489 listenerList.add(UndoableEditListener.class, listener);
0490 }
0491
0492 /**
0493 * Removes an undo listener.
0494 *
0495 * @param listener the <code>UndoableEditListener</code> to remove
0496 * @see Document#removeDocumentListener
0497 */
0498 public void removeUndoableEditListener(UndoableEditListener listener) {
0499 listenerList.remove(UndoableEditListener.class, listener);
0500 }
0501
0502 /**
0503 * Returns an array of all the undoable edit listeners
0504 * registered on this document.
0505 *
0506 * @return all of this document's <code>UndoableEditListener</code>s
0507 * or an empty array if no undoable edit listeners are
0508 * currently registered
0509 *
0510 * @see #addUndoableEditListener
0511 * @see #removeUndoableEditListener
0512 *
0513 * @since 1.4
0514 */
0515 public UndoableEditListener[] getUndoableEditListeners() {
0516 return (UndoableEditListener[]) listenerList
0517 .getListeners(UndoableEditListener.class);
0518 }
0519
0520 /**
0521 * A convenience method for looking up a property value. It is
0522 * equivalent to:
0523 * <pre>
0524 * getDocumentProperties().get(key);
0525 * </pre>
0526 *
0527 * @param key the non-<code>null</code> property key
0528 * @return the value of this property or <code>null</code>
0529 * @see #getDocumentProperties
0530 */
0531 public final Object getProperty(Object key) {
0532 return getDocumentProperties().get(key);
0533 }
0534
0535 /**
0536 * A convenience method for storing up a property value. It is
0537 * equivalent to:
0538 * <pre>
0539 * getDocumentProperties().put(key, value);
0540 * </pre>
0541 * If <code>value</code> is <code>null</code> this method will
0542 * remove the property.
0543 *
0544 * @param key the non-<code>null</code> key
0545 * @param value the property value
0546 * @see #getDocumentProperties
0547 */
0548 public final void putProperty(Object key, Object value) {
0549 if (value != null) {
0550 getDocumentProperties().put(key, value);
0551 } else {
0552 getDocumentProperties().remove(key);
0553 }
0554 if (key == TextAttribute.RUN_DIRECTION
0555 && Boolean.TRUE.equals(getProperty(I18NProperty))) {
0556 //REMIND - this needs to flip on the i18n property if run dir
0557 //is rtl and the i18n property is not already on.
0558 writeLock();
0559 try {
0560 DefaultDocumentEvent e = new DefaultDocumentEvent(0,
0561 getLength(), DocumentEvent.EventType.INSERT);
0562 updateBidi(e);
0563 } finally {
0564 writeUnlock();
0565 }
0566 }
0567 }
0568
0569 /**
0570 * Removes some content from the document.
0571 * Removing content causes a write lock to be held while the
0572 * actual changes are taking place. Observers are notified
0573 * of the change on the thread that called this method.
0574 * <p>
0575 * This method is thread safe, although most Swing methods
0576 * are not. Please see
0577 * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
0578 * to Use Threads</A> for more information.
0579 *
0580 * @param offs the starting offset >= 0
0581 * @param len the number of characters to remove >= 0
0582 * @exception BadLocationException the given remove position is not a valid
0583 * position within the document
0584 * @see Document#remove
0585 */
0586 public void remove(int offs, int len) throws BadLocationException {
0587 DocumentFilter filter = getDocumentFilter();
0588
0589 writeLock();
0590 try {
0591 if (filter != null) {
0592 filter.remove(getFilterBypass(), offs, len);
0593 } else {
0594 handleRemove(offs, len);
0595 }
0596 } finally {
0597 writeUnlock();
0598 }
0599 }
0600
0601 /**
0602 * Performs the actual work of the remove. It is assumed the caller
0603 * will have obtained a <code>writeLock</code> before invoking this.
0604 */
0605 void handleRemove(int offs, int len) throws BadLocationException {
0606 if (len > 0) {
0607 if (offs < 0 || (offs + len) > getLength()) {
0608 throw new BadLocationException("Invalid remove",
0609 getLength() + 1);
0610 }
0611 DefaultDocumentEvent chng = new DefaultDocumentEvent(offs,
0612 len, DocumentEvent.EventType.REMOVE);
0613
0614 boolean isComposedTextElement = false;
0615 // Check whether the position of interest is the composed text
0616 isComposedTextElement = Utilities.isComposedTextElement(
0617 this , offs);
0618
0619 removeUpdate(chng);
0620 UndoableEdit u = data.remove(offs, len);
0621 if (u != null) {
0622 chng.addEdit(u);
0623 }
0624 postRemoveUpdate(chng);
0625 // Mark the edit as done.
0626 chng.end();
0627 fireRemoveUpdate(chng);
0628 // only fire undo if Content implementation supports it
0629 // undo for the composed text is not supported for now
0630 if ((u != null) && !isComposedTextElement) {
0631 fireUndoableEditUpdate(new UndoableEditEvent(this , chng));
0632 }
0633 }
0634 }
0635
0636 /**
0637 * Deletes the region of text from <code>offset</code> to
0638 * <code>offset + length</code>, and replaces it with <code>text</code>.
0639 * It is up to the implementation as to how this is implemented, some
0640 * implementations may treat this as two distinct operations: a remove
0641 * followed by an insert, others may treat the replace as one atomic
0642 * operation.
0643 *
0644 * @param offset index of child element
0645 * @param length length of text to delete, may be 0 indicating don't
0646 * delete anything
0647 * @param text text to insert, <code>null</code> indicates no text to insert
0648 * @param attrs AttributeSet indicating attributes of inserted text,
0649 * <code>null</code>
0650 * is legal, and typically treated as an empty attributeset,
0651 * but exact interpretation is left to the subclass
0652 * @exception BadLocationException the given position is not a valid
0653 * position within the document
0654 * @since 1.4
0655 */
0656 public void replace(int offset, int length, String text,
0657 AttributeSet attrs) throws BadLocationException {
0658 if (length == 0 && (text == null || text.length() == 0)) {
0659 return;
0660 }
0661 DocumentFilter filter = getDocumentFilter();
0662
0663 writeLock();
0664 try {
0665 if (filter != null) {
0666 filter.replace(getFilterBypass(), offset, length, text,
0667 attrs);
0668 } else {
0669 if (length > 0) {
0670 remove(offset, length);
0671 }
0672 if (text != null && text.length() > 0) {
0673 insertString(offset, text, attrs);
0674 }
0675 }
0676 } finally {
0677 writeUnlock();
0678 }
0679 }
0680
0681 /**
0682 * Inserts some content into the document.
0683 * Inserting content causes a write lock to be held while the
0684 * actual changes are taking place, followed by notification
0685 * to the observers on the thread that grabbed the write lock.
0686 * <p>
0687 * This method is thread safe, although most Swing methods
0688 * are not. Please see
0689 * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
0690 * to Use Threads</A> for more information.
0691 *
0692 * @param offs the starting offset >= 0
0693 * @param str the string to insert; does nothing with null/empty strings
0694 * @param a the attributes for the inserted content
0695 * @exception BadLocationException the given insert position is not a valid
0696 * position within the document
0697 * @see Document#insertString
0698 */
0699 public void insertString(int offs, String str, AttributeSet a)
0700 throws BadLocationException {
0701 if ((str == null) || (str.length() == 0)) {
0702 return;
0703 }
0704 DocumentFilter filter = getDocumentFilter();
0705
0706 writeLock();
0707 try {
0708 if (filter != null) {
0709 filter.insertString(getFilterBypass(), offs, str, a);
0710 } else {
0711 handleInsertString(offs, str, a);
0712 }
0713 } finally {
0714 writeUnlock();
0715 }
0716 }
0717
0718 /**
0719 * Performs the actual work of inserting the text; it is assumed the
0720 * caller has obtained a write lock before invoking this.
0721 */
0722 void handleInsertString(int offs, String str, AttributeSet a)
0723 throws BadLocationException {
0724 if ((str == null) || (str.length() == 0)) {
0725 return;
0726 }
0727 UndoableEdit u = data.insertString(offs, str);
0728 DefaultDocumentEvent e = new DefaultDocumentEvent(offs, str
0729 .length(), DocumentEvent.EventType.INSERT);
0730 if (u != null) {
0731 e.addEdit(u);
0732 }
0733
0734 // see if complex glyph layout support is needed
0735 if (getProperty(I18NProperty).equals(Boolean.FALSE)) {
0736 // if a default direction of right-to-left has been specified,
0737 // we want complex layout even if the text is all left to right.
0738 Object d = getProperty(TextAttribute.RUN_DIRECTION);
0739 if ((d != null)
0740 && (d.equals(TextAttribute.RUN_DIRECTION_RTL))) {
0741 putProperty(I18NProperty, Boolean.TRUE);
0742 } else {
0743 char[] chars = str.toCharArray();
0744 if (SwingUtilities2.isComplexLayout(chars, 0,
0745 chars.length)) {
0746 putProperty(I18NProperty, Boolean.TRUE);
0747 }
0748 }
0749 }
0750
0751 insertUpdate(e, a);
0752 // Mark the edit as done.
0753 e.end();
0754 fireInsertUpdate(e);
0755 // only fire undo if Content implementation supports it
0756 // undo for the composed text is not supported for now
0757 if (u != null
0758 && (a == null || !a
0759 .isDefined(StyleConstants.ComposedTextAttribute))) {
0760 fireUndoableEditUpdate(new UndoableEditEvent(this , e));
0761 }
0762 }
0763
0764 /**
0765 * Gets a sequence of text from the document.
0766 *
0767 * @param offset the starting offset >= 0
0768 * @param length the number of characters to retrieve >= 0
0769 * @return the text
0770 * @exception BadLocationException the range given includes a position
0771 * that is not a valid position within the document
0772 * @see Document#getText
0773 */
0774 public String getText(int offset, int length)
0775 throws BadLocationException {
0776 if (length < 0) {
0777 throw new BadLocationException("Length must be positive",
0778 length);
0779 }
0780 String str = data.getString(offset, length);
0781 return str;
0782 }
0783
0784 /**
0785 * Fetches the text contained within the given portion
0786 * of the document.
0787 * <p>
0788 * If the partialReturn property on the txt parameter is false, the
0789 * data returned in the Segment will be the entire length requested and
0790 * may or may not be a copy depending upon how the data was stored.
0791 * If the partialReturn property is true, only the amount of text that
0792 * can be returned without creating a copy is returned. Using partial
0793 * returns will give better performance for situations where large
0794 * parts of the document are being scanned. The following is an example
0795 * of using the partial return to access the entire document:
0796 * <p>
0797 * <pre>
0798 * int nleft = doc.getDocumentLength();
0799 * Segment text = new Segment();
0800 * int offs = 0;
0801 * text.setPartialReturn(true);
0802 * while (nleft > 0) {
0803 * doc.getText(offs, nleft, text);
0804 * // do something with text
0805 * nleft -= text.count;
0806 * offs += text.count;
0807 * }
0808 * </pre>
0809 *
0810 * @param offset the starting offset >= 0
0811 * @param length the number of characters to retrieve >= 0
0812 * @param txt the Segment object to retrieve the text into
0813 * @exception BadLocationException the range given includes a position
0814 * that is not a valid position within the document
0815 */
0816 public void getText(int offset, int length, Segment txt)
0817 throws BadLocationException {
0818 if (length < 0) {
0819 throw new BadLocationException("Length must be positive",
0820 length);
0821 }
0822 data.getChars(offset, length, txt);
0823 }
0824
0825 /**
0826 * Returns a position that will track change as the document
0827 * is altered.
0828 * <p>
0829 * This method is thread safe, although most Swing methods
0830 * are not. Please see
0831 * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
0832 * to Use Threads</A> for more information.
0833 *
0834 * @param offs the position in the model >= 0
0835 * @return the position
0836 * @exception BadLocationException if the given position does not
0837 * represent a valid location in the associated document
0838 * @see Document#createPosition
0839 */
0840 public synchronized Position createPosition(int offs)
0841 throws BadLocationException {
0842 return data.createPosition(offs);
0843 }
0844
0845 /**
0846 * Returns a position that represents the start of the document. The
0847 * position returned can be counted on to track change and stay
0848 * located at the beginning of the document.
0849 *
0850 * @return the position
0851 */
0852 public final Position getStartPosition() {
0853 Position p;
0854 try {
0855 p = createPosition(0);
0856 } catch (BadLocationException bl) {
0857 p = null;
0858 }
0859 return p;
0860 }
0861
0862 /**
0863 * Returns a position that represents the end of the document. The
0864 * position returned can be counted on to track change and stay
0865 * located at the end of the document.
0866 *
0867 * @return the position
0868 */
0869 public final Position getEndPosition() {
0870 Position p;
0871 try {
0872 p = createPosition(data.length());
0873 } catch (BadLocationException bl) {
0874 p = null;
0875 }
0876 return p;
0877 }
0878
0879 /**
0880 * Gets all root elements defined. Typically, there
0881 * will only be one so the default implementation
0882 * is to return the default root element.
0883 *
0884 * @return the root element
0885 */
0886 public Element[] getRootElements() {
0887 Element[] elems = new Element[2];
0888 elems[0] = getDefaultRootElement();
0889 elems[1] = getBidiRootElement();
0890 return elems;
0891 }
0892
0893 /**
0894 * Returns the root element that views should be based upon
0895 * unless some other mechanism for assigning views to element
0896 * structures is provided.
0897 *
0898 * @return the root element
0899 * @see Document#getDefaultRootElement
0900 */
0901 public abstract Element getDefaultRootElement();
0902
0903 // ---- local methods -----------------------------------------
0904
0905 /**
0906 * Returns the <code>FilterBypass</code>. This will create one if one
0907 * does not yet exist.
0908 */
0909 private DocumentFilter.FilterBypass getFilterBypass() {
0910 if (filterBypass == null) {
0911 filterBypass = new DefaultFilterBypass();
0912 }
0913 return filterBypass;
0914 }
0915
0916 /**
0917 * Returns the root element of the bidirectional structure for this
0918 * document. Its children represent character runs with a given
0919 * Unicode bidi level.
0920 */
0921 public Element getBidiRootElement() {
0922 return bidiRoot;
0923 }
0924
0925 /**
0926 * Returns true if the text in the range <code>p0</code> to
0927 * <code>p1</code> is left to right.
0928 */
0929 boolean isLeftToRight(int p0, int p1) {
0930 if (!getProperty(I18NProperty).equals(Boolean.TRUE)) {
0931 return true;
0932 }
0933 Element bidiRoot = getBidiRootElement();
0934 int index = bidiRoot.getElementIndex(p0);
0935 Element bidiElem = bidiRoot.getElement(index);
0936 if (bidiElem.getEndOffset() >= p1) {
0937 AttributeSet bidiAttrs = bidiElem.getAttributes();
0938 return ((StyleConstants.getBidiLevel(bidiAttrs) % 2) == 0);
0939 }
0940 return true;
0941 }
0942
0943 /**
0944 * Get the paragraph element containing the given position. Sub-classes
0945 * must define for themselves what exactly constitutes a paragraph. They
0946 * should keep in mind however that a paragraph should at least be the
0947 * unit of text over which to run the Unicode bidirectional algorithm.
0948 *
0949 * @param pos the starting offset >= 0
0950 * @return the element */
0951 public abstract Element getParagraphElement(int pos);
0952
0953 /**
0954 * Fetches the context for managing attributes. This
0955 * method effectively establishes the strategy used
0956 * for compressing AttributeSet information.
0957 *
0958 * @return the context
0959 */
0960 protected final AttributeContext getAttributeContext() {
0961 return context;
0962 }
0963
0964 /**
0965 * Updates document structure as a result of text insertion. This
0966 * will happen within a write lock. If a subclass of
0967 * this class reimplements this method, it should delegate to the
0968 * superclass as well.
0969 *
0970 * @param chng a description of the change
0971 * @param attr the attributes for the change
0972 */
0973 protected void insertUpdate(DefaultDocumentEvent chng,
0974 AttributeSet attr) {
0975 if (getProperty(I18NProperty).equals(Boolean.TRUE))
0976 updateBidi(chng);
0977
0978 // Check if a multi byte is encountered in the inserted text.
0979 if (chng.type == DocumentEvent.EventType.INSERT
0980 && chng.getLength() > 0
0981 && !Boolean.TRUE.equals(getProperty(MultiByteProperty))) {
0982 Segment segment = SegmentCache.getSharedSegment();
0983 try {
0984 getText(chng.getOffset(), chng.getLength(), segment);
0985 segment.first();
0986 do {
0987 if ((int) segment.current() > 255) {
0988 putProperty(MultiByteProperty, Boolean.TRUE);
0989 break;
0990 }
0991 } while (segment.next() != Segment.DONE);
0992 } catch (BadLocationException ble) {
0993 // Should never happen
0994 }
0995 SegmentCache.releaseSharedSegment(segment);
0996 }
0997 }
0998
0999 /**
1000 * Updates any document structure as a result of text removal. This
1001 * method is called before the text is actually removed from the Content.
1002 * This will happen within a write lock. If a subclass
1003 * of this class reimplements this method, it should delegate to the
1004 * superclass as well.
1005 *
1006 * @param chng a description of the change
1007 */
1008 protected void removeUpdate(DefaultDocumentEvent chng) {
1009 }
1010
1011 /**
1012 * Updates any document structure as a result of text removal. This
1013 * method is called after the text has been removed from the Content.
1014 * This will happen within a write lock. If a subclass
1015 * of this class reimplements this method, it should delegate to the
1016 * superclass as well.
1017 *
1018 * @param chng a description of the change
1019 */
1020 protected void postRemoveUpdate(DefaultDocumentEvent chng) {
1021 if (getProperty(I18NProperty).equals(Boolean.TRUE))
1022 updateBidi(chng);
1023 }
1024
1025 /**
1026 * Update the bidi element structure as a result of the given change
1027 * to the document. The given change will be updated to reflect the
1028 * changes made to the bidi structure.
1029 *
1030 * This method assumes that every offset in the model is contained in
1031 * exactly one paragraph. This method also assumes that it is called
1032 * after the change is made to the default element structure.
1033 */
1034 void updateBidi(DefaultDocumentEvent chng) {
1035
1036 // Calculate the range of paragraphs affected by the change.
1037 int firstPStart;
1038 int lastPEnd;
1039 if (chng.type == DocumentEvent.EventType.INSERT
1040 || chng.type == DocumentEvent.EventType.CHANGE) {
1041 int chngStart = chng.getOffset();
1042 int chngEnd = chngStart + chng.getLength();
1043 firstPStart = getParagraphElement(chngStart)
1044 .getStartOffset();
1045 lastPEnd = getParagraphElement(chngEnd).getEndOffset();
1046 } else if (chng.type == DocumentEvent.EventType.REMOVE) {
1047 Element paragraph = getParagraphElement(chng.getOffset());
1048 firstPStart = paragraph.getStartOffset();
1049 lastPEnd = paragraph.getEndOffset();
1050 } else {
1051 throw new Error("Internal error: unknown event type.");
1052 }
1053 //System.out.println("updateBidi: firstPStart = " + firstPStart + " lastPEnd = " + lastPEnd );
1054
1055 // Calculate the bidi levels for the affected range of paragraphs. The
1056 // levels array will contain a bidi level for each character in the
1057 // affected text.
1058 byte levels[] = calculateBidiLevels(firstPStart, lastPEnd);
1059
1060 Vector newElements = new Vector();
1061
1062 // Calculate the first span of characters in the affected range with
1063 // the same bidi level. If this level is the same as the level of the
1064 // previous bidi element (the existing bidi element containing
1065 // firstPStart-1), then merge in the previous element. If not, but
1066 // the previous element overlaps the affected range, truncate the
1067 // previous element at firstPStart.
1068 int firstSpanStart = firstPStart;
1069 int removeFromIndex = 0;
1070 if (firstSpanStart > 0) {
1071 int prevElemIndex = bidiRoot
1072 .getElementIndex(firstPStart - 1);
1073 removeFromIndex = prevElemIndex;
1074 Element prevElem = bidiRoot.getElement(prevElemIndex);
1075 int prevLevel = StyleConstants.getBidiLevel(prevElem
1076 .getAttributes());
1077 //System.out.println("createbidiElements: prevElem= " + prevElem + " prevLevel= " + prevLevel + "level[0] = " + levels[0]);
1078 if (prevLevel == levels[0]) {
1079 firstSpanStart = prevElem.getStartOffset();
1080 } else if (prevElem.getEndOffset() > firstPStart) {
1081 newElements.addElement(new BidiElement(bidiRoot,
1082 prevElem.getStartOffset(), firstPStart,
1083 prevLevel));
1084 } else {
1085 removeFromIndex++;
1086 }
1087 }
1088
1089 int firstSpanEnd = 0;
1090 while ((firstSpanEnd < levels.length)
1091 && (levels[firstSpanEnd] == levels[0]))
1092 firstSpanEnd++;
1093
1094 // Calculate the last span of characters in the affected range with
1095 // the same bidi level. If this level is the same as the level of the
1096 // next bidi element (the existing bidi element containing lastPEnd),
1097 // then merge in the next element. If not, but the next element
1098 // overlaps the affected range, adjust the next element to start at
1099 // lastPEnd.
1100 int lastSpanEnd = lastPEnd;
1101 Element newNextElem = null;
1102 int removeToIndex = bidiRoot.getElementCount() - 1;
1103 if (lastSpanEnd <= getLength()) {
1104 int nextElemIndex = bidiRoot.getElementIndex(lastPEnd);
1105 removeToIndex = nextElemIndex;
1106 Element nextElem = bidiRoot.getElement(nextElemIndex);
1107 int nextLevel = StyleConstants.getBidiLevel(nextElem
1108 .getAttributes());
1109 if (nextLevel == levels[levels.length - 1]) {
1110 lastSpanEnd = nextElem.getEndOffset();
1111 } else if (nextElem.getStartOffset() < lastPEnd) {
1112 newNextElem = new BidiElement(bidiRoot, lastPEnd,
1113 nextElem.getEndOffset(), nextLevel);
1114 } else {
1115 removeToIndex--;
1116 }
1117 }
1118
1119 int lastSpanStart = levels.length;
1120 while ((lastSpanStart > firstSpanEnd)
1121 && (levels[lastSpanStart - 1] == levels[levels.length - 1]))
1122 lastSpanStart--;
1123
1124 // If the first and last spans are contiguous and have the same level,
1125 // merge them and create a single new element for the entire span.
1126 // Otherwise, create elements for the first and last spans as well as
1127 // any spans in between.
1128 if ((firstSpanEnd == lastSpanStart)
1129 && (levels[0] == levels[levels.length - 1])) {
1130 newElements.addElement(new BidiElement(bidiRoot,
1131 firstSpanStart, lastSpanEnd, levels[0]));
1132 } else {
1133 // Create an element for the first span.
1134 newElements.addElement(new BidiElement(bidiRoot,
1135 firstSpanStart, firstSpanEnd + firstPStart,
1136 levels[0]));
1137 // Create elements for the spans in between the first and last
1138 for (int i = firstSpanEnd; i < lastSpanStart;) {
1139 //System.out.println("executed line 872");
1140 int j;
1141 for (j = i; (j < levels.length)
1142 && (levels[j] == levels[i]); j++)
1143 ;
1144 newElements.addElement(new BidiElement(bidiRoot,
1145 firstPStart + i, firstPStart + j,
1146 (int) levels[i]));
1147 i = j;
1148 }
1149 // Create an element for the last span.
1150 newElements.addElement(new BidiElement(bidiRoot,
1151 lastSpanStart + firstPStart, lastSpanEnd,
1152 levels[levels.length - 1]));
1153 }
1154
1155 if (newNextElem != null)
1156 newElements.addElement(newNextElem);
1157
1158 // Calculate the set of existing bidi elements which must be
1159 // removed.
1160 int removedElemCount = 0;
1161 if (bidiRoot.getElementCount() > 0) {
1162 removedElemCount = removeToIndex - removeFromIndex + 1;
1163 }
1164 Element[] removedElems = new Element[removedElemCount];
1165 for (int i = 0; i < removedElemCount; i++) {
1166 removedElems[i] = bidiRoot.getElement(removeFromIndex + i);
1167 }
1168
1169 Element[] addedElems = new Element[newElements.size()];
1170 newElements.copyInto(addedElems);
1171
1172 // Update the change record.
1173 ElementEdit ee = new ElementEdit(bidiRoot, removeFromIndex,
1174 removedElems, addedElems);
1175 chng.addEdit(ee);
1176
1177 // Update the bidi element structure.
1178 bidiRoot.replace(removeFromIndex, removedElems.length,
1179 addedElems);
1180 }
1181
1182 /**
1183 * Calculate the levels array for a range of paragraphs.
1184 */
1185 private byte[] calculateBidiLevels(int firstPStart, int lastPEnd) {
1186
1187 byte levels[] = new byte[lastPEnd - firstPStart];
1188 int levelsEnd = 0;
1189 Boolean defaultDirection = null;
1190 Object d = getProperty(TextAttribute.RUN_DIRECTION);
1191 if (d instanceof Boolean) {
1192 defaultDirection = (Boolean) d;
1193 }
1194
1195 // For each paragraph in the given range of paragraphs, get its
1196 // levels array and add it to the levels array for the entire span.
1197 for (int o = firstPStart; o < lastPEnd;) {
1198 Element p = getParagraphElement(o);
1199 int pStart = p.getStartOffset();
1200 int pEnd = p.getEndOffset();
1201
1202 // default run direction for the paragraph. This will be
1203 // null if there is no direction override specified (i.e.
1204 // the direction will be determined from the content).
1205 Boolean direction = defaultDirection;
1206 d = p.getAttributes().getAttribute(
1207 TextAttribute.RUN_DIRECTION);
1208 if (d instanceof Boolean) {
1209 direction = (Boolean) d;
1210 }
1211
1212 //System.out.println("updateBidi: paragraph start = " + pStart + " paragraph end = " + pEnd);
1213
1214 // Create a Bidi over this paragraph then get the level
1215 // array.
1216 Segment seg = SegmentCache.getSharedSegment();
1217 try {
1218 getText(pStart, pEnd - pStart, seg);
1219 } catch (BadLocationException e) {
1220 throw new Error("Internal error: " + e.toString());
1221 }
1222 // REMIND(bcb) we should really be using a Segment here.
1223 Bidi bidiAnalyzer;
1224 int bidiflag = Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT;
1225 if (direction != null) {
1226 if (TextAttribute.RUN_DIRECTION_LTR.equals(direction)) {
1227 bidiflag = Bidi.DIRECTION_LEFT_TO_RIGHT;
1228 } else {
1229 bidiflag = Bidi.DIRECTION_RIGHT_TO_LEFT;
1230 }
1231 }
1232 bidiAnalyzer = new Bidi(seg.array, seg.offset, null, 0,
1233 seg.count, bidiflag);
1234 BidiUtils.getLevels(bidiAnalyzer, levels, levelsEnd);
1235 levelsEnd += bidiAnalyzer.getLength();
1236
1237 o = p.getEndOffset();
1238 SegmentCache.releaseSharedSegment(seg);
1239 }
1240
1241 // REMIND(bcb) remove this code when debugging is done.
1242 if (levelsEnd != levels.length)
1243 throw new Error("levelsEnd assertion failed.");
1244
1245 return levels;
1246 }
1247
1248 /**
1249 * Gives a diagnostic dump.
1250 *
1251 * @param out the output stream
1252 */
1253 public void dump(PrintStream out) {
1254 Element root = getDefaultRootElement();
1255 if (root instanceof AbstractElement) {
1256 ((AbstractElement) root).dump(out, 0);
1257 }
1258 bidiRoot.dump(out, 0);
1259 }
1260
1261 /**
1262 * Gets the content for the document.
1263 *
1264 * @return the content
1265 */
1266 protected final Content getContent() {
1267 return data;
1268 }
1269
1270 /**
1271 * Creates a document leaf element.
1272 * Hook through which elements are created to represent the
1273 * document structure. Because this implementation keeps
1274 * structure and content separate, elements grow automatically
1275 * when content is extended so splits of existing elements
1276 * follow. The document itself gets to decide how to generate
1277 * elements to give flexibility in the type of elements used.
1278 *
1279 * @param parent the parent element
1280 * @param a the attributes for the element
1281 * @param p0 the beginning of the range >= 0
1282 * @param p1 the end of the range >= p0
1283 * @return the new element
1284 */
1285 protected Element createLeafElement(Element parent, AttributeSet a,
1286 int p0, int p1) {
1287 return new LeafElement(parent, a, p0, p1);
1288 }
1289
1290 /**
1291 * Creates a document branch element, that can contain other elements.
1292 *
1293 * @param parent the parent element
1294 * @param a the attributes
1295 * @return the element
1296 */
1297 protected Element createBranchElement(Element parent, AttributeSet a) {
1298 return new BranchElement(parent, a);
1299 }
1300
1301 // --- Document locking ----------------------------------
1302
1303 /**
1304 * Fetches the current writing thread if there is one.
1305 * This can be used to distinguish whether a method is
1306 * being called as part of an existing modification or
1307 * if a lock needs to be acquired and a new transaction
1308 * started.
1309 *
1310 * @return the thread actively modifying the document
1311 * or <code>null</code> if there are no modifications in progress
1312 */
1313 protected synchronized final Thread getCurrentWriter() {
1314 return currWriter;
1315 }
1316
1317 /**
1318 * Acquires a lock to begin mutating the document this lock
1319 * protects. There can be no writing, notification of changes, or
1320 * reading going on in order to gain the lock. Additionally a thread is
1321 * allowed to gain more than one <code>writeLock</code>,
1322 * as long as it doesn't attempt to gain additional <code>writeLock</code>s
1323 * from within document notification. Attempting to gain a
1324 * <code>writeLock</code> from within a DocumentListener notification will
1325 * result in an <code>IllegalStateException</code>. The ability
1326 * to obtain more than one <code>writeLock</code> per thread allows
1327 * subclasses to gain a writeLock, perform a number of operations, then
1328 * release the lock.
1329 * <p>
1330 * Calls to <code>writeLock</code>
1331 * must be balanced with calls to <code>writeUnlock</code>, else the
1332 * <code>Document</code> will be left in a locked state so that no
1333 * reading or writing can be done.
1334 *
1335 * @exception IllegalStateException thrown on illegal lock
1336 * attempt. If the document is implemented properly, this can
1337 * only happen if a document listener attempts to mutate the
1338 * document. This situation violates the bean event model
1339 * where order of delivery is not guaranteed and all listeners
1340 * should be notified before further mutations are allowed.
1341 */
1342 protected synchronized final void writeLock() {
1343 try {
1344 while ((numReaders > 0) || (currWriter != null)) {
1345 if (Thread.currentThread() == currWriter) {
1346 if (notifyingListeners) {
1347 // Assuming one doesn't do something wrong in a
1348 // subclass this should only happen if a
1349 // DocumentListener tries to mutate the document.
1350 throw new IllegalStateException(
1351 "Attempt to mutate in notification");
1352 }
1353 numWriters++;
1354 return;
1355 }
1356 wait();
1357 }
1358 currWriter = Thread.currentThread();
1359 numWriters = 1;
1360 } catch (InterruptedException e) {
1361 throw new Error("Interrupted attempt to aquire write lock");
1362 }
1363 }
1364
1365 /**
1366 * Releases a write lock previously obtained via <code>writeLock</code>.
1367 * After decrementing the lock count if there are no oustanding locks
1368 * this will allow a new writer, or readers.
1369 *
1370 * @see #writeLock
1371 */
1372 protected synchronized final void writeUnlock() {
1373 if (--numWriters <= 0) {
1374 numWriters = 0;
1375 currWriter = null;
1376 notifyAll();
1377 }
1378 }
1379
1380 /**
1381 * Acquires a lock to begin reading some state from the
1382 * document. There can be multiple readers at the same time.
1383 * Writing blocks the readers until notification of the change
1384 * to the listeners has been completed. This method should
1385 * be used very carefully to avoid unintended compromise
1386 * of the document. It should always be balanced with a
1387 * <code>readUnlock</code>.
1388 *
1389 * @see #readUnlock
1390 */
1391 public synchronized final void readLock() {
1392 try {
1393 while (currWriter != null) {
1394 if (currWriter == Thread.currentThread()) {
1395 // writer has full read access.... may try to acquire
1396 // lock in notification
1397 return;
1398 }
1399 wait();
1400 }
1401 numReaders += 1;
1402 } catch (InterruptedException e) {
1403 throw new Error("Interrupted attempt to aquire read lock");
1404 }
1405 }
1406
1407 /**
1408 * Does a read unlock. This signals that one
1409 * of the readers is done. If there are no more readers
1410 * then writing can begin again. This should be balanced
1411 * with a readLock, and should occur in a finally statement
1412 * so that the balance is guaranteed. The following is an
1413 * example.
1414 * <pre><code>
1415 * readLock();
1416 * try {
1417 * // do something
1418 * } finally {
1419 * readUnlock();
1420 * }
1421 * </code></pre>
1422 *
1423 * @see #readLock
1424 */
1425 public synchronized final void readUnlock() {
1426 if (currWriter == Thread.currentThread()) {
1427 // writer has full read access.... may try to acquire
1428 // lock in notification
1429 return;
1430 }
1431 if (numReaders <= 0) {
1432 throw new StateInvariantError(BAD_LOCK_STATE);
1433 }
1434 numReaders -= 1;
1435 notify();
1436 }
1437
1438 // --- serialization ---------------------------------------------
1439
1440 private void readObject(ObjectInputStream s)
1441 throws ClassNotFoundException, IOException {
1442 s.defaultReadObject();
1443 listenerList = new EventListenerList();
1444
1445 // Restore bidi structure
1446 //REMIND(bcb) This creates an initial bidi element to account for
1447 //the \n that exists by default in the content.
1448 bidiRoot = new BidiRootElement();
1449 try {
1450 writeLock();
1451 Element[] p = new Element[1];
1452 p[0] = new BidiElement(bidiRoot, 0, 1, 0);
1453 bidiRoot.replace(0, 0, p);
1454 } finally {
1455 writeUnlock();
1456 }
1457 // At this point bidi root is only partially correct. To fully
1458 // restore it we need access to getDefaultRootElement. But, this
1459 // is created by the subclass and at this point will be null. We
1460 // thus use registerValidation.
1461 s.registerValidation(new ObjectInputValidation() {
1462 public void validateObject() {
1463 try {
1464 writeLock();
1465 DefaultDocumentEvent e = new DefaultDocumentEvent(
1466 0, getLength(),
1467 DocumentEvent.EventType.INSERT);
1468 updateBidi(e);
1469 } finally {
1470 writeUnlock();
1471 }
1472 }
1473 }, 0);
1474 }
1475
1476 // ----- member variables ------------------------------------------
1477
1478 private transient int numReaders;
1479 private transient Thread currWriter;
1480 /**
1481 * The number of writers, all obtained from <code>currWriter</code>.
1482 */
1483 private transient int numWriters;
1484 /**
1485 * True will notifying listeners.
1486 */
1487 private transient boolean notifyingListeners;
1488
1489 private static Boolean defaultI18NProperty;
1490
1491 /**
1492 * Storage for document-wide properties.
1493 */
1494 private Dictionary<Object, Object> documentProperties = null;
1495
1496 /**
1497 * The event listener list for the document.
1498 */
1499 protected EventListenerList listenerList = new EventListenerList();
1500
1501 /**
1502 * Where the text is actually stored, and a set of marks
1503 * that track change as the document is edited are managed.
1504 */
1505 private Content data;
1506
1507 /**
1508 * Factory for the attributes. This is the strategy for
1509 * attribute compression and control of the lifetime of
1510 * a set of attributes as a collection. This may be shared
1511 * with other documents.
1512 */
1513 private AttributeContext context;
1514
1515 /**
1516 * The root of the bidirectional structure for this document. Its children
1517 * represent character runs with the same Unicode bidi level.
1518 */
1519 private transient BranchElement bidiRoot;
1520
1521 /**
1522 * Filter for inserting/removing of text.
1523 */
1524 private DocumentFilter documentFilter;
1525
1526 /**
1527 * Used by DocumentFilter to do actual insert/remove.
1528 */
1529 private transient DocumentFilter.FilterBypass filterBypass;
1530
1531 private static final String BAD_LOCK_STATE = "document lock failure";
1532
1533 /**
1534 * Error message to indicate a bad location.
1535 */
1536 protected static final String BAD_LOCATION = "document location failure";
1537
1538 /**
1539 * Name of elements used to represent paragraphs
1540 */
1541 public static final String ParagraphElementName = "paragraph";
1542
1543 /**
1544 * Name of elements used to represent content
1545 */
1546 public static final String ContentElementName = "content";
1547
1548 /**
1549 * Name of elements used to hold sections (lines/paragraphs).
1550 */
1551 public static final String SectionElementName = "section";
1552
1553 /**
1554 * Name of elements used to hold a unidirectional run
1555 */
1556 public static final String BidiElementName = "bidi level";
1557
1558 /**
1559 * Name of the attribute used to specify element
1560 * names.
1561 */
1562 public static final String ElementNameAttribute = "$ename";
1563
1564 /**
1565 * Document property that indicates whether internationalization
1566 * functions such as text reordering or reshaping should be
1567 * performed. This property should not be publicly exposed,
1568 * since it is used for implementation convenience only. As a
1569 * side effect, copies of this property may be in its subclasses
1570 * that live in different packages (e.g. HTMLDocument as of now),
1571 * so those copies should also be taken care of when this property
1572 * needs to be modified.
1573 */
1574 static final String I18NProperty = "i18n";
1575
1576 /**
1577 * Document property that indicates if a character has been inserted
1578 * into the document that is more than one byte long. GlyphView uses
1579 * this to determine if it should use BreakIterator.
1580 */
1581 static final Object MultiByteProperty = "multiByte";
1582
1583 /**
1584 * Document property that indicates asynchronous loading is
1585 * desired, with the thread priority given as the value.
1586 */
1587 static final String AsyncLoadPriority = "load priority";
1588
1589 /**
1590 * Interface to describe a sequence of character content that
1591 * can be edited. Implementations may or may not support a
1592 * history mechanism which will be reflected by whether or not
1593 * mutations return an UndoableEdit implementation.
1594 * @see AbstractDocument
1595 */
1596 public interface Content {
1597
1598 /**
1599 * Creates a position within the content that will
1600 * track change as the content is mutated.
1601 *
1602 * @param offset the offset in the content >= 0
1603 * @return a Position
1604 * @exception BadLocationException for an invalid offset
1605 */
1606 public Position createPosition(int offset)
1607 throws BadLocationException;
1608
1609 /**
1610 * Current length of the sequence of character content.
1611 *
1612 * @return the length >= 0
1613 */
1614 public int length();
1615
1616 /**
1617 * Inserts a string of characters into the sequence.
1618 *
1619 * @param where offset into the sequence to make the insertion >= 0
1620 * @param str string to insert
1621 * @return if the implementation supports a history mechanism,
1622 * a reference to an <code>Edit</code> implementation will be returned,
1623 * otherwise returns <code>null</code>
1624 * @exception BadLocationException thrown if the area covered by
1625 * the arguments is not contained in the character sequence
1626 */
1627 public UndoableEdit insertString(int where, String str)
1628 throws BadLocationException;
1629
1630 /**
1631 * Removes some portion of the sequence.
1632 *
1633 * @param where The offset into the sequence to make the
1634 * insertion >= 0.
1635 * @param nitems The number of items in the sequence to remove >= 0.
1636 * @return If the implementation supports a history mechansim,
1637 * a reference to an Edit implementation will be returned,
1638 * otherwise null.
1639 * @exception BadLocationException Thrown if the area covered by
1640 * the arguments is not contained in the character sequence.
1641 */
1642 public UndoableEdit remove(int where, int nitems)
1643 throws BadLocationException;
1644
1645 /**
1646 * Fetches a string of characters contained in the sequence.
1647 *
1648 * @param where Offset into the sequence to fetch >= 0.
1649 * @param len number of characters to copy >= 0.
1650 * @return the string
1651 * @exception BadLocationException Thrown if the area covered by
1652 * the arguments is not contained in the character sequence.
1653 */
1654 public String getString(int where, int len)
1655 throws BadLocationException;
1656
1657 /**
1658 * Gets a sequence of characters and copies them into a Segment.
1659 *
1660 * @param where the starting offset >= 0
1661 * @param len the number of characters >= 0
1662 * @param txt the target location to copy into
1663 * @exception BadLocationException Thrown if the area covered by
1664 * the arguments is not contained in the character sequence.
1665 */
1666 public void getChars(int where, int len, Segment txt)
1667 throws BadLocationException;
1668 }
1669
1670 /**
1671 * An interface that can be used to allow MutableAttributeSet
1672 * implementations to use pluggable attribute compression
1673 * techniques. Each mutation of the attribute set can be
1674 * used to exchange a previous AttributeSet instance with
1675 * another, preserving the possibility of the AttributeSet
1676 * remaining immutable. An implementation is provided by
1677 * the StyleContext class.
1678 *
1679 * The Element implementations provided by this class use
1680 * this interface to provide their MutableAttributeSet
1681 * implementations, so that different AttributeSet compression
1682 * techniques can be employed. The method
1683 * <code>getAttributeContext</code> should be implemented to
1684 * return the object responsible for implementing the desired
1685 * compression technique.
1686 *
1687 * @see StyleContext
1688 */
1689 public interface AttributeContext {
1690
1691 /**
1692 * Adds an attribute to the given set, and returns
1693 * the new representative set.
1694 *
1695 * @param old the old attribute set
1696 * @param name the non-null attribute name
1697 * @param value the attribute value
1698 * @return the updated attribute set
1699 * @see MutableAttributeSet#addAttribute
1700 */
1701 public AttributeSet addAttribute(AttributeSet old, Object name,
1702 Object value);
1703
1704 /**
1705 * Adds a set of attributes to the element.
1706 *
1707 * @param old the old attribute set
1708 * @param attr the attributes to add
1709 * @return the updated attribute set
1710 * @see MutableAttributeSet#addAttribute
1711 */
1712 public AttributeSet addAttributes(AttributeSet old,
1713 AttributeSet attr);
1714
1715 /**
1716 * Removes an attribute from the set.
1717 *
1718 * @param old the old attribute set
1719 * @param name the non-null attribute name
1720 * @return the updated attribute set
1721 * @see MutableAttributeSet#removeAttribute
1722 */
1723 public AttributeSet removeAttribute(AttributeSet old,
1724 Object name);
1725
1726 /**
1727 * Removes a set of attributes for the element.
1728 *
1729 * @param old the old attribute set
1730 * @param names the attribute names
1731 * @return the updated attribute set
1732 * @see MutableAttributeSet#removeAttributes
1733 */
1734 public AttributeSet removeAttributes(AttributeSet old,
1735 Enumeration<?> names);
1736
1737 /**
1738 * Removes a set of attributes for the element.
1739 *
1740 * @param old the old attribute set
1741 * @param attrs the attributes
1742 * @return the updated attribute set
1743 * @see MutableAttributeSet#removeAttributes
1744 */
1745 public AttributeSet removeAttributes(AttributeSet old,
1746 AttributeSet attrs);
1747
1748 /**
1749 * Fetches an empty AttributeSet.
1750 *
1751 * @return the attribute set
1752 */
1753 public AttributeSet getEmptySet();
1754
1755 /**
1756 * Reclaims an attribute set.
1757 * This is a way for a MutableAttributeSet to mark that it no
1758 * longer need a particular immutable set. This is only necessary
1759 * in 1.1 where there are no weak references. A 1.1 implementation
1760 * would call this in its finalize method.
1761 *
1762 * @param a the attribute set to reclaim
1763 */
1764 public void reclaim(AttributeSet a);
1765 }
1766
1767 /**
1768 * Implements the abstract part of an element. By default elements
1769 * support attributes by having a field that represents the immutable
1770 * part of the current attribute set for the element. The element itself
1771 * implements MutableAttributeSet which can be used to modify the set
1772 * by fetching a new immutable set. The immutable sets are provided
1773 * by the AttributeContext associated with the document.
1774 * <p>
1775 * <strong>Warning:</strong>
1776 * Serialized objects of this class will not be compatible with
1777 * future Swing releases. The current serialization support is
1778 * appropriate for short term storage or RMI between applications running
1779 * the same version of Swing. As of 1.4, support for long term storage
1780 * of all JavaBeans<sup><font size="-2">TM</font></sup>
1781 * has been added to the <code>java.beans</code> package.
1782 * Please see {@link java.beans.XMLEncoder}.
1783 */
1784 public abstract class AbstractElement implements Element,
1785 MutableAttributeSet, Serializable, TreeNode {
1786
1787 /**
1788 * Creates a new AbstractElement.
1789 *
1790 * @param parent the parent element
1791 * @param a the attributes for the element
1792 * @since 1.4
1793 */
1794 public AbstractElement(Element parent, AttributeSet a) {
1795 this .parent = parent;
1796 attributes = getAttributeContext().getEmptySet();
1797 if (a != null) {
1798 addAttributes(a);
1799 }
1800 }
1801
1802 private final void indent(PrintWriter out, int n) {
1803 for (int i = 0; i < n; i++) {
1804 out.print(" ");
1805 }
1806 }
1807
1808 /**
1809 * Dumps a debugging representation of the element hierarchy.
1810 *
1811 * @param psOut the output stream
1812 * @param indentAmount the indentation level >= 0
1813 */
1814 public void dump(PrintStream psOut, int indentAmount) {
1815 PrintWriter out;
1816 try {
1817 out = new PrintWriter(new OutputStreamWriter(psOut,
1818 "JavaEsc"), true);
1819 } catch (UnsupportedEncodingException e) {
1820 out = new PrintWriter(psOut, true);
1821 }
1822 indent(out, indentAmount);
1823 if (getName() == null) {
1824 out.print("<??");
1825 } else {
1826 out.print("<" + getName());
1827 }
1828 if (getAttributeCount() > 0) {
1829 out.println("");
1830 // dump the attributes
1831 Enumeration names = attributes.getAttributeNames();
1832 while (names.hasMoreElements()) {
1833 Object name = names.nextElement();
1834 indent(out, indentAmount + 1);
1835 out.println(name + "=" + getAttribute(name));
1836 }
1837 indent(out, indentAmount);
1838 }
1839 out.println(">");
1840
1841 if (isLeaf()) {
1842 indent(out, indentAmount + 1);
1843 out.print("[" + getStartOffset() + "," + getEndOffset()
1844 + "]");
1845 Content c = getContent();
1846 try {
1847 String contentStr = c.getString(getStartOffset(),
1848 getEndOffset() - getStartOffset())/*.trim()*/;
1849 if (contentStr.length() > 40) {
1850 contentStr = contentStr.substring(0, 40)
1851 + "...";
1852 }
1853 out.println("[" + contentStr + "]");
1854 } catch (BadLocationException e) {
1855 ;
1856 }
1857
1858 } else {
1859 int n = getElementCount();
1860 for (int i = 0; i < n; i++) {
1861 AbstractElement e = (AbstractElement) getElement(i);
1862 e.dump(psOut, indentAmount + 1);
1863 }
1864 }
1865 }
1866
1867 // --- AttributeSet ----------------------------
1868 // delegated to the immutable field "attributes"
1869
1870 /**
1871 * Gets the number of attributes that are defined.
1872 *
1873 * @return the number of attributes >= 0
1874 * @see AttributeSet#getAttributeCount
1875 */
1876 public int getAttributeCount() {
1877 return attributes.getAttributeCount();
1878 }
1879
1880 /**
1881 * Checks whether a given attribute is defined.
1882 *
1883 * @param attrName the non-null attribute name
1884 * @return true if the attribute is defined
1885 * @see AttributeSet#isDefined
1886 */
1887 public boolean isDefined(Object attrName) {
1888 return attributes.isDefined(attrName);
1889 }
1890
1891 /**
1892 * Checks whether two attribute sets are equal.
1893 *
1894 * @param attr the attribute set to check against
1895 * @return true if the same
1896 * @see AttributeSet#isEqual
1897 */
1898 public boolean isEqual(AttributeSet attr) {
1899 return attributes.isEqual(attr);
1900 }
1901
1902 /**
1903 * Copies a set of attributes.
1904 *
1905 * @return the copy
1906 * @see AttributeSet#copyAttributes
1907 */
1908 public AttributeSet copyAttributes() {
1909 return attributes.copyAttributes();
1910 }
1911
1912 /**
1913 * Gets the value of an attribute.
1914 *
1915 * @param attrName the non-null attribute name
1916 * @return the attribute value
1917 * @see AttributeSet#getAttribute
1918 */
1919 public Object getAttribute(Object attrName) {
1920 Object value = attributes.getAttribute(attrName);
1921 if (value == null) {
1922 // The delegate nor it's resolvers had a match,
1923 // so we'll try to resolve through the parent
1924 // element.
1925 AttributeSet a = (parent != null) ? parent
1926 .getAttributes() : null;
1927 if (a != null) {
1928 value = a.getAttribute(attrName);
1929 }
1930 }
1931 return value;
1932 }
1933
1934 /**
1935 * Gets the names of all attributes.
1936 *
1937 * @return the attribute names as an enumeration
1938 * @see AttributeSet#getAttributeNames
1939 */
1940 public Enumeration<?> getAttributeNames() {
1941 return attributes.getAttributeNames();
1942 }
1943
1944 /**
1945 * Checks whether a given attribute name/value is defined.
1946 *
1947 * @param name the non-null attribute name
1948 * @param value the attribute value
1949 * @return true if the name/value is defined
1950 * @see AttributeSet#containsAttribute
1951 */
1952 public boolean containsAttribute(Object name, Object value) {
1953 return attributes.containsAttribute(name, value);
1954 }
1955
1956 /**
1957 * Checks whether the element contains all the attributes.
1958 *
1959 * @param attrs the attributes to check
1960 * @return true if the element contains all the attributes
1961 * @see AttributeSet#containsAttributes
1962 */
1963 public boolean containsAttributes(AttributeSet attrs) {
1964 return attributes.containsAttributes(attrs);
1965 }
1966
1967 /**
1968 * Gets the resolving parent.
1969 * If not overridden, the resolving parent defaults to
1970 * the parent element.
1971 *
1972 * @return the attributes from the parent, <code>null</code> if none
1973 * @see AttributeSet#getResolveParent
1974 */
1975 public AttributeSet getResolveParent() {
1976 AttributeSet a = attributes.getResolveParent();
1977 if ((a == null) && (parent != null)) {
1978 a = parent.getAttributes();
1979 }
1980 return a;
1981 }
1982
1983 // --- MutableAttributeSet ----------------------------------
1984 // should fetch a new immutable record for the field
1985 // "attributes".
1986
1987 /**
1988 * Adds an attribute to the element.
1989 *
1990 * @param name the non-null attribute name
1991 * @param value the attribute value
1992 * @see MutableAttributeSet#addAttribute
1993 */
1994 public void addAttribute(Object name, Object value) {
1995 checkForIllegalCast();
1996 AttributeContext context = getAttributeContext();
1997 attributes = context.addAttribute(attributes, name, value);
1998 }
1999
2000 /**
2001 * Adds a set of attributes to the element.
2002 *
2003 * @param attr the attributes to add
2004 * @see MutableAttributeSet#addAttribute
2005 */
2006 public void addAttributes(AttributeSet attr) {
2007 checkForIllegalCast();
2008 AttributeContext context = getAttributeContext();
2009 attributes = context.addAttributes(attributes, attr);
2010 }
2011
2012 /**
2013 * Removes an attribute from the set.
2014 *
2015 * @param name the non-null attribute name
2016 * @see MutableAttributeSet#removeAttribute
2017 */
2018 public void removeAttribute(Object name) {
2019 checkForIllegalCast();
2020 AttributeContext context = getAttributeContext();
2021 attributes = context.removeAttribute(attributes, name);
2022 }
2023
2024 /**
2025 * Removes a set of attributes for the element.
2026 *
2027 * @param names the attribute names
2028 * @see MutableAttributeSet#removeAttributes
2029 */
2030 public void removeAttributes(Enumeration<?> names) {
2031 checkForIllegalCast();
2032 AttributeContext context = getAttributeContext();
2033 attributes = context.removeAttributes(attributes, names);
2034 }
2035
2036 /**
2037 * Removes a set of attributes for the element.
2038 *
2039 * @param attrs the attributes
2040 * @see MutableAttributeSet#removeAttributes
2041 */
2042 public void removeAttributes(AttributeSet attrs) {
2043 checkForIllegalCast();
2044 AttributeContext context = getAttributeContext();
2045 if (attrs == this ) {
2046 attributes = context.getEmptySet();
2047 } else {
2048 attributes = context
2049 .removeAttributes(attributes, attrs);
2050 }
2051 }
2052
2053 /**
2054 * Sets the resolving parent.
2055 *
2056 * @param parent the parent, null if none
2057 * @see MutableAttributeSet#setResolveParent
2058 */
2059 public void setResolveParent(AttributeSet parent) {
2060 checkForIllegalCast();
2061 AttributeContext context = getAttributeContext();
2062 if (parent != null) {
2063 attributes = context.addAttribute(attributes,
2064 StyleConstants.ResolveAttribute, parent);
2065 } else {
2066 attributes = context.removeAttribute(attributes,
2067 StyleConstants.ResolveAttribute);
2068 }
2069 }
2070
2071 private final void checkForIllegalCast() {
2072 Thread t = getCurrentWriter();
2073 if ((t == null) || (t != Thread.currentThread())) {
2074 throw new StateInvariantError(
2075 "Illegal cast to MutableAttributeSet");
2076 }
2077 }
2078
2079 // --- Element methods -------------------------------------
2080
2081 /**
2082 * Retrieves the underlying model.
2083 *
2084 * @return the model
2085 */
2086 public Document getDocument() {
2087 return AbstractDocument.this ;
2088 }
2089
2090 /**
2091 * Gets the parent of the element.
2092 *
2093 * @return the parent
2094 */
2095 public Element getParentElement() {
2096 return parent;
2097 }
2098
2099 /**
2100 * Gets the attributes for the element.
2101 *
2102 * @return the attribute set
2103 */
2104 public AttributeSet getAttributes() {
2105 return this ;
2106 }
2107
2108 /**
2109 * Gets the name of the element.
2110 *
2111 * @return the name, null if none
2112 */
2113 public String getName() {
2114 if (attributes.isDefined(ElementNameAttribute)) {
2115 return (String) attributes
2116 .getAttribute(ElementNameAttribute);
2117 }
2118 return null;
2119 }
2120
2121 /**
2122 * Gets the starting offset in the model for the element.
2123 *
2124 * @return the offset >= 0
2125 */
2126 public abstract int getStartOffset();
2127
2128 /**
2129 * Gets the ending offset in the model for the element.
2130 *
2131 * @return the offset >= 0
2132 */
2133 public abstract int getEndOffset();
2134
2135 /**
2136 * Gets a child element.
2137 *
2138 * @param index the child index, >= 0 && < getElementCount()
2139 * @return the child element
2140 */
2141 public abstract Element getElement(int index);
2142
2143 /**
2144 * Gets the number of children for the element.
2145 *
2146 * @return the number of children >= 0
2147 */
2148 public abstract int getElementCount();
2149
2150 /**
2151 * Gets the child element index closest to the given model offset.
2152 *
2153 * @param offset the offset >= 0
2154 * @return the element index >= 0
2155 */
2156 public abstract int getElementIndex(int offset);
2157
2158 /**
2159 * Checks whether the element is a leaf.
2160 *
2161 * @return true if a leaf
2162 */
2163 public abstract boolean isLeaf();
2164
2165 // --- TreeNode methods -------------------------------------
2166
2167 /**
2168 * Returns the child <code>TreeNode</code> at index
2169 * <code>childIndex</code>.
2170 */
2171 public TreeNode getChildAt(int childIndex) {
2172 return (TreeNode) getElement(childIndex);
2173 }
2174
2175 /**
2176 * Returns the number of children <code>TreeNode</code>'s
2177 * receiver contains.
2178 * @return the number of children <code>TreeNodews</code>'s
2179 * receiver contains
2180 */
2181 public int getChildCount() {
2182 return getElementCount();
2183 }
2184
2185 /**
2186 * Returns the parent <code>TreeNode</code> of the receiver.
2187 * @return the parent <code>TreeNode</code> of the receiver
2188 */
2189 public TreeNode getParent() {
2190 return (TreeNode) getParentElement();
2191 }
2192
2193 /**
2194 * Returns the index of <code>node</code> in the receivers children.
2195 * If the receiver does not contain <code>node</code>, -1 will be
2196 * returned.
2197 * @param node the location of interest
2198 * @return the index of <code>node</code> in the receiver's
2199 * children, or -1 if absent
2200 */
2201 public int getIndex(TreeNode node) {
2202 for (int counter = getChildCount() - 1; counter >= 0; counter--)
2203 if (getChildAt(counter) == node)
2204 return counter;
2205 return -1;
2206 }
2207
2208 /**
2209 * Returns true if the receiver allows children.
2210 * @return true if the receiver allows children, otherwise false
2211 */
2212 public abstract boolean getAllowsChildren();
2213
2214 /**
2215 * Returns the children of the receiver as an
2216 * <code>Enumeration</code>.
2217 * @return the children of the receiver as an <code>Enumeration</code>
2218 */
2219 public abstract Enumeration children();
2220
2221 // --- serialization ---------------------------------------------
2222
2223 private void writeObject(ObjectOutputStream s)
2224 throws IOException {
2225 s.defaultWriteObject();
2226 StyleContext.writeAttributeSet(s, attributes);
2227 }
2228
2229 private void readObject(ObjectInputStream s)
2230 throws ClassNotFoundException, IOException {
2231 s.defaultReadObject();
2232 MutableAttributeSet attr = new SimpleAttributeSet();
2233 StyleContext.readAttributeSet(s, attr);
2234 AttributeContext context = getAttributeContext();
2235 attributes = context.addAttributes(
2236 SimpleAttributeSet.EMPTY, attr);
2237 }
2238
2239 // ---- variables -----------------------------------------------------
2240
2241 private Element parent;
2242 private transient AttributeSet attributes;
2243
2244 }
2245
2246 /**
2247 * Implements a composite element that contains other elements.
2248 * <p>
2249 * <strong>Warning:</strong>
2250 * Serialized objects of this class will not be compatible with
2251 * future Swing releases. The current serialization support is
2252 * appropriate for short term storage or RMI between applications running
2253 * the same version of Swing. As of 1.4, support for long term storage
2254 * of all JavaBeans<sup><font size="-2">TM</font></sup>
2255 * has been added to the <code>java.beans</code> package.
2256 * Please see {@link java.beans.XMLEncoder}.
2257 */
2258 public class BranchElement extends AbstractElement {
2259
2260 /**
2261 * Constructs a composite element that initially contains
2262 * no children.
2263 *
2264 * @param parent The parent element
2265 * @param a the attributes for the element
2266 * @since 1.4
2267 */
2268 public BranchElement(Element parent, AttributeSet a) {
2269 super (parent, a);
2270 children = new AbstractElement[1];
2271 nchildren = 0;
2272 lastIndex = -1;
2273 }
2274
2275 /**
2276 * Gets the child element that contains
2277 * the given model position.
2278 *
2279 * @param pos the position >= 0
2280 * @return the element, null if none
2281 */
2282 public Element positionToElement(int pos) {
2283 int index = getElementIndex(pos);
2284 Element child = children[index];
2285 int p0 = child.getStartOffset();
2286 int p1 = child.getEndOffset();
2287 if ((pos >= p0) && (pos < p1)) {
2288 return child;
2289 }
2290 return null;
2291 }
2292
2293 /**
2294 * Replaces content with a new set of elements.
2295 *
2296 * @param offset the starting offset >= 0
2297 * @param length the length to replace >= 0
2298 * @param elems the new elements
2299 */
2300 public void replace(int offset, int length, Element[] elems) {
2301 int delta = elems.length - length;
2302 int src = offset + length;
2303 int nmove = nchildren - src;
2304 int dest = src + delta;
2305 if ((nchildren + delta) >= children.length) {
2306 // need to grow the array
2307 int newLength = Math.max(2 * children.length, nchildren
2308 + delta);
2309 AbstractElement[] newChildren = new AbstractElement[newLength];
2310 System.arraycopy(children, 0, newChildren, 0, offset);
2311 System.arraycopy(elems, 0, newChildren, offset,
2312 elems.length);
2313 System.arraycopy(children, src, newChildren, dest,
2314 nmove);
2315 children = newChildren;
2316 } else {
2317 // patch the existing array
2318 System.arraycopy(children, src, children, dest, nmove);
2319 System.arraycopy(elems, 0, children, offset,
2320 elems.length);
2321 }
2322 nchildren = nchildren + delta;
2323 }
2324
2325 /**
2326 * Converts the element to a string.
2327 *
2328 * @return the string
2329 */
2330 public String toString() {
2331 return "BranchElement(" + getName() + ") "
2332 + getStartOffset() + "," + getEndOffset() + "\n";
2333 }
2334
2335 // --- Element methods -----------------------------------
2336
2337 /**
2338 * Gets the element name.
2339 *
2340 * @return the element name
2341 */
2342 public String getName() {
2343 String nm = super .getName();
2344 if (nm == null) {
2345 nm = ParagraphElementName;
2346 }
2347 return nm;
2348 }
2349
2350 /**
2351 * Gets the starting offset in the model for the element.
2352 *
2353 * @return the offset >= 0
2354 */
2355 public int getStartOffset() {
2356 return children[0].getStartOffset();
2357 }
2358
2359 /**
2360 * Gets the ending offset in the model for the element.
2361 * @throws NullPointerException if this element has no children
2362 *
2363 * @return the offset >= 0
2364 */
2365 public int getEndOffset() {
2366 Element child = (nchildren > 0) ? children[nchildren - 1]
2367 : children[0];
2368 return child.getEndOffset();
2369 }
2370
2371 /**
2372 * Gets a child element.
2373 *
2374 * @param index the child index, >= 0 && < getElementCount()
2375 * @return the child element, null if none
2376 */
2377 public Element getElement(int index) {
2378 if (index < nchildren) {
2379 return children[index];
2380 }
2381 return null;
2382 }
2383
2384 /**
2385 * Gets the number of children for the element.
2386 *
2387 * @return the number of children >= 0
2388 */
2389 public int getElementCount() {
2390 return nchildren;
2391 }
2392
2393 /**
2394 * Gets the child element index closest to the given model offset.
2395 *
2396 * @param offset the offset >= 0
2397 * @return the element index >= 0
2398 */
2399 public int getElementIndex(int offset) {
2400 int index;
2401 int lower = 0;
2402 int upper = nchildren - 1;
2403 int mid = 0;
2404 int p0 = getStartOffset();
2405 int p1;
2406
2407 if (nchildren == 0) {
2408 return 0;
2409 }
2410 if (offset >= getEndOffset()) {
2411 return nchildren - 1;
2412 }
2413
2414 // see if the last index can be used.
2415 if ((lastIndex >= lower) && (lastIndex <= upper)) {
2416 Element lastHit = children[lastIndex];
2417 p0 = lastHit.getStartOffset();
2418 p1 = lastHit.getEndOffset();
2419 if ((offset >= p0) && (offset < p1)) {
2420 return lastIndex;
2421 }
2422
2423 // last index wasn't a hit, but it does give useful info about
2424 // where a hit (if any) would be.
2425 if (offset < p0) {
2426 upper = lastIndex;
2427 } else {
2428 lower = lastIndex;
2429 }
2430 }
2431
2432 while (lower <= upper) {
2433 mid = lower + ((upper - lower) / 2);
2434 Element elem = children[mid];
2435 p0 = elem.getStartOffset();
2436 p1 = elem.getEndOffset();
2437 if ((offset >= p0) && (offset < p1)) {
2438 // found the location
2439 index = mid;
2440 lastIndex = index;
2441 return index;
2442 } else if (offset < p0) {
2443 upper = mid - 1;
2444 } else {
2445 lower = mid + 1;
2446 }
2447 }
2448
2449 // didn't find it, but we indicate the index of where it would belong
2450 if (offset < p0) {
2451 index = mid;
2452 } else {
2453 index = mid + 1;
2454 }
2455 lastIndex = index;
2456 return index;
2457 }
2458
2459 /**
2460 * Checks whether the element is a leaf.
2461 *
2462 * @return true if a leaf
2463 */
2464 public boolean isLeaf() {
2465 return false;
2466 }
2467
2468 // ------ TreeNode ----------------------------------------------
2469
2470 /**
2471 * Returns true if the receiver allows children.
2472 * @return true if the receiver allows children, otherwise false
2473 */
2474 public boolean getAllowsChildren() {
2475 return true;
2476 }
2477
2478 /**
2479 * Returns the children of the receiver as an
2480 * <code>Enumeration</code>.
2481 * @return the children of the receiver
2482 */
2483 public Enumeration children() {
2484 if (nchildren == 0)
2485 return null;
2486
2487 Vector tempVector = new Vector(nchildren);
2488
2489 for (int counter = 0; counter < nchildren; counter++)
2490 tempVector.addElement(children[counter]);
2491 return tempVector.elements();
2492 }
2493
2494 // ------ members ----------------------------------------------
2495
2496 private AbstractElement[] children;
2497 private int nchildren;
2498 private int lastIndex;
2499 }
2500
2501 /**
2502 * Implements an element that directly represents content of
2503 * some kind.
2504 * <p>
2505 * <strong>Warning:</strong>
2506 * Serialized objects of this class will not be compatible with
2507 * future Swing releases. The current serialization support is
2508 * appropriate for short term storage or RMI between applications running
2509 * the same version of Swing. As of 1.4, support for long term storage
2510 * of all JavaBeans<sup><font size="-2">TM</font></sup>
2511 * has been added to the <code>java.beans</code> package.
2512 * Please see {@link java.beans.XMLEncoder}.
2513 *
2514 * @see Element
2515 */
2516 public class LeafElement extends AbstractElement {
2517
2518 /**
2519 * Constructs an element that represents content within the
2520 * document (has no children).
2521 *
2522 * @param parent The parent element
2523 * @param a The element attributes
2524 * @param offs0 The start offset >= 0
2525 * @param offs1 The end offset >= offs0
2526 * @since 1.4
2527 */
2528 public LeafElement(Element parent, AttributeSet a, int offs0,
2529 int offs1) {
2530 super (parent, a);
2531 try {
2532 p0 = createPosition(offs0);
2533 p1 = createPosition(offs1);
2534 } catch (BadLocationException e) {
2535 p0 = null;
2536 p1 = null;
2537 throw new StateInvariantError(
2538 "Can't create Position references");
2539 }
2540 }
2541
2542 /**
2543 * Converts the element to a string.
2544 *
2545 * @return the string
2546 */
2547 public String toString() {
2548 return "LeafElement(" + getName() + ") " + p0 + "," + p1
2549 + "\n";
2550 }
2551
2552 // --- Element methods ---------------------------------------------
2553
2554 /**
2555 * Gets the starting offset in the model for the element.
2556 *
2557 * @return the offset >= 0
2558 */
2559 public int getStartOffset() {
2560 return p0.getOffset();
2561 }
2562
2563 /**
2564 * Gets the ending offset in the model for the element.
2565 *
2566 * @return the offset >= 0
2567 */
2568 public int getEndOffset() {
2569 return p1.getOffset();
2570 }
2571
2572 /**
2573 * Gets the element name.
2574 *
2575 * @return the name
2576 */
2577 public String getName() {
2578 String nm = super .getName();
2579 if (nm == null) {
2580 nm = ContentElementName;
2581 }
2582 return nm;
2583 }
2584
2585 /**
2586 * Gets the child element index closest to the given model offset.
2587 *
2588 * @param pos the offset >= 0
2589 * @return the element index >= 0
2590 */
2591 public int getElementIndex(int pos) {
2592 return -1;
2593 }
2594
2595 /**
2596 * Gets a child element.
2597 *
2598 * @param index the child index, >= 0 && < getElementCount()
2599 * @return the child element
2600 */
2601 public Element getElement(int index) {
2602 return null;
2603 }
2604
2605 /**
2606 * Returns the number of child elements.
2607 *
2608 * @return the number of children >= 0
2609 */
2610 public int getElementCount() {
2611 return 0;
2612 }
2613
2614 /**
2615 * Checks whether the element is a leaf.
2616 *
2617 * @return true if a leaf
2618 */
2619 public boolean isLeaf() {
2620 return true;
2621 }
2622
2623 // ------ TreeNode ----------------------------------------------
2624
2625 /**
2626 * Returns true if the receiver allows children.
2627 * @return true if the receiver allows children, otherwise false
2628 */
2629 public boolean getAllowsChildren() {
2630 return false;
2631 }
2632
2633 /**
2634 * Returns the children of the receiver as an
2635 * <code>Enumeration</code>.
2636 * @return the children of the receiver
2637 */
2638 public Enumeration children() {
2639 return null;
2640 }
2641
2642 // --- serialization ---------------------------------------------
2643
2644 private void writeObject(ObjectOutputStream s)
2645 throws IOException {
2646 s.defaultWriteObject();
2647 s.writeInt(p0.getOffset());
2648 s.writeInt(p1.getOffset());
2649 }
2650
2651 private void readObject(ObjectInputStream s)
2652 throws ClassNotFoundException, IOException {
2653 s.defaultReadObject();
2654
2655 // set the range with positions that track change
2656 int off0 = s.readInt();
2657 int off1 = s.readInt();
2658 try {
2659 p0 = createPosition(off0);
2660 p1 = createPosition(off1);
2661 } catch (BadLocationException e) {
2662 p0 = null;
2663 p1 = null;
2664 throw new IOException(
2665 "Can't restore Position references");
2666 }
2667 }
2668
2669 // ---- members -----------------------------------------------------
2670
2671 private transient Position p0;
2672 private transient Position p1;
2673 }
2674
2675 /**
2676 * Represents the root element of the bidirectional element structure.
2677 * The root element is the only element in the bidi element structure
2678 * which contains children.
2679 */
2680 class BidiRootElement extends BranchElement {
2681
2682 BidiRootElement() {
2683 super (null, null);
2684 }
2685
2686 /**
2687 * Gets the name of the element.
2688 * @return the name
2689 */
2690 public String getName() {
2691 return "bidi root";
2692 }
2693 }
2694
2695 /**
2696 * Represents an element of the bidirectional element structure.
2697 */
2698 class BidiElement extends LeafElement {
2699
2700 /**
2701 * Creates a new BidiElement.
2702 */
2703 BidiElement(Element parent, int start, int end, int level) {
2704 super (parent, new SimpleAttributeSet(), start, end);
2705 addAttribute(StyleConstants.BidiLevel, new Integer(level));
2706 //System.out.println("BidiElement: start = " + start
2707 // + " end = " + end + " level = " + level );
2708 }
2709
2710 /**
2711 * Gets the name of the element.
2712 * @return the name
2713 */
2714 public String getName() {
2715 return BidiElementName;
2716 }
2717
2718 int getLevel() {
2719 Integer o = (Integer) getAttribute(StyleConstants.BidiLevel);
2720 if (o != null) {
2721 return o.intValue();
2722 }
2723 return 0; // Level 0 is base level (non-embedded) left-to-right
2724 }
2725
2726 boolean isLeftToRight() {
2727 return ((getLevel() % 2) == 0);
2728 }
2729 }
2730
2731 /**
2732 * Stores document changes as the document is being
2733 * modified. Can subsequently be used for change notification
2734 * when done with the document modification transaction.
2735 * This is used by the AbstractDocument class and its extensions
2736 * for broadcasting change information to the document listeners.
2737 */
2738 public class DefaultDocumentEvent extends CompoundEdit implements
2739 DocumentEvent {
2740
2741 /**
2742 * Constructs a change record.
2743 *
2744 * @param offs the offset into the document of the change >= 0
2745 * @param len the length of the change >= 0
2746 * @param type the type of event (DocumentEvent.EventType)
2747 * @since 1.4
2748 */
2749 public DefaultDocumentEvent(int offs, int len,
2750 DocumentEvent.EventType type) {
2751 super ();
2752 offset = offs;
2753 length = len;
2754 this .type = type;
2755 }
2756
2757 /**
2758 * Returns a string description of the change event.
2759 *
2760 * @return a string
2761 */
2762 public String toString() {
2763 return edits.toString();
2764 }
2765
2766 // --- CompoundEdit methods --------------------------
2767
2768 /**
2769 * Adds a document edit. If the number of edits crosses
2770 * a threshold, this switches on a hashtable lookup for
2771 * ElementChange implementations since access of these
2772 * needs to be relatively quick.
2773 *
2774 * @param anEdit a document edit record
2775 * @return true if the edit was added
2776 */
2777 public boolean addEdit(UndoableEdit anEdit) {
2778 // if the number of changes gets too great, start using
2779 // a hashtable for to locate the change for a given element.
2780 if ((changeLookup == null) && (edits.size() > 10)) {
2781 changeLookup = new Hashtable();
2782 int n = edits.size();
2783 for (int i = 0; i < n; i++) {
2784 Object o = edits.elementAt(i);
2785 if (o instanceof DocumentEvent.ElementChange) {
2786 DocumentEvent.ElementChange ec = (DocumentEvent.ElementChange) o;
2787 changeLookup.put(ec.getElement(), ec);
2788 }
2789 }
2790 }
2791
2792 // if we have a hashtable... add the entry if it's
2793 // an ElementChange.
2794 if ((changeLookup != null)
2795 && (anEdit instanceof DocumentEvent.ElementChange)) {
2796 DocumentEvent.ElementChange ec = (DocumentEvent.ElementChange) anEdit;
2797 changeLookup.put(ec.getElement(), ec);
2798 }
2799 return super .addEdit(anEdit);
2800 }
2801
2802 /**
2803 * Redoes a change.
2804 *
2805 * @exception CannotRedoException if the change cannot be redone
2806 */
2807 public void redo() throws CannotRedoException {
2808 writeLock();
2809 try {
2810 // change the state
2811 super .redo();
2812 // fire a DocumentEvent to notify the view(s)
2813 UndoRedoDocumentEvent ev = new UndoRedoDocumentEvent(
2814 this , false);
2815 if (type == DocumentEvent.EventType.INSERT) {
2816 fireInsertUpdate(ev);
2817 } else if (type == DocumentEvent.EventType.REMOVE) {
2818 fireRemoveUpdate(ev);
2819 } else {
2820 fireChangedUpdate(ev);
2821 }
2822 } finally {
2823 writeUnlock();
2824 }
2825 }
2826
2827 /**
2828 * Undoes a change.
2829 *
2830 * @exception CannotUndoException if the change cannot be undone
2831 */
2832 public void undo() throws CannotUndoException {
2833 writeLock();
2834 try {
2835 // change the state
2836 super .undo();
2837 // fire a DocumentEvent to notify the view(s)
2838 UndoRedoDocumentEvent ev = new UndoRedoDocumentEvent(
2839 this , true);
2840 if (type == DocumentEvent.EventType.REMOVE) {
2841 fireInsertUpdate(ev);
2842 } else if (type == DocumentEvent.EventType.INSERT) {
2843 fireRemoveUpdate(ev);
2844 } else {
2845 fireChangedUpdate(ev);
2846 }
2847 } finally {
2848 writeUnlock();
2849 }
2850 }
2851
2852 /**
2853 * DefaultDocument events are significant. If you wish to aggregate
2854 * DefaultDocumentEvents to present them as a single edit to the user
2855 * place them into a CompoundEdit.
2856 *
2857 * @return whether the event is significant for edit undo purposes
2858 */
2859 public boolean isSignificant() {
2860 return true;
2861 }
2862
2863 /**
2864 * Provides a localized, human readable description of this edit
2865 * suitable for use in, say, a change log.
2866 *
2867 * @return the description
2868 */
2869 public String getPresentationName() {
2870 DocumentEvent.EventType type = getType();
2871 if (type == DocumentEvent.EventType.INSERT)
2872 return UIManager
2873 .getString("AbstractDocument.additionText");
2874 if (type == DocumentEvent.EventType.REMOVE)
2875 return UIManager
2876 .getString("AbstractDocument.deletionText");
2877 return UIManager
2878 .getString("AbstractDocument.styleChangeText");
2879 }
2880
2881 /**
2882 * Provides a localized, human readable description of the undoable
2883 * form of this edit, e.g. for use as an Undo menu item. Typically
2884 * derived from getDescription();
2885 *
2886 * @return the description
2887 */
2888 public String getUndoPresentationName() {
2889 return UIManager.getString("AbstractDocument.undoText")
2890 + " " + getPresentationName();
2891 }
2892
2893 /**
2894 * Provides a localized, human readable description of the redoable
2895 * form of this edit, e.g. for use as a Redo menu item. Typically
2896 * derived from getPresentationName();
2897 *
2898 * @return the description
2899 */
2900 public String getRedoPresentationName() {
2901 return UIManager.getString("AbstractDocument.redoText")
2902 + " " + getPresentationName();
2903 }
2904
2905 // --- DocumentEvent methods --------------------------
2906
2907 /**
2908 * Returns the type of event.
2909 *
2910 * @return the event type as a DocumentEvent.EventType
2911 * @see DocumentEvent#getType
2912 */
2913 public DocumentEvent.EventType getType() {
2914 return type;
2915 }
2916
2917 /**
2918 * Returns the offset within the document of the start of the change.
2919 *
2920 * @return the offset >= 0
2921 * @see DocumentEvent#getOffset
2922 */
2923 public int getOffset() {
2924 return offset;
2925 }
2926
2927 /**
2928 * Returns the length of the change.
2929 *
2930 * @return the length >= 0
2931 * @see DocumentEvent#getLength
2932 */
2933 public int getLength() {
2934 return length;
2935 }
2936
2937 /**
2938 * Gets the document that sourced the change event.
2939 *
2940 * @return the document
2941 * @see DocumentEvent#getDocument
2942 */
2943 public Document getDocument() {
2944 return AbstractDocument.this ;
2945 }
2946
2947 /**
2948 * Gets the changes for an element.
2949 *
2950 * @param elem the element
2951 * @return the changes
2952 */
2953 public DocumentEvent.ElementChange getChange(Element elem) {
2954 if (changeLookup != null) {
2955 return (DocumentEvent.ElementChange) changeLookup
2956 .get(elem);
2957 }
2958 int n = edits.size();
2959 for (int i = 0; i < n; i++) {
2960 Object o = edits.elementAt(i);
2961 if (o instanceof DocumentEvent.ElementChange) {
2962 DocumentEvent.ElementChange c = (DocumentEvent.ElementChange) o;
2963 if (elem.equals(c.getElement())) {
2964 return c;
2965 }
2966 }
2967 }
2968 return null;
2969 }
2970
2971 // --- member variables ------------------------------------
2972
2973 private int offset;
2974 private int length;
2975 private Hashtable changeLookup;
2976 private DocumentEvent.EventType type;
2977
2978 }
2979
2980 /**
2981 * This event used when firing document changes while Undo/Redo
2982 * operations. It just wraps DefaultDocumentEvent and delegates
2983 * all calls to it except getType() which depends on operation
2984 * (Undo or Redo).
2985 */
2986 class UndoRedoDocumentEvent implements DocumentEvent {
2987 private DefaultDocumentEvent src = null;
2988 private boolean isUndo;
2989 private EventType type = null;
2990
2991 public UndoRedoDocumentEvent(DefaultDocumentEvent src,
2992 boolean isUndo) {
2993 this .src = src;
2994 this .isUndo = isUndo;
2995 if (isUndo) {
2996 if (src.getType().equals(EventType.INSERT)) {
2997 type = EventType.REMOVE;
2998 } else if (src.getType().equals(EventType.REMOVE)) {
2999 type = EventType.INSERT;
3000 } else {
3001 type = src.getType();
3002 }
3003 } else {
3004 type = src.getType();
3005 }
3006 }
3007
3008 public DefaultDocumentEvent getSource() {
3009 return src;
3010 }
3011
3012 // DocumentEvent methods delegated to DefaultDocumentEvent source
3013 // except getType() which depends on operation (Undo or Redo).
3014 public int getOffset() {
3015 return src.getOffset();
3016 }
3017
3018 public int getLength() {
3019 return src.getLength();
3020 }
3021
3022 public Document getDocument() {
3023 return src.getDocument();
3024 }
3025
3026 public DocumentEvent.EventType getType() {
3027 return type;
3028 }
3029
3030 public DocumentEvent.ElementChange getChange(Element elem) {
3031 return src.getChange(elem);
3032 }
3033 }
3034
3035 /**
3036 * An implementation of ElementChange that can be added to the document
3037 * event.
3038 */
3039 public static class ElementEdit extends AbstractUndoableEdit
3040 implements DocumentEvent.ElementChange {
3041
3042 /**
3043 * Constructs an edit record. This does not modify the element
3044 * so it can safely be used to <em>catch up</em> a view to the
3045 * current model state for views that just attached to a model.
3046 *
3047 * @param e the element
3048 * @param index the index into the model >= 0
3049 * @param removed a set of elements that were removed
3050 * @param added a set of elements that were added
3051 */
3052 public ElementEdit(Element e, int index, Element[] removed,
3053 Element[] added) {
3054 super ();
3055 this .e = e;
3056 this .index = index;
3057 this .removed = removed;
3058 this .added = added;
3059 }
3060
3061 /**
3062 * Returns the underlying element.
3063 *
3064 * @return the element
3065 */
3066 public Element getElement() {
3067 return e;
3068 }
3069
3070 /**
3071 * Returns the index into the list of elements.
3072 *
3073 * @return the index >= 0
3074 */
3075 public int getIndex() {
3076 return index;
3077 }
3078
3079 /**
3080 * Gets a list of children that were removed.
3081 *
3082 * @return the list
3083 */
3084 public Element[] getChildrenRemoved() {
3085 return removed;
3086 }
3087
3088 /**
3089 * Gets a list of children that were added.
3090 *
3091 * @return the list
3092 */
3093 public Element[] getChildrenAdded() {
3094 return added;
3095 }
3096
3097 /**
3098 * Redoes a change.
3099 *
3100 * @exception CannotRedoException if the change cannot be redone
3101 */
3102 public void redo() throws CannotRedoException {
3103 super .redo();
3104
3105 // Since this event will be reused, switch around added/removed.
3106 Element[] tmp = removed;
3107 removed = added;
3108 added = tmp;
3109
3110 // PENDING(prinz) need MutableElement interface, canRedo() should check
3111 ((AbstractDocument.BranchElement) e).replace(index,
3112 removed.length, added);
3113 }
3114
3115 /**
3116 * Undoes a change.
3117 *
3118 * @exception CannotUndoException if the change cannot be undone
3119 */
3120 public void undo() throws CannotUndoException {
3121 super .undo();
3122 // PENDING(prinz) need MutableElement interface, canUndo() should check
3123 ((AbstractDocument.BranchElement) e).replace(index,
3124 added.length, removed);
3125
3126 // Since this event will be reused, switch around added/removed.
3127 Element[] tmp = removed;
3128 removed = added;
3129 added = tmp;
3130 }
3131
3132 private Element e;
3133 private int index;
3134 private Element[] removed;
3135 private Element[] added;
3136 }
3137
3138 private class DefaultFilterBypass extends
3139 DocumentFilter.FilterBypass {
3140 public Document getDocument() {
3141 return AbstractDocument.this ;
3142 }
3143
3144 public void remove(int offset, int length)
3145 throws BadLocationException {
3146 handleRemove(offset, length);
3147 }
3148
3149 public void insertString(int offset, String string,
3150 AttributeSet attr) throws BadLocationException {
3151 handleInsertString(offset, string, attr);
3152 }
3153
3154 public void replace(int offset, int length, String text,
3155 AttributeSet attrs) throws BadLocationException {
3156 handleRemove(offset, length);
3157 handleInsertString(offset, text, attrs);
3158 }
3159 }
3160 }
|