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