org.apache.velocity.util.DuckType Maven / Gradle / Ivy
The newest version!
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, 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;
}
}