(re-)add notion of a special Annotation-specific StubConfigurer subclass, AnnotationConfigurer.  Not required for use, but if used, provides methods to simplify the creation of nested annotations a bit

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/proxy/branches/version-2.0-work@1000298 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/stub/src/main/java/org/apache/commons/proxy2/stub/AnnotationConfigurer.java b/stub/src/main/java/org/apache/commons/proxy2/stub/AnnotationConfigurer.java
new file mode 100644
index 0000000..2345715
--- /dev/null
+++ b/stub/src/main/java/org/apache/commons/proxy2/stub/AnnotationConfigurer.java
@@ -0,0 +1,66 @@
+/*
+ * 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 java.lang.annotation.Annotation;
+
+/**
+ * Special {@link StubConfigurer} subclass that makes creating nested annotations (somewhat more) convenient.
+ *
+ * @param <A>
+ */
+public abstract class AnnotationConfigurer<A extends Annotation> extends StubConfigurer<A> {
+    AnnotationFactory annotationFactory;
+
+    /**
+     * Create a child annotation of the specified type using a StubConfigurer.
+     * @param <T>
+     * @param configurer, should not be <code>this</code>
+     * @return T
+     * @throws IllegalStateException if called other than when an {@link AnnotationFactory} is executing {@link #configure(Object)}
+     * @throws IllegalArgumentException if <code>configurer == this</code>
+     */
+    protected final <T extends Annotation> T child(StubConfigurer<T> configurer) {
+        if (configurer == this) {
+            throw new IllegalArgumentException("An AnnotationConfigurer cannot configure its own child annotation");
+        }
+        return requireAnnotationFactory().create(configurer);
+    }
+
+    /**
+     * Create a child annotation of the specified type with default behavior.
+     * @param <T>
+     * @param annotationType
+     * @return T
+     * @throws IllegalStateException if called other than when an {@link AnnotationFactory} is executing {@link #configure(Object)}
+     */
+    protected final <T extends Annotation> T child(Class<T> annotationType) {
+        return requireAnnotationFactory().create(annotationType);
+    }
+
+    /**
+     * Get the registered annotationFactory.
+     * @return AnnotationFactory
+     * @throws IllegalStateException if no ongoing annotation stubbing could be detected
+     */
+    synchronized AnnotationFactory requireAnnotationFactory() throws IllegalStateException {
+        if (annotationFactory == null) {
+            throw new IllegalStateException("Could not detect ongoing annotation stubbing");
+        }
+        return annotationFactory;
+    }
+}
\ No newline at end of file
diff --git a/stub/src/main/java/org/apache/commons/proxy2/stub/AnnotationFactory.java b/stub/src/main/java/org/apache/commons/proxy2/stub/AnnotationFactory.java
index 346d113..e33fbfd 100644
--- a/stub/src/main/java/org/apache/commons/proxy2/stub/AnnotationFactory.java
+++ b/stub/src/main/java/org/apache/commons/proxy2/stub/AnnotationFactory.java
@@ -155,7 +155,7 @@
 
     private static final ThreadLocal<Object> CONFIGURER = new ThreadLocal<Object>();
 
-    private static final StubConfigurer<Annotation> SHARED_CONFIGURER = new StubConfigurer<Annotation>() {
+    private final StubConfigurer<Annotation> sharedConfigurer = new StubConfigurer<Annotation>() {
 
         /**
          * {@inheritDoc}
@@ -175,7 +175,21 @@
             if (o instanceof StubConfigurer<?>) {
                 @SuppressWarnings("unchecked")
                 final StubConfigurer<Annotation> configurer = (StubConfigurer<Annotation>) o;
-                configurer.configure(requireStubInterceptor(), stub);
+                boolean deregisterFactory = false;
+                synchronized (configurer) {
+                    try {
+                        if (configurer instanceof AnnotationConfigurer<?>) {
+                            AnnotationConfigurer<?> annotationConfigurer = (AnnotationConfigurer<?>) configurer;
+                            deregisterFactory = true;
+                            annotationConfigurer.annotationFactory = AnnotationFactory.this;
+                        }
+                        configurer.configure(requireStubInterceptor(), stub);
+                    } finally {
+                        if (deregisterFactory) {
+                            ((AnnotationConfigurer<?>) configurer).annotationFactory = null;
+                        }
+                    }
+                }
             }
         }
     };
@@ -186,7 +200,7 @@
      * Create a new AnnotationFactory instance.
      */
     public AnnotationFactory() {
-        this.proxyFactory = new StubProxyFactory(PROXY_FACTORY, SHARED_CONFIGURER);
+        this.proxyFactory = new StubProxyFactory(PROXY_FACTORY, sharedConfigurer);
     }
 
     /**
diff --git a/stub/src/test/java/org/apache/commons/proxy2/stub/AnnotationFactoryTest.java b/stub/src/test/java/org/apache/commons/proxy2/stub/AnnotationFactoryTest.java
index 9937d59..d4c2139 100644
--- a/stub/src/test/java/org/apache/commons/proxy2/stub/AnnotationFactoryTest.java
+++ b/stub/src/test/java/org/apache/commons/proxy2/stub/AnnotationFactoryTest.java
@@ -19,6 +19,8 @@
 
 import static org.junit.Assert.*;
 
+import java.lang.annotation.Annotation;
+
 import org.junit.Before;
 import org.junit.Test;
 
@@ -36,11 +38,11 @@
     @Test
     public void testDefaultAnnotation() {
         CustomAnnotation customAnnotation = annotationFactory.create(CustomAnnotation.class);
+        assertNotNull(customAnnotation);
         assertEquals(CustomAnnotation.class, customAnnotation.annotationType());
         assertEquals("", customAnnotation.annString());
         assertEquals(0, customAnnotation.finiteValues().length);
         assertNull(customAnnotation.someType());
-
     }
 
     @Test
@@ -54,6 +56,7 @@
             }
 
         });
+        assertNotNull(customAnnotation);
         assertEquals(CustomAnnotation.class, customAnnotation.annotationType());
         assertEquals("hey", customAnnotation.annString());
         assertArrayEquals(new FiniteValues[] { FiniteValues.ONE, FiniteValues.THREE }, customAnnotation.finiteValues());
@@ -61,21 +64,103 @@
     }
 
     @Test
-    public void testNestedStubbedAnnotation() {
+    public void testStubbedAnnotationWithDefaultChild() {
         NestingAnnotation nestingAnnotation =
-            annotationFactory.create(new StubConfigurer<NestingAnnotation>() {
+            annotationFactory.create(new AnnotationConfigurer<NestingAnnotation>() {
                 @Override
                 protected void configure(NestingAnnotation stub) {
-                    when(stub.child()).thenReturn(annotationFactory.create(CustomAnnotation.class))
+                    when(stub.child()).thenReturn(child(CustomAnnotation.class))
                         .when(stub.somethingElse()).thenReturn("somethingElse");
                 }
             });
+        assertNotNull(nestingAnnotation);
+        assertNotNull(nestingAnnotation.child());
         assertEquals("", nestingAnnotation.child().annString());
         assertEquals(0, nestingAnnotation.child().finiteValues().length);
         assertEquals(null, nestingAnnotation.child().someType());
         assertEquals("somethingElse", nestingAnnotation.somethingElse());
     }
 
+    @Test
+    public void testStubbedAnnotationWithConfiguredChild() {
+        NestingAnnotation nestingAnnotation = annotationFactory.create(new AnnotationConfigurer<NestingAnnotation>() {
+            @Override
+            protected void configure(NestingAnnotation stub) {
+                when(stub.child()).thenReturn(child(new StubConfigurer<CustomAnnotation>() {
+
+                    @Override
+                    protected void configure(CustomAnnotation stub) {
+                        when(stub.annString()).thenReturn("wow").when(stub.finiteValues())
+                            .thenReturn(FiniteValues.values()).when(stub.someType()).thenReturn(Class.class);
+                    }
+                })).when(stub.somethingElse()).thenReturn("somethingElse");
+            }
+        });
+        assertNotNull(nestingAnnotation);
+        assertNotNull(nestingAnnotation.child());
+        assertEquals("wow", nestingAnnotation.child().annString());
+        assertEquals(3, nestingAnnotation.child().finiteValues().length);
+        assertEquals(Class.class, nestingAnnotation.child().someType());
+        assertEquals("somethingElse", nestingAnnotation.somethingElse());
+    }
+
+    @Test
+    public void testStubbedAnnotationWithDoubleNesting() {
+        OuterContainer outerContainer = annotationFactory.create(new AnnotationConfigurer<OuterContainer>() {
+
+            @Override
+            protected void configure(OuterContainer stub) {
+                when(stub.nest()).thenReturn(child(new AnnotationConfigurer<NestingAnnotation>() {
+
+                    @Override
+                    protected void configure(NestingAnnotation stub) {
+                        when(stub.child()).thenReturn(child(new AnnotationConfigurer<CustomAnnotation>() {
+
+                            @Override
+                            protected void configure(CustomAnnotation stub) {
+                                when(stub.annString()).thenReturn("wow").when(stub.finiteValues())
+                                .thenReturn(FiniteValues.values()).when(stub.someType()).thenReturn(Class.class);
+                            }
+
+                        })).when(stub.somethingElse()).thenReturn("somethingElse");
+                    }
+                }));
+            }
+        });
+        assertNotNull(outerContainer);
+        NestingAnnotation nestingAnnotation = outerContainer.nest();
+        assertNotNull(nestingAnnotation);
+        assertNotNull(nestingAnnotation.child());
+        assertEquals("wow", nestingAnnotation.child().annString());
+        assertEquals(3, nestingAnnotation.child().finiteValues().length);
+        assertEquals(Class.class, nestingAnnotation.child().someType());
+        assertEquals("somethingElse", nestingAnnotation.somethingElse());
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testCannotConfigureOwnChild() {
+        annotationFactory.create(new AnnotationConfigurer<NestingAnnotation>() {
+
+            @Override
+            protected void configure(NestingAnnotation stub) {
+                child(this);
+            }
+        });
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void testChildRequiresOngoingStubbing() {
+        new AnnotationConfigurer<Annotation>() {
+            {
+                child(CustomAnnotation.class);
+            }
+
+            @Override
+            protected void configure(Annotation stub) {
+            }
+        };
+    }
+
     public @interface NestingAnnotation {
         CustomAnnotation child();
 
@@ -90,6 +175,10 @@
         Class<?> someType();
     }
 
+    public @interface OuterContainer {
+        NestingAnnotation nest();
+    }
+
     public enum FiniteValues {
         ONE, TWO, THREE;
     }