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.



git-svn-id: https://svn.apache.org/repos/asf/velocity/engine/trunk@1754151 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index c3e1c21..0c1e6ff 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -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>
       
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/app/VelocityEngine.java b/velocity-engine-core/src/main/java/org/apache/velocity/app/VelocityEngine.java
index 81b25a9..1c8ed28 100644
--- a/velocity-engine-core/src/main/java/org/apache/velocity/app/VelocityEngine.java
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/app/VelocityEngine.java
@@ -161,7 +161,7 @@
      * 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)
     {
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeConstants.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeConstants.java
index 510c346..6e0b8b7 100644
--- a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeConstants.java
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeConstants.java
@@ -240,6 +240,9 @@
     /** 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";
 
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeInstance.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeInstance.java
index 72dc646..767b653 100644
--- a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeInstance.java
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeInstance.java
@@ -49,7 +49,6 @@
 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 @@
     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 @@
         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 @@
     }
 
     /**
-     *  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
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeServices.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeServices.java
index e39b16c..d3db014 100644
--- a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeServices.java
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeServices.java
@@ -445,15 +445,6 @@
      */
     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.
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeSingleton.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeSingleton.java
index a9bd538..1e70f5e 100644
--- a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeSingleton.java
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeSingleton.java
@@ -524,17 +524,6 @@
     }
 
     /**
-     *  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()
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIdentifier.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIdentifier.java
index d37a9e7..4295f7e 100644
--- a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIdentifier.java
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIdentifier.java
@@ -148,7 +148,7 @@
                  *  uberspector
                  */
 
-                vg = rsvc.getUberspect().getPropertyGet(o,identifier, uberInfo);
+                vg = rsvc.getUberspect().getPropertyGet(o, identifier, uberInfo);
 
                 if (vg != null && vg.isCacheable() && (o != null))
                 {
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/GetExecutor.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/GetExecutor.java
index 5569274..0e93224 100644
--- a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/GetExecutor.java
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/GetExecutor.java
@@ -78,6 +78,17 @@
         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
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/Pair.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/Pair.java
new file mode 100644
index 0000000..ec80ea4
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/Pair.java
@@ -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;
+    }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ClassMap.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ClassMap.java
index d977b6f..cc44745 100644
--- a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ClassMap.java
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ClassMap.java
@@ -38,6 +38,7 @@
  * @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 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 @@
             log.debug("== Class: {}", clazz);
         }
 
-        methodCache = createMethodCache();
+        methodCache = createMethodCache(conversionHandler);
 
         if (debugReflection && log.isDebugEnabled())
         {
@@ -108,9 +121,9 @@
      * 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 @@
         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);
         }
 
         /**
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ConversionHandler.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ConversionHandler.java
new file mode 100644
index 0000000..3ce2d22
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ConversionHandler.java
@@ -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);
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ConversionHandlerImpl.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ConversionHandlerImpl.java
new file mode 100644
index 0000000..1ab05b9
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ConversionHandlerImpl.java
@@ -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);
+            }
+        }
+    }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/Converter.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/Converter.java
index c4a241a..f84b4e6 100644
--- a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/Converter.java
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/Converter.java
@@ -19,17 +19,20 @@
  * 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);
 }
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectionUtils.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectionUtils.java
index faf7b4d..02d3298 100644
--- a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectionUtils.java
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectionUtils.java
@@ -19,6 +19,10 @@
  * 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 @@
  * @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 @@
                                                         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 @@
             return isMethodInvocationConvertible(formal.getComponentType(),
                                                  actual, false);
         }
-
-        /* Check for manual conversions */
-        if (isExplicitlyConvertible(formal, actual))
-        {
-            return true;
-        }
-
         return false;
     }
 
@@ -189,45 +263,4 @@
         }
         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;
-    }
 }
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/Introspector.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/Introspector.java
index fdfef55..4082a4b 100644
--- a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/Introspector.java
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/Introspector.java
@@ -60,7 +60,17 @@
      */
     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);
     }
 
     /**
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectorBase.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectorBase.java
index 5e18549..f2d8dee 100644
--- a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectorBase.java
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectorBase.java
@@ -60,14 +60,18 @@
 
     /** 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;
     }
     
     /**
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectorCache.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectorCache.java
index a3d80bc..3b9e4db 100644
--- a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectorCache.java
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectorCache.java
@@ -19,30 +19,113 @@
  * 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 @@
      * @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 @@
      * @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;
+    }
 
 }
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectorCacheImpl.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectorCacheImpl.java
deleted file mode 100644
index 142d3b7..0000000
--- a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectorCacheImpl.java
+++ /dev/null
@@ -1,179 +0,0 @@
-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.slf4j.Logger;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * 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 final class IntrospectorCacheImpl implements 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();
-
-    /**
-     * C'tor
-     */
-    public IntrospectorCacheImpl(final Logger log)
-    {
-	    this.log = log;
-    }
-
-    /**
-     * Clears the internal cache.
-     */
-    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,
-     * 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.
-     */
-    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,
-     * check whether this is due to a class change and purge the caches
-     * eventually.
-     *
-     * @param c The class to look up.
-     * @return A ClassFieldMap object or null if it does not exist in the cache.
-     */
-    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
-     * cache.  Also adds the qualified name to the name-&gt;class map
-     * for later Classloader change detection.
-     *
-     * @param c The class for which the class map gets generated.
-     * @return A ClassMap object.
-     */
-    public ClassMap put(final Class c)
-    {
-        final ClassMap classMap = new ClassMap(c, log);
-        final ClassFieldMap classFieldMap = new ClassFieldMap(c, log);
-        synchronized (classMapCache)
-        {
-            classMapCache.put(c, classMap);
-            classFieldMapCache.put(c, classFieldMap);
-            classNameCache.add(c.getName());
-        }
-        return classMap;
-    }
-
-}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/MethodMap.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/MethodMap.java
index 0f5b498..5bdd5a4 100644
--- a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/MethodMap.java
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/MethodMap.java
@@ -19,6 +19,8 @@
  * 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 @@
  * @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 @@
     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 @@
         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 @@
                 {
                     bestMatch = method;
                     bestMatchTypes = method.getParameterTypes();
+                    bestMatchComp = compare(bestMatchTypes, unboxedArgs);
                 }
                 else
                 {
@@ -155,35 +185,60 @@
                     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 @@
                             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 @@
      * @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 @@
         // 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 @@
             }
         }
 
+        /* 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 @@
      * @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 @@
                 // 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 @@
             // (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 @@
         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);
+    }
 }
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/UberspectImpl.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/UberspectImpl.java
index 8fd919a..ab61547 100644
--- a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/UberspectImpl.java
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/UberspectImpl.java
@@ -20,6 +20,8 @@
  */
 
 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.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 @@
  * @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 @@
     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 @@
      */
     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 @@
         if (!executor.isAlive())
         {
             executor = new BooleanPropertyExecutor(log, introspector, claz,
-                                                   identifier);
+                    identifier);
         }
 
         /*
@@ -350,7 +415,7 @@
     /**
      *  Implementation of VelMethod
      */
-    public static class VelMethodImpl implements VelMethod
+    public class VelMethodImpl implements VelMethod
     {
         final Method method;
         Boolean isVarArg;
@@ -415,7 +480,10 @@
             {
                 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 @@
             {
                 // 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 @@
         }
 
         /**
+         * @see org.apache.velocity.util.introspection.VelMethod#getMethod()
+         */
+        public Method getMethod()
+        {
+            return method;
+        }
+
+        /**
          * @see org.apache.velocity.util.introspection.VelMethod#getReturnType()
          */
         public Class getReturnType()
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/VelMethod.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/VelMethod.java
index f72ccc9..9cf9eae 100644
--- a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/VelMethod.java
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/VelMethod.java
@@ -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 @@
  * under the License.    
  */
 
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
 /**
  *  Method used for regular method invocation
  *
@@ -58,6 +59,13 @@
     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
      */
diff --git a/velocity-engine-core/src/main/resources/org/apache/velocity/runtime/defaults/velocity.properties b/velocity-engine-core/src/main/resources/org/apache/velocity/runtime/defaults/velocity.properties
index ac6ba1b..956b605 100644
--- a/velocity-engine-core/src/main/resources/org/apache/velocity/runtime/defaults/velocity.properties
+++ b/velocity-engine-core/src/main/resources/org/apache/velocity/runtime/defaults/velocity.properties
@@ -186,6 +186,14 @@
 
 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
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/BaseTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/BaseTestCase.java
index b777c33..75c6660 100644
--- a/velocity-engine-core/src/test/java/org/apache/velocity/test/BaseTestCase.java
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/BaseTestCase.java
@@ -62,22 +62,26 @@
         }
     }
 
-    protected void setUp() throws Exception
+    protected VelocityEngine createEngine()
     {
-        engine = new VelocityEngine();
-
-        //by default, make the engine's log output go to the test-report
-        log = new TestLogger(false, false);
-        engine.setProperty(RuntimeConstants.RUNTIME_LOG_INSTANCE, log);
+        VelocityEngine ret = new VelocityEngine();
+        ret.setProperty(RuntimeConstants.RUNTIME_LOG_INSTANCE, log);
 
         // use string resource loader by default, instead of file
-        engine.setProperty(RuntimeConstants.RESOURCE_LOADER, "file,string");
-        engine.addProperty("string.resource.loader.class", StringResourceLoader.class.getName());
-        engine.addProperty("string.resource.loader.repository.name", stringRepoName);
-        engine.addProperty("string.resource.loader.repository.static", "false");
+        ret.setProperty(RuntimeConstants.RESOURCE_LOADER, "file,string");
+        ret.addProperty("string.resource.loader.class", StringResourceLoader.class.getName());
+        ret.addProperty("string.resource.loader.repository.name", stringRepoName);
+        ret.addProperty("string.resource.loader.repository.static", "false");
 
-        setUpEngine(engine);
+        setUpEngine(ret);
+        return ret;
+    }
 
+    protected void setUp() throws Exception
+    {
+        //by default, make the engine's log output go to the test-report
+        log = new TestLogger(false, false);
+        engine = createEngine();
         context = new VelocityContext();
         setUpContext(context);
     }
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/ClassloaderChangeTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/ClassloaderChangeTestCase.java
index da35484..d6f7b2d 100644
--- a/velocity-engine-core/src/test/java/org/apache/velocity/test/ClassloaderChangeTestCase.java
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/ClassloaderChangeTestCase.java
@@ -26,7 +26,7 @@
 import org.apache.velocity.VelocityContext;
 import org.apache.velocity.app.VelocityEngine;
 import org.apache.velocity.test.misc.TestLogger;
-import org.apache.velocity.util.introspection.IntrospectorCacheImpl;
+import org.apache.velocity.util.introspection.IntrospectorCache;
 
 import java.io.ByteArrayOutputStream;
 import java.io.InputStream;
@@ -126,7 +126,7 @@
             fail("Output from doIt() incorrect");
         }
 
-        if (!logger.getLog().contains(IntrospectorCacheImpl.CACHEDUMP_MSG))
+        if (!logger.getLog().contains(IntrospectorCache.CACHEDUMP_MSG))
         {
             fail("Didn't see introspector cache dump.");
         }
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/Introspector2TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/Introspector2TestCase.java
index 3d2a5f9..9afaebb 100644
--- a/velocity-engine-core/src/test/java/org/apache/velocity/test/Introspector2TestCase.java
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/Introspector2TestCase.java
@@ -24,6 +24,7 @@
 import org.apache.velocity.app.Velocity;
 import org.apache.velocity.runtime.RuntimeSingleton;
 import org.apache.velocity.test.misc.TestLogger;
+import org.apache.velocity.util.introspection.Introspector;
 
 import java.lang.reflect.Method;
 
@@ -70,7 +71,9 @@
 
         Object[] params = { new Foo(), new Foo() };
 
-        method = RuntimeSingleton.getIntrospector()
+        Introspector introspector = new Introspector(log);
+
+        method = introspector
             .getMethod( Tester.class, "find", params );
 
         if ( method == null)
@@ -87,7 +90,7 @@
          *  now test for failure due to ambiguity
          */
 
-        method = RuntimeSingleton.getIntrospector()
+        method = introspector
             .getMethod( Tester2.class, "find", params );
 
         if ( method != null)
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/Introspector3TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/Introspector3TestCase.java
index c48d952..d7a53f0 100644
--- a/velocity-engine-core/src/test/java/org/apache/velocity/test/Introspector3TestCase.java
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/Introspector3TestCase.java
@@ -22,6 +22,7 @@
 import junit.framework.Test;
 import junit.framework.TestSuite;
 import org.apache.velocity.runtime.RuntimeSingleton;
+import org.apache.velocity.util.introspection.Introspector;
 
 import java.lang.reflect.Method;
 import java.util.ArrayList;
@@ -66,19 +67,20 @@
         Object[] longInt = {  new Long(1), new Integer(2) };
         Object[] longLong = {  new Long(1), new Long(2) };
 
-        method = RuntimeSingleton.getIntrospector().getMethod(
+        Introspector introspector = new Introspector(log);
+        method = introspector.getMethod(
             MethodProvider.class, "lii", listIntInt);
         result = (String) method.invoke(mp, listIntInt);
 
         assertTrue(result.equals("lii"));
 
-        method = RuntimeSingleton.getIntrospector().getMethod(
+        method = introspector.getMethod(
             MethodProvider.class, "ii", intInt);
         result = (String) method.invoke(mp, intInt);
 
         assertTrue(result.equals("ii"));
 
-        method = RuntimeSingleton.getIntrospector().getMethod(
+        method = introspector.getMethod(
             MethodProvider.class, "ll", longInt);
         result = (String) method.invoke(mp, longInt);
 
@@ -88,13 +90,13 @@
          * test overloading with primitives
          */
 
-        method = RuntimeSingleton.getIntrospector().getMethod(
+        method = introspector.getMethod(
             MethodProvider.class, "ll", longLong);
         result = (String) method.invoke(mp, longLong);
 
         assertTrue(result.equals("ll"));
 
-        method = RuntimeSingleton.getIntrospector().getMethod(
+        method = introspector.getMethod(
             MethodProvider.class, "lll", listLongList);
         result = (String) method.invoke(mp, listLongList);
 
@@ -105,7 +107,7 @@
          */
 
         Object [] oa = {null, new Integer(0)};
-        method = RuntimeSingleton.getIntrospector().getMethod(
+        method = introspector.getMethod(
             MethodProvider.class, "lll", oa );
         result = (String) method.invoke(mp, oa);
 
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/IntrospectorTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/IntrospectorTestCase.java
index e2535ea..7476ef6 100644
--- a/velocity-engine-core/src/test/java/org/apache/velocity/test/IntrospectorTestCase.java
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/IntrospectorTestCase.java
@@ -22,6 +22,8 @@
 import junit.framework.Test;
 import junit.framework.TestSuite;
 import org.apache.velocity.runtime.RuntimeSingleton;
+import org.apache.velocity.test.misc.TestLogger;
+import org.apache.velocity.util.introspection.Introspector;
 
 import java.lang.reflect.Method;
 
@@ -41,9 +43,13 @@
 {
     private static MethodProvider mp;
 
+    private Introspector introspector;
+    
     public void setUp()
     {
         mp = new MethodProvider();
+        log = new TestLogger();
+        introspector = new Introspector(log);
     }
 
     /**
@@ -71,8 +77,8 @@
         // Test boolean primitive.
         Object[] booleanParams = { new Boolean(true) };
         String type = "boolean";
-        Method method = RuntimeSingleton.getIntrospector().getMethod(
-            MethodProvider.class, type + "Method", booleanParams);
+        Method method = introspector.getMethod(
+                MethodProvider.class, type + "Method", booleanParams);
         String result = (String) method.invoke(mp, booleanParams);
 
         assertEquals("Method could not be found", type, result);
@@ -84,8 +90,8 @@
         // Test byte primitive.
         Object[] byteParams = { new Byte("1") };
         String type = "byte";
-        Method method = RuntimeSingleton.getIntrospector().getMethod(
-            MethodProvider.class, type + "Method", byteParams);
+        Method method = introspector.getMethod(
+                MethodProvider.class, type + "Method", byteParams);
         String result = (String) method.invoke(mp, byteParams);
 
         assertEquals("Method could not be found", type, result);
@@ -97,8 +103,8 @@
         // Test char primitive.
         Object[] characterParams = { new Character('a') };
         String type = "character";
-        Method method = RuntimeSingleton.getIntrospector().getMethod(
-            MethodProvider.class, type + "Method", characterParams);
+        Method method = introspector.getMethod(
+                MethodProvider.class, type + "Method", characterParams);
         String result = (String) method.invoke(mp, characterParams);
 
         assertEquals("Method could not be found", type, result);
@@ -111,8 +117,8 @@
         // Test double primitive.
         Object[] doubleParams = { new Double((double)1) };
         String type = "double";
-        Method method = RuntimeSingleton.getIntrospector().getMethod(
-            MethodProvider.class, type + "Method", doubleParams);
+        Method method = introspector.getMethod(
+                MethodProvider.class, type + "Method", doubleParams);
         String result = (String) method.invoke(mp, doubleParams);
 
         assertEquals("Method could not be found", type, result);
@@ -125,8 +131,8 @@
         // Test float primitive.
         Object[] floatParams = { new Float((float)1) };
         String type = "float";
-        Method method = RuntimeSingleton.getIntrospector().getMethod(
-            MethodProvider.class, type + "Method", floatParams);
+        Method method = introspector.getMethod(
+                MethodProvider.class, type + "Method", floatParams);
         String result = (String) method.invoke(mp, floatParams);
 
         assertEquals("Method could not be found", type, result);
@@ -139,8 +145,8 @@
         // Test integer primitive.
         Object[] integerParams = { new Integer((int)1) };
         String type = "integer";
-        Method method = RuntimeSingleton.getIntrospector().getMethod(
-            MethodProvider.class, type + "Method", integerParams);
+        Method method = introspector.getMethod(
+                MethodProvider.class, type + "Method", integerParams);
         String result = (String) method.invoke(mp, integerParams);
 
         assertEquals("Method could not be found", type, result);
@@ -153,8 +159,8 @@
         // Test long primitive.
         Object[] longParams = { new Long((long)1) };
         String type = "long";
-        Method method = RuntimeSingleton.getIntrospector().getMethod(
-            MethodProvider.class, type + "Method", longParams);
+        Method method = introspector.getMethod(
+                MethodProvider.class, type + "Method", longParams);
         String result = (String) method.invoke(mp, longParams);
 
         assertEquals("Method could not be found", type, result);
@@ -166,8 +172,8 @@
         // Test short primitive.
         Object[] shortParams = { new Short((short)1) };
         String type = "short";
-        Method method = RuntimeSingleton.getIntrospector().getMethod(
-            MethodProvider.class, type + "Method", shortParams);
+        Method method = introspector.getMethod(
+                MethodProvider.class, type + "Method", shortParams);
         String result = (String) method.invoke(mp, shortParams);
 
         assertEquals("Method could not be found", type, result);
@@ -180,8 +186,8 @@
 
         Object[] params = {};
 
-        Method method = RuntimeSingleton.getIntrospector().getMethod(
-            MethodProvider.class, "untouchable", params);
+        Method method = introspector.getMethod(
+                MethodProvider.class, "untouchable", params);
 
         assertNull("able to access a private-access method.", method);
     }
@@ -192,8 +198,8 @@
         // Test really untouchable
         Object[] params = {};
 
-        Method method = RuntimeSingleton.getIntrospector().getMethod(
-            MethodProvider.class, "reallyuntouchable", params);
+        Method method = introspector.getMethod(
+                MethodProvider.class, "reallyuntouchable", params);
 
         assertNull("able to access a private-access method.", method);
     }
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/StrictReferenceTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/StrictReferenceTestCase.java
index ec53ce5..62f7e8a 100644
--- a/velocity-engine-core/src/test/java/org/apache/velocity/test/StrictReferenceTestCase.java
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/StrictReferenceTestCase.java
@@ -19,10 +19,13 @@
  * under the License.    
  */
 
+import org.apache.velocity.app.VelocityEngine;
 import org.apache.velocity.exception.MethodInvocationException;
 import org.apache.velocity.exception.ParseErrorException;
 import org.apache.velocity.exception.VelocityException;
 import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.runtime.resource.loader.StringResourceLoader;
+import org.apache.velocity.test.misc.TestLogger;
 
 /**
  * Test strict reference mode turned on by the velocity property
@@ -35,10 +38,21 @@
         super(name);
     }
 
+    // second engine to test WITH conversions
+    VelocityEngine engine2;
+
     public void setUp() throws Exception
     {
         super.setUp();
+
+        /* first engine without conversions */
         engine.setProperty(RuntimeConstants.RUNTIME_REFERENCES_STRICT, Boolean.TRUE);
+        engine.setProperty(RuntimeConstants.CONVERSION_HANDLER_CLASS, "none");
+
+        /* second engine with conversions */
+        engine2 = createEngine();
+        engine.setProperty(RuntimeConstants.RUNTIME_REFERENCES_STRICT, Boolean.TRUE);
+
         context.put("NULL", null);
         context.put("bar", null);
         context.put("TRUE", Boolean.TRUE);
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/VelTools66TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/VelTools66TestCase.java
index 2e34130..831e094 100644
--- a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/VelTools66TestCase.java
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/VelTools66TestCase.java
@@ -80,9 +80,9 @@
     {
         Method verifyMethod = TestInterface.class.getMethod("getTestValue", new Class[0]);
 
-
         RuntimeInstance ri = new RuntimeInstance();
-        Introspector introspector = ri.getIntrospector();
+        log = new TestLogger();
+        Introspector introspector = new Introspector(log);
 
         Method testMethod = introspector.getMethod(TestObject.class, "getTestValue", new Object[0]);
         assertNotNull(testMethod);
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/ConversionHandlerTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/ConversionHandlerTestCase.java
new file mode 100644
index 0000000..445c0a1
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/ConversionHandlerTestCase.java
@@ -0,0 +1,313 @@
+/**
+ * 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.
+ */
+package org.apache.velocity.test.util.introspection;
+
+import junit.framework.TestSuite;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.app.event.MethodExceptionEventHandler;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.runtime.RuntimeInstance;
+import org.apache.velocity.test.BaseTestCase;
+import org.apache.velocity.test.misc.TestLogger;
+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.Uberspect;
+import org.apache.velocity.util.introspection.UberspectImpl;
+
+import java.io.BufferedWriter;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * Test case for conversion handler
+ */
+public class ConversionHandlerTestCase extends BaseTestCase
+{
+    private static final String RESULT_DIR = TEST_RESULT_DIR + "/conversion";
+
+    private static final String COMPARE_DIR = TEST_COMPARE_DIR + "/conversion/compare";
+
+    public ConversionHandlerTestCase(String name)
+    {
+        super(name);
+    }
+
+    public void setUp()
+            throws Exception
+    {
+        super.setUp();
+    }
+
+    /**
+     * Test suite
+     * @return test suite
+     */
+    public static junit.framework.Test suite()
+    {
+        return new TestSuite(ConversionHandlerTestCase.class);
+    }
+    
+    public void testConversionsWithoutHandler()
+    throws Exception
+    {
+        /*
+         *  local scope, cache on
+         */
+        VelocityEngine ve = createEngine(false);
+
+        testConversions(ve, "test_conv.vtl", "test_conv_without_handler");
+    }
+
+    public void testConversionsWithHandler()
+            throws Exception
+    {
+        /*
+         *  local scope, cache on
+         */
+        VelocityEngine ve = createEngine(true);
+
+        testConversions(ve, "test_conv.vtl", "test_conv_with_handler");
+    }
+
+    public void testConversionMatrix()
+            throws Exception
+    {
+        VelocityEngine ve = createEngine(true);
+        testConversions(ve, "matrix.vhtml", "matrix");
+    }
+
+    public void testCustomConverter()
+    {
+        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.init();
+        Uberspect uberspect = ve.getUberspect();
+        assertTrue(uberspect instanceof UberspectImpl);
+        UberspectImpl ui = (UberspectImpl)uberspect;
+        ConversionHandler ch = ui.getConversionHandler();
+        assertTrue(ch != null);
+        ch.addConverter(Float.class, Obj.class, new Converter<Float>()
+        {
+            @Override
+            public Float convert(Object o)
+            {
+                return 4.5f;
+            }
+        });
+        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());
+    }
+
+    /**
+     * Test conversions
+     * @param ve
+     * @param templateFile template
+     * @param outputBaseFileName
+     * @throws Exception
+     */
+    private void testConversions(VelocityEngine ve, String templateFile, String outputBaseFileName)
+            throws Exception
+    {
+        assureResultsDirectoryExists(RESULT_DIR);
+
+        FileOutputStream fos = new FileOutputStream (getFileName(
+                RESULT_DIR, outputBaseFileName, RESULT_FILE_EXT));
+
+        VelocityContext context = createContext();
+
+        Writer writer = new BufferedWriter(new OutputStreamWriter(fos));
+
+        Template template = ve.getTemplate(templateFile);
+        template.merge(context, writer);
+
+        /**
+         * Write to the file
+         */
+        writer.flush();
+        writer.close();
+
+        if (!isMatch(RESULT_DIR, COMPARE_DIR, outputBaseFileName,
+                RESULT_FILE_EXT,CMP_FILE_EXT))
+        {
+            String result = getFileContents(RESULT_DIR, outputBaseFileName, RESULT_FILE_EXT);
+            String compare = getFileContents(COMPARE_DIR, outputBaseFileName, CMP_FILE_EXT);
+
+            String msg = "Processed template did not match expected output\n"+
+                "-----Result-----\n"+ result +
+                "----Expected----\n"+ compare +
+                "----------------";
+            
+            fail(msg);
+        }
+    }
+
+    /**
+     * Return and initialize engine
+     * @return
+     */
+    private VelocityEngine createEngine(boolean withConversionsHandler)
+    throws Exception
+    {
+        VelocityEngine ve = new VelocityEngine();
+        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");
+        if (withConversionsHandler)
+        {
+            ve.setProperty(RuntimeConstants.EVENTHANDLER_METHODEXCEPTION, PrintException.class.getName());
+        }
+        else
+        {
+            ve.setProperty(RuntimeConstants.CONVERSION_HANDLER_CLASS, "none");
+        }
+        ve.init();
+        
+        return ve;
+    }
+
+    public static class PrintException implements MethodExceptionEventHandler
+    {
+        public Object methodException(Class claz,
+                                      String method,
+                                      Exception e,
+                                      Info info)
+        {
+            return method + " -> " + e.getClass().getSimpleName() + ": " + e.getMessage();
+        }
+    }
+
+    private VelocityContext createContext()
+    {
+        VelocityContext context = new VelocityContext();
+        Map<String, Object> map = new TreeMap<String, Object>();
+        map.put("A. bool-true", true);
+        map.put("B. bool-false", false);
+        map.put("C. byte-0", (byte)0);
+        map.put("D. byte-1", (byte)1);
+        map.put("E. short", (short)125);
+        map.put("F. int", 24323);
+        map.put("G. long", 5235235L);
+        map.put("H. float", 34523.345f);
+        map.put("I. double", 54235.3253d);
+        map.put("J. char", '@');
+        map.put("K. object", new Obj());
+        map.put("L. enum", Obj.Color.GREEN);
+        map.put("M. string", new String("foo"));
+        map.put("M. string-green", new String("green"));
+        map.put("N. string-empty", new String());
+        map.put("O. string-false", new String("false"));
+        map.put("P. string-true", new String("true"));
+        map.put("Q. string-zero", new String("0"));
+        map.put("R. string-integral", new String("123"));
+        map.put("S. string-big-integral", new String("12345678"));
+        map.put("T. string-floating", new String("123.345"));
+        map.put("U. null", null);
+        context.put("map", map);
+        context.put("target", new Obj());
+        Class[] types =
+                {
+                        Boolean.TYPE,
+                        Character.TYPE,
+                        Byte.TYPE,
+                        Short.TYPE,
+                        Integer.TYPE,
+                        Long.TYPE,
+                        Float.TYPE,
+                        Double.TYPE,
+                        Boolean.class,
+                        Character.class,
+                        Byte.class,
+                        Short.class,
+                        Integer.class,
+                        Long.class,
+                        Float.class,
+                        Double.class,
+                        Object.class
+                };
+        context.put("types", types);
+        context.put("introspect", new Introspect());
+        return context;
+    }
+
+    public static class Obj
+    {
+        public enum Color { RED, GREEN };
+
+        public String integralBoolean(boolean b) { return "boolean ok: " + b; }
+        public String integralByte(byte b) { return "byte ok: " + b; }
+        public String integralShort(short s) { return "short ok: " + s; }
+        public String integralInt(int i) { return "int ok: " + i; }
+        public String integralLong(long l) { return "long ok: " + l; }
+        public String integralFloat(float f) { return "float ok: " + f; }
+        public String integralDouble(double d) { return "double ok: " + d; }
+        public String integralChar(char c) { return "char ok: " + c; }
+        public String objectBoolean(Boolean b) { return "Boolean ok: " + b; }
+        public String objectByte(Byte b) { return "Byte ok: " + b; }
+        public String objectShort(Short s) { return "Short ok: " + s; }
+        public String objectInt(Integer i) { return "Integer ok: " + i; }
+        public String objectLong(Long l) { return "Long ok: " + l; }
+        public String objectFloat(Float f) { return "Float ok: " + f; }
+        public String objectDouble(Double d) { return "Double ok: " + d; }
+        public String objectCharacter(Character c) { return "Character ok: " + c; }
+        public String objectNumber(Number b) { return "Number ok: " + b; }
+        public String objectObject(Object o) { return "Object ok: " + o; }
+        public String objectString(String s) { return "String ok: " + s; }
+        public String objectEnum(Color c) { return "Enum ok: " + c; }
+
+        public String toString() { return "instance of Obj"; }
+    }
+
+    public static class Introspect
+    {
+        private ConversionHandler handler;
+        public Introspect()
+        {
+            handler = new ConversionHandlerImpl();
+        }
+        public boolean isStrictlyConvertible(Class expected, Class provided)
+        {
+            return IntrospectionUtils.isStrictMethodInvocationConvertible(expected, provided, false);
+        }
+        public boolean isImplicitlyConvertible(Class expected, Class provided)
+        {
+            return IntrospectionUtils.isMethodInvocationConvertible(expected, provided, false);
+        }
+        public boolean isExplicitlyConvertible(Class expected, Class provided)
+        {
+            return handler.isExplicitlyConvertible(expected, provided, false);
+        }
+    }
+}
diff --git a/velocity-engine-core/src/test/resources/conversion/compare/matrix.cmp b/velocity-engine-core/src/test/resources/conversion/compare/matrix.cmp
new file mode 100644
index 0000000..bb46c36
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/conversion/compare/matrix.cmp
@@ -0,0 +1,1600 @@
+<html>
+  <head>
+    <style type="text/css">
+    table
+    {
+        border: solid 1px black;
+        border-collapse: collapse;
+    }
+	td, th
+    {
+        border: solid 1px black;
+    }
+
+    </style>
+  </head>
+  <body>
+    <table>
+      <thead>
+        <tr>
+          <th>
+            strict<br/>
+            implicit<br/>
+            explicit
+          </th>
+          <th>boolean</th>
+          <th>char</th>
+          <th>byte</th>
+          <th>short</th>
+          <th>int</th>
+          <th>long</th>
+          <th>float</th>
+          <th>double</th>
+          <th>class java.lang.Boolean</th>
+          <th>class java.lang.Character</th>
+          <th>class java.lang.Byte</th>
+          <th>class java.lang.Short</th>
+          <th>class java.lang.Integer</th>
+          <th>class java.lang.Long</th>
+          <th>class java.lang.Float</th>
+          <th>class java.lang.Double</th>
+          <th>class java.lang.Object</th>
+          <th>null</th>
+        </tr>
+        <tr>
+          <th>formal: boolean</th>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+              <td>&nbsp;</td>
+          </tr>
+        <tr>
+          <th>formal: char</th>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+              <td>&nbsp;</td>
+          </tr>
+        <tr>
+          <th>formal: byte</th>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+              <td>&nbsp;</td>
+          </tr>
+        <tr>
+          <th>formal: short</th>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+              <td>&nbsp;</td>
+          </tr>
+        <tr>
+          <th>formal: int</th>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+              <td>&nbsp;</td>
+          </tr>
+        <tr>
+          <th>formal: long</th>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+              <td>&nbsp;</td>
+          </tr>
+        <tr>
+          <th>formal: float</th>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+              <td>&nbsp;</td>
+          </tr>
+        <tr>
+          <th>formal: double</th>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+              <td>&nbsp;</td>
+          </tr>
+        <tr>
+          <th>formal: class java.lang.Boolean</th>
+            <td>
+            false<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+              <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+          </tr>
+        <tr>
+          <th>formal: class java.lang.Character</th>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+              <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+          </tr>
+        <tr>
+          <th>formal: class java.lang.Byte</th>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+              <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+          </tr>
+        <tr>
+          <th>formal: class java.lang.Short</th>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+              <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+          </tr>
+        <tr>
+          <th>formal: class java.lang.Integer</th>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+              <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+          </tr>
+        <tr>
+          <th>formal: class java.lang.Long</th>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+              <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+          </tr>
+        <tr>
+          <th>formal: class java.lang.Float</th>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            true
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+              <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+          </tr>
+        <tr>
+          <th>formal: class java.lang.Double</th>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+              <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+          </tr>
+        <tr>
+          <th>formal: class java.lang.Object</th>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            false<br/>
+            false<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+            <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+              <td>
+            true<br/>
+            true<br/>
+            false
+          </td>
+          </tr>
+      </thead>
+      <tbody>
+      </tbody>
+    </table>
+  </body>
+</html>
+
+    
diff --git a/velocity-engine-core/src/test/resources/conversion/compare/test_conv_with_handler.cmp b/velocity-engine-core/src/test/resources/conversion/compare/test_conv_with_handler.cmp
new file mode 100644
index 0000000..71764b4
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/conversion/compare/test_conv_with_handler.cmp
@@ -0,0 +1,638 @@
+A. bool-true Value java.lang.Boolean true
+  boolean ok: true
+  byte ok: 1
+  short ok: 1
+  int ok: 1
+  long ok: 1
+  $target.integralFloat($value)
+  $target.integralDouble($value)
+  $target.integralChar($value)
+  Boolean ok: true
+  Byte ok: 1
+  Short ok: 1
+  Integer ok: 1
+  Long ok: 1
+  $target.objectFloat($value)
+  $target.objectDouble($value)
+  $target.objectCharacter($value)
+  $target.objectNumber($value)
+  Object ok: true
+  $target.objectEnum($value)
+  String ok: true
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+B. bool-false Value java.lang.Boolean false
+  boolean ok: false
+  byte ok: 0
+  short ok: 0
+  int ok: 0
+  long ok: 0
+  $target.integralFloat($value)
+  $target.integralDouble($value)
+  $target.integralChar($value)
+  Boolean ok: false
+  Byte ok: 0
+  Short ok: 0
+  Integer ok: 0
+  Long ok: 0
+  $target.objectFloat($value)
+  $target.objectDouble($value)
+  $target.objectCharacter($value)
+  $target.objectNumber($value)
+  Object ok: false
+  $target.objectEnum($value)
+  String ok: false
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+C. byte-0 Value java.lang.Byte 0
+  boolean ok: false
+  byte ok: 0
+  short ok: 0
+  int ok: 0
+  long ok: 0
+  float ok: 0.0
+  double ok: 0.0
+  $target.integralChar($value)
+  Boolean ok: false
+  Byte ok: 0
+  $target.objectShort($value)
+  $target.objectInt($value)
+  $target.objectLong($value)
+  $target.objectFloat($value)
+  $target.objectDouble($value)
+  $target.objectCharacter($value)
+  Number ok: 0
+  Object ok: 0
+  $target.objectEnum($value)
+  String ok: 0
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+D. byte-1 Value java.lang.Byte 1
+  boolean ok: true
+  byte ok: 1
+  short ok: 1
+  int ok: 1
+  long ok: 1
+  float ok: 1.0
+  double ok: 1.0
+  $target.integralChar($value)
+  Boolean ok: true
+  Byte ok: 1
+  $target.objectShort($value)
+  $target.objectInt($value)
+  $target.objectLong($value)
+  $target.objectFloat($value)
+  $target.objectDouble($value)
+  $target.objectCharacter($value)
+  Number ok: 1
+  Object ok: 1
+  $target.objectEnum($value)
+  String ok: 1
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+E. short Value java.lang.Short 125
+  boolean ok: true
+  byte ok: 125
+  short ok: 125
+  int ok: 125
+  long ok: 125
+  float ok: 125.0
+  double ok: 125.0
+  $target.integralChar($value)
+  Boolean ok: true
+  Byte ok: 125
+  Short ok: 125
+  $target.objectInt($value)
+  $target.objectLong($value)
+  $target.objectFloat($value)
+  $target.objectDouble($value)
+  $target.objectCharacter($value)
+  Number ok: 125
+  Object ok: 125
+  $target.objectEnum($value)
+  String ok: 125
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+F. int Value java.lang.Integer 24323
+  boolean ok: true
+  integralByte -> NumberFormatException: value out of range for byte type: 24323
+  short ok: 24323
+  int ok: 24323
+  long ok: 24323
+  float ok: 24323.0
+  double ok: 24323.0
+  $target.integralChar($value)
+  Boolean ok: true
+  objectByte -> NumberFormatException: value out of range for byte type: 24323
+  Short ok: 24323
+  Integer ok: 24323
+  $target.objectLong($value)
+  $target.objectFloat($value)
+  $target.objectDouble($value)
+  $target.objectCharacter($value)
+  Number ok: 24323
+  Object ok: 24323
+  $target.objectEnum($value)
+  String ok: 24323
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+G. long Value java.lang.Long 5235235
+  boolean ok: true
+  integralByte -> NumberFormatException: value out of range for byte type: 5235235
+  integralShort -> NumberFormatException: value out of range for short type: 5235235
+  int ok: 5235235
+  long ok: 5235235
+  float ok: 5235235.0
+  double ok: 5235235.0
+  $target.integralChar($value)
+  Boolean ok: true
+  objectByte -> NumberFormatException: value out of range for byte type: 5235235
+  objectShort -> NumberFormatException: value out of range for short type: 5235235
+  Integer ok: 5235235
+  Long ok: 5235235
+  $target.objectFloat($value)
+  $target.objectDouble($value)
+  $target.objectCharacter($value)
+  Number ok: 5235235
+  Object ok: 5235235
+  $target.objectEnum($value)
+  String ok: 5235235
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+H. float Value java.lang.Float 34523.344
+  boolean ok: true
+  integralByte -> NumberFormatException: value out of range for byte type: 34523
+  integralShort -> NumberFormatException: value out of range for short type: 34523
+  int ok: 34523
+  long ok: 34523
+  float ok: 34523.344
+  double ok: 34523.34375
+  $target.integralChar($value)
+  Boolean ok: true
+  objectByte -> NumberFormatException: value out of range for byte type: 34523
+  objectShort -> NumberFormatException: value out of range for short type: 34523
+  Integer ok: 34523
+  Long ok: 34523
+  Float ok: 34523.344
+  $target.objectDouble($value)
+  $target.objectCharacter($value)
+  Number ok: 34523.344
+  Object ok: 34523.344
+  $target.objectEnum($value)
+  String ok: 34523.344
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+I. double Value java.lang.Double 54235.3253
+  boolean ok: true
+  integralByte -> NumberFormatException: value out of range for byte type: 54235
+  integralShort -> NumberFormatException: value out of range for short type: 54235
+  int ok: 54235
+  long ok: 54235
+  float ok: 54235.324
+  double ok: 54235.3253
+  $target.integralChar($value)
+  Boolean ok: true
+  objectByte -> NumberFormatException: value out of range for byte type: 54235
+  objectShort -> NumberFormatException: value out of range for short type: 54235
+  Integer ok: 54235
+  Long ok: 54235
+  Float ok: 54235.324
+  Double ok: 54235.3253
+  $target.objectCharacter($value)
+  Number ok: 54235.3253
+  Object ok: 54235.3253
+  $target.objectEnum($value)
+  String ok: 54235.3253
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+J. char Value java.lang.Character @
+  boolean ok: true
+  $target.integralByte($value)
+  $target.integralShort($value)
+  $target.integralInt($value)
+  $target.integralLong($value)
+  $target.integralFloat($value)
+  $target.integralDouble($value)
+  char ok: @
+  Boolean ok: true
+  $target.objectByte($value)
+  $target.objectShort($value)
+  $target.objectInt($value)
+  $target.objectLong($value)
+  $target.objectFloat($value)
+  $target.objectDouble($value)
+  Character ok: @
+  $target.objectNumber($value)
+  Object ok: @
+  $target.objectEnum($value)
+  String ok: @
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+K. object Value org.apache.velocity.test.util.introspection.ConversionHandlerTestCase$Obj instance of Obj
+  $target.integralBoolean($value)
+  $target.integralByte($value)
+  $target.integralShort($value)
+  $target.integralInt($value)
+  $target.integralLong($value)
+  $target.integralFloat($value)
+  $target.integralDouble($value)
+  $target.integralChar($value)
+  $target.objectBoolean($value)
+  $target.objectByte($value)
+  $target.objectShort($value)
+  $target.objectInt($value)
+  $target.objectLong($value)
+  $target.objectFloat($value)
+  $target.objectDouble($value)
+  $target.objectCharacter($value)
+  $target.objectNumber($value)
+  Object ok: instance of Obj
+  $target.objectEnum($value)
+  String ok: instance of Obj
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+L. enum Value org.apache.velocity.test.util.introspection.ConversionHandlerTestCase$Obj$Color GREEN
+  $target.integralBoolean($value)
+  $target.integralByte($value)
+  $target.integralShort($value)
+  $target.integralInt($value)
+  $target.integralLong($value)
+  $target.integralFloat($value)
+  $target.integralDouble($value)
+  $target.integralChar($value)
+  $target.objectBoolean($value)
+  $target.objectByte($value)
+  $target.objectShort($value)
+  $target.objectInt($value)
+  $target.objectLong($value)
+  $target.objectFloat($value)
+  $target.objectDouble($value)
+  $target.objectCharacter($value)
+  $target.objectNumber($value)
+  Object ok: GREEN
+  Enum ok: GREEN
+  String ok: GREEN
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+M. string Value java.lang.String foo
+  boolean ok: false
+  integralByte -> NumberFormatException: For input string: "foo"
+  integralShort -> NumberFormatException: For input string: "foo"
+  integralInt -> NumberFormatException: For input string: "foo"
+  integralLong -> NumberFormatException: For input string: "foo"
+  integralFloat -> NumberFormatException: For input string: "foo"
+  integralDouble -> NumberFormatException: For input string: "foo"
+  $target.integralChar($value)
+  Boolean ok: false
+  objectByte -> NumberFormatException: For input string: "foo"
+  objectShort -> NumberFormatException: For input string: "foo"
+  objectInt -> NumberFormatException: For input string: "foo"
+  objectLong -> NumberFormatException: For input string: "foo"
+  objectFloat -> NumberFormatException: For input string: "foo"
+  objectDouble -> NumberFormatException: For input string: "foo"
+  $target.objectCharacter($value)
+  $target.objectNumber($value)
+  Object ok: foo
+  objectEnum -> IllegalArgumentException: No enum constant org.apache.velocity.test.util.introspection.ConversionHandlerTestCase.Obj.Color.foo
+  String ok: foo
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+M. string-green Value java.lang.String green
+  boolean ok: false
+  integralByte -> NumberFormatException: For input string: "green"
+  integralShort -> NumberFormatException: For input string: "green"
+  integralInt -> NumberFormatException: For input string: "green"
+  integralLong -> NumberFormatException: For input string: "green"
+  integralFloat -> NumberFormatException: For input string: "green"
+  integralDouble -> NumberFormatException: For input string: "green"
+  $target.integralChar($value)
+  Boolean ok: false
+  objectByte -> NumberFormatException: For input string: "green"
+  objectShort -> NumberFormatException: For input string: "green"
+  objectInt -> NumberFormatException: For input string: "green"
+  objectLong -> NumberFormatException: For input string: "green"
+  objectFloat -> NumberFormatException: For input string: "green"
+  objectDouble -> NumberFormatException: For input string: "green"
+  $target.objectCharacter($value)
+  $target.objectNumber($value)
+  Object ok: green
+  objectEnum -> IllegalArgumentException: No enum constant org.apache.velocity.test.util.introspection.ConversionHandlerTestCase.Obj.Color.green
+  String ok: green
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+N. string-empty Value java.lang.String 
+  boolean ok: false
+  integralByte -> NumberFormatException: For input string: ""
+  integralShort -> NumberFormatException: For input string: ""
+  integralInt -> NumberFormatException: For input string: ""
+  integralLong -> NumberFormatException: For input string: ""
+  integralFloat -> NumberFormatException: empty String
+  integralDouble -> NumberFormatException: empty String
+  $target.integralChar($value)
+  Boolean ok: false
+  objectByte -> NumberFormatException: For input string: ""
+  objectShort -> NumberFormatException: For input string: ""
+  objectInt -> NumberFormatException: For input string: ""
+  objectLong -> NumberFormatException: For input string: ""
+  objectFloat -> NumberFormatException: empty String
+  objectDouble -> NumberFormatException: empty String
+  $target.objectCharacter($value)
+  $target.objectNumber($value)
+  Object ok: 
+  objectEnum -> IllegalArgumentException: No enum constant org.apache.velocity.test.util.introspection.ConversionHandlerTestCase.Obj.Color.
+  String ok: 
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+O. string-false Value java.lang.String false
+  boolean ok: false
+  integralByte -> NumberFormatException: For input string: "false"
+  integralShort -> NumberFormatException: For input string: "false"
+  integralInt -> NumberFormatException: For input string: "false"
+  integralLong -> NumberFormatException: For input string: "false"
+  integralFloat -> NumberFormatException: For input string: "false"
+  integralDouble -> NumberFormatException: For input string: "false"
+  $target.integralChar($value)
+  Boolean ok: false
+  objectByte -> NumberFormatException: For input string: "false"
+  objectShort -> NumberFormatException: For input string: "false"
+  objectInt -> NumberFormatException: For input string: "false"
+  objectLong -> NumberFormatException: For input string: "false"
+  objectFloat -> NumberFormatException: For input string: "false"
+  objectDouble -> NumberFormatException: For input string: "false"
+  $target.objectCharacter($value)
+  $target.objectNumber($value)
+  Object ok: false
+  objectEnum -> IllegalArgumentException: No enum constant org.apache.velocity.test.util.introspection.ConversionHandlerTestCase.Obj.Color.false
+  String ok: false
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+P. string-true Value java.lang.String true
+  boolean ok: true
+  integralByte -> NumberFormatException: For input string: "true"
+  integralShort -> NumberFormatException: For input string: "true"
+  integralInt -> NumberFormatException: For input string: "true"
+  integralLong -> NumberFormatException: For input string: "true"
+  integralFloat -> NumberFormatException: For input string: "true"
+  integralDouble -> NumberFormatException: For input string: "true"
+  $target.integralChar($value)
+  Boolean ok: true
+  objectByte -> NumberFormatException: For input string: "true"
+  objectShort -> NumberFormatException: For input string: "true"
+  objectInt -> NumberFormatException: For input string: "true"
+  objectLong -> NumberFormatException: For input string: "true"
+  objectFloat -> NumberFormatException: For input string: "true"
+  objectDouble -> NumberFormatException: For input string: "true"
+  $target.objectCharacter($value)
+  $target.objectNumber($value)
+  Object ok: true
+  objectEnum -> IllegalArgumentException: No enum constant org.apache.velocity.test.util.introspection.ConversionHandlerTestCase.Obj.Color.true
+  String ok: true
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+Q. string-zero Value java.lang.String 0
+  boolean ok: false
+  byte ok: 0
+  short ok: 0
+  int ok: 0
+  long ok: 0
+  float ok: 0.0
+  double ok: 0.0
+  $target.integralChar($value)
+  Boolean ok: false
+  Byte ok: 0
+  Short ok: 0
+  Integer ok: 0
+  Long ok: 0
+  Float ok: 0.0
+  Double ok: 0.0
+  $target.objectCharacter($value)
+  $target.objectNumber($value)
+  Object ok: 0
+  objectEnum -> IllegalArgumentException: No enum constant org.apache.velocity.test.util.introspection.ConversionHandlerTestCase.Obj.Color.0
+  String ok: 0
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+R. string-integral Value java.lang.String 123
+  boolean ok: false
+  byte ok: 123
+  short ok: 123
+  int ok: 123
+  long ok: 123
+  float ok: 123.0
+  double ok: 123.0
+  $target.integralChar($value)
+  Boolean ok: false
+  Byte ok: 123
+  Short ok: 123
+  Integer ok: 123
+  Long ok: 123
+  Float ok: 123.0
+  Double ok: 123.0
+  $target.objectCharacter($value)
+  $target.objectNumber($value)
+  Object ok: 123
+  objectEnum -> IllegalArgumentException: No enum constant org.apache.velocity.test.util.introspection.ConversionHandlerTestCase.Obj.Color.123
+  String ok: 123
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+S. string-big-integral Value java.lang.String 12345678
+  boolean ok: false
+  integralByte -> NumberFormatException: Value out of range. Value:"12345678" Radix:10
+  integralShort -> NumberFormatException: Value out of range. Value:"12345678" Radix:10
+  int ok: 12345678
+  long ok: 12345678
+  float ok: 1.2345678E7
+  double ok: 1.2345678E7
+  $target.integralChar($value)
+  Boolean ok: false
+  objectByte -> NumberFormatException: Value out of range. Value:"12345678" Radix:10
+  objectShort -> NumberFormatException: Value out of range. Value:"12345678" Radix:10
+  Integer ok: 12345678
+  Long ok: 12345678
+  Float ok: 1.2345678E7
+  Double ok: 1.2345678E7
+  $target.objectCharacter($value)
+  $target.objectNumber($value)
+  Object ok: 12345678
+  objectEnum -> IllegalArgumentException: No enum constant org.apache.velocity.test.util.introspection.ConversionHandlerTestCase.Obj.Color.12345678
+  String ok: 12345678
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+T. string-floating Value java.lang.String 123.345
+  boolean ok: false
+  integralByte -> NumberFormatException: For input string: "123.345"
+  integralShort -> NumberFormatException: For input string: "123.345"
+  integralInt -> NumberFormatException: For input string: "123.345"
+  integralLong -> NumberFormatException: For input string: "123.345"
+  float ok: 123.345
+  double ok: 123.345
+  $target.integralChar($value)
+  Boolean ok: false
+  objectByte -> NumberFormatException: For input string: "123.345"
+  objectShort -> NumberFormatException: For input string: "123.345"
+  objectInt -> NumberFormatException: For input string: "123.345"
+  objectLong -> NumberFormatException: For input string: "123.345"
+  Float ok: 123.345
+  Double ok: 123.345
+  $target.objectCharacter($value)
+  $target.objectNumber($value)
+  Object ok: 123.345
+  objectEnum -> IllegalArgumentException: No enum constant org.apache.velocity.test.util.introspection.ConversionHandlerTestCase.Obj.Color.123.345
+  String ok: 123.345
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+U. null Value $value.class.name $value
+  $target.integralBoolean($value)
+  $target.integralByte($value)
+  $target.integralShort($value)
+  $target.integralInt($value)
+  $target.integralLong($value)
+  $target.integralFloat($value)
+  $target.integralDouble($value)
+  $target.integralChar($value)
+  Boolean ok: null
+  Byte ok: null
+  Short ok: null
+  Integer ok: null
+  Long ok: null
+  Float ok: null
+  Double ok: null
+  Character ok: null
+  Number ok: null
+  Object ok: null
+  Enum ok: null
+  String ok: null
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
diff --git a/velocity-engine-core/src/test/resources/conversion/compare/test_conv_without_handler.cmp b/velocity-engine-core/src/test/resources/conversion/compare/test_conv_without_handler.cmp
new file mode 100644
index 0000000..a34d5b2
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/conversion/compare/test_conv_without_handler.cmp
@@ -0,0 +1,638 @@
+A. bool-true Value java.lang.Boolean true
+  boolean ok: true
+  $target.integralByte($value)
+  $target.integralShort($value)
+  $target.integralInt($value)
+  $target.integralLong($value)
+  $target.integralFloat($value)
+  $target.integralDouble($value)
+  $target.integralChar($value)
+  Boolean ok: true
+  $target.objectByte($value)
+  $target.objectShort($value)
+  $target.objectInt($value)
+  $target.objectLong($value)
+  $target.objectFloat($value)
+  $target.objectDouble($value)
+  $target.objectCharacter($value)
+  $target.objectNumber($value)
+  Object ok: true
+  $target.objectEnum($value)
+  $target.objectString($value)
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+B. bool-false Value java.lang.Boolean false
+  boolean ok: false
+  $target.integralByte($value)
+  $target.integralShort($value)
+  $target.integralInt($value)
+  $target.integralLong($value)
+  $target.integralFloat($value)
+  $target.integralDouble($value)
+  $target.integralChar($value)
+  Boolean ok: false
+  $target.objectByte($value)
+  $target.objectShort($value)
+  $target.objectInt($value)
+  $target.objectLong($value)
+  $target.objectFloat($value)
+  $target.objectDouble($value)
+  $target.objectCharacter($value)
+  $target.objectNumber($value)
+  Object ok: false
+  $target.objectEnum($value)
+  $target.objectString($value)
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+C. byte-0 Value java.lang.Byte 0
+  $target.integralBoolean($value)
+  byte ok: 0
+  short ok: 0
+  int ok: 0
+  long ok: 0
+  float ok: 0.0
+  double ok: 0.0
+  $target.integralChar($value)
+  $target.objectBoolean($value)
+  Byte ok: 0
+  $target.objectShort($value)
+  $target.objectInt($value)
+  $target.objectLong($value)
+  $target.objectFloat($value)
+  $target.objectDouble($value)
+  $target.objectCharacter($value)
+  Number ok: 0
+  Object ok: 0
+  $target.objectEnum($value)
+  $target.objectString($value)
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+D. byte-1 Value java.lang.Byte 1
+  $target.integralBoolean($value)
+  byte ok: 1
+  short ok: 1
+  int ok: 1
+  long ok: 1
+  float ok: 1.0
+  double ok: 1.0
+  $target.integralChar($value)
+  $target.objectBoolean($value)
+  Byte ok: 1
+  $target.objectShort($value)
+  $target.objectInt($value)
+  $target.objectLong($value)
+  $target.objectFloat($value)
+  $target.objectDouble($value)
+  $target.objectCharacter($value)
+  Number ok: 1
+  Object ok: 1
+  $target.objectEnum($value)
+  $target.objectString($value)
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+E. short Value java.lang.Short 125
+  $target.integralBoolean($value)
+  $target.integralByte($value)
+  short ok: 125
+  int ok: 125
+  long ok: 125
+  float ok: 125.0
+  double ok: 125.0
+  $target.integralChar($value)
+  $target.objectBoolean($value)
+  $target.objectByte($value)
+  Short ok: 125
+  $target.objectInt($value)
+  $target.objectLong($value)
+  $target.objectFloat($value)
+  $target.objectDouble($value)
+  $target.objectCharacter($value)
+  Number ok: 125
+  Object ok: 125
+  $target.objectEnum($value)
+  $target.objectString($value)
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+F. int Value java.lang.Integer 24323
+  $target.integralBoolean($value)
+  $target.integralByte($value)
+  $target.integralShort($value)
+  int ok: 24323
+  long ok: 24323
+  float ok: 24323.0
+  double ok: 24323.0
+  $target.integralChar($value)
+  $target.objectBoolean($value)
+  $target.objectByte($value)
+  $target.objectShort($value)
+  Integer ok: 24323
+  $target.objectLong($value)
+  $target.objectFloat($value)
+  $target.objectDouble($value)
+  $target.objectCharacter($value)
+  Number ok: 24323
+  Object ok: 24323
+  $target.objectEnum($value)
+  $target.objectString($value)
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+G. long Value java.lang.Long 5235235
+  $target.integralBoolean($value)
+  $target.integralByte($value)
+  $target.integralShort($value)
+  $target.integralInt($value)
+  long ok: 5235235
+  float ok: 5235235.0
+  double ok: 5235235.0
+  $target.integralChar($value)
+  $target.objectBoolean($value)
+  $target.objectByte($value)
+  $target.objectShort($value)
+  $target.objectInt($value)
+  Long ok: 5235235
+  $target.objectFloat($value)
+  $target.objectDouble($value)
+  $target.objectCharacter($value)
+  Number ok: 5235235
+  Object ok: 5235235
+  $target.objectEnum($value)
+  $target.objectString($value)
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+H. float Value java.lang.Float 34523.344
+  $target.integralBoolean($value)
+  $target.integralByte($value)
+  $target.integralShort($value)
+  $target.integralInt($value)
+  $target.integralLong($value)
+  float ok: 34523.344
+  double ok: 34523.34375
+  $target.integralChar($value)
+  $target.objectBoolean($value)
+  $target.objectByte($value)
+  $target.objectShort($value)
+  $target.objectInt($value)
+  $target.objectLong($value)
+  Float ok: 34523.344
+  $target.objectDouble($value)
+  $target.objectCharacter($value)
+  Number ok: 34523.344
+  Object ok: 34523.344
+  $target.objectEnum($value)
+  $target.objectString($value)
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+I. double Value java.lang.Double 54235.3253
+  $target.integralBoolean($value)
+  $target.integralByte($value)
+  $target.integralShort($value)
+  $target.integralInt($value)
+  $target.integralLong($value)
+  $target.integralFloat($value)
+  double ok: 54235.3253
+  $target.integralChar($value)
+  $target.objectBoolean($value)
+  $target.objectByte($value)
+  $target.objectShort($value)
+  $target.objectInt($value)
+  $target.objectLong($value)
+  $target.objectFloat($value)
+  Double ok: 54235.3253
+  $target.objectCharacter($value)
+  Number ok: 54235.3253
+  Object ok: 54235.3253
+  $target.objectEnum($value)
+  $target.objectString($value)
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+J. char Value java.lang.Character @
+  $target.integralBoolean($value)
+  $target.integralByte($value)
+  $target.integralShort($value)
+  $target.integralInt($value)
+  $target.integralLong($value)
+  $target.integralFloat($value)
+  $target.integralDouble($value)
+  char ok: @
+  $target.objectBoolean($value)
+  $target.objectByte($value)
+  $target.objectShort($value)
+  $target.objectInt($value)
+  $target.objectLong($value)
+  $target.objectFloat($value)
+  $target.objectDouble($value)
+  Character ok: @
+  $target.objectNumber($value)
+  Object ok: @
+  $target.objectEnum($value)
+  $target.objectString($value)
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+K. object Value org.apache.velocity.test.util.introspection.ConversionHandlerTestCase$Obj instance of Obj
+  $target.integralBoolean($value)
+  $target.integralByte($value)
+  $target.integralShort($value)
+  $target.integralInt($value)
+  $target.integralLong($value)
+  $target.integralFloat($value)
+  $target.integralDouble($value)
+  $target.integralChar($value)
+  $target.objectBoolean($value)
+  $target.objectByte($value)
+  $target.objectShort($value)
+  $target.objectInt($value)
+  $target.objectLong($value)
+  $target.objectFloat($value)
+  $target.objectDouble($value)
+  $target.objectCharacter($value)
+  $target.objectNumber($value)
+  Object ok: instance of Obj
+  $target.objectEnum($value)
+  $target.objectString($value)
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+L. enum Value org.apache.velocity.test.util.introspection.ConversionHandlerTestCase$Obj$Color GREEN
+  $target.integralBoolean($value)
+  $target.integralByte($value)
+  $target.integralShort($value)
+  $target.integralInt($value)
+  $target.integralLong($value)
+  $target.integralFloat($value)
+  $target.integralDouble($value)
+  $target.integralChar($value)
+  $target.objectBoolean($value)
+  $target.objectByte($value)
+  $target.objectShort($value)
+  $target.objectInt($value)
+  $target.objectLong($value)
+  $target.objectFloat($value)
+  $target.objectDouble($value)
+  $target.objectCharacter($value)
+  $target.objectNumber($value)
+  Object ok: GREEN
+  Enum ok: GREEN
+  $target.objectString($value)
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+M. string Value java.lang.String foo
+  $target.integralBoolean($value)
+  $target.integralByte($value)
+  $target.integralShort($value)
+  $target.integralInt($value)
+  $target.integralLong($value)
+  $target.integralFloat($value)
+  $target.integralDouble($value)
+  $target.integralChar($value)
+  $target.objectBoolean($value)
+  $target.objectByte($value)
+  $target.objectShort($value)
+  $target.objectInt($value)
+  $target.objectLong($value)
+  $target.objectFloat($value)
+  $target.objectDouble($value)
+  $target.objectCharacter($value)
+  $target.objectNumber($value)
+  Object ok: foo
+  $target.objectEnum($value)
+  String ok: foo
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+M. string-green Value java.lang.String green
+  $target.integralBoolean($value)
+  $target.integralByte($value)
+  $target.integralShort($value)
+  $target.integralInt($value)
+  $target.integralLong($value)
+  $target.integralFloat($value)
+  $target.integralDouble($value)
+  $target.integralChar($value)
+  $target.objectBoolean($value)
+  $target.objectByte($value)
+  $target.objectShort($value)
+  $target.objectInt($value)
+  $target.objectLong($value)
+  $target.objectFloat($value)
+  $target.objectDouble($value)
+  $target.objectCharacter($value)
+  $target.objectNumber($value)
+  Object ok: green
+  $target.objectEnum($value)
+  String ok: green
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+N. string-empty Value java.lang.String 
+  $target.integralBoolean($value)
+  $target.integralByte($value)
+  $target.integralShort($value)
+  $target.integralInt($value)
+  $target.integralLong($value)
+  $target.integralFloat($value)
+  $target.integralDouble($value)
+  $target.integralChar($value)
+  $target.objectBoolean($value)
+  $target.objectByte($value)
+  $target.objectShort($value)
+  $target.objectInt($value)
+  $target.objectLong($value)
+  $target.objectFloat($value)
+  $target.objectDouble($value)
+  $target.objectCharacter($value)
+  $target.objectNumber($value)
+  Object ok: 
+  $target.objectEnum($value)
+  String ok: 
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+O. string-false Value java.lang.String false
+  $target.integralBoolean($value)
+  $target.integralByte($value)
+  $target.integralShort($value)
+  $target.integralInt($value)
+  $target.integralLong($value)
+  $target.integralFloat($value)
+  $target.integralDouble($value)
+  $target.integralChar($value)
+  $target.objectBoolean($value)
+  $target.objectByte($value)
+  $target.objectShort($value)
+  $target.objectInt($value)
+  $target.objectLong($value)
+  $target.objectFloat($value)
+  $target.objectDouble($value)
+  $target.objectCharacter($value)
+  $target.objectNumber($value)
+  Object ok: false
+  $target.objectEnum($value)
+  String ok: false
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+P. string-true Value java.lang.String true
+  $target.integralBoolean($value)
+  $target.integralByte($value)
+  $target.integralShort($value)
+  $target.integralInt($value)
+  $target.integralLong($value)
+  $target.integralFloat($value)
+  $target.integralDouble($value)
+  $target.integralChar($value)
+  $target.objectBoolean($value)
+  $target.objectByte($value)
+  $target.objectShort($value)
+  $target.objectInt($value)
+  $target.objectLong($value)
+  $target.objectFloat($value)
+  $target.objectDouble($value)
+  $target.objectCharacter($value)
+  $target.objectNumber($value)
+  Object ok: true
+  $target.objectEnum($value)
+  String ok: true
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+Q. string-zero Value java.lang.String 0
+  $target.integralBoolean($value)
+  $target.integralByte($value)
+  $target.integralShort($value)
+  $target.integralInt($value)
+  $target.integralLong($value)
+  $target.integralFloat($value)
+  $target.integralDouble($value)
+  $target.integralChar($value)
+  $target.objectBoolean($value)
+  $target.objectByte($value)
+  $target.objectShort($value)
+  $target.objectInt($value)
+  $target.objectLong($value)
+  $target.objectFloat($value)
+  $target.objectDouble($value)
+  $target.objectCharacter($value)
+  $target.objectNumber($value)
+  Object ok: 0
+  $target.objectEnum($value)
+  String ok: 0
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+R. string-integral Value java.lang.String 123
+  $target.integralBoolean($value)
+  $target.integralByte($value)
+  $target.integralShort($value)
+  $target.integralInt($value)
+  $target.integralLong($value)
+  $target.integralFloat($value)
+  $target.integralDouble($value)
+  $target.integralChar($value)
+  $target.objectBoolean($value)
+  $target.objectByte($value)
+  $target.objectShort($value)
+  $target.objectInt($value)
+  $target.objectLong($value)
+  $target.objectFloat($value)
+  $target.objectDouble($value)
+  $target.objectCharacter($value)
+  $target.objectNumber($value)
+  Object ok: 123
+  $target.objectEnum($value)
+  String ok: 123
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+S. string-big-integral Value java.lang.String 12345678
+  $target.integralBoolean($value)
+  $target.integralByte($value)
+  $target.integralShort($value)
+  $target.integralInt($value)
+  $target.integralLong($value)
+  $target.integralFloat($value)
+  $target.integralDouble($value)
+  $target.integralChar($value)
+  $target.objectBoolean($value)
+  $target.objectByte($value)
+  $target.objectShort($value)
+  $target.objectInt($value)
+  $target.objectLong($value)
+  $target.objectFloat($value)
+  $target.objectDouble($value)
+  $target.objectCharacter($value)
+  $target.objectNumber($value)
+  Object ok: 12345678
+  $target.objectEnum($value)
+  String ok: 12345678
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+T. string-floating Value java.lang.String 123.345
+  $target.integralBoolean($value)
+  $target.integralByte($value)
+  $target.integralShort($value)
+  $target.integralInt($value)
+  $target.integralLong($value)
+  $target.integralFloat($value)
+  $target.integralDouble($value)
+  $target.integralChar($value)
+  $target.objectBoolean($value)
+  $target.objectByte($value)
+  $target.objectShort($value)
+  $target.objectInt($value)
+  $target.objectLong($value)
+  $target.objectFloat($value)
+  $target.objectDouble($value)
+  $target.objectCharacter($value)
+  $target.objectNumber($value)
+  Object ok: 123.345
+  $target.objectEnum($value)
+  String ok: 123.345
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+U. null Value $value.class.name $value
+  $target.integralBoolean($value)
+  $target.integralByte($value)
+  $target.integralShort($value)
+  $target.integralInt($value)
+  $target.integralLong($value)
+  $target.integralFloat($value)
+  $target.integralDouble($value)
+  $target.integralChar($value)
+  Boolean ok: null
+  Byte ok: null
+  Short ok: null
+  Integer ok: null
+  Long ok: null
+  Float ok: null
+  Double ok: null
+  Character ok: null
+  Number ok: null
+  Object ok: null
+  Enum ok: null
+  String ok: null
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
diff --git a/velocity-engine-core/src/test/resources/conversion/matrix.vhtml b/velocity-engine-core/src/test/resources/conversion/matrix.vhtml
new file mode 100644
index 0000000..16b0fb4
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/conversion/matrix.vhtml
@@ -0,0 +1,58 @@
+<html>
+  <head>
+    <style type="text/css">
+    table
+    {
+        border: solid 1px black;
+        border-collapse: collapse;
+    }
+	td, th
+    {
+        border: solid 1px black;
+    }
+
+    </style>
+  </head>
+  <body>
+    <table>
+      <thead>
+        <tr>
+          <th>
+            strict<br/>
+            implicit<br/>
+            explicit
+          </th>
+#foreach($col in $types)          
+          <th>$col</th>
+#end
+          <th>null</th>
+        </tr>
+#foreach($row in $types)          
+        <tr>
+          <th>formal: $row</th>
+  #foreach($col in $types)
+          <td>
+            $introspect.isStrictlyConvertible($row, $col)<br/>
+            $introspect.isImplicitlyConvertible($row, $col)<br/>
+            $introspect.isExplicitlyConvertible($row, $col)
+          </td>
+  #end
+  #if($row.isPrimitive())          
+          <td>&nbsp;</td>
+  #else
+          <td>
+            $introspect.isStrictlyConvertible($row, $null)<br/>
+            $introspect.isImplicitlyConvertible($row, $null)<br/>
+            $introspect.isExplicitlyConvertible($row, $null)
+          </td>
+  #end
+        </tr>
+#end        
+      </thead>
+      <tbody>
+      </tbody>
+    </table>
+  </body>
+</html>
+
+    
diff --git a/velocity-engine-core/src/test/resources/conversion/test_conv.vtl b/velocity-engine-core/src/test/resources/conversion/test_conv.vtl
new file mode 100644
index 0000000..cd8b3b1
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/conversion/test_conv.vtl
@@ -0,0 +1,32 @@
+#foreach($key in $map.keySet())##
+#set($value = $map[$key])##
+$key Value $value.class.name $value
+  $target.integralBoolean($value)
+  $target.integralByte($value)
+  $target.integralShort($value)
+  $target.integralInt($value)
+  $target.integralLong($value)
+  $target.integralFloat($value)
+  $target.integralDouble($value)
+  $target.integralChar($value)
+  $target.objectBoolean($value)
+  $target.objectByte($value)
+  $target.objectShort($value)
+  $target.objectInt($value)
+  $target.objectLong($value)
+  $target.objectFloat($value)
+  $target.objectDouble($value)
+  $target.objectCharacter($value)
+  $target.objectNumber($value)
+  $target.objectObject($value)
+  $target.objectEnum($value)
+  $target.objectString($value)
+  $target.valueOfBoolean($value)
+  $target.valueOfShort($value)
+  $target.valueOfByte($value)
+  $target.valueOfInt($value)
+  $target.valueOfLong($value)
+  $target.valueOfFloat($value)
+  $target.valueOfDouble($value)
+  $target.valueOfString($value)
+#end##
\ No newline at end of file