View Javadoc

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  public class Introspector
56  {
57      // allows "hasProperty" or "property" forms
58      public static boolean strict = false; 
59      
60  	// print exception stack traces
61  	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      private static Map getterMethods = new Hashtable();
68      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      public static Class WILD = Introspector.class;
74      
75      // empty class array - prevents having to create one every time
76      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              useOGNL = ( Class.forName( "ognl.Ognl" ) != null );
86          } 
87          catch ( ClassNotFoundException t )
88          {
89              useOGNL = false;
90          }
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         Method result = null;
106         
107         result = getMethodFromClass( objectClass, aProperty, paramTypes, true );
108 
109         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         Method result = null;
124         
125         result = getMethodFromClass( objectClass, aProperty, paramTypes, false );
126 
127         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         Map classesToMethods = (doGetter ?
146                                 getterMethods :
147                                 setterMethods);
148 
149         Map allMethods = (Map) classesToMethods.get( objectClass );
150         if (allMethods == null)
151         {
152             // need to build maps for this class
153             mapPropertiesForClass( objectClass );
154             // now the map should exist
155             allMethods = (Map) classesToMethods.get( objectClass );
156         }
157 
158         Method[] methods = (Method[]) allMethods.get( aProperty );
159         if ( methods == null )
160         {
161             return null; // property doesn't exist 
162         }
163 
164         methods_loop: // walks through all methods for name
165         for ( int i = 0; i < methods.length; i++ )
166         {
167             Class[] types = methods[i].getParameterTypes();
168 
169             // if parameter lengths don't match
170             if ( types.length != paramTypes.length )
171             {
172                 // System.out.println( aMethodName + " : " + types.length + " != " + paramTypes.length );
173                 continue methods_loop; // continue with outer loop
174             }
175 
176             // match up each parameter
177             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                 if ( types[j].isPrimitive() )
182                 {
183                     if ( types[j] == Boolean.TYPE )
184                     {
185                         types[j] = Boolean.class;
186                     }
187                     else
188                     if ( types[j] == Character.TYPE )
189                     {
190                         types[j] = Character.class;
191                     }
192                     else
193                     if ( types[j] == Byte.TYPE )
194                     {
195                         types[j] = Byte.class;
196                     }
197                     else
198                     if ( types[j] == Short.TYPE )
199                     {
200                         types[j] = Short.class;
201                     }
202                     else
203                     if ( types[j] == Integer.TYPE )
204                     {
205                         types[j] = Integer.class;
206                     }
207                     else
208                     if ( types[j] == Long.TYPE )
209                     {
210                         types[j] = Long.class;
211                     }
212                     else
213                     if ( types[j] == Float.TYPE )
214                     {
215                         types[j] = Float.class;
216                     }
217                     else
218                     if ( types[j] == Double.TYPE )
219                     {
220                         types[j] = Double.class;
221                     }
222                 }
223 
224                 // if parameters don't match
225                 if ( ( paramTypes[j] != WILD ) && ( ! types[j].isAssignableFrom( paramTypes[j] ) ) )
226                 {
227 //                          System.out.println( "Introspector.getMethodFromClass: " +
228 //                              aProperty + " : " + types[j] + " != " + paramTypes[j] );
229                     continue methods_loop; // continue with outer loop
230                 }
231             }
232 
233             // all params match
234             return methods[i];
235         }
236 
237         // no match
238         return null;
239     }
240     
241     static private final Method[] getAllMethodsForClass( Class aClass )
242     {
243         Method[] local = aClass.getDeclaredMethods(); // only local
244         Method[] all = aClass.getMethods(); // all public
245         Method[] result = new Method[ local.length + all.length ];
246         System.arraycopy( local, 0, result, 0, local.length );
247         System.arraycopy( all, 0, result, local.length, all.length );
248         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             Map readProperties = new HashMap();
262             getterMethods.put( objectClass, readProperties );
263             Map writeProperties = new HashMap();
264             setterMethods.put( objectClass, writeProperties );
265 
266             String name, property;
267             Method[] methods = getAllMethodsForClass( objectClass ); // throws SecurityException
268             for ( int i = 0; i < methods.length; i++ )
269             {
270                 name = methods[i].getName();
271                 methods[i].setAccessible( true ); // throws SecurityException
272                 if ( name.startsWith( "set" ) )
273                 {
274                     name = name.substring( 3 );
275                     if ( ! "".equals( name ) ) // excludes "set()"
276                     {
277                         putMethodIntoPropertyMap( name, methods[i], writeProperties );
278                     }
279                 }
280                 else
281                 if ( methods[i].getReturnType() != void.class )
282                 {
283                     String fullname = name; 
284                     if ( name.startsWith( "get" ) )
285                     {
286                         name = name.substring( 3 );
287                     }
288                     else
289                     if ( name.startsWith( "is" ) )
290                     {
291                         name = name.substring( 2 );
292                     }
293                     else
294                     if ( name.startsWith( "has" ) && ( !strict ) ) // what about hashCode()?
295                     {
296                         name = name.substring( 3 );
297                     }
298 					
299                     if ( ! "".equals( name ) && ( !strict ) ) // excludes "get()", "has()", and "is()"
300                     {
301                         putMethodIntoPropertyMap( name, methods[i], readProperties );
302                         if ( fullname != name )
303                         { // allows us to match properties that include the get/set prefix as well
304                             putMethodIntoPropertyMap( fullname, methods[i], readProperties );
305                         }
306                     }
307                 }
308             }
309         }
310         catch ( SecurityException se )
311         {
312             System.out.println( "Introspector.getMethodFromClass: " + se );
313             // this class will show up with empty getter/setter maps
314         }
315     }
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         StringBuffer buffer = new StringBuffer( aProperty );
325         buffer.setCharAt(0, Character.toLowerCase(buffer.charAt(0)));
326         String key = buffer.toString();
327         
328         // build array of methods for property
329         Method[] result = (Method[]) aMap.get( key );
330         if ( result == null )
331         {
332             result = new Method[] { aMethod };
333         }
334         else
335         {
336             // create new array that's larger by one and copy 
337             int i;
338             Method[] enlarged = new Method[ result.length + 1 ];
339             for ( i = 0; i < result.length; i ++ )
340             {
341                 enlarged[i] = result[i];
342             }
343             // add the new method to end
344             enlarged[i] = aMethod;
345             result = enlarged;
346         }
347         aMap.put( key, result );
348     }
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         if ( params.length == 0 )
364         {
365             return getPropertyReadMethod( 
366                 objectClass, aProperty, EMPTY_CLASS_ARRAY );    
367         }
368         
369         Class[] paramList = new Class[ params.length ];
370         for ( int i = 0; i < params.length; i++ )
371         {
372             if ( params[i] != null )
373             {
374                 paramList[i] = params[i].getClass();
375             }
376             else
377             {
378                 paramList[i] = WILD;
379             }
380         }
381         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         Class[] paramList = new Class[ params.length ];
397         for ( int i = 0; i < params.length; i++ )
398         {
399             if ( params[i] != null )
400             {
401                 paramList[i] = params[i].getClass();
402             }
403             else
404             {
405                 paramList[i] = WILD;
406             }
407         }
408         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         Map properties = (Map) getterMethods.get( objectClass );
420         if ( properties == null )
421         {
422             // need to build maps for this class
423             mapPropertiesForClass( objectClass );
424             // now the map should exist
425             properties = (Map) getterMethods.get( objectClass );
426         }
427         
428         // put property names into string array
429         Set keys = properties.keySet();
430         Iterator it = keys.iterator();
431         int len = keys.size();
432         String[] result = new String[ len ];
433         for ( int i = 0; i < len; i++ )
434         {
435             result[i] = (String) it.next();
436         }
437         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         Map properties = (Map) setterMethods.get( objectClass );
449         if ( properties == null )
450         {
451             // need to build maps for this class
452             mapPropertiesForClass( objectClass );
453             // now the map should exist
454             properties = (Map) setterMethods.get( objectClass );
455         }
456         
457         // put property names into string array
458         Set keys = properties.keySet();
459         Iterator it = keys.iterator();
460         int len = keys.size();
461         String[] result = new String[ len ];
462         for ( int i = 0; i < len; i++ )
463         {
464             result[i] = (String) it.next();
465         }
466         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         List properties = new ArrayList();
481         String[] classProperties = 
482             getReadPropertiesForClass( anObject.getClass() );
483         if ( anObject instanceof List )
484         {
485             properties.addAll( getPropertiesForList( (List) anObject ) );
486         }
487         if ( anObject instanceof Map )
488         {
489             properties.addAll( getPropertiesForMap( (Map) anObject ) );
490         }
491         int i;
492         int len = classProperties.length + properties.size();
493         String[] result = new String[ len ];
494         for ( i = 0; i < classProperties.length; i++ )
495         {
496             result[i] = classProperties[i];
497         }
498         Iterator it = properties.iterator();
499         while ( it.hasNext() ) 
500         {
501             result[i++] = it.next().toString();
502         }
503         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         List properties = new ArrayList();
518         String[] classProperties = 
519             getWritePropertiesForClass( anObject.getClass() );
520         if ( anObject instanceof List )
521         {
522             properties.addAll( getPropertiesForList( (List) anObject ) );
523         }
524         if ( anObject instanceof Map )
525         {
526             properties.addAll( getPropertiesForMap( (Map) anObject ) );
527         }
528         
529         int i;
530         int len = classProperties.length + properties.size();
531         String[] result = new String[ len ];
532         for ( i = 0; i < classProperties.length; i++ )
533         {
534             result[i] = classProperties[i];
535         }
536         Iterator it = properties.iterator();
537         while ( it.hasNext() ) 
538         {
539             result[i++] = it.next().toString();
540         }
541         return result;
542     }
543     
544     private static List getPropertiesForList( List aList )
545     {
546         List result = new ArrayList();
547         int len = aList.size();
548         for ( int i = 0; i < len; i++ )
549         {
550             result.add( new Integer( i ).toString() );
551         }
552         return result;
553     }
554 
555     private static List getPropertiesForMap( Map aMap )
556     {
557         List result = new ArrayList();
558         Iterator it = ((Map)aMap).keySet().iterator();
559         while ( it.hasNext() )
560         {
561             result.add( it.next().toString() );
562         }
563         return result;
564     }
565 
566     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         if ( ( aProperty == null ) || ( "".equals( aProperty ) ) )
578         {
579             return anObject;
580         }
581         
582         if ( useOGNL && aProperty.startsWith( "ognl:" ) )
583         {
584             try
585             {
586                 return ognl.Ognl.getValue( aProperty, anObject );
587             }
588             catch ( Throwable t )
589             {
590                 if ( debug )
591                 {
592                     System.err.println(
593                         "Introspector.getValueForObject: " 
594                         + anObject + "' ( " + anObject.getClass() + " )" 
595                         + ", ognl:" + aProperty  );
596                     System.err.println( t );
597                 }                    
598                 return null;
599             }
600         }
601         
602         boolean invert = false;
603         if ( aProperty.startsWith( "!" ) )
604         {
605             aProperty = aProperty.substring(1);
606             invert = true;
607         }
608         
609         Object result = null;
610 	    try
611 	    {
612 	    	Method m = Introspector.getPropertyReadMethod(
613 		    	anObject.getClass(), aProperty, EMPTY_ARRAY );
614 		    if ( m != null )
615 		    {
616 				result = m.invoke( anObject, EMPTY_ARRAY );
617 			}
618             else // no method, try for field
619             {
620                 try
621                 {
622                     Field field = anObject.getClass().getDeclaredField( aProperty );
623                     if ( field != null )
624                     {
625                         field.setAccessible( true ); // throws SecurityException
626                         result = field.get( anObject );
627                     }
628                 }
629                 catch ( Throwable t )
630                 {
631                     // ignore for now
632                 }
633             }
634             
635             if ( result == null )
636             {
637                 if ( anObject instanceof Map )
638                 {
639                     result = ((Map)anObject).get( aProperty );
640                 }
641                 else
642                 if ( anObject instanceof List )
643                 {
644                     result = ((List)anObject).get( Integer.parseInt( aProperty ) );
645                 }
646             }
647             
648             if ( invert )
649             {
650                 Object inverted = ValueConverter.invert( result );
651                 if ( inverted != null ) result = inverted;
652             }
653             //System.out.println( "getValueForObject: " + anObject + " : " + aProperty + " : " + result );
654             return result;
655 	    } 
656 	    catch ( Throwable exc )
657 	    {
658             if ( exc instanceof InvocationTargetException )
659             {
660                 exc = ((InvocationTargetException)exc).getTargetException();   
661             }
662             if ( exc instanceof RuntimeException )
663             {
664                 throw (RuntimeException)exc;   
665             }
666 			if ( debug )
667 			{
668 				System.out.println(
669 					"Introspector.getValueForObject: " 
670 					+ anObject + "' ( " + anObject.getClass() + " )" 
671 					+ ", " + aProperty + ": " );
672             }                    
673             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         if ( useOGNL && aProperty.startsWith( "ognl:" ) )
691         {
692             try
693             {
694                 ognl.Ognl.setValue( aProperty, anObject, aValue );
695             }
696             catch ( Throwable t )
697             {
698                 if ( debug )
699                 {
700                     System.err.println(
701                         "Introspector.setValueForObject: " 
702                         + anObject + "' ( " + anObject.getClass() + " )" 
703                         + ", ognl:" + aProperty + " : " + aValue );
704                     System.err.println( t );
705                 }                    
706             }
707             return null;
708         }
709         
710 	    try
711 	    {
712             if ( aProperty.startsWith( "!" ) )
713             {
714                 aProperty = aProperty.substring(1);
715                 Object inverted = ValueConverter.invert( aValue );
716                 if ( inverted != null ) aValue = inverted;
717             }
718         
719             Method m = null;
720             if ( aValue != null )
721             {
722                 m = Introspector.getPropertyWriteMethod(
723                     anObject.getClass(), aProperty, new Class[] { aValue.getClass() } );
724             }
725             if ( m == null )
726             {
727                 m = Introspector.getPropertyWriteMethod(
728                     anObject.getClass(), aProperty, new Class[] { WILD } );
729                 if ( ( m != null ) && ( aValue != null ) )
730                 {
731                     // check for null primitive
732                     if ( ( aValue == null ) && 
733                     ( m.getParameterTypes()[0].isPrimitive() ) )
734                     {
735                         throw new NullPrimitiveException();
736                     }
737                     
738                     // convert if possible
739                     Object o = ValueConverter.convertObjectToClass( 
740                         aValue, m.getParameterTypes()[0] );
741                     if ( o != null )
742                     {
743                         aValue = o;
744                     }
745                 }
746             }
747 		    if ( m != null )
748 		    {
749 				return m.invoke( anObject, new Object[] { aValue } );
750 			}
751             else // no method, try for field
752             {
753                 try
754                 {
755                     Field field = anObject.getClass().getDeclaredField( aProperty );
756                     if ( field != null )
757                     {
758                         field.setAccessible( true ); // throws SecurityException
759                         field.set( anObject, aValue );
760                         return null;
761                     }
762                 }
763                 catch ( Throwable t )
764                 {
765                     // ignore for now
766                 }
767             }
768             
769 			if ( anObject instanceof Map )
770 			{
771 				return ((Map)anObject).put( aProperty, aValue );
772 			}
773 			if ( anObject instanceof List )
774 			{
775                 List list = (List) anObject;
776                 int i = Integer.parseInt( aProperty );
777                 if ( list.size() < i+1 )
778                 {
779                     // expand list as necessary
780                     for ( int j = list.size(); j <= i; j++ )
781                     {
782                         list.add( new Object() ); // placeholder
783                     }
784                 }
785 				return list.set( i, aValue );
786 			}
787 	    } 
788 	    catch ( Throwable exc )
789 	    {
790             if ( exc instanceof IllegalArgumentException )
791             {
792 				System.out.println(
793 					"Introspector.setValueForObject: " 
794 					+ anObject + " , " + aProperty + " , '" 
795 					+ aValue + "' ):" );
796 				System.out.println( exc );
797             }
798             else
799             if ( exc instanceof InvocationTargetException )
800             {
801                 exc = ((InvocationTargetException)exc).getTargetException();   
802             }
803             if ( exc instanceof RuntimeException )
804             {
805                 throw (RuntimeException)exc;   
806             }
807 			if ( debug )
808 			{
809 				System.out.println(
810 					"Introspector.setValueForObject: " 
811 					+ anObject + " , " + aProperty + " , '" 
812 					+ aValue + "' ):" );
813 			}
814             throw new WotonomyException( exc );
815 	    }
816         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 		int i = aProperty.indexOf( SEPARATOR );
830 		if ( i == -1 ) return getValueForObject( anObject, aProperty );
831 
832 		String pathElement = aProperty.substring( 0, i );
833 		String remainder = aProperty.substring( i+1 );
834 		
835 		Object result = getValueForObject( anObject, pathElement );
836 		if ( result == null ) return null;
837 		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 		int i = aProperty.indexOf( SEPARATOR );
852 		if ( i == -1 ) return setValueForObject( anObject, aProperty, aValue );
853 
854 		String pathElement = aProperty.substring( 0, i );
855 		String remainder = aProperty.substring( i+1 );
856 		
857 		Object result = getValueForObject( anObject, pathElement );
858 		if ( result == null ) 
859 		{
860 			result = new HashMap(2);
861 			setValueForObject( anObject, pathElement, result );
862 		}
863 		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 		debug = isDebug;
873 	}	
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