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ö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 }