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 */
011package com.thoughtworks.proxy.toys.multicast;
012
013import java.lang.reflect.Array;
014import java.lang.reflect.Method;
015import java.util.ArrayList;
016import java.util.List;
017
018import com.thoughtworks.proxy.Invoker;
019import com.thoughtworks.proxy.ProxyFactory;
020import 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 */
032public 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}