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 11-May-2004
010     */
011    package com.thoughtworks.proxy.toys.multicast;
012    
013    import java.lang.reflect.Array;
014    import java.lang.reflect.Method;
015    import java.util.ArrayList;
016    import java.util.List;
017    
018    import com.thoughtworks.proxy.Invoker;
019    import com.thoughtworks.proxy.ProxyFactory;
020    import com.thoughtworks.proxy.kit.ReflectionUtils;
021    
022    /**
023     * A {@link Invoker} implementation that multicasts calls to multiple targets. Proxies generated by this class will
024     * forward all method invocations to an array of underlying objects. The behavior is recursive, so return values will
025     * also be multicasting objects.
026     *
027     * @author Aslak Hellesøy
028     * @author Chris Stevenson
029     * @author Jörg Schaible
030     * @since 0.1
031     */
032    public class MulticastingInvoker<T> implements Invoker {
033        private static final long serialVersionUID = 1L;
034        private static final Method multicastTargetsDirect;
035        private static final Method multicastTargetsIndirect;
036        private static final Method getTargetsInArray;
037        private static final Method getTargetsInTypedArray;
038    
039        static {
040            try {
041                multicastTargetsDirect = Multicast.class.getMethod("multicastTargets", Method.class, Object[].class);
042                multicastTargetsIndirect = Multicast.class.getMethod("multicastTargets", Class.class, String.class, Object[].class);
043                getTargetsInArray = Multicast.class.getMethod("getTargetsInArray");
044                getTargetsInTypedArray = Multicast.class.getMethod("getTargetsInArray", Class.class);
045            } catch (NoSuchMethodException e) {
046                throw new ExceptionInInitializerError(e.toString());
047            }
048        }
049    
050        private Class<?>[] types;
051        private ProxyFactory proxyFactory;
052        private Object[] targets;
053    
054        /**
055         * Construct a MulticastingInvoker.
056         *
057         * @param type         the implemented types
058         * @param proxyFactory the {@link ProxyFactory} to use
059         * @param targets      the target instances where the proxy delegates a call
060         * @since 0.1
061         */
062        public MulticastingInvoker(final Class<?>[] type, final ProxyFactory proxyFactory, final Object[] targets) {
063            this.types = type;
064            this.proxyFactory = proxyFactory;
065            this.targets = targets;
066        }
067    
068        /**
069         * Create a proxy for this Invoker.
070         *
071         * @return the new proxy
072         * @since 0.1
073         */
074        public T proxy() {
075            final Class<?>[] classes;
076            int i;
077            i = types.length;
078            while (--i >= 0 && types[i] != Multicast.class) {
079            }
080            if (i < 0) {
081                classes = new Class[types.length + 1];
082                if (classes.length > 1) {
083                    System.arraycopy(types, 0, classes, 1, types.length);
084                }
085                classes[0] = Multicast.class;
086            } else {
087                classes = types;
088            }
089            return proxyFactory.<T>createProxy(this, classes);
090        }
091    
092        public Object invoke(final Object proxy, Method method, Object[] args) throws Throwable {
093            if (getTargetsInArray.equals(method)) {
094                return targets;
095            } else if (getTargetsInTypedArray.equals(method)) {
096                final Object[] elements = Object[].class.cast(Array.newInstance(Class.class.cast(args[0]), targets.length));
097                System.arraycopy(targets, 0, elements, 0, targets.length);
098                return elements;
099            } else if (multicastTargetsDirect.equals(method)) {
100                method = (Method) args[0];
101                args = (Object[]) args[1];
102            } else if (multicastTargetsIndirect.equals(method)) {
103                final Object[] newArgs = args[2] == null ? new Object[0] : Object[].class.cast(args[2]);
104                method = ReflectionUtils.getMatchingMethod(Class.class.cast(args[0]), String.class.cast(args[1]), newArgs);
105                args = newArgs;
106            }
107            final List<Object> invocationResults = new ArrayList<Object>();
108            for (Object target : targets) {
109                if (method.getDeclaringClass().isInstance(target)) {
110                    Object result = method.invoke(target, args);
111                    if (result != null) {
112                        invocationResults.add(result);
113                    }
114                }
115            }
116            if (invocationResults.size() == 0) {
117                return null;
118            } else if (invocationResults.size() == 1) {
119                return invocationResults.get(0);
120            } else if (method.getReturnType().equals(byte.class)) {
121                return addBytes(invocationResults.toArray());
122            } else if (method.getReturnType().equals(char.class)) {
123                return addChars(invocationResults.toArray());
124            } else if (method.getReturnType().equals(short.class)) {
125                return addShorts(invocationResults.toArray());
126            } else if (method.getReturnType().equals(int.class)) {
127                return addIntegers(invocationResults.toArray());
128            } else if (method.getReturnType().equals(long.class)) {
129                return addLongs(invocationResults.toArray());
130            } else if (method.getReturnType().equals(float.class)) {
131                return addFloats(invocationResults.toArray());
132            } else if (method.getReturnType().equals(double.class)) {
133                return addDoubles(invocationResults.toArray());
134            } else if (method.getReturnType().equals(boolean.class)) {
135                return andBooleans(invocationResults.toArray());
136            } else {
137                return Multicasting.proxy(invocationResults.toArray()).build(proxyFactory);
138            }
139        }
140    
141        private static Byte addBytes(final Object[] args) {
142            byte result = 0;
143            for (Object arg : args) {
144                result += Byte.class.cast(arg);
145            }
146            return result;
147        }
148    
149        private static Character addChars(final Object[] args) {
150            char result = 0;
151            for (Object arg : args) {
152                result += Character.class.cast(arg);
153            }
154            return result;
155        }
156    
157        private static Short addShorts(final Object[] args) {
158            short result = 0;
159            for (Object arg : args) {
160                result += Short.class.cast(arg);
161            }
162            return result;
163        }
164    
165        private static Integer addIntegers(final Object[] args) {
166            int result = 0;
167            for (Object arg : args) {
168                result += Integer.class.cast(arg);
169            }
170            return result;
171        }
172    
173        private static Long addLongs(final Object[] args) {
174            long result = 0;
175            for (Object arg : args) {
176                result += Long.class.cast(arg);
177            }
178            return result;
179        }
180    
181        private static Float addFloats(final Object[] args) {
182            float result = 0;
183            for (Object arg : args) {
184                result += Float.class.cast(arg);
185            }
186            return result;
187        }
188    
189        private static Double addDoubles(final Object[] args) {
190            double result = 0;
191            for (Object arg : args) {
192                result += Double.class.cast(arg);
193            }
194            return result;
195        }
196    
197        private static Boolean andBooleans(final Object[] args) {
198            for (Object arg : args) {
199                if (!Boolean.class.cast(arg)) {
200                    return Boolean.FALSE;
201                }
202            }
203            return Boolean.TRUE;
204        }
205    }