
com.healthmarketscience.common.util.ReflectionFactory Maven / Gradle / Ivy
/*
Copyright (c) 2007 Health Market Science, Inc.
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.
*/
package com.healthmarketscience.common.util;
import java.util.Collections;
import java.util.List;
import org.apache.commons.lang.ArrayUtils;
/**
* Instead of writing custom factories for custom types, this provides a
* single factory implementation that uses reflection to instantiate the
* desired objects. The factory supports two-stage construction of the
* objects by defining a "configure" method name. One custom exception type
* will be thrown by the creation method.
*
* Note, there are a few methods that may be overridden to further customize
* the factory's behavior, see {@link #getClassLoader}, {@link #expandType},
* and {@link #throwException}.
*
* @author James Ahlborn
*/
public class ReflectionFactory
{
private final Class extends RetType> _requiredType;
private final Class extends ExType> _exType;
private final String _constructorMethodName;
private final Class>[] _constructorParamTypes;
private final String _configureMethodName;
private final Class>[] _configureParamTypes;
/**
* @param requiredType created classes must be of this type
* @param exType the type of exception to throw on failure. must have
* public constructor of the form
* {@code (String, Throwable)}
* @param constructorParamTypes the constructor parameter types for the
* instantiated classes, may be
* {@code null}
*/
public ReflectionFactory(Class extends RetType> requiredType,
Class extends ExType> exType,
Class>... constructorParamTypes) {
this(requiredType, exType, null, constructorParamTypes, null, null);
}
/**
* @param requiredType created classes must be of this type
* @param exType the type of exception to throw on failure. must have
* public constructor of the form
* {@code (String, Throwable)}
* @param configureMethodName iff non-{@code null} the name of a public
* method that must be declared on the created
* instance, which does second-stage
* initialization. otherwise, no second-stage
* initialization is attempted
*/
public ReflectionFactory(Class extends RetType> requiredType,
Class extends ExType> exType,
String configureMethodName) {
this(requiredType, exType, null, null, configureMethodName, null);
}
/**
* @param requiredType created classes must be of this type
* @param exType the type of exception to throw on failure. must have
* public constructor of the form
* {@code (String, Throwable)}
* @param constructorParamTypes the constructor parameter types for the
* instantiated classes, may be
* {@code null}
* @param configureMethodName iff non-{@code null} the name of a public
* method that must be declared on the created
* instance, which does second-stage
* initialization. otherwise, no second-stage
* initialization is attempted
* @param configureParamTypes the second-stage initialization parameter
* types for the instantiated classes, may be
* {@code null}
*/
public ReflectionFactory(Class extends RetType> requiredType,
Class extends ExType> exType,
Class>[] constructorParamTypes,
String configureMethodName,
Class>[] configureParamTypes) {
this(requiredType, exType, null, constructorParamTypes,
configureMethodName, configureParamTypes);
}
/**
* @param requiredType created classes must be of this type
* @param exType the type of exception to throw on failure. must have
* public constructor of the form
* {@code (String, Throwable)}
* @param constructorMethodName iff non-{@code null} the name of a public
* static method that must be declared on the
* classes, which is used to construct the
* instances. otherwise, a constructor is used
* @param constructorParamTypes the constructor parameter types for the
* instantiated classes, may be
* {@code null}. The caller should not modify
* this array after passing it in.
* @param configureMethodName iff non-{@code null} the name of a public
* method that must be declared on the created
* instance, which does second-stage
* initialization. otherwise, no second-stage
* initialization is attempted
* @param configureParamTypes the second-stage initialization parameter
* types for the instantiated classes, may be
* {@code null}. The caller should not modify this
* array after passing it in.
*/
public ReflectionFactory(Class extends RetType> requiredType,
Class extends ExType> exType,
String constructorMethodName,
Class>[] constructorParamTypes,
String configureMethodName,
Class>[] configureParamTypes) {
_requiredType = requiredType;
_exType = exType;
_constructorMethodName = constructorMethodName;
_constructorParamTypes = constructorParamTypes;
_configureMethodName = configureMethodName;
_configureParamTypes = configureParamTypes;
}
protected Class extends RetType> getRequiredType() {
return _requiredType;
}
protected Class extends ExType> getExceptionType() {
return _exType;
}
protected String getConstructorMethodName() {
return _constructorMethodName;
}
protected Class>[] getConstructorParamTypes() {
return _constructorParamTypes;
}
protected String getConfigureMethodName() {
return _configureMethodName;
}
protected Class>[] getConfigureParamTypes() {
return _configureParamTypes;
}
/**
* May be overridden to choose an alternate classloader for loading classes
* of the instantiated objects.
* @return classloader to use to load classes for reflective object
* instantiation
*/
protected ClassLoader getClassLoader() {
return this.getClass().getClassLoader();
}
/**
* May be overridden to provide "custom" expansions of the given type name
* (default package names, etc). All output type names are expected to be
* fully-qualified class names. The first type name to result in an actual
* Class will be used for the object creation attempt. Default
* implementation merely returns the given string.
* @return a list of strings which will be tried in order as prefixes for
* loading classes
*/
protected List expandType(String typeName)
throws ExType
{
return Collections.singletonList(typeName);
}
/**
* Constructs an object of the given type with the given constructor params.
* @throws ExType due to some failure during object creation
*/
public RetType create(String typeName,
Object... constructorParams)
throws ExType
{
return create(typeName, constructorParams, (Object[])null);
}
/**
* Constructs and optionally configures an object of the given type with the
* given constructor params and optional configuration params.
* @throws ExType due to some failure during object creation
*/
public RetType create(String typeName,
Object[] constructorParams,
Object[] configureParams)
throws ExType
{
if((getConfigureMethodName() == null) &&
(!ArrayUtils.isEmpty(configureParams))) {
throw new IllegalArgumentException(
"configure params given but no configure method defined");
}
try {
List expandedTypeNames = expandType(typeName);
Class> tmpClazz = null;
for(String expandedTypeName : expandedTypeNames) {
try {
tmpClazz = Class.forName(expandedTypeName, true, getClassLoader());
// found a legitimate class
typeName = expandedTypeName;
break;
} catch(ClassNotFoundException e) { // NOPMD
// keep trying
}
}
if(tmpClazz == null) {
// we could not find any classes of the given type
throwException("failed to find type(s) " + expandedTypeNames, null);
// we should never get here
throw new UnreachableStatementException();
}
// attempt to coerce to required type
Class extends RetType> objClazz =
tmpClazz.asSubclass(getRequiredType());
// attempt to instantiate using either constructor or static constructor
// method
RetType obj =
((getConstructorMethodName() == null) ?
(RetType)(objClazz.getConstructor(getConstructorParamTypes())
.newInstance(constructorParams)) :
(getRequiredType().cast(
objClazz.getMethod(getConstructorMethodName(),
getConstructorParamTypes())
.invoke(null, constructorParams))));
if(getConfigureMethodName() != null) {
// attempt to configure
obj.getClass().getMethod(
getConfigureMethodName(), getConfigureParamTypes())
.invoke(obj, configureParams);
}
// all good!
return obj;
} catch(Exception e) {
// wrap in correct exception type
throwException("failed instantiating type " + typeName, e);
// we should never get here
throw new UnreachableStatementException(e);
}
}
/**
* Must throw an exception, either of the desired type for this factory or a
* RuntimeException (or Error). Default implementation rethrows the given
* exception if it is already the correct type, otherwise reflectively
* constructs the desired exception type using the
* {@code (String, Throwable)} constructor. If that
* construction fails, a RuntimeException is thrown.
*
* @param msg message for the new exception
* @param t optional Throwable cause for the new exception
* @throws ExType due to some failure during object creation
*/
@SuppressWarnings("PMD.AvoidThrowingRawExceptionTypes")
protected void throwException(String msg, Throwable t)
throws ExType
{
if(getExceptionType().isInstance(t)) {
throw _exType.cast(t);
}
ExType newEx = null;
try {
newEx = getExceptionType().getConstructor(String.class, Throwable.class)
.newInstance(msg, t);
} catch(Exception e) {
throw new RuntimeException("failed instantiating exception " +
getExceptionType(), e);
}
throw newEx;
}
}