This fixes the harmony testcases for View, CompositeView and BoxView and applies a small optimization to BoxView and CompositeView. I made these two classes grow their View and layout arrays more efficiently by not making a copy on each change, but instead grow the array by doubling its size, and having a pointer numChildren that points at the array end.

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

        * javax/swing/text/View.java
        (height): Removed unneeded field.
        (width): Removed unneeded field.
        (getBreakWeight): Return GoodBreakWeight when pos is after
        the view's span.
        (getToolTipText): Check view index more carefully. Avoid
        Rectangle creation.
        (insertUpdate): Only execute method body if view count > 0.
        When updateChildren returns false, clear the ec variable.
        (updateChildren): Added null checks.
        (viewToModel): Initialize bias array correctly.
        * javax/swing/text/CompositeView.java
        (children): Made private.
        (numChildren): New field.
        (loadChildren): Check factory for null. Don't load children
        when factory is null.
        (replace): Removed null check. Nullify removed children. Made
        growing the array more efficient.
        (getViewCount): Return numChildren rather then the real array
        size.
        * javax/swing/text/BoxView.java
        (getViewAtPoint): Fixed algorithm for finding the view.
        (replace): Made array growing more efficient.
        (replaceLayoutArray): New helper method for growing/patching
        the layout arrays.
        (viewToModel): Make sure we have a valid layout.


/Roman
Index: javax/swing/text/View.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/text/View.java,v
retrieving revision 1.35
diff -u -1 -2 -r1.35 View.java
--- javax/swing/text/View.java	12 Aug 2006 22:16:12 -0000	1.35
+++ javax/swing/text/View.java	28 Aug 2006 15:47:46 -0000
@@ -48,25 +48,24 @@
 import javax.swing.event.DocumentEvent;
 
 public abstract class View implements SwingConstants
 {
   public static final int BadBreakWeight = 0;
   public static final int ExcellentBreakWeight = 2000;
   public static final int ForcedBreakWeight = 3000;
   public static final int GoodBreakWeight = 1000;
 
   public static final int X_AXIS = 0;
   public static final int Y_AXIS = 1;
     
-  private float width, height;
   private Element elt;
   private View parent;
 
   /**
    * Creates a new <code>View</code> instance.
    *
    * @param elem an <code>Element</code> value
    */
   public View(Element elem)
   {
     elt = elem;
   }
@@ -298,52 +297,56 @@
   public int getViewIndex(float x, float y, Shape allocation)
   {
     return -1;
   }
   
   /**
    * @since 1.4
    */
   public String getToolTipText(float x, float y, Shape allocation)
   {
     int index = getViewIndex(x, y, allocation);
 
-    if (index < -1)
-      return null;
-
-    Shape childAllocation = getChildAllocation(index, allocation);
-
-    if (childAllocation.getBounds().contains(x, y))
-      return getView(index).getToolTipText(x, y, childAllocation);
-
-    return null;
+    String text = null;
+    if (index >= 0)
+      {
+        allocation = getChildAllocation(index, allocation);
+        Rectangle r = allocation instanceof Rectangle ? (Rectangle) allocation
+                                                      : allocation.getBounds();
+        if (r.contains(x, y))
+          text = getView(index).getToolTipText(x, y, allocation);
+      }
+    return text;
   }
 
   /**
    * @since 1.3
    */
   public Graphics getGraphics()
   {
     return getContainer().getGraphics();
   }
 
   public void preferenceChanged(View child, boolean width, boolean height)
   {
     if (parent != null)
       parent.preferenceChanged(this, width, height);
   }
 
   public int getBreakWeight(int axis, float pos, float len)
   {
-    return BadBreakWeight;
+    int weight = BadBreakWeight;
+    if (len > getPreferredSpan(axis))
+      weight = GoodBreakWeight;
+    return weight;
   }
 
   public View breakView(int axis, int offset, float pos, float len)
   {
     return this;
   }
 
   /**
    * @since 1.3
    */
   public int getViewIndex(int pos, Position.Bias b)
   {
@@ -361,30 +364,36 @@
    * <li>Call [EMAIL PROTECTED] #forwardUpdate}. This forwards the DocumentEvent to
    * the child views.<li>
    * <li>Call [EMAIL PROTECTED] #updateLayout}. Gives the view a chance to either
    * repair its layout, reschedule layout or do nothing at all.</li>
    * </ul>
    *
    * @param ev the DocumentEvent that describes the change
    * @param shape the shape of the view
    * @param vf the ViewFactory for creating child views
    */
   public void insertUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)
   {
-    Element el = getElement();
-    DocumentEvent.ElementChange ec = ev.getChange(el);
-    if (ec != null)
-      updateChildren(ec, ev, vf);
-    forwardUpdate(ec, ev, shape, vf);
-    updateLayout(ec, ev, shape);
+    if (getViewCount() > 0)
+      {
+        Element el = getElement();
+        DocumentEvent.ElementChange ec = ev.getChange(el);
+        if (ec != null)
+          {
+            if (! updateChildren(ec, ev, vf))
+              ec = null;
+          }
+        forwardUpdate(ec, ev, shape, vf);
+        updateLayout(ec, ev, shape);
+      }
   }
 
   /**
    * Receive notification about a remove update to the text model.
    *
    * The default implementation of this method does the following:
    * <ul>
    * <li>Call [EMAIL PROTECTED] #updateChildren} if the element that this view is
    * responsible for has changed. This makes sure that the children can
    * correctly represent the model.<li>
    * <li>Call [EMAIL PROTECTED] #forwardUpdate}. This forwards the DocumentEvent to
    * the child views.<li>
@@ -462,28 +471,33 @@
    *         that they are responsible for and should then return false.
    *
    * @since 1.3
    */
   protected boolean updateChildren(DocumentEvent.ElementChange ec,
                                    DocumentEvent ev,
                                    ViewFactory vf)
   {
     Element[] added = ec.getChildrenAdded();
     Element[] removed = ec.getChildrenRemoved();
     int index = ec.getIndex();
 
-    View[] newChildren = new View[added.length];
-    for (int i = 0; i < added.length; ++i)
-      newChildren[i] = vf.create(added[i]);
-    replace(index, removed.length, newChildren);
+    View[] newChildren = null;
+    if (added != null)
+      {
+        newChildren = new View[added.length];
+        for (int i = 0; i < added.length; ++i)
+          newChildren[i] = vf.create(added[i]);
+      }
+    int numRemoved = removed != null ? removed.length : 0;
+    replace(index, numRemoved, newChildren);
 
     return true;
   }
 
   /**
    * Forwards the DocumentEvent to child views that need to get notified
    * of the change to the model. This calles [EMAIL PROTECTED] #forwardUpdateToView}
    * for each View that must be forwarded to.
    *
    * If <code>ec</code> is not <code>null</code> (this means there have been
    * structural changes to the element that this view is responsible for) this
    * method should recognize this and don't notify newly added child views.
@@ -747,25 +761,27 @@
    * @param x the x coordinate in the view space
    * @param y the y coordinate in the view space
    * @param a the allocation of this <code>View</code>
    *
    * @return the position in the document that corresponds to the screen
    *         coordinates <code>x, y</code>
    *
    * @deprecated Use [EMAIL PROTECTED] #viewToModel(float, float, Shape, Position.Bias[])}
    *             instead.
    */
   public int viewToModel(float x, float y, Shape a)
   {
-    return viewToModel(x, y, a, new Position.Bias[0]);
+    Position.Bias[] biasRet = new Position.Bias[1];
+    biasRet[0] = Position.Bias.Forward;
+    return viewToModel(x, y, a, biasRet);
   }
 
   /**
    * Dumps the complete View hierarchy. This method can be used for debugging
    * purposes.
    */
   protected void dump()
   {
     // Climb up the hierarchy to the parent.
     View parent = getParent();
     if (parent != null)
       parent.dump();
Index: javax/swing/text/CompositeView.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/text/CompositeView.java,v
retrieving revision 1.21
diff -u -1 -2 -r1.21 CompositeView.java
--- javax/swing/text/CompositeView.java	25 Aug 2006 21:03:46 -0000	1.21
+++ javax/swing/text/CompositeView.java	28 Aug 2006 15:47:47 -0000
@@ -47,25 +47,30 @@
  * An abstract base implementation of [EMAIL PROTECTED] View} that manages child
  * <code>View</code>s.
  *
  * @author Roman Kennke ([EMAIL PROTECTED])
  */
 public abstract class CompositeView
   extends View
 {
 
   /**
    * The child views of this <code>CompositeView</code>.
    */
-  View[] children;
+  private View[] children;
+
+  /**
+   * The number of child views.
+   */
+  private int numChildren;
 
   /**
    * The allocation of this <code>View</code> minus its insets. This is
    * initialized in [EMAIL PROTECTED] #getInsideAllocation} and reused and modified in
    * [EMAIL PROTECTED] #childAllocation(int, Rectangle)}.
    */
   Rectangle insideAllocation;
 
   /**
    * The insets of this <code>CompositeView</code>. This is initialized
    * in [EMAIL PROTECTED] #setInsets}.
    */
@@ -92,61 +97,64 @@
 
   /**
    * Loads the child views of this <code>CompositeView</code>. This method
    * is called from [EMAIL PROTECTED] #setParent} to initialize the child views of
    * this composite view.
    *
    * @param f the view factory to use for creating new child views
    *
    * @see #setParent
    */
   protected void loadChildren(ViewFactory f)
   {
-    Element el = getElement();
-    int count = el.getElementCount();
-    View[] newChildren = new View[count];
-    for (int i = 0; i < count; ++i)
+    if (f != null)
       {
-        Element child = el.getElement(i);
-        View view = f.create(child);
-        newChildren[i] = view;
+        Element el = getElement();
+        int count = el.getElementCount();
+        View[] newChildren = new View[count];
+        for (int i = 0; i < count; ++i)
+          {
+            Element child = el.getElement(i);
+            View view = f.create(child);
+            newChildren[i] = view;
+          }
+        // I'd have called replace(0, getViewCount(), newChildren) here
+        // in order to replace all existing views. However according to
+        // Harmony's tests this is not what the RI does.
+        replace(0, 0, newChildren);
       }
-    // I'd have called replace(0, getViewCount(), newChildren) here
-    // in order to replace all existing views. However according to
-    // Harmony's tests this is not what the RI does.
-    replace(0, 0, newChildren);
   }
 
   /**
    * Sets the parent of this <code>View</code>.
    * In addition to setting the parent, this calls [EMAIL PROTECTED] #loadChildren}, if
    * this <code>View</code> does not already have its children initialized.
    *
    * @param parent the parent to set
    */
   public void setParent(View parent)
   {
     super.setParent(parent);
     if (parent != null && ((children == null) || children.length == 0))
       loadChildren(getViewFactory());
   }
 
   /**
    * Returns the number of child views.
    *
    * @return the number of child views
    */
   public int getViewCount()
   {
-    return children.length;
+    return numChildren;
   }
 
   /**
    * Returns the child view at index <code>n</code>.
    *
    * @param n the index of the requested child view
    *
    * @return the child view at index <code>n</code>
    */
   public View getView(int n)
   {
     return children[n];
@@ -160,45 +168,55 @@
    *
    * @param offset the start offset from where to remove children
    * @param length the number of children to remove
    * @param views the views that replace the removed children
    */
   public void replace(int offset, int length, View[] views)
   {
     // Make sure we have an array. The Harmony testsuite indicates that we
     // have to do something like this.
     if (views == null)
       views = new View[0];
 
-    // Check for null views to add.
-    for (int i = 0; i < views.length; ++i)
-      if (views[i] == null)
-        throw new NullPointerException("Added views must not be null");
-
-    int endOffset = offset + length;
-
     // First we set the parent of the removed children to null.
+    int endOffset = offset + length;
     for (int i = offset; i < endOffset; ++i)
       {
         if (children[i].getParent() == this)
           children[i].setParent(null);
+        children[i] = null;
       }
 
-    View[] newChildren = new View[children.length - length + views.length];
-    System.arraycopy(children, 0, newChildren, 0, offset);
-    System.arraycopy(views, 0, newChildren, offset, views.length);
-    System.arraycopy(children, offset + length, newChildren,
-                     offset + views.length,
-                     children.length - (offset + length));
-    children = newChildren;
+    // Update the children array.
+    int delta = views.length - length;
+    int src = offset + length;
+    int numMove = numChildren - src;
+    int dst = src + delta;
+    if (numChildren + delta > children.length)
+      {
+        // Grow array.
+        int newLength = Math.max(2 * children.length, numChildren + delta);
+        View[] newChildren = new View[newLength];
+        System.arraycopy(children, 0, newChildren, 0, offset);
+        System.arraycopy(views, 0, newChildren, offset, views.length);
+        System.arraycopy(children, src, newChildren, dst, numMove);
+        children = newChildren;
+      }
+    else
+      {
+        // Patch existing array.
+        System.arraycopy(children, src, children, dst, numMove);
+        System.arraycopy(views, 0, children, offset, views.length);
+      }
+    numChildren += delta;
 
     // Finally we set the parent of the added children to this.
     for (int i = 0; i < views.length; ++i)
       views[i].setParent(this);
   }
 
   /**
    * Returns the allocation for the specified child <code>View</code>.
    *
    * @param index the index of the child view
    * @param a the allocation for this view
    *
Index: javax/swing/text/BoxView.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/text/BoxView.java,v
retrieving revision 1.22
diff -u -1 -2 -r1.22 BoxView.java
--- javax/swing/text/BoxView.java	25 Aug 2006 11:20:46 -0000	1.22
+++ javax/swing/text/BoxView.java	28 Aug 2006 15:47:47 -0000
@@ -213,74 +213,84 @@
    *
    * In addition this invalidates the layout and resizes the internal cache
    * for the child allocations. The old children's cached allocations can
    * still be accessed (although they are not guaranteed to be valid), and
    * the new children will have an initial offset and span of 0.
    *
    * @param offset the start offset from where to remove children
    * @param length the number of children to remove
    * @param views the views that replace the removed children
    */
   public void replace(int offset, int length, View[] views)
   {
-    int numViews = 0;
-    if (views != null)
-      numViews = views.length;
-
-    // Resize and copy data for cache arrays.
-    // The spansX cache.
-    int oldSize = getViewCount();
-
-    int[] newSpansX = new int[oldSize - length + numViews];
-    System.arraycopy(spans[X_AXIS], 0, newSpansX, 0, offset);
-    System.arraycopy(spans[X_AXIS], offset + length, newSpansX,
-                     offset + numViews,
-                     oldSize - (offset + length));
-    spans[X_AXIS] = newSpansX;
-
-    // The spansY cache.
-    int[] newSpansY = new int[oldSize - length + numViews];
-    System.arraycopy(spans[Y_AXIS], 0, newSpansY, 0, offset);
-    System.arraycopy(spans[Y_AXIS], offset + length, newSpansY,
-                     offset + numViews,
-                     oldSize - (offset + length));
-    spans[Y_AXIS] = newSpansY;
-
-    // The offsetsX cache.
-    int[] newOffsetsX = new int[oldSize - length + numViews];
-    System.arraycopy(offsets[X_AXIS], 0, newOffsetsX, 0, offset);
-    System.arraycopy(offsets[X_AXIS], offset + length, newOffsetsX,
-                     offset + numViews,
-                     oldSize - (offset + length));
-    offsets[X_AXIS] = newOffsetsX;
-
-    // The offsetsY cache.
-    int[] newOffsetsY = new int[oldSize - length + numViews];
-    System.arraycopy(offsets[Y_AXIS], 0, newOffsetsY, 0, offset);
-    System.arraycopy(offsets[Y_AXIS], offset + length, newOffsetsY,
-                     offset + numViews,
-                     oldSize - (offset + length));
-    offsets[Y_AXIS] = newOffsetsY;
+    int oldNumChildren = getViewCount();
 
     // Actually perform the replace.
     super.replace(offset, length, views);
 
+    // Resize and copy data for cache arrays.
+    int newItems = views != null ? views.length : 0;
+    int delta = newItems - length;
+    int src = offset + length;
+    int numMove = oldNumChildren - src;
+    int dst = src + delta;
+    offsets[X_AXIS] = replaceLayoutArray(offsets[X_AXIS], offset,
+                                         oldNumChildren, delta, src, dst,
+                                         numMove);
+    spans[X_AXIS] = replaceLayoutArray(spans[X_AXIS], offset,
+                                       oldNumChildren, delta, src, dst,
+                                       numMove);
+    offsets[Y_AXIS] = replaceLayoutArray(offsets[Y_AXIS], offset,
+                                         oldNumChildren, delta, src, dst,
+                                         numMove);
+    spans[Y_AXIS] = replaceLayoutArray(spans[Y_AXIS], offset,
+                                       oldNumChildren, delta, src, dst,
+                                       numMove);
+
     // Invalidate layout information.
     layoutValid[X_AXIS] = false;
     requirementsValid[X_AXIS] = false;
     layoutValid[Y_AXIS] = false;
     requirementsValid[Y_AXIS] = false;
   }
 
   /**
+   * Helper method. This updates the layout cache arrays in response
+   * to a call to [EMAIL PROTECTED] #replace(int, int, View[])}.
+   *
+   * @param oldArray the old array
+   *
+   * @return the replaced array
+   */
+  private int[] replaceLayoutArray(int[] oldArray, int offset, int numChildren,
+                                   int delta, int src, int dst, int numMove)
+
+  {
+    int[] newArray;
+    if (numChildren + delta > oldArray.length)
+      {
+        int newLength = Math.max(2 * oldArray.length, numChildren + delta);
+        newArray = new int[newLength];
+        System.arraycopy(oldArray, 0, newArray, 0, offset);
+        System.arraycopy(oldArray, src, newArray, dst, numMove);
+      }
+    else
+      {
+        newArray = oldArray;
+        System.arraycopy(newArray, src, newArray, dst, numMove);
+      }
+    return newArray;
+  }
+
+  /**
    * Renders the <code>Element</code> that is associated with this
    * <code>View</code>.
    *
    * @param g the <code>Graphics</code> context to render to
    * @param a the allocated region for the <code>Element</code>
    */
   public void paint(Graphics g, Shape a)
   {
     Rectangle alloc;
     if (a instanceof Rectangle)
       alloc = (Rectangle) a;
     else
@@ -642,42 +652,72 @@
    *
    * @param x the X coordinate
    * @param y the Y coordinate
    * @param r the inner allocation of this <code>BoxView</code> on entry,
    *        the allocation of the found child on exit
    *
    * @return the child <code>View</code> at the specified location
    */
   protected View getViewAtPoint(int x, int y, Rectangle r)
   {
     View result = null;
     int count = getViewCount();
-    Rectangle copy = new Rectangle(r);
-
-    for (int i = 0; i < count; ++i)
+    if (myAxis == X_AXIS)
       {
-        copy.setBounds(r);
-        // The next call modifies copy.
-        childAllocation(i, copy);
-        if (copy.contains(x, y))
-          {
-            // Modify r on success.
-            r.setBounds(copy);
-            result = getView(i);
-            break;
+        // Border case. Requested point is left from the box.
+        if (x < r.x + offsets[X_AXIS][0])
+          {
+            childAllocation(0, r);
+            result = getView(0);
+          }
+        else
+          {
+            // Search views inside box.
+            for (int i = 0; i < count && result == null; i++)
+              {
+                if (x < r.x + offsets[X_AXIS][i])
+                  {
+                    childAllocation(i - 1, r);
+                    result = getView(i - 1);
+                  }
+              }
           }
       }
-    
-    if (result == null && count > 0)
-      return getView(count - 1);
+    else // Same algorithm for Y_AXIS.
+      {
+        // Border case. Requested point is above the box.
+        if (y < r.y + offsets[Y_AXIS][0])
+          {
+            childAllocation(0, r);
+            result = getView(0);
+          }
+        else
+          {
+            // Search views inside box.
+            for (int i = 0; i < count && result == null; i++)
+              {
+                if (y < r.y + offsets[Y_AXIS][i])
+                  {
+                    childAllocation(i - 1, r);
+                    result = getView(i - 1);
+                  }
+              }
+          }
+      }
+    // Not found, other border case: point is right from or below the box.
+    if (result == null)
+      {
+        childAllocation(count - 1, r);
+        result = getView(count - 1);
+      }
     return result;
   }
 
   /**
    * Computes the allocation for a child <code>View</code>. The parameter
    * <code>a</code> stores the allocation of this <code>CompositeView</code>
    * and is then adjusted to hold the allocation of the child view.
    * 
    * @param index
    *          the index of the child <code>View</code>
    * @param a
    *          the allocation of this <code>CompositeView</code> before the
@@ -1030,25 +1070,29 @@
     return ret;
   }
 
   protected void forwardUpdate(DocumentEvent.ElementChange ec, DocumentEvent e,
                                Shape a, ViewFactory vf)
   {
     // FIXME: What to do here?
     super.forwardUpdate(ec, e, a, vf);
   }
 
   public int viewToModel(float x, float y, Shape a, Position.Bias[] bias)
   {
-    // FIXME: What to do here?
+    if (! isAllocationValid())
+      {
+        Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+        setSize(r.width, r.height);
+      }
     return super.viewToModel(x, y, a, bias);
   }
 
   protected boolean flipEastAndWestAtEnds(int position, Position.Bias bias)
   {
     // FIXME: What to do here?
     return super.flipEastAndWestAtEnds(position, bias);
   }
 
   /**
    * Updates the view's cached requirements along the specified axis if
    * necessary. The requirements are only updated if the layout for the

Reply via email to