Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to write a general purpose multiplexer in Java?

I've got a lot of listeners that are registered using setListener methods, rather than addListener. So in order to allow multiple listeners to register to an object, I've got to use multiplexers. That's fine, but now I've got to create a multiplexer for every listener interface I have. So my question is: is it possible to implement Mux.create() as required for the following code?

AppleListener appleListener1 = new AppleProcessorA();
AppleListener appleListener2 = new AppleProcessorB();
AppleListener appleListenerMux = Mux.create(appleListener1, appleListener2);
Apple apple = new Apple();
apple.setListener(appleListenerMux);

OrangeListener orangeListener1 = new OrangeProcessorA();
OrangeListener orangeListener2 = new OrangeProcessorB();
OrangeListener orangeListenerMux = Mux.create(orangeListener1, orangeListener2);
Orange apple = new Orange();
orange.setListener(orangeListenerMux);

class Mux {
   public static <T> T create(T... outputs) { }
}

I imagine this might be possible using reflection. Is there any reason using reflection would be a bad idea? (performance comes to mind)

like image 307
Dylan P Avatar asked Apr 08 '13 05:04

Dylan P


1 Answers

It's possible using a dynamic Proxy.

The easiest way is to also pass in the desired interface as the first parameter to your Mux.create() call. Otherwise, you'll have to use reflection to attempt to guess the desired interface from all the concrete listener instances provided (hard to determine if all listener objects implement several interfaces in common).

Here's the short of it:

public class Mux {

    /**
     * @param targetInterface
     *            the interface to create a proxy for
     * @param instances
     *            the concrete instances to delegate to
     * @return a proxy that'll delegate to all the arguments
     */
    @SuppressWarnings("unchecked")
    public static <T> T create(Class<T> targetInterface, final T... instances) {
        ClassLoader classLoader = targetInterface.getClassLoader();
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method m, Object[] args)
                    throws Throwable {
                for (T instance : instances) {
                    m.invoke(instance, args);
                }
                return null;
            }
        };
        return (T) Proxy.newProxyInstance(classLoader,
                new Class<?>[] { targetInterface }, handler);
    }
}

Which you would use, for example, as follows:

Apple apple = new Apple();
AppleListener l1 = new AppleListenerA();
AppleListener l2 = new AppleListenerB();
apple.setListener(Mux.create(AppleListener.class, l1, l2));
apple.doSomething(); // will notify all listeners

This works by simply creating a dynamic Proxy that is cast to the target type T. That proxy uses an InvocationHandler that merely delegates all method calls to the proxy to given concrete instances.

Note that while in general I finalize all parameters and local variables wherever possible, I only finalized T... instances in this case to highlight the fact that if instances was not final, then referencing it within an anonymous inner class wouldn't be allowed (you'll get a "Cannot refer to a non-final variable args inside an inner class defined in a different method").

Also note that the above assumes that the actual method calls don't return any meaningful (or useful) values, hence the handler also returns null for all method calls. You'll need to add quite a bit more code if you want to collect return values and return those as well in a meaningful way.


Alternatively, one can inspect all given instances to determine the common interfaces they all implement, and pass all those to newProxyInstance(). This makes Mux.create() a lot more convenient to use, at the loss of some control over its behavior.

/**
 * @param instances
 *            the arguments
 * @return a proxy that'll delegate to all the arguments
 */
@SuppressWarnings("unchecked")
public static <T> T create(final T... instances) {

    // Inspect common interfaces
    final Set<Class<?>> commonInterfaces = new HashSet<Class<?>>();
    commonInterfaces.addAll(Arrays.asList(instances[0].getClass()
            .getInterfaces()));

    // Or skip instances[0]
    for (final T instance : instances) {
        commonInterfaces.retainAll(Arrays.asList(instance.getClass()
                .getInterfaces()));
    }

    // Or use ClassLoader.getSystemClassLoader();
    final ClassLoader classLoader = instances[0].getClass().getClassLoader();

    // magic
    final InvocationHandler handler = new InvocationHandler() {
        @Override
        public Object invoke(final Object proxy, final Method m, final Object[] args)
                throws Throwable {
            for (final T instance : instances) {
                m.invoke(instance, args);
            }
            return null;
        }
    };

    final Class<?>[] targetInterfaces = commonInterfaces
            .toArray(new Class<?>[commonInterfaces.size()]);
    return (T) Proxy.newProxyInstance(classLoader, targetInterfaces,
            handler);
}
like image 68
Alistair A. Israel Avatar answered Oct 21 '22 18:10

Alistair A. Israel