Author: mbenson Date: Thu Mar 24 16:13:04 2011 New Revision: 1085021 URL: http://svn.apache.org/viewvc?rev=1085021&view=rev Log: handle stubbed methods w/o args separately from methods with args
Modified: commons/proper/proxy/branches/version-2.0-work/stub/src/main/java/org/apache/commons/proxy2/stub/StubInterceptor.java Modified: commons/proper/proxy/branches/version-2.0-work/stub/src/main/java/org/apache/commons/proxy2/stub/StubInterceptor.java URL: http://svn.apache.org/viewvc/commons/proper/proxy/branches/version-2.0-work/stub/src/main/java/org/apache/commons/proxy2/stub/StubInterceptor.java?rev=1085021&r1=1085020&r2=1085021&view=diff ============================================================================== --- commons/proper/proxy/branches/version-2.0-work/stub/src/main/java/org/apache/commons/proxy2/stub/StubInterceptor.java (original) +++ commons/proper/proxy/branches/version-2.0-work/stub/src/main/java/org/apache/commons/proxy2/stub/StubInterceptor.java Thu Mar 24 16:13:04 2011 @@ -18,10 +18,14 @@ package org.apache.commons.proxy2.stub; import java.lang.reflect.Method; -import java.util.ArrayDeque; +import java.util.ArrayList; import java.util.Arrays; -import java.util.Deque; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.commons.lang3.Pair; import org.apache.commons.lang3.reflect.TypeUtils; import org.apache.commons.proxy2.Interceptor; import org.apache.commons.proxy2.Invocation; @@ -41,26 +45,21 @@ abstract class StubInterceptor implement /** Serialization version */ private static final long serialVersionUID = 1L; + /** + * This is an interface because we plan to add more sophisticated stub matching in the future. + */ private interface InvocationMatcher { boolean matches(Invocation invocation); } - private static abstract class Result { - InvocationMatcher invocationMatcher; - - Result(InvocationMatcher invocationMatcher) { - super(); - this.invocationMatcher = invocationMatcher; - } - - abstract Object getResult() throws Throwable; + private interface Result { + Object getResult() throws Throwable; } - private static class Answer extends Result { + private static class Answer implements Result { private Object answer; - Answer(InvocationMatcher invocationMatcher, Object answer) { - super(invocationMatcher); + Answer(Object answer) { this.answer = answer; } @@ -73,30 +72,32 @@ abstract class StubInterceptor implement } } - private static class Throw extends Result { + private static class Throw implements Result { private ObjectProvider<? extends Throwable> throwableProvider; /** * Create a new Throw instance. * @param invocationMatcher */ - Throw(InvocationMatcher invocationMatcher, ObjectProvider<? extends Throwable> throwableProvider) { - super(invocationMatcher); + Throw(ObjectProvider<? extends Throwable> throwableProvider) { this.throwableProvider = throwableProvider; } /** * {@inheritDoc} */ - @Override - Object getResult() throws Throwable { + public Object getResult() throws Throwable { throw throwableProvider.getObject(); } } private boolean complete; private RecordedInvocation currentInvocation; - private Deque<Result> resultStack = new ArrayDeque<Result>(); + private Map<String, Result> noArgResults = new HashMap<String, Result>(); + + // we generalize to the List interface here so that we can replace an empty set of results with a shared immutable instance: + private List<Pair<InvocationMatcher, ? extends Result>> matchingResultStack = + new ArrayList<Pair<InvocationMatcher, ? extends Result>>(); /** * Create a new StubInterceptor instance. @@ -110,10 +111,17 @@ abstract class StubInterceptor implement */ public Object intercept(Invocation invocation) throws Throwable { if (complete) { - for (Result result : resultStack) { - if (result.invocationMatcher.matches(invocation)) { + if (invocation.getMethod().getParameterTypes().length == 0) { + Result result = noArgResults.get(invocation.getMethod().getName()); + if (result != null) { return result.getResult(); } + } else { + for (Pair<InvocationMatcher, ? extends Result> pair : matchingResultStack) { + if (pair.getLeftElement().matches(invocation)) { + return pair.getRightElement().getResult(); + } + } } return interceptFallback(invocation); } @@ -133,74 +141,81 @@ abstract class StubInterceptor implement * Provide a return value to the currently stubbed method. * @param o {@link ObjectProvider} or hard value */ - void addAnswer(Object o) { - resultStack.push(validAnswer(o)); + synchronized void addAnswer(Object o) { + assertCanAddResult(); + Method m = currentInvocation.getInvokedMethod(); + boolean valid; + if (o instanceof ObjectProvider<?>) { + //compiler checked: + valid = true; + } else { + valid = acceptsValue(m, o); + } + if (!valid) { + throw new IllegalArgumentException(String.format("%s does not specify a valid return value for %s", o, + m)); + } + addResult(new Answer(o)); } /** * Respond to the currently stubbed method with a thrown exception. * @param throwableProvider */ - void addThrow(ObjectProvider<? extends Throwable> throwableProvider) { - resultStack.push(new Throw(currentMatcher(), throwableProvider)); + synchronized void addThrow(ObjectProvider<? extends Throwable> throwableProvider) { + assertCanAddResult(); + addResult(new Throw(throwableProvider)); } - private synchronized InvocationMatcher currentMatcher() { + private void assertCanAddResult() { if (complete) { throw new IllegalStateException("Answers not permitted; stubbing already marked as complete."); } if (currentInvocation == null) { throw new IllegalStateException("No ongoing stubbing found for any method"); } - try { - final RecordedInvocation recordedInvocation = currentInvocation; - return new InvocationMatcher() { - - public boolean matches(Invocation invocation) { - return invocation.getMethod().getName().equals(recordedInvocation.getInvokedMethod().getName()) - && Arrays.equals(invocation.getArguments(), recordedInvocation.getArguments()); - } - - }; - } finally { - currentInvocation = null; - } } - /** - * Validate and return the requested answer to the current invocation. - * @param o - * @return Answer - */ - synchronized Answer validAnswer(Object o) { - if (currentInvocation == null) { - //fall through and let currentMatcher() throw the exception - } else { - Method m = currentInvocation.getInvokedMethod(); - boolean valid; - if (o instanceof ObjectProvider<?>) { - //compiler checked: - valid = true; + private void addResult(Result result) { + try { + if (currentInvocation.getInvokedMethod().getParameterTypes().length == 0) { + //match on method name only: + noArgResults.put(currentInvocation.getInvokedMethod().getName(), result); } else { - valid = acceptsValue(m, o); - } - if (!valid) { - throw new IllegalArgumentException(String.format("%s does not specify a valid return value for %s", o, - m)); + InvocationMatcher invocationMatcher; + //TODO use an approach like that of Mockito wrt capturing arg matchers, falling back to force equality like so: + final RecordedInvocation recordedInvocation = currentInvocation; + invocationMatcher = new InvocationMatcher() { + + public boolean matches(Invocation invocation) { + return invocation.getMethod().getName().equals(recordedInvocation.getInvokedMethod().getName()) + && Arrays.equals(invocation.getArguments(), recordedInvocation.getArguments()); + } + + }; + //add to beginning, for priority, hence "stack" nomenclature: + matchingResultStack.add(0, Pair.of(invocationMatcher, result)); } + } finally { + currentInvocation = null; } - return new Answer(currentMatcher(), o); } /** * Mark stubbing as complete. */ - void complete() { + synchronized void complete() { this.complete = true; + if (noArgResults.isEmpty()) { + noArgResults = Collections.emptyMap(); + } + if (matchingResultStack.isEmpty()) { + matchingResultStack = Collections.emptyList(); + } } /** - * Fallback behavior + * Provide fallback behavior. * @param invocation * @return result * @throws Throwable