Source for javax.swing.text.AbstractDocument

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