SLING-8986 osgi-mock: Incorrect selection of fields with assignable types for Set references
diff --git a/core/src/main/java/org/apache/sling/testing/mock/osgi/OsgiServiceUtil.java b/core/src/main/java/org/apache/sling/testing/mock/osgi/OsgiServiceUtil.java
index 8726f9d..27135b9 100644
--- a/core/src/main/java/org/apache/sling/testing/mock/osgi/OsgiServiceUtil.java
+++ b/core/src/main/java/org/apache/sling/testing/mock/osgi/OsgiServiceUtil.java
@@ -25,9 +25,12 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.SortedSet;
+import java.util.TreeSet;
 
 import org.apache.commons.lang3.StringUtils;
 import org.apache.felix.scr.impl.inject.Annotations;
@@ -37,6 +40,7 @@
 import org.apache.sling.testing.mock.osgi.OsgiMetadataUtil.Reference;
 import org.apache.sling.testing.mock.osgi.OsgiMetadataUtil.ReferencePolicy;
 import org.apache.sling.testing.mock.osgi.OsgiMetadataUtil.ReferencePolicyOption;
+import org.jetbrains.annotations.NotNull;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.framework.ServiceReference;
@@ -351,7 +355,22 @@
         }
         return null;
     }
-    
+
+    private static Field getCollectionField(Class clazz, String fieldName) {
+        Field[] fields = clazz.getDeclaredFields();
+        for (Field field : fields) {
+            if (StringUtils.equals(field.getName(), fieldName) && Collection.class.isAssignableFrom(field.getType())) {
+                return field;
+            }
+        }
+        // not found? check super classes
+        Class<?> superClass = clazz.getSuperclass();
+        if (superClass != null && superClass != Object.class) {
+            return getCollectionField(superClass, fieldName);
+        }
+        return null;
+    }
+
     private static void setField(Object target, Field field, Object value) {
         try {
             field.setAccessible(true);
@@ -494,20 +513,7 @@
                                 item = serviceInfo.getServiceReference();
                             }
                         }
-                        // 1. collection
-                        Field field = getFieldWithAssignableType(targetClass, fieldName, Collection.class);
-                        if (field != null) {
-                            if (bind) {
-                                addToCollection(target, field, item);
-                            }
-                            else {
-                                removeFromCollection(target, field, item);
-                            }
-                            return;
-                        }
-                        
-                        // 2. list
-                        field = getField(targetClass, fieldName, List.class);
+                        Field field = getCollectionField(targetClass, fieldName);
                         if (field != null) {
                             if (bind) {
                                 addToCollection(target, field, item);
@@ -551,17 +557,14 @@
             field.setAccessible(true);
             Collection<Object> collection = (Collection<Object>)field.get(target);
             if (collection == null) {
-                collection = new ArrayList<Object>();
+                collection = newCollectionInstance(field.getType());
             }
             if (item != null) {
                 collection.add(item);
             }
             field.set(target, collection);
             
-        } catch (IllegalAccessException ex) {
-            throw new RuntimeException("Unable to set field '" + field.getName() + "' for class "
-                    + target.getClass().getName(), ex);
-        } catch (IllegalArgumentException ex) {
+        } catch (IllegalAccessException | IllegalArgumentException | InstantiationException ex) {
             throw new RuntimeException("Unable to set field '" + field.getName() + "' for class "
                     + target.getClass().getName(), ex);
         }
@@ -573,22 +576,34 @@
             field.setAccessible(true);
             Collection<Object> collection = (Collection<Object>)field.get(target);
             if (collection == null) {
-                collection = new ArrayList<Object>();
+                collection = newCollectionInstance(field.getType());
             }
             if (item != null) {
                 collection.remove(item);
             }
             field.set(target, collection);
             
-        } catch (IllegalAccessException ex) {
-            throw new RuntimeException("Unable to set field '" + field.getName() + "' for class "
-                    + target.getClass().getName(), ex);
-        } catch (IllegalArgumentException ex) {
+        } catch (IllegalAccessException | IllegalArgumentException | InstantiationException ex) {
             throw new RuntimeException("Unable to set field '" + field.getName() + "' for class "
                     + target.getClass().getName(), ex);
         }
     }
 
+    @SuppressWarnings({ "unchecked", "null" })
+    private static @NotNull Collection<Object> newCollectionInstance(Class<?> collectionType)
+            throws InstantiationException, IllegalAccessException {
+        if (collectionType == List.class) {
+            return new ArrayList<>();
+        }
+        if (collectionType == Set.class) {
+            return new HashSet<>();
+        }
+        if (collectionType == SortedSet.class) {
+            return new TreeSet();
+        }
+        return (Collection)collectionType.newInstance();
+    }
+
     /**
      * Directly invoke bind method on service for the given reference.
      * @param reference Reference metadata
diff --git a/core/src/test/java/org/apache/sling/testing/mock/osgi/OsgiMetadataUtilTest.java b/core/src/test/java/org/apache/sling/testing/mock/osgi/OsgiMetadataUtilTest.java
index 896d68f..b6387b9 100644
--- a/core/src/test/java/org/apache/sling/testing/mock/osgi/OsgiMetadataUtilTest.java
+++ b/core/src/test/java/org/apache/sling/testing/mock/osgi/OsgiMetadataUtilTest.java
@@ -66,7 +66,7 @@
     public void testReferences() {
         OsgiMetadata metadata = OsgiMetadataUtil.getMetadata(OsgiServiceUtilTest.Service3.class);
         List<Reference> references = metadata.getReferences();
-        assertEquals(4, references.size());
+        assertEquals(5, references.size());
 
         Reference ref1 = references.get(2);
         assertEquals("reference2", ref1.getName());
diff --git a/core/src/test/java/org/apache/sling/testing/mock/osgi/OsgiServiceUtilTest.java b/core/src/test/java/org/apache/sling/testing/mock/osgi/OsgiServiceUtilTest.java
index a962daa..e22a64e 100644
--- a/core/src/test/java/org/apache/sling/testing/mock/osgi/OsgiServiceUtilTest.java
+++ b/core/src/test/java/org/apache/sling/testing/mock/osgi/OsgiServiceUtilTest.java
@@ -27,9 +27,11 @@
 
 import java.util.ArrayList;
 import java.util.Dictionary;
+import java.util.HashSet;
 import java.util.Hashtable;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import org.junit.After;
 import org.junit.Before;
@@ -97,6 +99,40 @@
         assertEquals(1, reference3Configs.size());
         assertEquals(200, reference3Configs.get(0).get(Constants.SERVICE_RANKING));
 
+        Set<ServiceSuperInterface3> references3Set = service3.getReferences3Set();
+        assertEquals(1, references3Set.size());
+        assertSame(service2, references3Set.iterator().next());
+
+        assertTrue(MockOsgi.deactivate(service3, bundleContext));
+        assertNull(service3.getComponentContext());
+    }
+
+    @Test
+    public void testService3OsgiR6() {
+        Service3OsgiR6 service3 = new Service3OsgiR6();
+        assertTrue(MockOsgi.injectServices(service3, bundleContext));
+
+        Dictionary<String, Object> service3Config = new Hashtable<String, Object>();
+        service3Config.put("prop1", "value1");
+        assertTrue(MockOsgi.activate(service3, bundleContext, service3Config));
+
+        assertNotNull(service3.getComponentContext());
+        assertEquals(service3Config.get("prop1"), service3.getComponentContext().getProperties().get("prop1"));
+
+        assertSame(service1, service3.getReference1());
+
+        List<ServiceInterface2> references2 = service3.getReferences2();
+        assertEquals(1, references2.size());
+        assertSame(service2, references2.get(0));
+
+        List<ServiceSuperInterface3> references3 = service3.getReferences3();
+        assertEquals(1, references3.size());
+        assertSame(service2, references3.get(0));
+
+        Set<ServiceSuperInterface3> references3Set = service3.getReferences3Set();
+        assertEquals(1, references3Set.size());
+        assertSame(service2, references3Set.iterator().next());
+
         assertTrue(MockOsgi.deactivate(service3, bundleContext));
         assertNull(service3.getComponentContext());
     }
@@ -241,12 +277,16 @@
         @Reference(cardinality = ReferenceCardinality.OPTIONAL, bind="bindReference1Optional", unbind="unbindReference1Optional")
         private ServiceInterface1Optional reference1Optional;
 
-        private List<ServiceReference> references2 = new ArrayList<ServiceReference>();
+        private List<ServiceReference> references2 = new ArrayList<>();
 
         @Reference(name = "reference3", service = ServiceInterface3.class, cardinality = ReferenceCardinality.MULTIPLE,
                 bind="bindReference3", unbind="unbindReference3")
-        private List<ServiceSuperInterface3> references3 = new ArrayList<ServiceSuperInterface3>();
-        private List<Map<String, Object>> reference3Configs = new ArrayList<Map<String, Object>>();
+        private List<ServiceSuperInterface3> references3 = new ArrayList<>();
+        private List<Map<String, Object>> reference3Configs = new ArrayList<>();
+
+        @Reference(name = "references3Set", service = ServiceInterface3.class, cardinality = ReferenceCardinality.MULTIPLE,
+                bind="bindReferenceSet3", unbind="unbindReferenceSet3")
+        private Set<ServiceSuperInterface3> references3Set = new HashSet<>();
 
         private ComponentContext componentContext;
         private Map<String, Object> config;
@@ -276,7 +316,7 @@
         }
 
         public List<ServiceInterface2> getReferences2() {
-            List<ServiceInterface2> services = new ArrayList<ServiceInterface2>();
+            List<ServiceInterface2> services = new ArrayList<>();
             for (ServiceReference<?> serviceReference : references2) {
                 services.add((ServiceInterface2)componentContext.getBundleContext().getService(serviceReference));
             }
@@ -291,6 +331,10 @@
             return this.reference3Configs;
         }
 
+        public Set<ServiceSuperInterface3> getReferences3Set() {
+            return this.references3Set;
+        }
+
         public ComponentContext getComponentContext() {
             return this.componentContext;
         }
@@ -333,6 +377,14 @@
             reference3Configs.remove(serviceConfig);
         }
 
+        protected void bindReference3Set(ServiceSuperInterface3 service, Map<String, Object> serviceConfig) {
+            references3Set.add(service);
+        }
+
+        protected void unbindReference3Set(ServiceSuperInterface3 service, Map<String, Object> serviceConfig) {
+            references3Set.remove(service);
+        }
+
     }
 
     public static class Service3OsgiR6 {
@@ -343,6 +395,7 @@
         private List<ServiceSuperInterface3> references3;
         private List<ServiceSuperInterface3> references3Filtered;
         private ServiceSuperInterface3 reference3DynamicFiltered;
+        private Set<ServiceSuperInterface3> references3Set;
 
         private ComponentContext componentContext;
         private Map<String, Object> config;
@@ -392,6 +445,10 @@
             return this.reference3DynamicFiltered;
         }
 
+        public Set<ServiceSuperInterface3> getReferences3Set() {
+            return this.references3Set;
+        }
+
         public ComponentContext getComponentContext() {
             return this.componentContext;
         }
diff --git a/core/src/test/resources/OSGI-INF/org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest.xml b/core/src/test/resources/OSGI-INF/org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest.xml
index b4a4266..8bb4422 100644
--- a/core/src/test/resources/OSGI-INF/org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest.xml
+++ b/core/src/test/resources/OSGI-INF/org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest.xml
@@ -42,6 +42,7 @@
     <reference name="reference1Optional" interface="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$ServiceInterface1Optional" cardinality="0..1" policy="dynamic" bind="bindReference1Optional" unbind="unbindReference1Optional"/>
     <reference name="reference2" interface="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$ServiceInterface2" cardinality="1..n" policy="dynamic" bind="bindReference2" unbind="unbindReference2"/>
     <reference name="reference3" interface="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$ServiceInterface3" cardinality="0..n" policy="dynamic" bind="bindReference3" unbind="unbindReference3"/>
+    <reference name="reference3Set" interface="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$ServiceInterface3" cardinality="0..n" policy="dynamic" field="references3" bind="bindReference3Set" unbind="unbindReference3Set"/>
   </scr:component>
   <scr:component name="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$Service3OsgiR6" activate="activate" deactivate="deactivate" modified="modified">
     <implementation class="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$Service3OsgiR6"/>
@@ -52,6 +53,7 @@
     <reference name="reference3" interface="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$ServiceInterface3" cardinality="0..n" policy="dynamic" field="references3" field-collection-type="service"/>
     <reference name="references3Filtered" interface="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$ServiceInterface3" cardinality="0..n" policy="dynamic" field="references3Filtered" field-collection-type="service" target="(prop1=abc)"/>
     <reference name="reference3DynamicFiltered" interface="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$ServiceInterface3" cardinality="0..1" policy="dynamic" field="reference3DynamicFiltered" field-collection-type="service"/>
+    <reference name="reference3Set" interface="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$ServiceInterface3" cardinality="0..n" policy="dynamic" field="references3Set" field-collection-type="service"/>
   </scr:component>
   <scr:component name="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$Service3StaticGreedy" activate="activate" deactivate="deactivate" modified="modified">
     <implementation class="org.apache.sling.testing.mock.osgi.OsgiServiceUtilTest$Service3StaticGreedyImpl"/>