FELIX-4403 preliminary annotation support, does not handle arrays

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1616078 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/helper/Annotations.java b/scr/src/main/java/org/apache/felix/scr/impl/helper/Annotations.java
new file mode 100644
index 0000000..8e5b3e8
--- /dev/null
+++ b/scr/src/main/java/org/apache/felix/scr/impl/helper/Annotations.java
@@ -0,0 +1,67 @@
+/*
+ * 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.felix.scr.impl.helper;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.osgi.framework.Bundle;
+
+public class Annotations
+{
+    
+    public <T> T toObject(Class<T> clazz, Map<String, Object> props, Bundle b )
+    {     
+        Map<String, Object> m = new HashMap<String, Object>();
+        
+        Method[] methods = clazz.getDeclaredMethods();
+        for ( Method method: methods )
+        {
+            String name = method.getName();
+            //fix up name
+            Object raw = props.get(name);
+            Object cooked = Coercions.coerce( method.getReturnType(), raw, b );
+            m.put( name, cooked );
+        }
+        
+        InvocationHandler h = new Handler(m);
+        return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class<?>[] { clazz }, h);
+    }
+    
+    private static class Handler implements InvocationHandler 
+    {
+        
+        private final Map<String, Object> values;
+       
+        public Handler(Map<String, Object> values)
+        {
+            this.values = values;
+        }
+
+        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
+        {
+            return values.get(method.getName());
+        }
+        
+    }
+
+}
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/helper/Coercions.java b/scr/src/main/java/org/apache/felix/scr/impl/helper/Coercions.java
index 0b747b4..8f18f09 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/helper/Coercions.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/helper/Coercions.java
@@ -43,6 +43,52 @@
     private static final long long0 = 0;
     private static final short short0 = 0;
     
+    public static Object coerce(Class<?> type, Object raw, Bundle bundle )
+    {
+        if (type == Byte.class || type == byte.class)
+        {
+            return coerceToByte(raw);
+        }
+        if (type == Boolean.class || type == boolean.class)
+        {
+            return coerceToBoolean(raw);
+        }
+        if (type == Class.class)
+        {
+            return coerceToClass(raw, bundle);
+        }
+        if (type == Double.class || type == double.class)
+        {
+            return coerceToDouble(raw);
+        }
+        if (type.isEnum())
+        {
+            Class clazz = type; //TODO is there a better way to do ? enum creation?
+            return coerceToEnum(raw, clazz);
+        }
+        if (type == Float.class || type == float.class)
+        {
+            return coerceToFloat(raw);
+        }
+        if (type == Integer.class || type == int.class)
+        {
+            return coerceToInteger(raw);
+        }
+        if (type == Long.class || type == long.class)
+        {
+            return coerceToLong(raw);
+        }
+        if (type == Short.class || type == short.class)
+        {
+            return coerceToShort(raw);
+        }
+        if (type == String.class)
+        {
+            return coerceToString(raw);
+        }
+        throw new ComponentException ("unexpected output type " + type);
+    }
+    
     public static byte coerceToByte(Object o)
     {
         o = multipleToSingle( o, byte0 );
diff --git a/scr/src/test/java/org/apache/felix/scr/impl/helper/AnnotationTest.java b/scr/src/test/java/org/apache/felix/scr/impl/helper/AnnotationTest.java
new file mode 100644
index 0000000..a515d65
--- /dev/null
+++ b/scr/src/test/java/org/apache/felix/scr/impl/helper/AnnotationTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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.felix.scr.impl.helper;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.easymock.EasyMock;
+import org.osgi.framework.Bundle;
+
+import junit.framework.TestCase;
+
+public class AnnotationTest extends TestCase
+{
+    public enum E1 {a, b, c}
+    public @interface A1 {
+        boolean bool();
+        byte byt();
+        Class<?> clas();
+        E1 e1();
+        double doubl();
+        float floa();
+        int integer();
+        long lon();
+        short shor();
+        String string();
+    }
+    
+    private Bundle mockBundle() throws ClassNotFoundException
+    {
+        Bundle b = EasyMock.createMock(Bundle.class);
+        EasyMock.expect(b.loadClass(String.class.getName())).andReturn((Class) String.class).anyTimes();
+        EasyMock.replay(b);
+        return b;
+    }
+    
+    public void testA1() throws Exception
+    {
+        Map<String, Object> values = allValues();
+        
+        Object o = new Annotations().toObject( A1.class, values, mockBundle());
+        assertTrue("expected an A1", o instanceof A1);
+        
+        A1 a = (A1) o;
+        assertEquals(true, a.bool());
+        assertEquals((byte)12, a.byt());
+        assertEquals(String.class, a.clas());
+        assertEquals(E1.a, a.e1());
+        assertEquals(3.14d, a.doubl());
+        assertEquals(500f, a.floa());
+        assertEquals(3, a.integer());
+        assertEquals(12345678l,  a.lon());
+        assertEquals((short)3, a.shor());
+        assertEquals("3", a.string());
+    }
+
+    private Map<String, Object> allValues()
+    {
+        Map<String, Object> values = new HashMap();
+        values.put("bool", "true");
+        values.put("byt", 12l);
+        values.put("clas", String.class.getName());
+        values.put("e1", E1.a.toString());
+        values.put("doubl", "3.14");
+        values.put("floa", 500l);
+        values.put("integer", 3.0d);
+        values.put("lon", "12345678");
+        values.put("shor", 3l);
+        values.put("string", 3);
+        return values;
+    }
+
+    public @interface A2 {
+        boolean bool() default true;
+        byte byt() default 5;
+        Class<?> clas() default Integer.class;
+        E1 e1() default E1.b;
+        double doubl() default -2;
+        float floa() default -4;
+        int integer() default -5;
+        long lon() default Long.MIN_VALUE;
+        short shor() default -8;
+        String string() default "default";
+    }
+    
+    public void testA2AllValues() throws Exception
+    {
+        Map<String, Object> values = allValues();
+        
+        Object o = new Annotations().toObject( A2.class, values, mockBundle());
+        assertTrue("expected an A2", o instanceof A2);
+        
+        A2 a = (A2) o;
+        assertEquals(true, a.bool());
+        assertEquals((byte)12, a.byt());
+        assertEquals(String.class, a.clas());
+        assertEquals(E1.a, a.e1());
+        assertEquals(3.14d, a.doubl());
+        assertEquals(500f, a.floa());
+        assertEquals(3, a.integer());
+        assertEquals(12345678l,  a.lon());
+        assertEquals((short)3, a.shor());
+        assertEquals("3", a.string());
+    }
+
+}