• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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