Source for javax.swing.text.AbstractDocument

   1: /* AbstractDocument.java --
   2:    Copyright (C) 2002, 2004, 2005  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.io.PrintStream;
  42: import java.io.Serializable;
  43: import java.util.Dictionary;
  44: import java.util.Enumeration;
  45: import java.util.EventListener;
  46: import java.util.Hashtable;
  47: import java.util.Vector;
  48: 
  49: import javax.swing.event.DocumentEvent;
  50: import javax.swing.event.DocumentListener;
  51: import javax.swing.event.EventListenerList;
  52: import javax.swing.event.UndoableEditEvent;
  53: import javax.swing.event.UndoableEditListener;
  54: import javax.swing.tree.TreeNode;
  55: import javax.swing.undo.AbstractUndoableEdit;
  56: import javax.swing.undo.CompoundEdit;
  57: import javax.swing.undo.UndoableEdit;
  58: 
  59: /**
  60:  * An abstract base implementation for the {@link Document} interface.
  61:  * This class provides some common functionality for all <code>Element</code>s,
  62:  * most notably it implements a locking mechanism to make document modification
  63:  * thread-safe.
  64:  *
  65:  * @author original author unknown
  66:  * @author Roman Kennke (roman@kennke.org)
  67:  */
  68: public abstract class AbstractDocument implements Document, Serializable
  69: {
  70:   /** The serialization UID (compatible with JDK1.5). */
  71:   private static final long serialVersionUID = 6842927725919637215L;
  72: 
  73:   /**
  74:    * Standard error message to indicate a bad location.
  75:    */
  76:   protected static final String BAD_LOCATION = "document location failure";
  77: 
  78:   /**
  79:    * Standard name for unidirectional <code>Element</code>s.
  80:    */
  81:   public static final String BidiElementName = "bidi level";
  82: 
  83:   /**
  84:    * Standard name for content <code>Element</code>s. These are usually
  85:    * {@link LeafElement}s.
  86:    */
  87:   public static final String ContentElementName = "content";
  88: 
  89:   /**
  90:    * Standard name for paragraph <code>Element</code>s. These are usually
  91:    * {@link BranchElement}s.
  92:    */
  93:   public static final String ParagraphElementName = "paragraph";
  94: 
  95:   /**
  96:    * Standard name for section <code>Element</code>s. These are usually
  97:    * {@link DefaultStyledDocument.SectionElement}s.
  98:    */
  99:   public static final String SectionElementName = "section";
 100: 
 101:   /**
 102:    * Attribute key for storing the element name.
 103:    */
 104:   public static final String ElementNameAttribute = "$ename";
 105: 
 106:   /**
 107:    * The actual content model of this <code>Document</code>.
 108:    */
 109:   Content content;
 110: 
 111:   /**
 112:    * The AttributeContext for this <code>Document</code>.
 113:    */
 114:   AttributeContext context;
 115: 
 116:   /**
 117:    * The currently installed <code>DocumentFilter</code>.
 118:    */
 119:   DocumentFilter documentFilter;
 120: 
 121:   /**
 122:    * The documents properties.
 123:    */
 124:   Dictionary properties;
 125: 
 126:   /**
 127:    * Manages event listeners for this <code>Document</code>.
 128:    */
 129:   protected EventListenerList listenerList = new EventListenerList();
 130:   
 131:   /**
 132:    * Stores the current writer thread.  Used for locking.
 133:    */ 
 134:   private Thread currentWriter = null;
 135:   
 136:   /**
 137:    * The number of readers.  Used for locking.
 138:    */
 139:   private int numReaders = 0;
 140:   
 141:   /**
 142:    * Tells if there are one or more writers waiting.
 143:    */
 144:   private int numWritersWaiting = 0;  
 145: 
 146:   /**
 147:    * A condition variable that readers and writers wait on.
 148:    */
 149:   Object documentCV = new Object();
 150: 
 151:   
 152:   /**
 153:    * Creates a new <code>AbstractDocument</code> with the specified
 154:    * {@link Content} model.
 155:    *
 156:    * @param doc the <code>Content</code> model to be used in this
 157:    *        <code>Document<code>
 158:    *
 159:    * @see GapContent
 160:    * @see StringContent
 161:    */
 162:   protected AbstractDocument(Content doc)
 163:   {
 164:     this(doc, StyleContext.getDefaultStyleContext());
 165:   }
 166: 
 167:   /**
 168:    * Creates a new <code>AbstractDocument</code> with the specified
 169:    * {@link Content} model and {@link AttributeContext}.
 170:    *
 171:    * @param doc the <code>Content</code> model to be used in this
 172:    *        <code>Document<code>
 173:    * @param ctx the <code>AttributeContext</code> to use
 174:    *
 175:    * @see GapContent
 176:    * @see StringContent
 177:    */
 178:   protected AbstractDocument(Content doc, AttributeContext ctx)
 179:   {
 180:     content = doc;
 181:     context = ctx;
 182:   }
 183: 
 184:   /**
 185:    * Returns the paragraph {@link Element} that holds the specified position.
 186:    *
 187:    * @param pos the position for which to get the paragraph element
 188:    *
 189:    * @return the paragraph {@link Element} that holds the specified position
 190:    */
 191:   public abstract Element getParagraphElement(int pos);
 192: 
 193:   /**
 194:    * Returns the default root {@link Element} of this <code>Document</code>.
 195:    * Usual <code>Document</code>s only have one root element and return this.
 196:    * However, there may be <code>Document</code> implementations that
 197:    * support multiple root elements, they have to return a default root element
 198:    * here.
 199:    *
 200:    * @return the default root {@link Element} of this <code>Document</code>
 201:    */
 202:   public abstract Element getDefaultRootElement();
 203: 
 204:   /**
 205:    * Creates and returns a branch element with the specified
 206:    * <code>parent</code> and <code>attributes</code>. Note that the new
 207:    * <code>Element</code> is linked to the parent <code>Element</code>
 208:    * through {@link Element#getParentElement}, but it is not yet added
 209:    * to the parent <code>Element</code> as child.
 210:    *
 211:    * @param parent the parent <code>Element</code> for the new branch element
 212:    * @param attributes the text attributes to be installed in the new element
 213:    *
 214:    * @return the new branch <code>Element</code>
 215:    *
 216:    * @see BranchElement
 217:    */
 218:   protected Element createBranchElement(Element parent,
 219:                     AttributeSet attributes)
 220:   {
 221:     return new BranchElement(parent, attributes);
 222:   }
 223: 
 224:   /**
 225:    * Creates and returns a leaf element with the specified
 226:    * <code>parent</code> and <code>attributes</code>. Note that the new
 227:    * <code>Element</code> is linked to the parent <code>Element</code>
 228:    * through {@link Element#getParentElement}, but it is not yet added
 229:    * to the parent <code>Element</code> as child.
 230:    *
 231:    * @param parent the parent <code>Element</code> for the new branch element
 232:    * @param attributes the text attributes to be installed in the new element
 233:    *
 234:    * @return the new branch <code>Element</code>
 235:    *
 236:    * @see LeafElement
 237:    */
 238:   protected Element createLeafElement(Element parent, AttributeSet attributes,
 239:                       int start, int end)
 240:   {
 241:     return new LeafElement(parent, attributes, start, end);
 242:   }
 243: 
 244:   /**
 245:    * Creates a {@link Position} that keeps track of the location at the
 246:    * specified <code>offset</code>.
 247:    *
 248:    * @param offset the location in the document to keep track by the new
 249:    *        <code>Position</code>
 250:    *
 251:    * @return the newly created <code>Position</code>
 252:    *
 253:    * @throws BadLocationException if <code>offset</code> is not a valid
 254:    *         location in the documents content model
 255:    */
 256:   public Position createPosition(final int offset) throws BadLocationException
 257:   {
 258:     return content.createPosition(offset);
 259:   }
 260: 
 261:   /**
 262:    * Notifies all registered listeners when the document model changes.
 263:    *
 264:    * @param event the <code>DocumentEvent</code> to be fired
 265:    */
 266:   protected void fireChangedUpdate(DocumentEvent event)
 267:   {
 268:     DocumentListener[] listeners = getDocumentListeners();
 269: 
 270:     for (int index = 0; index < listeners.length; ++index)
 271:       listeners[index].changedUpdate(event);
 272:   }
 273: 
 274:   /**
 275:    * Notifies all registered listeners when content is inserted in the document
 276:    * model.
 277:    *
 278:    * @param event the <code>DocumentEvent</code> to be fired
 279:    */
 280:   protected void fireInsertUpdate(DocumentEvent event)
 281:   {
 282:     DocumentListener[] listeners = getDocumentListeners();
 283: 
 284:     for (int index = 0; index < listeners.length; ++index)
 285:       listeners[index].insertUpdate(event);
 286:   }
 287: 
 288:   /**
 289:    * Notifies all registered listeners when content is removed from the
 290:    * document model.
 291:    *
 292:    * @param event the <code>DocumentEvent</code> to be fired
 293:    */
 294:   protected void fireRemoveUpdate(DocumentEvent event)
 295:   {
 296:     DocumentListener[] listeners = getDocumentListeners();
 297: 
 298:     for (int index = 0; index < listeners.length; ++index)
 299:       listeners[index].removeUpdate(event);
 300:   }
 301: 
 302:   /**
 303:    * Notifies all registered listeners when an <code>UndoableEdit</code> has
 304:    * been performed on this <code>Document</code>.
 305:    *
 306:    * @param event the <code>UndoableEditEvent</code> to be fired
 307:    */
 308:   protected void fireUndoableEditUpdate(UndoableEditEvent event)
 309:   {
 310:     UndoableEditListener[] listeners = getUndoableEditListeners();
 311: 
 312:     for (int index = 0; index < listeners.length; ++index)
 313:       listeners[index].undoableEditHappened(event);
 314:   }
 315: 
 316:   /**
 317:    * Returns the asynchronous loading priority. Returns <code>-1</code> if this
 318:    * document should not be loaded asynchronously.
 319:    *
 320:    * @return the asynchronous loading priority
 321:    */
 322:   public int getAsynchronousLoadPriority()
 323:   {
 324:     return 0;
 325:   }
 326: 
 327:   /**
 328:    * Returns the {@link AttributeContext} used in this <code>Document</code>.
 329:    *
 330:    * @return the {@link AttributeContext} used in this <code>Document</code>
 331:    */
 332:   protected AttributeContext getAttributeContext()
 333:   {
 334:     return context;
 335:   }
 336: 
 337:   /**
 338:    * Returns the root element for bidirectional content.
 339:    *
 340:    * @return the root element for bidirectional content
 341:    */
 342:   public Element getBidiRootElement()
 343:   {
 344:     return null;
 345:   }
 346: 
 347:   /**
 348:    * Returns the {@link Content} model for this <code>Document</code>
 349:    *
 350:    * @return the {@link Content} model for this <code>Document</code>
 351:    *
 352:    * @see GapContent
 353:    * @see StringContent
 354:    */
 355:   protected final Content getContent()
 356:   {
 357:     return content;
 358:   }
 359: 
 360:   /**
 361:    * Returns the thread that currently modifies this <code>Document</code>
 362:    * if there is one, otherwise <code>null</code>. This can be used to
 363:    * distinguish between a method call that is part of an ongoing modification
 364:    * or if it is a separate modification for which a new lock must be aquired.
 365:    *
 366:    * @return the thread that currently modifies this <code>Document</code>
 367:    *         if there is one, otherwise <code>null</code>
 368:    */
 369:   protected Thread getCurrentWriter()
 370:   {
 371:     return currentWriter;
 372:   }
 373: 
 374:   /**
 375:    * Returns the properties of this <code>Document</code>.
 376:    *
 377:    * @return the properties of this <code>Document</code>
 378:    */
 379:   public Dictionary getDocumentProperties()
 380:   {
 381:     // FIXME: make me thread-safe
 382:     if (properties == null)
 383:       properties = new Hashtable();
 384: 
 385:     return properties;
 386:   }
 387: 
 388:   /**
 389:    * Returns a {@link Position} which will always mark the end of the
 390:    * <code>Document</code>.
 391:    *
 392:    * @return a {@link Position} which will always mark the end of the
 393:    *         <code>Document</code>
 394:    */
 395:   public Position getEndPosition()
 396:   {
 397:     // FIXME: Properly implement this by calling Content.createPosition().
 398:     return new Position() 
 399:       {        
 400:         public int getOffset() 
 401:         { 
 402:           return getLength(); 
 403:         } 
 404:       };
 405:   }
 406: 
 407:   /**
 408:    * Returns the length of this <code>Document</code>'s content.
 409:    *
 410:    * @return the length of this <code>Document</code>'s content
 411:    */
 412:   public int getLength()
 413:   {
 414:     // We return Content.getLength() -1 here because there is always an
 415:     // implicit \n at the end of the Content which does count in Content
 416:     // but not in Document.
 417:     return content.length() - 1;
 418:   }
 419: 
 420:   /**
 421:    * Returns all registered listeners of a given listener type.
 422:    *
 423:    * @param listenerType the type of the listeners to be queried
 424:    *
 425:    * @return all registered listeners of the specified type
 426:    */
 427:   public EventListener[] getListeners(Class listenerType)
 428:   {
 429:     return listenerList.getListeners(listenerType);
 430:   }
 431: 
 432:   /**
 433:    * Returns a property from this <code>Document</code>'s property list.
 434:    *
 435:    * @param key the key of the property to be fetched
 436:    *
 437:    * @return the property for <code>key</code> or <code>null</code> if there
 438:    *         is no such property stored
 439:    */
 440:   public Object getProperty(Object key)
 441:   {
 442:     // FIXME: make me thread-safe
 443:     Object value = null;
 444:     if (properties != null)
 445:       value = properties.get(key);
 446: 
 447:     return value;
 448:   }
 449: 
 450:   /**
 451:    * Returns all root elements of this <code>Document</code>. By default
 452:    * this just returns the single root element returned by
 453:    * {@link #getDefaultRootElement()}. <code>Document</code> implementations
 454:    * that support multiple roots must override this method and return all roots
 455:    * here.
 456:    *
 457:    * @return all root elements of this <code>Document</code>
 458:    */
 459:   public Element[] getRootElements()
 460:   {
 461:     Element[] elements = new Element[1];
 462:     elements[0] = getDefaultRootElement();
 463:     return elements;
 464:   }
 465: 
 466:   /**
 467:    * Returns a {@link Position} which will always mark the beginning of the
 468:    * <code>Document</code>.
 469:    *
 470:    * @return a {@link Position} which will always mark the beginning of the
 471:    *         <code>Document</code>
 472:    */
 473:   public Position getStartPosition()
 474:   {
 475:     // FIXME: Properly implement this using Content.createPosition().
 476:     return new Position() 
 477:       {        
 478:         public int getOffset() 
 479:         { 
 480:           return 0; 
 481:         } 
 482:       };
 483:   }
 484: 
 485:   /**
 486:    * Returns a piece of this <code>Document</code>'s content.
 487:    *
 488:    * @param offset the start offset of the content
 489:    * @param length the length of the content
 490:    *
 491:    * @return the piece of content specified by <code>offset</code> and
 492:    *         <code>length</code>
 493:    *
 494:    * @throws BadLocationException if <code>offset</code> or <code>offset +
 495:    *         length</code> are invalid locations with this
 496:    *         <code>Document</code>
 497:    */
 498:   public String getText(int offset, int length) throws BadLocationException
 499:   {
 500:     return content.getString(offset, length);
 501:   }
 502: 
 503:   /**
 504:    * Fetches a piece of this <code>Document</code>'s content and stores
 505:    * it in the given {@link Segment}.
 506:    *
 507:    * @param offset the start offset of the content
 508:    * @param length the length of the content
 509:    * @param segment the <code>Segment</code> to store the content in
 510:    *
 511:    * @throws BadLocationException if <code>offset</code> or <code>offset +
 512:    *         length</code> are invalid locations with this
 513:    *         <code>Document</code>
 514:    */
 515:   public void getText(int offset, int length, Segment segment)
 516:     throws BadLocationException
 517:   {
 518:     content.getChars(offset, length, segment);
 519:   }
 520: 
 521:   /**
 522:    * Inserts a String into this <code>Document</code> at the specified
 523:    * position and assigning the specified attributes to it.
 524:    *
 525:    * @param offset the location at which the string should be inserted
 526:    * @param text the content to be inserted
 527:    * @param attributes the text attributes to be assigned to that string
 528:    *
 529:    * @throws BadLocationException if <code>offset</code> is not a valid
 530:    *         location in this <code>Document</code>
 531:    */
 532:   public void insertString(int offset, String text, AttributeSet attributes)
 533:     throws BadLocationException
 534:   {
 535:     // Just return when no text to insert was given.
 536:     if (text == null || text.length() == 0)
 537:       return;
 538:     DefaultDocumentEvent event =
 539:       new DefaultDocumentEvent(offset, text.length(),
 540:                    DocumentEvent.EventType.INSERT);
 541:     
 542:     writeLock();
 543:     UndoableEdit undo = content.insertString(offset, text);
 544:     insertUpdate(event, attributes);
 545:     writeUnlock();
 546: 
 547:     fireInsertUpdate(event);
 548:     if (undo != null)
 549:       fireUndoableEditUpdate(new UndoableEditEvent(this, undo));
 550:   }
 551: 
 552:   /**
 553:    * Called to indicate that text has been inserted into this
 554:    * <code>Document</code>. The default implementation does nothing.
 555:    * This method is executed within a write lock.
 556:    *
 557:    * @param chng the <code>DefaultDocumentEvent</code> describing the change
 558:    * @param attr the attributes of the changed content
 559:    */
 560:   protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr)
 561:   {
 562:     // Do nothing here. Subclasses may want to override this.
 563:   }
 564: 
 565:   /**
 566:    * Called after some content has been removed from this
 567:    * <code>Document</code>. The default implementation does nothing.
 568:    * This method is executed within a write lock.
 569:    *
 570:    * @param chng the <code>DefaultDocumentEvent</code> describing the change
 571:    */
 572:   protected void postRemoveUpdate(DefaultDocumentEvent chng)
 573:   {
 574:     // Do nothing here. Subclasses may want to override this.
 575:   }
 576: 
 577:   /**
 578:    * Stores a property in this <code>Document</code>'s property list.
 579:    *
 580:    * @param key the key of the property to be stored
 581:    * @param value the value of the property to be stored
 582:    */
 583:   public void putProperty(Object key, Object value)
 584:   {
 585:     // FIXME: make me thread-safe
 586:     if (properties == null)
 587:       properties = new Hashtable();
 588: 
 589:     properties.put(key, value);
 590:   }
 591: 
 592:   /**
 593:    * Blocks until a read lock can be obtained.  Must block if there is
 594:    * currently a writer modifying the <code>Document</code>.
 595:    */
 596:   public void readLock()
 597:   {
 598:     if (currentWriter != null && currentWriter.equals(Thread.currentThread()))
 599:       return;
 600:     synchronized (documentCV)
 601:       {
 602:         while (currentWriter != null || numWritersWaiting > 0)
 603:           {
 604:             try
 605:               {
 606:                 documentCV.wait();
 607:               }
 608:             catch (InterruptedException ie)
 609:               {
 610:                 throw new Error("interrupted trying to get a readLock");
 611:               }
 612:           }
 613:           numReaders++;
 614:       }
 615:   }
 616: 
 617:   /**
 618:    * Releases the read lock. If this was the only reader on this
 619:    * <code>Document</code>, writing may begin now.
 620:    */
 621:   public void readUnlock()
 622:   {
 623:     // Note we could have a problem here if readUnlock was called without a
 624:     // prior call to readLock but the specs simply warn users to ensure that
 625:     // balance by using a finally block:
 626:     // readLock()
 627:     // try
 628:     // { 
 629:     //   doSomethingHere 
 630:     // }
 631:     // finally
 632:     // {
 633:     //   readUnlock();
 634:     // }
 635:     
 636:     // All that the JDK seems to check for is that you don't call unlock
 637:     // more times than you've previously called lock, but it doesn't make
 638:     // sure that the threads calling unlock were the same ones that called lock
 639: 
 640:     // FIXME: the reference implementation throws a 
 641:     // javax.swing.text.StateInvariantError here
 642:     if (numReaders == 0)
 643:       throw new IllegalStateException("document lock failure");
 644:     
 645:     synchronized (documentCV)
 646:     {
 647:       // If currentWriter is not null, the application code probably had a 
 648:       // writeLock and then tried to obtain a readLock, in which case 
 649:       // numReaders wasn't incremented
 650:       if (currentWriter == null)
 651:         {
 652:           numReaders --;
 653:           if (numReaders == 0 && numWritersWaiting != 0)
 654:             documentCV.notify();
 655:         }
 656:     }
 657:   }
 658: 
 659:   /**
 660:    * Removes a piece of content from this <code>Document</code>.
 661:    *
 662:    * @param offset the start offset of the fragment to be removed
 663:    * @param length the length of the fragment to be removed
 664:    *
 665:    * @throws BadLocationException if <code>offset</code> or
 666:    *         <code>offset + length</code> or invalid locations within this
 667:    *         document
 668:    */
 669:   public void remove(int offset, int length) throws BadLocationException
 670:   {
 671:     DefaultDocumentEvent event =
 672:       new DefaultDocumentEvent(offset, length,
 673:                    DocumentEvent.EventType.REMOVE);
 674:     
 675:     // Here we set up the parameters for an ElementChange, if one
 676:     // needs to be added to the DocumentEvent later
 677:     Element root = getDefaultRootElement();
 678:     int start = root.getElementIndex(offset);
 679:     int end = root.getElementIndex(offset + length);
 680:     
 681:     Element[] removed = new Element[end - start + 1];
 682:     for (int i = start; i <= end; i++)
 683:       removed[i - start] = root.getElement(i);
 684:     
 685:     removeUpdate(event);
 686: 
 687:     Element[] added = new Element[1];
 688:     added[0] = root.getElement(start);
 689:     boolean shouldFire = content.getString(offset, length).length() != 0;
 690:     
 691:     writeLock();
 692:     UndoableEdit temp = content.remove(offset, length);
 693:     writeUnlock();
 694:     
 695:     postRemoveUpdate(event);
 696:     
 697:     GapContent.UndoRemove changes = null;
 698:     if (content instanceof GapContent)
 699:       changes = (GapContent.UndoRemove) temp;
 700: 
 701:     if (changes != null && !(start == end))
 702:       {
 703:         // We need to add an ElementChange to our DocumentEvent
 704:         ElementEdit edit = new ElementEdit (root, start, removed, added);
 705:         event.addEdit(edit);
 706:       }
 707:     
 708:     if (shouldFire)
 709:       fireRemoveUpdate(event);
 710:   }
 711: 
 712:   /**
 713:    * Replaces a piece of content in this <code>Document</code> with
 714:    * another piece of content.
 715:    *
 716:    * @param offset the start offset of the fragment to be removed
 717:    * @param length the length of the fragment to be removed
 718:    * @param text the text to replace the content with
 719:    * @param attributes the text attributes to assign to the new content
 720:    *
 721:    * @throws BadLocationException if <code>offset</code> or
 722:    *         <code>offset + length</code> or invalid locations within this
 723:    *         document
 724:    *
 725:    * @since 1.4
 726:    */
 727:   public void replace(int offset, int length, String text,
 728:               AttributeSet attributes)
 729:     throws BadLocationException
 730:   {
 731:     remove(offset, length);
 732:     insertString(offset, text, attributes);
 733:   }
 734: 
 735:   /**
 736:    * Adds a <code>DocumentListener</code> object to this document.
 737:    *
 738:    * @param listener the listener to add
 739:    */
 740:   public void addDocumentListener(DocumentListener listener)
 741:   {
 742:     listenerList.add(DocumentListener.class, listener);
 743:   }
 744: 
 745:   /**
 746:    * Removes a <code>DocumentListener</code> object from this document.
 747:    *
 748:    * @param listener the listener to remove
 749:    */
 750:   public void removeDocumentListener(DocumentListener listener)
 751:   {
 752:     listenerList.remove(DocumentListener.class, listener);
 753:   }
 754: 
 755:   /**
 756:    * Returns all registered <code>DocumentListener</code>s.
 757:    *
 758:    * @return all registered <code>DocumentListener</code>s
 759:    */
 760:   public DocumentListener[] getDocumentListeners()
 761:   {
 762:     return (DocumentListener[]) getListeners(DocumentListener.class);
 763:   }
 764: 
 765:   /**
 766:    * Adds an {@link UndoableEditListener} to this <code>Document</code>.
 767:    *
 768:    * @param listener the listener to add
 769:    */
 770:   public void addUndoableEditListener(UndoableEditListener listener)
 771:   {
 772:     listenerList.add(UndoableEditListener.class, listener);
 773:   }
 774: 
 775:   /**
 776:    * Removes an {@link UndoableEditListener} from this <code>Document</code>.
 777:    *
 778:    * @param listener the listener to remove
 779:    */
 780:   public void removeUndoableEditListener(UndoableEditListener listener)
 781:   {
 782:     listenerList.remove(UndoableEditListener.class, listener);
 783:   }
 784: 
 785:   /**
 786:    * Returns all registered {@link UndoableEditListener}s.
 787:    *
 788:    * @return all registered {@link UndoableEditListener}s
 789:    */
 790:   public UndoableEditListener[] getUndoableEditListeners()
 791:   {
 792:     return (UndoableEditListener[]) getListeners(UndoableEditListener.class);
 793:   }
 794: 
 795:   /**
 796:    * Called before some content gets removed from this <code>Document</code>.
 797:    * The default implementation does nothing but may be overridden by
 798:    * subclasses to modify the <code>Document</code> structure in response
 799:    * to a remove request. The method is executed within a write lock.
 800:    *
 801:    * @param chng the <code>DefaultDocumentEvent</code> describing the change
 802:    */
 803:   protected void removeUpdate(DefaultDocumentEvent chng)
 804:   {
 805:     // Do nothing here. Subclasses may wish to override this.
 806:   }
 807: 
 808:   /**
 809:    * Called to render this <code>Document</code> visually. It obtains a read
 810:    * lock, ensuring that no changes will be made to the <code>document</code>
 811:    * during the rendering process. It then calls the {@link Runnable#run()}
 812:    * method on <code>runnable</code>. This method <em>must not</em> attempt
 813:    * to modifiy the <code>Document</code>, since a deadlock will occur if it
 814:    * tries to obtain a write lock. When the {@link Runnable#run()} method
 815:    * completes (either naturally or by throwing an exception), the read lock
 816:    * is released. Note that there is nothing in this method related to
 817:    * the actual rendering. It could be used to execute arbitrary code within
 818:    * a read lock.
 819:    *
 820:    * @param runnable the {@link Runnable} to execute
 821:    */
 822:   public void render(Runnable runnable)
 823:   {
 824:     readLock();
 825:     try
 826:     {
 827:       runnable.run();
 828:     }
 829:     finally
 830:     {
 831:       readUnlock();
 832:     }
 833:   }
 834: 
 835:   /**
 836:    * Sets the asynchronous loading priority for this <code>Document</code>.
 837:    * A value of <code>-1</code> indicates that this <code>Document</code>
 838:    * should be loaded synchronously.
 839:    *
 840:    * @param p the asynchronous loading priority to set
 841:    */
 842:   public void setAsynchronousLoadPriority(int p)
 843:   {
 844:     // TODO: Implement this properly.
 845:   }
 846: 
 847:   /**
 848:    * Sets the properties of this <code>Document</code>.
 849:    *
 850:    * @param p the document properties to set
 851:    */
 852:   public void setDocumentProperties(Dictionary p)
 853:   {
 854:     // FIXME: make me thread-safe
 855:     properties = p;
 856:   }
 857: 
 858:   /**
 859:    * Blocks until a write lock can be obtained.  Must wait if there are 
 860:    * readers currently reading or another thread is currently writing.
 861:    */
 862:   protected void writeLock()
 863:   {
 864:     if (currentWriter!= null && currentWriter.equals(Thread.currentThread()))
 865:       return;
 866:     synchronized (documentCV)
 867:       {
 868:         numWritersWaiting++;
 869:         while (numReaders > 0)
 870:           {
 871:             try
 872:               {
 873:                 documentCV.wait();
 874:               }
 875:             catch (InterruptedException ie)
 876:               {
 877:                 throw new Error("interruped while trying to obtain write lock");
 878:               }
 879:           }
 880:         numWritersWaiting --;
 881:         currentWriter = Thread.currentThread();
 882:       }
 883:   }
 884: 
 885:   /**
 886:    * Releases the write lock. This allows waiting readers or writers to
 887:    * obtain the lock.
 888:    */
 889:   protected void writeUnlock()
 890:   {
 891:     synchronized (documentCV)
 892:     {
 893:         if (Thread.currentThread().equals(currentWriter))
 894:           {
 895:             currentWriter = null;
 896:             documentCV.notifyAll();
 897:           }
 898:     }
 899:   }
 900: 
 901:   /**
 902:    * Returns the currently installed {@link DocumentFilter} for this
 903:    * <code>Document</code>.
 904:    *
 905:    * @return the currently installed {@link DocumentFilter} for this
 906:    *         <code>Document</code>
 907:    *
 908:    * @since 1.4
 909:    */
 910:   public DocumentFilter getDocumentFilter()
 911:   {
 912:     return documentFilter;
 913:   }
 914: 
 915:   /**
 916:    * Sets the {@link DocumentFilter} for this <code>Document</code>.
 917:    *
 918:    * @param filter the <code>DocumentFilter</code> to set
 919:    *
 920:    * @since 1.4
 921:    */
 922:   public void setDocumentFilter(DocumentFilter filter)
 923:   {
 924:     this.documentFilter = filter;
 925:   }
 926: 
 927:   /**
 928:    * Dumps diagnostic information to the specified <code>PrintStream</code>.
 929:    *
 930:    * @param out the stream to write the diagnostic information to
 931:    */
 932:   public void dump(PrintStream out)
 933:   {
 934:     ((AbstractElement) getDefaultRootElement()).dump(out, 0);
 935:   }
 936: 
 937:   /**
 938:    * Defines a set of methods for managing text attributes for one or more
 939:    * <code>Document</code>s.
 940:    *
 941:    * Replicating {@link AttributeSet}s throughout a <code>Document</code> can
 942:    * be very expensive. Implementations of this interface are intended to
 943:    * provide intelligent management of <code>AttributeSet</code>s, eliminating
 944:    * costly duplication.
 945:    *
 946:    * @see StyleContext
 947:    */
 948:   public interface AttributeContext
 949:   {
 950:     /**
 951:      * Returns an {@link AttributeSet} that contains the attributes
 952:      * of <code>old</code> plus the new attribute specified by
 953:      * <code>name</code> and <code>value</code>.
 954:      *
 955:      * @param old the attribute set to be merged with the new attribute
 956:      * @param name the name of the attribute to be added
 957:      * @param value the value of the attribute to be added
 958:      *
 959:      * @return the old attributes plus the new attribute
 960:      */
 961:     AttributeSet addAttribute(AttributeSet old, Object name, Object value);
 962: 
 963:     /**
 964:      * Returns an {@link AttributeSet} that contains the attributes
 965:      * of <code>old</code> plus the new attributes in <code>attributes</code>.
 966:      *
 967:      * @param old the set of attributes where to add the new attributes
 968:      * @param attributes the attributes to be added
 969:      *
 970:      * @return an {@link AttributeSet} that contains the attributes
 971:      *         of <code>old</code> plus the new attributes in
 972:      *         <code>attributes</code>
 973:      */
 974:     AttributeSet addAttributes(AttributeSet old, AttributeSet attributes);
 975: 
 976:     /**
 977:      * Returns an empty {@link AttributeSet}.
 978:      *
 979:      * @return  an empty {@link AttributeSet}
 980:      */
 981:     AttributeSet getEmptySet();
 982: 
 983:     /**
 984:      * Called to indicate that the attributes in <code>attributes</code> are
 985:      * no longer used.
 986:      *
 987:      * @param attributes the attributes are no longer used
 988:      */
 989:     void reclaim(AttributeSet attributes);
 990: 
 991:     /**
 992:      * Returns a {@link AttributeSet} that has the attribute with the specified
 993:      * <code>name</code> removed from <code>old</code>.
 994:      *
 995:      * @param old the attribute set from which an attribute is removed
 996:      * @param name the name of the attribute to be removed
 997:      *
 998:      * @return the attributes of <code>old</code> minus the attribute
 999:      *         specified by <code>name</code>
1000:      */
1001:     AttributeSet removeAttribute(AttributeSet old, Object name);
1002: 
1003:     /**
1004:      * Removes all attributes in <code>attributes</code> from <code>old</code>
1005:      * and returns the resulting <code>AttributeSet</code>.
1006:      *
1007:      * @param old the set of attributes from which to remove attributes
1008:      * @param attributes the attributes to be removed from <code>old</code>
1009:      *
1010:      * @return the attributes of <code>old</code> minus the attributes in
1011:      *         <code>attributes</code>
1012:      */
1013:     AttributeSet removeAttributes(AttributeSet old, AttributeSet attributes);
1014: 
1015:     /**
1016:      * Removes all attributes specified by <code>names</code> from
1017:      * <code>old</code> and returns the resulting <code>AttributeSet</code>.
1018:      *
1019:      * @param old the set of attributes from which to remove attributes
1020:      * @param names the names of the attributes to be removed from
1021:      *        <code>old</code>
1022:      *
1023:      * @return the attributes of <code>old</code> minus the attributes in
1024:      *         <code>attributes</code>
1025:      */
1026:     AttributeSet removeAttributes(AttributeSet old, Enumeration names);
1027:   }
1028: 
1029:   /**
1030:    * A sequence of data that can be edited. This is were the actual content
1031:    * in <code>AbstractDocument</code>'s is stored.
1032:    */
1033:   public interface Content
1034:   {
1035:     /**
1036:      * Creates a {@link Position} that keeps track of the location at
1037:      * <code>offset</code>.
1038:      *
1039:      * @return a {@link Position} that keeps track of the location at
1040:      *         <code>offset</code>.
1041:      *
1042:      * @throw BadLocationException if <code>offset</code> is not a valid
1043:      *        location in this <code>Content</code> model
1044:      */
1045:     Position createPosition(int offset) throws BadLocationException;
1046: 
1047:     /**
1048:      * Returns the length of the content.
1049:      *
1050:      * @return the length of the content
1051:      */
1052:     int length();
1053: 
1054:     /**
1055:      * Inserts a string into the content model.
1056:      *
1057:      * @param where the offset at which to insert the string
1058:      * @param str the string to be inserted
1059:      *
1060:      * @return an <code>UndoableEdit</code> or <code>null</code> if undo is
1061:      *         not supported by this <code>Content</code> model
1062:      *
1063:      * @throws BadLocationException if <code>where</code> is not a valid
1064:      *         location in this <code>Content</code> model
1065:      */
1066:     UndoableEdit insertString(int where, String str)
1067:       throws BadLocationException;
1068: 
1069:     /**
1070:      * Removes a piece of content from the content model.
1071:      *
1072:      * @param where the offset at which to remove content
1073:      * @param nitems the number of characters to be removed
1074:      *
1075:      * @return an <code>UndoableEdit</code> or <code>null</code> if undo is
1076:      *         not supported by this <code>Content</code> model
1077:      *
1078:      * @throws BadLocationException if <code>where</code> is not a valid
1079:      *         location in this <code>Content</code> model
1080:      */
1081:     UndoableEdit remove(int where, int nitems) throws BadLocationException;
1082: 
1083:     /**
1084:      * Returns a piece of content.
1085:      *
1086:      * @param where the start offset of the requested fragment
1087:      * @param len the length of the requested fragment
1088:      *
1089:      * @return the requested fragment
1090:      * @throws BadLocationException if <code>offset</code> or
1091:      *         <code>offset + len</code>is not a valid
1092:      *         location in this <code>Content</code> model
1093:      */
1094:     String getString(int where, int len) throws BadLocationException;
1095: 
1096:     /**
1097:      * Fetches a piece of content and stores it in <code>txt</code>.
1098:      *
1099:      * @param where the start offset of the requested fragment
1100:      * @param len the length of the requested fragment
1101:      * @param txt the <code>Segment</code> where to fragment is stored into
1102:      *
1103:      * @throws BadLocationException if <code>offset</code> or
1104:      *         <code>offset + len</code>is not a valid
1105:      *         location in this <code>Content</code> model
1106:      */
1107:     void getChars(int where, int len, Segment txt) throws BadLocationException;
1108:   }
1109: 
1110:   /**
1111:    * An abstract base implementation of the {@link Element} interface.
1112:    */
1113:   public abstract class AbstractElement
1114:     implements Element, MutableAttributeSet, TreeNode, Serializable
1115:   {
1116:     /** The serialization UID (compatible with JDK1.5). */
1117:     private static final long serialVersionUID = 1712240033321461704L;
1118: 
1119:     /** The number of characters that this Element spans. */
1120:     int count;
1121: 
1122:     /** The starting offset of this Element. */
1123:     int offset;
1124: 
1125:     /** The attributes of this Element. */
1126:     AttributeSet attributes;
1127: 
1128:     /** The parent element. */
1129:     Element element_parent;
1130: 
1131:     /** The parent in the TreeNode interface. */
1132:     TreeNode tree_parent;
1133: 
1134:     /** The children of this element. */
1135:     Vector tree_children;
1136: 
1137:     /**
1138:      * Creates a new instance of <code>AbstractElement</code> with a
1139:      * specified parent <code>Element</code> and <code>AttributeSet</code>.
1140:      *
1141:      * @param p the parent of this <code>AbstractElement</code>
1142:      * @param s the attributes to be assigned to this
1143:      *        <code>AbstractElement</code>
1144:      */
1145:     public AbstractElement(Element p, AttributeSet s)
1146:     {
1147:       element_parent = p;
1148:       AttributeContext ctx = getAttributeContext();
1149:       attributes = ctx.getEmptySet();
1150:       if (s != null)
1151:         attributes = ctx.addAttributes(attributes, s);
1152:     }
1153: 
1154:     /**
1155:      * Returns the child nodes of this <code>Element</code> as an
1156:      * <code>Enumeration</code> of {@link TreeNode}s.
1157:      *
1158:      * @return the child nodes of this <code>Element</code> as an
1159:      *         <code>Enumeration</code> of {@link TreeNode}s
1160:      */
1161:     public abstract Enumeration children();
1162: 
1163:     /**
1164:      * Returns <code>true</code> if this <code>AbstractElement</code>
1165:      * allows children.
1166:      *
1167:      * @return <code>true</code> if this <code>AbstractElement</code>
1168:      *         allows children
1169:      */
1170:     public abstract boolean getAllowsChildren();
1171: 
1172:     /**
1173:      * Returns the child of this <code>AbstractElement</code> at
1174:      * <code>index</code>.
1175:      *
1176:      * @param index the position in the child list of the child element to
1177:      *        be returned
1178:      *
1179:      * @return the child of this <code>AbstractElement</code> at
1180:      *         <code>index</code>
1181:      */
1182:     public TreeNode getChildAt(int index)
1183:     {
1184:       return (TreeNode) tree_children.get(index);
1185:     }
1186: 
1187:     /**
1188:      * Returns the number of children of this <code>AbstractElement</code>.
1189:      *
1190:      * @return the number of children of this <code>AbstractElement</code>
1191:      */
1192:     public int getChildCount()
1193:     {
1194:       return tree_children.size();
1195:     }
1196: 
1197:     /**
1198:      * Returns the index of a given child <code>TreeNode</code> or
1199:      * <code>-1</code> if <code>node</code> is not a child of this
1200:      * <code>AbstractElement</code>.
1201:      *
1202:      * @param node the node for which the index is requested
1203:      *
1204:      * @return the index of a given child <code>TreeNode</code> or
1205:      *         <code>-1</code> if <code>node</code> is not a child of this
1206:      *         <code>AbstractElement</code>
1207:      */
1208:     public int getIndex(TreeNode node)
1209:     {
1210:       return tree_children.indexOf(node);
1211:     }
1212: 
1213:     /**
1214:      * Returns the parent <code>TreeNode</code> of this
1215:      * <code>AbstractElement</code> or <code>null</code> if this element
1216:      * has no parent.
1217:      *
1218:      * @return the parent <code>TreeNode</code> of this
1219:      *         <code>AbstractElement</code> or <code>null</code> if this
1220:      *         element has no parent
1221:      */
1222:     public TreeNode getParent()
1223:     {
1224:       return tree_parent;
1225:     }
1226: 
1227:     /**
1228:      * Returns <code>true</code> if this <code>AbstractElement</code> is a
1229:      * leaf element, <code>false</code> otherwise.
1230:      *
1231:      * @return <code>true</code> if this <code>AbstractElement</code> is a
1232:      *         leaf element, <code>false</code> otherwise
1233:      */
1234:     public abstract boolean isLeaf();
1235: 
1236:     /**
1237:      * Adds an attribute to this element.
1238:      *
1239:      * @param name the name of the attribute to be added
1240:      * @param value the value of the attribute to be added
1241:      */
1242:     public void addAttribute(Object name, Object value)
1243:     {
1244:       attributes = getAttributeContext().addAttribute(attributes, name, value);
1245:     }
1246: 
1247:     /**
1248:      * Adds a set of attributes to this element.
1249:      *
1250:      * @param attrs the attributes to be added to this element
1251:      */
1252:     public void addAttributes(AttributeSet attrs)
1253:     {
1254:       attributes = getAttributeContext().addAttributes(attributes, attrs);
1255:     }
1256: 
1257:     /**
1258:      * Removes an attribute from this element.
1259:      *
1260:      * @param name the name of the attribute to be removed
1261:      */
1262:     public void removeAttribute(Object name)
1263:     {
1264:       attributes = getAttributeContext().removeAttribute(attributes, name);
1265:     }
1266: 
1267:     /**
1268:      * Removes a set of attributes from this element.
1269:      *
1270:      * @param attrs the attributes to be removed
1271:      */
1272:     public void removeAttributes(AttributeSet attrs)
1273:     {
1274:       attributes = getAttributeContext().removeAttributes(attributes, attrs);
1275:     }
1276: 
1277:     /**
1278:      * Removes a set of attribute from this element.
1279:      *
1280:      * @param names the names of the attributes to be removed
1281:      */
1282:     public void removeAttributes(Enumeration names)
1283:     {
1284:       attributes = getAttributeContext().removeAttributes(attributes, names);
1285:     }
1286: 
1287:     /**
1288:      * Sets the parent attribute set against which the element can resolve
1289:      * attributes that are not defined in itself.
1290:      *
1291:      * @param parent the resolve parent to set
1292:      */
1293:     public void setResolveParent(AttributeSet parent)
1294:     {
1295:       attributes = getAttributeContext().addAttribute(attributes,
1296:                                                       ResolveAttribute,
1297:                                                       parent);
1298:     }
1299: 
1300:     /**
1301:      * Returns <code>true</code> if this element contains the specified
1302:      * attribute.
1303:      *
1304:      * @param name the name of the attribute to check
1305:      * @param value the value of the attribute to check
1306:      *
1307:      * @return <code>true</code> if this element contains the specified
1308:      *         attribute
1309:      */
1310:     public boolean containsAttribute(Object name, Object value)
1311:     {
1312:       return attributes.containsAttribute(name, value);
1313:     }
1314: 
1315:     /**
1316:      * Returns <code>true</code> if this element contains all of the
1317:      * specified attributes.
1318:      *
1319:      * @param attrs the attributes to check
1320:      *
1321:      * @return <code>true</code> if this element contains all of the
1322:      *         specified attributes
1323:      */
1324:     public boolean containsAttributes(AttributeSet attrs)
1325:     {
1326:       return attributes.containsAttributes(attrs);
1327:     }
1328: 
1329:     /**
1330:      * Returns a copy of the attributes of this element.
1331:      *
1332:      * @return a copy of the attributes of this element
1333:      */
1334:     public AttributeSet copyAttributes()
1335:     {
1336:       return attributes.copyAttributes();
1337:     }
1338: 
1339:     /**
1340:      * Returns the attribute value with the specified key. If this attribute
1341:      * is not defined in this element and this element has a resolving
1342:      * parent, the search goes upward to the resolve parent chain.
1343:      *
1344:      * @param key the key of the requested attribute
1345:      *
1346:      * @return the attribute value for <code>key</code> of <code>null</code>
1347:      *         if <code>key</code> is not found locally and cannot be resolved
1348:      *         in this element's resolve parents
1349:      */
1350:     public Object getAttribute(Object key)
1351:     {
1352:       return attributes.getAttribute(key);
1353:     }
1354: 
1355:     /**
1356:      * Returns the number of defined attributes in this element.
1357:      *
1358:      * @return the number of defined attributes in this element
1359:      */
1360:     public int getAttributeCount()
1361:     {
1362:       return attributes.getAttributeCount();
1363:     }
1364: 
1365:     /**
1366:      * Returns the names of the attributes of this element.
1367:      *
1368:      * @return the names of the attributes of this element
1369:      */
1370:     public Enumeration getAttributeNames()
1371:     {
1372:       return attributes.getAttributeNames();
1373:     }
1374: 
1375:     /**
1376:      * Returns the resolve parent of this element.
1377:      * This is taken from the AttributeSet, but if this is null,
1378:      * this method instead returns the Element's parent's 
1379:      * AttributeSet
1380:      *
1381:      * @return the resolve parent of this element
1382:      *
1383:      * @see #setResolveParent(AttributeSet)
1384:      */
1385:     public AttributeSet getResolveParent()
1386:     {
1387:       if (attributes.getResolveParent() != null)
1388:         return attributes.getResolveParent();
1389:       return element_parent.getAttributes();
1390:     }
1391: 
1392:     /**
1393:      * Returns <code>true</code> if an attribute with the specified name
1394:      * is defined in this element, <code>false</code> otherwise.
1395:      *
1396:      * @param attrName the name of the requested attributes
1397:      *
1398:      * @return <code>true</code> if an attribute with the specified name
1399:      *         is defined in this element, <code>false</code> otherwise
1400:      */
1401:     public boolean isDefined(Object attrName)
1402:     {
1403:       return attributes.isDefined(attrName);
1404:     }
1405: 
1406:     /**
1407:      * Returns <code>true</code> if the specified <code>AttributeSet</code>
1408:      * is equal to this element's <code>AttributeSet</code>, <code>false</code>
1409:      * otherwise.
1410:      *
1411:      * @param attrs the attributes to compare this element to
1412:      *
1413:      * @return <code>true</code> if the specified <code>AttributeSet</code>
1414:      *         is equal to this element's <code>AttributeSet</code>,
1415:      *         <code>false</code> otherwise
1416:      */
1417:     public boolean isEqual(AttributeSet attrs) 
1418:     {
1419:       return attributes.isEqual(attrs);
1420:     }
1421: 
1422:     /**
1423:      * Returns the attributes of this element.
1424:      *
1425:      * @return the attributes of this element
1426:      */
1427:     public AttributeSet getAttributes()
1428:     {
1429:       return this;
1430:     }
1431: 
1432:     /**
1433:      * Returns the {@link Document} to which this element belongs.
1434:      *
1435:      * @return the {@link Document} to which this element belongs
1436:      */
1437:     public Document getDocument()
1438:     {
1439:       return AbstractDocument.this;
1440:     }
1441: 
1442:     /**
1443:      * Returns the child element at the specified <code>index</code>.
1444:      *
1445:      * @param index the index of the requested child element
1446:      *
1447:      * @return the requested element
1448:      */
1449:     public abstract Element getElement(int index);
1450: 
1451:     /**
1452:      * Returns the name of this element.
1453:      *
1454:      * @return the name of this element
1455:      */
1456:     public String getName()
1457:     {
1458:       return (String) getAttribute(NameAttribute);
1459:     }
1460: 
1461:     /**
1462:      * Returns the parent element of this element.
1463:      *
1464:      * @return the parent element of this element
1465:      */
1466:     public Element getParentElement()
1467:     {
1468:       return element_parent;
1469:     }
1470: 
1471:     /**
1472:      * Returns the offset inside the document model that is after the last
1473:      * character of this element.
1474:      *
1475:      * @return the offset inside the document model that is after the last
1476:      *         character of this element
1477:      */
1478:     public abstract int getEndOffset();
1479: 
1480:     /**
1481:      * Returns the number of child elements of this element.
1482:      *
1483:      * @return the number of child elements of this element
1484:      */
1485:     public abstract int getElementCount();
1486: 
1487:     /**
1488:      * Returns the index of the child element that spans the specified
1489:      * offset in the document model.
1490:      *
1491:      * @param offset the offset for which the responsible element is searched
1492:      *
1493:      * @return the index of the child element that spans the specified
1494:      *         offset in the document model
1495:      */
1496:     public abstract int getElementIndex(int offset);
1497: 
1498:     /**
1499:      * Returns the start offset if this element inside the document model.
1500:      *
1501:      * @return the start offset if this element inside the document model
1502:      */
1503:     public abstract int getStartOffset();
1504: 
1505:     /**
1506:      * Prints diagnostic output to the specified stream.
1507:      *
1508:      * @param stream the stream to write to
1509:      * @param indent the indentation level
1510:      */
1511:     public void dump(PrintStream stream, int indent)
1512:     {
1513:       StringBuffer b = new StringBuffer();
1514:       for (int i = 0; i < indent; ++i)
1515:         b.append(' ');
1516:       b.append('<');
1517:       b.append(getName());
1518:       // Dump attributes if there are any.
1519:       if (getAttributeCount() > 0)
1520:         {
1521:           b.append('\n');
1522:           Enumeration attNames = getAttributeNames();
1523:           while (attNames.hasMoreElements())
1524:             {
1525:               for (int i = 0; i < indent + 2; ++i)
1526:                 b.append(' ');
1527:               Object attName = attNames.nextElement();
1528:               b.append(attName);
1529:               b.append('=');
1530:               Object attribute = getAttribute(attName);
1531:               b.append(attribute);
1532:               b.append('\n');
1533:             }
1534:         }
1535:       b.append(">\n");
1536: 
1537:       // Dump element content for leaf elements.
1538:       if (isLeaf())
1539:         {
1540:           for (int i = 0; i < indent + 2; ++i)
1541:             b.append(' ');
1542:           int start = getStartOffset();
1543:           int end = getEndOffset();
1544:           b.append('[');
1545:           b.append(start);
1546:           b.append(',');
1547:           b.append(end);
1548:           b.append("][");
1549:           try
1550:             {
1551:               b.append(getDocument().getText(start, end - start));
1552:             }
1553:           catch (BadLocationException ex)
1554:             {
1555:               AssertionError err = new AssertionError("BadLocationException "
1556:                                                       + "must not be thrown "
1557:                                                       + "here.");
1558:               err.initCause(ex);
1559:           throw err;
1560:             }
1561:           b.append("]\n");
1562:         }
1563:       stream.print(b.toString());
1564: 
1565:       // Dump child elements if any.
1566:       int count = getElementCount();
1567:       for (int i = 0; i < count; ++i)
1568:         {
1569:           Element el = getElement(i);
1570:           if (el instanceof AbstractElement)
1571:             ((AbstractElement) el).dump(stream, indent + 2);
1572:         }
1573:     }
1574:   }
1575: 
1576:   /**
1577:    * An implementation of {@link Element} to represent composite
1578:    * <code>Element</code>s that contain other <code>Element</code>s.
1579:    */
1580:   public class BranchElement extends AbstractElement
1581:   {
1582:     /** The serialization UID (compatible with JDK1.5). */
1583:     private static final long serialVersionUID = -6037216547466333183L;
1584: 
1585:     /** The child elements of this BranchElement. */
1586:     private Element[] children = new Element[0];
1587: 
1588:     /**
1589:      * Creates a new <code>BranchElement</code> with the specified
1590:      * parent and attributes.
1591:      *
1592:      * @param parent the parent element of this <code>BranchElement</code>
1593:      * @param attributes the attributes to set on this
1594:      *        <code>BranchElement</code>
1595:      */
1596:     public BranchElement(Element parent, AttributeSet attributes)
1597:     {
1598:       super(parent, attributes);
1599:     }
1600: 
1601:     /**
1602:      * Returns the children of this <code>BranchElement</code>.
1603:      *
1604:      * @return the children of this <code>BranchElement</code>
1605:      */
1606:     public Enumeration children()
1607:     {
1608:       if (children.length == 0)
1609:         return null;
1610: 
1611:       Vector tmp = new Vector();
1612: 
1613:       for (int index = 0; index < children.length; ++index)
1614:     tmp.add(children[index]);
1615:       
1616:       return tmp.elements();
1617:     }
1618: 
1619:     /**
1620:      * Returns <code>true</code> since <code>BranchElements</code> allow
1621:      * child elements.
1622:      *
1623:      * @return <code>true</code> since <code>BranchElements</code> allow
1624:      *         child elements
1625:      */
1626:     public boolean getAllowsChildren()
1627:     {
1628:       return true;
1629:     }
1630: 
1631:     /**
1632:      * Returns the child element at the specified <code>index</code>.
1633:      *
1634:      * @param index the index of the requested child element
1635:      *
1636:      * @return the requested element
1637:      */
1638:     public Element getElement(int index)
1639:     {
1640:       if (index < 0 || index >= children.length)
1641:     return null;
1642: 
1643:       return children[index];
1644:     }
1645: 
1646:     /**
1647:      * Returns the number of child elements of this element.
1648:      *
1649:      * @return the number of child elements of this element
1650:      */
1651:     public int getElementCount()
1652:     {
1653:       return children.length;
1654:     }
1655: 
1656:     /**
1657:      * Returns the index of the child element that spans the specified
1658:      * offset in the document model.
1659:      *
1660:      * @param offset the offset for which the responsible element is searched
1661:      *
1662:      * @return the index of the child element that spans the specified
1663:      *         offset in the document model
1664:      */
1665:     public int getElementIndex(int offset)
1666:     {
1667:       // If offset is less than the start offset of our first child,
1668:       // return 0
1669:       if (offset < getStartOffset())
1670:         return 0;
1671:       
1672:       // XXX: There is surely a better algorithm
1673:       // as beginning from first element each time.
1674:       for (int index = 0; index < children.length - 1; ++index)
1675:         {
1676:           Element elem = children[index];
1677: 
1678:           if ((elem.getStartOffset() <= offset)
1679:                && (offset < elem.getEndOffset()))
1680:             return index;
1681:           // If the next element's start offset is greater than offset
1682:           // then we have to return the closest Element, since no Elements
1683:           // will contain the offset
1684:           if (children[index + 1].getStartOffset() > offset)
1685:             {
1686:               if ((offset - elem.getEndOffset()) > (children[index + 1].getStartOffset() - offset))
1687:                 return index + 1;
1688:               else
1689:                 return index;
1690:             }
1691:         }
1692: 
1693:       // If offset is greater than the index of the last element, return
1694:       // the index of the last element.
1695:       return getElementCount() - 1;
1696:     }
1697: 
1698:     /**
1699:      * Returns the offset inside the document model that is after the last
1700:      * character of this element.
1701:      * This is the end offset of the last child element. If this element
1702:      * has no children, this method throws a <code>NullPointerException</code>.
1703:      *
1704:      * @return the offset inside the document model that is after the last
1705:      *         character of this element
1706:      *
1707:      * @throws NullPointerException if this branch element has no children
1708:      */
1709:     public int getEndOffset()
1710:     {
1711:       if (getElementCount() == 0)
1712:         throw new NullPointerException("This BranchElement has no children.");
1713:       return children[children.length - 1].getEndOffset();
1714:     }
1715: 
1716:     /**
1717:      * Returns the name of this element. This is {@link #ParagraphElementName}
1718:      * in this case.
1719:      *
1720:      * @return the name of this element
1721:      */
1722:     public String getName()
1723:     {
1724:       return ParagraphElementName;
1725:     }
1726: 
1727:     /**
1728:      * Returns the start offset of this element inside the document model.
1729:      * This is the start offset of the first child element. If this element
1730:      * has no children, this method throws a <code>NullPointerException</code>.
1731:      *
1732:      * @return the start offset of this element inside the document model
1733:      *
1734:      * @throws NullPointerException if this branch element has no children
1735:      */
1736:     public int getStartOffset()
1737:     {
1738:       if (getElementCount() == 0)
1739:         throw new NullPointerException("This BranchElement has no children.");
1740:       return children[0].getStartOffset();
1741:     }
1742: 
1743:     /**
1744:      * Returns <code>false</code> since <code>BranchElement</code> are no
1745:      * leafes.
1746:      *
1747:      * @return <code>false</code> since <code>BranchElement</code> are no
1748:      *         leafes
1749:      */
1750:     public boolean isLeaf()
1751:     {
1752:       return false;
1753:     }
1754: 
1755:     /**
1756:      * Returns the <code>Element</code> at the specified <code>Document</code>
1757:      * offset.
1758:      *
1759:      * @return the <code>Element</code> at the specified <code>Document</code>
1760:      *         offset
1761:      *
1762:      * @see #getElementIndex(int)
1763:      */
1764:     public Element positionToElement(int position)
1765:     {
1766:       // XXX: There is surely a better algorithm
1767:       // as beginning from first element each time.
1768:       for (int index = 0; index < children.length; ++index)
1769:         {
1770:       Element elem = children[index];
1771: 
1772:       if ((elem.getStartOffset() <= position)
1773:           && (position < elem.getEndOffset()))
1774:         return elem;
1775:         }
1776: 
1777:       return null;
1778:     }
1779: 
1780:     /**
1781:      * Replaces a set of child elements with a new set of child elemens.
1782:      *
1783:      * @param offset the start index of the elements to be removed
1784:      * @param length the number of elements to be removed
1785:      * @param elements the new elements to be inserted
1786:      */
1787:     public void replace(int offset, int length, Element[] elements)
1788:     {
1789:       Element[] target = new Element[children.length - length
1790:                      + elements.length];
1791:       System.arraycopy(children, 0, target, 0, offset);
1792:       System.arraycopy(elements, 0, target, offset, elements.length);
1793:       System.arraycopy(children, offset + length, target,
1794:                offset + elements.length,
1795:                children.length - offset - length);
1796:       children = target;
1797:     }
1798: 
1799:     /**
1800:      * Returns a string representation of this element.
1801:      *
1802:      * @return a string representation of this element
1803:      */
1804:     public String toString()
1805:     {
1806:       return ("BranchElement(" + getName() + ") "
1807:           + getStartOffset() + "," + getEndOffset() + "\n");
1808:     }
1809:   }
1810: 
1811:   /**
1812:    * Stores the changes when a <code>Document</code> is beeing modified.
1813:    */
1814:   public class DefaultDocumentEvent extends CompoundEdit
1815:     implements DocumentEvent
1816:   {
1817:     /** The serialization UID (compatible with JDK1.5). */
1818:     private static final long serialVersionUID = 5230037221564563284L;
1819: 
1820:     /** The starting offset of the change. */
1821:     private int offset;
1822: 
1823:     /** The length of the change. */
1824:     private int length;
1825: 
1826:     /** The type of change. */
1827:     private DocumentEvent.EventType type;
1828: 
1829:     /**
1830:      * Maps <code>Element</code> to their change records.
1831:      */
1832:     Hashtable changes;
1833: 
1834:     /**
1835:      * Creates a new <code>DefaultDocumentEvent</code>.
1836:      *
1837:      * @param offset the starting offset of the change
1838:      * @param length the length of the change
1839:      * @param type the type of change
1840:      */
1841:     public DefaultDocumentEvent(int offset, int length,
1842:                 DocumentEvent.EventType type)
1843:     {
1844:       this.offset = offset;
1845:       this.length = length;
1846:       this.type = type;
1847:       changes = new Hashtable();
1848:     }
1849: 
1850:     /**
1851:      * Adds an UndoableEdit to this <code>DocumentEvent</code>. If this
1852:      * edit is an instance of {@link ElementEdit}, then this record can
1853:      * later be fetched by calling {@link #getChange}.
1854:      *
1855:      * @param edit the undoable edit to add
1856:      */
1857:     public boolean addEdit(UndoableEdit edit)
1858:     {
1859:       // XXX - Fully qualify ElementChange to work around gcj bug #2499.
1860:       if (edit instanceof DocumentEvent.ElementChange)
1861:         {
1862:           DocumentEvent.ElementChange elEdit =
1863:             (DocumentEvent.ElementChange) edit;
1864:           changes.put(elEdit.getElement(), elEdit);
1865:         }
1866:       return super.addEdit(edit);
1867:     }
1868: 
1869:     /**
1870:      * Returns the document that has been modified.
1871:      *
1872:      * @return the document that has been modified
1873:      */
1874:     public Document getDocument()
1875:     {
1876:       return AbstractDocument.this;
1877:     }
1878: 
1879:     /**
1880:      * Returns the length of the modification.
1881:      *
1882:      * @return the length of the modification
1883:      */
1884:     public int getLength()
1885:     {
1886:       return length;
1887:     }
1888: 
1889:     /**
1890:      * Returns the start offset of the modification.
1891:      *
1892:      * @return the start offset of the modification
1893:      */
1894:     public int getOffset()
1895:     {
1896:       return offset;
1897:     }
1898: 
1899:     /**
1900:      * Returns the type of the modification.
1901:      *
1902:      * @return the type of the modification
1903:      */
1904:     public DocumentEvent.EventType getType()
1905:     {
1906:       return type;
1907:     }
1908: 
1909:     /**
1910:      * Returns the changes for an element.
1911:      *
1912:      * @param elem the element for which the changes are requested
1913:      *
1914:      * @return the changes for <code>elem</code> or <code>null</code> if
1915:      *         <code>elem</code> has not been changed
1916:      */
1917:     public DocumentEvent.ElementChange getChange(Element elem)
1918:     {
1919:       // XXX - Fully qualify ElementChange to work around gcj bug #2499.
1920:       return (DocumentEvent.ElementChange) changes.get(elem);
1921:     }
1922:   }
1923:   
1924:   /**
1925:    * An implementation of {@link DocumentEvent.ElementChange} to be added
1926:    * to {@link DefaultDocumentEvent}s.
1927:    */
1928:   public static class ElementEdit extends AbstractUndoableEdit
1929:     implements DocumentEvent.ElementChange
1930:   {
1931:     /** The serial version UID of ElementEdit. */
1932:     private static final long serialVersionUID = -1216620962142928304L;
1933: 
1934:     /**
1935:      * The changed element.
1936:      */
1937:     private Element elem;
1938: 
1939:     /**
1940:      * The index of the change.
1941:      */
1942:     private int index;
1943: 
1944:     /**
1945:      * The removed elements.
1946:      */
1947:     private Element[] removed;
1948: 
1949:     /**
1950:      * The added elements.
1951:      */
1952:     private Element[] added;
1953:     
1954:     /**
1955:      * Creates a new <code>ElementEdit</code>.
1956:      *
1957:      * @param elem the changed element
1958:      * @param index the index of the change
1959:      * @param removed the removed elements
1960:      * @param added the added elements
1961:      */
1962:     public ElementEdit(Element elem, int index,
1963:                Element[] removed, Element[] added)
1964:     {
1965:       this.elem = elem;
1966:       this.index = index;
1967:       this.removed = removed;
1968:       this.added = added;
1969:     }
1970: 
1971:     /**
1972:      * Returns the added elements.
1973:      *
1974:      * @return the added elements
1975:      */
1976:     public Element[] getChildrenAdded()
1977:     {
1978:       return added;
1979:     }
1980: 
1981:     /**
1982:      * Returns the removed elements.
1983:      *
1984:      * @return the removed elements
1985:      */
1986:     public Element[] getChildrenRemoved()
1987:     {
1988:       return removed;
1989:     }
1990: 
1991:     /**
1992:      * Returns the changed element.
1993:      *
1994:      * @return the changed element
1995:      */
1996:     public Element getElement()
1997:     {
1998:       return elem;
1999:     }
2000: 
2001:     /**
2002:      * Returns the index of the change.
2003:      *
2004:      * @return the index of the change
2005:      */
2006:     public int getIndex()
2007:     {
2008:       return index;
2009:     }
2010:   }
2011: 
2012:   /**
2013:    * An implementation of {@link Element} that represents a leaf in the
2014:    * document structure. This is used to actually store content.
2015:    */
2016:   public class LeafElement extends AbstractElement
2017:   {
2018:     /** The serialization UID (compatible with JDK1.5). */
2019:     private static final long serialVersionUID = -8906306331347768017L;
2020: 
2021:     /** Manages the start offset of this element. */
2022:     Position startPos;
2023: 
2024:     /** Manages the end offset of this element. */
2025:     Position endPos;
2026: 
2027:     /**
2028:      * Creates a new <code>LeafElement</code>.
2029:      *
2030:      * @param parent the parent of this <code>LeafElement</code>
2031:      * @param attributes the attributes to be set
2032:      * @param start the start index of this element inside the document model
2033:      * @param end the end index of this element inside the document model
2034:      */
2035:     public LeafElement(Element parent, AttributeSet attributes, int start,
2036:                        int end)
2037:     {
2038:       super(parent, attributes);
2039:     {
2040:       try
2041:         {
2042:           if (parent != null)
2043:         {
2044:           startPos = parent.getDocument().createPosition(start);
2045:           endPos = parent.getDocument().createPosition(end);
2046:         }
2047:           else
2048:         {
2049:           startPos = createPosition(start);
2050:           endPos = createPosition(end);
2051:         }
2052:         }
2053:       catch (BadLocationException ex)
2054:         {
2055:           AssertionError as;
2056:           as = new AssertionError("BadLocationException thrown "
2057:                       + "here. start=" + start
2058:                       + ", end=" + end
2059:                       + ", length=" + getLength());
2060:           as.initCause(ex);
2061:           throw as;
2062:         }
2063:     }
2064:     }
2065: 
2066:     /**
2067:      * Returns <code>null</code> since <code>LeafElement</code>s cannot have
2068:      * children.
2069:      *
2070:      * @return <code>null</code> since <code>LeafElement</code>s cannot have
2071:      *         children
2072:      */
2073:     public Enumeration children()
2074:     {
2075:       return null;
2076:     }
2077: 
2078:     /**
2079:      * Returns <code>false</code> since <code>LeafElement</code>s cannot have
2080:      * children.
2081:      *
2082:      * @return <code>false</code> since <code>LeafElement</code>s cannot have
2083:      *         children
2084:      */
2085:     public boolean getAllowsChildren()
2086:     {
2087:       return false;
2088:     }
2089: 
2090:     /**
2091:      * Returns <code>null</code> since <code>LeafElement</code>s cannot have
2092:      * children.
2093:      *
2094:      * @return <code>null</code> since <code>LeafElement</code>s cannot have
2095:      *         children
2096:      */
2097:     public Element getElement(int index)
2098:     {
2099:       return null;
2100:     }
2101: 
2102:     /**
2103:      * Returns <code>0</code> since <code>LeafElement</code>s cannot have
2104:      * children.
2105:      *
2106:      * @return <code>0</code> since <code>LeafElement</code>s cannot have
2107:      *         children
2108:      */
2109:     public int getElementCount()
2110:     {
2111:       return 0;
2112:     }
2113: 
2114:     /**
2115:      * Returns <code>-1</code> since <code>LeafElement</code>s cannot have
2116:      * children.
2117:      *
2118:      * @return <code>-1</code> since <code>LeafElement</code>s cannot have
2119:      *         children
2120:      */
2121:     public int getElementIndex(int offset)
2122:     {
2123:       return -1;
2124:     }
2125: 
2126:     /**
2127:      * Returns the end offset of this <code>Element</code> inside the
2128:      * document.
2129:      *
2130:      * @return the end offset of this <code>Element</code> inside the
2131:      *         document
2132:      */
2133:     public int getEndOffset()
2134:     {
2135:       return endPos.getOffset();
2136:     }
2137: 
2138:     /**
2139:      * Returns the name of this <code>Element</code>. This is
2140:      * {@link #ContentElementName} in this case.
2141:      *
2142:      * @return the name of this <code>Element</code>
2143:      */
2144:     public String getName()
2145:     {
2146:       return ContentElementName;
2147:     }
2148: 
2149:     /**
2150:      * Returns the start offset of this <code>Element</code> inside the
2151:      * document.
2152:      *
2153:      * @return the start offset of this <code>Element</code> inside the
2154:      *         document
2155:      */
2156:     public int getStartOffset()
2157:     {
2158:       return startPos.getOffset();
2159:     }
2160: 
2161:     /**
2162:      * Returns <code>true</code>.
2163:      *
2164:      * @return <code>true</code>
2165:      */
2166:     public boolean isLeaf()
2167:     {
2168:       return true;
2169:     }
2170: 
2171:     /**
2172:      * Returns a string representation of this <code>Element</code>.
2173:      *
2174:      * @return a string representation of this <code>Element</code>
2175:      */
2176:     public String toString()
2177:     {
2178:       return ("LeafElement(" + getName() + ") "
2179:           + getStartOffset() + "," + getEndOffset() + "\n");
2180:     }
2181:   }
2182: }