Source for javax.swing.text.DefaultStyledDocument

   1: /* DefaultStyledDocument.java --
   2:    Copyright (C) 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.awt.Color;
  42: import java.awt.Font;
  43: import java.io.Serializable;
  44: import java.util.Enumeration;
  45: import java.util.Stack;
  46: import java.util.Vector;
  47: 
  48: import javax.swing.event.ChangeEvent;
  49: import javax.swing.event.ChangeListener;
  50: import javax.swing.event.DocumentEvent;
  51: import javax.swing.event.UndoableEditEvent;
  52: import javax.swing.undo.AbstractUndoableEdit;
  53: import javax.swing.undo.UndoableEdit;
  54: 
  55: /**
  56:  * The default implementation of {@link StyledDocument}.
  57:  *
  58:  * The document is modeled as an {@link Element} tree, which has
  59:  * a {@link SectionElement} as single root, which has one or more
  60:  * {@link AbstractDocument.BranchElement}s as paragraph nodes
  61:  * and each paragraph node having one or more
  62:  * {@link AbstractDocument.LeafElement}s as content nodes.
  63:  *
  64:  * @author Michael Koch (konqueror@gmx.de)
  65:  * @author Roman Kennke (roman@kennke.org)
  66:  */
  67: public class DefaultStyledDocument extends AbstractDocument
  68:   implements StyledDocument
  69: {
  70:   /**
  71:    * An {@link UndoableEdit} that can undo attribute changes to an element.
  72:    *
  73:    * @author Roman Kennke (kennke@aicas.com)
  74:    */
  75:   public static class AttributeUndoableEdit
  76:     extends AbstractUndoableEdit
  77:   {
  78:     /**
  79:      * A copy of the old attributes.
  80:      */
  81:     protected AttributeSet copy;
  82: 
  83:     /**
  84:      * The new attributes.
  85:      */
  86:     protected AttributeSet newAttributes;
  87: 
  88:     /**
  89:      * If the new attributes replaced the old attributes or if they only were
  90:      * added to them.
  91:      */
  92:     protected boolean isReplacing;
  93: 
  94:     /**
  95:      * The element that has changed.
  96:      */
  97:     protected Element element;
  98: 
  99:     /**
 100:      * Creates a new <code>AttributeUndoableEdit</code>.
 101:      *
 102:      * @param el the element that changes attributes
 103:      * @param newAtts the new attributes
 104:      * @param replacing if the new attributes replace the old or only append to
 105:      *        them
 106:      */
 107:     public AttributeUndoableEdit(Element el, AttributeSet newAtts,
 108:                                  boolean replacing)
 109:     {
 110:       element = el;
 111:       newAttributes = newAtts;
 112:       isReplacing = replacing;
 113:       copy = el.getAttributes().copyAttributes();
 114:     }
 115: 
 116:     /**
 117:      * Undos the attribute change. The <code>copy</code> field is set as
 118:      * attributes on <code>element</code>.
 119:      */
 120:     public void undo()
 121:     {
 122:       super.undo();
 123:       AttributeSet atts = element.getAttributes();
 124:       if (atts instanceof MutableAttributeSet)
 125:         {
 126:           MutableAttributeSet mutable = (MutableAttributeSet) atts;
 127:           mutable.removeAttributes(atts);
 128:           mutable.addAttributes(copy);
 129:         }
 130:     }
 131: 
 132:     /**
 133:      * Redos an attribute change. This adds <code>newAttributes</code> to the
 134:      * <code>element</code>'s attribute set, possibly clearing all attributes
 135:      * if <code>isReplacing</code> is true.
 136:      */
 137:     public void redo()
 138:     {
 139:       super.undo();
 140:       AttributeSet atts = element.getAttributes();
 141:       if (atts instanceof MutableAttributeSet)
 142:         {
 143:           MutableAttributeSet mutable = (MutableAttributeSet) atts;
 144:           if (isReplacing)
 145:             mutable.removeAttributes(atts);
 146:           mutable.addAttributes(newAttributes);
 147:         }
 148:     }
 149:   }
 150: 
 151:   /**
 152:    * Carries specification information for new {@link Element}s that should
 153:    * be created in {@link ElementBuffer}. This allows the parsing process
 154:    * to be decoupled from the <code>Element</code> creation process.
 155:    */
 156:   public static class ElementSpec
 157:   {
 158:     /**
 159:      * This indicates a start tag. This is a possible value for
 160:      * {@link #getType}.
 161:      */
 162:     public static final short StartTagType = 1;
 163: 
 164:     /**
 165:      * This indicates an end tag. This is a possible value for
 166:      * {@link #getType}.
 167:      */
 168:     public static final short EndTagType = 2;
 169: 
 170:     /**
 171:      * This indicates a content element. This is a possible value for
 172:      * {@link #getType}.
 173:      */
 174:     public static final short ContentType = 3;
 175: 
 176:     /**
 177:      * This indicates that the data associated with this spec should be joined
 178:      * with what precedes it. This is a possible value for
 179:      * {@link #getDirection}.
 180:      */
 181:     public static final short JoinPreviousDirection = 4;
 182: 
 183:     /**
 184:      * This indicates that the data associated with this spec should be joined
 185:      * with what follows it. This is a possible value for
 186:      * {@link #getDirection}.
 187:      */
 188:     public static final short JoinNextDirection = 5;
 189: 
 190:     /**
 191:      * This indicates that the data associated with this spec should be used
 192:      * to create a new element. This is a possible value for
 193:      * {@link #getDirection}.
 194:      */
 195:     public static final short OriginateDirection = 6;
 196: 
 197:     /**
 198:      * This indicates that the data associated with this spec should be joined
 199:      * to the fractured element. This is a possible value for
 200:      * {@link #getDirection}.
 201:      */
 202:     public static final short JoinFractureDirection = 7;
 203: 
 204:     /**
 205:      * The type of the tag.
 206:      */
 207:     short type;
 208: 
 209:     /**
 210:      * The direction of the tag.
 211:      */
 212:     short direction;
 213: 
 214:     /**
 215:      * The offset of the content.
 216:      */
 217:     int offset;
 218: 
 219:     /**
 220:      * The length of the content.
 221:      */
 222:     int length;
 223: 
 224:     /**
 225:      * The actual content.
 226:      */
 227:     char[] content;
 228: 
 229:     /**
 230:      * The attributes for the tag.
 231:      */
 232:     AttributeSet attributes;
 233: 
 234:     /**
 235:      * Creates a new <code>ElementSpec</code> with no content, length or
 236:      * offset. This is most useful for start and end tags.
 237:      *
 238:      * @param a the attributes for the element to be created
 239:      * @param type the type of the tag
 240:      */
 241:     public ElementSpec(AttributeSet a, short type)
 242:     {
 243:       this(a, type, 0);
 244:     }
 245: 
 246:     /**
 247:      * Creates a new <code>ElementSpec</code> that specifies the length but
 248:      * not the offset of an element. Such <code>ElementSpec</code>s are
 249:      * processed sequentially from a known starting point.
 250:      *
 251:      * @param a the attributes for the element to be created
 252:      * @param type the type of the tag
 253:      * @param len the length of the element
 254:      */
 255:     public ElementSpec(AttributeSet a, short type, int len)
 256:     {
 257:       this(a, type, null, 0, len);
 258:     }
 259:  
 260:     /**
 261:      * Creates a new <code>ElementSpec</code> with document content.
 262:      *
 263:      * @param a the attributes for the element to be created
 264:      * @param type the type of the tag
 265:      * @param txt the actual content
 266:      * @param offs the offset into the <code>txt</code> array
 267:      * @param len the length of the element
 268:      */
 269:     public ElementSpec(AttributeSet a, short type, char[] txt, int offs,
 270:                        int len)
 271:     {
 272:       attributes = a;
 273:       this.type = type;
 274:       offset = offs;
 275:       length = len;
 276:       content = txt;
 277:       direction = OriginateDirection;
 278:     }
 279: 
 280:     /**
 281:      * Sets the type of the element.
 282:      *
 283:      * @param type the type of the element to be set
 284:      */
 285:     public void setType(short type)
 286:     {
 287:       this.type = type;
 288:     }
 289: 
 290:     /**
 291:      * Returns the type of the element.
 292:      *
 293:      * @return the type of the element
 294:      */
 295:     public short getType()
 296:     {
 297:       return type;
 298:     }
 299: 
 300:     /**
 301:      * Sets the direction of the element.
 302:      *
 303:      * @param dir the direction of the element to be set
 304:      */
 305:     public void setDirection(short dir)
 306:     {
 307:       direction = dir;
 308:     }
 309: 
 310:     /**
 311:      * Returns the direction of the element.
 312:      *
 313:      * @return the direction of the element
 314:      */
 315:     public short getDirection()
 316:     {
 317:       return direction;
 318:     }
 319: 
 320:     /**
 321:      * Returns the attributes of the element.
 322:      *
 323:      * @return the attributes of the element
 324:      */
 325:     public AttributeSet getAttributes()
 326:     {
 327:       return attributes;
 328:     }
 329: 
 330:     /**
 331:      * Returns the actual content of the element.
 332:      *
 333:      * @return the actual content of the element
 334:      */
 335:     public char[] getArray()
 336:     {
 337:       return content;
 338:     }
 339: 
 340:     /**
 341:      * Returns the offset of the content.
 342:      *
 343:      * @return the offset of the content
 344:      */
 345:     public int getOffset()
 346:     {
 347:       return offset;
 348:     }
 349: 
 350:     /**
 351:      * Returns the length of the content.
 352:      *
 353:      * @return the length of the content
 354:      */
 355:     public int getLength()
 356:     {
 357:       return length;
 358:     }
 359: 
 360:     /**
 361:      * Returns a String representation of this <code>ElementSpec</code>
 362:      * describing the type, direction and length of this
 363:      * <code>ElementSpec</code>.
 364:      *
 365:      * @return a String representation of this <code>ElementSpec</code>
 366:      */
 367:     public String toString()
 368:     {
 369:       StringBuilder b = new StringBuilder();
 370:       switch (type)
 371:         {
 372:         case StartTagType:
 373:           b.append("StartTag");
 374:           break;
 375:         case EndTagType:
 376:           b.append("EndTag");
 377:           break;
 378:         case ContentType:
 379:           b.append("Content");
 380:           break;
 381:         default:
 382:           b.append("??");
 383:           break;
 384:         }
 385: 
 386:       b.append(':');
 387: 
 388:       switch (direction)
 389:         {
 390:         case JoinPreviousDirection:
 391:           b.append("JoinPrevious");
 392:           break;
 393:         case JoinNextDirection:
 394:           b.append("JoinNext");
 395:           break;
 396:         case OriginateDirection:
 397:           b.append("Originate");
 398:           break;
 399:         case JoinFractureDirection:
 400:           b.append("Fracture");
 401:           break;
 402:         default:
 403:           b.append("??");
 404:           break;
 405:         }
 406: 
 407:       b.append(':');
 408:       b.append(length);
 409: 
 410:       return b.toString();
 411:     }
 412:   }
 413: 
 414:   /**
 415:    * Performs all <em>structural</code> changes to the <code>Element</code>
 416:    * hierarchy.
 417:    */
 418:   public class ElementBuffer implements Serializable
 419:   {
 420:     /** The serialization UID (compatible with JDK1.5). */
 421:     private static final long serialVersionUID = 1688745877691146623L;
 422: 
 423:     /** The root element of the hierarchy. */
 424:     private Element root;
 425: 
 426:     /** Holds the offset for structural changes. */
 427:     private int offset;
 428: 
 429:     /** Holds the length of structural changes. */
 430:     private int length;
 431:     
 432:     /** Holds the end offset for structural changes. **/
 433:     private int endOffset;
 434: 
 435:     /**
 436:      * The number of inserted end tags. This is a counter which always gets
 437:      * incremented when an end tag is inserted. This is evaluated before
 438:      * content insertion to go up the element stack.
 439:      */
 440:     private int numEndTags;
 441: 
 442:     /**
 443:      * The number of inserted start tags. This is a counter which always gets
 444:      * incremented when an end tag is inserted. This is evaluated before
 445:      * content insertion to go up the element stack.
 446:      */
 447:     private int numStartTags;
 448: 
 449:     /**
 450:      * The current position in the element tree. This is used for bulk inserts
 451:      * using ElementSpecs.
 452:      */
 453:     private Stack elementStack;
 454: 
 455:     /**
 456:      * Holds fractured elements during insertion of end and start tags.
 457:      * Inserting an end tag may lead to fracturing of the current paragraph
 458:      * element. The elements that have been cut off may be added to the
 459:      * next paragraph that is created in the next start tag.
 460:      */
 461:     Element[] fracture;
 462: 
 463:     /**
 464:      * The ElementChange that describes the latest changes.
 465:      */
 466:     DefaultDocumentEvent documentEvent;
 467: 
 468:     /**
 469:      * Creates a new <code>ElementBuffer</code> for the specified
 470:      * <code>root</code> element.
 471:      *
 472:      * @param root the root element for this <code>ElementBuffer</code>
 473:      */
 474:     public ElementBuffer(Element root)
 475:     {
 476:       this.root = root;
 477:       elementStack = new Stack();
 478:     }
 479: 
 480:     /**
 481:      * Returns the root element of this <code>ElementBuffer</code>.
 482:      *
 483:      * @return the root element of this <code>ElementBuffer</code>
 484:      */
 485:     public Element getRootElement()
 486:     {
 487:       return root;
 488:     }
 489: 
 490:     /**
 491:      * Updates the element structure of the document in response to removal of
 492:      * content. It removes the affected {@link Element}s from the document
 493:      * structure.
 494:      *
 495:      * This method sets some internal parameters and delegates the work
 496:      * to {@link #removeUpdate}.
 497:      *
 498:      * @param offs the offset from which content is remove
 499:      * @param len the length of the removed content
 500:      * @param ev the document event that records the changes
 501:      */
 502:     public void remove(int offs, int len, DefaultDocumentEvent ev)
 503:     {
 504:       offset = offs;
 505:       length = len;
 506:       documentEvent = ev;
 507:       removeUpdate();
 508:     }
 509: 
 510:     /**
 511:      * Updates the element structure of the document in response to removal of
 512:      * content. It removes the affected {@link Element}s from the document
 513:      * structure.
 514:      */
 515:     protected void removeUpdate()
 516:     {
 517:       int startParagraph = root.getElementIndex(offset);
 518:       int endParagraph = root.getElementIndex(offset + length);
 519:       Element[] empty = new Element[0];
 520:       int removeStart = -1;
 521:       int removeEnd = -1;
 522:       for (int i = startParagraph;  i < endParagraph; i++)
 523:         {
 524:           Element paragraph = root.getElement(i);
 525:           int contentStart = paragraph.getElementIndex(offset);
 526:           int contentEnd = paragraph.getElementIndex(offset + length);
 527:           if (contentStart == paragraph.getStartOffset()
 528:               && contentEnd == paragraph.getEndOffset())
 529:             {
 530:               // In this case we only need to remove the whole paragraph. We
 531:               // do this in one go after this loop and only record the indices
 532:               // here.
 533:               if (removeStart == -1)
 534:                 {
 535:                   removeStart = i;
 536:                   removeEnd = i;
 537:                 }
 538:               else
 539:                 removeEnd = i;
 540:             }
 541:           else
 542:             {
 543:               // In this case we remove a couple of child elements from this
 544:               // paragraph.
 545:               int removeLen = contentEnd - contentStart;
 546:               Element[] removed = new Element[removeLen];
 547:               for (int j = contentStart; j < contentEnd; j++)
 548:                 removed[j] = paragraph.getElement(j);
 549:               ((BranchElement) paragraph).replace(contentStart, removeLen,
 550:                                                   empty);
 551:               documentEvent.addEdit(new ElementEdit(paragraph, contentStart,
 552:                                                     removed, empty));
 553:             }
 554:         }
 555:       // Now we remove paragraphs from the root that have been tagged for
 556:       // removal.
 557:       if (removeStart != -1)
 558:         {
 559:           int removeLen = removeEnd - removeStart;
 560:           Element[] removed = new Element[removeLen];
 561:           for (int i = removeStart; i < removeEnd; i++)
 562:             removed[i] = root.getElement(i);
 563:           ((BranchElement) root).replace(removeStart, removeLen, empty);
 564:           documentEvent.addEdit(new ElementEdit(root, removeStart, removed,
 565:                                                 empty));
 566:         }
 567:     }
 568: 
 569:     /**
 570:      * Modifies the element structure so that the specified interval starts
 571:      * and ends at an element boundary. Content and paragraph elements
 572:      * are split and created as necessary.
 573:      *
 574:      * This also updates the <code>DefaultDocumentEvent</code> to reflect the
 575:      * structural changes.
 576:      *
 577:      * The bulk work is delegated to {@link #changeUpdate()}.
 578:      *
 579:      * @param offset the start index of the interval to be changed
 580:      * @param length the length of the interval to be changed
 581:      * @param ev the <code>DefaultDocumentEvent</code> describing the change
 582:      */
 583:     public void change(int offset, int length, DefaultDocumentEvent ev)
 584:     {
 585:       this.offset = offset;
 586:       this.length = length;
 587:       documentEvent = ev;
 588:       changeUpdate();
 589:     }
 590: 
 591:     /**
 592:      * Performs the actual work for {@link #change}.
 593:      * The elements at the interval boundaries are split up (if necessary)
 594:      * so that the interval boundaries are located at element boundaries.
 595:      */
 596:     protected void changeUpdate()
 597:     {
 598:       // Split up the element at the start offset if necessary.
 599:       Element el = getCharacterElement(offset);
 600:       Element[] res = split(el, offset, 0);
 601:       BranchElement par = (BranchElement) el.getParentElement();
 602:       if (res[1] != null)
 603:         {
 604:           int index = par.getElementIndex(offset);
 605:           Element[] removed;
 606:           Element[] added;
 607:           if (res[0] == null)
 608:             {
 609:               removed = new Element[0];
 610:               added = new Element[]{ res[1] };
 611:               index++;
 612:             }
 613:           else
 614:             {
 615:               removed = new Element[]{ el };
 616:               added = new Element[]{ res[0], res[1] };
 617:             }
 618:           par.replace(index, removed.length, added);
 619:           addEdit(par, index, removed, added);
 620:         }
 621: 
 622:       int endOffset = offset + length;
 623:       el = getCharacterElement(endOffset);
 624:       res = split(el, endOffset, 0);
 625:       par = (BranchElement) el.getParentElement();
 626:       if (res[1] != null)
 627:         {
 628:           int index = par.getElementIndex(offset);
 629:           Element[] removed;
 630:           Element[] added;
 631:           if (res[1] == null)
 632:             {
 633:               removed = new Element[0];
 634:               added = new Element[]{ res[1] };
 635:             }
 636:           else
 637:             {
 638:               removed = new Element[]{ el };
 639:               added = new Element[]{ res[0], res[1] };
 640:             }
 641:           par.replace(index, removed.length, added);
 642:           addEdit(par, index, removed, added);
 643:         }
 644:     }
 645: 
 646:     /**
 647:      * Splits an element if <code>offset</code> is not alread at its boundary.
 648:      *
 649:      * @param el the Element to possibly split
 650:      * @param offset the offset at which to possibly split
 651:      * @param space the amount of space to create between the splitted parts
 652:      *
 653:      * @return An array of elements which represent the split result. This
 654:      *         array has two elements, the two parts of the split. The first
 655:      *         element might be null, which means that the element which should
 656:      *         be splitted can remain in place. The second element might also
 657:      *         be null, which means that the offset is already at an element
 658:      *         boundary and the element doesn't need to be splitted.
 659:      *          
 660:      */
 661:     private Element[] split(Element el, int offset, int space)
 662:     {
 663:       // If we are at an element boundary, then return an empty array.
 664:       if ((offset == el.getStartOffset() || offset == el.getEndOffset())
 665:           && space == 0 && el.isLeaf())
 666:         return new Element[2];
 667: 
 668:       // If the element is an instance of BranchElement, then we recursivly
 669:       // call this method to perform the split.
 670:       Element[] res = new Element[2];
 671:       if (el instanceof BranchElement)
 672:         {
 673:           int index = el.getElementIndex(offset);
 674:           Element child = el.getElement(index);
 675:           Element[] result = split(child, offset, space);
 676:           Element[] removed;
 677:           Element[] added;
 678:           Element[] newAdded;
 679: 
 680:           int count = el.getElementCount();
 681:           if (!(result[1] == null))
 682:             {
 683:               // This is the case when we can keep the first element.
 684:               if (result[0] == null)
 685:                 {
 686:                   removed = new Element[count - index - 1];
 687:                   newAdded = new Element[count - index - 1];
 688:                   added = new Element[]{};
 689:                 }
 690:               // This is the case when we may not keep the first element.
 691:               else
 692:                 {
 693:                   removed = new Element[count - index];
 694:                   newAdded = new Element[count - index];
 695:                   added = new Element[]{result[0]};
 696:                 }
 697:               newAdded[0] = result[1];
 698:               for (int i = index; i < count; i++)
 699:                 {
 700:                   Element el2 = el.getElement(i);
 701:                   int ind = i - count + removed.length;
 702:                   removed[ind] = el2;
 703:                   if (ind != 0)
 704:                     newAdded[ind] = el2;
 705:                 }
 706: 
 707:               ((BranchElement) el).replace(index, removed.length, added);
 708:               addEdit(el, index, removed, added);
 709:               BranchElement newPar =
 710:                 (BranchElement) createBranchElement(el.getParentElement(),
 711:                                                     el.getAttributes());
 712:               newPar.replace(0, 0, newAdded);
 713:               res = new Element[]{ null, newPar };
 714:             }
 715:           else
 716:             {
 717:               removed = new Element[count - index];
 718:               for (int i = index; i < count; ++i)
 719:                 removed[i - index] = el.getElement(i);
 720:               added = new Element[0];
 721:               ((BranchElement) el).replace(index, removed.length,
 722:                                            added);
 723:               addEdit(el, index, removed, added);
 724:               BranchElement newPar =
 725:                 (BranchElement) createBranchElement(el.getParentElement(),
 726:                                                     el.getAttributes());
 727:               newPar.replace(0, 0, removed);
 728:               res = new Element[]{ null, newPar };
 729:             }
 730:         }
 731:       else if (el instanceof LeafElement)
 732:         {
 733:           BranchElement par = (BranchElement) el.getParentElement();
 734:           Element el1 = createLeafElement(par, el.getAttributes(),
 735:                                           el.getStartOffset(), offset);
 736:           Element el2 = createLeafElement(par, el.getAttributes(),
 737:                                           offset + space, el.getEndOffset());
 738:           res = new Element[]{ el1, el2 };
 739:         }
 740:       return res;
 741:     }
 742: 
 743:     /**
 744:      * Inserts new <code>Element</code> in the document at the specified
 745:      * position.
 746:      *
 747:      * Most of the work is done by {@link #insertUpdate}, after some fields
 748:      * have been prepared for it.
 749:      *
 750:      * @param offset the location in the document at which the content is
 751:      *        inserted
 752:      * @param length the length of the inserted content
 753:      * @param data the element specifications for the content to be inserted
 754:      * @param ev the document event that is updated to reflect the structural
 755:      *        changes
 756:      */
 757:     public void insert(int offset, int length, ElementSpec[] data,
 758:                        DefaultDocumentEvent ev)
 759:     {
 760:       if (length == 0)
 761:         return;
 762:       this.offset = offset;
 763:       this.length = length;
 764:       this.endOffset = offset + length;
 765:       documentEvent = ev;
 766:       // Push the root and the paragraph at offset onto the element stack.
 767:       elementStack.clear();
 768:       elementStack.push(root);
 769:       elementStack.push(root.getElement(root.getElementIndex(offset)));
 770:       numEndTags = 0;
 771:       numStartTags = 0;
 772:       insertUpdate(data);
 773:     }
 774: 
 775:     /**
 776:      * Performs the actual structural change for {@link #insert}. This
 777:      * creates a bunch of {@link Element}s as specified by <code>data</code>
 778:      * and inserts it into the document as specified in the arguments to
 779:      * {@link #insert}.
 780:      *
 781:      * @param data the element specifications for the elements to be inserte
 782:      */ 
 783:     protected void insertUpdate(ElementSpec[] data)
 784:     {
 785:       if (data[0].getType() == ElementSpec.EndTagType)
 786:         {
 787:           // fracture deepest child here
 788:           BranchElement paragraph = (BranchElement) elementStack.peek();
 789:           Element curr = paragraph.getParentElement();
 790:           int index = curr.getElementIndex(offset);
 791:           while (!curr.isLeaf())
 792:             {
 793:               index = curr.getElementIndex(offset);
 794:               curr = curr.getElement(index);
 795:             }
 796:           Element parent = curr.getParentElement();
 797:           Element newEl1 = createLeafElement(parent,
 798:                                              curr.getAttributes(),
 799:                                              curr.getStartOffset(), offset);
 800:           Element grandParent = parent.getParentElement();
 801:           BranchElement nextBranch = 
 802:             (BranchElement) grandParent.getElement
 803:               (grandParent.getElementIndex(parent.getEndOffset()));
 804:           Element firstLeaf = nextBranch.getElement(0);
 805:           while (!firstLeaf.isLeaf())
 806:             {
 807:               firstLeaf = firstLeaf.getElement(0);
 808:             }
 809:           BranchElement parent2 = (BranchElement) firstLeaf.getParentElement();
 810:           Element newEl2 = 
 811:             createLeafElement(parent2, 
 812:                               firstLeaf.getAttributes(), 
 813:                               offset, firstLeaf.getEndOffset());
 814:           parent2.replace(0, 1, new Element[] { newEl2 });
 815:           
 816:           
 817:           ((BranchElement) parent).
 818:               replace(index, 1, new Element[] { newEl1 });
 819:         }
 820:       
 821:       for (int i = 0; i < data.length; i++)
 822:         {
 823:           BranchElement paragraph = (BranchElement) elementStack.peek();
 824:           switch (data[i].getType())
 825:             {
 826:             case ElementSpec.StartTagType:
 827:               switch (data[i].getDirection())
 828:                 {
 829:                 case ElementSpec.JoinFractureDirection:
 830:                   insertFracture(data[i]);
 831:                   break;
 832:                 case ElementSpec.JoinNextDirection:
 833:                   int index = paragraph.getElementIndex(offset);
 834:                   elementStack.push(paragraph.getElement(index));
 835:                   break;
 836:                 case ElementSpec.OriginateDirection:
 837:                   Element current = (Element) elementStack.peek();
 838:                   Element newParagraph =
 839:                     insertParagraph((BranchElement) current, offset);
 840:                   elementStack.push(newParagraph);
 841:                   break;
 842:                 default:
 843:                   break;
 844:                 }
 845:               break;
 846:             case ElementSpec.EndTagType:
 847:               elementStack.pop();
 848:               break;
 849:             case ElementSpec.ContentType:
 850:               insertContentTag(data[i]);
 851:               break;
 852:             }
 853:         }
 854:       endEdit();
 855:     }
 856: 
 857:     /**
 858:      * Finishes an insertion by possibly evaluating the outstanding start and
 859:      * end tags. However, this is only performed if the event has received any
 860:      * modifications.
 861:      */
 862:     private void endEdit()
 863:     {
 864:       if (documentEvent.modified)
 865:         prepareContentInsertion();
 866:     }
 867: 
 868:     /**
 869:      * Evaluates the number of inserted end tags and performs the corresponding
 870:      * structural changes.
 871:      */
 872:     private void prepareContentInsertion()
 873:     {
 874:       while (numEndTags > 0)
 875:         {
 876:           elementStack.pop();
 877:           numEndTags--;
 878:         }
 879: 
 880:       while (numStartTags > 0)
 881:         {
 882:           Element current = (Element) elementStack.peek();
 883:           Element newParagraph =
 884:             insertParagraph((BranchElement) current, offset);
 885:           elementStack.push(newParagraph);
 886:           numStartTags--;
 887:         }
 888:     }
 889: 
 890:     private Element insertParagraph(BranchElement par, int offset)
 891:     {
 892:       Element current = par.getElement(par.getElementIndex(offset));
 893:       Element[] res = split(current, offset, 0);
 894:       int index = par.getElementIndex(offset);
 895:       Element ret;
 896:       if (res[1] != null)
 897:         {
 898:           Element[] removed;
 899:           Element[] added;
 900:           if (res[0] == null)
 901:             {
 902:               removed = new Element[0];
 903:               if (res[1] instanceof BranchElement)
 904:                 {
 905:                   added = new Element[]{ res[1] };
 906:                   ret = res[1];
 907:                 }
 908:               else
 909:                 {
 910:                   ret = createBranchElement(par, null);
 911:                   added = new Element[]{ ret, res[1] };
 912:                 }
 913:               index++;
 914:             }
 915:           else
 916:             {
 917:               removed = new Element[]{ current };
 918:               if (res[1] instanceof BranchElement)
 919:                 {
 920:                   ret = res[1];
 921:                   added = new Element[]{ res[0], res[1] };
 922:                 }
 923:               else
 924:                 {
 925:                   ret = createBranchElement(par, null);
 926:                   added = new Element[]{ res[0], ret, res[1] };
 927:                 }
 928:             }
 929:           par.replace(index, removed.length, added);
 930:           addEdit(par, index, removed, added);
 931:         }
 932:       else
 933:         {
 934:           ret = createBranchElement(par, null);
 935:           Element[] added = new Element[]{ ret };
 936:           par.replace(index, 0, added);
 937:           addEdit(par, index, new Element[0], added);
 938:         }
 939:       return ret;
 940:     }
 941:     
 942:     /**
 943:      * Inserts a fracture into the document structure.
 944:      * 
 945:      * @param tag - the element spec.
 946:      */
 947:     private void insertFracture(ElementSpec tag)
 948:     {
 949:       // This is the parent of the paragraph about to be fractured.  We will
 950:       // create a new child of this parent.
 951:       BranchElement parent = (BranchElement) elementStack.peek();
 952:       int parentIndex = parent.getElementIndex(offset);
 953:       
 954:       // This is the old paragraph.  We must remove all its children that 
 955:       // occur after offset and move them to a new paragraph.  We must
 956:       // also recreate its child that occurs at offset to have the proper
 957:       // end offset.  The remainder of this child will also go in the new
 958:       // paragraph.
 959:       BranchElement previous = (BranchElement) parent.getElement(parentIndex);
 960:       
 961:       // This is the new paragraph.
 962:       BranchElement newBranch = 
 963:         (BranchElement) createBranchElement(parent, previous.getAttributes());
 964:       
 965:       
 966:       // The steps we must take to properly fracture are:
 967:       // 1. Recreate the LeafElement at offset to have the correct end offset.
 968:       // 2. Create a new LeafElement with the remainder of the LeafElement in 
 969:       //    #1 ==> this is whatever was in that LeafElement to the right of the
 970:       //    inserted newline.
 971:       // 3. Find the paragraph at offset and remove all its children that 
 972:       //    occur _after_ offset.  These will be moved to the newly created
 973:       //    paragraph.
 974:       // 4. Move the LeafElement created in #2 and all the LeafElements removed
 975:       //    in #3 to the newly created paragraph.
 976:       // 5. Add the new paragraph to the parent.
 977:       int previousIndex = previous.getElementIndex(offset);
 978:       int numReplaced = previous.getElementCount() - previousIndex;
 979:       Element previousLeaf = previous.getElement(previousIndex);
 980:       AttributeSet prevLeafAtts = previous.getAttributes();
 981:       
 982:       // This recreates the child at offset to have the proper end offset.  
 983:       // (Step 1).
 984:       Element newPreviousLeaf = 
 985:         createLeafElement(previous, 
 986:                           prevLeafAtts, previousLeaf.getStartOffset(), 
 987:                           offset);
 988:       // This creates the new child, which is the remainder of the old child.  
 989:       // (Step 2).
 990:       
 991:       Element firstLeafInNewBranch = 
 992:         createLeafElement(newBranch, prevLeafAtts, 
 993:                           offset, previousLeaf.getEndOffset());
 994:       
 995:       // Now we move the new LeafElement and all the old children that occurred
 996:       // after the offset to the new paragraph.  (Step 4).
 997:       Element[] newLeaves = new Element[numReplaced];
 998:       newLeaves[0] = firstLeafInNewBranch;
 999:       for (int i = 1; i < numReplaced; i++)
1000:         newLeaves[i] = previous.getElement(previousIndex + i);
1001:       newBranch.replace(0, 0, newLeaves);
1002:       addEdit(newBranch, 0, null, newLeaves);
1003:             
1004:       // Now we remove the children after the offset from the previous 
1005:       // paragraph. (Step 3).
1006:       int removeSize = previous.getElementCount() - previousIndex;
1007:       Element[] add = new Element[] { newPreviousLeaf };
1008:       Element[] remove = new Element[removeSize];
1009:       for (int j = 0; j < removeSize; j++)
1010:         remove[j] = previous.getElement(previousIndex + j);
1011:       previous.replace(previousIndex, removeSize, add);
1012:       addEdit(previous, previousIndex, remove, add);
1013:       
1014:       // Finally we add the new paragraph to the parent. (Step 5).
1015:       Element[] nb = new Element[] { newBranch };
1016:       int index = parentIndex + 1;
1017:       parent.replace(index, 0, nb);
1018:       addEdit(parent, index, null, nb);
1019:     }
1020:     
1021:     /**
1022:      * Inserts a content element into the document structure.
1023:      * 
1024:      * @param tag the element spec
1025:      */
1026:     private void insertContentTag(ElementSpec tag)
1027:     {
1028:       prepareContentInsertion();
1029:       int len = tag.getLength();
1030:       int dir = tag.getDirection();
1031:       AttributeSet tagAtts = tag.getAttributes();
1032:       if (dir == ElementSpec.JoinPreviousDirection)
1033:         {
1034:           // The mauve tests to this class show that a JoinPrevious insertion
1035:           // does not add any edits to the document event. To me this means
1036:           // that nothing is done here. The previous element naturally should
1037:           // expand so that it covers the new characters.
1038:         }
1039:       else if (dir == ElementSpec.JoinNextDirection)
1040:         {
1041:           // FIXME:
1042:           // Have to handle JoinNext differently depending on whether
1043:           // or not it comes after a fracture.  If comes after a fracture, 
1044:           // the insertFracture method takes care of everything and nothing
1045:           // needs to be done here.  Otherwise, we need to adjust the
1046:           // Element structure.  For now, I check if the elementStack's 
1047:           // top Element is the immediate parent of the LeafElement at
1048:           // offset - if so, we did not come immediately after a 
1049:           // fracture.  This seems awkward and should probably be improved.
1050:           // We may be doing too much in insertFracture because we are 
1051:           // adjusting the offsets, the correct thing to do may be to 
1052:           // create a new branch element and push it on to element stack
1053:           // and then this method here can be more general.
1054:           
1055:           BranchElement paragraph = (BranchElement) elementStack.peek();
1056:           int index = paragraph.getElementIndex(offset);
1057:           Element target = paragraph.getElement(index);
1058:           if (target.isLeaf() && paragraph.getElementCount() > (index + 1))
1059:             {
1060:               Element next = paragraph.getElement(index + 1);
1061:               Element newEl1 = createLeafElement(paragraph,
1062:                                                  target.getAttributes(),
1063:                                                  target.getStartOffset(),
1064:                                                  offset);
1065:               Element newEl2 = createLeafElement(paragraph,
1066:                                                  next.getAttributes(), offset,
1067:                                                  next.getEndOffset());
1068:               Element[] add = new Element[] { newEl1, newEl2 };
1069:               paragraph.replace (index, 2, add);
1070:               addEdit(paragraph, index, new Element[] { target, next }, add);
1071:             }
1072:         }
1073:       else if (dir == ElementSpec.OriginateDirection)
1074:         {
1075:           BranchElement paragraph = (BranchElement) elementStack.peek();
1076:           int index = paragraph.getElementIndex(offset);
1077:           Element current = paragraph.getElement(index);
1078:           
1079:           Element[] added;
1080:           Element[] removed = new Element[] {current};
1081:           Element[] splitRes = split(current, offset, length);
1082:           if (splitRes[0] == null)
1083:             {
1084:               added = new Element[2];
1085:               added[0] = createLeafElement(paragraph, tagAtts,
1086:                                            offset, endOffset);
1087:               added[1] = splitRes[1];
1088:               removed = new Element[0];
1089:               index++;
1090:             }
1091:           else if (current.getStartOffset() == offset)
1092:             { 
1093:               // This is if the new insertion happens immediately before 
1094:               // the <code>current</code> Element.  In this case there are 2 
1095:               // resulting Elements.              
1096:               added = new Element[2];
1097:               added[0] = createLeafElement(paragraph, tagAtts, offset,
1098:                                            endOffset);
1099:               added[1] = splitRes[1];
1100:             }
1101:           else if (current.getEndOffset() == endOffset)
1102:             {
1103:               // This is if the new insertion happens right at the end of 
1104:               // the <code>current</code> Element.  In this case there are 
1105:               // 2 resulting Elements.
1106:               added = new Element[2];
1107:               added[0] = splitRes[0];
1108:               added[1] = createLeafElement(paragraph, tagAtts, offset,
1109:                                            endOffset);
1110:             }
1111:           else
1112:             {
1113:               // This is if the new insertion is in the middle of the 
1114:               // <code>current</code> Element.  In this case 
1115:               // there will be 3 resulting Elements.
1116:               added = new Element[3];
1117:               added[0] = splitRes[0];
1118:               added[1] = createLeafElement(paragraph, tagAtts, offset,
1119:                                            endOffset);
1120:               added[2] = splitRes[1];
1121:             }          
1122:           paragraph.replace(index, removed.length, added);
1123:           addEdit(paragraph, index, removed, added);
1124:         }
1125:       offset += len;
1126:     }
1127:     
1128:     /**
1129:      * Creates a copy of the element <code>clonee</code> that has the parent
1130:      * <code>parent</code>.
1131:      * @param parent the parent of the newly created Element
1132:      * @param clonee the Element to clone
1133:      * @return the cloned Element
1134:      */
1135:     public Element clone (Element parent, Element clonee)
1136:     {
1137:       // If the Element we want to clone is a leaf, then simply copy it
1138:       if (clonee.isLeaf())
1139:         return createLeafElement(parent, clonee.getAttributes(),
1140:                                  clonee.getStartOffset(), clonee.getEndOffset());
1141:       
1142:       // Otherwise create a new BranchElement with the desired parent and 
1143:       // the clonee's attributes
1144:       BranchElement result = (BranchElement) createBranchElement(parent, clonee.getAttributes());
1145:       
1146:       // And clone all the of clonee's children
1147:       Element[] children = new Element[clonee.getElementCount()];
1148:       for (int i = 0; i < children.length; i++)
1149:         children[i] = clone(result, clonee.getElement(i));
1150:       
1151:       // Make the cloned children the children of the BranchElement
1152:       result.replace(0, 0, children);
1153:       return result;
1154:     }
1155: 
1156:     /**
1157:      * Adds an ElementChange for a given element modification to the document
1158:      * event. If there already is an ElementChange registered for this element,
1159:      * this method tries to merge the ElementChanges together. However, this
1160:      * is only possible if the indices of the new and old ElementChange are
1161:      * equal.
1162:      *
1163:      * @param e the element
1164:      * @param i the index of the change
1165:      * @param removed the removed elements, or <code>null</code>
1166:      * @param added the added elements, or <code>null</code>
1167:      */
1168:     private void addEdit(Element e, int i, Element[] removed, Element[] added)
1169:     {
1170:       // Perform sanity check first.
1171:       DocumentEvent.ElementChange ec = documentEvent.getChange(e);
1172: 
1173:       // Merge the existing stuff with the new stuff.
1174:       Element[] oldAdded = ec == null ? null: ec.getChildrenAdded();
1175:       Element[] newAdded;
1176:       if (oldAdded != null && added != null)
1177:         {
1178:           if (ec.getIndex() <= i)
1179:             {
1180:               int index = i - ec.getIndex();
1181:               // Merge adds together.
1182:               newAdded = new Element[oldAdded.length + added.length];
1183:               System.arraycopy(oldAdded, 0, newAdded, 0, index);
1184:               System.arraycopy(added, 0, newAdded, index, added.length);
1185:               System.arraycopy(oldAdded, index, newAdded, index + added.length,
1186:                                oldAdded.length - index);
1187:               i = ec.getIndex();
1188:             }
1189:           else
1190:             throw new AssertionError("Not yet implemented case.");
1191:         }
1192:       else if (added != null)
1193:         newAdded = added;
1194:       else if (oldAdded != null)
1195:         newAdded = oldAdded;
1196:       else
1197:         newAdded = new Element[0];
1198: 
1199:       Element[] oldRemoved = ec == null ? null: ec.getChildrenRemoved();
1200:       Element[] newRemoved;
1201:       if (oldRemoved != null && removed != null)
1202:         {
1203:           if (ec.getIndex() <= i)
1204:             {
1205:               int index = i - ec.getIndex();
1206:               // Merge removes together.
1207:               newRemoved = new Element[oldRemoved.length + removed.length];
1208:               System.arraycopy(oldAdded, 0, newRemoved, 0, index);
1209:               System.arraycopy(removed, 0, newRemoved, index, removed.length);
1210:               System.arraycopy(oldRemoved, index, newRemoved,
1211:                                index + removed.length,
1212:                                oldRemoved.length - index);
1213:               i = ec.getIndex();
1214:             }
1215:           else
1216:             throw new AssertionError("Not yet implemented case.");
1217:         }
1218:       else if (removed != null)
1219:         newRemoved = removed;
1220:       else if (oldRemoved != null)
1221:         newRemoved = oldRemoved;
1222:       else
1223:         newRemoved = new Element[0];
1224: 
1225:       // Replace the existing edit for the element with the merged.
1226:       documentEvent.addEdit(new ElementEdit(e, i, newRemoved, newAdded));
1227:     }
1228:   }
1229: 
1230:   /**
1231:    * An element type for sections. This is a simple BranchElement with
1232:    * a unique name.
1233:    */
1234:   protected class SectionElement extends BranchElement
1235:   {
1236:     /**
1237:      * Creates a new SectionElement.
1238:      */
1239:     public SectionElement()
1240:     {
1241:       super(null, null);
1242:     }
1243: 
1244:     /**
1245:      * Returns the name of the element. This method always returns
1246:      * &quot;section&quot;.
1247:      *
1248:      * @return the name of the element
1249:      */
1250:     public String getName()
1251:     {
1252:       return SectionElementName;
1253:     }
1254:   }
1255: 
1256:   /**
1257:    * Receives notification when any of the document's style changes and calls
1258:    * {@link DefaultStyledDocument#styleChanged(Style)}.
1259:    *
1260:    * @author Roman Kennke (kennke@aicas.com)
1261:    */
1262:   private class StyleChangeListener
1263:     implements ChangeListener
1264:   {
1265: 
1266:     /**
1267:      * Receives notification when any of the document's style changes and calls
1268:      * {@link DefaultStyledDocument#styleChanged(Style)}.
1269:      *
1270:      * @param event the change event
1271:      */
1272:     public void stateChanged(ChangeEvent event)
1273:     {
1274:       Style style = (Style) event.getSource();
1275:       styleChanged(style);
1276:     }
1277:   }
1278: 
1279:   /** The serialization UID (compatible with JDK1.5). */
1280:   private static final long serialVersionUID = 940485415728614849L;
1281: 
1282:   /**
1283:    * The default size to use for new content buffers.
1284:    */
1285:   public static final int BUFFER_SIZE_DEFAULT = 4096;
1286: 
1287:   /**
1288:    * The <code>EditorBuffer</code> that is used to manage to
1289:    * <code>Element</code> hierarchy.
1290:    */
1291:   protected DefaultStyledDocument.ElementBuffer buffer;
1292: 
1293:   /**
1294:    * Listens for changes on this document's styles and notifies styleChanged().
1295:    */
1296:   private StyleChangeListener styleChangeListener;
1297: 
1298:   /**
1299:    * Creates a new <code>DefaultStyledDocument</code>.
1300:    */
1301:   public DefaultStyledDocument()
1302:   {
1303:     this(new GapContent(BUFFER_SIZE_DEFAULT), new StyleContext());
1304:   }
1305: 
1306:   /**
1307:    * Creates a new <code>DefaultStyledDocument</code> that uses the
1308:    * specified {@link StyleContext}.
1309:    *
1310:    * @param context the <code>StyleContext</code> to use
1311:    */
1312:   public DefaultStyledDocument(StyleContext context)
1313:   {
1314:     this(new GapContent(BUFFER_SIZE_DEFAULT), context);
1315:   }
1316: 
1317:   /**
1318:    * Creates a new <code>DefaultStyledDocument</code> that uses the
1319:    * specified {@link StyleContext} and {@link Content} buffer.
1320:    *
1321:    * @param content the <code>Content</code> buffer to use
1322:    * @param context the <code>StyleContext</code> to use
1323:    */
1324:   public DefaultStyledDocument(AbstractDocument.Content content,
1325:                    StyleContext context)
1326:   {
1327:     super(content, context);
1328:     buffer = new ElementBuffer(createDefaultRoot());
1329:     setLogicalStyle(0, context.getStyle(StyleContext.DEFAULT_STYLE));
1330:   }
1331: 
1332:   /**
1333:    * Adds a style into the style hierarchy. Unspecified style attributes
1334:    * can be resolved in the <code>parent</code> style, if one is specified.
1335:    *
1336:    * While it is legal to add nameless styles (<code>nm == null</code),
1337:    * you must be aware that the client application is then responsible
1338:    * for managing the style hierarchy, since unnamed styles cannot be
1339:    * looked up by their name.
1340:    *
1341:    * @param nm the name of the style or <code>null</code> if the style should
1342:    *           be unnamed
1343:    * @param parent the parent in which unspecified style attributes are
1344:    *           resolved, or <code>null</code> if that is not necessary
1345:    *
1346:    * @return the newly created <code>Style</code>
1347:    */
1348:   public Style addStyle(String nm, Style parent)
1349:   {
1350:     StyleContext context = (StyleContext) getAttributeContext();
1351:     Style newStyle = context.addStyle(nm, parent);
1352: 
1353:     // Register change listener.
1354:     if (styleChangeListener == null)
1355:       styleChangeListener = new StyleChangeListener();
1356:     newStyle.addChangeListener(styleChangeListener);
1357: 
1358:     return newStyle;
1359:   }
1360: 
1361:   /**
1362:    * Create the default root element for this kind of <code>Document</code>.
1363:    *
1364:    * @return the default root element for this kind of <code>Document</code>
1365:    */
1366:   protected AbstractDocument.AbstractElement createDefaultRoot()
1367:   {
1368:     Element[] tmp;
1369:     // FIXME: Create a SecionElement here instead of a BranchElement.
1370:     // Use createBranchElement() and createLeafElement instead.
1371:     SectionElement section = new SectionElement();
1372: 
1373:     BranchElement paragraph = new BranchElement(section, null);
1374:     tmp = new Element[1];
1375:     tmp[0] = paragraph;
1376:     section.replace(0, 0, tmp);
1377: 
1378:     LeafElement leaf = new LeafElement(paragraph, null, 0, 1);
1379:     tmp = new Element[1];
1380:     tmp[0] = leaf;
1381:     paragraph.replace(0, 0, tmp);
1382: 
1383:     return section;
1384:   }
1385: 
1386:   /**
1387:    * Returns the <code>Element</code> that corresponds to the character
1388:    * at the specified position.
1389:    *
1390:    * @param position the position of which we query the corresponding
1391:    *        <code>Element</code>
1392:    *
1393:    * @return the <code>Element</code> that corresponds to the character
1394:    *         at the specified position
1395:    */
1396:   public Element getCharacterElement(int position)
1397:   {
1398:     Element element = getDefaultRootElement();
1399: 
1400:     while (!element.isLeaf())
1401:       {
1402:         int index = element.getElementIndex(position);
1403:         element = element.getElement(index);
1404:       }
1405:     
1406:     return element;
1407:   }
1408: 
1409:   /**
1410:    * Extracts a background color from a set of attributes.
1411:    *
1412:    * @param attributes the attributes from which to get a background color
1413:    *
1414:    * @return the background color that correspond to the attributes
1415:    */
1416:   public Color getBackground(AttributeSet attributes)
1417:   {
1418:     StyleContext context = (StyleContext) getAttributeContext();
1419:     return context.getBackground(attributes);
1420:   }
1421: 
1422:   /**
1423:    * Returns the default root element.
1424:    *
1425:    * @return the default root element
1426:    */
1427:   public Element getDefaultRootElement()
1428:   {
1429:     return buffer.getRootElement();
1430:   }
1431: 
1432:   /**
1433:    * Extracts a font from a set of attributes.
1434:    *
1435:    * @param attributes the attributes from which to get a font
1436:    *
1437:    * @return the font that correspond to the attributes
1438:    */
1439:   public Font getFont(AttributeSet attributes)
1440:   {
1441:     StyleContext context = (StyleContext) getAttributeContext();
1442:     return context.getFont(attributes);
1443:   }
1444:   
1445:   /**
1446:    * Extracts a foreground color from a set of attributes.
1447:    *
1448:    * @param attributes the attributes from which to get a foreground color
1449:    *
1450:    * @return the foreground color that correspond to the attributes
1451:    */
1452:   public Color getForeground(AttributeSet attributes)
1453:   {
1454:     StyleContext context = (StyleContext) getAttributeContext();
1455:     return context.getForeground(attributes);
1456:   }
1457: 
1458:   /**
1459:    * Returns the logical <code>Style</code> for the specified position.
1460:    *
1461:    * @param position the position from which to query to logical style
1462:    *
1463:    * @return the logical <code>Style</code> for the specified position
1464:    */
1465:   public Style getLogicalStyle(int position)
1466:   {
1467:     Element paragraph = getParagraphElement(position);
1468:     AttributeSet attributes = paragraph.getAttributes();
1469:     AttributeSet a = attributes.getResolveParent();
1470:     // If the resolve parent is not of type Style, we return null.
1471:     if (a instanceof Style)
1472:       return (Style) a;
1473:     return null;
1474:   }
1475: 
1476:   /**
1477:    * Returns the paragraph element for the specified position.
1478:    * If the position is outside the bounds of the document's root element,
1479:    * then the closest element is returned. That is the last paragraph if
1480:    * <code>position >= endIndex</code> or the first paragraph if
1481:    * <code>position < startIndex</code>.
1482:    *
1483:    * @param position the position for which to query the paragraph element
1484:    *
1485:    * @return the paragraph element for the specified position
1486:    */
1487:   public Element getParagraphElement(int position)
1488:   {
1489:     BranchElement root = (BranchElement) getDefaultRootElement();
1490:     int start = root.getStartOffset();
1491:     int end = root.getEndOffset();
1492:     if (position >= end)
1493:       position = end - 1;
1494:     else if (position < start)
1495:       position = start;
1496: 
1497:     Element par = root.positionToElement(position);
1498: 
1499:     assert par != null : "The paragraph element must not be null";
1500:     return par;
1501:   }
1502: 
1503:   /**
1504:    * Looks up and returns a named <code>Style</code>.
1505:    *
1506:    * @param nm the name of the <code>Style</code>
1507:    *
1508:    * @return the found <code>Style</code> of <code>null</code> if no such
1509:    *         <code>Style</code> exists
1510:    */
1511:   public Style getStyle(String nm)
1512:   {
1513:     StyleContext context = (StyleContext) getAttributeContext();
1514:     return context.getStyle(nm);
1515:   }
1516: 
1517:   /**
1518:    * Removes a named <code>Style</code> from the style hierarchy.
1519:    *
1520:    * @param nm the name of the <code>Style</code> to be removed
1521:    */
1522:   public void removeStyle(String nm)
1523:   {
1524:     StyleContext context = (StyleContext) getAttributeContext();
1525:     context.removeStyle(nm);
1526:   }
1527: 
1528:   /**
1529:    * Sets text attributes for the fragment specified by <code>offset</code>
1530:    * and <code>length</code>.
1531:    *
1532:    * @param offset the start offset of the fragment
1533:    * @param length the length of the fragment
1534:    * @param attributes the text attributes to set
1535:    * @param replace if <code>true</code>, the attributes of the current
1536:    *     selection are overridden, otherwise they are merged
1537:    */
1538:   public void setCharacterAttributes(int offset, int length,
1539:                      AttributeSet attributes,
1540:                      boolean replace)
1541:   {
1542:     // Exit early if length is 0, so no DocumentEvent is created or fired.
1543:     if (length == 0)
1544:       return;
1545:     try
1546:       {
1547:         // Must obtain a write lock for this method.  writeLock() and
1548:         // writeUnlock() should always be in try/finally block to make
1549:         // sure that locking happens in a balanced manner.
1550:         writeLock();
1551:         DefaultDocumentEvent ev = 
1552:           new DefaultDocumentEvent(
1553:                                    offset, 
1554:                                    length, 
1555:                                    DocumentEvent.EventType.CHANGE);
1556: 
1557:         // Modify the element structure so that the interval begins at an
1558:         // element
1559:         // start and ends at an element end.
1560:         buffer.change(offset, length, ev);
1561: 
1562:         Element root = getDefaultRootElement();
1563:         // Visit all paragraph elements within the specified interval
1564:         int end = offset + length;
1565:         Element curr;
1566:         for (int pos = offset; pos < end; )
1567:           {
1568:             // Get the CharacterElement at offset pos.
1569:             curr = getCharacterElement(pos);
1570:             if (pos == curr.getEndOffset())
1571:               break;
1572:             
1573:             MutableAttributeSet a = (MutableAttributeSet) curr.getAttributes();
1574:             ev.addEdit(new AttributeUndoableEdit(curr, attributes, replace));
1575:             // If replace is true, remove all the old attributes.
1576:             if (replace)
1577:               a.removeAttributes(a);
1578:             // Add all the new attributes.
1579:             a.addAttributes(attributes);
1580:             // Increment pos so we can check the next CharacterElement.
1581:             pos = curr.getEndOffset();
1582:           }
1583:         fireChangedUpdate(ev);
1584:         fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
1585:       }
1586:     finally
1587:       {
1588:         writeUnlock();
1589:       }
1590:   }
1591:   
1592:   /**
1593:    * Sets the logical style for the paragraph at the specified position.
1594:    *
1595:    * @param position the position at which the logical style is added
1596:    * @param style the style to set for the current paragraph
1597:    */
1598:   public void setLogicalStyle(int position, Style style)
1599:   {
1600:     Element el = getParagraphElement(position);
1601:     // getParagraphElement doesn't return null but subclasses might so
1602:     // we check for null here.
1603:     if (el == null)
1604:       return;
1605:     try
1606:     {
1607:       writeLock();    
1608:       if (el instanceof AbstractElement)
1609:         {
1610:           AbstractElement ael = (AbstractElement) el;
1611:           ael.setResolveParent(style);
1612:           int start = el.getStartOffset();
1613:           int end = el.getEndOffset();
1614:           DefaultDocumentEvent ev = 
1615:             new DefaultDocumentEvent (
1616:                                       start, 
1617:                                       end - start, 
1618:                                       DocumentEvent.EventType.CHANGE);
1619:           // FIXME: Add an UndoableEdit to this event and fire it.
1620:           fireChangedUpdate(ev);
1621:         }
1622:       else
1623:         throw new 
1624:         AssertionError("paragraph elements are expected to be"
1625:                        + "instances of AbstractDocument.AbstractElement");
1626:     }
1627:     finally
1628:     {
1629:       writeUnlock();
1630:     }
1631:   }
1632: 
1633:   /**
1634:    * Sets text attributes for the paragraph at the specified fragment.
1635:    *
1636:    * @param offset the beginning of the fragment
1637:    * @param length the length of the fragment
1638:    * @param attributes the text attributes to set
1639:    * @param replace if <code>true</code>, the attributes of the current
1640:    *     selection are overridden, otherwise they are merged
1641:    */
1642:   public void setParagraphAttributes(int offset, int length,
1643:                                      AttributeSet attributes,
1644:                                      boolean replace)
1645:   {
1646:     try
1647:       {
1648:         // Must obtain a write lock for this method.  writeLock() and
1649:         // writeUnlock() should always be in try/finally blocks to make
1650:         // sure that locking occurs in a balanced manner.
1651:         writeLock();
1652:         
1653:         // Create a DocumentEvent to use for changedUpdate().
1654:         DefaultDocumentEvent ev = 
1655:           new DefaultDocumentEvent (
1656:                                     offset, 
1657:                                     length, 
1658:                                     DocumentEvent.EventType.CHANGE);
1659:         
1660:         // Have to iterate through all the _paragraph_ elements that are
1661:         // contained or partially contained in the interval
1662:         // (offset, offset + length).
1663:         Element rootElement = getDefaultRootElement();
1664:         int startElement = rootElement.getElementIndex(offset);
1665:         int endElement = rootElement.getElementIndex(offset + length - 1);
1666:         if (endElement < startElement)
1667:           endElement = startElement;
1668:         
1669:         for (int i = startElement; i <= endElement; i++)
1670:           {
1671:             Element par = rootElement.getElement(i);
1672:             MutableAttributeSet a = (MutableAttributeSet) par.getAttributes();
1673:             // Add the change to the DocumentEvent.
1674:             ev.addEdit(new AttributeUndoableEdit(par, attributes, replace));
1675:             // If replace is true remove the old attributes.
1676:             if (replace)
1677:               a.removeAttributes(a);
1678:             // Add the new attributes.
1679:             a.addAttributes(attributes);
1680:           }
1681:         fireChangedUpdate(ev);
1682:         fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
1683:       }
1684:     finally
1685:       {
1686:         writeUnlock();
1687:       }
1688:   }
1689: 
1690:   /**
1691:    * Called in response to content insert actions. This is used to
1692:    * update the element structure.
1693:    *
1694:    * @param ev the <code>DocumentEvent</code> describing the change
1695:    * @param attr the attributes for the change
1696:    */
1697:   protected void insertUpdate(DefaultDocumentEvent ev, AttributeSet attr)
1698:   {
1699:     super.insertUpdate(ev, attr);
1700:     // If the attribute set is null, use an empty attribute set.
1701:     if (attr == null)
1702:       attr = SimpleAttributeSet.EMPTY;
1703:     int offset = ev.getOffset();
1704:     int length = ev.getLength();
1705:     int endOffset = offset + length;
1706:     AttributeSet paragraphAttributes = 
1707:       getParagraphElement(endOffset).getAttributes();    
1708:     Segment txt = new Segment();
1709:     try
1710:       {
1711:         getText(offset, length, txt);
1712:       }
1713:     catch (BadLocationException ex)
1714:       {
1715:         AssertionError ae = new AssertionError("Unexpected bad location");
1716:         ae.initCause(ex);
1717:         throw ae;
1718:       }
1719: 
1720:     int len = 0;
1721:     Vector specs = new Vector();
1722:     ElementSpec finalStartTag = null;
1723:     short finalStartDirection = ElementSpec.OriginateDirection;
1724:     boolean prevCharWasNewline = false;
1725:     Element prev = getCharacterElement(offset);
1726:     Element next = getCharacterElement(endOffset);    
1727:     Element prevParagraph = getParagraphElement(offset);
1728:     Element paragraph = getParagraphElement(endOffset);
1729:     
1730:     int segmentEnd = txt.offset + txt.count;
1731:     
1732:     // Check to see if we're inserting immediately after a newline.
1733:     if (offset > 0)
1734:       {
1735:         try
1736:         {
1737:           String s = getText(offset - 1, 1);
1738:           if (s.equals("\n"))
1739:             {
1740:               finalStartDirection = 
1741:                 handleInsertAfterNewline(specs, offset, endOffset,
1742:                                          prevParagraph,
1743:                                          paragraph,
1744:                                          paragraphAttributes);
1745:               
1746:               prevCharWasNewline = true;
1747:               // Find the final start tag from the ones just created.
1748:               for (int i = 0; i < specs.size(); i++)
1749:                 if (((ElementSpec) specs.get(i)).getType() 
1750:                     == ElementSpec.StartTagType)
1751:                   finalStartTag = (ElementSpec)specs.get(i);
1752:             }
1753:         }
1754:         catch (BadLocationException ble)
1755:         {          
1756:           // This shouldn't happen.
1757:           AssertionError ae = new AssertionError();
1758:           ae.initCause(ble);
1759:           throw ae;
1760:         }        
1761:       }
1762: 
1763:         
1764:     for (int i = txt.offset; i < segmentEnd; ++i)
1765:       {
1766:         len++;
1767:         if (txt.array[i] == '\n')
1768:           {
1769:             // Add the ElementSpec for the content.
1770:             specs.add(new ElementSpec(attr, ElementSpec.ContentType, len));            
1771: 
1772:             // Add ElementSpecs for the newline.
1773:             specs.add(new ElementSpec(null, ElementSpec.EndTagType));
1774:             finalStartTag = new ElementSpec(paragraphAttributes,
1775:                                                    ElementSpec.StartTagType);
1776:             specs.add(finalStartTag);
1777:             len = 0;
1778:           }
1779:       }
1780: 
1781:     // Create last element if last character hasn't been a newline.
1782:     if (len > 0)                      
1783:       specs.add(new ElementSpec(attr, ElementSpec.ContentType, len));
1784: 
1785:     // Set the direction of the last spec of type StartTagType.  
1786:     // If we are inserting after a newline then this value comes from 
1787:     // handleInsertAfterNewline.
1788:     if (finalStartTag != null)
1789:       {        
1790:         if (prevCharWasNewline)
1791:           finalStartTag.setDirection(finalStartDirection);
1792:         else if (prevParagraph.getEndOffset() != endOffset)
1793:           {
1794:             try
1795:               {
1796:                 String last = getText(endOffset - 1, 1);
1797:                 if (!last.equals("\n"))
1798:                   finalStartTag.setDirection(ElementSpec.JoinFractureDirection);
1799:               }
1800:             catch (BadLocationException ble)
1801:               {
1802:                 // This shouldn't happen.
1803:                 AssertionError ae = new AssertionError();
1804:                 ae.initCause(ble);
1805:                 throw ae;
1806:               } 
1807:           }
1808:         else
1809:           {
1810:             // If there is an element AFTER this one, then set the 
1811:             // direction to JoinNextDirection.
1812:             Element parent = prevParagraph.getParentElement();
1813:             int index = parent.getElementIndex(offset);
1814:             if (index + 1 < parent.getElementCount()
1815:                 && !parent.getElement(index + 1).isLeaf())
1816:               finalStartTag.setDirection(ElementSpec.JoinNextDirection);
1817:           }
1818:       }
1819:     
1820:     // If we are at the last index, then check if we could probably be
1821:     // joined with the next element.
1822:     // This means:
1823:     //  - we must be a ContentTag
1824:     //  - if there is a next Element, we must have the same attributes
1825:     //  - if there is no next Element, but one will be created,
1826:     //    we must have the same attributes as the higher-level run.
1827:     ElementSpec last = (ElementSpec) specs.lastElement();
1828:     if (last.getType() == ElementSpec.ContentType)
1829:       {
1830:         Element currentRun = 
1831:           prevParagraph.getElement(prevParagraph.getElementIndex(offset));
1832:         if (currentRun.getEndOffset() == endOffset)
1833:           {
1834:             if (endOffset < getLength() && next.getAttributes().isEqual(attr)
1835:                 && last.getType() == ElementSpec.ContentType)
1836:               last.setDirection(ElementSpec.JoinNextDirection);
1837:           }
1838:         else
1839:           {
1840:             if (finalStartTag != null
1841:                 && finalStartTag.getDirection() == 
1842:                   ElementSpec.JoinFractureDirection
1843:                 && currentRun.getAttributes().isEqual(attr))
1844:               {
1845:                 last.setDirection(ElementSpec.JoinNextDirection);
1846:               }
1847:           }
1848:       }
1849:     
1850:     // If we are at the first new element, then check if it could be
1851:     // joined with the previous element.
1852:     ElementSpec first = (ElementSpec) specs.firstElement();
1853:     if (prev.getAttributes().isEqual(attr)
1854:         && first.getType() == ElementSpec.ContentType)
1855:       first.setDirection(ElementSpec.JoinPreviousDirection);
1856:     
1857:     ElementSpec[] elSpecs =
1858:       (ElementSpec[]) specs.toArray(new ElementSpec[specs.size()]);
1859: 
1860:     buffer.insert(offset, length, elSpecs, ev);
1861:   }
1862: 
1863:   /**
1864:    * A helper method to set up the ElementSpec buffer for the special
1865:    * case of an insertion occurring immediately after a newline.
1866:    * @param specs the ElementSpec buffer to initialize.
1867:    */
1868:   short handleInsertAfterNewline(Vector specs, int offset, int endOffset,
1869:                                 Element prevParagraph, Element paragraph,
1870:                                 AttributeSet a)
1871:   {
1872:     if (prevParagraph.getParentElement() == paragraph.getParentElement())
1873:       {
1874:         specs.add(new ElementSpec(a, ElementSpec.EndTagType));
1875:         specs.add(new ElementSpec(a, ElementSpec.StartTagType));
1876:         if (prevParagraph.getEndOffset() != endOffset)
1877:           return ElementSpec.JoinFractureDirection;
1878:         // If there is an Element after this one, use JoinNextDirection.
1879:         Element parent = paragraph.getParentElement();
1880:         if (parent.getElementCount() > parent.getElementIndex(offset) + 1)
1881:           return ElementSpec.JoinNextDirection;
1882:       }
1883:     else
1884:       {
1885:         // TODO: What to do here?
1886:       }
1887:     return ElementSpec.OriginateDirection;
1888:   }
1889:   
1890:   /**
1891:    * Updates the document structure in response to text removal. This is
1892:    * forwarded to the {@link ElementBuffer} of this document. Any changes to
1893:    * the document structure are added to the specified document event and
1894:    * sent to registered listeners.
1895:    *
1896:    * @param ev the document event that records the changes to the document
1897:    */
1898:   protected void removeUpdate(DefaultDocumentEvent ev)
1899:   {
1900:     super.removeUpdate(ev);
1901:     buffer.remove(ev.getOffset(), ev.getLength(), ev);
1902:   }
1903: 
1904:   /**
1905:    * Returns an enumeration of all style names.
1906:    *
1907:    * @return an enumeration of all style names
1908:    */
1909:   public Enumeration getStyleNames()
1910:   {
1911:     StyleContext context = (StyleContext) getAttributeContext();
1912:     return context.getStyleNames();
1913:   }
1914: 
1915:   /**
1916:    * Called when any of this document's styles changes.
1917:    *
1918:    * @param style the style that changed
1919:    */
1920:   protected void styleChanged(Style style)
1921:   {
1922:     // Nothing to do here. This is intended to be overridden by subclasses.
1923:   }
1924: 
1925:   void printElements (Element start, int pad)
1926:   {
1927:     for (int i = 0; i < pad; i++)
1928:       System.out.print(" ");
1929:     if (pad == 0)
1930:       System.out.println ("ROOT ELEMENT ("+start.getStartOffset()+", "+start.getEndOffset()+")");
1931:     else if (start instanceof AbstractDocument.BranchElement)
1932:       System.out.println ("BranchElement ("+start.getStartOffset()+", "+start.getEndOffset()+")");
1933:     else
1934:       {
1935:         {
1936:           try
1937:             {
1938:               System.out.println ("LeafElement ("+start.getStartOffset()+", "
1939:                                   + start.getEndOffset()+"): "+ 
1940:                                   start.getDocument().
1941:                                   getText(start.getStartOffset(), 
1942:                                           start.getEndOffset() - 
1943:                                           start.getStartOffset()));
1944:             }
1945:           catch (BadLocationException ble)
1946:             {
1947:             }
1948:         }
1949:       }
1950:     for (int i = 0; i < start.getElementCount(); i ++)
1951:       printElements (start.getElement(i), pad+3);
1952:   }
1953:   
1954:   /**
1955:    * Inserts a bulk of structured content at once.
1956:    *
1957:    * @param offset the offset at which the content should be inserted
1958:    * @param data the actual content spec to be inserted
1959:    */
1960:   protected void insert(int offset, ElementSpec[] data)
1961:     throws BadLocationException
1962:   {
1963:     if (data == null || data.length == 0)
1964:       return;
1965:     try
1966:       {
1967:         // writeLock() and writeUnlock() should always be in a try/finally
1968:         // block so that locking balance is guaranteed even if some 
1969:         // exception is thrown.
1970:         writeLock();
1971:         
1972:         // First we collect the content to be inserted.
1973:         StringBuffer contentBuffer = new StringBuffer();
1974:         for (int i = 0; i < data.length; i++)
1975:           {
1976:             // Collect all inserts into one so we can get the correct
1977:             // ElementEdit
1978:             ElementSpec spec = data[i];
1979:             if (spec.getArray() != null && spec.getLength() > 0)
1980:               contentBuffer.append(spec.getArray(), spec.getOffset(),
1981:                                    spec.getLength());
1982:           }
1983: 
1984:         int length = contentBuffer.length();
1985: 
1986:         // If there was no content inserted then exit early.
1987:         if (length == 0)
1988:           return;
1989:         
1990:         UndoableEdit edit = content.insertString(offset,
1991:                                                  contentBuffer.toString());
1992: 
1993:         // Create the DocumentEvent with the ElementEdit added
1994:         DefaultDocumentEvent ev = 
1995:           new DefaultDocumentEvent(offset,
1996:                                    length,
1997:                                    DocumentEvent.EventType.INSERT);
1998:         ev.addEdit(edit);
1999: 
2000:         // Finally we must update the document structure and fire the insert
2001:         // update event.
2002:         buffer.insert(offset, length, data, ev);
2003:         fireInsertUpdate(ev);
2004:         fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
2005:       }
2006:     finally
2007:       {
2008:         writeUnlock();
2009:       }
2010:   }
2011: 
2012:   /**
2013:    * Initializes the <code>DefaultStyledDocument</code> with the specified
2014:    * data.
2015:    *
2016:    * @param data the specification of the content with which the document is
2017:    *        initialized
2018:    */
2019:   protected void create(ElementSpec[] data)
2020:   {
2021:     try
2022:       {
2023:         // Clear content.
2024:         content.remove(0, content.length());
2025:         // Clear buffer and root element.
2026:         buffer = new ElementBuffer(createDefaultRoot());
2027:         // Insert the data.
2028:         insert(0, data);
2029:       }
2030:     catch (BadLocationException ex)
2031:       {
2032:         AssertionError err = new AssertionError("Unexpected bad location");
2033:         err.initCause(ex);
2034:         throw err;
2035:       }
2036:   }
2037:   
2038:   static boolean attributeSetsAreSame (AttributeSet a, AttributeSet b)
2039:   {
2040:     return (a == null && b == null) || (a != null && a.isEqual(b));
2041:   }
2042: }