• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.apache.velocity.util.introspection;
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 org.apache.commons.lang3.reflect.TypeUtils;
23 
24 import java.lang.reflect.Array;
25 import java.lang.reflect.GenericArrayType;
26 import java.lang.reflect.ParameterizedType;
27 import java.lang.reflect.Type;
28 import java.lang.reflect.TypeVariable;
29 import java.lang.reflect.WildcardType;
30 import java.util.HashMap;
31 import java.util.Map;
32 
33 /**
34  *
35  * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
36  * @author <a href="mailto:bob@werken.com">Bob McWhirter</a>
37  * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph Reck</a>
38  * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
39  * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
40  * @author Nathan Bubna
41  * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
42  * @version $Id: IntrospectionUtils.java 476785 2006-11-19 10:06:21Z henning $
43  * @since 1.6
44  */
45 public class IntrospectionUtils
46 {
47     /**
48      * boxing helper maps for standard types
49      */
50     static Map<Class<?>, Class<?>> boxingMap, unboxingMap;
51 
52     static
53     {
54         boxingMap = new HashMap<>();
boxingMap.put(Boolean.TYPE, Boolean.class)55         boxingMap.put(Boolean.TYPE, Boolean.class);
boxingMap.put(Character.TYPE, Character.class)56         boxingMap.put(Character.TYPE, Character.class);
boxingMap.put(Byte.TYPE, Byte.class)57         boxingMap.put(Byte.TYPE, Byte.class);
boxingMap.put(Short.TYPE, Short.class)58         boxingMap.put(Short.TYPE, Short.class);
boxingMap.put(Integer.TYPE, Integer.class)59         boxingMap.put(Integer.TYPE, Integer.class);
boxingMap.put(Long.TYPE, Long.class)60         boxingMap.put(Long.TYPE, Long.class);
boxingMap.put(Float.TYPE, Float.class)61         boxingMap.put(Float.TYPE, Float.class);
boxingMap.put(Double.TYPE, Double.class)62         boxingMap.put(Double.TYPE, Double.class);
63 
64         unboxingMap = new HashMap<>();
65         for (Map.Entry<Class<?>, Class<?>> entry : boxingMap.entrySet())
66         {
entry.getKey()67             unboxingMap.put(entry.getValue(), entry.getKey());
68         }
69     }
70 
71     /**
72      * returns boxed type (or input type if not a primitive type)
73      * @param clazz input class
74      * @return boxed class
75      */
getBoxedClass(Class clazz)76     public static Class<?> getBoxedClass(Class clazz)
77     {
78         Class<?> boxed = boxingMap.get(clazz);
79         return boxed == null ? clazz : boxed;
80     }
81 
82     /**
83      * returns unboxed type (or input type if not successful)
84      * @param clazz input class
85      * @return unboxed class
86      */
getUnboxedClass(Class clazz)87     public static Class<?> getUnboxedClass(Class clazz)
88     {
89         Class<?> unboxed = unboxingMap.get(clazz);
90         return unboxed == null ? clazz : unboxed;
91     }
92 
93     /**
94      * returns the Class corresponding to a Type, if possible
95      * @param type the input Type
96      * @return found Class, if any
97      */
getTypeClass(Type type)98     public static Class<?> getTypeClass(Type type)
99     {
100         if (type == null)
101         {
102             return null;
103         }
104         if (type instanceof Class<?>)
105         {
106             return (Class<?>)type;
107         }
108         else if (type instanceof ParameterizedType)
109         {
110             return (Class<?>)((ParameterizedType)type).getRawType();
111         }
112         else if (type instanceof GenericArrayType)
113         {
114             Type componentType = ((GenericArrayType)type).getGenericComponentType();
115             Class<?> componentClass = getTypeClass(componentType);
116             if (componentClass != null)
117             {
118                 return Array.newInstance(componentClass, 0).getClass();
119             }
120         }
121         else if (type instanceof TypeVariable)
122         {
123             Type[] bounds = TypeUtils.getImplicitBounds((TypeVariable)type);
124             if (bounds.length == 1) return getTypeClass(bounds[0]);
125         }
126         else if (type instanceof WildcardType)
127         {
128             Type[] bounds = TypeUtils.getImplicitUpperBounds((WildcardType)type);
129             if (bounds.length == 1) return getTypeClass(bounds[0]);
130         }
131         return null;
132     }
133 
134     /**
135      * Determines whether a type represented by a class object is
136      * convertible to another type represented by a class object using a
137      * method invocation conversion, treating object types of primitive
138      * types as if they were primitive types (that is, a Boolean actual
139      * parameter type matches boolean primitive formal type). This behavior
140      * is because this method is used to determine applicable methods for
141      * an actual parameter list, and primitive types are represented by
142      * their object duals in reflective method calls.
143      *
144      * @param formal the formal parameter type to which the actual
145      * parameter type should be convertible
146      * @param actual the actual parameter type.
147      * @param possibleVarArg whether or not we're dealing with the last parameter
148      * in the method declaration
149      * @return true if either formal type is assignable from actual type,
150      * or formal is a primitive type and actual is its corresponding object
151      * type or an object type of a primitive type that can be converted to
152      * the formal type.
153      */
isMethodInvocationConvertible(Type formal, Class<?> actual, boolean possibleVarArg)154     public static boolean isMethodInvocationConvertible(Type formal,
155                                                         Class<?> actual,
156                                                         boolean possibleVarArg)
157     {
158         Class<?> formalClass = getTypeClass(formal);
159         if (formalClass != null)
160         {
161             /* if it's a null, it means the arg was null */
162             if (actual == null)
163             {
164                 return !formalClass.isPrimitive();
165             }
166 
167             /* Check for identity or widening reference conversion */
168             if (formalClass.isAssignableFrom(actual))
169             {
170                 return true;
171             }
172 
173             /* 2.0: Since MethodMap's comparison functions now use this method with potentially reversed arguments order,
174              * actual can be a primitive type. */
175 
176             /* Check for boxing */
177             if (!formalClass.isPrimitive() && actual.isPrimitive())
178             {
179                 Class<?> boxed = boxingMap.get(actual);
180                 if (boxed != null && (boxed == formalClass || formalClass.isAssignableFrom(boxed))) return true;
181             }
182 
183             if (formalClass.isPrimitive())
184             {
185                 if (actual.isPrimitive())
186                 {
187                     /* check for widening primitive conversion */
188                     if (formalClass == Short.TYPE && actual == Byte.TYPE)
189                         return true;
190                     if (formalClass == Integer.TYPE && (
191                         actual == Byte.TYPE || actual == Short.TYPE))
192                         return true;
193                     if (formalClass == Long.TYPE && (
194                         actual == Byte.TYPE || actual == Short.TYPE || actual == Integer.TYPE))
195                         return true;
196                     if (formalClass == Float.TYPE && (
197                         actual == Byte.TYPE || actual == Short.TYPE || actual == Integer.TYPE ||
198                             actual == Long.TYPE))
199                         return true;
200                     if (formalClass == Double.TYPE && (
201                         actual == Byte.TYPE || actual == Short.TYPE || actual == Integer.TYPE ||
202                             actual == Long.TYPE || actual == Float.TYPE))
203                         return true;
204                 } else
205                 {
206                     /* Check for unboxing with widening primitive conversion. */
207                     if (formalClass == Boolean.TYPE && actual == Boolean.class)
208                         return true;
209                     if (formalClass == Character.TYPE && actual == Character.class)
210                         return true;
211                     if (formalClass == Byte.TYPE && actual == Byte.class)
212                         return true;
213                     if (formalClass == Short.TYPE && (actual == Short.class || actual == Byte.class))
214                         return true;
215                     if (formalClass == Integer.TYPE && (actual == Integer.class || actual == Short.class ||
216                         actual == Byte.class))
217                         return true;
218                     if (formalClass == Long.TYPE && (actual == Long.class || actual == Integer.class ||
219                         actual == Short.class || actual == Byte.class))
220                         return true;
221                     if (formalClass == Float.TYPE && (actual == Float.class || actual == Long.class ||
222                         actual == Integer.class || actual == Short.class || actual == Byte.class))
223                         return true;
224                     if (formalClass == Double.TYPE && (actual == Double.class || actual == Float.class ||
225                         actual == Long.class || actual == Integer.class || actual == Short.class ||
226                         actual == Byte.class))
227                         return true;
228                 }
229             }
230 
231             /* Check for vararg conversion. */
232             if (possibleVarArg && formalClass.isArray())
233             {
234                 if (actual.isArray())
235                 {
236                     actual = actual.getComponentType();
237                 }
238                 return isMethodInvocationConvertible(formalClass.getComponentType(),
239                     actual, false);
240             }
241             return false;
242         }
243         else
244         {
245             // no distinction between strict and implicit, not a big deal in this case
246             if (TypeUtils.isAssignable(actual, formal))
247             {
248                 return true;
249             }
250             return possibleVarArg && TypeUtils.isArrayType(formal) &&
251                 TypeUtils.isAssignable(actual, TypeUtils.getArrayComponentType(formal));
252         }
253     }
254 
255     /**
256      * Determines whether a type represented by a class object is
257      * convertible to another type represented by a class object using a
258      * method invocation conversion, without matching object and primitive
259      * types. This method is used to determine the more specific type when
260      * comparing signatures of methods.
261      *
262      * @param formal the formal parameter type to which the actual
263      * parameter type should be convertible
264      * @param actual the actual parameter type.
265      * @param possibleVarArg whether or not we're dealing with the last parameter
266      * in the method declaration
267      * @return true if either formal type is assignable from actual type,
268      * or formal and actual are both primitive types and actual can be
269      * subject to widening conversion to formal.
270      */
isStrictMethodInvocationConvertible(Type formal, Class<?> actual, boolean possibleVarArg)271     public static boolean isStrictMethodInvocationConvertible(Type formal,
272                                                               Class<?> actual,
273                                                               boolean possibleVarArg)
274     {
275         Class<?> formalClass = getTypeClass(formal);
276         if (formalClass != null)
277         {
278             /* Check for nullity */
279             if (actual == null)
280             {
281                 return !formalClass.isPrimitive();
282             }
283 
284             /* Check for identity or widening reference conversion */
285             if (formalClass.isAssignableFrom(actual))
286             {
287                 return true;
288             }
289 
290             /* Check for widening primitive conversion. */
291             if (formalClass.isPrimitive())
292             {
293                 if (formal == Short.TYPE && (actual == Byte.TYPE))
294                     return true;
295                 if (formal == Integer.TYPE &&
296                     (actual == Short.TYPE || actual == Byte.TYPE))
297                     return true;
298                 if (formal == Long.TYPE &&
299                     (actual == Integer.TYPE || actual == Short.TYPE ||
300                         actual == Byte.TYPE))
301                     return true;
302                 if (formal == Float.TYPE &&
303                     (actual == Long.TYPE || actual == Integer.TYPE ||
304                         actual == Short.TYPE || actual == Byte.TYPE))
305                     return true;
306                 if (formal == Double.TYPE &&
307                     (actual == Float.TYPE || actual == Long.TYPE ||
308                         actual == Integer.TYPE || actual == Short.TYPE ||
309                         actual == Byte.TYPE))
310                     return true;
311             }
312 
313             /* Check for vararg conversion. */
314             if (possibleVarArg && formalClass.isArray())
315             {
316                 if (actual.isArray())
317                 {
318                     actual = actual.getComponentType();
319                 }
320                 return isStrictMethodInvocationConvertible(formalClass.getComponentType(),
321                     actual, false);
322             }
323             return false;
324         }
325         else
326         {
327             // no distinction between strict and implicit, not a big deal in this case
328             if (TypeUtils.isAssignable(actual, formal))
329             {
330                 return true;
331             }
332             return possibleVarArg && TypeUtils.isArrayType(formal) &&
333                 TypeUtils.isAssignable(actual, TypeUtils.getArrayComponentType(formal));
334         }
335     }
336 }
337