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: - ReflectorException – In case anything goes wrong here...
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: - ReflectorException – In case anything goes wrong here...
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: - ReflectorException – In case of an error looking up or invoking the method.
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: - ReflectorException – In case of an error looking up or invoking the method.
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: - ReflectorException – In case we can't retrieve the proper constructor.
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: - ReflectorException – In case we can't retrieve the proper method.
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: - ReflectorException – in case of a lookup error.
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: - ReflectorException – in case of a lookup error.
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;
}
}