add the ability to program an annotation from (ugh) a string-keyed attribute map

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/proxy/branches/version-2.0-work@1000566 13f79535-47bb-0310-9956-ffa450edef68
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 7e96787..6d08125 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
@@ -23,6 +23,7 @@
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
+import java.util.Map;
 
 import org.apache.commons.lang3.AnnotationUtils;
 import org.apache.commons.lang3.Pair;
@@ -148,6 +149,41 @@
         }
     }
 
+    private static class MapBasedAnnotationConfigurer<A extends Annotation> extends StubConfigurer<A> {
+        final Map<String, Object> attributes;
+
+        /**
+         * Create a new {@link MapBasedAnnotationConfigurer} instance.
+         * @param stubType
+         * @param attributes
+         */
+        public MapBasedAnnotationConfigurer(Class<A> stubType, Map<String, Object> attributes) {
+            super(stubType);
+            this.attributes = attributes;
+        }
+
+        @Override
+        protected void configure(A stub) {
+            When<Object> bud;
+            StubConfiguration dy = this;
+            for (Map.Entry<String, Object> attr : attributes.entrySet()) {
+                Method m;
+                try {
+                    m = getStubType().getDeclaredMethod(attr.getKey());
+                } catch (Exception e1) {
+                    throw new IllegalArgumentException(String.format("Could not detect annotation attribute %1$s", attr.getKey()));
+                }
+                try {
+                    bud = dy.when(m.invoke(stub));
+                } catch (Exception e) {
+                    //it must have happened on the invoke, so we didn't call when... it shouldn't happen, but we'll simply skip:
+                    continue;
+                }
+                dy = bud.thenReturn(attr.getValue());
+            }
+        }
+    }
+
     private static final Invoker ANNOTATION_INVOKER = new Invoker() {
 
         /** Serialization version */
@@ -247,6 +283,32 @@
         return result;
     }
 
+    /**
+     * Create an annotation of <code>annotationType</code> with behavior specified by a {@link String}-keyed {@link Map}.
+     * @param <A>
+     * @param classLoader
+     * @param annotationType
+     * @param attributes
+     * @return stubbed annotation proxy
+     */
+    public <A extends Annotation> A create(Class<A> annotationType, Map<String, Object> attributes) {
+        return attributes == null || attributes.isEmpty() ? create(annotationType)
+            : create(new MapBasedAnnotationConfigurer<A>(annotationType, attributes));
+    }
+
+    /**
+     * Create an annotation of <code>annotationType</code> with behavior specified by a {@link String}-keyed {@link Map}.
+     * @param <A>
+     * @param classLoader
+     * @param annotationType
+     * @param attributes
+     * @return stubbed annotation proxy
+     */
+    public <A extends Annotation> A create(ClassLoader classLoader, Class<A> annotationType, Map<String, Object> attributes) {
+        return attributes == null || attributes.isEmpty() ? create(classLoader, annotationType) : create(classLoader,
+            new MapBasedAnnotationConfigurer<A>(annotationType, attributes));
+    }
+
     private <A extends Annotation> A createInternal(ClassLoader classLoader, Object configurer) {
         final Object existingConfigurer = CONFIGURER.get();
         final boolean outerContext = CONTEXT.get() == null;
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 d4c2139..47e8334 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
@@ -20,6 +20,8 @@
 import static org.junit.Assert.*;
 
 import java.lang.annotation.Annotation;
+import java.util.HashMap;
+import java.util.Map;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -161,6 +163,26 @@
         };
     }
 
+    @Test
+    public void testAttributes() {
+        Map<String, Object> attributes = new HashMap<String, Object>();
+        attributes.put("annString", "foo");
+        attributes.put("finiteValues", FiniteValues.values());
+        attributes.put("someType", Object.class);
+        CustomAnnotation customAnnotation = annotationFactory.create(CustomAnnotation.class, attributes);
+        assertNotNull(customAnnotation);
+        assertEquals("foo", customAnnotation.annString());
+        assertEquals(3, customAnnotation.finiteValues().length);
+        assertEquals(Object.class, customAnnotation.someType());
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testBadAttributes() {
+        Map<String, Object> attributes = new HashMap<String, Object>();
+        attributes.put("annString", 100);
+        annotationFactory.create(CustomAnnotation.class, attributes);
+    }
+    
     public @interface NestingAnnotation {
         CustomAnnotation child();