1 package org.apache.velocity.util; 2 3 /* 4 * Licensed to the Apache Software Foundation (ASF) under one 5 * or more contributor license agreements. See the NOTICE file 6 * distributed with this work for additional information 7 * regarding copyright ownership. The ASF licenses this file 8 * to you under the Apache License, Version 2.0 (the 9 * "License"); you may not use this file except in compliance 10 * with the License. You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, 15 * software distributed under the License is distributed on an 16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 * KIND, either express or implied. See the License for the 18 * specific language governing permissions and limitations 19 * under the License. 20 */ 21 22 import java.lang.reflect.Array; 23 import java.lang.reflect.Method; 24 import java.lang.reflect.Modifier; 25 import java.math.BigDecimal; 26 import java.util.HashMap; 27 import java.util.Map; 28 29 import static org.apache.velocity.runtime.parser.node.MathUtils.isZero; 30 31 /** 32 * Support for getAs<java.lang.reflect.Type>() convention for rendering (String), evaluating (Boolean) 33 * or doing math with (Number) references. 34 * 35 * @author Nathan Bubna 36 * @since 2.0 37 */ 38 public class DuckType 39 { 40 protected enum Types 41 { 42 STRING("getAsString"), 43 NUMBER("getAsNumber"), 44 BOOLEAN("getAsBoolean"), 45 EMPTY("isEmpty"), 46 LENGTH("length"), 47 SIZE("size"); 48 49 final String name; 50 final Map<Class<?>, Object> cache = new HashMap<>(); 51 Types(String name)52 Types(String name) 53 { 54 this.name = name; 55 } 56 set(Class<?> c, Object o)57 void set(Class<?> c, Object o) 58 { 59 cache.put(c, o); 60 } 61 get(Class<?> c)62 Object get(Class<?> c) 63 { 64 return cache.get(c); 65 } 66 } 67 68 protected static final Object NO_METHOD = new Object(); 69 70 /** 71 * Clears the internal cache of all the underlying Types. 72 */ clearCache()73 public static void clearCache() 74 { 75 for(Types type : Types.values()) 76 { 77 type.cache.clear(); 78 } 79 } 80 asString(Object value)81 public static String asString(Object value) 82 { 83 return asString(value, true); 84 } 85 asString(Object value, boolean coerceType)86 public static String asString(Object value, boolean coerceType) 87 { 88 if (value == null) 89 { 90 return null; 91 } 92 if (value instanceof String) 93 { 94 return (String)value; 95 } 96 if (coerceType && value.getClass().isArray()) 97 { 98 // nicify arrays string representation 99 StringBuilder builder = new StringBuilder(); 100 builder.append('['); 101 int len = Array.getLength(value); 102 for (int i = 0; i < len; ++i) 103 { 104 if (i > 0) builder.append(", "); 105 builder.append(asString(Array.get(value, i))); 106 } 107 builder.append(']'); 108 return builder.toString(); 109 } 110 Object got = get(value, Types.STRING); 111 if (got == NO_METHOD) 112 { 113 return coerceType ? value.toString() : null; 114 } 115 return (String)got; 116 } 117 asNull(Object value)118 public static boolean asNull(Object value) 119 { 120 return value == null || 121 get(value, Types.STRING) == null || 122 get(value, Types.NUMBER) == null; 123 } 124 asBoolean(Object value, boolean coerceType)125 public static boolean asBoolean(Object value, boolean coerceType) 126 { 127 if (value == null) 128 { 129 return false; 130 } 131 if (value instanceof Boolean) 132 { 133 return (Boolean) value; 134 } 135 Object got = get(value, Types.BOOLEAN); 136 if (got != NO_METHOD) 137 { 138 return (Boolean) got; 139 } 140 if (coerceType) 141 { 142 return !asEmpty(value); 143 } 144 return true; 145 } 146 147 // see VELOCITY-692 for discussion about empty values asEmpty(Object value)148 public static boolean asEmpty(Object value) 149 { 150 // empty variable 151 if (value == null) 152 { 153 return true; 154 } 155 156 // empty array 157 if (value.getClass().isArray()) 158 { 159 return Array.getLength(value) == 0;// [] is false 160 } 161 162 // isEmpty() for object / string 163 Object isEmpty = get(value, Types.EMPTY); 164 if (isEmpty != NO_METHOD) 165 { 166 return (Boolean)isEmpty; 167 } 168 169 // isEmpty() for object / other char sequences 170 Object length = get(value, Types.LENGTH); 171 if (length != NO_METHOD && length instanceof Number) 172 { 173 return isZero((Number)length); 174 } 175 176 // size() object / collection 177 Object size = get(value, Types.SIZE); 178 if (size != NO_METHOD && size instanceof Number) 179 { 180 return isZero((Number)size); 181 } 182 183 // zero numbers are false 184 if (value instanceof Number) 185 { 186 return isZero((Number)value); 187 } 188 189 // null getAsString() 190 Object asString = get(value, Types.STRING); 191 if (asString == null) 192 { 193 return true;// duck null 194 } 195 // empty getAsString() 196 else if (asString != NO_METHOD) 197 { 198 return ((String)asString).length() == 0; 199 } 200 201 // null getAsNumber() 202 Object asNumber = get(value, Types.NUMBER); 203 if (asNumber == null) 204 { 205 return true; 206 } 207 // zero numbers are false 208 else if (asNumber != NO_METHOD && asNumber instanceof Number) 209 { 210 return isZero((Number)asNumber); 211 } 212 213 return false; 214 } 215 asNumber(Object value)216 public static Number asNumber(Object value) 217 { 218 return asNumber(value, true); 219 } 220 asNumber(Object value, boolean coerceType)221 public static Number asNumber(Object value, boolean coerceType) 222 { 223 if (value == null) 224 { 225 return null; 226 } 227 if (value instanceof Number) 228 { 229 return (Number)value; 230 } 231 Object got = get(value, Types.NUMBER); 232 if (got != NO_METHOD) 233 { 234 return (Number)got; 235 } 236 if (coerceType) 237 { 238 String string = asString(value);// coerce to string 239 if (string != null) 240 { 241 return new BigDecimal(string); 242 } 243 } 244 return null; 245 } 246 get(Object value, Types type)247 protected static Object get(Object value, Types type) 248 { 249 try 250 { 251 // check cache 252 Class<?> c = value.getClass(); 253 Object cached = type.get(c); 254 if (cached == NO_METHOD) 255 { 256 return cached; 257 } 258 if (cached != null) 259 { 260 return ((Method)cached).invoke(value); 261 } 262 // ok, search the class 263 Method method = findMethod(c, type); 264 if (method == null) 265 { 266 type.set(c, NO_METHOD); 267 return NO_METHOD; 268 } 269 type.set(c, method); 270 return method.invoke(value); 271 } 272 catch (RuntimeException re) 273 { 274 throw re; 275 } 276 catch (Exception e) 277 { 278 throw new RuntimeException(e);// no checked exceptions, please 279 } 280 } 281 findMethod(Class<?> c, Types type)282 protected static Method findMethod(Class<?> c, Types type) 283 { 284 if (c == null || c == Object.class) 285 { 286 return null; 287 } 288 Method m = getMethod(c, type.name); 289 if (m != null) 290 { 291 return m; 292 } 293 for (Class<?> i : c.getInterfaces()) 294 { 295 m = findMethod(i, type); 296 if (m != null) 297 { 298 return m; 299 } 300 } 301 m = findMethod(c.getSuperclass(), type); 302 if (m != null) 303 { 304 return m; 305 } 306 return null; 307 } 308 getMethod(Class<?> c, String name)309 private static Method getMethod(Class<?> c, String name) 310 { 311 if (Modifier.isPublic(c.getModifiers())) 312 { 313 try 314 { 315 Method m = c.getDeclaredMethod(name); 316 if (Modifier.isPublic(m.getModifiers())) 317 { 318 return m; 319 } 320 } 321 catch (NoSuchMethodException nsme) {} 322 } 323 return null; 324 } 325 326 } 327