org.apache.commons.jexl2.internal.introspection.MethodKey Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of commons-jexl Show documentation
Show all versions of commons-jexl Show documentation
Jexl is an implementation of the JSTL Expression Language with extensions.
/*
* 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.jexl2.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.
*
* 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 & uses less memory.
*
* For the parameters methods:
* @author Jason van Zyl
* @author Bob McWhirter
* @author Christoph Reck
* @author Geir Magnusson Jr.
* @author Attila Szegedi
* @author Nathan Bubna
*/
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 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> 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;
}
// CSOFF: NeedBraces
/* 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;
}
// CSON: NeedBraces
/* 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;
}
// CSOFF: NeedBraces
/* 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;
}
// CSON: NeedBraces
/* 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 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);
// 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 methods, Class>[] classes) {
LinkedList 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 maximals = new LinkedList();
for (Iterator applicable = applicables.iterator();
applicable.hasNext();) {
T app = applicable.next();
Class>[] appArgs = getParameterTypes(app);
boolean lessSpecific = false;
for (Iterator 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;
}
/*
* 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 getApplicables(List methods, Class>[] classes) {
LinkedList list = new LinkedList();
for (Iterator 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 (methodArgs.length > classes.length) {
// if there's just one more methodArg than class arg
// and the last methodArg is an array, then treat it as a vararg
return methodArgs.length == classes.length + 1 && methodArgs[methodArgs.length - 1].isArray();
}
if (methodArgs.length == classes.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 < 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 METHODS = new Parameters() {
@Override
protected Class>[] getParameterTypes(Method app) {
return app.getParameterTypes();
}
};
/**
* The parameter matching service for constructors.
*/
private static final Parameters> CONSTRUCTORS = new Parameters>() {
@Override
protected Class>[] getParameterTypes(Constructor> app) {
return app.getParameterTypes();
}
};
}