Package net.sourceforge.stripes.controller

Source Code of net.sourceforge.stripes.controller.DefaultObjectFactory$DefaultConstructorWrapper

/* 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;
    }
}
TOP

Related Classes of net.sourceforge.stripes.controller.DefaultObjectFactory$DefaultConstructorWrapper

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.