//-------------------------------------------------------------------------- // 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 ariba.util.expr; import java.beans.IndexedPropertyDescriptor; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; import java.math.BigDecimal; import java.math.BigInteger; import java.security.Permission; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import ariba.util.fieldtype.PrimitiveTypeProvider; /** * This is an abstract class with static methods that define runtime * caching information in AribaExpr. * @author Luke Blanshard (blanshlu@netscape.net) * @author Drew Davidson (drew@ognl.org) */ public abstract class ExprRuntime extends Object { 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 = "AribaExpr.NoConversionPossible"; /** Not an indexed property */ public static int INDEXED_PROPERTY_NONE = 0; /** JavaBeans IndexedProperty */ public static int INDEXED_PROPERTY_INT = 1; /** AribaExpr ObjectIndexedProperty */ public static int INDEXED_PROPERTY_OBJECT = 2; // NULL_STRING give a string displaying "null". public static final String NULL_DISPLY_STRING = "" + null; // NULL_STRING_FIELD is a value when null is casted to a string. public static final String NULL_VALUE_STRING = ""; private static final String SET_PREFIX = "set"; private static final String GET_PREFIX = "get"; private static final String IS_PREFIX = "is"; /** Prefix padding for hexadecimal numbers to HEX_LENGTH. */ private static final Map HEX_PADDING = new HashMap(); /** Hexadecimal prefix for printing "pointers". */ private static final String HEX_PREFIX = "0x"; private static final int HEX_LENGTH = 8; /** Returned by <CODE>getUniqueDescriptor()</CODE> when the object is <CODE>null</CODE>. */ private static final String NULL_OBJECT_STRING = "<null>"; private static ClassCache methodAccessors = new ClassCache(); private static ClassCache elementsAccessors = new ClassCache(); private static ClassCache nullHandlers = new ClassCache(); private static ClassCache propertyDescriptorCache = new ClassCache(); private static ClassCache constructorCache = new ClassCache(); private static ClassCache staticMethodCache = new ClassCache(); private static ClassCache instanceMethodCache = new ClassCache(); private static ClassCache invokePermissionCache = new ClassCache(); private static ClassCache fieldCache = new ClassCache(); private static List superclasses = new ArrayList(); /* Used by fieldCache lookup */ private static ClassCache[] declaredMethods = new ClassCache[] { new ClassCache(), new ClassCache() }; /* set, get */ private static Map primitiveTypes = new HashMap(101); private static ClassCache primitiveDefaults = new ClassCache(); private static Map methodParameterTypesCache = new HashMap(101); private static Map ctorParameterTypesCache = new HashMap(101); private static SecurityManager securityManager = null; // System.getSecurityManager(); private static EvaluationPool evaluationPool = new EvaluationPool(); private static ObjectArrayPool objectArrayPool = new ObjectArrayPool(); /** This is a highly specialized map for storing values keyed by Class objects. */ private static class ClassCache extends Object { /* this MUST be a power of 2 */ private static final int TABLE_SIZE = 512; /* ...and now you see why. The table size is used as a mask for generating hashes */ private static final int TABLE_SIZE_MASK = TABLE_SIZE - 1; private Entry[] table; private static class Entry extends Object { protected Entry next; protected Class key; protected Object value; public Entry(Class key, Object value) { super(); this.key = key; this.value = value; } } public ClassCache() { super(); this.table = new Entry[TABLE_SIZE]; } public final Object get(Class key) { Object result = null; int i = key.hashCode() & TABLE_SIZE_MASK; for (Entry entry = table[i]; entry != null; entry = entry.next) { if (entry.key == key) { result = entry.value; break; } } return result; } public final Object put(Class key, Object value) { Object result = null; int i = key.hashCode() & TABLE_SIZE_MASK; Entry entry = table[i]; if (entry == null) { table[i] = new Entry(key, value); } else { if (entry.key == key) { result = entry.value; entry.value = value; } else { while (true) { if (entry.key == key) { /* replace value */ result = entry.value; entry.value = value; break; } else { if (entry.next == null) { /* add value */ entry.next = new Entry(key, value); break; } } entry = entry.next; } } } return result; } } static { /* We're missing these in util.fieldvalue.OrderedList... setElementsAccessor( Map.class, new MapElementsAccessor() ); // Over values setElementsAccessor( Number.class, new NumberElementsAccessor() ); // Integers, 0 ... setElementsAccessor( Object.class, new ObjectElementsAccessor() ); // Iterator over one element -- target obj setElementsAccessor( Iterator.class, new IteratorElementsAccessor() ); setElementsAccessor( Enumeration.class, new EnumerationElementsAccessor() ); */ 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(Byte.TYPE, new Byte((byte)0)); primitiveDefaults.put(Short.TYPE, 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)); } /** 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. */ public static Class getTargetClass(Object o) { return (o == null) ? null : ((o instanceof Class) ? (Class)o : o.getClass()); } /** Returns the base name (the class name without the package name prepended) of the object given. */ public static String getBaseName(Object o) { return (o == null) ? null : getClassBaseName(o.getClass()); } /** Returns the base name (the class name without the package name prepended) of the class given. */ public static String getClassBaseName(Class c) { String s = c.getName(); return s.substring(s.lastIndexOf('.') + 1); } public static String getClassName(Object o, boolean fullyQualified) { if (!(o instanceof Class)) { o = o.getClass(); } return getClassName((Class)o, fullyQualified); } public static String getClassName(Class c, boolean fullyQualified) { return fullyQualified ? c.getName() : getClassBaseName(c); } /** Returns the package name of the object's class. */ public static String getPackageName(Object o) { return (o == null) ? null : getClassPackageName(o.getClass()); } /** Returns the package name of the class given. */ public static String getClassPackageName(Class c) { String s = c.getName(); int i = s.lastIndexOf('.'); return (i < 0) ? null : s.substring(0, i); } /** Returns a "pointer" string in the usual format for these things - 0x<hex digits>. */ public static String getPointerString(int num) { StringBuffer result = new StringBuffer(); String hex = Integer.toHexString(num), pad; Integer l = new Integer(hex.length()); //result.append(HEX_PREFIX); if ((pad = (String)HEX_PADDING.get(l)) == null) { StringBuffer pb = new StringBuffer(); for (int i = hex.length(); i < HEX_LENGTH; i++) { pb.append('0'); } pad = new String(pb); HEX_PADDING.put(l, pad); } result.append(pad); result.append(hex); return new String(result); } /** Returns a "pointer" string in the usual format for these things - 0x<hex digits> for the object given. This will always return a unique value for each object. */ public static String getPointerString(Object o) { return getPointerString((o == null) ? 0 : System.identityHashCode(o)); } /** Returns a unique descriptor string that includes the object's class and a unique integer identifier. If fullyQualified is true then the class name will be fully qualified to include the package name, else it will be just the class' base name. */ public static String getUniqueDescriptor(Object object, boolean fullyQualified) { StringBuffer result = new StringBuffer(); if (object != null) { if (object instanceof Proxy) { Class interfaceClass = object.getClass().getInterfaces()[0]; result.append(getClassName(interfaceClass, fullyQualified)); result.append('^'); object = Proxy.getInvocationHandler(object); } result.append(getClassName(object, fullyQualified)); result.append('@'); result.append(getPointerString(object)); } else { result.append(NULL_OBJECT_STRING); } return new String(result); } /** Returns a unique descriptor string that includes the object's class' base name and a unique integer identifier. */ public static String getUniqueDescriptor(Object object) { return getUniqueDescriptor(object, false); } /** Utility to convert a List into an Object[] array. If the list is zero elements this will return a constant array; toArray() on List always returns a new object and this is wasteful for our purposes. */ public static Object[] toArray(List list) { Object[] result; int size = list.size(); if (size == 0) { result = NoArguments; } else { result = getObjectArrayPool().create(list.size()); for (int i = 0; i < size; i++) { result[i] = list.get(i); } } return result; } /** Returns the parameter types of the given method. */ public static Class[] getParameterTypes(Method m) { synchronized(methodParameterTypesCache) { Class[] result; if ((result = (Class[])methodParameterTypesCache.get(m)) == null) { methodParameterTypesCache.put(m, result = m.getParameterTypes()); } return result; } } /** Returns the parameter types of the given method. */ public static Class[] getParameterTypes(Constructor c) { synchronized(ctorParameterTypesCache) { Class[] result; if ((result = (Class[])ctorParameterTypesCache.get(c)) == null) { ctorParameterTypesCache.put(c, result = c.getParameterTypes()); } return result; } } /** * Gets the SecurityManager that AribaExpr uses to determine permissions for * invoking methods. * * @return SecurityManager for AribaExpr */ public static SecurityManager getSecurityManager() { return securityManager; } /** * Sets the SecurityManager that AribaExpr uses to determine permissions for * invoking methods. * * @param value SecurityManager to set */ public static void setSecurityManager(SecurityManager value) { securityManager = value; } /** Permission will be named "invoke.<declaring-class>.<method-name>". */ public static Permission getPermission(Method method) { Permission result = null; 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 ExprInvokePermission("invoke." + mc.getName() + "." + method.getName()); permissions.put(method.getName(), result); } } return result; } public static Object invokeMethod( Object target, Method method, Object[] argsArray ) throws InvocationTargetException, IllegalAccessException { Object result; boolean wasAccessible = true; if (securityManager != null) { try { securityManager.checkPermission(getPermission(method)); } catch (SecurityException ex) { throw new IllegalAccessException("Method [" + method + "] cannot be accessed."); } } if (!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) { if (!(wasAccessible = ((AccessibleObject)method).isAccessible())) { ((AccessibleObject)method).setAccessible(true); } } result = method.invoke( target, argsArray ); if (!wasAccessible) { ((AccessibleObject)method).setAccessible(false); } return result; } /** * Gets the class for a method argument that is appropriate for looking up methods * by reflection, by looking for the standard primitive wrapper classes and * exchanging for them their underlying primitive class objects. Other classes are * passed through unchanged. * * @param arg an object that is being passed to a method * @return the class to use to look up the method */ public static 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; } /** * Tells whether the given object's type can be widen to the given class * ---that is, whether the given object can be passed as an argument * to a method or constructor with type widdening conversion. */ public static final boolean canWideningTo( Object object, Class c ) { boolean result = false; if ( object != null ) { if (TypeConversionHelper.canConvert(c, object)) { result = true; } } else { // If object is null, class cannot be a primitive type. result = !c.isPrimitive(); } return result; } /** * Tells whether the given array of objects is compatible with the given array of * classes---that is, whether the given array of objects can be passed as arguments * to a method or constructor whose parameter types are the given array of classes. */ public static final boolean canArgsWideningTo( Object[] args, Class[] classes ) { boolean result = true; if ( args.length != classes.length ) { result = false; } else { for ( int index=0, count=args.length; result && (index < count); ++index ) { result = canWideningTo(args[index], classes[index]); } } return result; } /** * 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. */ public static final boolean isTypeCompatible( Object object, Class c ) { boolean result = true; if ( object != null ) { if ( c.isPrimitive() ) { if ( getArgClass(object) != c) { result = false; } } else if ( !c.isInstance(object) ) { result = false; } } return result; } /** * Tells whether the given array of objects is compatible with the given array of * classes---that is, whether the given array of objects can be passed as arguments * to a method or constructor whose parameter types are the given array of classes. */ public static final boolean areArgsCompatible( Object[] args, Class[] classes ) { boolean result = true; if ( args.length != classes.length ) { result = false; } else { for ( int index=0, count=args.length; result && (index < count); ++index ) { result = isTypeCompatible(args[index], classes[index]); } } return result; } /** * Tells whether the first array of classes is more specific than the second. * Assumes that the two arrays are of the same length. */ 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 ( PrimitiveTypeProvider.isSupportedType(c1.getName()) && PrimitiveTypeProvider.isSupportedType(c2.getName())) { // joliang : handle the primtive type widening. If type in // classes2 is the widened type, return true. return PrimitiveTypeProvider.isWideningTypeOf( c2.getName(), c1.getName()); } 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 final String getModifierString(int modifiers) { String result; if (Modifier.isPublic(modifiers)) result = "public"; else if (Modifier.isProtected(modifiers)) result = "protected"; else if (Modifier.isPrivate(modifiers)) result = "private"; else result = ""; if (Modifier.isStatic(modifiers)) result = "static " + result; if (Modifier.isFinal(modifiers)) result = "final " + result; if (Modifier.isNative(modifiers)) result = "native " + result; if (Modifier.isSynchronized(modifiers)) result = "synchronized " + result; if (Modifier.isTransient(modifiers)) result = "transient " + result; return result; } public static final Class classForName( ExprContext context, String className ) throws ClassNotFoundException { Class result = (Class)primitiveTypes.get(className); if (result == null) { ClassResolver resolver; if ((context == null) || ((resolver = context.getClassResolver()) == null)) { resolver = ExprContext.DEFAULT_CLASS_RESOLVER; } result = resolver.classForName(className, context); } return result; } public static final boolean isInstance( ExprContext context, Object value, String className ) throws ExprException { try { Class c = classForName( context, className); return c.isInstance( value ); } catch (ClassNotFoundException e) { throw new ExprException( "No such class: " + className, e ); } } public static Object getPrimitiveDefaultValue( Class forClass ) { return primitiveDefaults.get(forClass); } public static Object getConvertedType( ExprContext context, Object target, Member member, String propertyName, Object value, Class type) { return context.getTypeConverter().convertValue(context, target, member, propertyName, value, type); } public static boolean getConvertedTypes( ExprContext 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 == ExprRuntime.NoConversionPossible) { result = false; } else { newArgs[i] = v; } } } } return result; } public static Method getConvertedMethodAndArgs( ExprContext 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 = getParameterTypes(m); if (getConvertedTypes( context, target, m, propertyName, parameterTypes, args, newArgs )) { result = m; } } } return result; } public static Constructor getConvertedConstructorAndArgs( ExprContext 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. */ public static Method getAppropriateMethod( ExprContext context, Object source, Object target, String methodName, String propertyName, List methods, Object[] args, Object[] actualArgs ) { Method result = null; Class[] resultParameterTypes = null; if (methods != null) { for (int i = 0, icount = methods.size(); i < icount; i++) { Method m = (Method)methods.get(i); Class[] mParameterTypes = getParameterTypes(m); // joliang : when checking method arguments, take into account // that the type can be widen. if ( canArgsWideningTo(args, mParameterTypes) && ((result == null) || isMoreSpecific(mParameterTypes, resultParameterTypes)) ) { result = m; resultParameterTypes = mParameterTypes; System.arraycopy(args, 0, actualArgs, 0, args.length); for (int j = 0; j < mParameterTypes.length; j++) { Class type = mParameterTypes[j]; // joliang : if it is a supported primitive type, then // convert the type to the parameter type. if (PrimitiveTypeProvider.isSupportedType(type.getName())) { actualArgs[j] = getConvertedType(context, source, result, propertyName, actualArgs[j], type); } } } } } if ( result == null ) { result = getConvertedMethodAndArgs( context, target, propertyName, methods, args, actualArgs ); } return result; } public static Object callAppropriateMethod( ExprContext 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, methodName, propertyName, methods, args, actualArgs ); if ( (method == null) || !isMethodAccessible(context, source, method, propertyName) ) { StringBuffer buffer = new StringBuffer(); if (args != null) { for (int i = 0, ilast = args.length - 1; i <= ilast; i++) { Object arg = args[i]; buffer.append((arg == null) ? NULL_DISPLY_STRING : arg.getClass().getName()); if (i < ilast) { buffer.append(", "); } } } throw new NoSuchMethodException( methodName + "(" + buffer + ")" ); } return invokeMethod(target, method, actualArgs); } 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 final Object callStaticMethod( ExprContext context, String className, String methodName, Object[] args ) throws ExprException, MethodFailedException { try { Object result; Class targetClass = classForName(context, className); MethodAccessor ma = getMethodAccessor(targetClass); return ma.callStaticMethod(context, targetClass, methodName, args); } catch (ClassNotFoundException ex) { throw new MethodFailedException(className, methodName, ex); } } public static final Object callMethod( ExprContext context, Object target, String methodName, String propertyName, Object[] args ) throws ExprException, MethodFailedException { Object result; if (target != null) { MethodAccessor ma = getMethodAccessor(target.getClass()); result = ma.callMethod(context, target, methodName, args); } else { throw new NullPointerException("target is null for method " + methodName); } return result; } public static final Object callConstructor( ExprContext context, String className, Object[] args ) throws ExprException { Throwable reason = null; Object[] actualArgs = args; try { Constructor ctor = null; Class[] ctorParameterTypes = null; Class target = classForName(context, className); List constructors = getConstructors(target); for (int i = 0, icount = constructors.size(); i < icount; i++) { Constructor c = (Constructor)constructors.get(i); 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 ); } public static final Object getMethodValue(ExprContext context, Object target, String propertyName) throws ExprException, IllegalAccessException, NoSuchMethodException, IntrospectionException { return getMethodValue(context, target, propertyName, false); } /** 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. */ public static final Object getMethodValue(ExprContext context, Object target, String propertyName, boolean checkAccessAndExistence) throws ExprException, IllegalAccessException, NoSuchMethodException, IntrospectionException { Object result = null; Method m = getGetMethod(context, (target == null) ? null : target.getClass(), propertyName); 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 ExprException(propertyName, ex.getTargetException()); } } else { throw new NoSuchMethodException(propertyName); } } return result; } public static final boolean setMethodValue(ExprContext context, Object target, String propertyName, Object value) throws ExprException, IllegalAccessException, NoSuchMethodException, MethodFailedException, IntrospectionException { return setMethodValue(context, target, propertyName, value, false); } public static final boolean setMethodValue(ExprContext context, Object target, String propertyName, Object value, boolean checkAccessAndExistence) throws ExprException, IllegalAccessException, NoSuchMethodException, MethodFailedException, 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 final List getConstructors(Class targetClass) { List result; synchronized(constructorCache) { if ((result = (List)constructorCache.get(targetClass)) == null) { constructorCache.put(targetClass, result = Arrays.asList(targetClass.getConstructors())); } } return result; } public static final Map getMethods( Class targetClass, boolean staticMethods ) { ClassCache cache = (staticMethods ? staticMethodCache : instanceMethodCache); Map result; synchronized(cache) { if ((result = (Map)cache.get(targetClass)) == null) { cache.put(targetClass, result = new HashMap(23)); for (Class c = targetClass; c != null; c = c.getSuperclass()) { Method[] ma = c.getDeclaredMethods(); for (int i = 0, icount = ma.length; i < icount; i++) { if (Modifier.isStatic(ma[i].getModifiers()) == staticMethods) { List ml = (List)result.get(ma[i].getName()); if (ml == null) result.put(ma[i].getName(), ml = new ArrayList()); ml.add(ma[i]); } } } } } return result; } public static final List getMethods( Class targetClass, String name, boolean staticMethods ) { return (List)getMethods(targetClass, staticMethods).get(name); } public static final Map getFields(Class targetClass) { Map result; synchronized(fieldCache) { if ((result = (Map)fieldCache.get(targetClass)) == null) { Field fa[]; result = new HashMap(23); fa = targetClass.getDeclaredFields(); for (int i = 0; i < fa.length; i++) { result.put(fa[i].getName(), fa[i]); } fieldCache.put(targetClass, result); } } return result; } public static final Field getField(Class inClass, String name) { Field result = null; synchronized(fieldCache) { Object o = getFields(inClass).get(name); if (o == null) { superclasses.clear(); for (Class sc = inClass; (sc != null) && (result == 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 (int i = 0, icount = superclasses.size(); i < icount; i++) { getFields((Class)superclasses.get(i)).put(name, (result == null) ? NotFound : result); } } else { if (o instanceof Field) { result = (Field)o; } else { if (result == NotFound) result = null; } } } return result; } public static final Object getFieldValue(ExprContext context, Object target, String propertyName) throws NoSuchFieldException { return getFieldValue(context, target, propertyName, false); } public static final Object getFieldValue(ExprContext context, Object target, String propertyName, boolean checkAccessAndExistence) throws NoSuchFieldException { Object result = null; 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 { Object state = null; if ((f != null) && !Modifier.isStatic(f.getModifiers())) { state = context.getMemberAccess().setup(context, target, f, propertyName); result = f.get(target); context.getMemberAccess().restore(context, target, f, propertyName, state); } else throw new NoSuchFieldException(propertyName); } catch (IllegalAccessException ex) { throw new NoSuchFieldException(propertyName); } } } return result; } public static final boolean setFieldValue(ExprContext context, Object target, String propertyName, Object value) throws ExprException { boolean result = false; try { Field f = getField( (target == null) ? null : target.getClass(), propertyName ); Object state; if ((f != null) && !Modifier.isStatic(f.getModifiers())) { 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; } public static final boolean isFieldAccessible(ExprContext context, Object target, Class inClass, String propertyName) { return isFieldAccessible(context, target, getField(inClass, propertyName), propertyName); } public static final boolean isFieldAccessible(ExprContext context, Object target, Field field, String propertyName) { return context.getMemberAccess().isAccessible(context, target, field, propertyName); } public static final boolean hasField(ExprContext context, Object target, Class inClass, String propertyName) { Field f = getField(inClass, propertyName); return (f != null) && isFieldAccessible(context, target, f, propertyName); } public static final void setStaticField( ExprContext context, String className, String fieldName, Object value) throws ExprException { Exception reason = null; try { Class c = classForName(context, className); /* Check for virtual static field "class"; this cannot interfere with normal static fields because it is a reserved word. */ if (fieldName.equals("class")) { throw new ExprException("Field 'class' cannot be modified."); } else { Field f = c.getField(fieldName); if ( !Modifier.isStatic(f.getModifiers()) ) { throw new ExprException( "Field " + fieldName + " of class " + className + " is not static." ); } if (Modifier.isFinal(f.getModifiers())) { throw new ExprException( "Field " + fieldName + " of class " + className + " is final." ); } f.set(null, value); } } catch (ClassNotFoundException e) { reason = e; } catch (NoSuchFieldException e) { reason = e; } catch (SecurityException e) { reason = e; } catch (IllegalAccessException e) { reason = e; } throw new ExprException( "Could not set static field " + fieldName + " from class " + className, reason ); } public static final Object getStaticField( ExprContext context, String className, String fieldName ) throws ExprException { Exception reason = null; try { Class c = classForName(context, className); /* 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 { Field f = c.getField(fieldName); if ( !Modifier.isStatic(f.getModifiers()) ) throw new ExprException( "Field " + fieldName + " of class " + className + " is not static" ); return f.get(null); } } catch (ClassNotFoundException e) { reason = e; } catch (NoSuchFieldException e) { reason = e; } catch (SecurityException e) { reason = e; } catch (IllegalAccessException e) { reason = e; } throw new ExprException( "Could not get static field " + fieldName + " from class " + className, reason ); } public static final List getDeclaredMethods(Class targetClass, String propertyName, boolean findSets) { List result = null; ClassCache cache = declaredMethods[findSets ? 0 : 1]; synchronized(cache) { Map propertyCache = (Map)cache.get(targetClass); if ((propertyCache == null) || ((result = (List)propertyCache.get(propertyName)) == null)) { String baseName = Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1); int len = baseName.length(); for (Class c = targetClass; c != null; c = c.getSuperclass()) { Method[] methods = c.getDeclaredMethods(); for (int i = 0; i < methods.length; i++) { String ms = methods[i].getName(); if (ms.endsWith(baseName)) { boolean isSet = false, isGet = false, isIs = false; if ((isSet = ms.startsWith(SET_PREFIX)) || (isGet = ms.startsWith(GET_PREFIX)) || (isIs = ms.startsWith(IS_PREFIX))) { int prefixLength = (isIs ? 2 : 3); if (isSet == findSets) { if (baseName.length() == (ms.length() - prefixLength)) { if (result == null) { result = new ArrayList(); } result.add(methods[i]); } } } } } } if (propertyCache == null) { cache.put(targetClass, propertyCache = new HashMap(101)); } propertyCache.put(propertyName, (result == null) ? NotFoundList : result); } return (result == NotFoundList) ? null : result; } } public static final Method getGetMethod(ExprContext context, Class targetClass, String propertyName) throws IntrospectionException, ExprException { Method result = null; PropertyDescriptor pd = getPropertyDescriptor(targetClass, propertyName); if (pd == null) { List methods = getDeclaredMethods(targetClass, propertyName, false /* find 'get' methods */); if (methods != null) { for (int i = 0, icount = methods.size(); i < icount; i++) { Method m = (Method)methods.get(i); Class[] mParameterTypes = getParameterTypes(m); if (mParameterTypes.length == 0) { result = m; break; } } } } else { result = pd.getReadMethod(); } return result; } public static final boolean isMethodAccessible(ExprContext context, Object target, Method method, String propertyName) { return (method == null) ? false : context.getMemberAccess().isAccessible(context, target, method, propertyName); } public static final boolean hasGetMethod(ExprContext context, Object target, Class targetClass, String propertyName) throws IntrospectionException, ExprException { return isMethodAccessible(context, target, getGetMethod(context, targetClass, propertyName), propertyName); } public static final Method getSetMethod(ExprContext context, Class targetClass, String propertyName) throws IntrospectionException, ExprException { Method result = null; PropertyDescriptor pd = getPropertyDescriptor(targetClass, propertyName); if (pd == null) { List methods = getDeclaredMethods(targetClass, propertyName, true /* find 'set' methods */); if (methods != null) { for (int i = 0, icount = methods.size(); i < icount; i++) { Method m = (Method)methods.get(i); Class[] mParameterTypes = getParameterTypes(m); if (mParameterTypes.length == 1) { result = m; break; } } } } else { result = pd.getWriteMethod(); } return result; } public static final boolean hasSetMethod(ExprContext context, Object target, Class targetClass, String propertyName) throws IntrospectionException, ExprException { return isMethodAccessible(context, target, getSetMethod(context, targetClass, propertyName), propertyName); } public static final boolean hasGetProperty( ExprContext context, Object target, Object oname ) throws IntrospectionException, ExprException { 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( ExprContext context, Object target, Object oname ) throws IntrospectionException, ExprException { 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; } private static final void findObjectIndexedPropertyDescriptors(Class targetClass, Map intoMap) throws ExprException { 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 ExprException("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 */ public static final Map getPropertyDescriptors(Class targetClass) throws IntrospectionException, ExprException { Map result; synchronized(propertyDescriptorCache) { if ((result = (Map)propertyDescriptorCache.get(targetClass)) == null) { PropertyDescriptor[] pda = Introspector.getBeanInfo(targetClass).getPropertyDescriptors(); result = new HashMap(101); for (int i = 0, icount = pda.length; i < icount; i++) { result.put(pda[i].getName(), pda[i]); } 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()). */ public static final PropertyDescriptor getPropertyDescriptor(Class targetClass, String propertyName) throws IntrospectionException, ExprException { return (targetClass == null) ? null : (PropertyDescriptor)getPropertyDescriptors(targetClass).get(propertyName); } public static final PropertyDescriptor[] getPropertyDescriptorsArray(Class targetClass) throws IntrospectionException { PropertyDescriptor[] result = null; if (targetClass != null) { synchronized(propertyDescriptorCache) { if ((result = (PropertyDescriptor[])propertyDescriptorCache.get(targetClass)) == null) { propertyDescriptorCache.put(targetClass, result = Introspector.getBeanInfo(targetClass).getPropertyDescriptors()); } } } return result; } /** Gets the property descriptor with the given name for the target class given. @param targetClass Class for which property descriptor is desired @param name Name of property @return PropertyDescriptor of the named property or null if the class has no property with the given name */ public static final PropertyDescriptor getPropertyDescriptorFromArray(Class targetClass, String name) throws IntrospectionException { PropertyDescriptor result = null; PropertyDescriptor[] pda = getPropertyDescriptorsArray(targetClass); for (int i = 0, icount = pda.length; (result == null) && (i < icount); i++) { if (pda[i].getName().compareTo(name) == 0) { result = pda[i]; } } return result; } public static final void setMethodAccessor(Class cls, MethodAccessor accessor) { synchronized(methodAccessors) { methodAccessors.put( cls, accessor ); } } public static final MethodAccessor getMethodAccessor( Class cls ) throws ExprException { MethodAccessor answer = (MethodAccessor)getHandler( cls, methodAccessors ); if ( answer != null ) return answer; throw new ExprException( "No method accessor for " + cls ); } public static final NullHandler getNullHandler( Class cls ) throws ExprException { NullHandler answer = (NullHandler)getHandler( cls, nullHandlers ); if ( answer != null ) return answer; throw new ExprException( "No null handler for class " + cls ); } public static final void setNullHandler( Class cls, NullHandler handler ) { synchronized(nullHandlers) { nullHandlers.put( cls, handler ); } } private static final Object getHandler( Class forClass, ClassCache handlers ) { Object answer = 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; } /** Determines the index property type, if any. Returns <code>INDEXED_PROPERTY_NONE</code> if the property is not index-accessible as determined by AribaExpr or JavaBeans. If it is indexable then this will return whether it is a JavaBeans indexed property, conforming to the indexed property patterns (returns <code>INDEXED_PROPERTY_INT</code>) or if it conforms to the AribaExpr arbitrary object indexable (returns <code>INDEXED_PROPERTY_OBJECT</code>). */ public static final int getIndexedPropertyType( ExprContext context, Class sourceClass, String name) throws ExprException { 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 ExprException("problem determining if '" + name + "' is an indexed property", ex); } return result; } public static final Object getIndexedProperty( ExprContext context, Object source, String name, Object index ) throws ExprException { Throwable reason = null; 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 ExprException("property '" + name + "' is not an indexed property"); } } return callMethod(context, source, m.getName(), name, args); } catch (ExprException ex) { throw ex; } catch (Exception ex) { throw new ExprException("getting indexed property descriptor for '" + name + "'", ex); } finally { objectArrayPool.recycle(args); } } public static final void setIndexedProperty( ExprContext context, Object source, String name, Object index, Object value ) throws ExprException { Throwable reason = null; 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 ExprException("property '" + name + "' is not an indexed property"); } } callMethod(context, source, m.getName(), name, args); } catch (ExprException ex) { throw ex; } catch (Exception ex) { throw new ExprException("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 Object convert (Object source) throws ExprException { if (source == null) { return null; } try { return ConvertibleFieldValue.convert(source); } catch (Exception e) { throw new ExprException(e.getLocalizedMessage()); } } }