Coverage Report - org.codehaus.plexus.util.reflection.Reflector
 
Classes in this File Line Coverage Branch Coverage Complexity
Reflector
34%
71/208
29%
26/88
8
 
 1  
 package org.codehaus.plexus.util.reflection;
 2  
 
 3  
 /*
 4  
  * Copyright The Codehaus Foundation.
 5  
  *
 6  
  * Licensed under the Apache License, Version 2.0 (the "License");
 7  
  * you may not use this file except in compliance with the License.
 8  
  * You may obtain a copy of the License at
 9  
  *
 10  
  *     http://www.apache.org/licenses/LICENSE-2.0
 11  
  *
 12  
  * Unless required by applicable law or agreed to in writing, software
 13  
  * distributed under the License is distributed on an "AS IS" BASIS,
 14  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 15  
  * See the License for the specific language governing permissions and
 16  
  * limitations under the License.
 17  
  */
 18  
 
 19  
 import java.lang.reflect.Constructor;
 20  
 import java.lang.reflect.Field;
 21  
 import java.lang.reflect.InvocationTargetException;
 22  
 import java.lang.reflect.Method;
 23  
 
 24  
 import java.util.HashMap;
 25  
 import java.util.Map;
 26  
 
 27  
 /**
 28  
  * Utility class used to instantiate an object using reflection. This utility hides many of the gory details needed to
 29  
  * do this.
 30  
  *
 31  
  * @author John Casey
 32  
  */
 33  
 public final class Reflector
 34  
 {
 35  
     private static final String CONSTRUCTOR_METHOD_NAME = "$$CONSTRUCTOR$$";
 36  
 
 37  
     private static final String GET_INSTANCE_METHOD_NAME = "getInstance";
 38  
 
 39  10
     private Map<String, Map<String, Map<String, Method>>> classMaps =
 40  
         new HashMap<String, Map<String, Map<String, Method>>>();
 41  
 
 42  
     /** Ensure no instances of Reflector are created...this is a utility. */
 43  
     public Reflector()
 44  10
     {
 45  10
     }
 46  
 
 47  
     /**
 48  
      * Create a new instance of a class, given the array of parameters... Uses constructor caching to find a constructor
 49  
      * that matches the parameter types, either specifically (first choice) or abstractly...
 50  
      *
 51  
      * @param theClass The class to instantiate
 52  
      * @param params The parameters to pass to the constructor
 53  
      * @return The instantiated object
 54  
      * @throws ReflectorException In case anything goes wrong here...
 55  
      */
 56  
     @SuppressWarnings( { "UnusedDeclaration" } )
 57  
     public <T> T newInstance( Class<T> theClass, Object[] params )
 58  
         throws ReflectorException
 59  
     {
 60  0
         if ( params == null )
 61  
         {
 62  0
             params = new Object[0];
 63  
         }
 64  
 
 65  0
         Class[] paramTypes = new Class[params.length];
 66  
 
 67  0
         for ( int i = 0, len = params.length; i < len; i++ )
 68  
         {
 69  0
             paramTypes[i] = params[i].getClass();
 70  
         }
 71  
 
 72  
         try
 73  
         {
 74  0
             Constructor<T> con = getConstructor( theClass, paramTypes );
 75  
 
 76  0
             if ( con == null )
 77  
             {
 78  0
                 StringBuilder buffer = new StringBuilder();
 79  
 
 80  0
                 buffer.append( "Constructor not found for class: " );
 81  0
                 buffer.append( theClass.getName() );
 82  0
                 buffer.append( " with specified or ancestor parameter classes: " );
 83  
 
 84  0
                 for ( Class paramType : paramTypes )
 85  
                 {
 86  0
                     buffer.append( paramType.getName() );
 87  0
                     buffer.append( ',' );
 88  
                 }
 89  
 
 90  0
                 buffer.setLength( buffer.length() - 1 );
 91  
 
 92  0
                 throw new ReflectorException( buffer.toString() );
 93  
             }
 94  
 
 95  0
             return con.newInstance( params );
 96  
         }
 97  0
         catch ( InstantiationException ex )
 98  
         {
 99  0
             throw new ReflectorException( ex );
 100  
         }
 101  0
         catch ( InvocationTargetException ex )
 102  
         {
 103  0
             throw new ReflectorException( ex );
 104  
         }
 105  0
         catch ( IllegalAccessException ex )
 106  
         {
 107  0
             throw new ReflectorException( ex );
 108  
         }
 109  
     }
 110  
 
 111  
     /**
 112  
      * Retrieve the singleton instance of a class, given the array of parameters... Uses constructor caching to find a
 113  
      * constructor that matches the parameter types, either specifically (first choice) or abstractly...
 114  
      *
 115  
      * @param theClass The class to retrieve the singleton of
 116  
      * @param initParams The parameters to pass to the constructor
 117  
      * @return The singleton object
 118  
      * @throws ReflectorException In case anything goes wrong here...
 119  
      */
 120  
     @SuppressWarnings( { "UnusedDeclaration" } )
 121  
     public <T> T getSingleton( Class<T> theClass, Object[] initParams )
 122  
         throws ReflectorException
 123  
     {
 124  0
         Class[] paramTypes = new Class[initParams.length];
 125  
 
 126  0
         for ( int i = 0, len = initParams.length; i < len; i++ )
 127  
         {
 128  0
             paramTypes[i] = initParams[i].getClass();
 129  
         }
 130  
 
 131  
         try
 132  
         {
 133  0
             Method method = getMethod( theClass, GET_INSTANCE_METHOD_NAME, paramTypes );
 134  
 
 135  
             // noinspection unchecked
 136  0
             return (T) method.invoke( null, initParams );
 137  
         }
 138  0
         catch ( InvocationTargetException ex )
 139  
         {
 140  0
             throw new ReflectorException( ex );
 141  
         }
 142  0
         catch ( IllegalAccessException ex )
 143  
         {
 144  0
             throw new ReflectorException( ex );
 145  
         }
 146  
     }
 147  
 
 148  
     /**
 149  
      * Invoke the specified method on the specified target with the specified params...
 150  
      *
 151  
      * @param target The target of the invocation
 152  
      * @param methodName The method name to invoke
 153  
      * @param params The parameters to pass to the method invocation
 154  
      * @return The result of the method call
 155  
      * @throws ReflectorException In case of an error looking up or invoking the method.
 156  
      */
 157  
     @SuppressWarnings( { "UnusedDeclaration" } )
 158  
     public Object invoke( Object target, String methodName, Object[] params )
 159  
         throws ReflectorException
 160  
     {
 161  0
         if ( params == null )
 162  
         {
 163  0
             params = new Object[0];
 164  
         }
 165  
 
 166  0
         Class[] paramTypes = new Class[params.length];
 167  
 
 168  0
         for ( int i = 0, len = params.length; i < len; i++ )
 169  
         {
 170  0
             paramTypes[i] = params[i].getClass();
 171  
         }
 172  
 
 173  
         try
 174  
         {
 175  0
             Method method = getMethod( target.getClass(), methodName, paramTypes );
 176  
 
 177  0
             if ( method == null )
 178  
             {
 179  0
                 StringBuilder buffer = new StringBuilder();
 180  
 
 181  0
                 buffer.append( "Singleton-producing method named '" ).append( methodName ).append( "' not found with specified parameter classes: " );
 182  
 
 183  0
                 for ( Class paramType : paramTypes )
 184  
                 {
 185  0
                     buffer.append( paramType.getName() );
 186  0
                     buffer.append( ',' );
 187  
                 }
 188  
 
 189  0
                 buffer.setLength( buffer.length() - 1 );
 190  
 
 191  0
                 throw new ReflectorException( buffer.toString() );
 192  
             }
 193  
 
 194  0
             return method.invoke( target, params );
 195  
         }
 196  0
         catch ( InvocationTargetException ex )
 197  
         {
 198  0
             throw new ReflectorException( ex );
 199  
         }
 200  0
         catch ( IllegalAccessException ex )
 201  
         {
 202  0
             throw new ReflectorException( ex );
 203  
         }
 204  
     }
 205  
 
 206  
     @SuppressWarnings( { "UnusedDeclaration" } )
 207  
     public Object getStaticField( Class targetClass, String fieldName )
 208  
         throws ReflectorException
 209  
     {
 210  
         try
 211  
         {
 212  0
             Field field = targetClass.getField( fieldName );
 213  
 
 214  0
             return field.get( null );
 215  
         }
 216  0
         catch ( SecurityException e )
 217  
         {
 218  0
             throw new ReflectorException( e );
 219  
         }
 220  0
         catch ( NoSuchFieldException e )
 221  
         {
 222  0
             throw new ReflectorException( e );
 223  
         }
 224  0
         catch ( IllegalArgumentException e )
 225  
         {
 226  0
             throw new ReflectorException( e );
 227  
         }
 228  0
         catch ( IllegalAccessException e )
 229  
         {
 230  0
             throw new ReflectorException( e );
 231  
         }
 232  
     }
 233  
 
 234  
     @SuppressWarnings( { "UnusedDeclaration" } )
 235  
     public Object getField( Object target, String fieldName )
 236  
         throws ReflectorException
 237  
     {
 238  0
         return getField( target, fieldName, false );
 239  
     }
 240  
 
 241  
     public Object getField( Object target, String fieldName, boolean breakAccessibility )
 242  
         throws ReflectorException
 243  
     {
 244  1
         Class targetClass = target.getClass();
 245  1
         while ( targetClass != null )
 246  
         {
 247  
             try
 248  
             {
 249  1
                 Field field = targetClass.getDeclaredField( fieldName );
 250  
 
 251  1
                 boolean accessibilityBroken = false;
 252  1
                 if ( !field.isAccessible() && breakAccessibility )
 253  
                 {
 254  1
                     field.setAccessible( true );
 255  1
                     accessibilityBroken = true;
 256  
                 }
 257  
 
 258  1
                 Object result = field.get( target );
 259  
 
 260  1
                 if ( accessibilityBroken )
 261  
                 {
 262  1
                     field.setAccessible( false );
 263  
                 }
 264  
 
 265  1
                 return result;
 266  
             }
 267  0
             catch ( SecurityException e )
 268  
             {
 269  0
                 throw new ReflectorException( e );
 270  
             }
 271  0
             catch ( NoSuchFieldException e )
 272  
             {
 273  0
                 if ( targetClass == Object.class )
 274  0
                     throw new ReflectorException( e );
 275  0
                 targetClass = targetClass.getSuperclass();
 276  
             }
 277  0
             catch ( IllegalAccessException e )
 278  
             {
 279  0
                 throw new ReflectorException( e );
 280  0
             }
 281  
         }
 282  
         // Never reached, but needed to satisfy compiler
 283  0
         return null;
 284  
     }
 285  
 
 286  
     /**
 287  
      * Invoke the specified static method with the specified params...
 288  
      *
 289  
      * @param targetClass The target class of the invocation
 290  
      * @param methodName The method name to invoke
 291  
      * @param params The parameters to pass to the method invocation
 292  
      * @return The result of the method call
 293  
      * @throws ReflectorException In case of an error looking up or invoking the method.
 294  
      */
 295  
     @SuppressWarnings( { "UnusedDeclaration" } )
 296  
     public Object invokeStatic( Class targetClass, String methodName, Object[] params )
 297  
         throws ReflectorException
 298  
     {
 299  0
         if ( params == null )
 300  
         {
 301  0
             params = new Object[0];
 302  
         }
 303  
 
 304  0
         Class[] paramTypes = new Class[params.length];
 305  
 
 306  0
         for ( int i = 0, len = params.length; i < len; i++ )
 307  
         {
 308  0
             paramTypes[i] = params[i].getClass();
 309  
         }
 310  
 
 311  
         try
 312  
         {
 313  0
             Method method = getMethod( targetClass, methodName, paramTypes );
 314  
 
 315  0
             if ( method == null )
 316  
             {
 317  0
                 StringBuilder buffer = new StringBuilder();
 318  
 
 319  0
                 buffer.append( "Singleton-producing method named \'" ).append( methodName ).append( "\' not found with specified parameter classes: " );
 320  
 
 321  0
                 for ( Class paramType : paramTypes )
 322  
                 {
 323  0
                     buffer.append( paramType.getName() );
 324  0
                     buffer.append( ',' );
 325  
                 }
 326  
 
 327  0
                 buffer.setLength( buffer.length() - 1 );
 328  
 
 329  0
                 throw new ReflectorException( buffer.toString() );
 330  
             }
 331  
 
 332  0
             return method.invoke( null, params );
 333  
         }
 334  0
         catch ( InvocationTargetException ex )
 335  
         {
 336  0
             throw new ReflectorException( ex );
 337  
         }
 338  0
         catch ( IllegalAccessException ex )
 339  
         {
 340  0
             throw new ReflectorException( ex );
 341  
         }
 342  
     }
 343  
 
 344  
     /**
 345  
      * Return the constructor, checking the cache first and storing in cache if not already there..
 346  
      *
 347  
      * @param targetClass The class to get the constructor from
 348  
      * @param params The classes of the parameters which the constructor should match.
 349  
      * @return the Constructor object that matches.
 350  
      * @throws ReflectorException In case we can't retrieve the proper constructor.
 351  
      */
 352  
     public <T> Constructor<T> getConstructor( Class<T> targetClass, Class[] params )
 353  
         throws ReflectorException
 354  
     {
 355  0
         Map<String, Constructor<T>> constructorMap = getConstructorMap( targetClass );
 356  
 
 357  0
         StringBuilder key = new StringBuilder( 200 );
 358  
 
 359  0
         key.append( "(" );
 360  
 
 361  0
         for ( Class param : params )
 362  
         {
 363  0
             key.append( param.getName() );
 364  0
             key.append( "," );
 365  
         }
 366  
 
 367  0
         if ( params.length > 0 )
 368  
         {
 369  0
             key.setLength( key.length() - 1 );
 370  
         }
 371  
 
 372  0
         key.append( ")" );
 373  
 
 374  
         Constructor<T> constructor;
 375  
 
 376  0
         String paramKey = key.toString();
 377  
 
 378  0
         synchronized ( paramKey.intern() )
 379  
         {
 380  0
             constructor = constructorMap.get( paramKey );
 381  
 
 382  0
             if ( constructor == null )
 383  
             {
 384  
                 @SuppressWarnings( { "unchecked" } )
 385  0
                 Constructor<T>[] cands = (Constructor<T>[]) targetClass.getConstructors();
 386  
 
 387  0
                 for ( Constructor<T> cand : cands )
 388  
                 {
 389  0
                     Class[] types = cand.getParameterTypes();
 390  
 
 391  0
                     if ( params.length != types.length )
 392  
                     {
 393  0
                         continue;
 394  
                     }
 395  
 
 396  0
                     for ( int j = 0, len2 = params.length; j < len2; j++ )
 397  
                     {
 398  0
                         if ( !types[j].isAssignableFrom( params[j] ) )
 399  
                         {
 400  
                             continue;
 401  
                         }
 402  
                     }
 403  
 
 404  
                     // we got it, so store it!
 405  0
                     constructor = cand;
 406  0
                     constructorMap.put( paramKey, constructor );
 407  
                 }
 408  
             }
 409  0
         }
 410  
 
 411  0
         if ( constructor == null )
 412  
         {
 413  0
             throw new ReflectorException( "Error retrieving constructor object for: " + targetClass.getName()
 414  
                 + paramKey );
 415  
         }
 416  
 
 417  0
         return constructor;
 418  
     }
 419  
 
 420  
     public Object getObjectProperty( Object target, String propertyName )
 421  
         throws ReflectorException
 422  
     {
 423  
         Object returnValue;
 424  
 
 425  3
         if ( propertyName == null || propertyName.trim().length() < 1 )
 426  
         {
 427  0
             throw new ReflectorException( "Cannot retrieve value for empty property." );
 428  
         }
 429  
 
 430  3
         String beanAccessor = "get" + Character.toUpperCase( propertyName.charAt( 0 ) );
 431  3
         if ( propertyName.trim().length() > 1 )
 432  
         {
 433  3
             beanAccessor += propertyName.substring( 1 ).trim();
 434  
         }
 435  
 
 436  3
         Class targetClass = target.getClass();
 437  3
         Class[] emptyParams = {};
 438  
 
 439  3
         Method method = _getMethod( targetClass, beanAccessor, emptyParams );
 440  3
         if ( method == null )
 441  
         {
 442  2
             method = _getMethod( targetClass, propertyName, emptyParams );
 443  
         }
 444  3
         if ( method != null )
 445  
         {
 446  
             try
 447  
             {
 448  2
                 returnValue = method.invoke( target, new Object[] {} );
 449  
             }
 450  0
             catch ( IllegalAccessException e )
 451  
             {
 452  0
                 throw new ReflectorException( "Error retrieving property \'" + propertyName + "\' from \'" + targetClass
 453  
                     + "\'", e );
 454  
             }
 455  0
             catch ( InvocationTargetException e )
 456  
             {
 457  0
                 throw new ReflectorException( "Error retrieving property \'" + propertyName + "\' from \'" + targetClass
 458  
                     + "\'", e );
 459  2
             }
 460  
         }
 461  
 
 462  3
         if ( method != null )
 463  
         {
 464  
             try
 465  
             {
 466  2
                 returnValue = method.invoke( target, new Object[] {} );
 467  
             }
 468  0
             catch ( IllegalAccessException e )
 469  
             {
 470  0
                 throw new ReflectorException( "Error retrieving property \'" + propertyName + "\' from \'" + targetClass
 471  
                     + "\'", e );
 472  
             }
 473  0
             catch ( InvocationTargetException e )
 474  
             {
 475  0
                 throw new ReflectorException( "Error retrieving property \'" + propertyName + "\' from \'" + targetClass
 476  
                     + "\'", e );
 477  2
             }
 478  
         }
 479  
         else
 480  
         {
 481  1
             returnValue = getField( target, propertyName, true );
 482  1
             if ( returnValue == null )
 483  
             {
 484  
                 // TODO: Check if exception is the right action! Field exists, but contains null
 485  0
                 throw new ReflectorException( "Neither method: \'" + propertyName + "\' nor bean accessor: \'"
 486  
                     + beanAccessor + "\' can be found for class: \'" + targetClass + "\', and retrieval of field: \'"
 487  
                     + propertyName + "\' returned null as value." );
 488  
             }
 489  
         }
 490  
 
 491  3
         return returnValue;
 492  
     }
 493  
 
 494  
     /**
 495  
      * Return the method, checking the cache first and storing in cache if not already there..
 496  
      *
 497  
      * @param targetClass The class to get the method from
 498  
      * @param params The classes of the parameters which the method should match.
 499  
      * @return the Method object that matches.
 500  
      * @throws ReflectorException In case we can't retrieve the proper method.
 501  
      */
 502  
     public Method getMethod( Class targetClass, String methodName, Class[] params )
 503  
         throws ReflectorException
 504  
     {
 505  0
         Method method = _getMethod( targetClass, methodName, params );
 506  
 
 507  0
         if ( method == null )
 508  
         {
 509  0
             throw new ReflectorException( "Method: \'" + methodName + "\' not found in class: \'" + targetClass
 510  
                 + "\'" );
 511  
         }
 512  
 
 513  0
         return method;
 514  
     }
 515  
 
 516  
     private Method _getMethod( Class targetClass, String methodName, Class[] params )
 517  
         throws ReflectorException
 518  
     {
 519  5
         Map<String, Method> methodMap = (Map<String, Method>) getMethodMap( targetClass, methodName );
 520  
 
 521  5
         StringBuilder key = new StringBuilder( 200 );
 522  
 
 523  5
         key.append( "(" );
 524  
 
 525  5
         for ( Class param : params )
 526  
         {
 527  0
             key.append( param.getName() );
 528  0
             key.append( "," );
 529  
         }
 530  
 
 531  5
         key.append( ")" );
 532  
 
 533  
         Method method;
 534  
 
 535  5
         String paramKey = key.toString();
 536  
 
 537  5
         synchronized ( paramKey.intern() )
 538  
         {
 539  5
             method = (Method) methodMap.get( paramKey );
 540  
 
 541  5
             if ( method == null )
 542  
             {
 543  5
                 Method[] cands = targetClass.getMethods();
 544  
 
 545  70
                 for ( Method cand : cands )
 546  
                 {
 547  65
                     String name = cand.getName();
 548  
 
 549  65
                     if ( !methodName.equals( name ) )
 550  
                     {
 551  63
                         continue;
 552  
                     }
 553  
 
 554  2
                     Class[] types = cand.getParameterTypes();
 555  
 
 556  2
                     if ( params.length != types.length )
 557  
                     {
 558  0
                         continue;
 559  
                     }
 560  
 
 561  2
                     for ( int j = 0, len2 = params.length; j < len2; j++ )
 562  
                     {
 563  0
                         if ( !types[j].isAssignableFrom( params[j] ) )
 564  
                         {
 565  
                             continue;
 566  
                         }
 567  
                     }
 568  
 
 569  
                     // we got it, so store it!
 570  2
                     method = cand;
 571  2
                     methodMap.put( paramKey, method );
 572  
                 }
 573  
             }
 574  5
         }
 575  
 
 576  5
         return method;
 577  
     }
 578  
 
 579  
     /**
 580  
      * Retrieve the cache of constructors for the specified class.
 581  
      *
 582  
      * @param theClass the class to lookup.
 583  
      * @return The cache of constructors.
 584  
      * @throws ReflectorException in case of a lookup error.
 585  
      */
 586  
     private <T> Map<String, Constructor<T>> getConstructorMap( Class<T> theClass )
 587  
         throws ReflectorException
 588  
     {
 589  0
         return (Map<String, Constructor<T>>) getMethodMap( theClass, CONSTRUCTOR_METHOD_NAME );
 590  
     }
 591  
 
 592  
     /**
 593  
      * Retrieve the cache of methods for the specified class and method name.
 594  
      *
 595  
      * @param theClass the class to lookup.
 596  
      * @param methodName The name of the method to lookup.
 597  
      * @return The cache of constructors.
 598  
      * @throws ReflectorException in case of a lookup error.
 599  
      */
 600  
     private Map<String, ?> getMethodMap( Class theClass, String methodName )
 601  
         throws ReflectorException
 602  
     {
 603  
         Map<String, Method> methodMap;
 604  
 
 605  5
         if ( theClass == null )
 606  
         {
 607  0
             return null;
 608  
         }
 609  
 
 610  5
         String className = theClass.getName();
 611  
 
 612  5
         synchronized ( className.intern() )
 613  
         {
 614  5
             Map<String, Map<String, Method>> classMethods = classMaps.get( className );
 615  
 
 616  5
             if ( classMethods == null )
 617  
             {
 618  3
                 classMethods = new HashMap<String, Map<String, Method>>();
 619  3
                 methodMap = new HashMap<String, Method>();
 620  3
                 classMethods.put( methodName, methodMap );
 621  3
                 classMaps.put( className, classMethods );
 622  
             }
 623  
             else
 624  
             {
 625  2
                 String key = className + "::" + methodName;
 626  
 
 627  2
                 synchronized ( key.intern() )
 628  
                 {
 629  2
                     methodMap = classMethods.get( methodName );
 630  
 
 631  2
                     if ( methodMap == null )
 632  
                     {
 633  2
                         methodMap = new HashMap<String, Method>();
 634  2
                         classMethods.put( methodName, methodMap );
 635  
                     }
 636  2
                 }
 637  
             }
 638  5
         }
 639  
 
 640  5
         return methodMap;
 641  
     }
 642  
 }