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