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