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