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