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