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