FELIX-4607 initial implementation of configuration with nested annotations/interfaces.  Only supports arrays or single elements, not collections.  Does not support subtypes.

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1618545 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/helper/ActivateMethod.java b/scr/src/main/java/org/apache/felix/scr/impl/helper/ActivateMethod.java
index 121e79d..474e359 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/helper/ActivateMethod.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/helper/ActivateMethod.java
@@ -271,7 +271,7 @@
             {
                 param[i] = Annotations.toObject(parameterTypes[i], 
                     (Map<String, Object>) ap.getComponentContext().getProperties(),
-                    ap.getComponentContext().getBundleContext().getBundle());
+                    ap.getComponentContext().getBundleContext().getBundle(), m_supportsInterfaces);
             }
         }
 
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
index 1d69628..d182974 100644
--- 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
@@ -18,37 +18,178 @@
  */
 package org.apache.felix.scr.impl.helper;
 
+import java.lang.reflect.Array;
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import javax.swing.text.Keymap;
+
 import org.osgi.framework.Bundle;
 
 public class Annotations
 {
     
-    static public <T> T toObject(Class<T> clazz, Map<String, Object> props, Bundle b )
+    static public <T> T toObject(Class<T> clazz, Map<String, Object> props, Bundle b, boolean supportsInterfaces )
     {     
         Map<String, Object> m = new HashMap<String, Object>();
         
         Method[] methods = clazz.getDeclaredMethods();
+        Map<String, Method> complexFields = new HashMap<String, Method>();
         for ( Method method: methods )
         {
             String name = method.getName();
-            name = fixup(name);
-            Object raw = props.get(name);
-            Object cooked = Coercions.coerce( method.getReturnType(), raw, b );
+            String key = fixup(name);
+            Object raw = props.get(key);
+            Class<?> returnType = method.getReturnType();
+            Object cooked;
+            if ( returnType.isInterface() || returnType.isAnnotation())
+            {
+                complexFields.put(key, method);
+                continue;
+            }
+            if (returnType.isArray())
+            {
+                Class<?> componentType = returnType.getComponentType();
+                if ( componentType.isInterface() || componentType.isAnnotation())
+                {
+                    complexFields.put(key, method);
+                    continue;
+                }
+                cooked = coerceToArray(componentType, raw, b);
+            }
+            else
+            {
+                cooked = Coercions.coerce( returnType, raw, b );
+            }
             m.put( name, cooked );
         }
+        if (!complexFields.isEmpty())
+        {
+            if (!supportsInterfaces )
+            {
+            //error
+                return null;//??
+            }
+            Map<String, List<Map<String, Object>>> nested = extractSubMaps(complexFields.keySet(), props);
+            for (Map.Entry<String, Method> entry: complexFields.entrySet())
+            {
+                List<Map<String, Object>> proplist = nested.get(entry.getKey());
+                Method method = entry.getValue();
+                Class<?> returnType  = method.getReturnType();
+                if (returnType.isArray())
+                {
+                    Class<?> componentType = returnType.getComponentType();
+                    Object result = Array.newInstance(componentType, proplist.size());
+                    for (int i = 0; i < proplist.size(); i++)
+                    {
+                        Map<String, Object> rawElement = proplist.get(i);
+                        Object cooked = toObject(componentType, rawElement, b, supportsInterfaces);
+                        Array.set(result, i, cooked);
+                    }
+                    m.put(method.getName(), result);
+                }
+                else
+                {
+                    if (!proplist.isEmpty())
+                    {
+                        Object cooked = toObject(returnType, proplist.get(0), b, supportsInterfaces);
+                        m.put(method.getName(), cooked);
+                    }
+                }
+            }
+        }
         
         InvocationHandler h = new Handler(m);
         return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class<?>[] { clazz }, h);
     }
     
+    private static Map<String, List<Map<String, Object>>> extractSubMaps(Collection<String> keys, Map<String, Object> map) 
+    {
+        Map<String, List<Map<String, Object>>> result = new HashMap<String, List<Map<String, Object>>>();
+        //Form a regexp to recognize all the keys as prefixes in the map keys.
+        StringBuilder b = new StringBuilder("(");
+        for (String key: keys)
+        {
+            b.append(key).append("|");
+        }
+        b.deleteCharAt(b.length() -1);
+        b.append(")\\.([0-9]*)\\.(.*)");
+        Pattern p = Pattern.compile(b.toString());
+        for (Map.Entry<String, Object> entry: map.entrySet())
+        {
+            String longKey = entry.getKey();
+            Matcher m = p.matcher(longKey);
+            if (m.matches())
+            {
+                String key = m.group(1);
+                int index = Integer.parseInt(m.group(2));
+                String subkey = m.group(3);
+                List<Map<String, Object>> subMapsForKey = result.get(key);
+                if (subMapsForKey == null)
+                {
+                    subMapsForKey = new ArrayList<Map<String, Object>>();
+                    result.put(key, subMapsForKey);
+                }
+                //make sure there is room for the possible new submap
+                for (int i = subMapsForKey.size(); i <= index; i++)
+                {
+                    subMapsForKey.add(new HashMap<String, Object>());                    
+                }
+                Map<String, Object> subMap = subMapsForKey.get(index);
+                subMap.put(subkey, entry.getValue());
+            }
+        }
+        return result;
+    }
+
+    private static Object coerceToArray(Class<?> componentType, Object raw, Bundle bundle)
+    {
+        if (raw == null)
+        {
+            return null;
+        }
+        if (raw.getClass().isArray())
+        {
+            int size = Array.getLength(raw);
+            Object result = Array.newInstance(componentType, size);
+            for (int i = 0; i < size; i++)
+            {
+                Object rawElement = Array.get(raw, i);
+                Object cooked = Coercions.coerce(componentType, rawElement, bundle);
+                Array.set(result, i, cooked);
+            }
+            return result;
+        }
+        if (raw instanceof Collection)
+        {
+            Collection raws = (Collection) raw;
+            int size = raws.size();
+            Object result = Array.newInstance(componentType, size);
+            int i = 0;
+            for (Object rawElement: raws)
+            {
+                Object cooked = Coercions.coerce(componentType, rawElement, bundle);
+                Array.set(result, i++, cooked);
+            }
+            return result;
+
+        }
+        Object cooked = Coercions.coerce(componentType, raw, bundle);
+        Object result = Array.newInstance(componentType, 1);
+        Array.set(result, 0, cooked);
+        return result;
+
+    }
+    
     private static final Pattern p = Pattern.compile("(\\$\\$)|(\\$)|(__)|(_)");
     
     static String fixup(String name)
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 2fff2f6..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
@@ -45,44 +45,6 @@
     
     public static Object coerce(Class<?> type, Object raw, Bundle bundle )
     {
-        if (type.isArray())
-        {
-            if (raw == null)
-            {
-                return null;
-            }
-            Class<?> componentType = type.getComponentType();
-            if (raw.getClass().isArray())
-            {
-                int size = Array.getLength(raw);
-                Object result = Array.newInstance(componentType, size);
-                for (int i = 0; i < size; i++)
-                {
-                    Object rawElement = Array.get(raw, i);
-                    Object cooked = coerce(componentType, rawElement, bundle);
-                    Array.set(result, i, cooked);
-                }
-                return result;
-            }
-            if (raw instanceof Collection)
-            {
-                Collection raws = (Collection) raw;
-                int size = raws.size();
-                Object result = Array.newInstance(componentType, size);
-                int i = 0;
-                for (Object rawElement: raws)
-                {
-                    Object cooked = coerce(componentType, rawElement, bundle);
-                    Array.set(result, i++, cooked);
-                }
-                return result;
-                
-            }
-            Object cooked = coerce(componentType, raw, bundle);
-            Object result = Array.newInstance(componentType, 1);
-            Array.set(result, 0, cooked);
-            return result;
-        }
         if (type == Byte.class || type == byte.class)
         {
             return coerceToByte(raw);
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
index f1c8327..dbc286c 100644
--- 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
@@ -79,10 +79,15 @@
     {
         Map<String, Object> values = allValues();
         
-        Object o = Annotations.toObject( A1.class, values, mockBundle());
+        Object o = Annotations.toObject( A1.class, values, mockBundle(), false);
         assertTrue("expected an A1", o instanceof A1);
         
         A1 a = (A1) o;
+        checkA1(a);
+    }
+
+    private void checkA1(A1 a)
+    {
         assertEquals(true, a.bool());
         assertEquals((byte)12, a.byt());
         assertEquals(String.class, a.clas());
@@ -99,7 +104,7 @@
     {
         Map<String, Object> values = arrayValues();
         
-        Object o = Annotations.toObject( A1.class, values, mockBundle());
+        Object o = Annotations.toObject( A1.class, values, mockBundle(), false);
         assertTrue("expected an A1", o instanceof A1);
         
         A1 a = (A1) o;
@@ -135,7 +140,7 @@
     {
         Map<String, Object> values = new HashMap<String, Object>();
         
-        Object o = Annotations.toObject( A1.class, values, mockBundle());
+        Object o = Annotations.toObject( A1.class, values, mockBundle(), false);
         assertTrue("expected an A1", o instanceof A1);
         
         A1 a = (A1) o;
@@ -168,7 +173,7 @@
     {
         Map<String, Object> values = allValues();
         
-        Object o = Annotations.toObject( A2.class, values, mockBundle());
+        Object o = Annotations.toObject( A2.class, values, mockBundle(), false);
         assertTrue("expected an A2", o instanceof A2);
         
         A2 a = (A2) o;
@@ -201,7 +206,7 @@
     {
         Map<String, Object> values = new HashMap<String, Object>();
         
-        Object o = Annotations.toObject( A1Arrays.class, values, mockBundle());
+        Object o = Annotations.toObject( A1Arrays.class, values, mockBundle(), false);
         assertTrue("expected an A1Arrays", o instanceof A1Arrays);
         
         A1Arrays a = (A1Arrays) o;
@@ -221,7 +226,7 @@
     {
         Map<String, Object> values = allValues();
         
-        Object o = Annotations.toObject( A1Arrays.class, values, mockBundle());
+        Object o = Annotations.toObject( A1Arrays.class, values, mockBundle(), false);
         assertTrue("expected an A1Arrays", o instanceof A1Arrays);
         
         A1Arrays a = (A1Arrays) o;
@@ -297,7 +302,7 @@
 
     private void doA1ArrayTest(Map<String, Object> values) throws ClassNotFoundException
     {
-        Object o = Annotations.toObject( A1Arrays.class, values, mockBundle());
+        Object o = Annotations.toObject( A1Arrays.class, values, mockBundle(), false);
         assertTrue("expected an A1Arrays", o instanceof A1Arrays);
         
         A1Arrays a = (A1Arrays) o;
@@ -313,5 +318,94 @@
         assertArrayEquals(new String[] {}, a.string());
     }
 
+    public @interface B1 {
+        boolean bool();
+        byte byt();
+        Class<?> clas();
+        E1 e1();
+        double doubl();
+        float floa();
+        int integer();
+        long lon();
+        short shor();
+        String string();
+        A1 a1();
+        A1[] a1array();
+    }
+    
+    public void testB1() throws Exception
+    {
+        Map<String, Object> values = b1Values();
+        
+        Object o = Annotations.toObject( B1.class, values, mockBundle(), true);
+        assertTrue("expected an B1 " + o, o instanceof B1);
+        B1 b = (B1) o;
+        checkB1(b);        
+    }
+
+    private void checkB1(B1 b)
+    {
+        checkA1(b.a1());
+        assertEquals(3, b.a1array().length);
+        checkA1(b.a1array()[0]);
+        checkA1(b.a1array()[1]);
+        checkA1(b.a1array()[2]);
+    }
+
+    private Map<String, Object> b1Values()
+    {
+        Map<String, Object> a1Values = allValues();
+        Map<String, Object> values = allValues();
+        nest(values, "a1", 0, a1Values);
+        nest(values, "a1array", 0, a1Values);
+        nest(values, "a1array", 1, a1Values);
+        nest(values, "a1array", 2, a1Values);
+        return values;
+    }
+
+    private void nest(Map<String, Object> values, String key, int i,
+        Map<String, Object> a1Values)
+    {
+        for (Map.Entry<String, Object> entry: a1Values.entrySet())
+        {
+            values.put(key + "." + i + "." + entry.getKey(), entry.getValue());
+        }
+    }
+
+    public @interface C1 {
+        boolean bool();
+        byte byt();
+        Class<?> clas();
+        E1 e1();
+        double doubl();
+        float floa();
+        int integer();
+        long lon();
+        short shor();
+        String string();
+        B1 b1();
+        B1[] b1array();
+    }
+
+    public void testC1() throws Exception
+    {
+        Map<String, Object> b1Values = b1Values();
+        Map<String, Object> values = allValues();
+        nest(values, "b1", 0, b1Values);
+        nest(values, "b1array", 0, b1Values);
+        nest(values, "b1array", 1, b1Values);
+        nest(values, "b1array", 2, b1Values);
+        
+        Object o = Annotations.toObject( C1.class, values, mockBundle(), true);
+        assertTrue("expected an B1 " + o, o instanceof C1);
+        C1 c = (C1) o;
+        checkB1(c.b1());  
+        assertEquals(3, c.b1array().length);
+        checkB1(c.b1array()[0]);
+        checkB1(c.b1array()[1]);
+        checkB1(c.b1array()[2]);
+        
+    }
+
 
 }