FELIX-4403 Implement array support in annotation elements

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1616080 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 1d7e7db..121e79d 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
@@ -40,13 +40,18 @@
     protected static final Class<?> BUNDLE_CONTEXT_CLASS = BundleContext.class;
     protected static final Class<?> INTEGER_CLASS = Integer.class;
 
-    protected final boolean m_supportsInterfaces = false; //TODO configure
+    protected final boolean m_supportsInterfaces;
 
 
     public ActivateMethod( final String methodName,
-            final boolean methodRequired, final Class<?> componentClass, final DSVersion dsVersion, final boolean configurableServiceProperties )
+            final boolean methodRequired,
+            final Class<?> componentClass,
+            final DSVersion dsVersion, 
+            final boolean configurableServiceProperties, 
+            boolean supportsInterfaces )
     {
         super( methodName, methodRequired, componentClass, dsVersion, configurableServiceProperties );
+        m_supportsInterfaces = 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 ae7fbf3..1d69628 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
@@ -23,6 +23,8 @@
 import java.lang.reflect.Proxy;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import org.osgi.framework.Bundle;
 
@@ -37,7 +39,7 @@
         for ( Method method: methods )
         {
             String name = method.getName();
-            //fix up name
+            name = fixup(name);
             Object raw = props.get(name);
             Object cooked = Coercions.coerce( method.getReturnType(), raw, b );
             m.put( name, cooked );
@@ -47,6 +49,26 @@
         return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class<?>[] { clazz }, h);
     }
     
+    private static final Pattern p = Pattern.compile("(\\$\\$)|(\\$)|(__)|(_)");
+    
+    static String fixup(String name)
+    {
+        Matcher m = p.matcher(name);
+        StringBuffer b = new StringBuffer();
+        while (m.find())
+        {
+            String replacement = "";//null;
+            if (m.group(1) != null) replacement = "\\$";
+            if (m.group(2) != null) replacement = "";
+            if (m.group(3) != null) replacement = "_";
+            if (m.group(4) != null) replacement = ".";
+            
+            m.appendReplacement(b, replacement);
+        }
+        m.appendTail(b);
+        return b.toString();
+    }
+
     private static class Handler implements InvocationHandler 
     {
         
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 8f18f09..2fff2f6 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,6 +45,44 @@
     
     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/main/java/org/apache/felix/scr/impl/helper/ComponentMethods.java b/scr/src/main/java/org/apache/felix/scr/impl/helper/ComponentMethods.java
index c6489f2..c97e43c 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/helper/ComponentMethods.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/helper/ComponentMethods.java
@@ -47,12 +47,13 @@
         }
         DSVersion dsVersion = componentMetadata.getDSVersion();
         boolean configurableServiceProperties = componentMetadata.isConfigurableServiceProperties();
+        boolean supportsInterfaces = componentMetadata.isConfigureWithInterfaces();
         m_activateMethod = new ActivateMethod( componentMetadata.getActivate(), componentMetadata
-                .isActivateDeclared(), implementationObjectClass, dsVersion, configurableServiceProperties );
+                .isActivateDeclared(), implementationObjectClass, dsVersion, configurableServiceProperties, supportsInterfaces );
         m_deactivateMethod = new DeactivateMethod( componentMetadata.getDeactivate(),
-                componentMetadata.isDeactivateDeclared(), implementationObjectClass, dsVersion, configurableServiceProperties );
+                componentMetadata.isDeactivateDeclared(), implementationObjectClass, dsVersion, configurableServiceProperties, supportsInterfaces );
 
-        m_modifiedMethod = new ModifiedMethod( componentMetadata.getModified(), implementationObjectClass, dsVersion, configurableServiceProperties );
+        m_modifiedMethod = new ModifiedMethod( componentMetadata.getModified(), implementationObjectClass, dsVersion, configurableServiceProperties, supportsInterfaces );
 
         for ( ReferenceMetadata referenceMetadata: componentMetadata.getDependencies() )
         {
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/helper/DeactivateMethod.java b/scr/src/main/java/org/apache/felix/scr/impl/helper/DeactivateMethod.java
index 6040dbd..386e51d 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/helper/DeactivateMethod.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/helper/DeactivateMethod.java
@@ -30,11 +30,10 @@
         return true;
     }
 
-
     public DeactivateMethod( final String methodName,
-            final boolean methodRequired, final Class<?> componentClass, final DSVersion dsVersion, final boolean configurableServiceProperties )
+            final boolean methodRequired, final Class<?> componentClass, final DSVersion dsVersion, final boolean configurableServiceProperties, boolean supportsInterfaces )
     {
-        super( methodName, methodRequired, componentClass, dsVersion, configurableServiceProperties );
+        super( methodName, methodRequired, componentClass, dsVersion, configurableServiceProperties, supportsInterfaces );
     }
 
     protected String getMethodNamePrefix()
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/helper/ModifiedMethod.java b/scr/src/main/java/org/apache/felix/scr/impl/helper/ModifiedMethod.java
index 107832b..56ce711 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/helper/ModifiedMethod.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/helper/ModifiedMethod.java
@@ -25,9 +25,9 @@
 {
 
     public ModifiedMethod( final String methodName,
-            final Class<?> componentClass, final DSVersion dsVersion, final boolean configurableServiceProperties )
+            final Class<?> componentClass, final DSVersion dsVersion, final boolean configurableServiceProperties, boolean supportsInterfaces )
     {
-        super( methodName, methodName != null, componentClass, dsVersion, configurableServiceProperties );
+        super( methodName, methodName != null, componentClass, dsVersion, configurableServiceProperties, supportsInterfaces );
     }
 
 
diff --git a/scr/src/test/java/org/apache/felix/scr/impl/helper/ActivateMethodTest.java b/scr/src/test/java/org/apache/felix/scr/impl/helper/ActivateMethodTest.java
index 93214af..39a4c2e 100644
--- a/scr/src/test/java/org/apache/felix/scr/impl/helper/ActivateMethodTest.java
+++ b/scr/src/test/java/org/apache/felix/scr/impl/helper/ActivateMethodTest.java
@@ -294,7 +294,7 @@
     {
         ComponentContainer<?> container = newContainer();
         SingleComponentManager<?> icm = new SingleComponentManager( container, new ComponentMethods() );
-        ActivateMethod am = new ActivateMethod( methodName, methodName != null, obj.getClass(), version, false );
+        ActivateMethod am = new ActivateMethod( methodName, methodName != null, obj.getClass(), version, false, false );
         am.invoke( obj, new ActivatorParameter( m_ctx, -1 ), null, icm );
         Method m = am.getMethod();
         assertNotNull( m );
@@ -365,7 +365,7 @@
     {
         ComponentContainer container = newContainer();
         SingleComponentManager icm = new SingleComponentManager( container, new ComponentMethods() );
-        ActivateMethod am = new ActivateMethod( methodName, methodName != null, obj.getClass(), version, false );
+        ActivateMethod am = new ActivateMethod( methodName, methodName != null, obj.getClass(), version, false, false );
         am.invoke( obj, new ActivatorParameter( m_ctx, -1 ), null, icm );
         Method m = am.getMethod();
         assertNull( m );
@@ -397,7 +397,7 @@
     }
     public void testMethodSorting() throws Exception
     {
-        ActivateMethod am = new ActivateMethod( "a", true, Sort.class, DSVersion.DS11, false );
+        ActivateMethod am = new ActivateMethod( "a", true, Sort.class, DSVersion.DS11, false, false );
         List<Method> ms = am.getSortedMethods(Sort.class);
         assertEquals(8, ms.size());
         assertEquals(1, ms.get(0).getParameterTypes().length);
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 a2d228c..f1c8327 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
@@ -18,7 +18,11 @@
  */
 package org.apache.felix.scr.impl.helper;
 
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import org.easymock.EasyMock;
@@ -28,6 +32,26 @@
 
 public class AnnotationTest extends TestCase
 {
+    
+    public void testNameFixup() throws Exception
+    {
+        assertEquals("foo", Annotations.fixup("foo"));
+        assertEquals("foo", Annotations.fixup("$foo"));
+        assertEquals("foo", Annotations.fixup("foo$"));
+        assertEquals("$foo", Annotations.fixup("$$foo"));
+        assertEquals("foobar", Annotations.fixup("foo$bar"));
+        assertEquals("foo$bar", Annotations.fixup("foo$$bar"));
+        assertEquals("foo.", Annotations.fixup("foo_"));
+        assertEquals("foo_", Annotations.fixup("foo__"));
+        assertEquals(".foo", Annotations.fixup("_foo"));
+        assertEquals("_foo", Annotations.fixup("__foo"));
+        assertEquals("foo.bar", Annotations.fixup("foo_bar"));
+        assertEquals("foo_bar", Annotations.fixup("foo__bar"));
+        assertEquals("foo$", Annotations.fixup("foo$$$"));
+        assertEquals("foo_.", Annotations.fixup("foo___"));
+        assertEquals("foo..bar", Annotations.fixup("foo$_$_bar"));
+    }
+
     public enum E1 {a, b, c}
     public @interface A1 {
         boolean bool();
@@ -46,6 +70,7 @@
     {
         Bundle b = EasyMock.createMock(Bundle.class);
         EasyMock.expect(b.loadClass(String.class.getName())).andReturn((Class) String.class).anyTimes();
+        EasyMock.expect(b.loadClass(Integer.class.getName())).andReturn((Class) Integer.class).anyTimes();
         EasyMock.replay(b);
         return b;
     }
@@ -70,6 +95,26 @@
         assertEquals("3", a.string());
     }
 
+    public void testA1FromArray() throws Exception
+    {
+        Map<String, Object> values = arrayValues();
+        
+        Object o = 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(null, a.string());
+    }
+
     private Map<String, Object> allValues()
     {
         Map<String, Object> values = new HashMap();
@@ -86,6 +131,26 @@
         return values;
     }
 
+    public void testA1NoValues() throws Exception
+    {
+        Map<String, Object> values = new HashMap<String, Object>();
+        
+        Object o = Annotations.toObject( A1.class, values, mockBundle());
+        assertTrue("expected an A1", o instanceof A1);
+        
+        A1 a = (A1) o;
+        assertEquals(false, a.bool());
+        assertEquals((byte)0, a.byt());
+        assertEquals(null, a.clas());
+        assertEquals(null, a.e1());
+        assertEquals(0d, a.doubl());
+        assertEquals(0f, a.floa());
+        assertEquals(0, a.integer());
+        assertEquals(0l,  a.lon());
+        assertEquals((short)0, a.shor());
+        assertEquals(null, a.string());
+    }
+
     public @interface A2 {
         boolean bool() default true;
         byte byt() default 5;
@@ -118,5 +183,135 @@
         assertEquals((short)3, a.shor());
         assertEquals("3", a.string());
     }
+    
+    public @interface A1Arrays {
+        boolean[] bool();
+        byte[] byt();
+        Class<?>[] clas();
+        E1[] e1();
+        double[] doubl();
+        float[] floa();
+        int[] integer();
+        long[] lon();
+        short[] shor();
+        String[] string();
+    }
+    
+    public void testA1ArraysNoValues() throws Exception
+    {
+        Map<String, Object> values = new HashMap<String, Object>();
+        
+        Object o = Annotations.toObject( A1Arrays.class, values, mockBundle());
+        assertTrue("expected an A1Arrays", o instanceof A1Arrays);
+        
+        A1Arrays a = (A1Arrays) o;
+        assertEquals(null, a.bool());
+        assertEquals(null, a.byt());
+        assertEquals(null, a.clas());
+        assertEquals(null, a.e1());
+        assertEquals(null, a.doubl());
+        assertEquals(null, a.floa());
+        assertEquals(null, a.integer());
+        assertEquals(null,  a.lon());
+        assertEquals(null, a.shor());
+        assertEquals(null, a.string());
+    }
+
+    public void testA1Array() throws Exception
+    {
+        Map<String, Object> values = allValues();
+        
+        Object o = Annotations.toObject( A1Arrays.class, values, mockBundle());
+        assertTrue("expected an A1Arrays", o instanceof A1Arrays);
+        
+        A1Arrays a = (A1Arrays) o;
+        assertArrayEquals(new boolean[] {true}, a.bool());
+        assertArrayEquals(new byte[] {(byte)12}, a.byt());
+        assertArrayEquals(new Class<?>[] {String.class}, a.clas());
+        assertArrayEquals(new E1[] {E1.a}, a.e1());
+        assertArrayEquals(new double[] {3.14d}, a.doubl());
+        assertArrayEquals(new float[] {500f}, a.floa());
+        assertArrayEquals(new int[] {3}, a.integer());
+        assertArrayEquals(new long[] {12345678l},  a.lon());
+        assertArrayEquals(new short[] {(short)3}, a.shor());
+        assertArrayEquals(new String[] {"3"}, a.string());
+    }
+
+    private void assertArrayEquals(Object a, Object b)
+    {
+        assertTrue(a.getClass().isArray());
+        assertTrue(b.getClass().isArray());
+        assertEquals("wrong length", Array.getLength(a), Array.getLength(b));
+        assertEquals("wrong type", a.getClass().getComponentType(), b.getClass().getComponentType());
+        for (int i = 0; i < Array.getLength(a); i++)
+        {
+            assertEquals("different value at " + i, Array.get(a, i), Array.get(b, i));
+        }
+        
+    }
+
+    private Map<String, Object> arrayValues()
+    {
+        Map<String, Object> values = new HashMap();
+        values.put("bool", new boolean[] {true, false});
+        values.put("byt", new byte[] {12, 3});
+        values.put("clas", new String[] {String.class.getName(), Integer.class.getName()});
+        values.put("e1", new String[] {E1.a.name(), E1.b.name()});
+        values.put("doubl", new double[] {3.14, 2.78, 9});
+        values.put("floa", new float[] {500, 37.44f});
+        values.put("integer", new int[] {3, 6, 9});
+        values.put("lon", new long[] {12345678l, -1});
+        values.put("shor", new short[] {3, 88});
+        values.put("string", new String[] {});
+        return values;
+    }
+    
+    public void testA1ArrayFromArray() throws Exception
+    {
+        Map<String, Object> values = arrayValues();
+        
+        doA1ArrayTest(values);
+    }
+
+    public void testA1ArrayFromCollection() throws Exception
+    {
+        Map<String, Object> values = arrayValues();
+        Map<String, Object> collectionValues = new HashMap<String, Object>();
+        for (Map.Entry<String, Object> entry: values.entrySet())
+        {
+            collectionValues.put(entry.getKey(), toList(entry.getValue()));
+        }
+        
+        doA1ArrayTest(collectionValues);
+    }
+
+    private List<?> toList(Object value)
+    {
+        List result = new ArrayList();
+        for (int i = 0; i < Array.getLength(value); i++)
+        {
+            result.add(Array.get(value, i));
+        }
+        return result;
+    }
+
+    private void doA1ArrayTest(Map<String, Object> values) throws ClassNotFoundException
+    {
+        Object o = Annotations.toObject( A1Arrays.class, values, mockBundle());
+        assertTrue("expected an A1Arrays", o instanceof A1Arrays);
+        
+        A1Arrays a = (A1Arrays) o;
+        assertArrayEquals(new boolean[] {true, false}, a.bool());
+        assertArrayEquals(new byte[] {12, 3}, a.byt());
+        assertArrayEquals(new Class<?>[] {String.class, Integer.class}, a.clas());
+        assertArrayEquals(new E1[] {E1.a, E1.b}, a.e1());
+        assertArrayEquals(new double[] {3.14, 2.78, 9}, a.doubl());
+        assertArrayEquals(new float[] {500f, 37.44f}, a.floa());
+        assertArrayEquals(new int[] {3, 6, 9}, a.integer());
+        assertArrayEquals(new long[] {12345678l, -1},  a.lon());
+        assertArrayEquals(new short[] {(short)3, 88}, a.shor());
+        assertArrayEquals(new String[] {}, a.string());
+    }
+
 
 }