Here comes some locking fixes and an optimization for AbstractDocument that I did some days ago.

2006-08-09  Roman Kennke  <[EMAIL PROTECTED]>

        * javax/swing/text/AbstractDocument.java
        (insertString): Perform modifications inside a write lock.
        (insertStringImpl): Don't lock here. This is already done
        in insertString().
        (replace): Perform modifications inside a write lock.
        (AbstractElement.AbstractElement): Call addAttributes() to
        add the attributes.
        (AbstractElement.getName): Fetch name from the ElementNameAttibute.
        (BranchElement.lastIndex): New field. Optimizes getElementIndex().
        (BranchElement.BranchElement): Set lastIndex to -1.
        (BranchElement.getElementIndex): Implemented more efficient
        search.

/Roman
Index: javax/swing/text/AbstractDocument.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/text/AbstractDocument.java,v
retrieving revision 1.60
diff -u -1 -2 -r1.60 AbstractDocument.java
--- javax/swing/text/AbstractDocument.java	27 Jul 2006 15:39:41 -0000	1.60
+++ javax/swing/text/AbstractDocument.java	9 Aug 2006 15:14:29 -0000
@@ -565,58 +565,59 @@
    * @param text the content to be inserted
    * @param attributes the text attributes to be assigned to that string
    *
    * @throws BadLocationException if <code>offset</code> is not a valid
    *         location in this <code>Document</code>
    */
   public void insertString(int offset, String text, AttributeSet attributes)
     throws BadLocationException
   {
     // 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)
     throws BadLocationException
   {
     // Just return when no text to insert was given.
     if (text == null || text.length() == 0)
       return;
     DefaultDocumentEvent event =
       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);
-
-        fireInsertUpdate(event);
-        if (undo != null)
-          fireUndoableEditUpdate(new UndoableEditEvent(this, undo));
-      }
-    finally
-      {
-        writeUnlock();
-      }
+    UndoableEdit undo = content.insertString(offset, text);
+    if (undo != null)
+      event.addEdit(undo);
+
+    insertUpdate(event, attributes);
+
+    fireInsertUpdate(event);
+
+    if (undo != null)
+      fireUndoableEditUpdate(new UndoableEditEvent(this, undo));
   }
 
   /**
    * Called to indicate that text has been inserted into this
    * <code>Document</code>. The default implementation does nothing.
    * This method is executed within a write lock.
    *
    * @param chng the <code>DefaultDocumentEvent</code> describing the change
    * @param attr the attributes of the changed content
    */
   protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr)
   {
@@ -805,39 +806,46 @@
    *         document
    *
    * @since 1.4
    */
   public void replace(int offset, int length, String text,
                       AttributeSet attributes)
     throws BadLocationException
   {
     // Bail out if we have a bogus replacement (Behavior observed in RI).
     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,
 		      AttributeSet attributes)
     throws BadLocationException
   {
     removeImpl(offset, length);
     insertStringImpl(offset, text, attributes);
   }
 
   /**
    * Adds a <code>DocumentListener</code> object to this document.
@@ -1246,25 +1254,25 @@
      * specified parent <code>Element</code> and <code>AttributeSet</code>.
      *
      * @param p the parent of this <code>AbstractElement</code>
      * @param s the attributes to be assigned to this
      *        <code>AbstractElement</code>
      */
     public AbstractElement(Element p, AttributeSet s)
     {
       element_parent = p;
       AttributeContext ctx = getAttributeContext();
       attributes = ctx.getEmptySet();
       if (s != null)
-        attributes = ctx.addAttributes(attributes, s);
+        addAttributes(s);
     }
 
     /**
      * Returns the child nodes of this <code>Element</code> as an
      * <code>Enumeration</code> of [EMAIL PROTECTED] TreeNode}s.
      *
      * @return the child nodes of this <code>Element</code> as an
      *         <code>Enumeration</code> of [EMAIL PROTECTED] TreeNode}s
      */
     public abstract Enumeration children();
 
     /**
@@ -1558,25 +1566,25 @@
      *
      * @return the requested element
      */
     public abstract Element getElement(int index);
 
     /**
      * Returns the name of this element.
      *
      * @return the name of this element
      */
     public String getName()
     {
-      return (String) getAttribute(NameAttribute);
+      return (String) attributes.getAttribute(ElementNameAttribute);
     }
 
     /**
      * Returns the parent element of this element.
      *
      * @return the parent element of this element
      */
     public Element getParentElement()
     {
       return element_parent;
     }
 
@@ -1696,36 +1704,42 @@
 
     /**
      * The child elements of this BranchElement.
      */
     private Element[] children;;
 
     /**
      * The number of children in the branch element.
      */
     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.
      *
      * @param parent the parent element of this <code>BranchElement</code>
      * @param attributes the attributes to set on this
      *        <code>BranchElement</code>
      */
     public BranchElement(Element parent, AttributeSet attributes)
     {
       super(parent, attributes);
       children = new Element[1];
       numChildren = 0;
+      lastIndex = -1;
     }
 
     /**
      * Returns the children of this <code>BranchElement</code>.
      *
      * @return the children of this <code>BranchElement</code>
      */
     public Enumeration children()
     {
       if (children.length == 0)
         return null;
 
@@ -1776,53 +1790,91 @@
 
     /**
      * Returns the index of the child element that spans the specified
      * offset in the document model.
      *
      * @param offset the offset for which the responsible element is searched
      *
      * @return the index of the child element that spans the specified
      *         offset in the document model
      */
     public int getElementIndex(int offset)
     {
-      // If offset is less than the start offset of our first child,
-      // return 0
-      if (offset < getStartOffset())
-        return 0;
-
-      // XXX: There is surely a better algorithm
-      // as beginning from first element each time.
-      for (int index = 0; index < numChildren - 1; ++index)
+      // 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.
+
+      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];
+          // Try lastIndex.
+          if (lastIndex >= i0 && lastIndex <= i1)
+            {
+              Element last = getElement(lastIndex);
+              p0 = last.getStartOffset();
+              p1 = last.getEndOffset();
+              if (offset >= p0 && offset < p1)
+                index = lastIndex;
+              else
+                {
+                  // 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 ((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)
+          if (index == -1)
             {
-              if ((offset - elem.getEndOffset()) > (children[index + 1].getStartOffset() - offset))
-                return index + 1;
+              // Didn't find it. Return the boundary index.
+              if (offset < p0)
+                index = i;
               else
-                return index;
+                index = i + 1;
             }
-        }
 
-      // If offset is greater than the index of the last element, return
-      // the index of the last element.
-      return getElementCount() - 1;
+          lastIndex = index;
+        }
+      return index;
     }
 
     /**
      * Returns the offset inside the document model that is after the last
      * character of this element.
      * This is the end offset of the last child element. If this element
      * has no children, this method throws a <code>NullPointerException</code>.
      *
      * @return the offset inside the document model that is after the last
      *         character of this element
      *
      * @throws NullPointerException if this branch element has no children

Reply via email to