package org.codehaus.plexus.util.reflection;

/*
 * Copyright The Codehaus Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import java.util.HashMap;
import java.util.Map;

Utility class used to instantiate an object using reflection. This utility hides many of the gory details needed to do this.
Author:John Casey
/** * Utility class used to instantiate an object using reflection. This utility hides many of the gory details needed to * do this. * * @author John Casey */
public final class Reflector { private static final String CONSTRUCTOR_METHOD_NAME = "$$CONSTRUCTOR$$"; private static final String GET_INSTANCE_METHOD_NAME = "getInstance"; private Map<String, Map<String, Map<String, Method>>> classMaps = new HashMap<String, Map<String, Map<String, Method>>>();
Ensure no instances of Reflector are created...this is a utility.
/** Ensure no instances of Reflector are created...this is a utility. */
public Reflector() { }
Create a new instance of a class, given the array of parameters... Uses constructor caching to find a constructor that matches the parameter types, either specifically (first choice) or abstractly...
Params:
  • theClass – The class to instantiate
  • params – The parameters to pass to the constructor
Throws:
Returns:The instantiated object
/** * Create a new instance of a class, given the array of parameters... Uses constructor caching to find a constructor * that matches the parameter types, either specifically (first choice) or abstractly... * * @param theClass The class to instantiate * @param params The parameters to pass to the constructor * @return The instantiated object * @throws ReflectorException In case anything goes wrong here... */
@SuppressWarnings( { "UnusedDeclaration" } ) public <T> T newInstance( Class<T> theClass, Object[] params ) throws ReflectorException { if ( params == null ) { params = new Object[0]; } Class[] paramTypes = new Class[params.length]; for ( int i = 0, len = params.length; i < len; i++ ) { paramTypes[i] = params[i].getClass(); } try { Constructor<T> con = getConstructor( theClass, paramTypes ); if ( con == null ) { StringBuilder buffer = new StringBuilder(); buffer.append( "Constructor not found for class: " ); buffer.append( theClass.getName() ); buffer.append( " with specified or ancestor parameter classes: " ); for ( Class paramType : paramTypes ) { buffer.append( paramType.getName() ); buffer.append( ',' ); } buffer.setLength( buffer.length() - 1 ); throw new ReflectorException( buffer.toString() ); } return con.newInstance( params ); } catch ( InstantiationException ex ) { throw new ReflectorException( ex ); } catch ( InvocationTargetException ex ) { throw new ReflectorException( ex ); } catch ( IllegalAccessException ex ) { throw new ReflectorException( ex ); } }
Retrieve the singleton instance of a class, given the array of parameters... Uses constructor caching to find a constructor that matches the parameter types, either specifically (first choice) or abstractly...
Params:
  • theClass – The class to retrieve the singleton of
  • initParams – The parameters to pass to the constructor
Throws:
Returns:The singleton object
/** * Retrieve the singleton instance of a class, given the array of parameters... Uses constructor caching to find a * constructor that matches the parameter types, either specifically (first choice) or abstractly... * * @param theClass The class to retrieve the singleton of * @param initParams The parameters to pass to the constructor * @return The singleton object * @throws ReflectorException In case anything goes wrong here... */
@SuppressWarnings( { "UnusedDeclaration" } ) public <T> T getSingleton( Class<T> theClass, Object[] initParams ) throws ReflectorException { Class[] paramTypes = new Class[initParams.length]; for ( int i = 0, len = initParams.length; i < len; i++ ) { paramTypes[i] = initParams[i].getClass(); } try { Method method = getMethod( theClass, GET_INSTANCE_METHOD_NAME, paramTypes ); // noinspection unchecked return (T) method.invoke( null, initParams ); } catch ( InvocationTargetException ex ) { throw new ReflectorException( ex ); } catch ( IllegalAccessException ex ) { throw new ReflectorException( ex ); } }
Invoke the specified method on the specified target with the specified params...
Params:
  • target – The target of the invocation
  • methodName – The method name to invoke
  • params – The parameters to pass to the method invocation
Throws:
Returns:The result of the method call
/** * Invoke the specified method on the specified target with the specified params... * * @param target The target of the invocation * @param methodName The method name to invoke * @param params The parameters to pass to the method invocation * @return The result of the method call * @throws ReflectorException In case of an error looking up or invoking the method. */
@SuppressWarnings( { "UnusedDeclaration" } ) public Object invoke( Object target, String methodName, Object[] params ) throws ReflectorException { if ( params == null ) { params = new Object[0]; } Class[] paramTypes = new Class[params.length]; for ( int i = 0, len = params.length; i < len; i++ ) { paramTypes[i] = params[i].getClass(); } try { Method method = getMethod( target.getClass(), methodName, paramTypes ); if ( method == null ) { StringBuilder buffer = new StringBuilder(); buffer.append( "Singleton-producing method named '" ).append( methodName ).append( "' not found with specified parameter classes: " ); for ( Class paramType : paramTypes ) { buffer.append( paramType.getName() ); buffer.append( ',' ); } buffer.setLength( buffer.length() - 1 ); throw new ReflectorException( buffer.toString() ); } return method.invoke( target, params ); } catch ( InvocationTargetException ex ) { throw new ReflectorException( ex ); } catch ( IllegalAccessException ex ) { throw new ReflectorException( ex ); } } @SuppressWarnings( { "UnusedDeclaration" } ) public Object getStaticField( Class targetClass, String fieldName ) throws ReflectorException { try { Field field = targetClass.getField( fieldName ); return field.get( null ); } catch ( SecurityException e ) { throw new ReflectorException( e ); } catch ( NoSuchFieldException e ) { throw new ReflectorException( e ); } catch ( IllegalArgumentException e ) { throw new ReflectorException( e ); } catch ( IllegalAccessException e ) { throw new ReflectorException( e ); } } @SuppressWarnings( { "UnusedDeclaration" } ) public Object getField( Object target, String fieldName ) throws ReflectorException { return getField( target, fieldName, false ); } public Object getField( Object target, String fieldName, boolean breakAccessibility ) throws ReflectorException { Class targetClass = target.getClass(); while ( targetClass != null ) { try { Field field = targetClass.getDeclaredField( fieldName ); boolean accessibilityBroken = false; if ( !field.isAccessible() && breakAccessibility ) { field.setAccessible( true ); accessibilityBroken = true; } Object result = field.get( target ); if ( accessibilityBroken ) { field.setAccessible( false ); } return result; } catch ( SecurityException e ) { throw new ReflectorException( e ); } catch ( NoSuchFieldException e ) { if ( targetClass == Object.class ) throw new ReflectorException( e ); targetClass = targetClass.getSuperclass(); } catch ( IllegalAccessException e ) { throw new ReflectorException( e ); } } // Never reached, but needed to satisfy compiler return null; }
Invoke the specified static method with the specified params...
Params:
  • targetClass – The target class of the invocation
  • methodName – The method name to invoke
  • params – The parameters to pass to the method invocation
Throws:
Returns:The result of the method call
/** * Invoke the specified static method with the specified params... * * @param targetClass The target class of the invocation * @param methodName The method name to invoke * @param params The parameters to pass to the method invocation * @return The result of the method call * @throws ReflectorException In case of an error looking up or invoking the method. */
@SuppressWarnings( { "UnusedDeclaration" } ) public Object invokeStatic( Class targetClass, String methodName, Object[] params ) throws ReflectorException { if ( params == null ) { params = new Object[0]; } Class[] paramTypes = new Class[params.length]; for ( int i = 0, len = params.length; i < len; i++ ) { paramTypes[i] = params[i].getClass(); } try { Method method = getMethod( targetClass, methodName, paramTypes ); if ( method == null ) { StringBuilder buffer = new StringBuilder(); buffer.append( "Singleton-producing method named \'" ).append( methodName ).append( "\' not found with specified parameter classes: " ); for ( Class paramType : paramTypes ) { buffer.append( paramType.getName() ); buffer.append( ',' ); } buffer.setLength( buffer.length() - 1 ); throw new ReflectorException( buffer.toString() ); } return method.invoke( null, params ); } catch ( InvocationTargetException ex ) { throw new ReflectorException( ex ); } catch ( IllegalAccessException ex ) { throw new ReflectorException( ex ); } }
Return the constructor, checking the cache first and storing in cache if not already there..
Params:
  • targetClass – The class to get the constructor from
  • params – The classes of the parameters which the constructor should match.
Throws:
Returns:the Constructor object that matches.
/** * Return the constructor, checking the cache first and storing in cache if not already there.. * * @param targetClass The class to get the constructor from * @param params The classes of the parameters which the constructor should match. * @return the Constructor object that matches. * @throws ReflectorException In case we can't retrieve the proper constructor. */
public <T> Constructor<T> getConstructor( Class<T> targetClass, Class[] params ) throws ReflectorException { Map<String, Constructor<T>> constructorMap = getConstructorMap( targetClass ); StringBuilder key = new StringBuilder( 200 ); key.append( "(" ); for ( Class param : params ) { key.append( param.getName() ); key.append( "," ); } if ( params.length > 0 ) { key.setLength( key.length() - 1 ); } key.append( ")" ); Constructor<T> constructor; String paramKey = key.toString(); synchronized ( paramKey.intern() ) { constructor = constructorMap.get( paramKey ); if ( constructor == null ) { @SuppressWarnings( { "unchecked" } ) Constructor<T>[] cands = (Constructor<T>[]) targetClass.getConstructors(); for ( Constructor<T> cand : cands ) { Class[] types = cand.getParameterTypes(); if ( params.length != types.length ) { continue; } for ( int j = 0, len2 = params.length; j < len2; j++ ) { if ( !types[j].isAssignableFrom( params[j] ) ) { continue; } } // we got it, so store it! constructor = cand; constructorMap.put( paramKey, constructor ); } } } if ( constructor == null ) { throw new ReflectorException( "Error retrieving constructor object for: " + targetClass.getName() + paramKey ); } return constructor; } public Object getObjectProperty( Object target, String propertyName ) throws ReflectorException { Object returnValue; if ( propertyName == null || propertyName.trim().length() < 1 ) { throw new ReflectorException( "Cannot retrieve value for empty property." ); } String beanAccessor = "get" + Character.toUpperCase( propertyName.charAt( 0 ) ); if ( propertyName.trim().length() > 1 ) { beanAccessor += propertyName.substring( 1 ).trim(); } Class targetClass = target.getClass(); Class[] emptyParams = {}; Method method = _getMethod( targetClass, beanAccessor, emptyParams ); if ( method == null ) { method = _getMethod( targetClass, propertyName, emptyParams ); } if ( method != null ) { try { returnValue = method.invoke( target, new Object[] {} ); } catch ( IllegalAccessException e ) { throw new ReflectorException( "Error retrieving property \'" + propertyName + "\' from \'" + targetClass + "\'", e ); } catch ( InvocationTargetException e ) { throw new ReflectorException( "Error retrieving property \'" + propertyName + "\' from \'" + targetClass + "\'", e ); } } if ( method != null ) { try { returnValue = method.invoke( target, new Object[] {} ); } catch ( IllegalAccessException e ) { throw new ReflectorException( "Error retrieving property \'" + propertyName + "\' from \'" + targetClass + "\'", e ); } catch ( InvocationTargetException e ) { throw new ReflectorException( "Error retrieving property \'" + propertyName + "\' from \'" + targetClass + "\'", e ); } } else { returnValue = getField( target, propertyName, true ); if ( returnValue == null ) { // TODO: Check if exception is the right action! Field exists, but contains null throw new ReflectorException( "Neither method: \'" + propertyName + "\' nor bean accessor: \'" + beanAccessor + "\' can be found for class: \'" + targetClass + "\', and retrieval of field: \'" + propertyName + "\' returned null as value." ); } } return returnValue; }
Return the method, checking the cache first and storing in cache if not already there..
Params:
  • targetClass – The class to get the method from
  • params – The classes of the parameters which the method should match.
Throws:
Returns:the Method object that matches.
/** * Return the method, checking the cache first and storing in cache if not already there.. * * @param targetClass The class to get the method from * @param params The classes of the parameters which the method should match. * @return the Method object that matches. * @throws ReflectorException In case we can't retrieve the proper method. */
public Method getMethod( Class targetClass, String methodName, Class[] params ) throws ReflectorException { Method method = _getMethod( targetClass, methodName, params ); if ( method == null ) { throw new ReflectorException( "Method: \'" + methodName + "\' not found in class: \'" + targetClass + "\'" ); } return method; } private Method _getMethod( Class targetClass, String methodName, Class[] params ) throws ReflectorException { Map<String, Method> methodMap = (Map<String, Method>) getMethodMap( targetClass, methodName ); StringBuilder key = new StringBuilder( 200 ); key.append( "(" ); for ( Class param : params ) { key.append( param.getName() ); key.append( "," ); } key.append( ")" ); Method method; String paramKey = key.toString(); synchronized ( paramKey.intern() ) { method = (Method) methodMap.get( paramKey ); if ( method == null ) { Method[] cands = targetClass.getMethods(); for ( Method cand : cands ) { String name = cand.getName(); if ( !methodName.equals( name ) ) { continue; } Class[] types = cand.getParameterTypes(); if ( params.length != types.length ) { continue; } for ( int j = 0, len2 = params.length; j < len2; j++ ) { if ( !types[j].isAssignableFrom( params[j] ) ) { continue; } } // we got it, so store it! method = cand; methodMap.put( paramKey, method ); } } } return method; }
Retrieve the cache of constructors for the specified class.
Params:
  • theClass – the class to lookup.
Throws:
Returns:The cache of constructors.
/** * Retrieve the cache of constructors for the specified class. * * @param theClass the class to lookup. * @return The cache of constructors. * @throws ReflectorException in case of a lookup error. */
private <T> Map<String, Constructor<T>> getConstructorMap( Class<T> theClass ) throws ReflectorException { return (Map<String, Constructor<T>>) getMethodMap( theClass, CONSTRUCTOR_METHOD_NAME ); }
Retrieve the cache of methods for the specified class and method name.
Params:
  • theClass – the class to lookup.
  • methodName – The name of the method to lookup.
Throws:
Returns:The cache of constructors.
/** * Retrieve the cache of methods for the specified class and method name. * * @param theClass the class to lookup. * @param methodName The name of the method to lookup. * @return The cache of constructors. * @throws ReflectorException in case of a lookup error. */
private Map<String, ?> getMethodMap( Class theClass, String methodName ) throws ReflectorException { Map<String, Method> methodMap; if ( theClass == null ) { return null; } String className = theClass.getName(); synchronized ( className.intern() ) { Map<String, Map<String, Method>> classMethods = classMaps.get( className ); if ( classMethods == null ) { classMethods = new HashMap<String, Map<String, Method>>(); methodMap = new HashMap<String, Method>(); classMethods.put( methodName, methodMap ); classMaps.put( className, classMethods ); } else { String key = className + "::" + methodName; synchronized ( key.intern() ) { methodMap = classMethods.get( methodName ); if ( methodMap == null ) { methodMap = new HashMap<String, Method>(); classMethods.put( methodName, methodMap ); } } } } return methodMap; } }