001/*
002 * (c) 2003-2005, 2009, 2010 ThoughtWorks Ltd
003 * All rights reserved.
004 *
005 * The software in this package is published under the terms of the BSD
006 * style license a copy of which has been included with this distribution in
007 * the LICENSE.txt file.
008 * 
009 * Created on 24-May-2004
010 */
011package com.thoughtworks.proxy.toys.delegate;
012
013import static com.thoughtworks.proxy.toys.delegate.DelegationMode.DIRECT;
014import static com.thoughtworks.proxy.toys.delegate.DelegationMode.SIGNATURE;
015
016import java.io.IOException;
017import java.io.ObjectInputStream;
018import java.lang.reflect.InvocationTargetException;
019import java.lang.reflect.Method;
020import java.util.HashMap;
021import java.util.Map;
022
023import com.thoughtworks.proxy.Invoker;
024import com.thoughtworks.proxy.ProxyFactory;
025import com.thoughtworks.proxy.factory.StandardProxyFactory;
026import com.thoughtworks.proxy.kit.ObjectReference;
027import com.thoughtworks.proxy.kit.ReflectionUtils;
028import com.thoughtworks.proxy.kit.SimpleReference;
029
030
031/**
032 * Invoker that delegates method calls to an object.
033 * <p>
034 * This forms the basis of many other proxy toys. The delegation behavior was factored out of
035 * <tt>HotSwappingInvoker</tt>.
036 * </p>
037 *
038 * @author Aslak Helles&oslash;y
039 * @author Paul Hammant
040 * @author Dan North
041 * @author J&ouml;rg Schaible
042 * @see com.thoughtworks.proxy.toys.hotswap.HotSwappingInvoker
043 * @since 0.1
044 */
045public class DelegatingInvoker<T> implements Invoker {
046
047    private static final long serialVersionUID = 1L;
048    private transient Map<Method, Method> methodCache;
049    private ProxyFactory proxyFactory;
050    private ObjectReference<T> delegateReference;
051    private DelegationMode delegationMode;
052
053    /**
054     * Construct a DelegatingInvoker.
055     *
056     * @param proxyFactory      the {@link ProxyFactory} to use
057     * @param delegateReference the {@link ObjectReference} of the delegate
058     * @param delegationMode    one of the delegation modes
059     * @throws IllegalArgumentException if the <tt>delegationMode</tt> is not one of the predefined constants of
060     *                                  {@link Delegating}
061     * @since 1.0
062     */
063    public DelegatingInvoker(final ProxyFactory proxyFactory, final ObjectReference<T> delegateReference,
064                             final DelegationMode delegationMode) {
065        this.proxyFactory = proxyFactory;
066        this.delegateReference = delegateReference;
067        this.delegationMode = delegationMode;
068        this.methodCache = new HashMap<Method, Method>();
069    }
070
071    /**
072     * Construct a DelegatingInvoker with a {@link StandardProxyFactory} and {@link DelegationMode#SIGNATURE}.
073     *
074     * @param delegate the delegated object
075     * @since 0.1
076     */
077    public DelegatingInvoker(final T delegate) {
078        this(new StandardProxyFactory(), new SimpleReference<T>(delegate), SIGNATURE);
079    }
080
081    public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
082        final Object result;
083        Object delegate = delegate();
084
085        // equals(...) and hashCode()
086        if (method.equals(ReflectionUtils.equals)) {
087            // Note: equals will normally compare the classes directly, so we have to dereference
088            // all delegates and then swap the call (in case of our argument is also a delegation proxy).
089            final Object arg = args[0];
090            while (delegate != null && proxyFactory.isProxyClass(delegate.getClass())) {
091                Invoker invoker = proxyFactory.getInvoker(delegate);
092                if (invoker instanceof DelegatingInvoker<?>) {
093                    delegate = DelegatingInvoker.class.cast(invoker).delegate();
094                }
095            }
096            if (arg == null) {
097                result = delegate == null;
098            } else {
099                result = arg.equals(delegate);
100            }
101        } else if (method.equals(ReflectionUtils.hashCode)) {
102            // equals and hashCode must be consistent
103            result = delegate == null ? 47 : delegate.hashCode();
104
105            // null delegatable
106        } else if (delegate == null) {
107            result = null;
108
109            // regular method call
110        } else {
111            Method methodToCall = methodCache.get(method);
112            if (methodToCall == null) {
113                methodToCall = getMethodToInvoke(method, args);
114                methodCache.put(method, methodToCall);
115            }
116            result = invokeOnDelegate(methodToCall, args);
117        }
118        return result;
119    }
120
121    /**
122     * Retrieve the delegated object in derived classes.
123     *
124     * @return the delegated object
125     */
126    protected T delegate() {
127        return delegateReference.get();
128    }
129
130    /**
131     * Lookup a matching method. The lookup will only be done once for every method called on the proxy.
132     *
133     * @param method the invoked method on the proxy
134     * @param args   the arguments for the invocation
135     * @return the matching method
136     * @throws DelegationException if no matching method can be found
137     * @since 0.2
138     */
139    protected Method getMethodToInvoke(final Method method, final Object[] args) {
140        if (delegationMode == DIRECT) {
141            return method;
142        } else {
143            final String methodName = method.getName();
144            try {
145                return delegate().getClass().getMethod(methodName, method.getParameterTypes());
146            } catch (Exception e) {
147                throw new DelegationException("Unable to find method " + methodName, e, delegate());
148            }
149        }
150    }
151
152    /**
153     * Invoke the given method on the delegate.
154     *
155     * @param method the method to invoke
156     * @param args   the arguments for the invocation
157     * @return the method's result
158     * @throws InvocationTargetException if the invoked method throws any exception
159     * @since 0.1
160     */
161    protected Object invokeOnDelegate(final Method method, final Object[] args) throws InvocationTargetException {
162        final Object delegate = delegate();
163        try {
164            return method.invoke(delegate, args);
165        } catch (InvocationTargetException e) {
166            throw e;
167        } catch (Exception e) {
168            throw new DelegationException("Problem invoking " + method, e, delegate);
169        }
170    }
171
172    /**
173     * Retrieve the {@link ObjectReference} of the delegate.
174     *
175     * @return the reference of the delegate
176     * @since 0.2
177     */
178    protected ObjectReference<T> getDelegateReference() {
179        return this.delegateReference;
180    }
181
182    /**
183     * Retrieve the {@link ProxyFactory} to use.
184     *
185     * @return the ProxyFactory
186     * @since 0.2
187     */
188    protected ProxyFactory getProxyFactory() {
189        return this.proxyFactory;
190    }
191
192    /**
193     * Compares a DelegatingInvoker with another one for equality. Two DelegatingInvoker are equal, if they have both
194     * the same <tt>delegation mode</tt> and their delegees are equal.
195     *
196     * @see java.lang.Object#equals(java.lang.Object)
197     * @since 0.2
198     */
199    @Override
200    public boolean equals(final Object obj) {
201        if (obj instanceof DelegatingInvoker<?>) {
202            final DelegatingInvoker<?> invoker = DelegatingInvoker.class.cast(obj);
203            return invoker.delegationMode == delegationMode && delegate().equals(invoker.delegate());
204        }
205        return false;
206    }
207
208    @Override
209    public int hashCode() {
210        final Object delegate = delegate();
211        return delegationMode.hashCode()
212            * (delegate == null ? System.identityHashCode(this) : delegate.hashCode());
213    }
214
215    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
216        in.defaultReadObject();
217        methodCache = new HashMap<Method, Method>();
218    }
219}