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 --> ClassMap object.
+ */
+ private final Map classMapCache = new HashMap();
+
+ /**
+ * Holds the field maps for the classes we know about. Map: Class --> 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 --> ClassMap object.
- */
- private final Map classMapCache = new HashMap();
-
- /**
- * Holds the field maps for the classes we know about. Map: Class --> 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->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> </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> </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> </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> </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> </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> </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> </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> </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> </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