diff options
Diffstat (limited to 'velocity-engine-core/src/main/java/org/apache/velocity/util/DuckType.java')
-rw-r--r-- | velocity-engine-core/src/main/java/org/apache/velocity/util/DuckType.java | 326 |
1 files changed, 326 insertions, 0 deletions
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/DuckType.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/DuckType.java new file mode 100644 index 00000000..7eaddd32 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/DuckType.java @@ -0,0 +1,326 @@ +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. + */ + +import java.lang.reflect.Array; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Map; + +import static org.apache.velocity.runtime.parser.node.MathUtils.isZero; + +/** + * Support for getAs<java.lang.reflect.Type>() convention for rendering (String), evaluating (Boolean) + * or doing math with (Number) references. + * + * @author Nathan Bubna + * @since 2.0 + */ +public class DuckType +{ + protected enum Types + { + STRING("getAsString"), + NUMBER("getAsNumber"), + BOOLEAN("getAsBoolean"), + EMPTY("isEmpty"), + LENGTH("length"), + SIZE("size"); + + final String name; + final Map<Class<?>, Object> cache = new HashMap<>(); + + Types(String name) + { + this.name = name; + } + + void set(Class<?> c, Object o) + { + cache.put(c, o); + } + + Object get(Class<?> c) + { + return cache.get(c); + } + } + + protected static final Object NO_METHOD = new Object(); + + /** + * Clears the internal cache of all the underlying Types. + */ + public static void clearCache() + { + for(Types type : Types.values()) + { + type.cache.clear(); + } + } + + public static String asString(Object value) + { + return asString(value, true); + } + + public static String asString(Object value, boolean coerceType) + { + if (value == null) + { + return null; + } + if (value instanceof String) + { + return (String)value; + } + if (coerceType && value.getClass().isArray()) + { + // nicify arrays string representation + StringBuilder builder = new StringBuilder(); + builder.append('['); + int len = Array.getLength(value); + for (int i = 0; i < len; ++i) + { + if (i > 0) builder.append(", "); + builder.append(asString(Array.get(value, i))); + } + builder.append(']'); + return builder.toString(); + } + Object got = get(value, Types.STRING); + if (got == NO_METHOD) + { + return coerceType ? value.toString() : null; + } + return (String)got; + } + + public static boolean asNull(Object value) + { + return value == null || + get(value, Types.STRING) == null || + get(value, Types.NUMBER) == null; + } + + public static boolean asBoolean(Object value, boolean coerceType) + { + if (value == null) + { + return false; + } + if (value instanceof Boolean) + { + return (Boolean) value; + } + Object got = get(value, Types.BOOLEAN); + if (got != NO_METHOD) + { + return (Boolean) got; + } + if (coerceType) + { + return !asEmpty(value); + } + return true; + } + + // see VELOCITY-692 for discussion about empty values + public static boolean asEmpty(Object value) + { + // empty variable + if (value == null) + { + return true; + } + + // empty array + if (value.getClass().isArray()) + { + return Array.getLength(value) == 0;// [] is false + } + + // isEmpty() for object / string + Object isEmpty = get(value, Types.EMPTY); + if (isEmpty != NO_METHOD) + { + return (Boolean)isEmpty; + } + + // isEmpty() for object / other char sequences + Object length = get(value, Types.LENGTH); + if (length != NO_METHOD && length instanceof Number) + { + return isZero((Number)length); + } + + // size() object / collection + Object size = get(value, Types.SIZE); + if (size != NO_METHOD && size instanceof Number) + { + return isZero((Number)size); + } + + // zero numbers are false + if (value instanceof Number) + { + return isZero((Number)value); + } + + // null getAsString() + Object asString = get(value, Types.STRING); + if (asString == null) + { + return true;// duck null + } + // empty getAsString() + else if (asString != NO_METHOD) + { + return ((String)asString).length() == 0; + } + + // null getAsNumber() + Object asNumber = get(value, Types.NUMBER); + if (asNumber == null) + { + return true; + } + // zero numbers are false + else if (asNumber != NO_METHOD && asNumber instanceof Number) + { + return isZero((Number)asNumber); + } + + return false; + } + + public static Number asNumber(Object value) + { + return asNumber(value, true); + } + + public static Number asNumber(Object value, boolean coerceType) + { + if (value == null) + { + return null; + } + if (value instanceof Number) + { + return (Number)value; + } + Object got = get(value, Types.NUMBER); + if (got != NO_METHOD) + { + return (Number)got; + } + if (coerceType) + { + String string = asString(value);// coerce to string + if (string != null) + { + return new BigDecimal(string); + } + } + return null; + } + + protected static Object get(Object value, Types type) + { + try + { + // check cache + Class<?> c = value.getClass(); + Object cached = type.get(c); + if (cached == NO_METHOD) + { + return cached; + } + if (cached != null) + { + return ((Method)cached).invoke(value); + } + // ok, search the class + Method method = findMethod(c, type); + if (method == null) + { + type.set(c, NO_METHOD); + return NO_METHOD; + } + type.set(c, method); + return method.invoke(value); + } + catch (RuntimeException re) + { + throw re; + } + catch (Exception e) + { + throw new RuntimeException(e);// no checked exceptions, please + } + } + + protected static Method findMethod(Class<?> c, Types type) + { + if (c == null || c == Object.class) + { + return null; + } + Method m = getMethod(c, type.name); + if (m != null) + { + return m; + } + for (Class<?> i : c.getInterfaces()) + { + m = findMethod(i, type); + if (m != null) + { + return m; + } + } + m = findMethod(c.getSuperclass(), type); + if (m != null) + { + return m; + } + return null; + } + + private static Method getMethod(Class<?> c, String name) + { + if (Modifier.isPublic(c.getModifiers())) + { + try + { + Method m = c.getDeclaredMethod(name); + if (Modifier.isPublic(m.getModifiers())) + { + return m; + } + } + catch (NoSuchMethodException nsme) {} + } + return null; + } + +} |