1 /* 2 * Copyright (c) 1997, 2013, 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 public abstract class AbstractDocument implements Document, Serializable { 100 101 /** 102 * Constructs a new <code>AbstractDocument</code>, wrapped around some 103 * specified content storage mechanism. 104 * 105 * @param data the content 106 */ 107 protected AbstractDocument(Content data) { 108 this(data, StyleContext.getDefaultStyleContext()); 109 } 110 111 /** 112 * Constructs a new <code>AbstractDocument</code>, wrapped around some 113 * specified content storage mechanism. 114 * 115 * @param data the content 116 * @param context the attribute context 117 */ 118 protected AbstractDocument(Content data, AttributeContext context) { 119 this.data = data; 120 this.context = context; 121 bidiRoot = new BidiRootElement(); 122 123 if (defaultI18NProperty == null) { 124 // determine default setting for i18n support 125 String o = java.security.AccessController.doPrivileged( 126 new java.security.PrivilegedAction<String>() { 127 public String run() { 128 return System.getProperty(I18NProperty); 129 } 130 } 131 ); 132 if (o != null) { 133 defaultI18NProperty = Boolean.valueOf(o); 134 } else { 135 defaultI18NProperty = Boolean.FALSE; 136 } 137 } 138 putProperty( I18NProperty, defaultI18NProperty); 139 140 //REMIND(bcb) This creates an initial bidi element to account for 141 //the \n that exists by default in the content. Doing it this way 142 //seems to expose a little too much knowledge of the content given 143 //to us by the sub-class. Consider having the sub-class' constructor 144 //make an initial call to insertUpdate. 145 writeLock(); 146 try { 147 Element[] p = new Element[1]; 148 p[0] = new BidiElement( bidiRoot, 0, 1, 0 ); 149 bidiRoot.replace(0,0,p); 150 } finally { 151 writeUnlock(); 152 } 153 } 154 155 /** 156 * Supports managing a set of properties. Callers 157 * can use the <code>documentProperties</code> dictionary 158 * to annotate the document with document-wide properties. 159 * 160 * @return a non-<code>null</code> <code>Dictionary</code> 161 * @see #setDocumentProperties 162 */ 163 public Dictionary<Object,Object> getDocumentProperties() { 164 if (documentProperties == null) { 165 documentProperties = new Hashtable<Object, Object>(2); 166 } 167 return documentProperties; 168 } 169 170 /** 171 * Replaces the document properties dictionary for this document. 172 * 173 * @param x the new dictionary 174 * @see #getDocumentProperties 175 */ 176 public void setDocumentProperties(Dictionary<Object,Object> x) { 177 documentProperties = x; 178 } 179 180 /** 181 * Notifies all listeners that have registered interest for 182 * notification on this event type. The event instance 183 * is lazily created using the parameters passed into 184 * the fire method. 185 * 186 * @param e the event 187 * @see EventListenerList 188 */ 189 protected void fireInsertUpdate(DocumentEvent e) { 190 notifyingListeners = true; 191 try { 192 // Guaranteed to return a non-null array 193 Object[] listeners = listenerList.getListenerList(); 194 // Process the listeners last to first, notifying 195 // those that are interested in this event 196 for (int i = listeners.length-2; i>=0; i-=2) { 197 if (listeners[i]==DocumentListener.class) { 198 // Lazily create the event: 199 // if (e == null) 200 // e = new ListSelectionEvent(this, firstIndex, lastIndex); 201 ((DocumentListener)listeners[i+1]).insertUpdate(e); 202 } 203 } 204 } finally { 205 notifyingListeners = false; 206 } 207 } 208 209 /** 210 * Notifies all listeners that have registered interest for 211 * notification on this event type. The event instance 212 * is lazily created using the parameters passed into 213 * the fire method. 214 * 215 * @param e the event 216 * @see EventListenerList 217 */ 218 protected void fireChangedUpdate(DocumentEvent e) { 219 notifyingListeners = true; 220 try { 221 // Guaranteed to return a non-null array 222 Object[] listeners = listenerList.getListenerList(); 223 // Process the listeners last to first, notifying 224 // those that are interested in this event 225 for (int i = listeners.length-2; i>=0; i-=2) { 226 if (listeners[i]==DocumentListener.class) { 227 // Lazily create the event: 228 // if (e == null) 229 // e = new ListSelectionEvent(this, firstIndex, lastIndex); 230 ((DocumentListener)listeners[i+1]).changedUpdate(e); 231 } 232 } 233 } finally { 234 notifyingListeners = false; 235 } 236 } 237 238 /** 239 * Notifies all listeners that have registered interest for 240 * notification on this event type. The event instance 241 * is lazily created using the parameters passed into 242 * the fire method. 243 * 244 * @param e the event 245 * @see EventListenerList 246 */ 247 protected void fireRemoveUpdate(DocumentEvent e) { 248 notifyingListeners = true; 249 try { 250 // Guaranteed to return a non-null array 251 Object[] listeners = listenerList.getListenerList(); 252 // Process the listeners last to first, notifying 253 // those that are interested in this event 254 for (int i = listeners.length-2; i>=0; i-=2) { 255 if (listeners[i]==DocumentListener.class) { 256 // Lazily create the event: 257 // if (e == null) 258 // e = new ListSelectionEvent(this, firstIndex, lastIndex); 259 ((DocumentListener)listeners[i+1]).removeUpdate(e); 260 } 261 } 262 } finally { 263 notifyingListeners = false; 264 } 265 } 266 267 /** 268 * Notifies all listeners that have registered interest for 269 * notification on this event type. The event instance 270 * is lazily created using the parameters passed into 271 * the fire method. 272 * 273 * @param e the event 274 * @see EventListenerList 275 */ 276 protected void fireUndoableEditUpdate(UndoableEditEvent e) { 277 // Guaranteed to return a non-null array 278 Object[] listeners = listenerList.getListenerList(); 279 // Process the listeners last to first, notifying 280 // those that are interested in this event 281 for (int i = listeners.length-2; i>=0; i-=2) { 282 if (listeners[i]==UndoableEditListener.class) { 283 // Lazily create the event: 284 // if (e == null) 285 // e = new ListSelectionEvent(this, firstIndex, lastIndex); 286 ((UndoableEditListener)listeners[i+1]).undoableEditHappened(e); 287 } 288 } 289 } 290 291 /** 292 * Returns an array of all the objects currently registered 293 * as <code><em>Foo</em>Listener</code>s 294 * upon this document. 295 * <code><em>Foo</em>Listener</code>s are registered using the 296 * <code>add<em>Foo</em>Listener</code> method. 297 * 298 * <p> 299 * You can specify the <code>listenerType</code> argument 300 * with a class literal, such as 301 * <code><em>Foo</em>Listener.class</code>. 302 * For example, you can query a 303 * document <code>d</code> 304 * for its document listeners with the following code: 305 * 306 * <pre>DocumentListener[] mls = (DocumentListener[])(d.getListeners(DocumentListener.class));</pre> 307 * 308 * If no such listeners exist, this method returns an empty array. 309 * 310 * @param listenerType the type of listeners requested; this parameter 311 * should specify an interface that descends from 312 * <code>java.util.EventListener</code> 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 */ 908 public Element getBidiRootElement() { 909 return bidiRoot; 910 } 911 912 /** 913 * Returns true if the text in the range <code>p0</code> to 914 * <code>p1</code> is left to right. 915 */ 916 static boolean isLeftToRight(Document doc, int p0, int p1) { 917 if (Boolean.TRUE.equals(doc.getProperty(I18NProperty))) { 918 if (doc instanceof AbstractDocument) { 919 AbstractDocument adoc = (AbstractDocument) doc; 920 Element bidiRoot = adoc.getBidiRootElement(); 921 int index = bidiRoot.getElementIndex(p0); 922 Element bidiElem = bidiRoot.getElement(index); 923 if (bidiElem.getEndOffset() >= p1) { 924 AttributeSet bidiAttrs = bidiElem.getAttributes(); 925 return ((StyleConstants.getBidiLevel(bidiAttrs) % 2) == 0); 926 } 927 } 928 } 929 return true; 930 } 931 932 /** 933 * Get the paragraph element containing the given position. Sub-classes 934 * must define for themselves what exactly constitutes a paragraph. They 935 * should keep in mind however that a paragraph should at least be the 936 * unit of text over which to run the Unicode bidirectional algorithm. 937 * 938 * @param pos the starting offset >= 0 939 * @return the element */ 940 public abstract Element getParagraphElement(int pos); 941 942 943 /** 944 * Fetches the context for managing attributes. This 945 * method effectively establishes the strategy used 946 * for compressing AttributeSet information. 947 * 948 * @return the context 949 */ 950 protected final AttributeContext getAttributeContext() { 951 return context; 952 } 953 954 /** 955 * Updates document structure as a result of text insertion. This 956 * will happen within a write lock. If a subclass of 957 * this class reimplements this method, it should delegate to the 958 * superclass as well. 959 * 960 * @param chng a description of the change 961 * @param attr the attributes for the change 962 */ 963 protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) { 964 if( getProperty(I18NProperty).equals( Boolean.TRUE ) ) 965 updateBidi( chng ); 966 967 // Check if a multi byte is encountered in the inserted text. 968 if (chng.type == DocumentEvent.EventType.INSERT && 969 chng.getLength() > 0 && 970 !Boolean.TRUE.equals(getProperty(MultiByteProperty))) { 971 Segment segment = SegmentCache.getSharedSegment(); 972 try { 973 getText(chng.getOffset(), chng.getLength(), segment); 974 segment.first(); 975 do { 976 if ((int)segment.current() > 255) { 977 putProperty(MultiByteProperty, Boolean.TRUE); 978 break; 979 } 980 } while (segment.next() != Segment.DONE); 981 } catch (BadLocationException ble) { 982 // Should never happen 983 } 984 SegmentCache.releaseSharedSegment(segment); 985 } 986 } 987 988 /** 989 * Updates any document structure as a result of text removal. This 990 * method is called before the text is actually removed from the Content. 991 * This will happen within a write lock. If a subclass 992 * of this class reimplements this method, it should delegate to the 993 * superclass as well. 994 * 995 * @param chng a description of the change 996 */ 997 protected void removeUpdate(DefaultDocumentEvent chng) { 998 } 999 1000 /** 1001 * Updates any document structure as a result of text removal. This 1002 * method is called after the text has been removed from the Content. 1003 * This will happen within a write lock. If a subclass 1004 * of this class reimplements this method, it should delegate to the 1005 * superclass as well. 1006 * 1007 * @param chng a description of the change 1008 */ 1009 protected void postRemoveUpdate(DefaultDocumentEvent chng) { 1010 if( getProperty(I18NProperty).equals( Boolean.TRUE ) ) 1011 updateBidi( chng ); 1012 } 1013 1014 1015 /** 1016 * Update the bidi element structure as a result of the given change 1017 * to the document. The given change will be updated to reflect the 1018 * changes made to the bidi structure. 1019 * 1020 * This method assumes that every offset in the model is contained in 1021 * exactly one paragraph. This method also assumes that it is called 1022 * after the change is made to the default element structure. 1023 */ 1024 void updateBidi( DefaultDocumentEvent chng ) { 1025 1026 // Calculate the range of paragraphs affected by the change. 1027 int firstPStart; 1028 int lastPEnd; 1029 if( chng.type == DocumentEvent.EventType.INSERT 1030 || chng.type == DocumentEvent.EventType.CHANGE ) 1031 { 1032 int chngStart = chng.getOffset(); 1033 int chngEnd = chngStart + chng.getLength(); 1034 firstPStart = getParagraphElement(chngStart).getStartOffset(); 1035 lastPEnd = getParagraphElement(chngEnd).getEndOffset(); 1036 } else if( chng.type == DocumentEvent.EventType.REMOVE ) { 1037 Element paragraph = getParagraphElement( chng.getOffset() ); 1038 firstPStart = paragraph.getStartOffset(); 1039 lastPEnd = paragraph.getEndOffset(); 1040 } else { 1041 throw new Error("Internal error: unknown event type."); 1042 } 1043 //System.out.println("updateBidi: firstPStart = " + firstPStart + " lastPEnd = " + lastPEnd ); 1044 1045 1046 // Calculate the bidi levels for the affected range of paragraphs. The 1047 // levels array will contain a bidi level for each character in the 1048 // affected text. 1049 byte levels[] = calculateBidiLevels( firstPStart, lastPEnd ); 1050 1051 1052 Vector<Element> newElements = new Vector<Element>(); 1053 1054 // Calculate the first span of characters in the affected range with 1055 // the same bidi level. If this level is the same as the level of the 1056 // previous bidi element (the existing bidi element containing 1057 // firstPStart-1), then merge in the previous element. If not, but 1058 // the previous element overlaps the affected range, truncate the 1059 // previous element at firstPStart. 1060 int firstSpanStart = firstPStart; 1061 int removeFromIndex = 0; 1062 if( firstSpanStart > 0 ) { 1063 int prevElemIndex = bidiRoot.getElementIndex(firstPStart-1); 1064 removeFromIndex = prevElemIndex; 1065 Element prevElem = bidiRoot.getElement(prevElemIndex); 1066 int prevLevel=StyleConstants.getBidiLevel(prevElem.getAttributes()); 1067 //System.out.println("createbidiElements: prevElem= " + prevElem + " prevLevel= " + prevLevel + "level[0] = " + levels[0]); 1068 if( prevLevel==levels[0] ) { 1069 firstSpanStart = prevElem.getStartOffset(); 1070 } else if( prevElem.getEndOffset() > firstPStart ) { 1071 newElements.addElement(new BidiElement(bidiRoot, 1072 prevElem.getStartOffset(), 1073 firstPStart, prevLevel)); 1074 } else { 1075 removeFromIndex++; 1076 } 1077 } 1078 1079 int firstSpanEnd = 0; 1080 while((firstSpanEnd<levels.length) && (levels[firstSpanEnd]==levels[0])) 1081 firstSpanEnd++; 1082 1083 1084 // Calculate the last span of characters in the affected range with 1085 // the same bidi level. If this level is the same as the level of the 1086 // next bidi element (the existing bidi element containing lastPEnd), 1087 // then merge in the next element. If not, but the next element 1088 // overlaps the affected range, adjust the next element to start at 1089 // lastPEnd. 1090 int lastSpanEnd = lastPEnd; 1091 Element newNextElem = null; 1092 int removeToIndex = bidiRoot.getElementCount() - 1; 1093 if( lastSpanEnd <= getLength() ) { 1094 int nextElemIndex = bidiRoot.getElementIndex( lastPEnd ); 1095 removeToIndex = nextElemIndex; 1096 Element nextElem = bidiRoot.getElement( nextElemIndex ); 1097 int nextLevel = StyleConstants.getBidiLevel(nextElem.getAttributes()); 1098 if( nextLevel == levels[levels.length-1] ) { 1099 lastSpanEnd = nextElem.getEndOffset(); 1100 } else if( nextElem.getStartOffset() < lastPEnd ) { 1101 newNextElem = new BidiElement(bidiRoot, lastPEnd, 1102 nextElem.getEndOffset(), 1103 nextLevel); 1104 } else { 1105 removeToIndex--; 1106 } 1107 } 1108 1109 int lastSpanStart = levels.length; 1110 while( (lastSpanStart>firstSpanEnd) 1111 && (levels[lastSpanStart-1]==levels[levels.length-1]) ) 1112 lastSpanStart--; 1113 1114 1115 // If the first and last spans are contiguous and have the same level, 1116 // merge them and create a single new element for the entire span. 1117 // Otherwise, create elements for the first and last spans as well as 1118 // any spans in between. 1119 if((firstSpanEnd==lastSpanStart)&&(levels[0]==levels[levels.length-1])){ 1120 newElements.addElement(new BidiElement(bidiRoot, firstSpanStart, 1121 lastSpanEnd, levels[0])); 1122 } else { 1123 // Create an element for the first span. 1124 newElements.addElement(new BidiElement(bidiRoot, firstSpanStart, 1125 firstSpanEnd+firstPStart, 1126 levels[0])); 1127 // Create elements for the spans in between the first and last 1128 for( int i=firstSpanEnd; i<lastSpanStart; ) { 1129 //System.out.println("executed line 872"); 1130 int j; 1131 for( j=i; (j<levels.length) && (levels[j] == levels[i]); j++ ); 1132 newElements.addElement(new BidiElement(bidiRoot, firstPStart+i, 1133 firstPStart+j, 1134 (int)levels[i])); 1135 i=j; 1136 } 1137 // Create an element for the last span. 1138 newElements.addElement(new BidiElement(bidiRoot, 1139 lastSpanStart+firstPStart, 1140 lastSpanEnd, 1141 levels[levels.length-1])); 1142 } 1143 1144 if( newNextElem != null ) 1145 newElements.addElement( newNextElem ); 1146 1147 1148 // Calculate the set of existing bidi elements which must be 1149 // removed. 1150 int removedElemCount = 0; 1151 if( bidiRoot.getElementCount() > 0 ) { 1152 removedElemCount = removeToIndex - removeFromIndex + 1; 1153 } 1154 Element[] removedElems = new Element[removedElemCount]; 1155 for( int i=0; i<removedElemCount; i++ ) { 1156 removedElems[i] = bidiRoot.getElement(removeFromIndex+i); 1157 } 1158 1159 Element[] addedElems = new Element[ newElements.size() ]; 1160 newElements.copyInto( addedElems ); 1161 1162 // Update the change record. 1163 ElementEdit ee = new ElementEdit( bidiRoot, removeFromIndex, 1164 removedElems, addedElems ); 1165 chng.addEdit( ee ); 1166 1167 // Update the bidi element structure. 1168 bidiRoot.replace( removeFromIndex, removedElems.length, addedElems ); 1169 } 1170 1171 1172 /** 1173 * Calculate the levels array for a range of paragraphs. 1174 */ 1175 private byte[] calculateBidiLevels( int firstPStart, int lastPEnd ) { 1176 1177 byte levels[] = new byte[ lastPEnd - firstPStart ]; 1178 int levelsEnd = 0; 1179 Boolean defaultDirection = null; 1180 Object d = getProperty(TextAttribute.RUN_DIRECTION); 1181 if (d instanceof Boolean) { 1182 defaultDirection = (Boolean) d; 1183 } 1184 1185 // For each paragraph in the given range of paragraphs, get its 1186 // levels array and add it to the levels array for the entire span. 1187 for(int o=firstPStart; o<lastPEnd; ) { 1188 Element p = getParagraphElement( o ); 1189 int pStart = p.getStartOffset(); 1190 int pEnd = p.getEndOffset(); 1191 1192 // default run direction for the paragraph. This will be 1193 // null if there is no direction override specified (i.e. 1194 // the direction will be determined from the content). 1195 Boolean direction = defaultDirection; 1196 d = p.getAttributes().getAttribute(TextAttribute.RUN_DIRECTION); 1197 if (d instanceof Boolean) { 1198 direction = (Boolean) d; 1199 } 1200 1201 //System.out.println("updateBidi: paragraph start = " + pStart + " paragraph end = " + pEnd); 1202 1203 // Create a Bidi over this paragraph then get the level 1204 // array. 1205 Segment seg = SegmentCache.getSharedSegment(); 1206 try { 1207 getText(pStart, pEnd-pStart, seg); 1208 } catch (BadLocationException e ) { 1209 throw new Error("Internal error: " + e.toString()); 1210 } 1211 // REMIND(bcb) we should really be using a Segment here. 1212 Bidi bidiAnalyzer; 1213 int bidiflag = Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT; 1214 if (direction != null) { 1215 if (TextAttribute.RUN_DIRECTION_LTR.equals(direction)) { 1216 bidiflag = Bidi.DIRECTION_LEFT_TO_RIGHT; 1217 } else { 1218 bidiflag = Bidi.DIRECTION_RIGHT_TO_LEFT; 1219 } 1220 } 1221 bidiAnalyzer = new Bidi(seg.array, seg.offset, null, 0, seg.count, 1222 bidiflag); 1223 BidiUtils.getLevels(bidiAnalyzer, levels, levelsEnd); 1224 levelsEnd += bidiAnalyzer.getLength(); 1225 1226 o = p.getEndOffset(); 1227 SegmentCache.releaseSharedSegment(seg); 1228 } 1229 1230 // REMIND(bcb) remove this code when debugging is done. 1231 if( levelsEnd != levels.length ) 1232 throw new Error("levelsEnd assertion failed."); 1233 1234 return levels; 1235 } 1236 1237 /** 1238 * Gives a diagnostic dump. 1239 * 1240 * @param out the output stream 1241 */ 1242 public void dump(PrintStream out) { 1243 Element root = getDefaultRootElement(); 1244 if (root instanceof AbstractElement) { 1245 ((AbstractElement)root).dump(out, 0); 1246 } 1247 bidiRoot.dump(out,0); 1248 } 1249 1250 /** 1251 * Gets the content for the document. 1252 * 1253 * @return the content 1254 */ 1255 protected final Content getContent() { 1256 return data; 1257 } 1258 1259 /** 1260 * Creates a document leaf element. 1261 * Hook through which elements are created to represent the 1262 * document structure. Because this implementation keeps 1263 * structure and content separate, elements grow automatically 1264 * when content is extended so splits of existing elements 1265 * follow. The document itself gets to decide how to generate 1266 * elements to give flexibility in the type of elements used. 1267 * 1268 * @param parent the parent element 1269 * @param a the attributes for the element 1270 * @param p0 the beginning of the range >= 0 1271 * @param p1 the end of the range >= p0 1272 * @return the new element 1273 */ 1274 protected Element createLeafElement(Element parent, AttributeSet a, int p0, int p1) { 1275 return new LeafElement(parent, a, p0, p1); 1276 } 1277 1278 /** 1279 * Creates a document branch element, that can contain other elements. 1280 * 1281 * @param parent the parent element 1282 * @param a the attributes 1283 * @return the element 1284 */ 1285 protected Element createBranchElement(Element parent, AttributeSet a) { 1286 return new BranchElement(parent, a); 1287 } 1288 1289 // --- Document locking ---------------------------------- 1290 1291 /** 1292 * Fetches the current writing thread if there is one. 1293 * This can be used to distinguish whether a method is 1294 * being called as part of an existing modification or 1295 * if a lock needs to be acquired and a new transaction 1296 * started. 1297 * 1298 * @return the thread actively modifying the document 1299 * or <code>null</code> if there are no modifications in progress 1300 */ 1301 protected synchronized final Thread getCurrentWriter() { 1302 return currWriter; 1303 } 1304 1305 /** 1306 * Acquires a lock to begin mutating the document this lock 1307 * protects. There can be no writing, notification of changes, or 1308 * reading going on in order to gain the lock. Additionally a thread is 1309 * allowed to gain more than one <code>writeLock</code>, 1310 * as long as it doesn't attempt to gain additional <code>writeLock</code>s 1311 * from within document notification. Attempting to gain a 1312 * <code>writeLock</code> from within a DocumentListener notification will 1313 * result in an <code>IllegalStateException</code>. The ability 1314 * to obtain more than one <code>writeLock</code> per thread allows 1315 * subclasses to gain a writeLock, perform a number of operations, then 1316 * release the lock. 1317 * <p> 1318 * Calls to <code>writeLock</code> 1319 * must be balanced with calls to <code>writeUnlock</code>, else the 1320 * <code>Document</code> will be left in a locked state so that no 1321 * reading or writing can be done. 1322 * 1323 * @exception IllegalStateException thrown on illegal lock 1324 * attempt. If the document is implemented properly, this can 1325 * only happen if a document listener attempts to mutate the 1326 * document. This situation violates the bean event model 1327 * where order of delivery is not guaranteed and all listeners 1328 * should be notified before further mutations are allowed. 1329 */ 1330 protected synchronized final void writeLock() { 1331 try { 1332 while ((numReaders > 0) || (currWriter != null)) { 1333 if (Thread.currentThread() == currWriter) { 1334 if (notifyingListeners) { 1335 // Assuming one doesn't do something wrong in a 1336 // subclass this should only happen if a 1337 // DocumentListener tries to mutate the document. 1338 throw new IllegalStateException( 1339 "Attempt to mutate in notification"); 1340 } 1341 numWriters++; 1342 return; 1343 } 1344 wait(); 1345 } 1346 currWriter = Thread.currentThread(); 1347 numWriters = 1; 1348 } catch (InterruptedException e) { 1349 throw new Error("Interrupted attempt to acquire write lock"); 1350 } 1351 } 1352 1353 /** 1354 * Releases a write lock previously obtained via <code>writeLock</code>. 1355 * After decrementing the lock count if there are no outstanding locks 1356 * this will allow a new writer, or readers. 1357 * 1358 * @see #writeLock 1359 */ 1360 protected synchronized final void writeUnlock() { 1361 if (--numWriters <= 0) { 1362 numWriters = 0; 1363 currWriter = null; 1364 notifyAll(); 1365 } 1366 } 1367 1368 /** 1369 * Acquires a lock to begin reading some state from the 1370 * document. There can be multiple readers at the same time. 1371 * Writing blocks the readers until notification of the change 1372 * to the listeners has been completed. This method should 1373 * be used very carefully to avoid unintended compromise 1374 * of the document. It should always be balanced with a 1375 * <code>readUnlock</code>. 1376 * 1377 * @see #readUnlock 1378 */ 1379 public synchronized final void readLock() { 1380 try { 1381 while (currWriter != null) { 1382 if (currWriter == Thread.currentThread()) { 1383 // writer has full read access.... may try to acquire 1384 // lock in notification 1385 return; 1386 } 1387 wait(); 1388 } 1389 numReaders += 1; 1390 } catch (InterruptedException e) { 1391 throw new Error("Interrupted attempt to acquire read lock"); 1392 } 1393 } 1394 1395 /** 1396 * Does a read unlock. This signals that one 1397 * of the readers is done. If there are no more readers 1398 * then writing can begin again. This should be balanced 1399 * with a readLock, and should occur in a finally statement 1400 * so that the balance is guaranteed. The following is an 1401 * example. 1402 * <pre><code> 1403 * readLock(); 1404 * try { 1405 * // do something 1406 * } finally { 1407 * readUnlock(); 1408 * } 1409 * </code></pre> 1410 * 1411 * @see #readLock 1412 */ 1413 public synchronized final void readUnlock() { 1414 if (currWriter == Thread.currentThread()) { 1415 // writer has full read access.... may try to acquire 1416 // lock in notification 1417 return; 1418 } 1419 if (numReaders <= 0) { 1420 throw new StateInvariantError(BAD_LOCK_STATE); 1421 } 1422 numReaders -= 1; 1423 notify(); 1424 } 1425 1426 // --- serialization --------------------------------------------- 1427 1428 private void readObject(ObjectInputStream s) 1429 throws ClassNotFoundException, IOException 1430 { 1431 s.defaultReadObject(); 1432 listenerList = new EventListenerList(); 1433 1434 // Restore bidi structure 1435 //REMIND(bcb) This creates an initial bidi element to account for 1436 //the \n that exists by default in the content. 1437 bidiRoot = new BidiRootElement(); 1438 try { 1439 writeLock(); 1440 Element[] p = new Element[1]; 1441 p[0] = new BidiElement( bidiRoot, 0, 1, 0 ); 1442 bidiRoot.replace(0,0,p); 1443 } finally { 1444 writeUnlock(); 1445 } 1446 // At this point bidi root is only partially correct. To fully 1447 // restore it we need access to getDefaultRootElement. But, this 1448 // is created by the subclass and at this point will be null. We 1449 // thus use registerValidation. 1450 s.registerValidation(new ObjectInputValidation() { 1451 public void validateObject() { 1452 try { 1453 writeLock(); 1454 DefaultDocumentEvent e = new DefaultDocumentEvent 1455 (0, getLength(), 1456 DocumentEvent.EventType.INSERT); 1457 updateBidi( e ); 1458 } 1459 finally { 1460 writeUnlock(); 1461 } 1462 } 1463 }, 0); 1464 } 1465 1466 // ----- member variables ------------------------------------------ 1467 1468 private transient int numReaders; 1469 private transient Thread currWriter; 1470 /** 1471 * The number of writers, all obtained from <code>currWriter</code>. 1472 */ 1473 private transient int numWriters; 1474 /** 1475 * True will notifying listeners. 1476 */ 1477 private transient boolean notifyingListeners; 1478 1479 private static Boolean defaultI18NProperty; 1480 1481 /** 1482 * Storage for document-wide properties. 1483 */ 1484 private Dictionary<Object,Object> documentProperties = null; 1485 1486 /** 1487 * The event listener list for the document. 1488 */ 1489 protected EventListenerList listenerList = new EventListenerList(); 1490 1491 /** 1492 * Where the text is actually stored, and a set of marks 1493 * that track change as the document is edited are managed. 1494 */ 1495 private Content data; 1496 1497 /** 1498 * Factory for the attributes. This is the strategy for 1499 * attribute compression and control of the lifetime of 1500 * a set of attributes as a collection. This may be shared 1501 * with other documents. 1502 */ 1503 private AttributeContext context; 1504 1505 /** 1506 * The root of the bidirectional structure for this document. Its children 1507 * represent character runs with the same Unicode bidi level. 1508 */ 1509 private transient BranchElement bidiRoot; 1510 1511 /** 1512 * Filter for inserting/removing of text. 1513 */ 1514 private DocumentFilter documentFilter; 1515 1516 /** 1517 * Used by DocumentFilter to do actual insert/remove. 1518 */ 1519 private transient DocumentFilter.FilterBypass filterBypass; 1520 1521 private static final String BAD_LOCK_STATE = "document lock failure"; 1522 1523 /** 1524 * Error message to indicate a bad location. 1525 */ 1526 protected static final String BAD_LOCATION = "document location failure"; 1527 1528 /** 1529 * Name of elements used to represent paragraphs 1530 */ 1531 public static final String ParagraphElementName = "paragraph"; 1532 1533 /** 1534 * Name of elements used to represent content 1535 */ 1536 public static final String ContentElementName = "content"; 1537 1538 /** 1539 * Name of elements used to hold sections (lines/paragraphs). 1540 */ 1541 public static final String SectionElementName = "section"; 1542 1543 /** 1544 * Name of elements used to hold a unidirectional run 1545 */ 1546 public static final String BidiElementName = "bidi level"; 1547 1548 /** 1549 * Name of the attribute used to specify element 1550 * names. 1551 */ 1552 public static final String ElementNameAttribute = "$ename"; 1553 1554 /** 1555 * Document property that indicates whether internationalization 1556 * functions such as text reordering or reshaping should be 1557 * performed. This property should not be publicly exposed, 1558 * since it is used for implementation convenience only. As a 1559 * side effect, copies of this property may be in its subclasses 1560 * that live in different packages (e.g. HTMLDocument as of now), 1561 * so those copies should also be taken care of when this property 1562 * needs to be modified. 1563 */ 1564 static final String I18NProperty = "i18n"; 1565 1566 /** 1567 * Document property that indicates if a character has been inserted 1568 * into the document that is more than one byte long. GlyphView uses 1569 * this to determine if it should use BreakIterator. 1570 */ 1571 static final Object MultiByteProperty = "multiByte"; 1572 1573 /** 1574 * Document property that indicates asynchronous loading is 1575 * desired, with the thread priority given as the value. 1576 */ 1577 static final String AsyncLoadPriority = "load priority"; 1578 1579 /** 1580 * Interface to describe a sequence of character content that 1581 * can be edited. Implementations may or may not support a 1582 * history mechanism which will be reflected by whether or not 1583 * mutations return an UndoableEdit implementation. 1584 * @see AbstractDocument 1585 */ 1586 public interface Content { 1587 1588 /** 1589 * Creates a position within the content that will 1590 * track change as the content is mutated. 1591 * 1592 * @param offset the offset in the content >= 0 1593 * @return a Position 1594 * @exception BadLocationException for an invalid offset 1595 */ 1596 public Position createPosition(int offset) throws BadLocationException; 1597 1598 /** 1599 * Current length of the sequence of character content. 1600 * 1601 * @return the length >= 0 1602 */ 1603 public int length(); 1604 1605 /** 1606 * Inserts a string of characters into the sequence. 1607 * 1608 * @param where offset into the sequence to make the insertion >= 0 1609 * @param str string to insert 1610 * @return if the implementation supports a history mechanism, 1611 * a reference to an <code>Edit</code> implementation will be returned, 1612 * otherwise returns <code>null</code> 1613 * @exception BadLocationException thrown if the area covered by 1614 * the arguments is not contained in the character sequence 1615 */ 1616 public UndoableEdit insertString(int where, String str) throws BadLocationException; 1617 1618 /** 1619 * Removes some portion of the sequence. 1620 * 1621 * @param where The offset into the sequence to make the 1622 * insertion >= 0. 1623 * @param nitems The number of items in the sequence to remove >= 0. 1624 * @return If the implementation supports a history mechanism, 1625 * a reference to an Edit implementation will be returned, 1626 * otherwise null. 1627 * @exception BadLocationException Thrown if the area covered by 1628 * the arguments is not contained in the character sequence. 1629 */ 1630 public UndoableEdit remove(int where, int nitems) throws BadLocationException; 1631 1632 /** 1633 * Fetches a string of characters contained in the sequence. 1634 * 1635 * @param where Offset into the sequence to fetch >= 0. 1636 * @param len number of characters to copy >= 0. 1637 * @return the string 1638 * @exception BadLocationException Thrown if the area covered by 1639 * the arguments is not contained in the character sequence. 1640 */ 1641 public String getString(int where, int len) throws BadLocationException; 1642 1643 /** 1644 * Gets a sequence of characters and copies them into a Segment. 1645 * 1646 * @param where the starting offset >= 0 1647 * @param len the number of characters >= 0 1648 * @param txt the target location to copy into 1649 * @exception BadLocationException Thrown if the area covered by 1650 * the arguments is not contained in the character sequence. 1651 */ 1652 public void getChars(int where, int len, Segment txt) throws BadLocationException; 1653 } 1654 1655 /** 1656 * An interface that can be used to allow MutableAttributeSet 1657 * implementations to use pluggable attribute compression 1658 * techniques. Each mutation of the attribute set can be 1659 * used to exchange a previous AttributeSet instance with 1660 * another, preserving the possibility of the AttributeSet 1661 * remaining immutable. An implementation is provided by 1662 * the StyleContext class. 1663 * 1664 * The Element implementations provided by this class use 1665 * this interface to provide their MutableAttributeSet 1666 * implementations, so that different AttributeSet compression 1667 * techniques can be employed. The method 1668 * <code>getAttributeContext</code> should be implemented to 1669 * return the object responsible for implementing the desired 1670 * compression technique. 1671 * 1672 * @see StyleContext 1673 */ 1674 public interface AttributeContext { 1675 1676 /** 1677 * Adds an attribute to the given set, and returns 1678 * the new representative set. 1679 * 1680 * @param old the old attribute set 1681 * @param name the non-null attribute name 1682 * @param value the attribute value 1683 * @return the updated attribute set 1684 * @see MutableAttributeSet#addAttribute 1685 */ 1686 public AttributeSet addAttribute(AttributeSet old, Object name, Object value); 1687 1688 /** 1689 * Adds a set of attributes to the element. 1690 * 1691 * @param old the old attribute set 1692 * @param attr the attributes to add 1693 * @return the updated attribute set 1694 * @see MutableAttributeSet#addAttribute 1695 */ 1696 public AttributeSet addAttributes(AttributeSet old, AttributeSet attr); 1697 1698 /** 1699 * Removes an attribute from the set. 1700 * 1701 * @param old the old attribute set 1702 * @param name the non-null attribute name 1703 * @return the updated attribute set 1704 * @see MutableAttributeSet#removeAttribute 1705 */ 1706 public AttributeSet removeAttribute(AttributeSet old, Object name); 1707 1708 /** 1709 * Removes a set of attributes for the element. 1710 * 1711 * @param old the old attribute set 1712 * @param names the attribute names 1713 * @return the updated attribute set 1714 * @see MutableAttributeSet#removeAttributes 1715 */ 1716 public AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names); 1717 1718 /** 1719 * Removes a set of attributes for the element. 1720 * 1721 * @param old the old attribute set 1722 * @param attrs the attributes 1723 * @return the updated attribute set 1724 * @see MutableAttributeSet#removeAttributes 1725 */ 1726 public AttributeSet removeAttributes(AttributeSet old, AttributeSet attrs); 1727 1728 /** 1729 * Fetches an empty AttributeSet. 1730 * 1731 * @return the attribute set 1732 */ 1733 public AttributeSet getEmptySet(); 1734 1735 /** 1736 * Reclaims an attribute set. 1737 * This is a way for a MutableAttributeSet to mark that it no 1738 * longer need a particular immutable set. This is only necessary 1739 * in 1.1 where there are no weak references. A 1.1 implementation 1740 * would call this in its finalize method. 1741 * 1742 * @param a the attribute set to reclaim 1743 */ 1744 public void reclaim(AttributeSet a); 1745 } 1746 1747 /** 1748 * Implements the abstract part of an element. By default elements 1749 * support attributes by having a field that represents the immutable 1750 * part of the current attribute set for the element. The element itself 1751 * implements MutableAttributeSet which can be used to modify the set 1752 * by fetching a new immutable set. The immutable sets are provided 1753 * by the AttributeContext associated with the document. 1754 * <p> 1755 * <strong>Warning:</strong> 1756 * Serialized objects of this class will not be compatible with 1757 * future Swing releases. The current serialization support is 1758 * appropriate for short term storage or RMI between applications running 1759 * the same version of Swing. As of 1.4, support for long term storage 1760 * of all JavaBeans™ 1761 * has been added to the <code>java.beans</code> package. 1762 * Please see {@link java.beans.XMLEncoder}. 1763 */ 1764 public abstract class AbstractElement implements Element, MutableAttributeSet, Serializable, TreeNode { 1765 1766 /** 1767 * Creates a new AbstractElement. 1768 * 1769 * @param parent the parent element 1770 * @param a the attributes for the element 1771 * @since 1.4 1772 */ 1773 public AbstractElement(Element parent, AttributeSet a) { 1774 this.parent = parent; 1775 attributes = getAttributeContext().getEmptySet(); 1776 if (a != null) { 1777 addAttributes(a); 1778 } 1779 } 1780 1781 private final void indent(PrintWriter out, int n) { 1782 for (int i = 0; i < n; i++) { 1783 out.print(" "); 1784 } 1785 } 1786 1787 /** 1788 * Dumps a debugging representation of the element hierarchy. 1789 * 1790 * @param psOut the output stream 1791 * @param indentAmount the indentation level >= 0 1792 */ 1793 public void dump(PrintStream psOut, int indentAmount) { 1794 PrintWriter out; 1795 try { 1796 out = new PrintWriter(new OutputStreamWriter(psOut,"JavaEsc"), 1797 true); 1798 } catch (UnsupportedEncodingException e){ 1799 out = new PrintWriter(psOut,true); 1800 } 1801 indent(out, indentAmount); 1802 if (getName() == null) { 1803 out.print("<??"); 1804 } else { 1805 out.print("<" + getName()); 1806 } 1807 if (getAttributeCount() > 0) { 1808 out.println(""); 1809 // dump the attributes 1810 Enumeration names = attributes.getAttributeNames(); 1811 while (names.hasMoreElements()) { 1812 Object name = names.nextElement(); 1813 indent(out, indentAmount + 1); 1814 out.println(name + "=" + getAttribute(name)); 1815 } 1816 indent(out, indentAmount); 1817 } 1818 out.println(">"); 1819 1820 if (isLeaf()) { 1821 indent(out, indentAmount+1); 1822 out.print("[" + getStartOffset() + "," + getEndOffset() + "]"); 1823 Content c = getContent(); 1824 try { 1825 String contentStr = c.getString(getStartOffset(), 1826 getEndOffset() - getStartOffset())/*.trim()*/; 1827 if (contentStr.length() > 40) { 1828 contentStr = contentStr.substring(0, 40) + "..."; 1829 } 1830 out.println("["+contentStr+"]"); 1831 } catch (BadLocationException e) { 1832 } 1833 1834 } else { 1835 int n = getElementCount(); 1836 for (int i = 0; i < n; i++) { 1837 AbstractElement e = (AbstractElement) getElement(i); 1838 e.dump(psOut, indentAmount+1); 1839 } 1840 } 1841 } 1842 1843 // --- AttributeSet ---------------------------- 1844 // delegated to the immutable field "attributes" 1845 1846 /** 1847 * Gets the number of attributes that are defined. 1848 * 1849 * @return the number of attributes >= 0 1850 * @see AttributeSet#getAttributeCount 1851 */ 1852 public int getAttributeCount() { 1853 return attributes.getAttributeCount(); 1854 } 1855 1856 /** 1857 * Checks whether a given attribute is defined. 1858 * 1859 * @param attrName the non-null attribute name 1860 * @return true if the attribute is defined 1861 * @see AttributeSet#isDefined 1862 */ 1863 public boolean isDefined(Object attrName) { 1864 return attributes.isDefined(attrName); 1865 } 1866 1867 /** 1868 * Checks whether two attribute sets are equal. 1869 * 1870 * @param attr the attribute set to check against 1871 * @return true if the same 1872 * @see AttributeSet#isEqual 1873 */ 1874 public boolean isEqual(AttributeSet attr) { 1875 return attributes.isEqual(attr); 1876 } 1877 1878 /** 1879 * Copies a set of attributes. 1880 * 1881 * @return the copy 1882 * @see AttributeSet#copyAttributes 1883 */ 1884 public AttributeSet copyAttributes() { 1885 return attributes.copyAttributes(); 1886 } 1887 1888 /** 1889 * Gets the value of an attribute. 1890 * 1891 * @param attrName the non-null attribute name 1892 * @return the attribute value 1893 * @see AttributeSet#getAttribute 1894 */ 1895 public Object getAttribute(Object attrName) { 1896 Object value = attributes.getAttribute(attrName); 1897 if (value == null) { 1898 // The delegate nor it's resolvers had a match, 1899 // so we'll try to resolve through the parent 1900 // element. 1901 AttributeSet a = (parent != null) ? parent.getAttributes() : null; 1902 if (a != null) { 1903 value = a.getAttribute(attrName); 1904 } 1905 } 1906 return value; 1907 } 1908 1909 /** 1910 * Gets the names of all attributes. 1911 * 1912 * @return the attribute names as an enumeration 1913 * @see AttributeSet#getAttributeNames 1914 */ 1915 public Enumeration<?> getAttributeNames() { 1916 return attributes.getAttributeNames(); 1917 } 1918 1919 /** 1920 * Checks whether a given attribute name/value is defined. 1921 * 1922 * @param name the non-null attribute name 1923 * @param value the attribute value 1924 * @return true if the name/value is defined 1925 * @see AttributeSet#containsAttribute 1926 */ 1927 public boolean containsAttribute(Object name, Object value) { 1928 return attributes.containsAttribute(name, value); 1929 } 1930 1931 1932 /** 1933 * Checks whether the element contains all the attributes. 1934 * 1935 * @param attrs the attributes to check 1936 * @return true if the element contains all the attributes 1937 * @see AttributeSet#containsAttributes 1938 */ 1939 public boolean containsAttributes(AttributeSet attrs) { 1940 return attributes.containsAttributes(attrs); 1941 } 1942 1943 /** 1944 * Gets the resolving parent. 1945 * If not overridden, the resolving parent defaults to 1946 * the parent element. 1947 * 1948 * @return the attributes from the parent, <code>null</code> if none 1949 * @see AttributeSet#getResolveParent 1950 */ 1951 public AttributeSet getResolveParent() { 1952 AttributeSet a = attributes.getResolveParent(); 1953 if ((a == null) && (parent != null)) { 1954 a = parent.getAttributes(); 1955 } 1956 return a; 1957 } 1958 1959 // --- MutableAttributeSet ---------------------------------- 1960 // should fetch a new immutable record for the field 1961 // "attributes". 1962 1963 /** 1964 * Adds an attribute to the element. 1965 * 1966 * @param name the non-null attribute name 1967 * @param value the attribute value 1968 * @see MutableAttributeSet#addAttribute 1969 */ 1970 public void addAttribute(Object name, Object value) { 1971 checkForIllegalCast(); 1972 AttributeContext context = getAttributeContext(); 1973 attributes = context.addAttribute(attributes, name, value); 1974 } 1975 1976 /** 1977 * Adds a set of attributes to the element. 1978 * 1979 * @param attr the attributes to add 1980 * @see MutableAttributeSet#addAttribute 1981 */ 1982 public void addAttributes(AttributeSet attr) { 1983 checkForIllegalCast(); 1984 AttributeContext context = getAttributeContext(); 1985 attributes = context.addAttributes(attributes, attr); 1986 } 1987 1988 /** 1989 * Removes an attribute from the set. 1990 * 1991 * @param name the non-null attribute name 1992 * @see MutableAttributeSet#removeAttribute 1993 */ 1994 public void removeAttribute(Object name) { 1995 checkForIllegalCast(); 1996 AttributeContext context = getAttributeContext(); 1997 attributes = context.removeAttribute(attributes, name); 1998 } 1999 2000 /** 2001 * Removes a set of attributes for the element. 2002 * 2003 * @param names the attribute names 2004 * @see MutableAttributeSet#removeAttributes 2005 */ 2006 public void removeAttributes(Enumeration<?> names) { 2007 checkForIllegalCast(); 2008 AttributeContext context = getAttributeContext(); 2009 attributes = context.removeAttributes(attributes, names); 2010 } 2011 2012 /** 2013 * Removes a set of attributes for the element. 2014 * 2015 * @param attrs the attributes 2016 * @see MutableAttributeSet#removeAttributes 2017 */ 2018 public void removeAttributes(AttributeSet attrs) { 2019 checkForIllegalCast(); 2020 AttributeContext context = getAttributeContext(); 2021 if (attrs == this) { 2022 attributes = context.getEmptySet(); 2023 } else { 2024 attributes = context.removeAttributes(attributes, attrs); 2025 } 2026 } 2027 2028 /** 2029 * Sets the resolving parent. 2030 * 2031 * @param parent the parent, null if none 2032 * @see MutableAttributeSet#setResolveParent 2033 */ 2034 public void setResolveParent(AttributeSet parent) { 2035 checkForIllegalCast(); 2036 AttributeContext context = getAttributeContext(); 2037 if (parent != null) { 2038 attributes = 2039 context.addAttribute(attributes, StyleConstants.ResolveAttribute, 2040 parent); 2041 } else { 2042 attributes = 2043 context.removeAttribute(attributes, StyleConstants.ResolveAttribute); 2044 } 2045 } 2046 2047 private final void checkForIllegalCast() { 2048 Thread t = getCurrentWriter(); 2049 if ((t == null) || (t != Thread.currentThread())) { 2050 throw new StateInvariantError("Illegal cast to MutableAttributeSet"); 2051 } 2052 } 2053 2054 // --- Element methods ------------------------------------- 2055 2056 /** 2057 * Retrieves the underlying model. 2058 * 2059 * @return the model 2060 */ 2061 public Document getDocument() { 2062 return AbstractDocument.this; 2063 } 2064 2065 /** 2066 * Gets the parent of the element. 2067 * 2068 * @return the parent 2069 */ 2070 public Element getParentElement() { 2071 return parent; 2072 } 2073 2074 /** 2075 * Gets the attributes for the element. 2076 * 2077 * @return the attribute set 2078 */ 2079 public AttributeSet getAttributes() { 2080 return this; 2081 } 2082 2083 /** 2084 * Gets the name of the element. 2085 * 2086 * @return the name, null if none 2087 */ 2088 public String getName() { 2089 if (attributes.isDefined(ElementNameAttribute)) { 2090 return (String) attributes.getAttribute(ElementNameAttribute); 2091 } 2092 return null; 2093 } 2094 2095 /** 2096 * Gets the starting offset in the model for the element. 2097 * 2098 * @return the offset >= 0 2099 */ 2100 public abstract int getStartOffset(); 2101 2102 /** 2103 * Gets the ending offset in the model for the element. 2104 * 2105 * @return the offset >= 0 2106 */ 2107 public abstract int getEndOffset(); 2108 2109 /** 2110 * Gets a child element. 2111 * 2112 * @param index the child index, >= 0 && < getElementCount() 2113 * @return the child element 2114 */ 2115 public abstract Element getElement(int index); 2116 2117 /** 2118 * Gets the number of children for the element. 2119 * 2120 * @return the number of children >= 0 2121 */ 2122 public abstract int getElementCount(); 2123 2124 /** 2125 * Gets the child element index closest to the given model offset. 2126 * 2127 * @param offset the offset >= 0 2128 * @return the element index >= 0 2129 */ 2130 public abstract int getElementIndex(int offset); 2131 2132 /** 2133 * Checks whether the element is a leaf. 2134 * 2135 * @return true if a leaf 2136 */ 2137 public abstract boolean isLeaf(); 2138 2139 // --- TreeNode methods ------------------------------------- 2140 2141 /** 2142 * Returns the child <code>TreeNode</code> at index 2143 * <code>childIndex</code>. 2144 */ 2145 public TreeNode getChildAt(int childIndex) { 2146 return (TreeNode)getElement(childIndex); 2147 } 2148 2149 /** 2150 * Returns the number of children <code>TreeNode</code>'s 2151 * receiver contains. 2152 * @return the number of children <code>TreeNodews</code>'s 2153 * receiver contains 2154 */ 2155 public int getChildCount() { 2156 return getElementCount(); 2157 } 2158 2159 /** 2160 * Returns the parent <code>TreeNode</code> of the receiver. 2161 * @return the parent <code>TreeNode</code> of the receiver 2162 */ 2163 public TreeNode getParent() { 2164 return (TreeNode)getParentElement(); 2165 } 2166 2167 /** 2168 * Returns the index of <code>node</code> in the receivers children. 2169 * If the receiver does not contain <code>node</code>, -1 will be 2170 * returned. 2171 * @param node the location of interest 2172 * @return the index of <code>node</code> in the receiver's 2173 * children, or -1 if absent 2174 */ 2175 public int getIndex(TreeNode node) { 2176 for(int counter = getChildCount() - 1; counter >= 0; counter--) 2177 if(getChildAt(counter) == node) 2178 return counter; 2179 return -1; 2180 } 2181 2182 /** 2183 * Returns true if the receiver allows children. 2184 * @return true if the receiver allows children, otherwise false 2185 */ 2186 public abstract boolean getAllowsChildren(); 2187 2188 2189 /** 2190 * Returns the children of the receiver as an 2191 * <code>Enumeration</code>. 2192 * @return the children of the receiver as an <code>Enumeration</code> 2193 */ 2194 public abstract Enumeration children(); 2195 2196 2197 // --- serialization --------------------------------------------- 2198 2199 private void writeObject(ObjectOutputStream s) throws IOException { 2200 s.defaultWriteObject(); 2201 StyleContext.writeAttributeSet(s, attributes); 2202 } 2203 2204 private void readObject(ObjectInputStream s) 2205 throws ClassNotFoundException, IOException 2206 { 2207 s.defaultReadObject(); 2208 MutableAttributeSet attr = new SimpleAttributeSet(); 2209 StyleContext.readAttributeSet(s, attr); 2210 AttributeContext context = getAttributeContext(); 2211 attributes = context.addAttributes(SimpleAttributeSet.EMPTY, attr); 2212 } 2213 2214 // ---- variables ----------------------------------------------------- 2215 2216 private Element parent; 2217 private transient AttributeSet attributes; 2218 2219 } 2220 2221 /** 2222 * Implements a composite element that contains other elements. 2223 * <p> 2224 * <strong>Warning:</strong> 2225 * Serialized objects of this class will not be compatible with 2226 * future Swing releases. The current serialization support is 2227 * appropriate for short term storage or RMI between applications running 2228 * the same version of Swing. As of 1.4, support for long term storage 2229 * of all JavaBeans™ 2230 * has been added to the <code>java.beans</code> package. 2231 * Please see {@link java.beans.XMLEncoder}. 2232 */ 2233 public class BranchElement extends AbstractElement { 2234 2235 /** 2236 * Constructs a composite element that initially contains 2237 * no children. 2238 * 2239 * @param parent The parent element 2240 * @param a the attributes for the element 2241 * @since 1.4 2242 */ 2243 public BranchElement(Element parent, AttributeSet a) { 2244 super(parent, a); 2245 children = new AbstractElement[1]; 2246 nchildren = 0; 2247 lastIndex = -1; 2248 } 2249 2250 /** 2251 * Gets the child element that contains 2252 * the given model position. 2253 * 2254 * @param pos the position >= 0 2255 * @return the element, null if none 2256 */ 2257 public Element positionToElement(int pos) { 2258 int index = getElementIndex(pos); 2259 Element child = children[index]; 2260 int p0 = child.getStartOffset(); 2261 int p1 = child.getEndOffset(); 2262 if ((pos >= p0) && (pos < p1)) { 2263 return child; 2264 } 2265 return null; 2266 } 2267 2268 /** 2269 * Replaces content with a new set of elements. 2270 * 2271 * @param offset the starting offset >= 0 2272 * @param length the length to replace >= 0 2273 * @param elems the new elements 2274 */ 2275 public void replace(int offset, int length, Element[] elems) { 2276 int delta = elems.length - length; 2277 int src = offset + length; 2278 int nmove = nchildren - src; 2279 int dest = src + delta; 2280 if ((nchildren + delta) >= children.length) { 2281 // need to grow the array 2282 int newLength = Math.max(2*children.length, nchildren + delta); 2283 AbstractElement[] newChildren = new AbstractElement[newLength]; 2284 System.arraycopy(children, 0, newChildren, 0, offset); 2285 System.arraycopy(elems, 0, newChildren, offset, elems.length); 2286 System.arraycopy(children, src, newChildren, dest, nmove); 2287 children = newChildren; 2288 } else { 2289 // patch the existing array 2290 System.arraycopy(children, src, children, dest, nmove); 2291 System.arraycopy(elems, 0, children, offset, elems.length); 2292 } 2293 nchildren = nchildren + delta; 2294 } 2295 2296 /** 2297 * Converts the element to a string. 2298 * 2299 * @return the string 2300 */ 2301 public String toString() { 2302 return "BranchElement(" + getName() + ") " + getStartOffset() + "," + 2303 getEndOffset() + "\n"; 2304 } 2305 2306 // --- Element methods ----------------------------------- 2307 2308 /** 2309 * Gets the element name. 2310 * 2311 * @return the element name 2312 */ 2313 public String getName() { 2314 String nm = super.getName(); 2315 if (nm == null) { 2316 nm = ParagraphElementName; 2317 } 2318 return nm; 2319 } 2320 2321 /** 2322 * Gets the starting offset in the model for the element. 2323 * 2324 * @return the offset >= 0 2325 */ 2326 public int getStartOffset() { 2327 return children[0].getStartOffset(); 2328 } 2329 2330 /** 2331 * Gets the ending offset in the model for the element. 2332 * @throws NullPointerException if this element has no children 2333 * 2334 * @return the offset >= 0 2335 */ 2336 public int getEndOffset() { 2337 Element child = 2338 (nchildren > 0) ? children[nchildren - 1] : children[0]; 2339 return child.getEndOffset(); 2340 } 2341 2342 /** 2343 * Gets a child element. 2344 * 2345 * @param index the child index, >= 0 && < getElementCount() 2346 * @return the child element, null if none 2347 */ 2348 public Element getElement(int index) { 2349 if (index < nchildren) { 2350 return children[index]; 2351 } 2352 return null; 2353 } 2354 2355 /** 2356 * Gets the number of children for the element. 2357 * 2358 * @return the number of children >= 0 2359 */ 2360 public int getElementCount() { 2361 return nchildren; 2362 } 2363 2364 /** 2365 * Gets the child element index closest to the given model offset. 2366 * 2367 * @param offset the offset >= 0 2368 * @return the element index >= 0 2369 */ 2370 public int getElementIndex(int offset) { 2371 int index; 2372 int lower = 0; 2373 int upper = nchildren - 1; 2374 int mid = 0; 2375 int p0 = getStartOffset(); 2376 int p1; 2377 2378 if (nchildren == 0) { 2379 return 0; 2380 } 2381 if (offset >= getEndOffset()) { 2382 return nchildren - 1; 2383 } 2384 2385 // see if the last index can be used. 2386 if ((lastIndex >= lower) && (lastIndex <= upper)) { 2387 Element lastHit = children[lastIndex]; 2388 p0 = lastHit.getStartOffset(); 2389 p1 = lastHit.getEndOffset(); 2390 if ((offset >= p0) && (offset < p1)) { 2391 return lastIndex; 2392 } 2393 2394 // last index wasn't a hit, but it does give useful info about 2395 // where a hit (if any) would be. 2396 if (offset < p0) { 2397 upper = lastIndex; 2398 } else { 2399 lower = lastIndex; 2400 } 2401 } 2402 2403 while (lower <= upper) { 2404 mid = lower + ((upper - lower) / 2); 2405 Element elem = children[mid]; 2406 p0 = elem.getStartOffset(); 2407 p1 = elem.getEndOffset(); 2408 if ((offset >= p0) && (offset < p1)) { 2409 // found the location 2410 index = mid; 2411 lastIndex = index; 2412 return index; 2413 } else if (offset < p0) { 2414 upper = mid - 1; 2415 } else { 2416 lower = mid + 1; 2417 } 2418 } 2419 2420 // didn't find it, but we indicate the index of where it would belong 2421 if (offset < p0) { 2422 index = mid; 2423 } else { 2424 index = mid + 1; 2425 } 2426 lastIndex = index; 2427 return index; 2428 } 2429 2430 /** 2431 * Checks whether the element is a leaf. 2432 * 2433 * @return true if a leaf 2434 */ 2435 public boolean isLeaf() { 2436 return false; 2437 } 2438 2439 2440 // ------ TreeNode ---------------------------------------------- 2441 2442 /** 2443 * Returns true if the receiver allows children. 2444 * @return true if the receiver allows children, otherwise false 2445 */ 2446 public boolean getAllowsChildren() { 2447 return true; 2448 } 2449 2450 2451 /** 2452 * Returns the children of the receiver as an 2453 * <code>Enumeration</code>. 2454 * @return the children of the receiver 2455 */ 2456 public Enumeration children() { 2457 if(nchildren == 0) 2458 return null; 2459 2460 Vector<AbstractElement> tempVector = new Vector<AbstractElement>(nchildren); 2461 2462 for(int counter = 0; counter < nchildren; counter++) 2463 tempVector.addElement(children[counter]); 2464 return tempVector.elements(); 2465 } 2466 2467 // ------ members ---------------------------------------------- 2468 2469 private AbstractElement[] children; 2470 private int nchildren; 2471 private int lastIndex; 2472 } 2473 2474 /** 2475 * Implements an element that directly represents content of 2476 * some kind. 2477 * <p> 2478 * <strong>Warning:</strong> 2479 * Serialized objects of this class will not be compatible with 2480 * future Swing releases. The current serialization support is 2481 * appropriate for short term storage or RMI between applications running 2482 * the same version of Swing. As of 1.4, support for long term storage 2483 * of all JavaBeans™ 2484 * has been added to the <code>java.beans</code> package. 2485 * Please see {@link java.beans.XMLEncoder}. 2486 * 2487 * @see Element 2488 */ 2489 public class LeafElement extends AbstractElement { 2490 2491 /** 2492 * Constructs an element that represents content within the 2493 * document (has no children). 2494 * 2495 * @param parent The parent element 2496 * @param a The element attributes 2497 * @param offs0 The start offset >= 0 2498 * @param offs1 The end offset >= offs0 2499 * @since 1.4 2500 */ 2501 public LeafElement(Element parent, AttributeSet a, int offs0, int offs1) { 2502 super(parent, a); 2503 try { 2504 p0 = createPosition(offs0); 2505 p1 = createPosition(offs1); 2506 } catch (BadLocationException e) { 2507 p0 = null; 2508 p1 = null; 2509 throw new StateInvariantError("Can't create Position references"); 2510 } 2511 } 2512 2513 /** 2514 * Converts the element to a string. 2515 * 2516 * @return the string 2517 */ 2518 public String toString() { 2519 return "LeafElement(" + getName() + ") " + p0 + "," + p1 + "\n"; 2520 } 2521 2522 // --- Element methods --------------------------------------------- 2523 2524 /** 2525 * Gets the starting offset in the model for the element. 2526 * 2527 * @return the offset >= 0 2528 */ 2529 public int getStartOffset() { 2530 return p0.getOffset(); 2531 } 2532 2533 /** 2534 * Gets the ending offset in the model for the element. 2535 * 2536 * @return the offset >= 0 2537 */ 2538 public int getEndOffset() { 2539 return p1.getOffset(); 2540 } 2541 2542 /** 2543 * Gets the element name. 2544 * 2545 * @return the name 2546 */ 2547 public String getName() { 2548 String nm = super.getName(); 2549 if (nm == null) { 2550 nm = ContentElementName; 2551 } 2552 return nm; 2553 } 2554 2555 /** 2556 * Gets the child element index closest to the given model offset. 2557 * 2558 * @param pos the offset >= 0 2559 * @return the element index >= 0 2560 */ 2561 public int getElementIndex(int pos) { 2562 return -1; 2563 } 2564 2565 /** 2566 * Gets a child element. 2567 * 2568 * @param index the child index, >= 0 && < getElementCount() 2569 * @return the child element 2570 */ 2571 public Element getElement(int index) { 2572 return null; 2573 } 2574 2575 /** 2576 * Returns the number of child elements. 2577 * 2578 * @return the number of children >= 0 2579 */ 2580 public int getElementCount() { 2581 return 0; 2582 } 2583 2584 /** 2585 * Checks whether the element is a leaf. 2586 * 2587 * @return true if a leaf 2588 */ 2589 public boolean isLeaf() { 2590 return true; 2591 } 2592 2593 // ------ TreeNode ---------------------------------------------- 2594 2595 /** 2596 * Returns true if the receiver allows children. 2597 * @return true if the receiver allows children, otherwise false 2598 */ 2599 public boolean getAllowsChildren() { 2600 return false; 2601 } 2602 2603 2604 /** 2605 * Returns the children of the receiver as an 2606 * <code>Enumeration</code>. 2607 * @return the children of the receiver 2608 */ 2609 public Enumeration children() { 2610 return null; 2611 } 2612 2613 // --- serialization --------------------------------------------- 2614 2615 private void writeObject(ObjectOutputStream s) throws IOException { 2616 s.defaultWriteObject(); 2617 s.writeInt(p0.getOffset()); 2618 s.writeInt(p1.getOffset()); 2619 } 2620 2621 private void readObject(ObjectInputStream s) 2622 throws ClassNotFoundException, IOException 2623 { 2624 s.defaultReadObject(); 2625 2626 // set the range with positions that track change 2627 int off0 = s.readInt(); 2628 int off1 = s.readInt(); 2629 try { 2630 p0 = createPosition(off0); 2631 p1 = createPosition(off1); 2632 } catch (BadLocationException e) { 2633 p0 = null; 2634 p1 = null; 2635 throw new IOException("Can't restore Position references"); 2636 } 2637 } 2638 2639 // ---- members ----------------------------------------------------- 2640 2641 private transient Position p0; 2642 private transient Position p1; 2643 } 2644 2645 /** 2646 * Represents the root element of the bidirectional element structure. 2647 * The root element is the only element in the bidi element structure 2648 * which contains children. 2649 */ 2650 class BidiRootElement extends BranchElement { 2651 2652 BidiRootElement() { 2653 super( null, null ); 2654 } 2655 2656 /** 2657 * Gets the name of the element. 2658 * @return the name 2659 */ 2660 public String getName() { 2661 return "bidi root"; 2662 } 2663 } 2664 2665 /** 2666 * Represents an element of the bidirectional element structure. 2667 */ 2668 class BidiElement extends LeafElement { 2669 2670 /** 2671 * Creates a new BidiElement. 2672 */ 2673 BidiElement(Element parent, int start, int end, int level) { 2674 super(parent, new SimpleAttributeSet(), start, end); 2675 addAttribute(StyleConstants.BidiLevel, Integer.valueOf(level)); 2676 //System.out.println("BidiElement: start = " + start 2677 // + " end = " + end + " level = " + level ); 2678 } 2679 2680 /** 2681 * Gets the name of the element. 2682 * @return the name 2683 */ 2684 public String getName() { 2685 return BidiElementName; 2686 } 2687 2688 int getLevel() { 2689 Integer o = (Integer) getAttribute(StyleConstants.BidiLevel); 2690 if (o != null) { 2691 return o.intValue(); 2692 } 2693 return 0; // Level 0 is base level (non-embedded) left-to-right 2694 } 2695 2696 boolean isLeftToRight() { 2697 return ((getLevel() % 2) == 0); 2698 } 2699 } 2700 2701 /** 2702 * Stores document changes as the document is being 2703 * modified. Can subsequently be used for change notification 2704 * when done with the document modification transaction. 2705 * This is used by the AbstractDocument class and its extensions 2706 * for broadcasting change information to the document listeners. 2707 */ 2708 public class DefaultDocumentEvent extends CompoundEdit implements DocumentEvent { 2709 2710 /** 2711 * Constructs a change record. 2712 * 2713 * @param offs the offset into the document of the change >= 0 2714 * @param len the length of the change >= 0 2715 * @param type the type of event (DocumentEvent.EventType) 2716 * @since 1.4 2717 */ 2718 public DefaultDocumentEvent(int offs, int len, DocumentEvent.EventType type) { 2719 super(); 2720 offset = offs; 2721 length = len; 2722 this.type = type; 2723 } 2724 2725 /** 2726 * Returns a string description of the change event. 2727 * 2728 * @return a string 2729 */ 2730 public String toString() { 2731 return edits.toString(); 2732 } 2733 2734 // --- CompoundEdit methods -------------------------- 2735 2736 /** 2737 * Adds a document edit. If the number of edits crosses 2738 * a threshold, this switches on a hashtable lookup for 2739 * ElementChange implementations since access of these 2740 * needs to be relatively quick. 2741 * 2742 * @param anEdit a document edit record 2743 * @return true if the edit was added 2744 */ 2745 public boolean addEdit(UndoableEdit anEdit) { 2746 // if the number of changes gets too great, start using 2747 // a hashtable for to locate the change for a given element. 2748 if ((changeLookup == null) && (edits.size() > 10)) { 2749 changeLookup = new Hashtable<Element, ElementChange>(); 2750 int n = edits.size(); 2751 for (int i = 0; i < n; i++) { 2752 Object o = edits.elementAt(i); 2753 if (o instanceof DocumentEvent.ElementChange) { 2754 DocumentEvent.ElementChange ec = (DocumentEvent.ElementChange) o; 2755 changeLookup.put(ec.getElement(), ec); 2756 } 2757 } 2758 } 2759 2760 // if we have a hashtable... add the entry if it's 2761 // an ElementChange. 2762 if ((changeLookup != null) && (anEdit instanceof DocumentEvent.ElementChange)) { 2763 DocumentEvent.ElementChange ec = (DocumentEvent.ElementChange) anEdit; 2764 changeLookup.put(ec.getElement(), ec); 2765 } 2766 return super.addEdit(anEdit); 2767 } 2768 2769 /** 2770 * Redoes a change. 2771 * 2772 * @exception CannotRedoException if the change cannot be redone 2773 */ 2774 public void redo() throws CannotRedoException { 2775 writeLock(); 2776 try { 2777 // change the state 2778 super.redo(); 2779 // fire a DocumentEvent to notify the view(s) 2780 UndoRedoDocumentEvent ev = new UndoRedoDocumentEvent(this, false); 2781 if (type == DocumentEvent.EventType.INSERT) { 2782 fireInsertUpdate(ev); 2783 } else if (type == DocumentEvent.EventType.REMOVE) { 2784 fireRemoveUpdate(ev); 2785 } else { 2786 fireChangedUpdate(ev); 2787 } 2788 } finally { 2789 writeUnlock(); 2790 } 2791 } 2792 2793 /** 2794 * Undoes a change. 2795 * 2796 * @exception CannotUndoException if the change cannot be undone 2797 */ 2798 public void undo() throws CannotUndoException { 2799 writeLock(); 2800 try { 2801 // change the state 2802 super.undo(); 2803 // fire a DocumentEvent to notify the view(s) 2804 UndoRedoDocumentEvent ev = new UndoRedoDocumentEvent(this, true); 2805 if (type == DocumentEvent.EventType.REMOVE) { 2806 fireInsertUpdate(ev); 2807 } else if (type == DocumentEvent.EventType.INSERT) { 2808 fireRemoveUpdate(ev); 2809 } else { 2810 fireChangedUpdate(ev); 2811 } 2812 } finally { 2813 writeUnlock(); 2814 } 2815 } 2816 2817 /** 2818 * DefaultDocument events are significant. If you wish to aggregate 2819 * DefaultDocumentEvents to present them as a single edit to the user 2820 * place them into a CompoundEdit. 2821 * 2822 * @return whether the event is significant for edit undo purposes 2823 */ 2824 public boolean isSignificant() { 2825 return true; 2826 } 2827 2828 2829 /** 2830 * Provides a localized, human readable description of this edit 2831 * suitable for use in, say, a change log. 2832 * 2833 * @return the description 2834 */ 2835 public String getPresentationName() { 2836 DocumentEvent.EventType type = getType(); 2837 if(type == DocumentEvent.EventType.INSERT) 2838 return UIManager.getString("AbstractDocument.additionText"); 2839 if(type == DocumentEvent.EventType.REMOVE) 2840 return UIManager.getString("AbstractDocument.deletionText"); 2841 return UIManager.getString("AbstractDocument.styleChangeText"); 2842 } 2843 2844 /** 2845 * Provides a localized, human readable description of the undoable 2846 * form of this edit, e.g. for use as an Undo menu item. Typically 2847 * derived from getDescription(); 2848 * 2849 * @return the description 2850 */ 2851 public String getUndoPresentationName() { 2852 return UIManager.getString("AbstractDocument.undoText") + " " + 2853 getPresentationName(); 2854 } 2855 2856 /** 2857 * Provides a localized, human readable description of the redoable 2858 * form of this edit, e.g. for use as a Redo menu item. Typically 2859 * derived from getPresentationName(); 2860 * 2861 * @return the description 2862 */ 2863 public String getRedoPresentationName() { 2864 return UIManager.getString("AbstractDocument.redoText") + " " + 2865 getPresentationName(); 2866 } 2867 2868 // --- DocumentEvent methods -------------------------- 2869 2870 /** 2871 * Returns the type of event. 2872 * 2873 * @return the event type as a DocumentEvent.EventType 2874 * @see DocumentEvent#getType 2875 */ 2876 public DocumentEvent.EventType getType() { 2877 return type; 2878 } 2879 2880 /** 2881 * Returns the offset within the document of the start of the change. 2882 * 2883 * @return the offset >= 0 2884 * @see DocumentEvent#getOffset 2885 */ 2886 public int getOffset() { 2887 return offset; 2888 } 2889 2890 /** 2891 * Returns the length of the change. 2892 * 2893 * @return the length >= 0 2894 * @see DocumentEvent#getLength 2895 */ 2896 public int getLength() { 2897 return length; 2898 } 2899 2900 /** 2901 * Gets the document that sourced the change event. 2902 * 2903 * @return the document 2904 * @see DocumentEvent#getDocument 2905 */ 2906 public Document getDocument() { 2907 return AbstractDocument.this; 2908 } 2909 2910 /** 2911 * Gets the changes for an element. 2912 * 2913 * @param elem the element 2914 * @return the changes 2915 */ 2916 public DocumentEvent.ElementChange getChange(Element elem) { 2917 if (changeLookup != null) { 2918 return changeLookup.get(elem); 2919 } 2920 int n = edits.size(); 2921 for (int i = 0; i < n; i++) { 2922 Object o = edits.elementAt(i); 2923 if (o instanceof DocumentEvent.ElementChange) { 2924 DocumentEvent.ElementChange c = (DocumentEvent.ElementChange) o; 2925 if (elem.equals(c.getElement())) { 2926 return c; 2927 } 2928 } 2929 } 2930 return null; 2931 } 2932 2933 // --- member variables ------------------------------------ 2934 2935 private int offset; 2936 private int length; 2937 private Hashtable<Element, ElementChange> changeLookup; 2938 private DocumentEvent.EventType type; 2939 2940 } 2941 2942 /** 2943 * This event used when firing document changes while Undo/Redo 2944 * operations. It just wraps DefaultDocumentEvent and delegates 2945 * all calls to it except getType() which depends on operation 2946 * (Undo or Redo). 2947 */ 2948 class UndoRedoDocumentEvent implements DocumentEvent { 2949 private DefaultDocumentEvent src = null; 2950 private EventType type = null; 2951 2952 public UndoRedoDocumentEvent(DefaultDocumentEvent src, boolean isUndo) { 2953 this.src = src; 2954 if(isUndo) { 2955 if(src.getType().equals(EventType.INSERT)) { 2956 type = EventType.REMOVE; 2957 } else if(src.getType().equals(EventType.REMOVE)) { 2958 type = EventType.INSERT; 2959 } else { 2960 type = src.getType(); 2961 } 2962 } else { 2963 type = src.getType(); 2964 } 2965 } 2966 2967 public DefaultDocumentEvent getSource() { 2968 return src; 2969 } 2970 2971 // DocumentEvent methods delegated to DefaultDocumentEvent source 2972 // except getType() which depends on operation (Undo or Redo). 2973 public int getOffset() { 2974 return src.getOffset(); 2975 } 2976 2977 public int getLength() { 2978 return src.getLength(); 2979 } 2980 2981 public Document getDocument() { 2982 return src.getDocument(); 2983 } 2984 2985 public DocumentEvent.EventType getType() { 2986 return type; 2987 } 2988 2989 public DocumentEvent.ElementChange getChange(Element elem) { 2990 return src.getChange(elem); 2991 } 2992 } 2993 2994 /** 2995 * An implementation of ElementChange that can be added to the document 2996 * event. 2997 */ 2998 public static class ElementEdit extends AbstractUndoableEdit implements DocumentEvent.ElementChange { 2999 3000 /** 3001 * Constructs an edit record. This does not modify the element 3002 * so it can safely be used to <em>catch up</em> a view to the 3003 * current model state for views that just attached to a model. 3004 * 3005 * @param e the element 3006 * @param index the index into the model >= 0 3007 * @param removed a set of elements that were removed 3008 * @param added a set of elements that were added 3009 */ 3010 public ElementEdit(Element e, int index, Element[] removed, Element[] added) { 3011 super(); 3012 this.e = e; 3013 this.index = index; 3014 this.removed = removed; 3015 this.added = added; 3016 } 3017 3018 /** 3019 * Returns the underlying element. 3020 * 3021 * @return the element 3022 */ 3023 public Element getElement() { 3024 return e; 3025 } 3026 3027 /** 3028 * Returns the index into the list of elements. 3029 * 3030 * @return the index >= 0 3031 */ 3032 public int getIndex() { 3033 return index; 3034 } 3035 3036 /** 3037 * Gets a list of children that were removed. 3038 * 3039 * @return the list 3040 */ 3041 public Element[] getChildrenRemoved() { 3042 return removed; 3043 } 3044 3045 /** 3046 * Gets a list of children that were added. 3047 * 3048 * @return the list 3049 */ 3050 public Element[] getChildrenAdded() { 3051 return added; 3052 } 3053 3054 /** 3055 * Redoes a change. 3056 * 3057 * @exception CannotRedoException if the change cannot be redone 3058 */ 3059 public void redo() throws CannotRedoException { 3060 super.redo(); 3061 3062 // Since this event will be reused, switch around added/removed. 3063 Element[] tmp = removed; 3064 removed = added; 3065 added = tmp; 3066 3067 // PENDING(prinz) need MutableElement interface, canRedo() should check 3068 ((AbstractDocument.BranchElement)e).replace(index, removed.length, added); 3069 } 3070 3071 /** 3072 * Undoes a change. 3073 * 3074 * @exception CannotUndoException if the change cannot be undone 3075 */ 3076 public void undo() throws CannotUndoException { 3077 super.undo(); 3078 // PENDING(prinz) need MutableElement interface, canUndo() should check 3079 ((AbstractDocument.BranchElement)e).replace(index, added.length, removed); 3080 3081 // Since this event will be reused, switch around added/removed. 3082 Element[] tmp = removed; 3083 removed = added; 3084 added = tmp; 3085 } 3086 3087 private Element e; 3088 private int index; 3089 private Element[] removed; 3090 private Element[] added; 3091 } 3092 3093 3094 private class DefaultFilterBypass extends DocumentFilter.FilterBypass { 3095 public Document getDocument() { 3096 return AbstractDocument.this; 3097 } 3098 3099 public void remove(int offset, int length) throws 3100 BadLocationException { 3101 handleRemove(offset, length); 3102 } 3103 3104 public void insertString(int offset, String string, 3105 AttributeSet attr) throws 3106 BadLocationException { 3107 handleInsertString(offset, string, attr); 3108 } 3109 3110 public void replace(int offset, int length, String text, 3111 AttributeSet attrs) throws BadLocationException { 3112 handleRemove(offset, length); 3113 handleInsertString(offset, text, attrs); 3114 } 3115 } 3116 }