aboutsummaryrefslogtreecommitdiff
path: root/velocity-engine-core/src
diff options
context:
space:
mode:
authorClaude Brisson <cbrisson@apache.org>2019-02-26 14:30:46 +0000
committerClaude Brisson <cbrisson@apache.org>2019-02-26 14:30:46 +0000
commit1a19a60c60668035f33e893bbf9fcd7552275c0b (patch)
treeac1648c055f25a24d7ce07d9fcf30ebd44251095 /velocity-engine-core/src
parentb408079d78c15edc4bfc9e342019252caf4ef19c (diff)
parent733a45e075f17bf0c91b3531a95b77a928ae5211 (diff)
downloadapache-velocity-engine-1a19a60c60668035f33e893bbf9fcd7552275c0b.tar.gz
[engine] Merge branch VELOCITY-892
git-svn-id: https://svn.apache.org/repos/asf/velocity/engine/trunk@1854386 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'velocity-engine-core/src')
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeConstants.java30
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeServices.java2
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/Pair.java79
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ClassMap.java6
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ConversionHandler.java9
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ConversionHandlerImpl.java625
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectionUtils.java281
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/Introspector.java2
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectorBase.java6
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectorCache.java4
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/MethodMap.java164
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/TypeConversionHandler.java67
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/TypeConversionHandlerImpl.java658
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/UberspectImpl.java105
-rw-r--r--velocity-engine-core/src/main/resources/org/apache/velocity/runtime/defaults/velocity.properties2
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/ConversionHandlerTestCase.java115
16 files changed, 1211 insertions, 944 deletions
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 59a10d5e..3ffa3362 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
@@ -252,22 +252,16 @@ public interface RuntimeConstants
/*
* ----------------------------------------------------------------------
- * G E N E R A L R U N T I M E C O N F I G U R A T I O N
+ * I N T R O S P E C T I O N C O N F I G U R A T I O N
* ----------------------------------------------------------------------
*/
- /** Switch for the interpolation facility for string literals. */
- String INTERPOLATE_STRINGLITERALS = "runtime.interpolate.string.literals";
-
- /** The character encoding for the templates. Used by the parser in processing the input streams. */
- String INPUT_ENCODING = "input.encoding";
-
- /** Default Encoding is UTF-8. */
- String ENCODING_DEFAULT = "UTF-8";
-
/** key name for uberspector. Multiple classnames can be specified,in which case uberspectors will be chained. */
String UBERSPECT_CLASSNAME = "runtime.introspector.uberspect";
+ /** key for Conversion Manager instance */
+ String CONVERSION_HANDLER_INSTANCE = "runtime.conversion.handler.instance";
+
/** key for Conversion Manager class */
String CONVERSION_HANDLER_CLASS = "runtime.conversion.handler.class";
@@ -277,6 +271,22 @@ public interface RuntimeConstants
/** A comma separated list of classes to restrict access to in the SecureIntrospector. */
String INTROSPECTOR_RESTRICT_CLASSES = "introspector.restrict.classes";
+
+ /*
+ * ----------------------------------------------------------------------
+ * G E N E R A L R U N T I M E C O N F I G U R A T I O N
+ * ----------------------------------------------------------------------
+ */
+
+ /** Switch for the interpolation facility for string literals. */
+ String INTERPOLATE_STRINGLITERALS = "runtime.interpolate.string.literals";
+
+ /** The character encoding for the templates. Used by the parser in processing the input streams. */
+ String INPUT_ENCODING = "input.encoding";
+
+ /** Default Encoding is UTF-8. */
+ String ENCODING_DEFAULT = "UTF-8";
+
/** Switch for ignoring nulls in math equations vs throwing exceptions. */
String STRICT_MATH = "runtime.strict.math";
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 eadf03a0..7c317321 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
@@ -110,7 +110,7 @@ public interface RuntimeServices
* @param key
* @param value
*/
- void addProperty(String key, Object value);
+ void addProperty(String key, Object value);
/**
* Clear the values pertaining to a particular
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
deleted file mode 100644
index 8d4ce92c..00000000
--- a/velocity-engine-core/src/main/java/org/apache/velocity/util/Pair.java
+++ /dev/null
@@ -1,79 +0,0 @@
-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 fe7416bc..021c3291 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
@@ -73,7 +73,7 @@ public class ClassMap
* @param conversionHandler conversion handler
* @since 2.0
*/
- public ClassMap(final Class clazz, final Logger log, final ConversionHandler conversionHandler)
+ public ClassMap(final Class clazz, final Logger log, final TypeConversionHandler conversionHandler)
{
this.clazz = clazz;
this.log = log;
@@ -121,7 +121,7 @@ public class ClassMap
* are taken from all the public methods
* that our class, its parents and their implemented interfaces provide.
*/
- private MethodCache createMethodCache(ConversionHandler conversionHandler)
+ private MethodCache createMethodCache(TypeConversionHandler conversionHandler)
{
MethodCache methodCache = new MethodCache(log, conversionHandler);
//
@@ -231,7 +231,7 @@ public class ClassMap
/** Map of methods that are searchable according to method parameters to find a match */
private final MethodMap methodMap;
- private MethodCache(Logger log, ConversionHandler conversionHandler)
+ private MethodCache(Logger log, TypeConversionHandler conversionHandler)
{
this.log = log;
methodMap = new MethodMap(conversionHandler);
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
index f52eec1b..d38bc7a7 100644
--- 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
@@ -27,13 +27,17 @@ package org.apache.velocity.util.introspection;
*
* @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
* @version $Id: ConversionHandler.java $
+ * @deprecated use {@link TypeConversionHandler}
+ * @see TypeConversionHandler
* @since 2.0
*/
+@Deprecated
public interface ConversionHandler
{
/**
* Check to see if the conversion can be done using an explicit conversion
+ *
* @param formal expected formal type
* @param actual provided argument type
* @return null if no conversion is needed, or the appropriate Converter object
@@ -55,10 +59,11 @@ public interface ConversionHandler
/**
* Add the given converter to the handler. Implementation should be thread-safe.
*
- * @param formal expected formal type
- * @param actual provided argument type
+ * @param formal expected formal type
+ * @param actual provided argument type
* @param converter converter
* @since 2.0
*/
void addConverter(Class formal, Class actual, Converter converter);
}
+
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
deleted file mode 100644
index 23ceb88f..00000000
--- a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ConversionHandlerImpl.java
+++ /dev/null
@@ -1,625 +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.apache.commons.lang3.LocaleUtils;
-import org.apache.velocity.util.Pair;
-
-import java.util.HashMap;
-import java.util.Locale;
-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<>();
-
- 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.class, Number.class), numberToBool);
- standardConverterMap.put(new Pair<>(Boolean.class, Byte.TYPE), numberToBool);
- standardConverterMap.put(new Pair<>(Boolean.class, Short.TYPE), numberToBool);
- standardConverterMap.put(new Pair<>(Boolean.class, Integer.TYPE), numberToBool);
- standardConverterMap.put(new Pair<>(Boolean.class, Long.TYPE), numberToBool);
- standardConverterMap.put(new Pair<>(Boolean.class, Float.TYPE), numberToBool);
- standardConverterMap.put(new Pair<>(Boolean.class, Double.TYPE), 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);
- standardConverterMap.put(new Pair<>(Boolean.TYPE, Number.class), numberToBool);
- standardConverterMap.put(new Pair<>(Boolean.TYPE, Byte.TYPE), numberToBool);
- standardConverterMap.put(new Pair<>(Boolean.TYPE, Short.TYPE), numberToBool);
- standardConverterMap.put(new Pair<>(Boolean.TYPE, Integer.TYPE), numberToBool);
- standardConverterMap.put(new Pair<>(Boolean.TYPE, Long.TYPE), numberToBool);
- standardConverterMap.put(new Pair<>(Boolean.TYPE, Float.TYPE), numberToBool);
- standardConverterMap.put(new Pair<>(Boolean.TYPE, Double.TYPE), numberToBool);
-
- /* character -> boolean */
- Converter<Boolean> charToBoolean = new Converter<Boolean>()
- {
- @Override
- public Boolean convert(Object o)
- {
- return o == null ? null : (Character) o != 0;
- }
- };
- standardConverterMap.put(new Pair<>(Boolean.class, Character.class), charToBoolean);
- standardConverterMap.put(new Pair<>(Boolean.class, Character.TYPE), charToBoolean);
- standardConverterMap.put(new Pair<>(Boolean.TYPE, Character.class), charToBoolean);
- standardConverterMap.put(new Pair<>(Boolean.TYPE, Character.TYPE), 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.class, Number.class), narrowingToByte);
- standardConverterMap.put(new Pair<>(Byte.class, Short.TYPE), narrowingToByte);
- standardConverterMap.put(new Pair<>(Byte.class, Integer.TYPE), narrowingToByte);
- standardConverterMap.put(new Pair<>(Byte.class, Long.TYPE), narrowingToByte);
- standardConverterMap.put(new Pair<>(Byte.class, Float.TYPE), narrowingToByte);
- standardConverterMap.put(new Pair<>(Byte.class, Double.TYPE), 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);
- standardConverterMap.put(new Pair<>(Byte.TYPE, Number.class), narrowingToByte);
- standardConverterMap.put(new Pair<>(Byte.TYPE, Short.TYPE), narrowingToByte);
- standardConverterMap.put(new Pair<>(Byte.TYPE, Integer.TYPE), narrowingToByte);
- standardConverterMap.put(new Pair<>(Byte.TYPE, Long.TYPE), narrowingToByte);
- standardConverterMap.put(new Pair<>(Byte.TYPE, Float.TYPE), narrowingToByte);
- standardConverterMap.put(new Pair<>(Byte.TYPE, Double.TYPE), 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.class, Number.class), narrowingToShort);
- standardConverterMap.put(new Pair<>(Short.class, Integer.TYPE), narrowingToShort);
- standardConverterMap.put(new Pair<>(Short.class, Long.TYPE), narrowingToShort);
- standardConverterMap.put(new Pair<>(Short.class, Float.TYPE), narrowingToShort);
- standardConverterMap.put(new Pair<>(Short.class, Double.TYPE), 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);
- standardConverterMap.put(new Pair<>(Short.TYPE, Number.class), narrowingToShort);
- standardConverterMap.put(new Pair<>(Short.TYPE, Integer.TYPE), narrowingToShort);
- standardConverterMap.put(new Pair<>(Short.TYPE, Long.TYPE), narrowingToShort);
- standardConverterMap.put(new Pair<>(Short.TYPE, Float.TYPE), narrowingToShort);
- standardConverterMap.put(new Pair<>(Short.TYPE, Double.TYPE), 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.class, Number.class), narrowingToInteger);
- standardConverterMap.put(new Pair<>(Integer.class, Long.TYPE), narrowingToInteger);
- standardConverterMap.put(new Pair<>(Integer.class, Float.TYPE), narrowingToInteger);
- standardConverterMap.put(new Pair<>(Integer.class, Double.TYPE), 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);
- standardConverterMap.put(new Pair<>(Integer.TYPE, Number.class), narrowingToInteger);
- standardConverterMap.put(new Pair<>(Integer.TYPE, Long.TYPE), narrowingToInteger);
- standardConverterMap.put(new Pair<>(Integer.TYPE, Float.TYPE), narrowingToInteger);
- standardConverterMap.put(new Pair<>(Integer.TYPE, Double.TYPE), narrowingToInteger);
-
- /* widening towards Integer */
- Converter<Integer> wideningToInteger = new Converter<Integer>()
- {
- @Override
- public Integer convert(Object o)
- {
- if (o == null) return null;
- return ((Number) o).intValue();
- }
- };
- standardConverterMap.put(new Pair<>(Integer.class, Short.class), wideningToInteger);
- standardConverterMap.put(new Pair<>(Integer.class, Short.TYPE), wideningToInteger);
-
- /* 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.class, Number.class), narrowingToLong);
- standardConverterMap.put(new Pair<>(Long.class, Float.TYPE), narrowingToLong);
- standardConverterMap.put(new Pair<>(Long.class, Double.TYPE), narrowingToLong);
- standardConverterMap.put(new Pair<>(Long.TYPE, Float.class), narrowingToLong);
- standardConverterMap.put(new Pair<>(Long.TYPE, Double.class), narrowingToLong);
- standardConverterMap.put(new Pair<>(Long.TYPE, Number.class), narrowingToLong);
- standardConverterMap.put(new Pair<>(Long.TYPE, Float.TYPE), narrowingToLong);
- standardConverterMap.put(new Pair<>(Long.TYPE, Double.TYPE), narrowingToLong);
-
- /* widening towards Long */
- Converter<Long> wideningToLong = new Converter<Long>()
- {
- @Override
- public Long convert(Object o)
- {
- if (o == null) return null;
- return ((Number) o).longValue();
- }
- };
- standardConverterMap.put(new Pair<>(Long.class, Short.class), wideningToLong);
- standardConverterMap.put(new Pair<>(Long.class, Integer.class), wideningToLong);
- standardConverterMap.put(new Pair<>(Long.class, Short.TYPE), wideningToLong);
- standardConverterMap.put(new Pair<>(Long.class, Integer.TYPE), wideningToLong);
-
- /* 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.class, Number.class), narrowingToFloat);
- standardConverterMap.put(new Pair<>(Float.class, Double.TYPE), narrowingToFloat);
- standardConverterMap.put(new Pair<>(Float.TYPE, Double.class), narrowingToFloat);
- standardConverterMap.put(new Pair<>(Float.TYPE, Number.class), narrowingToFloat);
- standardConverterMap.put(new Pair<>(Float.TYPE, Double.TYPE), narrowingToFloat);
-
- /* exact towards Float */
- Converter<Float> toFloat = new Converter<Float>()
- {
- @Override
- public Float convert(Object o)
- {
- if (o == null) return null;
- return ((Number) o).floatValue();
- }
- };
- standardConverterMap.put(new Pair<>(Float.class, Short.class), toFloat);
- standardConverterMap.put(new Pair<>(Float.class, Integer.class), toFloat);
- standardConverterMap.put(new Pair<>(Float.class, Long.class), toFloat);
- standardConverterMap.put(new Pair<>(Float.class, Short.TYPE), toFloat);
- standardConverterMap.put(new Pair<>(Float.class, Integer.TYPE), toFloat);
- standardConverterMap.put(new Pair<>(Float.class, Long.TYPE), toFloat);
-
- /* 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);
-
- /* exact or widening towards Double */
- Converter<Double> toDouble = new Converter<Double>()
- {
- @Override
- public Double convert(Object o)
- {
- if (o == null) return null;
- return ((Number) o).doubleValue();
- }
- };
- standardConverterMap.put(new Pair<>(Double.class, Short.class), toDouble);
- standardConverterMap.put(new Pair<>(Double.class, Integer.class), toDouble);
- standardConverterMap.put(new Pair<>(Double.class, Long.class), toDouble);
- standardConverterMap.put(new Pair<>(Double.class, Float.class), toDouble);
- standardConverterMap.put(new Pair<>(Double.class, Number.class), toDouble);
- standardConverterMap.put(new Pair<>(Double.class, Short.TYPE), toDouble);
- standardConverterMap.put(new Pair<>(Double.class, Integer.TYPE), toDouble);
- standardConverterMap.put(new Pair<>(Double.class, Long.TYPE), toDouble);
- standardConverterMap.put(new Pair<>(Double.class, Float.TYPE), toDouble);
- standardConverterMap.put(new Pair<>(Double.TYPE, Number.class), toDouble);
-
- /* 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 ? (byte)1 : (byte)0;
- }
- };
- standardConverterMap.put(new Pair<>(Byte.class, Boolean.class), booleanToByte);
- standardConverterMap.put(new Pair<>(Byte.class, Boolean.TYPE), booleanToByte);
- standardConverterMap.put(new Pair<>(Byte.TYPE, Boolean.class), booleanToByte);
- standardConverterMap.put(new Pair<>(Byte.TYPE, Boolean.TYPE), booleanToByte);
-
- /* boolean to short */
- Converter<Short> booleanToShort = new Converter<Short>()
- {
- @Override
- public Short convert(Object o)
- {
- return o == null ? null : (Boolean) o ? (short)1 : (short)0;
- }
- };
- standardConverterMap.put(new Pair<>(Short.class, Boolean.class), booleanToShort);
- standardConverterMap.put(new Pair<>(Short.class, Boolean.TYPE), booleanToShort);
- standardConverterMap.put(new Pair<>(Short.TYPE, Boolean.class), booleanToShort);
- standardConverterMap.put(new Pair<>(Short.TYPE, Boolean.TYPE), booleanToShort);
-
- /* boolean to integer */
- Converter<Integer> booleanToInteger = new Converter<Integer>()
- {
- @Override
- public Integer convert(Object o)
- {
- return o == null ? null : (Boolean) o ? (Integer)1 : (Integer)0;
- }
- };
- standardConverterMap.put(new Pair<>(Integer.class, Boolean.class), booleanToInteger);
- standardConverterMap.put(new Pair<>(Integer.class, Boolean.TYPE), booleanToInteger);
- standardConverterMap.put(new Pair<>(Integer.TYPE, Boolean.class), booleanToInteger);
- standardConverterMap.put(new Pair<>(Integer.TYPE, Boolean.TYPE), booleanToInteger);
-
- /* boolean to long */
- Converter<Long> booleanToLong = new Converter<Long>()
- {
- @Override
- public Long convert(Object o)
- {
- return o == null ? null : (Boolean) o ? 1L : 0L;
- }
- };
- standardConverterMap.put(new Pair<>(Long.class, Boolean.class), booleanToLong);
- standardConverterMap.put(new Pair<>(Long.class, Boolean.TYPE), booleanToLong);
- standardConverterMap.put(new Pair<>(Long.TYPE, Boolean.class), booleanToLong);
- standardConverterMap.put(new Pair<>(Long.TYPE, Boolean.TYPE), booleanToLong);
-
- /* to string */
- toString = new Converter<String>()
- {
- @Override
- public String convert(Object o)
- {
- return String.valueOf(o);
- }
- };
-
- /* string to locale */
- Converter<Locale> stringToLocale = new Converter<Locale>()
- {
- @Override
- public Locale convert(Object o)
- {
- return o == null ? null : LocaleUtils.toLocale((String)o);
- }
- };
- standardConverterMap.put(new Pair<>(Locale.class, String.class), stringToLocale);
- }
-
- /**
- * Constructor
- */
- public ConversionHandlerImpl()
- {
- converterCacheMap = new ConcurrentHashMap<>();
- }
-
- /**
- * 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)
- {
- /*
- * for consistency, we also have to check standard implicit convertibility
- * since it may not have been checked before by the calling code
- */
- if (formal == actual ||
- IntrospectionUtils.isMethodInvocationConvertible(formal, actual, possibleVarArg) ||
- 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/IntrospectionUtils.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectionUtils.java
index 38dcfde0..78e0ad79 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,14 @@ package org.apache.velocity.util.introspection;
* under the License.
*/
+import org.apache.commons.lang3.reflect.TypeUtils;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@@ -66,7 +74,7 @@ public class IntrospectionUtils
* @param clazz input class
* @return boxed class
*/
- static Class getBoxedClass(Class clazz)
+ public static Class getBoxedClass(Class clazz)
{
Class boxed = boxingMap.get(clazz);
return boxed == null ? clazz : boxed;
@@ -77,16 +85,52 @@ public class IntrospectionUtils
* @param clazz input class
* @return unboxed class
*/
- static Class getUnboxedClass(Class clazz)
+ public static Class getUnboxedClass(Class clazz)
{
Class unboxed = unboxingMap.get(clazz);
return unboxed == null ? clazz : unboxed;
}
/**
- *
+ * returns the Class corresponding to a Type, if possible
+ * @param type the input Type
+ * @return found Class, if any
*/
-
+ public static Class getTypeClass(Type type)
+ {
+ if (type == null)
+ {
+ return null;
+ }
+ if (type instanceof Class)
+ {
+ return (Class)type;
+ }
+ else if (type instanceof ParameterizedType)
+ {
+ return (Class)((ParameterizedType)type).getRawType();
+ }
+ else if (type instanceof GenericArrayType)
+ {
+ Type componentType = ((GenericArrayType)type).getGenericComponentType();
+ Class componentClass = getTypeClass(componentType);
+ if (componentClass != null)
+ {
+ return Array.newInstance(componentClass, 0).getClass();
+ }
+ }
+ else if (type instanceof TypeVariable)
+ {
+ Type[] bounds = TypeUtils.getImplicitBounds((TypeVariable)type);
+ if (bounds.length == 1) return getTypeClass(bounds[0]);
+ }
+ else if (type instanceof WildcardType)
+ {
+ Type[] bounds = TypeUtils.getImplicitUpperBounds((WildcardType)type);
+ if (bounds.length == 1) return getTypeClass(bounds[0]);
+ }
+ return null;
+ }
/**
* Determines whether a type represented by a class object is
@@ -108,92 +152,105 @@ public class IntrospectionUtils
* type or an object type of a primitive type that can be converted to
* the formal type.
*/
- public static boolean isMethodInvocationConvertible(Class formal,
+ public static boolean isMethodInvocationConvertible(Type formal,
Class actual,
boolean possibleVarArg)
{
- /* if it's a null, it means the arg was null */
- if (actual == null)
+ Class formalClass = getTypeClass(formal);
+ if (formalClass != null)
{
- return !formal.isPrimitive();
- }
+ /* if it's a null, it means the arg was null */
+ if (actual == null)
+ {
+ return !formalClass.isPrimitive();
+ }
- /* Check for identity or widening reference conversion */
- if (formal.isAssignableFrom(actual))
- {
- return true;
- }
+ /* Check for identity or widening reference conversion */
+ if (formalClass.isAssignableFrom(actual))
+ {
+ return true;
+ }
- /* 2.0: Since MethodMap's comparison functions now use this method with potentially reversed arguments order,
- * actual can be a primitive type. */
+ /* 2.0: Since MethodMap's comparison functions now use this method with potentially reversed arguments order,
+ * actual can be a primitive type. */
- /* Check for boxing */
- if (!formal.isPrimitive() && actual.isPrimitive())
- {
- Class boxed = boxingMap.get(actual);
- if (boxed != null && boxed == formal || formal.isAssignableFrom(boxed)) return true;
- }
+ /* Check for boxing */
+ if (!formalClass.isPrimitive() && actual.isPrimitive())
+ {
+ Class boxed = boxingMap.get(actual);
+ if (boxed != null && boxed == formalClass || formalClass.isAssignableFrom(boxed)) return true;
+ }
- if (formal.isPrimitive())
- {
- if (actual.isPrimitive())
+ if (formalClass.isPrimitive())
{
- /* check for widening primitive conversion */
- if (formal == Short.TYPE && actual == Byte.TYPE)
- return true;
- if (formal == Integer.TYPE && (
+ if (actual.isPrimitive())
+ {
+ /* check for widening primitive conversion */
+ if (formalClass == Short.TYPE && actual == Byte.TYPE)
+ return true;
+ if (formalClass == Integer.TYPE && (
actual == Byte.TYPE || actual == Short.TYPE))
- return true;
- if (formal == Long.TYPE && (
+ return true;
+ if (formalClass == Long.TYPE && (
actual == Byte.TYPE || actual == Short.TYPE || actual == Integer.TYPE))
- return true;
- if (formal == Float.TYPE && (
+ return true;
+ if (formalClass == Float.TYPE && (
actual == Byte.TYPE || actual == Short.TYPE || actual == Integer.TYPE ||
- actual == Long.TYPE))
- return true;
- if (formal == Double.TYPE && (
+ actual == Long.TYPE))
+ return true;
+ if (formalClass == Double.TYPE && (
actual == Byte.TYPE || actual == Short.TYPE || actual == Integer.TYPE ||
- actual == Long.TYPE || actual == Float.TYPE))
- return true;
- }
- else
- {
- /* Check for unboxing with widening primitive conversion. */
- if (formal == Boolean.TYPE && actual == Boolean.class)
- return true;
- if (formal == Character.TYPE && actual == Character.class)
- return true;
- if (formal == Byte.TYPE && actual == Byte.class)
- return true;
- if (formal == Short.TYPE && (actual == Short.class || actual == Byte.class))
- return true;
- if (formal == Integer.TYPE && (actual == Integer.class || actual == Short.class ||
+ actual == Long.TYPE || actual == Float.TYPE))
+ return true;
+ } else
+ {
+ /* Check for unboxing with widening primitive conversion. */
+ if (formalClass == Boolean.TYPE && actual == Boolean.class)
+ return true;
+ if (formalClass == Character.TYPE && actual == Character.class)
+ return true;
+ if (formalClass == Byte.TYPE && actual == Byte.class)
+ return true;
+ if (formalClass == Short.TYPE && (actual == Short.class || actual == Byte.class))
+ return true;
+ if (formalClass == Integer.TYPE && (actual == Integer.class || actual == Short.class ||
actual == Byte.class))
- return true;
- if (formal == Long.TYPE && (actual == Long.class || actual == Integer.class ||
+ return true;
+ if (formalClass == Long.TYPE && (actual == Long.class || actual == Integer.class ||
actual == Short.class || actual == Byte.class))
- return true;
- if (formal == Float.TYPE && (actual == Float.class || actual == Long.class ||
+ return true;
+ if (formalClass == Float.TYPE && (actual == Float.class || actual == Long.class ||
actual == Integer.class || actual == Short.class || actual == Byte.class))
- return true;
- if (formal == Double.TYPE && (actual == Double.class || actual == Float.class ||
+ return true;
+ if (formalClass == Double.TYPE && (actual == Double.class || actual == Float.class ||
actual == Long.class || actual == Integer.class || actual == Short.class ||
actual == Byte.class))
- return true;
+ return true;
+ }
}
- }
- /* Check for vararg conversion. */
- if (possibleVarArg && formal.isArray())
+ /* Check for vararg conversion. */
+ if (possibleVarArg && formalClass.isArray())
+ {
+ if (actual.isArray())
+ {
+ actual = actual.getComponentType();
+ }
+ return isMethodInvocationConvertible(formalClass.getComponentType(),
+ actual, false);
+ }
+ return false;
+ }
+ else
{
- if (actual.isArray())
+ // no distinction between strict and implicit, not a big deal in this case
+ if (TypeUtils.isAssignable(actual, formal))
{
- actual = actual.getComponentType();
+ return true;
}
- return isMethodInvocationConvertible(formal.getComponentType(),
- actual, false);
+ return possibleVarArg && TypeUtils.isArrayType(formal) &&
+ TypeUtils.isAssignable(actual, TypeUtils.getArrayComponentType(formal));
}
- return false;
}
/**
@@ -212,55 +269,69 @@ public class IntrospectionUtils
* or formal and actual are both primitive types and actual can be
* subject to widening conversion to formal.
*/
- public static boolean isStrictMethodInvocationConvertible(Class formal,
+ public static boolean isStrictMethodInvocationConvertible(Type formal,
Class actual,
boolean possibleVarArg)
{
- /* Check for nullity */
- if (actual == null)
- {
- return !formal.isPrimitive();
- }
-
- /* Check for identity or widening reference conversion */
- if(formal.isAssignableFrom(actual))
+ Class formalClass = getTypeClass(formal);
+ if (formalClass != null)
{
- return true;
- }
+ /* Check for nullity */
+ if (actual == null)
+ {
+ return !formalClass.isPrimitive();
+ }
- /* Check for widening primitive conversion. */
- if(formal.isPrimitive())
- {
- if(formal == Short.TYPE && (actual == Byte.TYPE))
- return true;
- if(formal == Integer.TYPE &&
- (actual == Short.TYPE || actual == Byte.TYPE))
- return true;
- if(formal == Long.TYPE &&
- (actual == Integer.TYPE || actual == Short.TYPE ||
- actual == Byte.TYPE))
- return true;
- if(formal == Float.TYPE &&
- (actual == Long.TYPE || actual == Integer.TYPE ||
- actual == Short.TYPE || actual == Byte.TYPE))
- return true;
- if(formal == Double.TYPE &&
- (actual == Float.TYPE || actual == Long.TYPE ||
- actual == Integer.TYPE || actual == Short.TYPE ||
- actual == Byte.TYPE))
+ /* Check for identity or widening reference conversion */
+ if (formalClass.isAssignableFrom(actual))
+ {
return true;
- }
+ }
- /* Check for vararg conversion. */
- if (possibleVarArg && formal.isArray())
+ /* Check for widening primitive conversion. */
+ if (formalClass.isPrimitive())
+ {
+ if (formal == Short.TYPE && (actual == Byte.TYPE))
+ return true;
+ if (formal == Integer.TYPE &&
+ (actual == Short.TYPE || actual == Byte.TYPE))
+ return true;
+ if (formal == Long.TYPE &&
+ (actual == Integer.TYPE || actual == Short.TYPE ||
+ actual == Byte.TYPE))
+ return true;
+ if (formal == Float.TYPE &&
+ (actual == Long.TYPE || actual == Integer.TYPE ||
+ actual == Short.TYPE || actual == Byte.TYPE))
+ return true;
+ if (formal == Double.TYPE &&
+ (actual == Float.TYPE || actual == Long.TYPE ||
+ actual == Integer.TYPE || actual == Short.TYPE ||
+ actual == Byte.TYPE))
+ return true;
+ }
+
+ /* Check for vararg conversion. */
+ if (possibleVarArg && formalClass.isArray())
+ {
+ if (actual.isArray())
+ {
+ actual = actual.getComponentType();
+ }
+ return isStrictMethodInvocationConvertible(formalClass.getComponentType(),
+ actual, false);
+ }
+ return false;
+ }
+ else
{
- if (actual.isArray())
+ // no distinction between strict and implicit, not a big deal in this case
+ if (TypeUtils.isAssignable(actual, formal))
{
- actual = actual.getComponentType();
+ return true;
}
- return isStrictMethodInvocationConvertible(formal.getComponentType(),
- actual, false);
+ return possibleVarArg && TypeUtils.isArrayType(formal) &&
+ TypeUtils.isAssignable(actual, TypeUtils.getArrayComponentType(formal));
}
- return false;
}
}
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 1407242a..512d434e 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
@@ -68,7 +68,7 @@ public class Introspector extends IntrospectorBase
* @param conversionHandler conversion handler
* @since 2.0
*/
- public Introspector(final Logger log, ConversionHandler conversionHandler)
+ public Introspector(final Logger log, TypeConversionHandler 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 e98ec35b..654a66b6 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
@@ -62,17 +62,13 @@ public abstract class IntrospectorBase
/** The Introspector Cache */
private final IntrospectorCache introspectorCache;
- /** The Conversion handler */
- private final ConversionHandler conversionHandler;
-
/**
* C'tor.
*/
- protected IntrospectorBase(final Logger log, final ConversionHandler conversionHandler)
+ protected IntrospectorBase(final Logger log, final TypeConversionHandler conversionHandler)
{
this.log = log;
introspectorCache = new IntrospectorCache(log, conversionHandler);
- this.conversionHandler = conversionHandler;
}
/**
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 742dcc3b..ae59f15a 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
@@ -68,12 +68,12 @@ public final class IntrospectorCache
/**
* Conversion handler
*/
- private final ConversionHandler conversionHandler;
+ private final TypeConversionHandler conversionHandler;
/**
* C'tor
*/
- public IntrospectorCache(final Logger log, final ConversionHandler conversionHandler)
+ public IntrospectorCache(final Logger log, final TypeConversionHandler conversionHandler)
{
this.log = log;
this.conversionHandler = conversionHandler;
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 3fcdccee..f9f1e000 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,9 +19,11 @@ package org.apache.velocity.util.introspection;
* under the License.
*/
+import org.apache.commons.lang3.reflect.TypeUtils;
import org.apache.velocity.exception.VelocityException;
import java.lang.reflect.Method;
+import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
@@ -54,7 +56,7 @@ public class MethodMap
private static final int IMPLCITLY_CONVERTIBLE = 2;
private static final int STRICTLY_CONVERTIBLE = 3;
- ConversionHandler conversionHandler;
+ TypeConversionHandler conversionHandler;
/**
* Default constructor
@@ -69,7 +71,7 @@ public class MethodMap
* @param conversionHandler conversion handler
* @since 2.0
*/
- public MethodMap(ConversionHandler conversionHandler)
+ public MethodMap(TypeConversionHandler conversionHandler)
{
this.conversionHandler = conversionHandler;
}
@@ -173,7 +175,7 @@ public class MethodMap
Method method;
/* cache arguments classes array */
- Class[] methodTypes;
+ Type[] methodTypes;
/* specificity: how does the best match compare to provided arguments
* one one LESS_SPECIFIC, MORE_SPECIFIC or INCOMPARABLE */
@@ -190,9 +192,9 @@ public class MethodMap
{
this.method = method;
this.applicability = applicability;
- this.methodTypes = method.getParameterTypes();
+ this.methodTypes = method.getGenericParameterTypes();
this.specificity = compare(methodTypes, unboxedArgs);
- this.varargs = methodTypes.length > 0 && methodTypes[methodTypes.length - 1].isArray();
+ this.varargs = methodTypes.length > 0 && TypeUtils.isArrayType(methodTypes[methodTypes.length - 1]);
}
}
@@ -322,74 +324,74 @@ public class MethodMap
/**
* Determines which method signature (represented by a class array) is more
* specific. This defines a partial ordering on the method signatures.
- * @param c1 first signature to compare
- * @param c2 second signature to compare
+ * @param t1 first signature to compare
+ * @param t2 second signature to compare
* @return MORE_SPECIFIC if c1 is more specific than c2, LESS_SPECIFIC if
* c1 is less specific than c2, INCOMPARABLE if they are incomparable.
*/
- private int compare(Class[] c1, Class[] c2)
+ private int compare(Type[] t1, Type[] t2)
{
- boolean c1IsVararag = false;
- boolean c2IsVararag = false;
+ boolean t1IsVararag = false;
+ boolean t2IsVararag = false;
boolean fixedLengths = false;
// compare lengths to handle comparisons where the size of the arrays
// doesn't match, but the methods are both applicable due to the fact
// that one is a varargs method
- if (c1.length > c2.length)
+ if (t1.length > t2.length)
{
- int l2 = c2.length;
+ int l2 = t2.length;
if (l2 == 0)
{
return MORE_SPECIFIC;
}
- c2 = Arrays.copyOf(c2, c1.length);
- Class itemClass = c2[l2 - 1].getComponentType();
+ t2 = Arrays.copyOf(t2, t1.length);
+ Type itemType = TypeUtils.getArrayComponentType(t2[l2 - 1]);
/* if item class is null, then it implies the vaarg is #1
* (and receives an empty array)
*/
- if (itemClass == null)
+ if (itemType == null)
{
/* by construct, we have c1.length = l2 + 1 */
- c1IsVararag = true;
- c2[c1.length - 1] = null;
+ t1IsVararag = true;
+ t2[t1.length - 1] = null;
}
else
{
- c2IsVararag = true;
- for (int i = l2 - 1; i < c1.length; ++i)
+ t2IsVararag = true;
+ for (int i = l2 - 1; i < t1.length; ++i)
{
/* also overwrite the vaargs itself */
- c2[i] = itemClass;
+ t2[i] = itemType;
}
}
fixedLengths = true;
}
- else if (c2.length > c1.length)
+ else if (t2.length > t1.length)
{
- int l1 = c1.length;
+ int l1 = t1.length;
if (l1 == 0)
{
return LESS_SPECIFIC;
}
- c1 = Arrays.copyOf(c1, c2.length);
- Class itemClass = c1[l1 - 1].getComponentType();
+ t1 = Arrays.copyOf(t1, t2.length);
+ Type itemType = TypeUtils.getArrayComponentType(t1[l1 - 1]);
/* if item class is null, then it implies the vaarg is #2
* (and receives an empty array)
*/
- if (itemClass == null)
+ if (itemType == null)
{
/* by construct, we have c2.length = l1 + 1 */
- c2IsVararag = true;
- c1[c2.length - 1] = null;
+ t2IsVararag = true;
+ t1[t2.length - 1] = null;
}
else
{
- c1IsVararag = true;
- for (int i = l1 - 1; i < c2.length; ++i)
+ t1IsVararag = true;
+ for (int i = l1 - 1; i < t2.length; ++i)
{
/* also overwrite the vaargs itself */
- c1[i] = itemClass;
+ t1[i] = itemType;
}
}
fixedLengths = true;
@@ -398,52 +400,72 @@ public class MethodMap
/* ok, move on and compare those of equal lengths */
int fromC1toC2 = STRICTLY_CONVERTIBLE;
int fromC2toC1 = STRICTLY_CONVERTIBLE;
- for(int i = 0; i < c1.length; ++i)
+ for(int i = 0; i < t1.length; ++i)
{
- boolean last = !fixedLengths && (i == c1.length - 1);
- if (c1[i] != c2[i])
+ Class c1 = t1[i] == null ? null : IntrospectionUtils.getTypeClass(t1[i]);
+ Class c2 = t2[i] == null ? null : IntrospectionUtils.getTypeClass(t2[i]);
+ boolean last = !fixedLengths && (i == t1.length - 1);
+ if (t1[i] == null && t2[i] != null || t1[i] != null && t2[i] == null || !t1[i].equals(t2[i]))
{
- if (c1[i] == null)
+ if (t1[i] == null)
{
fromC2toC1 = NOT_CONVERTIBLE;
- if (c2[i].isPrimitive())
+ if (c2 != null && c2.isPrimitive())
{
fromC1toC2 = NOT_CONVERTIBLE;
}
}
- else if (c2[i] == null)
+ else if (t2[i] == null)
{
fromC1toC2 = NOT_CONVERTIBLE;
- if (c1[i].isPrimitive())
+ if (c1 != null && c1.isPrimitive())
{
fromC2toC1 = NOT_CONVERTIBLE;
}
}
else
{
- switch (fromC1toC2)
+ if (c1 != null)
{
- case STRICTLY_CONVERTIBLE:
- if (isStrictConvertible(c2[i], c1[i], last)) break;
- fromC1toC2 = IMPLCITLY_CONVERTIBLE;
- case IMPLCITLY_CONVERTIBLE:
- if (isConvertible(c2[i], c1[i], last)) break;
- fromC1toC2 = EXPLICITLY_CONVERTIBLE;
- case EXPLICITLY_CONVERTIBLE:
- if (isExplicitlyConvertible(c2[i], c1[i], last)) break;
- fromC1toC2 = NOT_CONVERTIBLE;
+ switch (fromC1toC2)
+ {
+ case STRICTLY_CONVERTIBLE:
+ if (isStrictConvertible(t2[i], c1, last)) break;
+ fromC1toC2 = IMPLCITLY_CONVERTIBLE;
+ case IMPLCITLY_CONVERTIBLE:
+ if (isConvertible(t2[i], c1, last)) break;
+ fromC1toC2 = EXPLICITLY_CONVERTIBLE;
+ case EXPLICITLY_CONVERTIBLE:
+ if (isExplicitlyConvertible(t2[i], c1, last)) break;
+ fromC1toC2 = NOT_CONVERTIBLE;
+ }
+ }
+ else if (fromC1toC2 > NOT_CONVERTIBLE)
+ {
+ fromC1toC2 = TypeUtils.isAssignable(t1[i], t2[i]) ?
+ Math.min(fromC1toC2, IMPLCITLY_CONVERTIBLE) :
+ NOT_CONVERTIBLE;
+ }
+ if (c2 != null)
+ {
+ switch (fromC2toC1)
+ {
+ case STRICTLY_CONVERTIBLE:
+ if (isStrictConvertible(t1[i], c2, last)) break;
+ fromC2toC1 = IMPLCITLY_CONVERTIBLE;
+ case IMPLCITLY_CONVERTIBLE:
+ if (isConvertible(t1[i], c2, last)) break;
+ fromC2toC1 = EXPLICITLY_CONVERTIBLE;
+ case EXPLICITLY_CONVERTIBLE:
+ if (isExplicitlyConvertible(t1[i], c2, last)) break;
+ fromC2toC1 = NOT_CONVERTIBLE;
+ }
}
- switch (fromC2toC1)
+ else if (fromC2toC1 > NOT_CONVERTIBLE)
{
- case STRICTLY_CONVERTIBLE:
- if (isStrictConvertible(c1[i], c2[i], last)) break;
- fromC2toC1 = IMPLCITLY_CONVERTIBLE;
- case IMPLCITLY_CONVERTIBLE:
- if (isConvertible(c1[i], c2[i], last)) break;
- fromC2toC1 = EXPLICITLY_CONVERTIBLE;
- case EXPLICITLY_CONVERTIBLE:
- if (isExplicitlyConvertible(c1[i], c2[i], last)) break;
- fromC2toC1 = NOT_CONVERTIBLE;
+ fromC2toC1 = TypeUtils.isAssignable(t2[i], t1[i]) ?
+ Math.min(fromC2toC1, IMPLCITLY_CONVERTIBLE) :
+ NOT_CONVERTIBLE;
}
}
}
@@ -472,8 +494,8 @@ public class MethodMap
* If one method accepts varargs and the other does not,
* call the non-vararg one more specific.
*/
- boolean last1Array = c1IsVararag || !fixedLengths && c1[c1.length - 1].isArray();
- boolean last2Array = c2IsVararag || !fixedLengths && c2[c2.length - 1].isArray();
+ boolean last1Array = t1IsVararag || !fixedLengths && TypeUtils.isArrayType (t1[t1.length - 1]);
+ boolean last2Array = t2IsVararag || !fixedLengths && TypeUtils.isArrayType(t2[t2.length - 1]);
if (last1Array && !last2Array)
{
return LESS_SPECIFIC;
@@ -499,14 +521,13 @@ public class MethodMap
*/
private int getApplicability(Method method, Class[] classes)
{
- Class[] methodArgs = method.getParameterTypes();
+ Type[] methodArgs = method.getGenericParameterTypes();
int ret = STRICTLY_CONVERTIBLE;
if (methodArgs.length > classes.length)
{
// if there's just one more methodArg than class arg
// and the last methodArg is an array, then treat it as a vararg
- if (methodArgs.length == classes.length + 1 &&
- methodArgs[methodArgs.length - 1].isArray())
+ if (methodArgs.length == classes.length + 1 && TypeUtils.isArrayType(methodArgs[methodArgs.length - 1]))
{
// all the args preceding the vararg must match
for (int i = 0; i < classes.length; i++)
@@ -541,13 +562,14 @@ public class MethodMap
// (e.g. String when the method is expecting String...)
for(int i = 0; i < classes.length; ++i)
{
- if (!isStrictConvertible(methodArgs[i], classes[i], i == classes.length - 1 && methodArgs[i].isArray()))
+ boolean possibleVararg = i == classes.length - 1 && TypeUtils.isArrayType(methodArgs[i]);
+ if (!isStrictConvertible(methodArgs[i], classes[i], possibleVararg))
{
- if (isConvertible(methodArgs[i], classes[i], i == classes.length - 1 && methodArgs[i].isArray()))
+ if (isConvertible(methodArgs[i], classes[i], possibleVararg))
{
ret = Math.min(ret, IMPLCITLY_CONVERTIBLE);
}
- else if (isExplicitlyConvertible(methodArgs[i], classes[i], i == classes.length - 1 && methodArgs[i].isArray()))
+ else if (isExplicitlyConvertible(methodArgs[i], classes[i], possibleVararg))
{
ret = Math.min(ret, EXPLICITLY_CONVERTIBLE);
}
@@ -562,8 +584,8 @@ public class MethodMap
else if (methodArgs.length > 0) // more arguments given than the method accepts; check for varargs
{
// check that the last methodArg is an array
- Class lastarg = methodArgs[methodArgs.length - 1];
- if (!lastarg.isArray())
+ Type lastarg = methodArgs[methodArgs.length - 1];
+ if (!TypeUtils.isArrayType(lastarg))
{
return NOT_CONVERTIBLE;
}
@@ -589,7 +611,7 @@ public class MethodMap
}
// check that all remaining arguments are convertible to the vararg type
- Class vararg = lastarg.getComponentType();
+ Type vararg = TypeUtils.getArrayComponentType(lastarg);
for (int i = methodArgs.length - 1; i < classes.length; ++i)
{
if (!isStrictConvertible(vararg, classes[i], false))
@@ -621,7 +643,7 @@ public class MethodMap
* @param possibleVarArg
* @return convertible
*/
- private boolean isConvertible(Class formal, Class actual, boolean possibleVarArg)
+ private boolean isConvertible(Type formal, Class actual, boolean possibleVarArg)
{
return IntrospectionUtils.
isMethodInvocationConvertible(formal, actual, possibleVarArg);
@@ -636,7 +658,7 @@ public class MethodMap
* @param possibleVarArg
* @return convertible
*/
- private static boolean isStrictConvertible(Class formal, Class actual, boolean possibleVarArg)
+ private static boolean isStrictConvertible(Type formal, Class actual, boolean possibleVarArg)
{
return IntrospectionUtils.
isStrictMethodInvocationConvertible(formal, actual, possibleVarArg);
@@ -650,7 +672,7 @@ public class MethodMap
* @param possibleVarArg
* @return
*/
- private boolean isExplicitlyConvertible(Class formal, Class actual, boolean possibleVarArg)
+ private boolean isExplicitlyConvertible(Type formal, Class actual, boolean possibleVarArg)
{
return conversionHandler != null && conversionHandler.isExplicitlyConvertible(formal, actual, possibleVarArg);
}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/TypeConversionHandler.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/TypeConversionHandler.java
new file mode 100644
index 00000000..0df26147
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/TypeConversionHandler.java
@@ -0,0 +1,67 @@
+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 java.lang.reflect.Type;
+
+/**
+ * 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>isExplicitlyConvertible</code> returned true with the same arguments.
+ *
+ * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
+ * @version $Id: ConversionHandler.java $
+ * @since 2.1
+ */
+
+public interface TypeConversionHandler
+{
+ /**
+ * 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.1
+ */
+ boolean isExplicitlyConvertible(Type 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.1
+ */
+ Converter getNeededConverter(Type formal, 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.1
+ */
+ void addConverter(Type formal, Class actual, Converter converter);
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/TypeConversionHandlerImpl.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/TypeConversionHandlerImpl.java
new file mode 100644
index 00000000..9ee2c61d
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/TypeConversionHandlerImpl.java
@@ -0,0 +1,658 @@
+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.commons.lang3.LocaleUtils;
+import org.apache.commons.lang3.reflect.TypeUtils;
+import org.apache.commons.lang3.tuple.Pair;
+
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 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: TypeConversionHandlerImpl.java $
+ * @since 2.0
+ */
+
+public class TypeConversionHandlerImpl implements TypeConversionHandler
+{
+ /**
+ * standard narrowing and string parsing conversions.
+ */
+ static Map<Pair<String, String>, 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<String, String>, Converter> converterCacheMap;
+
+ static final String BOOLEAN_TYPE = "boolean";
+ static final String BYTE_TYPE = "byte";
+ static final String SHORT_TYPE = "short";
+ static final String INTEGER_TYPE = "int";
+ static final String LONG_TYPE = "long";
+ static final String FLOAT_TYPE = "float";
+ static final String DOUBLE_TYPE = "double";
+ static final String CHARACTER_TYPE = "char";
+ static final String BOOLEAN_CLASS = "java.lang.Boolean";
+ static final String BYTE_CLASS = "java.lang.Byte";
+ static final String SHORT_CLASS = "java.lang.Short";
+ static final String INTEGER_CLASS = "java.lang.Integer";
+ static final String LONG_CLASS = "java.lang.Long";
+ static final String FLOAT_CLASS = "java.lang.Float";
+ static final String DOUBLE_CLASS = "java.lang.Double";
+ static final String NUMBER_CLASS = "java.lang.Number";
+ static final String CHARACTER_CLASS = "java.lang.Character";
+ static final String STRING_CLASS = "java.lang.String";
+ static final String LOCALE_CLASS = "java.util.Locale";
+
+ static
+ {
+ standardConverterMap = new HashMap<>();
+
+ 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(Pair.of(BOOLEAN_CLASS, BYTE_CLASS), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_CLASS, SHORT_CLASS), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_CLASS, INTEGER_CLASS), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_CLASS, LONG_CLASS), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_CLASS, FLOAT_CLASS), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_CLASS, DOUBLE_CLASS), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_CLASS, NUMBER_CLASS), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_CLASS, BYTE_TYPE), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_CLASS, SHORT_TYPE), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_CLASS, INTEGER_TYPE), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_CLASS, LONG_TYPE), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_CLASS, FLOAT_TYPE), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_CLASS, DOUBLE_TYPE), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_TYPE, BYTE_CLASS), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_TYPE, SHORT_CLASS), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_TYPE, INTEGER_CLASS), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_TYPE, LONG_CLASS), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_TYPE, FLOAT_CLASS), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_TYPE, DOUBLE_CLASS), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_TYPE, NUMBER_CLASS), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_TYPE, BYTE_TYPE), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_TYPE, SHORT_TYPE), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_TYPE, INTEGER_TYPE), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_TYPE, LONG_TYPE), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_TYPE, FLOAT_TYPE), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_TYPE, DOUBLE_TYPE), numberToBool);
+
+ /* character -> boolean */
+ Converter<Boolean> charToBoolean = new Converter<Boolean>()
+ {
+ @Override
+ public Boolean convert(Object o)
+ {
+ return o == null ? null : (Character) o != 0;
+ }
+ };
+ standardConverterMap.put(Pair.of(BOOLEAN_CLASS, CHARACTER_CLASS), charToBoolean);
+ standardConverterMap.put(Pair.of(BOOLEAN_CLASS, CHARACTER_TYPE), charToBoolean);
+ standardConverterMap.put(Pair.of(BOOLEAN_TYPE, CHARACTER_CLASS), charToBoolean);
+ standardConverterMap.put(Pair.of(BOOLEAN_TYPE, CHARACTER_TYPE), charToBoolean);
+
+ /* string -> boolean */
+ Converter<Boolean> stringToBoolean = new Converter<Boolean>()
+ {
+ @Override
+ public Boolean convert(Object o)
+ {
+ return Boolean.valueOf(String.valueOf(o));
+ }
+ };
+ standardConverterMap.put(Pair.of(BOOLEAN_CLASS, STRING_CLASS), stringToBoolean);
+ standardConverterMap.put(Pair.of(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(Pair.of(BYTE_CLASS, SHORT_CLASS), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_CLASS, INTEGER_CLASS), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_CLASS, LONG_CLASS), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_CLASS, FLOAT_CLASS), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_CLASS, DOUBLE_CLASS), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_CLASS, NUMBER_CLASS), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_CLASS, SHORT_TYPE), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_CLASS, INTEGER_TYPE), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_CLASS, LONG_TYPE), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_CLASS, FLOAT_TYPE), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_CLASS, DOUBLE_TYPE), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_TYPE, SHORT_CLASS), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_TYPE, INTEGER_CLASS), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_TYPE, LONG_CLASS), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_TYPE, FLOAT_CLASS), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_TYPE, DOUBLE_CLASS), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_TYPE, NUMBER_CLASS), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_TYPE, SHORT_TYPE), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_TYPE, INTEGER_TYPE), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_TYPE, LONG_TYPE), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_TYPE, FLOAT_TYPE), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_TYPE, DOUBLE_TYPE), narrowingToByte);
+
+ /* string to byte */
+ Converter<Byte> stringToByte = new Converter<Byte>()
+ {
+ @Override
+ public Byte convert(Object o)
+ {
+ return Byte.valueOf(String.valueOf(o));
+ }
+ };
+ standardConverterMap.put(Pair.of(BYTE_CLASS, STRING_CLASS), stringToByte);
+ standardConverterMap.put(Pair.of(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(Pair.of(SHORT_CLASS, INTEGER_CLASS), narrowingToShort);
+ standardConverterMap.put(Pair.of(SHORT_CLASS, LONG_CLASS), narrowingToShort);
+ standardConverterMap.put(Pair.of(SHORT_CLASS, FLOAT_CLASS), narrowingToShort);
+ standardConverterMap.put(Pair.of(SHORT_CLASS, DOUBLE_CLASS), narrowingToShort);
+ standardConverterMap.put(Pair.of(SHORT_CLASS, NUMBER_CLASS), narrowingToShort);
+ standardConverterMap.put(Pair.of(SHORT_CLASS, INTEGER_TYPE), narrowingToShort);
+ standardConverterMap.put(Pair.of(SHORT_CLASS, LONG_TYPE), narrowingToShort);
+ standardConverterMap.put(Pair.of(SHORT_CLASS, FLOAT_TYPE), narrowingToShort);
+ standardConverterMap.put(Pair.of(SHORT_CLASS, DOUBLE_TYPE), narrowingToShort);
+ standardConverterMap.put(Pair.of(SHORT_TYPE, INTEGER_CLASS), narrowingToShort);
+ standardConverterMap.put(Pair.of(SHORT_TYPE, LONG_CLASS), narrowingToShort);
+ standardConverterMap.put(Pair.of(SHORT_TYPE, FLOAT_CLASS), narrowingToShort);
+ standardConverterMap.put(Pair.of(SHORT_TYPE, DOUBLE_CLASS), narrowingToShort);
+ standardConverterMap.put(Pair.of(SHORT_TYPE, NUMBER_CLASS), narrowingToShort);
+ standardConverterMap.put(Pair.of(SHORT_TYPE, INTEGER_TYPE), narrowingToShort);
+ standardConverterMap.put(Pair.of(SHORT_TYPE, LONG_TYPE), narrowingToShort);
+ standardConverterMap.put(Pair.of(SHORT_TYPE, FLOAT_TYPE), narrowingToShort);
+ standardConverterMap.put(Pair.of(SHORT_TYPE, DOUBLE_TYPE), narrowingToShort);
+
+ /* string to short */
+ Converter<Short> stringToShort = new Converter<Short>()
+ {
+ @Override
+ public Short convert(Object o)
+ {
+ return Short.valueOf(String.valueOf(o));
+ }
+ };
+ standardConverterMap.put(Pair.of(SHORT_CLASS, STRING_CLASS), stringToShort);
+ standardConverterMap.put(Pair.of(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(Pair.of(INTEGER_CLASS, LONG_CLASS), narrowingToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_CLASS, FLOAT_CLASS), narrowingToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_CLASS, DOUBLE_CLASS), narrowingToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_CLASS, NUMBER_CLASS), narrowingToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_CLASS, LONG_TYPE), narrowingToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_CLASS, FLOAT_TYPE), narrowingToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_CLASS, DOUBLE_TYPE), narrowingToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_TYPE, LONG_CLASS), narrowingToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_TYPE, FLOAT_CLASS), narrowingToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_TYPE, DOUBLE_CLASS), narrowingToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_TYPE, NUMBER_CLASS), narrowingToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_TYPE, LONG_TYPE), narrowingToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_TYPE, FLOAT_TYPE), narrowingToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_TYPE, DOUBLE_TYPE), narrowingToInteger);
+
+ /* widening towards Integer */
+ Converter<Integer> wideningToInteger = new Converter<Integer>()
+ {
+ @Override
+ public Integer convert(Object o)
+ {
+ if (o == null) return null;
+ return ((Number) o).intValue();
+ }
+ };
+ standardConverterMap.put(Pair.of(INTEGER_CLASS, SHORT_CLASS), wideningToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_CLASS, SHORT_TYPE), wideningToInteger);
+
+ /* string to int */
+ Converter<Integer> stringToInteger = new Converter<Integer>()
+ {
+ @Override
+ public Integer convert(Object o)
+ {
+ return Integer.valueOf(String.valueOf(o));
+ }
+ };
+ standardConverterMap.put(Pair.of(INTEGER_CLASS, STRING_CLASS), stringToInteger);
+ standardConverterMap.put(Pair.of(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(Pair.of(LONG_CLASS, FLOAT_CLASS), narrowingToLong);
+ standardConverterMap.put(Pair.of(LONG_CLASS, DOUBLE_CLASS), narrowingToLong);
+ standardConverterMap.put(Pair.of(LONG_CLASS, NUMBER_CLASS), narrowingToLong);
+ standardConverterMap.put(Pair.of(LONG_CLASS, FLOAT_TYPE), narrowingToLong);
+ standardConverterMap.put(Pair.of(LONG_CLASS, DOUBLE_TYPE), narrowingToLong);
+ standardConverterMap.put(Pair.of(LONG_TYPE, FLOAT_CLASS), narrowingToLong);
+ standardConverterMap.put(Pair.of(LONG_TYPE, DOUBLE_CLASS), narrowingToLong);
+ standardConverterMap.put(Pair.of(LONG_TYPE, NUMBER_CLASS), narrowingToLong);
+ standardConverterMap.put(Pair.of(LONG_TYPE, FLOAT_TYPE), narrowingToLong);
+ standardConverterMap.put(Pair.of(LONG_TYPE, DOUBLE_TYPE), narrowingToLong);
+
+ /* widening towards Long */
+ Converter<Long> wideningToLong = new Converter<Long>()
+ {
+ @Override
+ public Long convert(Object o)
+ {
+ if (o == null) return null;
+ return ((Number) o).longValue();
+ }
+ };
+ standardConverterMap.put(Pair.of(LONG_CLASS, SHORT_CLASS), wideningToLong);
+ standardConverterMap.put(Pair.of(LONG_CLASS, INTEGER_CLASS), wideningToLong);
+ standardConverterMap.put(Pair.of(LONG_CLASS, SHORT_TYPE), wideningToLong);
+ standardConverterMap.put(Pair.of(LONG_CLASS, INTEGER_TYPE), wideningToLong);
+
+ /* string to long */
+ Converter<Long> stringToLong = new Converter<Long>()
+ {
+ @Override
+ public Long convert(Object o)
+ {
+ return Long.valueOf(String.valueOf(o));
+ }
+ };
+ standardConverterMap.put(Pair.of(LONG_CLASS, STRING_CLASS), stringToLong);
+ standardConverterMap.put(Pair.of(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(Pair.of(FLOAT_CLASS, DOUBLE_CLASS), narrowingToFloat);
+ standardConverterMap.put(Pair.of(FLOAT_CLASS, NUMBER_CLASS), narrowingToFloat);
+ standardConverterMap.put(Pair.of(FLOAT_CLASS, DOUBLE_TYPE), narrowingToFloat);
+ standardConverterMap.put(Pair.of(FLOAT_TYPE, DOUBLE_CLASS), narrowingToFloat);
+ standardConverterMap.put(Pair.of(FLOAT_TYPE, NUMBER_CLASS), narrowingToFloat);
+ standardConverterMap.put(Pair.of(FLOAT_TYPE, DOUBLE_TYPE), narrowingToFloat);
+
+ /* exact towards Float */
+ Converter<Float> toFloat = new Converter<Float>()
+ {
+ @Override
+ public Float convert(Object o)
+ {
+ if (o == null) return null;
+ return ((Number) o).floatValue();
+ }
+ };
+ standardConverterMap.put(Pair.of(FLOAT_CLASS, SHORT_CLASS), toFloat);
+ standardConverterMap.put(Pair.of(FLOAT_CLASS, INTEGER_CLASS), toFloat);
+ standardConverterMap.put(Pair.of(FLOAT_CLASS, LONG_CLASS), toFloat);
+ standardConverterMap.put(Pair.of(FLOAT_CLASS, SHORT_TYPE), toFloat);
+ standardConverterMap.put(Pair.of(FLOAT_CLASS, INTEGER_TYPE), toFloat);
+ standardConverterMap.put(Pair.of(FLOAT_CLASS, LONG_TYPE), toFloat);
+
+ /* string to float */
+ Converter<Float> stringToFloat = new Converter<Float>()
+ {
+ @Override
+ public Float convert(Object o)
+ {
+ return Float.valueOf(String.valueOf(o));
+ }
+ };
+ standardConverterMap.put(Pair.of(FLOAT_CLASS, STRING_CLASS), stringToFloat);
+ standardConverterMap.put(Pair.of(FLOAT_TYPE, STRING_CLASS), stringToFloat);
+
+ /* exact or widening towards Double */
+ Converter<Double> toDouble = new Converter<Double>()
+ {
+ @Override
+ public Double convert(Object o)
+ {
+ if (o == null) return null;
+ return ((Number) o).doubleValue();
+ }
+ };
+ standardConverterMap.put(Pair.of(DOUBLE_CLASS, SHORT_CLASS), toDouble);
+ standardConverterMap.put(Pair.of(DOUBLE_CLASS, INTEGER_CLASS), toDouble);
+ standardConverterMap.put(Pair.of(DOUBLE_CLASS, LONG_CLASS), toDouble);
+ standardConverterMap.put(Pair.of(DOUBLE_CLASS, FLOAT_CLASS), toDouble);
+ standardConverterMap.put(Pair.of(DOUBLE_CLASS, NUMBER_CLASS), toDouble);
+ standardConverterMap.put(Pair.of(DOUBLE_CLASS, SHORT_TYPE), toDouble);
+ standardConverterMap.put(Pair.of(DOUBLE_CLASS, INTEGER_TYPE), toDouble);
+ standardConverterMap.put(Pair.of(DOUBLE_CLASS, LONG_TYPE), toDouble);
+ standardConverterMap.put(Pair.of(DOUBLE_CLASS, FLOAT_TYPE), toDouble);
+ standardConverterMap.put(Pair.of(DOUBLE_TYPE, NUMBER_CLASS), toDouble);
+
+ /* string to double */
+ Converter<Double> stringToDouble = new Converter<Double>()
+ {
+ @Override
+ public Double convert(Object o)
+ {
+ return Double.valueOf(String.valueOf(o));
+ }
+ };
+ standardConverterMap.put(Pair.of(DOUBLE_CLASS, STRING_CLASS), stringToDouble);
+ standardConverterMap.put(Pair.of(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 ? (byte)1 : (byte)0;
+ }
+ };
+ standardConverterMap.put(Pair.of(BYTE_CLASS, BOOLEAN_CLASS), booleanToByte);
+ standardConverterMap.put(Pair.of(BYTE_CLASS, BOOLEAN_TYPE), booleanToByte);
+ standardConverterMap.put(Pair.of(BYTE_TYPE, BOOLEAN_CLASS), booleanToByte);
+ standardConverterMap.put(Pair.of(BYTE_TYPE, BOOLEAN_TYPE), booleanToByte);
+
+ /* boolean to short */
+ Converter<Short> booleanToShort = new Converter<Short>()
+ {
+ @Override
+ public Short convert(Object o)
+ {
+ return o == null ? null : (Boolean) o ? (short)1 : (short)0;
+ }
+ };
+ standardConverterMap.put(Pair.of(SHORT_CLASS, BOOLEAN_CLASS), booleanToShort);
+ standardConverterMap.put(Pair.of(SHORT_CLASS, BOOLEAN_TYPE), booleanToShort);
+ standardConverterMap.put(Pair.of(SHORT_TYPE, BOOLEAN_CLASS), booleanToShort);
+ standardConverterMap.put(Pair.of(SHORT_TYPE, BOOLEAN_TYPE), booleanToShort);
+
+ /* boolean to integer */
+ Converter<Integer> booleanToInteger = new Converter<Integer>()
+ {
+ @Override
+ public Integer convert(Object o)
+ {
+ return o == null ? null : (Boolean) o ? (Integer)1 : (Integer)0;
+ }
+ };
+ standardConverterMap.put(Pair.of(INTEGER_CLASS, BOOLEAN_CLASS), booleanToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_CLASS, BOOLEAN_TYPE), booleanToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_TYPE, BOOLEAN_CLASS), booleanToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_TYPE, BOOLEAN_TYPE), booleanToInteger);
+
+ /* boolean to lonf */
+ Converter<Long> booleanToLong = new Converter<Long>()
+ {
+ @Override
+ public Long convert(Object o)
+ {
+ return o == null ? null : (Boolean) o ? 1L : 0L;
+ }
+ };
+ standardConverterMap.put(Pair.of(LONG_CLASS, BOOLEAN_CLASS), booleanToLong);
+ standardConverterMap.put(Pair.of(LONG_CLASS, BOOLEAN_TYPE), booleanToLong);
+ standardConverterMap.put(Pair.of(LONG_TYPE, BOOLEAN_CLASS), booleanToLong);
+ standardConverterMap.put(Pair.of(LONG_TYPE, BOOLEAN_TYPE), booleanToLong);
+
+ /* to string */
+ toString = new Converter<String>()
+ {
+ @Override
+ public String convert(Object o)
+ {
+ return String.valueOf(o);
+ }
+ };
+
+ /* string to locale */
+ Converter<Locale> stringToLocale = new Converter<Locale>()
+ {
+ @Override
+ public Locale convert(Object o)
+ {
+ return o == null ? null : LocaleUtils.toLocale((String)o);
+ }
+ };
+ standardConverterMap.put(Pair.of(LOCALE_CLASS, STRING_CLASS), stringToLocale);
+ }
+
+ /**
+ * Constructor
+ */
+ public TypeConversionHandlerImpl()
+ {
+ converterCacheMap = new ConcurrentHashMap<>();
+ }
+
+ /**
+ * Check to see if the conversion can be done using an explicit conversion
+ * @param actual found argument type
+ * @param formal expected formal type
+ * @return true if actual class can be explicitely converted to expected formal type
+ * @since 2.1
+ */
+ @Override
+ public boolean isExplicitlyConvertible(Type formal, Class actual, boolean possibleVarArg)
+ {
+ /*
+ * for consistency, we also have to check standard implicit convertibility
+ * since it may not have been checked before by the calling code
+ */
+ Class formalClass = IntrospectionUtils.getTypeClass(formal);
+ if (formalClass != null && formalClass == actual ||
+ IntrospectionUtils.isMethodInvocationConvertible(formal, actual, possibleVarArg) ||
+ getNeededConverter(formal, actual) != null)
+ {
+ return true;
+ }
+
+ /* Check var arg */
+ if (possibleVarArg && TypeUtils.isArrayType(formal))
+ {
+ if (actual.isArray())
+ {
+ actual = actual.getComponentType();
+ }
+ return isExplicitlyConvertible(TypeUtils.getArrayComponentType(formal), 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.1
+ */
+ @Override
+ public Converter getNeededConverter(Type formal, Class actual)
+ {
+ if (actual == null)
+ {
+ return null;
+ }
+ Pair<String, String> key = Pair.of(formal.getTypeName(), actual.getTypeName());
+
+ /* 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)
+ {
+ Class formalClass = IntrospectionUtils.getTypeClass(formal);
+ /* check for conversion towards string */
+ if (formal == String.class)
+ {
+ converter = toString;
+ }
+ /* check for String -> Enum constant conversion */
+ else if (formalClass != null && formalClass.isEnum() && actual == String.class)
+ {
+ final Class<Enum> enumClass = (Class<Enum>)formalClass;
+ converter = new Converter()
+ {
+ @Override
+ public Object convert(Object o)
+ {
+ return Enum.valueOf(enumClass, (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.1
+ */
+ @Override
+ public void addConverter(Type formal, Class actual, Converter converter)
+ {
+ Pair<String, String> key = Pair.of(formal.getTypeName(), actual.getTypeName());
+ converterCacheMap.put(key, converter);
+ Class formalClass = IntrospectionUtils.getTypeClass(formal);
+ if (formalClass != null)
+ {
+ if (formalClass.isPrimitive())
+ {
+ key = Pair.of(IntrospectionUtils.getBoxedClass(formalClass).getTypeName(), actual.getTypeName());
+ converterCacheMap.put(key, converter);
+ }
+ else
+ {
+ Class unboxedFormal = IntrospectionUtils.getUnboxedClass(formalClass);
+ if (unboxedFormal != formalClass)
+ {
+ key = Pair.of(unboxedFormal.getTypeName(), actual.getTypeName());
+ converterCacheMap.put(key, converter);
+ }
+ }
+ }
+ }
+}
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 cdda364d..03127892 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
@@ -19,6 +19,7 @@ package org.apache.velocity.util.introspection;
* under the License.
*/
+import org.apache.commons.lang3.Conversion;
import org.apache.velocity.exception.VelocityException;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.RuntimeServices;
@@ -42,6 +43,7 @@ import org.slf4j.Logger;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.lang.reflect.Type;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map;
@@ -69,7 +71,7 @@ public class UberspectImpl implements Uberspect, RuntimeServicesAware
/**
* the conversion handler
*/
- protected ConversionHandler conversionHandler;
+ protected TypeConversionHandler conversionHandler;
/**
* runtime services
@@ -86,7 +88,7 @@ public class UberspectImpl implements Uberspect, RuntimeServicesAware
introspector = new Introspector(log, conversionHandler);
}
- public ConversionHandler getConversionHandler()
+ public TypeConversionHandler getConversionHandler()
{
return conversionHandler;
}
@@ -95,51 +97,86 @@ public class UberspectImpl implements Uberspect, RuntimeServicesAware
* sets the runtime services
* @param rs runtime services
*/
+ @SuppressWarnings("deprecation")
public void setRuntimeServices(RuntimeServices rs)
{
rsvc = rs;
log = rsvc.getLog("introspection");
- String conversionHandlerClass = rs.getString(RuntimeConstants.CONVERSION_HANDLER_CLASS);
- if (conversionHandlerClass == null || conversionHandlerClass.equals("none"))
+ Object conversionHandlerInstance = rs.getProperty(RuntimeConstants.CONVERSION_HANDLER_INSTANCE);
+ if (conversionHandlerInstance == null)
{
- conversionHandler = null;
- }
- else
- {
- Object o = null;
-
- try
+ String conversionHandlerClass = rs.getString(RuntimeConstants.CONVERSION_HANDLER_CLASS);
+ if (conversionHandlerClass != null && !conversionHandlerClass.equals("none"))
{
- o = ClassUtils.getNewInstance(conversionHandlerClass);
- }
- catch (ClassNotFoundException cnfe )
- {
- String err = "The specified class for ConversionHandler (" + conversionHandlerClass
+ try
+ {
+ conversionHandlerInstance = ClassUtils.getNewInstance(conversionHandlerClass);
+ }
+ catch (ClassNotFoundException cnfe )
+ {
+ String err = "The specified class for ConversionHandler (" + conversionHandlerClass
+ ") does not exist or is not accessible to the current classloader.";
- log.error(err);
- throw new VelocityException(err, cnfe);
- }
- catch (InstantiationException ie)
- {
- throw new VelocityException("Could not instantiate class '" + conversionHandlerClass + "'", ie);
+ log.error(err);
+ throw new VelocityException(err, cnfe);
+ }
+ catch (InstantiationException ie)
+ {
+ throw new VelocityException("Could not instantiate class '" + conversionHandlerClass + "'", ie);
+ }
+ catch (IllegalAccessException ae)
+ {
+ throw new VelocityException("Cannot access class '" + conversionHandlerClass + "'", ae);
+ }
}
- catch (IllegalAccessException ae)
+ }
+
+ if (conversionHandlerInstance != null)
+ {
+ if (conversionHandlerInstance instanceof ConversionHandler)
{
- throw new VelocityException("Cannot access class '" + conversionHandlerClass + "'", ae);
- }
+ log.warn("The ConversionHandler interface is deprecated - see the TypeConversionHandler interface");
+ final ConversionHandler ch = (ConversionHandler)conversionHandlerInstance;
+ conversionHandler = new TypeConversionHandler()
+ {
+ @Override
+ public boolean isExplicitlyConvertible(Type formal, Class actual, boolean possibleVarArg)
+ {
+ Class formalClass = IntrospectionUtils.getTypeClass(formal);
+ if (formalClass != null) return ch.isExplicitlyConvertible(formalClass, actual, possibleVarArg);
+ else return false;
+ }
- if (!(o instanceof ConversionHandler))
+ @Override
+ public Converter getNeededConverter(Type formal, Class actual)
+ {
+ Class formalClass = IntrospectionUtils.getTypeClass(formal);
+ if (formalClass != null) return ch.getNeededConverter(formalClass, actual);
+ else return null;
+ }
+
+ @Override
+ public void addConverter(Type formal, Class actual, Converter converter)
+ {
+ Class formalClass = IntrospectionUtils.getTypeClass(formal);
+ if (formalClass != null) ch.addConverter(formalClass, actual, converter);
+ else throw new UnsupportedOperationException("This conversion handler doesn't know how to handle Type: " + formal.getTypeName());
+ }
+ };
+ }
+ else if (!(conversionHandlerInstance instanceof TypeConversionHandler))
{
- String err = "The specified class for ResourceManager (" + conversionHandlerClass
- + ") does not implement " + ConversionHandler.class.getName()
+ String err = "The specified class or provided instance for the conversion handler (" + conversionHandlerInstance.getClass().getName()
+ + ") does not implement " + TypeConversionHandler.class.getName()
+ "; Velocity is not initialized correctly.";
log.error(err);
throw new VelocityException(err);
}
-
- conversionHandler = (ConversionHandler) o;
+ else
+ {
+ conversionHandler = (TypeConversionHandler)conversionHandlerInstance;
+ }
}
}
@@ -256,7 +293,7 @@ public class UberspectImpl implements Uberspect, RuntimeServicesAware
Method m = introspector.getMethod(obj.getClass(), methodName, args);
if (m != null)
{
- return new VelMethodImpl(m, false, getNeededConverters(m.getParameterTypes(), args));
+ return new VelMethodImpl(m, false, getNeededConverters(m.getGenericParameterTypes(), args));
}
Class cls = obj.getClass();
@@ -269,7 +306,7 @@ public class UberspectImpl implements Uberspect, RuntimeServicesAware
{
// and create a method that knows to wrap the value
// before invoking the method
- return new VelMethodImpl(m, true, getNeededConverters(m.getParameterTypes(), args));
+ return new VelMethodImpl(m, true, getNeededConverters(m.getGenericParameterTypes(), args));
}
}
// watch for classes, to allow calling their static methods (VELOCITY-102)
@@ -278,7 +315,7 @@ public class UberspectImpl implements Uberspect, RuntimeServicesAware
m = introspector.getMethod((Class)obj, methodName, args);
if (m != null)
{
- return new VelMethodImpl(m, false, getNeededConverters(m.getParameterTypes(), args));
+ return new VelMethodImpl(m, false, getNeededConverters(m.getGenericParameterTypes(), args));
}
}
return null;
@@ -288,7 +325,7 @@ public class UberspectImpl implements Uberspect, RuntimeServicesAware
* get the list of needed converters to adapt passed argument types to method types
* @return null if not conversion needed, otherwise an array containing needed converters
*/
- private Converter[] getNeededConverters(Class[] expected, Object[] provided)
+ private Converter[] getNeededConverters(Type[] expected, Object[] provided)
{
if (conversionHandler == null) return null;
// var args are not handled here - CB TODO
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 ffc91fe0..b29d7e66 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
@@ -210,7 +210,7 @@ runtime.introspector.uberspect = org.apache.velocity.util.introspection.Uberspec
# Sets the data types Conversion Handler used by the default uberspector
# ----------------------------------------------------------------------------
-runtime.conversion.handler.class = org.apache.velocity.util.introspection.ConversionHandlerImpl
+runtime.conversion.handler.class = org.apache.velocity.util.introspection.TypeConversionHandlerImpl
# ----------------------------------------------------------------------------
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
index 6856e83e..13142141 100644
--- 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
@@ -19,6 +19,8 @@
package org.apache.velocity.test.util.introspection;
import junit.framework.TestSuite;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.reflect.TypeUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
@@ -28,11 +30,11 @@ import org.apache.velocity.context.Context;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.RuntimeInstance;
import org.apache.velocity.test.BaseTestCase;
-import org.apache.velocity.util.introspection.ConversionHandler;
-import org.apache.velocity.util.introspection.ConversionHandlerImpl;
import org.apache.velocity.util.introspection.Converter;
import org.apache.velocity.util.introspection.Info;
import org.apache.velocity.util.introspection.IntrospectionUtils;
+import org.apache.velocity.util.introspection.TypeConversionHandler;
+import org.apache.velocity.util.introspection.TypeConversionHandlerImpl;
import org.apache.velocity.util.introspection.Uberspect;
import org.apache.velocity.util.introspection.UberspectImpl;
@@ -41,6 +43,9 @@ import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.Writer;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
@@ -114,7 +119,7 @@ public class ConversionHandlerTestCase extends BaseTestCase
Uberspect uberspect = ve.getUberspect();
assertTrue(uberspect instanceof UberspectImpl);
UberspectImpl ui = (UberspectImpl)uberspect;
- ConversionHandler ch = ui.getConversionHandler();
+ TypeConversionHandler ch = ui.getConversionHandler();
assertTrue(ch != null);
ch.addConverter(Float.class, Obj.class, new Converter<Float>()
{
@@ -124,11 +129,87 @@ public class ConversionHandlerTestCase extends BaseTestCase
return 4.5f;
}
});
+ ch.addConverter(TypeUtils.parameterize(List.class, Integer.class), String.class, new Converter<List<Integer>>()
+ {
+ @Override
+ public List<Integer> convert(Object o)
+ {
+ return Arrays.<Integer>asList(1,2,3);
+ }
+ });
+ ch.addConverter(TypeUtils.parameterize(List.class, String.class), String.class, new Converter<List<String>>()
+ {
+ @Override
+ public List<String> convert(Object o)
+ {
+ return Arrays.<String>asList("a", "b", "c");
+ }
+ });
VelocityContext context = new VelocityContext();
context.put("obj", new Obj());
Writer writer = new StringWriter();
ve.evaluate(context, writer, "test", "$obj.integralFloat($obj) / $obj.objectFloat($obj)");
assertEquals("float ok: 4.5 / Float ok: 4.5", writer.toString());
+ writer = new StringWriter();
+ ve.evaluate(context, writer, "test", "$obj.iWantAStringList('anything')");
+ assertEquals("correct", writer.toString());
+ writer = new StringWriter();
+ ve.evaluate(context, writer, "test", "$obj.iWantAnIntegerList('anything')");
+ assertEquals("correct", writer.toString());
+ }
+
+ /* converts *everything* to string "foo" */
+ public static class MyCustomConverter implements TypeConversionHandler
+ {
+ Converter<String> myCustomConverter = new Converter<String>()
+ {
+
+ @Override
+ public String convert(Object o)
+ {
+ return "foo";
+ }
+ };
+
+ @Override
+ public boolean isExplicitlyConvertible(Type formal, Class actual, boolean possibleVarArg)
+ {
+ return true;
+ }
+
+ @Override
+ public Converter getNeededConverter(Type formal, Class actual)
+ {
+ return myCustomConverter;
+ }
+
+ @Override
+ public void addConverter(Type formal, Class actual, Converter converter)
+ {
+ throw new RuntimeException("not implemented");
+ }
+ }
+
+ public void testCustomConversionHandlerInstance()
+ {
+ RuntimeInstance ve = new RuntimeInstance();
+ ve.setProperty( Velocity.VM_PERM_INLINE_LOCAL, Boolean.TRUE);
+ ve.setProperty(Velocity.RUNTIME_LOG_INSTANCE, log);
+ ve.setProperty(RuntimeConstants.RESOURCE_LOADER, "file");
+ ve.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, TEST_COMPARE_DIR + "/conversion");
+ ve.setProperty(RuntimeConstants.CONVERSION_HANDLER_INSTANCE, new MyCustomConverter());
+ ve.init();
+ Uberspect uberspect = ve.getUberspect();
+ assertTrue(uberspect instanceof UberspectImpl);
+ UberspectImpl ui = (UberspectImpl)uberspect;
+ TypeConversionHandler ch = ui.getConversionHandler();
+ assertTrue(ch != null);
+ assertTrue(ch instanceof MyCustomConverter);
+ VelocityContext context = new VelocityContext();
+ context.put("obj", new Obj());
+ Writer writer = new StringWriter();
+ ve.evaluate(context, writer, "test", "$obj.objectString(1.0)");
+ assertEquals("String ok: foo", writer.toString());
}
/**
@@ -174,6 +255,15 @@ public class ConversionHandlerTestCase extends BaseTestCase
}
}
+ public void testOtherConversions() throws Exception
+ {
+ VelocityEngine ve = createEngine(false);
+ VelocityContext context = createContext();
+ StringWriter writer = new StringWriter();
+ ve.evaluate(context, writer,"test", "$strings.join(['foo', 'bar'], ',')");
+ assertEquals("foo,bar", writer.toString());
+ }
+
/**
* Return and initialize engine
* @return
@@ -264,6 +354,7 @@ public class ConversionHandlerTestCase extends BaseTestCase
};
context.put("types", types);
context.put("introspect", new Introspect());
+ context.put("strings", new StringUtils());
return context;
}
@@ -294,14 +385,28 @@ public class ConversionHandlerTestCase extends BaseTestCase
public String locale(Locale loc) { return "Locale ok: " + loc; }
public String toString() { return "instance of Obj"; }
+
+ public String iWantAStringList(List<String> list)
+ {
+ if (list != null && list.size() == 3 && list.get(0).equals("a") && list.get(1).equals("b") && list.get(2).equals("c"))
+ return "correct";
+ else return "wrong";
+ }
+
+ public String iWantAnIntegerList(List<Integer> list)
+ {
+ if (list != null && list.size() == 3 && list.get(0).equals(1) && list.get(1).equals(2) && list.get(2).equals(3))
+ return "correct";
+ else return "wrong";
+ }
}
public static class Introspect
{
- private ConversionHandler handler;
+ private TypeConversionHandler handler;
public Introspect()
{
- handler = new ConversionHandlerImpl();
+ handler = new TypeConversionHandlerImpl();
}
public boolean isStrictlyConvertible(Class expected, Class provided)
{