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
011package org.picocontainer.gems.behaviors;
012
013import com.thoughtworks.proxy.ProxyFactory;
014import com.thoughtworks.proxy.factory.StandardProxyFactory;
015import com.thoughtworks.proxy.toys.delegate.Delegating;
016
017import org.picocontainer.ComponentAdapter;
018import org.picocontainer.PicoContainer;
019import org.picocontainer.PicoCompositionException;
020import org.picocontainer.behaviors.AbstractBehavior;
021
022import java.lang.reflect.Method;
023import 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")
056public 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<? extends 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}