aboutsummaryrefslogtreecommitdiff
path: root/velocity-engine-core/src/main/java/org/apache/velocity/util/DuckType.java
diff options
context:
space:
mode:
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.java326
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;
+ }
+
+}