Author: cbrisson Date: Sat Apr 8 13:20:55 2017 New Revision: 1790680 URL: http://svn.apache.org/viewvc?rev=1790680&view=rev Log: [engine] Full review of method disambiguation
Modified: velocity/engine/trunk/pom.xml velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectionUtils.java velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/MethodMap.java velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/UberspectImplTestCase.java Modified: velocity/engine/trunk/pom.xml URL: http://svn.apache.org/viewvc/velocity/engine/trunk/pom.xml?rev=1790680&r1=1790679&r2=1790680&view=diff ============================================================================== --- velocity/engine/trunk/pom.xml (original) +++ velocity/engine/trunk/pom.xml Sat Apr 8 13:20:55 2017 @@ -83,8 +83,8 @@ <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> - <debug>false</debug> - <optimize>true</optimize> + <debug>true</debug> + <optimize>false</optimize> <showDeprecation>true</showDeprecation> <showWarning>true</showWarning> <source>${maven.compiler.source}</source> Modified: velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectionUtils.java URL: http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectionUtils.java?rev=1790680&r1=1790679&r2=1790680&view=diff ============================================================================== --- velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectionUtils.java (original) +++ velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectionUtils.java Sat Apr 8 13:20:55 2017 @@ -216,10 +216,10 @@ public class IntrospectionUtils Class actual, boolean possibleVarArg) { - /* we shouldn't get a null into, but if so */ - if (actual == null && !formal.isPrimitive()) + /* Check for nullity */ + if (actual == null) { - return true; + return !formal.isPrimitive(); } /* Check for identity or widening reference conversion */ Modified: velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/MethodMap.java URL: http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/MethodMap.java?rev=1790680&r1=1790679&r2=1790680&view=diff ============================================================================== --- velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/MethodMap.java (original) +++ velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/MethodMap.java Sat Apr 8 13:20:55 2017 @@ -19,10 +19,14 @@ package org.apache.velocity.util.introsp * under the License. */ +import org.apache.velocity.exception.VelocityException; + import java.lang.reflect.Method; import java.util.ArrayList; -import java.util.Iterator; +import java.util.Arrays; +import java.util.LinkedList; import java.util.List; +import java.util.ListIterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -38,9 +42,17 @@ import java.util.concurrent.ConcurrentHa */ public class MethodMap { - private static final int MORE_SPECIFIC = 0; - private static final int LESS_SPECIFIC = 1; - private static final int INCOMPARABLE = 2; + /* Constants for specificity */ + private static final int INCOMPARABLE = 0; + private static final int MORE_SPECIFIC = 1; + private static final int EQUIVALENT = 2; + private static final int LESS_SPECIFIC = 3; + + /* Constants for applicability */ + private static final int NOT_CONVERTIBLE = 0; + private static final int EXPLICITLY_CONVERTIBLE = 1; + private static final int IMPLCITLY_CONVERTIBLE = 2; + private static final int STRICTLY_CONVERTIBLE = 3; ConversionHandler conversionHandler; @@ -155,12 +167,43 @@ public class MethodMap return getBestMatch(methodList, classes); } + private class Match + { + /* target method */ + Method method; + + /* cache arguments classes array */ + Class[] methodTypes; + + /* specificity: how does the best match compare to provided arguments + * one one LESS_SPECIFIC, MORE_SPECIFIC or INCOMPARABLE */ + int specificity; + + /* applicability which conversion level is needed against provided arguments + * one of STRICTLY_CONVERTIBLE, IMPLICITLY_CONVERTIBLE and EXPLICITLY_CONVERTIBLE_ */ + int applicability; + + Match(Method method, int applicability, Class[] unboxedArgs) + { + this.method = method; + this.applicability = applicability; + this.methodTypes = method.getParameterTypes(); + this.specificity = compare(methodTypes, unboxedArgs); + } + } + + private static boolean onlyNullOrObjects(Class[] args) + { + for (Class cls : args) + { + if (cls != null && cls != Object.class) return false; + } + return true; + } + private Method getBestMatch(List<Method> methods, Class[] args) { - List equivalentMatches = null; - Method bestMatch = null; - Class[] bestMatchTypes = null; - int bestMatchComp = INCOMPARABLE; /* how does the best match compare to provided type */ + List<Match> bestMatches = new LinkedList<Match>(); Class[] unboxedArgs = new Class[args.length]; for (int i = 0; i < args.length; ++i) { @@ -168,128 +211,79 @@ public class MethodMap } for (Method method : methods) { - if (isApplicable(method, args)) + int applicability = getApplicability(method, args); + if (applicability > NOT_CONVERTIBLE) { - if (bestMatch == null) + Match match = new Match(method, applicability, unboxedArgs); + if (bestMatches.size() == 0) { - bestMatch = method; - bestMatchTypes = method.getParameterTypes(); - bestMatchComp = compare(bestMatchTypes, unboxedArgs); - } else - { - Class[] methodTypes = method.getParameterTypes(); - switch (compare(methodTypes, bestMatchTypes)) - { - case MORE_SPECIFIC: - /* do not retain method if it's more specific than (or incomparable to) provided (unboxed) arguments - * while best batch is less specific - */ - if (bestMatchComp == LESS_SPECIFIC && compare(methodTypes, unboxedArgs) != LESS_SPECIFIC) - { - break; - } - if (equivalentMatches == null) - { - bestMatch = method; - bestMatchTypes = methodTypes; - bestMatchComp = compare(bestMatchTypes, unboxedArgs); - } else + bestMatches.add(match); + } + else + { + /* filter existing matches */ + boolean keepMethod = true; + for (ListIterator<Match> it = bestMatches.listIterator(); keepMethod && it.hasNext();) + { + Match best = it.next(); + /* do not retain match if it's more specific than (or incomparable to) provided (unboxed) arguments + * while one of the best matches is less specific + */ + if (best.specificity == LESS_SPECIFIC && match.specificity < EQUIVALENT) /* != LESS_SPECIFIC && != EQUIVALENT */ + { + keepMethod = false; + } + /* drop considered best match if match is less specific than (unboxed) provided args while + * the considered best match is more specific or incomparable + */ + else if (match.specificity == LESS_SPECIFIC && best.specificity < EQUIVALENT) /* != LESS_SPECIFIC && != EQUIVALENT */ + { + it.remove(); + } + /* compare methods between them */ + else + { + /* but only if some provided args are non null and not Object */ + if (!onlyNullOrObjects(args)) { - /* have to beat all other ambiguous ones... */ - int ambiguities = equivalentMatches.size(); - for (int a = 0; a < ambiguities; a++) + switch (compare(match.methodTypes, best.methodTypes)) { - Method other = (Method) equivalentMatches.get(a); - switch (compare(methodTypes, other.getParameterTypes())) - { - case MORE_SPECIFIC: - /* ...and thus replace them all... - * but do not retain method if it's more specific than (or incomparable to) provided (unboxed) arguments - * while best batch is less specific - */ - if (bestMatchComp == LESS_SPECIFIC && compare(methodTypes, unboxedArgs) != LESS_SPECIFIC) - { - break; - } - bestMatch = method; - bestMatchTypes = methodTypes; - bestMatchComp = compare(bestMatchTypes, unboxedArgs); - equivalentMatches = null; - ambiguities = 0; - break; - - case INCOMPARABLE: - /* ...join them...*/ - equivalentMatches.add(method); - break; - - case LESS_SPECIFIC: - /* retain it anyway if less specific than (unboxed) provided args while - * bestmatch is more specific - */ - if (bestMatchComp == MORE_SPECIFIC && compare(methodTypes, unboxedArgs) == LESS_SPECIFIC) - { - bestMatch = method; - bestMatchTypes = methodTypes; - bestMatchComp = compare(bestMatchTypes, unboxedArgs); - equivalentMatches = null; - ambiguities = 0; - } - break; - } + case LESS_SPECIFIC: + keepMethod = false; + break; + case MORE_SPECIFIC: + it.remove(); + break; + case EQUIVALENT: + case INCOMPARABLE: + /* compare applicability */ + if (best.applicability > match.applicability) + { + keepMethod = false; + } else if (best.applicability < match.applicability) + { + it.remove(); + } + /* otherwise it's an equivalent match */ + break; } } - break; - - case INCOMPARABLE: - /* do not retain method if it's more specific than (or incomparable to) provided (unboxed) arguments - * while best batch is less specific - */ - if (bestMatchComp == LESS_SPECIFIC && compare(methodTypes, unboxedArgs) != LESS_SPECIFIC) - { - break; - } - /* retain it anyway if less specific than (unboxed) provided args while - * bestmatch is more specific or incomparable - */ - if (bestMatchComp != LESS_SPECIFIC && compare(methodTypes, unboxedArgs) == LESS_SPECIFIC) - { - bestMatch = method; - bestMatchTypes = methodTypes; - bestMatchComp = compare(bestMatchTypes, unboxedArgs); - equivalentMatches = null; - break; - } - if (equivalentMatches == null) - { - equivalentMatches = new ArrayList(bestMatchTypes.length); - } - equivalentMatches.add(method); - break; - - case LESS_SPECIFIC: - /* retain it anyway if less specific than (unboxed) provided args while - * bestmatch is more specific or incomparable - */ - if (bestMatchComp != LESS_SPECIFIC && compare(methodTypes, unboxedArgs) == LESS_SPECIFIC) - { - bestMatch = method; - bestMatchTypes = methodTypes; - bestMatchComp = compare(bestMatchTypes, unboxedArgs); - equivalentMatches = null; - } - break; + } + } + if (keepMethod) + { + bestMatches.add(match); } } } } - if (equivalentMatches != null) + switch (bestMatches.size()) { - System.out.println("ambiguous: "+equivalentMatches); - throw new AmbiguousException(); + case 0: return null; + case 1: return bestMatches.get(0).method; + default: throw new AmbiguousException(); } - return bestMatch; } /** @@ -317,104 +311,172 @@ public class MethodMap { boolean c1MoreSpecific = false; boolean c2MoreSpecific = false; + boolean fixedLengths = false; // compare lengths to handle comparisons where the size of the arrays // doesn't match, but the methods are both applicable due to the fact // that one is a varargs method if (c1.length > c2.length) { - return MORE_SPECIFIC; - } - if (c2.length > c1.length) - { - return LESS_SPECIFIC; - } - - // ok, move on and compare those of equal lengths - for(int i = 0; i < c1.length; ++i) - { - if(c1[i] != c2[i] && c1[i] != null && c2[i] != null) + int l2 = c2.length; + if (l2 == 0) { - boolean last = (i == c1.length - 1); - c1MoreSpecific = - c1MoreSpecific || - isStrictConvertible(c2[i], c1[i], last) || - c2[i] == Object.class;//Object is always least-specific - c2MoreSpecific = - c2MoreSpecific || - isStrictConvertible(c1[i], c2[i], last) || - c1[i] == Object.class;//Object is always least-specific + return MORE_SPECIFIC; } + c2 = Arrays.copyOf(c2, c1.length); + Class itemClass = c2[l2 - 1].getComponentType(); + /* if item class is null, then it implies the vaarg is #1 + * (and receives an empty array) + */ + if (itemClass == null) + { + /* by construct, we have c1.length = l2 + 1 */ + c2[c1.length - 1] = c1[c1.length - 1]; + } + else + { + for (int i = l2 - 1; i < c1.length; ++i) + { + /* also overwrite the vaargs itself */ + c2[i] = itemClass; + } + } + fixedLengths = true; } - - /* check for conversions */ - if (!c1MoreSpecific && !c2MoreSpecific) + else if (c2.length > c1.length) { - for(int i = 0; i < c1.length; ++i) + int l1 = c1.length; + if (l1 == 0) + { + return LESS_SPECIFIC; + } + c1 = Arrays.copyOf(c1, c2.length); + Class itemClass = c1[l1 - 1].getComponentType(); + /* if item class is null, then it implies the vaarg is #2 + * (and receives an empty array) + */ + if (itemClass == null) { - boolean last = (i == c1.length - 1); - if (c1[i] != c2[i] && c1[i] != null && c2[i] != null) + /* by construct, we have c2.length = l1 + 1 */ + c1[c2.length - 1] = c2[c2.length - 1]; + } + else + { + for (int i = l1 - 1; i < c2.length; ++i) { - c1MoreSpecific = c1MoreSpecific || isConvertible(c2[i], c1[i], last); - c2MoreSpecific = c2MoreSpecific || isConvertible(c1[i], c2[i], last); + /* also overwrite the vaargs itself */ + c1[i] = itemClass; } } + fixedLengths = true; } - if(c1MoreSpecific) + /* ok, move on and compare those of equal lengths */ + int fromC1toC2 = STRICTLY_CONVERTIBLE; + int fromC2toC1 = STRICTLY_CONVERTIBLE; + for(int i = 0; i < c1.length; ++i) { - if(c2MoreSpecific) + boolean last = !fixedLengths && (i == c1.length - 1); + if (c1[i] != c2[i]) { - /* - * If one method accepts varargs and the other does not, - * call the non-vararg one more specific. - */ - boolean last1Array = c1[c1.length - 1].isArray(); - boolean last2Array = c2[c2.length - 1].isArray(); - if (last1Array && !last2Array) + if (c1[i] == null) { - return LESS_SPECIFIC; + fromC2toC1 = NOT_CONVERTIBLE; + if (c2[i].isPrimitive()) + { + fromC1toC2 = NOT_CONVERTIBLE; + } } - if (!last1Array && last2Array) + else if (c2[i] == null) { - return MORE_SPECIFIC; + fromC1toC2 = NOT_CONVERTIBLE; + if (c1[i].isPrimitive()) + { + fromC2toC1 = NOT_CONVERTIBLE; + } + } + else + { + switch (fromC1toC2) + { + case STRICTLY_CONVERTIBLE: + if (isStrictConvertible(c2[i], c1[i], last)) break; + fromC1toC2 = IMPLCITLY_CONVERTIBLE; + case IMPLCITLY_CONVERTIBLE: + if (isConvertible(c2[i], c1[i], last)) break; + fromC1toC2 = EXPLICITLY_CONVERTIBLE; + case EXPLICITLY_CONVERTIBLE: + if (isExplicitlyConvertible(c2[i], c1[i], last)) break; + fromC1toC2 = NOT_CONVERTIBLE; + } + switch (fromC2toC1) + { + case STRICTLY_CONVERTIBLE: + if (isStrictConvertible(c1[i], c2[i], last)) break; + fromC2toC1 = IMPLCITLY_CONVERTIBLE; + case IMPLCITLY_CONVERTIBLE: + if (isConvertible(c1[i], c2[i], last)) break; + fromC2toC1 = EXPLICITLY_CONVERTIBLE; + case EXPLICITLY_CONVERTIBLE: + if (isExplicitlyConvertible(c1[i], c2[i], last)) break; + fromC2toC1 = NOT_CONVERTIBLE; + } } - - /* - * Incomparable due to cross-assignable arguments (i.e. - * foo(String, Object) vs. foo(Object, String)) - */ - return INCOMPARABLE; } + } - return MORE_SPECIFIC; + if (fromC1toC2 == NOT_CONVERTIBLE && fromC2toC1 == NOT_CONVERTIBLE) + { + /* + * Incomparable due to cross-assignable arguments (i.e. + * foo(String, Foo) vs. foo(Foo, String)) + */ + return INCOMPARABLE; } - if(c2MoreSpecific) + if (fromC1toC2 > fromC2toC1) + { + return MORE_SPECIFIC; + } + else if (fromC2toC1 > fromC1toC2) { return LESS_SPECIFIC; } - - /* - * Incomparable due to non-related arguments (i.e. - * foo(Runnable) vs. foo(Serializable)) - */ - - return INCOMPARABLE; + else + { + /* + * If one method accepts varargs and the other does not, + * call the non-vararg one more specific. + */ + boolean last1Array = !fixedLengths && c1[c1.length - 1].isArray(); + boolean last2Array = !fixedLengths && c2[c2.length - 1].isArray(); + if (last1Array && !last2Array) + { + return LESS_SPECIFIC; + } + if (!last1Array && last2Array) + { + return MORE_SPECIFIC; + } + } + return EQUIVALENT; } /** - * Returns true if the supplied method is applicable to actual - * argument types. + * Returns the applicability of the supplied method against actual argument types. * * @param method method that will be called * @param classes arguments to method - * @return true if method is applicable to arguments + * @return the level of applicability: + * 0 = not applicable + * 1 = explicitly applicable (i.e. using stock or custom conversion handlers) + * 2 = implicitly applicable (i.e. using JAva implicit boxing/unboxing and primitive types widening) + * 3 = strictly applicable */ - private boolean isApplicable(Method method, Class[] classes) + private int getApplicability(Method method, Class[] classes) { Class[] methodArgs = method.getParameterTypes(); - + int ret = STRICTLY_CONVERTIBLE; if (methodArgs.length > classes.length) { // if there's just one more methodArg than class arg @@ -425,17 +487,27 @@ public class MethodMap // all the args preceding the vararg must match for (int i = 0; i < classes.length; i++) { - if (!isConvertible(methodArgs[i], classes[i], false) && - !isExplicitlyConvertible(methodArgs[i], classes[i], false)) + if (!isStrictConvertible(methodArgs[i], classes[i], false)) { - return false; + if (isConvertible(methodArgs[i], classes[i], false)) + { + ret = Math.min(ret, IMPLCITLY_CONVERTIBLE); + } + else if (isExplicitlyConvertible(methodArgs[i], classes[i], false)) + { + ret = Math.min(ret, EXPLICITLY_CONVERTIBLE); + } + else + { + return NOT_CONVERTIBLE; + } } } - return true; + return ret; } else { - return false; + return NOT_CONVERTIBLE; } } else if (methodArgs.length == classes.length) @@ -445,21 +517,23 @@ public class MethodMap // (e.g. String when the method is expecting String...) for(int i = 0; i < classes.length; ++i) { - if(!isConvertible(methodArgs[i], classes[i], false) && - !isExplicitlyConvertible(methodArgs[i], classes[i], false)) + if (!isStrictConvertible(methodArgs[i], classes[i], i == classes.length - 1 && methodArgs[i].isArray())) { - // if we're on the last arg and the method expects an array - if (i == classes.length - 1 && methodArgs[i].isArray()) + if (isConvertible(methodArgs[i], classes[i], i == classes.length - 1 && methodArgs[i].isArray())) + { + ret = Math.min(ret, IMPLCITLY_CONVERTIBLE); + } + else if (isExplicitlyConvertible(methodArgs[i], classes[i], i == classes.length - 1 && methodArgs[i].isArray())) + { + ret = Math.min(ret, EXPLICITLY_CONVERTIBLE); + } + else { - // check to see if the last arg is convertible - // to the array's component type - return isConvertible(methodArgs[i], classes[i], true) || - isExplicitlyConvertible(methodArgs[i], classes[i], true); + return NOT_CONVERTIBLE; } - return false; } } - return true; + return ret; } else if (methodArgs.length > 0) // more arguments given than the method accepts; check for varargs { @@ -467,15 +541,26 @@ public class MethodMap Class lastarg = methodArgs[methodArgs.length - 1]; if (!lastarg.isArray()) { - return false; + return NOT_CONVERTIBLE; } - // check that they all match up to the last method arg + // check that they all match up to the last method arg component type for (int i = 0; i < methodArgs.length - 1; ++i) { - if (!isConvertible(methodArgs[i], classes[i], false)) + if (!isStrictConvertible(methodArgs[i], classes[i], false)) { - return false; + if (isConvertible(methodArgs[i], classes[i], false)) + { + ret = Math.min(ret, IMPLCITLY_CONVERTIBLE); + } + else if (isExplicitlyConvertible(methodArgs[i], classes[i], false)) + { + ret = Math.min(ret, EXPLICITLY_CONVERTIBLE); + } + else + { + return NOT_CONVERTIBLE; + } } } @@ -483,14 +568,25 @@ public class MethodMap Class vararg = lastarg.getComponentType(); for (int i = methodArgs.length - 1; i < classes.length; ++i) { - if (!isConvertible(vararg, classes[i], false)) + if (!isStrictConvertible(vararg, classes[i], false)) { - return false; + if (isConvertible(vararg, classes[i], false)) + { + ret = Math.min(ret, IMPLCITLY_CONVERTIBLE); + } + else if (isExplicitlyConvertible(vararg, classes[i], false)) + { + ret = Math.min(ret, EXPLICITLY_CONVERTIBLE); + } + else + { + return NOT_CONVERTIBLE; + } } } - return true; + return ret; } - return false; + return NOT_CONVERTIBLE; } /** Modified: velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/UberspectImplTestCase.java URL: http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/UberspectImplTestCase.java?rev=1790680&r1=1790679&r2=1790680&view=diff ============================================================================== --- velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/UberspectImplTestCase.java (original) +++ velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/UberspectImplTestCase.java Sat Apr 8 13:20:55 2017 @@ -4,6 +4,7 @@ import junit.framework.Test; import junit.framework.TestSuite; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.Velocity; +import org.apache.velocity.app.VelocityEngine; import org.apache.velocity.runtime.RuntimeConstants; import org.apache.velocity.test.BaseTestCase; import org.apache.velocity.test.misc.TestLogger; @@ -49,50 +50,33 @@ public class UberspectImplTestCase exten } @Override - public void setUp() - throws Exception + protected void setUpEngine(VelocityEngine engine) { - Velocity.reset(); - Velocity.setProperty(RuntimeConstants.RUNTIME_LOG_INSTANCE, new TestLogger()); - Velocity.addProperty(RuntimeConstants.UBERSPECT_CLASSNAME, - "org.apache.velocity.util.introspection.UberspectImpl"); - Velocity.init(); + engine.setProperty(RuntimeConstants.RUNTIME_LOG_INSTANCE, new TestLogger()); + engine.addProperty(RuntimeConstants.UBERSPECT_CLASSNAME, "org.apache.velocity.util.introspection.UberspectImpl"); } @Override - public void tearDown() + protected void setUpContext(VelocityContext context) { + context.put("privateClass", new PrivateClass()); + context.put("privateMethod", new PrivateMethod()); + context.put("publicMethod", new PublicMethod()); + context.put("iterable", new SomeIterable()); + context.put("over", new OverloadedMethods()); } public void testPrivateIterator() throws Exception { - VelocityContext context = new VelocityContext(); - context.put("privateClass", new PrivateClass()); - context.put("privateMethod", new PrivateMethod()); - context.put("publicMethod", new PublicMethod()); - StringWriter writer = new StringWriter(); - - Velocity.evaluate(context, writer, "test", "#foreach($i in $privateClass)$i#end"); - assertEquals(writer.toString(), ""); - - writer = new StringWriter(); - Velocity.evaluate(context, writer, "test", "#foreach($i in $privateMethod)$i#end"); - assertEquals(writer.toString(), ""); - - writer = new StringWriter(); - Velocity.evaluate(context, writer, "test", "#foreach($i in $publicMethod)$i#end"); - assertEquals(writer.toString(), "123"); + assertEvalEquals("", "#foreach($i in $privateClass)$i#end"); + assertEvalEquals("", "#foreach($i in $privateMethod)$i#end"); + assertEvalEquals("123", "#foreach($i in $publicMethod)$i#end"); } public void testIterableForeach() { - VelocityContext context = new VelocityContext(); - context.put("iterable", new SomeIterable()); - StringWriter writer = new StringWriter(); - - Velocity.evaluate(context, writer, "test", "#foreach($i in $iterable)$i#end"); - assertEquals(writer.toString(), "123"); + assertEvalEquals("123", "#foreach($i in $iterable)$i#end"); } private class PrivateClass @@ -132,26 +116,20 @@ public class UberspectImplTestCase exten public String foo() { return "foo0"; } public String foo(String arg1) { return "foo1"; } public String foo(String arg1, String arg2) { return "foo2"; } + + public String bar(Number n, int i) { return "bar1"; } + public String bar(Number n, String s) { return "bar2"; } } public void testOverloadedMethods() { - VelocityContext context = new VelocityContext(); - context.put("over", new OverloadedMethods()); - StringWriter writer = new StringWriter(); - Velocity.evaluate(context, writer, "test", "$over.foo()"); - assertEquals(writer.toString(), "foo0"); - writer = new StringWriter(); - Velocity.evaluate(context, writer, "test", "$over.foo('a')"); - assertEquals(writer.toString(), "foo1"); - writer = new StringWriter(); - Velocity.evaluate(context, writer, "test", "$over.foo($null)"); - assertEquals(writer.toString(), "foo1"); - writer = new StringWriter(); - Velocity.evaluate(context, writer, "test", "$over.foo('a', 'b')"); - assertEquals(writer.toString(), "foo2"); - writer = new StringWriter(); - Velocity.evaluate(context, writer, "test", "$over.foo('a', $null)"); - assertEquals(writer.toString(), "foo2"); + assertEvalEquals("foo0", "$over.foo()"); + assertEvalEquals("foo1", "$over.foo('a')"); + assertEvalEquals("foo1", "$over.foo($null)"); + assertEvalEquals("foo2", "$over.foo('a', 'b')"); + assertEvalEquals("foo2", "$over.foo('a', $null)"); + assertEvalEquals("bar1", "$over.bar(1,1)"); + assertEvalEquals("$over.bar(1,1.1)", "$over.bar(1,1.1)"); // this one is definitely ambiguous + assertEvalEquals("bar2", "$over.bar(1,'1.1')"); } }