001 /* AbstractDocument.java -- 002 Copyright (C) 2002, 2004, 2005, 2006 Free Software Foundation, Inc. 003 004 This file is part of GNU Classpath. 005 006 GNU Classpath is free software; you can redistribute it and/or modify 007 it under the terms of the GNU General Public License as published by 008 the Free Software Foundation; either version 2, or (at your option) 009 any later version. 010 011 GNU Classpath is distributed in the hope that it will be useful, but 012 WITHOUT ANY WARRANTY; without even the implied warranty of 013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 General Public License for more details. 015 016 You should have received a copy of the GNU General Public License 017 along with GNU Classpath; see the file COPYING. If not, write to the 018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 019 02110-1301 USA. 020 021 Linking this library statically or dynamically with other modules is 022 making a combined work based on this library. Thus, the terms and 023 conditions of the GNU General Public License cover the whole 024 combination. 025 026 As a special exception, the copyright holders of this library give you 027 permission to link this library with independent modules to produce an 028 executable, regardless of the license terms of these independent 029 modules, and to copy and distribute the resulting executable under 030 terms of your choice, provided that you also meet, for each linked 031 independent module, the terms and conditions of the license of that 032 module. An independent module is a module which is not derived from 033 or based on this library. If you modify this library, you may extend 034 this exception to your version of the library, but you are not 035 obligated to do so. If you do not wish to do so, delete this 036 exception statement from your version. */ 037 038 039 package javax.swing.text; 040 041 import java.awt.font.TextAttribute; 042 import java.io.PrintStream; 043 import java.io.Serializable; 044 import java.text.Bidi; 045 import java.util.ArrayList; 046 import java.util.Dictionary; 047 import java.util.Enumeration; 048 import java.util.EventListener; 049 import java.util.HashMap; 050 import java.util.Hashtable; 051 import java.util.Vector; 052 053 import javax.swing.event.DocumentEvent; 054 import javax.swing.event.DocumentListener; 055 import javax.swing.event.EventListenerList; 056 import javax.swing.event.UndoableEditEvent; 057 import javax.swing.event.UndoableEditListener; 058 import javax.swing.text.DocumentFilter; 059 import javax.swing.tree.TreeNode; 060 import javax.swing.undo.AbstractUndoableEdit; 061 import javax.swing.undo.CompoundEdit; 062 import javax.swing.undo.UndoableEdit; 063 064 /** 065 * An abstract base implementation for the {@link Document} interface. 066 * This class provides some common functionality for all <code>Element</code>s, 067 * most notably it implements a locking mechanism to make document modification 068 * thread-safe. 069 * 070 * @author original author unknown 071 * @author Roman Kennke (roman@kennke.org) 072 */ 073 public abstract class AbstractDocument implements Document, Serializable 074 { 075 /** The serialization UID (compatible with JDK1.5). */ 076 private static final long serialVersionUID = 6842927725919637215L; 077 078 /** 079 * Standard error message to indicate a bad location. 080 */ 081 protected static final String BAD_LOCATION = "document location failure"; 082 083 /** 084 * Standard name for unidirectional <code>Element</code>s. 085 */ 086 public static final String BidiElementName = "bidi level"; 087 088 /** 089 * Standard name for content <code>Element</code>s. These are usually 090 * {@link LeafElement}s. 091 */ 092 public static final String ContentElementName = "content"; 093 094 /** 095 * Standard name for paragraph <code>Element</code>s. These are usually 096 * {@link BranchElement}s. 097 */ 098 public static final String ParagraphElementName = "paragraph"; 099 100 /** 101 * Standard name for section <code>Element</code>s. These are usually 102 * {@link DefaultStyledDocument.SectionElement}s. 103 */ 104 public static final String SectionElementName = "section"; 105 106 /** 107 * Attribute key for storing the element name. 108 */ 109 public static final String ElementNameAttribute = "$ename"; 110 111 /** 112 * Standard name for the bidi root element. 113 */ 114 private static final String BidiRootName = "bidi root"; 115 116 /** 117 * Key for storing the asynchronous load priority. 118 */ 119 private static final String AsyncLoadPriority = "load priority"; 120 121 /** 122 * Key for storing the I18N state. 123 */ 124 private static final String I18N = "i18n"; 125 126 /** 127 * The actual content model of this <code>Document</code>. 128 */ 129 Content content; 130 131 /** 132 * The AttributeContext for this <code>Document</code>. 133 */ 134 AttributeContext context; 135 136 /** 137 * The currently installed <code>DocumentFilter</code>. 138 */ 139 DocumentFilter documentFilter; 140 141 /** 142 * The documents properties. 143 */ 144 Dictionary properties; 145 146 /** 147 * Manages event listeners for this <code>Document</code>. 148 */ 149 protected EventListenerList listenerList = new EventListenerList(); 150 151 /** 152 * Stores the current writer thread. Used for locking. 153 */ 154 private Thread currentWriter = null; 155 156 /** 157 * The number of readers. Used for locking. 158 */ 159 private int numReaders = 0; 160 161 /** 162 * The number of current writers. If this is > 1 then the same thread entered 163 * the write lock more than once. 164 */ 165 private int numWriters = 0; 166 167 /** An instance of a DocumentFilter.FilterBypass which allows calling 168 * the insert, remove and replace method without checking for an installed 169 * document filter. 170 */ 171 private DocumentFilter.FilterBypass bypass; 172 173 /** 174 * The bidi root element. 175 */ 176 private BidiRootElement bidiRoot; 177 178 /** 179 * True when we are currently notifying any listeners. This is used 180 * to detect illegal situations in writeLock(). 181 */ 182 private transient boolean notifyListeners; 183 184 /** 185 * Creates a new <code>AbstractDocument</code> with the specified 186 * {@link Content} model. 187 * 188 * @param doc the <code>Content</code> model to be used in this 189 * <code>Document<code> 190 * 191 * @see GapContent 192 * @see StringContent 193 */ 194 protected AbstractDocument(Content doc) 195 { 196 this(doc, StyleContext.getDefaultStyleContext()); 197 } 198 199 /** 200 * Creates a new <code>AbstractDocument</code> with the specified 201 * {@link Content} model and {@link AttributeContext}. 202 * 203 * @param doc the <code>Content</code> model to be used in this 204 * <code>Document<code> 205 * @param ctx the <code>AttributeContext</code> to use 206 * 207 * @see GapContent 208 * @see StringContent 209 */ 210 protected AbstractDocument(Content doc, AttributeContext ctx) 211 { 212 content = doc; 213 context = ctx; 214 215 // FIXME: Fully implement bidi. 216 bidiRoot = new BidiRootElement(); 217 218 // FIXME: This is determined using a Mauve test. Make the document 219 // actually use this. 220 putProperty(I18N, Boolean.FALSE); 221 222 // Add one child to the bidi root. 223 writeLock(); 224 try 225 { 226 Element[] children = new Element[1]; 227 children[0] = new BidiElement(bidiRoot, 0, 1, 0); 228 bidiRoot.replace(0, 0, children); 229 } 230 finally 231 { 232 writeUnlock(); 233 } 234 } 235 236 /** Returns the DocumentFilter.FilterBypass instance for this 237 * document and create it if it does not exist yet. 238 * 239 * @return This document's DocumentFilter.FilterBypass instance. 240 */ 241 private DocumentFilter.FilterBypass getBypass() 242 { 243 if (bypass == null) 244 bypass = new Bypass(); 245 246 return bypass; 247 } 248 249 /** 250 * Returns the paragraph {@link Element} that holds the specified position. 251 * 252 * @param pos the position for which to get the paragraph element 253 * 254 * @return the paragraph {@link Element} that holds the specified position 255 */ 256 public abstract Element getParagraphElement(int pos); 257 258 /** 259 * Returns the default root {@link Element} of this <code>Document</code>. 260 * Usual <code>Document</code>s only have one root element and return this. 261 * However, there may be <code>Document</code> implementations that 262 * support multiple root elements, they have to return a default root element 263 * here. 264 * 265 * @return the default root {@link Element} of this <code>Document</code> 266 */ 267 public abstract Element getDefaultRootElement(); 268 269 /** 270 * Creates and returns a branch element with the specified 271 * <code>parent</code> and <code>attributes</code>. Note that the new 272 * <code>Element</code> is linked to the parent <code>Element</code> 273 * through {@link Element#getParentElement}, but it is not yet added 274 * to the parent <code>Element</code> as child. 275 * 276 * @param parent the parent <code>Element</code> for the new branch element 277 * @param attributes the text attributes to be installed in the new element 278 * 279 * @return the new branch <code>Element</code> 280 * 281 * @see BranchElement 282 */ 283 protected Element createBranchElement(Element parent, 284 AttributeSet attributes) 285 { 286 return new BranchElement(parent, attributes); 287 } 288 289 /** 290 * Creates and returns a leaf element with the specified 291 * <code>parent</code> and <code>attributes</code>. Note that the new 292 * <code>Element</code> is linked to the parent <code>Element</code> 293 * through {@link Element#getParentElement}, but it is not yet added 294 * to the parent <code>Element</code> as child. 295 * 296 * @param parent the parent <code>Element</code> for the new branch element 297 * @param attributes the text attributes to be installed in the new element 298 * 299 * @return the new branch <code>Element</code> 300 * 301 * @see LeafElement 302 */ 303 protected Element createLeafElement(Element parent, AttributeSet attributes, 304 int start, int end) 305 { 306 return new LeafElement(parent, attributes, start, end); 307 } 308 309 /** 310 * Creates a {@link Position} that keeps track of the location at the 311 * specified <code>offset</code>. 312 * 313 * @param offset the location in the document to keep track by the new 314 * <code>Position</code> 315 * 316 * @return the newly created <code>Position</code> 317 * 318 * @throws BadLocationException if <code>offset</code> is not a valid 319 * location in the documents content model 320 */ 321 public synchronized Position createPosition(final int offset) 322 throws BadLocationException 323 { 324 return content.createPosition(offset); 325 } 326 327 /** 328 * Notifies all registered listeners when the document model changes. 329 * 330 * @param event the <code>DocumentEvent</code> to be fired 331 */ 332 protected void fireChangedUpdate(DocumentEvent event) 333 { 334 notifyListeners = true; 335 try 336 { 337 DocumentListener[] listeners = getDocumentListeners(); 338 for (int index = 0; index < listeners.length; ++index) 339 listeners[index].changedUpdate(event); 340 } 341 finally 342 { 343 notifyListeners = false; 344 } 345 } 346 347 /** 348 * Notifies all registered listeners when content is inserted in the document 349 * model. 350 * 351 * @param event the <code>DocumentEvent</code> to be fired 352 */ 353 protected void fireInsertUpdate(DocumentEvent event) 354 { 355 notifyListeners = true; 356 try 357 { 358 DocumentListener[] listeners = getDocumentListeners(); 359 for (int index = 0; index < listeners.length; ++index) 360 listeners[index].insertUpdate(event); 361 } 362 finally 363 { 364 notifyListeners = false; 365 } 366 } 367 368 /** 369 * Notifies all registered listeners when content is removed from the 370 * document model. 371 * 372 * @param event the <code>DocumentEvent</code> to be fired 373 */ 374 protected void fireRemoveUpdate(DocumentEvent event) 375 { 376 notifyListeners = true; 377 try 378 { 379 DocumentListener[] listeners = getDocumentListeners(); 380 for (int index = 0; index < listeners.length; ++index) 381 listeners[index].removeUpdate(event); 382 } 383 finally 384 { 385 notifyListeners = false; 386 } 387 } 388 389 /** 390 * Notifies all registered listeners when an <code>UndoableEdit</code> has 391 * been performed on this <code>Document</code>. 392 * 393 * @param event the <code>UndoableEditEvent</code> to be fired 394 */ 395 protected void fireUndoableEditUpdate(UndoableEditEvent event) 396 { 397 UndoableEditListener[] listeners = getUndoableEditListeners(); 398 399 for (int index = 0; index < listeners.length; ++index) 400 listeners[index].undoableEditHappened(event); 401 } 402 403 /** 404 * Returns the asynchronous loading priority. Returns <code>-1</code> if this 405 * document should not be loaded asynchronously. 406 * 407 * @return the asynchronous loading priority 408 */ 409 public int getAsynchronousLoadPriority() 410 { 411 Object val = getProperty(AsyncLoadPriority); 412 int prio = -1; 413 if (val != null) 414 prio = ((Integer) val).intValue(); 415 return prio; 416 } 417 418 /** 419 * Returns the {@link AttributeContext} used in this <code>Document</code>. 420 * 421 * @return the {@link AttributeContext} used in this <code>Document</code> 422 */ 423 protected final AttributeContext getAttributeContext() 424 { 425 return context; 426 } 427 428 /** 429 * Returns the root element for bidirectional content. 430 * 431 * @return the root element for bidirectional content 432 */ 433 public Element getBidiRootElement() 434 { 435 return bidiRoot; 436 } 437 438 /** 439 * Returns the {@link Content} model for this <code>Document</code> 440 * 441 * @return the {@link Content} model for this <code>Document</code> 442 * 443 * @see GapContent 444 * @see StringContent 445 */ 446 protected final Content getContent() 447 { 448 return content; 449 } 450 451 /** 452 * Returns the thread that currently modifies this <code>Document</code> 453 * if there is one, otherwise <code>null</code>. This can be used to 454 * distinguish between a method call that is part of an ongoing modification 455 * or if it is a separate modification for which a new lock must be aquired. 456 * 457 * @return the thread that currently modifies this <code>Document</code> 458 * if there is one, otherwise <code>null</code> 459 */ 460 protected final synchronized Thread getCurrentWriter() 461 { 462 return currentWriter; 463 } 464 465 /** 466 * Returns the properties of this <code>Document</code>. 467 * 468 * @return the properties of this <code>Document</code> 469 */ 470 public Dictionary<Object, Object> getDocumentProperties() 471 { 472 // FIXME: make me thread-safe 473 if (properties == null) 474 properties = new Hashtable(); 475 476 return properties; 477 } 478 479 /** 480 * Returns a {@link Position} which will always mark the end of the 481 * <code>Document</code>. 482 * 483 * @return a {@link Position} which will always mark the end of the 484 * <code>Document</code> 485 */ 486 public final Position getEndPosition() 487 { 488 Position p; 489 try 490 { 491 p = createPosition(content.length()); 492 } 493 catch (BadLocationException ex) 494 { 495 // Shouldn't really happen. 496 p = null; 497 } 498 return p; 499 } 500 501 /** 502 * Returns the length of this <code>Document</code>'s content. 503 * 504 * @return the length of this <code>Document</code>'s content 505 */ 506 public int getLength() 507 { 508 // We return Content.getLength() -1 here because there is always an 509 // implicit \n at the end of the Content which does count in Content 510 // but not in Document. 511 return content.length() - 1; 512 } 513 514 /** 515 * Returns all registered listeners of a given listener type. 516 * 517 * @param listenerType the type of the listeners to be queried 518 * 519 * @return all registered listeners of the specified type 520 */ 521 public <T extends EventListener> T[] getListeners(Class<T> listenerType) 522 { 523 return listenerList.getListeners(listenerType); 524 } 525 526 /** 527 * Returns a property from this <code>Document</code>'s property list. 528 * 529 * @param key the key of the property to be fetched 530 * 531 * @return the property for <code>key</code> or <code>null</code> if there 532 * is no such property stored 533 */ 534 public final Object getProperty(Object key) 535 { 536 // FIXME: make me thread-safe 537 Object value = null; 538 if (properties != null) 539 value = properties.get(key); 540 541 return value; 542 } 543 544 /** 545 * Returns all root elements of this <code>Document</code>. By default 546 * this just returns the single root element returned by 547 * {@link #getDefaultRootElement()}. <code>Document</code> implementations 548 * that support multiple roots must override this method and return all roots 549 * here. 550 * 551 * @return all root elements of this <code>Document</code> 552 */ 553 public Element[] getRootElements() 554 { 555 Element[] elements = new Element[2]; 556 elements[0] = getDefaultRootElement(); 557 elements[1] = getBidiRootElement(); 558 return elements; 559 } 560 561 /** 562 * Returns a {@link Position} which will always mark the beginning of the 563 * <code>Document</code>. 564 * 565 * @return a {@link Position} which will always mark the beginning of the 566 * <code>Document</code> 567 */ 568 public final Position getStartPosition() 569 { 570 Position p; 571 try 572 { 573 p = createPosition(0); 574 } 575 catch (BadLocationException ex) 576 { 577 // Shouldn't really happen. 578 p = null; 579 } 580 return p; 581 } 582 583 /** 584 * Returns a piece of this <code>Document</code>'s content. 585 * 586 * @param offset the start offset of the content 587 * @param length the length of the content 588 * 589 * @return the piece of content specified by <code>offset</code> and 590 * <code>length</code> 591 * 592 * @throws BadLocationException if <code>offset</code> or <code>offset + 593 * length</code> are invalid locations with this 594 * <code>Document</code> 595 */ 596 public String getText(int offset, int length) throws BadLocationException 597 { 598 return content.getString(offset, length); 599 } 600 601 /** 602 * Fetches a piece of this <code>Document</code>'s content and stores 603 * it in the given {@link Segment}. 604 * 605 * @param offset the start offset of the content 606 * @param length the length of the content 607 * @param segment the <code>Segment</code> to store the content in 608 * 609 * @throws BadLocationException if <code>offset</code> or <code>offset + 610 * length</code> are invalid locations with this 611 * <code>Document</code> 612 */ 613 public void getText(int offset, int length, Segment segment) 614 throws BadLocationException 615 { 616 content.getChars(offset, length, segment); 617 } 618 619 /** 620 * Inserts a String into this <code>Document</code> at the specified 621 * position and assigning the specified attributes to it. 622 * 623 * <p>If a {@link DocumentFilter} is installed in this document, the 624 * corresponding method of the filter object is called.</p> 625 * 626 * <p>The method has no effect when <code>text</code> is <code>null</code> 627 * or has a length of zero.</p> 628 * 629 * 630 * @param offset the location at which the string should be inserted 631 * @param text the content to be inserted 632 * @param attributes the text attributes to be assigned to that string 633 * 634 * @throws BadLocationException if <code>offset</code> is not a valid 635 * location in this <code>Document</code> 636 */ 637 public void insertString(int offset, String text, AttributeSet attributes) 638 throws BadLocationException 639 { 640 // Bail out if we have a bogus insertion (Behavior observed in RI). 641 if (text == null || text.length() == 0) 642 return; 643 644 writeLock(); 645 try 646 { 647 if (documentFilter == null) 648 insertStringImpl(offset, text, attributes); 649 else 650 documentFilter.insertString(getBypass(), offset, text, attributes); 651 } 652 finally 653 { 654 writeUnlock(); 655 } 656 } 657 658 void insertStringImpl(int offset, String text, AttributeSet attributes) 659 throws BadLocationException 660 { 661 // Just return when no text to insert was given. 662 if (text == null || text.length() == 0) 663 return; 664 DefaultDocumentEvent event = 665 new DefaultDocumentEvent(offset, text.length(), 666 DocumentEvent.EventType.INSERT); 667 668 UndoableEdit undo = content.insertString(offset, text); 669 if (undo != null) 670 event.addEdit(undo); 671 672 // Check if we need bidi layout. 673 if (getProperty(I18N).equals(Boolean.FALSE)) 674 { 675 Object dir = getProperty(TextAttribute.RUN_DIRECTION); 676 if (TextAttribute.RUN_DIRECTION_RTL.equals(dir)) 677 putProperty(I18N, Boolean.TRUE); 678 else 679 { 680 char[] chars = text.toCharArray(); 681 if (Bidi.requiresBidi(chars, 0, chars.length)) 682 putProperty(I18N, Boolean.TRUE); 683 } 684 } 685 686 insertUpdate(event, attributes); 687 688 fireInsertUpdate(event); 689 690 if (undo != null) 691 fireUndoableEditUpdate(new UndoableEditEvent(this, undo)); 692 } 693 694 /** 695 * Called to indicate that text has been inserted into this 696 * <code>Document</code>. The default implementation does nothing. 697 * This method is executed within a write lock. 698 * 699 * @param chng the <code>DefaultDocumentEvent</code> describing the change 700 * @param attr the attributes of the changed content 701 */ 702 protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) 703 { 704 if (Boolean.TRUE.equals(getProperty(I18N))) 705 updateBidi(chng); 706 } 707 708 /** 709 * Called after some content has been removed from this 710 * <code>Document</code>. The default implementation does nothing. 711 * This method is executed within a write lock. 712 * 713 * @param chng the <code>DefaultDocumentEvent</code> describing the change 714 */ 715 protected void postRemoveUpdate(DefaultDocumentEvent chng) 716 { 717 if (Boolean.TRUE.equals(getProperty(I18N))) 718 updateBidi(chng); 719 } 720 721 /** 722 * Stores a property in this <code>Document</code>'s property list. 723 * 724 * @param key the key of the property to be stored 725 * @param value the value of the property to be stored 726 */ 727 public final void putProperty(Object key, Object value) 728 { 729 // FIXME: make me thread-safe 730 if (properties == null) 731 properties = new Hashtable(); 732 733 if (value == null) 734 properties.remove(key); 735 else 736 properties.put(key, value); 737 738 // Update bidi structure if the RUN_DIRECTION is set. 739 if (TextAttribute.RUN_DIRECTION.equals(key)) 740 { 741 if (TextAttribute.RUN_DIRECTION_RTL.equals(value) 742 && Boolean.FALSE.equals(getProperty(I18N))) 743 putProperty(I18N, Boolean.TRUE); 744 745 if (Boolean.TRUE.equals(getProperty(I18N))) 746 { 747 writeLock(); 748 try 749 { 750 DefaultDocumentEvent ev = 751 new DefaultDocumentEvent(0, getLength(), 752 DocumentEvent.EventType.INSERT); 753 updateBidi(ev); 754 } 755 finally 756 { 757 writeUnlock(); 758 } 759 } 760 } 761 } 762 763 /** 764 * Updates the bidi element structure. 765 * 766 * @param ev the document event for the change 767 */ 768 private void updateBidi(DefaultDocumentEvent ev) 769 { 770 // Determine start and end offset of the paragraphs to be scanned. 771 int start = 0; 772 int end = 0; 773 DocumentEvent.EventType type = ev.getType(); 774 if (type == DocumentEvent.EventType.INSERT 775 || type == DocumentEvent.EventType.CHANGE) 776 { 777 int offs = ev.getOffset(); 778 int endOffs = offs + ev.getLength(); 779 start = getParagraphElement(offs).getStartOffset(); 780 end = getParagraphElement(endOffs).getEndOffset(); 781 } 782 else if (type == DocumentEvent.EventType.REMOVE) 783 { 784 Element par = getParagraphElement(ev.getOffset()); 785 start = par.getStartOffset(); 786 end = par.getEndOffset(); 787 } 788 else 789 assert false : "Unknown event type"; 790 791 // Determine the bidi levels for the affected range. 792 Bidi[] bidis = getBidis(start, end); 793 794 int removeFrom = 0; 795 int removeTo = 0; 796 797 int offs = 0; 798 int lastRunStart = 0; 799 int lastRunEnd = 0; 800 int lastRunLevel = 0; 801 ArrayList newEls = new ArrayList(); 802 for (int i = 0; i < bidis.length; i++) 803 { 804 Bidi bidi = bidis[i]; 805 int numRuns = bidi.getRunCount(); 806 for (int r = 0; r < numRuns; r++) 807 { 808 if (r == 0 && i == 0) 809 { 810 if (start > 0) 811 { 812 // Try to merge with the previous element if it has the 813 // same bidi level as the first run. 814 int prevElIndex = bidiRoot.getElementIndex(start - 1); 815 removeFrom = prevElIndex; 816 Element prevEl = bidiRoot.getElement(prevElIndex); 817 AttributeSet atts = prevEl.getAttributes(); 818 int prevElLevel = StyleConstants.getBidiLevel(atts); 819 if (prevElLevel == bidi.getRunLevel(r)) 820 { 821 // Merge previous element with current run. 822 lastRunStart = prevEl.getStartOffset() - start; 823 lastRunEnd = bidi.getRunLimit(r); 824 lastRunLevel = bidi.getRunLevel(r); 825 } 826 else if (prevEl.getEndOffset() > start) 827 { 828 // Split previous element and replace by 2 new elements. 829 lastRunStart = 0; 830 lastRunEnd = bidi.getRunLimit(r); 831 lastRunLevel = bidi.getRunLevel(r); 832 newEls.add(new BidiElement(bidiRoot, 833 prevEl.getStartOffset(), 834 start, prevElLevel)); 835 } 836 else 837 { 838 // Simply start new run at start location. 839 lastRunStart = 0; 840 lastRunEnd = bidi.getRunLimit(r); 841 lastRunLevel = bidi.getRunLevel(r); 842 removeFrom++; 843 } 844 } 845 else 846 { 847 // Simply start new run at start location. 848 lastRunStart = 0; 849 lastRunEnd = bidi.getRunLimit(r); 850 lastRunLevel = bidi.getRunLevel(r); 851 removeFrom = 0; 852 } 853 } 854 if (i == bidis.length - 1 && r == numRuns - 1) 855 { 856 if (end <= getLength()) 857 { 858 // Try to merge last element with next element. 859 int nextIndex = bidiRoot.getElementIndex(end); 860 Element nextEl = bidiRoot.getElement(nextIndex); 861 AttributeSet atts = nextEl.getAttributes(); 862 int nextLevel = StyleConstants.getBidiLevel(atts); 863 int level = bidi.getRunLevel(r); 864 if (lastRunLevel == level && level == nextLevel) 865 { 866 // Merge runs together. 867 if (lastRunStart + start == nextEl.getStartOffset()) 868 removeTo = nextIndex - 1; 869 else 870 { 871 newEls.add(new BidiElement(bidiRoot, start + lastRunStart, 872 nextEl.getEndOffset(), level)); 873 removeTo = nextIndex; 874 } 875 } 876 else if (lastRunLevel == level) 877 { 878 // Merge current and last run. 879 int endOffs = offs + bidi.getRunLimit(r); 880 newEls.add(new BidiElement(bidiRoot, start + lastRunStart, 881 start + endOffs, level)); 882 if (start + endOffs == nextEl.getStartOffset()) 883 removeTo = nextIndex - 1; 884 else 885 { 886 newEls.add(new BidiElement(bidiRoot, start + endOffs, 887 nextEl.getEndOffset(), 888 nextLevel)); 889 removeTo = nextIndex; 890 } 891 } 892 else if (level == nextLevel) 893 { 894 // Merge current and next run. 895 newEls.add(new BidiElement(bidiRoot, start + lastRunStart, 896 start + lastRunEnd, 897 lastRunLevel)); 898 newEls.add(new BidiElement(bidiRoot, start + lastRunEnd, 899 nextEl.getEndOffset(), level)); 900 removeTo = nextIndex; 901 } 902 else 903 { 904 // Split next element. 905 int endOffs = offs + bidi.getRunLimit(r); 906 newEls.add(new BidiElement(bidiRoot, start + lastRunStart, 907 start + lastRunEnd, 908 lastRunLevel)); 909 newEls.add(new BidiElement(bidiRoot, start + lastRunEnd, 910 start + endOffs, level)); 911 newEls.add(new BidiElement(bidiRoot, start + endOffs, 912 nextEl.getEndOffset(), 913 nextLevel)); 914 removeTo = nextIndex; 915 } 916 } 917 else 918 { 919 removeTo = bidiRoot.getElementIndex(end); 920 int level = bidi.getRunLevel(r); 921 int runEnd = offs + bidi.getRunLimit(r); 922 923 if (level == lastRunLevel) 924 { 925 // Merge with previous. 926 lastRunEnd = offs + runEnd; 927 newEls.add(new BidiElement(bidiRoot, 928 start + lastRunStart, 929 start + runEnd, level)); 930 } 931 else 932 { 933 // Create element for last run and current run. 934 newEls.add(new BidiElement(bidiRoot, start + lastRunStart, 935 start + lastRunEnd, 936 lastRunLevel)); 937 newEls.add(new BidiElement(bidiRoot, 938 start + lastRunEnd, 939 start + runEnd, 940 level)); 941 } 942 } 943 } 944 else 945 { 946 int level = bidi.getRunLevel(r); 947 int runEnd = bidi.getRunLimit(r); 948 949 if (level == lastRunLevel) 950 { 951 // Merge with previous. 952 lastRunEnd = offs + runEnd; 953 } 954 else 955 { 956 // Create element for last run and update values for 957 // current run. 958 newEls.add(new BidiElement(bidiRoot, start + lastRunStart, 959 start + lastRunEnd, 960 lastRunLevel)); 961 lastRunStart = lastRunEnd; 962 lastRunEnd = offs + runEnd; 963 lastRunLevel = level; 964 } 965 } 966 } 967 offs += bidi.getLength(); 968 } 969 970 // Determine the bidi elements which are to be removed. 971 int numRemoved = 0; 972 if (bidiRoot.getElementCount() > 0) 973 numRemoved = removeTo - removeFrom + 1; 974 Element[] removed = new Element[numRemoved]; 975 for (int i = 0; i < numRemoved; i++) 976 removed[i] = bidiRoot.getElement(removeFrom + i); 977 978 Element[] added = new Element[newEls.size()]; 979 added = (Element[]) newEls.toArray(added); 980 981 // Update the event. 982 ElementEdit edit = new ElementEdit(bidiRoot, removeFrom, removed, added); 983 ev.addEdit(edit); 984 985 // Update the structure. 986 bidiRoot.replace(removeFrom, numRemoved, added); 987 } 988 989 /** 990 * Determines the Bidi objects for the paragraphs in the specified range. 991 * 992 * @param start the start of the range 993 * @param end the end of the range 994 * 995 * @return the Bidi analysers for the paragraphs in the range 996 */ 997 private Bidi[] getBidis(int start, int end) 998 { 999 // Determine the default run direction from the document property. 1000 Boolean defaultDir = null; 1001 Object o = getProperty(TextAttribute.RUN_DIRECTION); 1002 if (o instanceof Boolean) 1003 defaultDir = (Boolean) o; 1004 1005 // Scan paragraphs and add their level arrays to the overall levels array. 1006 ArrayList bidis = new ArrayList(); 1007 Segment s = new Segment(); 1008 for (int i = start; i < end;) 1009 { 1010 Element par = getParagraphElement(i); 1011 int pStart = par.getStartOffset(); 1012 int pEnd = par.getEndOffset(); 1013 1014 // Determine the default run direction of the paragraph. 1015 Boolean dir = defaultDir; 1016 o = par.getAttributes().getAttribute(TextAttribute.RUN_DIRECTION); 1017 if (o instanceof Boolean) 1018 dir = (Boolean) o; 1019 1020 // Bidi over the paragraph. 1021 try 1022 { 1023 getText(pStart, pEnd - pStart, s); 1024 } 1025 catch (BadLocationException ex) 1026 { 1027 assert false : "Must not happen"; 1028 } 1029 int flag = Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT; 1030 if (dir != null) 1031 { 1032 if (TextAttribute.RUN_DIRECTION_LTR.equals(dir)) 1033 flag = Bidi.DIRECTION_LEFT_TO_RIGHT; 1034 else 1035 flag = Bidi.DIRECTION_RIGHT_TO_LEFT; 1036 } 1037 Bidi bidi = new Bidi(s.array, s.offset, null, 0, s.count, flag); 1038 bidis.add(bidi); 1039 i = pEnd; 1040 } 1041 Bidi[] ret = new Bidi[bidis.size()]; 1042 ret = (Bidi[]) bidis.toArray(ret); 1043 return ret; 1044 } 1045 1046 /** 1047 * Blocks until a read lock can be obtained. Must block if there is 1048 * currently a writer modifying the <code>Document</code>. 1049 */ 1050 public final synchronized void readLock() 1051 { 1052 try 1053 { 1054 while (currentWriter != null) 1055 { 1056 if (currentWriter == Thread.currentThread()) 1057 return; 1058 wait(); 1059 } 1060 numReaders++; 1061 } 1062 catch (InterruptedException ex) 1063 { 1064 throw new Error("Interrupted during grab read lock"); 1065 } 1066 } 1067 1068 /** 1069 * Releases the read lock. If this was the only reader on this 1070 * <code>Document</code>, writing may begin now. 1071 */ 1072 public final synchronized void readUnlock() 1073 { 1074 // Note we could have a problem here if readUnlock was called without a 1075 // prior call to readLock but the specs simply warn users to ensure that 1076 // balance by using a finally block: 1077 // readLock() 1078 // try 1079 // { 1080 // doSomethingHere 1081 // } 1082 // finally 1083 // { 1084 // readUnlock(); 1085 // } 1086 1087 // All that the JDK seems to check for is that you don't call unlock 1088 // more times than you've previously called lock, but it doesn't make 1089 // sure that the threads calling unlock were the same ones that called lock 1090 1091 // If the current thread holds the write lock, and attempted to also obtain 1092 // a readLock, then numReaders hasn't been incremented and we don't need 1093 // to unlock it here. 1094 if (currentWriter == Thread.currentThread()) 1095 return; 1096 1097 // FIXME: the reference implementation throws a 1098 // javax.swing.text.StateInvariantError here 1099 if (numReaders <= 0) 1100 throw new IllegalStateException("document lock failure"); 1101 1102 // If currentWriter is not null, the application code probably had a 1103 // writeLock and then tried to obtain a readLock, in which case 1104 // numReaders wasn't incremented 1105 numReaders--; 1106 notify(); 1107 } 1108 1109 /** 1110 * Removes a piece of content from this <code>Document</code>. 1111 * 1112 * <p>If a {@link DocumentFilter} is installed in this document, the 1113 * corresponding method of the filter object is called. The 1114 * <code>DocumentFilter</code> is called even if <code>length</code> 1115 * is zero. This is different from {@link #replace}.</p> 1116 * 1117 * <p>Note: When <code>length</code> is zero or below the call is not 1118 * forwarded to the underlying {@link AbstractDocument.Content} instance 1119 * of this document and no exception is thrown.</p> 1120 * 1121 * @param offset the start offset of the fragment to be removed 1122 * @param length the length of the fragment to be removed 1123 * 1124 * @throws BadLocationException if <code>offset</code> or 1125 * <code>offset + length</code> or invalid locations within this 1126 * document 1127 */ 1128 public void remove(int offset, int length) throws BadLocationException 1129 { 1130 writeLock(); 1131 try 1132 { 1133 DocumentFilter f = getDocumentFilter(); 1134 if (f == null) 1135 removeImpl(offset, length); 1136 else 1137 f.remove(getBypass(), offset, length); 1138 } 1139 finally 1140 { 1141 writeUnlock(); 1142 } 1143 } 1144 1145 void removeImpl(int offset, int length) throws BadLocationException 1146 { 1147 // The RI silently ignores all requests that have a negative length. 1148 // Don't ask my why, but that's how it is. 1149 if (length > 0) 1150 { 1151 if (offset < 0 || offset > getLength()) 1152 throw new BadLocationException("Invalid remove position", offset); 1153 1154 if (offset + length > getLength()) 1155 throw new BadLocationException("Invalid remove length", offset); 1156 1157 DefaultDocumentEvent event = 1158 new DefaultDocumentEvent(offset, length, 1159 DocumentEvent.EventType.REMOVE); 1160 1161 // The order of the operations below is critical! 1162 removeUpdate(event); 1163 UndoableEdit temp = content.remove(offset, length); 1164 1165 postRemoveUpdate(event); 1166 fireRemoveUpdate(event); 1167 } 1168 } 1169 1170 /** 1171 * Replaces a piece of content in this <code>Document</code> with 1172 * another piece of content. 1173 * 1174 * <p>If a {@link DocumentFilter} is installed in this document, the 1175 * corresponding method of the filter object is called.</p> 1176 * 1177 * <p>The method has no effect if <code>length</code> is zero (and 1178 * only zero) and, at the same time, <code>text</code> is 1179 * <code>null</code> or has zero length.</p> 1180 * 1181 * @param offset the start offset of the fragment to be removed 1182 * @param length the length of the fragment to be removed 1183 * @param text the text to replace the content with 1184 * @param attributes the text attributes to assign to the new content 1185 * 1186 * @throws BadLocationException if <code>offset</code> or 1187 * <code>offset + length</code> or invalid locations within this 1188 * document 1189 * 1190 * @since 1.4 1191 */ 1192 public void replace(int offset, int length, String text, 1193 AttributeSet attributes) 1194 throws BadLocationException 1195 { 1196 // Bail out if we have a bogus replacement (Behavior observed in RI). 1197 if (length == 0 1198 && (text == null || text.length() == 0)) 1199 return; 1200 1201 writeLock(); 1202 try 1203 { 1204 if (documentFilter == null) 1205 { 1206 // It is important to call the methods which again do the checks 1207 // of the arguments and the DocumentFilter because subclasses may 1208 // have overridden these methods and provide crucial behavior 1209 // which would be skipped if we call the non-checking variants. 1210 // An example for this is PlainDocument where insertString can 1211 // provide a filtering of newlines. 1212 remove(offset, length); 1213 insertString(offset, text, attributes); 1214 } 1215 else 1216 documentFilter.replace(getBypass(), offset, length, text, attributes); 1217 } 1218 finally 1219 { 1220 writeUnlock(); 1221 } 1222 } 1223 1224 void replaceImpl(int offset, int length, String text, 1225 AttributeSet attributes) 1226 throws BadLocationException 1227 { 1228 removeImpl(offset, length); 1229 insertStringImpl(offset, text, attributes); 1230 } 1231 1232 /** 1233 * Adds a <code>DocumentListener</code> object to this document. 1234 * 1235 * @param listener the listener to add 1236 */ 1237 public void addDocumentListener(DocumentListener listener) 1238 { 1239 listenerList.add(DocumentListener.class, listener); 1240 } 1241 1242 /** 1243 * Removes a <code>DocumentListener</code> object from this document. 1244 * 1245 * @param listener the listener to remove 1246 */ 1247 public void removeDocumentListener(DocumentListener listener) 1248 { 1249 listenerList.remove(DocumentListener.class, listener); 1250 } 1251 1252 /** 1253 * Returns all registered <code>DocumentListener</code>s. 1254 * 1255 * @return all registered <code>DocumentListener</code>s 1256 */ 1257 public DocumentListener[] getDocumentListeners() 1258 { 1259 return (DocumentListener[]) getListeners(DocumentListener.class); 1260 } 1261 1262 /** 1263 * Adds an {@link UndoableEditListener} to this <code>Document</code>. 1264 * 1265 * @param listener the listener to add 1266 */ 1267 public void addUndoableEditListener(UndoableEditListener listener) 1268 { 1269 listenerList.add(UndoableEditListener.class, listener); 1270 } 1271 1272 /** 1273 * Removes an {@link UndoableEditListener} from this <code>Document</code>. 1274 * 1275 * @param listener the listener to remove 1276 */ 1277 public void removeUndoableEditListener(UndoableEditListener listener) 1278 { 1279 listenerList.remove(UndoableEditListener.class, listener); 1280 } 1281 1282 /** 1283 * Returns all registered {@link UndoableEditListener}s. 1284 * 1285 * @return all registered {@link UndoableEditListener}s 1286 */ 1287 public UndoableEditListener[] getUndoableEditListeners() 1288 { 1289 return (UndoableEditListener[]) getListeners(UndoableEditListener.class); 1290 } 1291 1292 /** 1293 * Called before some content gets removed from this <code>Document</code>. 1294 * The default implementation does nothing but may be overridden by 1295 * subclasses to modify the <code>Document</code> structure in response 1296 * to a remove request. The method is executed within a write lock. 1297 * 1298 * @param chng the <code>DefaultDocumentEvent</code> describing the change 1299 */ 1300 protected void removeUpdate(DefaultDocumentEvent chng) 1301 { 1302 // Do nothing here. Subclasses may wish to override this. 1303 } 1304 1305 /** 1306 * Called to render this <code>Document</code> visually. It obtains a read 1307 * lock, ensuring that no changes will be made to the <code>document</code> 1308 * during the rendering process. It then calls the {@link Runnable#run()} 1309 * method on <code>runnable</code>. This method <em>must not</em> attempt 1310 * to modifiy the <code>Document</code>, since a deadlock will occur if it 1311 * tries to obtain a write lock. When the {@link Runnable#run()} method 1312 * completes (either naturally or by throwing an exception), the read lock 1313 * is released. Note that there is nothing in this method related to 1314 * the actual rendering. It could be used to execute arbitrary code within 1315 * a read lock. 1316 * 1317 * @param runnable the {@link Runnable} to execute 1318 */ 1319 public void render(Runnable runnable) 1320 { 1321 readLock(); 1322 try 1323 { 1324 runnable.run(); 1325 } 1326 finally 1327 { 1328 readUnlock(); 1329 } 1330 } 1331 1332 /** 1333 * Sets the asynchronous loading priority for this <code>Document</code>. 1334 * A value of <code>-1</code> indicates that this <code>Document</code> 1335 * should be loaded synchronously. 1336 * 1337 * @param p the asynchronous loading priority to set 1338 */ 1339 public void setAsynchronousLoadPriority(int p) 1340 { 1341 Integer val = p >= 0 ? new Integer(p) : null; 1342 putProperty(AsyncLoadPriority, val); 1343 } 1344 1345 /** 1346 * Sets the properties of this <code>Document</code>. 1347 * 1348 * @param p the document properties to set 1349 */ 1350 public void setDocumentProperties(Dictionary<Object, Object> p) 1351 { 1352 // FIXME: make me thread-safe 1353 properties = p; 1354 } 1355 1356 /** 1357 * Blocks until a write lock can be obtained. Must wait if there are 1358 * readers currently reading or another thread is currently writing. 1359 */ 1360 protected synchronized final void writeLock() 1361 { 1362 try 1363 { 1364 while (numReaders > 0 || currentWriter != null) 1365 { 1366 if (Thread.currentThread() == currentWriter) 1367 { 1368 if (notifyListeners) 1369 throw new IllegalStateException("Mutation during notify"); 1370 numWriters++; 1371 return; 1372 } 1373 wait(); 1374 } 1375 currentWriter = Thread.currentThread(); 1376 numWriters = 1; 1377 } 1378 catch (InterruptedException ex) 1379 { 1380 throw new Error("Interupted during grab write lock"); 1381 } 1382 } 1383 1384 /** 1385 * Releases the write lock. This allows waiting readers or writers to 1386 * obtain the lock. 1387 */ 1388 protected final synchronized void writeUnlock() 1389 { 1390 if (--numWriters <= 0) 1391 { 1392 numWriters = 0; 1393 currentWriter = null; 1394 notifyAll(); 1395 } 1396 } 1397 1398 /** 1399 * Returns the currently installed {@link DocumentFilter} for this 1400 * <code>Document</code>. 1401 * 1402 * @return the currently installed {@link DocumentFilter} for this 1403 * <code>Document</code> 1404 * 1405 * @since 1.4 1406 */ 1407 public DocumentFilter getDocumentFilter() 1408 { 1409 return documentFilter; 1410 } 1411 1412 /** 1413 * Sets the {@link DocumentFilter} for this <code>Document</code>. 1414 * 1415 * @param filter the <code>DocumentFilter</code> to set 1416 * 1417 * @since 1.4 1418 */ 1419 public void setDocumentFilter(DocumentFilter filter) 1420 { 1421 this.documentFilter = filter; 1422 } 1423 1424 /** 1425 * Dumps diagnostic information to the specified <code>PrintStream</code>. 1426 * 1427 * @param out the stream to write the diagnostic information to 1428 */ 1429 public void dump(PrintStream out) 1430 { 1431 ((AbstractElement) getDefaultRootElement()).dump(out, 0); 1432 ((AbstractElement) getBidiRootElement()).dump(out, 0); 1433 } 1434 1435 /** 1436 * Defines a set of methods for managing text attributes for one or more 1437 * <code>Document</code>s. 1438 * 1439 * Replicating {@link AttributeSet}s throughout a <code>Document</code> can 1440 * be very expensive. Implementations of this interface are intended to 1441 * provide intelligent management of <code>AttributeSet</code>s, eliminating 1442 * costly duplication. 1443 * 1444 * @see StyleContext 1445 */ 1446 public interface AttributeContext 1447 { 1448 /** 1449 * Returns an {@link AttributeSet} that contains the attributes 1450 * of <code>old</code> plus the new attribute specified by 1451 * <code>name</code> and <code>value</code>. 1452 * 1453 * @param old the attribute set to be merged with the new attribute 1454 * @param name the name of the attribute to be added 1455 * @param value the value of the attribute to be added 1456 * 1457 * @return the old attributes plus the new attribute 1458 */ 1459 AttributeSet addAttribute(AttributeSet old, Object name, Object value); 1460 1461 /** 1462 * Returns an {@link AttributeSet} that contains the attributes 1463 * of <code>old</code> plus the new attributes in <code>attributes</code>. 1464 * 1465 * @param old the set of attributes where to add the new attributes 1466 * @param attributes the attributes to be added 1467 * 1468 * @return an {@link AttributeSet} that contains the attributes 1469 * of <code>old</code> plus the new attributes in 1470 * <code>attributes</code> 1471 */ 1472 AttributeSet addAttributes(AttributeSet old, AttributeSet attributes); 1473 1474 /** 1475 * Returns an empty {@link AttributeSet}. 1476 * 1477 * @return an empty {@link AttributeSet} 1478 */ 1479 AttributeSet getEmptySet(); 1480 1481 /** 1482 * Called to indicate that the attributes in <code>attributes</code> are 1483 * no longer used. 1484 * 1485 * @param attributes the attributes are no longer used 1486 */ 1487 void reclaim(AttributeSet attributes); 1488 1489 /** 1490 * Returns a {@link AttributeSet} that has the attribute with the specified 1491 * <code>name</code> removed from <code>old</code>. 1492 * 1493 * @param old the attribute set from which an attribute is removed 1494 * @param name the name of the attribute to be removed 1495 * 1496 * @return the attributes of <code>old</code> minus the attribute 1497 * specified by <code>name</code> 1498 */ 1499 AttributeSet removeAttribute(AttributeSet old, Object name); 1500 1501 /** 1502 * Removes all attributes in <code>attributes</code> from <code>old</code> 1503 * and returns the resulting <code>AttributeSet</code>. 1504 * 1505 * @param old the set of attributes from which to remove attributes 1506 * @param attributes the attributes to be removed from <code>old</code> 1507 * 1508 * @return the attributes of <code>old</code> minus the attributes in 1509 * <code>attributes</code> 1510 */ 1511 AttributeSet removeAttributes(AttributeSet old, AttributeSet attributes); 1512 1513 /** 1514 * Removes all attributes specified by <code>names</code> from 1515 * <code>old</code> and returns the resulting <code>AttributeSet</code>. 1516 * 1517 * @param old the set of attributes from which to remove attributes 1518 * @param names the names of the attributes to be removed from 1519 * <code>old</code> 1520 * 1521 * @return the attributes of <code>old</code> minus the attributes in 1522 * <code>attributes</code> 1523 */ 1524 AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names); 1525 } 1526 1527 /** 1528 * A sequence of data that can be edited. This is were the actual content 1529 * in <code>AbstractDocument</code>'s is stored. 1530 */ 1531 public interface Content 1532 { 1533 /** 1534 * Creates a {@link Position} that keeps track of the location at 1535 * <code>offset</code>. 1536 * 1537 * @return a {@link Position} that keeps track of the location at 1538 * <code>offset</code>. 1539 * 1540 * @throw BadLocationException if <code>offset</code> is not a valid 1541 * location in this <code>Content</code> model 1542 */ 1543 Position createPosition(int offset) throws BadLocationException; 1544 1545 /** 1546 * Returns the length of the content. 1547 * 1548 * @return the length of the content 1549 */ 1550 int length(); 1551 1552 /** 1553 * Inserts a string into the content model. 1554 * 1555 * @param where the offset at which to insert the string 1556 * @param str the string to be inserted 1557 * 1558 * @return an <code>UndoableEdit</code> or <code>null</code> if undo is 1559 * not supported by this <code>Content</code> model 1560 * 1561 * @throws BadLocationException if <code>where</code> is not a valid 1562 * location in this <code>Content</code> model 1563 */ 1564 UndoableEdit insertString(int where, String str) 1565 throws BadLocationException; 1566 1567 /** 1568 * Removes a piece of content from the content model. 1569 * 1570 * @param where the offset at which to remove content 1571 * @param nitems the number of characters to be removed 1572 * 1573 * @return an <code>UndoableEdit</code> or <code>null</code> if undo is 1574 * not supported by this <code>Content</code> model 1575 * 1576 * @throws BadLocationException if <code>where</code> is not a valid 1577 * location in this <code>Content</code> model 1578 */ 1579 UndoableEdit remove(int where, int nitems) throws BadLocationException; 1580 1581 /** 1582 * Returns a piece of content. 1583 * 1584 * @param where the start offset of the requested fragment 1585 * @param len the length of the requested fragment 1586 * 1587 * @return the requested fragment 1588 * @throws BadLocationException if <code>offset</code> or 1589 * <code>offset + len</code>is not a valid 1590 * location in this <code>Content</code> model 1591 */ 1592 String getString(int where, int len) throws BadLocationException; 1593 1594 /** 1595 * Fetches a piece of content and stores it in <code>txt</code>. 1596 * 1597 * @param where the start offset of the requested fragment 1598 * @param len the length of the requested fragment 1599 * @param txt the <code>Segment</code> where to fragment is stored into 1600 * 1601 * @throws BadLocationException if <code>offset</code> or 1602 * <code>offset + len</code>is not a valid 1603 * location in this <code>Content</code> model 1604 */ 1605 void getChars(int where, int len, Segment txt) throws BadLocationException; 1606 } 1607 1608 /** 1609 * An abstract base implementation of the {@link Element} interface. 1610 */ 1611 public abstract class AbstractElement 1612 implements Element, MutableAttributeSet, TreeNode, Serializable 1613 { 1614 /** The serialization UID (compatible with JDK1.5). */ 1615 private static final long serialVersionUID = 1712240033321461704L; 1616 1617 /** The number of characters that this Element spans. */ 1618 int count; 1619 1620 /** The starting offset of this Element. */ 1621 int offset; 1622 1623 /** The attributes of this Element. */ 1624 AttributeSet attributes; 1625 1626 /** The parent element. */ 1627 Element element_parent; 1628 1629 /** The parent in the TreeNode interface. */ 1630 TreeNode tree_parent; 1631 1632 /** The children of this element. */ 1633 Vector tree_children; 1634 1635 /** 1636 * Creates a new instance of <code>AbstractElement</code> with a 1637 * specified parent <code>Element</code> and <code>AttributeSet</code>. 1638 * 1639 * @param p the parent of this <code>AbstractElement</code> 1640 * @param s the attributes to be assigned to this 1641 * <code>AbstractElement</code> 1642 */ 1643 public AbstractElement(Element p, AttributeSet s) 1644 { 1645 element_parent = p; 1646 AttributeContext ctx = getAttributeContext(); 1647 attributes = ctx.getEmptySet(); 1648 if (s != null) 1649 addAttributes(s); 1650 } 1651 1652 /** 1653 * Returns the child nodes of this <code>Element</code> as an 1654 * <code>Enumeration</code> of {@link TreeNode}s. 1655 * 1656 * @return the child nodes of this <code>Element</code> as an 1657 * <code>Enumeration</code> of {@link TreeNode}s 1658 */ 1659 public abstract Enumeration children(); 1660 1661 /** 1662 * Returns <code>true</code> if this <code>AbstractElement</code> 1663 * allows children. 1664 * 1665 * @return <code>true</code> if this <code>AbstractElement</code> 1666 * allows children 1667 */ 1668 public abstract boolean getAllowsChildren(); 1669 1670 /** 1671 * Returns the child of this <code>AbstractElement</code> at 1672 * <code>index</code>. 1673 * 1674 * @param index the position in the child list of the child element to 1675 * be returned 1676 * 1677 * @return the child of this <code>AbstractElement</code> at 1678 * <code>index</code> 1679 */ 1680 public TreeNode getChildAt(int index) 1681 { 1682 return (TreeNode) tree_children.get(index); 1683 } 1684 1685 /** 1686 * Returns the number of children of this <code>AbstractElement</code>. 1687 * 1688 * @return the number of children of this <code>AbstractElement</code> 1689 */ 1690 public int getChildCount() 1691 { 1692 return tree_children.size(); 1693 } 1694 1695 /** 1696 * Returns the index of a given child <code>TreeNode</code> or 1697 * <code>-1</code> if <code>node</code> is not a child of this 1698 * <code>AbstractElement</code>. 1699 * 1700 * @param node the node for which the index is requested 1701 * 1702 * @return the index of a given child <code>TreeNode</code> or 1703 * <code>-1</code> if <code>node</code> is not a child of this 1704 * <code>AbstractElement</code> 1705 */ 1706 public int getIndex(TreeNode node) 1707 { 1708 return tree_children.indexOf(node); 1709 } 1710 1711 /** 1712 * Returns the parent <code>TreeNode</code> of this 1713 * <code>AbstractElement</code> or <code>null</code> if this element 1714 * has no parent. 1715 * 1716 * @return the parent <code>TreeNode</code> of this 1717 * <code>AbstractElement</code> or <code>null</code> if this 1718 * element has no parent 1719 */ 1720 public TreeNode getParent() 1721 { 1722 return tree_parent; 1723 } 1724 1725 /** 1726 * Returns <code>true</code> if this <code>AbstractElement</code> is a 1727 * leaf element, <code>false</code> otherwise. 1728 * 1729 * @return <code>true</code> if this <code>AbstractElement</code> is a 1730 * leaf element, <code>false</code> otherwise 1731 */ 1732 public abstract boolean isLeaf(); 1733 1734 /** 1735 * Adds an attribute to this element. 1736 * 1737 * @param name the name of the attribute to be added 1738 * @param value the value of the attribute to be added 1739 */ 1740 public void addAttribute(Object name, Object value) 1741 { 1742 attributes = getAttributeContext().addAttribute(attributes, name, value); 1743 } 1744 1745 /** 1746 * Adds a set of attributes to this element. 1747 * 1748 * @param attrs the attributes to be added to this element 1749 */ 1750 public void addAttributes(AttributeSet attrs) 1751 { 1752 attributes = getAttributeContext().addAttributes(attributes, attrs); 1753 } 1754 1755 /** 1756 * Removes an attribute from this element. 1757 * 1758 * @param name the name of the attribute to be removed 1759 */ 1760 public void removeAttribute(Object name) 1761 { 1762 attributes = getAttributeContext().removeAttribute(attributes, name); 1763 } 1764 1765 /** 1766 * Removes a set of attributes from this element. 1767 * 1768 * @param attrs the attributes to be removed 1769 */ 1770 public void removeAttributes(AttributeSet attrs) 1771 { 1772 attributes = getAttributeContext().removeAttributes(attributes, attrs); 1773 } 1774 1775 /** 1776 * Removes a set of attribute from this element. 1777 * 1778 * @param names the names of the attributes to be removed 1779 */ 1780 public void removeAttributes(Enumeration<?> names) 1781 { 1782 attributes = getAttributeContext().removeAttributes(attributes, names); 1783 } 1784 1785 /** 1786 * Sets the parent attribute set against which the element can resolve 1787 * attributes that are not defined in itself. 1788 * 1789 * @param parent the resolve parent to set 1790 */ 1791 public void setResolveParent(AttributeSet parent) 1792 { 1793 attributes = getAttributeContext().addAttribute(attributes, 1794 ResolveAttribute, 1795 parent); 1796 } 1797 1798 /** 1799 * Returns <code>true</code> if this element contains the specified 1800 * attribute. 1801 * 1802 * @param name the name of the attribute to check 1803 * @param value the value of the attribute to check 1804 * 1805 * @return <code>true</code> if this element contains the specified 1806 * attribute 1807 */ 1808 public boolean containsAttribute(Object name, Object value) 1809 { 1810 return attributes.containsAttribute(name, value); 1811 } 1812 1813 /** 1814 * Returns <code>true</code> if this element contains all of the 1815 * specified attributes. 1816 * 1817 * @param attrs the attributes to check 1818 * 1819 * @return <code>true</code> if this element contains all of the 1820 * specified attributes 1821 */ 1822 public boolean containsAttributes(AttributeSet attrs) 1823 { 1824 return attributes.containsAttributes(attrs); 1825 } 1826 1827 /** 1828 * Returns a copy of the attributes of this element. 1829 * 1830 * @return a copy of the attributes of this element 1831 */ 1832 public AttributeSet copyAttributes() 1833 { 1834 return attributes.copyAttributes(); 1835 } 1836 1837 /** 1838 * Returns the attribute value with the specified key. If this attribute 1839 * is not defined in this element and this element has a resolving 1840 * parent, the search goes upward to the resolve parent chain. 1841 * 1842 * @param key the key of the requested attribute 1843 * 1844 * @return the attribute value for <code>key</code> of <code>null</code> 1845 * if <code>key</code> is not found locally and cannot be resolved 1846 * in this element's resolve parents 1847 */ 1848 public Object getAttribute(Object key) 1849 { 1850 Object result = attributes.getAttribute(key); 1851 if (result == null) 1852 { 1853 AttributeSet resParent = getResolveParent(); 1854 if (resParent != null) 1855 result = resParent.getAttribute(key); 1856 } 1857 return result; 1858 } 1859 1860 /** 1861 * Returns the number of defined attributes in this element. 1862 * 1863 * @return the number of defined attributes in this element 1864 */ 1865 public int getAttributeCount() 1866 { 1867 return attributes.getAttributeCount(); 1868 } 1869 1870 /** 1871 * Returns the names of the attributes of this element. 1872 * 1873 * @return the names of the attributes of this element 1874 */ 1875 public Enumeration<?> getAttributeNames() 1876 { 1877 return attributes.getAttributeNames(); 1878 } 1879 1880 /** 1881 * Returns the resolve parent of this element. 1882 * This is taken from the AttributeSet, but if this is null, 1883 * this method instead returns the Element's parent's 1884 * AttributeSet 1885 * 1886 * @return the resolve parent of this element 1887 * 1888 * @see #setResolveParent(AttributeSet) 1889 */ 1890 public AttributeSet getResolveParent() 1891 { 1892 return attributes.getResolveParent(); 1893 } 1894 1895 /** 1896 * Returns <code>true</code> if an attribute with the specified name 1897 * is defined in this element, <code>false</code> otherwise. 1898 * 1899 * @param attrName the name of the requested attributes 1900 * 1901 * @return <code>true</code> if an attribute with the specified name 1902 * is defined in this element, <code>false</code> otherwise 1903 */ 1904 public boolean isDefined(Object attrName) 1905 { 1906 return attributes.isDefined(attrName); 1907 } 1908 1909 /** 1910 * Returns <code>true</code> if the specified <code>AttributeSet</code> 1911 * is equal to this element's <code>AttributeSet</code>, <code>false</code> 1912 * otherwise. 1913 * 1914 * @param attrs the attributes to compare this element to 1915 * 1916 * @return <code>true</code> if the specified <code>AttributeSet</code> 1917 * is equal to this element's <code>AttributeSet</code>, 1918 * <code>false</code> otherwise 1919 */ 1920 public boolean isEqual(AttributeSet attrs) 1921 { 1922 return attributes.isEqual(attrs); 1923 } 1924 1925 /** 1926 * Returns the attributes of this element. 1927 * 1928 * @return the attributes of this element 1929 */ 1930 public AttributeSet getAttributes() 1931 { 1932 return this; 1933 } 1934 1935 /** 1936 * Returns the {@link Document} to which this element belongs. 1937 * 1938 * @return the {@link Document} to which this element belongs 1939 */ 1940 public Document getDocument() 1941 { 1942 return AbstractDocument.this; 1943 } 1944 1945 /** 1946 * Returns the child element at the specified <code>index</code>. 1947 * 1948 * @param index the index of the requested child element 1949 * 1950 * @return the requested element 1951 */ 1952 public abstract Element getElement(int index); 1953 1954 /** 1955 * Returns the name of this element. 1956 * 1957 * @return the name of this element 1958 */ 1959 public String getName() 1960 { 1961 return (String) attributes.getAttribute(ElementNameAttribute); 1962 } 1963 1964 /** 1965 * Returns the parent element of this element. 1966 * 1967 * @return the parent element of this element 1968 */ 1969 public Element getParentElement() 1970 { 1971 return element_parent; 1972 } 1973 1974 /** 1975 * Returns the offset inside the document model that is after the last 1976 * character of this element. 1977 * 1978 * @return the offset inside the document model that is after the last 1979 * character of this element 1980 */ 1981 public abstract int getEndOffset(); 1982 1983 /** 1984 * Returns the number of child elements of this element. 1985 * 1986 * @return the number of child elements of this element 1987 */ 1988 public abstract int getElementCount(); 1989 1990 /** 1991 * Returns the index of the child element that spans the specified 1992 * offset in the document model. 1993 * 1994 * @param offset the offset for which the responsible element is searched 1995 * 1996 * @return the index of the child element that spans the specified 1997 * offset in the document model 1998 */ 1999 public abstract int getElementIndex(int offset); 2000 2001 /** 2002 * Returns the start offset if this element inside the document model. 2003 * 2004 * @return the start offset if this element inside the document model 2005 */ 2006 public abstract int getStartOffset(); 2007 2008 /** 2009 * Prints diagnostic output to the specified stream. 2010 * 2011 * @param stream the stream to write to 2012 * @param indent the indentation level 2013 */ 2014 public void dump(PrintStream stream, int indent) 2015 { 2016 StringBuffer b = new StringBuffer(); 2017 for (int i = 0; i < indent; ++i) 2018 b.append(' '); 2019 b.append('<'); 2020 b.append(getName()); 2021 // Dump attributes if there are any. 2022 if (getAttributeCount() > 0) 2023 { 2024 b.append('\n'); 2025 Enumeration attNames = getAttributeNames(); 2026 while (attNames.hasMoreElements()) 2027 { 2028 for (int i = 0; i < indent + 2; ++i) 2029 b.append(' '); 2030 Object attName = attNames.nextElement(); 2031 b.append(attName); 2032 b.append('='); 2033 Object attribute = getAttribute(attName); 2034 b.append(attribute); 2035 b.append('\n'); 2036 } 2037 } 2038 if (getAttributeCount() > 0) 2039 { 2040 for (int i = 0; i < indent; ++i) 2041 b.append(' '); 2042 } 2043 b.append(">\n"); 2044 2045 // Dump element content for leaf elements. 2046 if (isLeaf()) 2047 { 2048 for (int i = 0; i < indent + 2; ++i) 2049 b.append(' '); 2050 int start = getStartOffset(); 2051 int end = getEndOffset(); 2052 b.append('['); 2053 b.append(start); 2054 b.append(','); 2055 b.append(end); 2056 b.append("]["); 2057 try 2058 { 2059 b.append(getDocument().getText(start, end - start)); 2060 } 2061 catch (BadLocationException ex) 2062 { 2063 AssertionError err = new AssertionError("BadLocationException " 2064 + "must not be thrown " 2065 + "here."); 2066 err.initCause(ex); 2067 throw err; 2068 } 2069 b.append("]\n"); 2070 } 2071 stream.print(b.toString()); 2072 2073 // Dump child elements if any. 2074 int count = getElementCount(); 2075 for (int i = 0; i < count; ++i) 2076 { 2077 Element el = getElement(i); 2078 if (el instanceof AbstractElement) 2079 ((AbstractElement) el).dump(stream, indent + 2); 2080 } 2081 } 2082 } 2083 2084 /** 2085 * An implementation of {@link Element} to represent composite 2086 * <code>Element</code>s that contain other <code>Element</code>s. 2087 */ 2088 public class BranchElement extends AbstractElement 2089 { 2090 /** The serialization UID (compatible with JDK1.5). */ 2091 private static final long serialVersionUID = -6037216547466333183L; 2092 2093 /** 2094 * The child elements of this BranchElement. 2095 */ 2096 private Element[] children; 2097 2098 /** 2099 * The number of children in the branch element. 2100 */ 2101 private int numChildren; 2102 2103 /** 2104 * The last found index in getElementIndex(). Used for faster searching. 2105 */ 2106 private int lastIndex; 2107 2108 /** 2109 * Creates a new <code>BranchElement</code> with the specified 2110 * parent and attributes. 2111 * 2112 * @param parent the parent element of this <code>BranchElement</code> 2113 * @param attributes the attributes to set on this 2114 * <code>BranchElement</code> 2115 */ 2116 public BranchElement(Element parent, AttributeSet attributes) 2117 { 2118 super(parent, attributes); 2119 children = new Element[1]; 2120 numChildren = 0; 2121 lastIndex = -1; 2122 } 2123 2124 /** 2125 * Returns the children of this <code>BranchElement</code>. 2126 * 2127 * @return the children of this <code>BranchElement</code> 2128 */ 2129 public Enumeration children() 2130 { 2131 if (numChildren == 0) 2132 return null; 2133 2134 Vector tmp = new Vector(); 2135 2136 for (int index = 0; index < numChildren; ++index) 2137 tmp.add(children[index]); 2138 2139 return tmp.elements(); 2140 } 2141 2142 /** 2143 * Returns <code>true</code> since <code>BranchElements</code> allow 2144 * child elements. 2145 * 2146 * @return <code>true</code> since <code>BranchElements</code> allow 2147 * child elements 2148 */ 2149 public boolean getAllowsChildren() 2150 { 2151 return true; 2152 } 2153 2154 /** 2155 * Returns the child element at the specified <code>index</code>. 2156 * 2157 * @param index the index of the requested child element 2158 * 2159 * @return the requested element 2160 */ 2161 public Element getElement(int index) 2162 { 2163 if (index < 0 || index >= numChildren) 2164 return null; 2165 2166 return children[index]; 2167 } 2168 2169 /** 2170 * Returns the number of child elements of this element. 2171 * 2172 * @return the number of child elements of this element 2173 */ 2174 public int getElementCount() 2175 { 2176 return numChildren; 2177 } 2178 2179 /** 2180 * Returns the index of the child element that spans the specified 2181 * offset in the document model. 2182 * 2183 * @param offset the offset for which the responsible element is searched 2184 * 2185 * @return the index of the child element that spans the specified 2186 * offset in the document model 2187 */ 2188 public int getElementIndex(int offset) 2189 { 2190 // Implemented using an improved linear search. 2191 // This makes use of the fact that searches are not random but often 2192 // close to the previous search. So we try to start the binary 2193 // search at the last found index. 2194 2195 int i0 = 0; // The lower bounds. 2196 int i1 = numChildren - 1; // The upper bounds. 2197 int index = -1; // The found index. 2198 2199 int p0 = getStartOffset(); 2200 int p1; // Start and end offset local variables. 2201 2202 if (numChildren == 0) 2203 index = 0; 2204 else if (offset >= getEndOffset()) 2205 index = numChildren - 1; 2206 else 2207 { 2208 // Try lastIndex. 2209 if (lastIndex >= i0 && lastIndex <= i1) 2210 { 2211 Element last = getElement(lastIndex); 2212 p0 = last.getStartOffset(); 2213 p1 = last.getEndOffset(); 2214 if (offset >= p0 && offset < p1) 2215 index = lastIndex; 2216 else 2217 { 2218 // Narrow the search bounds using the lastIndex, even 2219 // if it hasn't been a hit. 2220 if (offset < p0) 2221 i1 = lastIndex; 2222 else 2223 i0 = lastIndex; 2224 } 2225 } 2226 // The actual search. 2227 int i = 0; 2228 while (i0 <= i1 && index == -1) 2229 { 2230 i = i0 + (i1 - i0) / 2; 2231 Element el = getElement(i); 2232 p0 = el.getStartOffset(); 2233 p1 = el.getEndOffset(); 2234 if (offset >= p0 && offset < p1) 2235 { 2236 // Found it! 2237 index = i; 2238 } 2239 else if (offset < p0) 2240 i1 = i - 1; 2241 else 2242 i0 = i + 1; 2243 } 2244 2245 if (index == -1) 2246 { 2247 // Didn't find it. Return the boundary index. 2248 if (offset < p0) 2249 index = i; 2250 else 2251 index = i + 1; 2252 } 2253 2254 lastIndex = index; 2255 } 2256 return index; 2257 } 2258 2259 /** 2260 * Returns the offset inside the document model that is after the last 2261 * character of this element. 2262 * This is the end offset of the last child element. If this element 2263 * has no children, this method throws a <code>NullPointerException</code>. 2264 * 2265 * @return the offset inside the document model that is after the last 2266 * character of this element 2267 * 2268 * @throws NullPointerException if this branch element has no children 2269 */ 2270 public int getEndOffset() 2271 { 2272 // This might accss one cached element or trigger an NPE for 2273 // numChildren == 0. This is checked by a Mauve test. 2274 Element child = numChildren > 0 ? children[numChildren - 1] 2275 : children[0]; 2276 return child.getEndOffset(); 2277 } 2278 2279 /** 2280 * Returns the name of this element. This is {@link #ParagraphElementName} 2281 * in this case. 2282 * 2283 * @return the name of this element 2284 */ 2285 public String getName() 2286 { 2287 return ParagraphElementName; 2288 } 2289 2290 /** 2291 * Returns the start offset of this element inside the document model. 2292 * This is the start offset of the first child element. If this element 2293 * has no children, this method throws a <code>NullPointerException</code>. 2294 * 2295 * @return the start offset of this element inside the document model 2296 * 2297 * @throws NullPointerException if this branch element has no children and 2298 * no startOffset value has been cached 2299 */ 2300 public int getStartOffset() 2301 { 2302 // Do not explicitly throw an NPE here. If the first element is null 2303 // then the NPE gets thrown anyway. If it isn't, then it either 2304 // holds a real value (for numChildren > 0) or a cached value 2305 // (for numChildren == 0) as we don't fully remove elements in replace() 2306 // when removing single elements. 2307 // This is checked by a Mauve test. 2308 return children[0].getStartOffset(); 2309 } 2310 2311 /** 2312 * Returns <code>false</code> since <code>BranchElement</code> are no 2313 * leafes. 2314 * 2315 * @return <code>false</code> since <code>BranchElement</code> are no 2316 * leafes 2317 */ 2318 public boolean isLeaf() 2319 { 2320 return false; 2321 } 2322 2323 /** 2324 * Returns the <code>Element</code> at the specified <code>Document</code> 2325 * offset. 2326 * 2327 * @return the <code>Element</code> at the specified <code>Document</code> 2328 * offset 2329 * 2330 * @see #getElementIndex(int) 2331 */ 2332 public Element positionToElement(int position) 2333 { 2334 // XXX: There is surely a better algorithm 2335 // as beginning from first element each time. 2336 for (int index = 0; index < numChildren; ++index) 2337 { 2338 Element elem = children[index]; 2339 2340 if ((elem.getStartOffset() <= position) 2341 && (position < elem.getEndOffset())) 2342 return elem; 2343 } 2344 2345 return null; 2346 } 2347 2348 /** 2349 * Replaces a set of child elements with a new set of child elemens. 2350 * 2351 * @param offset the start index of the elements to be removed 2352 * @param length the number of elements to be removed 2353 * @param elements the new elements to be inserted 2354 */ 2355 public void replace(int offset, int length, Element[] elements) 2356 { 2357 int delta = elements.length - length; 2358 int copyFrom = offset + length; // From where to copy. 2359 int copyTo = copyFrom + delta; // Where to copy to. 2360 int numMove = numChildren - copyFrom; // How many elements are moved. 2361 if (numChildren + delta > children.length) 2362 { 2363 // Gotta grow the array. 2364 int newSize = Math.max(2 * children.length, numChildren + delta); 2365 Element[] target = new Element[newSize]; 2366 System.arraycopy(children, 0, target, 0, offset); 2367 System.arraycopy(elements, 0, target, offset, elements.length); 2368 System.arraycopy(children, copyFrom, target, copyTo, numMove); 2369 children = target; 2370 } 2371 else 2372 { 2373 System.arraycopy(children, copyFrom, children, copyTo, numMove); 2374 System.arraycopy(elements, 0, children, offset, elements.length); 2375 } 2376 numChildren += delta; 2377 } 2378 2379 /** 2380 * Returns a string representation of this element. 2381 * 2382 * @return a string representation of this element 2383 */ 2384 public String toString() 2385 { 2386 return ("BranchElement(" + getName() + ") " 2387 + getStartOffset() + "," + getEndOffset() + "\n"); 2388 } 2389 } 2390 2391 /** 2392 * Stores the changes when a <code>Document</code> is beeing modified. 2393 */ 2394 public class DefaultDocumentEvent extends CompoundEdit 2395 implements DocumentEvent 2396 { 2397 /** The serialization UID (compatible with JDK1.5). */ 2398 private static final long serialVersionUID = 5230037221564563284L; 2399 2400 /** 2401 * The threshold that indicates when we switch to using a Hashtable. 2402 */ 2403 private static final int THRESHOLD = 10; 2404 2405 /** The starting offset of the change. */ 2406 private int offset; 2407 2408 /** The length of the change. */ 2409 private int length; 2410 2411 /** The type of change. */ 2412 private DocumentEvent.EventType type; 2413 2414 /** 2415 * Maps <code>Element</code> to their change records. This is only 2416 * used when the changes array gets too big. We can use an 2417 * (unsync'ed) HashMap here, since changes to this are (should) always 2418 * be performed inside a write lock. 2419 */ 2420 private HashMap changes; 2421 2422 /** 2423 * Indicates if this event has been modified or not. This is used to 2424 * determine if this event is thrown. 2425 */ 2426 private boolean modified; 2427 2428 /** 2429 * Creates a new <code>DefaultDocumentEvent</code>. 2430 * 2431 * @param offset the starting offset of the change 2432 * @param length the length of the change 2433 * @param type the type of change 2434 */ 2435 public DefaultDocumentEvent(int offset, int length, 2436 DocumentEvent.EventType type) 2437 { 2438 this.offset = offset; 2439 this.length = length; 2440 this.type = type; 2441 modified = false; 2442 } 2443 2444 /** 2445 * Adds an UndoableEdit to this <code>DocumentEvent</code>. If this 2446 * edit is an instance of {@link ElementEdit}, then this record can 2447 * later be fetched by calling {@link #getChange}. 2448 * 2449 * @param edit the undoable edit to add 2450 */ 2451 public boolean addEdit(UndoableEdit edit) 2452 { 2453 // Start using Hashtable when we pass a certain threshold. This 2454 // gives a good memory/performance compromise. 2455 if (changes == null && edits.size() > THRESHOLD) 2456 { 2457 changes = new HashMap(); 2458 int count = edits.size(); 2459 for (int i = 0; i < count; i++) 2460 { 2461 Object o = edits.elementAt(i); 2462 if (o instanceof ElementChange) 2463 { 2464 ElementChange ec = (ElementChange) o; 2465 changes.put(ec.getElement(), ec); 2466 } 2467 } 2468 } 2469 2470 if (changes != null && edit instanceof ElementChange) 2471 { 2472 ElementChange elEdit = (ElementChange) edit; 2473 changes.put(elEdit.getElement(), elEdit); 2474 } 2475 return super.addEdit(edit); 2476 } 2477 2478 /** 2479 * Returns the document that has been modified. 2480 * 2481 * @return the document that has been modified 2482 */ 2483 public Document getDocument() 2484 { 2485 return AbstractDocument.this; 2486 } 2487 2488 /** 2489 * Returns the length of the modification. 2490 * 2491 * @return the length of the modification 2492 */ 2493 public int getLength() 2494 { 2495 return length; 2496 } 2497 2498 /** 2499 * Returns the start offset of the modification. 2500 * 2501 * @return the start offset of the modification 2502 */ 2503 public int getOffset() 2504 { 2505 return offset; 2506 } 2507 2508 /** 2509 * Returns the type of the modification. 2510 * 2511 * @return the type of the modification 2512 */ 2513 public DocumentEvent.EventType getType() 2514 { 2515 return type; 2516 } 2517 2518 /** 2519 * Returns the changes for an element. 2520 * 2521 * @param elem the element for which the changes are requested 2522 * 2523 * @return the changes for <code>elem</code> or <code>null</code> if 2524 * <code>elem</code> has not been changed 2525 */ 2526 public ElementChange getChange(Element elem) 2527 { 2528 ElementChange change = null; 2529 if (changes != null) 2530 { 2531 change = (ElementChange) changes.get(elem); 2532 } 2533 else 2534 { 2535 int count = edits.size(); 2536 for (int i = 0; i < count && change == null; i++) 2537 { 2538 Object o = edits.get(i); 2539 if (o instanceof ElementChange) 2540 { 2541 ElementChange ec = (ElementChange) o; 2542 if (elem.equals(ec.getElement())) 2543 change = ec; 2544 } 2545 } 2546 } 2547 return change; 2548 } 2549 2550 /** 2551 * Returns a String description of the change event. This returns the 2552 * toString method of the Vector of edits. 2553 */ 2554 public String toString() 2555 { 2556 return edits.toString(); 2557 } 2558 } 2559 2560 /** 2561 * An implementation of {@link DocumentEvent.ElementChange} to be added 2562 * to {@link DefaultDocumentEvent}s. 2563 */ 2564 public static class ElementEdit extends AbstractUndoableEdit 2565 implements DocumentEvent.ElementChange 2566 { 2567 /** The serial version UID of ElementEdit. */ 2568 private static final long serialVersionUID = -1216620962142928304L; 2569 2570 /** 2571 * The changed element. 2572 */ 2573 private Element elem; 2574 2575 /** 2576 * The index of the change. 2577 */ 2578 private int index; 2579 2580 /** 2581 * The removed elements. 2582 */ 2583 private Element[] removed; 2584 2585 /** 2586 * The added elements. 2587 */ 2588 private Element[] added; 2589 2590 /** 2591 * Creates a new <code>ElementEdit</code>. 2592 * 2593 * @param elem the changed element 2594 * @param index the index of the change 2595 * @param removed the removed elements 2596 * @param added the added elements 2597 */ 2598 public ElementEdit(Element elem, int index, 2599 Element[] removed, Element[] added) 2600 { 2601 this.elem = elem; 2602 this.index = index; 2603 this.removed = removed; 2604 this.added = added; 2605 } 2606 2607 /** 2608 * Returns the added elements. 2609 * 2610 * @return the added elements 2611 */ 2612 public Element[] getChildrenAdded() 2613 { 2614 return added; 2615 } 2616 2617 /** 2618 * Returns the removed elements. 2619 * 2620 * @return the removed elements 2621 */ 2622 public Element[] getChildrenRemoved() 2623 { 2624 return removed; 2625 } 2626 2627 /** 2628 * Returns the changed element. 2629 * 2630 * @return the changed element 2631 */ 2632 public Element getElement() 2633 { 2634 return elem; 2635 } 2636 2637 /** 2638 * Returns the index of the change. 2639 * 2640 * @return the index of the change 2641 */ 2642 public int getIndex() 2643 { 2644 return index; 2645 } 2646 } 2647 2648 /** 2649 * An implementation of {@link Element} that represents a leaf in the 2650 * document structure. This is used to actually store content. 2651 */ 2652 public class LeafElement extends AbstractElement 2653 { 2654 /** The serialization UID (compatible with JDK1.5). */ 2655 private static final long serialVersionUID = -8906306331347768017L; 2656 2657 /** 2658 * Manages the start offset of this element. 2659 */ 2660 private Position startPos; 2661 2662 /** 2663 * Manages the end offset of this element. 2664 */ 2665 private Position endPos; 2666 2667 /** 2668 * Creates a new <code>LeafElement</code>. 2669 * 2670 * @param parent the parent of this <code>LeafElement</code> 2671 * @param attributes the attributes to be set 2672 * @param start the start index of this element inside the document model 2673 * @param end the end index of this element inside the document model 2674 */ 2675 public LeafElement(Element parent, AttributeSet attributes, int start, 2676 int end) 2677 { 2678 super(parent, attributes); 2679 try 2680 { 2681 startPos = createPosition(start); 2682 endPos = createPosition(end); 2683 } 2684 catch (BadLocationException ex) 2685 { 2686 AssertionError as; 2687 as = new AssertionError("BadLocationException thrown " 2688 + "here. start=" + start 2689 + ", end=" + end 2690 + ", length=" + getLength()); 2691 as.initCause(ex); 2692 throw as; 2693 } 2694 } 2695 2696 /** 2697 * Returns <code>null</code> since <code>LeafElement</code>s cannot have 2698 * children. 2699 * 2700 * @return <code>null</code> since <code>LeafElement</code>s cannot have 2701 * children 2702 */ 2703 public Enumeration children() 2704 { 2705 return null; 2706 } 2707 2708 /** 2709 * Returns <code>false</code> since <code>LeafElement</code>s cannot have 2710 * children. 2711 * 2712 * @return <code>false</code> since <code>LeafElement</code>s cannot have 2713 * children 2714 */ 2715 public boolean getAllowsChildren() 2716 { 2717 return false; 2718 } 2719 2720 /** 2721 * Returns <code>null</code> since <code>LeafElement</code>s cannot have 2722 * children. 2723 * 2724 * @return <code>null</code> since <code>LeafElement</code>s cannot have 2725 * children 2726 */ 2727 public Element getElement(int index) 2728 { 2729 return null; 2730 } 2731 2732 /** 2733 * Returns <code>0</code> since <code>LeafElement</code>s cannot have 2734 * children. 2735 * 2736 * @return <code>0</code> since <code>LeafElement</code>s cannot have 2737 * children 2738 */ 2739 public int getElementCount() 2740 { 2741 return 0; 2742 } 2743 2744 /** 2745 * Returns <code>-1</code> since <code>LeafElement</code>s cannot have 2746 * children. 2747 * 2748 * @return <code>-1</code> since <code>LeafElement</code>s cannot have 2749 * children 2750 */ 2751 public int getElementIndex(int offset) 2752 { 2753 return -1; 2754 } 2755 2756 /** 2757 * Returns the end offset of this <code>Element</code> inside the 2758 * document. 2759 * 2760 * @return the end offset of this <code>Element</code> inside the 2761 * document 2762 */ 2763 public int getEndOffset() 2764 { 2765 return endPos.getOffset(); 2766 } 2767 2768 /** 2769 * Returns the name of this <code>Element</code>. This is 2770 * {@link #ContentElementName} in this case. 2771 * 2772 * @return the name of this <code>Element</code> 2773 */ 2774 public String getName() 2775 { 2776 String name = super.getName(); 2777 if (name == null) 2778 name = ContentElementName; 2779 return name; 2780 } 2781 2782 /** 2783 * Returns the start offset of this <code>Element</code> inside the 2784 * document. 2785 * 2786 * @return the start offset of this <code>Element</code> inside the 2787 * document 2788 */ 2789 public int getStartOffset() 2790 { 2791 return startPos.getOffset(); 2792 } 2793 2794 /** 2795 * Returns <code>true</code>. 2796 * 2797 * @return <code>true</code> 2798 */ 2799 public boolean isLeaf() 2800 { 2801 return true; 2802 } 2803 2804 /** 2805 * Returns a string representation of this <code>Element</code>. 2806 * 2807 * @return a string representation of this <code>Element</code> 2808 */ 2809 public String toString() 2810 { 2811 return ("LeafElement(" + getName() + ") " 2812 + getStartOffset() + "," + getEndOffset() + "\n"); 2813 } 2814 } 2815 2816 /** 2817 * The root element for bidirectional text. 2818 */ 2819 private class BidiRootElement 2820 extends BranchElement 2821 { 2822 /** 2823 * Creates a new bidi root element. 2824 */ 2825 BidiRootElement() 2826 { 2827 super(null, null); 2828 } 2829 2830 /** 2831 * Returns the name of the element. 2832 * 2833 * @return the name of the element 2834 */ 2835 public String getName() 2836 { 2837 return BidiRootName; 2838 } 2839 } 2840 2841 /** 2842 * A leaf element for the bidi structure. 2843 */ 2844 private class BidiElement 2845 extends LeafElement 2846 { 2847 /** 2848 * Creates a new BidiElement. 2849 * 2850 * @param parent the parent element 2851 * @param start the start offset 2852 * @param end the end offset 2853 * @param level the bidi level 2854 */ 2855 BidiElement(Element parent, int start, int end, int level) 2856 { 2857 super(parent, new SimpleAttributeSet(), start, end); 2858 addAttribute(StyleConstants.BidiLevel, new Integer(level)); 2859 } 2860 2861 /** 2862 * Returns the name of the element. 2863 * 2864 * @return the name of the element 2865 */ 2866 public String getName() 2867 { 2868 return BidiElementName; 2869 } 2870 } 2871 2872 /** A class whose methods delegate to the insert, remove and replace methods 2873 * of this document which do not check for an installed DocumentFilter. 2874 */ 2875 class Bypass extends DocumentFilter.FilterBypass 2876 { 2877 2878 public Document getDocument() 2879 { 2880 return AbstractDocument.this; 2881 } 2882 2883 public void insertString(int offset, String string, AttributeSet attr) 2884 throws BadLocationException 2885 { 2886 AbstractDocument.this.insertStringImpl(offset, string, attr); 2887 } 2888 2889 public void remove(int offset, int length) 2890 throws BadLocationException 2891 { 2892 AbstractDocument.this.removeImpl(offset, length); 2893 } 2894 2895 public void replace(int offset, int length, String string, 2896 AttributeSet attrs) 2897 throws BadLocationException 2898 { 2899 AbstractDocument.this.replaceImpl(offset, length, string, attrs); 2900 } 2901 2902 } 2903 2904 }