In an effort to clear the Mauve regressions for Swing I looked into AbstractDocument and GapContent again. Basically I removed some explicit checks to not throw BadLocationException in strange cases. This is compliant with the RI, but bends the spec a little. See that other thread on [EMAIL PROTECTED]

A while ago I started the bidi stuff in AbstractDocument. This will now have to go in here, as I don't want to throw it out again. It's not complete yet.

This patch should make some Mauve and Intel tests happy.

2006-07-27  Roman Kennke  <[EMAIL PROTECTED]>

        * javax/swing/text/AbstractDocument.java
        (documentCV): Made field private.
        (bypass): Made field private.
        (bidiRoot): New field.
        (AbstractDocument): Initialize bidiRoot.
        (getBidiRootElement): Return bidiRoot.
        (getRootElements): Adjusted to also return the bidiRoot element.
        (BranchElement.startOffset): Removed unneeded field.
        (BranchElement.endOffset): Removed unneeded field.
        (BranchElement.BranchElement): Removed unneeded fields.
        (BranchElement.getEndOffset): Don't explicitly throw NPE here. This is
        done automatically when there's no element left in the array.
        (BranchElement.getStartOffset): Likewise.
        (BranchElement.replace): Reordered calculations to avoid double
        calculations.
        (removeImpl): Silently ignore requests with length <= 0.
        * javax/swing/text/GapContent.java
        (createPosition): Removed explicit check for correct offset.
        This class can deal with offsets outside the document.
        (shiftEnd): Update all positions, even those outside the
        document.
        (adjustPositionsInRange): Fixed to also adjust positions outside
        the document boundary.


/Roman
Index: javax/swing/text/AbstractDocument.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/text/AbstractDocument.java,v
retrieving revision 1.59
diff -u -1 -2 -r1.59 AbstractDocument.java
--- javax/swing/text/AbstractDocument.java	22 Jun 2006 15:14:00 -0000	1.59
+++ javax/swing/text/AbstractDocument.java	27 Jul 2006 15:15:25 -0000
@@ -138,32 +138,37 @@
    * The number of readers.  Used for locking.
    */
   private int numReaders = 0;
   
   /**
    * Tells if there are one or more writers waiting.
    */
   private int numWritersWaiting = 0;  
 
   /**
    * A condition variable that readers and writers wait on.
    */
-  Object documentCV = new Object();
+  private Object documentCV = new Object();
 
   /** An instance of a DocumentFilter.FilterBypass which allows calling
    * the insert, remove and replace method without checking for an installed
    * document filter.
    */
-  DocumentFilter.FilterBypass bypass;
-  
+  private DocumentFilter.FilterBypass bypass;
+
+  /**
+   * The bidi root element.
+   */
+  private Element bidiRoot;
+
   /**
    * Creates a new <code>AbstractDocument</code> with the specified
    * [EMAIL PROTECTED] Content} model.
    *
    * @param doc the <code>Content</code> model to be used in this
    *        <code>Document<code>
    *
    * @see GapContent
    * @see StringContent
    */
   protected AbstractDocument(Content doc)
   {
@@ -180,24 +185,27 @@
    *
    * @see GapContent
    * @see StringContent
    */
   protected AbstractDocument(Content doc, AttributeContext ctx)
   {
     content = doc;
     context = ctx;
 
     // FIXME: This is determined using a Mauve test. Make the document
     // actually use this.
     putProperty("i18n", Boolean.FALSE);
+
+    // FIXME: Fully implement bidi.
+    bidiRoot = new BranchElement(null, null);
   }
   
   /** Returns the DocumentFilter.FilterBypass instance for this
    * document and create it if it does not exist yet.
    *  
    * @return This document's DocumentFilter.FilterBypass instance.
    */
   private DocumentFilter.FilterBypass getBypass()
   {
     if (bypass == null)
       bypass = new Bypass();
     
@@ -355,25 +363,25 @@
   protected final AttributeContext getAttributeContext()
   {
     return context;
   }
 
   /**
    * Returns the root element for bidirectional content.
    *
    * @return the root element for bidirectional content
    */
   public Element getBidiRootElement()
   {
-    return null;
+    return bidiRoot;
   }
 
   /**
    * Returns the [EMAIL PROTECTED] Content} model for this <code>Document</code>
    *
    * @return the [EMAIL PROTECTED] Content} model for this <code>Document</code>
    *
    * @see GapContent
    * @see StringContent
    */
   protected final Content getContent()
   {
@@ -472,26 +480,27 @@
 
   /**
    * Returns all root elements of this <code>Document</code>. By default
    * this just returns the single root element returned by
    * [EMAIL PROTECTED] #getDefaultRootElement()}. <code>Document</code> implementations
    * that support multiple roots must override this method and return all roots
    * here.
    *
    * @return all root elements of this <code>Document</code>
    */
   public Element[] getRootElements()
   {
-    Element[] elements = new Element[1];
+    Element[] elements = new Element[2];
     elements[0] = getDefaultRootElement();
+    elements[1] = getBidiRootElement();
     return elements;
   }
 
   /**
    * Returns a [EMAIL PROTECTED] Position} which will always mark the beginning of the
    * <code>Document</code>.
    *
    * @return a [EMAIL PROTECTED] Position} which will always mark the beginning of the
    *         <code>Document</code>
    */
   public final Position getStartPosition()
   {
@@ -734,53 +743,54 @@
    *         document
    */
   public void remove(int offset, int length) throws BadLocationException
   {
     if (documentFilter == null)
       removeImpl(offset, length);
     else
       documentFilter.remove(getBypass(), offset, length);
   }
   
   void removeImpl(int offset, int length) throws BadLocationException
   {
-    if (offset < 0 || offset > getLength())
-      throw new BadLocationException("Invalid remove position", offset);
-
-    if (offset + length > getLength())
-      throw new BadLocationException("Invalid remove length", offset);
+    // The RI silently ignores all requests that have a negative length.
+    // Don't ask my why, but that's how it is.
+    if (length > 0)
+      {
+        if (offset < 0 || offset > getLength())
+          throw new BadLocationException("Invalid remove position", offset);
 
-    // Prevent some unneccessary method invocation (observed in the RI). 
-    if (length == 0)
-      return;
+        if (offset + length > getLength())
+          throw new BadLocationException("Invalid remove length", offset);
 
-    DefaultDocumentEvent event =
-      new DefaultDocumentEvent(offset, length,
-			       DocumentEvent.EventType.REMOVE);
+        DefaultDocumentEvent event =
+          new DefaultDocumentEvent(offset, length,
+                                   DocumentEvent.EventType.REMOVE);
     
-    try
-      {
-        writeLock();
+        try
+        {
+          writeLock();
         
-        // The order of the operations below is critical!        
-        removeUpdate(event);
-        UndoableEdit temp = content.remove(offset, length);
+          // The order of the operations below is critical!        
+          removeUpdate(event);
+          UndoableEdit temp = content.remove(offset, length);
         
-        postRemoveUpdate(event);
-        fireRemoveUpdate(event);
+          postRemoveUpdate(event);
+          fireRemoveUpdate(event);
+        }
+        finally
+          {
+            writeUnlock();
+          }
       }
-    finally
-      {
-        writeUnlock();
-      } 
   }
 
   /**
    * Replaces a piece of content in this <code>Document</code> with
    * another piece of content.
    * 
    * <p>If a [EMAIL PROTECTED] DocumentFilter} is installed in this document, the
    * corresponding method of the filter object is called.</p>
    * 
    * <p>The method has no effect if <code>length</code> is zero (and
    * only zero) and, at the same time, <code>text</code> is
    * <code>null</code> or has zero length.</p>
@@ -1686,50 +1696,36 @@
 
     /**
      * The child elements of this BranchElement.
      */
     private Element[] children;;
 
     /**
      * The number of children in the branch element.
      */
     private int numChildren;
 
     /**
-     * The cached startOffset value. This is used in the case when a
-     * BranchElement (temporarily) has no child elements.
-     */
-    private int startOffset;
-
-    /**
-     * The cached endOffset value. This is used in the case when a
-     * BranchElement (temporarily) has no child elements.
-     */
-    private int endOffset;
-
-    /**
      * 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;
-      startOffset = -1;
-      endOffset = -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;
 
@@ -1824,33 +1820,29 @@
      * 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
      */
     public int getEndOffset()
     {
-      if (numChildren == 0)
-        {
-          if (endOffset == -1)
-            throw new NullPointerException("BranchElement has no children.");
-        }
-      else
-        endOffset = children[numChildren - 1].getEndOffset();
-
-      return endOffset;
+      // This might accss one cached element or trigger an NPE for
+      // numChildren == 0. This is checked by a Mauve test.
+      Element child = numChildren > 0 ? children[numChildren - 1]
+                                      : children[0];
+      return child.getEndOffset();
     }
 
     /**
      * Returns the name of this element. This is [EMAIL PROTECTED] #ParagraphElementName}
      * in this case.
      *
      * @return the name of this element
      */
     public String getName()
     {
       return ParagraphElementName;
     }
@@ -1858,33 +1850,31 @@
     /**
      * Returns the start offset of this element inside the document model.
      * This is the start offset of the first child element. If this element
      * has no children, this method throws a <code>NullPointerException</code>.
      *
      * @return the start offset of this element inside the document model
      *
      * @throws NullPointerException if this branch element has no children and
      *         no startOffset value has been cached
      */
     public int getStartOffset()
     {
-      if (numChildren == 0)
-        {
-          if (startOffset == -1)
-            throw new NullPointerException("BranchElement has no children.");
-        }
-      else
-        startOffset = children[0].getStartOffset();
-
-      return startOffset;
+      // Do not explicitly throw an NPE here. If the first element is null
+      // then the NPE gets thrown anyway. If it isn't, then it either
+      // holds a real value (for numChildren > 0) or a cached value
+      // (for numChildren == 0) as we don't fully remove elements in replace()
+      // when removing single elements.
+      // This is checked by a Mauve test.
+      return children[0].getStartOffset();
     }
 
     /**
      * Returns <code>false</code> since <code>BranchElement</code> are no
      * leafes.
      *
      * @return <code>false</code> since <code>BranchElement</code> are no
      *         leafes
      */
     public boolean isLeaf()
     {
       return false;
@@ -1915,45 +1905,44 @@
       return null;
     }
 
     /**
      * Replaces a set of child elements with a new set of child elemens.
      *
      * @param offset the start index of the elements to be removed
      * @param length the number of elements to be removed
      * @param elements the new elements to be inserted
      */
     public void replace(int offset, int length, Element[] elements)
     {
-      if (numChildren + elements.length - length > children.length)
+      int delta = elements.length - length;
+      int copyFrom = offset + length; // From where to copy.
+      int copyTo = copyFrom + delta;    // Where to copy to.
+      int numMove = numChildren - copyFrom; // How many elements are moved. 
+      if (numChildren + delta > children.length)
         {
           // Gotta grow the array.
-          int newSize = Math.max(2 * children.length,
-                                 numChildren + elements.length - length);
+          int newSize = Math.max(2 * children.length, numChildren + delta);
           Element[] target = new Element[newSize];
           System.arraycopy(children, 0, target, 0, offset);
           System.arraycopy(elements, 0, target, offset, elements.length);
-          System.arraycopy(children, offset + length, target,
-                           offset + elements.length,
-                           numChildren - offset - length);
+          System.arraycopy(children, copyFrom, target, copyTo, numMove);
           children = target;
         }
       else
         {
-          System.arraycopy(children, offset + length, children,
-                           offset + elements.length,
-                           numChildren - offset - length);
+          System.arraycopy(children, copyFrom, children, copyTo, numMove);
           System.arraycopy(elements, 0, children, offset, elements.length);
         }
-      numChildren += elements.length - length;
+      numChildren += delta;
     }
 
     /**
      * Returns a string representation of this element.
      *
      * @return a string representation of this element
      */
     public String toString()
     {
       return ("BranchElement(" + getName() + ") "
 	      + getStartOffset() + "," + getEndOffset() + "\n");
     }
Index: javax/swing/text/GapContent.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/text/GapContent.java,v
retrieving revision 1.53
diff -u -1 -2 -r1.53 GapContent.java
--- javax/swing/text/GapContent.java	26 Jul 2006 07:55:19 -0000	1.53
+++ javax/swing/text/GapContent.java	27 Jul 2006 15:15:25 -0000
@@ -523,26 +523,28 @@
   /**
    * Creates and returns a mark at the specified position.
    * 
    * @param offset the position at which to create the mark
    * 
    * @return the create Position object for the mark
    * 
    * @throws BadLocationException if the offset is not a valid position in the
    *         buffer
    */
   public Position createPosition(final int offset) throws BadLocationException
   {
-    if (offset < 0 || offset > length())
-      throw new BadLocationException("Position offset out of bounds", offset);
+    // Implementation note: We used to perform explicit check on the offset
+    // here. However, this makes some Mauve and Intel/Harmony tests fail
+    // and luckily enough the GapContent can very well deal with offsets
+    // outside the buffer bounds. So I removed that check.
 
     // We try to find a GapContentPosition at the specified offset and return
     // that. Otherwise we must create a new one.
     GapContentPosition pos = null;
     Set positionSet = positions.keySet();
     for (Iterator i = positionSet.iterator(); i.hasNext();)
       {
         GapContentPosition p = (GapContentPosition) i.next();
         if (p.getOffset() == offset)
           {
             pos = p;
             break;
@@ -565,25 +567,25 @@
    * of the new buffer array. This does change the gapEnd mark but not the
    * gapStart mark.
    * 
    * @param newSize the new size of the gap
    */
   protected void shiftEnd(int newSize)
   {
     assert newSize > (gapEnd - gapStart) : "The new gap size must be greater "
                                            + "than the old gap size";
 
     int delta = newSize - gapEnd + gapStart;
     // Update the marks after the gapEnd.
-    adjustPositionsInRange(gapEnd, buffer.length, delta);
+    adjustPositionsInRange(gapEnd, -1, delta);
 
     // Copy the data around.
     char[] newBuf = (char[]) allocateArray(length() + newSize);
     System.arraycopy(buffer, 0, newBuf, 0, gapStart);
     System.arraycopy(buffer, gapEnd, newBuf, gapStart + newSize, buffer.length
         - gapEnd);
     gapEnd = gapStart + newSize;
     buffer = newBuf;
 
   }
 
   /**
@@ -780,43 +782,49 @@
         for (int i = startIndex; i <= endIndex; i++)
           ((Mark) marks.get(i)).mark = toStart ? start : end;
       }
 
   }
 
   /**
    * Adjusts the mark of all <code>Position</code>s that are in the range 
    * specified by <code>offset</code> and </code>length</code> within 
    * the buffer array by <code>increment</code>
    *
    * @param startOffs the start offset of the range to search
-   * @param endOffs the length of the range to search
+   * @param endOffs the length of the range to search, -1 means all to the end
    * @param incr the increment
    */
   private void adjustPositionsInRange(int startOffs, int endOffs, int incr)
   {
     synchronized (this)
       {
         // Find the start and end indices in the positionMarks array.
         Mark m = new Mark(0); // For comparison / search only.
 
         m.mark = startOffs;
         int startIndex = search(marks, m);
         if (startIndex < 0) // Translate to insertion index, if not found.
           startIndex = - startIndex - 1;
 
         m.mark = endOffs;
-        int endIndex = search(marks, m);
-        if (endIndex < 0) // Translate to insertion index - 1, if not found.
-          endIndex = - endIndex - 2;
+        int endIndex;
+        if (endOffs == -1)
+          endIndex = marks.size() - 1;
+        else
+          {
+            endIndex = search(marks, m);
+            if (endIndex < 0) // Translate to insertion index - 1, if not found.
+              endIndex = - endIndex - 2;
+          }
         // Actually adjust the marks.
         for (int i = startIndex; i <= endIndex; i++) {
           ((Mark) marks.get(i)).mark += incr;
         }
       }
 
   }
 
   /**
    * Resets all <code>Position</code> that have an offset of <code>0</code>,
    * to also have an array index of <code>0</code>. This might be necessary
    * after a call to <code>shiftGap(0)</code>, since then the marks at offset

Reply via email to