OSDN Git Service

Merged gcj-eclipse branch to trunk.
[pf3gnuchains/gcc-fork.git] / libjava / classpath / javax / swing / text / AbstractDocument.java
index eb46a8c..eead8de 100644 (file)
@@ -38,11 +38,15 @@ exception statement from your version. */
 
 package javax.swing.text;
 
+import java.awt.font.TextAttribute;
 import java.io.PrintStream;
 import java.io.Serializable;
+import java.text.Bidi;
+import java.util.ArrayList;
 import java.util.Dictionary;
 import java.util.Enumeration;
 import java.util.EventListener;
+import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.Vector;
 
@@ -105,6 +109,21 @@ public abstract class AbstractDocument implements Document, Serializable
   public static final String ElementNameAttribute = "$ename";
 
   /**
+   * Standard name for the bidi root element.
+   */
+  private static final String BidiRootName = "bidi root";
+
+  /**
+   * Key for storing the asynchronous load priority.
+   */
+  private static final String AsyncLoadPriority = "load priority";
+
+  /**
+   * Key for storing the I18N state.
+   */
+  private static final String I18N = "i18n";
+
+  /**
    * The actual content model of this <code>Document</code>.
    */
   Content content;
@@ -140,14 +159,10 @@ public abstract class AbstractDocument implements Document, Serializable
   private int numReaders = 0;
   
   /**
-   * Tells if there are one or more writers waiting.
+   * The number of current writers. If this is > 1 then the same thread entered
+   * the write lock more than once.
    */
-  private int numWritersWaiting = 0;  
-
-  /**
-   * A condition variable that readers and writers wait on.
-   */
-  private Object documentCV = new Object();
+  private int numWriters = 0;  
 
   /** An instance of a DocumentFilter.FilterBypass which allows calling
    * the insert, remove and replace method without checking for an installed
@@ -158,7 +173,13 @@ public abstract class AbstractDocument implements Document, Serializable
   /**
    * The bidi root element.
    */
-  private Element bidiRoot;
+  private BidiRootElement bidiRoot;
+
+  /**
+   * True when we are currently notifying any listeners. This is used
+   * to detect illegal situations in writeLock().
+   */
+  private transient boolean notifyListeners;
 
   /**
    * Creates a new <code>AbstractDocument</code> with the specified
@@ -191,12 +212,25 @@ public abstract class AbstractDocument implements Document, Serializable
     content = doc;
     context = ctx;
 
+    // FIXME: Fully implement bidi.
+    bidiRoot = new BidiRootElement();
+
     // FIXME: This is determined using a Mauve test. Make the document
     // actually use this.
-    putProperty("i18n", Boolean.FALSE);
+    putProperty(I18N, Boolean.FALSE);
 
-    // FIXME: Fully implement bidi.
-    bidiRoot = new BranchElement(null, null);
+    // Add one child to the bidi root.
+    writeLock();
+    try
+      {
+        Element[] children = new Element[1];
+        children[0] = new BidiElement(bidiRoot, 0, 1, 0);
+        bidiRoot.replace(0, 0, children);
+      }
+    finally
+      {
+        writeUnlock();
+      }
   }
   
   /** Returns the DocumentFilter.FilterBypass instance for this
@@ -284,7 +318,8 @@ public abstract class AbstractDocument implements Document, Serializable
    * @throws BadLocationException if <code>offset</code> is not a valid
    *         location in the documents content model
    */
-  public Position createPosition(final int offset) throws BadLocationException
+  public synchronized Position createPosition(final int offset)
+    throws BadLocationException
   {
     return content.createPosition(offset);
   }
@@ -296,10 +331,17 @@ public abstract class AbstractDocument implements Document, Serializable
    */
   protected void fireChangedUpdate(DocumentEvent event)
   {
-    DocumentListener[] listeners = getDocumentListeners();
-
-    for (int index = 0; index < listeners.length; ++index)
-      listeners[index].changedUpdate(event);
+    notifyListeners = true;
+    try
+      {
+        DocumentListener[] listeners = getDocumentListeners();
+        for (int index = 0; index < listeners.length; ++index)
+          listeners[index].changedUpdate(event);
+      }
+    finally
+      {
+        notifyListeners = false;
+      }
   }
 
   /**
@@ -310,10 +352,17 @@ public abstract class AbstractDocument implements Document, Serializable
    */
   protected void fireInsertUpdate(DocumentEvent event)
   {
-    DocumentListener[] listeners = getDocumentListeners();
-
-    for (int index = 0; index < listeners.length; ++index)
-      listeners[index].insertUpdate(event);
+    notifyListeners = true;
+    try
+      {
+        DocumentListener[] listeners = getDocumentListeners();
+        for (int index = 0; index < listeners.length; ++index)
+          listeners[index].insertUpdate(event);
+      }
+    finally
+      {
+        notifyListeners = false;
+      }
   }
 
   /**
@@ -324,10 +373,17 @@ public abstract class AbstractDocument implements Document, Serializable
    */
   protected void fireRemoveUpdate(DocumentEvent event)
   {
-    DocumentListener[] listeners = getDocumentListeners();
-
-    for (int index = 0; index < listeners.length; ++index)
-      listeners[index].removeUpdate(event);
+    notifyListeners = true;
+    try
+      {
+        DocumentListener[] listeners = getDocumentListeners();
+        for (int index = 0; index < listeners.length; ++index)
+          listeners[index].removeUpdate(event);
+      }
+    finally
+      {
+        notifyListeners = false;
+      }
   }
 
   /**
@@ -352,7 +408,11 @@ public abstract class AbstractDocument implements Document, Serializable
    */
   public int getAsynchronousLoadPriority()
   {
-    return 0;
+    Object val = getProperty(AsyncLoadPriority);
+    int prio = -1;
+    if (val != null)
+      prio = ((Integer) val).intValue(); 
+    return prio;
   }
 
   /**
@@ -397,7 +457,7 @@ public abstract class AbstractDocument implements Document, Serializable
    * @return the thread that currently modifies this <code>Document</code>
    *         if there is one, otherwise <code>null</code>
    */
-  protected final Thread getCurrentWriter()
+  protected final synchronized Thread getCurrentWriter()
   {
     return currentWriter;
   }
@@ -407,7 +467,7 @@ public abstract class AbstractDocument implements Document, Serializable
    *
    * @return the properties of this <code>Document</code>
    */
-  public Dictionary getDocumentProperties()
+  public Dictionary<Object, Object> getDocumentProperties()
   {
     // FIXME: make me thread-safe
     if (properties == null)
@@ -425,14 +485,17 @@ public abstract class AbstractDocument implements Document, Serializable
    */
   public final Position getEndPosition()
   {
-    // FIXME: Properly implement this by calling Content.createPosition().
-    return new Position() 
-      {        
-        public int getOffset() 
-        { 
-          return getLength(); 
-        } 
-      };
+    Position p;
+    try
+      {
+        p = createPosition(content.length());
+      }
+    catch (BadLocationException ex)
+      {
+        // Shouldn't really happen.
+        p = null;
+      }
+    return p;
   }
 
   /**
@@ -455,7 +518,7 @@ public abstract class AbstractDocument implements Document, Serializable
    *
    * @return all registered listeners of the specified type
    */
-  public EventListener[] getListeners(Class listenerType)
+  public <T extends EventListener> T[] getListeners(Class<T> listenerType)
   {
     return listenerList.getListeners(listenerType);
   }
@@ -504,14 +567,17 @@ public abstract class AbstractDocument implements Document, Serializable
    */
   public final Position getStartPosition()
   {
-    // FIXME: Properly implement this using Content.createPosition().
-    return new Position() 
-      {        
-        public int getOffset() 
-        { 
-          return 0; 
-        } 
-      };
+    Position p;
+    try
+      {
+        p = createPosition(0);
+      }
+    catch (BadLocationException ex)
+      {
+        // Shouldn't really happen.
+        p = null;
+      }
+    return p;
   }
 
   /**
@@ -574,11 +640,19 @@ public abstract class AbstractDocument implements Document, Serializable
     // Bail out if we have a bogus insertion (Behavior observed in RI).
     if (text == null || text.length() == 0)
       return;
-    
-    if (documentFilter == null)
-      insertStringImpl(offset, text, attributes);
-    else
-      documentFilter.insertString(getBypass(), offset, text, attributes);
+
+    writeLock();
+    try
+      {
+        if (documentFilter == null)
+          insertStringImpl(offset, text, attributes);
+        else
+          documentFilter.insertString(getBypass(), offset, text, attributes);
+      }
+    finally
+      {
+        writeUnlock();
+      }
   }
 
   void insertStringImpl(int offset, String text, AttributeSet attributes)
@@ -591,23 +665,30 @@ public abstract class AbstractDocument implements Document, Serializable
       new DefaultDocumentEvent(offset, text.length(),
                               DocumentEvent.EventType.INSERT);
 
-    try
-      {
-        writeLock();
-        UndoableEdit undo = content.insertString(offset, text);
-        if (undo != null)
-          event.addEdit(undo);
-
-        insertUpdate(event, attributes);
+    UndoableEdit undo = content.insertString(offset, text);
+    if (undo != null)
+      event.addEdit(undo);
 
-        fireInsertUpdate(event);
-        if (undo != null)
-          fireUndoableEditUpdate(new UndoableEditEvent(this, undo));
-      }
-    finally
+    // Check if we need bidi layout.
+    if (getProperty(I18N).equals(Boolean.FALSE))
       {
-        writeUnlock();
+        Object dir = getProperty(TextAttribute.RUN_DIRECTION);
+        if (TextAttribute.RUN_DIRECTION_RTL.equals(dir))
+          putProperty(I18N, Boolean.TRUE);
+        else
+          {
+            char[] chars = text.toCharArray();
+            if (Bidi.requiresBidi(chars, 0, chars.length))
+              putProperty(I18N, Boolean.TRUE);
+          }
       }
+
+    insertUpdate(event, attributes);
+
+    fireInsertUpdate(event);
+
+    if (undo != null)
+      fireUndoableEditUpdate(new UndoableEditEvent(this, undo));
   }
 
   /**
@@ -620,7 +701,8 @@ public abstract class AbstractDocument implements Document, Serializable
    */
   protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr)
   {
-    // Do nothing here. Subclasses may want to override this.
+    if (Boolean.TRUE.equals(getProperty(I18N)))
+      updateBidi(chng);
   }
 
   /**
@@ -632,7 +714,8 @@ public abstract class AbstractDocument implements Document, Serializable
    */
   protected void postRemoveUpdate(DefaultDocumentEvent chng)
   {
-    // Do nothing here. Subclasses may want to override this.
+    if (Boolean.TRUE.equals(getProperty(I18N)))
+      updateBidi(chng);
   }
 
   /**
@@ -647,31 +730,338 @@ public abstract class AbstractDocument implements Document, Serializable
     if (properties == null)
       properties = new Hashtable();
 
-    properties.put(key, value);
+    if (value == null)
+      properties.remove(key);
+    else
+      properties.put(key, value);
+
+    // Update bidi structure if the RUN_DIRECTION is set.
+    if (TextAttribute.RUN_DIRECTION.equals(key))
+      {
+        if (TextAttribute.RUN_DIRECTION_RTL.equals(value)
+            && Boolean.FALSE.equals(getProperty(I18N)))
+          putProperty(I18N, Boolean.TRUE);
+
+        if (Boolean.TRUE.equals(getProperty(I18N)))
+          {
+            writeLock();
+            try
+              {
+                DefaultDocumentEvent ev =
+                  new DefaultDocumentEvent(0, getLength(),
+                                           DocumentEvent.EventType.INSERT);
+                updateBidi(ev);
+              }
+            finally
+              {
+                writeUnlock();
+              }
+          }
+      }
   }
 
   /**
-   * Blocks until a read lock can be obtained.  Must block if there is
-   * currently a writer modifying the <code>Document</code>.
+   * Updates the bidi element structure.
+   *
+   * @param ev the document event for the change
    */
-  public final void readLock()
+  private void updateBidi(DefaultDocumentEvent ev)
   {
-    if (currentWriter != null && currentWriter.equals(Thread.currentThread()))
-      return;
-    synchronized (documentCV)
+    // Determine start and end offset of the paragraphs to be scanned.
+    int start = 0;
+    int end = 0;
+    DocumentEvent.EventType type = ev.getType();
+    if (type == DocumentEvent.EventType.INSERT
+        || type == DocumentEvent.EventType.CHANGE)
+      {
+        int offs = ev.getOffset();
+        int endOffs = offs + ev.getLength();
+        start = getParagraphElement(offs).getStartOffset();
+        end = getParagraphElement(endOffs).getEndOffset();
+      }
+    else if (type == DocumentEvent.EventType.REMOVE)
       {
-        while (currentWriter != null || numWritersWaiting > 0)
+        Element par = getParagraphElement(ev.getOffset());
+        start = par.getStartOffset();
+        end = par.getEndOffset();
+      }
+    else
+      assert false : "Unknown event type";
+
+    // Determine the bidi levels for the affected range.
+    Bidi[] bidis = getBidis(start, end);
+
+    int removeFrom = 0;
+    int removeTo = 0;
+
+    int offs = 0;
+    int lastRunStart = 0;
+    int lastRunEnd = 0;
+    int lastRunLevel = 0;
+    ArrayList newEls = new ArrayList();
+    for (int i = 0; i < bidis.length; i++)
+      {
+        Bidi bidi = bidis[i];
+        int numRuns = bidi.getRunCount();
+        for (int r = 0; r < numRuns; r++)
           {
-            try
+            if (r == 0 && i == 0)
+              {
+                if (start > 0)
+                  {
+                    // Try to merge with the previous element if it has the
+                    // same bidi level as the first run.
+                    int prevElIndex = bidiRoot.getElementIndex(start - 1);
+                    removeFrom = prevElIndex;
+                    Element prevEl = bidiRoot.getElement(prevElIndex);
+                    AttributeSet atts = prevEl.getAttributes();
+                    int prevElLevel = StyleConstants.getBidiLevel(atts);
+                    if (prevElLevel == bidi.getRunLevel(r))
+                      {
+                        // Merge previous element with current run.
+                        lastRunStart = prevEl.getStartOffset() - start;
+                        lastRunEnd = bidi.getRunLimit(r);
+                        lastRunLevel  = bidi.getRunLevel(r);
+                      }
+                    else if (prevEl.getEndOffset() > start)
+                      {
+                        // Split previous element and replace by 2 new elements.
+                        lastRunStart = 0;
+                        lastRunEnd = bidi.getRunLimit(r);
+                        lastRunLevel = bidi.getRunLevel(r);
+                        newEls.add(new BidiElement(bidiRoot,
+                                                   prevEl.getStartOffset(),
+                                                   start, prevElLevel));
+                      }
+                    else
+                      {
+                        // Simply start new run at start location.
+                        lastRunStart = 0;
+                        lastRunEnd = bidi.getRunLimit(r);
+                        lastRunLevel = bidi.getRunLevel(r);
+                        removeFrom++;
+                      }
+                  }
+                else
+                  {
+                    // Simply start new run at start location.
+                    lastRunStart = 0;
+                    lastRunEnd = bidi.getRunLimit(r);
+                    lastRunLevel = bidi.getRunLevel(r);
+                    removeFrom = 0;
+                  }
+              }
+            if (i == bidis.length - 1 && r == numRuns - 1)
               {
-                documentCV.wait();
+                if (end <= getLength())
+                  {
+                    // Try to merge last element with next element.
+                    int nextIndex = bidiRoot.getElementIndex(end);
+                    Element nextEl = bidiRoot.getElement(nextIndex);
+                    AttributeSet atts = nextEl.getAttributes();
+                    int nextLevel = StyleConstants.getBidiLevel(atts);
+                    int level = bidi.getRunLevel(r);
+                    if (lastRunLevel == level && level == nextLevel)
+                      {
+                        // Merge runs together.
+                        if (lastRunStart + start == nextEl.getStartOffset())
+                          removeTo = nextIndex - 1;
+                        else
+                          {
+                            newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
+                                                       nextEl.getEndOffset(), level));
+                            removeTo = nextIndex;
+                          }
+                      }
+                    else if (lastRunLevel == level)
+                      {
+                        // Merge current and last run.
+                        int endOffs = offs + bidi.getRunLimit(r);
+                        newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
+                                                   start + endOffs, level));
+                        if (start + endOffs == nextEl.getStartOffset())
+                          removeTo = nextIndex - 1;
+                        else
+                          {
+                            newEls.add(new BidiElement(bidiRoot, start + endOffs,
+                                                       nextEl.getEndOffset(),
+                                                       nextLevel));
+                            removeTo = nextIndex;
+                          }
+                      }
+                    else if (level == nextLevel)
+                      {
+                        // Merge current and next run.
+                        newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
+                                                   start + lastRunEnd,
+                                                   lastRunLevel));
+                        newEls.add(new BidiElement(bidiRoot, start + lastRunEnd,
+                                                   nextEl.getEndOffset(), level));
+                        removeTo = nextIndex;
+                      }
+                    else
+                      {
+                        // Split next element.
+                        int endOffs = offs + bidi.getRunLimit(r);
+                        newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
+                                                   start + lastRunEnd,
+                                                   lastRunLevel));
+                        newEls.add(new BidiElement(bidiRoot, start + lastRunEnd,
+                                                   start + endOffs, level));
+                        newEls.add(new BidiElement(bidiRoot, start + endOffs,
+                                                   nextEl.getEndOffset(),
+                                                   nextLevel));
+                        removeTo = nextIndex;
+                      }
+                  }
+                else
+                  {
+                    removeTo = bidiRoot.getElementIndex(end);
+                    int level = bidi.getRunLevel(r);
+                    int runEnd = offs + bidi.getRunLimit(r);
+
+                    if (level == lastRunLevel)
+                      {
+                        // Merge with previous.
+                        lastRunEnd = offs + runEnd;
+                        newEls.add(new BidiElement(bidiRoot,
+                                                  start + lastRunStart,
+                                                  start + runEnd, level));
+                      }
+                    else
+                      {
+                        // Create element for last run and current run.
+                        newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
+                                                   start + lastRunEnd,
+                                                   lastRunLevel));
+                        newEls.add(new BidiElement(bidiRoot,
+                                                   start + lastRunEnd,
+                                                   start + runEnd,
+                                                   level));
+                       }
+                  }
               }
-            catch (InterruptedException ie)
+            else
               {
-                throw new Error("interrupted trying to get a readLock");
+                int level = bidi.getRunLevel(r);
+                int runEnd = bidi.getRunLimit(r);
+
+                if (level == lastRunLevel)
+                  {
+                    // Merge with previous.
+                    lastRunEnd = offs + runEnd;
+                  }
+                else
+                  {
+                    // Create element for last run and update values for
+                    // current run.
+                    newEls.add(new BidiElement(bidiRoot, start + lastRunStart,
+                                               start + lastRunEnd,
+                                               lastRunLevel));
+                    lastRunStart = lastRunEnd;
+                    lastRunEnd = offs + runEnd;
+                    lastRunLevel = level;
+                  }
               }
           }
-          numReaders++;
+        offs += bidi.getLength();
+      }
+
+    // Determine the bidi elements which are to be removed.
+    int numRemoved = 0;
+    if (bidiRoot.getElementCount() > 0)
+      numRemoved = removeTo - removeFrom + 1;
+    Element[] removed = new Element[numRemoved];
+    for (int i = 0; i < numRemoved; i++)
+      removed[i] = bidiRoot.getElement(removeFrom + i);
+
+    Element[] added = new Element[newEls.size()];
+    added = (Element[]) newEls.toArray(added);
+
+    // Update the event.
+    ElementEdit edit = new ElementEdit(bidiRoot, removeFrom, removed, added);
+    ev.addEdit(edit);
+
+    // Update the structure.
+    bidiRoot.replace(removeFrom, numRemoved, added);
+  }
+
+  /**
+   * Determines the Bidi objects for the paragraphs in the specified range.
+   *
+   * @param start the start of the range
+   * @param end the end of the range
+   *
+   * @return the Bidi analysers for the paragraphs in the range
+   */
+  private Bidi[] getBidis(int start, int end)
+  {
+    // Determine the default run direction from the document property.
+    Boolean defaultDir = null;
+    Object o = getProperty(TextAttribute.RUN_DIRECTION);
+    if (o instanceof Boolean)
+      defaultDir = (Boolean) o;
+
+    // Scan paragraphs and add their level arrays to the overall levels array.
+    ArrayList bidis = new ArrayList();
+    Segment s = new Segment();
+    for (int i = start; i < end;)
+      {
+        Element par = getParagraphElement(i);
+        int pStart = par.getStartOffset();
+        int pEnd = par.getEndOffset();
+
+        // Determine the default run direction of the paragraph.
+        Boolean dir = defaultDir;
+        o = par.getAttributes().getAttribute(TextAttribute.RUN_DIRECTION);
+        if (o instanceof Boolean)
+          dir = (Boolean) o;
+
+        // Bidi over the paragraph.
+        try
+          {
+            getText(pStart, pEnd - pStart, s);
+          }
+        catch (BadLocationException ex)
+          {
+            assert false : "Must not happen";
+          }
+        int flag = Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT;
+        if (dir != null)
+          {
+            if (TextAttribute.RUN_DIRECTION_LTR.equals(dir))
+              flag = Bidi.DIRECTION_LEFT_TO_RIGHT;
+            else
+              flag = Bidi.DIRECTION_RIGHT_TO_LEFT;
+          }
+        Bidi bidi = new Bidi(s.array, s.offset, null, 0, s.count, flag);
+        bidis.add(bidi);
+        i = pEnd;
+      }
+    Bidi[] ret = new Bidi[bidis.size()];
+    ret = (Bidi[]) bidis.toArray(ret);
+    return ret;
+  }
+
+  /**
+   * Blocks until a read lock can be obtained.  Must block if there is
+   * currently a writer modifying the <code>Document</code>.
+   */
+  public final synchronized void readLock()
+  {
+    try
+      {
+        while (currentWriter != null)
+          {
+            if (currentWriter == Thread.currentThread())
+              return;
+            wait();
+          }
+        numReaders++;
+      }
+    catch (InterruptedException ex)
+      {
+        throw new Error("Interrupted during grab read lock");
       }
   }
 
@@ -679,7 +1069,7 @@ public abstract class AbstractDocument implements Document, Serializable
    * Releases the read lock. If this was the only reader on this
    * <code>Document</code>, writing may begin now.
    */
-  public final void readUnlock()
+  public final synchronized void readUnlock()
   {
     // Note we could have a problem here if readUnlock was called without a
     // prior call to readLock but the specs simply warn users to ensure that
@@ -706,21 +1096,14 @@ public abstract class AbstractDocument implements Document, Serializable
 
     // FIXME: the reference implementation throws a 
     // javax.swing.text.StateInvariantError here
-    if (numReaders == 0)
+    if (numReaders <= 0)
       throw new IllegalStateException("document lock failure");
     
-    synchronized (documentCV)
-    {
-      // If currentWriter is not null, the application code probably had a 
-      // writeLock and then tried to obtain a readLock, in which case 
-      // numReaders wasn't incremented
-      if (currentWriter == null)
-        {
-          numReaders --;
-          if (numReaders == 0 && numWritersWaiting != 0)
-            documentCV.notify();
-        }
-    }
+    // If currentWriter is not null, the application code probably had a 
+    // writeLock and then tried to obtain a readLock, in which case 
+    // numReaders wasn't incremented
+    numReaders--;
+    notify();
   }
 
   /**
@@ -744,12 +1127,21 @@ public abstract class AbstractDocument implements Document, Serializable
    */
   public void remove(int offset, int length) throws BadLocationException
   {
-    if (documentFilter == null)
-      removeImpl(offset, length);
-    else
-      documentFilter.remove(getBypass(), offset, length);
+    writeLock();
+    try
+      {
+        DocumentFilter f = getDocumentFilter();
+        if (f == null)
+          removeImpl(offset, length);
+        else
+          f.remove(getBypass(), offset, length);
+      }
+    finally
+      {
+        writeUnlock();
+      }
   }
-  
+
   void removeImpl(int offset, int length) throws BadLocationException
   {
     // The RI silently ignores all requests that have a negative length.
@@ -766,21 +1158,12 @@ public abstract class AbstractDocument implements Document, Serializable
           new DefaultDocumentEvent(offset, length,
                                    DocumentEvent.EventType.REMOVE);
     
-        try
-        {
-          writeLock();
-        
-          // The order of the operations below is critical!        
-          removeUpdate(event);
-          UndoableEdit temp = content.remove(offset, length);
-        
-          postRemoveUpdate(event);
-          fireRemoveUpdate(event);
-        }
-        finally
-          {
-            writeUnlock();
-          }
+        // The order of the operations below is critical!        
+        removeUpdate(event);
+        UndoableEdit temp = content.remove(offset, length);
+
+        postRemoveUpdate(event);
+        fireRemoveUpdate(event);
       }
   }
 
@@ -814,21 +1197,28 @@ public abstract class AbstractDocument implements Document, Serializable
     if (length == 0 
         && (text == null || text.length() == 0))
       return;
-    
-    if (documentFilter == null)
+
+    writeLock();
+    try
       {
-        // It is important to call the methods which again do the checks
-        // of the arguments and the DocumentFilter because subclasses may
-        // have overridden these methods and provide crucial behavior
-        // which would be skipped if we call the non-checking variants.
-        // An example for this is PlainDocument where insertString can
-        // provide a filtering of newlines.
-        remove(offset, length);
-        insertString(offset, text, attributes);
+        if (documentFilter == null)
+          {
+            // It is important to call the methods which again do the checks
+            // of the arguments and the DocumentFilter because subclasses may
+            // have overridden these methods and provide crucial behavior
+            // which would be skipped if we call the non-checking variants.
+            // An example for this is PlainDocument where insertString can
+            // provide a filtering of newlines.
+            remove(offset, length);
+            insertString(offset, text, attributes);
+          }
+        else
+          documentFilter.replace(getBypass(), offset, length, text, attributes);
+      }
+    finally
+      {
+        writeUnlock();
       }
-    else
-      documentFilter.replace(getBypass(), offset, length, text, attributes);
-    
   }
   
   void replaceImpl(int offset, int length, String text,
@@ -948,7 +1338,8 @@ public abstract class AbstractDocument implements Document, Serializable
    */
   public void setAsynchronousLoadPriority(int p)
   {
-    // TODO: Implement this properly.
+    Integer val = p >= 0 ? new Integer(p) : null;
+    putProperty(AsyncLoadPriority, val);
   }
 
   /**
@@ -956,7 +1347,7 @@ public abstract class AbstractDocument implements Document, Serializable
    *
    * @param p the document properties to set
    */
-  public void setDocumentProperties(Dictionary p)
+  public void setDocumentProperties(Dictionary<Object, Object> p)
   {
     // FIXME: make me thread-safe
     properties = p;
@@ -966,26 +1357,27 @@ public abstract class AbstractDocument implements Document, Serializable
    * Blocks until a write lock can be obtained.  Must wait if there are 
    * readers currently reading or another thread is currently writing.
    */
-  protected final void writeLock()
+  protected synchronized final void writeLock()
   {
-    if (currentWriter != null && currentWriter.equals(Thread.currentThread()))
-      return;
-    synchronized (documentCV)
+    try
       {
-        numWritersWaiting++;
-        while (numReaders > 0)
+        while (numReaders > 0 || currentWriter != null)
           {
-            try
-              {
-                documentCV.wait();
-              }
-            catch (InterruptedException ie)
+            if (Thread.currentThread() == currentWriter)
               {
-                throw new Error("interruped while trying to obtain write lock");
+                if (notifyListeners)
+                  throw new IllegalStateException("Mutation during notify");
+                numWriters++;
+                return;
               }
+            wait();
           }
-        numWritersWaiting --;
         currentWriter = Thread.currentThread();
+        numWriters = 1;
+      }
+    catch (InterruptedException ex)
+      {
+        throw new Error("Interupted during grab write lock");
       }
   }
 
@@ -993,16 +1385,14 @@ public abstract class AbstractDocument implements Document, Serializable
    * Releases the write lock. This allows waiting readers or writers to
    * obtain the lock.
    */
-  protected final void writeUnlock()
+  protected final synchronized void writeUnlock()
   {
-    synchronized (documentCV)
-    {
-        if (Thread.currentThread().equals(currentWriter))
-          {
-            currentWriter = null;
-            documentCV.notifyAll();
-          }
-    }
+    if (--numWriters <= 0)
+      {
+        numWriters = 0;
+        currentWriter = null;
+        notifyAll();
+      }
   }
 
   /**
@@ -1039,6 +1429,7 @@ public abstract class AbstractDocument implements Document, Serializable
   public void dump(PrintStream out)
   {
     ((AbstractElement) getDefaultRootElement()).dump(out, 0);
+    ((AbstractElement) getBidiRootElement()).dump(out, 0);
   }
 
   /**
@@ -1130,7 +1521,7 @@ public abstract class AbstractDocument implements Document, Serializable
      * @return the attributes of <code>old</code> minus the attributes in
      *         <code>attributes</code>
      */
-    AttributeSet removeAttributes(AttributeSet old, Enumeration names);
+    AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names);
   }
 
   /**
@@ -1255,7 +1646,7 @@ public abstract class AbstractDocument implements Document, Serializable
       AttributeContext ctx = getAttributeContext();
       attributes = ctx.getEmptySet();
       if (s != null)
-        attributes = ctx.addAttributes(attributes, s);
+        addAttributes(s);
     }
 
     /**
@@ -1386,7 +1777,7 @@ public abstract class AbstractDocument implements Document, Serializable
      *
      * @param names the names of the attributes to be removed
      */
-    public void removeAttributes(Enumeration names)
+    public void removeAttributes(Enumeration<?> names)
     {
       attributes = getAttributeContext().removeAttributes(attributes, names);
     }
@@ -1481,7 +1872,7 @@ public abstract class AbstractDocument implements Document, Serializable
      *
      * @return the names of the attributes of this element
      */
-    public Enumeration getAttributeNames()
+    public Enumeration<?> getAttributeNames()
     {
       return attributes.getAttributeNames();
     }
@@ -1567,7 +1958,7 @@ public abstract class AbstractDocument implements Document, Serializable
      */
     public String getName()
     {
-      return (String) getAttribute(NameAttribute);
+      return (String) attributes.getAttribute(ElementNameAttribute);
     }
 
     /**
@@ -1644,6 +2035,11 @@ public abstract class AbstractDocument implements Document, Serializable
               b.append('\n');
             }
         }
+      if (getAttributeCount() > 0)
+        {
+          for (int i = 0; i < indent; ++i)
+            b.append(' ');
+        }
       b.append(">\n");
 
       // Dump element content for leaf elements.
@@ -1705,6 +2101,11 @@ public abstract class AbstractDocument implements Document, Serializable
     private int numChildren;
 
     /**
+     * The last found index in getElementIndex(). Used for faster searching.
+     */
+    private int lastIndex;
+
+    /**
      * Creates a new <code>BranchElement</code> with the specified
      * parent and attributes.
      *
@@ -1717,6 +2118,7 @@ public abstract class AbstractDocument implements Document, Serializable
       super(parent, attributes);
       children = new Element[1];
       numChildren = 0;
+      lastIndex = -1;
     }
 
     /**
@@ -1726,7 +2128,7 @@ public abstract class AbstractDocument implements Document, Serializable
      */
     public Enumeration children()
     {
-      if (children.length == 0)
+      if (numChildren == 0)
         return null;
 
       Vector tmp = new Vector();
@@ -1785,35 +2187,73 @@ public abstract class AbstractDocument implements Document, Serializable
      */
     public int getElementIndex(int offset)
     {
-      // If offset is less than the start offset of our first child,
-      // return 0
-      if (offset < getStartOffset())
-        return 0;
+      // Implemented using an improved linear search.
+      // This makes use of the fact that searches are not random but often
+      // close to the previous search. So we try to start the binary
+      // search at the last found index.
 
-      // XXX: There is surely a better algorithm
-      // as beginning from first element each time.
-      for (int index = 0; index < numChildren - 1; ++index)
+      int i0 = 0; // The lower bounds.
+      int i1 = numChildren - 1; // The upper bounds.
+      int index = -1; // The found index.
+
+      int p0 = getStartOffset();
+      int p1; // Start and end offset local variables.
+
+      if (numChildren == 0)
+        index = 0;
+      else if (offset >= getEndOffset())
+        index = numChildren - 1;
+      else
         {
-          Element elem = children[index];
-
-          if ((elem.getStartOffset() <= offset)
-               && (offset < elem.getEndOffset()))
-            return index;
-          // If the next element's start offset is greater than offset
-          // then we have to return the closest Element, since no Elements
-          // will contain the offset
-          if (children[index + 1].getStartOffset() > offset)
+          // Try lastIndex.
+          if (lastIndex >= i0 && lastIndex <= i1)
             {
-              if ((offset - elem.getEndOffset()) > (children[index + 1].getStartOffset() - offset))
-                return index + 1;
+              Element last = getElement(lastIndex);
+              p0 = last.getStartOffset();
+              p1 = last.getEndOffset();
+              if (offset >= p0 && offset < p1)
+                index = lastIndex;
               else
-                return index;
+                {
+                  // Narrow the search bounds using the lastIndex, even
+                  // if it hasn't been a hit.
+                  if (offset < p0)
+                    i1 = lastIndex;
+                  else
+                    i0 = lastIndex;
+                }
+            }
+          // The actual search.
+          int i = 0;
+          while (i0 <= i1 && index == -1)
+            {
+              i = i0 + (i1 - i0) / 2;
+              Element el = getElement(i);
+              p0 = el.getStartOffset();
+              p1 = el.getEndOffset();
+              if (offset >= p0 && offset < p1)
+                {
+                  // Found it!
+                  index = i;
+                }
+              else if (offset < p0)
+                i1 = i - 1;
+              else
+                i0 = i + 1;
             }
-        }
 
-      // If offset is greater than the index of the last element, return
-      // the index of the last element.
-      return getElementCount() - 1;
+          if (index == -1)
+            {
+              // Didn't find it. Return the boundary index.
+              if (offset < p0)
+                index = i;
+              else
+                index = i + 1;
+            }
+
+          lastIndex = index;
+        }
+      return index;
     }
 
     /**
@@ -1957,6 +2397,11 @@ public abstract class AbstractDocument implements Document, Serializable
     /** The serialization UID (compatible with JDK1.5). */
     private static final long serialVersionUID = 5230037221564563284L;
 
+    /**
+     * The threshold that indicates when we switch to using a Hashtable.
+     */
+    private static final int THRESHOLD = 10;
+    
     /** The starting offset of the change. */
     private int offset;
 
@@ -1967,15 +2412,18 @@ public abstract class AbstractDocument implements Document, Serializable
     private DocumentEvent.EventType type;
 
     /**
-     * Maps <code>Element</code> to their change records.
+     * Maps <code>Element</code> to their change records. This is only
+     * used when the changes array gets too big. We can use an
+     * (unsync'ed) HashMap here, since changes to this are (should) always
+     * be performed inside a write lock. 
      */
-    Hashtable changes;
+    private HashMap changes;
 
     /**
      * Indicates if this event has been modified or not. This is used to
      * determine if this event is thrown.
      */
-    boolean modified;
+    private boolean modified;
 
     /**
      * Creates a new <code>DefaultDocumentEvent</code>.
@@ -1990,7 +2438,6 @@ public abstract class AbstractDocument implements Document, Serializable
       this.offset = offset;
       this.length = length;
       this.type = type;
-      changes = new Hashtable();
       modified = false;
     }
 
@@ -2004,9 +2451,27 @@ public abstract class AbstractDocument implements Document, Serializable
     public boolean addEdit(UndoableEdit edit)
     {
       // XXX - Fully qualify ElementChange to work around gcj bug #2499.
-      if (edit instanceof DocumentEvent.ElementChange)
+
+      // Start using Hashtable when we pass a certain threshold. This
+      // gives a good memory/performance compromise.
+      if (changes == null && edits.size() > THRESHOLD)
+        {
+          changes = new HashMap();
+          int count = edits.size();
+          for (int i = 0; i < count; i++)
+            {
+              Object o = edits.elementAt(i);
+              if (o instanceof DocumentEvent.ElementChange)
+                {
+                  DocumentEvent.ElementChange ec =
+                    (DocumentEvent.ElementChange) o;
+                  changes.put(ec.getElement(), ec);
+                }
+            }
+        }
+
+      if (changes != null && edit instanceof DocumentEvent.ElementChange)
         {
-          modified = true;
           DocumentEvent.ElementChange elEdit =
             (DocumentEvent.ElementChange) edit;
           changes.put(elEdit.getElement(), elEdit);
@@ -2065,7 +2530,27 @@ public abstract class AbstractDocument implements Document, Serializable
     public DocumentEvent.ElementChange getChange(Element elem)
     {
       // XXX - Fully qualify ElementChange to work around gcj bug #2499.
-      return (DocumentEvent.ElementChange) changes.get(elem);
+      DocumentEvent.ElementChange change = null;
+      if (changes != null)
+        {
+          change = (DocumentEvent.ElementChange) changes.get(elem);
+        }
+      else
+        {
+          int count = edits.size();
+          for (int i = 0; i < count && change == null; i++)
+            {
+              Object o = edits.get(i);
+              if (o instanceof DocumentEvent.ElementChange)
+                {
+                  DocumentEvent.ElementChange ec =
+                    (DocumentEvent.ElementChange) o;
+                  if (elem.equals(ec.getElement()))
+                    change = ec;
+                }
+            }
+        }
+      return change;
     }
     
     /**
@@ -2333,7 +2818,63 @@ public abstract class AbstractDocument implements Document, Serializable
              + getStartOffset() + "," + getEndOffset() + "\n");
     }
   }
-  
+
+  /**
+   * The root element for bidirectional text.
+   */
+  private class BidiRootElement
+    extends BranchElement
+  {
+    /**
+     * Creates a new bidi root element.
+     */
+    BidiRootElement()
+    {
+      super(null, null);
+    }
+
+    /**
+     * Returns the name of the element.
+     *
+     * @return the name of the element
+     */
+    public String getName()
+    {
+      return BidiRootName;
+    }
+  }
+
+  /**
+   * A leaf element for the bidi structure.
+   */
+  private class BidiElement
+    extends LeafElement
+  {
+    /**
+     * Creates a new BidiElement.
+     *
+     * @param parent the parent element
+     * @param start the start offset
+     * @param end the end offset
+     * @param level the bidi level
+     */
+    BidiElement(Element parent, int start, int end, int level)
+    {
+      super(parent, new SimpleAttributeSet(), start, end);
+      addAttribute(StyleConstants.BidiLevel, new Integer(level));
+    }
+
+    /**
+     * Returns the name of the element.
+     *
+     * @return the name of the element
+     */
+    public String getName()
+    {
+      return BidiElementName;
+    }
+  }
+
   /** A class whose methods delegate to the insert, remove and replace methods
    * of this document which do not check for an installed DocumentFilter.
    */