View Javadoc

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.Map;
27  import java.util.HashMap;
28  
29  /**
30   * Support for getAs<Type>() convention for rendering (String), evaluating (Boolean)
31   * or doing math with (Number) references.
32   *
33   * @author Nathan Bubna
34   * @since 2.0
35   */
36  public class DuckType
37  {
38      protected enum Types
39      {
40          STRING("getAsString"),
41          NUMBER("getAsNumber"),
42          BOOLEAN("getAsBoolean"),
43          EMPTY("isEmpty");
44  
45          final String name;
46          final Map<Class,Object> cache = new HashMap();
47  
48          Types(String name)
49          {
50              this.name = name;
51          }
52  
53          void set(Class c, Object o)
54          {
55              cache.put(c, o);
56          }
57  
58          Object get(Class c)
59          {
60              return cache.get(c);
61          }
62      }
63  
64      protected static final Object NO_METHOD = new Object();
65  
66      public static String asString(Object value)
67      {
68          return asString(value, true);
69      }
70  
71      public static String asString(Object value, boolean coerceType)
72      {
73          if (value == null)
74          {
75              return null;
76          }
77          if (value instanceof String)
78          {
79              return (String)value;
80          }
81          Object got = get(value, Types.STRING);
82          if (got == NO_METHOD)
83          {
84              return coerceType ? value.toString() : null;
85          }
86          return (String)got;
87      }
88  
89      public static boolean asNull(Object value)
90      {
91          if (value == null ||
92              get(value, Types.STRING) == null ||
93              get(value, Types.NUMBER) == null)
94          {
95              return true;
96          }
97          return false;
98      }
99  
100     public static boolean asBoolean(Object value)
101     {
102         return asBoolean(value, true);
103     }
104 
105     public static boolean asBoolean(Object value, boolean coerceType)
106     {
107         if (value == null)
108         {
109             return false;
110         }
111         if (value instanceof Boolean)
112         {
113             return ((Boolean)value).booleanValue();
114         }
115         Object got = get(value, Types.BOOLEAN);
116         if (got != NO_METHOD)
117         {
118             return ((Boolean)got).booleanValue();
119         }
120         if (coerceType)
121         {
122             return !asEmpty(value);
123         }
124         return true;
125     }
126 
127     // see VELOCITY-692 for discussion about empty values
128     public static boolean asEmpty(Object value)
129     {
130         // empty variable
131         if (value == null)
132         {
133             return true;
134         }
135 
136         // empty string
137         if (value instanceof CharSequence)
138         {
139             return ((CharSequence)value).length() == 0;
140         }
141 
142         // isEmpty() object
143         Object isEmpty = get(value, Types.EMPTY);
144         if (isEmpty != NO_METHOD)
145         {
146             return (Boolean)isEmpty;
147         }
148 
149         // empty array
150         if (value.getClass().isArray())
151         {
152             return Array.getLength(value) == 0;// [] is false
153         }
154 
155         // null getAsString()
156         Object asString = get(value, Types.STRING);
157         if (asString == null)
158         {
159             return true;// duck null
160         }
161         // empty getAsString()
162         else if (asString != NO_METHOD)
163         {
164             return ((String)asString).length() == 0;
165         }
166 
167         // null getAsNumber()
168         Object asNumber = get(value, Types.NUMBER);
169         if (asNumber == null)
170         {
171             return true;
172         }
173 
174         // empty toString()
175         String string = value.toString();
176         return string == null || string.length() == 0;
177     }
178 
179     public static Number asNumber(Object value)
180     {
181         return asNumber(value, true);
182     }
183 
184     public static Number asNumber(Object value, boolean coerceType)
185     {
186         if (value == null)
187         {
188             return null;
189         }
190         if (value instanceof Number)
191         {
192             return (Number)value;
193         }
194         Object got = get(value, Types.NUMBER);
195         if (got != NO_METHOD)
196         {
197             return (Number)got;
198         }
199         if (coerceType)
200         {
201             String string = asString(value);// coerce to string
202             if (string != null)
203             {
204                 return new BigDecimal(string);
205             }
206         }
207         return null;
208     }
209 
210     protected static Object get(Object value, Types type)
211     {
212         try
213         {
214             // check cache
215             Class c = value.getClass();
216             Object cached = type.get(c);
217             if (cached == NO_METHOD)
218             {
219                 return cached;
220             }
221             if (cached != null)
222             {
223                 return ((Method)cached).invoke(value);
224             }
225             // ok, search the class
226             Method method = findMethod(c, type);
227             if (method == null)
228             {
229                 type.set(c, NO_METHOD);
230                 return NO_METHOD;
231             }
232             type.set(c, method);
233             return method.invoke(value);
234         }
235         catch (RuntimeException re)
236         {
237             throw re;
238         }
239         catch (Exception e)
240         {
241             throw new RuntimeException(e);// no checked exceptions, please
242         }
243     }
244 
245     protected static Method findMethod(Class c, Types type)
246     {
247         if (c == null || c == Object.class)
248         {
249             return null;
250         }
251         Method m = getMethod(c, type.name);
252         if (m != null)
253         {
254             return m;
255         }
256         for (Class i : c.getInterfaces())
257         {
258             m = findMethod(i, type);
259             if (m != null)
260             {
261                 return m;
262             }
263         }
264         m = findMethod(c.getSuperclass(), type);
265         if (m != null)
266         {
267             return m;
268         }
269         return null;
270     }
271 
272     private static Method getMethod(Class c, String name)
273     {
274         if (Modifier.isPublic(c.getModifiers()))
275         {
276             try
277             {
278                 Method m = c.getDeclaredMethod(name);
279                 if (Modifier.isPublic(m.getModifiers()))
280                 {
281                     return m;
282                 }
283             }
284             catch (NoSuchMethodException nsme) {}
285         }
286         return null;
287     }
288 
289 }