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