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

com.feilong.lib.ognl.OgnlRuntime Maven / Gradle / Ivy

Go to download

feilong is a suite of core and expanded libraries that include utility classes, http, excel,cvs, io classes, and much much more.

There is a newer version: 4.3.0
Show newest version
// --------------------------------------------------------------------------
// Copyright (c) 1998-2004, Drew Davidson and Luke Blanshard
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// Neither the name of the Drew Davidson nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
// DAMAGE.
// --------------------------------------------------------------------------
package com.feilong.lib.ognl;

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.GenericArrayType;
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.Type;
import java.lang.reflect.TypeVariable;
import java.math.BigDecimal;
import java.math.BigInteger;
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.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import com.feilong.lib.ognl.enhance.ExpressionCompiler;
import com.feilong.lib.ognl.enhance.OgnlExpressionCompiler;
import com.feilong.lib.ognl.internal.ClassCache;
import com.feilong.lib.ognl.internal.ClassCacheImpl;
import com.feilong.lib.ognl.security.OgnlSecurityManagerFactory;
import com.feilong.lib.ognl.security.UserMethod;

/**
 * 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..
  • *
* * @author Luke Blanshard ([email protected]) * @author Drew Davidson ([email protected]) */ public class OgnlRuntime{ private static final String classNameClassPool = com.feilong.lib.javassist.ClassPool.class.getName(); /** * 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 List NotFoundList = new ArrayList(); public static final Map NotFoundMap = new HashMap(); public static final Object[] NoArguments = new Object[] {}; public static final Class[] NoArgumentTypes = new Class[] {}; /** * Token returned by TypeConverter for no conversion possible */ public static final Object NoConversionPossible = "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. */ private static final String SET_PREFIX = "set"; /** * Java beans standard get method prefix. */ private static final String GET_PREFIX = "get"; /** * Java beans standard is boolean getter prefix. */ private static final String IS_PREFIX = "is"; /** * 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_ACESS_HANDLER = "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 = "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_ACESS_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_ACESS_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", new 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_REF = setAccessibleMethod; } try{ setAccessibleMethodArray = AccessibleObject.class .getMethod("setAccessible", new Class[] { 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", new Class[] { 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", new Class[] {}); // 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 = "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 = "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 ClassCache _methodAccessors = new ClassCacheImpl(); static final ClassCache _propertyAccessors = new ClassCacheImpl(); static final ClassCache _elementsAccessors = new ClassCacheImpl(); static final ClassCache _nullHandlers = new ClassCacheImpl(); static final ClassCache _propertyDescriptorCache = new ClassCacheImpl(); static final ClassCache _constructorCache = new ClassCacheImpl(); static final ClassCache _staticMethodCache = new ClassCacheImpl(); static final ClassCache _instanceMethodCache = new ClassCacheImpl(); static final ClassCache _invokePermissionCache = new ClassCacheImpl(); static final ClassCache _fieldCache = new ClassCacheImpl(); static final List _superclasses = new ArrayList(); /* * Used by * fieldCache lookup */ static final ClassCache[] _declaredMethods = new ClassCache[] { new ClassCacheImpl(), new ClassCacheImpl() }; static final Map _primitiveTypes = new HashMap(101); static final ClassCache _primitiveDefaults = new ClassCacheImpl(); static final Map _methodParameterTypesCache = new HashMap(101); static final Map _genericMethodParameterTypesCache = new HashMap(101); static final Map _ctorParameterTypesCache = new HashMap(101); static SecurityManager _securityManager = System.getSecurityManager(); static final EvaluationPool _evaluationPool = new EvaluationPool(); static final ObjectArrayPool _objectArrayPool = new ObjectArrayPool(); static final Map _methodAccessCache = new ConcurrentHashMap<>(); static final Map _methodPermCache = new ConcurrentHashMap<>(); static final ClassPropertyMethodCache cacheSetMethod = new ClassPropertyMethodCache(); static final ClassPropertyMethodCache cacheGetMethod = new ClassPropertyMethodCache(); static ClassCacheInspector _cacheInspector; private static OgnlExpressionCompiler _compiler; /** * Lazy loading of Javassist library */ static{ try{ Class.forName(classNameClassPool); _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]; private static IdentityHashMap PRIMITIVE_WRAPPER_CLASSES = new IdentityHashMap(); /** * Used to provide primitive type equivalent conversions into and out of * native / object types. */ static{ PRIMITIVE_WRAPPER_CLASSES.put(Boolean.TYPE, Boolean.class); PRIMITIVE_WRAPPER_CLASSES.put(Boolean.class, Boolean.TYPE); PRIMITIVE_WRAPPER_CLASSES.put(Byte.TYPE, Byte.class); PRIMITIVE_WRAPPER_CLASSES.put(Byte.class, Byte.TYPE); PRIMITIVE_WRAPPER_CLASSES.put(Character.TYPE, Character.class); PRIMITIVE_WRAPPER_CLASSES.put(Character.class, Character.TYPE); PRIMITIVE_WRAPPER_CLASSES.put(Short.TYPE, Short.class); PRIMITIVE_WRAPPER_CLASSES.put(Short.class, Short.TYPE); PRIMITIVE_WRAPPER_CLASSES.put(Integer.TYPE, Integer.class); PRIMITIVE_WRAPPER_CLASSES.put(Integer.class, Integer.TYPE); PRIMITIVE_WRAPPER_CLASSES.put(Long.TYPE, Long.class); PRIMITIVE_WRAPPER_CLASSES.put(Long.class, Long.TYPE); PRIMITIVE_WRAPPER_CLASSES.put(Float.TYPE, Float.class); PRIMITIVE_WRAPPER_CLASSES.put(Float.class, Float.TYPE); PRIMITIVE_WRAPPER_CLASSES.put(Double.TYPE, Double.class); PRIMITIVE_WRAPPER_CLASSES.put(Double.class, Double.TYPE); } private static final Map NUMERIC_CASTS = new HashMap(); /** * Constant strings for casting different primitive types. */ static{ NUMERIC_CASTS.put(Double.class, "(double)"); NUMERIC_CASTS.put(Float.class, "(float)"); NUMERIC_CASTS.put(Integer.class, "(int)"); NUMERIC_CASTS.put(Long.class, "(long)"); NUMERIC_CASTS.put(BigDecimal.class, "(double)"); NUMERIC_CASTS.put(BigInteger.class, ""); } private static final Map NUMERIC_VALUES = new HashMap(); /** * 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) */ static{ NUMERIC_VALUES.put(Double.class, "doubleValue()"); NUMERIC_VALUES.put(Float.class, "floatValue()"); NUMERIC_VALUES.put(Integer.class, "intValue()"); NUMERIC_VALUES.put(Long.class, "longValue()"); NUMERIC_VALUES.put(Short.class, "shortValue()"); NUMERIC_VALUES.put(Byte.class, "byteValue()"); NUMERIC_VALUES.put(BigDecimal.class, "doubleValue()"); NUMERIC_VALUES.put(BigInteger.class, "doubleValue()"); NUMERIC_VALUES.put(Boolean.class, "booleanValue()"); } private static final Map NUMERIC_LITERALS = new HashMap(); /** * Numeric primitive literal string expressions. */ static{ NUMERIC_LITERALS.put(Integer.class, ""); NUMERIC_LITERALS.put(Integer.TYPE, ""); NUMERIC_LITERALS.put(Long.class, "l"); NUMERIC_LITERALS.put(Long.TYPE, "l"); NUMERIC_LITERALS.put(BigInteger.class, "d"); NUMERIC_LITERALS.put(Float.class, "f"); NUMERIC_LITERALS.put(Float.TYPE, "f"); NUMERIC_LITERALS.put(Double.class, "d"); NUMERIC_LITERALS.put(Double.TYPE, "d"); NUMERIC_LITERALS.put(BigInteger.class, "d"); NUMERIC_LITERALS.put(BigDecimal.class, "d"); } private static final Map NUMERIC_DEFAULTS = new HashMap(); static{ NUMERIC_DEFAULTS.put(Boolean.class, Boolean.FALSE); NUMERIC_DEFAULTS.put(Byte.class, new Byte((byte) 0)); NUMERIC_DEFAULTS.put(Short.class, new Short((short) 0)); NUMERIC_DEFAULTS.put(Character.class, new Character((char) 0)); NUMERIC_DEFAULTS.put(Integer.class, new Integer(0)); NUMERIC_DEFAULTS.put(Long.class, new Long(0L)); NUMERIC_DEFAULTS.put(Float.class, new Float(0.0f)); NUMERIC_DEFAULTS.put(Double.class, new Double(0.0)); NUMERIC_DEFAULTS.put(BigInteger.class, new BigInteger("0")); NUMERIC_DEFAULTS.put(BigDecimal.class, new BigDecimal(0.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); _primitiveTypes.put("boolean", Boolean.TYPE); _primitiveTypes.put("byte", Byte.TYPE); _primitiveTypes.put("short", Short.TYPE); _primitiveTypes.put("char", Character.TYPE); _primitiveTypes.put("int", Integer.TYPE); _primitiveTypes.put("long", Long.TYPE); _primitiveTypes.put("float", Float.TYPE); _primitiveTypes.put("double", Double.TYPE); _primitiveDefaults.put(Boolean.TYPE, Boolean.FALSE); _primitiveDefaults.put(Boolean.class, Boolean.FALSE); _primitiveDefaults.put(Byte.TYPE, new Byte((byte) 0)); _primitiveDefaults.put(Byte.class, new Byte((byte) 0)); _primitiveDefaults.put(Short.TYPE, new Short((short) 0)); _primitiveDefaults.put(Short.class, new Short((short) 0)); _primitiveDefaults.put(Character.TYPE, new Character((char) 0)); _primitiveDefaults.put(Integer.TYPE, new Integer(0)); _primitiveDefaults.put(Long.TYPE, new Long(0L)); _primitiveDefaults.put(Float.TYPE, new Float(0.0f)); _primitiveDefaults.put(Double.TYPE, new Double(0.0)); _primitiveDefaults.put(BigInteger.class, new BigInteger("0")); _primitiveDefaults.put(BigDecimal.class, new BigDecimal(0.0)); } /** * 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(){ synchronized (_methodParameterTypesCache){ _methodParameterTypesCache.clear(); } synchronized (_ctorParameterTypesCache){ _ctorParameterTypesCache.clear(); } synchronized (_propertyDescriptorCache){ _propertyDescriptorCache.clear(); } synchronized (_constructorCache){ _constructorCache.clear(); } synchronized (_staticMethodCache){ _staticMethodCache.clear(); } synchronized (_instanceMethodCache){ _instanceMethodCache.clear(); } synchronized (_invokePermissionCache){ _invokePermissionCache.clear(); } synchronized (_fieldCache){ _fieldCache.clear(); _superclasses.clear(); // Used by fieldCache lookup (synchronized on _fieldCache). } synchronized (_declaredMethods[0]){ _declaredMethods[0].clear(); } synchronized (_declaredMethods[1]){ _declaredMethods[1].clear(); } _methodAccessCache.clear(); _methodPermCache.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(); synchronized (_genericMethodParameterTypesCache){ _genericMethodParameterTypesCache.clear(); } } public static String getNumericValueGetter(Class type){ return (String) NUMERIC_VALUES.get(type); } public static Class getPrimitiveWrapperClass(Class primitiveClass){ return (Class) PRIMITIVE_WRAPPER_CLASSES.get(primitiveClass); } public static String getNumericLiteral(Class type){ return (String) NUMERIC_LITERALS.get(type); } public static OgnlExpressionCompiler getCompiler(){ return _compiler; } /** * 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 class given. * * @param c * the Class from which to retrieve its name. * @return the base classname of c. */ private static String getClassBaseName(Class c){ String s = c.getName(); return s.substring(s.lastIndexOf('.') + 1); } private static String getClassName(Class c,boolean fullyQualified){ return fullyQualified ? c.getName() : getClassBaseName(c); } /** * Returns the parameter types of the given method. * * @param m * 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. */ private static Class[] getParameterTypes(Method m){ synchronized (_methodParameterTypesCache){ Class[] result; if ((result = (Class[]) _methodParameterTypesCache.get(m)) == null){ _methodParameterTypesCache.put(m, result = m.getParameterTypes()); } return result; } } /** * 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 m * The method to find types for. * @return Array of parameter types for the given method. */ private static Class[] findParameterTypes(Class type,Method m){ Type[] genTypes = m.getGenericParameterTypes(); Class[] types = new Class[genTypes.length]; boolean noGenericParameter = true; for (int i = 0; i < genTypes.length; i++){ if (Class.class.isInstance(genTypes[i])){ types[i] = (Class) genTypes[i]; continue; } noGenericParameter = false; break; } if (noGenericParameter){ return types; } if (type == null){ return getParameterTypes(m); } final Type typeGenericSuperclass = type.getGenericSuperclass(); if (typeGenericSuperclass == null || !ParameterizedType.class.isInstance(typeGenericSuperclass) || m.getDeclaringClass().getTypeParameters() == null){ return getParameterTypes(m); } if ((types = (Class[]) _genericMethodParameterTypesCache.get(m)) != null){ ParameterizedType genericSuperclass = (ParameterizedType) typeGenericSuperclass; if (Arrays.equals(types, genericSuperclass.getActualTypeArguments())){ return types; } } ParameterizedType param = (ParameterizedType) typeGenericSuperclass; TypeVariable[] declaredTypes = m.getDeclaringClass().getTypeParameters(); types = new Class[genTypes.length]; for (int i = 0; i < genTypes.length; i++){ TypeVariable paramType = null; if (TypeVariable.class.isInstance(genTypes[i])){ paramType = (TypeVariable) genTypes[i]; }else if (GenericArrayType.class.isInstance(genTypes[i])){ paramType = (TypeVariable) ((GenericArrayType) genTypes[i]).getGenericComponentType(); }else if (ParameterizedType.class.isInstance(genTypes[i])){ types[i] = (Class) ((ParameterizedType) genTypes[i]).getRawType(); continue; }else if (Class.class.isInstance(genTypes[i])){ types[i] = (Class) genTypes[i]; continue; } Class resolved = resolveType(param, paramType, declaredTypes); if (resolved != null){ if (GenericArrayType.class.isInstance(genTypes[i])){ resolved = Array.newInstance(resolved, 0).getClass(); } types[i] = resolved; continue; } types[i] = m.getParameterTypes()[i]; } synchronized (_genericMethodParameterTypesCache){ _genericMethodParameterTypesCache.put(m, types); } return types; } static Class resolveType(ParameterizedType param,TypeVariable var,TypeVariable[] declaredTypes){ if (param.getActualTypeArguments().length < 1){ return null; } for (int i = 0; i < declaredTypes.length; i++){ if (!TypeVariable.class.isInstance(param.getActualTypeArguments()[i]) && declaredTypes[i].getName().equals(var.getName())){ return (Class) param.getActualTypeArguments()[i]; } } return null; } static Class findType(Type[] types,Class type){ for (Type type2 : types){ if (Class.class.isInstance(type2) && type.isAssignableFrom((Class) type2)){ return (Class) type2; } } return null; } /** * Returns the parameter types of the given method. * * @param c * 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. */ private static Class[] getParameterTypes(Constructor c){ Class[] result; if ((result = (Class[]) _ctorParameterTypesCache.get(c)) == null){ synchronized (_ctorParameterTypesCache){ if ((result = (Class[]) _ctorParameterTypesCache.get(c)) == null){ _ctorParameterTypesCache.put(c, result = c.getParameterTypes()); } } } return result; } /** * 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>". */ private static Permission getPermission(Method method){ Permission result; Class mc = method.getDeclaringClass(); synchronized (_invokePermissionCache){ Map permissions = (Map) _invokePermissionCache.get(mc); if (permissions == null){ _invokePermissionCache.put(mc, permissions = new HashMap(101)); } if ((result = (Permission) permissions.get(method.getName())) == null){ result = new OgnlInvokePermission("invoke." + mc.getName() + "." + method.getName()); permissions.put(method.getName(), result); } } return result; } private 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 (!(((AccessibleObject) 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); if (checkPermission){ // TODO: ***** This check/block to be removed if/when SecurityManager sandbox logic is merged from v3.1.x ***** throw new IllegalAccessException("Method [" + method + "] cannot be accessed."); } } 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 */ private static final 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; } private 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. */ private static final 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; } if (report.conversionNeeded[0]){ return false; // we don't allow conversions during this path... } return true; } private static final 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 = (Class) PRIMITIVE_WRAPPER_CLASSES.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. } /** * 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. */ private 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; } private 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{ 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 final 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 Class classForName(OgnlContext context,String className) throws ClassNotFoundException{ Class result = (Class) _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 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 NUMERIC_DEFAULTS.get(forClass); } private 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); } private 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 = (Constructor) 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. */ private 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 != null && Class.class.isInstance(source)){ 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); 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; } private 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 = (Method) 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 (Object method : methods){ Method m = (Method) method; Class[] mParameterTypes = findParameterTypes(typeClass, m); ArgsCompatbilityReport report = areArgsCompatible(argClasses, mParameterTypes, m); if (report == null){ continue; } String methodName = m.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(m, 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 ... // TODO why are all of them on the list and not only the most specific one? // check for same signature if (Arrays.equals(mm.mMethod.getParameterTypes(), m.getParameterTypes()) && mm.mMethod.getName().equals(m.getName())){ boolean retsAreEqual = mm.mMethod.getReturnType().equals(m.getReturnType()); // it is the same method. we use the most specific one... if (mm.mMethod.getDeclaringClass().isAssignableFrom(m.getDeclaringClass())){ if (!retsAreEqual && !mm.mMethod.getReturnType().isAssignableFrom(m.getReturnType())){ System.err.println( "Two methods with same method signature but return types conflict? \"" + mm.mMethod + "\" and \"" + m + "\" please report!"); } mm = new MatchingMethod(m, score, report, mParameterTypes); failure = null; }else if (!m.getDeclaringClass().isAssignableFrom(mm.mMethod.getDeclaringClass())){ // this should't happen System.err.println( "Two methods with same method signature but not providing classes assignable? \"" + mm.mMethod + "\" and \"" + m + "\" please report!"); }else if (!retsAreEqual && !m.getReturnType().isAssignableFrom(mm.mMethod.getReturnType())){ System.err.println( "Two methods with same method signature but return types conflict? \"" + mm.mMethod + "\" and \"" + m + "\" please report!"); } }else{ // two methods with same score - direct compare to find the better one... // legacy wins over varargs if ((m.isVarArgs() || mm.mMethod.isVarArgs())){ if (m.isVarArgs() && !mm.mMethod.isVarArgs()){ // keep with current }else if (!m.isVarArgs() && mm.mMethod.isVarArgs()){ // legacy wins... mm = new MatchingMethod(m, score, report, mParameterTypes); failure = null; }else{ // both arguments are varargs... System.err.println( "Two vararg methods with same score(" + score + "): \"" + mm.mMethod + "\" and \"" + m + "\" 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 \"" + m + "\""); } }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{ // 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 \"" + m + "\""); } } } if (scoreCurr == scoreOther){ if (failure == null){ boolean currentIsAbstract = Modifier.isAbstract(mm.mMethod.getModifiers()); boolean otherIsAbstract = Modifier.isAbstract(m.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 \"" + m + "\" please report!"); } } }else if (scoreCurr > scoreOther){ // other wins... mm = new MatchingMethod(m, 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 = null; Object[] actualArgs = _objectArrayPool.create(args.length); try{ Method method = getAppropriateMethod(context, source, target, propertyName, methodName, methods, args, actualArgs); if ((method == null) || !isMethodAccessible(context, source, method, propertyName)){ StringBuffer buffer = new StringBuffer(); 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]; 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){ ArrayList varArgsList = new ArrayList(); for (int j = i; j < actualArgs.length; j++){ if (actualArgs[j] != null){ varArgsList.add(actualArgs[j]); } } varArgs = varArgsList.toArray(); }else{ varArgs = new Object[0]; } // If this is the only parameter, explode the array if (actualArgs.length == 1){ convertedArgs = varArgs; }else{ // there are more parameters, varargs is the last one convertedArgs[i] = varArgs; } break; } } } return invokeMethod(target, method, convertedArgs); }catch (NoSuchMethodException e){ reason = e; }catch (IllegalAccessException e){ reason = e; }catch (InvocationTargetException e){ reason = e.getTargetException(); }finally{ _objectArrayPool.recycle(actualArgs); } throw new MethodFailedException(source, methodName, reason); } public static Object callStaticMethod(OgnlContext context,String className,String methodName,Object[] args) throws OgnlException{ try{ Class targetClass = classForName(context, className); if (targetClass == null){ throw new ClassNotFoundException("Unable to resolve class with name " + className); } MethodAccessor ma = getMethodAccessor(targetClass); return ma.callStaticMethod(context, targetClass, methodName, args); }catch (ClassNotFoundException ex){ throw new MethodFailedException(className, methodName, ex); } } /** * Invokes the specified method against the target object. * * @param context * The current execution context. * @param target * The object to invoke the method on. * @param methodName * Name of the method - as in "getValue" or "add", etc.. * @param args * Optional arguments needed for method. * @return Result of invoking method. * * @throws OgnlException * For lots of different reasons. */ public static Object callMethod(OgnlContext context,Object target,String methodName,Object[] args) throws OgnlException{ if (target == null){ throw new NullPointerException("target is null for method " + methodName); } return getMethodAccessor(target.getClass()).callMethod(context, target, methodName, args); } public static Object callConstructor(OgnlContext context,String className,Object[] args) throws OgnlException{ Throwable reason = null; Object[] actualArgs = args; try{ Constructor ctor = null; Class[] ctorParameterTypes = null; Class target = classForName(context, className); List constructors = getConstructors(target); for (Object constructor : constructors){ Constructor c = (Constructor) constructor; Class[] cParameterTypes = getParameterTypes(c); if (areArgsCompatible(args, cParameterTypes) && (ctor == null || isMoreSpecific(cParameterTypes, ctorParameterTypes))){ ctor = c; ctorParameterTypes = cParameterTypes; } } if (ctor == null){ actualArgs = _objectArrayPool.create(args.length); if ((ctor = getConvertedConstructorAndArgs(context, target, constructors, args, actualArgs)) == null){ throw new NoSuchMethodException(); } } if (!context.getMemberAccess().isAccessible(context, target, ctor, null)){ throw new IllegalAccessException("access denied to " + target.getName() + "()"); } return ctor.newInstance(actualArgs); }catch (ClassNotFoundException e){ reason = e; }catch (NoSuchMethodException e){ reason = e; }catch (IllegalAccessException e){ reason = e; }catch (InvocationTargetException e){ reason = e.getTargetException(); }catch (InstantiationException e){ reason = e; }finally{ if (actualArgs != args){ _objectArrayPool.recycle(actualArgs); } } throw new MethodFailedException(className, "new", reason); } /** * If the checkAccessAndExistence flag is true this method will check to see if the method * exists and if it is accessible according to the context's MemberAccess. If neither test * passes this will return NotFound. * * @param context * the current execution context. * @param target * the object to invoke the property name get on. * @param propertyName * the name of the property to be retrieved from target. * @param checkAccessAndExistence * true if this method should check access levels and existence for propertyName of target, false otherwise. * @return the result invoking property retrieval of propertyName for target. * @throws OgnlException * for lots of different reasons. * @throws IllegalAccessException * if access not permitted. * @throws NoSuchMethodException * if no property accessor exists. * @throws IntrospectionException * on errors using {@link Introspector}. */ public static final Object getMethodValue(OgnlContext context,Object target,String propertyName,boolean checkAccessAndExistence) throws OgnlException,IllegalAccessException,NoSuchMethodException,IntrospectionException{ Object result = null; Method m = getGetMethod(context, (target == null) ? null : target.getClass(), propertyName); if (m == null){ m = getReadMethod((target == null) ? null : target.getClass(), propertyName, null); } if (checkAccessAndExistence){ if ((m == null) || !context.getMemberAccess().isAccessible(context, target, m, propertyName)){ result = NotFound; } } if (result == null){ if (m != null){ try{ result = invokeMethod(target, m, NoArguments); }catch (InvocationTargetException ex){ throw new OgnlException(propertyName, ex.getTargetException()); } }else{ throw new NoSuchMethodException(propertyName); } } return result; } public static boolean setMethodValue(OgnlContext context,Object target,String propertyName,Object value,boolean checkAccessAndExistence) throws OgnlException,IllegalAccessException,NoSuchMethodException,IntrospectionException{ boolean result = true; Method m = getSetMethod(context, (target == null) ? null : target.getClass(), propertyName); if (checkAccessAndExistence){ if ((m == null) || !context.getMemberAccess().isAccessible(context, target, m, propertyName)){ result = false; } } if (result){ if (m != null){ Object[] args = _objectArrayPool.create(value); try{ callAppropriateMethod(context, target, target, m.getName(), propertyName, Collections.nCopies(1, m), args); }finally{ _objectArrayPool.recycle(args); } }else{ result = false; } } return result; } public static List getConstructors(Class targetClass){ List result; if ((result = (List) _constructorCache.get(targetClass)) == null){ synchronized (_constructorCache){ if ((result = (List) _constructorCache.get(targetClass)) == null){ _constructorCache.put(targetClass, result = Arrays.asList(targetClass.getConstructors())); } } } return result; } private static Map getMethods(Class targetClass,boolean staticMethods){ ClassCache cache = (staticMethods ? _staticMethodCache : _instanceMethodCache); Map result; if ((result = (Map) cache.get(targetClass)) == null){ synchronized (cache){ if ((result = (Map) cache.get(targetClass)) == null){ result = new HashMap(23); collectMethods(targetClass, result, staticMethods); cache.put(targetClass, result); } } } return result; } private static void collectMethods(Class c,Map result,boolean staticMethods){ Method[] ma; try{ ma = c.getDeclaredMethods(); }catch (SecurityException ignored){ ma = c.getMethods(); } for (Method element : ma){ if (c.isInterface()){ if (isDefaultMethod(element)){ addMethodToResult(result, element); } continue; } // skip over synthetic methods if (!isMethodCallable(element)){ continue; } if (Modifier.isStatic(element.getModifiers()) == staticMethods){ addMethodToResult(result, element); } } final Class superclass = c.getSuperclass(); if (superclass != null){ collectMethods(superclass, result, staticMethods); } for (final Class iface : c.getInterfaces()){ collectMethods(iface, result, staticMethods); } } private static void addMethodToResult(Map result,Method method){ List ml = (List) result.get(method.getName()); if (ml == null){ result.put(method.getName(), ml = new ArrayList()); } ml.add(method); } /** * Backport of java.lang.reflect.Method#isDefault() * * JDK8+ supports Default Methods for interfaces. Default Methods are defined as: * public, non-abstract and declared within an interface (must also be non-static). * * @param method * The Method to check against the requirements for a Default Method. * * @return true If the Method qualifies as a Default Method, false otherwise. */ private static boolean isDefaultMethod(Method method){ return ((method.getModifiers() & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC) && method.getDeclaringClass().isInterface(); } /** * Determine if the provided Method is a non-Default public Interface method. * * Public non-Default Methods are defined as: * public, abstract, non-static and declared within an interface. * * @param method * The Method to check against the requirements for a non-Default Method. * * @return true If method qualifies as a non-Default public Interface method, false otherwise. * * @since 3.1.25 */ private static boolean isNonDefaultPublicInterfaceMethod(Method method){ return ((method.getModifiers() & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == (Modifier.ABSTRACT | Modifier.PUBLIC)) && method.getDeclaringClass().isInterface(); } public static List getMethods(Class targetClass,String name,boolean staticMethods){ return (List) getMethods(targetClass, staticMethods).get(name); } private static Map getFields(Class targetClass){ Map result; if ((result = (Map) _fieldCache.get(targetClass)) == null){ synchronized (_fieldCache){ if ((result = (Map) _fieldCache.get(targetClass)) == null){ Field fa[]; result = new HashMap(23); try{ fa = targetClass.getDeclaredFields(); }catch (SecurityException ignored){ fa = targetClass.getFields(); } for (Field element : fa){ result.put(element.getName(), element); } _fieldCache.put(targetClass, result); } } } return result; } public static Field getField(Class inClass,String name){ Field result = null; Object o = getFields(inClass).get(name); if (o == null){ synchronized (_fieldCache){ o = getFields(inClass).get(name); if (o == null){ _superclasses.clear(); for (Class sc = inClass; (sc != null); sc = sc.getSuperclass()){ if ((o = getFields(sc).get(name)) == NotFound){ break; } _superclasses.add(sc); if ((result = (Field) o) != null){ break; } } /* * Bubble the found value (either cache miss or actual field) to all supeclasses * that we saw for quicker access next time. */ for (Object _superclasse : _superclasses){ getFields((Class) _superclasse).put(name, (result == null) ? NotFound : result); } }else{ if (o instanceof Field){ result = (Field) o; }else{ if (result == NotFound){ result = null; } } } } }else{ if (o instanceof Field){ result = (Field) o; }else{ if (result == NotFound){ result = null; } } } return result; } public static Object getFieldValue(OgnlContext context,Object target,String propertyName,boolean checkAccessAndExistence) throws NoSuchFieldException{ Object result = null; final Field f = getField((target == null) ? null : target.getClass(), propertyName); if (checkAccessAndExistence){ if ((f == null) || !context.getMemberAccess().isAccessible(context, target, f, propertyName)){ result = NotFound; } } if (result == null){ if (f == null){ throw new NoSuchFieldException(propertyName); }else{ try{ if (!Modifier.isStatic(f.getModifiers())){ final Object state = context.getMemberAccess().setup(context, target, f, propertyName); try{ result = f.get(target); }finally{ context.getMemberAccess().restore(context, target, f, propertyName, state); } }else{ throw new NoSuchFieldException(propertyName); } }catch (IllegalAccessException ex){ throw new NoSuchFieldException(propertyName); } } } return result; } public static boolean setFieldValue(OgnlContext context,Object target,String propertyName,Object value) throws OgnlException{ boolean result = false; try{ final Field f = getField((target == null) ? null : target.getClass(), propertyName); if (f != null){ final int fModifiers = f.getModifiers(); if (!Modifier.isStatic(f.getModifiers()) && !Modifier.isFinal(fModifiers)){ final Object state = context.getMemberAccess().setup(context, target, f, propertyName); try{ if (isTypeCompatible(value, f.getType()) || ((value = getConvertedType(context, target, f, propertyName, value, f.getType())) != null)){ f.set(target, value); result = true; } }finally{ context.getMemberAccess().restore(context, target, f, propertyName, state); } } } }catch (IllegalAccessException ex){ throw new NoSuchPropertyException(target, propertyName, ex); } return result; } private static boolean isFieldAccessible(OgnlContext context,Object target,Class inClass,String propertyName){ return isFieldAccessible(context, target, getField(inClass, propertyName), propertyName); } private static boolean isFieldAccessible(OgnlContext context,Object target,Field field,String propertyName){ return context.getMemberAccess().isAccessible(context, target, field, propertyName); } private static boolean hasField(OgnlContext context,Object target,Class inClass,String propertyName){ Field f = getField(inClass, propertyName); return (f != null) && isFieldAccessible(context, target, f, propertyName); } /** * Method name is getStaticField(), but actually behaves more like "getStaticFieldValue()". *

* Typical usage: Returns the value (not the actual {@link Field}) for the given (static) fieldName. * May return the {@link Enum} constant value for the given fieldName when className is an {@link Enum}. * May return a {@link Class} instance when the given fieldName is "class". *

* * @param context * The current ognl context * @param className * The name of the class which contains the field * @param fieldName * The name of the field whose value should be returned * * @return The value of the (static) fieldName * @throws OgnlException * for lots of different reasons. */ public static Object getStaticField(OgnlContext context,String className,String fieldName) throws OgnlException{ Exception reason = null; try{ final Class c = classForName(context, className); if (c == null){ throw new OgnlException("Unable to find class " + className + " when resolving field name of " + fieldName); } /* * Check for virtual static field "class"; this cannot interfere with normal static * fields because it is a reserved word. */ if (fieldName.equals("class")){ return c; }else if (c.isEnum()){ try{ return Enum.valueOf(c, fieldName); }catch (IllegalArgumentException e){ // ignore it, try static field } } final Field f = getField(c, fieldName); if (f == null){ throw new NoSuchFieldException(fieldName); } if (!Modifier.isStatic(f.getModifiers())){ throw new OgnlException("Field " + fieldName + " of class " + className + " is not static"); } Object result = null; if (context.getMemberAccess().isAccessible(context, null, f, null)){ final Object state = context.getMemberAccess().setup(context, null, f, null); try{ result = f.get(null); }finally{ context.getMemberAccess().restore(context, null, f, null, state); } }else{ throw new IllegalAccessException("Access to " + fieldName + " of class " + className + " is forbidden"); } return result; }catch (ClassNotFoundException e){ reason = e; }catch (NoSuchFieldException e){ reason = e; }catch (SecurityException e){ reason = e; }catch (IllegalAccessException e){ reason = e; } throw new OgnlException("Could not get static field " + fieldName + " from class " + className, reason); } private static String capitalizeBeanPropertyName(String propertyName){ if (propertyName.length() == 1){ return propertyName.toUpperCase(); } // don't capitalize getters/setters if (propertyName.startsWith(GET_PREFIX) && propertyName.endsWith("()")){ if (Character.isUpperCase(propertyName.substring(3, 4).charAt(0))){ return propertyName; } } if (propertyName.startsWith(SET_PREFIX) && propertyName.endsWith(")")){ if (Character.isUpperCase(propertyName.substring(3, 4).charAt(0))){ return propertyName; } } if (propertyName.startsWith(IS_PREFIX) && propertyName.endsWith("()")){ if (Character.isUpperCase(propertyName.substring(2, 3).charAt(0))){ return propertyName; } } char first = propertyName.charAt(0); char second = propertyName.charAt(1); if (Character.isLowerCase(first) && Character.isUpperCase(second)){ return propertyName; }else{ char[] chars = propertyName.toCharArray(); chars[0] = Character.toUpperCase(chars[0]); return new String(chars); } } private static List getDeclaredMethods(Class targetClass,String propertyName,boolean findSets){ List result = null; ClassCache cache = _declaredMethods[findSets ? 0 : 1]; Map propertyCache = (Map) cache.get(targetClass); if ((propertyCache == null) || ((result = (List) propertyCache.get(propertyName)) == null)){ synchronized (cache){ propertyCache = (Map) cache.get(targetClass); if ((propertyCache == null) || ((result = (List) propertyCache.get(propertyName)) == null)){ String baseName = capitalizeBeanPropertyName(propertyName); result = new ArrayList(); collectAccessors(targetClass, baseName, result, findSets); if (propertyCache == null){ cache.put(targetClass, propertyCache = new HashMap(101)); } propertyCache.put(propertyName, result.isEmpty() ? NotFoundList : result); return result.isEmpty() ? null : result; } } } return (result == NotFoundList) ? null : result; } private static void collectAccessors(Class c,String baseName,List result,boolean findSets){ Method[] methods; try{ methods = c.getDeclaredMethods(); }catch (SecurityException ignored){ methods = c.getMethods(); } for (Method method : methods){ if (c.isInterface()){ if (isDefaultMethod(method) || isNonDefaultPublicInterfaceMethod(method)){ addIfAccessor(result, method, baseName, findSets); } continue; } if (!isMethodCallable(method)){ continue; } addIfAccessor(result, method, baseName, findSets); } final Class superclass = c.getSuperclass(); if (superclass != null){ collectAccessors(superclass, baseName, result, findSets); } for (final Class iface : c.getInterfaces()){ collectAccessors(iface, baseName, result, findSets); } } private static void addIfAccessor(List result,Method method,String baseName,boolean findSets){ final String ms = method.getName(); if (ms.endsWith(baseName)){ boolean isSet = false, isIs = false; if ((isSet = ms.startsWith(SET_PREFIX)) || ms.startsWith(GET_PREFIX) || (isIs = ms.startsWith(IS_PREFIX))){ int prefixLength = (isIs ? 2 : 3); if (isSet == findSets){ if (baseName.length() == (ms.length() - prefixLength)){ result.add(method); } } } } } /** * Convenience used to check if a method is volatile or synthetic so as to avoid * calling un-callable methods. * * @param m * The method to check. * @return True if the method should be callable, false otherwise. */ static boolean isMethodCallable(Method m){ if (m.isSynthetic() || Modifier.isVolatile(m.getModifiers())){ return false; } return true; } /** * cache get methods * * @param context * the current execution context. * @param targetClass * the Class to invoke the property name "getter" retrieval on. * @param propertyName * the name of the property for which a "getter" is sought. * @return the Method representing a "getter" for propertyName of targetClass. * @throws OgnlException * for lots of different reasons. * @throws IntrospectionException * on errors using {@link Introspector}. */ private static Method getGetMethod(OgnlContext context,Class targetClass,String propertyName) throws IntrospectionException,OgnlException{ // Cache is a map in two levels, so we provide two keys (see comments in ClassPropertyMethodCache below) Method method = cacheGetMethod.get(targetClass, propertyName); if (method != null){ return method; } // By checking key existence now and not before calling 'get', we will save a map resolution 90% of the times if (cacheGetMethod.containsKey(targetClass, propertyName)){ return null; } method = _getGetMethod(context, targetClass, propertyName); // will be null if not found - will cache it anyway cacheGetMethod.put(targetClass, propertyName, method); return method; } /** * Returns a qualifying get (getter) method, if one is available for the given targetClass and propertyName. * * Note: From OGNL 3.1.25 onward, this method will attempt to find the first get getter method(s) that match: * 1) First get (getter) method, whether public or not. * 2) First public get (getter) method, provided the method's declaring class is also public. * This may be the same as 1), if 1) is also public and its declaring class is also public. * 3) First public non-Default interface get (getter) method, provided the method's declaring class is also public. * The order of preference (priority) for the above matches will be 2 (1st public getter), * 3 (1st public non-Default interface getter), 1 (1st getter of any kind). * This updated methodology should help limit the need to modify method accessibility levels in some circumstances. * * @param context * The current execution context. * @param targetClass * Class to search for a get method (getter). * @param propertyName * Name of the property for the get method (getter). * * @return * @throws IntrospectionException * @throws OgnlException */ private static Method _getGetMethod(OgnlContext context,Class targetClass,String propertyName) throws IntrospectionException,OgnlException{ Method result = null; List methods = getDeclaredMethods(targetClass, propertyName, false /* find 'get' methods */); if (methods != null){ Method firstGetter = null; Method firstPublicGetter = null; Method firstNonDefaultPublicInterfaceGetter = null; for (Object method : methods){ Method m = (Method) method; Class[] mParameterTypes = findParameterTypes(targetClass, m); //getParameterTypes(m); if (mParameterTypes.length == 0){ boolean declaringClassIsPublic = Modifier.isPublic(m.getDeclaringClass().getModifiers()); if (firstGetter == null){ firstGetter = m; if (_useFirstMatchGetSetLookup){ break; // Stop looking (emulate original logic, return 1st match) } } if (firstPublicGetter == null && Modifier.isPublic(m.getModifiers()) && declaringClassIsPublic){ firstPublicGetter = m; break; // Stop looking (this is the best possible match) } if (firstNonDefaultPublicInterfaceGetter == null && isNonDefaultPublicInterfaceMethod(m) && declaringClassIsPublic){ firstNonDefaultPublicInterfaceGetter = m; } } } result = (firstPublicGetter != null) ? firstPublicGetter : (firstNonDefaultPublicInterfaceGetter != null) ? firstNonDefaultPublicInterfaceGetter : firstGetter; } return result; } private static boolean isMethodAccessible(OgnlContext context,Object target,Method method,String propertyName){ return (method != null) && context.getMemberAccess().isAccessible(context, target, method, propertyName); } private static boolean hasGetMethod(OgnlContext context,Object target,Class targetClass,String propertyName) throws IntrospectionException,OgnlException{ return isMethodAccessible(context, target, getGetMethod(context, targetClass, propertyName), propertyName); } /** * cache set methods method * * @param context * the current execution context. * @param targetClass * the Class to invoke the property name "setter" retrieval on. * @param propertyName * the name of the property for which a "setter" is sought. * @return the Method representing a "setter" for propertyName of targetClass. * @throws IntrospectionException * on errors using {@link Introspector}. * @throws OgnlException * for lots of different reasons. */ private static Method getSetMethod(OgnlContext context,Class targetClass,String propertyName) throws IntrospectionException,OgnlException{ // Cache is a map in two levels, so we provide two keys (see comments in ClassPropertyMethodCache below) Method method = cacheSetMethod.get(targetClass, propertyName); if (method != null){ return method; } // By checking key existence now and not before calling 'get', we will save a map resolution 90% of the times if (cacheSetMethod.containsKey(targetClass, propertyName)){ return null; } method = _getSetMethod(context, targetClass, propertyName); // will be null if not found - will cache it anyway cacheSetMethod.put(targetClass, propertyName, method); return method; } /** * Returns a qualifying set (setter) method, if one is available for the given targetClass and propertyName. * * Note: From OGNL 3.1.25 onward, this method will attempt to find the first set setter method(s) that match: * 1) First set (setter) method, whether public or not. * 2) First public set (setter) method, provided the method's declaring class is also public. * This may be the same as 1), if 1) is also public and its declaring class is also public. * 3) First public non-Default interface set (setter) method, provided the method's declaring class is also public. * The order of preference (priority) for the above matches will be 2 (1st public setter), * 3 (1st public non-Default interface setter), 1 (1st setter of any kind). * This updated methodology should help limit the need to modify method accessibility levels in some circumstances. * * @param context * The current execution context. * @param targetClass * Class to search for a set method (setter). * @param propertyName * Name of the property for the set method (setter). * * @return * @throws IntrospectionException * @throws OgnlException */ private static Method _getSetMethod(OgnlContext context,Class targetClass,String propertyName) throws IntrospectionException,OgnlException{ Method result = null; List methods = getDeclaredMethods(targetClass, propertyName, true /* find 'set' methods */); if (methods != null){ Method firstSetter = null; Method firstPublicSetter = null; Method firstNonDefaultPublicInterfaceSetter = null; for (Object method : methods){ Method m = (Method) method; Class[] mParameterTypes = findParameterTypes(targetClass, m); //getParameterTypes(m); if (mParameterTypes.length == 1){ boolean declaringClassIsPublic = Modifier.isPublic(m.getDeclaringClass().getModifiers()); if (firstSetter == null){ firstSetter = m; if (_useFirstMatchGetSetLookup){ break; // Stop looking (emulate original logic, return 1st match) } } if (firstPublicSetter == null && Modifier.isPublic(m.getModifiers()) && declaringClassIsPublic){ firstPublicSetter = m; break; // Stop looking (this is the best possible match) } if (firstNonDefaultPublicInterfaceSetter == null && isNonDefaultPublicInterfaceMethod(m) && declaringClassIsPublic){ firstNonDefaultPublicInterfaceSetter = m; } } } result = (firstPublicSetter != null) ? firstPublicSetter : (firstNonDefaultPublicInterfaceSetter != null) ? firstNonDefaultPublicInterfaceSetter : firstSetter; } return result; } private static final boolean hasSetMethod(OgnlContext context,Object target,Class targetClass,String propertyName) throws IntrospectionException,OgnlException{ return isMethodAccessible(context, target, getSetMethod(context, targetClass, propertyName), propertyName); } public static final boolean hasGetProperty(OgnlContext context,Object target,Object oname) throws IntrospectionException,OgnlException{ Class targetClass = (target == null) ? null : target.getClass(); String name = oname.toString(); return hasGetMethod(context, target, targetClass, name) || hasField(context, target, targetClass, name); } public static final boolean hasSetProperty(OgnlContext context,Object target,Object oname) throws IntrospectionException,OgnlException{ Class targetClass = (target == null) ? null : target.getClass(); String name = oname.toString(); return hasSetMethod(context, target, targetClass, name) || hasField(context, target, targetClass, name); } private static final boolean indexMethodCheck(List methods){ boolean result = false; if (methods.size() > 0){ Method fm = (Method) methods.get(0); Class[] fmpt = getParameterTypes(fm); int fmpc = fmpt.length; Class lastMethodClass = fm.getDeclaringClass(); result = true; for (int i = 1; result && (i < methods.size()); i++){ Method m = (Method) methods.get(i); Class c = m.getDeclaringClass(); // Check to see if more than one method implemented per class if (lastMethodClass == c){ result = false; }else{ Class[] mpt = getParameterTypes(fm); int mpc = fmpt.length; if (fmpc != mpc){ result = false; } for (int j = 0; j < fmpc; j++){ if (fmpt[j] != mpt[j]){ result = false; break; } } } lastMethodClass = c; } } return result; } static void findObjectIndexedPropertyDescriptors(Class targetClass,Map intoMap) throws OgnlException{ Map allMethods = getMethods(targetClass, false); Map pairs = new HashMap(101); for (Iterator it = allMethods.keySet().iterator(); it.hasNext();){ String methodName = (String) it.next(); List methods = (List) allMethods.get(methodName); /* * Only process set/get where there is exactly one implementation of the method per * class and those implementations are all the same */ if (indexMethodCheck(methods)){ boolean isGet = false, isSet = false; Method m = (Method) methods.get(0); if (((isSet = methodName.startsWith(SET_PREFIX)) || (isGet = methodName.startsWith(GET_PREFIX))) && (methodName.length() > 3)){ String propertyName = Introspector.decapitalize(methodName.substring(3)); Class[] parameterTypes = getParameterTypes(m); int parameterCount = parameterTypes.length; if (isGet && (parameterCount == 1) && (m.getReturnType() != Void.TYPE)){ List pair = (List) pairs.get(propertyName); if (pair == null){ pairs.put(propertyName, pair = new ArrayList()); } pair.add(m); } if (isSet && (parameterCount == 2) && (m.getReturnType() == Void.TYPE)){ List pair = (List) pairs.get(propertyName); if (pair == null){ pairs.put(propertyName, pair = new ArrayList()); } pair.add(m); } } } } for (Iterator it = pairs.keySet().iterator(); it.hasNext();){ String propertyName = (String) it.next(); List methods = (List) pairs.get(propertyName); if (methods.size() == 2){ Method method1 = (Method) methods.get(0), method2 = (Method) methods.get(1), setMethod = (method1.getParameterTypes().length == 2) ? method1 : method2, getMethod = (setMethod == method1) ? method2 : method1; Class keyType = getMethod.getParameterTypes()[0], propertyType = getMethod.getReturnType(); if (keyType == setMethod.getParameterTypes()[0]){ if (propertyType == setMethod.getParameterTypes()[1]){ ObjectIndexedPropertyDescriptor propertyDescriptor; try{ propertyDescriptor = new ObjectIndexedPropertyDescriptor(propertyName, propertyType, getMethod, setMethod); }catch (Exception ex){ throw new OgnlException( "creating object indexed property descriptor for '" + propertyName + "' in " + targetClass, ex); } intoMap.put(propertyName, propertyDescriptor); } } } } } /** * This method returns the property descriptors for the given class as a Map. * * @param targetClass * The class to get the descriptors for. * @return Map map of property descriptors for class. * * @throws IntrospectionException * on errors using {@link Introspector}. * @throws OgnlException * On general errors. */ public static Map getPropertyDescriptors(Class targetClass) throws IntrospectionException,OgnlException{ Map result; if ((result = (Map) _propertyDescriptorCache.get(targetClass)) == null){ synchronized (_propertyDescriptorCache){ if ((result = (Map) _propertyDescriptorCache.get(targetClass)) == null){ PropertyDescriptor[] pda = Introspector.getBeanInfo(targetClass).getPropertyDescriptors(); result = new HashMap(101); for (PropertyDescriptor element : pda){ // workaround for Introspector bug 6528714 (bugs.sun.com) if (element.getReadMethod() != null && !isMethodCallable(element.getReadMethod())){ element.setReadMethod( findClosestMatchingMethod( targetClass, element.getReadMethod(), element.getName(), element.getPropertyType(), true)); } if (element.getWriteMethod() != null && !isMethodCallable(element.getWriteMethod())){ element.setWriteMethod( findClosestMatchingMethod( targetClass, element.getWriteMethod(), element.getName(), element.getPropertyType(), false)); } result.put(element.getName(), element); } findObjectIndexedPropertyDescriptors(targetClass, result); _propertyDescriptorCache.put(targetClass, result); } } } return result; } /** * This method returns a PropertyDescriptor for the given class and property name using a Map * lookup (using getPropertyDescriptorsMap()). * * @param targetClass * the class to get the descriptors for. * @param propertyName * the property name of targetClass for which a Descriptor is requested. * @return the PropertyDescriptor for propertyName of targetClass. * @throws IntrospectionException * on errors using {@link Introspector}. * @throws OgnlException * On general errors. */ public static PropertyDescriptor getPropertyDescriptor(Class targetClass,String propertyName) throws IntrospectionException,OgnlException{ if (targetClass == null){ return null; } return (PropertyDescriptor) getPropertyDescriptors(targetClass).get(propertyName); } static Method findClosestMatchingMethod(Class targetClass,Method m,String propertyName,Class propertyType,boolean isReadMethod){ List methods = getDeclaredMethods(targetClass, propertyName, !isReadMethod); if (methods != null){ for (Object method1 : methods){ Method method = (Method) method1; if (method.getName().equals(m.getName()) && m.getReturnType().isAssignableFrom(m.getReturnType()) && method.getReturnType() == propertyType && method.getParameterTypes().length == m.getParameterTypes().length){ return method; } } } return m; } public static void setMethodAccessor(Class cls,MethodAccessor accessor){ synchronized (_methodAccessors){ _methodAccessors.put(cls, accessor); } } public static MethodAccessor getMethodAccessor(Class cls) throws OgnlException{ MethodAccessor answer = (MethodAccessor) getHandler(cls, _methodAccessors); if (answer != null){ return answer; } throw new OgnlException("No method accessor for " + cls); } public static void setPropertyAccessor(Class cls,PropertyAccessor accessor){ synchronized (_propertyAccessors){ _propertyAccessors.put(cls, accessor); } } public static PropertyAccessor getPropertyAccessor(Class cls) throws OgnlException{ PropertyAccessor answer = (PropertyAccessor) getHandler(cls, _propertyAccessors); if (answer != null){ return answer; } throw new OgnlException("No property accessor for class " + cls); } public static ElementsAccessor getElementsAccessor(Class cls) throws OgnlException{ ElementsAccessor answer = (ElementsAccessor) getHandler(cls, _elementsAccessors); if (answer != null){ return answer; } throw new OgnlException("No elements accessor for class " + cls); } public static void setElementsAccessor(Class cls,ElementsAccessor accessor){ synchronized (_elementsAccessors){ _elementsAccessors.put(cls, accessor); } } public static NullHandler getNullHandler(Class cls) throws OgnlException{ NullHandler answer = (NullHandler) getHandler(cls, _nullHandlers); if (answer != null){ return answer; } throw new OgnlException("No null handler for class " + cls); } public static void setNullHandler(Class cls,NullHandler handler){ synchronized (_nullHandlers){ _nullHandlers.put(cls, handler); } } private static Object getHandler(Class forClass,ClassCache handlers){ Object answer = null; if ((answer = handlers.get(forClass)) == null){ synchronized (handlers){ if ((answer = handlers.get(forClass)) == null){ Class keyFound; if (forClass.isArray()){ answer = handlers.get(Object[].class); keyFound = null; }else{ keyFound = forClass; outer: for (Class c = forClass; c != null; c = c.getSuperclass()){ answer = handlers.get(c); if (answer == null){ Class[] interfaces = c.getInterfaces(); for (int index = 0, count = interfaces.length; index < count; ++index){ Class iface = interfaces[index]; answer = handlers.get(iface); if (answer == null){ /* Try super-interfaces */ answer = getHandler(iface, handlers); } if (answer != null){ keyFound = iface; break outer; } } }else{ keyFound = c; break; } } } if (answer != null){ if (keyFound != forClass){ handlers.put(forClass, answer); } } } } } return answer; } public static Object getProperty(OgnlContext context,Object source,Object name) throws OgnlException{ PropertyAccessor accessor; if (source == null){ throw new OgnlException("source is null for getProperty(null, \"" + name + "\")"); } if ((accessor = getPropertyAccessor(getTargetClass(source))) == null){ throw new OgnlException("No property accessor for " + getTargetClass(source).getName()); } return accessor.getProperty(context, source, name); } public static void setProperty(OgnlContext context,Object target,Object name,Object value) throws OgnlException{ PropertyAccessor accessor; if (target == null){ throw new OgnlException("target is null for setProperty(null, \"" + name + "\", " + value + ")"); } if ((accessor = getPropertyAccessor(getTargetClass(target))) == null){ throw new OgnlException("No property accessor for " + getTargetClass(target).getName()); } accessor.setProperty(context, target, name, value); } /** * Determines the index property type, if any. Returns INDEXED_PROPERTY_NONE if * the property is not index-accessible as determined by OGNL or JavaBeans. If it is indexable * then this will return whether it is a JavaBeans indexed property, conforming to the indexed * property patterns (returns INDEXED_PROPERTY_INT) or if it conforms to the * OGNL arbitrary object indexable (returns INDEXED_PROPERTY_OBJECT). * * @param context * the current execution context. * @param sourceClass * the Class to invoke indexed property type retrieval on. * @param name * the name of the property for which an indexed property type is sought. * @return the indexed property type (int) for the property name of sourceClass. Returns INDEXED_PROPERTY_NONE if name is * not an indexed property. * @throws OgnlException * for lots of different reasons. */ public static int getIndexedPropertyType(OgnlContext context,Class sourceClass,String name) throws OgnlException{ int result = INDEXED_PROPERTY_NONE; try{ PropertyDescriptor pd = getPropertyDescriptor(sourceClass, name); if (pd != null){ if (pd instanceof IndexedPropertyDescriptor){ result = INDEXED_PROPERTY_INT; }else{ if (pd instanceof ObjectIndexedPropertyDescriptor){ result = INDEXED_PROPERTY_OBJECT; } } } }catch (Exception ex){ throw new OgnlException("problem determining if '" + name + "' is an indexed property", ex); } return result; } public static Object getIndexedProperty(OgnlContext context,Object source,String name,Object index) throws OgnlException{ Object[] args = _objectArrayPool.create(index); try{ PropertyDescriptor pd = getPropertyDescriptor((source == null) ? null : source.getClass(), name); Method m; if (pd instanceof IndexedPropertyDescriptor){ m = ((IndexedPropertyDescriptor) pd).getIndexedReadMethod(); }else{ if (pd instanceof ObjectIndexedPropertyDescriptor){ m = ((ObjectIndexedPropertyDescriptor) pd).getIndexedReadMethod(); }else{ throw new OgnlException("property '" + name + "' is not an indexed property"); } } return callMethod(context, source, m.getName(), args); }catch (OgnlException ex){ throw ex; }catch (Exception ex){ throw new OgnlException("getting indexed property descriptor for '" + name + "'", ex); }finally{ _objectArrayPool.recycle(args); } } public static void setIndexedProperty(OgnlContext context,Object source,String name,Object index,Object value) throws OgnlException{ Object[] args = _objectArrayPool.create(index, value); try{ PropertyDescriptor pd = getPropertyDescriptor((source == null) ? null : source.getClass(), name); Method m; if (pd instanceof IndexedPropertyDescriptor){ m = ((IndexedPropertyDescriptor) pd).getIndexedWriteMethod(); }else{ if (pd instanceof ObjectIndexedPropertyDescriptor){ m = ((ObjectIndexedPropertyDescriptor) pd).getIndexedWriteMethod(); }else{ throw new OgnlException("property '" + name + "' is not an indexed property"); } } callMethod(context, source, m.getName(), args); }catch (OgnlException ex){ throw ex; }catch (Exception ex){ throw new OgnlException("getting indexed property descriptor for '" + name + "'", ex); }finally{ _objectArrayPool.recycle(args); } } public static EvaluationPool getEvaluationPool(){ return _evaluationPool; } public static ObjectArrayPool getObjectArrayPool(){ return _objectArrayPool; } public static Method getMethod(OgnlContext context,Class target,String name,Node[] children,boolean includeStatic) throws Exception{ Class[] parms; if (children != null && children.length > 0){ parms = new Class[children.length]; // used to reset context after loop Class currType = context.getCurrentType(); Class currAccessor = context.getCurrentAccessor(); Object cast = context.get(ExpressionCompiler.PRE_CAST); context.setCurrentObject(context.getRoot()); context.setCurrentType(context.getRoot() != null ? context.getRoot().getClass() : null); context.setCurrentAccessor(null); context.setPreviousType(null); for (int i = 0; i < children.length; i++){ children[i].toGetSourceString(context, context.getRoot()); parms[i] = context.getCurrentType(); } context.put(ExpressionCompiler.PRE_CAST, cast); context.setCurrentType(currType); context.setCurrentAccessor(currAccessor); context.setCurrentObject(target); }else{ parms = EMPTY_CLASS_ARRAY; } List methods = OgnlRuntime.getMethods(target, name, includeStatic); if (methods == null){ return null; } for (Object method : methods){ Method m = (Method) method; boolean varArgs = m.isVarArgs(); if (parms.length != m.getParameterTypes().length && !varArgs){ continue; } Class[] mparms = m.getParameterTypes(); boolean matched = true; for (int p = 0; p < mparms.length; p++){ if (varArgs && mparms[p].isArray()){ continue; } if (parms[p] == null){ matched = false; break; } if (parms[p] == mparms[p]){ continue; } if (mparms[p].isPrimitive() && Character.TYPE != mparms[p] && Byte.TYPE != mparms[p] && Number.class.isAssignableFrom(parms[p]) && OgnlRuntime.getPrimitiveWrapperClass(parms[p]) == mparms[p]){ continue; } matched = false; break; } if (matched){ return m; } } return null; } /** * Finds the best possible match for a method on the specified target class with a matching * name. * *

* The name matched will also try different combinations like is + name, has + name, get + name, etc.. *

* * @param target * The class to find a matching method against. * @param name * The name of the method. * @return The most likely matching {@link Method}, or null if none could be found. */ public static Method getReadMethod(Class target,String name){ return getReadMethod(target, name, null); } public static Method getReadMethod(Class target,String name,Class[] argClasses){ try{ if (name.indexOf('"') >= 0){ name = name.replaceAll("\"", ""); } name = name.toLowerCase(); Method[] methods = target.getMethods(); // exact matches first ArrayList candidates = new ArrayList<>(); for (Method method : methods){ if (!isMethodCallable(method)){ continue; } if ((method.getName().equalsIgnoreCase(name) || method.getName().toLowerCase().equals("get" + name) || method.getName().toLowerCase().equals("has" + name) || method.getName().toLowerCase().equals("is" + name)) && !method.getName().startsWith("set")){ candidates.add(method); } } if (!candidates.isEmpty()){ MatchingMethod mm = findBestMethod(candidates, target, name, argClasses); if (mm != null){ return mm.mMethod; } } for (Method m : methods){ if (!isMethodCallable(m)){ continue; } if (m.getName().equalsIgnoreCase(name) && !m.getName().startsWith("set") && !m.getName().startsWith("get") && !m.getName().startsWith("is") && !m.getName().startsWith("has") && m.getReturnType() != Void.TYPE){ if (!candidates.contains(m)){ candidates.add(m); } } } if (!candidates.isEmpty()){ MatchingMethod mm = findBestMethod(candidates, target, name, argClasses); if (mm != null){ return mm.mMethod; } } // try one last time adding a get to beginning if (!name.startsWith("get")){ Method ret = OgnlRuntime.getReadMethod(target, "get" + name, argClasses); if (ret != null){ return ret; } } if (!candidates.isEmpty()){ // we need to do conversions. // TODO we have to find out which conversions are possible! int reqArgCount = argClasses == null ? 0 : argClasses.length; for (Method m : candidates){ if (m.getParameterTypes().length == reqArgCount){ return m; } } } }catch (Throwable t){ throw OgnlOps.castToRuntime(t); } return null; } public static Method getWriteMethod(Class target,String name){ return getWriteMethod(target, name, null); } public static Method getWriteMethod(Class target,String name,Class[] argClasses){ try{ if (name.indexOf('"') >= 0){ name = name.replaceAll("\"", ""); } BeanInfo info = Introspector.getBeanInfo(target); MethodDescriptor[] methods = info.getMethodDescriptors(); ArrayList candidates = new ArrayList<>(); for (MethodDescriptor method : methods){ if (!isMethodCallable(method.getMethod())){ continue; } if ((method.getName().equalsIgnoreCase(name) || method.getName().toLowerCase().equals(name.toLowerCase()) || method.getName().toLowerCase().equals("set" + name.toLowerCase())) && !method.getName().startsWith("get")){ candidates.add(method.getMethod()); } } if (!candidates.isEmpty()){ MatchingMethod mm = findBestMethod(candidates, target, name, argClasses); if (mm != null){ return mm.mMethod; } } // try again on pure class Method[] cmethods = target.getClass().getMethods(); for (int i = 0; i < cmethods.length; i++){ if (!isMethodCallable(cmethods[i])){ continue; } if ((cmethods[i].getName().equalsIgnoreCase(name) || cmethods[i].getName().toLowerCase().equals(name.toLowerCase()) || cmethods[i].getName().toLowerCase().equals("set" + name.toLowerCase())) && !cmethods[i].getName().startsWith("get")){ Method m = methods[i].getMethod(); if (!candidates.contains(m)){ candidates.add(m); } } } if (!candidates.isEmpty()){ MatchingMethod mm = findBestMethod(candidates, target, name, argClasses); if (mm != null){ return mm.mMethod; } } // try one last time adding a set to beginning if (!name.startsWith("set")){ Method ret = OgnlRuntime.getReadMethod(target, "set" + name, argClasses); if (ret != null){ return ret; } } if (!candidates.isEmpty()){ // we need to do conversions. // TODO we have to find out which conversions are possible! int reqArgCount = argClasses == null ? 0 : argClasses.length; for (Method m : candidates){ if (m.getParameterTypes().length == reqArgCount){ return m; } } if (argClasses == null && candidates.size() == 1){ // this seems to be the TestCase TestOgnlRuntime.test_Complicated_Inheritance() - is this a real world use case? return candidates.get(0); } } }catch (Throwable t){ throw OgnlOps.castToRuntime(t); } return null; } public static PropertyDescriptor getProperty(Class target,String name){ try{ BeanInfo info = Introspector.getBeanInfo(target); PropertyDescriptor[] pds = info.getPropertyDescriptors(); for (PropertyDescriptor pd : pds){ if (pd.getName().equalsIgnoreCase(name) // || pd.getName().toLowerCase().equals(name.toLowerCase())// || pd.getName().toLowerCase().endsWith(name.toLowerCase())){ return pd; } } }catch (Throwable t){ throw OgnlOps.castToRuntime(t); } return null; } public static boolean isBoolean(String expression){ if (expression == null){ return false; } if ("true".equals(expression) || "false".equals(expression) || "!true".equals(expression) || "!false".equals(expression) || "(true)".equals(expression) || "!(true)".equals(expression) || "(false)".equals(expression) || "!(false)".equals(expression) || expression.startsWith("ognl.OgnlOps")){ return true; } return false; } /** * Compares the {@link OgnlContext#getCurrentType()} and {@link OgnlContext#getPreviousType()} class types * on the stack to determine if a numeric expression should force object conversion. *

* Normally used in conjunction with the forceConversion parameter of * {@link OgnlRuntime#getChildSource(OgnlContext,Object,Node,boolean)}. *

* * @param context * The current context. * @return True, if the class types on the stack wouldn't be comparable in a pure numeric expression such as o1 >= o2. */ public static boolean shouldConvertNumericTypes(OgnlContext context){ if (context.getCurrentType() == null || context.getPreviousType() == null){ return true; } if (context.getCurrentType() == context.getPreviousType() && context.getCurrentType().isPrimitive() && context.getPreviousType().isPrimitive()){ return false; } return context.getCurrentType() != null && !context.getCurrentType().isArray() && context.getPreviousType() != null && !context.getPreviousType().isArray(); } /** * Attempts to get the java source string represented by the specific child expression * via the {@link JavaSource#toGetSourceString(OgnlContext,Object)} interface method. * * @param context * The ognl context to pass to the child. * @param target * The current object target to use. * @param child * The child expression. * @return The result of calling {@link JavaSource#toGetSourceString(OgnlContext,Object)} plus additional * enclosures of {@link OgnlOps#convertValue(Object,Class,boolean)} for conversions. * @throws OgnlException * Mandatory exception throwing catching.. (blehh) */ public static String getChildSource(OgnlContext context,Object target,Node child) throws OgnlException{ return getChildSource(context, target, child, false); } /** * Attempts to get the java source string represented by the specific child expression * via the {@link JavaSource#toGetSourceString(OgnlContext,Object)} interface method. * * @param context * The ognl context to pass to the child. * @param target * The current object target to use. * @param child * The child expression. * @param forceConversion * If true, forces {@link OgnlOps#convertValue(Object,Class)} conversions on the objects. * @return The result of calling {@link JavaSource#toGetSourceString(OgnlContext,Object)} plus additional * enclosures of {@link OgnlOps#convertValue(Object,Class,boolean)} for conversions. * @throws OgnlException * Mandatory exception throwing catching.. (blehh) */ public static String getChildSource(OgnlContext context,Object target,Node child,boolean forceConversion) throws OgnlException{ String pre = (String) context.get("_currentChain"); if (pre == null){ pre = ""; } try{ child.getValue(context, target); }catch (NullPointerException e){ // ignore }catch (ArithmeticException e){ context.setCurrentType(int.class); return "0"; }catch (Throwable t){ throw OgnlOps.castToRuntime(t); } String source = null; try{ source = child.toGetSourceString(context, target); }catch (Throwable t){ throw OgnlOps.castToRuntime(t); } // handle root / method expressions that may not have proper root java source access if (!ASTConst.class.isInstance(child) && (target == null || context.getRoot() != target)){ source = pre + source; } if (context.getRoot() != null){ source = ExpressionCompiler.getRootExpression(child, context.getRoot(), context) + source; context.setCurrentAccessor(context.getRoot().getClass()); } if (ASTChain.class.isInstance(child)){ String cast = (String) context.remove(ExpressionCompiler.PRE_CAST); if (cast == null){ cast = ""; } source = cast + source; } if (source == null || source.trim().length() < 1){ source = "null"; } return source; } /* * The idea behind this class is to provide a very fast way to cache getter/setter methods indexed by their class * and property name. * * Instead of creating any kind of complex key object (or a String key by appending class name and property), this * class directly uses the Class clazz and the String propertyName as keys of two levels of ConcurrentHashMaps, * so that it takes advantage of the fact that these two classes are immutable and that their respective hashCode() * and equals() methods are extremely fast and optimized. These two aspects should improve Map access performance. * * Also, using these structure instead of any other kind of key on a single-level map should save a lot of memory * given no specialized cache objects (be them of a specific CacheKey class or mere Strings) ever have to be created * for simply accessing the cache in search for a getter/setter method. * */ private static final class ClassPropertyMethodCache{ // ConcurrentHashMaps do not allow null keys or values, so we will use one of this class's own methods as // a replacement for signaling when the true cached value is 'null' private static final Method NULL_REPLACEMENT; private final ConcurrentHashMap, ConcurrentHashMap> cache = new ConcurrentHashMap<>(); static{ try{ NULL_REPLACEMENT = ClassPropertyMethodCache.class.getDeclaredMethod("get", new Class[] { Class.class, String.class }); }catch (NoSuchMethodException e){ throw new RuntimeException(e); // Will never happen, it's our own method, we know it exists } } ClassPropertyMethodCache(){ super(); } Method get(Class clazz,String propertyName){ ConcurrentHashMap methodsByPropertyName = this.cache.get(clazz); if (methodsByPropertyName == null){ methodsByPropertyName = new ConcurrentHashMap<>(); this.cache.put(clazz, methodsByPropertyName); } Method method = methodsByPropertyName.get(propertyName); if (method == NULL_REPLACEMENT){ return null; } return method; } void put(Class clazz,String propertyName,Method method){ ConcurrentHashMap methodsByPropertyName = this.cache.get(clazz); if (methodsByPropertyName == null){ methodsByPropertyName = new ConcurrentHashMap<>(); this.cache.put(clazz, methodsByPropertyName); } methodsByPropertyName.put(propertyName, (method == null ? NULL_REPLACEMENT : method)); } boolean containsKey(Class clazz,String propertyName){ ConcurrentHashMap methodsByPropertyName = this.cache.get(clazz); if (methodsByPropertyName == null){ return false; } return methodsByPropertyName.containsKey(propertyName); } /** * Allow clearing for the underlying cache of the ClassPropertyMethodCache. * * @since 3.1.25 */ void clear(){ this.cache.clear(); } } /** * Detect the (reported) Major Java version running OGNL. * * Should support naming conventions of pre-JDK9 and JDK9+. * See JEP 223: New Version-String Scheme for details. * * @return Detected Major Java Version, or 5 (minimum supported version for OGNL) if unable to detect. * * @since 3.1.25 */ static int detectMajorJavaVersion(){ int majorVersion = -1; try{ majorVersion = parseMajorJavaVersion(System.getProperty("java.version")); }catch (Exception ex){ // Unavailable (SecurityException, etc.) } if (majorVersion == -1){ majorVersion = 5; // Return minimum supported Java version for OGNL } return majorVersion; } /** * Parse a Java version string to determine the Major Java version. * * Should support naming conventions of pre-JDK9 and JDK9+. * See JEP 223: New Version-String Scheme for details. * * @return Detected Major Java Version, or 5 (minimum supported version for OGNL) if unable to detect. * * @since 3.1.25 */ static int parseMajorJavaVersion(String versionString){ int majorVersion = -1; try{ if (versionString != null && versionString.length() > 0){ final String[] sections = versionString.split("[\\.\\-\\+]"); final int firstSection; final int secondSection; if (sections.length > 0){ // Should not happen, guard anyway if (sections[0].length() > 0){ if (sections.length > 1 && sections[1].length() > 0){ firstSection = Integer.parseInt(sections[0]); if (sections[1].matches("\\d+")){ secondSection = Integer.parseInt(sections[1]); }else{ secondSection = -1; } }else{ firstSection = Integer.parseInt(sections[0]); secondSection = -1; } if (firstSection == 1 && secondSection != -1){ majorVersion = secondSection; // Pre-JDK 9 versioning }else{ majorVersion = firstSection; // JDK9+ versioning } } } } }catch (Exception ex){ // Unavailable (NumberFormatException, etc.) } if (majorVersion == -1){ majorVersion = 5; // Return minimum supported Java version for OGNL } return majorVersion; } /** * Returns an indication as to whether the current state indicates the * JDK9+ (9 and later) access handler is being used / should be used. This * is based on a combination of the detected Major Java Version and the * Java option flag {@link OgnlRuntime#USE_JDK9PLUS_ACESS_HANDLER}. * * @return true if the JDK9 and later access handler is being used / should be used, false otherwise. * * @since 3.1.25 */ public static boolean usingJDK9PlusAccessHandler(){ return (_jdk9Plus && _useJDK9PlusAccessHandler); } }