/* Copyright 2008-2009 Ben Gunter
*
* 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 net.sourceforge.stripes.controller;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import net.sourceforge.stripes.config.Configuration;
import net.sourceforge.stripes.config.TargetTypes;
import net.sourceforge.stripes.exception.StripesRuntimeException;
import net.sourceforge.stripes.util.Log;
import net.sourceforge.stripes.util.ReflectUtil;
import net.sourceforge.stripes.util.TypeHandlerCache;
/**
* <p>
* An implementation of {@link ObjectFactory} that simply calls {@link Class#newInstance()} to
* obtain a new instance.
* </p>
*
* @author Ben Gunter
* @since Stripes 1.5.1
*/
@SuppressWarnings("unchecked")
public class DefaultObjectFactory implements ObjectFactory {
/**
* An implementation of {@link ConstructorWrapper} that calls back to
* {@link DefaultObjectFactory#newInstance(Constructor, Object...)} to instantiate a class.
*/
public static class DefaultConstructorWrapper<T> implements ConstructorWrapper<T> {
private ObjectFactory factory;
private Constructor<T> constructor;
/**
* Wrap the given constructor.
*
* @param factory The object factory whose
* {@link ObjectFactory#newInstance(Constructor, Object...)} method will be
* called when invoking the constructor.
* @param constructor The constructor to wrap.
*/
public DefaultConstructorWrapper(ObjectFactory factory, Constructor<T> constructor) {
this.factory = factory;
this.constructor = constructor;
}
/** Get the {@link Constructor} object wrapped by this instance. */
public Constructor<T> getConstructor() {
return constructor;
}
/** Invoke the constructor with the specified arguments and return the new object. */
public T newInstance(Object... args) {
return factory.newInstance(constructor, args);
}
}
private static final Log log = Log.getInstance(DefaultObjectFactory.class);
/**
* Holds a map of commonly used interface types (mostly collections) to a class that implements
* the interface and will, by default, be instantiated when an instance of the interface is
* needed.
*/
protected final Map<Class<?>, Class<?>> interfaceImplementations = new HashMap<Class<?>, Class<?>>();
{
interfaceImplementations.put(Collection.class, ArrayList.class);
interfaceImplementations.put(List.class, ArrayList.class);
interfaceImplementations.put(Set.class, HashSet.class);
interfaceImplementations.put(SortedSet.class, TreeSet.class);
interfaceImplementations.put(Queue.class, LinkedList.class);
interfaceImplementations.put(Map.class, HashMap.class);
interfaceImplementations.put(SortedMap.class, TreeMap.class);
}
private Configuration configuration;
private TypeHandlerCache<List<ObjectPostProcessor>> postProcessors;
/** Does nothing. */
public void init(Configuration configuration) throws Exception {
this.configuration = configuration;
}
/** Get the {@link Configuration} that was passed into {@link #init(Configuration)}. */
public Configuration getConfiguration() {
return configuration;
}
/**
* Register a post-processor that will be allowed to manipulate instances of {@code targetType}
* after they are created and before they are returned. The types to which the post-processor
* will apply are determined by the value of the {@link TargetTypes} annotation on the class. If
* there is no such annotation, then the post-processor will process all instances created by
* the object factory.
*
* @param postProcessor The post-processor to use.
*/
public synchronized void addPostProcessor(ObjectPostProcessor postProcessor) {
// The cache will be null by default to indicate that there are no post-processors
if (postProcessors == null) {
postProcessors = new TypeHandlerCache<List<ObjectPostProcessor>>();
}
// Determine target types from type arguments
List<Class<?>> targetTypes = new ArrayList<Class<?>>();
Type[] typeArguments = ReflectUtil.getActualTypeArguments(postProcessor.getClass(),
ObjectPostProcessor.class);
if ((typeArguments != null) && (typeArguments.length == 1)
&& !typeArguments[0].equals(Object.class)) {
if (typeArguments[0] instanceof Class) {
targetTypes.add((Class<?>) typeArguments[0]);
}
else {
log.warn("Type parameter for non-abstract post-processor [", postProcessor
.getClass().getName(), "] is not a class.");
}
}
// Determine target types from annotation; if no annotation then process everything
TargetTypes annotation = postProcessor.getClass().getAnnotation(TargetTypes.class);
if (annotation != null)
targetTypes.addAll(Arrays.asList(annotation.value()));
// Default to Object
if (targetTypes.isEmpty())
targetTypes.add(Object.class);
// Register post-processor for each target type
for (Class<?> targetType : targetTypes) {
List<ObjectPostProcessor> list = postProcessors.getHandler(targetType);
if (list == null) {
list = new ArrayList<ObjectPostProcessor>();
postProcessors.add(targetType, list);
}
log.debug("Adding post-processor of type ", postProcessor.getClass().getName(),
" for ", targetType);
list.add(postProcessor);
}
postProcessor.setObjectFactory(this);
}
/**
* Calls {@link Class#newInstance()} and returns the newly created object.
*
* @param clazz The class to instantiate.
* @return The new object
*/
public <T> T newInstance(Class<T> clazz) {
try {
if (clazz.isInterface())
return postProcess(newInterfaceInstance(clazz));
else
return postProcess(clazz.newInstance());
}
catch (InstantiationException e) {
throw new StripesRuntimeException("Could not instantiate " + clazz, e);
}
catch (IllegalAccessException e) {
throw new StripesRuntimeException("Could not instantiate " + clazz, e);
}
}
/**
* Attempts to determine an implementing class for the interface provided and instantiate it
* using a default constructor.
*
* @param interfaceType an interface (or abstract class) to make an instance of
* @return an instance of the interface type supplied
* @throws InstantiationException if no implementation type has been configured
* @throws IllegalAccessException if thrown by the JVM during class instantiation
*/
public <T> T newInterfaceInstance(Class<T> interfaceType) throws InstantiationException,
IllegalAccessException {
Class impl = getImplementingClass(interfaceType);
if (impl == null) {
throw new InstantiationException(
"Stripes needed to instantiate a property who's declared type as an " +
"interface (which obviously cannot be instantiated. The interface is not " +
"one that Stripes is aware of, so no implementing class was known. The " +
"interface type was: '" + interfaceType.getName() + "'. To fix this " +
"you'll need to do one of three things. 1) Change the getter/setter methods " +
"to use a concrete type so that Stripes can instantiate it. 2) in the bean's " +
"setContext() method pre-instantiate the property so Stripes doesn't have to. " +
"3) Bug the Stripes author ;) If the interface is a JDK type it can easily be " +
"fixed. If not, if enough people ask, a generic way to handle the problem " +
"might get implemented.");
}
else {
return newInstance((Class<T>) impl);
}
}
/**
* Looks up the default implementing type for the supplied interface. This is done based on a
* static map of known common interface types and implementing classes.
*
* @param iface an interface for which an implementing class is needed
* @return a Class object representing the implementing type, or null if one is not found
*/
public Class<?> getImplementingClass(Class<?> iface) {
return interfaceImplementations.get(iface);
}
/**
* Register a class as the default implementation of an interface. The implementation class will
* be returned from future calls to {@link #getImplementingClass(Class)} when the argument is
* {@code iface}.
*
* @param iface The interface class
* @param impl The implementation class
*/
public <T> void addImplementingClass(Class<T> iface, Class<? extends T> impl) {
if (!iface.isInterface())
throw new IllegalArgumentException("Class " + iface.getName() + " is not an interface");
else
interfaceImplementations.put(iface, impl);
}
/**
* Create a new instance of {@code clazz} by looking up the specified constructor and passing it
* and its parameters to {@link #newInstance(Constructor, Object...)}.
*
* @param clazz The class to instantiate.
* @param constructorArgTypes The type parameters of the constructor to be invoked. (See
* {@link Class#getConstructor(Class...)}.)
* @param constructorArgs The parameters to pass to the constructor. (See
* {@link Constructor#newInstance(Object...)}.)
* @return A new instance of the class.
*/
public <T> T newInstance(Class<T> clazz, Class<?>[] constructorArgTypes,
Object[] constructorArgs) {
try {
Constructor<T> constructor = clazz.getConstructor(constructorArgTypes);
return postProcess(newInstance(constructor, constructorArgs));
}
catch (SecurityException e) {
throw new StripesRuntimeException("Could not instantiate " + clazz, e);
}
catch (NoSuchMethodException e) {
throw new StripesRuntimeException("Could not instantiate " + clazz, e);
}
catch (IllegalArgumentException e) {
throw new StripesRuntimeException("Could not instantiate " + clazz, e);
}
}
/**
* Calls {@link Constructor#newInstance(Object...)} with the given parameters, passes the new
* object to {@link #postProcess(Object)} and returns it.
*
* @param constructor The constructor to invoke.
* @param params The parameters to pass to the constructor.
*/
public <T> T newInstance(Constructor<T> constructor, Object... params) {
try {
return postProcess(constructor.newInstance(params));
}
catch (InstantiationException e) {
throw new StripesRuntimeException("Could not invoke constructor " + constructor, e);
}
catch (IllegalAccessException e) {
throw new StripesRuntimeException("Could not invoke constructor " + constructor, e);
}
catch (InvocationTargetException e) {
throw new StripesRuntimeException("Could not invoke constructor " + constructor, e);
}
}
/**
* Get a {@link ConstructorWrapper} that wraps the constructor for the given class that accepts
* parameters of the given types.
*
* @param clazz The class to look up the constructor in.
* @param parameterTypes The parameter types that the constructor accepts.
*/
public <T> DefaultConstructorWrapper<T> constructor(Class<T> clazz, Class<?>... parameterTypes) {
try {
return new DefaultConstructorWrapper<T>(this, clazz.getConstructor(parameterTypes));
}
catch (SecurityException e) {
throw new StripesRuntimeException("Could not instantiate " + clazz, e);
}
catch (NoSuchMethodException e) {
throw new StripesRuntimeException("Could not instantiate " + clazz, e);
}
}
/**
* Perform post-processing on objects created by {@link #newInstance(Class)} or
* {@link #newInstance(Class, Class[], Object[])}. Subclasses that do not need to change the way
* objects are instantiated but do need to do something to the objects before returning them may
* override this method to achieve that.
*
* @param object A newly created object.
* @return The given object, unchanged.
*/
protected <T> T postProcess(T object) {
if (postProcessors != null) {
List<ObjectPostProcessor> list = postProcessors.getHandler(object.getClass());
if (list != null) {
for (ObjectPostProcessor postProcessor : list) {
object = (T) postProcessor.postProcess(object);
}
}
}
return object;
}
}