Source code: ognl/OgnlRuntime.java
1 //--------------------------------------------------------------------------
2 // Copyright (c) 1998-2004, Drew Davidson and Luke Blanshard
3 // All rights reserved.
4 //
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions are
7 // met:
8 //
9 // Redistributions of source code must retain the above copyright notice,
10 // this list of conditions and the following disclaimer.
11 // Redistributions in binary form must reproduce the above copyright
12 // notice, this list of conditions and the following disclaimer in the
13 // documentation and/or other materials provided with the distribution.
14 // Neither the name of the Drew Davidson nor the names of its contributors
15 // may be used to endorse or promote products derived from this software
16 // without specific prior written permission.
17 //
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21 // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22 // COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23 // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24 // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
25 // OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26 // AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27 // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
28 // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
29 // DAMAGE.
30 //--------------------------------------------------------------------------
31 package ognl;
32
33 import java.beans.*;
34 import java.io.*;
35 import java.lang.reflect.*;
36 import java.math.*;
37 import java.security.*;
38 import java.util.*;
39
40 /**
41 * This is an abstract class with static methods that define runtime
42 * caching information in OGNL.
43 * @author Luke Blanshard (blanshlu@netscape.net)
44 * @author Drew Davidson (drew@ognl.org)
45 */
46 public abstract class OgnlRuntime extends Object
47 {
48 public static final Object NotFound = new Object();
49 public static final List NotFoundList = new ArrayList();
50 public static final Map NotFoundMap = new HashMap();
51 public static final Object[] NoArguments = new Object[] {};
52 public static final Class[] NoArgumentTypes = new Class[] {};
53
54 /** Token returned by TypeConverter for no conversion possible */
55 public static final Object NoConversionPossible = "ognl.NoConversionPossible";
56
57 /** Not an indexed property */
58 public static int INDEXED_PROPERTY_NONE = 0;
59 /** JavaBeans IndexedProperty */
60 public static int INDEXED_PROPERTY_INT = 1;
61 /** OGNL ObjectIndexedProperty */
62 public static int INDEXED_PROPERTY_OBJECT = 2;
63
64 public static final String NULL_STRING = "" + null;
65
66 private static final String SET_PREFIX = "set";
67 private static final String GET_PREFIX = "get";
68 private static final String IS_PREFIX = "is";
69
70 /**
71 Prefix padding for hexadecimal numbers to HEX_LENGTH.
72 */
73 private static final Map HEX_PADDING = new HashMap();
74
75 /**
76 Hexadecimal prefix for printing "pointers".
77 */
78 private static final String HEX_PREFIX = "0x";
79
80 private static final int HEX_LENGTH = 8;
81 /**
82 Returned by <CODE>getUniqueDescriptor()</CODE> when the
83 object is <CODE>null</CODE>.
84 */
85 private static final String NULL_OBJECT_STRING = "<null>";
86
87
88 private static ClassCache methodAccessors = new ClassCache();
89 private static ClassCache propertyAccessors = new ClassCache();
90 private static ClassCache elementsAccessors = new ClassCache();
91 private static ClassCache nullHandlers = new ClassCache();
92 private static ClassCache propertyDescriptorCache = new ClassCache();
93 private static ClassCache constructorCache = new ClassCache();
94 private static ClassCache staticMethodCache = new ClassCache();
95 private static ClassCache instanceMethodCache = new ClassCache();
96 private static ClassCache invokePermissionCache = new ClassCache();
97 private static ClassCache fieldCache = new ClassCache();
98 private static List superclasses = new ArrayList(); /* Used by fieldCache lookup */
99 private static ClassCache[] declaredMethods = new ClassCache[] { new ClassCache(), new ClassCache() }; /* set, get */
100 private static Map primitiveTypes = new HashMap(101);
101 private static ClassCache primitiveDefaults = new ClassCache();
102 private static Map methodParameterTypesCache = new HashMap(101);
103 private static Map ctorParameterTypesCache = new HashMap(101);
104 private static SecurityManager securityManager = System.getSecurityManager();
105 private static EvaluationPool evaluationPool = new EvaluationPool();
106 private static ObjectArrayPool objectArrayPool = new ObjectArrayPool();
107
108 /**
109 This is a highly specialized map for storing values keyed by Class objects.
110 */
111 private static class ClassCache extends Object
112 {
113 /* this MUST be a power of 2 */
114 private static final int TABLE_SIZE = 512;
115
116 /* ...and now you see why. The table size is used as a mask for generating hashes */
117 private static final int TABLE_SIZE_MASK = TABLE_SIZE - 1;
118
119 private Entry[] table;
120
121 private static class Entry extends Object
122 {
123 protected Entry next;
124 protected Class key;
125 protected Object value;
126
127 public Entry(Class key, Object value)
128 {
129 super();
130 this.key = key;
131 this.value = value;
132 }
133 }
134
135 public ClassCache()
136 {
137 super();
138 this.table = new Entry[TABLE_SIZE];
139 }
140
141 public final Object get(Class key)
142 {
143 Object result = null;
144 int i = key.hashCode() & TABLE_SIZE_MASK;
145
146 for (Entry entry = table[i]; entry != null; entry = entry.next) {
147 if (entry.key == key) {
148 result = entry.value;
149 break;
150 }
151 }
152 return result;
153 }
154
155 public final Object put(Class key, Object value)
156 {
157 Object result = null;
158 int i = key.hashCode() & TABLE_SIZE_MASK;
159 Entry entry = table[i];
160
161 if (entry == null) {
162 table[i] = new Entry(key, value);
163 } else {
164 if (entry.key == key) {
165 result = entry.value;
166 entry.value = value;
167 } else {
168 while (true) {
169 if (entry.key == key) {
170 /* replace value */
171 result = entry.value;
172 entry.value = value;
173 break;
174 } else {
175 if (entry.next == null) {
176 /* add value */
177 entry.next = new Entry(key, value);
178 break;
179 }
180 }
181 entry = entry.next;
182 }
183 }
184 }
185 return result;
186 }
187 }
188
189 static
190 {
191 PropertyAccessor p = new ArrayPropertyAccessor();
192 setPropertyAccessor( Object.class, new ObjectPropertyAccessor() );
193 setPropertyAccessor( byte[].class, p );
194 setPropertyAccessor( short[].class, p );
195 setPropertyAccessor( char[].class, p );
196 setPropertyAccessor( int[].class, p );
197 setPropertyAccessor( long[].class, p );
198 setPropertyAccessor( float[].class, p );
199 setPropertyAccessor( double[].class, p );
200 setPropertyAccessor( Object[].class, p );
201 setPropertyAccessor( List.class, new ListPropertyAccessor() );
202 setPropertyAccessor( Map.class, new MapPropertyAccessor() );
203 setPropertyAccessor( Set.class, new SetPropertyAccessor() );
204 setPropertyAccessor( Iterator.class, new IteratorPropertyAccessor() );
205 setPropertyAccessor( Enumeration.class, new EnumerationPropertyAccessor() );
206
207 ElementsAccessor e = new ArrayElementsAccessor();
208 setElementsAccessor( Object.class, new ObjectElementsAccessor() );
209 setElementsAccessor( byte[].class, e );
210 setElementsAccessor( short[].class, e );
211 setElementsAccessor( char[].class, e );
212 setElementsAccessor( int[].class, e );
213 setElementsAccessor( long[].class, e );
214 setElementsAccessor( float[].class, e );
215 setElementsAccessor( double[].class, e );
216 setElementsAccessor( Object[].class, e );
217 setElementsAccessor( Collection.class, new CollectionElementsAccessor() );
218 setElementsAccessor( Map.class, new MapElementsAccessor() );
219 setElementsAccessor( Iterator.class, new IteratorElementsAccessor() );
220 setElementsAccessor( Enumeration.class, new EnumerationElementsAccessor() );
221 setElementsAccessor( Number.class, new NumberElementsAccessor() );
222
223 NullHandler nh = new ObjectNullHandler();
224 setNullHandler( Object.class, nh);
225 setNullHandler( byte[].class, nh );
226 setNullHandler( short[].class, nh );
227 setNullHandler( char[].class, nh );
228 setNullHandler( int[].class, nh );
229 setNullHandler( long[].class, nh );
230 setNullHandler( float[].class, nh );
231 setNullHandler( double[].class, nh );
232 setNullHandler( Object[].class, nh );
233
234 MethodAccessor ma = new ObjectMethodAccessor();
235 setMethodAccessor( Object.class, ma );
236 setMethodAccessor( byte[].class, ma );
237 setMethodAccessor( short[].class, ma );
238 setMethodAccessor( char[].class, ma );
239 setMethodAccessor( int[].class, ma );
240 setMethodAccessor( long[].class, ma );
241 setMethodAccessor( float[].class, ma );
242 setMethodAccessor( double[].class, ma );
243 setMethodAccessor( Object[].class, ma );
244
245 primitiveTypes.put("boolean", Boolean.TYPE );
246 primitiveTypes.put("byte", Byte.TYPE );
247 primitiveTypes.put("short", Short.TYPE );
248 primitiveTypes.put("char", Character.TYPE );
249 primitiveTypes.put("int", Integer.TYPE );
250 primitiveTypes.put("long", Long.TYPE );
251 primitiveTypes.put("float", Float.TYPE );
252 primitiveTypes.put("double", Double.TYPE );
253
254 primitiveDefaults.put(Boolean.TYPE, Boolean.FALSE);
255 primitiveDefaults.put(Byte.TYPE, new Byte((byte)0));
256 primitiveDefaults.put(Short.TYPE, new Short((short)0));
257 primitiveDefaults.put(Character.TYPE, new Character((char)0));
258 primitiveDefaults.put(Integer.TYPE, new Integer(0));
259 primitiveDefaults.put(Long.TYPE, new Long(0L));
260 primitiveDefaults.put(Float.TYPE, new Float(0.0f));
261 primitiveDefaults.put(Double.TYPE, new Double(0.0));
262 primitiveDefaults.put(BigInteger.class, new BigInteger("0"));
263 primitiveDefaults.put(BigDecimal.class, new BigDecimal(0.0));
264 }
265
266 /**
267 Gets the "target" class of an object for looking up accessors that
268 are registered on the target. If the object is a Class object this
269 will return the Class itself, else it will return object's getClass()
270 result.
271 */
272 public static Class getTargetClass(Object o)
273 {
274 return (o == null) ? null : ((o instanceof Class) ? (Class)o : o.getClass());
275 }
276
277 /**
278 Returns the base name (the class name without the
279 package name prepended) of the object given.
280 */
281 public static String getBaseName(Object o)
282 {
283 return (o == null) ? null : getClassBaseName(o.getClass());
284 }
285
286 /**
287 Returns the base name (the class name without the
288 package name prepended) of the class given.
289 */
290 public static String getClassBaseName(Class c)
291 {
292 String s = c.getName();
293
294 return s.substring(s.lastIndexOf('.') + 1);
295 }
296
297 public static String getClassName(Object o, boolean fullyQualified)
298 {
299 if (!(o instanceof Class)) {
300 o = o.getClass();
301 }
302 return getClassName((Class)o, fullyQualified);
303 }
304
305 public static String getClassName(Class c, boolean fullyQualified)
306 {
307 return fullyQualified ? c.getName() : getClassBaseName(c);
308 }
309
310 /**
311 Returns the package name of the object's class.
312 */
313 public static String getPackageName(Object o)
314 {
315 return (o == null) ? null : getClassPackageName(o.getClass());
316 }
317
318 /**
319 Returns the package name of the class given.
320 */
321 public static String getClassPackageName(Class c)
322 {
323 String s = c.getName();
324 int i = s.lastIndexOf('.');
325
326 return (i < 0) ? null : s.substring(0, i);
327 }
328
329 /**
330 Returns a "pointer" string in the usual format for these
331 things - 0x<hex digits>.
332 */
333 public static String getPointerString(int num)
334 {
335 StringBuffer result = new StringBuffer();
336 String hex = Integer.toHexString(num),
337 pad;
338 Integer l = new Integer(hex.length());
339
340 //result.append(HEX_PREFIX);
341 if ((pad = (String)HEX_PADDING.get(l)) == null) {
342 StringBuffer pb = new StringBuffer();
343
344 for (int i = hex.length(); i < HEX_LENGTH; i++) {
345 pb.append('0');
346 }
347 pad = new String(pb);
348 HEX_PADDING.put(l, pad);
349 }
350 result.append(pad);
351 result.append(hex);
352 return new String(result);
353 }
354
355 /**
356 Returns a "pointer" string in the usual format for these
357 things - 0x<hex digits> for the object given. This will
358 always return a unique value for each object.
359 */
360 public static String getPointerString(Object o)
361 {
362 return getPointerString((o == null) ? 0 : System.identityHashCode(o));
363 }
364
365 /**
366 Returns a unique descriptor string that includes the object's
367 class and a unique integer identifier. If fullyQualified is
368 true then the class name will be fully qualified to include
369 the package name, else it will be just the class' base name.
370 */
371 public static String getUniqueDescriptor(Object object, boolean fullyQualified)
372 {
373 StringBuffer result = new StringBuffer();
374
375 if (object != null) {
376 if (object instanceof Proxy) {
377 Class interfaceClass = object.getClass().getInterfaces()[0];
378
379 result.append(getClassName(interfaceClass, fullyQualified));
380 result.append('^');
381 object = Proxy.getInvocationHandler(object);
382 }
383 result.append(getClassName(object, fullyQualified));
384 result.append('@');
385 result.append(getPointerString(object));
386 } else {
387 result.append(NULL_OBJECT_STRING);
388 }
389 return new String(result);
390 }
391
392 /**
393 Returns a unique descriptor string that includes the object's
394 class' base name and a unique integer identifier.
395 */
396 public static String getUniqueDescriptor(Object object)
397 {
398 return getUniqueDescriptor(object, false);
399 }
400
401 /**
402 Utility to convert a List into an Object[] array. If the list is zero
403 elements this will return a constant array; toArray() on List always
404 returns a new object and this is wasteful for our purposes.
405 */
406 public static Object[] toArray(List list)
407 {
408 Object[] result;
409 int size = list.size();
410
411 if (size == 0) {
412 result = NoArguments;
413 } else {
414 result = getObjectArrayPool().create(list.size());
415 for (int i = 0; i < size; i++) {
416 result[i] = list.get(i);
417 }
418 }
419 return result;
420 }
421
422 /**
423 Returns the parameter types of the given method.
424 */
425 public static Class[] getParameterTypes(Method m)
426 {
427 synchronized(methodParameterTypesCache) {
428 Class[] result;
429
430 if ((result = (Class[])methodParameterTypesCache.get(m)) == null) {
431 methodParameterTypesCache.put(m, result = m.getParameterTypes());
432 }
433 return result;
434 }
435 }
436
437 /**
438 Returns the parameter types of the given method.
439 */
440 public static Class[] getParameterTypes(Constructor c)
441 {
442 synchronized(ctorParameterTypesCache) {
443 Class[] result;
444
445 if ((result = (Class[])ctorParameterTypesCache.get(c)) == null) {
446 ctorParameterTypesCache.put(c, result = c.getParameterTypes());
447 }
448 return result;
449 }
450 }
451
452 /**
453 Permission will be named "invoke.<declaring-class>.<method-name>".
454 */
455 public static Permission getPermission(Method method)
456 {
457 Permission result = null;
458 Class mc = method.getDeclaringClass();
459
460 synchronized(invokePermissionCache) {
461 Map permissions = (Map)invokePermissionCache.get(mc);
462
463 if (permissions == null) {
464 invokePermissionCache.put(mc, permissions = new HashMap(101));
465 }
466 if ((result = (Permission)permissions.get(method.getName())) == null) {
467 result = new OgnlInvokePermission("invoke." + mc.getName() + "." + method.getName());
468 permissions.put(method.getName(), result);
469 }
470 }
471 return result;
472 }
473
474 public static Object invokeMethod( Object target, Method method, Object[] argsArray ) throws InvocationTargetException, IllegalAccessException
475 {
476 Object result;
477 boolean wasAccessible = true;
478
479 if (securityManager != null) {
480 try {
481 securityManager.checkPermission(getPermission(method));
482 } catch (SecurityException ex) {
483 throw new IllegalAccessException("Method [" + method + "] cannot be accessed.");
484 }
485 }
486 if (!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
487 if (!(wasAccessible = ((AccessibleObject)method).isAccessible())) {
488 ((AccessibleObject)method).setAccessible(true);
489 }
490 }
491 result = method.invoke( target, argsArray );
492 if (!wasAccessible) {
493 ((AccessibleObject)method).setAccessible(false);
494 }
495 return result;
496 }
497
498 /**
499 * Gets the class for a method argument that is appropriate for looking up methods
500 * by reflection, by looking for the standard primitive wrapper classes and
501 * exchanging for them their underlying primitive class objects. Other classes are
502 * passed through unchanged.
503 *
504 * @param arg an object that is being passed to a method
505 * @return the class to use to look up the method
506 */
507 public static final Class getArgClass( Object arg )
508 {
509 if ( arg == null )
510 return null;
511 Class c = arg.getClass();
512 if ( c == Boolean.class )
513 return Boolean.TYPE;
514 else if ( c.getSuperclass() == Number.class ) {
515 if ( c == Integer.class )
516 return Integer.TYPE;
517 if ( c == Double.class )
518 return Double.TYPE;
519 if ( c == Byte.class )
520 return Byte.TYPE;
521 if ( c == Long.class )
522 return Long.TYPE;
523 if ( c == Float.class )
524 return Float.TYPE;
525 if ( c == Short.class )
526 return Short.TYPE;
527 }
528 else if ( c == Character.class )
529 return Character.TYPE;
530 return c;
531 }
532
533 /**
534 * Tells whether the given object is compatible with the given class
535 * ---that is, whether the given object can be passed as an argument
536 * to a method or constructor whose parameter type is the given class.
537 * If object is null this will return true because null is compatible
538 * with any type.
539 */
540 public static final boolean isTypeCompatible( Object object, Class c )
541 {
542 boolean result = true;
543
544 if ( object != null ) {
545 if ( c.isPrimitive() ) {
546 if ( getArgClass(object) != c ) {
547 result = false;
548 }
549 } else if ( !c.isInstance(object) ) {
550 result = false;
551 }
552 }
553 return result;
554 }
555
556 /**
557 * Tells whether the given array of objects is compatible with the given array of
558 * classes---that is, whether the given array of objects can be passed as arguments
559 * to a method or constructor whose parameter types are the given array of classes.
560 */
561 public static final boolean areArgsCompatible( Object[] args, Class[] classes )
562 {
563 boolean result = true;
564
565 if ( args.length != classes.length ) {
566 result = false;
567 } else {
568 for ( int index=0, count=args.length; result && (index < count); ++index ) {
569 result = isTypeCompatible(args[index], classes[index]);
570 }
571 }
572 return result;
573 }
574
575 /**
576 * Tells whether the first array of classes is more specific than the second.
577 * Assumes that the two arrays are of the same length.
578 */
579 public static final boolean isMoreSpecific( Class[] classes1, Class[] classes2 )
580 {
581 for ( int index=0, count=classes1.length; index < count; ++index )
582 {
583 Class c1 = classes1[index], c2 = classes2[index];
584 if ( c1 == c2 )
585 continue;
586 else if ( c1.isPrimitive() )
587 return true;
588 else if ( c1.isAssignableFrom(c2) )
589 return false;
590 else if ( c2.isAssignableFrom(c1) )
591 return true;
592 }
593
594 // They are the same! So the first is not more specific than the second.
595 return false;
596 }
597
598 public static final String getModifierString(int modifiers)
599 {
600 String result;
601
602 if (Modifier.isPublic(modifiers))
603 result = "public";
604 else
605 if (Modifier.isProtected(modifiers))
606 result = "protected";
607 else
608 if (Modifier.isPrivate(modifiers))
609 result = "private";
610 else
611 result = "";
612 if (Modifier.isStatic(modifiers))
613 result = "static " + result;
614 if (Modifier.isFinal(modifiers))
615 result = "final " + result;
616 if (Modifier.isNative(modifiers))
617 result = "native " + result;
618 if (Modifier.isSynchronized(modifiers))
619 result = "synchronized " + result;
620 if (Modifier.isTransient(modifiers))
621 result = "transient " + result;
622 return result;
623 }
624
625 public static final Class classForName( OgnlContext context, String className ) throws ClassNotFoundException
626 {
627 Class result = (Class)primitiveTypes.get(className);
628
629 if (result == null) {
630 ClassResolver resolver;
631
632 if ((context == null) || ((resolver = context.getClassResolver()) == null)) {
633 resolver = OgnlContext.DEFAULT_CLASS_RESOLVER;
634 }
635 result = resolver.classForName(className, context);
636 }
637 return result;
638 }
639
640 public static final boolean isInstance( OgnlContext context, Object value, String className ) throws OgnlException
641 {
642 try
643 {
644 Class c = classForName( context, className);
645 return c.isInstance( value );
646 }
647 catch (ClassNotFoundException e)
648 {
649 throw new OgnlException( "No such class: " + className, e );
650 }
651 }
652
653 public static Object getPrimitiveDefaultValue( Class forClass )
654 {
655 return primitiveDefaults.get(forClass);
656 }
657
658 public static Object getConvertedType( OgnlContext context, Object target, Member member, String propertyName, Object value, Class type)
659 {
660 return context.getTypeConverter().convertValue(context, target, member, propertyName, value, type);
661 }
662
663 public static boolean getConvertedTypes( OgnlContext context, Object target, Member member, String propertyName, Class[] parameterTypes, Object[] args, Object[] newArgs)
664 {
665 boolean result = false;
666
667 if (parameterTypes.length == args.length) {
668 result = true;
669 for (int i = 0, ilast = parameterTypes.length - 1; result && (i <= ilast); i++) {
670 Object arg = args[i];
671 Class type = parameterTypes[i];
672
673 if (isTypeCompatible(arg, type)) {
674 newArgs[i] = arg;
675 } else {
676 Object v = getConvertedType(context, target, member, propertyName, arg, type);
677
678 if (v == OgnlRuntime.NoConversionPossible) {
679 result = false;
680 } else {
681 newArgs[i] = v;
682 }
683 }
684 }
685 }
686 return result;
687 }
688
689 public static Method getConvertedMethodAndArgs( OgnlContext context, Object target, String propertyName, List methods, Object[] args, Object[] newArgs)
690 {
691 Method result = null;
692 TypeConverter converter = context.getTypeConverter();
693
694 if ((converter != null) && (methods != null)) {
695 for (int i = 0, icount = methods.size(); (result == null) && (i < icount); i++) {
696 Method m = (Method)methods.get(i);
697 Class[] parameterTypes = getParameterTypes(m);
698
699 if (getConvertedTypes( context, target, m, propertyName, parameterTypes, args, newArgs )) {
700 result = m;
701 }
702 }
703 }
704 return result;
705 }
706
707 public static Constructor getConvertedConstructorAndArgs( OgnlContext context, Object target, List constructors, Object[] args, Object[] newArgs )
708 {
709 Constructor result = null;
710 TypeConverter converter = context.getTypeConverter();
711
712 if ((converter != null) && (constructors != null)) {
713 for (int i = 0, icount = constructors.size(); (result == null) && (i < icount); i++) {
714 Constructor ctor = (Constructor)constructors.get(i);
715 Class[] parameterTypes = getParameterTypes(ctor);
716
717 if (getConvertedTypes( context, target, ctor, null, parameterTypes, args, newArgs )) {
718 result = ctor;
719 }
720 }
721 }
722 return result;
723 }
724
725 /**
726 Gets the appropriate method to be called for the given target, method name and arguments.
727 If successful this method will return the Method within the target that can be called
728 and the converted arguments in actualArgs. If unsuccessful this method will return
729 null and the actualArgs will be empty.
730 */
731 public static Method getAppropriateMethod( OgnlContext context, Object source, Object target, String methodName, String propertyName, List methods, Object[] args, Object[] actualArgs )
732 {
733 Method result = null;
734 Class[] resultParameterTypes = null;
735
736 if (methods != null) {
737 for (int i = 0, icount = methods.size(); i < icount; i++) {
738 Method m = (Method)methods.get(i);
739 Class[] mParameterTypes = getParameterTypes(m);
740
741 if ( areArgsCompatible(args, mParameterTypes) && ((result == null) || isMoreSpecific(mParameterTypes, resultParameterTypes)) ) {
742 result = m;
743 resultParameterTypes = mParameterTypes;
744 System.arraycopy(args, 0, actualArgs, 0, args.length);
745 for (int j = 0; j < mParameterTypes.length; j++) {
746 Class type = mParameterTypes[j];
747
748 if (type.isPrimitive() && (actualArgs[j] == null)) {
749 actualArgs[j] = getConvertedType(context, source, result, propertyName, null, type);
750 }
751 }
752 }
753 }
754 }
755 if ( result == null ) {
756 result = getConvertedMethodAndArgs( context, target, propertyName, methods, args, actualArgs );
757 }
758 return result;
759 }
760
761 public static Object callAppropriateMethod( OgnlContext context, Object source, Object target, String methodName, String propertyName, List methods, Object[] args ) throws MethodFailedException
762 {
763 Throwable reason = null;
764 Object[] actualArgs = objectArrayPool.create(args.length);
765
766 try {
767 Method method = getAppropriateMethod( context, source, target, methodName, propertyName, methods, args, actualArgs );
768
769 if ( (method == null) || !isMethodAccessible(context, source, method, propertyName) )
770 {
771 StringBuffer buffer = new StringBuffer();
772
773 if (args != null) {
774 for (int i = 0, ilast = args.length - 1; i <= ilast; i++) {
775 Object arg = args[i];
776
777 buffer.append((arg == null) ? NULL_STRING : arg.getClass().getName());
778 if (i < ilast) {
779 buffer.append(", ");
780 }
781 }
782 }
783 throw new NoSuchMethodException( methodName + "(" + buffer + ")" );
784 }
785 return invokeMethod(target, method, actualArgs);
786 }
787 catch (NoSuchMethodException e)
788 { reason = e; }
789 catch (IllegalAccessException e)
790 { reason = e; }
791 catch (InvocationTargetException e)
792 { reason = e.getTargetException(); }
793 finally {
794 objectArrayPool.recycle(actualArgs);
795 }
796 throw new MethodFailedException( source, methodName, reason );
797 }
798
799 public static final Object callStaticMethod( OgnlContext context, String className, String methodName, Object[] args ) throws OgnlException, MethodFailedException
800 {
801 try {
802 Object result;
803 Class targetClass = classForName(context, className);
804 MethodAccessor ma = getMethodAccessor(targetClass);
805
806 return ma.callStaticMethod(context, targetClass, methodName, args);
807 } catch (ClassNotFoundException ex) {
808 throw new MethodFailedException(className, methodName, ex);
809 }
810 }
811
812 public static final Object callMethod( OgnlContext context, Object target, String methodName, String propertyName, Object[] args ) throws OgnlException, MethodFailedException
813 {
814 Object result;
815
816 if (target != null) {
817 MethodAccessor ma = getMethodAccessor(target.getClass());
818
819 result = ma.callMethod(context, target, methodName, args);
820 } else {
821 throw new NullPointerException("target is null for method " + methodName);
822 }
823 return result;
824 }
825
826 public static final Object callConstructor( OgnlContext context, String className, Object[] args ) throws OgnlException
827 {
828 Throwable reason = null;
829 Object[] actualArgs = args;
830
831 try
832 {
833 Constructor ctor = null;
834 Class[] ctorParameterTypes = null;
835 Class target = classForName(context, className);
836 List constructors = getConstructors(target);
837
838 for (int i = 0, icount = constructors.size(); i < icount; i++)
839 {
840 Constructor c = (Constructor)constructors.get(i);
841 Class[] cParameterTypes = getParameterTypes(c);
842
843 if ( areArgsCompatible(args, cParameterTypes) && (ctor == null || isMoreSpecific(cParameterTypes, ctorParameterTypes)) ) {
844 ctor = c;
845 ctorParameterTypes = cParameterTypes;
846 }
847 }
848 if ( ctor == null )
849 {
850 actualArgs = objectArrayPool.create(args.length);
851 if ((ctor = getConvertedConstructorAndArgs( context, target, constructors, args, actualArgs )) == null) {
852 throw new NoSuchMethodException();
853 }
854 }
855 if (!context.getMemberAccess().isAccessible(context, target, ctor, null)) {
856 throw new IllegalAccessException("access denied to " + target.getName() + "()");
857 }
858 return ctor.newInstance( actualArgs );
859 }
860 catch (ClassNotFoundException e)
861 { reason = e; }
862 catch (NoSuchMethodException e)
863 { reason = e; }
864 catch (IllegalAccessException e)
865 { reason = e; }
866 catch (InvocationTargetException e)
867 { reason = e.getTargetException(); }
868 catch (InstantiationException e)
869 { reason = e; }
870 finally {
871 if (actualArgs != args) {
872 objectArrayPool.recycle(actualArgs);
873 }
874 }
875
876 throw new MethodFailedException( className, "new", reason );
877 }
878
879 public static final Object getMethodValue(OgnlContext context, Object target, String propertyName) throws OgnlException, IllegalAccessException, NoSuchMethodException, IntrospectionException
880 {
881 return getMethodValue(context, target, propertyName, false);
882 }
883
884 /**
885 If the checkAccessAndExistence flag is true this method will check to see if the
886 method exists and if it is accessible according to the context's MemberAccess.
887 If neither test passes this will return NotFound.
888 */
889 public static final Object getMethodValue(OgnlContext context, Object target, String propertyName, boolean checkAccessAndExistence) throws OgnlException, IllegalAccessException, NoSuchMethodException, IntrospectionException
890 {
891 Object result = null;
892 Method m = getGetMethod(context, (target == null) ? null : target.getClass(), propertyName);
893
894 if (checkAccessAndExistence) {
895 if ((m == null) || !context.getMemberAccess().isAccessible(context, target, m, propertyName)) {
896 result = NotFound;
897 }
898 }
899 if (result == null) {
900 if (m != null)
901 {
902 try
903 {
904 result = invokeMethod(target, m, NoArguments);
905 }
906 catch (InvocationTargetException ex)
907 {
908 throw new OgnlException(propertyName, ex.getTargetException());
909 }
910 } else {
911 throw new NoSuchMethodException(propertyName);
912 }
913 }
914 return result;
915 }
916
917 public static final boolean setMethodValue(OgnlContext context, Object target, String propertyName, Object value) throws OgnlException, IllegalAccessException, NoSuchMethodException, MethodFailedException, IntrospectionException
918 {
919 return setMethodValue(context, target, propertyName, value, false);
920 }
921
922 public static final boolean setMethodValue(OgnlContext context, Object target, String propertyName, Object value, boolean checkAccessAndExistence) throws OgnlException, IllegalAccessException, NoSuchMethodException, MethodFailedException, IntrospectionException
923 {
924 boolean result = true;
925 Method m = getSetMethod(context, (target == null) ? null : target.getClass(), propertyName);
926
927 if (checkAccessAndExistence) {
928 if ((m == null) || !context.getMemberAccess().isAccessible(context, target, m, propertyName)) {
929 result = false;
930 }
931 }
932 if (result) {
933 if (m != null) {
934 Object[] args = objectArrayPool.create(value);
935
936 try {
937 callAppropriateMethod(context, target, target, m.getName(), propertyName, Collections.nCopies(1, m), args);
938 } finally {
939 objectArrayPool.recycle(args);
940 }
941 } else {
942 result = false;
943 }
944 }
945 return result;
946 }
947
948 public static final List getConstructors(Class targetClass)
949 {
950 List result;
951
952 synchronized(constructorCache) {
953 if ((result = (List)constructorCache.get(targetClass)) == null) {
954 constructorCache.put(targetClass, result = Arrays.asList(targetClass.getConstructors()));
955 }
956 }
957 return result;
958 }
959
960 public static final Map getMethods( Class targetClass, boolean staticMethods )
961 {
962 ClassCache cache = (staticMethods ? staticMethodCache : instanceMethodCache);
963 Map result;
964
965 synchronized(cache) {
966 if ((result = (Map)cache.get(targetClass)) == null)
967 {
968 cache.put(targetClass, result = new HashMap(23));
969 for (Class c = targetClass; c != null; c = c.getSuperclass()) {
970 Method[] ma = c.getDeclaredMethods();
971
972 for (int i = 0, icount = ma.length; i < icount; i++)
973 {
974 if (Modifier.isStatic(ma[i].getModifiers()) == staticMethods) {
975 List ml = (List)result.get(ma[i].getName());
976
977 if (ml == null)
978 result.put(ma[i].getName(), ml = new ArrayList());
979 ml.add(ma[i]);
980 }
981 }
982 }
983 }
984 }
985 return result;
986 }
987
988 public static final List getMethods( Class targetClass, String name, boolean staticMethods )
989 {
990 return (List)getMethods(targetClass, staticMethods).get(name);
991 }
992
993 public static final Map getFields(Class targetClass)
994 {
995 Map result;
996
997 synchronized(fieldCache) {
998 if ((result = (Map)fieldCache.get(targetClass)) == null)
999 {
1000 Field fa[];
1001
1002 result = new HashMap(23);
1003 fa = targetClass.getDeclaredFields();
1004 for (int i = 0; i < fa.length; i++) {
1005 result.put(fa[i].getName(), fa[i]);
1006 }
1007 fieldCache.put(targetClass, result);
1008 }
1009 }
1010 return result;
1011 }
1012
1013 public static final Field getField(Class inClass, String name)
1014 {
1015 Field result = null;
1016
1017 synchronized(fieldCache)
1018 {
1019 Object o = getFields(inClass).get(name);
1020
1021 if (o == null)
1022 {
1023 superclasses.clear();
1024 for (Class sc = inClass; (sc != null) && (result == null); sc = sc.getSuperclass())
1025 {
1026 if ((o = getFields(sc).get(name)) == NotFound)
1027 break;
1028 superclasses.add(sc);
1029 if ((result = (Field)o) != null)
1030 break;
1031 }
1032 /*
1033 Bubble the found value (either cache miss or actual field)
1034 to all supeclasses that we saw for quicker access next time.
1035 */
1036 for (int i = 0, icount = superclasses.size(); i < icount; i++)
1037 {
1038 getFields((Class)superclasses.get(i)).put(name, (result == null) ? NotFound : result);
1039 }
1040 }
1041 else
1042 {
1043 if (o instanceof Field)
1044 {
1045 result = (Field)o;
1046 }
1047 else
1048 {
1049 if (result == NotFound)
1050 result = null;
1051 }
1052 }
1053 }
1054 return result;
1055 }
1056
1057 public static final Object getFieldValue(OgnlContext context, Object target, String propertyName) throws NoSuchFieldException
1058 {
1059 return getFieldValue(context, target, propertyName, false);
1060 }
1061
1062 public static final Object getFieldValue(OgnlContext context, Object target, String propertyName, boolean checkAccessAndExistence) throws NoSuchFieldException
1063 {
1064 Object result = null;
1065 Field f = getField((target == null) ? null : target.getClass(), propertyName);
1066
1067 if (checkAccessAndExistence) {
1068 if ((f == null) || !context.getMemberAccess().isAccessible(context, target, f, propertyName)) {
1069 result = NotFound;
1070 }
1071 }
1072 if (result == null) {
1073 if (f == null) {
1074 throw new NoSuchFieldException(propertyName);
1075 } else {
1076 try
1077 {
1078 Object state = null;
1079
1080 if ((f != null) && !Modifier.isStatic(f.getModifiers()))
1081 {
1082 state = context.getMemberAccess().setup(context, target, f, propertyName);
1083 result = f.get(target);
1084 context.getMemberAccess().restore(context, target, f, propertyName, state);
1085 }
1086 else
1087 throw new NoSuchFieldException(propertyName);
1088 }
1089 catch (IllegalAccessException ex)
1090 {
1091 throw new NoSuchFieldException(propertyName);
1092 }
1093 }
1094 }
1095 return result;
1096 }
1097
1098 public static final boolean setFieldValue(OgnlContext context, Object target, String propertyName, Object value) throws OgnlException
1099 {
1100 boolean result = false;
1101