001 /*
002 * (c) 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-Feb-2005
010 */
011 package com.thoughtworks.proxy.toys.dispatch;
012
013 import static com.thoughtworks.proxy.toys.delegate.DelegationMode.DIRECT;
014
015 import java.io.IOException;
016 import java.io.InvalidObjectException;
017 import java.io.ObjectInputStream;
018 import java.io.ObjectOutputStream;
019 import java.lang.reflect.Method;
020 import java.util.ArrayList;
021 import java.util.Arrays;
022 import java.util.HashSet;
023 import java.util.List;
024 import java.util.Set;
025
026 import com.thoughtworks.proxy.Invoker;
027 import com.thoughtworks.proxy.ProxyFactory;
028 import com.thoughtworks.proxy.factory.InvokerReference;
029 import com.thoughtworks.proxy.factory.StandardProxyFactory;
030 import com.thoughtworks.proxy.kit.ObjectReference;
031 import com.thoughtworks.proxy.kit.ReflectionUtils;
032 import com.thoughtworks.proxy.toys.delegate.DelegatingInvoker;
033
034
035 /**
036 * Invoker that dispatches all invocations to different objects according the membership of the method.
037 *
038 * @author Jörg Schaible after idea by Rickard Öberg
039 * @since 0.2
040 */
041 public class DispatchingInvoker implements Invoker {
042 private static final long serialVersionUID = 1L;
043 private List<Class<?>> types;
044 private Invoker[] invokers;
045 private transient Set<Method>[] methodSets;
046 private transient Method[] toStringMethods;
047
048 /**
049 * Construct a DispatchingInvoker.
050 *
051 * @param proxyFactory the {@link ProxyFactory} to use
052 * @param types the types of the generated proxy
053 * @param delegateReferences the {@link ObjectReference ObjectReferences} for the delegates
054 * @since 0.2
055 */
056 public DispatchingInvoker(
057 final ProxyFactory proxyFactory, final Class<?>[] types, final ObjectReference<Object>[] delegateReferences) {
058 this.types = Arrays.asList(types);
059 invokers = new Invoker[types.length];
060 toStringMethods = new Method[types.length];
061 @SuppressWarnings("unchecked")
062 Set<Method>[] sets = new Set[types.length];
063 methodSets = sets;
064 for (int i = 0; i < types.length; i++) {
065 for (final ObjectReference<Object> delegateReference : delegateReferences) {
066 if (types[i].isAssignableFrom(delegateReference.get().getClass())) {
067 invokers[i] = new DelegatingInvoker<Object>(proxyFactory, delegateReference, DIRECT);
068 methodSets[i] = new HashSet<Method>(Arrays.asList(types[i].getMethods()));
069 for (Method method : methodSets[i]) {
070 if (method.getName().equals("toString") && method.getParameterTypes().length == 0) {
071 toStringMethods[i] = method;
072 break;
073 }
074 }
075 break;
076 }
077 }
078 if (invokers[i] == null) {
079 throw new DispatchingException("Cannot dispatch type " + types[i].getName(), types[i]);
080 }
081 }
082 }
083
084 /**
085 * Constructor used by pure reflection serialization.
086 *
087 * @since 0.2
088 */
089 protected DispatchingInvoker() {
090 }
091
092 public Object invoke(final Object proxy, Method method, final Object[] args) throws Throwable {
093 if (method.equals(ReflectionUtils.equals)) {
094 final Object arg = args[0];
095 if (new StandardProxyFactory().isProxyClass(arg.getClass())
096 && (InvokerReference.class.cast(arg)).getInvoker() instanceof DispatchingInvoker) {
097 final DispatchingInvoker invoker = DispatchingInvoker.class.cast((InvokerReference.class.cast(arg)).getInvoker());
098 if (types.size() == invoker.types.size()) {
099 boolean isEqual = true;
100 for (int i = 0; isEqual && i < types.size(); ++i) {
101 final Class<?> type = types.get(i);
102 for (int j = 0; isEqual && j < invoker.types.size(); ++j) {
103 if (invoker.types.get(j).equals(type)) {
104 if (!invokers[i].equals(invoker.invokers[j])) {
105 isEqual = false;
106 }
107 }
108 }
109 }
110 return isEqual;
111 }
112 }
113 return Boolean.FALSE;
114 } else if (method.equals(ReflectionUtils.hashCode)) {
115 return hashCode();
116 } else if (method.equals(ReflectionUtils.toString)) {
117 for (int i = 0; i < invokers.length; i++) {
118 Method toString = toStringMethods[i];
119 if (toString != null && toString.getDeclaringClass().isAssignableFrom(proxy.getClass())) {
120 return invokers[i].invoke(proxy, method, args);
121 }
122 }
123 return types.toString();
124 } else {
125 for (int i = 0; i < invokers.length; i++) {
126 if (methodSets[i].contains(method)) {
127 return invokers[i].invoke(proxy, method, args);
128 }
129 }
130 }
131 throw new RuntimeException("Cannot dispatch method " + method.getName());
132 }
133
134 private void writeObject(final ObjectOutputStream out) throws IOException {
135 out.defaultWriteObject();
136 @SuppressWarnings("unchecked")
137 final List<Class<?>>[] types = new List[methodSets.length];
138 @SuppressWarnings("unchecked")
139 final List<String>[] names = new List[methodSets.length];
140 @SuppressWarnings("unchecked")
141 final List<Class<?>[]>[] arguments = new List[methodSets.length];
142 for (int i = 0; i < methodSets.length; i++) {
143 final Method[] methods = methodSets[i].toArray(new Method[methodSets[i].size()]);
144 types[i] = new ArrayList<Class<?>>();
145 names[i] = new ArrayList<String>();
146 arguments[i] = new ArrayList<Class<?>[]>();
147 for (Method method : methods) {
148 types[i].add(method.getDeclaringClass());
149 names[i].add(method.getName());
150 arguments[i].add(method.getParameterTypes());
151 }
152 }
153 out.writeObject(types);
154 out.writeObject(names);
155 out.writeObject(arguments);
156 }
157
158 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
159 in.defaultReadObject();
160 @SuppressWarnings("unchecked")
161 final List<Class<?>>[] types = List[].class.cast(in.readObject());
162 @SuppressWarnings("unchecked")
163 final List<String>[] names = List[].class.cast(in.readObject());
164 @SuppressWarnings("unchecked")
165 final List<Class<?>[]>[] arguments = List[].class.cast(in.readObject());
166 @SuppressWarnings("unchecked")
167 final Set<Method>[] set = new Set[types.length];
168 methodSets = set;
169 toStringMethods = new Method[types.length];
170 try {
171 for (int i = 0; i < methodSets.length; i++) {
172 methodSets[i] = new HashSet<Method>();
173 for (int j = 0; j < types[i].size(); j++) {
174 final Class<?> type = types[i].get(j);
175 final String name = names[i].get(j);
176 final Class<?>[] argumentTypes = arguments[i].get(j);
177 final Method method = type.getMethod(name, argumentTypes);
178 methodSets[i].add(method);
179 if (name.equals("toString") && argumentTypes.length == 0) {
180 toStringMethods[i] = method;
181 }
182 }
183 }
184 } catch (final NoSuchMethodException e) {
185 throw new InvalidObjectException(e.getMessage());
186 }
187 }
188 }