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 }