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