Author: cbrisson
Date: Tue Feb 26 14:30:46 2019
New Revision: 1854386

URL: http://svn.apache.org/viewvc?rev=1854386&view=rev
Log:
[engine] Merge branch VELOCITY-892

Added:
    
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/TypeConversionHandler.java
      - copied unchanged from r1854372, 
velocity/engine/branches/VELOCITY-892/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/TypeConversionHandler.java
    
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/TypeConversionHandlerImpl.java
      - copied, changed from r1854372, 
velocity/engine/branches/VELOCITY-892/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/TypeConversionHandlerImpl.java
Removed:
    
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/Pair.java
    
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ConversionHandlerImpl.java
Modified:
    velocity/engine/trunk/   (props changed)
    velocity/engine/trunk/pom.xml
    velocity/engine/trunk/velocity-engine-core/pom.xml
    velocity/engine/trunk/velocity-engine-core/src/main/java/   (props changed)
    
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeConstants.java
    
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeServices.java
    
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ClassMap.java
    
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ConversionHandler.java
    
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/Introspector.java
    
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectorBase.java
    
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectorCache.java
    
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/MethodMap.java
    
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/UberspectImpl.java
    
velocity/engine/trunk/velocity-engine-core/src/main/resources/org/apache/velocity/runtime/defaults/velocity.properties
    velocity/engine/trunk/velocity-engine-core/src/test/java/   (props changed)
    
velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/ConversionHandlerTestCase.java

Propchange: velocity/engine/trunk/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Tue Feb 26 14:30:46 2019
@@ -1,3 +1,4 @@
 /velocity/engine/branches/2.0_Exp:958513,991637-995742
+/velocity/engine/branches/VELOCITY-892:1844076-1854372
 /velocity/engine/branches/VELOCITY-898:1843220-1843786
 /velocity/engine/trunk:992133,1032159

Modified: velocity/engine/trunk/pom.xml
URL: 
http://svn.apache.org/viewvc/velocity/engine/trunk/pom.xml?rev=1854386&r1=1854385&r2=1854386&view=diff
==============================================================================
--- velocity/engine/trunk/pom.xml (original)
+++ velocity/engine/trunk/pom.xml Tue Feb 26 14:30:46 2019
@@ -45,8 +45,8 @@
     <slf4j.version>1.7.26</slf4j.version>
     <surefire.plugin.version>2.19.1</surefire.plugin.version>
     <jira.browse.url>https://issues.apache.org/jira/browse</jira.browse.url>
-    <maven.compiler.source>1.7</maven.compiler.source>
-    <maven.compiler.target>1.7</maven.compiler.target>
+    <maven.compiler.source>1.8</maven.compiler.source>
+    <maven.compiler.target>1.8</maven.compiler.target>
   </properties>
 
   <!-- java 8 doclint checks are too strict for our current javadoc state... 
-->
@@ -144,6 +144,9 @@
                 <enforceBytecodeVersion>
                   <maxJdkVersion>${maven.compiler.target}</maxJdkVersion>
                 </enforceBytecodeVersion>
+                <requireJavaVersion>
+                  <version>[1.8,)</version>
+                </requireJavaVersion>
               </rules>
               <fail>true</fail>
             </configuration>

Modified: velocity/engine/trunk/velocity-engine-core/pom.xml
URL: 
http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/pom.xml?rev=1854386&r1=1854385&r2=1854386&view=diff
==============================================================================
--- velocity/engine/trunk/velocity-engine-core/pom.xml (original)
+++ velocity/engine/trunk/velocity-engine-core/pom.xml Tue Feb 26 14:30:46 2019
@@ -35,6 +35,8 @@
 
     <!-- command line switch -Dparser.nodefiles=true generates AST Node 
classes (new structures added to parser) -->
     <parser.nodefiles>false</parser.nodefiles>
+
+    <parser.debug>false</parser.debug>
     
     <!-- You can modify those properties locally to test
          the DataSourceResourceLoader against other engines.
@@ -169,9 +171,9 @@
           <buildParser>true</buildParser>
           <buildNodeFiles>${parser.nodefiles}</buildNodeFiles>
           <multi>true</multi>
-          <debugParser>false</debugParser>
-          <debugLookAhead>false</debugLookAhead>
-          <debugTokenManager>false</debugTokenManager>
+          <debugParser>${parser.debug}</debugParser>
+          <debugLookAhead>${parser.debug}</debugLookAhead>
+          <debugTokenManager>${parser.debug}</debugTokenManager>
           <jdkVersion>${maven.compiler.target}</jdkVersion>
           <nodeUsesParser>true</nodeUsesParser>
           <nodePackage>org.apache.velocity.runtime.parser.node</nodePackage>

Propchange: velocity/engine/trunk/velocity-engine-core/src/main/java/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Tue Feb 26 14:30:46 2019
@@ -1,4 +1,5 @@
 /velocity/engine/branches/2.0_Exp/src/java:958513,991637-995742
 /velocity/engine/branches/2.0_Exp/velocity-engine-core/src/main/java:958513
+/velocity/engine/branches/VELOCITY-892/velocity-engine-core/src/main/java:1844076-1854372
 
/velocity/engine/branches/VELOCITY-898/velocity-engine-core/src/main/java:1843220-1843786
 /velocity/engine/trunk/src/java:1032134

Modified: 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeConstants.java
URL: 
http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeConstants.java?rev=1854386&r1=1854385&r2=1854386&view=diff
==============================================================================
--- 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeConstants.java
 (original)
+++ 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeConstants.java
 Tue Feb 26 14:30:46 2019
@@ -252,22 +252,16 @@ public interface RuntimeConstants
 
     /*
      * ----------------------------------------------------------------------
-     * G E N E R A L  R U N T I M E  C O N F I G U R A T I O N
+     * I N T R O S P E C T I O N  C O N F I G U R A T I O N
      * ----------------------------------------------------------------------
      */
 
-    /** Switch for the interpolation facility for string literals. */
-    String INTERPOLATE_STRINGLITERALS = "runtime.interpolate.string.literals";
-
-    /** The character encoding for the templates. Used by the parser in 
processing the input streams. */
-    String INPUT_ENCODING = "input.encoding";
-
-    /** Default Encoding is UTF-8. */
-    String ENCODING_DEFAULT = "UTF-8";
-
     /** key name for uberspector. Multiple classnames can be specified,in 
which case uberspectors will be chained. */
     String UBERSPECT_CLASSNAME = "runtime.introspector.uberspect";
 
+    /** key for Conversion Manager instance */
+    String CONVERSION_HANDLER_INSTANCE = "runtime.conversion.handler.instance";
+
     /** key for Conversion Manager class */
     String CONVERSION_HANDLER_CLASS = "runtime.conversion.handler.class";
 
@@ -277,6 +271,22 @@ public interface RuntimeConstants
     /** A comma separated list of classes to restrict access to in the 
SecureIntrospector. */
     String INTROSPECTOR_RESTRICT_CLASSES = "introspector.restrict.classes";
 
+
+    /*
+     * ----------------------------------------------------------------------
+     * G E N E R A L  R U N T I M E  C O N F I G U R A T I O N
+     * ----------------------------------------------------------------------
+     */
+
+    /** Switch for the interpolation facility for string literals. */
+    String INTERPOLATE_STRINGLITERALS = "runtime.interpolate.string.literals";
+
+    /** The character encoding for the templates. Used by the parser in 
processing the input streams. */
+    String INPUT_ENCODING = "input.encoding";
+
+    /** Default Encoding is UTF-8. */
+    String ENCODING_DEFAULT = "UTF-8";
+
     /** Switch for ignoring nulls in math equations vs throwing exceptions. */
     String STRICT_MATH = "runtime.strict.math";
 

Modified: 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeServices.java
URL: 
http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeServices.java?rev=1854386&r1=1854385&r2=1854386&view=diff
==============================================================================
--- 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeServices.java
 (original)
+++ 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeServices.java
 Tue Feb 26 14:30:46 2019
@@ -110,7 +110,7 @@ public interface RuntimeServices
      * @param key
      * @param value
      */
- void addProperty(String key, Object value);
+    void addProperty(String key, Object value);
 
     /**
      * Clear the values pertaining to a particular

Modified: 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ClassMap.java
URL: 
http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ClassMap.java?rev=1854386&r1=1854385&r2=1854386&view=diff
==============================================================================
--- 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ClassMap.java
 (original)
+++ 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ClassMap.java
 Tue Feb 26 14:30:46 2019
@@ -73,7 +73,7 @@ public class ClassMap
      * @param conversionHandler conversion handler
      * @since 2.0
      */
-    public ClassMap(final Class clazz, final Logger log, final 
ConversionHandler conversionHandler)
+    public ClassMap(final Class clazz, final Logger log, final 
TypeConversionHandler conversionHandler)
     {
         this.clazz = clazz;
         this.log = log;
@@ -121,7 +121,7 @@ public class ClassMap
      * are taken from all the public methods
      * that our class, its parents and their implemented interfaces provide.
      */
-    private MethodCache createMethodCache(ConversionHandler conversionHandler)
+    private MethodCache createMethodCache(TypeConversionHandler 
conversionHandler)
     {
         MethodCache methodCache = new MethodCache(log, conversionHandler);
        //
@@ -231,7 +231,7 @@ public class ClassMap
         /** Map of methods that are searchable according to method parameters 
to find a match */
         private final MethodMap methodMap;
 
-        private MethodCache(Logger log, ConversionHandler conversionHandler)
+        private MethodCache(Logger log, TypeConversionHandler 
conversionHandler)
         {
             this.log = log;
             methodMap = new MethodMap(conversionHandler);

Modified: 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ConversionHandler.java
URL: 
http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ConversionHandler.java?rev=1854386&r1=1854385&r2=1854386&view=diff
==============================================================================
--- 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ConversionHandler.java
 (original)
+++ 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ConversionHandler.java
 Tue Feb 26 14:30:46 2019
@@ -27,13 +27,17 @@ package org.apache.velocity.util.introsp
  *
  * @author <a href="mailto:[email protected]";>Claude Brisson</a>
  * @version $Id: ConversionHandler.java $
+ * @deprecated use {@link TypeConversionHandler}
+ * @see TypeConversionHandler
  * @since 2.0
  */
 
+@Deprecated
 public interface ConversionHandler
 {
     /**
      * Check to see if the conversion can be done using an explicit conversion
+     *
      * @param formal expected formal type
      * @param actual provided argument type
      * @return null if no conversion is needed, or the appropriate Converter 
object
@@ -55,10 +59,11 @@ public interface ConversionHandler
     /**
      * Add the given converter to the handler. Implementation should be 
thread-safe.
      *
-     * @param formal expected formal type
-     * @param actual provided argument type
+     * @param formal    expected formal type
+     * @param actual    provided argument type
      * @param converter converter
      * @since 2.0
      */
     void addConverter(Class formal, Class actual, Converter converter);
 }
+

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=1854386&r1=1854385&r2=1854386&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
 Tue Feb 26 14:30:46 2019
@@ -19,6 +19,14 @@ package org.apache.velocity.util.introsp
  * under the License.
  */
 
+import org.apache.commons.lang3.reflect.TypeUtils;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
@@ -66,7 +74,7 @@ public class IntrospectionUtils
      * @param clazz input class
      * @return boxed class
      */
-    static Class getBoxedClass(Class clazz)
+    public static Class getBoxedClass(Class clazz)
     {
         Class boxed = boxingMap.get(clazz);
         return boxed == null ? clazz : boxed;
@@ -77,16 +85,52 @@ public class IntrospectionUtils
      * @param clazz input class
      * @return unboxed class
      */
-    static Class getUnboxedClass(Class clazz)
+    public static Class getUnboxedClass(Class clazz)
     {
         Class unboxed = unboxingMap.get(clazz);
         return unboxed == null ? clazz : unboxed;
     }
 
     /**
-     *
+     * returns the Class corresponding to a Type, if possible
+     * @param type the input Type
+     * @return found Class, if any
      */
-
+    public static Class getTypeClass(Type type)
+    {
+        if (type == null)
+        {
+            return null;
+        }
+        if (type instanceof Class)
+        {
+            return (Class)type;
+        }
+        else if (type instanceof ParameterizedType)
+        {
+            return (Class)((ParameterizedType)type).getRawType();
+        }
+        else if (type instanceof GenericArrayType)
+        {
+            Type componentType = 
((GenericArrayType)type).getGenericComponentType();
+            Class componentClass = getTypeClass(componentType);
+            if (componentClass != null)
+            {
+                return Array.newInstance(componentClass, 0).getClass();
+            }
+        }
+        else if (type instanceof TypeVariable)
+        {
+            Type[] bounds = TypeUtils.getImplicitBounds((TypeVariable)type);
+            if (bounds.length == 1) return getTypeClass(bounds[0]);
+        }
+        else if (type instanceof WildcardType)
+        {
+            Type[] bounds = 
TypeUtils.getImplicitUpperBounds((WildcardType)type);
+            if (bounds.length == 1) return getTypeClass(bounds[0]);
+        }
+        return null;
+    }
 
     /**
      * Determines whether a type represented by a class object is
@@ -108,92 +152,105 @@ public class IntrospectionUtils
      * type or an object type of a primitive type that can be converted to
      * the formal type.
      */
-    public static boolean isMethodInvocationConvertible(Class formal,
+    public static boolean isMethodInvocationConvertible(Type formal,
                                                         Class actual,
                                                         boolean possibleVarArg)
     {
-        /* if it's a null, it means the arg was null */
-        if (actual == null)
+        Class formalClass = getTypeClass(formal);
+        if (formalClass != null)
         {
-            return !formal.isPrimitive();
-        }
+            /* if it's a null, it means the arg was null */
+            if (actual == null)
+            {
+                return !formalClass.isPrimitive();
+            }
 
-        /* Check for identity or widening reference conversion */
-        if (formal.isAssignableFrom(actual))
-        {
-            return true;
-        }
+            /* Check for identity or widening reference conversion */
+            if (formalClass.isAssignableFrom(actual))
+            {
+                return true;
+            }
 
-        /* 2.0: Since MethodMap's comparison functions now use this method 
with potentially reversed arguments order,
-         * actual can be a primitive type. */
+            /* 2.0: Since MethodMap's comparison functions now use this method 
with potentially reversed arguments order,
+             * actual can be a primitive type. */
 
-        /* Check for boxing */
-        if (!formal.isPrimitive() && actual.isPrimitive())
-        {
-            Class boxed = boxingMap.get(actual);
-            if (boxed != null && boxed == formal || 
formal.isAssignableFrom(boxed)) return true;
-        }
+            /* Check for boxing */
+            if (!formalClass.isPrimitive() && actual.isPrimitive())
+            {
+                Class boxed = boxingMap.get(actual);
+                if (boxed != null && boxed == formalClass || 
formalClass.isAssignableFrom(boxed)) return true;
+            }
 
-        if (formal.isPrimitive())
-        {
-            if (actual.isPrimitive())
+            if (formalClass.isPrimitive())
             {
-                /* check for widening primitive conversion */
-                if (formal == Short.TYPE && actual == Byte.TYPE)
-                    return true;
-                if (formal == Integer.TYPE && (
+                if (actual.isPrimitive())
+                {
+                    /* check for widening primitive conversion */
+                    if (formalClass == Short.TYPE && actual == Byte.TYPE)
+                        return true;
+                    if (formalClass == Integer.TYPE && (
                         actual == Byte.TYPE || actual == Short.TYPE))
-                    return true;
-                if (formal == Long.TYPE && (
+                        return true;
+                    if (formalClass == Long.TYPE && (
                         actual == Byte.TYPE || actual == Short.TYPE || actual 
== Integer.TYPE))
-                    return true;
-                if (formal == Float.TYPE && (
+                        return true;
+                    if (formalClass == Float.TYPE && (
                         actual == Byte.TYPE || actual == Short.TYPE || actual 
== Integer.TYPE ||
-                                actual == Long.TYPE))
-                    return true;
-                if (formal == Double.TYPE && (
+                            actual == Long.TYPE))
+                        return true;
+                    if (formalClass == Double.TYPE && (
                         actual == Byte.TYPE || actual == Short.TYPE || actual 
== Integer.TYPE ||
-                                actual == Long.TYPE || actual == Float.TYPE))
-                    return true;
-            }
-            else
-            {
-                /* Check for unboxing with widening primitive conversion. */
-                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 == Long.TYPE || actual == Float.TYPE))
+                        return true;
+                } else
+                {
+                    /* Check for unboxing with widening primitive conversion. 
*/
+                    if (formalClass == Boolean.TYPE && actual == Boolean.class)
+                        return true;
+                    if (formalClass == Character.TYPE && actual == 
Character.class)
+                        return true;
+                    if (formalClass == Byte.TYPE && actual == Byte.class)
+                        return true;
+                    if (formalClass == Short.TYPE && (actual == Short.class || 
actual == Byte.class))
+                        return true;
+                    if (formalClass == Integer.TYPE && (actual == 
Integer.class || actual == Short.class ||
                         actual == Byte.class))
-                    return true;
-                if (formal == Long.TYPE && (actual == Long.class || actual == 
Integer.class ||
+                        return true;
+                    if (formalClass == 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 ||
+                        return true;
+                    if (formalClass == 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 ||
+                        return true;
+                    if (formalClass == Double.TYPE && (actual == Double.class 
|| actual == Float.class ||
                         actual == Long.class || actual == Integer.class || 
actual == Short.class ||
                         actual == Byte.class))
-                    return true;
+                        return true;
+                }
             }
-        }
 
-        /* Check for vararg conversion. */
-        if (possibleVarArg && formal.isArray())
+            /* Check for vararg conversion. */
+            if (possibleVarArg && formalClass.isArray())
+            {
+                if (actual.isArray())
+                {
+                    actual = actual.getComponentType();
+                }
+                return 
isMethodInvocationConvertible(formalClass.getComponentType(),
+                    actual, false);
+            }
+            return false;
+        }
+        else
         {
-            if (actual.isArray())
+            // no distinction between strict and implicit, not a big deal in 
this case
+            if (TypeUtils.isAssignable(actual, formal))
             {
-                actual = actual.getComponentType();
+                return true;
             }
-            return isMethodInvocationConvertible(formal.getComponentType(),
-                                                 actual, false);
+            return possibleVarArg && TypeUtils.isArrayType(formal) &&
+                TypeUtils.isAssignable(actual, 
TypeUtils.getArrayComponentType(formal));
         }
-        return false;
     }
 
     /**
@@ -212,55 +269,69 @@ public class IntrospectionUtils
      * or formal and actual are both primitive types and actual can be
      * subject to widening conversion to formal.
      */
-    public static boolean isStrictMethodInvocationConvertible(Class formal,
+    public static boolean isStrictMethodInvocationConvertible(Type formal,
                                                               Class actual,
                                                               boolean 
possibleVarArg)
     {
-        /* Check for nullity */
-        if (actual == null)
+        Class formalClass = getTypeClass(formal);
+        if (formalClass != null)
         {
-            return !formal.isPrimitive();
-        }
-
-        /* Check for identity or widening reference conversion */
-        if(formal.isAssignableFrom(actual))
-        {
-            return true;
-        }
+            /* Check for nullity */
+            if (actual == null)
+            {
+                return !formalClass.isPrimitive();
+            }
 
-        /* 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))
+            /* Check for identity or widening reference conversion */
+            if (formalClass.isAssignableFrom(actual))
+            {
                 return true;
-        }
+            }
 
-        /* Check for vararg conversion. */
-        if (possibleVarArg && formal.isArray())
+            /* Check for widening primitive conversion. */
+            if (formalClass.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 && formalClass.isArray())
+            {
+                if (actual.isArray())
+                {
+                    actual = actual.getComponentType();
+                }
+                return 
isStrictMethodInvocationConvertible(formalClass.getComponentType(),
+                    actual, false);
+            }
+            return false;
+        }
+        else
         {
-            if (actual.isArray())
+            // no distinction between strict and implicit, not a big deal in 
this case
+            if (TypeUtils.isAssignable(actual, formal))
             {
-                actual = actual.getComponentType();
+                return true;
             }
-            return 
isStrictMethodInvocationConvertible(formal.getComponentType(),
-                                                       actual, false);
+            return possibleVarArg && TypeUtils.isArrayType(formal) &&
+                TypeUtils.isAssignable(actual, 
TypeUtils.getArrayComponentType(formal));
         }
-        return false;
     }
 }

Modified: 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/Introspector.java
URL: 
http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/Introspector.java?rev=1854386&r1=1854385&r2=1854386&view=diff
==============================================================================
--- 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/Introspector.java
 (original)
+++ 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/Introspector.java
 Tue Feb 26 14:30:46 2019
@@ -68,7 +68,7 @@ public class Introspector extends Intros
      * @param conversionHandler conversion handler
      * @since 2.0
      */
-    public Introspector(final Logger log, ConversionHandler conversionHandler)
+    public Introspector(final Logger log, TypeConversionHandler 
conversionHandler)
     {
         super(log, conversionHandler);
     }

Modified: 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectorBase.java
URL: 
http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectorBase.java?rev=1854386&r1=1854385&r2=1854386&view=diff
==============================================================================
--- 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectorBase.java
 (original)
+++ 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectorBase.java
 Tue Feb 26 14:30:46 2019
@@ -62,17 +62,13 @@ public abstract class IntrospectorBase
     /** The Introspector Cache */
     private final IntrospectorCache introspectorCache;
 
-    /** The Conversion handler */
-    private final ConversionHandler conversionHandler;
-
     /**
      * C'tor.
      */
-    protected IntrospectorBase(final Logger log, final ConversionHandler 
conversionHandler)
+    protected IntrospectorBase(final Logger log, final TypeConversionHandler 
conversionHandler)
     {
         this.log = log;
         introspectorCache = new IntrospectorCache(log, conversionHandler);
-        this.conversionHandler = conversionHandler;
     }
 
     /**

Modified: 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectorCache.java
URL: 
http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectorCache.java?rev=1854386&r1=1854385&r2=1854386&view=diff
==============================================================================
--- 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectorCache.java
 (original)
+++ 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectorCache.java
 Tue Feb 26 14:30:46 2019
@@ -68,12 +68,12 @@ public final class IntrospectorCache
     /**
      * Conversion handler
      */
-    private final ConversionHandler conversionHandler;
+    private final TypeConversionHandler conversionHandler;
 
     /**
      * C'tor
      */
-    public IntrospectorCache(final Logger log, final ConversionHandler 
conversionHandler)
+    public IntrospectorCache(final Logger log, final TypeConversionHandler 
conversionHandler)
     {
         this.log = log;
         this.conversionHandler = conversionHandler;

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=1854386&r1=1854385&r2=1854386&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
 Tue Feb 26 14:30:46 2019
@@ -19,9 +19,11 @@ package org.apache.velocity.util.introsp
  * under the License.
  */
 
+import org.apache.commons.lang3.reflect.TypeUtils;
 import org.apache.velocity.exception.VelocityException;
 
 import java.lang.reflect.Method;
+import java.lang.reflect.Type;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.LinkedList;
@@ -54,7 +56,7 @@ public class MethodMap
     private static final int IMPLCITLY_CONVERTIBLE = 2;
     private static final int STRICTLY_CONVERTIBLE = 3;
 
-    ConversionHandler conversionHandler;
+    TypeConversionHandler conversionHandler;
 
     /**
      * Default constructor
@@ -69,7 +71,7 @@ public class MethodMap
      * @param conversionHandler conversion handler
      * @since 2.0
      */
-    public MethodMap(ConversionHandler conversionHandler)
+    public MethodMap(TypeConversionHandler conversionHandler)
     {
         this.conversionHandler = conversionHandler;
     }
@@ -173,7 +175,7 @@ public class MethodMap
         Method method;
 
         /* cache arguments classes array */
-        Class[] methodTypes;
+        Type[] methodTypes;
 
         /* specificity: how does the best match compare to provided arguments
          * one one LESS_SPECIFIC, MORE_SPECIFIC or INCOMPARABLE */
@@ -190,9 +192,9 @@ public class MethodMap
         {
             this.method = method;
             this.applicability = applicability;
-            this.methodTypes = method.getParameterTypes();
+            this.methodTypes = method.getGenericParameterTypes();
             this.specificity = compare(methodTypes, unboxedArgs);
-            this.varargs = methodTypes.length > 0 && 
methodTypes[methodTypes.length - 1].isArray();
+            this.varargs = methodTypes.length > 0 && 
TypeUtils.isArrayType(methodTypes[methodTypes.length - 1]);
         }
     }
 
@@ -322,74 +324,74 @@ public class MethodMap
     /**
      * Determines which method signature (represented by a class array) is more
      * specific. This defines a partial ordering on the method signatures.
-     * @param c1 first signature to compare
-     * @param c2 second signature to compare
+     * @param t1 first signature to compare
+     * @param t2 second signature to compare
      * @return MORE_SPECIFIC if c1 is more specific than c2, LESS_SPECIFIC if
      * c1 is less specific than c2, INCOMPARABLE if they are incomparable.
      */
-    private int compare(Class[] c1, Class[] c2)
+    private int compare(Type[] t1, Type[] t2)
     {
-        boolean c1IsVararag = false;
-        boolean c2IsVararag = false;
+        boolean t1IsVararag = false;
+        boolean t2IsVararag = 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)
+        if (t1.length > t2.length)
         {
-            int l2 = c2.length;
+            int l2 = t2.length;
             if (l2 == 0)
             {
                 return MORE_SPECIFIC;
             }
-            c2 = Arrays.copyOf(c2, c1.length);
-            Class itemClass = c2[l2 - 1].getComponentType();
+            t2 = Arrays.copyOf(t2, t1.length);
+            Type itemType = TypeUtils.getArrayComponentType(t2[l2 - 1]);
             /* if item class is null, then it implies the vaarg is #1
              * (and receives an empty array)
              */
-            if (itemClass == null)
+            if (itemType == null)
             {
                 /* by construct, we have c1.length = l2 + 1 */
-                c1IsVararag = true;
-                c2[c1.length - 1] = null;
+                t1IsVararag = true;
+                t2[t1.length - 1] = null;
             }
             else
             {
-                c2IsVararag = true;
-                for (int i = l2 - 1; i < c1.length; ++i)
+                t2IsVararag = true;
+                for (int i = l2 - 1; i < t1.length; ++i)
                 {
                 /* also overwrite the vaargs itself */
-                    c2[i] = itemClass;
+                    t2[i] = itemType;
                 }
             }
             fixedLengths = true;
         }
-        else if (c2.length > c1.length)
+        else if (t2.length > t1.length)
         {
-            int l1 = c1.length;
+            int l1 = t1.length;
             if (l1 == 0)
             {
                 return LESS_SPECIFIC;
             }
-            c1 = Arrays.copyOf(c1, c2.length);
-            Class itemClass = c1[l1 - 1].getComponentType();
+            t1 = Arrays.copyOf(t1, t2.length);
+            Type itemType = TypeUtils.getArrayComponentType(t1[l1 - 1]);
             /* if item class is null, then it implies the vaarg is #2
              * (and receives an empty array)
              */
-            if (itemClass == null)
+            if (itemType == null)
             {
                 /* by construct, we have c2.length = l1 + 1 */
-                c2IsVararag = true;
-                c1[c2.length - 1] = null;
+                t2IsVararag = true;
+                t1[t2.length - 1] = null;
             }
             else
             {
-                c1IsVararag = true;
-                for (int i = l1 - 1; i < c2.length; ++i)
+                t1IsVararag = true;
+                for (int i = l1 - 1; i < t2.length; ++i)
                 {
                 /* also overwrite the vaargs itself */
-                    c1[i] = itemClass;
+                    t1[i] = itemType;
                 }
             }
             fixedLengths = true;
@@ -398,52 +400,72 @@ public class MethodMap
         /* 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)
+        for(int i = 0; i < t1.length; ++i)
         {
-            boolean last = !fixedLengths && (i == c1.length - 1);
-            if (c1[i] != c2[i])
+            Class c1 = t1[i] == null ? null : 
IntrospectionUtils.getTypeClass(t1[i]);
+            Class c2 = t2[i] == null ? null : 
IntrospectionUtils.getTypeClass(t2[i]);
+            boolean last = !fixedLengths && (i == t1.length - 1);
+            if (t1[i] == null && t2[i] != null || t1[i] != null && t2[i] == 
null || !t1[i].equals(t2[i]))
             {
-                if (c1[i] == null)
+                if (t1[i] == null)
                 {
                     fromC2toC1 = NOT_CONVERTIBLE;
-                    if (c2[i].isPrimitive())
+                    if (c2 != null && c2.isPrimitive())
                     {
                         fromC1toC2 = NOT_CONVERTIBLE;
                     }
                 }
-                else if (c2[i] == null)
+                else if (t2[i] == null)
                 {
                     fromC1toC2 = NOT_CONVERTIBLE;
-                    if (c1[i].isPrimitive())
+                    if (c1 != null && c1.isPrimitive())
                     {
                         fromC2toC1 = NOT_CONVERTIBLE;
                     }
                 }
                 else
                 {
-                    switch (fromC1toC2)
+                    if (c1 != null)
                     {
-                        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;
+                        switch (fromC1toC2)
+                        {
+                            case STRICTLY_CONVERTIBLE:
+                                if (isStrictConvertible(t2[i], c1, last)) 
break;
+                                fromC1toC2 = IMPLCITLY_CONVERTIBLE;
+                            case IMPLCITLY_CONVERTIBLE:
+                                if (isConvertible(t2[i], c1, last)) break;
+                                fromC1toC2 = EXPLICITLY_CONVERTIBLE;
+                            case EXPLICITLY_CONVERTIBLE:
+                                if (isExplicitlyConvertible(t2[i], c1, last)) 
break;
+                                fromC1toC2 = NOT_CONVERTIBLE;
+                        }
+                    }
+                    else if (fromC1toC2 > NOT_CONVERTIBLE)
+                    {
+                        fromC1toC2 = TypeUtils.isAssignable(t1[i], t2[i]) ?
+                            Math.min(fromC1toC2, IMPLCITLY_CONVERTIBLE) :
+                            NOT_CONVERTIBLE;
+                    }
+                    if (c2 != null)
+                    {
+                        switch (fromC2toC1)
+                        {
+                            case STRICTLY_CONVERTIBLE:
+                                if (isStrictConvertible(t1[i], c2, last)) 
break;
+                                fromC2toC1 = IMPLCITLY_CONVERTIBLE;
+                            case IMPLCITLY_CONVERTIBLE:
+                                if (isConvertible(t1[i], c2, last)) break;
+                                fromC2toC1 = EXPLICITLY_CONVERTIBLE;
+                            case EXPLICITLY_CONVERTIBLE:
+                                if (isExplicitlyConvertible(t1[i], c2, last)) 
break;
+                                fromC2toC1 = NOT_CONVERTIBLE;
+                        }
+                    }
+                    else if (fromC2toC1 > NOT_CONVERTIBLE)
+                    {
+                        fromC2toC1 = TypeUtils.isAssignable(t2[i], t1[i]) ?
+                            Math.min(fromC2toC1, IMPLCITLY_CONVERTIBLE) :
+                            NOT_CONVERTIBLE;
                     }
                 }
             }
@@ -472,8 +494,8 @@ public class MethodMap
              * If one method accepts varargs and the other does not,
              * call the non-vararg one more specific.
              */
-            boolean last1Array = c1IsVararag || !fixedLengths && c1[c1.length 
- 1].isArray();
-            boolean last2Array = c2IsVararag || !fixedLengths && c2[c2.length 
- 1].isArray();
+            boolean last1Array = t1IsVararag || !fixedLengths && 
TypeUtils.isArrayType (t1[t1.length - 1]);
+            boolean last2Array = t2IsVararag || !fixedLengths && 
TypeUtils.isArrayType(t2[t2.length - 1]);
             if (last1Array && !last2Array)
             {
                 return LESS_SPECIFIC;
@@ -499,14 +521,13 @@ public class MethodMap
      */
     private int getApplicability(Method method, Class[] classes)
     {
-        Class[] methodArgs = method.getParameterTypes();
+        Type[] methodArgs = method.getGenericParameterTypes();
         int ret = STRICTLY_CONVERTIBLE;
         if (methodArgs.length > classes.length)
         {
             // 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())
+            if (methodArgs.length == classes.length + 1 && 
TypeUtils.isArrayType(methodArgs[methodArgs.length - 1]))
             {
                 // all the args preceding the vararg must match
                 for (int i = 0; i < classes.length; i++)
@@ -541,13 +562,14 @@ public class MethodMap
             // (e.g. String when the method is expecting String...)
             for(int i = 0; i < classes.length; ++i)
             {
-                if (!isStrictConvertible(methodArgs[i], classes[i], i == 
classes.length - 1 && methodArgs[i].isArray()))
+                boolean possibleVararg = i == classes.length - 1 && 
TypeUtils.isArrayType(methodArgs[i]);
+                if (!isStrictConvertible(methodArgs[i], classes[i], 
possibleVararg))
                 {
-                    if (isConvertible(methodArgs[i], classes[i], i == 
classes.length - 1 && methodArgs[i].isArray()))
+                    if (isConvertible(methodArgs[i], classes[i], 
possibleVararg))
                     {
                         ret = Math.min(ret, IMPLCITLY_CONVERTIBLE);
                     }
-                    else if (isExplicitlyConvertible(methodArgs[i], 
classes[i], i == classes.length - 1 && methodArgs[i].isArray()))
+                    else if (isExplicitlyConvertible(methodArgs[i], 
classes[i], possibleVararg))
                     {
                         ret = Math.min(ret, EXPLICITLY_CONVERTIBLE);
                     }
@@ -562,8 +584,8 @@ public class MethodMap
         else if (methodArgs.length > 0) // more arguments given than the 
method accepts; check for varargs
         {
             // check that the last methodArg is an array
-            Class lastarg = methodArgs[methodArgs.length - 1];
-            if (!lastarg.isArray())
+            Type lastarg = methodArgs[methodArgs.length - 1];
+            if (!TypeUtils.isArrayType(lastarg))
             {
                 return NOT_CONVERTIBLE;
             }
@@ -589,7 +611,7 @@ public class MethodMap
             }
 
             // check that all remaining arguments are convertible to the 
vararg type
-            Class vararg = lastarg.getComponentType();
+            Type vararg = TypeUtils.getArrayComponentType(lastarg);
             for (int i = methodArgs.length - 1; i < classes.length; ++i)
             {
                 if (!isStrictConvertible(vararg, classes[i], false))
@@ -621,7 +643,7 @@ public class MethodMap
      * @param possibleVarArg
      * @return convertible
      */
-    private boolean isConvertible(Class formal, Class actual, boolean 
possibleVarArg)
+    private boolean isConvertible(Type formal, Class actual, boolean 
possibleVarArg)
     {
         return IntrospectionUtils.
             isMethodInvocationConvertible(formal, actual, possibleVarArg);
@@ -636,7 +658,7 @@ public class MethodMap
      * @param possibleVarArg
      * @return convertible
      */
-    private static boolean isStrictConvertible(Class formal, Class actual, 
boolean possibleVarArg)
+    private static boolean isStrictConvertible(Type formal, Class actual, 
boolean possibleVarArg)
     {
         return IntrospectionUtils.
             isStrictMethodInvocationConvertible(formal, actual, 
possibleVarArg);
@@ -650,7 +672,7 @@ public class MethodMap
      * @param possibleVarArg
      * @return
      */
-    private boolean isExplicitlyConvertible(Class formal, Class actual, 
boolean possibleVarArg)
+    private boolean isExplicitlyConvertible(Type formal, Class actual, boolean 
possibleVarArg)
     {
         return conversionHandler != null && 
conversionHandler.isExplicitlyConvertible(formal, actual, possibleVarArg);
     }

Copied: 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/TypeConversionHandlerImpl.java
 (from r1854372, 
velocity/engine/branches/VELOCITY-892/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/TypeConversionHandlerImpl.java)
URL: 
http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/TypeConversionHandlerImpl.java?p2=velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/TypeConversionHandlerImpl.java&p1=velocity/engine/branches/VELOCITY-892/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/TypeConversionHandlerImpl.java&r1=1854372&r2=1854386&rev=1854386&view=diff
==============================================================================
--- 
velocity/engine/branches/VELOCITY-892/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/TypeConversionHandlerImpl.java
 (original)
+++ 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/TypeConversionHandlerImpl.java
 Tue Feb 26 14:30:46 2019
@@ -19,11 +19,13 @@ package org.apache.velocity.util.introsp
  * under the License.
  */
 
+import org.apache.commons.lang3.LocaleUtils;
 import org.apache.commons.lang3.reflect.TypeUtils;
 import org.apache.commons.lang3.tuple.Pair;
 
 import java.lang.reflect.Type;
 import java.util.HashMap;
+import java.util.Locale;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -89,6 +91,7 @@ public class TypeConversionHandlerImpl i
     static final String NUMBER_CLASS = "java.lang.Number";
     static final String CHARACTER_CLASS = "java.lang.Character";
     static final String STRING_CLASS = "java.lang.String";
+    static final String LOCALE_CLASS = "java.util.Locale";
 
     static
     {
@@ -512,6 +515,17 @@ public class TypeConversionHandlerImpl i
                 return String.valueOf(o);
             }
         };
+
+        /* string to locale */
+        Converter<Locale> stringToLocale = new Converter<Locale>()
+        {
+            @Override
+            public Locale convert(Object o)
+            {
+                return o == null ? null : LocaleUtils.toLocale((String)o);
+            }
+        };
+        standardConverterMap.put(Pair.of(LOCALE_CLASS, STRING_CLASS), 
stringToLocale);
     }
 
     /**

Modified: 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/UberspectImpl.java
URL: 
http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/UberspectImpl.java?rev=1854386&r1=1854385&r2=1854386&view=diff
==============================================================================
--- 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/UberspectImpl.java
 (original)
+++ 
velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/UberspectImpl.java
 Tue Feb 26 14:30:46 2019
@@ -19,6 +19,7 @@ package org.apache.velocity.util.introsp
  * under the License.
  */
 
+import org.apache.commons.lang3.Conversion;
 import org.apache.velocity.exception.VelocityException;
 import org.apache.velocity.runtime.RuntimeConstants;
 import org.apache.velocity.runtime.RuntimeServices;
@@ -42,6 +43,7 @@ import org.slf4j.Logger;
 import java.lang.reflect.Array;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.lang.reflect.Type;
 import java.util.Enumeration;
 import java.util.Iterator;
 import java.util.Map;
@@ -69,7 +71,7 @@ public class UberspectImpl implements Ub
     /**
      * the conversion handler
      */
-    protected ConversionHandler conversionHandler;
+    protected TypeConversionHandler conversionHandler;
 
     /**
      * runtime services
@@ -86,7 +88,7 @@ public class UberspectImpl implements Ub
         introspector = new Introspector(log, conversionHandler);
     }
 
-    public ConversionHandler getConversionHandler()
+    public TypeConversionHandler getConversionHandler()
     {
         return conversionHandler;
     }
@@ -95,51 +97,86 @@ public class UberspectImpl implements Ub
      * sets the runtime services
      * @param rs runtime services
      */
+    @SuppressWarnings("deprecation")
     public void setRuntimeServices(RuntimeServices rs)
     {
         rsvc = rs;
         log = rsvc.getLog("introspection");
 
-        String conversionHandlerClass = 
rs.getString(RuntimeConstants.CONVERSION_HANDLER_CLASS);
-        if (conversionHandlerClass == null || 
conversionHandlerClass.equals("none"))
+        Object conversionHandlerInstance = 
rs.getProperty(RuntimeConstants.CONVERSION_HANDLER_INSTANCE);
+        if (conversionHandlerInstance == null)
         {
-            conversionHandler = null;
-        }
-        else
-        {
-            Object o = null;
-
-            try
+            String conversionHandlerClass = 
rs.getString(RuntimeConstants.CONVERSION_HANDLER_CLASS);
+            if (conversionHandlerClass != null && 
!conversionHandlerClass.equals("none"))
             {
-                o = ClassUtils.getNewInstance(conversionHandlerClass);
-            }
-            catch (ClassNotFoundException cnfe )
-            {
-                String err = "The specified class for ConversionHandler (" + 
conversionHandlerClass
+                try
+                {
+                    conversionHandlerInstance = 
ClassUtils.getNewInstance(conversionHandlerClass);
+                }
+                catch (ClassNotFoundException cnfe )
+                {
+                    String err = "The specified class for ConversionHandler (" 
+ conversionHandlerClass
                         + ") does not exist or is not accessible to the 
current classloader.";
-                log.error(err);
-                throw new VelocityException(err, cnfe);
-            }
-            catch (InstantiationException ie)
-            {
-                throw new VelocityException("Could not instantiate class '" + 
conversionHandlerClass + "'", ie);
+                    log.error(err);
+                    throw new VelocityException(err, cnfe);
+                }
+                catch (InstantiationException ie)
+                {
+                    throw new VelocityException("Could not instantiate class 
'" + conversionHandlerClass + "'", ie);
+                }
+                catch (IllegalAccessException ae)
+                {
+                    throw new VelocityException("Cannot access class '" + 
conversionHandlerClass + "'", ae);
+                }
             }
-            catch (IllegalAccessException ae)
+        }
+
+        if (conversionHandlerInstance != null)
+        {
+            if (conversionHandlerInstance instanceof ConversionHandler)
             {
-                throw new VelocityException("Cannot access class '" + 
conversionHandlerClass + "'", ae);
-            }
+                log.warn("The ConversionHandler interface is deprecated - see 
the TypeConversionHandler interface");
+                final ConversionHandler ch = 
(ConversionHandler)conversionHandlerInstance;
+                conversionHandler = new TypeConversionHandler()
+                {
+                    @Override
+                    public boolean isExplicitlyConvertible(Type formal, Class 
actual, boolean possibleVarArg)
+                    {
+                        Class formalClass = 
IntrospectionUtils.getTypeClass(formal);
+                        if (formalClass != null) return 
ch.isExplicitlyConvertible(formalClass, actual, possibleVarArg);
+                        else return false;
+                    }
 
-            if (!(o instanceof ConversionHandler))
+                    @Override
+                    public Converter getNeededConverter(Type formal, Class 
actual)
+                    {
+                        Class formalClass = 
IntrospectionUtils.getTypeClass(formal);
+                        if (formalClass != null) return 
ch.getNeededConverter(formalClass, actual);
+                        else return null;
+                    }
+
+                    @Override
+                    public void addConverter(Type formal, Class actual, 
Converter converter)
+                    {
+                        Class formalClass = 
IntrospectionUtils.getTypeClass(formal);
+                        if (formalClass != null) ch.addConverter(formalClass, 
actual, converter);
+                        else throw new UnsupportedOperationException("This 
conversion handler doesn't know how to handle Type: " + formal.getTypeName());
+                    }
+                };
+            }
+            else if (!(conversionHandlerInstance instanceof 
TypeConversionHandler))
             {
-                String err = "The specified class for ResourceManager (" + 
conversionHandlerClass
-                        + ") does not implement " + 
ConversionHandler.class.getName()
+                String err = "The specified class or provided instance for the 
conversion handler (" + conversionHandlerInstance.getClass().getName()
+                        + ") does not implement " + 
TypeConversionHandler.class.getName()
                         + "; Velocity is not initialized correctly.";
 
                 log.error(err);
                 throw new VelocityException(err);
             }
-
-            conversionHandler = (ConversionHandler) o;
+            else
+            {
+                conversionHandler = 
(TypeConversionHandler)conversionHandlerInstance;
+            }
         }
     }
 
@@ -256,7 +293,7 @@ public class UberspectImpl implements Ub
         Method m = introspector.getMethod(obj.getClass(), methodName, args);
         if (m != null)
         {
-            return new VelMethodImpl(m, false, 
getNeededConverters(m.getParameterTypes(), args));
+            return new VelMethodImpl(m, false, 
getNeededConverters(m.getGenericParameterTypes(), args));
         }
 
         Class cls = obj.getClass();
@@ -269,7 +306,7 @@ public class UberspectImpl implements Ub
             {
                 // and create a method that knows to wrap the value
                 // before invoking the method
-                return new VelMethodImpl(m, true, 
getNeededConverters(m.getParameterTypes(), args));
+                return new VelMethodImpl(m, true, 
getNeededConverters(m.getGenericParameterTypes(), args));
             }
         }
         // watch for classes, to allow calling their static methods 
(VELOCITY-102)
@@ -278,7 +315,7 @@ public class UberspectImpl implements Ub
             m = introspector.getMethod((Class)obj, methodName, args);
             if (m != null)
             {
-                return new VelMethodImpl(m, false, 
getNeededConverters(m.getParameterTypes(), args));
+                return new VelMethodImpl(m, false, 
getNeededConverters(m.getGenericParameterTypes(), args));
             }
         }
         return null;
@@ -288,7 +325,7 @@ public class UberspectImpl implements Ub
      * get the list of needed converters to adapt passed argument types to 
method types
      * @return null if not conversion needed, otherwise an array containing 
needed converters
      */
-    private Converter[] getNeededConverters(Class[] expected, Object[] 
provided)
+    private Converter[] getNeededConverters(Type[] expected, Object[] provided)
     {
         if (conversionHandler == null) return null;
         // var args are not handled here - CB TODO

Modified: 
velocity/engine/trunk/velocity-engine-core/src/main/resources/org/apache/velocity/runtime/defaults/velocity.properties
URL: 
http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/resources/org/apache/velocity/runtime/defaults/velocity.properties?rev=1854386&r1=1854385&r2=1854386&view=diff
==============================================================================
--- 
velocity/engine/trunk/velocity-engine-core/src/main/resources/org/apache/velocity/runtime/defaults/velocity.properties
 (original)
+++ 
velocity/engine/trunk/velocity-engine-core/src/main/resources/org/apache/velocity/runtime/defaults/velocity.properties
 Tue Feb 26 14:30:46 2019
@@ -210,7 +210,7 @@ runtime.introspector.uberspect = org.apa
 # Sets the data types Conversion Handler used by the default uberspector
 # ----------------------------------------------------------------------------
 
-runtime.conversion.handler.class = 
org.apache.velocity.util.introspection.ConversionHandlerImpl
+runtime.conversion.handler.class = 
org.apache.velocity.util.introspection.TypeConversionHandlerImpl
 
 
 # ----------------------------------------------------------------------------

Propchange: velocity/engine/trunk/velocity-engine-core/src/test/java/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Tue Feb 26 14:30:46 2019
@@ -1,5 +1,6 @@
 /velocity/engine/branches/2.0_Exp/src/test:991639-992140
 
/velocity/engine/branches/2.0_Exp/velocity-engine-core/src/test/java:958513,991637-995742
+/velocity/engine/branches/VELOCITY-892/velocity-engine-core/src/test/java:1844076-1854372
 
/velocity/engine/branches/VELOCITY-898/velocity-engine-core/src/test/java:1843220-1843786
 /velocity/engine/trunk/src/test:1032134
 /velocity/engine/trunk/velocity-engine-core/src/test/java:992133,1032159

Modified: 
velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/ConversionHandlerTestCase.java
URL: 
http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/ConversionHandlerTestCase.java?rev=1854386&r1=1854385&r2=1854386&view=diff
==============================================================================
--- 
velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/ConversionHandlerTestCase.java
 (original)
+++ 
velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/ConversionHandlerTestCase.java
 Tue Feb 26 14:30:46 2019
@@ -19,6 +19,8 @@
 package org.apache.velocity.test.util.introspection;
 
 import junit.framework.TestSuite;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.reflect.TypeUtils;
 import org.apache.velocity.Template;
 import org.apache.velocity.VelocityContext;
 import org.apache.velocity.app.Velocity;
@@ -28,11 +30,11 @@ import org.apache.velocity.context.Conte
 import org.apache.velocity.runtime.RuntimeConstants;
 import org.apache.velocity.runtime.RuntimeInstance;
 import org.apache.velocity.test.BaseTestCase;
-import org.apache.velocity.util.introspection.ConversionHandler;
-import org.apache.velocity.util.introspection.ConversionHandlerImpl;
 import org.apache.velocity.util.introspection.Converter;
 import org.apache.velocity.util.introspection.Info;
 import org.apache.velocity.util.introspection.IntrospectionUtils;
+import org.apache.velocity.util.introspection.TypeConversionHandler;
+import org.apache.velocity.util.introspection.TypeConversionHandlerImpl;
 import org.apache.velocity.util.introspection.Uberspect;
 import org.apache.velocity.util.introspection.UberspectImpl;
 
@@ -41,6 +43,9 @@ import java.io.FileOutputStream;
 import java.io.OutputStreamWriter;
 import java.io.StringWriter;
 import java.io.Writer;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.TreeMap;
@@ -114,7 +119,7 @@ public class ConversionHandlerTestCase e
         Uberspect uberspect = ve.getUberspect();
         assertTrue(uberspect instanceof UberspectImpl);
         UberspectImpl ui = (UberspectImpl)uberspect;
-        ConversionHandler ch = ui.getConversionHandler();
+        TypeConversionHandler ch = ui.getConversionHandler();
         assertTrue(ch != null);
         ch.addConverter(Float.class, Obj.class, new Converter<Float>()
         {
@@ -124,11 +129,87 @@ public class ConversionHandlerTestCase e
                 return 4.5f;
             }
         });
+        ch.addConverter(TypeUtils.parameterize(List.class, Integer.class), 
String.class, new Converter<List<Integer>>()
+        {
+            @Override
+            public List<Integer> convert(Object o)
+            {
+                return Arrays.<Integer>asList(1,2,3);
+            }
+        });
+        ch.addConverter(TypeUtils.parameterize(List.class, String.class), 
String.class, new Converter<List<String>>()
+        {
+            @Override
+            public List<String> convert(Object o)
+            {
+                return Arrays.<String>asList("a", "b", "c");
+            }
+        });
         VelocityContext context = new VelocityContext();
         context.put("obj", new Obj());
         Writer writer = new StringWriter();
         ve.evaluate(context, writer, "test", "$obj.integralFloat($obj) / 
$obj.objectFloat($obj)");
         assertEquals("float ok: 4.5 / Float ok: 4.5", writer.toString());
+        writer = new StringWriter();
+        ve.evaluate(context, writer, "test", 
"$obj.iWantAStringList('anything')");
+        assertEquals("correct", writer.toString());
+        writer = new StringWriter();
+        ve.evaluate(context, writer, "test", 
"$obj.iWantAnIntegerList('anything')");
+        assertEquals("correct", writer.toString());
+    }
+
+    /* converts *everything* to string "foo" */
+    public static class MyCustomConverter implements TypeConversionHandler
+    {
+        Converter<String> myCustomConverter = new Converter<String>()
+        {
+
+            @Override
+            public String convert(Object o)
+            {
+                return "foo";
+            }
+        };
+
+        @Override
+        public boolean isExplicitlyConvertible(Type formal, Class actual, 
boolean possibleVarArg)
+        {
+            return true;
+        }
+
+        @Override
+        public Converter getNeededConverter(Type formal, Class actual)
+        {
+            return myCustomConverter;
+        }
+
+        @Override
+        public void addConverter(Type formal, Class actual, Converter 
converter)
+        {
+            throw new RuntimeException("not implemented");
+        }
+    }
+
+    public void testCustomConversionHandlerInstance()
+    {
+        RuntimeInstance ve = new RuntimeInstance();
+        ve.setProperty( Velocity.VM_PERM_INLINE_LOCAL, Boolean.TRUE);
+        ve.setProperty(Velocity.RUNTIME_LOG_INSTANCE, log);
+        ve.setProperty(RuntimeConstants.RESOURCE_LOADER, "file");
+        ve.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, 
TEST_COMPARE_DIR + "/conversion");
+        ve.setProperty(RuntimeConstants.CONVERSION_HANDLER_INSTANCE, new 
MyCustomConverter());
+        ve.init();
+        Uberspect uberspect = ve.getUberspect();
+        assertTrue(uberspect instanceof UberspectImpl);
+        UberspectImpl ui = (UberspectImpl)uberspect;
+        TypeConversionHandler ch = ui.getConversionHandler();
+        assertTrue(ch != null);
+        assertTrue(ch instanceof MyCustomConverter);
+        VelocityContext context = new VelocityContext();
+        context.put("obj", new Obj());
+        Writer writer = new StringWriter();
+        ve.evaluate(context, writer, "test", "$obj.objectString(1.0)");
+        assertEquals("String ok: foo", writer.toString());
     }
 
     /**
@@ -174,6 +255,15 @@ public class ConversionHandlerTestCase e
         }
     }
 
+    public void testOtherConversions() throws Exception
+    {
+        VelocityEngine ve = createEngine(false);
+        VelocityContext context = createContext();
+        StringWriter writer = new StringWriter();
+        ve.evaluate(context, writer,"test", "$strings.join(['foo', 'bar'], 
',')");
+        assertEquals("foo,bar", writer.toString());
+    }
+
     /**
      * Return and initialize engine
      * @return
@@ -264,6 +354,7 @@ public class ConversionHandlerTestCase e
                 };
         context.put("types", types);
         context.put("introspect", new Introspect());
+        context.put("strings", new StringUtils());
         return context;
     }
 
@@ -294,14 +385,28 @@ public class ConversionHandlerTestCase e
         public String locale(Locale loc) { return "Locale ok: " + loc; }
 
         public String toString() { return "instance of Obj"; }
+
+        public String iWantAStringList(List<String> list)
+        {
+            if (list != null && list.size() == 3 && list.get(0).equals("a") && 
list.get(1).equals("b") && list.get(2).equals("c"))
+                return "correct";
+            else return "wrong";
+        }
+
+        public String iWantAnIntegerList(List<Integer> list)
+        {
+            if (list != null && list.size() == 3 && list.get(0).equals(1) && 
list.get(1).equals(2) && list.get(2).equals(3))
+                return "correct";
+            else return "wrong";
+        }
     }
 
     public static class Introspect
     {
-        private ConversionHandler handler;
+        private TypeConversionHandler handler;
         public Introspect()
         {
-            handler = new ConversionHandlerImpl();
+            handler = new TypeConversionHandlerImpl();
         }
         public boolean isStrictlyConvertible(Class expected, Class provided)
         {


Reply via email to