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