Source for javax.swing.text.AbstractDocument

   1: /* AbstractDocument.java --
   2:    Copyright (C) 2002, 2004, 2005, 2006  Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: 
  39: package javax.swing.text;
  40: 
  41: import java.awt.font.TextAttribute;
  42: import java.io.PrintStream;
  43: import java.io.Serializable;
  44: import java.text.Bidi;
  45: import java.util.ArrayList;
  46: import java.util.Dictionary;
  47: import java.util.Enumeration;
  48: import java.util.EventListener;
  49: import java.util.HashMap;
  50: import java.util.Hashtable;
  51: import java.util.Vector;
  52: 
  53: import javax.swing.event.DocumentEvent;
  54: import javax.swing.event.DocumentListener;
  55: import javax.swing.event.EventListenerList;
  56: import javax.swing.event.UndoableEditEvent;
  57: import javax.swing.event.UndoableEditListener;
  58: import javax.swing.text.DocumentFilter;
  59: import javax.swing.tree.TreeNode;
  60: import javax.swing.undo.AbstractUndoableEdit;
  61: import javax.swing.undo.CompoundEdit;
  62: import javax.swing.undo.UndoableEdit;
  63: 
  64: /**
  65:  * An abstract base implementation for the {@link Document} interface.
  66:  * This class provides some common functionality for all <code>Element</code>s,
  67:  * most notably it implements a locking mechanism to make document modification
  68:  * thread-safe.
  69:  *
  70:  * @author original author unknown
  71:  * @author Roman Kennke (roman@kennke.org)
  72:  */
  73: public abstract class AbstractDocument implements Document, Serializable
  74: {
  75:   /** The serialization UID (compatible with JDK1.5). */
  76:   private static final long serialVersionUID = 6842927725919637215L;
  77: 
  78:   /**
  79:    * Standard error message to indicate a bad location.
  80:    */
  81:   protected static final String BAD_LOCATION = "document location failure";
  82: 
  83:   /**
  84:    * Standard name for unidirectional <code>Element</code>s.
  85:    */
  86:   public static final String BidiElementName = "bidi level";
  87: 
  88:   /**
  89:    * Standard name for content <code>Element</code>s. These are usually
  90:    * {@link LeafElement}s.
  91:    */
  92:   public static final String ContentElementName = "content";
  93: 
  94:   /**
  95:    * Standard name for paragraph <code>Element</code>s. These are usually
  96:    * {@link BranchElement}s.
  97:    */
  98:   public static final String ParagraphElementName = "paragraph";
  99: 
 100:   /**
 101:    * Standard name for section <code>Element</code>s. These are usually
 102:    * {@link DefaultStyledDocument.SectionElement}s.
 103:    */
 104:   public static final String SectionElementName = "section";
 105: 
 106:   /**
 107:    * Attribute key for storing the element name.
 108:    */
 109:   public static final String ElementNameAttribute = "$ename";
 110: 
 111:   /**
 112:    * Standard name for the bidi root element.
 113:    */
 114:   private static final String BidiRootName = "bidi root";
 115: 
 116:   /**
 117:    * Key for storing the asynchronous load priority.
 118:    */
 119:   private static final String AsyncLoadPriority = "load priority";
 120: 
 121:   /**
 122:    * Key for storing the I18N state.
 123:    */
 124:   private static final String I18N = "i18n";
 125: 
 126:   /**
 127:    * The actual content model of this <code>Document</code>.
 128:    */
 129:   Content content;
 130: 
 131:   /**
 132:    * The AttributeContext for this <code>Document</code>.
 133:    */
 134:   AttributeContext context;
 135: 
 136:   /**
 137:    * The currently installed <code>DocumentFilter</code>.
 138:    */
 139:   DocumentFilter documentFilter;
 140: 
 141:   /**
 142:    * The documents properties.
 143:    */
 144:   Dictionary properties;
 145: 
 146:   /**
 147:    * Manages event listeners for this <code>Document</code>.
 148:    */
 149:   protected EventListenerList listenerList = new EventListenerList();
 150:   
 151:   /**
 152:    * Stores the current writer thread.  Used for locking.
 153:    */ 
 154:   private Thread currentWriter = null;
 155:   
 156:   /**
 157:    * The number of readers.  Used for locking.
 158:    */
 159:   private int numReaders = 0;
 160:   
 161:   /**
 162:    * The number of current writers. If this is > 1 then the same thread entered
 163:    * the write lock more than once.
 164:    */
 165:   private int numWriters = 0;  
 166: 
 167:   /** An instance of a DocumentFilter.FilterBypass which allows calling
 168:    * the insert, remove and replace method without checking for an installed
 169:    * document filter.
 170:    */
 171:   private DocumentFilter.FilterBypass bypass;
 172: 
 173:   /**
 174:    * The bidi root element.
 175:    */
 176:   private BidiRootElement bidiRoot;
 177: 
 178:   /**
 179:    * True when we are currently notifying any listeners. This is used
 180:    * to detect illegal situations in writeLock().
 181:    */
 182:   private transient boolean notifyListeners;
 183: 
 184:   /**
 185:    * Creates a new <code>AbstractDocument</code> with the specified
 186:    * {@link Content} model.
 187:    *
 188:    * @param doc the <code>Content</code> model to be used in this
 189:    *        <code>Document<code>
 190:    *
 191:    * @see GapContent
 192:    * @see StringContent
 193:    */
 194:   protected AbstractDocument(Content doc)
 195:   {
 196:     this(doc, StyleContext.getDefaultStyleContext());
 197:   }
 198: 
 199:   /**
 200:    * Creates a new <code>AbstractDocument</code> with the specified
 201:    * {@link Content} model and {@link AttributeContext}.
 202:    *
 203:    * @param doc the <code>Content</code> model to be used in this
 204:    *        <code>Document<code>
 205:    * @param ctx the <code>AttributeContext</code> to use
 206:    *
 207:    * @see GapContent
 208:    * @see StringContent
 209:    */
 210:   protected AbstractDocument(Content doc, AttributeContext ctx)
 211:   {
 212:     content = doc;
 213:     context = ctx;
 214: 
 215:     // FIXME: Fully implement bidi.
 216:     bidiRoot = new BidiRootElement();
 217: 
 218:     // FIXME: This is determined using a Mauve test. Make the document
 219:     // actually use this.
 220:     putProperty(I18N, Boolean.FALSE);
 221: 
 222:     // Add one child to the bidi root.
 223:     writeLock();
 224:     try
 225:       {
 226:         Element[] children = new Element[1];
 227:         children[0] = new BidiElement(bidiRoot, 0, 1, 0);
 228:         bidiRoot.replace(0, 0, children);
 229:       }
 230:     finally
 231:       {
 232:         writeUnlock();
 233:       }
 234:   }
 235:   
 236:   /** Returns the DocumentFilter.FilterBypass instance for this
 237:    * document and create it if it does not exist yet.
 238:    *  
 239:    * @return This document's DocumentFilter.FilterBypass instance.
 240:    */
 241:   private DocumentFilter.FilterBypass getBypass()
 242:   {
 243:     if (bypass == null)
 244:       bypass = new Bypass();
 245:     
 246:     return bypass;
 247:   }
 248: 
 249:   /**
 250:    * Returns the paragraph {@link Element} that holds the specified position.
 251:    *
 252:    * @param pos the position for which to get the paragraph element
 253:    *
 254:    * @return the paragraph {@link Element} that holds the specified position
 255:    */
 256:   public abstract Element getParagraphElement(int pos);
 257: 
 258:   /**
 259:    * Returns the default root {@link Element} of this <code>Document</code>.
 260:    * Usual <code>Document</code>s only have one root element and return this.
 261:    * However, there may be <code>Document</code> implementations that
 262:    * support multiple root elements, they have to return a default root element
 263:    * here.
 264:    *
 265:    * @return the default root {@link Element} of this <code>Document</code>
 266:    */
 267:   public abstract Element getDefaultRootElement();
 268: 
 269:   /**
 270:    * Creates and returns a branch element with the specified
 271:    * <code>parent</code> and <code>attributes</code>. Note that the new
 272:    * <code>Element</code> is linked to the parent <code>Element</code>
 273:    * through {@link Element#getParentElement}, but it is not yet added
 274:    * to the parent <code>Element</code> as child.
 275:    *
 276:    * @param parent the parent <code>Element</code> for the new branch element
 277:    * @param attributes the text attributes to be installed in the new element
 278:    *
 279:    * @return the new branch <code>Element</code>
 280:    *
 281:    * @see BranchElement
 282:    */
 283:   protected Element createBranchElement(Element parent,
 284:                     AttributeSet attributes)
 285:   {
 286:     return new BranchElement(parent, attributes);
 287:   }
 288: 
 289:   /**
 290:    * Creates and returns a leaf element with the specified
 291:    * <code>parent</code> and <code>attributes</code>. Note that the new
 292:    * <code>Element</code> is linked to the parent <code>Element</code>
 293:    * through {@link Element#getParentElement}, but it is not yet added
 294:    * to the parent <code>Element</code> as child.
 295:    *
 296:    * @param parent the parent <code>Element</code> for the new branch element
 297:    * @param attributes the text attributes to be installed in the new element
 298:    *
 299:    * @return the new branch <code>Element</code>
 300:    *
 301:    * @see LeafElement
 302:    */
 303:   protected Element createLeafElement(Element parent, AttributeSet attributes,
 304:                       int start, int end)
 305:   {
 306:     return new LeafElement(parent, attributes, start, end);
 307:   }
 308: 
 309:   /**
 310:    * Creates a {@link Position} that keeps track of the location at the
 311:    * specified <code>offset</code>.
 312:    *
 313:    * @param offset the location in the document to keep track by the new
 314:    *        <code>Position</code>
 315:    *
 316:    * @return the newly created <code>Position</code>
 317:    *
 318:    * @throws BadLocationException if <code>offset</code> is not a valid
 319:    *         location in the documents content model
 320:    */
 321:   public synchronized Position createPosition(final int offset)
 322:     throws BadLocationException
 323:   {
 324:     return content.createPosition(offset);
 325:   }
 326: 
 327:   /**
 328:    * Notifies all registered listeners when the document model changes.
 329:    *
 330:    * @param event the <code>DocumentEvent</code> to be fired
 331:    */
 332:   protected void fireChangedUpdate(DocumentEvent event)
 333:   {
 334:     notifyListeners = true;
 335:     try
 336:       {
 337:         DocumentListener[] listeners = getDocumentListeners();
 338:         for (int index = 0; index < listeners.length; ++index)
 339:           listeners[index].changedUpdate(event);
 340:       }
 341:     finally
 342:       {
 343:         notifyListeners = false;
 344:       }
 345:   }
 346: 
 347:   /**
 348:    * Notifies all registered listeners when content is inserted in the document
 349:    * model.
 350:    *
 351:    * @param event the <code>DocumentEvent</code> to be fired
 352:    */
 353:   protected void fireInsertUpdate(DocumentEvent event)
 354:   {
 355:     notifyListeners = true;
 356:     try
 357:       {
 358:         DocumentListener[] listeners = getDocumentListeners();
 359:         for (int index = 0; index < listeners.length; ++index)
 360:           listeners[index].insertUpdate(event);
 361:       }
 362:     finally
 363:       {
 364:         notifyListeners = false;
 365:       }
 366:   }
 367: 
 368:   /**
 369:    * Notifies all registered listeners when content is removed from the
 370:    * document model.
 371:    *
 372:    * @param event the <code>DocumentEvent</code> to be fired
 373:    */
 374:   protected void fireRemoveUpdate(DocumentEvent event)
 375:   {
 376:     notifyListeners = true;
 377:     try
 378:       {
 379:         DocumentListener[] listeners = getDocumentListeners();
 380:         for (int index = 0; index < listeners.length; ++index)
 381:           listeners[index].removeUpdate(event);
 382:       }
 383:     finally
 384:       {
 385:         notifyListeners = false;
 386:       }
 387:   }
 388: 
 389:   /**
 390:    * Notifies all registered listeners when an <code>UndoableEdit</code> has
 391:    * been performed on this <code>Document</code>.
 392:    *
 393:    * @param event the <code>UndoableEditEvent</code> to be fired
 394:    */
 395:   protected void fireUndoableEditUpdate(UndoableEditEvent event)
 396:   {
 397:     UndoableEditListener[] listeners = getUndoableEditListeners();
 398: 
 399:     for (int index = 0; index < listeners.length; ++index)
 400:       listeners[index].undoableEditHappened(event);
 401:   }
 402: 
 403:   /**
 404:    * Returns the asynchronous loading priority. Returns <code>-1</code> if this
 405:    * document should not be loaded asynchronously.
 406:    *
 407:    * @return the asynchronous loading priority
 408:    */
 409:   public int getAsynchronousLoadPriority()
 410:   {
 411:     Object val = getProperty(AsyncLoadPriority);
 412:     int prio = -1;
 413:     if (val != null)
 414:       prio = ((Integer) val).intValue(); 
 415:     return prio;
 416:   }
 417: 
 418:   /**
 419:    * Returns the {@link AttributeContext} used in this <code>Document</code>.
 420:    *
 421:    * @return the {@link AttributeContext} used in this <code>Document</code>
 422:    */
 423:   protected final AttributeContext getAttributeContext()
 424:   {
 425:     return context;
 426:   }
 427: 
 428:   /**
 429:    * Returns the root element for bidirectional content.
 430:    *
 431:    * @return the root element for bidirectional content
 432:    */
 433:   public Element getBidiRootElement()
 434:   {
 435:     return bidiRoot;
 436:   }
 437: 
 438:   /**
 439:    * Returns the {@link Content} model for this <code>Document</code>
 440:    *
 441:    * @return the {@link Content} model for this <code>Document</code>
 442:    *
 443:    * @see GapContent
 444:    * @see StringContent
 445:    */
 446:   protected final Content getContent()
 447:   {
 448:     return content;
 449:   }
 450: 
 451:   /**
 452:    * Returns the thread that currently modifies this <code>Document</code>
 453:    * if there is one, otherwise <code>null</code>. This can be used to
 454:    * distinguish between a method call that is part of an ongoing modification
 455:    * or if it is a separate modification for which a new lock must be aquired.
 456:    *
 457:    * @return the thread that currently modifies this <code>Document</code>
 458:    *         if there is one, otherwise <code>null</code>
 459:    */
 460:   protected final synchronized Thread getCurrentWriter()
 461:   {
 462:     return currentWriter;
 463:   }
 464: 
 465:   /**
 466:    * Returns the properties of this <code>Document</code>.
 467:    *
 468:    * @return the properties of this <code>Document</code>
 469:    */
 470:   public Dictionary<Object, Object> getDocumentProperties()
 471:   {
 472:     // FIXME: make me thread-safe
 473:     if (properties == null)
 474:       properties = new Hashtable();
 475: 
 476:     return properties;
 477:   }
 478: 
 479:   /**
 480:    * Returns a {@link Position} which will always mark the end of the
 481:    * <code>Document</code>.
 482:    *
 483:    * @return a {@link Position} which will always mark the end of the
 484:    *         <code>Document</code>
 485:    */
 486:   public final Position getEndPosition()
 487:   {
 488:     Position p;
 489:     try
 490:       {
 491:         p = createPosition(content.length());
 492:       }
 493:     catch (BadLocationException ex)
 494:       {
 495:         // Shouldn't really happen.
 496:         p = null;
 497:       }
 498:     return p;
 499:   }
 500: 
 501:   /**
 502:    * Returns the length of this <code>Document</code>'s content.
 503:    *
 504:    * @return the length of this <code>Document</code>'s content
 505:    */
 506:   public int getLength()
 507:   {
 508:     // We return Content.getLength() -1 here because there is always an
 509:     // implicit \n at the end of the Content which does count in Content
 510:     // but not in Document.
 511:     return content.length() - 1;
 512:   }
 513: 
 514:   /**
 515:    * Returns all registered listeners of a given listener type.
 516:    *
 517:    * @param listenerType the type of the listeners to be queried
 518:    *
 519:    * @return all registered listeners of the specified type
 520:    */
 521:   public <T extends EventListener> T[] getListeners(Class<T> listenerType)
 522:   {
 523:     return listenerList.getListeners(listenerType);
 524:   }
 525: 
 526:   /**
 527:    * Returns a property from this <code>Document</code>'s property list.
 528:    *
 529:    * @param key the key of the property to be fetched
 530:    *
 531:    * @return the property for <code>key</code> or <code>null</code> if there
 532:    *         is no such property stored
 533:    */
 534:   public final Object getProperty(Object key)
 535:   {
 536:     // FIXME: make me thread-safe
 537:     Object value = null;
 538:     if (properties != null)
 539:       value = properties.get(key);
 540: 
 541:     return value;
 542:   }
 543: 
 544:   /**
 545:    * Returns all root elements of this <code>Document</code>. By default
 546:    * this just returns the single root element returned by
 547:    * {@link #getDefaultRootElement()}. <code>Document</code> implementations
 548:    * that support multiple roots must override this method and return all roots
 549:    * here.
 550:    *
 551:    * @return all root elements of this <code>Document</code>
 552:    */
 553:   public Element[] getRootElements()
 554:   {
 555:     Element[] elements = new Element[2];
 556:     elements[0] = getDefaultRootElement();
 557:     elements[1] = getBidiRootElement();
 558:     return elements;
 559:   }
 560: 
 561:   /**
 562:    * Returns a {@link Position} which will always mark the beginning of the
 563:    * <code>Document</code>.
 564:    *
 565:    * @return a {@link Position} which will always mark the beginning of the
 566:    *         <code>Document</code>
 567:    */
 568:   public final Position getStartPosition()
 569:   {
 570:     Position p;
 571:     try
 572:       {
 573:         p = createPosition(0);
 574:       }
 575:     catch (BadLocationException ex)
 576:       {
 577:         // Shouldn't really happen.
 578:         p = null;
 579:       }
 580:     return p;
 581:   }
 582: 
 583:   /**
 584:    * Returns a piece of this <code>Document</code>'s content.
 585:    *
 586:    * @param offset the start offset of the content
 587:    * @param length the length of the content
 588:    *
 589:    * @return the piece of content specified by <code>offset</code> and
 590:    *         <code>length</code>
 591:    *
 592:    * @throws BadLocationException if <code>offset</code> or <code>offset +
 593:    *         length</code> are invalid locations with this
 594:    *         <code>Document</code>
 595:    */
 596:   public String getText(int offset, int length) throws BadLocationException
 597:   {
 598:     return content.getString(offset, length);
 599:   }
 600: 
 601:   /**
 602:    * Fetches a piece of this <code>Document</code>'s content and stores
 603:    * it in the given {@link Segment}.
 604:    *
 605:    * @param offset the start offset of the content
 606:    * @param length the length of the content
 607:    * @param segment the <code>Segment</code> to store the content in
 608:    *
 609:    * @throws BadLocationException if <code>offset</code> or <code>offset +
 610:    *         length</code> are invalid locations with this
 611:    *         <code>Document</code>
 612:    */
 613:   public void getText(int offset, int length, Segment segment)
 614:     throws BadLocationException
 615:   {
 616:     content.getChars(offset, length, segment);
 617:   }
 618: 
 619:   /**
 620:    * Inserts a String into this <code>Document</code> at the specified
 621:    * position and assigning the specified attributes to it.
 622:    * 
 623:    * <p>If a {@link DocumentFilter} is installed in this document, the
 624:    * corresponding method of the filter object is called.</p>
 625:    * 
 626:    * <p>The method has no effect when <code>text</code> is <code>null</code>
 627:    * or has a length of zero.</p>
 628:    * 
 629:    *
 630:    * @param offset the location at which the string should be inserted
 631:    * @param text the content to be inserted
 632:    * @param attributes the text attributes to be assigned to that string
 633:    *
 634:    * @throws BadLocationException if <code>offset</code> is not a valid
 635:    *         location in this <code>Document</code>
 636:    */
 637:   public void insertString(int offset, String text, AttributeSet attributes)
 638:     throws BadLocationException
 639:   {
 640:     // Bail out if we have a bogus insertion (Behavior observed in RI).
 641:     if (text == null || text.length() == 0)
 642:       return;
 643: 
 644:     writeLock();
 645:     try
 646:       {
 647:         if (documentFilter == null)
 648:           insertStringImpl(offset, text, attributes);
 649:         else
 650:           documentFilter.insertString(getBypass(), offset, text, attributes);
 651:       }
 652:     finally
 653:       {
 654:         writeUnlock();
 655:       }
 656:   }
 657: 
 658:   void insertStringImpl(int offset, String text, AttributeSet attributes)
 659:     throws BadLocationException
 660:   {
 661:     // Just return when no text to insert was given.
 662:     if (text == null || text.length() == 0)
 663:       return;
 664:     DefaultDocumentEvent event =
 665:       new DefaultDocumentEvent(offset, text.length(),
 666:                    DocumentEvent.EventType.INSERT);
 667: 
 668:     UndoableEdit undo = content.insertString(offset, text);
 669:     if (undo != null)
 670:       event.addEdit(undo);
 671: 
 672:     // Check if we need bidi layout.
 673:     if (getProperty(I18N).equals(Boolean.FALSE))
 674:       {
 675:         Object dir = getProperty(TextAttribute.RUN_DIRECTION);
 676:         if (TextAttribute.RUN_DIRECTION_RTL.equals(dir))
 677:           putProperty(I18N, Boolean.TRUE);
 678:         else
 679:           {
 680:             char[] chars = text.toCharArray();
 681:             if (Bidi.requiresBidi(chars, 0, chars.length))
 682:               putProperty(I18N, Boolean.TRUE);
 683:           }
 684:       }
 685: 
 686:     insertUpdate(event, attributes);
 687: 
 688:     fireInsertUpdate(event);
 689: 
 690:     if (undo != null)
 691:       fireUndoableEditUpdate(new UndoableEditEvent(this, undo));
 692:   }
 693: 
 694:   /**
 695:    * Called to indicate that text has been inserted into this
 696:    * <code>Document</code>. The default implementation does nothing.
 697:    * This method is executed within a write lock.
 698:    *
 699:    * @param chng the <code>DefaultDocumentEvent</code> describing the change
 700:    * @param attr the attributes of the changed content
 701:    */
 702:   protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr)
 703:   {
 704:     if (Boolean.TRUE.equals(getProperty(I18N)))
 705:       updateBidi(chng);
 706:   }
 707: 
 708:   /**
 709:    * Called after some content has been removed from this
 710:    * <code>Document</code>. The default implementation does nothing.
 711:    * This method is executed within a write lock.
 712:    *
 713:    * @param chng the <code>DefaultDocumentEvent</code> describing the change
 714:    */
 715:   protected void postRemoveUpdate(DefaultDocumentEvent chng)
 716:   {
 717:     if (Boolean.TRUE.equals(getProperty(I18N)))
 718:       updateBidi(chng);
 719:   }
 720: 
 721:   /**
 722:    * Stores a property in this <code>Document</code>'s property list.
 723:    *
 724:    * @param key the key of the property to be stored
 725:    * @param value the value of the property to be stored
 726:    */
 727:   public final void putProperty(Object key, Object value)
 728:   {
 729:     // FIXME: make me thread-safe
 730:     if (properties == null)
 731:       properties = new Hashtable();
 732: 
 733:     if (value == null)
 734:       properties.remove(key);
 735:     else
 736:       properties.put(key, value);
 737: 
 738:     // Update bidi structure if the RUN_DIRECTION is set.
 739:     if (TextAttribute.RUN_DIRECTION.equals(key))
 740:       {
 741:         if (TextAttribute.RUN_DIRECTION_RTL.equals(value)
 742:             && Boolean.FALSE.equals(getProperty(I18N)))
 743:           putProperty(I18N, Boolean.TRUE);
 744: 
 745:         if (Boolean.TRUE.equals(getProperty(I18N)))
 746:           {
 747:             writeLock();
 748:             try
 749:               {
 750:                 DefaultDocumentEvent ev =
 751:                   new DefaultDocumentEvent(0, getLength(),
 752:                                            DocumentEvent.EventType.INSERT);
 753:                 updateBidi(ev);
 754:               }
 755:             finally
 756:               {
 757:                 writeUnlock();
 758:               }
 759:           }
 760:       }
 761:   }
 762: 
 763:   /**
 764:    * Updates the bidi element structure.
 765:    *
 766:    * @param ev the document event for the change
 767:    */
 768:   private void updateBidi(DefaultDocumentEvent ev)
 769:   {
 770:     // Determine start and end offset of the paragraphs to be scanned.
 771:     int start = 0;
 772:     int end = 0;
 773:     DocumentEvent.EventType type = ev.getType();
 774:     if (type == DocumentEvent.EventType.INSERT
 775:         || type == DocumentEvent.EventType.CHANGE)
 776:       {
 777:         int offs = ev.getOffset();
 778:         int endOffs = offs + ev.getLength();
 779:         start = getParagraphElement(offs).getStartOffset();
 780:         end = getParagraphElement(endOffs).getEndOffset();
 781:       }
 782:     else if (type == DocumentEvent.EventType.REMOVE)
 783:       {
 784:         Element par = getParagraphElement(ev.getOffset());
 785:         start = par.getStartOffset();
 786:         end = par.getEndOffset();
 787:       }
 788:     else
 789:       assert false : "Unknown event type";
 790: 
 791:     // Determine the bidi levels for the affected range.
 792:     Bidi[] bidis = getBidis(start, end);
 793: 
 794:     int removeFrom = 0;
 795:     int removeTo = 0;
 796: 
 797:     int offs = 0;
 798:     int lastRunStart = 0;
 799:     int lastRunEnd = 0;
 800:     int lastRunLevel = 0;
 801:     ArrayList newEls = new ArrayList();
 802:     for (int i = 0; i < bidis.length; i++)
 803:       {
 804:         Bidi bidi = bidis[i];
 805:         int numRuns = bidi.getRunCount();
 806:         for (int r = 0; r < numRuns; r++)
 807:           {
 808:             if (r == 0 && i == 0)
 809:               {
 810:                 if (start > 0)
 811:                   {
 812:                     // Try to merge with the previous element if it has the
 813:                     // same bidi level as the first run.
 814:                     int prevElIndex = bidiRoot.getElementIndex(start - 1);
 815:                     removeFrom = prevElIndex;
 816:                     Element prevEl = bidiRoot.getElement(prevElIndex);
 817:                     AttributeSet atts = prevEl.getAttributes();
 818:                     int prevElLevel = StyleConstants.getBidiLevel(atts);
 819:                     if (prevElLevel == bidi.getRunLevel(r))
 820:                       {
 821:                         // Merge previous element with current run.
 822:                         lastRunStart = prevEl.getStartOffset() - start;
 823:                         lastRunEnd = bidi.getRunLimit(r);
 824:                         lastRunLevel  = bidi.getRunLevel(r);
 825:                       }
 826:                     else if (prevEl.getEndOffset() > start)
 827:                       {
 828:                         // Split previous element and replace by 2 new elements.
 829:                         lastRunStart = 0;
 830:                         lastRunEnd = bidi.getRunLimit(r);
 831:                         lastRunLevel = bidi.getRunLevel(r);
 832:                         newEls.add(new BidiElement(bidiRoot,
 833:                                                    prevEl.getStartOffset(),
 834:                                                    start, prevElLevel));
 835:                       }
 836:                     else
 837:                       {
 838:                         // Simply start new run at start location.
 839:                         lastRunStart = 0;
 840:                         lastRunEnd = bidi.getRunLimit(r);
 841:                         lastRunLevel = bidi.getRunLevel(r);
 842:                         removeFrom++;
 843:                       }
 844:                   }
 845:                 else
 846:                   {
 847:                     // Simply start new run at start location.
 848:                     lastRunStart = 0;
 849:                     lastRunEnd = bidi.getRunLimit(r);
 850:                     lastRunLevel = bidi.getRunLevel(r);
 851:                     removeFrom = 0;
 852:                   }
 853:               }
 854:             if (i == bidis.length - 1 && r == numRuns - 1)
 855:               {
 856:                 if (end <= getLength())
 857:                   {
 858:                     // Try to merge last element with next element.
 859:                     int nextIndex = bidiRoot.getElementIndex(end);
 860:                     Element nextEl = bidiRoot.getElement(nextIndex);
 861:                     AttributeSet atts = nextEl.getAttributes();
 862:                     int nextLevel = StyleConstants.getBidiLevel(atts);
 863:                     int level = bidi.getRunLevel(r);
 864:                     if (lastRunLevel == level && level == nextLevel)
 865:                       {
 866:                         // Merge runs together.
 867:                         if (lastRunStart + start == nextEl.getStartOffset())
 868:                           removeTo = nextIndex - 1;
 869:                         else
 870:                           {
 871:                             newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
 872:                                                        nextEl.getEndOffset(), level));
 873:                             removeTo = nextIndex;
 874:                           }
 875:                       }
 876:                     else if (lastRunLevel == level)
 877:                       {
 878:                         // Merge current and last run.
 879:                         int endOffs = offs + bidi.getRunLimit(r);
 880:                         newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
 881: