AbstractDocument.java 86.1 KB
Newer Older
Tom Tromey committed
1
/* AbstractDocument.java --
2
   Copyright (C) 2002, 2004, 2005, 2006  Free Software Foundation, Inc.
Tom Tromey committed
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40

This file is part of GNU Classpath.

GNU Classpath is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

GNU Classpath is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Classpath; see the file COPYING.  If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.

Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.

As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module.  An independent module is a module which is not derived from
or based on this library.  If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so.  If you do not wish to do so, delete this
exception statement from your version. */


package javax.swing.text;

41 42
import gnu.java.lang.CPStringBuilder;

43
import java.awt.font.TextAttribute;
Tom Tromey committed
44 45
import java.io.PrintStream;
import java.io.Serializable;
46 47
import java.text.Bidi;
import java.util.ArrayList;
Tom Tromey committed
48 49 50
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.EventListener;
51
import java.util.HashMap;
Tom Tromey committed
52 53 54 55 56 57 58 59
import java.util.Hashtable;
import java.util.Vector;

import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.EventListenerList;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
60
import javax.swing.text.DocumentFilter;
Tom Tromey committed
61 62 63 64 65
import javax.swing.tree.TreeNode;
import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.CompoundEdit;
import javax.swing.undo.UndoableEdit;

Tom Tromey committed
66 67 68 69 70 71 72 73 74
/**
 * An abstract base implementation for the {@link Document} interface.
 * This class provides some common functionality for all <code>Element</code>s,
 * most notably it implements a locking mechanism to make document modification
 * thread-safe.
 *
 * @author original author unknown
 * @author Roman Kennke (roman@kennke.org)
 */
75
public abstract class AbstractDocument implements Document, Serializable
Tom Tromey committed
76
{
77 78
  /** The serialization UID (compatible with JDK1.5). */
  private static final long serialVersionUID = 6842927725919637215L;
Tom Tromey committed
79 80 81 82

  /**
   * Standard error message to indicate a bad location.
   */
Tom Tromey committed
83
  protected static final String BAD_LOCATION = "document location failure";
Tom Tromey committed
84 85 86 87

  /**
   * Standard name for unidirectional <code>Element</code>s.
   */
Tom Tromey committed
88
  public static final String BidiElementName = "bidi level";
Tom Tromey committed
89 90 91 92 93

  /**
   * Standard name for content <code>Element</code>s. These are usually
   * {@link LeafElement}s.
   */
Tom Tromey committed
94
  public static final String ContentElementName = "content";
Tom Tromey committed
95 96 97 98 99

  /**
   * Standard name for paragraph <code>Element</code>s. These are usually
   * {@link BranchElement}s.
   */
Tom Tromey committed
100
  public static final String ParagraphElementName = "paragraph";
Tom Tromey committed
101 102 103 104 105

  /**
   * Standard name for section <code>Element</code>s. These are usually
   * {@link DefaultStyledDocument.SectionElement}s.
   */
Tom Tromey committed
106
  public static final String SectionElementName = "section";
Tom Tromey committed
107 108 109 110

  /**
   * Attribute key for storing the element name.
   */
Tom Tromey committed
111 112
  public static final String ElementNameAttribute = "$ename";

Tom Tromey committed
113
  /**
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
   * Standard name for the bidi root element.
   */
  private static final String BidiRootName = "bidi root";

  /**
   * Key for storing the asynchronous load priority.
   */
  private static final String AsyncLoadPriority = "load priority";

  /**
   * Key for storing the I18N state.
   */
  private static final String I18N = "i18n";

  /**
Tom Tromey committed
129 130
   * The actual content model of this <code>Document</code>.
   */
Tom Tromey committed
131
  Content content;
Tom Tromey committed
132 133 134 135

  /**
   * The AttributeContext for this <code>Document</code>.
   */
Tom Tromey committed
136
  AttributeContext context;
Tom Tromey committed
137 138 139 140

  /**
   * The currently installed <code>DocumentFilter</code>.
   */
Tom Tromey committed
141 142
  DocumentFilter documentFilter;

Tom Tromey committed
143 144 145
  /**
   * The documents properties.
   */
Tom Tromey committed
146 147
  Dictionary properties;

Tom Tromey committed
148 149 150
  /**
   * Manages event listeners for this <code>Document</code>.
   */
Tom Tromey committed
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 160 161
  /**
   * The number of readers.  Used for locking.
   */
  private int numReaders = 0;
162

163
  /**
164 165
   * The number of current writers. If this is > 1 then the same thread entered
   * the write lock more than once.
166
   */
167
  private int numWriters = 0;
Tom Tromey committed
168

169 170 171 172
  /** An instance of a DocumentFilter.FilterBypass which allows calling
   * the insert, remove and replace method without checking for an installed
   * document filter.
   */
173 174 175 176 177
  private DocumentFilter.FilterBypass bypass;

  /**
   * The bidi root element.
   */
178 179 180 181 182 183 184
  private BidiRootElement bidiRoot;

  /**
   * True when we are currently notifying any listeners. This is used
   * to detect illegal situations in writeLock().
   */
  private transient boolean notifyListeners;
185

Tom Tromey committed
186 187 188 189 190 191 192 193 194 195
  /**
   * Creates a new <code>AbstractDocument</code> with the specified
   * {@link Content} model.
   *
   * @param doc the <code>Content</code> model to be used in this
   *        <code>Document<code>
   *
   * @see GapContent
   * @see StringContent
   */
Tom Tromey committed
196 197 198 199 200
  protected AbstractDocument(Content doc)
  {
    this(doc, StyleContext.getDefaultStyleContext());
  }

Tom Tromey committed
201 202 203 204 205 206 207 208 209 210 211
  /**
   * Creates a new <code>AbstractDocument</code> with the specified
   * {@link Content} model and {@link AttributeContext}.
   *
   * @param doc the <code>Content</code> model to be used in this
   *        <code>Document<code>
   * @param ctx the <code>AttributeContext</code> to use
   *
   * @see GapContent
   * @see StringContent
   */
Tom Tromey committed
212 213 214 215
  protected AbstractDocument(Content doc, AttributeContext ctx)
  {
    content = doc;
    context = ctx;
216

217 218 219
    // FIXME: Fully implement bidi.
    bidiRoot = new BidiRootElement();

220 221
    // FIXME: This is determined using a Mauve test. Make the document
    // actually use this.
222
    putProperty(I18N, Boolean.FALSE);
223

224 225 226 227 228 229 230 231 232 233 234 235
    // Add one child to the bidi root.
    writeLock();
    try
      {
        Element[] children = new Element[1];
        children[0] = new BidiElement(bidiRoot, 0, 1, 0);
        bidiRoot.replace(0, 0, children);
      }
    finally
      {
        writeUnlock();
      }
Tom Tromey committed
236
  }
237

238 239
  /** Returns the DocumentFilter.FilterBypass instance for this
   * document and create it if it does not exist yet.
240
   *
241 242 243 244 245 246
   * @return This document's DocumentFilter.FilterBypass instance.
   */
  private DocumentFilter.FilterBypass getBypass()
  {
    if (bypass == null)
      bypass = new Bypass();
247

248 249
    return bypass;
  }
Tom Tromey committed
250

Tom Tromey committed
251 252 253 254 255 256 257
  /**
   * Returns the paragraph {@link Element} that holds the specified position.
   *
   * @param pos the position for which to get the paragraph element
   *
   * @return the paragraph {@link Element} that holds the specified position
   */
Tom Tromey committed
258 259
  public abstract Element getParagraphElement(int pos);

Tom Tromey committed
260 261 262 263 264 265 266 267 268
  /**
   * Returns the default root {@link Element} of this <code>Document</code>.
   * Usual <code>Document</code>s only have one root element and return this.
   * However, there may be <code>Document</code> implementations that
   * support multiple root elements, they have to return a default root element
   * here.
   *
   * @return the default root {@link Element} of this <code>Document</code>
   */
Tom Tromey committed
269 270
  public abstract Element getDefaultRootElement();

Tom Tromey committed
271 272 273 274 275 276 277 278 279 280 281 282 283 284
  /**
   * Creates and returns a branch element with the specified
   * <code>parent</code> and <code>attributes</code>. Note that the new
   * <code>Element</code> is linked to the parent <code>Element</code>
   * through {@link Element#getParentElement}, but it is not yet added
   * to the parent <code>Element</code> as child.
   *
   * @param parent the parent <code>Element</code> for the new branch element
   * @param attributes the text attributes to be installed in the new element
   *
   * @return the new branch <code>Element</code>
   *
   * @see BranchElement
   */
Tom Tromey committed
285
  protected Element createBranchElement(Element parent,
286
                                        AttributeSet attributes)
Tom Tromey committed
287 288 289 290
  {
    return new BranchElement(parent, attributes);
  }

Tom Tromey committed
291 292 293 294 295 296 297 298 299 300 301 302 303 304
  /**
   * Creates and returns a leaf element with the specified
   * <code>parent</code> and <code>attributes</code>. Note that the new
   * <code>Element</code> is linked to the parent <code>Element</code>
   * through {@link Element#getParentElement}, but it is not yet added
   * to the parent <code>Element</code> as child.
   *
   * @param parent the parent <code>Element</code> for the new branch element
   * @param attributes the text attributes to be installed in the new element
   *
   * @return the new branch <code>Element</code>
   *
   * @see LeafElement
   */
Tom Tromey committed
305
  protected Element createLeafElement(Element parent, AttributeSet attributes,
306
                                      int start, int end)
Tom Tromey committed
307 308 309 310
  {
    return new LeafElement(parent, attributes, start, end);
  }

Tom Tromey committed
311 312 313 314 315 316 317 318 319 320 321 322
  /**
   * Creates a {@link Position} that keeps track of the location at the
   * specified <code>offset</code>.
   *
   * @param offset the location in the document to keep track by the new
   *        <code>Position</code>
   *
   * @return the newly created <code>Position</code>
   *
   * @throws BadLocationException if <code>offset</code> is not a valid
   *         location in the documents content model
   */
323 324
  public synchronized Position createPosition(final int offset)
    throws BadLocationException
Tom Tromey committed
325
  {
Tom Tromey committed
326
    return content.createPosition(offset);
Tom Tromey committed
327 328
  }

Tom Tromey committed
329 330 331 332 333
  /**
   * Notifies all registered listeners when the document model changes.
   *
   * @param event the <code>DocumentEvent</code> to be fired
   */
Tom Tromey committed
334 335
  protected void fireChangedUpdate(DocumentEvent event)
  {
336 337 338 339 340 341 342 343 344 345 346
    notifyListeners = true;
    try
      {
        DocumentListener[] listeners = getDocumentListeners();
        for (int index = 0; index < listeners.length; ++index)
          listeners[index].changedUpdate(event);
      }
    finally
      {
        notifyListeners = false;
      }
Tom Tromey committed
347 348
  }

Tom Tromey committed
349 350 351 352 353 354
  /**
   * Notifies all registered listeners when content is inserted in the document
   * model.
   *
   * @param event the <code>DocumentEvent</code> to be fired
   */
Tom Tromey committed
355 356
  protected void fireInsertUpdate(DocumentEvent event)
  {
357 358 359 360 361 362 363 364 365 366 367
    notifyListeners = true;
    try
      {
        DocumentListener[] listeners = getDocumentListeners();
        for (int index = 0; index < listeners.length; ++index)
          listeners[index].insertUpdate(event);
      }
    finally
      {
        notifyListeners = false;
      }
Tom Tromey committed
368 369
  }

Tom Tromey committed
370 371 372 373 374 375
  /**
   * Notifies all registered listeners when content is removed from the
   * document model.
   *
   * @param event the <code>DocumentEvent</code> to be fired
   */
Tom Tromey committed
376 377
  protected void fireRemoveUpdate(DocumentEvent event)
  {
378 379 380 381 382 383 384 385 386 387 388
    notifyListeners = true;
    try
      {
        DocumentListener[] listeners = getDocumentListeners();
        for (int index = 0; index < listeners.length; ++index)
          listeners[index].removeUpdate(event);
      }
    finally
      {
        notifyListeners = false;
      }
Tom Tromey committed
389 390
  }

Tom Tromey committed
391 392 393 394 395 396
  /**
   * Notifies all registered listeners when an <code>UndoableEdit</code> has
   * been performed on this <code>Document</code>.
   *
   * @param event the <code>UndoableEditEvent</code> to be fired
   */
Tom Tromey committed
397 398 399 400 401 402 403 404
  protected void fireUndoableEditUpdate(UndoableEditEvent event)
  {
    UndoableEditListener[] listeners = getUndoableEditListeners();

    for (int index = 0; index < listeners.length; ++index)
      listeners[index].undoableEditHappened(event);
  }

Tom Tromey committed
405 406 407 408 409 410
  /**
   * Returns the asynchronous loading priority. Returns <code>-1</code> if this
   * document should not be loaded asynchronously.
   *
   * @return the asynchronous loading priority
   */
Tom Tromey committed
411 412
  public int getAsynchronousLoadPriority()
  {
413 414 415
    Object val = getProperty(AsyncLoadPriority);
    int prio = -1;
    if (val != null)
416
      prio = ((Integer) val).intValue();
417
    return prio;
Tom Tromey committed
418 419
  }

Tom Tromey committed
420 421 422 423 424
  /**
   * Returns the {@link AttributeContext} used in this <code>Document</code>.
   *
   * @return the {@link AttributeContext} used in this <code>Document</code>
   */
425
  protected final AttributeContext getAttributeContext()
Tom Tromey committed
426 427 428 429
  {
    return context;
  }

Tom Tromey committed
430 431 432 433 434
  /**
   * Returns the root element for bidirectional content.
   *
   * @return the root element for bidirectional content
   */
Tom Tromey committed
435 436
  public Element getBidiRootElement()
  {
437
    return bidiRoot;
Tom Tromey committed
438 439
  }

Tom Tromey committed
440 441 442 443 444 445 446 447
  /**
   * Returns the {@link Content} model for this <code>Document</code>
   *
   * @return the {@link Content} model for this <code>Document</code>
   *
   * @see GapContent
   * @see StringContent
   */
448
  protected final Content getContent()
Tom Tromey committed
449 450 451 452
  {
    return content;
  }

Tom Tromey committed
453 454 455 456 457 458 459 460 461
  /**
   * Returns the thread that currently modifies this <code>Document</code>
   * if there is one, otherwise <code>null</code>. This can be used to
   * distinguish between a method call that is part of an ongoing modification
   * or if it is a separate modification for which a new lock must be aquired.
   *
   * @return the thread that currently modifies this <code>Document</code>
   *         if there is one, otherwise <code>null</code>
   */
462
  protected final synchronized Thread getCurrentWriter()
Tom Tromey committed
463
  {
464
    return currentWriter;
Tom Tromey committed
465 466
  }

Tom Tromey committed
467 468 469 470 471
  /**
   * Returns the properties of this <code>Document</code>.
   *
   * @return the properties of this <code>Document</code>
   */
472
  public Dictionary<Object, Object> getDocumentProperties()
Tom Tromey committed
473 474 475 476 477 478 479 480
  {
    // FIXME: make me thread-safe
    if (properties == null)
      properties = new Hashtable();

    return properties;
  }

Tom Tromey committed
481 482 483 484 485 486 487
  /**
   * Returns a {@link Position} which will always mark the end of the
   * <code>Document</code>.
   *
   * @return a {@link Position} which will always mark the end of the
   *         <code>Document</code>
   */
488
  public final Position getEndPosition()
Tom Tromey committed
489
  {
490 491 492 493 494 495 496 497 498 499 500
    Position p;
    try
      {
        p = createPosition(content.length());
      }
    catch (BadLocationException ex)
      {
        // Shouldn't really happen.
        p = null;
      }
    return p;
Tom Tromey committed
501 502
  }

Tom Tromey committed
503 504 505 506 507
  /**
   * Returns the length of this <code>Document</code>'s content.
   *
   * @return the length of this <code>Document</code>'s content
   */
Tom Tromey committed
508 509
  public int getLength()
  {
Tom Tromey committed
510 511 512
    // We return Content.getLength() -1 here because there is always an
    // implicit \n at the end of the Content which does count in Content
    // but not in Document.
Tom Tromey committed
513 514 515
    return content.length() - 1;
  }

Tom Tromey committed
516 517 518 519 520 521 522
  /**
   * Returns all registered listeners of a given listener type.
   *
   * @param listenerType the type of the listeners to be queried
   *
   * @return all registered listeners of the specified type
   */
523
  public <T extends EventListener> T[] getListeners(Class<T> listenerType)
Tom Tromey committed
524 525 526 527
  {
    return listenerList.getListeners(listenerType);
  }

Tom Tromey committed
528 529 530 531 532 533 534 535
  /**
   * Returns a property from this <code>Document</code>'s property list.
   *
   * @param key the key of the property to be fetched
   *
   * @return the property for <code>key</code> or <code>null</code> if there
   *         is no such property stored
   */
536
  public final Object getProperty(Object key)
Tom Tromey committed
537 538 539 540 541 542 543 544 545
  {
    // FIXME: make me thread-safe
    Object value = null;
    if (properties != null)
      value = properties.get(key);

    return value;
  }

Tom Tromey committed
546 547 548 549 550 551 552 553 554
  /**
   * Returns all root elements of this <code>Document</code>. By default
   * this just returns the single root element returned by
   * {@link #getDefaultRootElement()}. <code>Document</code> implementations
   * that support multiple roots must override this method and return all roots
   * here.
   *
   * @return all root elements of this <code>Document</code>
   */
Tom Tromey committed
555 556
  public Element[] getRootElements()
  {
557
    Element[] elements = new Element[2];
Tom Tromey committed
558
    elements[0] = getDefaultRootElement();
559
    elements[1] = getBidiRootElement();
Tom Tromey committed
560 561 562
    return elements;
  }

Tom Tromey committed
563 564 565 566 567 568 569
  /**
   * Returns a {@link Position} which will always mark the beginning of the
   * <code>Document</code>.
   *
   * @return a {@link Position} which will always mark the beginning of the
   *         <code>Document</code>
   */
570
  public final Position getStartPosition()
Tom Tromey committed
571
  {
572 573 574 575 576 577 578 579 580 581 582
    Position p;
    try
      {
        p = createPosition(0);
      }
    catch (BadLocationException ex)
      {
        // Shouldn't really happen.
        p = null;
      }
    return p;
Tom Tromey committed
583 584
  }

Tom Tromey committed
585 586 587 588 589 590 591 592 593 594 595 596 597
  /**
   * Returns a piece of this <code>Document</code>'s content.
   *
   * @param offset the start offset of the content
   * @param length the length of the content
   *
   * @return the piece of content specified by <code>offset</code> and
   *         <code>length</code>
   *
   * @throws BadLocationException if <code>offset</code> or <code>offset +
   *         length</code> are invalid locations with this
   *         <code>Document</code>
   */
Tom Tromey committed
598 599 600 601 602
  public String getText(int offset, int length) throws BadLocationException
  {
    return content.getString(offset, length);
  }

Tom Tromey committed
603 604 605 606 607 608 609 610 611 612 613 614
  /**
   * Fetches a piece of this <code>Document</code>'s content and stores
   * it in the given {@link Segment}.
   *
   * @param offset the start offset of the content
   * @param length the length of the content
   * @param segment the <code>Segment</code> to store the content in
   *
   * @throws BadLocationException if <code>offset</code> or <code>offset +
   *         length</code> are invalid locations with this
   *         <code>Document</code>
   */
Tom Tromey committed
615 616 617 618 619 620
  public void getText(int offset, int length, Segment segment)
    throws BadLocationException
  {
    content.getChars(offset, length, segment);
  }

Tom Tromey committed
621 622 623
  /**
   * Inserts a String into this <code>Document</code> at the specified
   * position and assigning the specified attributes to it.
624
   *
625 626
   * <p>If a {@link DocumentFilter} is installed in this document, the
   * corresponding method of the filter object is called.</p>
627
   *
628 629
   * <p>The method has no effect when <code>text</code> is <code>null</code>
   * or has a length of zero.</p>
630
   *
Tom Tromey committed
631 632 633 634 635 636 637 638
   *
   * @param offset the location at which the string should be inserted
   * @param text the content to be inserted
   * @param attributes the text attributes to be assigned to that string
   *
   * @throws BadLocationException if <code>offset</code> is not a valid
   *         location in this <code>Document</code>
   */
Tom Tromey committed
639 640 641
  public void insertString(int offset, String text, AttributeSet attributes)
    throws BadLocationException
  {
642 643 644
    // Bail out if we have a bogus insertion (Behavior observed in RI).
    if (text == null || text.length() == 0)
      return;
645 646 647 648 649 650 651 652 653 654 655 656 657

    writeLock();
    try
      {
        if (documentFilter == null)
          insertStringImpl(offset, text, attributes);
        else
          documentFilter.insertString(getBypass(), offset, text, attributes);
      }
    finally
      {
        writeUnlock();
      }
658 659 660 661 662
  }

  void insertStringImpl(int offset, String text, AttributeSet attributes)
    throws BadLocationException
  {
Tom Tromey committed
663 664 665 666 667
    // Just return when no text to insert was given.
    if (text == null || text.length() == 0)
      return;
    DefaultDocumentEvent event =
      new DefaultDocumentEvent(offset, text.length(),
668
                               DocumentEvent.EventType.INSERT);
669

670 671 672
    UndoableEdit undo = content.insertString(offset, text);
    if (undo != null)
      event.addEdit(undo);
673

674 675
    // Check if we need bidi layout.
    if (getProperty(I18N).equals(Boolean.FALSE))
676
      {
677 678 679 680 681 682 683 684 685
        Object dir = getProperty(TextAttribute.RUN_DIRECTION);
        if (TextAttribute.RUN_DIRECTION_RTL.equals(dir))
          putProperty(I18N, Boolean.TRUE);
        else
          {
            char[] chars = text.toCharArray();
            if (Bidi.requiresBidi(chars, 0, chars.length))
              putProperty(I18N, Boolean.TRUE);
          }
686
      }
687 688 689 690 691 692 693

    insertUpdate(event, attributes);

    fireInsertUpdate(event);

    if (undo != null)
      fireUndoableEditUpdate(new UndoableEditEvent(this, undo));
Tom Tromey committed
694 695
  }

Tom Tromey committed
696 697 698 699 700 701 702 703
  /**
   * Called to indicate that text has been inserted into this
   * <code>Document</code>. The default implementation does nothing.
   * This method is executed within a write lock.
   *
   * @param chng the <code>DefaultDocumentEvent</code> describing the change
   * @param attr the attributes of the changed content
   */
Tom Tromey committed
704 705
  protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr)
  {
706 707
    if (Boolean.TRUE.equals(getProperty(I18N)))
      updateBidi(chng);
Tom Tromey committed
708 709
  }

Tom Tromey committed
710 711 712 713 714 715 716
  /**
   * Called after some content has been removed from this
   * <code>Document</code>. The default implementation does nothing.
   * This method is executed within a write lock.
   *
   * @param chng the <code>DefaultDocumentEvent</code> describing the change
   */
Tom Tromey committed
717 718
  protected void postRemoveUpdate(DefaultDocumentEvent chng)
  {
719 720
    if (Boolean.TRUE.equals(getProperty(I18N)))
      updateBidi(chng);
Tom Tromey committed
721 722
  }

Tom Tromey committed
723 724 725 726 727 728
  /**
   * Stores a property in this <code>Document</code>'s property list.
   *
   * @param key the key of the property to be stored
   * @param value the value of the property to be stored
   */
729
  public final void putProperty(Object key, Object value)
Tom Tromey committed
730 731 732 733 734
  {
    // FIXME: make me thread-safe
    if (properties == null)
      properties = new Hashtable();

735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762
    if (value == null)
      properties.remove(key);
    else
      properties.put(key, value);

    // Update bidi structure if the RUN_DIRECTION is set.
    if (TextAttribute.RUN_DIRECTION.equals(key))
      {
        if (TextAttribute.RUN_DIRECTION_RTL.equals(value)
            && Boolean.FALSE.equals(getProperty(I18N)))
          putProperty(I18N, Boolean.TRUE);

        if (Boolean.TRUE.equals(getProperty(I18N)))
          {
            writeLock();
            try
              {
                DefaultDocumentEvent ev =
                  new DefaultDocumentEvent(0, getLength(),
                                           DocumentEvent.EventType.INSERT);
                updateBidi(ev);
              }
            finally
              {
                writeUnlock();
              }
          }
      }
Tom Tromey committed
763 764
  }

Tom Tromey committed
765
  /**
766 767 768
   * Updates the bidi element structure.
   *
   * @param ev the document event for the change
Tom Tromey committed
769
   */
770
  private void updateBidi(DefaultDocumentEvent ev)
Tom Tromey committed
771
  {
772 773 774 775 776 777 778 779 780 781 782 783 784
    // Determine start and end offset of the paragraphs to be scanned.
    int start = 0;
    int end = 0;
    DocumentEvent.EventType type = ev.getType();
    if (type == DocumentEvent.EventType.INSERT
        || type == DocumentEvent.EventType.CHANGE)
      {
        int offs = ev.getOffset();
        int endOffs = offs + ev.getLength();
        start = getParagraphElement(offs).getStartOffset();
        end = getParagraphElement(endOffs).getEndOffset();
      }
    else if (type == DocumentEvent.EventType.REMOVE)
785
      {
786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808
        Element par = getParagraphElement(ev.getOffset());
        start = par.getStartOffset();
        end = par.getEndOffset();
      }
    else
      assert false : "Unknown event type";

    // Determine the bidi levels for the affected range.
    Bidi[] bidis = getBidis(start, end);

    int removeFrom = 0;
    int removeTo = 0;

    int offs = 0;
    int lastRunStart = 0;
    int lastRunEnd = 0;
    int lastRunLevel = 0;
    ArrayList newEls = new ArrayList();
    for (int i = 0; i < bidis.length; i++)
      {
        Bidi bidi = bidis[i];
        int numRuns = bidi.getRunCount();
        for (int r = 0; r < numRuns; r++)
809
          {
810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856
            if (r == 0 && i == 0)
              {
                if (start > 0)
                  {
                    // Try to merge with the previous element if it has the
                    // same bidi level as the first run.
                    int prevElIndex = bidiRoot.getElementIndex(start - 1);
                    removeFrom = prevElIndex;
                    Element prevEl = bidiRoot.getElement(prevElIndex);
                    AttributeSet atts = prevEl.getAttributes();
                    int prevElLevel = StyleConstants.getBidiLevel(atts);
                    if (prevElLevel == bidi.getRunLevel(r))
                      {
                        // Merge previous element with current run.
                        lastRunStart = prevEl.getStartOffset() - start;
                        lastRunEnd = bidi.getRunLimit(r);
                        lastRunLevel  = bidi.getRunLevel(r);
                      }
                    else if (prevEl.getEndOffset() > start)
                      {
                        // Split previous element and replace by 2 new elements.
                        lastRunStart = 0;
                        lastRunEnd = bidi.getRunLimit(r);
                        lastRunLevel = bidi.getRunLevel(r);
                        newEls.add(new BidiElement(bidiRoot,
                                                   prevEl.getStartOffset(),
                                                   start, prevElLevel));
                      }
                    else
                      {
                        // Simply start new run at start location.
                        lastRunStart = 0;
                        lastRunEnd = bidi.getRunLimit(r);
                        lastRunLevel = bidi.getRunLevel(r);
                        removeFrom++;
                      }
                  }
                else
                  {
                    // Simply start new run at start location.
                    lastRunStart = 0;
                    lastRunEnd = bidi.getRunLimit(r);
                    lastRunLevel = bidi.getRunLevel(r);
                    removeFrom = 0;
                  }
              }
            if (i == bidis.length - 1 && r == numRuns - 1)
857
              {
858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944
                if (end <= getLength())
                  {
                    // Try to merge last element with next element.
                    int nextIndex = bidiRoot.getElementIndex(end);
                    Element nextEl = bidiRoot.getElement(nextIndex);
                    AttributeSet atts = nextEl.getAttributes();
                    int nextLevel = StyleConstants.getBidiLevel(atts);
                    int level = bidi.getRunLevel(r);
                    if (lastRunLevel == level && level == nextLevel)
                      {
                        // Merge runs together.
                        if (lastRunStart + start == nextEl.getStartOffset())
                          removeTo = nextIndex - 1;
                        else
                          {
                            newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
                                                       nextEl.getEndOffset(), level));
                            removeTo = nextIndex;
                          }
                      }
                    else if (lastRunLevel == level)
                      {
                        // Merge current and last run.
                        int endOffs = offs + bidi.getRunLimit(r);
                        newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
                                                   start + endOffs, level));
                        if (start + endOffs == nextEl.getStartOffset())
                          removeTo = nextIndex - 1;
                        else
                          {
                            newEls.add(new BidiElement(bidiRoot, start + endOffs,
                                                       nextEl.getEndOffset(),
                                                       nextLevel));
                            removeTo = nextIndex;
                          }
                      }
                    else if (level == nextLevel)
                      {
                        // Merge current and next run.
                        newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
                                                   start + lastRunEnd,
                                                   lastRunLevel));
                        newEls.add(new BidiElement(bidiRoot, start + lastRunEnd,
                                                   nextEl.getEndOffset(), level));
                        removeTo = nextIndex;
                      }
                    else
                      {
                        // Split next element.
                        int endOffs = offs + bidi.getRunLimit(r);
                        newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
                                                   start + lastRunEnd,
                                                   lastRunLevel));
                        newEls.add(new BidiElement(bidiRoot, start + lastRunEnd,
                                                   start + endOffs, level));
                        newEls.add(new BidiElement(bidiRoot, start + endOffs,
                                                   nextEl.getEndOffset(),
                                                   nextLevel));
                        removeTo = nextIndex;
                      }
                  }
                else
                  {
                    removeTo = bidiRoot.getElementIndex(end);
                    int level = bidi.getRunLevel(r);
                    int runEnd = offs + bidi.getRunLimit(r);

                    if (level == lastRunLevel)
                      {
                        // Merge with previous.
                        lastRunEnd = offs + runEnd;
                        newEls.add(new BidiElement(bidiRoot,
                                                  start + lastRunStart,
                                                  start + runEnd, level));
                      }
                    else
                      {
                        // Create element for last run and current run.
                        newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
                                                   start + lastRunEnd,
                                                   lastRunLevel));
                        newEls.add(new BidiElement(bidiRoot,
                                                   start + lastRunEnd,
                                                   start + runEnd,
                                                   level));
                       }
                  }
945
              }
946
            else
947
              {
948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966
                int level = bidi.getRunLevel(r);
                int runEnd = bidi.getRunLimit(r);

                if (level == lastRunLevel)
                  {
                    // Merge with previous.
                    lastRunEnd = offs + runEnd;
                  }
                else
                  {
                    // Create element for last run and update values for
                    // current run.
                    newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
                                               start + lastRunEnd,
                                               lastRunLevel));
                    lastRunStart = lastRunEnd;
                    lastRunEnd = offs + runEnd;
                    lastRunLevel = level;
                  }
967 968
              }
          }
969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066
        offs += bidi.getLength();
      }

    // Determine the bidi elements which are to be removed.
    int numRemoved = 0;
    if (bidiRoot.getElementCount() > 0)
      numRemoved = removeTo - removeFrom + 1;
    Element[] removed = new Element[numRemoved];
    for (int i = 0; i < numRemoved; i++)
      removed[i] = bidiRoot.getElement(removeFrom + i);

    Element[] added = new Element[newEls.size()];
    added = (Element[]) newEls.toArray(added);

    // Update the event.
    ElementEdit edit = new ElementEdit(bidiRoot, removeFrom, removed, added);
    ev.addEdit(edit);

    // Update the structure.
    bidiRoot.replace(removeFrom, numRemoved, added);
  }

  /**
   * Determines the Bidi objects for the paragraphs in the specified range.
   *
   * @param start the start of the range
   * @param end the end of the range
   *
   * @return the Bidi analysers for the paragraphs in the range
   */
  private Bidi[] getBidis(int start, int end)
  {
    // Determine the default run direction from the document property.
    Boolean defaultDir = null;
    Object o = getProperty(TextAttribute.RUN_DIRECTION);
    if (o instanceof Boolean)
      defaultDir = (Boolean) o;

    // Scan paragraphs and add their level arrays to the overall levels array.
    ArrayList bidis = new ArrayList();
    Segment s = new Segment();
    for (int i = start; i < end;)
      {
        Element par = getParagraphElement(i);
        int pStart = par.getStartOffset();
        int pEnd = par.getEndOffset();

        // Determine the default run direction of the paragraph.
        Boolean dir = defaultDir;
        o = par.getAttributes().getAttribute(TextAttribute.RUN_DIRECTION);
        if (o instanceof Boolean)
          dir = (Boolean) o;

        // Bidi over the paragraph.
        try
          {
            getText(pStart, pEnd - pStart, s);
          }
        catch (BadLocationException ex)
          {
            assert false : "Must not happen";
          }
        int flag = Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT;
        if (dir != null)
          {
            if (TextAttribute.RUN_DIRECTION_LTR.equals(dir))
              flag = Bidi.DIRECTION_LEFT_TO_RIGHT;
            else
              flag = Bidi.DIRECTION_RIGHT_TO_LEFT;
          }
        Bidi bidi = new Bidi(s.array, s.offset, null, 0, s.count, flag);
        bidis.add(bidi);
        i = pEnd;
      }
    Bidi[] ret = new Bidi[bidis.size()];
    ret = (Bidi[]) bidis.toArray(ret);
    return ret;
  }

  /**
   * Blocks until a read lock can be obtained.  Must block if there is
   * currently a writer modifying the <code>Document</code>.
   */
  public final synchronized void readLock()
  {
    try
      {
        while (currentWriter != null)
          {
            if (currentWriter == Thread.currentThread())
              return;
            wait();
          }
        numReaders++;
      }
    catch (InterruptedException ex)
      {
        throw new Error("Interrupted during grab read lock");
1067
      }
Tom Tromey committed
1068 1069
  }

Tom Tromey committed
1070 1071 1072 1073
  /**
   * Releases the read lock. If this was the only reader on this
   * <code>Document</code>, writing may begin now.
   */
1074
  public final synchronized void readUnlock()
Tom Tromey committed
1075
  {
1076 1077 1078 1079 1080
    // Note we could have a problem here if readUnlock was called without a
    // prior call to readLock but the specs simply warn users to ensure that
    // balance by using a finally block:
    // readLock()
    // try
1081 1082
    // {
    //   doSomethingHere
1083 1084 1085 1086 1087
    // }
    // finally
    // {
    //   readUnlock();
    // }
1088

1089 1090 1091 1092
    // All that the JDK seems to check for is that you don't call unlock
    // more times than you've previously called lock, but it doesn't make
    // sure that the threads calling unlock were the same ones that called lock

1093 1094 1095 1096 1097 1098
    // If the current thread holds the write lock, and attempted to also obtain
    // a readLock, then numReaders hasn't been incremented and we don't need
    // to unlock it here.
    if (currentWriter == Thread.currentThread())
      return;

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 1105

    // If currentWriter is not null, the application code probably had a
    // writeLock and then tried to obtain a readLock, in which case
1106 1107 1108
    // numReaders wasn't incremented
    numReaders--;
    notify();
Tom Tromey committed
1109 1110
  }

Tom Tromey committed
1111 1112
  /**
   * Removes a piece of content from this <code>Document</code>.
1113
   *
1114 1115 1116 1117
   * <p>If a {@link DocumentFilter} is installed in this document, the
   * corresponding method of the filter object is called. The
   * <code>DocumentFilter</code> is called even if <code>length</code>
   * is zero. This is different from {@link #replace}.</p>
1118
   *
1119 1120 1121
   * <p>Note: When <code>length</code> is zero or below the call is not
   * forwarded to the underlying {@link AbstractDocument.Content} instance
   * of this document and no exception is thrown.</p>
1122
   *
Tom Tromey committed
1123 1124 1125 1126 1127 1128 1129
   * @param offset the start offset of the fragment to be removed
   * @param length the length of the fragment to be removed
   *
   * @throws BadLocationException if <code>offset</code> or
   *         <code>offset + length</code> or invalid locations within this
   *         document
   */
Tom Tromey committed
1130 1131
  public void remove(int offset, int length) throws BadLocationException
  {
1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144
    writeLock();
    try
      {
        DocumentFilter f = getDocumentFilter();
        if (f == null)
          removeImpl(offset, length);
        else
          f.remove(getBypass(), offset, length);
      }
    finally
      {
        writeUnlock();
      }
1145
  }
1146

1147 1148
  void removeImpl(int offset, int length) throws BadLocationException
  {
1149 1150 1151
    // The RI silently ignores all requests that have a negative length.
    // Don't ask my why, but that's how it is.
    if (length > 0)
1152
      {
1153 1154 1155 1156 1157 1158 1159 1160 1161
        if (offset < 0 || offset > getLength())
          throw new BadLocationException("Invalid remove position", offset);

        if (offset + length > getLength())
          throw new BadLocationException("Invalid remove length", offset);

        DefaultDocumentEvent event =
          new DefaultDocumentEvent(offset, length,
                                   DocumentEvent.EventType.REMOVE);
1162 1163

        // The order of the operations below is critical!
1164 1165 1166 1167 1168
        removeUpdate(event);
        UndoableEdit temp = content.remove(offset, length);

        postRemoveUpdate(event);
        fireRemoveUpdate(event);
1169
      }
Tom Tromey committed
1170 1171 1172
  }

  /**
Tom Tromey committed
1173 1174
   * Replaces a piece of content in this <code>Document</code> with
   * another piece of content.
1175
   *
1176 1177
   * <p>If a {@link DocumentFilter} is installed in this document, the
   * corresponding method of the filter object is called.</p>
1178
   *
1179 1180 1181
   * <p>The method has no effect if <code>length</code> is zero (and
   * only zero) and, at the same time, <code>text</code> is
   * <code>null</code> or has zero length.</p>
Tom Tromey committed
1182 1183 1184 1185 1186 1187 1188 1189 1190
   *
   * @param offset the start offset of the fragment to be removed
   * @param length the length of the fragment to be removed
   * @param text the text to replace the content with
   * @param attributes the text attributes to assign to the new content
   *
   * @throws BadLocationException if <code>offset</code> or
   *         <code>offset + length</code> or invalid locations within this
   *         document
Tom Tromey committed
1191 1192 1193 1194
   *
   * @since 1.4
   */
  public void replace(int offset, int length, String text,
1195 1196 1197 1198
                      AttributeSet attributes)
    throws BadLocationException
  {
    // Bail out if we have a bogus replacement (Behavior observed in RI).
1199
    if (length == 0
1200 1201
        && (text == null || text.length() == 0))
      return;
1202 1203 1204

    writeLock();
    try
1205
      {
1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222
        if (documentFilter == null)
          {
            // It is important to call the methods which again do the checks
            // of the arguments and the DocumentFilter because subclasses may
            // have overridden these methods and provide crucial behavior
            // which would be skipped if we call the non-checking variants.
            // An example for this is PlainDocument where insertString can
            // provide a filtering of newlines.
            remove(offset, length);
            insertString(offset, text, attributes);
          }
        else
          documentFilter.replace(getBypass(), offset, length, text, attributes);
      }
    finally
      {
        writeUnlock();
1223 1224
      }
  }
1225

1226
  void replaceImpl(int offset, int length, String text,
1227
                      AttributeSet attributes)
Tom Tromey committed
1228 1229
    throws BadLocationException
  {
1230 1231
    removeImpl(offset, length);
    insertStringImpl(offset, text, attributes);
Tom Tromey committed
1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254
  }

  /**
   * Adds a <code>DocumentListener</code> object to this document.
   *
   * @param listener the listener to add
   */
  public void addDocumentListener(DocumentListener listener)
  {
    listenerList.add(DocumentListener.class, listener);
  }

  /**
   * Removes a <code>DocumentListener</code> object from this document.
   *
   * @param listener the listener to remove
   */
  public void removeDocumentListener(DocumentListener listener)
  {
    listenerList.remove(DocumentListener.class, listener);
  }

  /**
Tom Tromey committed
1255
   * Returns all registered <code>DocumentListener</code>s.
Tom Tromey committed
1256
   *
Tom Tromey committed
1257
   * @return all registered <code>DocumentListener</code>s
Tom Tromey committed
1258 1259 1260 1261 1262 1263 1264
   */
  public DocumentListener[] getDocumentListeners()
  {
    return (DocumentListener[]) getListeners(DocumentListener.class);
  }

  /**
Tom Tromey committed
1265
   * Adds an {@link UndoableEditListener} to this <code>Document</code>.
Tom Tromey committed
1266 1267 1268 1269 1270 1271 1272 1273 1274
   *
   * @param listener the listener to add
   */
  public void addUndoableEditListener(UndoableEditListener listener)
  {
    listenerList.add(UndoableEditListener.class, listener);
  }

  /**
Tom Tromey committed
1275
   * Removes an {@link UndoableEditListener} from this <code>Document</code>.
Tom Tromey committed
1276 1277 1278 1279 1280 1281 1282 1283 1284
   *
   * @param listener the listener to remove
   */
  public void removeUndoableEditListener(UndoableEditListener listener)
  {
    listenerList.remove(UndoableEditListener.class, listener);
  }

  /**
Tom Tromey committed
1285
   * Returns all registered {@link UndoableEditListener}s.
Tom Tromey committed
1286
   *
Tom Tromey committed
1287
   * @return all registered {@link UndoableEditListener}s
Tom Tromey committed
1288 1289 1290 1291 1292 1293
   */
  public UndoableEditListener[] getUndoableEditListeners()
  {
    return (UndoableEditListener[]) getListeners(UndoableEditListener.class);
  }

Tom Tromey committed
1294 1295 1296 1297 1298 1299 1300 1301
  /**
   * Called before some content gets removed from this <code>Document</code>.
   * The default implementation does nothing but may be overridden by
   * subclasses to modify the <code>Document</code> structure in response
   * to a remove request. The method is executed within a write lock.
   *
   * @param chng the <code>DefaultDocumentEvent</code> describing the change
   */
Tom Tromey committed
1302 1303
  protected void removeUpdate(DefaultDocumentEvent chng)
  {
Tom Tromey committed
1304
    // Do nothing here. Subclasses may wish to override this.
Tom Tromey committed
1305 1306
  }

Tom Tromey committed
1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321
  /**
   * Called to render this <code>Document</code> visually. It obtains a read
   * lock, ensuring that no changes will be made to the <code>document</code>
   * during the rendering process. It then calls the {@link Runnable#run()}
   * method on <code>runnable</code>. This method <em>must not</em> attempt
   * to modifiy the <code>Document</code>, since a deadlock will occur if it
   * tries to obtain a write lock. When the {@link Runnable#run()} method
   * completes (either naturally or by throwing an exception), the read lock
   * is released. Note that there is nothing in this method related to
   * the actual rendering. It could be used to execute arbitrary code within
   * a read lock.
   *
   * @param runnable the {@link Runnable} to execute
   */
  public void render(Runnable runnable)
Tom Tromey committed
1322
  {
1323 1324 1325 1326 1327 1328 1329 1330 1331
    readLock();
    try
    {
      runnable.run();
    }
    finally
    {
      readUnlock();
    }
Tom Tromey committed
1332 1333
  }

Tom Tromey committed
1334 1335 1336 1337 1338 1339 1340
  /**
   * Sets the asynchronous loading priority for this <code>Document</code>.
   * A value of <code>-1</code> indicates that this <code>Document</code>
   * should be loaded synchronously.
   *
   * @param p the asynchronous loading priority to set
   */
Tom Tromey committed
1341 1342
  public void setAsynchronousLoadPriority(int p)
  {
1343 1344
    Integer val = p >= 0 ? new Integer(p) : null;
    putProperty(AsyncLoadPriority, val);
Tom Tromey committed
1345 1346
  }

Tom Tromey committed
1347 1348 1349 1350 1351
  /**
   * Sets the properties of this <code>Document</code>.
   *
   * @param p the document properties to set
   */
1352
  public void setDocumentProperties(Dictionary<Object, Object> p)
Tom Tromey committed
1353 1354
  {
    // FIXME: make me thread-safe
Tom Tromey committed
1355
    properties = p;
Tom Tromey committed
1356 1357
  }

Tom Tromey committed
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.
Tom Tromey committed
1361
   */
1362
  protected synchronized final void writeLock()
Tom Tromey committed
1363
  {
1364
    try
1365
      {
1366
        while (numReaders > 0 || currentWriter != null)
1367
          {
1368
            if (Thread.currentThread() == currentWriter)
1369
              {
1370 1371 1372 1373
                if (notifyListeners)
                  throw new IllegalStateException("Mutation during notify");
                numWriters++;
                return;
1374
              }
1375
            wait();
1376 1377
          }
        currentWriter = Thread.currentThread();
1378 1379 1380 1381 1382
        numWriters = 1;
      }
    catch (InterruptedException ex)
      {
        throw new Error("Interupted during grab write lock");
1383
      }
Tom Tromey committed
1384 1385
  }

Tom Tromey committed
1386 1387 1388 1389
  /**
   * Releases the write lock. This allows waiting readers or writers to
   * obtain the lock.
   */
1390
  protected final synchronized void writeUnlock()
Tom Tromey committed
1391
  {
1392 1393 1394 1395 1396 1397
    if (--numWriters <= 0)
      {
        numWriters = 0;
        currentWriter = null;
        notifyAll();
      }
Tom Tromey committed
1398 1399 1400
  }

  /**
Tom Tromey committed
1401 1402 1403 1404 1405 1406
   * Returns the currently installed {@link DocumentFilter} for this
   * <code>Document</code>.
   *
   * @return the currently installed {@link DocumentFilter} for this
   *         <code>Document</code>
   *
Tom Tromey committed
1407 1408 1409 1410 1411 1412 1413 1414
   * @since 1.4
   */
  public DocumentFilter getDocumentFilter()
  {
    return documentFilter;
  }

  /**
Tom Tromey committed
1415 1416 1417 1418
   * Sets the {@link DocumentFilter} for this <code>Document</code>.
   *
   * @param filter the <code>DocumentFilter</code> to set
   *
Tom Tromey committed
1419 1420 1421 1422 1423 1424 1425
   * @since 1.4
   */
  public void setDocumentFilter(DocumentFilter filter)
  {
    this.documentFilter = filter;
  }

Tom Tromey committed
1426 1427 1428 1429 1430
  /**
   * Dumps diagnostic information to the specified <code>PrintStream</code>.
   *
   * @param out the stream to write the diagnostic information to
   */
Tom Tromey committed
1431 1432 1433
  public void dump(PrintStream out)
  {
    ((AbstractElement) getDefaultRootElement()).dump(out, 0);
1434
    ((AbstractElement) getBidiRootElement()).dump(out, 0);
Tom Tromey committed
1435 1436
  }

Tom Tromey committed
1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447
  /**
   * Defines a set of methods for managing text attributes for one or more
   * <code>Document</code>s.
   *
   * Replicating {@link AttributeSet}s throughout a <code>Document</code> can
   * be very expensive. Implementations of this interface are intended to
   * provide intelligent management of <code>AttributeSet</code>s, eliminating
   * costly duplication.
   *
   * @see StyleContext
   */
Tom Tromey committed
1448 1449
  public interface AttributeContext
  {
Tom Tromey committed
1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460
    /**
     * Returns an {@link AttributeSet} that contains the attributes
     * of <code>old</code> plus the new attribute specified by
     * <code>name</code> and <code>value</code>.
     *
     * @param old the attribute set to be merged with the new attribute
     * @param name the name of the attribute to be added
     * @param value the value of the attribute to be added
     *
     * @return the old attributes plus the new attribute
     */
Tom Tromey committed
1461 1462
    AttributeSet addAttribute(AttributeSet old, Object name, Object value);

Tom Tromey committed
1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473
    /**
     * Returns an {@link AttributeSet} that contains the attributes
     * of <code>old</code> plus the new attributes in <code>attributes</code>.
     *
     * @param old the set of attributes where to add the new attributes
     * @param attributes the attributes to be added
     *
     * @return an {@link AttributeSet} that contains the attributes
     *         of <code>old</code> plus the new attributes in
     *         <code>attributes</code>
     */
Tom Tromey committed
1474 1475
    AttributeSet addAttributes(AttributeSet old, AttributeSet attributes);

Tom Tromey committed
1476 1477 1478 1479 1480
    /**
     * Returns an empty {@link AttributeSet}.
     *
     * @return  an empty {@link AttributeSet}
     */
Tom Tromey committed
1481 1482
    AttributeSet getEmptySet();

Tom Tromey committed
1483 1484 1485 1486 1487 1488
    /**
     * Called to indicate that the attributes in <code>attributes</code> are
     * no longer used.
     *
     * @param attributes the attributes are no longer used
     */
Tom Tromey committed
1489 1490
    void reclaim(AttributeSet attributes);

Tom Tromey committed
1491 1492 1493 1494 1495 1496 1497 1498 1499 1500
    /**
     * Returns a {@link AttributeSet} that has the attribute with the specified
     * <code>name</code> removed from <code>old</code>.
     *
     * @param old the attribute set from which an attribute is removed
     * @param name the name of the attribute to be removed
     *
     * @return the attributes of <code>old</code> minus the attribute
     *         specified by <code>name</code>
     */
Tom Tromey committed
1501 1502
    AttributeSet removeAttribute(AttributeSet old, Object name);

Tom Tromey committed
1503 1504 1505 1506 1507 1508 1509 1510 1511 1512
    /**
     * Removes all attributes in <code>attributes</code> from <code>old</code>
     * and returns the resulting <code>AttributeSet</code>.
     *
     * @param old the set of attributes from which to remove attributes
     * @param attributes the attributes to be removed from <code>old</code>
     *
     * @return the attributes of <code>old</code> minus the attributes in
     *         <code>attributes</code>
     */
Tom Tromey committed
1513 1514
    AttributeSet removeAttributes(AttributeSet old, AttributeSet attributes);

Tom Tromey committed
1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525
    /**
     * Removes all attributes specified by <code>names</code> from
     * <code>old</code> and returns the resulting <code>AttributeSet</code>.
     *
     * @param old the set of attributes from which to remove attributes
     * @param names the names of the attributes to be removed from
     *        <code>old</code>
     *
     * @return the attributes of <code>old</code> minus the attributes in
     *         <code>attributes</code>
     */
1526
    AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names);
Tom Tromey committed
1527 1528
  }

Tom Tromey committed
1529 1530 1531 1532
  /**
   * A sequence of data that can be edited. This is were the actual content
   * in <code>AbstractDocument</code>'s is stored.
   */
Tom Tromey committed
1533 1534
  public interface Content
  {
Tom Tromey committed
1535 1536 1537 1538 1539 1540 1541 1542 1543 1544
    /**
     * Creates a {@link Position} that keeps track of the location at
     * <code>offset</code>.
     *
     * @return a {@link Position} that keeps track of the location at
     *         <code>offset</code>.
     *
     * @throw BadLocationException if <code>offset</code> is not a valid
     *        location in this <code>Content</code> model
     */
Tom Tromey committed
1545 1546
    Position createPosition(int offset) throws BadLocationException;

Tom Tromey committed
1547 1548 1549 1550 1551
    /**
     * Returns the length of the content.
     *
     * @return the length of the content
     */
Tom Tromey committed
1552 1553
    int length();

Tom Tromey committed
1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565
    /**
     * Inserts a string into the content model.
     *
     * @param where the offset at which to insert the string
     * @param str the string to be inserted
     *
     * @return an <code>UndoableEdit</code> or <code>null</code> if undo is
     *         not supported by this <code>Content</code> model
     *
     * @throws BadLocationException if <code>where</code> is not a valid
     *         location in this <code>Content</code> model
     */
Tom Tromey committed
1566 1567 1568
    UndoableEdit insertString(int where, String str)
      throws BadLocationException;

Tom Tromey committed
1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580
    /**
     * Removes a piece of content from the content model.
     *
     * @param where the offset at which to remove content
     * @param nitems the number of characters to be removed
     *
     * @return an <code>UndoableEdit</code> or <code>null</code> if undo is
     *         not supported by this <code>Content</code> model
     *
     * @throws BadLocationException if <code>where</code> is not a valid
     *         location in this <code>Content</code> model
     */
Tom Tromey committed
1581 1582
    UndoableEdit remove(int where, int nitems) throws BadLocationException;

Tom Tromey committed
1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593
    /**
     * Returns a piece of content.
     *
     * @param where the start offset of the requested fragment
     * @param len the length of the requested fragment
     *
     * @return the requested fragment
     * @throws BadLocationException if <code>offset</code> or
     *         <code>offset + len</code>is not a valid
     *         location in this <code>Content</code> model
     */
Tom Tromey committed
1594 1595
    String getString(int where, int len) throws BadLocationException;

Tom Tromey committed
1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606
    /**
     * Fetches a piece of content and stores it in <code>txt</code>.
     *
     * @param where the start offset of the requested fragment
     * @param len the length of the requested fragment
     * @param txt the <code>Segment</code> where to fragment is stored into
     *
     * @throws BadLocationException if <code>offset</code> or
     *         <code>offset + len</code>is not a valid
     *         location in this <code>Content</code> model
     */
Tom Tromey committed
1607 1608 1609
    void getChars(int where, int len, Segment txt) throws BadLocationException;
  }

Tom Tromey committed
1610 1611 1612
  /**
   * An abstract base implementation of the {@link Element} interface.
   */
Tom Tromey committed
1613 1614 1615
  public abstract class AbstractElement
    implements Element, MutableAttributeSet, TreeNode, Serializable
  {
1616 1617
    /** The serialization UID (compatible with JDK1.5). */
    private static final long serialVersionUID = 1712240033321461704L;
Tom Tromey committed
1618 1619

    /** The number of characters that this Element spans. */
Tom Tromey committed
1620
    int count;
Tom Tromey committed
1621 1622

    /** The starting offset of this Element. */
Tom Tromey committed
1623 1624
    int offset;

Tom Tromey committed
1625
    /** The attributes of this Element. */
Tom Tromey committed
1626 1627
    AttributeSet attributes;

Tom Tromey committed
1628
    /** The parent element. */
Tom Tromey committed
1629 1630
    Element element_parent;

Tom Tromey committed
1631
    /** The parent in the TreeNode interface. */
Tom Tromey committed
1632
    TreeNode tree_parent;
Tom Tromey committed
1633 1634

    /** The children of this element. */
Tom Tromey committed
1635 1636
    Vector tree_children;

Tom Tromey committed
1637 1638 1639 1640 1641 1642 1643 1644
    /**
     * Creates a new instance of <code>AbstractElement</code> with a
     * specified parent <code>Element</code> and <code>AttributeSet</code>.
     *
     * @param p the parent of this <code>AbstractElement</code>
     * @param s the attributes to be assigned to this
     *        <code>AbstractElement</code>
     */
Tom Tromey committed
1645 1646 1647
    public AbstractElement(Element p, AttributeSet s)
    {
      element_parent = p;
Tom Tromey committed
1648 1649 1650
      AttributeContext ctx = getAttributeContext();
      attributes = ctx.getEmptySet();
      if (s != null)
1651
        addAttributes(s);
Tom Tromey committed
1652 1653
    }

Tom Tromey committed
1654 1655 1656 1657 1658 1659 1660
    /**
     * Returns the child nodes of this <code>Element</code> as an
     * <code>Enumeration</code> of {@link TreeNode}s.
     *
     * @return the child nodes of this <code>Element</code> as an
     *         <code>Enumeration</code> of {@link TreeNode}s
     */
Tom Tromey committed
1661
    public abstract Enumeration children();
Tom Tromey committed
1662 1663 1664 1665 1666 1667 1668 1669

    /**
     * Returns <code>true</code> if this <code>AbstractElement</code>
     * allows children.
     *
     * @return <code>true</code> if this <code>AbstractElement</code>
     *         allows children
     */
Tom Tromey committed
1670
    public abstract boolean getAllowsChildren();
Tom Tromey committed
1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681

    /**
     * Returns the child of this <code>AbstractElement</code> at
     * <code>index</code>.
     *
     * @param index the position in the child list of the child element to
     *        be returned
     *
     * @return the child of this <code>AbstractElement</code> at
     *         <code>index</code>
     */
Tom Tromey committed
1682 1683 1684 1685
    public TreeNode getChildAt(int index)
    {
      return (TreeNode) tree_children.get(index);
    }
Tom Tromey committed
1686 1687 1688 1689 1690 1691

    /**
     * Returns the number of children of this <code>AbstractElement</code>.
     *
     * @return the number of children of this <code>AbstractElement</code>
     */
Tom Tromey committed
1692 1693 1694 1695
    public int getChildCount()
    {
      return tree_children.size();
    }
Tom Tromey committed
1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707

    /**
     * Returns the index of a given child <code>TreeNode</code> or
     * <code>-1</code> if <code>node</code> is not a child of this
     * <code>AbstractElement</code>.
     *
     * @param node the node for which the index is requested
     *
     * @return the index of a given child <code>TreeNode</code> or
     *         <code>-1</code> if <code>node</code> is not a child of this
     *         <code>AbstractElement</code>
     */
Tom Tromey committed
1708 1709 1710 1711 1712
    public int getIndex(TreeNode node)
    {
      return tree_children.indexOf(node);
    }

Tom Tromey committed
1713 1714 1715 1716 1717 1718 1719 1720 1721
    /**
     * Returns the parent <code>TreeNode</code> of this
     * <code>AbstractElement</code> or <code>null</code> if this element
     * has no parent.
     *
     * @return the parent <code>TreeNode</code> of this
     *         <code>AbstractElement</code> or <code>null</code> if this
     *         element has no parent
     */
Tom Tromey committed
1722 1723 1724 1725 1726
    public TreeNode getParent()
    {
      return tree_parent;
    }

Tom Tromey committed
1727 1728 1729 1730 1731 1732 1733
    /**
     * Returns <code>true</code> if this <code>AbstractElement</code> is a
     * leaf element, <code>false</code> otherwise.
     *
     * @return <code>true</code> if this <code>AbstractElement</code> is a
     *         leaf element, <code>false</code> otherwise
     */
Tom Tromey committed
1734 1735
    public abstract boolean isLeaf();

Tom Tromey committed
1736 1737 1738 1739 1740 1741
    /**
     * Adds an attribute to this element.
     *
     * @param name the name of the attribute to be added
     * @param value the value of the attribute to be added
     */
Tom Tromey committed
1742 1743 1744 1745 1746
    public void addAttribute(Object name, Object value)
    {
      attributes = getAttributeContext().addAttribute(attributes, name, value);
    }

Tom Tromey committed
1747 1748 1749 1750 1751
    /**
     * Adds a set of attributes to this element.
     *
     * @param attrs the attributes to be added to this element
     */
Tom Tromey committed
1752 1753 1754 1755 1756
    public void addAttributes(AttributeSet attrs)
    {
      attributes = getAttributeContext().addAttributes(attributes, attrs);
    }

Tom Tromey committed
1757 1758 1759 1760 1761
    /**
     * Removes an attribute from this element.
     *
     * @param name the name of the attribute to be removed
     */
Tom Tromey committed
1762 1763 1764 1765 1766
    public void removeAttribute(Object name)
    {
      attributes = getAttributeContext().removeAttribute(attributes, name);
    }

Tom Tromey committed
1767 1768 1769 1770 1771
    /**
     * Removes a set of attributes from this element.
     *
     * @param attrs the attributes to be removed
     */
Tom Tromey committed
1772 1773 1774 1775 1776
    public void removeAttributes(AttributeSet attrs)
    {
      attributes = getAttributeContext().removeAttributes(attributes, attrs);
    }

Tom Tromey committed
1777 1778 1779 1780 1781
    /**
     * Removes a set of attribute from this element.
     *
     * @param names the names of the attributes to be removed
     */
1782
    public void removeAttributes(Enumeration<?> names)
Tom Tromey committed
1783 1784 1785 1786
    {
      attributes = getAttributeContext().removeAttributes(attributes, names);
    }

Tom Tromey committed
1787 1788 1789 1790 1791 1792
    /**
     * Sets the parent attribute set against which the element can resolve
     * attributes that are not defined in itself.
     *
     * @param parent the resolve parent to set
     */
Tom Tromey committed
1793 1794
    public void setResolveParent(AttributeSet parent)
    {
Tom Tromey committed
1795 1796 1797
      attributes = getAttributeContext().addAttribute(attributes,
                                                      ResolveAttribute,
                                                      parent);
Tom Tromey committed
1798 1799
    }

Tom Tromey committed
1800 1801 1802 1803 1804 1805 1806 1807 1808 1809
    /**
     * Returns <code>true</code> if this element contains the specified
     * attribute.
     *
     * @param name the name of the attribute to check
     * @param value the value of the attribute to check
     *
     * @return <code>true</code> if this element contains the specified
     *         attribute
     */
Tom Tromey committed
1810 1811 1812 1813 1814
    public boolean containsAttribute(Object name, Object value)
    {
      return attributes.containsAttribute(name, value);
    }

Tom Tromey committed
1815 1816 1817 1818 1819 1820 1821 1822 1823
    /**
     * Returns <code>true</code> if this element contains all of the
     * specified attributes.
     *
     * @param attrs the attributes to check
     *
     * @return <code>true</code> if this element contains all of the
     *         specified attributes
     */
Tom Tromey committed
1824 1825 1826 1827 1828
    public boolean containsAttributes(AttributeSet attrs)
    {
      return attributes.containsAttributes(attrs);
    }

Tom Tromey committed
1829 1830 1831 1832 1833
    /**
     * Returns a copy of the attributes of this element.
     *
     * @return a copy of the attributes of this element
     */
Tom Tromey committed
1834 1835 1836 1837 1838
    public AttributeSet copyAttributes()
    {
      return attributes.copyAttributes();
    }

Tom Tromey committed
1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849
    /**
     * Returns the attribute value with the specified key. If this attribute
     * is not defined in this element and this element has a resolving
     * parent, the search goes upward to the resolve parent chain.
     *
     * @param key the key of the requested attribute
     *
     * @return the attribute value for <code>key</code> of <code>null</code>
     *         if <code>key</code> is not found locally and cannot be resolved
     *         in this element's resolve parents
     */
Tom Tromey committed
1850 1851
    public Object getAttribute(Object key)
    {
1852
      Object result = attributes.getAttribute(key);
1853
      if (result == null)
1854
        {
1855 1856 1857
          AttributeSet resParent = getResolveParent();
          if (resParent != null)
            result = resParent.getAttribute(key);
1858 1859
        }
      return result;
Tom Tromey committed
1860 1861
    }

Tom Tromey committed
1862 1863 1864 1865 1866
    /**
     * Returns the number of defined attributes in this element.
     *
     * @return the number of defined attributes in this element
     */
Tom Tromey committed
1867 1868 1869 1870
    public int getAttributeCount()
    {
      return attributes.getAttributeCount();
    }
Tom Tromey committed
1871 1872 1873 1874 1875 1876

    /**
     * Returns the names of the attributes of this element.
     *
     * @return the names of the attributes of this element
     */
1877
    public Enumeration<?> getAttributeNames()
Tom Tromey committed
1878 1879 1880
    {
      return attributes.getAttributeNames();
    }
Tom Tromey committed
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
Tom Tromey committed
1887 1888 1889 1890 1891
     *
     * @return the resolve parent of this element
     *
     * @see #setResolveParent(AttributeSet)
     */
Tom Tromey committed
1892 1893
    public AttributeSet getResolveParent()
    {
1894
      return attributes.getResolveParent();
Tom Tromey committed
1895 1896
    }

Tom Tromey committed
1897 1898 1899 1900 1901 1902 1903 1904 1905
    /**
     * Returns <code>true</code> if an attribute with the specified name
     * is defined in this element, <code>false</code> otherwise.
     *
     * @param attrName the name of the requested attributes
     *
     * @return <code>true</code> if an attribute with the specified name
     *         is defined in this element, <code>false</code> otherwise
     */
Tom Tromey committed
1906 1907 1908 1909
    public boolean isDefined(Object attrName)
    {
      return attributes.isDefined(attrName);
    }
Tom Tromey committed
1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921

    /**
     * Returns <code>true</code> if the specified <code>AttributeSet</code>
     * is equal to this element's <code>AttributeSet</code>, <code>false</code>
     * otherwise.
     *
     * @param attrs the attributes to compare this element to
     *
     * @return <code>true</code> if the specified <code>AttributeSet</code>
     *         is equal to this element's <code>AttributeSet</code>,
     *         <code>false</code> otherwise
     */
1922
    public boolean isEqual(AttributeSet attrs)
Tom Tromey committed
1923 1924 1925 1926
    {
      return attributes.isEqual(attrs);
    }

Tom Tromey committed
1927 1928 1929 1930 1931
    /**
     * Returns the attributes of this element.
     *
     * @return the attributes of this element
     */
Tom Tromey committed
1932 1933
    public AttributeSet getAttributes()
    {
Tom Tromey committed
1934
      return this;
Tom Tromey committed
1935 1936
    }

Tom Tromey committed
1937 1938 1939 1940 1941
    /**
     * Returns the {@link Document} to which this element belongs.
     *
     * @return the {@link Document} to which this element belongs
     */
Tom Tromey committed
1942 1943 1944 1945
    public Document getDocument()
    {
      return AbstractDocument.this;
    }
Tom Tromey committed
1946 1947 1948 1949 1950 1951 1952 1953

    /**
     * Returns the child element at the specified <code>index</code>.
     *
     * @param index the index of the requested child element
     *
     * @return the requested element
     */
Tom Tromey committed
1954
    public abstract Element getElement(int index);
Tom Tromey committed
1955 1956 1957 1958 1959 1960

    /**
     * Returns the name of this element.
     *
     * @return the name of this element
     */
Tom Tromey committed
1961 1962
    public String getName()
    {
1963
      return (String) attributes.getAttribute(ElementNameAttribute);
Tom Tromey committed
1964
    }
Tom Tromey committed
1965 1966 1967 1968 1969 1970

    /**
     * Returns the parent element of this element.
     *
     * @return the parent element of this element
     */
Tom Tromey committed
1971 1972 1973 1974
    public Element getParentElement()
    {
      return element_parent;
    }
Tom Tromey committed
1975 1976 1977 1978 1979 1980 1981 1982

    /**
     * Returns the offset inside the document model that is after the last
     * character of this element.
     *
     * @return the offset inside the document model that is after the last
     *         character of this element
     */
Tom Tromey committed
1983
    public abstract int getEndOffset();
Tom Tromey committed
1984 1985 1986 1987 1988 1989

    /**
     * Returns the number of child elements of this element.
     *
     * @return the number of child elements of this element
     */
Tom Tromey committed
1990
    public abstract int getElementCount();
Tom Tromey committed
1991 1992 1993 1994 1995 1996 1997 1998 1999 2000

    /**
     * Returns the index of the child element that spans the specified
     * offset in the document model.
     *
     * @param offset the offset for which the responsible element is searched
     *
     * @return the index of the child element that spans the specified
     *         offset in the document model
     */
Tom Tromey committed
2001
    public abstract int getElementIndex(int offset);
Tom Tromey committed
2002 2003 2004 2005 2006 2007

    /**
     * Returns the start offset if this element inside the document model.
     *
     * @return the start offset if this element inside the document model
     */
Tom Tromey committed
2008 2009
    public abstract int getStartOffset();

Tom Tromey committed
2010 2011 2012 2013 2014 2015
    /**
     * Prints diagnostic output to the specified stream.
     *
     * @param stream the stream to write to
     * @param indent the indentation level
     */
Tom Tromey committed
2016 2017
    public void dump(PrintStream stream, int indent)
    {
2018
      CPStringBuilder b = new CPStringBuilder();
Tom Tromey committed
2019
      for (int i = 0; i < indent; ++i)
2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039
        b.append(' ');
      b.append('<');
      b.append(getName());
      // Dump attributes if there are any.
      if (getAttributeCount() > 0)
        {
          b.append('\n');
          Enumeration attNames = getAttributeNames();
          while (attNames.hasMoreElements())
            {
              for (int i = 0; i < indent + 2; ++i)
                b.append(' ');
              Object attName = attNames.nextElement();
              b.append(attName);
              b.append('=');
              Object attribute = getAttribute(attName);
              b.append(attribute);
              b.append('\n');
            }
        }
2040 2041 2042 2043 2044
      if (getAttributeCount() > 0)
        {
          for (int i = 0; i < indent; ++i)
            b.append(' ');
        }
2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068
      b.append(">\n");

      // Dump element content for leaf elements.
      if (isLeaf())
        {
          for (int i = 0; i < indent + 2; ++i)
            b.append(' ');
          int start = getStartOffset();
          int end = getEndOffset();
          b.append('[');
          b.append(start);
          b.append(',');
          b.append(end);
          b.append("][");
          try
            {
              b.append(getDocument().getText(start, end - start));
            }
          catch (BadLocationException ex)
            {
              AssertionError err = new AssertionError("BadLocationException "
                                                      + "must not be thrown "
                                                      + "here.");
              err.initCause(ex);
2069
              throw err;
2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082
            }
          b.append("]\n");
        }
      stream.print(b.toString());

      // Dump child elements if any.
      int count = getElementCount();
      for (int i = 0; i < count; ++i)
        {
          Element el = getElement(i);
          if (el instanceof AbstractElement)
            ((AbstractElement) el).dump(stream, indent + 2);
        }
Tom Tromey committed
2083 2084 2085
    }
  }

Tom Tromey committed
2086 2087 2088 2089
  /**
   * An implementation of {@link Element} to represent composite
   * <code>Element</code>s that contain other <code>Element</code>s.
   */
Tom Tromey committed
2090 2091
  public class BranchElement extends AbstractElement
  {
2092 2093
    /** The serialization UID (compatible with JDK1.5). */
    private static final long serialVersionUID = -6037216547466333183L;
Tom Tromey committed
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 2107 2108 2109 2110
     * The last found index in getElementIndex(). Used for faster searching.
     */
    private int lastIndex;

    /**
Tom Tromey committed
2111 2112 2113 2114 2115 2116 2117
     * Creates a new <code>BranchElement</code> with the specified
     * parent and attributes.
     *
     * @param parent the parent element of this <code>BranchElement</code>
     * @param attributes the attributes to set on this
     *        <code>BranchElement</code>
     */
Tom Tromey committed
2118 2119 2120
    public BranchElement(Element parent, AttributeSet attributes)
    {
      super(parent, attributes);
2121 2122
      children = new Element[1];
      numChildren = 0;
2123
      lastIndex = -1;
Tom Tromey committed
2124 2125
    }

Tom Tromey committed
2126 2127 2128 2129 2130
    /**
     * Returns the children of this <code>BranchElement</code>.
     *
     * @return the children of this <code>BranchElement</code>
     */
Tom Tromey committed
2131 2132
    public Enumeration children()
    {
2133
      if (numChildren == 0)
Tom Tromey committed
2134 2135 2136 2137
        return null;

      Vector tmp = new Vector();

2138 2139
      for (int index = 0; index < numChildren; ++index)
        tmp.add(children[index]);
2140

Tom Tromey committed
2141 2142 2143
      return tmp.elements();
    }

Tom Tromey committed
2144 2145 2146 2147 2148 2149 2150
    /**
     * Returns <code>true</code> since <code>BranchElements</code> allow
     * child elements.
     *
     * @return <code>true</code> since <code>BranchElements</code> allow
     *         child elements
     */
Tom Tromey committed
2151 2152 2153 2154 2155
    public boolean getAllowsChildren()
    {
      return true;
    }

Tom Tromey committed
2156 2157 2158 2159 2160 2161 2162
    /**
     * Returns the child element at the specified <code>index</code>.
     *
     * @param index the index of the requested child element
     *
     * @return the requested element
     */
Tom Tromey committed
2163 2164
    public Element getElement(int index)
    {
2165 2166
      if (index < 0 || index >= numChildren)
        return null;
Tom Tromey committed
2167 2168 2169 2170

      return children[index];
    }

Tom Tromey committed
2171 2172 2173 2174 2175
    /**
     * Returns the number of child elements of this element.
     *
     * @return the number of child elements of this element
     */
Tom Tromey committed
2176 2177
    public int getElementCount()
    {
2178
      return numChildren;
Tom Tromey committed
2179 2180
    }

Tom Tromey committed
2181 2182 2183 2184 2185 2186 2187 2188 2189
    /**
     * Returns the index of the child element that spans the specified
     * offset in the document model.
     *
     * @param offset the offset for which the responsible element is searched
     *
     * @return the index of the child element that spans the specified
     *         offset in the document model
     */
Tom Tromey committed
2190 2191
    public int getElementIndex(int offset)
    {
2192 2193 2194 2195
      // Implemented using an improved linear search.
      // This makes use of the fact that searches are not random but often
      // close to the previous search. So we try to start the binary
      // search at the last found index.
2196

2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208
      int i0 = 0; // The lower bounds.
      int i1 = numChildren - 1; // The upper bounds.
      int index = -1; // The found index.

      int p0 = getStartOffset();
      int p1; // Start and end offset local variables.

      if (numChildren == 0)
        index = 0;
      else if (offset >= getEndOffset())
        index = numChildren - 1;
      else
Tom Tromey committed
2209
        {
2210 2211
          // Try lastIndex.
          if (lastIndex >= i0 && lastIndex <= i1)
2212
            {
2213 2214 2215 2216 2217
              Element last = getElement(lastIndex);
              p0 = last.getStartOffset();
              p1 = last.getEndOffset();
              if (offset >= p0 && offset < p1)
                index = lastIndex;
2218
              else
2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244
                {
                  // Narrow the search bounds using the lastIndex, even
                  // if it hasn't been a hit.
                  if (offset < p0)
                    i1 = lastIndex;
                  else
                    i0 = lastIndex;
                }
            }
          // The actual search.
          int i = 0;
          while (i0 <= i1 && index == -1)
            {
              i = i0 + (i1 - i0) / 2;
              Element el = getElement(i);
              p0 = el.getStartOffset();
              p1 = el.getEndOffset();
              if (offset >= p0 && offset < p1)
                {
                  // Found it!
                  index = i;
                }
              else if (offset < p0)
                i1 = i - 1;
              else
                i0 = i + 1;
2245
            }
Tom Tromey committed
2246

2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258
          if (index == -1)
            {
              // Didn't find it. Return the boundary index.
              if (offset < p0)
                index = i;
              else
                index = i + 1;
            }

          lastIndex = index;
        }
      return index;
Tom Tromey committed
2259 2260
    }

Tom Tromey committed
2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271
    /**
     * Returns the offset inside the document model that is after the last
     * character of this element.
     * This is the end offset of the last child element. If this element
     * has no children, this method throws a <code>NullPointerException</code>.
     *
     * @return the offset inside the document model that is after the last
     *         character of this element
     *
     * @throws NullPointerException if this branch element has no children
     */
Tom Tromey committed
2272 2273
    public int getEndOffset()
    {
2274 2275 2276 2277 2278
      // This might accss one cached element or trigger an NPE for
      // numChildren == 0. This is checked by a Mauve test.
      Element child = numChildren > 0 ? children[numChildren - 1]
                                      : children[0];
      return child.getEndOffset();
Tom Tromey committed
2279 2280
    }

Tom Tromey committed
2281 2282 2283 2284 2285 2286
    /**
     * Returns the name of this element. This is {@link #ParagraphElementName}
     * in this case.
     *
     * @return the name of this element
     */
Tom Tromey committed
2287 2288 2289 2290 2291
    public String getName()
    {
      return ParagraphElementName;
    }

Tom Tromey committed
2292 2293 2294 2295 2296 2297 2298
    /**
     * Returns the start offset of this element inside the document model.
     * This is the start offset of the first child element. If this element
     * has no children, this method throws a <code>NullPointerException</code>.
     *
     * @return the start offset of this element inside the document model
     *
2299 2300
     * @throws NullPointerException if this branch element has no children and
     *         no startOffset value has been cached
Tom Tromey committed
2301
     */
Tom Tromey committed
2302 2303
    public int getStartOffset()
    {
2304 2305 2306 2307 2308 2309 2310
      // Do not explicitly throw an NPE here. If the first element is null
      // then the NPE gets thrown anyway. If it isn't, then it either
      // holds a real value (for numChildren > 0) or a cached value
      // (for numChildren == 0) as we don't fully remove elements in replace()
      // when removing single elements.
      // This is checked by a Mauve test.
      return children[0].getStartOffset();
Tom Tromey committed
2311 2312
    }

Tom Tromey committed
2313 2314 2315 2316 2317 2318 2319
    /**
     * Returns <code>false</code> since <code>BranchElement</code> are no
     * leafes.
     *
     * @return <code>false</code> since <code>BranchElement</code> are no
     *         leafes
     */
Tom Tromey committed
2320 2321 2322 2323 2324
    public boolean isLeaf()
    {
      return false;
    }

Tom Tromey committed
2325 2326 2327 2328 2329 2330 2331 2332 2333
    /**
     * Returns the <code>Element</code> at the specified <code>Document</code>
     * offset.
     *
     * @return the <code>Element</code> at the specified <code>Document</code>
     *         offset
     *
     * @see #getElementIndex(int)
     */
Tom Tromey committed
2334 2335 2336 2337
    public Element positionToElement(int position)
    {
      // XXX: There is surely a better algorithm
      // as beginning from first element each time.
2338
      for (int index = 0; index < numChildren; ++index)
Tom Tromey committed
2339
        {
2340
          Element elem = children[index];
Tom Tromey committed
2341

2342 2343 2344
          if ((elem.getStartOffset() <= position)
              && (position < elem.getEndOffset()))
            return elem;
Tom Tromey committed
2345 2346 2347 2348 2349
        }

      return null;
    }

Tom Tromey committed
2350 2351 2352 2353 2354 2355 2356
    /**
     * Replaces a set of child elements with a new set of child elemens.
     *
     * @param offset the start index of the elements to be removed
     * @param length the number of elements to be removed
     * @param elements the new elements to be inserted
     */
Tom Tromey committed
2357 2358
    public void replace(int offset, int length, Element[] elements)
    {
2359 2360 2361
      int delta = elements.length - length;
      int copyFrom = offset + length; // From where to copy.
      int copyTo = copyFrom + delta;    // Where to copy to.
2362
      int numMove = numChildren - copyFrom; // How many elements are moved.
2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378
      if (numChildren + delta > children.length)
        {
          // Gotta grow the array.
          int newSize = Math.max(2 * children.length, numChildren + delta);
          Element[] target = new Element[newSize];
          System.arraycopy(children, 0, target, 0, offset);
          System.arraycopy(elements, 0, target, offset, elements.length);
          System.arraycopy(children, copyFrom, target, copyTo, numMove);
          children = target;
        }
      else
        {
          System.arraycopy(children, copyFrom, children, copyTo, numMove);
          System.arraycopy(elements, 0, children, offset, elements.length);
        }
      numChildren += delta;
Tom Tromey committed
2379 2380
    }

Tom Tromey committed
2381 2382 2383 2384 2385
    /**
     * Returns a string representation of this element.
     *
     * @return a string representation of this element
     */
Tom Tromey committed
2386 2387 2388
    public String toString()
    {
      return ("BranchElement(" + getName() + ") "
2389
              + getStartOffset() + "," + getEndOffset() + "\n");
Tom Tromey committed
2390 2391 2392
    }
  }

Tom Tromey committed
2393 2394 2395
  /**
   * Stores the changes when a <code>Document</code> is beeing modified.
   */
Tom Tromey committed
2396 2397 2398
  public class DefaultDocumentEvent extends CompoundEdit
    implements DocumentEvent
  {
2399 2400
    /** The serialization UID (compatible with JDK1.5). */
    private static final long serialVersionUID = 5230037221564563284L;
Tom Tromey committed
2401

2402 2403 2404 2405
    /**
     * The threshold that indicates when we switch to using a Hashtable.
     */
    private static final int THRESHOLD = 10;
2406

Tom Tromey committed
2407
    /** The starting offset of the change. */
Tom Tromey committed
2408
    private int offset;
Tom Tromey committed
2409 2410

    /** The length of the change. */
Tom Tromey committed
2411
    private int length;
Tom Tromey committed
2412 2413

    /** The type of change. */
Tom Tromey committed
2414 2415
    private DocumentEvent.EventType type;

Tom Tromey committed
2416
    /**
2417 2418 2419
     * Maps <code>Element</code> to their change records. This is only
     * used when the changes array gets too big. We can use an
     * (unsync'ed) HashMap here, since changes to this are (should) always
2420
     * be performed inside a write lock.
Tom Tromey committed
2421
     */
2422
    private HashMap changes;
Tom Tromey committed
2423 2424

    /**
2425 2426 2427
     * Indicates if this event has been modified or not. This is used to
     * determine if this event is thrown.
     */
2428
    private boolean modified;
2429 2430

    /**
Tom Tromey committed
2431 2432 2433 2434 2435 2436
     * Creates a new <code>DefaultDocumentEvent</code>.
     *
     * @param offset the starting offset of the change
     * @param length the length of the change
     * @param type the type of change
     */
Tom Tromey committed
2437
    public DefaultDocumentEvent(int offset, int length,
2438
                                DocumentEvent.EventType type)
Tom Tromey committed
2439 2440 2441 2442
    {
      this.offset = offset;
      this.length = length;
      this.type = type;
2443
      modified = false;
Tom Tromey committed
2444 2445
    }

Tom Tromey committed
2446 2447 2448 2449 2450 2451 2452 2453 2454
    /**
     * Adds an UndoableEdit to this <code>DocumentEvent</code>. If this
     * edit is an instance of {@link ElementEdit}, then this record can
     * later be fetched by calling {@link #getChange}.
     *
     * @param edit the undoable edit to add
     */
    public boolean addEdit(UndoableEdit edit)
    {
2455 2456 2457 2458 2459 2460 2461 2462 2463
      // Start using Hashtable when we pass a certain threshold. This
      // gives a good memory/performance compromise.
      if (changes == null && edits.size() > THRESHOLD)
        {
          changes = new HashMap();
          int count = edits.size();
          for (int i = 0; i < count; i++)
            {
              Object o = edits.elementAt(i);
2464
              if (o instanceof ElementChange)
2465
                {
2466
                  ElementChange ec = (ElementChange) o;
2467 2468 2469 2470 2471
                  changes.put(ec.getElement(), ec);
                }
            }
        }

2472
      if (changes != null && edit instanceof ElementChange)
Tom Tromey committed
2473
        {
2474
          ElementChange elEdit = (ElementChange) edit;
Tom Tromey committed
2475 2476 2477 2478 2479 2480 2481 2482 2483 2484
          changes.put(elEdit.getElement(), elEdit);
        }
      return super.addEdit(edit);
    }

    /**
     * Returns the document that has been modified.
     *
     * @return the document that has been modified
     */
Tom Tromey committed
2485 2486 2487 2488 2489
    public Document getDocument()
    {
      return AbstractDocument.this;
    }

Tom Tromey committed
2490 2491 2492 2493 2494
    /**
     * Returns the length of the modification.
     *
     * @return the length of the modification
     */
Tom Tromey committed
2495 2496 2497 2498 2499
    public int getLength()
    {
      return length;
    }

Tom Tromey committed
2500 2501 2502 2503 2504
    /**
     * Returns the start offset of the modification.
     *
     * @return the start offset of the modification
     */
Tom Tromey committed
2505 2506 2507 2508 2509
    public int getOffset()
    {
      return offset;
    }

Tom Tromey committed
2510 2511 2512 2513 2514
    /**
     * Returns the type of the modification.
     *
     * @return the type of the modification
     */
Tom Tromey committed
2515 2516 2517 2518 2519
    public DocumentEvent.EventType getType()
    {
      return type;
    }

Tom Tromey committed
2520 2521 2522 2523 2524 2525 2526 2527
    /**
     * Returns the changes for an element.
     *
     * @param elem the element for which the changes are requested
     *
     * @return the changes for <code>elem</code> or <code>null</code> if
     *         <code>elem</code> has not been changed
     */
2528
    public ElementChange getChange(Element elem)
Tom Tromey committed
2529
    {
2530
      ElementChange change = null;
2531 2532
      if (changes != null)
        {
2533
          change = (ElementChange) changes.get(elem);
2534 2535 2536 2537 2538 2539 2540
        }
      else
        {
          int count = edits.size();
          for (int i = 0; i < count && change == null; i++)
            {
              Object o = edits.get(i);
2541
              if (o instanceof ElementChange)
2542
                {
2543
                  ElementChange ec = (ElementChange) o;
2544 2545 2546 2547 2548 2549
                  if (elem.equals(ec.getElement()))
                    change = ec;
                }
            }
        }
      return change;
Tom Tromey committed
2550
    }
2551

2552 2553 2554 2555 2556 2557 2558 2559
    /**
     * Returns a String description of the change event.  This returns the
     * toString method of the Vector of edits.
     */
    public String toString()
    {
      return edits.toString();
    }
Tom Tromey committed
2560
  }
2561

Tom Tromey committed
2562 2563 2564 2565
  /**
   * An implementation of {@link DocumentEvent.ElementChange} to be added
   * to {@link DefaultDocumentEvent}s.
   */
Tom Tromey committed
2566 2567 2568
  public static class ElementEdit extends AbstractUndoableEdit
    implements DocumentEvent.ElementChange
  {
Tom Tromey committed
2569
    /** The serial version UID of ElementEdit. */
Tom Tromey committed
2570 2571
    private static final long serialVersionUID = -1216620962142928304L;

Tom Tromey committed
2572 2573 2574
    /**
     * The changed element.
     */
Tom Tromey committed
2575
    private Element elem;
Tom Tromey committed
2576 2577 2578 2579

    /**
     * The index of the change.
     */
Tom Tromey committed
2580
    private int index;
Tom Tromey committed
2581 2582 2583 2584

    /**
     * The removed elements.
     */
Tom Tromey committed
2585
    private Element[] removed;
Tom Tromey committed
2586 2587 2588 2589

    /**
     * The added elements.
     */
Tom Tromey committed
2590
    private Element[] added;
2591

Tom Tromey committed
2592 2593 2594 2595 2596 2597 2598 2599
    /**
     * Creates a new <code>ElementEdit</code>.
     *
     * @param elem the changed element
     * @param index the index of the change
     * @param removed the removed elements
     * @param added the added elements
     */
Tom Tromey committed
2600
    public ElementEdit(Element elem, int index,
2601
                       Element[] removed, Element[] added)
Tom Tromey committed
2602 2603 2604 2605 2606 2607 2608
    {
      this.elem = elem;
      this.index = index;
      this.removed = removed;
      this.added = added;
    }

Tom Tromey committed
2609 2610 2611 2612 2613
    /**
     * Returns the added elements.
     *
     * @return the added elements
     */
Tom Tromey committed
2614 2615 2616 2617
    public Element[] getChildrenAdded()
    {
      return added;
    }
Tom Tromey committed
2618 2619 2620 2621 2622 2623

    /**
     * Returns the removed elements.
     *
     * @return the removed elements
     */
Tom Tromey committed
2624 2625 2626 2627 2628
    public Element[] getChildrenRemoved()
    {
      return removed;
    }

Tom Tromey committed
2629 2630 2631 2632 2633
    /**
     * Returns the changed element.
     *
     * @return the changed element
     */
Tom Tromey committed
2634 2635 2636 2637 2638
    public Element getElement()
    {
      return elem;
    }

Tom Tromey committed
2639 2640 2641 2642 2643
    /**
     * Returns the index of the change.
     *
     * @return the index of the change
     */
Tom Tromey committed
2644 2645 2646 2647 2648 2649
    public int getIndex()
    {
      return index;
    }
  }

Tom Tromey committed
2650 2651 2652 2653
  /**
   * An implementation of {@link Element} that represents a leaf in the
   * document structure. This is used to actually store content.
   */
Tom Tromey committed
2654 2655
  public class LeafElement extends AbstractElement
  {
2656 2657
    /** The serialization UID (compatible with JDK1.5). */
    private static final long serialVersionUID = -8906306331347768017L;
Tom Tromey committed
2658

2659 2660 2661 2662 2663 2664 2665 2666 2667
    /**
     * Manages the start offset of this element.
     */
    private Position startPos;

    /**
     * Manages the end offset of this element.
     */
    private Position endPos;
Tom Tromey committed
2668

2669
    /**
Tom Tromey committed
2670 2671 2672 2673 2674 2675 2676
     * Creates a new <code>LeafElement</code>.
     *
     * @param parent the parent of this <code>LeafElement</code>
     * @param attributes the attributes to be set
     * @param start the start index of this element inside the document model
     * @param end the end index of this element inside the document model
     */
Tom Tromey committed
2677 2678 2679 2680
    public LeafElement(Element parent, AttributeSet attributes, int start,
                       int end)
    {
      super(parent, attributes);
2681
      try
2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695
        {
          startPos = createPosition(start);
          endPos = createPosition(end);
        }
      catch (BadLocationException ex)
        {
          AssertionError as;
          as = new AssertionError("BadLocationException thrown "
                                  + "here. start=" + start
                                  + ", end=" + end
                                  + ", length=" + getLength());
          as.initCause(ex);
          throw as;
        }
Tom Tromey committed
2696 2697
    }

Tom Tromey committed
2698 2699 2700 2701 2702 2703 2704
    /**
     * Returns <code>null</code> since <code>LeafElement</code>s cannot have
     * children.
     *
     * @return <code>null</code> since <code>LeafElement</code>s cannot have
     *         children
     */
Tom Tromey committed
2705 2706 2707 2708 2709
    public Enumeration children()
    {
      return null;
    }

Tom Tromey committed
2710 2711 2712 2713 2714 2715 2716
    /**
     * Returns <code>false</code> since <code>LeafElement</code>s cannot have
     * children.
     *
     * @return <code>false</code> since <code>LeafElement</code>s cannot have
     *         children
     */
Tom Tromey committed
2717 2718 2719 2720 2721
    public boolean getAllowsChildren()
    {
      return false;
    }

Tom Tromey committed
2722 2723 2724 2725 2726 2727 2728
    /**
     * Returns <code>null</code> since <code>LeafElement</code>s cannot have
     * children.
     *
     * @return <code>null</code> since <code>LeafElement</code>s cannot have
     *         children
     */
Tom Tromey committed
2729 2730 2731 2732 2733
    public Element getElement(int index)
    {
      return null;
    }

Tom Tromey committed
2734 2735 2736 2737 2738 2739 2740
    /**
     * Returns <code>0</code> since <code>LeafElement</code>s cannot have
     * children.
     *
     * @return <code>0</code> since <code>LeafElement</code>s cannot have
     *         children
     */
Tom Tromey committed
2741 2742 2743 2744 2745
    public int getElementCount()
    {
      return 0;
    }

Tom Tromey committed
2746 2747 2748 2749 2750 2751 2752
    /**
     * Returns <code>-1</code> since <code>LeafElement</code>s cannot have
     * children.
     *
     * @return <code>-1</code> since <code>LeafElement</code>s cannot have
     *         children
     */
Tom Tromey committed
2753 2754 2755 2756 2757
    public int getElementIndex(int offset)
    {
      return -1;
    }

Tom Tromey committed
2758 2759 2760 2761 2762 2763 2764
    /**
     * Returns the end offset of this <code>Element</code> inside the
     * document.
     *
     * @return the end offset of this <code>Element</code> inside the
     *         document
     */
Tom Tromey committed
2765 2766
    public int getEndOffset()
    {
2767
      return endPos.getOffset();
Tom Tromey committed
2768 2769
    }

Tom Tromey committed
2770 2771 2772 2773 2774 2775
    /**
     * Returns the name of this <code>Element</code>. This is
     * {@link #ContentElementName} in this case.
     *
     * @return the name of this <code>Element</code>
     */
Tom Tromey committed
2776 2777
    public String getName()
    {
2778 2779 2780 2781
      String name = super.getName();
      if (name == null)
        name = ContentElementName;
      return name;
Tom Tromey committed
2782 2783
    }

Tom Tromey committed
2784 2785 2786 2787 2788 2789 2790
    /**
     * Returns the start offset of this <code>Element</code> inside the
     * document.
     *
     * @return the start offset of this <code>Element</code> inside the
     *         document
     */
Tom Tromey committed
2791 2792
    public int getStartOffset()
    {
2793
      return startPos.getOffset();
Tom Tromey committed
2794 2795
    }

Tom Tromey committed
2796 2797 2798 2799 2800
    /**
     * Returns <code>true</code>.
     *
     * @return <code>true</code>
     */
Tom Tromey committed
2801 2802 2803 2804 2805
    public boolean isLeaf()
    {
      return true;
    }

Tom Tromey committed
2806 2807 2808 2809 2810
    /**
     * Returns a string representation of this <code>Element</code>.
     *
     * @return a string representation of this <code>Element</code>
     */
Tom Tromey committed
2811 2812 2813
    public String toString()
    {
      return ("LeafElement(" + getName() + ") "
2814
              + getStartOffset() + "," + getEndOffset() + "\n");
Tom Tromey committed
2815 2816
    }
  }
2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873

  /**
   * The root element for bidirectional text.
   */
  private class BidiRootElement
    extends BranchElement
  {
    /**
     * Creates a new bidi root element.
     */
    BidiRootElement()
    {
      super(null, null);
    }

    /**
     * Returns the name of the element.
     *
     * @return the name of the element
     */
    public String getName()
    {
      return BidiRootName;
    }
  }

  /**
   * A leaf element for the bidi structure.
   */
  private class BidiElement
    extends LeafElement
  {
    /**
     * Creates a new BidiElement.
     *
     * @param parent the parent element
     * @param start the start offset
     * @param end the end offset
     * @param level the bidi level
     */
    BidiElement(Element parent, int start, int end, int level)
    {
      super(parent, new SimpleAttributeSet(), start, end);
      addAttribute(StyleConstants.BidiLevel, new Integer(level));
    }

    /**
     * Returns the name of the element.
     *
     * @return the name of the element
     */
    public String getName()
    {
      return BidiElementName;
    }
  }

2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902
  /** A class whose methods delegate to the insert, remove and replace methods
   * of this document which do not check for an installed DocumentFilter.
   */
  class Bypass extends DocumentFilter.FilterBypass
  {

    public Document getDocument()
    {
      return AbstractDocument.this;
    }

    public void insertString(int offset, String string, AttributeSet attr)
    throws BadLocationException
    {
      AbstractDocument.this.insertStringImpl(offset, string, attr);
    }

    public void remove(int offset, int length)
    throws BadLocationException
    {
      AbstractDocument.this.removeImpl(offset, length);
    }

    public void replace(int offset, int length, String string,
                        AttributeSet attrs)
    throws BadLocationException
    {
      AbstractDocument.this.replaceImpl(offset, length, string, attrs);
    }
2903

2904
  }
2905

Tom Tromey committed
2906
}