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


Reply via email to