org.apache.ibatis.ognl.OgnlRuntime Maven / Gradle / Ivy
Show all versions of org.apache.servicemix.bundles.mybatis
/*
* 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.ibatis.ognl;
import org.apache.ibatis.ognl.enhance.ExpressionCompiler;
import org.apache.ibatis.ognl.enhance.OgnlExpressionCompiler;
import org.apache.ibatis.ognl.internal.CacheException;
import org.apache.ibatis.ognl.internal.entry.DeclaredMethodCacheEntry;
import org.apache.ibatis.ognl.internal.entry.GenericMethodParameterTypeCacheEntry;
import org.apache.ibatis.ognl.internal.entry.PermissionCacheEntry;
import org.apache.ibatis.ognl.security.OgnlSecurityManagerFactory;
import org.apache.ibatis.ognl.security.UserMethod;
import java.beans.BeanInfo;
import java.beans.IndexedPropertyDescriptor;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.MethodDescriptor;
import java.beans.PropertyDescriptor;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.Permission;
import java.security.Permissions;
import java.security.PrivilegedActionException;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* Utility class used by internal OGNL API to do various things like:
*
*
* - Handles majority of reflection logic / caching.
* - Utility methods for casting strings / various numeric types used by {@link OgnlExpressionCompiler}.
* - Core runtime configuration point for setting/using global {@link TypeConverter} / {@link OgnlExpressionCompiler} /
* {@link NullHandler} instances / etc..
*
*/
public class OgnlRuntime {
/**
* Constant expression used to indicate that a given method / property couldn't be found
* during reflection operations.
*/
public static final Object NotFound = new Object();
public static final Object[] NoArguments = new Object[]{};
/**
* Token returned by TypeConverter for no conversion possible
*/
public static final Object NoConversionPossible = "org.apache.ibatis.ognl.NoConversionPossible";
/**
* Not an indexed property
*/
public static int INDEXED_PROPERTY_NONE = 0;
/**
* JavaBeans IndexedProperty
*/
public static int INDEXED_PROPERTY_INT = 1;
/**
* OGNL ObjectIndexedProperty
*/
public static int INDEXED_PROPERTY_OBJECT = 2;
/**
* Constant string representation of null string.
*/
public static final String NULL_STRING = "" + null;
/**
* Java beans standard set method prefix.
*/
public static final String SET_PREFIX = "set";
/**
* Java beans standard get method prefix.
*/
public static final String GET_PREFIX = "get";
/**
* Java beans standard {@code is} boolean getter prefix.
*/
public static final String IS_PREFIX = "is";
/**
* Prefix padding for hexadecimal numbers to HEX_LENGTH.
*/
private static final Map HEX_PADDING = new HashMap<>();
private static final int HEX_LENGTH = 8;
/**
* Returned by getUniqueDescriptor()
when the object is null
.
*/
private static final String NULL_OBJECT_STRING = "";
/**
* Control usage of JDK9+ access handler using the JVM option:
* -Dognl.UseJDK9PlusAccessHandler=true
* -Dognl.UseJDK9PlusAccessHandler=false
*
* Note: Set to "true" to allow the new JDK9 and later behaviour, provided a newer JDK9+
* is detected. By default the standard pre-JDK9 AccessHandler will be used even when
* running on JDK9+, so users must "opt-in" in order to enable the alternate JDK9+ AccessHandler.
* Using the JDK9PlusAccessHandler may avoid / mask JDK9+ warnings of the form:
* "WARNING: Illegal reflective access by ognl.OgnlRuntime"
* or provide an alternative when running in environments set with "--illegal-access=deny".
*
* Note: The default behaviour is to use the standard pre-JDK9 access handler.
* Using the "false" value has the same effect as omitting the option completely.
*
* Warning: Users are strongly advised to review their code and confirm they really
* need the AccessHandler modifying access levels, looking at alternatives to avoid that need.
*/
static final String USE_JDK9PLUS_ACCESS_HANDLER = "org.apache.ibatis.ognl.UseJDK9PlusAccessHandler";
/**
* Control usage of "stricter" invocation processing by invokeMethod() using the JVM options:
* -Dognl.UseStricterInvocation=true
* -Dognl.UseStricterInvocation=false
*
* Note: Using the "true" value has the same effect as omitting the option completely.
* The default behaviour is to use the "stricter" invocation processing.
* Using the "false" value reverts to the older "less strict" invocation processing
* (in the event the "stricter" processing causes issues for existing applications).
*/
static final String USE_STRICTER_INVOCATION = "org.apache.ibatis.ognl.UseStricterInvocation";
/**
* Hold environment flag state associated with USE_JDK9PLUS_ACESS_HANDLER.
* Default: false (if not set)
*/
private static final boolean _useJDK9PlusAccessHandler;
static {
boolean initialFlagState = false;
try {
final String propertyString = System.getProperty(USE_JDK9PLUS_ACCESS_HANDLER);
if (propertyString != null && propertyString.length() > 0) {
initialFlagState = Boolean.parseBoolean(propertyString);
}
} catch (Exception ex) {
// Unavailable (SecurityException, etc.)
}
_useJDK9PlusAccessHandler = initialFlagState;
}
/**
* Hold environment flag state associated with USE_STRICTER_INVOCATION.
* Default: true (if not set)
*/
private static final boolean _useStricterInvocation;
static {
boolean initialFlagState = true;
try {
final String propertyString = System.getProperty(USE_STRICTER_INVOCATION);
if (propertyString != null && propertyString.length() > 0) {
initialFlagState = Boolean.parseBoolean(propertyString);
}
} catch (Exception ex) {
// Unavailable (SecurityException, etc.)
}
_useStricterInvocation = initialFlagState;
}
/*
* Attempt to detect the system-reported Major Java Version (e.g. 5, 7, 11).
*/
private static final int _majorJavaVersion = detectMajorJavaVersion();
private static final boolean _jdk9Plus = _majorJavaVersion >= 9;
/*
* Assign an accessibility modification mechanism, based on Major Java Version and Java option flag
* flag {@link OgnlRuntime#USE_JDK9PLUS_ACCESS_HANDLER}.
*
* Note: Will use the standard Pre-JDK9 accessibility modification mechanism unless OGNL is running
* on JDK9+ and the Java option flag has also been set true.
*/
private static final AccessibleObjectHandler _accessibleObjectHandler;
static {
_accessibleObjectHandler = usingJDK9PlusAccessHandler() ? AccessibleObjectHandlerJDK9Plus.createHandler() :
AccessibleObjectHandlerPreJDK9.createHandler();
}
/**
* Private references for use in blocking direct invocation by invokeMethod().
*/
private static final Method SYS_CONSOLE_REF;
private static final Method SYS_EXIT_REF;
private static final Method AO_SETACCESSIBLE_REF;
private static final Method AO_SETACCESSIBLE_ARR_REF;
/*
* Initialize the Method references used for blocking usage within invokeMethod().
*/
static {
Method setAccessibleMethod = null;
Method setAccessibleMethodArray = null;
Method systemExitMethod = null;
Method systemConsoleMethod = null;
try {
setAccessibleMethod = AccessibleObject.class.getMethod("setAccessible", boolean.class);
} catch (NoSuchMethodException nsme) {
// Should not happen. To debug, uncomment the next line.
//throw new IllegalStateException("OgnlRuntime initialization missing setAccessible method", nsme);
} catch (SecurityException se) {
// May be blocked by existing SecurityManager. To debug, uncomment the next line.
//throw new SecurityException("OgnlRuntime initialization cannot access setAccessible method", se);
} finally {
AO_SETACCESSIBLE_REF = setAccessibleMethod;
}
try {
setAccessibleMethodArray = AccessibleObject.class.getMethod("setAccessible", AccessibleObject[].class, boolean.class);
} catch (NoSuchMethodException nsme) {
// Should not happen. To debug, uncomment the next line.
//throw new IllegalStateException("OgnlRuntime initialization missing setAccessible method", nsme);
} catch (SecurityException se) {
// May be blocked by existing SecurityManager. To debug, uncomment the next line.
//throw new SecurityException("OgnlRuntime initialization cannot access setAccessible method", se);
} finally {
AO_SETACCESSIBLE_ARR_REF = setAccessibleMethodArray;
}
try {
systemExitMethod = System.class.getMethod("exit", int.class);
} catch (NoSuchMethodException nsme) {
// Should not happen. To debug, uncomment the next line.
//throw new IllegalStateException("OgnlRuntime initialization missing exit method", nsme);
} catch (SecurityException se) {
// May be blocked by existing SecurityManager. To debug, uncomment the next line.
//throw new SecurityException("OgnlRuntime initialization cannot access exit method", se);
} finally {
SYS_EXIT_REF = systemExitMethod;
}
try {
systemConsoleMethod = System.class.getMethod("console"); // Not available in JDK 1.5 or earlier
} catch (NoSuchMethodException nsme) {
// May happen for JDK 1.5 and earlier. To debug, uncomment the next line.
//throw new IllegalStateException("OgnlRuntime initialization missing console method", nsme);
} catch (SecurityException se) {
// May be blocked by existing SecurityManager. To debug, uncomment the next line.
//throw new SecurityException("OgnlRuntime initialization cannot access console method", se);
} finally {
SYS_CONSOLE_REF = systemConsoleMethod;
}
}
/**
* Control usage of the OGNL Security Manager using the JVM option:
* -Dognl.security.manager=true (or any non-null value other than 'disable')
*
* Omit '-Dognl.security.manager=' or nullify the property to disable the feature.
*
* To forcibly disable the feature (only possible at OGNL Library initialization, use the option:
* -Dognl.security.manager=forceDisableOnInit
*
* Users that have their own Security Manager implementations and no intention to use the OGNL SecurityManager
* sandbox may choose to use the 'forceDisableOnInit' flag option for performance reasons (avoiding overhead
* involving the system property security checks - when that feature will not be used).
*/
static final String OGNL_SECURITY_MANAGER = "org.apache.ibatis.ognl.security.manager";
static final String OGNL_SM_FORCE_DISABLE_ON_INIT = "forceDisableOnInit";
/**
* Hold environment flag state associated with OGNL_SECURITY_MANAGER. See
* {@link OgnlRuntime#OGNL_SECURITY_MANAGER} for more details.
* Default: false (if not set).
*/
private static final boolean _disableOgnlSecurityManagerOnInit;
static {
boolean initialFlagState = false;
try {
final String propertyString = System.getProperty(OGNL_SECURITY_MANAGER);
if (propertyString != null && propertyString.length() > 0) {
initialFlagState = OGNL_SM_FORCE_DISABLE_ON_INIT.equalsIgnoreCase(propertyString);
}
} catch (Exception ex) {
// Unavailable (SecurityException, etc.)
}
_disableOgnlSecurityManagerOnInit = initialFlagState;
}
/**
* Allow users to revert to the old "first match" lookup for getters/setters by OGNL using the JVM options:
* -Dognl.UseFirstMatchGetSetLookup=true
* -Dognl.UseFirstMatchGetSetLookup=false
*
* Note: Using the "false" value has the same effect as omitting the option completely.
* The default behaviour is to use the "best match" lookup for getters/setters.
* Using the "true" value reverts to the older "first match" lookup for getters/setters
* (in the event the "best match" processing causes issues for existing applications).
*/
static final String USE_FIRSTMATCH_GETSET_LOOKUP = "org.apache.ibatis.ognl.UseFirstMatchGetSetLookup";
/**
* Hold environment flag state associated with USE_FIRSTMATCH_GETSET_LOOKUP.
* Default: false (if not set)
*/
private static final boolean _useFirstMatchGetSetLookup;
static {
boolean initialFlagState = false;
try {
final String propertyString = System.getProperty(USE_FIRSTMATCH_GETSET_LOOKUP);
if (propertyString != null && propertyString.length() > 0) {
initialFlagState = Boolean.parseBoolean(propertyString);
}
} catch (Exception ex) {
// Unavailable (SecurityException, etc.)
}
_useFirstMatchGetSetLookup = initialFlagState;
}
static final OgnlCache cache = new OgnlCache();
private static final PrimitiveTypes primitiveTypes = new PrimitiveTypes();
private static final PrimitiveDefaults primitiveDefaults = new PrimitiveDefaults();
static SecurityManager securityManager = System.getSecurityManager();
static final EvaluationPool _evaluationPool = new EvaluationPool();
static final Map _methodAccessCache = new ConcurrentHashMap<>();
static final Map _methodPermCache = new ConcurrentHashMap<>();
static final ClassPropertyMethodCache cacheSetMethod = new ClassPropertyMethodCache();
static final ClassPropertyMethodCache cacheGetMethod = new ClassPropertyMethodCache();
/**
* Expression compiler used by {@link Ognl#compileExpression(OgnlContext, Object, String)} calls.
*/
private static OgnlExpressionCompiler _compiler;
/**
* Used to provide primitive type equivalent conversions into and out of native / object types.
*/
private static final PrimitiveWrapperClasses primitiveWrapperClasses = new PrimitiveWrapperClasses();
/**
* Constant strings for casting different primitive types.
*/
private static final NumericCasts numericCasts = new NumericCasts();
/**
* Constant strings for getting the primitive value of different native types on the generic {@link Number} object
* interface. (or the less generic BigDecimal/BigInteger types)
*/
private static final NumericValues numericValues = new NumericValues();
/**
* Numeric primitive literal string expressions.
*/
private static final NumericLiterals numericLiterals = new NumericLiterals();
private static final NumericDefaults numericDefaults = new NumericDefaults();
/*
* Lazy loading of Javassist library
*/
static {
try {
Class.forName("org.apache.ibatis.javassist.ClassPool");
_compiler = new ExpressionCompiler();
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Javassist library is missing in classpath! Please add missed dependency!", e);
} catch (RuntimeException rt) {
throw new IllegalStateException("Javassist library cannot be loaded, is it restricted by runtime environment?");
}
}
private static final Class>[] EMPTY_CLASS_ARRAY = new Class[0];
static {
PropertyAccessor p = new ArrayPropertyAccessor();
setPropertyAccessor(Object.class, new ObjectPropertyAccessor());
setPropertyAccessor(byte[].class, p);
setPropertyAccessor(short[].class, p);
setPropertyAccessor(char[].class, p);
setPropertyAccessor(int[].class, p);
setPropertyAccessor(long[].class, p);
setPropertyAccessor(float[].class, p);
setPropertyAccessor(double[].class, p);
setPropertyAccessor(Object[].class, p);
setPropertyAccessor(List.class, new ListPropertyAccessor());
setPropertyAccessor(Map.class, new MapPropertyAccessor());
setPropertyAccessor(Set.class, new SetPropertyAccessor());
setPropertyAccessor(Iterator.class, new IteratorPropertyAccessor());
setPropertyAccessor(Enumeration.class, new EnumerationPropertyAccessor());
ElementsAccessor e = new ArrayElementsAccessor();
setElementsAccessor(Object.class, new ObjectElementsAccessor());
setElementsAccessor(byte[].class, e);
setElementsAccessor(short[].class, e);
setElementsAccessor(char[].class, e);
setElementsAccessor(int[].class, e);
setElementsAccessor(long[].class, e);
setElementsAccessor(float[].class, e);
setElementsAccessor(double[].class, e);
setElementsAccessor(Object[].class, e);
setElementsAccessor(Collection.class, new CollectionElementsAccessor());
setElementsAccessor(Map.class, new MapElementsAccessor());
setElementsAccessor(Iterator.class, new IteratorElementsAccessor());
setElementsAccessor(Enumeration.class, new EnumerationElementsAccessor());
setElementsAccessor(Number.class, new NumberElementsAccessor());
NullHandler nh = new ObjectNullHandler();
setNullHandler(Object.class, nh);
setNullHandler(byte[].class, nh);
setNullHandler(short[].class, nh);
setNullHandler(char[].class, nh);
setNullHandler(int[].class, nh);
setNullHandler(long[].class, nh);
setNullHandler(float[].class, nh);
setNullHandler(double[].class, nh);
setNullHandler(Object[].class, nh);
MethodAccessor ma = new ObjectMethodAccessor();
setMethodAccessor(Object.class, ma);
setMethodAccessor(byte[].class, ma);
setMethodAccessor(short[].class, ma);
setMethodAccessor(char[].class, ma);
setMethodAccessor(int[].class, ma);
setMethodAccessor(long[].class, ma);
setMethodAccessor(float[].class, ma);
setMethodAccessor(double[].class, ma);
setMethodAccessor(Object[].class, ma);
}
/**
* Clears all of the cached reflection information normally used
* to improve the speed of expressions that operate on the same classes
* or are executed multiple times.
*
*
* Warning: Calling this too often can be a huge performance
* drain on your expressions - use with care.
*
*/
public static void clearCache() {
cache.clear();
}
/**
* Clears some additional caches used by OgnlRuntime. The existing {@link OgnlRuntime#clearCache()}
* clears the standard reflection-related caches, but some applications may have need to clear
* the additional caches as well.
*
* Clearing the additional caches may have greater impact than the {@link OgnlRuntime#clearCache()}
* method so it should only be used when the normal cache clear is insufficient.
*
*
* Warning: Calling this method too often can be a huge performance
* drain on your expressions - use with care.
*
*
* @since 3.1.25
*/
public static void clearAdditionalCache() {
cacheSetMethod.clear();
cacheGetMethod.clear();
cache.clear();
}
/**
* Get the Major Java Version detected by OGNL.
*
* @return Detected Major Java Version, or 5 (minimum supported version for OGNL) if unable to detect.
*/
public static int getMajorJavaVersion() {
return _majorJavaVersion;
}
/**
* Check if the detected Major Java Version is 9 or higher (JDK 9+).
*
* @return Return true if the Detected Major Java version is 9 or higher, otherwise false.
*/
public static boolean isJdk9Plus() {
return _jdk9Plus;
}
public static String getNumericValueGetter(Class> type) {
return numericValues.get(type);
}
public static Class> getPrimitiveWrapperClass(Class> primitiveClass) {
return primitiveWrapperClasses.get(primitiveClass);
}
public static String getNumericCast(Class extends Number> type) {
return numericCasts.get(type);
}
public static String getNumericLiteral(Class> type) {
return numericLiterals.get(type);
}
public static void setCompiler(OgnlExpressionCompiler compiler) {
_compiler = compiler;
}
public static OgnlExpressionCompiler getCompiler() {
return _compiler;
}
public static void compileExpression(OgnlContext context, Node expression, Object root)
throws Exception {
_compiler.compileExpression(context, expression, root);
}
/**
* Gets the "target" class of an object for looking up accessors that are registered on the
* target. If the object is a Class object this will return the Class itself, else it will
* return object's getClass() result.
*
* @param o the Object from which to retrieve its Class.
* @return the Class of o.
*/
public static Class> getTargetClass(Object o) {
return (o == null) ? null : ((o instanceof Class) ? (Class>) o : o.getClass());
}
/**
* Returns the base name (the class name without the package name prepended) of the object
* given.
*
* @param o the Object from which to retrieve its base classname.
* @return the base classname of o's Class.
*/
public static String getBaseName(Object o) {
return (o == null) ? null : getClassBaseName(o.getClass());
}
/**
* Returns the base name (the class name without the package name prepended) of the class given.
*
* @param c the Class from which to retrieve its name.
* @return the base classname of c.
*/
public static String getClassBaseName(Class> c) {
String s = c.getName();
return s.substring(s.lastIndexOf('.') + 1);
}
public static String getClassName(Object o, boolean fullyQualified) {
if (!(o instanceof Class)) {
o = o.getClass();
}
return getClassName((Class>) o, fullyQualified);
}
public static String getClassName(Class> c, boolean fullyQualified) {
return fullyQualified ? c.getName() : getClassBaseName(c);
}
/**
* Returns the package name of the object's class.
*
* @param o the Object from which to retrieve its Class package name.
* @return the package name of o's Class.
*/
public static String getPackageName(Object o) {
return (o == null) ? null : getClassPackageName(o.getClass());
}
/**
* Returns the package name of the class given.
*
* @param c the Class from which to retrieve its package name.
* @return the package name of c.
*/
public static String getClassPackageName(Class> c) {
String s = c.getName();
int i = s.lastIndexOf('.');
return (i < 0) ? null : s.substring(0, i);
}
/**
* Returns a "pointer" string in the usual format for these things - 0x<hex digits>.
*
* @param num the int to convert into a "pointer" string in hex format.
* @return the String representing num as a "pointer" string in hex format.
*/
public static String getPointerString(int num) {
StringBuilder result = new StringBuilder();
String hex = Integer.toHexString(num), pad;
Integer l = hex.length();
// result.append(HEX_PREFIX);
if ((pad = HEX_PADDING.get(l)) == null) {
StringBuilder pb = new StringBuilder();
for (int i = hex.length(); i < HEX_LENGTH; i++) {
pb.append('0');
}
pad = new String(pb);
HEX_PADDING.put(l, pad);
}
result.append(pad);
result.append(hex);
return new String(result);
}
/**
* Returns a "pointer" string in the usual format for these things - 0x<hex digits> for the
* object given. This will always return a unique value for each object.
*
* @param o the Object to convert into a "pointer" string in hex format.
* @return the String representing o as a "pointer" string in hex format.
*/
public static String getPointerString(Object o) {
return getPointerString((o == null) ? 0 : System.identityHashCode(o));
}
/**
* Returns a unique descriptor string that includes the object's class and a unique integer
* identifier. If fullyQualified is true then the class name will be fully qualified to include
* the package name, else it will be just the class' base name.
*
* @param object the Object for which a unique descriptor string is desired.
* @param fullyQualified true if the descriptor string is fully-qualified (package name), false for just the Class' base name.
* @return the unique descriptor String for the object, qualified as per fullyQualified parameter.
*/
public static String getUniqueDescriptor(Object object, boolean fullyQualified) {
StringBuilder result = new StringBuilder();
if (object != null) {
if (object instanceof Proxy) {
Class> interfaceClass = object.getClass().getInterfaces()[0];
result.append(getClassName(interfaceClass, fullyQualified));
result.append('^');
object = Proxy.getInvocationHandler(object);
}
result.append(getClassName(object, fullyQualified));
result.append('@');
result.append(getPointerString(object));
} else {
result.append(NULL_OBJECT_STRING);
}
return new String(result);
}
/**
* Returns a unique descriptor string that includes the object's class' base name and a unique
* integer identifier.
*
* @param object the Object for which a unique descriptor string is desired.
* @return the unique descriptor String for the object, NOT fully-qualified.
*/
public static String getUniqueDescriptor(Object object) {
return getUniqueDescriptor(object, false);
}
/**
* Returns the parameter types of the given method.
*
* @param method the Method whose parameter types are being queried.
* @return the array of Class elements representing m's parameters. May be null if m does not utilize parameters.
*/
public static Class>[] getParameterTypes(Method method) throws CacheException {
return cache.getMethodParameterTypes(method);
}
/**
* Finds the appropriate parameter types for the given {@link Method} and
* {@link Class} instance of the type the method is associated with. Correctly
* finds generic types if running in >= 1.5 jre as well.
*
* @param type The class type the method is being executed against.
* @param method The method to find types for.
* @return Array of parameter types for the given method.
*/
public static Class>[] findParameterTypes(Class> type, Method method) {
if (type == null || type.getGenericSuperclass() == null || !(type.getGenericSuperclass() instanceof ParameterizedType)) {
return getParameterTypes(method);
}
GenericMethodParameterTypeCacheEntry key = new GenericMethodParameterTypeCacheEntry(method, type);
return cache.getGenericMethodParameterTypes(key);
}
/**
* Returns the parameter types of the given method.
*
* @param constructor the Constructor whose parameter types are being queried.
* @return the array of Class elements representing c's parameters. May be null if c does not utilize parameters.
*/
public static Class>[] getParameterTypes(Constructor> constructor) throws CacheException {
return cache.getParameterTypes(constructor);
}
/**
* Gets the SecurityManager that OGNL uses to determine permissions for invoking methods.
*
* @return SecurityManager for OGNL
*/
public static SecurityManager getSecurityManager() {
return securityManager;
}
/**
* Sets the SecurityManager that OGNL uses to determine permissions for invoking methods.
*
* @param value SecurityManager to set
*/
public static void setSecurityManager(SecurityManager value) {
securityManager = value;
}
/**
* Permission will be named "invoke.<declaring-class>.<method-name>".
*
* @param method the Method whose Permission is being requested.
* @return the Permission for method named "invoke.<declaring-class>.<method-name>".
*/
public static Permission getPermission(Method method) throws CacheException {
PermissionCacheEntry key = new PermissionCacheEntry(method);
return cache.getInvokePermission(key);
}
public static Object invokeMethod(Object target, Method method, Object[] argsArray)
throws InvocationTargetException, IllegalAccessException {
boolean syncInvoke;
boolean checkPermission;
Boolean methodAccessCacheValue;
Boolean methodPermCacheValue;
if (_useStricterInvocation) {
final Class> methodDeclaringClass = method.getDeclaringClass(); // Note: synchronized(method) call below will already NPE, so no null check.
if ((AO_SETACCESSIBLE_REF != null && AO_SETACCESSIBLE_REF.equals(method)) ||
(AO_SETACCESSIBLE_ARR_REF != null && AO_SETACCESSIBLE_ARR_REF.equals(method)) ||
(SYS_EXIT_REF != null && SYS_EXIT_REF.equals(method)) ||
(SYS_CONSOLE_REF != null && SYS_CONSOLE_REF.equals(method)) ||
AccessibleObjectHandler.class.isAssignableFrom(methodDeclaringClass) ||
ClassResolver.class.isAssignableFrom(methodDeclaringClass) ||
MethodAccessor.class.isAssignableFrom(methodDeclaringClass) ||
MemberAccess.class.isAssignableFrom(methodDeclaringClass) ||
OgnlContext.class.isAssignableFrom(methodDeclaringClass) ||
Runtime.class.isAssignableFrom(methodDeclaringClass) ||
ClassLoader.class.isAssignableFrom(methodDeclaringClass) ||
ProcessBuilder.class.isAssignableFrom(methodDeclaringClass) ||
AccessibleObjectHandlerJDK9Plus.unsafeOrDescendant(methodDeclaringClass)) {
// Prevent calls to some specific methods, as well as all methods of certain classes/interfaces
// for which no (apparent) legitimate use cases exist for their usage within OGNL invokeMethod().
throw new IllegalAccessException("Method [" + method + "] cannot be called from within OGNL invokeMethod() " +
"under stricter invocation mode.");
}
}
// only synchronize method invocation if it actually requires it
synchronized (method) {
methodAccessCacheValue = _methodAccessCache.get(method);
if (methodAccessCacheValue == null) {
if (!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
if (!(method.isAccessible())) {
methodAccessCacheValue = Boolean.TRUE;
_methodAccessCache.put(method, methodAccessCacheValue);
} else {
methodAccessCacheValue = Boolean.FALSE;
_methodAccessCache.put(method, methodAccessCacheValue);
}
} else {
methodAccessCacheValue = Boolean.FALSE;
_methodAccessCache.put(method, methodAccessCacheValue);
}
}
syncInvoke = Boolean.TRUE.equals(methodAccessCacheValue);
methodPermCacheValue = _methodPermCache.get(method);
if (methodPermCacheValue == null) {
if (securityManager != null) {
try {
securityManager.checkPermission(getPermission(method));
methodPermCacheValue = Boolean.TRUE;
_methodPermCache.put(method, methodPermCacheValue);
} catch (SecurityException ex) {
methodPermCacheValue = Boolean.FALSE;
_methodPermCache.put(method, methodPermCacheValue);
throw new IllegalAccessException("Method [" + method + "] cannot be accessed.");
}
} else {
methodPermCacheValue = Boolean.TRUE;
_methodPermCache.put(method, methodPermCacheValue);
}
}
checkPermission = Boolean.FALSE.equals(methodPermCacheValue);
}
Object result;
if (syncInvoke) //if is not public and is not accessible
{
synchronized (method) {
if (checkPermission) {
try {
securityManager.checkPermission(getPermission(method));
} catch (SecurityException ex) {
throw new IllegalAccessException("Method [" + method + "] cannot be accessed.");
}
}
_accessibleObjectHandler.setAccessible(method, true);
try {
result = invokeMethodInsideSandbox(target, method, argsArray);
} finally {
_accessibleObjectHandler.setAccessible(method, false);
}
}
} else {
if (checkPermission) {
try {
securityManager.checkPermission(getPermission(method));
} catch (SecurityException ex) {
throw new IllegalAccessException("Method [" + method + "] cannot be accessed.");
}
}
result = invokeMethodInsideSandbox(target, method, argsArray);
}
return result;
}
private static Object invokeMethodInsideSandbox(Object target, Method method, Object[] argsArray)
throws InvocationTargetException, IllegalAccessException {
if (_disableOgnlSecurityManagerOnInit) {
return method.invoke(target, argsArray); // Feature was disabled at OGNL initialization.
}
try {
if (System.getProperty(OGNL_SECURITY_MANAGER) == null) {
return method.invoke(target, argsArray);
}
} catch (SecurityException ignored) {
// already enabled or user has applied a policy that doesn't allow read property so we have to honor user's sandbox
}
if (ClassLoader.class.isAssignableFrom(method.getDeclaringClass())) {
// to support OgnlSecurityManager.isAccessDenied
throw new IllegalAccessException("OGNL direct access to class loader denied!");
}
// creating object before entering sandbox to load classes out of the sandbox
UserMethod userMethod = new UserMethod(target, method, argsArray);
Permissions p = new Permissions(); // not any permission
ProtectionDomain pd = new ProtectionDomain(null, p);
AccessControlContext acc = new AccessControlContext(new ProtectionDomain[]{pd});
Object ognlSecurityManager = OgnlSecurityManagerFactory.getOgnlSecurityManager();
Long token;
try {
token = (Long) ognlSecurityManager.getClass().getMethod("enter").invoke(ognlSecurityManager);
} catch (NoSuchMethodException e) {
throw new InvocationTargetException(e);
}
if (token == null) {
// user has applied a policy that doesn't allow setSecurityManager so we have to honor user's sandbox
return method.invoke(target, argsArray);
}
// execute user method body with all permissions denied
try {
return AccessController.doPrivileged(userMethod, acc);
} catch (PrivilegedActionException e) {
if (e.getException() instanceof InvocationTargetException) {
throw (InvocationTargetException) e.getException();
}
throw new InvocationTargetException(e);
} finally {
try {
ognlSecurityManager.getClass().getMethod("leave", long.class).invoke(ognlSecurityManager, token);
} catch (NoSuchMethodException e) {
throw new InvocationTargetException(e);
}
}
}
/**
* Gets the class for a method argument that is appropriate for looking up methods by
* reflection, by looking for the standard primitive wrapper classes and exchanging for them
* their underlying primitive class objects. Other classes are passed through unchanged.
*
* @param arg an object that is being passed to a method
* @return the class to use to look up the method
*/
public static Class> getArgClass(Object arg) {
if (arg == null)
return null;
Class> c = arg.getClass();
if (c == Boolean.class)
return Boolean.TYPE;
else if (c.getSuperclass() == Number.class) {
if (c == Integer.class)
return Integer.TYPE;
if (c == Double.class)
return Double.TYPE;
if (c == Byte.class)
return Byte.TYPE;
if (c == Long.class)
return Long.TYPE;
if (c == Float.class)
return Float.TYPE;
if (c == Short.class)
return Short.TYPE;
} else if (c == Character.class)
return Character.TYPE;
return c;
}
public static Class>[] getArgClasses(Object[] args) {
if (args == null) return null;
Class>[] argClasses = new Class[args.length];
for (int i = 0; i < args.length; i++) {
argClasses[i] = getArgClass(args[i]);
}
return argClasses;
}
/**
* Tells whether the given object is compatible with the given class ---that is, whether the
* given object can be passed as an argument to a method or constructor whose parameter type is
* the given class. If object is null this will return true because null is compatible with any
* type.
*
* @param object the Object to check for type-compatibility with Class c.
* @param c the Class for which object's type-compatibility is being checked.
* @return true if object is type-compatible with c.
*/
public static boolean isTypeCompatible(Object object, Class> c) {
if (object == null)
return true;
ArgsCompatbilityReport report = new ArgsCompatbilityReport(0, new boolean[1]);
if (!isTypeCompatible(getArgClass(object), c, 0, report))
return false;
return !report.conversionNeeded[0]; // we don't allow conversions during this path...
}
public static boolean isTypeCompatible(Class> parameterClass, Class> methodArgumentClass, int index, ArgsCompatbilityReport report) {
if (parameterClass == null) {
// happens when we cannot determine parameter...
report.score += 500;
return true;
}
if (parameterClass == methodArgumentClass)
return true; // exact match, no additional score
//if (methodArgumentClass.isPrimitive())
// return false; // really? int can be assigned to long... *hmm*
if (methodArgumentClass.isArray()) {
if (parameterClass.isArray()) {
Class> pct = parameterClass.getComponentType();
Class> mct = methodArgumentClass.getComponentType();
if (mct.isAssignableFrom(pct)) {
// two arrays are better then a array and a list or other conversions...
report.score += 25;
return true;
}
//return isTypeCompatible(pct, mct, index, report); // check inner classes
}
if (Collection.class.isAssignableFrom(parameterClass)) {
// we have to assume that all Collections carry objects - generics access is of no use during runtime because of
// Type Erasure - http://www.angelikalanger.com/GenericsFAQ/FAQSections/TechnicalDetails.html#Type%20Erasure
Class> mct = methodArgumentClass.getComponentType();
if (mct == Object.class) {
report.conversionNeeded[index] = true;
report.score += 30;
return true;
} else {
// Okay, the items from the list *might* not match. we better don't do that...
return false;
}
}
} else if (Collection.class.isAssignableFrom(methodArgumentClass)) {
if (parameterClass.isArray()) {
// TODO get generics type here and do further evaluations...
report.conversionNeeded[index] = true;
report.score += 50;
return true;
}
if (Collection.class.isAssignableFrom(parameterClass)) {
if (methodArgumentClass.isAssignableFrom(parameterClass)) {
// direct possible List assignment - good match...
report.score += 2;
return true;
}
// TODO get generics type here and do further evaluations...
report.conversionNeeded[index] = true;
report.score += 50;
return true;
}
}
if (methodArgumentClass.isAssignableFrom(parameterClass)) {
report.score += 40; // works but might not the best match - weight of 50..
return true;
}
if (parameterClass.isPrimitive()) {
Class> ptc = primitiveWrapperClasses.get(parameterClass);
if (methodArgumentClass == ptc) {
report.score += 2; // quite an good match
return true;
}
if (methodArgumentClass.isAssignableFrom(ptc)) {
report.score += 10; // works but might not the best match - weight of 10..
return true;
}
}
return false; // dosn't match.
/*
boolean result = true;
if (parameterClass != null) {
if (methodArgumentClass.isPrimitive()) {
if (parameterClass != methodArgumentClass) {
result = false;
}
} else if (!methodArgumentClass.isAssignableFrom(parameterClass)) {
result = false;
}
}
return result;
*/
}
/**
* Tells whether the given array of objects is compatible with the given array of classes---that
* is, whether the given array of objects can be passed as arguments to a method or constructor
* whose parameter types are the given array of classes.
*/
public static class ArgsCompatbilityReport {
int score;
boolean[] conversionNeeded;
public ArgsCompatbilityReport(int score, boolean[] conversionNeeded) {
this.score = score;
this.conversionNeeded = conversionNeeded;
}
}
public static final ArgsCompatbilityReport NoArgsReport = new ArgsCompatbilityReport(0, new boolean[0]);
public static boolean areArgsCompatible(Object[] args, Class>[] classes) {
ArgsCompatbilityReport report = areArgsCompatible(getArgClasses(args), classes, null);
if (report == null)
return false;
for (boolean conversionNeeded : report.conversionNeeded)
if (conversionNeeded)
return false;
return true;
}
public static ArgsCompatbilityReport areArgsCompatible(Class>[] args, Class>[] classes, Method m) {
boolean varArgs = m != null && m.isVarArgs();
if (args == null || args.length == 0) { // handle methods without arguments
if (classes == null || classes.length == 0) {
return NoArgsReport;
} else {
if (varArgs) {
return NoArgsReport;
}
return null;
}
}
if (args.length != classes.length && !varArgs) {
return null;
} else if (varArgs) {
/*
* varArg's start with a penalty of 1000.
* There are some java compiler rules that are hopefully reflectet by this penalty:
* * Legacy beats Varargs
* * Widening beats Varargs
* * Boxing beats Varargs
*/
ArgsCompatbilityReport report = new ArgsCompatbilityReport(1000, new boolean[args.length]);
/*
* varargs signature is: method(type1, type2, typeN, typeV ...)
* This means: All arguments up to typeN needs exact matching, all varargs need to match typeV
*/
if (classes.length - 1 > args.length)
// we don't have enough arguments to provide the required 'fixed' arguments
return null;
// type check on fixed arguments
for (int index = 0, count = classes.length - 1; index < count; ++index)
if (!isTypeCompatible(args[index], classes[index], index, report))
return null;
// type check on varargs
Class> varArgsType = classes[classes.length - 1].getComponentType();
for (int index = classes.length - 1, count = args.length; index < count; ++index)
if (!isTypeCompatible(args[index], varArgsType, index, report))
return null;
return report;
} else {
ArgsCompatbilityReport report = new ArgsCompatbilityReport(0, new boolean[args.length]);
for (int index = 0, count = args.length; index < count; ++index)
if (!isTypeCompatible(args[index], classes[index], index, report))
return null;
return report;
}
}
/**
* Tells whether the first array of classes is more specific than the second. Assumes that the
* two arrays are of the same length.
*
* @param classes1 the Class array being checked to see if it is "more specific" than classes2.
* @param classes2 the Class array that classes1 is being checked against to see if classes1 is "more specific" than classes2.
* @return true if the classes1 Class contents are "more specific" than classes2 Class contents, false otherwise.
*/
public static boolean isMoreSpecific(Class>[] classes1, Class>[] classes2) {
for (int index = 0, count = classes1.length; index < count; ++index) {
Class> c1 = classes1[index], c2 = classes2[index];
if (c1 == c2)
continue;
else if (c1.isPrimitive())
return true;
else if (c1.isAssignableFrom(c2))
return false;
else if (c2.isAssignableFrom(c1))
return true;
}
// They are the same! So the first is not more specific than the second.
return false;
}
public static String getModifierString(int modifiers) {
String result;
if (Modifier.isPublic(modifiers))
result = "public";
else if (Modifier.isProtected(modifiers))
result = "protected";
else if (Modifier.isPrivate(modifiers))
result = "private";
else
result = "";
if (Modifier.isStatic(modifiers))
result = "static " + result;
if (Modifier.isFinal(modifiers))
result = "final " + result;
if (Modifier.isNative(modifiers))
result = "native " + result;
if (Modifier.isSynchronized(modifiers))
result = "synchronized " + result;
if (Modifier.isTransient(modifiers))
result = "transient " + result;
return result;
}
public static Class classForName(OgnlContext context, String className) throws ClassNotFoundException {
Class> result = primitiveTypes.get(className);
if (result == null) {
ClassResolver resolver;
if ((context == null) || ((resolver = context.getClassResolver()) == null)) {
resolver = new DefaultClassResolver();
}
result = resolver.classForName(className, context);
}
if (result == null)
throw new ClassNotFoundException("Unable to resolve class: " + className);
return (Class) result;
}
public static boolean isInstance(OgnlContext context, Object value, String className)
throws OgnlException {
try {
Class> c = classForName(context, className);
return c.isInstance(value);
} catch (ClassNotFoundException e) {
throw new OgnlException("No such class: " + className, e);
}
}
public static Object getPrimitiveDefaultValue(Class> forClass) {
return primitiveDefaults.get(forClass);
}
public static Object getNumericDefaultValue(Class> forClass) {
return numericDefaults.get(forClass);
}
public static Object getConvertedType(OgnlContext context, Object target, Member member, String propertyName,
Object value, Class> type) {
return context.getTypeConverter().convertValue(context, target, member, propertyName, value, type);
}
public static boolean getConvertedTypes(OgnlContext context, Object target, Member member, String propertyName,
Class>[] parameterTypes, Object[] args, Object[] newArgs) {
boolean result = false;
if (parameterTypes.length == args.length) {
result = true;
for (int i = 0, ilast = parameterTypes.length - 1; result && (i <= ilast); i++) {
Object arg = args[i];
Class> type = parameterTypes[i];
if (isTypeCompatible(arg, type)) {
newArgs[i] = arg;
} else {
Object v = getConvertedType(context, target, member, propertyName, arg, type);
if (v == OgnlRuntime.NoConversionPossible) {
result = false;
} else {
newArgs[i] = v;
}
}
}
}
return result;
}
public static Constructor> getConvertedConstructorAndArgs(OgnlContext context, Object target, List> constructors,
Object[] args, Object[] newArgs) {
Constructor> result = null;
TypeConverter converter = context.getTypeConverter();
if ((converter != null) && (constructors != null)) {
for (int i = 0, icount = constructors.size(); (result == null) && (i < icount); i++) {
Constructor> ctor = constructors.get(i);
Class>[] parameterTypes = getParameterTypes(ctor);
if (getConvertedTypes(context, target, ctor, null, parameterTypes, args, newArgs)) {
result = ctor;
}
}
}
return result;
}
/**
* Gets the appropriate method to be called for the given target, method name and arguments. If
* successful this method will return the Method within the target that can be called and the
* converted arguments in actualArgs. If unsuccessful this method will return null and the
* actualArgs will be empty.
*
* @param context The current execution context.
* @param source Target object to run against or method name.
* @param target Instance of object to be run against.
* @param propertyName Name of property to get method of.
* @param methodName Name of the method to get from known methods.
* @param methods List of current known methods.
* @param args Arguments originally passed in.
* @param actualArgs Converted arguments.
* @return Best method match or null if none could be found.
*/
public static Method getAppropriateMethod(OgnlContext context, Object source, Object target, String propertyName,
String methodName, List methods, Object[] args, Object[] actualArgs) {
Method result = null;
if (methods != null) {
Class> typeClass = target != null ? target.getClass() : null;
if (typeClass == null && source instanceof Class) {
typeClass = (Class>) source;
}
Class>[] argClasses = getArgClasses(args);
MatchingMethod mm = findBestMethod(methods, typeClass, methodName, argClasses);
if (mm != null) {
result = mm.mMethod;
Class>[] mParameterTypes = mm.mParameterTypes;
System.arraycopy(args, 0, actualArgs, 0, args.length);
if (actualArgs.length > 0) {
for (int j = 0; j < mParameterTypes.length; j++) {
Class> type = mParameterTypes[j];
if (mm.report.conversionNeeded[j] || (type.isPrimitive() && (actualArgs[j] == null))) {
actualArgs[j] = getConvertedType(context, source, result, propertyName, args[j], type);
}
}
}
}
}
if (result == null) {
result = getConvertedMethodAndArgs(context, target, propertyName, methods, args, actualArgs);
}
return result;
}
public static Method getConvertedMethodAndArgs(OgnlContext context, Object target, String propertyName,
List methods, Object[] args, Object[] newArgs) {
Method result = null;
TypeConverter converter = context.getTypeConverter();
if ((converter != null) && (methods != null)) {
for (int i = 0, icount = methods.size(); (result == null) && (i < icount); i++) {
Method m = methods.get(i);
Class>[] parameterTypes = findParameterTypes(target != null ? target.getClass() : null, m);//getParameterTypes(m);
if (getConvertedTypes(context, target, m, propertyName, parameterTypes, args, newArgs)) {
result = m;
}
}
}
return result;
}
private static class MatchingMethod {
Method mMethod;
int score;
ArgsCompatbilityReport report;
Class>[] mParameterTypes;
private MatchingMethod(Method method, int score, ArgsCompatbilityReport report, Class>[] mParameterTypes) {
this.mMethod = method;
this.score = score;
this.report = report;
this.mParameterTypes = mParameterTypes;
}
}
private static MatchingMethod findBestMethod(List methods, Class> typeClass, String name, Class>[] argClasses) {
MatchingMethod mm = null;
IllegalArgumentException failure = null;
for (Method method : methods) {
Class>[] mParameterTypes = findParameterTypes(typeClass, method);
ArgsCompatbilityReport report = areArgsCompatible(argClasses, mParameterTypes, method);
if (report == null)
continue;
String methodName = method.getName();
int score = report.score;
if (name.equals(methodName)) {
// exact match - no additinal score...
} else if (name.equalsIgnoreCase(methodName)) {
// minimal penalty..
score += 200;
} else if (methodName.toLowerCase().endsWith(name.toLowerCase())) {
// has a prefix...
score += 500;
} else {
// just in case...
score += 5000;
}
if (mm == null || mm.score > score) {
mm = new MatchingMethod(method, score, report, mParameterTypes);
failure = null;
} else if (mm.score == score) {
// it happens that we see the same method signature multiple times - for the current class or interfaces ...
// check for same signature
if (Arrays.equals(mm.mMethod.getParameterTypes(), method.getParameterTypes()) && mm.mMethod.getName().equals(method.getName())) {
// it is the same method. we use the public one...
if (!Modifier.isPublic(mm.mMethod.getDeclaringClass().getModifiers())
&& Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
mm = new MatchingMethod(method, score, report, mParameterTypes);
failure = null;
}
} else {
// two methods with same score - direct compare to find the better one...
// legacy wins over varargs
if (method.isVarArgs() || mm.mMethod.isVarArgs()) {
if (method.isVarArgs() && !mm.mMethod.isVarArgs()) {
// keep with current
} else if (!method.isVarArgs()) {
// legacy wins...
mm = new MatchingMethod(method, score, report, mParameterTypes);
failure = null;
} else {
// both arguments are varargs...
System.err.println("Two vararg methods with same score(" + score + "): \"" + mm.mMethod + "\" and \"" + method + "\" please report!");
}
} else {
int scoreCurr = 0;
int scoreOther = 0;
for (int j = 0; j < argClasses.length; j++) {
Class> argClass = argClasses[j];
Class> mcClass = mm.mParameterTypes[j];
Class> moClass = mParameterTypes[j];
if (argClass == null) { // TODO can we avoid this case?
// we don't know the class. use the most generic implementation...
if (mcClass == moClass) {
// equal args - no winner...
} else if (mcClass.isAssignableFrom(moClass)) {
scoreOther += 1000; // current wins...
} else if (moClass.isAssignableFrom(moClass)) {
scoreCurr += 1000; // other wins...
} else {
// both items can't be assigned to each other..
failure = new IllegalArgumentException("Can't decide wich method to use: \"" + mm.mMethod + "\" or \"" + method + "\"");
}
} else {
// we try to find the more specific implementation
if (mcClass == moClass) {
// equal args - no winner...
} else if (mcClass == argClass) {
scoreOther += 100; // current wins...
} else if (moClass == argClass) {
scoreCurr += 100; // other wins...
} else if (mcClass.isAssignableFrom(moClass)) {
scoreOther += 50; // current wins...
} else if (moClass.isAssignableFrom(moClass)) {
scoreCurr += 50; // other wins...
} else {
// both items can't be assigned to each other..
// TODO: if this happens we have to put some weight on the inheritance...
failure = new IllegalArgumentException("Can't decide wich method to use: \"" + mm.mMethod + "\" or \"" + method + "\"");
}
}
}
if (scoreCurr == scoreOther) {
if (failure == null) {
boolean currentIsAbstract = Modifier.isAbstract(mm.mMethod.getModifiers());
boolean otherIsAbstract = Modifier.isAbstract(method.getModifiers());
if (currentIsAbstract == otherIsAbstract) {
// Only report as an error when the score is equal and BOTH methods are abstract or BOTH are concrete.
// If one is abstract and the other concrete then either choice should work for OGNL,
// so we just keep the current choice and continue (without error output).
System.err.println("Two methods with same score(" + score + "): \"" + mm.mMethod + "\" and \"" + method + "\" please report!");
}
}
} else if (scoreCurr > scoreOther) {
// other wins...
mm = new MatchingMethod(method, score, report, mParameterTypes);
failure = null;
} // else current one wins...
}
}
}
}
if (failure != null)
throw failure;
return mm;
}
public static Object callAppropriateMethod(OgnlContext context, Object source, Object target, String methodName,
String propertyName, List methods, Object[] args)
throws MethodFailedException {
Throwable reason;
Object[] actualArgs = new Object[args.length];
try {
Method method = getAppropriateMethod(context, source, target, propertyName, methodName, methods, args, actualArgs);
if (!isMethodAccessible(context, source, method, propertyName)) {
StringBuilder buffer = new StringBuilder();
String className = "";
if (target != null) {
className = target.getClass().getName() + ".";
}
for (int i = 0, ilast = args.length - 1; i <= ilast; i++) {
Object arg = args[i];
buffer.append((arg == null) ? NULL_STRING : arg.getClass().getName());
if (i < ilast) {
buffer.append(", ");
}
}
throw new NoSuchMethodException(className + methodName + "(" + buffer + ")");
}
Object[] convertedArgs = actualArgs;
if (method.isVarArgs()) {
Class>[] parmTypes = method.getParameterTypes();
// split arguments in to two dimensional array for varargs reflection invocation
// where it is expected that the parameter passed in to invoke the method
// will look like "new Object[] { arrayOfNonVarArgsArguments, arrayOfVarArgsArguments }"
for (int i = 0; i < parmTypes.length; i++) {
if (parmTypes[i].isArray()) {
convertedArgs = new Object[i + 1];
if (actualArgs.length > 0) {
System.arraycopy(actualArgs, 0, convertedArgs, 0, convertedArgs.length);
}
Object[] varArgs;
// if they passed in varargs arguments grab them and dump in to new varargs array
if (actualArgs.length > i) {
List