ClassPathScanner now also scans for @Singleton without interfaces
authorMichael Vorburger <mike@vorburger.ch>
Sat, 22 Dec 2018 01:48:21 +0000 (02:48 +0100)
committerMichael Vorburger <mike@vorburger.ch>
Wed, 1 Jul 2020 23:38:15 +0000 (01:38 +0200)
Signed-off-by: Michael Vorburger <mike@vorburger.ch>
src/main/java/org/opendaylight/infrautils/inject/ClassPathScanner.java
src/main/java/org/opendaylight/infrautils/inject/guice/GuiceClassPathBinder.java
src/test/java/org/opendaylight/infrautils/inject/tests/ClassPathScannerTest.java
src/test/java/org/opendaylight/infrautils/inject/tests/ClassPathScannerTestNoInterfacesImplementation.java [new file with mode: 0644]

index 936d5691526f0220a30e0641c42e24db0ee9aee4..632269ab9a9273e0a12d496a11bfc5dc8f31a33a 100644 (file)
@@ -9,12 +9,14 @@ package org.opendaylight.infrautils.inject;
 
 import io.github.classgraph.ClassGraph;
 import io.github.classgraph.ClassInfo;
+import io.github.classgraph.ClassInfoList;
 import io.github.classgraph.ScanResult;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 import javax.inject.Singleton;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -28,6 +30,7 @@ public class ClassPathScanner {
     private static final Logger LOG = LoggerFactory.getLogger(ClassPathScanner.class);
 
     private final Map<String, Class> implementations = new HashMap<>();
+    private final Set<Class<?>> singletons = new HashSet<>();
 
     /**
      * Create a class path scanner, scanning packages with the given prefix for {@literal @}Singleton annotated classes.
@@ -43,13 +46,18 @@ public class ClassPathScanner {
                      .scan()) {
             Set<String> duplicateInterfaces = new HashSet<>();
             for (ClassInfo singletonInfo : scanResult.getClassesWithAnnotation(Singleton.class.getName())) {
-                for (ClassInfo interfaceInfo : singletonInfo.getInterfaces()) {
-                    String interfaceName = interfaceInfo.getName();
-                    if (!duplicateInterfaces.contains(interfaceName)) {
-                        if (implementations.put(interfaceName, singletonInfo.loadClass()) != null) {
-                            LOG.debug("{} is declared multiple times, ignoring it", interfaceName);
-                            implementations.remove(interfaceName);
-                            duplicateInterfaces.add(interfaceName);
+                ClassInfoList interfaces = singletonInfo.getInterfaces();
+                if (interfaces.isEmpty()) {
+                    singletons.add(singletonInfo.loadClass());
+                } else {
+                    for (ClassInfo interfaceInfo : interfaces) {
+                        String interfaceName = interfaceInfo.getName();
+                        if (!duplicateInterfaces.contains(interfaceName)) {
+                            if (implementations.put(interfaceName, singletonInfo.loadClass()) != null) {
+                                LOG.debug("{} is declared multiple times, ignoring it", interfaceName);
+                                implementations.remove(interfaceName);
+                                duplicateInterfaces.add(interfaceName);
+                            }
                         }
                     }
                 }
@@ -63,7 +71,7 @@ public class ClassPathScanner {
      * @param prefix the package prefix of Singleton implementations to consider
      * @param binder The binder (modeled as a generic consumer)
      */
-    public void bindAllSingletons(String prefix, BiConsumer<Class, Class> binder) {
+    public void bindAllSingletons(String prefix, BiConsumer<Class, Class> binder, Consumer<Class> singletonConsumer) {
         implementations.forEach((interfaceName, singletonClass) -> {
             if (singletonClass.getName().startsWith(prefix)) {
                 try {
@@ -76,6 +84,7 @@ public class ClassPathScanner {
                 }
             }
         });
-        // we do not want nor have to scan the @Singleton's @Inject annotated constructor; will also auto-discover.
+        singletons.stream().filter(singletonClass -> singletonClass.getName().startsWith(prefix))
+                .forEach(singletonClass -> singletonConsumer.accept(singletonClass));
     }
 }
index 430a8a35bfebb37fd8236cc106e498129e7366a7..ba2273a6f45b15df4f5a809d6a95424127756dc8 100644 (file)
@@ -29,6 +29,8 @@ public class GuiceClassPathBinder {
      */
     @SuppressWarnings("unchecked")
     public void bindAllSingletons(String prefix, Binder binder) {
-        scanner.bindAllSingletons(prefix, (contract, implementation) -> binder.bind(contract).to(implementation));
+        scanner.bindAllSingletons(prefix,
+            (contract, implementation) -> binder.bind(contract).to(implementation),
+            singleton -> binder.bind(singleton));
     }
 }
index f95d19f97802363e704632524dc1afd86b6ed4e7..a6723b665f6b9e05336862f00dd02dae7c52ef94 100644 (file)
@@ -10,7 +10,9 @@ package org.opendaylight.infrautils.inject.tests;
 import static com.google.common.truth.Truth.assertThat;
 
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 import org.junit.Test;
 import org.opendaylight.infrautils.inject.ClassPathScanner;
 
@@ -19,11 +21,22 @@ public class ClassPathScannerTest {
     private static final String PREFIX = "org.opendaylight.infrautils.inject.tests";
 
     @Test
-    public void testImplicitBinding() {
+    public void testClasspathScanning() {
+        Set<Class<?>> singletons = new HashSet<>();
         Map<Class<?>, Class<?>> bindings = new HashMap<>();
-        new ClassPathScanner(PREFIX).bindAllSingletons(PREFIX, bindings::put);
+        new ClassPathScanner(PREFIX).bindAllSingletons(PREFIX, bindings::put, singletons::add);
         assertThat(bindings).containsExactly(
                 ClassPathScannerTestTopInterface.class, ClassPathScannerTestImplementation.class,
                 ClassPathScannerTestAnotherInterface.class, ClassPathScannerTestImplementation.class);
+        assertThat(singletons).containsExactly(ClassPathScannerTestNoInterfacesImplementation.class);
+    }
+
+    @Test
+    public void testClasspathExclusion() {
+        Set<Class<?>> singletons = new HashSet<>();
+        Map<Class<?>, Class<?>> bindings = new HashMap<>();
+        new ClassPathScanner(PREFIX).bindAllSingletons("nope", bindings::put, singletons::add);
+        assertThat(bindings).isEmpty();
+        assertThat(singletons).isEmpty();
     }
 }
diff --git a/src/test/java/org/opendaylight/infrautils/inject/tests/ClassPathScannerTestNoInterfacesImplementation.java b/src/test/java/org/opendaylight/infrautils/inject/tests/ClassPathScannerTestNoInterfacesImplementation.java
new file mode 100644 (file)
index 0000000..eabe1bc
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ * Copyright © 2018 Red Hat, Inc. and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.infrautils.inject.tests;
+
+import javax.inject.Singleton;
+
+@Singleton
+public class ClassPathScannerTestNoInterfacesImplementation {
+}