Coverage Report - net.wotonomy.foundation.internal.Introspector
 
Classes in this File Line Coverage Branch Coverage Complexity
Introspector
0% 
0% 
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