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