001/* 002 * Copyright 2002-2017 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.expression.spel.support; 018 019import java.lang.reflect.Array; 020import java.lang.reflect.Method; 021import java.util.List; 022 023import org.springframework.core.MethodParameter; 024import org.springframework.core.convert.TypeDescriptor; 025import org.springframework.expression.EvaluationException; 026import org.springframework.expression.TypeConverter; 027import org.springframework.expression.spel.SpelEvaluationException; 028import org.springframework.util.Assert; 029import org.springframework.util.ClassUtils; 030import org.springframework.util.CollectionUtils; 031import org.springframework.util.MethodInvoker; 032 033/** 034 * Utility methods used by the reflection resolver code to discover the appropriate 035 * methods/constructors and fields that should be used in expressions. 036 * 037 * @author Andy Clement 038 * @author Juergen Hoeller 039 * @since 3.0 040 */ 041public class ReflectionHelper { 042 043 /** 044 * Compare argument arrays and return information about whether they match. 045 * A supplied type converter and conversionAllowed flag allow for matches to take 046 * into account that a type may be transformed into a different type by the converter. 047 * @param expectedArgTypes the types the method/constructor is expecting 048 * @param suppliedArgTypes the types that are being supplied at the point of invocation 049 * @param typeConverter a registered type converter 050 * @return a MatchInfo object indicating what kind of match it was, 051 * or {@code null} if it was not a match 052 */ 053 static ArgumentsMatchInfo compareArguments( 054 List<TypeDescriptor> expectedArgTypes, List<TypeDescriptor> suppliedArgTypes, TypeConverter typeConverter) { 055 056 Assert.isTrue(expectedArgTypes.size() == suppliedArgTypes.size(), 057 "Expected argument types and supplied argument types should be arrays of same length"); 058 059 ArgumentsMatchKind match = ArgumentsMatchKind.EXACT; 060 for (int i = 0; i < expectedArgTypes.size() && match != null; i++) { 061 TypeDescriptor suppliedArg = suppliedArgTypes.get(i); 062 TypeDescriptor expectedArg = expectedArgTypes.get(i); 063 if (!expectedArg.equals(suppliedArg)) { 064 // The user may supply null - and that will be ok unless a primitive is expected 065 if (suppliedArg == null) { 066 if (expectedArg.isPrimitive()) { 067 match = null; 068 } 069 } 070 else { 071 if (suppliedArg.isAssignableTo(expectedArg)) { 072 if (match != ArgumentsMatchKind.REQUIRES_CONVERSION) { 073 match = ArgumentsMatchKind.CLOSE; 074 } 075 } 076 else if (typeConverter.canConvert(suppliedArg, expectedArg)) { 077 match = ArgumentsMatchKind.REQUIRES_CONVERSION; 078 } 079 else { 080 match = null; 081 } 082 } 083 } 084 } 085 return (match != null ? new ArgumentsMatchInfo(match) : null); 086 } 087 088 /** 089 * Based on {@link MethodInvoker#getTypeDifferenceWeight(Class[], Object[])} but operates on TypeDescriptors. 090 */ 091 public static int getTypeDifferenceWeight(List<TypeDescriptor> paramTypes, List<TypeDescriptor> argTypes) { 092 int result = 0; 093 for (int i = 0; i < paramTypes.size(); i++) { 094 TypeDescriptor paramType = paramTypes.get(i); 095 TypeDescriptor argType = (i < argTypes.size() ? argTypes.get(i) : null); 096 if (argType == null) { 097 if (paramType.isPrimitive()) { 098 return Integer.MAX_VALUE; 099 } 100 } 101 else { 102 Class<?> paramTypeClazz = paramType.getType(); 103 if (!ClassUtils.isAssignable(paramTypeClazz, argType.getType())) { 104 return Integer.MAX_VALUE; 105 } 106 if (paramTypeClazz.isPrimitive()) { 107 paramTypeClazz = Object.class; 108 } 109 Class<?> superClass = argType.getType().getSuperclass(); 110 while (superClass != null) { 111 if (paramTypeClazz.equals(superClass)) { 112 result = result + 2; 113 superClass = null; 114 } 115 else if (ClassUtils.isAssignable(paramTypeClazz, superClass)) { 116 result = result + 2; 117 superClass = superClass.getSuperclass(); 118 } 119 else { 120 superClass = null; 121 } 122 } 123 if (paramTypeClazz.isInterface()) { 124 result = result + 1; 125 } 126 } 127 } 128 return result; 129 } 130 131 /** 132 * Compare argument arrays and return information about whether they match. 133 * A supplied type converter and conversionAllowed flag allow for matches to 134 * take into account that a type may be transformed into a different type by the 135 * converter. This variant of compareArguments also allows for a varargs match. 136 * @param expectedArgTypes the types the method/constructor is expecting 137 * @param suppliedArgTypes the types that are being supplied at the point of invocation 138 * @param typeConverter a registered type converter 139 * @return a MatchInfo object indicating what kind of match it was, 140 * or {@code null} if it was not a match 141 */ 142 static ArgumentsMatchInfo compareArgumentsVarargs( 143 List<TypeDescriptor> expectedArgTypes, List<TypeDescriptor> suppliedArgTypes, TypeConverter typeConverter) { 144 145 Assert.isTrue(!CollectionUtils.isEmpty(expectedArgTypes), 146 "Expected arguments must at least include one array (the varargs parameter)"); 147 Assert.isTrue(expectedArgTypes.get(expectedArgTypes.size() - 1).isArray(), 148 "Final expected argument should be array type (the varargs parameter)"); 149 150 ArgumentsMatchKind match = ArgumentsMatchKind.EXACT; 151 152 // Check up until the varargs argument: 153 154 // Deal with the arguments up to 'expected number' - 1 (that is everything but the varargs argument) 155 int argCountUpToVarargs = expectedArgTypes.size() - 1; 156 for (int i = 0; i < argCountUpToVarargs && match != null; i++) { 157 TypeDescriptor suppliedArg = suppliedArgTypes.get(i); 158 TypeDescriptor expectedArg = expectedArgTypes.get(i); 159 if (suppliedArg == null) { 160 if (expectedArg.isPrimitive()) { 161 match = null; 162 } 163 } 164 else { 165 if (!expectedArg.equals(suppliedArg)) { 166 if (suppliedArg.isAssignableTo(expectedArg)) { 167 if (match != ArgumentsMatchKind.REQUIRES_CONVERSION) { 168 match = ArgumentsMatchKind.CLOSE; 169 } 170 } 171 else if (typeConverter.canConvert(suppliedArg, expectedArg)) { 172 match = ArgumentsMatchKind.REQUIRES_CONVERSION; 173 } 174 else { 175 match = null; 176 } 177 } 178 } 179 } 180 181 // If already confirmed it cannot be a match, then return 182 if (match == null) { 183 return null; 184 } 185 186 if (suppliedArgTypes.size() == expectedArgTypes.size() && 187 expectedArgTypes.get(expectedArgTypes.size() - 1).equals( 188 suppliedArgTypes.get(suppliedArgTypes.size() - 1))) { 189 // Special case: there is one parameter left and it is an array and it matches the varargs 190 // expected argument - that is a match, the caller has already built the array. Proceed with it. 191 } 192 else { 193 // Now... we have the final argument in the method we are checking as a match and we have 0 194 // or more other arguments left to pass to it. 195 TypeDescriptor varargsDesc = expectedArgTypes.get(expectedArgTypes.size() - 1); 196 Class<?> varargsParamType = varargsDesc.getElementTypeDescriptor().getType(); 197 198 // All remaining parameters must be of this type or convertible to this type 199 for (int i = expectedArgTypes.size() - 1; i < suppliedArgTypes.size(); i++) { 200 TypeDescriptor suppliedArg = suppliedArgTypes.get(i); 201 if (suppliedArg == null) { 202 if (varargsParamType.isPrimitive()) { 203 match = null; 204 } 205 } 206 else { 207 if (varargsParamType != suppliedArg.getType()) { 208 if (ClassUtils.isAssignable(varargsParamType, suppliedArg.getType())) { 209 if (match != ArgumentsMatchKind.REQUIRES_CONVERSION) { 210 match = ArgumentsMatchKind.CLOSE; 211 } 212 } 213 else if (typeConverter.canConvert(suppliedArg, TypeDescriptor.valueOf(varargsParamType))) { 214 match = ArgumentsMatchKind.REQUIRES_CONVERSION; 215 } 216 else { 217 match = null; 218 } 219 } 220 } 221 } 222 } 223 224 return (match != null ? new ArgumentsMatchInfo(match) : null); 225 } 226 227 228 // TODO could do with more refactoring around argument handling and varargs 229 /** 230 * Convert a supplied set of arguments into the requested types. If the parameterTypes are related to 231 * a varargs method then the final entry in the parameterTypes array is going to be an array itself whose 232 * component type should be used as the conversion target for extraneous arguments. (For example, if the 233 * parameterTypes are {Integer, String[]} and the input arguments are {Integer, boolean, float} then both 234 * the boolean and float must be converted to strings). This method does *not* repackage the arguments 235 * into a form suitable for the varargs invocation - a subsequent call to setupArgumentsForVarargsInvocation handles that. 236 * @param converter the converter to use for type conversions 237 * @param arguments the arguments to convert to the requested parameter types 238 * @param method the target Method 239 * @return true if some kind of conversion occurred on the argument 240 * @throws SpelEvaluationException if there is a problem with conversion 241 */ 242 public static boolean convertAllArguments(TypeConverter converter, Object[] arguments, Method method) 243 throws SpelEvaluationException { 244 245 Integer varargsPosition = (method.isVarArgs() ? method.getParameterTypes().length - 1 : null); 246 return convertArguments(converter, arguments, method, varargsPosition); 247 } 248 249 /** 250 * Takes an input set of argument values and converts them to the types specified as the 251 * required parameter types. The arguments are converted 'in-place' in the input array. 252 * @param converter the type converter to use for attempting conversions 253 * @param arguments the actual arguments that need conversion 254 * @param methodOrCtor the target Method or Constructor 255 * @param varargsPosition the known position of the varargs argument, if any 256 * ({@code null} if not varargs) 257 * @return {@code true} if some kind of conversion occurred on an argument 258 * @throws EvaluationException if a problem occurs during conversion 259 */ 260 static boolean convertArguments(TypeConverter converter, Object[] arguments, Object methodOrCtor, 261 Integer varargsPosition) throws EvaluationException { 262 263 boolean conversionOccurred = false; 264 if (varargsPosition == null) { 265 for (int i = 0; i < arguments.length; i++) { 266 TypeDescriptor targetType = new TypeDescriptor(MethodParameter.forMethodOrConstructor(methodOrCtor, i)); 267 Object argument = arguments[i]; 268 arguments[i] = converter.convertValue(argument, TypeDescriptor.forObject(argument), targetType); 269 conversionOccurred |= (argument != arguments[i]); 270 } 271 } 272 else { 273 // Convert everything up to the varargs position 274 for (int i = 0; i < varargsPosition; i++) { 275 TypeDescriptor targetType = new TypeDescriptor(MethodParameter.forMethodOrConstructor(methodOrCtor, i)); 276 Object argument = arguments[i]; 277 arguments[i] = converter.convertValue(argument, TypeDescriptor.forObject(argument), targetType); 278 conversionOccurred |= (argument != arguments[i]); 279 } 280 MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, varargsPosition); 281 if (varargsPosition == arguments.length - 1) { 282 // If the target is varargs and there is just one more argument 283 // then convert it here 284 TypeDescriptor targetType = new TypeDescriptor(methodParam); 285 Object argument = arguments[varargsPosition]; 286 TypeDescriptor sourceType = TypeDescriptor.forObject(argument); 287 arguments[varargsPosition] = converter.convertValue(argument, sourceType, targetType); 288 // Three outcomes of that previous line: 289 // 1) the input argument was already compatible (ie. array of valid type) and nothing was done 290 // 2) the input argument was correct type but not in an array so it was made into an array 291 // 3) the input argument was the wrong type and got converted and put into an array 292 if (argument != arguments[varargsPosition] && 293 !isFirstEntryInArray(argument, arguments[varargsPosition])) { 294 conversionOccurred = true; // case 3 295 } 296 } 297 else { 298 // Convert remaining arguments to the varargs element type 299 TypeDescriptor targetType = new TypeDescriptor(methodParam).getElementTypeDescriptor(); 300 for (int i = varargsPosition; i < arguments.length; i++) { 301 Object argument = arguments[i]; 302 arguments[i] = converter.convertValue(argument, TypeDescriptor.forObject(argument), targetType); 303 conversionOccurred |= (argument != arguments[i]); 304 } 305 } 306 } 307 return conversionOccurred; 308 } 309 310 /** 311 * Check if the supplied value is the first entry in the array represented by the possibleArray value. 312 * @param value the value to check for in the array 313 * @param possibleArray an array object that may have the supplied value as the first element 314 * @return true if the supplied value is the first entry in the array 315 */ 316 private static boolean isFirstEntryInArray(Object value, Object possibleArray) { 317 if (possibleArray == null) { 318 return false; 319 } 320 Class<?> type = possibleArray.getClass(); 321 if (!type.isArray() || Array.getLength(possibleArray) == 0 || 322 !ClassUtils.isAssignableValue(type.getComponentType(), value)) { 323 return false; 324 } 325 Object arrayValue = Array.get(possibleArray, 0); 326 return (type.getComponentType().isPrimitive() ? arrayValue.equals(value) : arrayValue == value); 327 } 328 329 /** 330 * Package up the arguments so that they correctly match what is expected in parameterTypes. 331 * For example, if parameterTypes is {@code (int, String[])} because the second parameter 332 * was declared {@code String...}, then if arguments is {@code [1,"a","b"]} then it must be 333 * repackaged as {@code [1,new String[]{"a","b"}]} in order to match the expected types. 334 * @param requiredParameterTypes the types of the parameters for the invocation 335 * @param args the arguments to be setup ready for the invocation 336 * @return a repackaged array of arguments where any varargs setup has been done 337 */ 338 public static Object[] setupArgumentsForVarargsInvocation(Class<?>[] requiredParameterTypes, Object... args) { 339 // Check if array already built for final argument 340 int parameterCount = requiredParameterTypes.length; 341 int argumentCount = args.length; 342 343 // Check if repackaging is needed... 344 if (parameterCount != args.length || 345 requiredParameterTypes[parameterCount - 1] != 346 (args[argumentCount - 1] != null ? args[argumentCount - 1].getClass() : null)) { 347 348 int arraySize = 0; // zero size array if nothing to pass as the varargs parameter 349 if (argumentCount >= parameterCount) { 350 arraySize = argumentCount - (parameterCount - 1); 351 } 352 353 // Create an array for the varargs arguments 354 Object[] newArgs = new Object[parameterCount]; 355 System.arraycopy(args, 0, newArgs, 0, newArgs.length - 1); 356 357 // Now sort out the final argument, which is the varargs one. Before entering this method, 358 // the arguments should have been converted to the box form of the required type. 359 Class<?> componentType = requiredParameterTypes[parameterCount - 1].getComponentType(); 360 Object repackagedArgs = Array.newInstance(componentType, arraySize); 361 for (int i = 0; i < arraySize; i++) { 362 Array.set(repackagedArgs, i, args[parameterCount - 1 + i]); 363 } 364 newArgs[newArgs.length - 1] = repackagedArgs; 365 return newArgs; 366 } 367 return args; 368 } 369 370 371 enum ArgumentsMatchKind { 372 373 /** An exact match is where the parameter types exactly match what the method/constructor is expecting */ 374 EXACT, 375 376 /** A close match is where the parameter types either exactly match or are assignment-compatible */ 377 CLOSE, 378 379 /** A conversion match is where the type converter must be used to transform some of the parameter types */ 380 REQUIRES_CONVERSION 381 } 382 383 384 /** 385 * An instance of ArgumentsMatchInfo describes what kind of match was achieved 386 * between two sets of arguments - the set that a method/constructor is expecting 387 * and the set that are being supplied at the point of invocation. If the kind 388 * indicates that conversion is required for some of the arguments then the arguments 389 * that require conversion are listed in the argsRequiringConversion array. 390 */ 391 static class ArgumentsMatchInfo { 392 393 private final ArgumentsMatchKind kind; 394 395 ArgumentsMatchInfo(ArgumentsMatchKind kind) { 396 this.kind = kind; 397 } 398 399 public boolean isExactMatch() { 400 return (this.kind == ArgumentsMatchKind.EXACT); 401 } 402 403 public boolean isCloseMatch() { 404 return (this.kind == ArgumentsMatchKind.CLOSE); 405 } 406 407 public boolean isMatchRequiringConversion() { 408 return (this.kind == ArgumentsMatchKind.REQUIRES_CONVERSION); 409 } 410 411 @Override 412 public String toString() { 413 return "ArgumentMatchInfo: " + this.kind; 414 } 415 } 416 417}