FELIX-4631 : [DS][R6/RFC212] Implement field injection. WiP

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1637763 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/helper/ClassUtils.java b/scr/src/main/java/org/apache/felix/scr/impl/helper/ClassUtils.java
index 0b90999..dc50270 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/helper/ClassUtils.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/helper/ClassUtils.java
@@ -19,6 +19,7 @@
 package org.apache.felix.scr.impl.helper;
 
 
+import java.util.Collection;
 import java.util.Map;
 
 import org.apache.felix.scr.impl.Activator;
@@ -43,6 +44,7 @@
     public static final Class<?> MAP_CLASS = Map.class;
     public static final Class<?> MAP_ENTRY_CLASS = Map.Entry.class;
 
+    public static final Class<?> COLLECTION_CLASS = Collection.class;
 
     static {
         Class<?> serviceObjectsClass = null;
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/helper/FieldHandler.java b/scr/src/main/java/org/apache/felix/scr/impl/helper/FieldHandler.java
index 3bb61fcb..ee040ee 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/helper/FieldHandler.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/helper/FieldHandler.java
@@ -24,6 +24,8 @@
 import java.lang.reflect.Modifier;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
+import java.util.Collections;
+import java.util.Map;
 
 import org.apache.felix.scr.impl.manager.ComponentContextImpl;
 import org.apache.felix.scr.impl.manager.RefPair;
@@ -36,6 +38,14 @@
  */
 public class FieldHandler
 {
+    private enum ParamType {
+        serviceReference,
+        serviceObjects,
+        serviceType,
+        map,
+        tuple
+    }
+
     /** The reference metadata. */
     private final ReferenceMetadata metadata;
 
@@ -45,6 +55,9 @@
     /** The field used for the injection. */
     private volatile Field field;
 
+    /** Value type. */
+    private volatile ParamType valueType;
+
     /** State handling. */
     private volatile State state;
 
@@ -248,30 +261,52 @@
         {
             if ( fieldType.isAssignableFrom(referenceType) )
             {
-                // service
+                valueType = ParamType.serviceType;
             }
             else if ( fieldType == ClassUtils.SERVICE_REFERENCE_CLASS )
             {
-                // service reference
+                valueType = ParamType.serviceReference;
             }
             else if ( fieldType == ClassUtils.SERVICE_OBJECTS_CLASS )
             {
-                // service objects
+                valueType = ParamType.serviceObjects;
             }
             else if ( fieldType == ClassUtils.MAP_CLASS )
             {
-                // map
+                valueType = ParamType.map;
             }
             else if ( fieldType == ClassUtils.MAP_ENTRY_CLASS )
             {
-                // map entry
+                valueType = ParamType.tuple;
             }
             else
             {
-                logger.log( LogService.LOG_WARNING, "Field {0} in component {1} has unsupported type {2}", new Object[]
+                logger.log( LogService.LOG_ERROR, "Field {0} in component {1} has unsupported type {2}", new Object[]
                         {metadata.getField(), this.componentClass, fieldType.getName()}, null );
                 return null;
             }
+
+            // if the field is dynamic and optional it has to be volatile
+            if ( !metadata.isStatic() && metadata.isOptional() )
+            {
+                if ( !Modifier.isVolatile(f.getModifiers()) )
+                {
+                    logger.log( LogService.LOG_ERROR, "Field {0} in component {1} must be declared volatile to handle a dynamic reference", new Object[]
+                            {metadata.getField(), this.componentClass}, null );
+                    return null;
+                }
+            }
+        }
+        else
+        {
+            // multiple cardinality, field type must be collection or subtype
+            if ( !ClassUtils.COLLECTION_CLASS.isAssignableFrom(fieldType) )
+            {
+                logger.log( LogService.LOG_ERROR, "Field {0} in component {1} has unsupported type {2}", new Object[]
+                        {metadata.getField(), this.componentClass, fieldType.getName()}, null );
+                return null;
+            }
+
         }
         return f;
     }
@@ -282,7 +317,7 @@
         UPDATED
     };
 
-    private MethodResult invokeMethod( final METHOD_TYPE mType,
+    private MethodResult updateField( final METHOD_TYPE mType,
             final Object componentInstance,
             final BindParameters bp,
             final SimpleLogger logger )
@@ -291,11 +326,23 @@
         final ComponentContextImpl key = bp.getComponentContext();
         final RefPair<?, ?> refPair = bp.getRefPair();
 
-        final Object serviceObject = refPair.getServiceObject(key);
+        final Object obj;
+        switch ( this.valueType ) {
+            case serviceType : obj = refPair.getServiceObject(key); break;
+            case serviceReference : obj = refPair.getRef(); break;
+            case serviceObjects : obj = refPair.getServiceObjects(); break;
+            case map : obj = new ReadOnlyDictionary<String, Object>( refPair.getRef() ); break;
+            case tuple : final Object tupleKey = new ReadOnlyDictionary<String, Object>( refPair.getRef() );
+                         final Object tupleValue = refPair.getServiceObject(key);
+                         final Map<?, ?> tupleMap = Collections.singletonMap(tupleKey, tupleValue);
+                         obj = tupleMap.entrySet().iterator().next();
+                         break;
+            default: obj = null;
+        }
 
         try {
             if ( mType == METHOD_TYPE.BIND ) {
-                field.set(componentInstance, serviceObject);
+                field.set(componentInstance, obj);
             } else if ( mType == METHOD_TYPE.UNBIND ) {
                 field.set(componentInstance, null);
             }
@@ -480,99 +527,69 @@
                 final SimpleLogger logger )
             throws InvocationTargetException
         {
-            return baseMethod.invokeMethod( mType, componentInstance, rawParameter, logger );
+            return baseMethod.updateField( mType, componentInstance, rawParameter, logger );
         }
     }
 
-    public ReferenceMethod getBind() {
-        return new ReferenceMethod() {
+    public static final class ReferenceMethodImpl implements ReferenceMethod {
 
-            public MethodResult invoke(Object componentInstance,
-                    BindParameters rawParameter,
-                    MethodResult methodCallFailureResult, SimpleLogger logger) {
-                try
-                {
-                    return state.invoke( FieldHandler.this, METHOD_TYPE.BIND, componentInstance, rawParameter, logger );
-                }
-                catch ( InvocationTargetException ite )
-                {
-                    logger.log( LogService.LOG_ERROR, "The {0} field has thrown an exception", new Object[]
-                        { metadata.getField() }, ite.getCause() );
-                }
+        private final METHOD_TYPE methodType;
 
-                return methodCallFailureResult;
+        private final FieldHandler handler;
+
+        public ReferenceMethodImpl(final METHOD_TYPE mt, final FieldHandler handler) {
+            this.methodType = mt;
+            this.handler = handler;
+        }
+
+        public MethodResult invoke(final Object componentInstance,
+                final BindParameters rawParameter,
+                final MethodResult methodCallFailureResult,
+                final SimpleLogger logger) {
+            try
+            {
+                return handler.state.invoke( handler,
+                        methodType,
+                        componentInstance,
+                        rawParameter,
+                        logger );
+            }
+            catch ( final InvocationTargetException ite )
+            {
+                logger.log( LogService.LOG_ERROR, "The {0} field has thrown an exception", new Object[]
+                    { handler.metadata.getField() }, ite.getCause() );
             }
 
-            public <S, T> boolean getServiceObject(ComponentContextImpl<S> key,
-                    RefPair<S, T> refPair, BundleContext context,
-                    SimpleLogger logger) {
+            return methodCallFailureResult;
+        }
+
+        public <S, T> boolean getServiceObject(final ComponentContextImpl<S> key,
+                final RefPair<S, T> refPair,
+                final BundleContext context,
+                final SimpleLogger logger) {
+            if ( methodType != METHOD_TYPE.UNBIND )
+            {
                 //??? this resolves which we need.... better way?
-                if ( refPair.getServiceObject(key) == null )
+                if ( refPair.getServiceObject(key) == null
+                    )
+                    // TODO: && (handler.valueType == ParamType.serviceType || handler.valueType == ParamType.tuple ) )
                 {
                     return refPair.getServiceObject(key, context, logger);
                 }
-                return true;
             }
-        };
+            return true;
+        }
+
+    }
+    public ReferenceMethod getBind() {
+        return new ReferenceMethodImpl(METHOD_TYPE.BIND, this);
     }
 
     public ReferenceMethod getUnbind() {
-        return new ReferenceMethod() {
-
-            public MethodResult invoke(Object componentInstance,
-                    BindParameters rawParameter,
-                    MethodResult methodCallFailureResult, SimpleLogger logger) {
-                try
-                {
-                    return state.invoke( FieldHandler.this, METHOD_TYPE.UNBIND, componentInstance, rawParameter, logger );
-                }
-                catch ( InvocationTargetException ite )
-                {
-                    logger.log( LogService.LOG_ERROR, "The {0} field has thrown an exception", new Object[]
-                        { metadata.getField() }, ite.getCause() );
-                }
-
-                return methodCallFailureResult;
-            }
-
-            public <S, T> boolean getServiceObject(ComponentContextImpl<S> key,
-                    RefPair<S, T> refPair, BundleContext context,
-                    SimpleLogger logger) {
-                // TODO ?!?
-                return true;
-            }
-        };
+        return new ReferenceMethodImpl(METHOD_TYPE.UNBIND, this);
     }
 
     public ReferenceMethod getUpdated() {
-        return new ReferenceMethod() {
-
-            public MethodResult invoke(Object componentInstance,
-                    BindParameters rawParameter,
-                    MethodResult methodCallFailureResult, SimpleLogger logger) {
-                try
-                {
-                    return state.invoke( FieldHandler.this, METHOD_TYPE.UPDATED, componentInstance, rawParameter, logger );
-                }
-                catch ( InvocationTargetException ite )
-                {
-                    logger.log( LogService.LOG_ERROR, "The {0} field has thrown an exception", new Object[]
-                        { metadata.getField() }, ite.getCause() );
-                }
-
-                return methodCallFailureResult;
-            }
-
-            public <S, T> boolean getServiceObject(ComponentContextImpl<S> key,
-                    RefPair<S, T> refPair, BundleContext context,
-                    SimpleLogger logger) {
-                //??? this resolves which we need.... better way?
-                if ( refPair.getServiceObject(key) == null )
-                {
-                    return refPair.getServiceObject(key, context, logger);
-                }
-                return true;
-            }
-        };
+        return new ReferenceMethodImpl(METHOD_TYPE.UPDATED, this);
     }
 }