Skip to content

Commit

Permalink
Refactor ServiceHelper and add tests and docs. (langchain4j#452)
Browse files Browse the repository at this point in the history
  • Loading branch information
crutcher authored Jan 8, 2024
1 parent f8c8381 commit c22fd29
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 19 deletions.
Original file line number Diff line number Diff line change
@@ -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 <T> the type of service
* @return the list of services, empty if none
*/
public static <T> Collection<T> loadFactories(Class<T> clazz) {
return loadFactories(clazz, null);
}

public static <T> Collection<T> loadFactories(Class<T> clazz, ClassLoader classLoader) {
List<T> list = new ArrayList<>();
ServiceLoader<T> factories;
/**
* Load all the services of a given type.
*
* <p>Utility mechanism around {@code ServiceLoader.load()}</p>
*
* <ul>
* <li>If classloader is {@code null}, will try {@code ServiceLoader.load(clazz)}</li>
* <li>If classloader is not {@code null}, will try {@code ServiceLoader.load(clazz, classloader)}</li>
* </ul>
*
* <p>If the above return nothing, will fall back to {@code ServiceLoader.load(clazz, $this class loader$)}</p>
*
* @param clazz the type of service
* @param classLoader the classloader to use, may be null
* @param <T> the type of service
* @return the list of services, empty if none
*/
public static <T> Collection<T> loadFactories(Class<T> clazz, /* @Nullable */ ClassLoader classLoader) {
List<T> 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 <T> the type of service
* @return the list of services, empty if none
*/
private static <T> List<T> loadAll(ServiceLoader<T> loader) {
List<T> list = new ArrayList<>();
loader.iterator().forEachRemaining(list::add);
return list;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package dev.langchain4j.spi;

public interface ExampleService {
String getGreeting();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package dev.langchain4j.spi;

public class ExampleServiceGoodbye implements ExampleService{
@Override
public String getGreeting() {
return "Goodbye";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package dev.langchain4j.spi;

public class ExampleServiceHello implements ExampleService{
@Override
public String getGreeting() {
return "Hello";
}
}
Original file line number Diff line number Diff line change
@@ -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<ExampleService> 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();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dev.langchain4j.spi.ExampleServiceHello
dev.langchain4j.spi.ExampleServiceGoodbye

0 comments on commit c22fd29

Please sign in to comment.