Implementing new StubInterceptorBuilder functionality.

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/proxy/branches/version-2.0-work@1508275 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/core/src/main/java/org/apache/commons/proxy2/interceptor/InterceptorUtils.java b/core/src/main/java/org/apache/commons/proxy2/interceptor/InterceptorUtils.java
index c04b093..99db083 100644
--- a/core/src/main/java/org/apache/commons/proxy2/interceptor/InterceptorUtils.java
+++ b/core/src/main/java/org/apache/commons/proxy2/interceptor/InterceptorUtils.java
@@ -18,6 +18,7 @@
 package org.apache.commons.proxy2.interceptor;
 
 import org.apache.commons.proxy2.Interceptor;
+import org.apache.commons.proxy2.ObjectProvider;
 import org.apache.commons.proxy2.provider.ObjectProviderUtils;
 
 public final class InterceptorUtils
@@ -31,6 +32,11 @@
         return new ObjectProviderInterceptor(ObjectProviderUtils.constant(value));
     }
 
+    public static Interceptor provider(ObjectProvider<?> provider)
+    {
+        return new ObjectProviderInterceptor(provider);
+    }
+
 //----------------------------------------------------------------------------------------------------------------------
 // Constructors
 //----------------------------------------------------------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/commons/proxy2/interceptor/SwitchInterceptor.java b/core/src/main/java/org/apache/commons/proxy2/interceptor/SwitchInterceptor.java
index fe7eff5..9ecd078 100644
--- a/core/src/main/java/org/apache/commons/proxy2/interceptor/SwitchInterceptor.java
+++ b/core/src/main/java/org/apache/commons/proxy2/interceptor/SwitchInterceptor.java
@@ -21,13 +21,14 @@
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.commons.proxy2.Interceptor;
 import org.apache.commons.proxy2.Invocation;
+import org.apache.commons.proxy2.interceptor.matcher.InvocationMatcher;
 
 import java.io.Serializable;
 import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
- * A {@link SwitchInterceptor} maintains a list of {@link InvocationMatcher}/{@link Interceptor} pairs.  Each
+ * A {@link SwitchInterceptor} maintains a list of {@link org.apache.commons.proxy2.interceptor.matcher.InvocationMatcher}/{@link Interceptor} pairs.  Each
  * invocation will be checked against the registered InvocationMatchers.  If one matches the current invocation, then
  * the corresponding Interceptor will be called.  If no InvocationMatchers match, then the invocation will merely
  * {@link org.apache.commons.proxy2.Invocation#proceed()} method is called.
diff --git a/core/src/main/java/org/apache/commons/proxy2/interceptor/InvocationMatcher.java b/core/src/main/java/org/apache/commons/proxy2/interceptor/matcher/ArgumentMatcher.java
similarity index 75%
copy from core/src/main/java/org/apache/commons/proxy2/interceptor/InvocationMatcher.java
copy to core/src/main/java/org/apache/commons/proxy2/interceptor/matcher/ArgumentMatcher.java
index 14f97a2..f7a33e8 100644
--- a/core/src/main/java/org/apache/commons/proxy2/interceptor/InvocationMatcher.java
+++ b/core/src/main/java/org/apache/commons/proxy2/interceptor/matcher/ArgumentMatcher.java
@@ -15,19 +15,13 @@
  * limitations under the License.
  */
 
-package org.apache.commons.proxy2.interceptor;
+package org.apache.commons.proxy2.interceptor.matcher;
 
-import org.apache.commons.proxy2.Invocation;
-
-/**
- * An {@link InvocationMatcher} is used to conditionally match {@link Invocation} objects based on
- * some criteria such as method name, parameter values, etc.
- */
-public interface InvocationMatcher
+public interface ArgumentMatcher
 {
 //----------------------------------------------------------------------------------------------------------------------
 // Other Methods
 //----------------------------------------------------------------------------------------------------------------------
 
-    boolean matches(Invocation invocation);
+    boolean matches(Object argument);
 }
diff --git a/core/src/main/java/org/apache/commons/proxy2/interceptor/InvocationMatcher.java b/core/src/main/java/org/apache/commons/proxy2/interceptor/matcher/InvocationMatcher.java
similarity index 95%
rename from core/src/main/java/org/apache/commons/proxy2/interceptor/InvocationMatcher.java
rename to core/src/main/java/org/apache/commons/proxy2/interceptor/matcher/InvocationMatcher.java
index 14f97a2..a439b01 100644
--- a/core/src/main/java/org/apache/commons/proxy2/interceptor/InvocationMatcher.java
+++ b/core/src/main/java/org/apache/commons/proxy2/interceptor/matcher/InvocationMatcher.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.commons.proxy2.interceptor;
+package org.apache.commons.proxy2.interceptor.matcher;
 
 import org.apache.commons.proxy2.Invocation;
 
diff --git a/core/src/main/java/org/apache/commons/proxy2/interceptor/matcher/argument/ArgumentMatcherUtils.java b/core/src/main/java/org/apache/commons/proxy2/interceptor/matcher/argument/ArgumentMatcherUtils.java
new file mode 100644
index 0000000..d9888df
--- /dev/null
+++ b/core/src/main/java/org/apache/commons/proxy2/interceptor/matcher/argument/ArgumentMatcherUtils.java
@@ -0,0 +1,47 @@
+package org.apache.commons.proxy2.interceptor.matcher.argument;
+
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.proxy2.interceptor.matcher.ArgumentMatcher;
+
+public class ArgumentMatcherUtils
+{
+//----------------------------------------------------------------------------------------------------------------------
+// Static Methods
+//----------------------------------------------------------------------------------------------------------------------
+
+    public static ArgumentMatcher any()
+    {
+        return new ArgumentMatcher()
+        {
+            @Override
+            public boolean matches(Object argument)
+            {
+                return true;
+            }
+        };
+    }
+
+    public static ArgumentMatcher eq(final Object value)
+    {
+        return new ArgumentMatcher()
+        {
+            @Override
+            public boolean matches(Object argument)
+            {
+                return ObjectUtils.equals(argument, value);
+            }
+        };
+    }
+
+    public static ArgumentMatcher isInstance(final Class<?> type)
+    {
+        return new ArgumentMatcher()
+        {
+            @Override
+            public boolean matches(Object argument)
+            {
+                return type.isInstance(argument);
+            }
+        };
+    }
+}
diff --git a/core/src/main/java/org/apache/commons/proxy2/interceptor/matcher/MethodNameMatcher.java b/core/src/main/java/org/apache/commons/proxy2/interceptor/matcher/invocation/MethodNameMatcher.java
similarity index 93%
rename from core/src/main/java/org/apache/commons/proxy2/interceptor/matcher/MethodNameMatcher.java
rename to core/src/main/java/org/apache/commons/proxy2/interceptor/matcher/invocation/MethodNameMatcher.java
index 64afaba..7eaa7ef 100644
--- a/core/src/main/java/org/apache/commons/proxy2/interceptor/matcher/MethodNameMatcher.java
+++ b/core/src/main/java/org/apache/commons/proxy2/interceptor/matcher/invocation/MethodNameMatcher.java
@@ -15,10 +15,10 @@
  * limitations under the License.
  */
 
-package org.apache.commons.proxy2.interceptor.matcher;
+package org.apache.commons.proxy2.interceptor.matcher.invocation;
 
 import org.apache.commons.proxy2.Invocation;
-import org.apache.commons.proxy2.interceptor.InvocationMatcher;
+import org.apache.commons.proxy2.interceptor.matcher.InvocationMatcher;
 
 /**
  * A {@link MethodNameMatcher} simply checks to see that the method name of the invocation matches the target method
diff --git a/core/src/main/java/org/apache/commons/proxy2/interceptor/matcher/ReturnTypeMatcher.java b/core/src/main/java/org/apache/commons/proxy2/interceptor/matcher/invocation/ReturnTypeMatcher.java
similarity index 94%
rename from core/src/main/java/org/apache/commons/proxy2/interceptor/matcher/ReturnTypeMatcher.java
rename to core/src/main/java/org/apache/commons/proxy2/interceptor/matcher/invocation/ReturnTypeMatcher.java
index ad4cb93..9b7d743 100644
--- a/core/src/main/java/org/apache/commons/proxy2/interceptor/matcher/ReturnTypeMatcher.java
+++ b/core/src/main/java/org/apache/commons/proxy2/interceptor/matcher/invocation/ReturnTypeMatcher.java
@@ -15,10 +15,10 @@
  * limitations under the License.
  */
 
-package org.apache.commons.proxy2.interceptor.matcher;
+package org.apache.commons.proxy2.interceptor.matcher.invocation;
 
 import org.apache.commons.proxy2.Invocation;
-import org.apache.commons.proxy2.interceptor.InvocationMatcher;
+import org.apache.commons.proxy2.interceptor.matcher.InvocationMatcher;
 
 public class ReturnTypeMatcher implements InvocationMatcher
 {
diff --git a/core/src/test/java/org/apache/commons/proxy2/interceptor/TestSwitchInterceptor.java b/core/src/test/java/org/apache/commons/proxy2/interceptor/TestSwitchInterceptor.java
index cbfee3f..5007a08 100644
--- a/core/src/test/java/org/apache/commons/proxy2/interceptor/TestSwitchInterceptor.java
+++ b/core/src/test/java/org/apache/commons/proxy2/interceptor/TestSwitchInterceptor.java
@@ -18,7 +18,7 @@
 package org.apache.commons.proxy2.interceptor;
 
 import org.apache.commons.proxy2.Invocation;
-import org.apache.commons.proxy2.interceptor.matcher.MethodNameMatcher;
+import org.apache.commons.proxy2.interceptor.matcher.invocation.MethodNameMatcher;
 import org.apache.commons.proxy2.util.AbstractTestCase;
 import org.apache.commons.proxy2.util.Echo;
 import org.apache.commons.proxy2.util.MockInvocation;
diff --git a/core/src/test/java/org/apache/commons/proxy2/interceptor/matcher/TestMethodNameMatcher.java b/core/src/test/java/org/apache/commons/proxy2/interceptor/matcher/TestMethodNameMatcher.java
index 8922c51..b1aee66 100644
--- a/core/src/test/java/org/apache/commons/proxy2/interceptor/matcher/TestMethodNameMatcher.java
+++ b/core/src/test/java/org/apache/commons/proxy2/interceptor/matcher/TestMethodNameMatcher.java
@@ -17,6 +17,7 @@
 
 package org.apache.commons.proxy2.interceptor.matcher;
 
+import org.apache.commons.proxy2.interceptor.matcher.invocation.MethodNameMatcher;
 import org.apache.commons.proxy2.util.AbstractTestCase;
 import org.apache.commons.proxy2.util.Echo;
 import org.apache.commons.proxy2.util.MockInvocation;
diff --git a/core/src/test/java/org/apache/commons/proxy2/interceptor/matcher/TestReturnTypeMatcher.java b/core/src/test/java/org/apache/commons/proxy2/interceptor/matcher/TestReturnTypeMatcher.java
index 9c9cbbd..0eb4380 100644
--- a/core/src/test/java/org/apache/commons/proxy2/interceptor/matcher/TestReturnTypeMatcher.java
+++ b/core/src/test/java/org/apache/commons/proxy2/interceptor/matcher/TestReturnTypeMatcher.java
@@ -18,7 +18,7 @@
 package org.apache.commons.proxy2.interceptor.matcher;
 
 import org.apache.commons.proxy2.Invocation;
-import org.apache.commons.proxy2.interceptor.InvocationMatcher;
+import org.apache.commons.proxy2.interceptor.matcher.invocation.ReturnTypeMatcher;
 import org.apache.commons.proxy2.util.AbstractTestCase;
 import org.apache.commons.proxy2.util.Echo;
 import org.apache.commons.proxy2.util.MockInvocation;
diff --git a/stub/src/main/java/org/apache/commons/proxy2/stub/Behavior.java b/stub/src/main/java/org/apache/commons/proxy2/stub/Behavior.java
new file mode 100644
index 0000000..6e45552
--- /dev/null
+++ b/stub/src/main/java/org/apache/commons/proxy2/stub/Behavior.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.proxy2.stub;
+
+import org.apache.commons.proxy2.ObjectProvider;
+import org.apache.commons.proxy2.interceptor.InterceptorUtils;
+import org.apache.commons.proxy2.interceptor.matcher.ArgumentMatcher;
+import org.apache.commons.proxy2.interceptor.matcher.argument.ArgumentMatcherUtils;
+
+public abstract class Behavior<T>
+{
+//----------------------------------------------------------------------------------------------------------------------
+// Fields
+//----------------------------------------------------------------------------------------------------------------------
+
+    private TrainingContext trainingContext;
+
+//----------------------------------------------------------------------------------------------------------------------
+// Abstract Methods
+//----------------------------------------------------------------------------------------------------------------------
+
+    protected abstract void train(T stub);
+
+//----------------------------------------------------------------------------------------------------------------------
+// Other Methods
+//----------------------------------------------------------------------------------------------------------------------
+
+    protected <R> R any()
+    {
+        recordArgumentMatcher(ArgumentMatcherUtils.any());
+        return null;
+    }
+
+    private void recordArgumentMatcher(ArgumentMatcher matcher)
+    {
+        trainingContext.addArgumentMatcher(matcher);
+    }
+
+    protected <R> R eq(R value)
+    {
+        recordArgumentMatcher(ArgumentMatcherUtils.eq(value));
+        return null;
+    }
+
+    protected <R> R isInstance(Class<R> type)
+    {
+        recordArgumentMatcher(ArgumentMatcherUtils.isInstance(type));
+        return null;
+    }
+
+    void train(TrainingContext context, T stub)
+    {
+        this.trainingContext = context;
+        train(stub);
+    }
+
+    protected <R> When<R> when(R expression)
+    {
+        return new When();
+    }
+
+//----------------------------------------------------------------------------------------------------------------------
+// Inner Classes
+//----------------------------------------------------------------------------------------------------------------------
+
+    protected class When<R>
+    {
+        protected Behavior<T> thenReturn(R value)
+        {
+            trainingContext.setInterceptor(InterceptorUtils.constant(value));
+            return Behavior.this;
+        }
+
+        protected Behavior<T> thenReturn(ObjectProvider<R> provider)
+        {
+            trainingContext.setInterceptor(InterceptorUtils.provider(provider));
+            return Behavior.this;
+        }
+    }
+}
diff --git a/stub/src/main/java/org/apache/commons/proxy2/stub/StubInterceptorBuilder.java b/stub/src/main/java/org/apache/commons/proxy2/stub/StubInterceptorBuilder.java
new file mode 100644
index 0000000..af0e64e
--- /dev/null
+++ b/stub/src/main/java/org/apache/commons/proxy2/stub/StubInterceptorBuilder.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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
+{
+//----------------------------------------------------------------------------------------------------------------------
+// Fields
+//----------------------------------------------------------------------------------------------------------------------
+
+    private final ProxyFactory proxyFactory;
+    private final SwitchInterceptor interceptor = new SwitchInterceptor();
+
+//----------------------------------------------------------------------------------------------------------------------
+// Constructors
+//----------------------------------------------------------------------------------------------------------------------
+
+    public StubInterceptorBuilder(ProxyFactory proxyFactory)
+    {
+        this.proxyFactory = proxyFactory;
+    }
+
+//----------------------------------------------------------------------------------------------------------------------
+// Other Methods
+//----------------------------------------------------------------------------------------------------------------------
+
+    public Interceptor build()
+    {
+        return interceptor;
+    }
+
+    public <T> StubInterceptorBuilder configure(Class<T> type, Behavior<T> behavior)
+    {
+        final TrainingContext context = new TrainingContext(interceptor);
+        T stub = proxyFactory.createInvokerProxy(new TrainingContextInvoker(context), type);
+        behavior.train(context, stub);
+        return this;
+    }
+
+//----------------------------------------------------------------------------------------------------------------------
+// Inner Classes
+//----------------------------------------------------------------------------------------------------------------------
+
+    private static class TrainingContextInvoker implements Invoker
+    {
+        private final TrainingContext trainingContext;
+
+        private TrainingContextInvoker(TrainingContext trainingContext)
+        {
+            this.trainingContext = trainingContext;
+        }
+
+        @Override
+        public Object invoke(Object proxy, Method method, Object[] arguments) throws Throwable
+        {
+            trainingContext.stubMethodInvoked(method, arguments);
+            return ProxyUtils.nullValue(method.getReturnType());
+        }
+    }
+}
diff --git a/stub/src/main/java/org/apache/commons/proxy2/stub/TrainingContext.java b/stub/src/main/java/org/apache/commons/proxy2/stub/TrainingContext.java
new file mode 100644
index 0000000..ca0e0a7
--- /dev/null
+++ b/stub/src/main/java/org/apache/commons/proxy2/stub/TrainingContext.java
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.proxy2.stub;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.proxy2.Interceptor;
+import org.apache.commons.proxy2.Invocation;
+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.RecordedInvocation;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+public class TrainingContext
+{
+//----------------------------------------------------------------------------------------------------------------------
+// Fields
+//----------------------------------------------------------------------------------------------------------------------
+
+    private List<ArgumentMatcher> argumentMatchers = new LinkedList<ArgumentMatcher>();
+    private InvocationMatcher matcher;
+    private Interceptor interceptor;
+    private final SwitchInterceptor switchInterceptor;
+
+//----------------------------------------------------------------------------------------------------------------------
+// Constructors
+//----------------------------------------------------------------------------------------------------------------------
+
+    public TrainingContext(SwitchInterceptor switchInterceptor)
+    {
+        this.switchInterceptor = switchInterceptor;
+    }
+
+//----------------------------------------------------------------------------------------------------------------------
+// Other Methods
+//----------------------------------------------------------------------------------------------------------------------
+
+    public void addArgumentMatcher(ArgumentMatcher argumentMatcher)
+    {
+        argumentMatchers.add(argumentMatcher);
+    }
+
+    public void setInterceptor(Interceptor interceptor)
+    {
+        this.interceptor = interceptor;
+        addCase();
+    }
+
+    private void addCase()
+    {
+        if (matcher != null && interceptor != null)
+        {
+            switchInterceptor.when(matcher).then(interceptor);
+            matcher = null;
+            interceptor = null;
+            argumentMatchers.clear();
+        }
+    }
+
+    public void stubMethodInvoked(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 ArgumentMatchersMatcher(invocation, matchersArray);
+        }
+        else
+        {
+            throw new IllegalStateException("Either use exact arguments or argument matchers, but not both.");
+        }
+        addCase();
+    }
+
+//----------------------------------------------------------------------------------------------------------------------
+// Inner Classes
+//----------------------------------------------------------------------------------------------------------------------
+
+    private static class ArgumentMatchersMatcher implements InvocationMatcher
+    {
+        private final RecordedInvocation recordedInvocation;
+        private final ArgumentMatcher[] matchers;
+
+        private ArgumentMatchersMatcher(RecordedInvocation recordedInvocation, ArgumentMatcher[] matchers)
+        {
+            this.recordedInvocation = recordedInvocation;
+            this.matchers = 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 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.equals(invocation.getArguments(), recordedInvocation.getArguments());
+        }
+    }
+}
diff --git a/core/src/main/java/org/apache/commons/proxy2/interceptor/InvocationMatcher.java b/stub/src/test/java/org/apache/commons/proxy2/stub/StubInterface.java
similarity index 75%
copy from core/src/main/java/org/apache/commons/proxy2/interceptor/InvocationMatcher.java
copy to stub/src/test/java/org/apache/commons/proxy2/stub/StubInterface.java
index 14f97a2..5d8fae5 100644
--- a/core/src/main/java/org/apache/commons/proxy2/interceptor/InvocationMatcher.java
+++ b/stub/src/test/java/org/apache/commons/proxy2/stub/StubInterface.java
@@ -15,19 +15,15 @@
  * limitations under the License.
  */
 
-package org.apache.commons.proxy2.interceptor;
+package org.apache.commons.proxy2.stub;
 
-import org.apache.commons.proxy2.Invocation;
-
-/**
- * An {@link InvocationMatcher} is used to conditionally match {@link Invocation} objects based on
- * some criteria such as method name, parameter values, etc.
- */
-public interface InvocationMatcher
+public interface StubInterface
 {
 //----------------------------------------------------------------------------------------------------------------------
 // Other Methods
 //----------------------------------------------------------------------------------------------------------------------
 
-    boolean matches(Invocation invocation);
+    public String one(String value);
+    public String three(String arg1, String arg2);
+    public String two(String value);
 }
diff --git a/stub/src/test/java/org/apache/commons/proxy2/stub/TestStubInterceptorBuilder.java b/stub/src/test/java/org/apache/commons/proxy2/stub/TestStubInterceptorBuilder.java
new file mode 100644
index 0000000..983456a
--- /dev/null
+++ b/stub/src/test/java/org/apache/commons/proxy2/stub/TestStubInterceptorBuilder.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.proxy2.stub;
+
+import org.apache.commons.proxy2.Interceptor;
+import org.apache.commons.proxy2.ProxyFactory;
+import org.apache.commons.proxy2.cglib.CglibProxyFactory;
+import org.apache.commons.proxy2.invoker.NullInvoker;
+import org.apache.commons.proxy2.provider.ObjectProviderUtils;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class TestStubInterceptorBuilder
+{
+//----------------------------------------------------------------------------------------------------------------------
+// Fields
+//----------------------------------------------------------------------------------------------------------------------
+
+    private ProxyFactory proxyFactory;
+    private StubInterface target;
+
+//----------------------------------------------------------------------------------------------------------------------
+// Other Methods
+//----------------------------------------------------------------------------------------------------------------------
+
+    @Before
+    public void setUp()
+    {
+        this.proxyFactory = new CglibProxyFactory();
+        this.target = proxyFactory.createInvokerProxy(NullInvoker.INSTANCE, StubInterface.class);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testMixingArgumentMatchingStrategies()
+    {
+        StubInterceptorBuilder builder = new StubInterceptorBuilder(proxyFactory);
+        builder.configure(StubInterface.class, new Behavior<StubInterface>()
+        {
+            @Override
+            protected void train(StubInterface stub)
+            {
+                when(stub.three(isInstance(String.class), "World")).thenReturn(ObjectProviderUtils.constant("World"));
+            }
+        });
+    }
+
+    @Test
+    public void testWithArgumentMatchers()
+    {
+        StubInterceptorBuilder builder = new StubInterceptorBuilder(proxyFactory);
+        Interceptor interceptor = builder.configure(StubInterface.class, new Behavior<StubInterface>()
+        {
+            @Override
+            protected void train(StubInterface stub)
+            {
+                when(stub.one(isInstance(String.class))).thenReturn(ObjectProviderUtils.constant("World"));
+            }
+        }).build();
+
+        final StubInterface proxy = proxyFactory.createInterceptorProxy(target, interceptor, StubInterface.class);
+        assertEquals("World", proxy.one("Hello"));
+        assertEquals("World", proxy.one("Whatever"));
+    }
+
+    @Test
+    public void testWithMismatchedArgument()
+    {
+        StubInterceptorBuilder builder = new StubInterceptorBuilder(proxyFactory);
+        Interceptor interceptor = builder.configure(StubInterface.class, new Behavior<StubInterface>()
+        {
+            @Override
+            protected void train(StubInterface stub)
+            {
+                when(stub.one(eq("Hello"))).thenReturn("World");
+            }
+        }).build();
+        final StubInterface proxy = proxyFactory.createInterceptorProxy(target, interceptor, StubInterface.class);
+        assertEquals("World", proxy.one("Hello"));
+        assertEquals(null, proxy.one("Whatever"));
+    }
+
+    @Test
+    public void testWithMultipleMethodsTrained()
+    {
+        StubInterceptorBuilder builder = new StubInterceptorBuilder(proxyFactory);
+        Interceptor interceptor = builder.configure(StubInterface.class, new Behavior<StubInterface>()
+        {
+            @Override
+            protected void train(StubInterface stub)
+            {
+                when(stub.one("Hello")).thenReturn("World");
+                when(stub.two("Foo")).thenReturn("Bar");
+            }
+        }).build();
+
+        final StubInterface proxy = proxyFactory.createInterceptorProxy(target, interceptor, StubInterface.class);
+        assertEquals("World", proxy.one("Hello"));
+        assertEquals("Bar", proxy.two("Foo"));
+    }
+
+    @Test
+    public void testWithSingleMethodTrained()
+    {
+        StubInterceptorBuilder builder = new StubInterceptorBuilder(proxyFactory);
+        Interceptor interceptor = builder.configure(StubInterface.class, new Behavior<StubInterface>()
+        {
+            @Override
+            protected void train(StubInterface stub)
+            {
+                when(stub.one("Hello")).thenReturn("World");
+            }
+        }).build();
+
+        final StubInterface proxy = proxyFactory.createInterceptorProxy(target, interceptor, StubInterface.class);
+        assertEquals("World", proxy.one("Hello"));
+        assertEquals(null, proxy.two("Whatever"));
+        assertEquals(null, proxy.one("Mismatch!"));
+    }
+}