You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@velocity.apache.org by cb...@apache.org on 2016/07/26 16:11:29 UTC

svn commit: r1754151 [1/3] - in /velocity/engine/trunk: src/changes/ velocity-engine-core/src/main/java/org/apache/velocity/app/ velocity-engine-core/src/main/java/org/apache/velocity/runtime/ velocity-engine-core/src/main/java/org/apache/velocity/runt...

Author: cbrisson
Date: Tue Jul 26 16:11:28 2016
New Revision: 1754151

URL: http://svn.apache.org/viewvc?rev=1754151&view=rev
Log:
New datatypes conversion framework:

 - added the new interface o.a.v.util.introspection.ConversionHandler,
   and the new 'runtime.conversion.handler' configuration property
   which let the configuration handler be pluggable.

 - added a default implementation class o.a.v.util.introspection.ConversionHandlerImpl
   that will implicitly convert method arguments between all basic Java data types
   (boolean, numbers, string).

 - added a new Converter<T> interface to represent converters towards type T

 - added a public VelMethod.getMethod() getter to ease the work of people
   customizing introspection

 - added test case ConversionHandlerTestCase

 - removed half-finished IntrospectionCache 'pluggability' feature:
   since IntrospectionCache now needs a ConversionHandler a c'tor argument,
   made it a concrete class and removed IntrospectionCacheImpl.


Added:
    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/ConversionHandler.java
    velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ConversionHandlerImpl.java
    velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/ConversionHandlerTestCase.java
      - copied, changed from r1753736, velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/ParseWithMacroLibsTestCase.java
    velocity/engine/trunk/velocity-engine-core/src/test/resources/conversion/
    velocity/engine/trunk/velocity-engine-core/src/test/resources/conversion/compare/
    velocity/engine/trunk/velocity-engine-core/src/test/resources/conversion/compare/matrix.cmp
    velocity/engine/trunk/velocity-engine-core/src/test/resources/conversion/compare/test_conv_with_handler.cmp
    velocity/engine/trunk/velocity-engine-core/src/test/resources/conversion/compare/test_conv_without_handler.cmp
    velocity/engine/trunk/velocity-engine-core/src/test/resources/conversion/matrix.vhtml
    velocity/engine/trunk/velocity-engine-core/src/test/resources/conversion/test_conv.vtl
Removed:
    velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectorCacheImpl.java
Modified:
    velocity/engine/trunk/src/changes/changes.xml
    velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/app/VelocityEngine.java
    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/RuntimeInstance.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/runtime/RuntimeSingleton.java
    velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIdentifier.java
    velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/GetExecutor.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/Converter.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/java/org/apache/velocity/util/introspection/VelMethod.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/org/apache/velocity/test/BaseTestCase.java
    velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/ClassloaderChangeTestCase.java
    velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/Introspector2TestCase.java
    velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/Introspector3TestCase.java
    velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/IntrospectorTestCase.java
    velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/StrictReferenceTestCase.java
    velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/VelTools66TestCase.java

Modified: velocity/engine/trunk/src/changes/changes.xml
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/src/changes/changes.xml?rev=1754151&r1=1754150&r2=1754151&view=diff
==============================================================================
--- velocity/engine/trunk/src/changes/changes.xml (original)
+++ velocity/engine/trunk/src/changes/changes.xml Tue Jul 26 16:11:28 2016
@@ -28,6 +28,10 @@
     <release version="2.0" date="In Subversion">
 
       <action type="add" dev="cbrisson">
+        added a new pluggable ConversionHandler class which, by default, converts method arguments as needed between main basic Java data types (boolean, numbers and strings)
+      </action>
+      
+      <action type="add" dev="cbrisson">
         added a runtime.string.interning option to trigger Java String interning on or off
       </action>
       

Modified: velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/app/VelocityEngine.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/app/VelocityEngine.java?rev=1754151&r1=1754150&r2=1754151&view=diff
==============================================================================
--- velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/app/VelocityEngine.java (original)
+++ velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/app/VelocityEngine.java Tue Jul 26 16:11:28 2016
@@ -161,7 +161,7 @@ public class VelocityEngine implements R
      * is a subset of the parent application's configuration.
      *
      * @param  configuration
-     * @deprecated use {@link setExtendedProperties(ExtProperties)}
+     * @deprecated use {@link #setExtendedProperties(ExtProperties)}
      */
     public @Deprecated void setExtendedProperties( ExtendedProperties configuration)
     {

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=1754151&r1=1754150&r2=1754151&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 Jul 26 16:11:28 2016
@@ -240,6 +240,9 @@ public interface RuntimeConstants
     /** 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 class */
+    String CONVERSION_HANDLER_CLASS = "runtime.conversion.handler.class";
+
     /** A comma separated list of packages to restrict access to in the SecureIntrospector. */
     String INTROSPECTOR_RESTRICT_PACKAGES = "introspector.restrict.packages";
 

Modified: velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeInstance.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeInstance.java?rev=1754151&r1=1754150&r2=1754151&view=diff
==============================================================================
--- velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeInstance.java (original)
+++ velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeInstance.java Tue Jul 26 16:11:28 2016
@@ -49,7 +49,6 @@ import org.apache.velocity.util.ExtPrope
 import org.apache.velocity.util.RuntimeServicesAware;
 import org.apache.velocity.util.StringUtils;
 import org.apache.velocity.util.introspection.ChainableUberspector;
-import org.apache.velocity.util.introspection.Introspector;
 import org.apache.velocity.util.introspection.LinkingUberspector;
 import org.apache.velocity.util.introspection.Uberspect;
 import org.apache.velocity.util.introspection.UberspectLoggable;
@@ -175,12 +174,6 @@ public class RuntimeInstance implements
     private EventCartridge eventCartridge = null;
 
     /*
-     *  Each runtime instance has it's own introspector
-     *  to ensure that each instance is completely separate.
-     */
-    private Introspector introspector = null;
-
-    /*
      * Whether to use string interning
      */
     private boolean stringInterning = false;
@@ -302,11 +295,6 @@ public class RuntimeInstance implements
         vmFactory = new VelocimacroFactory( this );
 
         /*
-         *  make a new introspector and initialize it
-         */
-        introspector = new Introspector(getLog());
-
-        /*
          * and a store for the application attributes
          */
         applicationAttributes = new HashMap();
@@ -1807,15 +1795,6 @@ public class RuntimeInstance implements
     }
 
     /**
-     *  Return the Introspector for this instance
-     * @return The Introspector for this instance
-     */
-    public Introspector getIntrospector()
-    {
-        return introspector;
-    }
-
-    /**
      * Returns the event handlers for the application.
      * @return The event handlers for the application.
      * @since 1.5

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=1754151&r1=1754150&r2=1754151&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 Jul 26 16:11:28 2016
@@ -445,15 +445,6 @@ public interface RuntimeServices
      */
     public EventCartridge getApplicationEventCartridge();
 
-
-    /**
-     * Returns the configured method introspection/reflection
-     * implementation.
-     * @return The configured method introspection/reflection
-     * implementation.
-     */
-    public Introspector getIntrospector();
-
     /**
      * Returns true if the RuntimeInstance has been successfully initialized.
      * @return True if the RuntimeInstance has been successfully initialized.

Modified: velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeSingleton.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeSingleton.java?rev=1754151&r1=1754150&r2=1754151&view=diff
==============================================================================
--- velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeSingleton.java (original)
+++ velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeSingleton.java Tue Jul 26 16:11:28 2016
@@ -524,17 +524,6 @@ public class RuntimeSingleton implements
     }
 
     /**
-     *  Return the Introspector for this RuntimeInstance
-     *
-     *  @return Introspector object for this runtime instance
-     * @see RuntimeInstance#getIntrospector()
-     */
-    public static Introspector getIntrospector()
-    {
-        return ri.getIntrospector();
-    }
-
-    /**
      * Returns the event handlers for the application.
      * @return The event handlers for the application.
      * @see RuntimeInstance#getApplicationEventCartridge()

Modified: velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIdentifier.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIdentifier.java?rev=1754151&r1=1754150&r2=1754151&view=diff
==============================================================================
--- velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIdentifier.java (original)
+++ velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIdentifier.java Tue Jul 26 16:11:28 2016
@@ -148,7 +148,7 @@ public class ASTIdentifier extends Simpl
                  *  uberspector
                  */
 
-                vg = rsvc.getUberspect().getPropertyGet(o,identifier, uberInfo);
+                vg = rsvc.getUberspect().getPropertyGet(o, identifier, uberInfo);
 
                 if (vg != null && vg.isCacheable() && (o != null))
                 {

Modified: velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/GetExecutor.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/GetExecutor.java?rev=1754151&r1=1754150&r2=1754151&view=diff
==============================================================================
--- velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/GetExecutor.java (original)
+++ velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/GetExecutor.java Tue Jul 26 16:11:28 2016
@@ -78,6 +78,17 @@ public class GetExecutor extends Abstrac
         try
         {
             setMethod(introspector.getMethod(clazz, "get", params));
+            /* get(Number) or get(integral) are NOT admissible,
+             * as the key is a string
+             */
+            if (getMethod() != null)
+            {
+                Class<?> [] args = getMethod().getParameterTypes();
+                if (args.length == 1 && (args[0].isPrimitive() || Number.class.isAssignableFrom(args[0])))
+                {
+                    setMethod(null);
+                }
+            }
         }
         /**
          * pass through application level runtime exceptions

Added: velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/Pair.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/Pair.java?rev=1754151&view=auto
==============================================================================
--- velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/Pair.java (added)
+++ velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/Pair.java Tue Jul 26 16:11:28 2016
@@ -0,0 +1,79 @@
+package org.apache.velocity.util;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+
+/**
+ *  Combine two objects in a hashable pair
+ *
+ * @version $Id: Pair.java $
+ * @since 2.0
+ */
+
+public class Pair<A, B>
+{
+    private final A first;
+    private final B second;
+
+    public Pair(final A first, final B second)
+    {
+        this.first = first;
+        this.second = second;
+    }
+
+    public int hashCode()
+    {
+        int hashFirst = first != null ? first.hashCode() : 0;
+        int hashSecond = second != null ? second.hashCode() : 0;
+
+        return (hashFirst + hashSecond) * hashSecond + hashFirst;
+    }
+
+    public boolean equals(Object other)
+    {
+        if (other instanceof Pair)
+        {
+            Pair otherPair = (Pair) other;
+            return
+                    (( this.first == otherPair.first ||
+                            ( this.first != null && otherPair.first != null &&
+                                    this.first.equals(otherPair.first))) &&
+                            ( this.second == otherPair.second ||
+                                    ( this.second != null && otherPair.second != null &&
+                                            this.second.equals(otherPair.second))) );
+        }
+
+        return false;
+    }
+
+    public String toString()
+    {
+        return "(" + first + ", " + second + ")";
+    }
+
+    public A getFirst()
+    {
+        return first;
+    }
+
+    public B getSecond()
+    {
+        return second;
+    }
+}

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=1754151&r1=1754150&r2=1754151&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 Jul 26 16:11:28 2016
@@ -38,6 +38,7 @@ import java.util.concurrent.ConcurrentHa
  * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
  * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
  * @author Nathan Bubna
+ * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
  * @version $Id$
  */
 public class ClassMap
@@ -62,6 +63,18 @@ public class ClassMap
      */
     public ClassMap(final Class clazz, final Logger log)
     {
+        this(clazz, log, null);
+    }
+
+    /**
+     * Standard constructor
+     * @param clazz The class for which this ClassMap gets constructed.
+     * @param log logger
+     * @param conversionHandler conversion handler
+     * @since 2.0
+     */
+    public ClassMap(final Class clazz, final Logger log, final ConversionHandler conversionHandler)
+    {
         this.clazz = clazz;
         this.log = log;
 
@@ -71,7 +84,7 @@ public class ClassMap
             log.debug("== Class: {}", clazz);
         }
 
-        methodCache = createMethodCache();
+        methodCache = createMethodCache(conversionHandler);
 
         if (debugReflection && log.isDebugEnabled())
         {
@@ -108,9 +121,9 @@ public class ClassMap
      * are taken from all the public methods
      * that our class, its parents and their implemented interfaces provide.
      */
-    private MethodCache createMethodCache()
+    private MethodCache createMethodCache(ConversionHandler conversionHandler)
     {
-        MethodCache methodCache = new MethodCache(log);
+        MethodCache methodCache = new MethodCache(log, conversionHandler);
 	//
 	// Looks through all elements in the class hierarchy. This one is bottom-first (i.e. we start
 	// with the actual declaring class and its interfaces and then move up (superclass etc.) until we
@@ -219,11 +232,12 @@ public class ClassMap
         private final Map cache = new ConcurrentHashMap();
 
         /** Map of methods that are searchable according to method parameters to find a match */
-        private final MethodMap methodMap = new MethodMap();
+        private final MethodMap methodMap;
 
-        private MethodCache(Logger log)
+        private MethodCache(Logger log, ConversionHandler conversionHandler)
         {
             this.log = log;
+            methodMap = new MethodMap(conversionHandler);
         }
 
         /**

Added: 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=1754151&view=auto
==============================================================================
--- velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ConversionHandler.java (added)
+++ velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ConversionHandler.java Tue Jul 26 16:11:28 2016
@@ -0,0 +1,64 @@
+package org.apache.velocity.util.introspection;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * A conversion handler adds admissible conversions between Java types whenever Velocity introspection has to map
+ * VTL methods and property accessors to Java methods.
+ * Both methods must be consistent: <code>getNeededConverter</code> must not return <code>null</code> whenever
+ * <code>isExplicitelyConvertible</code> returned true with the same arguments.
+ *
+ * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
+ * @version $Id: ConversionHandler.java $
+ * @since 2.0
+ */
+
+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
+     * @since 2.0
+     */
+    public boolean isExplicitlyConvertible(Class formal, Class actual, boolean possibleVarArg);
+
+    /**
+     * Returns the appropriate Converter object needed for an explicit conversion
+     * Returns null if no conversion is needed.
+     *
+     * @param formal expected formal type
+     * @param actual provided argument type
+     * @return null if no conversion is needed, or the appropriate Converter object
+     * @since 2.0
+     */
+    public Converter getNeededConverter(final Class formal, final Class actual);
+
+    /**
+     * Add the given converter to the handler. Implementation should be thread-safe.
+     *
+     * @param formal expected formal type
+     * @param actual provided argument type
+     * @param converter converter
+     * @since 2.0
+     */
+    public void addConverter(Class formal, Class actual, Converter converter);
+}

Added: velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ConversionHandlerImpl.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ConversionHandlerImpl.java?rev=1754151&view=auto
==============================================================================
--- velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ConversionHandlerImpl.java (added)
+++ velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ConversionHandlerImpl.java Tue Jul 26 16:11:28 2016
@@ -0,0 +1,485 @@
+package org.apache.velocity.util.introspection;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.velocity.util.Pair;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * A conversion handler adds admissible conversions between Java types whenever Velocity introspection has to map
+ * VTL methods and property accessors to Java methods. This implementation is the default Conversion Handler
+ * for Velocity.
+ *
+ * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
+ * @version $Id: ConversionHandlerImpl.java $
+ * @since 2.0
+ */
+
+public class ConversionHandlerImpl implements ConversionHandler
+{
+    /**
+     * standard narrowing and string parsing conversions.
+     */
+    static Map<Pair<? extends Class, ? extends Class>, Converter> standardConverterMap;
+
+    /**
+     * basic toString converter
+     */
+    static Converter toString;
+
+    /**
+     * cache miss converter
+     */
+    static Converter cacheMiss;
+
+    /**
+     * min/max byte/short/int values as long
+     */
+    static final long minByte = Byte.MIN_VALUE, maxByte = Byte.MAX_VALUE,
+        minShort = Short.MIN_VALUE, maxShort = Short.MAX_VALUE,
+        minInt = Integer.MIN_VALUE, maxInt = Integer.MAX_VALUE;
+
+    /**
+     * min/max long values as double
+     */
+    static final double minLong = Long.MIN_VALUE, maxLong = Long.MAX_VALUE;
+
+    /**
+     * a converters cache map, initialized with the standard narrowing and string parsing conversions.
+     */
+    Map<Pair<? extends Class, ? extends Class>, Converter> converterCacheMap;
+
+    static
+    {
+        standardConverterMap = new HashMap<Pair<? extends Class, ? extends Class>, Converter>();
+
+        cacheMiss = new Converter<Object>()
+        {
+            @Override
+            public Object convert(Object o)
+            {
+                return o;
+            }
+        };
+
+        /* number -> boolean */
+        Converter<Boolean> numberToBool = new Converter<Boolean>()
+        {
+            @Override
+            public Boolean convert(Object o)
+            {
+                return o == null ? null : ((Number) o).intValue() != 0;
+            }
+        };
+        standardConverterMap.put(new Pair<>(Boolean.class, Byte.class), numberToBool);
+        standardConverterMap.put(new Pair<>(Boolean.class, Short.class), numberToBool);
+        standardConverterMap.put(new Pair<>(Boolean.class, Integer.class), numberToBool);
+        standardConverterMap.put(new Pair<>(Boolean.class, Long.class), numberToBool);
+        standardConverterMap.put(new Pair<>(Boolean.class, Float.class), numberToBool);
+        standardConverterMap.put(new Pair<>(Boolean.class, Double.class), numberToBool);
+        standardConverterMap.put(new Pair<>(Boolean.TYPE, Byte.class), numberToBool);
+        standardConverterMap.put(new Pair<>(Boolean.TYPE, Short.class), numberToBool);
+        standardConverterMap.put(new Pair<>(Boolean.TYPE, Integer.class), numberToBool);
+        standardConverterMap.put(new Pair<>(Boolean.TYPE, Long.class), numberToBool);
+        standardConverterMap.put(new Pair<>(Boolean.TYPE, Float.class), numberToBool);
+        standardConverterMap.put(new Pair<>(Boolean.TYPE, Double.class), numberToBool);
+
+        /* character -> boolean */
+        Converter<Boolean> charToBoolean = new Converter<Boolean>()
+        {
+            @Override
+            public Boolean convert(Object o)
+            {
+                return o == null ? null : ((Character) o).charValue() != 0;
+            }
+        };
+        standardConverterMap.put(new Pair<>(Boolean.class, Character.class), charToBoolean);
+        standardConverterMap.put(new Pair<>(Boolean.TYPE, Character.class), charToBoolean);
+
+        /* string -> boolean */
+        Converter<Boolean> stringToBoolean = new Converter<Boolean>()
+        {
+            @Override
+            public Boolean convert(Object o)
+            {
+                return Boolean.valueOf(String.valueOf(o));
+            }
+        };
+        standardConverterMap.put(new Pair<>(Boolean.class, String.class), stringToBoolean);
+        standardConverterMap.put(new Pair<>(Boolean.TYPE, String.class), stringToBoolean);
+
+        /* narrowing towards byte */
+        Converter<Byte> narrowingToByte = new Converter<Byte>()
+        {
+            @Override
+            public Byte convert(Object o)
+            {
+                if (o == null) return null;
+                long l = ((Number)o).longValue();
+                if (l < minByte || l > maxByte)
+                {
+                    throw new NumberFormatException("value out of range for byte type: " + l);
+                }
+                return ((Number) o).byteValue();
+            }
+        };
+        standardConverterMap.put(new Pair<>(Byte.class, Short.class), narrowingToByte);
+        standardConverterMap.put(new Pair<>(Byte.class, Integer.class), narrowingToByte);
+        standardConverterMap.put(new Pair<>(Byte.class, Long.class), narrowingToByte);
+        standardConverterMap.put(new Pair<>(Byte.class, Float.class), narrowingToByte);
+        standardConverterMap.put(new Pair<>(Byte.class, Double.class), narrowingToByte);
+        standardConverterMap.put(new Pair<>(Byte.TYPE, Short.class), narrowingToByte);
+        standardConverterMap.put(new Pair<>(Byte.TYPE, Integer.class), narrowingToByte);
+        standardConverterMap.put(new Pair<>(Byte.TYPE, Long.class), narrowingToByte);
+        standardConverterMap.put(new Pair<>(Byte.TYPE, Float.class), narrowingToByte);
+        standardConverterMap.put(new Pair<>(Byte.TYPE, Double.class), narrowingToByte);
+
+        /* string to byte */
+        Converter<Byte> stringToByte = new Converter<Byte>()
+        {
+            @Override
+            public Byte convert(Object o)
+            {
+                return Byte.valueOf(String.valueOf(o));
+            }
+        };
+        standardConverterMap.put(new Pair<>(Byte.class, String.class), stringToByte);
+        standardConverterMap.put(new Pair<>(Byte.TYPE, String.class), stringToByte);
+
+        /* narrowing towards short */
+        Converter<Short> narrowingToShort = new Converter<Short>()
+        {
+            @Override
+            public Short convert(Object o)
+            {
+                if (o == null) return null;
+                long l = ((Number)o).longValue();
+                if (l < minShort || l > maxShort)
+                {
+                    throw new NumberFormatException("value out of range for short type: " + l);
+                }
+                return ((Number) o).shortValue();
+            }
+        };
+        standardConverterMap.put(new Pair<>(Short.class, Integer.class), narrowingToShort);
+        standardConverterMap.put(new Pair<>(Short.class, Long.class), narrowingToShort);
+        standardConverterMap.put(new Pair<>(Short.class, Float.class), narrowingToShort);
+        standardConverterMap.put(new Pair<>(Short.class, Double.class), narrowingToShort);
+        standardConverterMap.put(new Pair<>(Short.TYPE, Integer.class), narrowingToShort);
+        standardConverterMap.put(new Pair<>(Short.TYPE, Long.class), narrowingToShort);
+        standardConverterMap.put(new Pair<>(Short.TYPE, Float.class), narrowingToShort);
+        standardConverterMap.put(new Pair<>(Short.TYPE, Double.class), narrowingToShort);
+
+        /* string to short */
+        Converter<Short> stringToShort = new Converter<Short>()
+        {
+            @Override
+            public Short convert(Object o)
+            {
+                return Short.valueOf(String.valueOf(o));
+            }
+        };
+        standardConverterMap.put(new Pair<>(Short.class, String.class), stringToShort);
+        standardConverterMap.put(new Pair<>(Short.TYPE, String.class), stringToShort);
+
+        /* narrowing towards int */
+        Converter<Integer> narrowingToInteger = new Converter<Integer>()
+        {
+            @Override
+            public Integer convert(Object o)
+            {
+                if (o == null) return null;
+                long l = ((Number)o).longValue();
+                if (l < minInt || l > maxInt)
+                {
+                    throw new NumberFormatException("value out of range for integer type: " + l);
+                }
+                return ((Number) o).intValue();
+            }
+        };
+        standardConverterMap.put(new Pair<>(Integer.class, Long.class), narrowingToInteger);
+        standardConverterMap.put(new Pair<>(Integer.class, Float.class), narrowingToInteger);
+        standardConverterMap.put(new Pair<>(Integer.class, Double.class), narrowingToInteger);
+        standardConverterMap.put(new Pair<>(Integer.TYPE, Long.class), narrowingToInteger);
+        standardConverterMap.put(new Pair<>(Integer.TYPE, Float.class), narrowingToInteger);
+        standardConverterMap.put(new Pair<>(Integer.TYPE, Double.class), narrowingToInteger);
+
+        /* string to int */
+        Converter<Integer> stringToInteger = new Converter<Integer>()
+        {
+            @Override
+            public Integer convert(Object o)
+            {
+                return Integer.valueOf(String.valueOf(o));
+            }
+        };
+        standardConverterMap.put(new Pair<>(Integer.class, String.class), stringToInteger);
+        standardConverterMap.put(new Pair<>(Integer.TYPE, String.class), stringToInteger);
+        
+        /* narrowing towards long */
+        Converter<Long> narrowingToLong = new Converter<Long>()
+        {
+            @Override
+            public Long convert(Object o)
+            {
+                if (o == null) return null;
+                double d = ((Number)o).doubleValue();
+                if (d < minLong || d > maxLong)
+                {
+                    throw new NumberFormatException("value out of range for long type: " + d);
+                }
+                return ((Number) o).longValue();
+            }
+        };
+        standardConverterMap.put(new Pair<>(Long.class, Float.class), narrowingToLong);
+        standardConverterMap.put(new Pair<>(Long.class, Double.class), narrowingToLong);
+        standardConverterMap.put(new Pair<>(Long.TYPE, Float.class), narrowingToLong);
+        standardConverterMap.put(new Pair<>(Long.TYPE, Double.class), narrowingToLong);
+
+        /* string to long */
+        Converter<Long> stringToLong = new Converter<Long>()
+        {
+            @Override
+            public Long convert(Object o)
+            {
+                return Long.valueOf(String.valueOf(o));
+            }
+        };
+        standardConverterMap.put(new Pair<>(Long.class, String.class), stringToLong);
+        standardConverterMap.put(new Pair<>(Long.TYPE, String.class), stringToLong);
+        
+        /* narrowing towards float */
+        Converter<Float> narrowingToFloat = new Converter<Float>()
+        {
+            @Override
+            public Float convert(Object o)
+            {
+                return o == null ? null : ((Number) o).floatValue();
+            }
+        };
+        standardConverterMap.put(new Pair<>(Float.class, Double.class), narrowingToFloat);
+        standardConverterMap.put(new Pair<>(Float.TYPE, Double.class), narrowingToFloat);
+
+        /* string to float */
+        Converter<Float> stringToFloat = new Converter<Float>()
+        {
+            @Override
+            public Float convert(Object o)
+            {
+                return Float.valueOf(String.valueOf(o));
+            }
+        };
+        standardConverterMap.put(new Pair<>(Float.class, String.class), stringToFloat);
+        standardConverterMap.put(new Pair<>(Float.TYPE, String.class), stringToFloat);
+
+        /* string to double */
+        Converter<Double> stringToDouble = new Converter<Double>()
+        {
+            @Override
+            public Double convert(Object o)
+            {
+                return Double.valueOf(String.valueOf(o));
+            }
+        };
+        standardConverterMap.put(new Pair<>(Double.class, String.class), stringToDouble);
+        standardConverterMap.put(new Pair<>(Double.TYPE, String.class), stringToDouble);
+
+        /* boolean to byte */
+        Converter<Byte> booleanToByte = new Converter<Byte>()
+        {
+            @Override
+            public Byte convert(Object o)
+            {
+                return o == null ? null : ((Boolean)o).booleanValue() ? (byte)1 : (byte)0;
+            }
+        };
+        standardConverterMap.put(new Pair<>(Byte.class, Boolean.class), booleanToByte);
+        standardConverterMap.put(new Pair<>(Byte.TYPE, Boolean.class), booleanToByte);
+
+        /* boolean to short */
+        Converter<Short> booleanToShort = new Converter<Short>()
+        {
+            @Override
+            public Short convert(Object o)
+            {
+                return o == null ? null : ((Boolean)o).booleanValue() ? (short)1 : (short)0;
+            }
+        };
+        standardConverterMap.put(new Pair<>(Short.class, Boolean.class), booleanToShort);
+        standardConverterMap.put(new Pair<>(Short.TYPE, Boolean.class), booleanToShort);
+        
+        /* boolean to integer */
+        Converter<Integer> booleanToInteger = new Converter<Integer>()
+        {
+            @Override
+            public Integer convert(Object o)
+            {
+                return o == null ? null : ((Boolean)o).booleanValue() ? (Integer)1 : (Integer)0;
+            }
+        };
+        standardConverterMap.put(new Pair<>(Integer.class, Boolean.class), booleanToInteger);
+        standardConverterMap.put(new Pair<>(Integer.TYPE, Boolean.class), booleanToInteger);
+        
+        /* boolean to lonf */
+        Converter<Long> booleanToLong = new Converter<Long>()
+        {
+            @Override
+            public Long convert(Object o)
+            {
+                return o == null ? null : ((Boolean)o).booleanValue() ? 1L : 0L;
+            }
+        };
+        standardConverterMap.put(new Pair<>(Long.class, Boolean.class), booleanToLong);
+        standardConverterMap.put(new Pair<>(Long.TYPE, Boolean.class), booleanToLong);
+        
+        /* to string */
+        toString = new Converter<String>()
+        {
+            @Override
+            public String convert(Object o)
+            {
+                return String.valueOf(o);
+            }
+        };
+        standardConverterMap.put(new Pair<>(String.class, Boolean.class), toString);
+        standardConverterMap.put(new Pair<>(String.class, Byte.class), toString);
+        standardConverterMap.put(new Pair<>(String.class, Short.class), toString);
+        standardConverterMap.put(new Pair<>(String.class, Integer.class), toString);
+        standardConverterMap.put(new Pair<>(String.class, Long.class), toString);
+        standardConverterMap.put(new Pair<>(String.class, Float.class), toString);
+        standardConverterMap.put(new Pair<>(String.class, Double.class), toString);
+        standardConverterMap.put(new Pair<>(String.class, Character.class), toString);
+        standardConverterMap.put(new Pair<>(String.class, Object.class), toString);
+    }
+
+    /**
+     * Constructor
+     */
+    public ConversionHandlerImpl()
+    {
+        converterCacheMap = new ConcurrentHashMap<Pair<? extends Class, ? extends Class>, Converter>();
+    }
+
+    /**
+     * Check to see if the conversion can be done using an explicit conversion
+     * @param actual found argument type
+     * @param formal expected formal type
+     * @return null if no conversion is needed, or the appropriate Converter object
+     * @since 2.0
+     */
+    @Override
+    public boolean isExplicitlyConvertible(Class formal, Class actual, boolean possibleVarArg)
+    {
+        if (getNeededConverter(formal, actual) != null)
+        {
+            return true;
+        }
+
+        /* Check var arg */
+        if (possibleVarArg && formal.isArray())
+        {
+            if (actual.isArray())
+            {
+                actual = actual.getComponentType();
+            }
+            return isExplicitlyConvertible(formal.getComponentType(), actual, false);
+        }
+        return false;
+    }
+
+
+    /**
+     * Returns the appropriate Converter object needed for an explicit conversion
+     * Returns null if no conversion is needed.
+     *
+     * @param actual found argument type
+     * @param formal expected formal type
+     * @return null if no conversion is needed, or the appropriate Converter object
+     * @since 2.0
+     */
+    @Override
+    public Converter getNeededConverter(final Class formal, final Class actual)
+    {
+        Pair<Class, Class> key = new Pair<>(formal, actual);
+
+        /* first check for a standard conversion */
+        Converter converter = standardConverterMap.get(key);
+        if (converter == null)
+        {
+            /* then the converters cache map */
+            converter = converterCacheMap.get(key);
+            if (converter == null)
+            {
+                /* check for conversion towards string */
+                if (formal == String.class)
+                {
+                    converter = toString;
+                }
+                /* check for String -> Enum constant conversion */
+                else if (formal.isEnum() && actual == String.class)
+                {
+                    converter = new Converter()
+                    {
+                        @Override
+                        public Object convert(Object o)
+                        {
+                            return Enum.valueOf((Class<Enum>) formal, (String) o);
+                        }
+                    };
+                }
+
+                converterCacheMap.put(key, converter == null ? cacheMiss : converter);
+            }
+        }
+        return converter == cacheMiss ? null : converter;
+    }
+
+    /**
+     * Add the given converter to the handler.
+     *
+     * @param formal expected formal type
+     * @param actual provided argument type
+     * @param converter converter
+     * @since 2.0
+     */
+    @Override
+    public void addConverter(Class formal, Class actual, Converter converter)
+    {
+        Pair<Class, Class> key = new Pair<>(formal, actual);
+        converterCacheMap.put(key, converter);
+        if (formal.isPrimitive())
+        {
+            key = new Pair<>(IntrospectionUtils.getBoxedClass(formal), actual);
+            converterCacheMap.put(key, converter);
+        }
+        else
+        {
+            Class unboxedFormal = IntrospectionUtils.getUnboxedClass(formal);
+            if (unboxedFormal != formal)
+            {
+                key = new Pair<>(unboxedFormal, actual);
+                converterCacheMap.put(key, converter);
+            }
+        }
+    }
+}

Modified: velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/Converter.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/Converter.java?rev=1754151&r1=1754150&r2=1754151&view=diff
==============================================================================
--- velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/Converter.java (original)
+++ velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/Converter.java Tue Jul 26 16:11:28 2016
@@ -19,17 +19,20 @@ package org.apache.velocity.util.introsp
  * under the License.    
  */
 
-import org.apache.velocity.runtime.parser.node.Node;
-import org.apache.velocity.util.StringUtils;
-
 /**
  * Converts a value to type T
  *
+ * @since 2.0
  * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
  * @version $Id$
  */
 
 public interface Converter<T>
 {
+    /**
+     * convert object to type T
+     * @param o input object
+     * @result converted object
+     */
     T convert(Object o);
 }

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=1754151&r1=1754150&r2=1754151&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 Jul 26 16:11:28 2016
@@ -19,6 +19,10 @@ package org.apache.velocity.util.introsp
  * under the License.    
  */
 
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
 /**
  *
  * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
@@ -27,11 +31,62 @@ package org.apache.velocity.util.introsp
  * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
  * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
  * @author Nathan Bubna
+ * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
  * @version $Id: IntrospectionUtils.java 476785 2006-11-19 10:06:21Z henning $
  * @since 1.6
  */
 public class IntrospectionUtils
 {
+    /**
+     * boxing helper maps for standard types
+     */
+    static Map<Class, Class> boxingMap, unboxingMap;
+
+    static
+    {
+        boxingMap = new HashMap<Class, Class>();
+        boxingMap.put(Boolean.TYPE, Boolean.class);
+        boxingMap.put(Character.TYPE, Character.class);
+        boxingMap.put(Byte.TYPE, Byte.class);
+        boxingMap.put(Short.TYPE, Short.class);
+        boxingMap.put(Integer.TYPE, Integer.class);
+        boxingMap.put(Long.TYPE, Long.class);
+        boxingMap.put(Float.TYPE, Float.class);
+        boxingMap.put(Double.TYPE, Double.class);
+
+        unboxingMap = new HashMap<Class, Class>();
+        for (Map.Entry<Class,Class> entry : (Set<Map.Entry<Class,Class>>)boxingMap.entrySet())
+        {
+            unboxingMap.put(entry.getValue(), entry.getKey());
+        }
+    }
+
+    /**
+     * returns boxed type (or input type if not a primitive type)
+     * @param clazz input class
+     * @return boxed class
+     */
+    static Class getBoxedClass(Class clazz)
+    {
+        Class boxed = boxingMap.get(clazz);
+        return boxed == null ? clazz : boxed;
+    }
+
+    /**
+     * returns unboxed type (or input type if not successful)
+     * @param clazz input class
+     * @return unboxed class
+     */
+    static Class getUnboxedClass(Class clazz)
+    {
+        Class unboxed = unboxingMap.get(clazz);
+        return unboxed == null ? clazz : unboxed;
+    }
+
+    /**
+     *
+     */
+
 
     /**
      * Determines whether a type represented by a class object is
@@ -58,48 +113,74 @@ public class IntrospectionUtils
                                                         boolean possibleVarArg)
     {
         /* if it's a null, it means the arg was null */
-        if (actual == null && !formal.isPrimitive())
+        if (actual == null)
         {
-            return true;
+            return !formal.isPrimitive();
         }
 
         /* Check for identity or widening reference conversion */
-        if (actual != null && formal.isAssignableFrom(actual))
+        if (formal.isAssignableFrom(actual))
         {
             return true;
         }
 
-        /* Check for boxing with widening primitive conversion. Note that
-         * actual parameters are never primitives. */
+        /* 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) return true;
+        }
+
         if (formal.isPrimitive())
         {
-            if(formal == Boolean.TYPE && actual == Boolean.class)
-                return true;
-            if(formal == Character.TYPE && actual == Character.class)
-                return true;
-            if(formal == Byte.TYPE && actual == Byte.class)
-                return true;
-            if(formal == Short.TYPE &&
-               (actual == Short.class || actual == Byte.class))
-                return true;
-            if(formal == Integer.TYPE &&
-               (actual == Integer.class || actual == Short.class ||
-                actual == Byte.class))
-                return true;
-            if(formal == Long.TYPE &&
-               (actual == Long.class || actual == Integer.class ||
-                actual == Short.class || actual == Byte.class))
-                return true;
-            if(formal == Float.TYPE &&
-               (actual == Float.class || actual == Long.class ||
-                actual == Integer.class || actual == Short.class ||
-                actual == Byte.class))
-                return true;
-            if(formal == Double.TYPE &&
-               (actual == Double.class || actual == Float.class ||
-                actual == Long.class || actual == Integer.class ||
-                actual == Short.class || actual == Byte.class))
-                return true;
+            if (actual.isPrimitive())
+            {
+                /* check for widening primitive conversion */
+                if (formal == Short.TYPE && actual == Byte.TYPE)
+                    return true;
+                if (formal == Integer.TYPE && (
+                        actual == Byte.TYPE || actual == Short.TYPE))
+                    return true;
+                if (formal == Long.TYPE && (
+                        actual == Byte.TYPE || actual == Short.TYPE || actual == Integer.TYPE))
+                    return true;
+                if (formal == Float.TYPE && (
+                        actual == Byte.TYPE || actual == Short.TYPE || actual == Integer.TYPE ||
+                                actual == Long.TYPE))
+                    return true;
+                if (formal == 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 == Byte.class))
+                    return true;
+                if (formal == Long.TYPE && (actual == Long.class || actual == Integer.class ||
+                        actual == Short.class || actual == Byte.class))
+                    return true;
+                if (formal == Float.TYPE && (actual == Float.class || actual == Long.class ||
+                        actual == Integer.class || actual == Short.class || actual == Byte.class))
+                    return true;
+                if (formal == Double.TYPE && (actual == Double.class || actual == Float.class ||
+                        actual == Long.class || actual == Integer.class || actual == Short.class ||
+                        actual == Byte.class))
+                    return true;
+            }
         }
 
         /* Check for vararg conversion. */
@@ -112,13 +193,6 @@ public class IntrospectionUtils
             return isMethodInvocationConvertible(formal.getComponentType(),
                                                  actual, false);
         }
-
-        /* Check for manual conversions */
-        if (isExplicitlyConvertible(formal, actual))
-        {
-            return true;
-        }
-
         return false;
     }
 
@@ -189,45 +263,4 @@ public class IntrospectionUtils
         }
         return false;
     }
-
-    /**
-     * Check to see if the conversion can be done using an explicit conversion
-     */
-    public static boolean isExplicitlyConvertible(Class formal, Class actual)
-    {
-        /* Check for String to Enum constant conversion */
-        if (formal.isEnum() && actual == String.class)
-        {
-            return true;
-        }
-        return false;
-    }
-
-
-    /**
-     * Returns the appropriate Converter object needed for an explicit conversion
-     * Returns null if no conversion is needed.
-     *
-     * @param actual found argument type
-     * @param formal expected formal type
-     * @return null if no conversion is needed, or the appropriate Converter object
-     * @since 2.0
-     */
-    public static Converter getNeededConverter(final Class formal, final Class actual)
-    {
-        /* the only current use case is the String -> Enum constant conversion. */
-        if (formal.isEnum() && actual == String.class)
-        {
-            Converter converter = new Converter()
-            {
-                @Override
-                public Object convert(Object o)
-                {
-                    return Enum.valueOf((Class<Enum>)formal, (String)o);
-                }
-            };
-            return converter;
-        }
-        return null;
-    }
 }

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=1754151&r1=1754150&r2=1754151&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 Jul 26 16:11:28 2016
@@ -60,7 +60,17 @@ public class Introspector extends Intros
      */
     public Introspector(final Logger log)
     {
-        super(log);
+        this(log, null);
+    }
+
+    /**
+     * @param log A Logger object to use for the introspector.
+     * @param conversionHandler conversion handler
+     * @since 2.0
+     */
+    public Introspector(final Logger log, ConversionHandler 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=1754151&r1=1754150&r2=1754151&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 Jul 26 16:11:28 2016
@@ -60,14 +60,18 @@ 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)
+    protected IntrospectorBase(final Logger log, final ConversionHandler conversionHandler)
     {
         this.log = log;
-        introspectorCache = new IntrospectorCacheImpl(log); // TODO: Load that from properties.
+        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=1754151&r1=1754150&r2=1754151&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 Jul 26 16:11:28 2016
@@ -19,30 +19,113 @@ package org.apache.velocity.util.introsp
  * under the License.    
  */
 
+import org.apache.commons.lang3.Conversion;
+import org.slf4j.Logger;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
 /**
- * The introspector cache API definition.
+ * This is the internal introspector cache implementation.
  *
  * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
  * @author <a href="mailto:cdauth@cdauth.eu">Candid Dauth</a>
  * @version $Id$
  * @since 1.5
  */
-public interface IntrospectorCache {
+public final class IntrospectorCache
+{
+    /**
+     * define a public string so that it can be looked for if interested
+     */
+    public final static String CACHEDUMP_MSG =
+            "IntrospectorCache detected classloader change. Dumping cache.";
+
+    /** Class logger */
+    private final Logger log;
+
+    /**
+     * Holds the method maps for the classes we know about. Map: Class --&gt; ClassMap object.
+     */
+    private final Map classMapCache = new HashMap();
+
+    /**
+     * Holds the field maps for the classes we know about. Map: Class --&gt; ClassFieldMap object.
+     */
+    private final Map classFieldMapCache = new HashMap();
+
+    /**
+     * Keep the names of the classes in another map. This is needed for a multi-classloader environment where it is possible
+     * to have Class 'Foo' loaded by a classloader and then get asked to introspect on 'Foo' from another class loader. While these
+     * two Class objects have the same name, a <code>classMethodMaps.get(Foo.class)</code> will return null. For that case, we
+     * keep a set of class names to recognize this case.  
+     */
+    private final Set classNameCache = new HashSet();
+
+    /**
+     * Conversion handler
+     */
+    private final ConversionHandler conversionHandler;
+
+    /**
+     * C'tor
+     */
+    public IntrospectorCache(final Logger log, final ConversionHandler conversionHandler)
+    {
+        this.log = log;
+        this.conversionHandler = conversionHandler;
+    }
 
     /**
      * Clears the internal cache.
      */
-    void clear();
+    public void clear()
+    {
+        synchronized (classMapCache)
+        {
+            classMapCache.clear();
+            classFieldMapCache.clear();
+            classNameCache.clear();
+            log.debug(CACHEDUMP_MSG);
+        }
+    }
 
     /**
-     * Lookup a given Class object in the cache. If it does not exist, 
+     * Lookup a given Class object in the cache. If it does not exist,
      * check whether this is due to a class change and purge the caches
      * eventually.
      *
      * @param c The class to look up.
      * @return A ClassMap object or null if it does not exist in the cache.
      */
-    ClassMap get(Class c);
+    public ClassMap get(final Class c)
+    {
+        if (c == null)
+        {
+            throw new IllegalArgumentException("class is null!");
+        }
+
+        ClassMap classMap = (ClassMap)classMapCache.get(c);
+        if (classMap == null)
+        {
+            /*
+             * check to see if we have it by name.
+             * if so, then we have an object with the same
+             * name but loaded through a different class loader.
+             * In that case, we will just dump the cache to be sure.
+             */
+            synchronized (classMapCache)
+            {
+                if (classNameCache.contains(c.getName()))
+                {
+                    clear();
+                }
+            }
+        }
+        return classMap;
+    }
 
     /**
      * Lookup a given Class object in the cache. If it does not exist,
@@ -52,7 +135,32 @@ public interface IntrospectorCache {
      * @param c The class to look up.
      * @return A ClassFieldMap object or null if it does not exist in the cache.
      */
-    ClassFieldMap getFieldMap(final Class c);
+    public ClassFieldMap getFieldMap(final Class c)
+    {
+        if (c == null)
+        {
+            throw new IllegalArgumentException("class is null!");
+        }
+
+        ClassFieldMap classFieldMap = (ClassFieldMap)classFieldMapCache.get(c);
+        if (classFieldMap == null)
+        {
+            /*
+             * check to see if we have it by name.
+             * if so, then we have an object with the same
+             * name but loaded through a different class loader.
+             * In that case, we will just dump the cache to be sure.
+             */
+            synchronized (classMapCache)
+            {
+                if (classNameCache.contains(c.getName()))
+                {
+                    clear();
+                }
+            }
+        }
+        return classFieldMap;
+    }
 
     /**
      * Creates a class map for specific class and registers it in the
@@ -62,6 +170,17 @@ public interface IntrospectorCache {
      * @param c The class for which the class map gets generated.
      * @return A ClassMap object.
      */
-    ClassMap put(Class c);
+    public ClassMap put(final Class c)
+    {
+        final ClassMap classMap = new ClassMap(c, log, conversionHandler);
+        final ClassFieldMap classFieldMap = new ClassFieldMap(c, log);
+        synchronized (classMapCache)
+        {
+            classMapCache.put(c, classMap);
+            classFieldMapCache.put(c, classFieldMap);
+            classNameCache.add(c.getName());
+        }
+        return classMap;
+    }
 
 }

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=1754151&r1=1754150&r2=1754151&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 Jul 26 16:11:28 2016
@@ -19,6 +19,8 @@ package org.apache.velocity.util.introsp
  * under the License.    
  */
 
+import org.apache.commons.lang3.Conversion;
+
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -33,6 +35,7 @@ import java.util.concurrent.ConcurrentHa
  * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph Reck</a>
  * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
  * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
+ * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
  * @version $Id$
  */
 public class MethodMap
@@ -41,6 +44,26 @@ public class MethodMap
     private static final int LESS_SPECIFIC = 1;
     private static final int INCOMPARABLE = 2;
 
+    ConversionHandler conversionHandler;
+
+    /**
+     * Default constructor
+     */
+    public MethodMap()
+    {
+        this(null);
+    }
+
+    /**
+     * Constructor with provided conversion handler
+     * @param conversionHandler conversion handler
+     * @since 2.0
+     */
+    public MethodMap(ConversionHandler conversionHandler)
+    {
+        this.conversionHandler = conversionHandler;
+    }
+
     /**
      * Keep track of all methods with the same name.
      */
@@ -134,11 +157,17 @@ public class MethodMap
         return getBestMatch(methodList, classes);
     }
 
-    private static Method getBestMatch(List methods, Class[] args)
+    private Method getBestMatch(List methods, Class[] args)
     {
         List equivalentMatches = null;
         Method bestMatch = null;
         Class[] bestMatchTypes = null;
+        int bestMatchComp = INCOMPARABLE; /* how does the best match compare to provided type */
+        Class[] unboxedArgs = new Class[args.length];
+        for (int i = 0; i < args.length; ++i)
+        {
+            unboxedArgs[i] = IntrospectionUtils.getUnboxedClass(args[i]);
+        }
         for (Iterator i = methods.iterator(); i.hasNext(); )
         {
             Method method = (Method)i.next();
@@ -148,6 +177,7 @@ public class MethodMap
                 {
                     bestMatch = method;
                     bestMatchTypes = method.getParameterTypes();
+                    bestMatchComp = compare(bestMatchTypes, unboxedArgs);
                 }
                 else
                 {
@@ -155,35 +185,60 @@ public class MethodMap
                     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
                             {
-                                // have to beat all other ambiguous ones...
+                                /* have to beat all other ambiguous ones... */
                                 int ambiguities = equivalentMatches.size();
-                                for (int a=0; a < ambiguities; a++)
+                                for (int a = 0; a < ambiguities; a++)
                                 {
                                     Method other = (Method)equivalentMatches.get(a);
                                     switch (compare(methodTypes, other.getParameterTypes()))
                                     {
                                         case MORE_SPECIFIC:
-                                            // ...and thus replace them all...
+                                        /* ...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...
+                                            /* ...join them...*/
                                             equivalentMatches.add(method);
                                             break;
 
                                         case LESS_SPECIFIC:
-                                            // ...or just go away.
+                                            /* 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;
+                                            }
                                             break;
                                     }
                                 }
@@ -199,7 +254,16 @@ public class MethodMap
                             break;
 
                         case LESS_SPECIFIC:
-                            // do nothing
+                            /* 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;
+                            }
                             break;
                     }
                 }
@@ -235,7 +299,7 @@ public class MethodMap
      * @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 static int compare(Class[] c1, Class[] c2)
+    private int compare(Class[] c1, Class[] c2)
     {
         boolean c1MoreSpecific = false;
         boolean c2MoreSpecific = false;
@@ -255,7 +319,7 @@ public class MethodMap
         // ok, move on and compare those of equal lengths
         for(int i = 0; i < c1.length; ++i)
         {
-            if(c1[i] != c2[i])
+            if(c1[i] != c2[i] && c1[i] != null && c2[i] != null)
             {
                 boolean last = (i == c1.length - 1);
                 c1MoreSpecific =
@@ -269,6 +333,20 @@ public class MethodMap
             }
         }
 
+        /* check for conversions */
+        if (!c1MoreSpecific && !c2MoreSpecific)
+        {
+            for(int i = 0; i < c1.length; ++i)
+            {
+                boolean last = (i == c1.length - 1);
+                if (c1[i] != c2[i] && c1[i] != null && c2[i] != null)
+                {
+                    c1MoreSpecific = c1MoreSpecific || isConvertible(c2[i], c1[i], last);
+                    c2MoreSpecific = c2MoreSpecific || isConvertible(c1[i], c2[i], last);
+                }
+            }
+        }
+
         if(c1MoreSpecific)
         {
             if(c2MoreSpecific)
@@ -319,7 +397,7 @@ public class MethodMap
      * @param classes arguments to method
      * @return true if method is applicable to arguments
      */
-    private static boolean isApplicable(Method method, Class[] classes)
+    private boolean isApplicable(Method method, Class[] classes)
     {
         Class[] methodArgs = method.getParameterTypes();
 
@@ -333,7 +411,8 @@ 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))
+                    if (!isConvertible(methodArgs[i], classes[i], false) &&
+                            !isExplicitlyConvertible(methodArgs[i], classes[i], false))
                     {
                         return false;
                     }
@@ -352,14 +431,16 @@ 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))
+                if(!isConvertible(methodArgs[i], classes[i], false) &&
+                        !isExplicitlyConvertible(methodArgs[i], classes[i], false))
                 {
                     // if we're on the last arg and the method expects an array
                     if (i == classes.length - 1 && methodArgs[i].isArray())
                     {
                         // check to see if the last arg is convertible
                         // to the array's component type
-                        return isConvertible(methodArgs[i], classes[i], true);
+                        return isConvertible(methodArgs[i], classes[i], true) ||
+                                isExplicitlyConvertible(methodArgs[i], classes[i], true);
                     }
                     return false;
                 }
@@ -397,17 +478,45 @@ public class MethodMap
         return true;
     }
 
-    private static boolean isConvertible(Class formal, Class actual,
-                                         boolean possibleVarArg)
+    /**
+     * Returns true if <code>actual</code> is convertible to <code>formal</code> by implicit Java method call conversions
+     *
+     * @param formal
+     * @param actual
+     * @param possibleVarArg
+     * @return convertible
+     */
+    private boolean isConvertible(Class formal, Class actual, boolean possibleVarArg)
     {
         return IntrospectionUtils.
             isMethodInvocationConvertible(formal, actual, possibleVarArg);
     }
 
-    private static boolean isStrictConvertible(Class formal, Class actual,
-                                               boolean possibleVarArg)
+    /**
+     * Returns true if <code>actual</code> is strictly convertible to <code>formal</code> (aka without implicit
+     * boxing/unboxing)
+     *
+     * @param formal
+     * @param actual
+     * @param possibleVarArg
+     * @return convertible
+     */
+    private static boolean isStrictConvertible(Class formal, Class actual, boolean possibleVarArg)
     {
         return IntrospectionUtils.
             isStrictMethodInvocationConvertible(formal, actual, possibleVarArg);
     }
+
+    /**
+     * Returns true if <code>actual</code> is convertible to <code>formal</code> using an explicit converter
+     *
+     * @param formal
+     * @param actual
+     * @param possibleVarArg
+     * @return
+     */
+    private boolean isExplicitlyConvertible(Class formal, Class actual, boolean possibleVarArg)
+    {
+        return conversionHandler != null && conversionHandler.isExplicitlyConvertible(formal, actual, possibleVarArg);
+    }
 }

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=1754151&r1=1754150&r2=1754151&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 Jul 26 16:11:28 2016
@@ -20,6 +20,8 @@ package org.apache.velocity.util.introsp
  */
 
 import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.runtime.RuntimeServices;
 import org.apache.velocity.runtime.parser.node.AbstractExecutor;
 import org.apache.velocity.runtime.parser.node.BooleanPropertyExecutor;
 import org.apache.velocity.runtime.parser.node.GetExecutor;
@@ -29,9 +31,12 @@ import org.apache.velocity.runtime.parse
 import org.apache.velocity.runtime.parser.node.PutExecutor;
 import org.apache.velocity.runtime.parser.node.SetExecutor;
 import org.apache.velocity.runtime.parser.node.SetPropertyExecutor;
+import org.apache.velocity.runtime.resource.ResourceManager;
 import org.apache.velocity.util.ArrayIterator;
 import org.apache.velocity.util.ArrayListWrapper;
+import org.apache.velocity.util.ClassUtils;
 import org.apache.velocity.util.EnumerationIterator;
+import org.apache.velocity.util.RuntimeServicesAware;
 import org.slf4j.Logger;
 
 import java.lang.reflect.Array;
@@ -49,7 +54,7 @@ import java.util.Map;
  * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
  * @version $Id$
  */
-public class UberspectImpl implements Uberspect, UberspectLoggable
+public class UberspectImpl implements Uberspect, UberspectLoggable, RuntimeServicesAware
 {
     /**
      *  Our runtime logger.
@@ -62,13 +67,72 @@ public class UberspectImpl implements Ub
     protected Introspector introspector;
 
     /**
+     * the conversion handler
+     */
+    protected ConversionHandler conversionHandler;
+
+    /**
      *  init - generates the Introspector. As the setup code
      *  makes sure that the log gets set before this is called,
      *  we can initialize the Introspector using the log object.
      */
     public void init()
     {
-        introspector = new Introspector(log);
+        introspector = new Introspector(log, conversionHandler);
+    }
+
+    public ConversionHandler getConversionHandler()
+    {
+        return conversionHandler;
+    }
+
+    /**
+     * sets the runtime services
+     * @param rs runtime services
+     */
+    public void setRuntimeServices(RuntimeServices rs)
+    {
+        String conversionHandlerClass = rs.getString(RuntimeConstants.CONVERSION_HANDLER_CLASS);
+        if (conversionHandlerClass == null || conversionHandlerClass.equals("none"))
+        {
+            conversionHandler = null;
+        }
+        else
+        {
+            Object o = null;
+
+            try
+            {
+                o = 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);
+            }
+            catch (IllegalAccessException ae)
+            {
+                throw new VelocityException("Cannot access class '" + conversionHandlerClass + "'", ae);
+            }
+
+            if (!(o instanceof ConversionHandler))
+            {
+                String err = "The specified class for ResourceManager (" + conversionHandlerClass
+                        + ") does not implement " + ConversionHandler.class.getName()
+                        + "; Velocity is not initialized correctly.";
+
+                log.error(err);
+                throw new VelocityException(err);
+            }
+
+            conversionHandler = (ConversionHandler) o;
+        }
     }
 
     /**
@@ -223,14 +287,15 @@ public class UberspectImpl implements Ub
      */
     private Converter[] getNeededConverters(Class[] expected, Object[] provided)
     {
-        // var args are not handled here
+        if (conversionHandler == null) return null;
+        // var args are not handled here - CB TODO
         int n = Math.min(expected.length, provided.length);
         Converter[] converters = null;
         for (int i = 0; i < n; ++i)
         {
             Object arg = provided[i];
             if (arg == null) continue;
-            Converter converter = IntrospectionUtils.getNeededConverter(expected[i], arg.getClass());
+            Converter converter = conversionHandler.getNeededConverter(expected[i], arg.getClass());
             if (converter != null)
             {
                 if (converters == null)
@@ -289,7 +354,7 @@ public class UberspectImpl implements Ub
         if (!executor.isAlive())
         {
             executor = new BooleanPropertyExecutor(log, introspector, claz,
-                                                   identifier);
+                    identifier);
         }
 
         /*
@@ -350,7 +415,7 @@ public class UberspectImpl implements Ub
     /**
      *  Implementation of VelMethod
      */
-    public static class VelMethodImpl implements VelMethod
+    public class VelMethodImpl implements VelMethod
     {
         final Method method;
         Boolean isVarArg;
@@ -415,7 +480,10 @@ public class UberspectImpl implements Ub
             {
                 for (int i = 0; i < actual.length; ++i)
                 {
-                    actual[i] = converters[i].convert(actual[i]);
+                    if (converters[i] != null)
+                    {
+                        actual[i] = converters[i].convert(actual[i]);
+                    }
                 }
             }
 
@@ -488,10 +556,7 @@ public class UberspectImpl implements Ub
             {
                 // make sure the last arg is an array of the expected type
                 Class argClass = actual[index].getClass();
-                if (!argClass.isArray() &&
-                    IntrospectionUtils.isMethodInvocationConvertible(type,
-                                                                     argClass,
-                                                                     false))
+                if (!argClass.isArray() && IntrospectionUtils.isMethodInvocationConvertible(type, argClass, false))
                 {
                     // create a 1-length array to hold and replace the last param
                     Object lastActual = Array.newInstance(type, 1);
@@ -541,6 +606,14 @@ public class UberspectImpl implements Ub
         }
 
         /**
+         * @see org.apache.velocity.util.introspection.VelMethod#getMethod()
+         */
+        public Method getMethod()
+        {
+            return method;
+        }
+
+        /**
          * @see org.apache.velocity.util.introspection.VelMethod#getReturnType()
          */
         public Class getReturnType()

Modified: velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/VelMethod.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/VelMethod.java?rev=1754151&r1=1754150&r2=1754151&view=diff
==============================================================================
--- velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/VelMethod.java (original)
+++ velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/VelMethod.java Tue Jul 26 16:11:28 2016
@@ -1,7 +1,5 @@
 package org.apache.velocity.util.introspection;
 
-import java.lang.reflect.InvocationTargetException;
-
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
@@ -21,6 +19,9 @@ import java.lang.reflect.InvocationTarge
  * under the License.    
  */
 
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
 /**
  *  Method used for regular method invocation
  *
@@ -58,6 +59,13 @@ public interface VelMethod
     public String getMethodName();
 
     /**
+     * returns the underlying Method
+     * @return the method
+     * @since 2.0
+     */
+    public Method getMethod();
+
+    /**
      *  returns the return type of the method invoked
      * @return The return type of the method invoked
      */

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=1754151&r1=1754150&r2=1754151&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 Jul 26 16:11:28 2016
@@ -186,6 +186,14 @@ parser.pool.size = 20
 
 runtime.introspector.uberspect = org.apache.velocity.util.introspection.UberspectImpl
 
+# ----------------------------------------------------------------------------
+# CONVERSION HANDLER
+# ----------------------------------------------------------------------------
+# Sets the data types Conversion Handler used by the default uberspector
+# ----------------------------------------------------------------------------
+
+runtime.conversion.handler.class = org.apache.velocity.util.introspection.ConversionHandlerImpl
+
 
 # ----------------------------------------------------------------------------
 # SECURE INTROSPECTOR