All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.commons.jexl3.internal.introspection.MethodKey Maven / Gradle / Ivy

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.commons.jexl3.internal.introspection;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * A method key usable by the introspector cache.
 * 

* This stores a method (or class) name and parameters. *

*

* 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. *

*

* 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. *

* 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 and uses less memory. */ public final class MethodKey { /** The initial size of the primitive conversion map. */ private static final int PRIMITIVE_SIZE = 11; /** 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(final String aMethod, final Object[] args) { // !! 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) { final Object arg = args[p]; // null arguments use void as Void.class as marker final 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(final Method aMethod) { this(aMethod.getName(), aMethod.getParameterTypes()); } /** * Creates a key from a constructor. * @param aCtor the constructor to generate the key from. */ MethodKey(final Constructor aCtor) { this(aCtor.getDeclaringClass().getName(), aCtor.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(final String aMethod, final Class[] args) { // !! 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) { final Class parm = 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; } @Override public int hashCode() { return hashCode; } @Override public boolean equals(final Object obj) { if (obj instanceof MethodKey) { final MethodKey key = (MethodKey) obj; return method.equals(key.method) && Arrays.equals(params, key.params); } return false; } @Override public String toString() { final StringBuilder builder = new StringBuilder(method); for (final 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() { final 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(); } /** * Checks whether a method accepts a variable number of arguments. *

May be due to a subtle bug in some JVMs, if a varargs method is an override, depending on (may be) the * class introspection order, the isVarargs flag on the method itself will be false. * To circumvent the potential problem, fetch the method with the same signature from the super-classes, * - which will be different if override -and get the varargs flag from it. * @param method the method to check for varargs * @return true if declared varargs, false otherwise */ public static boolean isVarArgs(final Method method) { if (method == null) { return false; } if (method.isVarArgs()) { return true; } // before climbing up the hierarchy, verify that the last parameter is an array final Class[] ptypes = method.getParameterTypes(); if (ptypes.length == 0 || ptypes[ptypes.length - 1].getComponentType() == null) { return false; } final String mname = method.getName(); // if this is an override, was it actually declared as varargs? Class clazz = method.getDeclaringClass(); do { try { final Method m = clazz.getMethod(mname, ptypes); if (m.isVarArgs()) { return true; } } catch (final NoSuchMethodException xignore) { // this should not happen... } clazz = clazz.getSuperclass(); } while(clazz != null); return false; } /** * 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(final Method[] methods) { return METHODS.getMostSpecific(this, methods); } /** * 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(final Constructor[] methods) { return CONSTRUCTORS.getMostSpecific(this, methods); } /** * 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(final Class formal, final Class actual, final boolean possibleVarArg) { return isInvocationConvertible(formal, actual, false, possibleVarArg); } /** * 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(final Class formal, final Class actual, final boolean possibleVarArg) { return isInvocationConvertible(formal, actual, true, possibleVarArg); } /** Converts a primitive type to its corresponding class. *

* If the argument type is primitive then we want to convert our * primitive type signature to the corresponding Object type so * introspection for methods with primitive types will work * correctly. *

* @param parm a may-be primitive type class * @return the equivalent object class */ static Class primitiveClass(final Class parm) { // it was marginally faster to get from the map than call isPrimitive... //if (!parm.isPrimitive()) return parm; final Class[] prim = CONVERTIBLES.get(parm); return prim == null ? parm : prim[0]; } /** * Helper to build class arrays. * @param args the classes * @return the array */ private static Class[] asArray(final Class... args) { return args; } /** * Maps from primitive types to invocation compatible classes. *

Considering the key as a parameter type, the value is the list of argument classes that are invocation * compatible with the parameter. Example is Long is invocation convertible to long. */ private static final Map, Class[]> CONVERTIBLES; static { CONVERTIBLES = new HashMap, Class[]>(PRIMITIVE_SIZE); CONVERTIBLES.put(Boolean.TYPE, asArray(Boolean.class)); CONVERTIBLES.put(Character.TYPE, asArray(Character.class)); CONVERTIBLES.put(Byte.TYPE, asArray(Byte.class)); CONVERTIBLES.put(Short.TYPE, asArray(Short.class, Byte.class)); CONVERTIBLES.put(Integer.TYPE, asArray(Integer.class, Short.class, Byte.class)); CONVERTIBLES.put(Long.TYPE, asArray(Long.class, Integer.class, Short.class, Byte.class)); CONVERTIBLES.put(Float.TYPE, asArray(Float.class, Long.class, Integer.class, Short.class, Byte.class)); CONVERTIBLES.put(Double.TYPE, asArray(Double.class, Float.class, Long.class, Integer.class, Short.class, Byte.class)); } /** * Maps from primitive types to invocation compatible primitive types. *

Considering the key as a parameter type, the value is the list of argument types that are invocation * compatible with the parameter. Example is 'int' is invocation convertible to 'long'. */ private static final Map, Class[]> STRICT_CONVERTIBLES; static { STRICT_CONVERTIBLES = new HashMap, Class[]>(PRIMITIVE_SIZE); STRICT_CONVERTIBLES.put(Short.TYPE, asArray(Byte.TYPE)); STRICT_CONVERTIBLES.put(Integer.TYPE, asArray(Short.TYPE, Byte.TYPE)); STRICT_CONVERTIBLES.put(Long.TYPE, asArray(Integer.TYPE, Short.TYPE, Byte.TYPE)); STRICT_CONVERTIBLES.put(Float.TYPE, asArray(Long.TYPE, Integer.TYPE, Short.TYPE, Byte.TYPE)); STRICT_CONVERTIBLES.put(Double.TYPE, asArray(Float.TYPE, Long.TYPE, Integer.TYPE, Short.TYPE, Byte.TYPE)); } /** * Determines parameter-argument invocation compatibility. * * @param formal the formal parameter type * @param actual the argument type * @param strict whether the check is strict or not * @param possibleVarArg whether or not we're dealing with the last parameter in the method declaration * @return true if compatible, false otherwise */ private static boolean isInvocationConvertible( final Class formal, Class actual, final boolean strict, final boolean possibleVarArg) { /* if it's a null, it means the arg was null */ if (actual == null && !formal.isPrimitive()) { return true; } /* system asssignable, both sides must be array or not */ if (actual != null && formal.isAssignableFrom(actual) && actual.isArray() == formal.isArray()) { return true; } /* catch all... */ if (!strict && formal == Object.class) { return true; } /* Primitive conversion check. */ if (formal.isPrimitive()) { final Class[] clist = strict ? STRICT_CONVERTIBLES.get(formal) : CONVERTIBLES.get(formal); if (clist != null) { for (final Class aClass : clist) { if (actual == aClass) { return true; } } } return false; } /* Check for vararg conversion. */ if (possibleVarArg && formal.isArray()) { if (actual != null && actual.isArray()) { actual = actual.getComponentType(); } return isInvocationConvertible(formal.getComponentType(), actual, strict, 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 = -201801091655L; /** Whether this exception should be considered severe. */ private final boolean severe; /** * A severe or not ambiguous exception. * @param flag logging flag */ AmbiguousException(final boolean flag) { this.severe = flag; } /** * Whether this exception is considered severe or benign. *

Note that this is meant in the context of an ambiguous exception; benign cases can only be triggered * by null arguments often related to runtime problems (not simply on overload signatures). * @return true if severe, false if benign. */ public boolean isSevere() { return severe; } } /** * Utility for parameters matching. * @param Method or Constructor */ private abstract static class Parameters { /** * Extract the parameter types from its applicable argument. * @param app a method or constructor * @return the parameters */ protected abstract Class[] getParameterTypes(T app); /** * Whether a constructor or method handles varargs. * @param app the constructor or method * @return true if varargs, false otherwise */ protected abstract boolean isVarArgs(T app); // CSOFF: RedundantThrows /** * Gets the most specific method that is applicable to actual argument types.

* Attempts to find the most specific applicable method using the * algorithm described in the JLS section 15.12.2 (with the exception that it can't * distinguish a primitive type argument from an object type argument, since in reflection * primitive type arguments are represented by their object counterparts, so for an argument of * type (say) java.lang.Integer, it will not be able to decide between a method that takes int and a * method that takes java.lang.Integer as a parameter. *

*

* This turns out to be a relatively rare case where this is needed - however, functionality * like this is needed. *

* * @param key a method key, esp its parameters * @param methods a list of methods * @return the most specific method. * @throws MethodKey.AmbiguousException if there is more than one. */ private T getMostSpecific(final MethodKey key, final T[] methods) { final Class[] args = key.params; final LinkedList applicables = getApplicables(methods, args); 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. */ final LinkedList maximals = new LinkedList(); for (final T app : applicables) { final Class[] parms = getParameterTypes(app); boolean lessSpecific = false; final Iterator maximal = maximals.iterator(); while(!lessSpecific && maximal.hasNext()) { final T max = maximal.next(); switch (moreSpecific(args, parms, 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; default: // nothing do do } } if (!lessSpecific) { maximals.addLast(app); } } // if we have more than one maximally specific method, this call is ambiguous... if (maximals.size() > 1) { throw ambiguousException(args, applicables); } return maximals.getFirst(); } // CSON: RedundantThrows /** * Creates an ambiguous exception. *

* This method computes the severity of the ambiguity. The only non-severe case is when there is * at least one null argument and at most one applicable method or constructor has a corresponding 'Object' * parameter. * We thus consider that ambiguity is benign in presence of null arguments but in the case where * the corresponding parameter is of type Object in more than one applicable overloads. *

* Rephrasing: *

    *
  • If all arguments are valid instances - no null argument -, ambiguity is severe.
  • *
  • If there is at least one null argument, the ambiguity is severe if more than one method has a * corresponding parameter of class 'Object'.
  • *
* * @param classes the argument args * @param applicables the list of applicable methods or constructors * @return an ambiguous exception */ private AmbiguousException ambiguousException (final Class[] classes, final List applicables) { boolean severe = false; int instanceArgCount = 0; // count the number of valid instances, aka not null for(int c = 0; c < classes.length; ++c) { final Class argClazz = classes[c]; if (Void.class.equals(argClazz)) { // count the number of methods for which the current arg maps to an Object parameter int objectParmCount = 0; for (final T app : applicables) { final Class[] parmClasses = getParameterTypes(app); final Class parmClass = parmClasses[c]; if (Object.class.equals(parmClass) && (objectParmCount++ == 2)) { severe = true; break; } } } else { instanceArgCount += 1; } } return new AmbiguousException(severe || instanceArgCount == classes.length); } /** * Determines which method signature (represented by a class array) is more * specific. This defines a partial ordering on the method signatures. * * @param a the arguments signature * @param c1 first method signature to compare * @param c2 second method 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(final Class[] a, final Class[] c1, final Class[] c2) { // 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 > a.length) { return LESS_SPECIFIC; } if (c2.length > a.length) { return MORE_SPECIFIC; } if (c1.length > c2.length) { return MORE_SPECIFIC; } if (c2.length > c1.length) { return LESS_SPECIFIC; } // same length, keep ultimate param offset for vararg checks final int length = c1.length; final int ultimate = c1.length - 1; // ok, move on and compare those of equal lengths for (int i = 0; i < length; ++i) { if (c1[i] != c2[i]) { final boolean last = (i == ultimate); // argument is null, prefer an Object param if (a[i] == Void.class) { if (c1[i] == Object.class && c2[i] != Object.class) { return MORE_SPECIFIC; } if (c1[i] != Object.class && c2[i] == Object.class) { return LESS_SPECIFIC; } } // prefer primitive on non null arg, non primitive otherwise boolean c1s = isPrimitive(c1[i], last); boolean c2s = isPrimitive(c2[i], last); if (c1s != c2s) { return (c1s == (a[i] != Void.class))? MORE_SPECIFIC : LESS_SPECIFIC; } // if c2 can be converted to c1 but not the opposite, // c1 is more specific than c2 c1s = isStrictConvertible(c2[i], c1[i], last); c2s = isStrictConvertible(c1[i], c2[i], last); if (c1s != c2s) { return c1s ? MORE_SPECIFIC : LESS_SPECIFIC; } } } // Incomparable due to non-related arguments (i.e.foo(Runnable) vs. foo(Serializable)) return INCOMPARABLE; } /** * Checks whether a parameter class is a primitive. * @param c the parameter class * @param possibleVarArg true if this is the last parameter which can be a primitive array (vararg call) * @return true if primitive, false otherwise */ private boolean isPrimitive(final Class c, final boolean possibleVarArg) { if (c != null) { if (c.isPrimitive()) { return true; } if (possibleVarArg) { final Class t = c.getComponentType(); return t != null && t.isPrimitive(); } } return false; } /** * 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 getApplicables(final T[] methods, final Class[] classes) { final LinkedList list = new LinkedList(); for (final T method : methods) { 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 actuals arguments signature for method * @return true if method is applicable to arguments */ private boolean isApplicable(final T method, final Class[] actuals) { final Class[] formals = getParameterTypes(method); // if same 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 (formals.length == actuals.length) { // 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 < actuals.length; ++i) { if (!isConvertible(formals[i], actuals[i], false)) { // if we're on the last arg and the method expects an array if (i == actuals.length - 1 && formals[i].isArray()) { // check to see if the last arg is convertible // to the array's component type return isConvertible(formals[i], actuals[i], true); } return false; } } return true; } // number of formal and actual differ, method must be vararg if (!isVarArgs(method)) { return false; } // less arguments than method parameters: vararg is null if (formals.length > actuals.length) { // only one parameter, the last (ie vararg) can be missing if (formals.length - actuals.length > 1) { return false; } // check that all present args match up to the method parms for (int i = 0; i < actuals.length; ++i) { if (!isConvertible(formals[i], actuals[i], false)) { return false; } } return true; } // more arguments given than the method accepts; check for varargs if (formals.length > 0 && actuals.length > 0) { // check that they all match up to the last method arg for (int i = 0; i < formals.length - 1; ++i) { if (!isConvertible(formals[i], actuals[i], false)) { return false; } } // check that all remaining arguments are convertible to the vararg type // (last parm is an array since method is vararg) final Class vararg = formals[formals.length - 1].getComponentType(); for (int i = formals.length - 1; i < actuals.length; ++i) { if (!isConvertible(vararg, actuals[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(final Class formal, final Class actual, final 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(final Class formal, final Class actual, final 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 METHODS = new Parameters() { @Override protected Class[] getParameterTypes(final Method app) { return app.getParameterTypes(); } @Override public boolean isVarArgs(final Method app) { return MethodKey.isVarArgs(app); } }; /** * The parameter matching service for constructors. */ private static final Parameters> CONSTRUCTORS = new Parameters>() { @Override protected Class[] getParameterTypes(final Constructor app) { return app.getParameterTypes(); } @Override public boolean isVarArgs(final Constructor app) { return app.isVarArgs(); } }; }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy