    001    /*****************************************************************************
    002     * Copyright (C) NanoContainer Organization. All rights reserved.            *
    003     * ------------------------------------------------------------------------- *
    004     * The software in this package is published under the terms of the BSD      *
    005     * style license a copy of which has been included with this distribution in *
    006     * the LICENSE.txt file.                                                     *
    007     *                                                                           *
    008     * Original code by Joerg Schaibe                                            *
    009     *****************************************************************************/
    010
    011    package org.picocontainer.gems.behaviors;
    012
    013    import com.thoughtworks.proxy.ProxyFactory;
    014    import com.thoughtworks.proxy.factory.StandardProxyFactory;
    015    import com.thoughtworks.proxy.toys.delegate.Delegating;
    016
    017    import org.picocontainer.ComponentAdapter;
    018    import org.picocontainer.PicoContainer;
    019    import org.picocontainer.PicoCompositionException;
    020    import org.picocontainer.behaviors.AbstractBehavior;
    021
    022    import java.lang.reflect.Method;
    023    import java.lang.reflect.Type;
    024
    025
    026    /**
    027     * ComponentAdapter that assimilates a component for a specific type.
    028     * <p>
    029     * Allows the instance of another {@link ComponentAdapter} to be converted into interface <code>type</code>, that the
    030     * instance is not assignable from. In other words the instance of the delegated adapter does NOT necessarily implement the
    031     * <code>type</code> interface.
    032     * </p>
    033     * <p>
    034     * For Example:
    035     * </p>
    036     * <code><pre>
    037     * public interface Foo {
    038     *     int size();
    039     * }
    040     *
    041     * public class Bar {
    042     *     public int size() {
    043     *         return 1;
    044     *     }
    045     * }
    046     *
    047     * new Assimilated(Foo.class, new InstanceAdapter(new Bar()));
    048     * </pre></code>
    049     * <p>
    050     * Notice how Bar does not implement the interface Foo. But Bar does have an identical <code>size()</code> method.
    051     * </p>
    052     * @author J&ouml;rg Schaible
    053     * @author Michael Ward
    054     */
    055    @SuppressWarnings("serial")
    056    public final class Assimilated<T> extends AbstractBehavior<T> {
    057
    058            private final Class<T> type;
    059        private final ProxyFactory proxyFactory;
    060        private final boolean isCompatible;
    061
    062        /**
    063         * Construct an Assimilated. The <code>type</code> may not implement the type of the component instance.
    064         * If the component instance <b>does</b> implement the interface, no proxy is used though.
    065         *
    066         * @param type The class type used as key.
    067         * @param delegate The delegated {@link ComponentAdapter}.
    068         * @param proxyFactory The {@link ProxyFactory} to use.
    069         * @throws PicoCompositionException Thrown if the <code>type</code> is not compatible and cannot be proxied.
    070         */
    071        @SuppressWarnings("unchecked")
    072        public Assimilated(final Class<T> type, final ComponentAdapter delegate, final ProxyFactory proxyFactory)
    073                throws PicoCompositionException {
    074            super(delegate);
    075            this.type = type;
    076            this.proxyFactory = proxyFactory;
    077            final Class<T> delegationType = delegate.getComponentImplementation();
    078            this.isCompatible = type.isAssignableFrom(delegationType);
    079            if (!isCompatible) {
    080                if (!proxyFactory.canProxy(type)) {
    081                    throw new PicoCompositionException("Cannot create proxy for type " + type.getName());
    082                }
    083                final Method[] methods = type.getMethods();
    084                for (final Method method : methods) {
    085                    try {
    086                        delegationType.getMethod(method.getName(), method.getParameterTypes());
    087                    } catch (final NoSuchMethodException e) {
    088                        throw new PicoCompositionException("Cannot create proxy for type "
    089                                                             + type.getName()
    090                                                             + ", because of incompatible method "
    091                                                             + method.toString());
    092                    }
    093                }
    094            }
    095        }
    096
    097        /**
    098         * Construct an Assimilated. The <code>type</code> may not implement the type of the component instance.
    099         * The implementation will use JDK {@link java.lang.reflect.Proxy} instances. If the component instant <b>does </b>
    100         * implement the interface, no proxy is used anyway.
    101         *
    102         * @param type The class type used as key.
    103         * @param delegate The delegated {@link ComponentAdapter}.
    104         *
    105         */
    106        @SuppressWarnings("unchecked")
    107            public Assimilated(final Class<T> type, final ComponentAdapter delegate) {
    108            this(type, delegate, new StandardProxyFactory());
    109        }
    110
    111        /**
    112         * Create and return a component instance. If the component instance and the type to assimilate is not compatible, a proxy
    113         * for the instance is generated, that implements the assimilated type.
    114         *
    115         * @see AbstractBehavior#getComponentInstance(org.picocontainer.PicoContainer, java.lang.Class into)
    116         */
    117        @Override
    118            public T getComponentInstance(final PicoContainer container, final Type into)
    119                throws PicoCompositionException  {
    120            return (T) (isCompatible ? super.getComponentInstance(container, into) : Delegating.object(
    121                    type, super.getComponentInstance(container, into), proxyFactory));
    122        }
    123
    124        public String getDescriptor() {
    125            return "Assimilated";
    126        }
    127
    128        /**
    129         * Return the type of the component. If the component type is not compatible with the type to assimilate, the assimilated
    130         * type is returned.
    131         *
    132         * @see AbstractBehavior#getComponentImplementation()
    133         */
    134        @Override
    135            public Class<T> getComponentImplementation() {
    136            return isCompatible ? super.getComponentImplementation() : type;
    137        }
    138
    139        /**
    140         * Return the key of the component. If the key of the delegated component is a type, that is not compatible with the type to
    141         * assimilate, then the assimilated type replaces the original type.
    142         *
    143         * @see AbstractBehavior#getComponentKey()
    144         */
    145        @Override
    146            public Object getComponentKey() {
    147            final Object key = super.getComponentKey();
    148            if (key instanceof Class && (!isCompatible || !type.isAssignableFrom((Class)key))) {
    149                return type;
    150            }
    151            return key;
    152        }
    153
    154    }