You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by cz...@apache.org on 2017/03/16 08:10:53 UTC

svn commit: r1787138 - in /felix/trunk/osgi-r7/scr/src: main/java/org/apache/felix/scr/impl/inject/Annotations.java test/java/org/apache/felix/scr/impl/inject/AnnotationTest.java

Author: cziegeler
Date: Thu Mar 16 08:10:53 2017
New Revision: 1787138

URL: http://svn.apache.org/viewvc?rev=1787138&view=rev
Log:
FELIX-5590 : Add support for single value annotations and new key mapping

Modified:
    felix/trunk/osgi-r7/scr/src/main/java/org/apache/felix/scr/impl/inject/Annotations.java
    felix/trunk/osgi-r7/scr/src/test/java/org/apache/felix/scr/impl/inject/AnnotationTest.java

Modified: felix/trunk/osgi-r7/scr/src/main/java/org/apache/felix/scr/impl/inject/Annotations.java
URL: http://svn.apache.org/viewvc/felix/trunk/osgi-r7/scr/src/main/java/org/apache/felix/scr/impl/inject/Annotations.java?rev=1787138&r1=1787137&r2=1787138&view=diff
==============================================================================
--- felix/trunk/osgi-r7/scr/src/main/java/org/apache/felix/scr/impl/inject/Annotations.java (original)
+++ felix/trunk/osgi-r7/scr/src/main/java/org/apache/felix/scr/impl/inject/Annotations.java Thu Mar 16 08:10:53 2017
@@ -18,16 +18,22 @@
  */
 package org.apache.felix.scr.impl.inject;
 
+import java.lang.annotation.Annotation;
 import java.lang.reflect.Array;
+import java.lang.reflect.Field;
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
 import java.lang.reflect.Proxy;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -37,18 +43,119 @@ import org.osgi.service.component.Compon
 
 public class Annotations
 {
+    /** Capture all methods defined by the annotation interface */
+    private static final Set<Method> ANNOTATION_METHODS = new HashSet<Method>();
+    static
+    {
+        for(final Method m : Annotation.class.getMethods())
+        {
+            ANNOTATION_METHODS.add(m);
+        }
+    }
+
+    /** Constant for the single element method */
+    private static final String VALUE_METHOD = "value";
+
+    /** Constant for the prefix constant. */
+    private static final String PREFIX_CONSTANT = "PREFIX_";
+
+    /**
+     * Check whether the provided type is a single element annotation.
+     * A single element annotation has a method named "value" and all
+     * other annotation methods must have a default value.
+     * @param clazz The provided type
+     * @return {@code true} if the type is a single element annotation.
+     */
+    static public boolean isSingleElementAnnotation(final Class<?> clazz)
+    {
+        boolean result = false;
+        if ( clazz.isAnnotation() )
+        {
+            result = true;
+            boolean hasValue = false;
+            for ( final Method method: clazz.getMethods() )
+            {
+                // filter out methods from Annotation
+                boolean isFromAnnotation = false;
+                for(final Method objMethod : ANNOTATION_METHODS)
+                {
+                    if ( objMethod.getName().equals(method.getName())
+                      && Arrays.equals(objMethod.getParameterTypes(), method.getParameterTypes()) )
+                    {
+                        isFromAnnotation = true;
+                        break;
+                    }
+                }
+                if ( isFromAnnotation )
+                {
+                    continue;
+                }
+                if ( VALUE_METHOD.equals(method.getName()) )
+                {
+                    hasValue = true;
+                    continue;
+                }
+                if ( method.getDefaultValue() == null )
+                {
+                    result = false;
+                    break;
+                }
+
+            }
+            if ( result )
+            {
+                result = hasValue;
+            }
+
+        }
+        return result;
+    }
+
+    static public String getPrefix(Class<?> clazz)
+    {
+        try
+        {
+            final Field f = clazz.getField(PREFIX_CONSTANT);
+            if ( Modifier.isStatic(f.getModifiers())
+                 && Modifier.isPublic(f.getModifiers())
+                 && Modifier.isFinal(f.getModifiers()) )
+            {
+                final Object value = f.get(null);
+                if ( value != null )
+                {
+                    return value.toString();
+                }
+            }
+        }
+        catch ( final Exception ignore)
+        {
+            // ignore
+        }
+        return null;
+    }
 
     @SuppressWarnings("unchecked")
 	static public <T> T toObject(Class<T> clazz, Map<String, Object> props, Bundle b, boolean supportsInterfaces )
     {
-        Map<String, Object> m = new HashMap<String, Object>();
+        final boolean isSingleElementAnn = isSingleElementAnnotation(clazz);
+        final String prefix = getPrefix(clazz);
+        final Map<String, Object> m = new HashMap<String, Object>();
 
-        Method[] methods = clazz.getMethods();
-        Map<String, Method> complexFields = new HashMap<String, Method>();
-        for ( Method method: methods )
+        final Map<String, Method> complexFields = new HashMap<String, Method>();
+        for ( final Method method: clazz.getMethods() )
         {
-            String name = method.getName();
-            String key = fixup(name);
+            final String name = method.getName();
+            final String key;
+            if ( isSingleElementAnn && name.equals(VALUE_METHOD) )
+            {
+                key = mapTypeNameToKey(clazz.getSimpleName());
+            }
+            else
+            {
+                final String mapped = mapIdentifierToKey(name);
+                key = (prefix == null ? mapped : prefix.concat(mapped));
+            }
+
             Object raw = props.get(key);
             Class<?> returnType = method.getReturnType();
             Object cooked;
@@ -211,7 +318,7 @@ public class Annotations
 
     private static final Pattern p = Pattern.compile("(\\$_\\$)|(\\$\\$)|(\\$)|(__)|(_)");
 
-    static String fixup(String name)
+    static String mapIdentifierToKey(String name)
     {
         Matcher m = p.matcher(name);
         StringBuffer b = new StringBuffer();
@@ -230,6 +337,26 @@ public class Annotations
         return b.toString();
     }
 
+    static String mapTypeNameToKey(String name)
+    {
+        final StringBuilder sb = new StringBuilder();
+        boolean lastLow = false;
+        for(final char c : name.toCharArray())
+        {
+            if ( lastLow && Character.isAlphabetic(c) && Character.isUpperCase(c) )
+            {
+                sb.append('.');
+            }
+            lastLow = false;
+            if ( Character.isAlphabetic(c) && Character.isLowerCase(c))
+            {
+                lastLow = true;
+            }
+            sb.append(Character.toLowerCase(c));
+        }
+        return sb.toString();
+    }
+
     private final static class Handler implements InvocationHandler
     {
         private final Map<String, Object> values;

Modified: felix/trunk/osgi-r7/scr/src/test/java/org/apache/felix/scr/impl/inject/AnnotationTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/osgi-r7/scr/src/test/java/org/apache/felix/scr/impl/inject/AnnotationTest.java?rev=1787138&r1=1787137&r2=1787138&view=diff
==============================================================================
--- felix/trunk/osgi-r7/scr/src/test/java/org/apache/felix/scr/impl/inject/AnnotationTest.java (original)
+++ felix/trunk/osgi-r7/scr/src/test/java/org/apache/felix/scr/impl/inject/AnnotationTest.java Thu Mar 16 08:10:53 2017
@@ -33,25 +33,32 @@ import junit.framework.TestCase;
 public class AnnotationTest extends TestCase
 {
 
-    public void testNameFixup() throws Exception
+    public void testMapIdentifierToKey() 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"));
-        assertEquals("six-prop", Annotations.fixup("six$_$prop"));
-        assertEquals("seven$.prop", Annotations.fixup("seven$$_$prop"));
+        assertEquals("foo", Annotations.mapIdentifierToKey("foo"));
+        assertEquals("foo", Annotations.mapIdentifierToKey("$foo"));
+        assertEquals("foo", Annotations.mapIdentifierToKey("foo$"));
+        assertEquals("$foo", Annotations.mapIdentifierToKey("$$foo"));
+        assertEquals("foobar", Annotations.mapIdentifierToKey("foo$bar"));
+        assertEquals("foo$bar", Annotations.mapIdentifierToKey("foo$$bar"));
+        assertEquals("foo.", Annotations.mapIdentifierToKey("foo_"));
+        assertEquals("foo_", Annotations.mapIdentifierToKey("foo__"));
+        assertEquals(".foo", Annotations.mapIdentifierToKey("_foo"));
+        assertEquals("_foo", Annotations.mapIdentifierToKey("__foo"));
+        assertEquals("foo.bar", Annotations.mapIdentifierToKey("foo_bar"));
+        assertEquals("foo_bar", Annotations.mapIdentifierToKey("foo__bar"));
+        assertEquals("foo$", Annotations.mapIdentifierToKey("foo$$$"));
+        assertEquals("foo_.", Annotations.mapIdentifierToKey("foo___"));
+        assertEquals("foo-.bar", Annotations.mapIdentifierToKey("foo$_$_bar"));
+        assertEquals("six-prop", Annotations.mapIdentifierToKey("six$_$prop"));
+        assertEquals("seven$.prop", Annotations.mapIdentifierToKey("seven$$_$prop"));
+    }
+
+    public void testMapTypeNameToKey() throws Exception
+    {
+        assertEquals("service.ranking", Annotations.mapTypeNameToKey("ServiceRanking"));
+        assertEquals("some_value", Annotations.mapTypeNameToKey("Some_Value"));
+        assertEquals("osgi.property", Annotations.mapTypeNameToKey("OSGiProperty"));
     }
 
     public enum E1 {a, b, c}
@@ -608,6 +615,70 @@ public class AnnotationTest extends Test
         assertOdder("three", ot.odder3());
     }
 
+    public @interface SETest1 {
+        String value();
+    }
+
+    public @interface SETest2 {
+        boolean foo() default true;
+        String value();
+    }
+
+    public @interface SETest3 {
+        boolean foo();
+        String value();
+    }
+
+    public @interface SETest4 {
+        boolean foo();
+        String values();
+    }
+
+    public @interface PrefixTest {
+        String PREFIX_ = "org.apache.";
+
+        boolean foo() default true;
+        String[] values() default {"a"};
+        String value();
+    }
+
+    public void testSingleElementAnnotation() throws Exception
+    {
+        assertFalse(Annotations.isSingleElementAnnotation(Map.class));
+        assertTrue(Annotations.isSingleElementAnnotation(SETest1.class));
+        assertTrue(Annotations.isSingleElementAnnotation(SETest2.class));
+        assertFalse(Annotations.isSingleElementAnnotation(SETest3.class));
+        assertFalse(Annotations.isSingleElementAnnotation(SETest4.class));
+    }
+
+    public void testGetPrefix() throws Exception
+    {
+        assertNull(Annotations.getPrefix(Map.class));
+        assertNull(Annotations.getPrefix(SETest1.class));
+        assertNull(Annotations.getPrefix(SETest2.class));
+        assertNull(Annotations.getPrefix(SETest3.class));
+        assertNull(Annotations.getPrefix(SETest4.class));
+        assertNull(Annotations.getPrefix(A1.class));
+        assertEquals("org.apache.", Annotations.getPrefix(PrefixTest.class));
+    }
+
+    public void testMappingWithPrefix() throws Exception
+    {
+        final Map<String, Object> values = new HashMap<String, Object>();
+        values.put("foo", false);
+        values.put("org.apache.foo", true);
+        values.put("values", "false-values");
+        values.put("org.apache.values", "true-values");
+        values.put("value", "false-value");
+        values.put("prefix.test", "true-value");
+
+        final PrefixTest o = Annotations.toObject( PrefixTest.class, values, mockBundle(), true);
+        assertEquals("true-value", o.value());
+        assertEquals(1, o.values().length);
+        assertEquals("true-values", o.values()[0]);
+        assertEquals(true, o.foo());
+    }
+
     private void assertOdd(String expectedContent, Object actual) {
         assertTrue("expected an Odd", actual instanceof Odd);
         assertEquals("Expected Odd contents", expectedContent, ((Odd)actual).getContent());