You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@velocity.apache.org by nb...@apache.org on 2007/09/17 18:57:59 UTC

svn commit: r576521 - in /velocity/engine/trunk/src: java/org/apache/velocity/util/introspection/ test/org/apache/velocity/test/

Author: nbubna
Date: Mon Sep 17 09:57:56 2007
New Revision: 576521

URL: http://svn.apache.org/viewvc?rev=576521&view=rev
Log:
support varargs in method calls (VELOCITY-534)

Added:
    velocity/engine/trunk/src/java/org/apache/velocity/util/introspection/IntrospectionUtils.java   (with props)
    velocity/engine/trunk/src/test/org/apache/velocity/test/VarargMethodsTestCase.java   (with props)
Modified:
    velocity/engine/trunk/src/java/org/apache/velocity/util/introspection/MethodMap.java
    velocity/engine/trunk/src/java/org/apache/velocity/util/introspection/UberspectImpl.java

Added: velocity/engine/trunk/src/java/org/apache/velocity/util/introspection/IntrospectionUtils.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/src/java/org/apache/velocity/util/introspection/IntrospectionUtils.java?rev=576521&view=auto
==============================================================================
--- velocity/engine/trunk/src/java/org/apache/velocity/util/introspection/IntrospectionUtils.java (added)
+++ velocity/engine/trunk/src/java/org/apache/velocity/util/introspection/IntrospectionUtils.java Mon Sep 17 09:57:56 2007
@@ -0,0 +1,192 @@
+package org.apache.velocity.util.introspection;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ *
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:bob@werken.com">Bob McWhirter</a>
+ * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph Reck</a>
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
+ * @author Nathan Bubna
+ * @version $Id: IntrospectionUtils.java 476785 2006-11-19 10:06:21Z henning $
+ */
+public class IntrospectionUtils
+{
+
+    /**
+     * Determines whether a type represented by a class object is
+     * convertible to another type represented by a class object using a
+     * method invocation conversion, treating object types of primitive
+     * types as if they were primitive types (that is, a Boolean actual
+     * parameter type matches boolean primitive formal type). This behavior
+     * is because this method is used to determine applicable methods for
+     * an actual parameter list, and primitive types are represented by
+     * their object duals in reflective method calls.
+     *
+     * @param formal the formal parameter type to which the actual
+     * parameter type should be convertible
+     * @param actual the actual parameter type.
+     * @param possibleVarArg whether or not we're dealing with the last parameter
+     * in the method declaration
+     * @return true if either formal type is assignable from actual type,
+     * or formal is a primitive type and actual is its corresponding object
+     * type or an object type of a primitive type that can be converted to
+     * the formal type.
+     */
+    public static boolean isMethodInvocationConvertible(Class formal,
+                                                        Class actual,
+                                                        boolean possibleVarArg)
+    {
+        /* if it's a null, it means the arg was null */
+        if (actual == null && !formal.isPrimitive())
+        {
+            return true;
+        }
+
+        /* Check for identity or widening reference conversion */
+        if (actual != null && formal.isAssignableFrom(actual))
+        {
+            return true;
+        }
+
+        /* Check for boxing with widening primitive conversion. Note that
+         * actual parameters are never primitives. */
+        if (formal.isPrimitive())
+        {
+            if(formal == Boolean.TYPE && actual == Boolean.class)
+                return true;
+            if(formal == Character.TYPE && actual == Character.class)
+                return true;
+            if(formal == Byte.TYPE && actual == Byte.class)
+                return true;
+            if(formal == Short.TYPE &&
+               (actual == Short.class || actual == Byte.class))
+                return true;
+            if(formal == Integer.TYPE &&
+               (actual == Integer.class || actual == Short.class ||
+                actual == Byte.class))
+                return true;
+            if(formal == Long.TYPE &&
+               (actual == Long.class || actual == Integer.class ||
+                actual == Short.class || actual == Byte.class))
+                return true;
+            if(formal == Float.TYPE &&
+               (actual == Float.class || actual == Long.class ||
+                actual == Integer.class || actual == Short.class ||
+                actual == Byte.class))
+                return true;
+            if(formal == Double.TYPE &&
+               (actual == Double.class || actual == Float.class ||
+                actual == Long.class || actual == Integer.class ||
+                actual == Short.class || actual == Byte.class))
+                return true;
+        }
+
+        /* Check for vararg conversion. */
+        if (possibleVarArg && formal.isArray())
+        {
+            if (actual.isArray())
+            {
+                actual = actual.getComponentType();
+            }
+            return isMethodInvocationConvertible(formal.getComponentType(),
+                                                 actual, false);
+        }
+        return false;
+    }
+
+    /**
+     * Determines whether a type represented by a class object is
+     * convertible to another type represented by a class object using a
+     * method invocation conversion, without matching object and primitive
+     * types. This method is used to determine the more specific type when
+     * comparing signatures of methods.
+     *
+     * @param formal the formal parameter type to which the actual
+     * parameter type should be convertible
+     * @param actual the actual parameter type.
+     * @param possibleVarArg whether or not we're dealing with the last parameter
+     * in the method declaration
+     * @return true if either formal type is assignable from actual type,
+     * or formal and actual are both primitive types and actual can be
+     * subject to widening conversion to formal.
+     */
+    public static boolean isStrictMethodInvocationConvertible(Class formal,
+                                                              Class actual,
+                                                              boolean possibleVarArg)
+    {
+        /* we shouldn't get a null into, but if so */
+        if (actual == null && !formal.isPrimitive())
+        {
+            return true;
+        }
+
+        /* Check for identity or widening reference conversion */
+        if(formal.isAssignableFrom(actual))
+        {
+            return true;
+        }
+
+        /* Check for widening primitive conversion. */
+        if(formal.isPrimitive())
+        {
+            if(formal == Short.TYPE && (actual == Byte.TYPE))
+                return true;
+            if(formal == Integer.TYPE &&
+               (actual == Short.TYPE || actual == Byte.TYPE))
+                return true;
+            if(formal == Long.TYPE &&
+               (actual == Integer.TYPE || actual == Short.TYPE ||
+                actual == Byte.TYPE))
+                return true;
+            if(formal == Float.TYPE &&
+               (actual == Long.TYPE || actual == Integer.TYPE ||
+                actual == Short.TYPE || actual == Byte.TYPE))
+                return true;
+            if(formal == Double.TYPE &&
+               (actual == Float.TYPE || actual == Long.TYPE ||
+                actual == Integer.TYPE || actual == Short.TYPE ||
+                actual == Byte.TYPE))
+                return true;
+        }
+
+        /* Check for vararg conversion. */
+        if (possibleVarArg && formal.isArray())
+        {
+            if (actual.isArray())
+            {
+                actual = actual.getComponentType();
+            }
+            return isStrictMethodInvocationConvertible(formal.getComponentType(),
+                                                       actual, false);
+        }
+        return false;
+    }
+}

Propchange: velocity/engine/trunk/src/java/org/apache/velocity/util/introspection/IntrospectionUtils.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: velocity/engine/trunk/src/java/org/apache/velocity/util/introspection/IntrospectionUtils.java
------------------------------------------------------------------------------
    svn:keywords = Revision

Propchange: velocity/engine/trunk/src/java/org/apache/velocity/util/introspection/IntrospectionUtils.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: velocity/engine/trunk/src/java/org/apache/velocity/util/introspection/MethodMap.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/src/java/org/apache/velocity/util/introspection/MethodMap.java?rev=576521&r1=576520&r2=576521&view=diff
==============================================================================
--- velocity/engine/trunk/src/java/org/apache/velocity/util/introspection/MethodMap.java (original)
+++ velocity/engine/trunk/src/java/org/apache/velocity/util/introspection/MethodMap.java Mon Sep 17 09:57:56 2007
@@ -244,12 +244,13 @@
         {
             if(c1[i] != c2[i])
             {
+                boolean last = (i == c1.length - 1);
                 c1MoreSpecific =
                     c1MoreSpecific ||
-                    isStrictMethodInvocationConvertible(c2[i], c1[i]);
+                    isStrictConvertible(c2[i], c1[i], last);
                 c2MoreSpecific =
                     c2MoreSpecific ||
-                    isStrictMethodInvocationConvertible(c1[i], c2[i]);
+                    isStrictConvertible(c1[i], c2[i], last);
             }
         }
 
@@ -318,158 +319,83 @@
     {
         Class[] methodArgs = method.getParameterTypes();
 
-        if(methodArgs.length != classes.length)
+        if (methodArgs.length > classes.length)
         {
-            return false;
-        }
-
-        for(int i = 0; i < classes.length; ++i)
-        {
-            if(!isMethodInvocationConvertible(methodArgs[i], classes[i]))
+            // if there's just one more methodArg than class arg
+            // and the last methodArg is an array, then treat it as a vararg
+            if (methodArgs.length == classes.length + 1 &&
+                methodArgs[methodArgs.length - 1].isArray())
+            {
+                return true;
+            }
+            else
             {
                 return false;
             }
         }
-
-        return true;
-    }
-
-    /**
-     * Determines whether a type represented by a class object is
-     * convertible to another type represented by a class object using a
-     * method invocation conversion, treating object types of primitive
-     * types as if they were primitive types (that is, a Boolean actual
-     * parameter type matches boolean primitive formal type). This behavior
-     * is because this method is used to determine applicable methods for
-     * an actual parameter list, and primitive types are represented by
-     * their object duals in reflective method calls.
-     *
-     * @param formal the formal parameter type to which the actual
-     * parameter type should be convertible
-     * @param actual the actual parameter type.
-     * @return true if either formal type is assignable from actual type,
-     * or formal is a primitive type and actual is its corresponding object
-     * type or an object type of a primitive type that can be converted to
-     * the formal type.
-     */
-    private static boolean isMethodInvocationConvertible(Class formal,
-                                                         Class actual)
-    {
-        /*
-         * if it's a null, it means the arg was null
-         */
-        if (actual == null && !formal.isPrimitive())
+        else if (methodArgs.length == classes.length)
         {
-            return true;
+            // this will properly match when the last methodArg
+            // is an array/varargs and the last class is the type of array
+            // (e.g. String when the method is expecting String...)
+            for(int i = 0; i < classes.length; ++i)
+            {
+                if(!isConvertible(methodArgs[i], classes[i], false))
+                {
+                    // if we're on the last arg and the method expects an array
+                    if (i == classes.length - 1 && methodArgs[i].isArray())
+                    {
+                        // check to see if the last arg is convertible
+                        // to the array's component type
+                        return isConvertible(methodArgs[i], classes[i], true);
+                    }
+                    return false;
+                }
+            }
         }
-
-        /*
-         *  Check for identity or widening reference conversion
-         */
-
-        if (actual != null && formal.isAssignableFrom(actual))
+        else // more arguments given than the method accepts; check for varargs
         {
-            return true;
-        }
+            // check that the last methodArg is an array
+            Class lastarg = methodArgs[methodArgs.length - 1];
+            if (!lastarg.isArray())
+            {
+                return false;
+            }
 
-        /*
-         * Check for boxing with widening primitive conversion. Note that
-         * actual parameters are never primitives.
-         */
+            // check that they all match up to the last method arg
+            for (int i = 0; i < methodArgs.length - 1; ++i)
+            {
+                if (!isConvertible(methodArgs[i], classes[i], false))
+                {
+                    return false;
+                }
+            }
 
-        if (formal.isPrimitive())
-        {
-            if(formal == Boolean.TYPE && actual == Boolean.class)
-                return true;
-            if(formal == Character.TYPE && actual == Character.class)
-                return true;
-            if(formal == Byte.TYPE && actual == Byte.class)
-                return true;
-            if(formal == Short.TYPE &&
-               (actual == Short.class || actual == Byte.class))
-                return true;
-            if(formal == Integer.TYPE &&
-               (actual == Integer.class || actual == Short.class ||
-                actual == Byte.class))
-                return true;
-            if(formal == Long.TYPE &&
-               (actual == Long.class || actual == Integer.class ||
-                actual == Short.class || actual == Byte.class))
-                return true;
-            if(formal == Float.TYPE &&
-               (actual == Float.class || actual == Long.class ||
-                actual == Integer.class || actual == Short.class ||
-                actual == Byte.class))
-                return true;
-            if(formal == Double.TYPE &&
-               (actual == Double.class || actual == Float.class ||
-                actual == Long.class || actual == Integer.class ||
-                actual == Short.class || actual == Byte.class))
-                return true;
+            // check that all remaining arguments are convertible to the vararg type
+            Class vararg = lastarg.getComponentType();
+            for (int i = methodArgs.length - 1; i < classes.length; ++i)
+            {
+                if (!isConvertible(vararg, classes[i], false))
+                {
+                    return false;
+                }
+            }
         }
 
-        return false;
+        return true;
     }
 
-    /**
-     * Determines whether a type represented by a class object is
-     * convertible to another type represented by a class object using a
-     * method invocation conversion, without matching object and primitive
-     * types. This method is used to determine the more specific type when
-     * comparing signatures of methods.
-     *
-     * @param formal the formal parameter type to which the actual
-     * parameter type should be convertible
-     * @param actual the actual parameter type.
-     * @return true if either formal type is assignable from actual type,
-     * or formal and actual are both primitive types and actual can be
-     * subject to widening conversion to formal.
-     */
-    private static boolean isStrictMethodInvocationConvertible(Class formal,
-                                                               Class actual)
+    private static boolean isConvertible(Class formal, Class actual,
+                                         boolean possibleVarArg)
     {
-        /*
-         * we shouldn't get a null into, but if so
-         */
-        if (actual == null && !formal.isPrimitive())
-        {
-            return true;
-        }
-
-        /*
-         *  Check for identity or widening reference conversion
-         */
-
-        if(formal.isAssignableFrom(actual))
-        {
-            return true;
-        }
-
-        /*
-         *  Check for widening primitive conversion.
-         */
+        return IntrospectionUtils.
+            isMethodInvocationConvertible(formal, actual, possibleVarArg);
+    }
 
-        if(formal.isPrimitive())
-        {
-            if(formal == Short.TYPE && (actual == Byte.TYPE))
-                return true;
-            if(formal == Integer.TYPE &&
-               (actual == Short.TYPE || actual == Byte.TYPE))
-                return true;
-            if(formal == Long.TYPE &&
-               (actual == Integer.TYPE || actual == Short.TYPE ||
-                actual == Byte.TYPE))
-                return true;
-            if(formal == Float.TYPE &&
-               (actual == Long.TYPE || actual == Integer.TYPE ||
-                actual == Short.TYPE || actual == Byte.TYPE))
-                return true;
-            if(formal == Double.TYPE &&
-               (actual == Float.TYPE || actual == Long.TYPE ||
-                actual == Integer.TYPE || actual == Short.TYPE ||
-                actual == Byte.TYPE))
-                return true;
-        }
-        return false;
+    private static boolean isStrictConvertible(Class formal, Class actual,
+                                               boolean possibleVarArg)
+    {
+        return IntrospectionUtils.
+            isStrictMethodInvocationConvertible(formal, actual, possibleVarArg);
     }
 }

Modified: velocity/engine/trunk/src/java/org/apache/velocity/util/introspection/UberspectImpl.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/src/java/org/apache/velocity/util/introspection/UberspectImpl.java?rev=576521&r1=576520&r2=576521&view=diff
==============================================================================
--- velocity/engine/trunk/src/java/org/apache/velocity/util/introspection/UberspectImpl.java (original)
+++ velocity/engine/trunk/src/java/org/apache/velocity/util/introspection/UberspectImpl.java Mon Sep 17 09:57:56 2007
@@ -19,6 +19,7 @@
  * under the License.    
  */
 
+import java.lang.reflect.Array;
 import java.lang.reflect.Method;
 import java.util.Collection;
 import java.util.Enumeration;
@@ -167,7 +168,8 @@
         {
             return new VelMethodImpl(m);
         }
-        else if (obj.getClass().isArray())
+        // if it's an array, check if we support this method automagically
+        if (obj.getClass().isArray())
         {
             // only return *supported* array methods
             if (VelArrayMethod.supports(methodName, args))
@@ -283,6 +285,7 @@
     public static class VelMethodImpl implements VelMethod
     {
         final Method method;
+        Boolean isVarArg;
 
         /**
          * @param m
@@ -300,10 +303,102 @@
         /**
          * @see VelMethod#invoke(java.lang.Object, java.lang.Object[])
          */
-        public Object invoke(Object o, Object[] params)
+        public Object invoke(Object o, Object[] actual)
             throws Exception
         {
-            return method.invoke(o, params);
+            if (isVarArg())
+            {
+                Class[] formal = method.getParameterTypes();
+                int index = formal.length - 1;
+                Class type = formal[index].getComponentType();
+                if (actual.length >= index)
+                {
+                    actual = handleVarArg(type, index, actual);
+                }
+            }
+            return method.invoke(o, actual);
+        }
+
+        /**
+         * @returns true if this method can accept a variable number of arguments
+         */
+        public boolean isVarArg()
+        {
+            if (isVarArg == null)
+            {
+                Class[] formal = method.getParameterTypes();
+                if (formal == null || formal.length == 0)
+                {
+                    this.isVarArg = Boolean.FALSE;
+                }
+                else
+                {
+                    Class last = formal[formal.length - 1];
+                    // if the last arg is an array, then
+                    // we consider this a varargs method
+                    this.isVarArg = Boolean.valueOf(last.isArray());
+                }
+            }
+            return isVarArg.booleanValue();
+        }
+
+        /**
+         * @param type The vararg class type (aka component type
+         *             of the expected array arg)
+         * @param index The index of the vararg in the method declaration
+         *              (This will always be one less than the number of
+         *               expected arguments.)
+         * @param actual The actual parameters being passed to this method
+         * @returns The actual parameters adjusted for the varargs in order
+         *          to fit the method declaration.
+         */
+        private Object[] handleVarArg(final Class type,
+                                      final int index,
+                                      Object[] actual)
+        {
+            // if no values are being passed into the vararg
+            if (actual.length == index)
+            {
+                // create an empty array of the expected type
+                actual = new Object[] { Array.newInstance(type, 0) };
+            }
+            // if one value is being passed into the vararg
+            else if (actual.length == index + 1)
+            {
+                // make sure the last arg is an array of the expected type
+                if (IntrospectionUtils.isMethodInvocationConvertible(type,
+                                                                     actual[index].getClass(),
+                                                                     false))
+                {
+                    // create a 1-length array to hold and replace the last param
+                    Object lastActual = Array.newInstance(type, 1);
+                    Array.set(lastActual, 0, actual[index]);
+                    actual[index] = lastActual;
+                }
+            }
+            // if multiple values are being passed into the vararg
+            else if (actual.length > index + 1)
+            {
+                // put the last and extra actual in an array of the expected type
+                int size = actual.length - index;
+                Object lastActual = Array.newInstance(type, size);
+                for (int i = 0; i < size; i++)
+                {
+                    Array.set(lastActual, i, actual[index + i]);
+                }
+
+                // put all into a new actual array of the appropriate size
+                Object[] newActual = new Object[index + 1];
+                for (int i = 0; i < index; i++)
+                {
+                    newActual[i] = actual[i];
+                }
+                newActual[index] = lastActual;
+
+                // replace the old actual array
+                actual = newActual;
+            }
+            return actual;
         }
 
         /**

Added: velocity/engine/trunk/src/test/org/apache/velocity/test/VarargMethodsTestCase.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/src/test/org/apache/velocity/test/VarargMethodsTestCase.java?rev=576521&view=auto
==============================================================================
--- velocity/engine/trunk/src/test/org/apache/velocity/test/VarargMethodsTestCase.java (added)
+++ velocity/engine/trunk/src/test/org/apache/velocity/test/VarargMethodsTestCase.java Mon Sep 17 09:57:56 2007
@@ -0,0 +1,213 @@
+package org.apache.velocity.test;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+
+import java.io.StringWriter;
+import java.lang.reflect.Array;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.runtime.log.SystemLogChute;
+
+/**
+ * Used to check that vararg method calls on references work properly
+ */
+public class VarargMethodsTestCase extends TestCase
+{
+    private VelocityEngine engine;
+    private VelocityContext context;
+
+    public VarargMethodsTestCase(final String name)
+    {
+        super(name);
+    }
+
+    public static Test suite ()
+    {
+        return new TestSuite(VarargMethodsTestCase.class);
+    }
+
+    public void setUp() throws Exception
+    {
+        engine = new VelocityEngine();
+
+        // make the engine's log output go to the test-report
+        SystemLogChute log = new SystemLogChute();
+        log.setEnabledLevel(SystemLogChute.INFO_ID);
+        log.setSystemErrLevel(SystemLogChute.WARN_ID);
+        engine.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, log);
+
+        context = new VelocityContext();
+        context.put("nice", new NiceTool());
+        context.put("nasty", new NastyTool());
+        context.put("objects", new Object[] { this, Test.class });
+        context.put("strings", new String[] { "one", "two" });
+        context.put("doubles", new double[] { 1.5, 2.5 });
+        context.put("float", Float.valueOf(1f));
+        context.put("ints", new int[] { 1, 2 });
+    }
+
+    public void tearDown()
+    {
+        engine = null;
+        context = null;
+    }
+
+    public void testStrings()
+    {
+        assertEvalEquals("onetwo", "$nice.var($strings)");
+        assertEvalEquals("onetwo", "$nice.var('one','two')");
+        assertEvalEquals("one", "$nice.var('one')");
+        assertEvalEquals("", "$nice.var()");
+    }
+
+    public void testDoubles()
+    {
+        assertEvalEquals("4.0", "$nice.add($doubles)");
+        assertEvalEquals("3.0", "$nice.add(1,2)");
+        assertEvalEquals("1.0", "$nice.add(1)");
+        assertEvalEquals("0.0", "$nice.add()");
+    }
+
+    public void testFloatToDoubleVarArg()
+    {
+        assertEvalEquals("1.0", "$nice.add($float)");
+    }
+
+    public void testStringVsStrings()
+    {
+        assertEvalEquals("onlyone", "$nasty.var('one')");
+        assertEvalEquals("onlynull", "$nasty.var($null)");
+        assertEvalEquals("", "$nasty.var()");
+    }
+
+    public void testIntVsDoubles()
+    {
+        assertEvalEquals("1", "$nasty.add(1)");
+        assertEvalEquals("1.0", "$nasty.add(1.0)");
+        assertEvalEquals("3.0", "$nasty.add(1.0,2)");
+    }
+
+    public void testInts()
+    {
+        assertEvalEquals("3", "$nasty.add($ints)");
+        assertEvalEquals("3", "$nasty.add(1,2)");
+        assertEvalEquals("1", "$nasty.add(1)");
+        // add(int[]) wins because it is "more specific"
+        assertEvalEquals("0", "$nasty.add()");
+    }
+
+    public void testStringsVsObjectsAKASubclassVararg()
+    {
+        assertEvalEquals("objects", "$nice.test($objects)");
+        assertEvalEquals("objects", "$nice.test($nice,$nasty,$ints)");
+        assertEvalEquals("strings", "$nice.test('foo')");
+    }
+
+
+    protected void assertEvalEquals(String expected, String template)
+    {
+        try
+        {
+            assertEquals(expected, evaluate(template));
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private String evaluate(String template) throws Exception
+    {
+        StringWriter writer = new StringWriter();
+        // use template as its own name, since our templates are short
+        engine.evaluate(context, writer, template, template);
+        return writer.toString();
+    }
+
+
+
+    public static class NiceTool
+    {
+        public String var(String[] ss)
+        {
+            StringBuffer out = new StringBuffer();
+            for (int i=0; i < ss.length; i++)
+            {
+                out.append(ss[i]);
+            }
+            return out.toString();
+        }
+
+        public double add(double[] dd)
+        {
+            double total = 0;
+            for (int i=0; i < dd.length; i++)
+            {
+                total += dd[i];
+            }
+            return total;
+        }
+
+        public String test(Object[] oo)
+        {
+            return "objects";
+        }
+
+        public String test(String[] oo)
+        {
+            return "strings";
+        }
+
+    }
+
+    public static class NastyTool extends NiceTool
+    {
+        public String var(String s)
+        {
+            return "only"+s;
+        }
+
+        public int add(int[] ii)
+        {
+            int total = 0;
+            for (int i=0; i < ii.length; i++)
+            {
+                total += ii[i];
+            }
+            return total;
+        }
+
+        public int add(int i)
+        {
+            return i;
+        }
+
+    }
+
+}
+
+

Propchange: velocity/engine/trunk/src/test/org/apache/velocity/test/VarargMethodsTestCase.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: velocity/engine/trunk/src/test/org/apache/velocity/test/VarargMethodsTestCase.java
------------------------------------------------------------------------------
    svn:keywords = Revision

Propchange: velocity/engine/trunk/src/test/org/apache/velocity/test/VarargMethodsTestCase.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain