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