001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.commons.jexl2.internal.introspection;
018 import java.util.List;
019 import java.util.LinkedList;
020 import java.util.Iterator;
021 import java.lang.reflect.Constructor;
022 import java.lang.reflect.Method;
023 import java.util.Arrays;
024
025 /**
026 * A method key usable by the introspector cache.
027 * <p>
028 * This stores a method (or class) name and parameters.
029 * </p>
030 * <p>
031 * This replaces the original key scheme which used to build the key
032 * by concatenating the method name and parameters class names as one string
033 * with the exception that primitive types were converted to their object class equivalents.
034 * </p>
035 * <p>
036 * The key is still based on the same information, it is just wrapped in an object instead.
037 * Primitive type classes are converted to they object equivalent to make a key;
038 * int foo(int) and int foo(Integer) do generate the same key.
039 * </p>
040 * A key can be constructed either from arguments (array of objects) or from parameters
041 * (array of class).
042 * Roughly 3x faster than string key to access the map & uses less memory.
043 *
044 * For the parameters methods:
045 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
046 * @author <a href="mailto:bob@werken.com">Bob McWhirter</a>
047 * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph Reck</a>
048 * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
049 * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
050 * @author Nathan Bubna
051 */
052 public final class MethodKey {
053 /** The hash code. */
054 private final int hashCode;
055 /** The method name. */
056 private final String method;
057 /** The parameters. */
058 private final Class<?>[] params;
059 /** A marker for empty parameter list. */
060 private static final Class<?>[] NOARGS = new Class<?>[0];
061 /** The hash code constants. */
062 private static final int HASH = 37;
063
064 /**
065 * Creates a key from a method name and a set of arguments.
066 * @param aMethod the method to generate the key from
067 * @param args the intended method arguments
068 */
069 public MethodKey(String aMethod, Object[] args) {
070 super();
071 // !! keep this in sync with the other ctor (hash code) !!
072 this.method = aMethod;
073 int hash = this.method.hashCode();
074 final int size;
075 // CSOFF: InnerAssignment
076 if (args != null && (size = args.length) > 0) {
077 this.params = new Class<?>[size];
078 for (int p = 0; p < size; ++p) {
079 Object arg = args[p];
080 // null arguments use void as Void.class as marker
081 Class<?> parm = arg == null ? Void.class : arg.getClass();
082 hash = (HASH * hash) + parm.hashCode();
083 this.params[p] = parm;
084 }
085 } else {
086 this.params = NOARGS;
087 }
088 this.hashCode = hash;
089 }
090
091 /**
092 * Creates a key from a method.
093 * @param aMethod the method to generate the key from.
094 */
095 MethodKey(Method aMethod) {
096 this(aMethod.getName(), aMethod.getParameterTypes());
097 }
098
099 /**
100 * Creates a key from a method name and a set of parameters.
101 * @param aMethod the method to generate the key from
102 * @param args the intended method parameters
103 */
104 MethodKey(String aMethod, Class<?>[] args) {
105 super();
106 // !! keep this in sync with the other ctor (hash code) !!
107 this.method = aMethod.intern();
108 int hash = this.method.hashCode();
109 final int size;
110 // CSOFF: InnerAssignment
111 if (args != null && (size = args.length) > 0) {
112 this.params = new Class<?>[size];
113 for (int p = 0; p < size; ++p) {
114 Class<?> parm = ClassMap.MethodCache.primitiveClass(args[p]);
115 hash = (HASH * hash) + parm.hashCode();
116 this.params[p] = parm;
117 }
118 } else {
119 this.params = NOARGS;
120 }
121 this.hashCode = hash;
122 }
123
124 /**
125 * Gets this key's method name.
126 * @return the method name
127 */
128 String getMethod() {
129 return method;
130 }
131
132 /**
133 * Gets this key's method parameter classes.
134 * @return the parameters
135 */
136 Class<?>[] getParameters() {
137 return params;
138 }
139
140 /** {@inheritDoc} */
141 @Override
142 public int hashCode() {
143 return hashCode;
144 }
145
146 /** {@inheritDoc} */
147 @Override
148 public boolean equals(Object obj) {
149 if (obj instanceof MethodKey) {
150 MethodKey key = (MethodKey) obj;
151 return method.equals(key.method) && Arrays.equals(params, key.params);
152 }
153 return false;
154 }
155
156 /** {@inheritDoc} */
157 @Override
158 public String toString() {
159 StringBuilder builder = new StringBuilder(method);
160 for (Class<?> c : params) {
161 builder.append(c == Void.class ? "null" : c.getName());
162 }
163 return builder.toString();
164 }
165
166 /**
167 * Outputs a human readable debug representation of this key.
168 * @return method(p0, p1, ...)
169 */
170 public String debugString() {
171 StringBuilder builder = new StringBuilder(method);
172 builder.append('(');
173 for (int i = 0; i < params.length; i++) {
174 if (i > 0) {
175 builder.append(", ");
176 }
177 builder.append(Void.class == params[i] ? "null" : params[i].getName());
178 }
179 builder.append(')');
180 return builder.toString();
181 }
182
183 /**
184 * Gets the most specific method that is applicable to the parameters of this key.
185 * @param methods a list of methods.
186 * @return the most specific method.
187 * @throws MethodKey.AmbiguousException if there is more than one.
188 */
189 public Method getMostSpecificMethod(List<Method> methods) {
190 return METHODS.getMostSpecific(methods, params);
191 }
192
193 /**
194 * Gets the most specific constructor that is applicable to the parameters of this key.
195 * @param methods a list of constructors.
196 * @return the most specific constructor.
197 * @throws MethodKey.AmbiguousException if there is more than one.
198 */
199 public Constructor<?> getMostSpecificConstructor(List<Constructor<?>> methods) {
200 return CONSTRUCTORS.getMostSpecific(methods, params);
201 }
202
203 /**
204 * Determines whether a type represented by a class object is
205 * convertible to another type represented by a class object using a
206 * method invocation conversion, treating object types of primitive
207 * types as if they were primitive types (that is, a Boolean actual
208 * parameter type matches boolean primitive formal type). This behavior
209 * is because this method is used to determine applicable methods for
210 * an actual parameter list, and primitive types are represented by
211 * their object duals in reflective method calls.
212 *
213 * @param formal the formal parameter type to which the actual
214 * parameter type should be convertible
215 * @param actual the actual parameter type.
216 * @param possibleVarArg whether or not we're dealing with the last parameter
217 * in the method declaration
218 * @return true if either formal type is assignable from actual type,
219 * or formal is a primitive type and actual is its corresponding object
220 * type or an object type of a primitive type that can be converted to
221 * the formal type.
222 */
223 public static boolean isInvocationConvertible(Class<?> formal,
224 Class<?> actual,
225 boolean possibleVarArg) {
226 /* if it's a null, it means the arg was null */
227 if (actual == null && !formal.isPrimitive()) {
228 return true;
229 }
230
231 /* Check for identity or widening reference conversion */
232 if (actual != null && formal.isAssignableFrom(actual)) {
233 return true;
234 }
235
236 // CSOFF: NeedBraces
237 /* Check for boxing with widening primitive conversion. Note that
238 * actual parameters are never primitives. */
239 if (formal.isPrimitive()) {
240 if (formal == Boolean.TYPE && actual == Boolean.class)
241 return true;
242 if (formal == Character.TYPE && actual == Character.class)
243 return true;
244 if (formal == Byte.TYPE && actual == Byte.class)
245 return true;
246 if (formal == Short.TYPE
247 && (actual == Short.class || actual == Byte.class))
248 return true;
249 if (formal == Integer.TYPE
250 && (actual == Integer.class || actual == Short.class
251 || actual == Byte.class))
252 return true;
253 if (formal == Long.TYPE
254 && (actual == Long.class || actual == Integer.class
255 || actual == Short.class || actual == Byte.class))
256 return true;
257 if (formal == Float.TYPE
258 && (actual == Float.class || actual == Long.class
259 || actual == Integer.class || actual == Short.class
260 || actual == Byte.class))
261 return true;
262 if (formal == Double.TYPE
263 && (actual == Double.class || actual == Float.class
264 || actual == Long.class || actual == Integer.class
265 || actual == Short.class || actual == Byte.class))
266 return true;
267 }
268 // CSON: NeedBraces
269
270 /* Check for vararg conversion. */
271 if (possibleVarArg && formal.isArray()) {
272 if (actual != null && actual.isArray()) {
273 actual = actual.getComponentType();
274 }
275 return isInvocationConvertible(formal.getComponentType(),
276 actual, false);
277 }
278 return false;
279 }
280
281 /**
282 * Determines whether a type represented by a class object is
283 * convertible to another type represented by a class object using a
284 * method invocation conversion, without matching object and primitive
285 * types. This method is used to determine the more specific type when
286 * comparing signatures of methods.
287 *
288 * @param formal the formal parameter type to which the actual
289 * parameter type should be convertible
290 * @param actual the actual parameter type.
291 * @param possibleVarArg whether or not we're dealing with the last parameter
292 * in the method declaration
293 * @return true if either formal type is assignable from actual type,
294 * or formal and actual are both primitive types and actual can be
295 * subject to widening conversion to formal.
296 */
297 public static boolean isStrictInvocationConvertible(Class<?> formal,
298 Class<?> actual,
299 boolean possibleVarArg) {
300 /* we shouldn't get a null into, but if so */
301 if (actual == null && !formal.isPrimitive()) {
302 return true;
303 }
304
305 /* Check for identity or widening reference conversion */
306 if (formal.isAssignableFrom(actual)) {
307 return true;
308 }
309
310 // CSOFF: NeedBraces
311 /* Check for widening primitive conversion. */
312 if (formal.isPrimitive()) {
313 if (formal == Short.TYPE && (actual == Byte.TYPE))
314 return true;
315 if (formal == Integer.TYPE
316 && (actual == Short.TYPE || actual == Byte.TYPE))
317 return true;
318 if (formal == Long.TYPE
319 && (actual == Integer.TYPE || actual == Short.TYPE
320 || actual == Byte.TYPE))
321 return true;
322 if (formal == Float.TYPE
323 && (actual == Long.TYPE || actual == Integer.TYPE
324 || actual == Short.TYPE || actual == Byte.TYPE))
325 return true;
326 if (formal == Double.TYPE
327 && (actual == Float.TYPE || actual == Long.TYPE
328 || actual == Integer.TYPE || actual == Short.TYPE
329 || actual == Byte.TYPE))
330 return true;
331 }
332 // CSON: NeedBraces
333
334 /* Check for vararg conversion. */
335 if (possibleVarArg && formal.isArray()) {
336 if (actual != null && actual.isArray()) {
337 actual = actual.getComponentType();
338 }
339 return isStrictInvocationConvertible(formal.getComponentType(),
340 actual, false);
341 }
342 return false;
343 }
344
345 /**
346 * whether a method/ctor is more specific than a previously compared one.
347 */
348 private static final int MORE_SPECIFIC = 0;
349 /**
350 * whether a method/ctor is less specific than a previously compared one.
351 */
352 private static final int LESS_SPECIFIC = 1;
353 /**
354 * A method/ctor doesn't match a previously compared one.
355 */
356 private static final int INCOMPARABLE = 2;
357
358 /**
359 * Simple distinguishable exception, used when
360 * we run across ambiguous overloading. Caught
361 * by the introspector.
362 */
363 public static class AmbiguousException extends RuntimeException {
364 /**
365 * Version Id for serializable.
366 */
367 private static final long serialVersionUID = -2314636505414551664L;
368 }
369
370 /**
371 * Utility for parameters matching.
372 * @param <T> Method or Constructor
373 */
374 private abstract static class Parameters<T> {
375 /**
376 * Extract the parameter types from its applicable argument.
377 * @param app a method or constructor
378 * @return the parameters
379 */
380 protected abstract Class<?>[] getParameterTypes(T app);
381
382 // CSOFF: RedundantThrows
383 /**
384 * Gets the most specific method that is applicable to actual argument types.
385 * @param methods a list of methods.
386 * @param classes list of argument types.
387 * @return the most specific method.
388 * @throws MethodKey.AmbiguousException if there is more than one.
389 */
390 private T getMostSpecific(List<T> methods, Class<?>[] classes) {
391 LinkedList<T> applicables = getApplicables(methods, classes);
392
393 if (applicables.isEmpty()) {
394 return null;
395 }
396
397 if (applicables.size() == 1) {
398 return applicables.getFirst();
399 }
400
401 /*
402 * This list will contain the maximally specific methods. Hopefully at
403 * the end of the below loop, the list will contain exactly one method,
404 * (the most specific method) otherwise we have ambiguity.
405 */
406
407 LinkedList<T> maximals = new LinkedList<T>();
408
409 for (Iterator<T> applicable = applicables.iterator();
410 applicable.hasNext();) {
411 T app = applicable.next();
412 Class<?>[] appArgs = getParameterTypes(app);
413
414 boolean lessSpecific = false;
415
416 for (Iterator<T> maximal = maximals.iterator();
417 !lessSpecific && maximal.hasNext();) {
418 T max = maximal.next();
419
420 // CSOFF: MissingSwitchDefault
421 switch (moreSpecific(appArgs, getParameterTypes(max))) {
422 case MORE_SPECIFIC:
423 /*
424 * This method is more specific than the previously
425 * known maximally specific, so remove the old maximum.
426 */
427 maximal.remove();
428 break;
429
430 case LESS_SPECIFIC:
431 /*
432 * This method is less specific than some of the
433 * currently known maximally specific methods, so we
434 * won't add it into the set of maximally specific
435 * methods
436 */
437
438 lessSpecific = true;
439 break;
440 }
441 } // CSON: MissingSwitchDefault
442
443 if (!lessSpecific) {
444 maximals.addLast(app);
445 }
446 }
447
448 if (maximals.size() > 1) {
449 // We have more than one maximally specific method
450 throw new AmbiguousException();
451 }
452
453 return maximals.getFirst();
454 } // CSON: RedundantThrows
455
456 /**
457 * Determines which method signature (represented by a class array) is more
458 * specific. This defines a partial ordering on the method signatures.
459 *
460 * @param c1 first signature to compare
461 * @param c2 second signature to compare
462 * @return MORE_SPECIFIC if c1 is more specific than c2, LESS_SPECIFIC if
463 * c1 is less specific than c2, INCOMPARABLE if they are incomparable.
464 */
465 private int moreSpecific(Class<?>[] c1, Class<?>[] c2) {
466 boolean c1MoreSpecific = false;
467 boolean c2MoreSpecific = false;
468
469 // compare lengths to handle comparisons where the size of the arrays
470 // doesn't match, but the methods are both applicable due to the fact
471 // that one is a varargs method
472 if (c1.length > c2.length) {
473 return MORE_SPECIFIC;
474 }
475 if (c2.length > c1.length) {
476 return LESS_SPECIFIC;
477 }
478
479 // ok, move on and compare those of equal lengths
480 for (int i = 0; i < c1.length; ++i) {
481 if (c1[i] != c2[i]) {
482 boolean last = (i == c1.length - 1);
483 c1MoreSpecific = c1MoreSpecific || isStrictConvertible(c2[i], c1[i], last);
484 c2MoreSpecific = c2MoreSpecific || isStrictConvertible(c1[i], c2[i], last);
485 }
486 }
487
488 if (c1MoreSpecific) {
489 if (c2MoreSpecific) {
490 /*
491 * Incomparable due to cross-assignable arguments (i.e.
492 * foo(String, Object) vs. foo(Object, String))
493 */
494
495 return INCOMPARABLE;
496 }
497
498 return MORE_SPECIFIC;
499 }
500
501 if (c2MoreSpecific) {
502 return LESS_SPECIFIC;
503 }
504
505 /*
506 * Incomparable due to non-related arguments (i.e.
507 * foo(Runnable) vs. foo(Serializable))
508 */
509
510 return INCOMPARABLE;
511 }
512
513 /**
514 * Returns all methods that are applicable to actual argument types.
515 *
516 * @param methods list of all candidate methods
517 * @param classes the actual types of the arguments
518 * @return a list that contains only applicable methods (number of
519 * formal and actual arguments matches, and argument types are assignable
520 * to formal types through a method invocation conversion).
521 */
522 private LinkedList<T> getApplicables(List<T> methods, Class<?>[] classes) {
523 LinkedList<T> list = new LinkedList<T>();
524
525 for (Iterator<T> imethod = methods.iterator(); imethod.hasNext();) {
526 T method = imethod.next();
527 if (isApplicable(method, classes)) {
528 list.add(method);
529 }
530
531 }
532 return list;
533 }
534
535 /**
536 * Returns true if the supplied method is applicable to actual
537 * argument types.
538 *
539 * @param method method that will be called
540 * @param classes arguments to method
541 * @return true if method is applicable to arguments
542 */
543 private boolean isApplicable(T method, Class<?>[] classes) {
544 Class<?>[] methodArgs = getParameterTypes(method);
545
546 if (methodArgs.length > classes.length) {
547 // if there's just one more methodArg than class arg
548 // and the last methodArg is an array, then treat it as a vararg
549 return methodArgs.length == classes.length + 1 && methodArgs[methodArgs.length - 1].isArray();
550 }
551 if (methodArgs.length == classes.length) {
552 // this will properly match when the last methodArg
553 // is an array/varargs and the last class is the type of array
554 // (e.g. String when the method is expecting String...)
555 for (int i = 0; i < classes.length; ++i) {
556 if (!isConvertible(methodArgs[i], classes[i], false)) {
557 // if we're on the last arg and the method expects an array
558 if (i == classes.length - 1 && methodArgs[i].isArray()) {
559 // check to see if the last arg is convertible
560 // to the array's component type
561 return isConvertible(methodArgs[i], classes[i], true);
562 }
563 return false;
564 }
565 }
566 return true;
567 }
568 // more arguments given than the method accepts; check for varargs
569 if (methodArgs.length > 0) {
570 // check that the last methodArg is an array
571 Class<?> lastarg = methodArgs[methodArgs.length - 1];
572 if (!lastarg.isArray()) {
573 return false;
574 }
575
576 // check that they all match up to the last method arg
577 for (int i = 0; i < methodArgs.length - 1; ++i) {
578 if (!isConvertible(methodArgs[i], classes[i], false)) {
579 return false;
580 }
581 }
582
583 // check that all remaining arguments are convertible to the vararg type
584 Class<?> vararg = lastarg.getComponentType();
585 for (int i = methodArgs.length - 1; i < classes.length; ++i) {
586 if (!isConvertible(vararg, classes[i], false)) {
587 return false;
588 }
589 }
590 return true;
591 }
592 // no match
593 return false;
594 }
595
596 /**
597 * @see #isInvocationConvertible(Class, Class, boolean)
598 * @param formal the formal parameter type to which the actual
599 * parameter type should be convertible
600 * @param actual the actual parameter type.
601 * @param possibleVarArg whether or not we're dealing with the last parameter
602 * in the method declaration
603 * @return see isMethodInvocationConvertible.
604 */
605 private boolean isConvertible(Class<?> formal, Class<?> actual,
606 boolean possibleVarArg) {
607 // if we see Void.class, the argument was null
608 return isInvocationConvertible(formal, actual.equals(Void.class)? null : actual, possibleVarArg);
609 }
610
611 /**
612 * @see #isStrictInvocationConvertible(Class, Class, boolean)
613 * @param formal the formal parameter type to which the actual
614 * parameter type should be convertible
615 * @param actual the actual parameter type.
616 * @param possibleVarArg whether or not we're dealing with the last parameter
617 * in the method declaration
618 * @return see isStrictMethodInvocationConvertible.
619 */
620 private boolean isStrictConvertible(Class<?> formal, Class<?> actual,
621 boolean possibleVarArg) {
622 // if we see Void.class, the argument was null
623 return isStrictInvocationConvertible(formal, actual.equals(Void.class)? null : actual, possibleVarArg);
624 }
625
626 }
627
628 /**
629 * The parameter matching service for methods.
630 */
631 private static final Parameters<Method> METHODS = new Parameters<Method>() {
632 @Override
633 protected Class<?>[] getParameterTypes(Method app) {
634 return app.getParameterTypes();
635 }
636 };
637
638
639 /**
640 * The parameter matching service for constructors.
641 */
642 private static final Parameters<Constructor<?>> CONSTRUCTORS = new Parameters<Constructor<?>>() {
643 @Override
644 protected Class<?>[] getParameterTypes(Constructor<?> app) {
645 return app.getParameterTypes();
646 }
647 };
648 }