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.Method; 25 import java.lang.reflect.Type; 26 import java.util.ArrayList; 27 import java.util.Arrays; 28 import java.util.LinkedList; 29 import java.util.List; 30 import java.util.ListIterator; 31 import java.util.Map; 32 import java.util.concurrent.ConcurrentHashMap; 33 34 /** 35 * 36 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> 37 * @author <a href="mailto:bob@werken.com">Bob McWhirter</a> 38 * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph Reck</a> 39 * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> 40 * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a> 41 * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a> 42 * @version $Id$ 43 */ 44 public class MethodMap 45 { 46 /* Constants for specificity */ 47 private static final int INCOMPARABLE = 0; 48 private static final int MORE_SPECIFIC = 1; 49 private static final int EQUIVALENT = 2; 50 private static final int LESS_SPECIFIC = 3; 51 52 /* Constants for applicability */ 53 private static final int NOT_CONVERTIBLE = 0; 54 private static final int EXPLICITLY_CONVERTIBLE = 1; 55 private static final int IMPLCITLY_CONVERTIBLE = 2; 56 private static final int STRICTLY_CONVERTIBLE = 3; 57 58 TypeConversionHandler conversionHandler; 59 60 /** 61 * Default constructor 62 */ MethodMap()63 public MethodMap() 64 { 65 this(null); 66 } 67 68 /** 69 * Constructor with provided conversion handler 70 * @param conversionHandler conversion handler 71 * @since 2.0 72 */ MethodMap(TypeConversionHandler conversionHandler)73 public MethodMap(TypeConversionHandler conversionHandler) 74 { 75 this.conversionHandler = conversionHandler; 76 } 77 78 /** 79 * Keep track of all methods with the same name. 80 */ 81 Map<String, List<Method>> methodByNameMap = new ConcurrentHashMap<>(); 82 83 /** 84 * Add a method to a list of methods by name. 85 * For a particular class we are keeping track 86 * of all the methods with the same name. 87 * @param method 88 */ add(Method method)89 public void add(Method method) 90 { 91 String methodName = method.getName(); 92 93 List<Method> l = get( methodName ); 94 95 if ( l == null) 96 { 97 l = new ArrayList<>(); 98 methodByNameMap.put(methodName, l); 99 } 100 101 l.add(method); 102 } 103 104 /** 105 * Return a list of methods with the same name. 106 * 107 * @param key 108 * @return List list of methods 109 */ get(String key)110 public List<Method> get(String key) 111 { 112 return methodByNameMap.get(key); 113 } 114 115 /** 116 * <p> 117 * Find a method. Attempts to find the 118 * most specific applicable method using the 119 * algorithm described in the JLS section 120 * 15.12.2 (with the exception that it can't 121 * distinguish a primitive type argument from 122 * an object type argument, since in reflection 123 * primitive type arguments are represented by 124 * their object counterparts, so for an argument of 125 * type (say) java.lang.Integer, it will not be able 126 * to decide between a method that takes int and a 127 * method that takes java.lang.Integer as a parameter. 128 * </p> 129 * 130 * <p> 131 * This turns out to be a relatively rare case 132 * where this is needed - however, functionality 133 * like this is needed. 134 * </p> 135 * 136 * @param methodName name of method 137 * @param args the actual arguments with which the method is called 138 * @return the most specific applicable method, or null if no 139 * method is applicable. 140 * @throws AmbiguousException if there is more than one maximally 141 * specific applicable method 142 */ find(String methodName, Object[] args)143 public Method find(String methodName, Object[] args) 144 throws AmbiguousException 145 { 146 List<Method> methodList = get(methodName); 147 148 if (methodList == null) 149 { 150 return null; 151 } 152 153 int l = args.length; 154 Class<?>[] classes = new Class[l]; 155 156 for(int i = 0; i < l; ++i) 157 { 158 Object arg = args[i]; 159 160 /* 161 * if we are careful down below, a null argument goes in there 162 * so we can know that the null was passed to the method 163 */ 164 classes[i] = 165 arg == null ? null : arg.getClass(); 166 } 167 168 return getBestMatch(methodList, classes); 169 } 170 171 private class Match 172 { 173 /* target method */ 174 Method method; 175 176 /* cache arguments classes array */ 177 Type[] methodTypes; 178 179 /* specificity: how does the best match compare to provided arguments 180 * one one LESS_SPECIFIC, MORE_SPECIFIC or INCOMPARABLE */ 181 int specificity; 182 183 /* applicability which conversion level is needed against provided arguments 184 * one of STRICTLY_CONVERTIBLE, IMPLICITLY_CONVERTIBLE and EXPLICITLY_CONVERTIBLE_ */ 185 int applicability; 186 187 /* whether the method has varrags */ 188 boolean varargs; 189 Match(Method method, int applicability, Class<?>[] unboxedArgs)190 Match(Method method, int applicability, Class<?>[] unboxedArgs) 191 { 192 this.method = method; 193 this.applicability = applicability; 194 this.methodTypes = method.getGenericParameterTypes(); 195 this.specificity = compare(methodTypes, unboxedArgs); 196 this.varargs = methodTypes.length > 0 && TypeUtils.isArrayType(methodTypes[methodTypes.length - 1]); 197 } 198 } 199 onlyNullOrObjects(Class<?>[] args)200 private static boolean onlyNullOrObjects(Class<?>[] args) 201 { 202 for (Class<?> cls : args) 203 { 204 if (cls != null && cls != Object.class) return false; 205 } 206 return args.length > 0; 207 } 208 getBestMatch(List<Method> methods, Class<?>[] args)209 private Method getBestMatch(List<Method> methods, Class<?>[] args) 210 { 211 List<Match> bestMatches = new LinkedList<>(); 212 Class<?>[] unboxedArgs = new Class<?>[args.length]; 213 for (int i = 0; i < args.length; ++i) 214 { 215 unboxedArgs[i] = IntrospectionUtils.getUnboxedClass(args[i]); 216 } 217 for (Method method : methods) 218 { 219 int applicability = getApplicability(method, unboxedArgs); 220 if (applicability > NOT_CONVERTIBLE) 221 { 222 Match match = new Match(method, applicability, unboxedArgs); 223 if (bestMatches.size() == 0) 224 { 225 bestMatches.add(match); 226 } 227 else 228 { 229 /* filter existing matches */ 230 boolean keepMethod = true; 231 for (ListIterator<Match> it = bestMatches.listIterator(); keepMethod && it.hasNext();) 232 { 233 Match best = it.next(); 234 /* do not retain match if it's more specific than (or incomparable to) provided (unboxed) arguments 235 * while one of the best matches is less specific 236 */ 237 if (best.specificity == LESS_SPECIFIC && match.specificity < EQUIVALENT) /* != LESS_SPECIFIC && != EQUIVALENT */ 238 { 239 keepMethod = false; 240 } 241 /* drop considered best match if match is less specific than (unboxed) provided args while 242 * the considered best match is more specific or incomparable 243 */ 244 else if (match.specificity == LESS_SPECIFIC && best.specificity < EQUIVALENT) /* != LESS_SPECIFIC && != EQUIVALENT */ 245 { 246 it.remove(); 247 } 248 /* compare applicability */ 249 else if (best.applicability > match.applicability) 250 { 251 keepMethod = false; 252 } 253 else if (best.applicability < match.applicability) 254 { 255 it.remove(); 256 } 257 /* compare methods between them */ 258 else 259 { 260 /* but only if some provided args are non null and not Object */ 261 if (onlyNullOrObjects(args)) 262 { 263 /* in this case we only favor non-varrags methods */ 264 if (match.varargs != best.varargs) 265 { 266 if (match.varargs) 267 { 268 keepMethod = false; 269 } 270 else if (best.varargs) 271 { 272 it.remove(); 273 } 274 } 275 } 276 else 277 { 278 switch (compare(match.methodTypes, best.methodTypes)) 279 { 280 case LESS_SPECIFIC: 281 keepMethod = false; 282 break; 283 case MORE_SPECIFIC: 284 it.remove(); 285 break; 286 case INCOMPARABLE: 287 /* Java compiler favors non-vararg methods. Let's do the same. */ 288 if (match.varargs != best.varargs) 289 { 290 if (match.varargs) 291 { 292 keepMethod = false; 293 } 294 else if (best.varargs) 295 { 296 it.remove(); 297 } 298 } 299 /* otherwise it's an equivalent match */ 300 break; 301 case EQUIVALENT: 302 break; 303 } 304 } 305 } 306 } 307 if (keepMethod) 308 { 309 bestMatches.add(match); 310 } 311 } 312 } 313 } 314 315 switch (bestMatches.size()) 316 { 317 case 0: return null; 318 case 1: return bestMatches.get(0).method; 319 default: throw new AmbiguousException(); 320 } 321 } 322 323 /** 324 * Simple distinguishable exception, used when 325 * we run across ambiguous overloading. Caught 326 * by the introspector. 327 */ 328 public static class AmbiguousException extends RuntimeException 329 { 330 /** 331 * Version Id for serializable 332 */ 333 private static final long serialVersionUID = -2314636505414551663L; 334 } 335 336 /** 337 * Determines which method signature (represented by a class array) is more 338 * specific. This defines a partial ordering on the method signatures. 339 * @param t1 first signature to compare 340 * @param t2 second signature to compare 341 * @return MORE_SPECIFIC if c1 is more specific than c2, LESS_SPECIFIC if 342 * c1 is less specific than c2, INCOMPARABLE if they are incomparable. 343 */ compare(Type[] t1, Type[] t2)344 private int compare(Type[] t1, Type[] t2) 345 { 346 boolean t1IsVararag = false; 347 boolean t2IsVararag = false; 348 boolean fixedLengths = false; 349 350 // compare lengths to handle comparisons where the size of the arrays 351 // doesn't match, but the methods are both applicable due to the fact 352 // that one is a varargs method 353 if (t1.length > t2.length) 354 { 355 int l2 = t2.length; 356 if (l2 == 0) 357 { 358 return MORE_SPECIFIC; 359 } 360 t2 = Arrays.copyOf(t2, t1.length); 361 Type itemType = TypeUtils.getArrayComponentType(t2[l2 - 1]); 362 /* if item class is null, then it implies the vaarg is #1 363 * (and receives an empty array) 364 */ 365 if (itemType == null) 366 { 367 /* by construct, we have c1.length = l2 + 1 */ 368 t1IsVararag = true; 369 t2[t1.length - 1] = null; 370 } 371 else 372 { 373 t2IsVararag = true; 374 for (int i = l2 - 1; i < t1.length; ++i) 375 { 376 /* also overwrite the vaargs itself */ 377 t2[i] = itemType; 378 } 379 } 380 fixedLengths = true; 381 } 382 else if (t2.length > t1.length) 383 { 384 int l1 = t1.length; 385 if (l1 == 0) 386 { 387 return LESS_SPECIFIC; 388 } 389 t1 = Arrays.copyOf(t1, t2.length); 390 Type itemType = TypeUtils.getArrayComponentType(t1[l1 - 1]); 391 /* if item class is null, then it implies the vaarg is #2 392 * (and receives an empty array) 393 */ 394 if (itemType == null) 395 { 396 /* by construct, we have c2.length = l1 + 1 */ 397 t2IsVararag = true; 398 t1[t2.length - 1] = null; 399 } 400 else 401 { 402 t1IsVararag = true; 403 for (int i = l1 - 1; i < t2.length; ++i) 404 { 405 /* also overwrite the vaargs itself */ 406 t1[i] = itemType; 407 } 408 } 409 fixedLengths = true; 410 } 411 412 /* ok, move on and compare those of equal lengths */ 413 int fromC1toC2 = STRICTLY_CONVERTIBLE; 414 int fromC2toC1 = STRICTLY_CONVERTIBLE; 415 for(int i = 0; i < t1.length; ++i) 416 { 417 Class<?> c1 = t1[i] == null ? null : IntrospectionUtils.getTypeClass(t1[i]); 418 Class<?> c2 = t2[i] == null ? null : IntrospectionUtils.getTypeClass(t2[i]); 419 boolean last = !fixedLengths && (i == t1.length - 1); 420 if (t1[i] == null && t2[i] != null || t1[i] != null && t2[i] == null || !t1[i].equals(t2[i])) 421 { 422 if (t1[i] == null) 423 { 424 fromC2toC1 = NOT_CONVERTIBLE; 425 if (c2 != null && c2.isPrimitive()) 426 { 427 fromC1toC2 = NOT_CONVERTIBLE; 428 } 429 } 430 else if (t2[i] == null) 431 { 432 fromC1toC2 = NOT_CONVERTIBLE; 433 if (c1 != null && c1.isPrimitive()) 434 { 435 fromC2toC1 = NOT_CONVERTIBLE; 436 } 437 } 438 else 439 { 440 if (c1 != null) 441 { 442 switch (fromC1toC2) 443 { 444 case STRICTLY_CONVERTIBLE: 445 if (isStrictConvertible(t2[i], c1, last)) break; 446 fromC1toC2 = IMPLCITLY_CONVERTIBLE; 447 case IMPLCITLY_CONVERTIBLE: 448 if (isConvertible(t2[i], c1, last)) break; 449 fromC1toC2 = EXPLICITLY_CONVERTIBLE; 450 case EXPLICITLY_CONVERTIBLE: 451 if (isExplicitlyConvertible(t2[i], c1, last)) break; 452 fromC1toC2 = NOT_CONVERTIBLE; 453 } 454 } 455 else if (fromC1toC2 > NOT_CONVERTIBLE) 456 { 457 fromC1toC2 = TypeUtils.isAssignable(t1[i], t2[i]) ? 458 Math.min(fromC1toC2, IMPLCITLY_CONVERTIBLE) : 459 NOT_CONVERTIBLE; 460 } 461 if (c2 != null) 462 { 463 switch (fromC2toC1) 464 { 465 case STRICTLY_CONVERTIBLE: 466 if (isStrictConvertible(t1[i], c2, last)) break; 467 fromC2toC1 = IMPLCITLY_CONVERTIBLE; 468 case IMPLCITLY_CONVERTIBLE: 469 if (isConvertible(t1[i], c2, last)) break; 470 fromC2toC1 = EXPLICITLY_CONVERTIBLE; 471 case EXPLICITLY_CONVERTIBLE: 472 if (isExplicitlyConvertible(t1[i], c2, last)) break; 473 fromC2toC1 = NOT_CONVERTIBLE; 474 } 475 } 476 else if (fromC2toC1 > NOT_CONVERTIBLE) 477 { 478 fromC2toC1 = TypeUtils.isAssignable(t2[i], t1[i]) ? 479 Math.min(fromC2toC1, IMPLCITLY_CONVERTIBLE) : 480 NOT_CONVERTIBLE; 481 } 482 } 483 } 484 } 485 486 if (fromC1toC2 == NOT_CONVERTIBLE && fromC2toC1 == NOT_CONVERTIBLE) 487 { 488 /* 489 * Incomparable due to cross-assignable arguments (i.e. 490 * foo(String, Foo) vs. foo(Foo, String)) 491 */ 492 return INCOMPARABLE; 493 } 494 495 if (fromC1toC2 > fromC2toC1) 496 { 497 return MORE_SPECIFIC; 498 } 499 else if (fromC2toC1 > fromC1toC2) 500 { 501 return LESS_SPECIFIC; 502 } 503 else 504 { 505 /* 506 * If one method accepts varargs and the other does not, 507 * call the non-vararg one more specific. 508 */ 509 boolean last1Array = t1IsVararag || !fixedLengths && TypeUtils.isArrayType (t1[t1.length - 1]); 510 boolean last2Array = t2IsVararag || !fixedLengths && TypeUtils.isArrayType(t2[t2.length - 1]); 511 if (last1Array && !last2Array) 512 { 513 return LESS_SPECIFIC; 514 } 515 if (!last1Array && last2Array) 516 { 517 return MORE_SPECIFIC; 518 } 519 } 520 return EQUIVALENT; 521 } 522 523 /** 524 * Returns the applicability of the supplied method against actual argument types. 525 * 526 * @param method method that will be called 527 * @param classes arguments to method 528 * @return the level of applicability: 529 * 0 = not applicable 530 * 1 = explicitly applicable (i.e. using stock or custom conversion handlers) 531 * 2 = implicitly applicable (i.e. using JAva implicit boxing/unboxing and primitive types widening) 532 * 3 = strictly applicable 533 */ getApplicability(Method method, Class<?>[] classes)534 private int getApplicability(Method method, Class<?>[] classes) 535 { 536 Type[] methodArgs = method.getGenericParameterTypes(); 537 int ret = STRICTLY_CONVERTIBLE; 538 if (methodArgs.length > classes.length) 539 { 540 // if there's just one more methodArg than class arg 541 // and the last methodArg is an array, then treat it as a vararg 542 if (methodArgs.length == classes.length + 1 && TypeUtils.isArrayType(methodArgs[methodArgs.length - 1])) 543 { 544 // all the args preceding the vararg must match 545 for (int i = 0; i < classes.length; i++) 546 { 547 if (!isStrictConvertible(methodArgs[i], classes[i], false)) 548 { 549 if (isConvertible(methodArgs[i], classes[i], false)) 550 { 551 ret = Math.min(ret, IMPLCITLY_CONVERTIBLE); 552 } 553 else if (isExplicitlyConvertible(methodArgs[i], classes[i], false)) 554 { 555 ret = Math.min(ret, EXPLICITLY_CONVERTIBLE); 556 } 557 else 558 { 559 return NOT_CONVERTIBLE; 560 } 561 } 562 } 563 return ret; 564 } 565 else 566 { 567 return NOT_CONVERTIBLE; 568 } 569 } 570 else if (methodArgs.length == classes.length) 571 { 572 // this will properly match when the last methodArg 573 // is an array/varargs and the last class is the type of array 574 // (e.g. String when the method is expecting String...) 575 for(int i = 0; i < classes.length; ++i) 576 { 577 boolean possibleVararg = i == classes.length - 1 && TypeUtils.isArrayType(methodArgs[i]); 578 if (!isStrictConvertible(methodArgs[i], classes[i], possibleVararg)) 579 { 580 if (isConvertible(methodArgs[i], classes[i], possibleVararg)) 581 { 582 ret = Math.min(ret, IMPLCITLY_CONVERTIBLE); 583 } 584 else if (isExplicitlyConvertible(methodArgs[i], classes[i], possibleVararg)) 585 { 586 ret = Math.min(ret, EXPLICITLY_CONVERTIBLE); 587 } 588 else 589 { 590 return NOT_CONVERTIBLE; 591 } 592 } 593 } 594 return ret; 595 } 596 else if (methodArgs.length > 0) // more arguments given than the method accepts; check for varargs 597 { 598 // check that the last methodArg is an array 599 Type lastarg = methodArgs[methodArgs.length - 1]; 600 if (!TypeUtils.isArrayType(lastarg)) 601 { 602 return NOT_CONVERTIBLE; 603 } 604 605 // check that they all match up to the last method arg component type 606 for (int i = 0; i < methodArgs.length - 1; ++i) 607 { 608 if (!isStrictConvertible(methodArgs[i], classes[i], false)) 609 { 610 if (isConvertible(methodArgs[i], classes[i], false)) 611 { 612 ret = Math.min(ret, IMPLCITLY_CONVERTIBLE); 613 } 614 else if (isExplicitlyConvertible(methodArgs[i], classes[i], false)) 615 { 616 ret = Math.min(ret, EXPLICITLY_CONVERTIBLE); 617 } 618 else 619 { 620 return NOT_CONVERTIBLE; 621 } 622 } 623 } 624 625 // check that all remaining arguments are convertible to the vararg type 626 Type vararg = TypeUtils.getArrayComponentType(lastarg); 627 for (int i = methodArgs.length - 1; i < classes.length; ++i) 628 { 629 if (!isStrictConvertible(vararg, classes[i], false)) 630 { 631 if (isConvertible(vararg, classes[i], false)) 632 { 633 ret = Math.min(ret, IMPLCITLY_CONVERTIBLE); 634 } 635 else if (isExplicitlyConvertible(vararg, classes[i], false)) 636 { 637 ret = Math.min(ret, EXPLICITLY_CONVERTIBLE); 638 } 639 else 640 { 641 return NOT_CONVERTIBLE; 642 } 643 } 644 } 645 return ret; 646 } 647 return NOT_CONVERTIBLE; 648 } 649 650 /** 651 * Returns true if <code>actual</code> is convertible to <code>formal</code> by implicit Java method call conversions 652 * 653 * @param formal 654 * @param actual 655 * @param possibleVarArg 656 * @return convertible 657 */ isConvertible(Type formal, Class<?> actual, boolean possibleVarArg)658 private boolean isConvertible(Type formal, Class<?> actual, boolean possibleVarArg) 659 { 660 return IntrospectionUtils. 661 isMethodInvocationConvertible(formal, actual, possibleVarArg); 662 } 663 664 /** 665 * Returns true if <code>actual</code> is strictly convertible to <code>formal</code> (aka without implicit 666 * boxing/unboxing) 667 * 668 * @param formal 669 * @param actual 670 * @param possibleVarArg 671 * @return convertible 672 */ isStrictConvertible(Type formal, Class<?> actual, boolean possibleVarArg)673 private static boolean isStrictConvertible(Type formal, Class<?> actual, boolean possibleVarArg) 674 { 675 return IntrospectionUtils. 676 isStrictMethodInvocationConvertible(formal, actual, possibleVarArg); 677 } 678 679 /** 680 * Returns true if <code>actual</code> is convertible to <code>formal</code> using an explicit converter 681 * 682 * @param formal 683 * @param actual 684 * @param possibleVarArg 685 * @return 686 */ isExplicitlyConvertible(Type formal, Class<?> actual, boolean possibleVarArg)687 private boolean isExplicitlyConvertible(Type formal, Class<?> actual, boolean possibleVarArg) 688 { 689 return conversionHandler != null && conversionHandler.isExplicitlyConvertible(formal, actual, possibleVarArg); 690 } 691 } 692