package org.archstudio.bna.logics.events; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.List; import org.archstudio.bna.IBNAWorld; import org.archstudio.bna.logics.AbstractThingLogic; import org.archstudio.bna.utils.BNAUtils; import org.archstudio.sysutils.Finally; import org.eclipse.jdt.annotation.Nullable; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; /** * Creates {@link #getProxyForInterface(Class) proxy objects} implementing a given interface which repeat method calls * made to the proxy object on all logics and {@link #addObject(Object) additional objects} implementing that interface * as well. * <p/> * This is useful for sending notification events to all logics from external sources. A listener passed in to the * interface can be used to add the proxy objects as a listener to its respective source of events. * * @author sahendrickson@gmail.com (Scott A. Hendrickson) */ public class ProxyLogic extends AbstractThingLogic { /** The listener to receive proxy object creation events. */ private final IProxyLogicListener proxyLogicListener; /** Additional objects on which to repeat calls made to the proxy objects. */ private final List<Object> additionalObjects = Lists.newCopyOnWriteArrayList(); /** * Creates proxy objects on demand that implement a given interface. Calls to these proxy object will be repeated on * all logics in the same world and all additional objects in {@link #additionalObjects} that implement the * interface. */ private final LoadingCache<Class<?>, Object> proxiesCache = CacheBuilder.newBuilder().build(new CacheLoader<Class<?>, Object>() { @Override public Object load(final Class<?> input) throws Exception { Object proxy = Proxy.newProxyInstance(input.getClassLoader(), new Class[] { input }, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try (Finally lock = BNAUtils.lock()) { for (Object o : logics.getThingLogics(input)) { method.invoke(o, args); } for (Object o : Iterables.filter(additionalObjects, input)) { method.invoke(o, args); } if ("equals".equals(method.getName()) && args.length == 1) { return super.equals(args[0]); } if ("hashCode".equals(method.getName()) && args.length == 0) { return super.hashCode(); } if ("toString".equals(method.getName()) && args.length == 0) { return super.toString(); } return null; } } }); if (proxyLogicListener != null) { try { proxyLogicListener.proxyObjectCreated(ProxyLogic.this, proxy); } catch (Exception ignored) { ignored.printStackTrace(); } } return proxy; } }); /** * Creates a new instance of {@link ProxyLogic} for the given world with the provided listener. * * @param world The world that will contain the logic. * @param proxyLogicListener A listener that should be notified about the creation of proxy logic events. */ public ProxyLogic(IBNAWorld world, @Nullable IProxyLogicListener proxyLogicListener) { super(world); BNAUtils.checkLock(); this.proxyLogicListener = proxyLogicListener; if (proxyLogicListener != null) { try { proxyLogicListener.proxyLogicCreated(this); } catch (Exception ignored) { ignored.printStackTrace(); } } } /** * Creates a new instance of {@link ProxyLogic} for the given world with a <code>null</code> listener. * * @param world The world that will contain the logic. */ public ProxyLogic(IBNAWorld world) { this(world, null); } /** * Adds an additional object for which proxy object invocations should be repeated. * * @param object An object that should be invoked when the proxy objects are invoked. * @return The object. */ public <T> T addObject(T object) { BNAUtils.checkLock(); additionalObjects.add(object); return object; } /** * Creates a proxy object implementing the given interface. Calls to these proxy object will also be made on all * logics in the same world and all {@link #addObject(Object) additional objects} that implement the interface. * * @param interfaceClass The interface for which methods should be delegated. * @return a proxy object implementing the given interface. */ @SuppressWarnings("unchecked") public <T> T getProxyForInterface(Class<T> interfaceClass) { BNAUtils.checkLock(); return (T) proxiesCache.getUnchecked(interfaceClass); } }