Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||||||
Introspector |
|
| 6.7368421052631575;6.737 |
1 | /* |
|
2 | Wotonomy: OpenStep design patterns for pure Java applications. |
|
3 | Copyright (C) 2000 Michael Powers |
|
4 | ||
5 | This library is free software; you can redistribute it and/or |
|
6 | modify it under the terms of the GNU Lesser General Public |
|
7 | License as published by the Free Software Foundation; either |
|
8 | version 2.1 of the License, or (at your option) any later version. |
|
9 | ||
10 | This library is distributed in the hope that it will be useful, |
|
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
13 | Lesser General Public License for more details. |
|
14 | ||
15 | You should have received a copy of the GNU Lesser General Public |
|
16 | License along with this library; if not, see http://www.gnu.org |
|
17 | */ |
|
18 | ||
19 | package net.wotonomy.foundation.internal; |
|
20 | ||
21 | import java.lang.reflect.Field; |
|
22 | import java.lang.reflect.InvocationTargetException; |
|
23 | import java.lang.reflect.Method; |
|
24 | import java.util.ArrayList; |
|
25 | import java.util.HashMap; |
|
26 | import java.util.Hashtable; |
|
27 | import java.util.Iterator; |
|
28 | import java.util.List; |
|
29 | import java.util.Map; |
|
30 | import java.util.Set; |
|
31 | ||
32 | /** |
|
33 | * This Introspector is a static utility class written to work |
|
34 | * around limitations in PropertyDescriptor and Introspector.<br><br> |
|
35 | * |
|
36 | * Of particular note are the get() and set() methods, which will attempt |
|
37 | * to get and set artibrary values on arbitrary objects to the best of its |
|
38 | * ability, converting values as appropriate. Properties of the form |
|
39 | * "property.nestedproperty.anotherproperty" are supported to get and set |
|
40 | * values on property values directly.<br><br> |
|
41 | * |
|
42 | * Note that for naming getter methods, this class supports "get", "is", |
|
43 | * and also the property name itself, which supports NeXT-style properties. |
|
44 | * Introspector supports Maps by treating the keys a property names, |
|
45 | * supports Lists by treating the indexes as property names. <br><br> |
|
46 | * |
|
47 | * Numeric and boolean types can be inverted by prepending a "!" before |
|
48 | * the name of the property, like "manager.!active" or "task.!lag". |
|
49 | * |
|
50 | * @author michael@mpowers.net |
|
51 | * @author $Author: cgruber $ |
|
52 | * @version $Revision: 893 $ |
|
53 | */ |
|
54 | ||
55 | 0 | public class Introspector |
56 | { |
|
57 | // allows "hasProperty" or "property" forms |
|
58 | 0 | public static boolean strict = false; |
59 | ||
60 | // print exception stack traces |
|
61 | 0 | private static boolean debug = true; |
62 | ||
63 | // path separator |
|
64 | public static final String SEPARATOR = "."; |
|
65 | ||
66 | // method cache - use hashtables for thread safety |
|
67 | 0 | private static Map getterMethods = new Hashtable(); |
68 | 0 | private static Map setterMethods = new Hashtable(); |
69 | ||
70 | // wildcard value - using this class to represent a "wildcard" generic class. |
|
71 | // we have to do this when matching methods by parameter types and a |
|
72 | // null value is passed in - can't tell what class the null should be. |
|
73 | 0 | public static Class WILD = Introspector.class; |
74 | ||
75 | // empty class array - prevents having to create one every time |
|
76 | 0 | private static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; |
77 | ||
78 | // use OGNL for property access |
|
79 | private static boolean useOGNL; |
|
80 | ||
81 | static |
|
82 | { |
|
83 | try |
|
84 | { |
|
85 | 0 | useOGNL = ( Class.forName( "ognl.Ognl" ) != null ); |
86 | } |
|
87 | 0 | catch ( ClassNotFoundException t ) |
88 | { |
|
89 | 0 | useOGNL = false; |
90 | 0 | } |
91 | } |
|
92 | ||
93 | /** |
|
94 | * Utility method to get the read method for a property belonging to a class. |
|
95 | * Will search for methods in the form of "getProperty" and failing that |
|
96 | * "isProperty" (to handle booleans). |
|
97 | * @param objectClass the class whose property methods will be retrieved. |
|
98 | * @param aProperty The property whose method will be retrieved. |
|
99 | * @param paramTypes An array of class objects representing the types of parameters. |
|
100 | * @return The appropriate method for the class, or null if not found. |
|
101 | */ |
|
102 | static public Method getPropertyReadMethod( |
|
103 | Class objectClass, String aProperty, Class[] paramTypes ) |
|
104 | { |
|
105 | 0 | Method result = null; |
106 | ||
107 | 0 | result = getMethodFromClass( objectClass, aProperty, paramTypes, true ); |
108 | ||
109 | 0 | return result; |
110 | } |
|
111 | ||
112 | /** |
|
113 | * Utility method to get the write method for a property belonging to a class. |
|
114 | * Will search for methods in the form of "setProperty". |
|
115 | * @param objectClass the class whose property methods will be retrieved. |
|
116 | * @param aProperty The property whose method will be retrieved. |
|
117 | * @param paramTypes An array of class objects representing the types of parameters. |
|
118 | * @return The appropriate method for the class, or null if not found. |
|
119 | */ |
|
120 | static public Method getPropertyWriteMethod( |
|
121 | Class objectClass, String aProperty, Class[] paramTypes ) |
|
122 | { |
|
123 | 0 | Method result = null; |
124 | ||
125 | 0 | result = getMethodFromClass( objectClass, aProperty, paramTypes, false ); |
126 | ||
127 | 0 | return result; |
128 | } |
|
129 | ||
130 | /** |
|
131 | * Gets a named method from a class. Using this method is preferred because |
|
132 | * the results are cached and should be faster than calling Class.getMethod(). |
|
133 | * Note that if an object has a "get" getter method and an "is" getter method |
|
134 | * with the same signature defined for a given property. The "get" method |
|
135 | * is called. |
|
136 | * @param objectClass the Class whose property methods will be retrieved. |
|
137 | * @param aMethodName A String containing the name of the desired method. |
|
138 | * @param paramTypes An array of class objects representing the types of parameters. |
|
139 | * @return The appropriate Method from the Class, or null if not found. |
|
140 | */ |
|
141 | static private Method getMethodFromClass( |
|
142 | Class objectClass, String aProperty, Class[] paramTypes, boolean doGetter) |
|
143 | { // System.out.print( "Introspector.getMethodFromClass: " + aMethodName + " : " ); |
|
144 | ||
145 | 0 | Map classesToMethods = (doGetter ? |
146 | 0 | getterMethods : |
147 | 0 | setterMethods); |
148 | ||
149 | 0 | Map allMethods = (Map) classesToMethods.get( objectClass ); |
150 | 0 | if (allMethods == null) |
151 | { |
|
152 | // need to build maps for this class |
|
153 | 0 | mapPropertiesForClass( objectClass ); |
154 | // now the map should exist |
|
155 | 0 | allMethods = (Map) classesToMethods.get( objectClass ); |
156 | } |
|
157 | ||
158 | 0 | Method[] methods = (Method[]) allMethods.get( aProperty ); |
159 | 0 | if ( methods == null ) |
160 | { |
|
161 | 0 | return null; // property doesn't exist |
162 | } |
|
163 | ||
164 | methods_loop: // walks through all methods for name |
|
165 | 0 | for ( int i = 0; i < methods.length; i++ ) |
166 | { |
|
167 | 0 | Class[] types = methods[i].getParameterTypes(); |
168 | ||
169 | // if parameter lengths don't match |
|
170 | 0 | if ( types.length != paramTypes.length ) |
171 | { |
|
172 | // System.out.println( aMethodName + " : " + types.length + " != " + paramTypes.length ); |
|
173 | 0 | continue methods_loop; // continue with outer loop |
174 | } |
|
175 | ||
176 | // match up each parameter |
|
177 | 0 | for ( int j = 0; j < types.length; j++ ) |
178 | { |
|
179 | // convert primitives so they'll match - ugly |
|
180 | // (would have thought isAssignableFrom() would catch this) |
|
181 | 0 | if ( types[j].isPrimitive() ) |
182 | { |
|
183 | 0 | if ( types[j] == Boolean.TYPE ) |
184 | { |
|
185 | 0 | types[j] = Boolean.class; |
186 | 0 | } |
187 | else |
|
188 | 0 | if ( types[j] == Character.TYPE ) |
189 | { |
|
190 | 0 | types[j] = Character.class; |
191 | 0 | } |
192 | else |
|
193 | 0 | if ( types[j] == Byte.TYPE ) |
194 | { |
|
195 | 0 | types[j] = Byte.class; |
196 | 0 | } |
197 | else |
|
198 | 0 | if ( types[j] == Short.TYPE ) |
199 | { |
|
200 | 0 | types[j] = Short.class; |
201 | 0 | } |
202 | else |
|
203 | 0 | if ( types[j] == Integer.TYPE ) |
204 | { |
|
205 | 0 | types[j] = Integer.class; |
206 | 0 | } |
207 | else |
|
208 | 0 | if ( types[j] == Long.TYPE ) |
209 | { |
|
210 | 0 | types[j] = Long.class; |
211 | 0 | } |
212 | else |
|
213 | 0 | if ( types[j] == Float.TYPE ) |
214 | { |
|
215 | 0 | types[j] = Float.class; |
216 | 0 | } |
217 | else |
|
218 | 0 | if ( types[j] == Double.TYPE ) |
219 | { |
|
220 | 0 | types[j] = Double.class; |
221 | } |
|
222 | } |
|
223 | ||
224 | // if parameters don't match |
|
225 | 0 | if ( ( paramTypes[j] != WILD ) && ( ! types[j].isAssignableFrom( paramTypes[j] ) ) ) |
226 | { |
|
227 | // System.out.println( "Introspector.getMethodFromClass: " + |
|
228 | // aProperty + " : " + types[j] + " != " + paramTypes[j] ); |
|
229 | 0 | continue methods_loop; // continue with outer loop |
230 | } |
|
231 | } |
|
232 | ||
233 | // all params match |
|
234 | 0 | return methods[i]; |
235 | } |
|
236 | ||
237 | // no match |
|
238 | 0 | return null; |
239 | } |
|
240 | ||
241 | static private final Method[] getAllMethodsForClass( Class aClass ) |
|
242 | { |
|
243 | 0 | Method[] local = aClass.getDeclaredMethods(); // only local |
244 | 0 | Method[] all = aClass.getMethods(); // all public |
245 | 0 | Method[] result = new Method[ local.length + all.length ]; |
246 | 0 | System.arraycopy( local, 0, result, 0, local.length ); |
247 | 0 | System.arraycopy( all, 0, result, local.length, all.length ); |
248 | 0 | return result; |
249 | } |
|
250 | ||
251 | /** |
|
252 | * Generates a map of properties to both getter or setter methods for the given class. |
|
253 | * Then assigned those maps into the appropriate getterMethods and setterMethods maps |
|
254 | * keyed by the specified class. Even on error, this method will at least place empty |
|
255 | * property maps into each of the methods maps. |
|
256 | */ |
|
257 | static private void mapPropertiesForClass( Class objectClass ) |
|
258 | { |
|
259 | try |
|
260 | { |
|
261 | 0 | Map readProperties = new HashMap(); |
262 | 0 | getterMethods.put( objectClass, readProperties ); |
263 | 0 | Map writeProperties = new HashMap(); |
264 | 0 | setterMethods.put( objectClass, writeProperties ); |
265 | ||
266 | String name, property; |
|
267 | 0 | Method[] methods = getAllMethodsForClass( objectClass ); // throws SecurityException |
268 | 0 | for ( int i = 0; i < methods.length; i++ ) |
269 | { |
|
270 | 0 | name = methods[i].getName(); |
271 | 0 | methods[i].setAccessible( true ); // throws SecurityException |
272 | 0 | if ( name.startsWith( "set" ) ) |
273 | { |
|
274 | 0 | name = name.substring( 3 ); |
275 | 0 | if ( ! "".equals( name ) ) // excludes "set()" |
276 | { |
|
277 | 0 | putMethodIntoPropertyMap( name, methods[i], writeProperties ); |
278 | 0 | } |
279 | } |
|
280 | else |
|
281 | 0 | if ( methods[i].getReturnType() != void.class ) |
282 | { |
|
283 | 0 | String fullname = name; |
284 | 0 | if ( name.startsWith( "get" ) ) |
285 | { |
|
286 | 0 | name = name.substring( 3 ); |
287 | 0 | } |
288 | else |
|
289 | 0 | if ( name.startsWith( "is" ) ) |
290 | { |
|
291 | 0 | name = name.substring( 2 ); |
292 | 0 | } |
293 | else |
|
294 | 0 | if ( name.startsWith( "has" ) && ( !strict ) ) // what about hashCode()? |
295 | { |
|
296 | 0 | name = name.substring( 3 ); |
297 | } |
|
298 | ||
299 | 0 | if ( ! "".equals( name ) && ( !strict ) ) // excludes "get()", "has()", and "is()" |
300 | { |
|
301 | 0 | putMethodIntoPropertyMap( name, methods[i], readProperties ); |
302 | 0 | if ( fullname != name ) |
303 | { // allows us to match properties that include the get/set prefix as well |
|
304 | 0 | putMethodIntoPropertyMap( fullname, methods[i], readProperties ); |
305 | } |
|
306 | } |
|
307 | } |
|
308 | } |
|
309 | } |
|
310 | 0 | catch ( SecurityException se ) |
311 | { |
|
312 | 0 | System.out.println( "Introspector.getMethodFromClass: " + se ); |
313 | // this class will show up with empty getter/setter maps |
|
314 | 0 | } |
315 | 0 | } |
316 | ||
317 | /** |
|
318 | * Places a property-method pair into one of the properties maps. |
|
319 | * This in effect maps a property to an array of methods. |
|
320 | */ |
|
321 | private static void putMethodIntoPropertyMap( String aProperty, Method aMethod, Map aMap ) |
|
322 | { |
|
323 | // ensure first character is lower case |
|
324 | 0 | StringBuffer buffer = new StringBuffer( aProperty ); |
325 | 0 | buffer.setCharAt(0, Character.toLowerCase(buffer.charAt(0))); |
326 | 0 | String key = buffer.toString(); |
327 | ||
328 | // build array of methods for property |
|
329 | 0 | Method[] result = (Method[]) aMap.get( key ); |
330 | 0 | if ( result == null ) |
331 | { |
|
332 | 0 | result = new Method[] { aMethod }; |
333 | 0 | } |
334 | else |
|
335 | { |
|
336 | // create new array that's larger by one and copy |
|
337 | int i; |
|
338 | 0 | Method[] enlarged = new Method[ result.length + 1 ]; |
339 | 0 | for ( i = 0; i < result.length; i ++ ) |
340 | { |
|
341 | 0 | enlarged[i] = result[i]; |
342 | } |
|
343 | // add the new method to end |
|
344 | 0 | enlarged[i] = aMethod; |
345 | 0 | result = enlarged; |
346 | } |
|
347 | 0 | aMap.put( key, result ); |
348 | 0 | } |
349 | ||
350 | /** |
|
351 | * Utility method to get a method for a property belonging to a class. |
|
352 | * Use this if you don't feel like making the Class array from the parameters |
|
353 | * you will be using - pass in the parameters themselves. |
|
354 | * @param objectClass the Class whose property methods will be retrieved. |
|
355 | * @param aProperty The property whose method will be retrieved. |
|
356 | * @param params An array of parameters to be used. |
|
357 | * @return The appropriate method for the class, or null if not found. |
|
358 | */ |
|
359 | static public Method getPropertyReadMethod( |
|
360 | Class objectClass, String aProperty, Object[] params ) |
|
361 | { |
|
362 | // optimization: avoid allocating class array for common case |
|
363 | 0 | if ( params.length == 0 ) |
364 | { |
|
365 | 0 | return getPropertyReadMethod( |
366 | 0 | objectClass, aProperty, EMPTY_CLASS_ARRAY ); |
367 | } |
|
368 | ||
369 | 0 | Class[] paramList = new Class[ params.length ]; |
370 | 0 | for ( int i = 0; i < params.length; i++ ) |
371 | { |
|
372 | 0 | if ( params[i] != null ) |
373 | { |
|
374 | 0 | paramList[i] = params[i].getClass(); |
375 | 0 | } |
376 | else |
|
377 | { |
|
378 | 0 | paramList[i] = WILD; |
379 | } |
|
380 | } |
|
381 | 0 | return getPropertyReadMethod( objectClass, aProperty, paramList ); |
382 | } |
|
383 | ||
384 | /** |
|
385 | * Utility method to get a method for a property belonging to a class. |
|
386 | * Use this if you don't feel like making the Class array from the parameters |
|
387 | * you will be using - pass in the parameters themselves. |
|
388 | * @param objectClass the Class whose property methods will be retrieved. |
|
389 | * @param aProperty The property whose method will be retrieved. |
|
390 | * @param params An array of parameters to be used. |
|
391 | * @return The appropriate method for the class, or null if not found. |
|
392 | */ |
|
393 | static public Method getPropertyWriteMethod( |
|
394 | Class objectClass, String aProperty, Object[] params ) |
|
395 | { |
|
396 | 0 | Class[] paramList = new Class[ params.length ]; |
397 | 0 | for ( int i = 0; i < params.length; i++ ) |
398 | { |
|
399 | 0 | if ( params[i] != null ) |
400 | { |
|
401 | 0 | paramList[i] = params[i].getClass(); |
402 | 0 | } |
403 | else |
|
404 | { |
|
405 | 0 | paramList[i] = WILD; |
406 | } |
|
407 | } |
|
408 | 0 | return getPropertyWriteMethod( objectClass, aProperty, paramList ); |
409 | } |
|
410 | ||
411 | /** |
|
412 | * Gets a list of the readable properties for the given class. |
|
413 | * Note that readable properties may not be writable - see getWriteProperties(). |
|
414 | * @return An array of property names in no particular order |
|
415 | * where each name is a string with the first character in lower case. |
|
416 | */ |
|
417 | public static String[] getReadPropertiesForClass( Class objectClass ) |
|
418 | { |
|
419 | 0 | Map properties = (Map) getterMethods.get( objectClass ); |
420 | 0 | if ( properties == null ) |
421 | { |
|
422 | // need to build maps for this class |
|
423 | 0 | mapPropertiesForClass( objectClass ); |
424 | // now the map should exist |
|
425 | 0 | properties = (Map) getterMethods.get( objectClass ); |
426 | } |
|
427 | ||
428 | // put property names into string array |
|
429 | 0 | Set keys = properties.keySet(); |
430 | 0 | Iterator it = keys.iterator(); |
431 | 0 | int len = keys.size(); |
432 | 0 | String[] result = new String[ len ]; |
433 | 0 | for ( int i = 0; i < len; i++ ) |
434 | { |
|
435 | 0 | result[i] = (String) it.next(); |
436 | } |
|
437 | 0 | return result; |
438 | } |
|
439 | ||
440 | /** |
|
441 | * Gets a list of the writable properties for the given class. |
|
442 | * Note that writable properties may not be writable - see getReadProperties(). |
|
443 | * @return An array of property names in no particular order |
|
444 | * where each name is a string with the first character in lower case. |
|
445 | */ |
|
446 | public static String[] getWritePropertiesForClass( Class objectClass ) |
|
447 | { |
|
448 | 0 | Map properties = (Map) setterMethods.get( objectClass ); |
449 | 0 | if ( properties == null ) |
450 | { |
|
451 | // need to build maps for this class |
|
452 | 0 | mapPropertiesForClass( objectClass ); |
453 | // now the map should exist |
|
454 | 0 | properties = (Map) setterMethods.get( objectClass ); |
455 | } |
|
456 | ||
457 | // put property names into string array |
|
458 | 0 | Set keys = properties.keySet(); |
459 | 0 | Iterator it = keys.iterator(); |
460 | 0 | int len = keys.size(); |
461 | 0 | String[] result = new String[ len ]; |
462 | 0 | for ( int i = 0; i < len; i++ ) |
463 | { |
|
464 | 0 | result[i] = (String) it.next(); |
465 | } |
|
466 | 0 | return result; |
467 | } |
|
468 | ||
469 | /** |
|
470 | * Gets a list of the readable properties for the given object, which may |
|
471 | * not be null. This method is more useful than getReadPropertiesForClass |
|
472 | * in that Maps will return their keys as properties and Lists will return |
|
473 | * their element indices as properties. |
|
474 | * Note that readable properties may not be writable - see getWriteProperties(). |
|
475 | * @return An array of property names in no particular order |
|
476 | * where each name is a string with the first character in lower case. |
|
477 | */ |
|
478 | public static String[] getReadPropertiesForObject( Object anObject ) |
|
479 | { |
|
480 | 0 | List properties = new ArrayList(); |
481 | 0 | String[] classProperties = |
482 | 0 | getReadPropertiesForClass( anObject.getClass() ); |
483 | 0 | if ( anObject instanceof List ) |
484 | { |
|
485 | 0 | properties.addAll( getPropertiesForList( (List) anObject ) ); |
486 | } |
|
487 | 0 | if ( anObject instanceof Map ) |
488 | { |
|
489 | 0 | properties.addAll( getPropertiesForMap( (Map) anObject ) ); |
490 | } |
|
491 | int i; |
|
492 | 0 | int len = classProperties.length + properties.size(); |
493 | 0 | String[] result = new String[ len ]; |
494 | 0 | for ( i = 0; i < classProperties.length; i++ ) |
495 | { |
|
496 | 0 | result[i] = classProperties[i]; |
497 | } |
|
498 | 0 | Iterator it = properties.iterator(); |
499 | 0 | while ( it.hasNext() ) |
500 | { |
|
501 | 0 | result[i++] = it.next().toString(); |
502 | 0 | } |
503 | 0 | return result; |
504 | } |
|
505 | ||
506 | /** |
|
507 | * Gets a list of the writable properties for the given object, which may |
|
508 | * not be null. This method is more useful than getWritePropertiesForClass |
|
509 | * in that Maps will return their keys as properties and Lists will return |
|
510 | * their element indices as properties. |
|
511 | * Note that writable properties may not be writable - see getReadProperties(). |
|
512 | * @return An array of property names in no particular order |
|
513 | * where each name is a string with the first character in lower case. |
|
514 | */ |
|
515 | public static String[] getWritePropertiesForObject( Object anObject ) |
|
516 | { |
|
517 | 0 | List properties = new ArrayList(); |
518 | 0 | String[] classProperties = |
519 | 0 | getWritePropertiesForClass( anObject.getClass() ); |
520 | 0 | if ( anObject instanceof List ) |
521 | { |
|
522 | 0 | properties.addAll( getPropertiesForList( (List) anObject ) ); |
523 | } |
|
524 | 0 | if ( anObject instanceof Map ) |
525 | { |
|
526 | 0 | properties.addAll( getPropertiesForMap( (Map) anObject ) ); |
527 | } |
|
528 | ||
529 | int i; |
|
530 | 0 | int len = classProperties.length + properties.size(); |
531 | 0 | String[] result = new String[ len ]; |
532 | 0 | for ( i = 0; i < classProperties.length; i++ ) |
533 | { |
|
534 | 0 | result[i] = classProperties[i]; |
535 | } |
|
536 | 0 | Iterator it = properties.iterator(); |
537 | 0 | while ( it.hasNext() ) |
538 | { |
|
539 | 0 | result[i++] = it.next().toString(); |
540 | 0 | } |
541 | 0 | return result; |
542 | } |
|
543 | ||
544 | private static List getPropertiesForList( List aList ) |
|
545 | { |
|
546 | 0 | List result = new ArrayList(); |
547 | 0 | int len = aList.size(); |
548 | 0 | for ( int i = 0; i < len; i++ ) |
549 | { |
|
550 | 0 | result.add( new Integer( i ).toString() ); |
551 | } |
|
552 | 0 | return result; |
553 | } |
|
554 | ||
555 | private static List getPropertiesForMap( Map aMap ) |
|
556 | { |
|
557 | 0 | List result = new ArrayList(); |
558 | 0 | Iterator it = ((Map)aMap).keySet().iterator(); |
559 | 0 | while ( it.hasNext() ) |
560 | { |
|
561 | 0 | result.add( it.next().toString() ); |
562 | 0 | } |
563 | 0 | return result; |
564 | } |
|
565 | ||
566 | 0 | private static Object[] EMPTY_ARRAY = new Object[0]; |
567 | ||
568 | /** |
|
569 | * Convenience to get a value for a property from an object. |
|
570 | * An empty property string is considered the identity property |
|
571 | * and simply returns the object. |
|
572 | * @throws MissingPropertyException if the property cannot be |
|
573 | * found on the object. |
|
574 | */ |
|
575 | public static Object getValueForObject( Object anObject, String aProperty ) |
|
576 | { |
|
577 | 0 | if ( ( aProperty == null ) || ( "".equals( aProperty ) ) ) |
578 | { |
|
579 | 0 | return anObject; |
580 | } |
|
581 | ||
582 | 0 | if ( useOGNL && aProperty.startsWith( "ognl:" ) ) |
583 | { |
|
584 | try |
|
585 | { |
|
586 | 0 | return ognl.Ognl.getValue( aProperty, anObject ); |
587 | } |
|
588 | 0 | catch ( Throwable t ) |
589 | { |
|
590 | 0 | if ( debug ) |
591 | { |
|
592 | 0 | System.err.println( |
593 | 0 | "Introspector.getValueForObject: " |
594 | 0 | + anObject + "' ( " + anObject.getClass() + " )" |
595 | 0 | + ", ognl:" + aProperty ); |
596 | 0 | System.err.println( t ); |
597 | } |
|
598 | 0 | return null; |
599 | } |
|
600 | } |
|
601 | ||
602 | 0 | boolean invert = false; |
603 | 0 | if ( aProperty.startsWith( "!" ) ) |
604 | { |
|
605 | 0 | aProperty = aProperty.substring(1); |
606 | 0 | invert = true; |
607 | } |
|
608 | ||
609 | 0 | Object result = null; |
610 | try |
|
611 | { |
|
612 | 0 | Method m = Introspector.getPropertyReadMethod( |
613 | 0 | anObject.getClass(), aProperty, EMPTY_ARRAY ); |
614 | 0 | if ( m != null ) |
615 | { |
|
616 | 0 | result = m.invoke( anObject, EMPTY_ARRAY ); |
617 | 0 | } |
618 | else // no method, try for field |
|
619 | { |
|
620 | try |
|
621 | { |
|
622 | 0 | Field field = anObject.getClass().getDeclaredField( aProperty ); |
623 | 0 | if ( field != null ) |
624 | { |
|
625 | 0 | field.setAccessible( true ); // throws SecurityException |
626 | 0 | result = field.get( anObject ); |
627 | } |
|
628 | } |
|
629 | 0 | catch ( Throwable t ) |
630 | { |
|
631 | // ignore for now |
|
632 | 0 | } |
633 | } |
|
634 | ||
635 | 0 | if ( result == null ) |
636 | { |
|
637 | 0 | if ( anObject instanceof Map ) |
638 | { |
|
639 | 0 | result = ((Map)anObject).get( aProperty ); |
640 | 0 | } |
641 | else |
|
642 | 0 | if ( anObject instanceof List ) |
643 | { |
|
644 | 0 | result = ((List)anObject).get( Integer.parseInt( aProperty ) ); |
645 | } |
|
646 | } |
|
647 | ||
648 | 0 | if ( invert ) |
649 | { |
|
650 | 0 | Object inverted = ValueConverter.invert( result ); |
651 | 0 | if ( inverted != null ) result = inverted; |
652 | } |
|
653 | //System.out.println( "getValueForObject: " + anObject + " : " + aProperty + " : " + result ); |
|
654 | 0 | return result; |
655 | } |
|
656 | 0 | catch ( Throwable exc ) |
657 | { |
|
658 | 0 | if ( exc instanceof InvocationTargetException ) |
659 | { |
|
660 | 0 | exc = ((InvocationTargetException)exc).getTargetException(); |
661 | } |
|
662 | 0 | if ( exc instanceof RuntimeException ) |
663 | { |
|
664 | 0 | throw (RuntimeException)exc; |
665 | } |
|
666 | 0 | if ( debug ) |
667 | { |
|
668 | 0 | System.out.println( |
669 | 0 | "Introspector.getValueForObject: " |
670 | 0 | + anObject + "' ( " + anObject.getClass() + " )" |
671 | 0 | + ", " + aProperty + ": " ); |
672 | } |
|
673 | 0 | throw new WotonomyException( exc ); |
674 | } |
|
675 | //! throw new MissingPropertyException(); |
|
676 | } |
|
677 | ||
678 | /** |
|
679 | * Convenience to set a value for a property from an object. |
|
680 | * Returns the return value from executing the specified method, |
|
681 | * or null if the method returns type void. |
|
682 | * @throws MissingPropertyException if the property cannot be |
|
683 | * found on the object. |
|
684 | * @throws NullPrimitiveException if the property is of primitive |
|
685 | * type and the value is null. |
|
686 | */ |
|
687 | public static Object setValueForObject( |
|
688 | Object anObject, String aProperty, Object aValue ) |
|
689 | { |
|
690 | 0 | if ( useOGNL && aProperty.startsWith( "ognl:" ) ) |
691 | { |
|
692 | try |
|
693 | { |
|
694 | 0 | ognl.Ognl.setValue( aProperty, anObject, aValue ); |
695 | } |
|
696 | 0 | catch ( Throwable t ) |
697 | { |
|
698 | 0 | if ( debug ) |
699 | { |
|
700 | 0 | System.err.println( |
701 | 0 | "Introspector.setValueForObject: " |
702 | 0 | + anObject + "' ( " + anObject.getClass() + " )" |
703 | 0 | + ", ognl:" + aProperty + " : " + aValue ); |
704 | 0 | System.err.println( t ); |
705 | } |
|
706 | 0 | } |
707 | 0 | return null; |
708 | } |
|
709 | ||
710 | try |
|
711 | { |
|
712 | 0 | if ( aProperty.startsWith( "!" ) ) |
713 | { |
|
714 | 0 | aProperty = aProperty.substring(1); |
715 | 0 | Object inverted = ValueConverter.invert( aValue ); |
716 | 0 | if ( inverted != null ) aValue = inverted; |
717 | } |
|
718 | ||
719 | 0 | Method m = null; |
720 | 0 | if ( aValue != null ) |
721 | { |
|
722 | 0 | m = Introspector.getPropertyWriteMethod( |
723 | 0 | anObject.getClass(), aProperty, new Class[] { aValue.getClass() } ); |
724 | } |
|
725 | 0 | if ( m == null ) |
726 | { |
|
727 | 0 | m = Introspector.getPropertyWriteMethod( |
728 | 0 | anObject.getClass(), aProperty, new Class[] { WILD } ); |
729 | 0 | if ( ( m != null ) && ( aValue != null ) ) |
730 | { |
|
731 | // check for null primitive |
|
732 | 0 | if ( ( aValue == null ) && |
733 | 0 | ( m.getParameterTypes()[0].isPrimitive() ) ) |
734 | { |
|
735 | 0 | throw new NullPrimitiveException(); |
736 | } |
|
737 | ||
738 | // convert if possible |
|
739 | 0 | Object o = ValueConverter.convertObjectToClass( |
740 | 0 | aValue, m.getParameterTypes()[0] ); |
741 | 0 | if ( o != null ) |
742 | { |
|
743 | 0 | aValue = o; |
744 | } |
|
745 | } |
|
746 | } |
|
747 | 0 | if ( m != null ) |
748 | { |
|
749 | 0 | return m.invoke( anObject, new Object[] { aValue } ); |
750 | } |
|
751 | else // no method, try for field |
|
752 | { |
|
753 | try |
|
754 | { |
|
755 | 0 | Field field = anObject.getClass().getDeclaredField( aProperty ); |
756 | 0 | if ( field != null ) |
757 | { |
|
758 | 0 | field.setAccessible( true ); // throws SecurityException |
759 | 0 | field.set( anObject, aValue ); |
760 | 0 | return null; |
761 | } |
|
762 | } |
|
763 | 0 | catch ( Throwable t ) |
764 | { |
|
765 | // ignore for now |
|
766 | 0 | } |
767 | } |
|
768 | ||
769 | 0 | if ( anObject instanceof Map ) |
770 | { |
|
771 | 0 | return ((Map)anObject).put( aProperty, aValue ); |
772 | } |
|
773 | 0 | if ( anObject instanceof List ) |
774 | { |
|
775 | 0 | List list = (List) anObject; |
776 | 0 | int i = Integer.parseInt( aProperty ); |
777 | 0 | if ( list.size() < i+1 ) |
778 | { |
|
779 | // expand list as necessary |
|
780 | 0 | for ( int j = list.size(); j <= i; j++ ) |
781 | { |
|
782 | 0 | list.add( new Object() ); // placeholder |
783 | } |
|
784 | } |
|
785 | 0 | return list.set( i, aValue ); |
786 | } |
|
787 | } |
|
788 | 0 | catch ( Throwable exc ) |
789 | { |
|
790 | 0 | if ( exc instanceof IllegalArgumentException ) |
791 | { |
|
792 | 0 | System.out.println( |
793 | 0 | "Introspector.setValueForObject: " |
794 | 0 | + anObject + " , " + aProperty + " , '" |
795 | 0 | + aValue + "' ):" ); |
796 | 0 | System.out.println( exc ); |
797 | 0 | } |
798 | else |
|
799 | 0 | if ( exc instanceof InvocationTargetException ) |
800 | { |
|
801 | 0 | exc = ((InvocationTargetException)exc).getTargetException(); |
802 | } |
|
803 | 0 | if ( exc instanceof RuntimeException ) |
804 | { |
|
805 | 0 | throw (RuntimeException)exc; |
806 | } |
|
807 | 0 | if ( debug ) |
808 | { |
|
809 | 0 | System.out.println( |
810 | 0 | "Introspector.setValueForObject: " |
811 | 0 | + anObject + " , " + aProperty + " , '" |
812 | 0 | + aValue + "' ):" ); |
813 | } |
|
814 | 0 | throw new WotonomyException( exc ); |
815 | 0 | } |
816 | 0 | return null; |
817 | //! throw new MissingPropertyException(); |
|
818 | } |
|
819 | ||
820 | /** |
|
821 | * Gets a value from an object or any of its child objects. |
|
822 | * This will parse the property string for "."'s and get |
|
823 | * values for each successive object's property in the path. |
|
824 | * An empty property string is considered the identity property |
|
825 | * and simply returns the object. |
|
826 | */ |
|
827 | public static Object get( Object anObject, String aProperty ) |
|
828 | { |
|
829 | 0 | int i = aProperty.indexOf( SEPARATOR ); |
830 | 0 | if ( i == -1 ) return getValueForObject( anObject, aProperty ); |
831 | ||
832 | 0 | String pathElement = aProperty.substring( 0, i ); |
833 | 0 | String remainder = aProperty.substring( i+1 ); |
834 | ||
835 | 0 | Object result = getValueForObject( anObject, pathElement ); |
836 | 0 | if ( result == null ) return null; |
837 | 0 | return get( result, remainder ); |
838 | } |
|
839 | ||
840 | /** |
|
841 | * Sets a value in an object or any of its child objects. |
|
842 | * This will parse the property string for "."'s and set |
|
843 | * values for each successive object's property in the path.<br><br> |
|
844 | * |
|
845 | * If a property is not found, this method will try to |
|
846 | * implicitly create hash maps (if possible) to fill out the path. |
|
847 | * This is useful when dealing with trees of nested maps. |
|
848 | */ |
|
849 | public static Object set( Object anObject, String aProperty, Object aValue ) |
|
850 | { |
|
851 | 0 | int i = aProperty.indexOf( SEPARATOR ); |
852 | 0 | if ( i == -1 ) return setValueForObject( anObject, aProperty, aValue ); |
853 | ||
854 | 0 | String pathElement = aProperty.substring( 0, i ); |
855 | 0 | String remainder = aProperty.substring( i+1 ); |
856 | ||
857 | 0 | Object result = getValueForObject( anObject, pathElement ); |
858 | 0 | if ( result == null ) |
859 | { |
|
860 | 0 | result = new HashMap(2); |
861 | 0 | setValueForObject( anObject, pathElement, result ); |
862 | } |
|
863 | 0 | return set( result, remainder, aValue ); |
864 | } |
|
865 | ||
866 | /** |
|
867 | * If set to true, exceptions printed to System.out.println. |
|
868 | * Defaults to true. |
|
869 | */ |
|
870 | public void setDebug( boolean isDebug ) |
|
871 | { |
|
872 | 0 | debug = isDebug; |
873 | 0 | } |
874 | } |
|
875 | ||
876 | /* |
|
877 | * $Log$ |
|
878 | * Revision 1.2 2006/02/16 13:11:47 cgruber |
|
879 | * Check in all sources in eclipse-friendly maven-enabled packages. |
|
880 | * |
|
881 | * Revision 1.19 2004/02/05 02:20:34 mpowers |
|
882 | * Added experimental ognl support (if ognl is present). |
|
883 | * |
|
884 | * Revision 1.18 2003/03/26 16:44:35 mpowers |
|
885 | * Now correctly reflecting on all methods, not just locally declared ones. |
|
886 | * |
|
887 | * Revision 1.17 2003/02/21 21:10:51 mpowers |
|
888 | * Now reaching package, protected, and private methods and fields. |
|
889 | * |
|
890 | * Revision 1.16 2003/01/28 22:11:59 mpowers |
|
891 | * Now more lenient in resolving properties starting with "is" "get" or "has". |
|
892 | * |
|
893 | * Revision 1.15 2003/01/27 15:10:54 mpowers |
|
894 | * Better handling for illegal argument exceptions. |
|
895 | * |
|
896 | * Revision 1.14 2003/01/18 23:30:42 mpowers |
|
897 | * WODisplayGroup now compiles. |
|
898 | * |
|
899 | * Revision 1.13 2002/10/11 15:35:12 mpowers |
|
900 | * Removed printlns. |
|
901 | * |
|
902 | * Revision 1.11 2001/05/02 17:58:41 mpowers |
|
903 | * Removed debugging code, added comments. |
|
904 | * |
|
905 | * Revision 1.10 2001/04/08 21:00:54 mpowers |
|
906 | * Changes to support new objectsForFetchSpecification scheme. |
|
907 | * |
|
908 | * Revision 1.9 2001/03/29 03:30:36 mpowers |
|
909 | * Refactored duplicator a bit. |
|
910 | * Disabled MissingPropertyExceptions for now. |
|
911 | * |
|
912 | * Revision 1.8 2001/03/28 17:52:45 mpowers |
|
913 | * Corrected the throws in the docs. |
|
914 | * |
|
915 | * Revision 1.7 2001/03/28 17:49:13 mpowers |
|
916 | * Better exception handling in Introspector. |
|
917 | * |
|
918 | * Revision 1.6 2001/03/13 21:40:20 mpowers |
|
919 | * Improved handling of runtime exceptions. |
|
920 | * |
|
921 | * Revision 1.5 2001/03/09 22:06:35 mpowers |
|
922 | * Now extracting the wrapped exception from InvocationTargetExceptions. |
|
923 | * |
|
924 | * Revision 1.4 2001/03/01 20:36:35 mpowers |
|
925 | * Better error handling and better handling of nulls. |
|
926 | * |
|
927 | * Revision 1.3 2001/01/17 16:20:57 mpowers |
|
928 | * Introspector now handles the identity property. |
|
929 | * |
|
930 | * Revision 1.2 2001/01/09 20:08:17 mpowers |
|
931 | * Slight optimization. |
|
932 | * |
|
933 | * Revision 1.1.1.1 2000/12/21 15:52:04 mpowers |
|
934 | * Contributing wotonomy. |
|
935 | * |
|
936 | * Revision 1.5 2000/12/20 16:25:46 michael |
|
937 | * Added log to all files. |
|
938 | * |
|
939 | * |
|
940 | */ |
|
941 |