Author: jcarman Date: Thu Aug 1 19:57:57 2013 New Revision: 1509402 URL: http://svn.apache.org/r1509402 Log: Refactoring training context to support nested training.
Added: commons/proper/proxy/branches/version-2.0-work/stub/src/main/java/org/apache/commons/proxy2/stub/TrainingContext.java commons/proper/proxy/branches/version-2.0-work/stub/src/test/java/org/apache/commons/proxy2/stub/RetentionWrapper.java Modified: commons/proper/proxy/branches/version-2.0-work/stub/src/main/java/org/apache/commons/proxy2/stub/StubInterceptorBuilder.java commons/proper/proxy/branches/version-2.0-work/stub/src/main/java/org/apache/commons/proxy2/stub/Trainer.java commons/proper/proxy/branches/version-2.0-work/stub/src/test/java/org/apache/commons/proxy2/stub/StubBuilderTest.java commons/proper/proxy/branches/version-2.0-work/stub/src/test/java/org/apache/commons/proxy2/stub/StubInterceptorBuilderTest.java commons/proper/proxy/branches/version-2.0-work/stub/src/test/java/org/apache/commons/proxy2/stub/StubInterface.java Modified: commons/proper/proxy/branches/version-2.0-work/stub/src/main/java/org/apache/commons/proxy2/stub/StubInterceptorBuilder.java URL: http://svn.apache.org/viewvc/commons/proper/proxy/branches/version-2.0-work/stub/src/main/java/org/apache/commons/proxy2/stub/StubInterceptorBuilder.java?rev=1509402&r1=1509401&r2=1509402&view=diff ============================================================================== --- commons/proper/proxy/branches/version-2.0-work/stub/src/main/java/org/apache/commons/proxy2/stub/StubInterceptorBuilder.java (original) +++ commons/proper/proxy/branches/version-2.0-work/stub/src/main/java/org/apache/commons/proxy2/stub/StubInterceptorBuilder.java Thu Aug 1 19:57:57 2013 @@ -18,13 +18,9 @@ package org.apache.commons.proxy2.stub; import org.apache.commons.proxy2.Interceptor; -import org.apache.commons.proxy2.Invoker; import org.apache.commons.proxy2.ProxyFactory; -import org.apache.commons.proxy2.ProxyUtils; import org.apache.commons.proxy2.interceptor.SwitchInterceptor; -import java.lang.reflect.Method; - public class StubInterceptorBuilder { //---------------------------------------------------------------------------------------------------------------------- @@ -56,8 +52,8 @@ public class StubInterceptorBuilder { try { - TrainingContext.set(interceptor); - T stub = proxyFactory.createInvokerProxy(new TrainingContextInvoker(), type); + TrainingContext trainingContext = TrainingContext.set(proxyFactory); + T stub = trainingContext.push(type, interceptor); trainer.train(stub); } finally @@ -66,18 +62,4 @@ public class StubInterceptorBuilder } return this; } - -//---------------------------------------------------------------------------------------------------------------------- -// Inner Classes -//---------------------------------------------------------------------------------------------------------------------- - - private static final class TrainingContextInvoker implements Invoker - { - @Override - public Object invoke(Object proxy, Method method, Object[] arguments) throws Throwable - { - TrainingContext.getTrainingContext().stubMethodInvoked(method, arguments); - return ProxyUtils.nullValue(method.getReturnType()); - } - } } Modified: commons/proper/proxy/branches/version-2.0-work/stub/src/main/java/org/apache/commons/proxy2/stub/Trainer.java URL: http://svn.apache.org/viewvc/commons/proper/proxy/branches/version-2.0-work/stub/src/main/java/org/apache/commons/proxy2/stub/Trainer.java?rev=1509402&r1=1509401&r2=1509402&view=diff ============================================================================== --- commons/proper/proxy/branches/version-2.0-work/stub/src/main/java/org/apache/commons/proxy2/stub/Trainer.java (original) +++ commons/proper/proxy/branches/version-2.0-work/stub/src/main/java/org/apache/commons/proxy2/stub/Trainer.java Thu Aug 1 19:57:57 2013 @@ -44,7 +44,7 @@ public abstract class Trainer<T> private void record(ArgumentMatcher matcher) { - trainingContext().addArgumentMatcher(matcher); + trainingContext().record(matcher); } protected <R> R eq(R value) @@ -61,17 +61,17 @@ public abstract class Trainer<T> protected void thenThrow(Exception e) { - trainingContext().setInterceptor(InterceptorUtils.throwing(e)); + trainingContext().then(InterceptorUtils.throwing(e)); } protected void thenThrow(ObjectProvider<? extends Exception> provider) { - trainingContext().setInterceptor(InterceptorUtils.throwing(provider)); + trainingContext().then(InterceptorUtils.throwing(provider)); } private TrainingContext trainingContext() { - return TrainingContext.getTrainingContext(); + return TrainingContext.getCurrent(); } protected <R> WhenObject<R> when(R expression) @@ -130,21 +130,29 @@ public abstract class Trainer<T> protected abstract class BaseWhen<R> { + protected Trainer<T> thenStub(Class<R> type, Trainer<R> trainer) + { + R trainee = trainingContext().push(type); + trainer.train(trainee); + trainingContext().then(InterceptorUtils.constant(trainingContext().popStub(type))); + return Trainer.this; + } + protected Trainer<T> thenThrow(Exception e) { - trainingContext().setInterceptor(InterceptorUtils.throwing(e)); + trainingContext().then(InterceptorUtils.throwing(e)); return Trainer.this; } protected Trainer<T> thenThrow(ObjectProvider<? extends Exception> provider) { - trainingContext().setInterceptor(InterceptorUtils.throwing(provider)); + trainingContext().then(InterceptorUtils.throwing(provider)); return Trainer.this; } protected <R> Trainer<T> thenAnswer(ObjectProvider<? extends R> provider) { - trainingContext().setInterceptor(InterceptorUtils.provider(provider)); + trainingContext().then(InterceptorUtils.provider(provider)); return Trainer.this; } } @@ -153,7 +161,7 @@ public abstract class Trainer<T> { protected Trainer<T> thenReturn(boolean... values) { - trainingContext().setInterceptor(InterceptorUtils.constant(ArrayUtils.clone(values))); + trainingContext().then(InterceptorUtils.constant(ArrayUtils.clone(values))); return Trainer.this; } } @@ -162,7 +170,7 @@ public abstract class Trainer<T> { protected Trainer<T> thenReturn(byte... values) { - trainingContext().setInterceptor(InterceptorUtils.constant(ArrayUtils.clone(values))); + trainingContext().then(InterceptorUtils.constant(ArrayUtils.clone(values))); return Trainer.this; } } @@ -171,7 +179,7 @@ public abstract class Trainer<T> { protected Trainer<T> thenReturn(char... values) { - trainingContext().setInterceptor(InterceptorUtils.constant(ArrayUtils.clone(values))); + trainingContext().then(InterceptorUtils.constant(ArrayUtils.clone(values))); return Trainer.this; } } @@ -180,7 +188,7 @@ public abstract class Trainer<T> { protected Trainer<T> thenReturn(double... values) { - trainingContext().setInterceptor(InterceptorUtils.constant(ArrayUtils.clone(values))); + trainingContext().then(InterceptorUtils.constant(ArrayUtils.clone(values))); return Trainer.this; } } @@ -189,7 +197,7 @@ public abstract class Trainer<T> { protected Trainer<T> thenReturn(float... values) { - trainingContext().setInterceptor(InterceptorUtils.constant(ArrayUtils.clone(values))); + trainingContext().then(InterceptorUtils.constant(ArrayUtils.clone(values))); return Trainer.this; } } @@ -198,7 +206,7 @@ public abstract class Trainer<T> { protected Trainer<T> thenReturn(int... values) { - trainingContext().setInterceptor(InterceptorUtils.constant(ArrayUtils.clone(values))); + trainingContext().then(InterceptorUtils.constant(ArrayUtils.clone(values))); return Trainer.this; } } @@ -207,7 +215,7 @@ public abstract class Trainer<T> { protected Trainer<T> thenReturn(long... values) { - trainingContext().setInterceptor(InterceptorUtils.constant(ArrayUtils.clone(values))); + trainingContext().then(InterceptorUtils.constant(ArrayUtils.clone(values))); return Trainer.this; } } @@ -216,7 +224,7 @@ public abstract class Trainer<T> { protected Trainer<T> thenReturn(R value) { - trainingContext().setInterceptor(InterceptorUtils.constant(value)); + trainingContext().then(InterceptorUtils.constant(value)); return Trainer.this; } } @@ -225,7 +233,7 @@ public abstract class Trainer<T> { protected Trainer<T> thenReturn(R... values) { - trainingContext().setInterceptor(InterceptorUtils.constant(ArrayUtils.clone(values))); + trainingContext().then(InterceptorUtils.constant(ArrayUtils.clone(values))); return Trainer.this; } } @@ -234,7 +242,7 @@ public abstract class Trainer<T> { protected Trainer<T> thenReturn(short... values) { - trainingContext().setInterceptor(InterceptorUtils.constant(ArrayUtils.clone(values))); + trainingContext().then(InterceptorUtils.constant(ArrayUtils.clone(values))); return Trainer.this; } } Added: commons/proper/proxy/branches/version-2.0-work/stub/src/main/java/org/apache/commons/proxy2/stub/TrainingContext.java URL: http://svn.apache.org/viewvc/commons/proper/proxy/branches/version-2.0-work/stub/src/main/java/org/apache/commons/proxy2/stub/TrainingContext.java?rev=1509402&view=auto ============================================================================== --- commons/proper/proxy/branches/version-2.0-work/stub/src/main/java/org/apache/commons/proxy2/stub/TrainingContext.java (added) +++ commons/proper/proxy/branches/version-2.0-work/stub/src/main/java/org/apache/commons/proxy2/stub/TrainingContext.java Thu Aug 1 19:57:57 2013 @@ -0,0 +1,223 @@ +package org.apache.commons.proxy2.stub; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.proxy2.*; +import org.apache.commons.proxy2.interceptor.SwitchInterceptor; +import org.apache.commons.proxy2.interceptor.matcher.ArgumentMatcher; +import org.apache.commons.proxy2.interceptor.matcher.InvocationMatcher; +import org.apache.commons.proxy2.invoker.NullInvoker; +import org.apache.commons.proxy2.invoker.RecordedInvocation; + +import java.lang.reflect.Method; +import java.util.*; + +public class TrainingContext +{ +//---------------------------------------------------------------------------------------------------------------------- +// Fields +//---------------------------------------------------------------------------------------------------------------------- + + private static final ThreadLocal<TrainingContext> TRAINING_CONTEXT = new ThreadLocal<TrainingContext>(); + + private final ProxyFactory proxyFactory; + + private Deque<TrainingContextFrame<?>> frameDeque = new LinkedList<TrainingContextFrame<?>>(); + +//---------------------------------------------------------------------------------------------------------------------- +// Static Methods +//---------------------------------------------------------------------------------------------------------------------- + + public static void clear() + { + TRAINING_CONTEXT.remove(); + } + + public static TrainingContext getCurrent() + { + return TRAINING_CONTEXT.get(); + } + + public static TrainingContext set(ProxyFactory proxyFactory) + { + TrainingContext context = new TrainingContext(proxyFactory); + TRAINING_CONTEXT.set(context); + return context; + } + +//---------------------------------------------------------------------------------------------------------------------- +// Constructors +//---------------------------------------------------------------------------------------------------------------------- + + public TrainingContext(ProxyFactory proxyFactory) + { + this.proxyFactory = proxyFactory; + } + +//---------------------------------------------------------------------------------------------------------------------- +// Other Methods +//---------------------------------------------------------------------------------------------------------------------- + + private TrainingContextFrame<?> peek() + { + return frameDeque.peek(); + } + + <T> T popStub(Class<T> type) + { + return proxyFactory.createInterceptorProxy( + proxyFactory.createInvokerProxy(NullInvoker.INSTANCE, type), + frameDeque.pop().stubInterceptor, + type); + } + + <T> T push(Class<T> type) + { + return push(type, new SwitchInterceptor()); + } + + <T> T push(Class<T> type, SwitchInterceptor switchInterceptor) + { + TrainingContextFrame<T> frame = new TrainingContextFrame<T>(switchInterceptor); + Invoker invoker = new TrainingInvoker(frame); + frameDeque.push(frame); + return proxyFactory.createInvokerProxy(invoker, type); + } + + public void record(ArgumentMatcher argumentMatcher) + { + peek().argumentMatchers.add(argumentMatcher); + } + + public void then(Interceptor interceptor) + { + peek().then(interceptor); + } + +//---------------------------------------------------------------------------------------------------------------------- +// Inner Classes +//---------------------------------------------------------------------------------------------------------------------- + + private static final class ExactArgumentsMatcher implements InvocationMatcher + { + private final RecordedInvocation recordedInvocation; + + private ExactArgumentsMatcher(RecordedInvocation recordedInvocation) + { + this.recordedInvocation = recordedInvocation; + } + + @Override + public boolean matches(Invocation invocation) + { + return invocation.getMethod().equals(recordedInvocation.getInvokedMethod()) && + Arrays.deepEquals(invocation.getArguments(), recordedInvocation.getArguments()); + } + } + + private static final class MatchingArgumentsMatcher implements InvocationMatcher + { + private final RecordedInvocation recordedInvocation; + private final ArgumentMatcher[] matchers; + + private MatchingArgumentsMatcher(RecordedInvocation recordedInvocation, ArgumentMatcher[] matchers) + { + this.recordedInvocation = recordedInvocation; + this.matchers = ArrayUtils.clone(matchers); + } + + @Override + public boolean matches(Invocation invocation) + { + return invocation.getMethod().equals(recordedInvocation.getInvokedMethod()) && + allArgumentsMatch(invocation.getArguments()); + } + + private boolean allArgumentsMatch(Object[] arguments) + { + for (int i = 0; i < arguments.length; i++) + { + Object argument = arguments[i]; + if (!matchers[i].matches(argument)) + { + return false; + } + } + return true; + } + } + + private static class TrainingContextFrame<T> + { + private final String id = UUID.randomUUID().toString(); + + private final SwitchInterceptor stubInterceptor; + + private final List<ArgumentMatcher> argumentMatchers = new LinkedList<ArgumentMatcher>(); + + private InvocationMatcher matcher = null; + + private TrainingContextFrame(SwitchInterceptor stubInterceptor) + { + this.stubInterceptor = stubInterceptor; + } + + private String getId() + { + return id; + } + + void then(Interceptor thenInterceptor) + { + if (matcher == null) + { + throw new IllegalStateException("No when!"); + } + stubInterceptor.when(matcher).then(thenInterceptor); + matcher = null; + } + + void methodInvoked(Method method, Object[] arguments) + { + final ArgumentMatcher[] matchersArray = argumentMatchers.toArray(new ArgumentMatcher[argumentMatchers.size()]); + argumentMatchers.clear(); + final RecordedInvocation invocation = new RecordedInvocation(method, arguments); + if (ArrayUtils.isEmpty(matchersArray)) + { + this.matcher = new ExactArgumentsMatcher(invocation); + } + else if (matchersArray.length == arguments.length) + { + this.matcher = new MatchingArgumentsMatcher(invocation, matchersArray); + } + else + { + throw new IllegalStateException("Either use exact arguments or argument matchers, but not both."); + } + } + } + + private static class TrainingInvoker implements Invoker + { + private final String id; + + private TrainingInvoker(TrainingContextFrame<?> frame) + { + this.id = frame.getId(); + } + + @Override + public Object invoke(Object proxy, Method method, Object[] arguments) throws Throwable + { + final TrainingContextFrame<?> frame = getCurrent().peek(); + if (!frame.getId().equals(id)) + { + throw new IllegalStateException("Wrong stub!"); + } + else + { + frame.methodInvoked(method, arguments); + } + return ProxyUtils.nullValue(method.getReturnType()); + } + } +} Added: commons/proper/proxy/branches/version-2.0-work/stub/src/test/java/org/apache/commons/proxy2/stub/RetentionWrapper.java URL: http://svn.apache.org/viewvc/commons/proper/proxy/branches/version-2.0-work/stub/src/test/java/org/apache/commons/proxy2/stub/RetentionWrapper.java?rev=1509402&view=auto ============================================================================== --- commons/proper/proxy/branches/version-2.0-work/stub/src/test/java/org/apache/commons/proxy2/stub/RetentionWrapper.java (added) +++ commons/proper/proxy/branches/version-2.0-work/stub/src/test/java/org/apache/commons/proxy2/stub/RetentionWrapper.java Thu Aug 1 19:57:57 2013 @@ -0,0 +1,17 @@ +package org.apache.commons.proxy2.stub; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface RetentionWrapper +{ +//---------------------------------------------------------------------------------------------------------------------- +// Other Methods +//---------------------------------------------------------------------------------------------------------------------- + + Retention value(); +} Modified: commons/proper/proxy/branches/version-2.0-work/stub/src/test/java/org/apache/commons/proxy2/stub/StubBuilderTest.java URL: http://svn.apache.org/viewvc/commons/proper/proxy/branches/version-2.0-work/stub/src/test/java/org/apache/commons/proxy2/stub/StubBuilderTest.java?rev=1509402&r1=1509401&r2=1509402&view=diff ============================================================================== --- commons/proper/proxy/branches/version-2.0-work/stub/src/test/java/org/apache/commons/proxy2/stub/StubBuilderTest.java (original) +++ commons/proper/proxy/branches/version-2.0-work/stub/src/test/java/org/apache/commons/proxy2/stub/StubBuilderTest.java Thu Aug 1 19:57:57 2013 @@ -190,5 +190,11 @@ public class StubBuilderTest { } + + @Override + public StubInterface stub() + { + return null; + } } } Modified: commons/proper/proxy/branches/version-2.0-work/stub/src/test/java/org/apache/commons/proxy2/stub/StubInterceptorBuilderTest.java URL: http://svn.apache.org/viewvc/commons/proper/proxy/branches/version-2.0-work/stub/src/test/java/org/apache/commons/proxy2/stub/StubInterceptorBuilderTest.java?rev=1509402&r1=1509401&r2=1509402&view=diff ============================================================================== --- commons/proper/proxy/branches/version-2.0-work/stub/src/test/java/org/apache/commons/proxy2/stub/StubInterceptorBuilderTest.java (original) +++ commons/proper/proxy/branches/version-2.0-work/stub/src/test/java/org/apache/commons/proxy2/stub/StubInterceptorBuilderTest.java Thu Aug 1 19:57:57 2013 @@ -25,6 +25,8 @@ import org.apache.commons.proxy2.provide import org.junit.Before; import org.junit.Test; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import static org.junit.Assert.*; @@ -346,4 +348,99 @@ public class StubInterceptorBuilderTest assertEquals(null, proxy.two("Whatever")); assertEquals(null, proxy.one("Mismatch!")); } + + @Test + public void testStubReturn() + { + final StubInterface proxy = createProxy(new Trainer<StubInterface>() + { + @Override + protected void train(StubInterface stub) + { + when(stub.stub()).thenStub(StubInterface.class, new Trainer<StubInterface>() + { + @Override + protected void train(StubInterface stub) + { + when(stub.one("Hello")).thenReturn("World"); + } + }); + } + }); + assertNotNull(proxy.stub()); + assertEquals("World", proxy.stub().one("Hello")); + } + + @Test(expected = IllegalStateException.class) + public void testUsingWrongStub() + { + createProxy(new Trainer<StubInterface>() + { + @Override + protected void train(final StubInterface parent) + { + when(parent.stub()).thenStub(StubInterface.class, new Trainer<StubInterface>() + { + @Override + protected void train(final StubInterface child) + { + when(parent.one("Hello")).thenReturn("World"); + } + }); + } + }); + } + + @Test(expected = IllegalStateException.class) + public void testThenBeforeWhen() + { + createProxy(new Trainer<StubInterface>() + { + @Override + protected void train(StubInterface stub) + { + thenThrow(new RuntimeException("Oops!")); + } + }); + } + + @Test + public void testWithNestedAnnotations() + { + Interceptor interceptor = builder.trainFor(RetentionWrapper.class, new Trainer<RetentionWrapper>() + { + @Override + protected void train(RetentionWrapper stub) + { + + when(stub.value()).thenStub(Retention.class, new Trainer<Retention>() + { + @Override + protected void train(Retention stub) + { + when(stub.value()).thenReturn(RetentionPolicy.RUNTIME); + } + }); + } + }).build(); + RetentionWrapper wrapper = proxyFactory.createInterceptorProxy(proxyFactory.createInvokerProxy(NullInvoker.INSTANCE), interceptor, RetentionWrapper.class); + assertNotNull(wrapper.value()); + assertEquals(RetentionPolicy.RUNTIME, wrapper.value().value()); + } + + @Test + public void testWithSimpleAnnotations() + { + Interceptor interceptor = builder.trainFor(Retention.class, new Trainer<Retention>() + { + @Override + protected void train(Retention stub) + { + when(stub.value()).thenReturn(RetentionPolicy.RUNTIME); + } + }).build(); + Retention wrapper = proxyFactory.createInterceptorProxy(proxyFactory.createInvokerProxy(NullInvoker.INSTANCE), interceptor, Retention.class); + assertEquals(RetentionPolicy.RUNTIME, wrapper.value()); + } + } Modified: commons/proper/proxy/branches/version-2.0-work/stub/src/test/java/org/apache/commons/proxy2/stub/StubInterface.java URL: http://svn.apache.org/viewvc/commons/proper/proxy/branches/version-2.0-work/stub/src/test/java/org/apache/commons/proxy2/stub/StubInterface.java?rev=1509402&r1=1509401&r2=1509402&view=diff ============================================================================== --- commons/proper/proxy/branches/version-2.0-work/stub/src/test/java/org/apache/commons/proxy2/stub/StubInterface.java (original) +++ commons/proper/proxy/branches/version-2.0-work/stub/src/test/java/org/apache/commons/proxy2/stub/StubInterface.java Thu Aug 1 19:57:57 2013 @@ -40,4 +40,6 @@ public interface StubInterface public String arrayParameter(String... strings); public void voidMethod(String arg); + + public StubInterface stub(); }