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    }