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     */
011    package com.thoughtworks.proxy.toys.delegate;
012    
013    import static com.thoughtworks.proxy.toys.delegate.DelegationMode.DIRECT;
014    import static com.thoughtworks.proxy.toys.delegate.DelegationMode.SIGNATURE;
015    
016    import java.io.IOException;
017    import java.io.ObjectInputStream;
018    import java.lang.reflect.InvocationTargetException;
019    import java.lang.reflect.Method;
020    import java.util.HashMap;
021    import java.util.Map;
022    
023    import com.thoughtworks.proxy.Invoker;
024    import com.thoughtworks.proxy.ProxyFactory;
025    import com.thoughtworks.proxy.factory.StandardProxyFactory;
026    import com.thoughtworks.proxy.kit.ObjectReference;
027    import com.thoughtworks.proxy.kit.ReflectionUtils;
028    import 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     */
045    public 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    }