From c22fd2957b33932d026b7008b3f332cf4238cf3b Mon Sep 17 00:00:00 2001 From: Crutcher Dunnavant Date: Mon, 8 Jan 2024 03:12:36 -0800 Subject: [PATCH] Refactor ServiceHelper and add tests and docs. (#452) --- .../dev/langchain4j/spi/ServiceHelper.java | 77 ++++++++++++++----- .../dev/langchain4j/spi/ExampleService.java | 5 ++ .../spi/ExampleServiceGoodbye.java | 8 ++ .../langchain4j/spi/ExampleServiceHello.java | 8 ++ .../langchain4j/spi/ServiceHelperTest.java | 26 +++++++ .../dev.langchain4j.spi.ExampleService | 2 + 6 files changed, 107 insertions(+), 19 deletions(-) create mode 100644 langchain4j-core/src/test/java/dev/langchain4j/spi/ExampleService.java create mode 100644 langchain4j-core/src/test/java/dev/langchain4j/spi/ExampleServiceGoodbye.java create mode 100644 langchain4j-core/src/test/java/dev/langchain4j/spi/ExampleServiceHello.java create mode 100644 langchain4j-core/src/test/java/dev/langchain4j/spi/ServiceHelperTest.java create mode 100644 langchain4j-core/src/test/resources/META-INF/services/dev.langchain4j.spi.ExampleService diff --git a/langchain4j-core/src/main/java/dev/langchain4j/spi/ServiceHelper.java b/langchain4j-core/src/main/java/dev/langchain4j/spi/ServiceHelper.java index 84ad96185cc..50cd1519f2f 100644 --- a/langchain4j-core/src/main/java/dev/langchain4j/spi/ServiceHelper.java +++ b/langchain4j-core/src/main/java/dev/langchain4j/spi/ServiceHelper.java @@ -1,37 +1,76 @@ package dev.langchain4j.spi; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.ServiceLoader; +/** + * Utility wrapper around {@code ServiceLoader.load()}. + */ public class ServiceHelper { + /** + * Utility class, no public constructor. + */ + private ServiceHelper() { + } + /** + * Load all the services of a given type. + * + * @param clazz the type of service + * @param the type of service + * @return the list of services, empty if none + */ public static Collection loadFactories(Class clazz) { return loadFactories(clazz, null); } - public static Collection loadFactories(Class clazz, ClassLoader classLoader) { - List list = new ArrayList<>(); - ServiceLoader factories; + /** + * Load all the services of a given type. + * + *

Utility mechanism around {@code ServiceLoader.load()}

+ * + *
    + *
  • If classloader is {@code null}, will try {@code ServiceLoader.load(clazz)}
  • + *
  • If classloader is not {@code null}, will try {@code ServiceLoader.load(clazz, classloader)}
  • + *
+ * + *

If the above return nothing, will fall back to {@code ServiceLoader.load(clazz, $this class loader$)}

+ * + * @param clazz the type of service + * @param classLoader the classloader to use, may be null + * @param the type of service + * @return the list of services, empty if none + */ + public static Collection loadFactories(Class clazz, /* @Nullable */ ClassLoader classLoader) { + List result; if (classLoader != null) { - factories = ServiceLoader.load(clazz, classLoader); + result = loadAll(ServiceLoader.load(clazz, classLoader)); } else { // this is equivalent to: // ServiceLoader.load(clazz, TCCL); - factories = ServiceLoader.load(clazz); + result = loadAll(ServiceLoader.load(clazz)); } - if (factories.iterator().hasNext()) { - factories.iterator().forEachRemaining(list::add); - return list; - } else { - // By default ServiceLoader.load uses the TCCL, this may not be enough in environment dealing with - // classloaders differently such as OSGi. So we should try to use the classloader having loaded this + if (result.isEmpty()) { + // By default, ServiceLoader.load uses the TCCL, this may not be enough in environment dealing with + // classloaders differently such as OSGi. So we should try to use the classloader having loaded this // class. In OSGi it would be the bundle exposing vert.x and so have access to all its classes. - factories = ServiceLoader.load(clazz, ServiceHelper.class.getClassLoader()); - if (factories.iterator().hasNext()) { - factories.iterator().forEachRemaining(list::add); - return list; - } else { - return Collections.emptyList(); - } + result = loadAll(ServiceLoader.load(clazz, ServiceHelper.class.getClassLoader())); } + return result; + } + + /** + * Load all the services from a ServiceLoader. + * + * @param loader the loader + * @param the type of service + * @return the list of services, empty if none + */ + private static List loadAll(ServiceLoader loader) { + List list = new ArrayList<>(); + loader.iterator().forEachRemaining(list::add); + return list; } } diff --git a/langchain4j-core/src/test/java/dev/langchain4j/spi/ExampleService.java b/langchain4j-core/src/test/java/dev/langchain4j/spi/ExampleService.java new file mode 100644 index 00000000000..1cd041271ae --- /dev/null +++ b/langchain4j-core/src/test/java/dev/langchain4j/spi/ExampleService.java @@ -0,0 +1,5 @@ +package dev.langchain4j.spi; + +public interface ExampleService { + String getGreeting(); +} diff --git a/langchain4j-core/src/test/java/dev/langchain4j/spi/ExampleServiceGoodbye.java b/langchain4j-core/src/test/java/dev/langchain4j/spi/ExampleServiceGoodbye.java new file mode 100644 index 00000000000..65adbcba86e --- /dev/null +++ b/langchain4j-core/src/test/java/dev/langchain4j/spi/ExampleServiceGoodbye.java @@ -0,0 +1,8 @@ +package dev.langchain4j.spi; + +public class ExampleServiceGoodbye implements ExampleService{ + @Override + public String getGreeting() { + return "Goodbye"; + } +} diff --git a/langchain4j-core/src/test/java/dev/langchain4j/spi/ExampleServiceHello.java b/langchain4j-core/src/test/java/dev/langchain4j/spi/ExampleServiceHello.java new file mode 100644 index 00000000000..cdef54286f8 --- /dev/null +++ b/langchain4j-core/src/test/java/dev/langchain4j/spi/ExampleServiceHello.java @@ -0,0 +1,8 @@ +package dev.langchain4j.spi; + +public class ExampleServiceHello implements ExampleService{ + @Override + public String getGreeting() { + return "Hello"; + } +} diff --git a/langchain4j-core/src/test/java/dev/langchain4j/spi/ServiceHelperTest.java b/langchain4j-core/src/test/java/dev/langchain4j/spi/ServiceHelperTest.java new file mode 100644 index 00000000000..b21062733b2 --- /dev/null +++ b/langchain4j-core/src/test/java/dev/langchain4j/spi/ServiceHelperTest.java @@ -0,0 +1,26 @@ +package dev.langchain4j.spi; + +import org.assertj.core.api.WithAssertions; +import org.junit.jupiter.api.Test; + +import java.util.Collection; + +class ServiceHelperTest implements WithAssertions { + public void assertServices(Collection services) { + assertThat(services).extracting(ExampleService::getGreeting) + .containsExactlyInAnyOrder("Hello", "Goodbye"); + } + + @SuppressWarnings("unused") + interface NotAService { + int unused(); + } + + @Test + public void test_loadFactories() { + assertServices(ServiceHelper.loadFactories(ExampleService.class)); + assertServices(ServiceHelper.loadFactories(ExampleService.class, ServiceHelperTest.class.getClassLoader())); + + assertThat(ServiceHelper.loadFactories(NotAService.class)).isEmpty(); + } +} \ No newline at end of file diff --git a/langchain4j-core/src/test/resources/META-INF/services/dev.langchain4j.spi.ExampleService b/langchain4j-core/src/test/resources/META-INF/services/dev.langchain4j.spi.ExampleService new file mode 100644 index 00000000000..df415e55db5 --- /dev/null +++ b/langchain4j-core/src/test/resources/META-INF/services/dev.langchain4j.spi.ExampleService @@ -0,0 +1,2 @@ +dev.langchain4j.spi.ExampleServiceHello +dev.langchain4j.spi.ExampleServiceGoodbye