/* * Copyright 2016 Nabarun Mondal * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.noga.njexl.lang.internal.introspection; import java.util.List; import java.util.LinkedList; import java.util.Iterator; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.Arrays; /** * A method key usable by the introspector cache. * <p> * This stores a method (or class) name and parameters. * </p> * <p> * This replaces the original key scheme which used to build the key * by concatenating the method name and parameters class names as one string * with the exception that primitive types were converted to their object class equivalents. * </p> * <p> * The key is still based on the same information, it is just wrapped in an object instead. * Primitive type classes are converted to they object equivalent to make a key; * int foo(int) and int foo(Integer) do generate the same key. * </p> * A key can be constructed either from arguments (array of objects) or from parameters * (array of class). * Roughly 3x faster than string key to access the map & uses less memory. */ public final class MethodKey { /** The hash code. */ private final int hashCode; /** The method name. */ private final String method; /** The parameters. */ private final Class<?>[] params; /** A marker for empty parameter list. */ private static final Class<?>[] NOARGS = new Class<?>[0]; /** The hash code constants. */ private static final int HASH = 37; /** * Creates a key from a method name and a set of arguments. * @param aMethod the method to generate the key from * @param args the intended method arguments */ public MethodKey(String aMethod, Object[] args) { super(); // !! keep this in sync with the other ctor (hash code) !! this.method = aMethod; int hash = this.method.hashCode(); final int size; // CSOFF: InnerAssignment if (args != null && (size = args.length) > 0) { this.params = new Class<?>[size]; for (int p = 0; p < size; ++p) { Object arg = args[p]; // null arguments use void as Void.class as marker Class<?> parm = arg == null ? Void.class : arg.getClass(); hash = (HASH * hash) + parm.hashCode(); this.params[p] = parm; } } else { this.params = NOARGS; } this.hashCode = hash; } /** * Creates a key from a method. * @param aMethod the method to generate the key from. */ MethodKey(Method aMethod) { this(aMethod.getName(), aMethod.getParameterTypes()); } /** * Creates a key from a method name and a set of parameters. * @param aMethod the method to generate the key from * @param args the intended method parameters */ MethodKey(String aMethod, Class<?>[] args) { super(); // !! keep this in sync with the other ctor (hash code) !! this.method = aMethod.intern(); int hash = this.method.hashCode(); final int size; // CSOFF: InnerAssignment if (args != null && (size = args.length) > 0) { this.params = new Class<?>[size]; for (int p = 0; p < size; ++p) { Class<?> parm = ClassMap.MethodCache.primitiveClass(args[p]); hash = (HASH * hash) + parm.hashCode(); this.params[p] = parm; } } else { this.params = NOARGS; } this.hashCode = hash; } /** * Gets this key's method name. * @return the method name */ String getMethod() { return method; } /** * Gets this key's method parameter classes. * @return the parameters */ Class<?>[] getParameters() { return params; } /** {@inheritDoc} */ @Override public int hashCode() { return hashCode; } /** {@inheritDoc} */ @Override public boolean equals(Object obj) { if (obj instanceof MethodKey) { MethodKey key = (MethodKey) obj; return method.equals(key.method) && Arrays.equals(params, key.params); } return false; } /** {@inheritDoc} */ @Override public String toString() { StringBuilder builder = new StringBuilder(method); for (Class<?> c : params) { builder.append(c == Void.class ? "null" : c.getName()); } return builder.toString(); } /** * Outputs a human readable debug representation of this key. * @return method(p0, p1, ...) */ public String debugString() { StringBuilder builder = new StringBuilder(method); builder.append('('); for (int i = 0; i < params.length; i++) { if (i > 0) { builder.append(", "); } builder.append(Void.class == params[i] ? "null" : params[i].getName()); } builder.append(')'); return builder.toString(); } /** * Gets the most specific method that is applicable to the parameters of this key. * @param methods a list of methods. * @return the most specific method. * @throws MethodKey.AmbiguousException if there is more than one. */ public Method getMostSpecificMethod(List<Method> methods) { return METHODS.getMostSpecific(methods, params); } /** * Gets the most specific constructor that is applicable to the parameters of this key. * @param methods a list of constructors. * @return the most specific constructor. * @throws MethodKey.AmbiguousException if there is more than one. */ public Constructor<?> getMostSpecificConstructor(List<Constructor<?>> methods) { return CONSTRUCTORS.getMostSpecific(methods, params); } /** * Determines whether a type represented by a class object is * convertible to another type represented by a class object using a * method invocation conversion, treating object types of primitive * types as if they were primitive types (that is, a Boolean actual * parameter type matches boolean primitive formal type). This behavior * is because this method is used to determine applicable methods for * an actual parameter list, and primitive types are represented by * their object duals in reflective method calls. * * @param formal the formal parameter type to which the actual * parameter type should be convertible * @param actual the actual parameter type. * @param possibleVarArg whether or not we're dealing with the last parameter * in the method declaration * @return true if either formal type is assignable from actual type, * or formal is a primitive type and actual is its corresponding object * type or an object type of a primitive type that can be converted to * the formal type. */ public static boolean isInvocationConvertible(Class<?> formal, Class<?> actual, boolean possibleVarArg) { /* if it's a null, it means the arg was null */ if (actual == null && !formal.isPrimitive()) { return true; } /* Check for identity or widening reference conversion */ if (actual != null && formal.isAssignableFrom(actual)) { return true; } /* Check for boxing with widening primitive conversion. Note that * actual parameters are never primitives. */ if (formal.isPrimitive()) { if (formal == Boolean.TYPE && actual == Boolean.class) { return true; } if (formal == Character.TYPE && actual == Character.class) { return true; } if (formal == Byte.TYPE && actual == Byte.class) { return true; } if (formal == Short.TYPE && (actual == Short.class || actual == Byte.class)) { return true; } if (formal == Integer.TYPE && (actual == Integer.class || actual == Short.class || actual == Byte.class)) { return true; } if (formal == Long.TYPE && (actual == Long.class || actual == Integer.class || actual == Short.class || actual == Byte.class)) { return true; } if (formal == Float.TYPE && (actual == Float.class || actual == Long.class || actual == Integer.class || actual == Short.class || actual == Byte.class)) { return true; } if (formal == Double.TYPE && (actual == Double.class || actual == Float.class || actual == Long.class || actual == Integer.class || actual == Short.class || actual == Byte.class)) { return true; } } /* Check for vararg conversion. */ if (possibleVarArg && formal.isArray()) { if (actual != null && actual.isArray()) { actual = actual.getComponentType(); } return isInvocationConvertible(formal.getComponentType(), actual, false); } return false; } /** * Determines whether a type represented by a class object is * convertible to another type represented by a class object using a * method invocation conversion, without matching object and primitive * types. This method is used to determine the more specific type when * comparing signatures of methods. * * @param formal the formal parameter type to which the actual * parameter type should be convertible * @param actual the actual parameter type. * @param possibleVarArg whether or not we're dealing with the last parameter * in the method declaration * @return true if either formal type is assignable from actual type, * or formal and actual are both primitive types and actual can be * subject to widening conversion to formal. */ public static boolean isStrictInvocationConvertible(Class<?> formal, Class<?> actual, boolean possibleVarArg) { /* we shouldn't get a null into, but if so */ if (actual == null && !formal.isPrimitive()) { return true; } /* Check for identity or widening reference conversion */ if (formal.isAssignableFrom(actual)) { return true; } /* Check for widening primitive conversion. */ if (formal.isPrimitive()) { if (formal == Short.TYPE && (actual == Byte.TYPE)) { return true; } if (formal == Integer.TYPE && (actual == Short.TYPE || actual == Byte.TYPE)) { return true; } if (formal == Long.TYPE && (actual == Integer.TYPE || actual == Short.TYPE || actual == Byte.TYPE)) { return true; } if (formal == Float.TYPE && (actual == Long.TYPE || actual == Integer.TYPE || actual == Short.TYPE || actual == Byte.TYPE)) { return true; } if (formal == Double.TYPE && (actual == Float.TYPE || actual == Long.TYPE || actual == Integer.TYPE || actual == Short.TYPE || actual == Byte.TYPE)) { return true; } } /* Check for vararg conversion. */ if (possibleVarArg && formal.isArray()) { if (actual != null && actual.isArray()) { actual = actual.getComponentType(); } return isStrictInvocationConvertible(formal.getComponentType(), actual, false); } return false; } /** * whether a method/ctor is more specific than a previously compared one. */ private static final int MORE_SPECIFIC = 0; /** * whether a method/ctor is less specific than a previously compared one. */ private static final int LESS_SPECIFIC = 1; /** * A method/ctor doesn't match a previously compared one. */ private static final int INCOMPARABLE = 2; /** * Simple distinguishable exception, used when * we run across ambiguous overloading. Caught * by the introspector. */ public static class AmbiguousException extends RuntimeException { /** * Version Id for serializable. */ private static final long serialVersionUID = -2314636505414551664L; } /** * Utility for parameters matching. * @param <T> Method or Constructor */ private abstract static class Parameters<T> { /** * Extract the parameter types from its applicable argument. * @param app a method or constructor * @return the parameters */ protected abstract Class<?>[] getParameterTypes(T app); // CSOFF: RedundantThrows /** * Gets the most specific method that is applicable to actual argument types. * @param methods a list of methods. * @param classes list of argument types. * @return the most specific method. * @throws MethodKey.AmbiguousException if there is more than one. */ private T getMostSpecific(List<T> methods, Class<?>[] classes) { LinkedList<T> applicables = getApplicables(methods, classes); if (applicables.isEmpty()) { return null; } if (applicables.size() == 1) { return applicables.getFirst(); } /* * This list will contain the maximally specific methods. Hopefully at * the end of the below loop, the list will contain exactly one method, * (the most specific method) otherwise we have ambiguity. */ LinkedList<T> maximals = new LinkedList<T>(); for (Iterator<T> applicable = applicables.iterator(); applicable.hasNext();) { T app = applicable.next(); Class<?>[] appArgs = getParameterTypes(app); boolean lessSpecific = false; for (Iterator<T> maximal = maximals.iterator(); !lessSpecific && maximal.hasNext();) { T max = maximal.next(); // CSOFF: MissingSwitchDefault switch (moreSpecific(appArgs, getParameterTypes(max))) { case MORE_SPECIFIC: /* * This method is more specific than the previously * known maximally specific, so remove the old maximum. */ maximal.remove(); break; case LESS_SPECIFIC: /* * This method is less specific than some of the * currently known maximally specific methods, so we * won't add it into the set of maximally specific * methods */ lessSpecific = true; break; } } // CSON: MissingSwitchDefault if (!lessSpecific) { maximals.addLast(app); } } if (maximals.size() > 1) { // We have more than one maximally specific method throw new AmbiguousException(); } return maximals.getFirst(); } // CSON: RedundantThrows /** * Determines which method signature (represented by a class array) is more * specific. This defines a partial ordering on the method signatures. * * @param c1 first signature to compare * @param c2 second signature to compare * @return MORE_SPECIFIC if c1 is more specific than c2, LESS_SPECIFIC if * c1 is less specific than c2, INCOMPARABLE if they are incomparable. */ private int moreSpecific(Class<?>[] c1, Class<?>[] c2) { boolean c1MoreSpecific = false; boolean c2MoreSpecific = false; // compare lengths to handle comparisons where the size of the arrays // doesn't match, but the methods are both applicable due to the fact // that one is a varargs method if (c1.length > c2.length) { return MORE_SPECIFIC; } if (c2.length > c1.length) { return LESS_SPECIFIC; } // ok, move on and compare those of equal lengths for (int i = 0; i < c1.length; ++i) { if (c1[i] != c2[i]) { boolean last = (i == c1.length - 1); c1MoreSpecific = c1MoreSpecific || isStrictConvertible(c2[i], c1[i], last); c2MoreSpecific = c2MoreSpecific || isStrictConvertible(c1[i], c2[i], last); } } if (c1MoreSpecific) { if (c2MoreSpecific) { // Incomparable due to cross-assignable arguments (i.e. foo(String, Object) vs. foo(Object, String)) return INCOMPARABLE; } return MORE_SPECIFIC; } if (c2MoreSpecific) { return LESS_SPECIFIC; } // attempt to choose by picking the one with the greater number of primitives or latest primitive parameter int primDiff = 0; for (int c = 0; c < c1.length; ++c) { if (c1[c].isPrimitive()) { primDiff += 1 << c; } if (c2[c].isPrimitive()) { primDiff -= 1 << c; } } if (primDiff > 0) { return MORE_SPECIFIC; } else if (primDiff < 0) { return LESS_SPECIFIC; } /* * Incomparable due to non-related arguments (i.e. * foo(Runnable) vs. foo(Serializable)) */ return INCOMPARABLE; } /** * Returns all methods that are applicable to actual argument types. * * @param methods list of all candidate methods * @param classes the actual types of the arguments * @return a list that contains only applicable methods (number of * formal and actual arguments matches, and argument types are assignable * to formal types through a method invocation conversion). */ private LinkedList<T> getApplicables(List<T> methods, Class<?>[] classes) { LinkedList<T> list = new LinkedList<T>(); for (Iterator<T> imethod = methods.iterator(); imethod.hasNext();) { T method = imethod.next(); if (isApplicable(method, classes)) { list.add(method); } } return list; } /** * Returns true if the supplied method is applicable to actual * argument types. * * @param method method that will be called * @param classes arguments to method * @return true if method is applicable to arguments */ private boolean isApplicable(T method, Class<?>[] classes) { Class<?>[] methodArgs = getParameterTypes(method); // if samee number or args or // there's just one more methodArg than class arg // and the last methodArg is an array, then treat it as a vararg if (methodArgs.length == classes.length || methodArgs.length == classes.length + 1 && methodArgs[methodArgs.length - 1].isArray()) { // this will properly match when the last methodArg // is an array/varargs and the last class is the type of array // (e.g. String when the method is expecting String...) for (int i = 0; i < classes.length; ++i) { if (!isConvertible(methodArgs[i], classes[i], false)) { // if we're on the last arg and the method expects an array if (i == classes.length - 1 && methodArgs[i].isArray()) { // check to see if the last arg is convertible // to the array's component type return isConvertible(methodArgs[i], classes[i], true); } return false; } } return true; } // more arguments given than the method accepts; check for varargs if (methodArgs.length > 0) { // check that the last methodArg is an array Class<?> lastarg = methodArgs[methodArgs.length - 1]; if (!lastarg.isArray()) { return false; } // check that they all match up to the last method arg for (int i = 0; i < methodArgs.length - 1; ++i) { if (!isConvertible(methodArgs[i], classes[i], false)) { return false; } } // check that all remaining arguments are convertible to the vararg type Class<?> vararg = lastarg.getComponentType(); for (int i = methodArgs.length - 1; i < classes.length; ++i) { if (!isConvertible(vararg, classes[i], false)) { return false; } } return true; } // no match return false; } /** * @see #isInvocationConvertible(Class, Class, boolean) * @param formal the formal parameter type to which the actual * parameter type should be convertible * @param actual the actual parameter type. * @param possibleVarArg whether or not we're dealing with the last parameter * in the method declaration * @return see isMethodInvocationConvertible. */ private boolean isConvertible(Class<?> formal, Class<?> actual, boolean possibleVarArg) { // if we see Void.class, the argument was null return isInvocationConvertible(formal, actual.equals(Void.class) ? null : actual, possibleVarArg); } /** * @see #isStrictInvocationConvertible(Class, Class, boolean) * @param formal the formal parameter type to which the actual * parameter type should be convertible * @param actual the actual parameter type. * @param possibleVarArg whether or not we're dealing with the last parameter * in the method declaration * @return see isStrictMethodInvocationConvertible. */ private boolean isStrictConvertible(Class<?> formal, Class<?> actual, boolean possibleVarArg) { // if we see Void.class, the argument was null return isStrictInvocationConvertible(formal, actual.equals(Void.class) ? null : actual, possibleVarArg); } } /** * The parameter matching service for methods. */ private static final Parameters<Method> METHODS = new Parameters<Method>() { @Override protected Class<?>[] getParameterTypes(Method app) { return app.getParameterTypes(); } }; /** * The parameter matching service for constructors. */ private static final Parameters<Constructor<?>> CONSTRUCTORS = new Parameters<Constructor<?>>() { @Override protected Class<?>[] getParameterTypes(Constructor<?> app) { return app.getParameterTypes(); } }; }