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øy
039 * @author Paul Hammant
040 * @author Dan North
041 * @author Jö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 }