/** * This implementation is variation from {@link #tee(Class, Object...)}, read that documentation * first. * <p> * The behavior modifies from that implementation is that all instances will be called by a * executor. This means that invocations into the returned instance will return immediately, * but instance invocation will happen async. It is guaranteed that any single instance will NOT * be invoked in parallel. Thus meaning that if you invoke into the proxy two times before * a give instance finishes processing the first invocation, the second invocation will queue and * only be invoked after the first finishes. * <p> * Under the hood {@link DefaultExecutorListenerHelper} is used to provide this behavior. * * @param <T> Type representing interface to multiple invocations of * @param executor Executor that instances will be invoked on to * @param teeInterface Interface class for which the returned instance must implement * @param instances Instances of said interface which invocations should be multiplied to * @return A returned interface which will map all invocations to all provided interfaces */ public static <T> T teeWithExecutor(Executor executor, Class<? super T> teeInterface, T ... instances) { return setupHelper(new DefaultExecutorListenerHelper<>(teeInterface, executor), instances); }
@Test (expected = IllegalArgumentException.class) public void teeCreateFail() { InvocationTee.tee(null); }
@Test (expected = IllegalArgumentException.class) public void teeWithExceptionThrowingCreateFail() { InvocationTee.teeWithExceptionThrowing(null); }
@Test public void teeWithExecutorCreateFail() { try { InvocationTee.teeWithExecutor(null, Runnable.class); fail("Exception should have thrown"); } catch (IllegalArgumentException e) { // expected } try { InvocationTee.teeWithExecutor(SameThreadSubmitterExecutor.instance(), null); fail("Exception should have thrown"); } catch (IllegalArgumentException e) { // expected } }
@Test public void invokeWithExecutorTest() { TestRunnable tr = new TestRunnable(); Runnable r = InvocationTee.teeWithExecutor(SameThreadSubmitterExecutor.instance(), Runnable.class, null, tr); r.run(); // should run just like the normal version assertTrue(tr.ranOnce()); } }
/** * This implementation is variation from {@link #tee(Class, Object...)}, read that documentation * first. * <p> * The behavior modifies from that implementation is that all instances will be called by a * executor. This means that invocations into the returned instance will return immediately, * but instance invocation will happen async. It is guaranteed that any single instance will NOT * be invoked in parallel. Thus meaning that if you invoke into the proxy two times before * a give instance finishes processing the first invocation, the second invocation will queue and * only be invoked after the first finishes. * <p> * Under the hood {@link DefaultExecutorListenerHelper} is used to provide this behavior. * * @param <T> Type representing interface to multiple invocations of * @param executor Executor that instances will be invoked on to * @param teeInterface Interface class for which the returned instance must implement * @param instances Instances of said interface which invocations should be multiplied to * @return A returned interface which will map all invocations to all provided interfaces */ public static <T> T teeWithExecutor(Executor executor, Class<? super T> teeInterface, T ... instances) { return setupHelper(new DefaultExecutorListenerHelper<>(teeInterface, executor), instances); }
@Test public void invokeTest() { TestRunnable tr = new TestRunnable(); Runnable r = InvocationTee.tee(Runnable.class, null, tr); r.run(); assertTrue(tr.ranOnce()); }
@Test public void invokeWithExceptionThrowingTest() { TestRunnable tr = new TestRunnable(); Runnable r = InvocationTee.teeWithExceptionThrowing(Runnable.class, null, tr); r.run(); // should run just like the normal version assertTrue(tr.ranOnce()); }
/** * This creates a tee proxy for a given class with a set of instances of said interface. Any * invocation to the returned instance (proxy), will be multiple to all provided instances. If * provided arguments are mutable, and an instance mutates that argument, future invoked * instances may see that modification. It is not deterministic which order the instances will * be invoked in. * <p> * If any listeners throw an exception it will be delegated to * {@link org.threadly.util.ExceptionUtils#handleException(Throwable)}. So a listener throwing * an exception will NOT interrupt other listeners from being invoked. If you want you can * handle thrown exceptions by setting an {@link org.threadly.util.ExceptionHandler} into * {@link org.threadly.util.ExceptionUtils}. Make sure the handler is set it in a way that makes * sense based off what thread will be invoking the returned interface. * <p> * Under the hood this depends on {@link ListenerHelper}, and thus has the same limitations. * Most specifically the provided {@code teeInterface} must in fact be an interface, and not a * an abstract class, or other non-interface types. In addition any invocations called to this * must be a {@code void} return type. * * @param <T> Type representing interface to multiple invocations of * @param teeInterface Interface class for which the returned instance must implement * @param instances Instances of said interface which invocations should be multiplied to * @return A returned interface which will map all invocations to all provided interfaces */ public static <T> T tee(Class<? super T> teeInterface, T ... instances) { return setupHelper(new ListenerHelper<>(teeInterface), instances); }
@Test public void invokeListenerExceptionTest() { TestRunnable tr = new TestRuntimeFailureRunnable(); Runnable r = InvocationTee.tee(Runnable.class, null, tr); r.run(); // no exception should be thrown assertTrue(tr.ranOnce()); }
@Test public void invokeWithExceptionThrowingListenerExceptionTest() { RuntimeException thrown = new SuppressedStackRuntimeException(); TestRunnable tr = new TestRuntimeFailureRunnable(thrown); Runnable r = InvocationTee.teeWithExceptionThrowing(Runnable.class, null, tr); try { r.run(); fail("Exception should have thrown"); } catch (RuntimeException e) { assertTrue(thrown == e); } }
/** * This creates a tee proxy for a given class with a set of instances of said interface. Any * invocation to the returned instance (proxy), will be multiple to all provided instances. If * provided arguments are mutable, and an instance mutates that argument, future invoked * instances may see that modification. It is not deterministic which order the instances will * be invoked in. * <p> * If any listeners throw an exception it will be delegated to * {@link org.threadly.util.ExceptionUtils#handleException(Throwable)}. So a listener throwing * an exception will NOT interrupt other listeners from being invoked. If you want you can * handle thrown exceptions by setting an {@link org.threadly.util.ExceptionHandler} into * {@link org.threadly.util.ExceptionUtils}. Make sure the handler is set it in a way that makes * sense based off what thread will be invoking the returned interface. * <p> * Under the hood this depends on {@link ListenerHelper}, and thus has the same limitations. * Most specifically the provided {@code teeInterface} must in fact be an interface, and not a * an abstract class, or other non-interface types. In addition any invocations called to this * must be a {@code void} return type. * * @param <T> Type representing interface to multiple invocations of * @param teeInterface Interface class for which the returned instance must implement * @param instances Instances of said interface which invocations should be multiplied to * @return A returned interface which will map all invocations to all provided interfaces */ public static <T> T tee(Class<? super T> teeInterface, T ... instances) { return setupHelper(new ListenerHelper<>(teeInterface), instances); }
@Test public void invokeTwiceTest() { TestRunnable tr1 = new TestRunnable(); TestRunnable tr2 = new TestRunnable(); Runnable r = InvocationTee.tee(Runnable.class, tr1, null, tr2, null); r.run(); r.run(); assertEquals(2, tr1.getRunCount()); assertEquals(2, tr2.getRunCount()); }
return setupHelper(lh, instances);
return setupHelper(lh, instances);