From a91753954aad42f0eeca2609a73d4f8e810dbb6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Hohwiller?= Date: Sun, 14 Apr 2024 21:54:28 +0200 Subject: [PATCH] improvements for ServerLoader support --- .../mmm/base/service/ServiceHelper.java | 313 ++++++++++-------- 1 file changed, 179 insertions(+), 134 deletions(-) diff --git a/core/src/main/java/io/github/mmm/base/service/ServiceHelper.java b/core/src/main/java/io/github/mmm/base/service/ServiceHelper.java index 8f186a6..46b375f 100644 --- a/core/src/main/java/io/github/mmm/base/service/ServiceHelper.java +++ b/core/src/main/java/io/github/mmm/base/service/ServiceHelper.java @@ -1,134 +1,179 @@ -/* Copyright (c) The m-m-m Team, Licensed under the Apache License, Version 2.0 - * http://www.apache.org/licenses/LICENSE-2.0 */ -package io.github.mmm.base.service; - -import java.util.Collection; -import java.util.Map; -import java.util.ServiceLoader; -import java.util.function.Function; - -import io.github.mmm.base.exception.DuplicateObjectException; -import io.github.mmm.base.exception.ObjectNotFoundException; - -/** - * Helper class for {@link ServiceLoader}. - * - * @since 1.0.0 - */ -public final class ServiceHelper { - - private ServiceHelper() { - - } - - /** - * @param type of the service. - * @param serviceLoader the {@link ServiceLoader} that has to be provided from the module declaring the service API - * and holds the {@code uses} statement in its {@code module-info}. - * @return the requested service. - */ - public static final S singleton(ServiceLoader serviceLoader) { - - return singleton(serviceLoader, true); - } - - /** - * @param type of the service. - * @param unique - {@code true} if an exception is thrown if the service is not unique, {@code false} otherwise (allow - * overriding default). - * @param serviceLoader the {@link ServiceLoader} that has to be provided from the module declaring the service API - * and holds the {@code uses} statement in its {@code module-info}. - * @return the requested service. - */ - public static final S singleton(ServiceLoader serviceLoader, boolean unique) { - - S service = null; - for (S currentService : serviceLoader) { - if (service == null) { - service = currentService; - } else { - if (unique) { - String type = serviceLoader.toString(); - throw new IllegalStateException(type); - } else if (!currentService.getClass().getName().startsWith("io.github.mmm.")) { - service = currentService; - } - } - } - if (service == null) { - String type = serviceLoader.toString(); - throw new ObjectNotFoundException(type); - } - return service; - } - - /** - * @param type of the service. - * @param serviceLoader the {@link ServiceLoader} that has to be provided from the module declaring the service API - * and holds the {@code uses} statement in its {@code module-info}. - * @param services the {@link Collection} where to add the services from the given {@link ServiceLoader}. - */ - public static final void all(ServiceLoader serviceLoader, Collection services) { - - all(serviceLoader, services, 1); - } - - /** - * @param type of the service. - * @param serviceLoader the {@link ServiceLoader} that has to be provided from the module declaring the service API - * and holds the {@code uses} statement in its {@code module-info}. - * @param services the {@link Collection} where to add the services from the given {@link ServiceLoader}. - * @param min the minimum number of services required. - */ - public static final void all(ServiceLoader serviceLoader, Collection services, int min) { - - int serviceCount = 0; - for (S service : serviceLoader) { - services.add(service); - serviceCount++; - } - if (serviceCount < min) { - throw new IllegalStateException("Required at least " + min + " service(s) for " + serviceLoader); - } - } - - /** - * @param type of the service. - * @param type of the {@link Map} key. - * @param serviceLoader the {@link ServiceLoader} that has to be provided from the module declaring the service API - * and holds the {@code uses} statement in its {@code module-info}. - * @param services the {@link Collection} where to add the services from the given {@link ServiceLoader}. - * @param keyFunction the {@link Function} to get the {@link Map} key from the service. - */ - public static final void all(ServiceLoader serviceLoader, Map services, Function keyFunction) { - - all(serviceLoader, services, keyFunction, 1); - } - - /** - * @param type of the service. - * @param type of the {@link Map} key. - * @param serviceLoader the {@link ServiceLoader} that has to be provided from the module declaring the service API - * and holds the {@code uses} statement in its {@code module-info}. - * @param services the {@link Collection} where to add the services from the given {@link ServiceLoader}. - * @param keyFunction the {@link Function} to get the {@link Map} key from the service. - * @param min the minimum number of services required. - */ - public static final void all(ServiceLoader serviceLoader, Map services, Function keyFunction, - int min) { - - int serviceCount = 0; - for (S service : serviceLoader) { - K key = keyFunction.apply(service); - S duplicate = services.put(key, service); - if (duplicate != null) { - throw new DuplicateObjectException(duplicate, key, service); - } - serviceCount++; - } - if (serviceCount < min) { - throw new IllegalStateException("Required at least " + min + " service(s) for " + serviceLoader); - } - } - -} +/* Copyright (c) The m-m-m Team, Licensed under the Apache License, Version 2.0 + * http://www.apache.org/licenses/LICENSE-2.0 */ +package io.github.mmm.base.service; + +import java.util.Collection; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.function.Function; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.github.mmm.base.exception.DuplicateObjectException; +import io.github.mmm.base.exception.ObjectNotFoundException; + +/** + * Helper class for {@link ServiceLoader}. + * + * @since 1.0.0 + */ +public final class ServiceHelper { + + private static final Logger LOG = LoggerFactory.getLogger(ServiceHelper.class); + + private ServiceHelper() { + + } + + /** + * @param type of the service. + * @param serviceLoader the {@link ServiceLoader} that has to be provided from the module declaring the service API + * and holds the {@code uses} statement in its {@code module-info}. + * @return the requested service. + */ + public static final S singleton(ServiceLoader serviceLoader) { + + return singleton(serviceLoader, true); + } + + /** + * @param type of the service. + * @param unique - {@code true} if an exception is thrown if the service is not unique, {@code false} otherwise (allow + * overriding default). + * @param serviceLoader the {@link ServiceLoader} that has to be provided from the module declaring the service API + * and holds the {@code uses} statement in its {@code module-info}. + * @return the requested service. + */ + public static final S singleton(ServiceLoader serviceLoader, boolean unique) { + + S service = null; + for (S currentService : serviceLoader) { + if (service == null) { + service = currentService; + } else { + service = handleDuplicate(currentService, service, serviceLoader, unique, null); + } + } + if (service == null) { + String type = serviceLoader.toString(); + throw new ObjectNotFoundException(type); + } + return service; + } + + private static S handleDuplicate(S current, S existing, ServiceLoader serviceLoader, boolean requireUnique, + Object key) { + + if (existing == null) { + return current; + } else if (current == null) { + return existing; // actually invalid usage but lets be tolerant + } + String currentType = current.getClass().getName(); + String existingType = existing.getClass().getName(); + boolean currentInternal = isInternalImplementation(currentType); + boolean existingInternal = isInternalImplementation(existingType); + if (requireUnique || (currentInternal == existingInternal)) { + String duplicateKey = serviceLoader.toString(); + if (key != null) { + duplicateKey = duplicateKey + "[" + key + "]"; + } + throw new DuplicateObjectException(existingType, duplicateKey, currentType); + } + if (currentInternal) { + current = existing; + String type = currentType; + currentType = existingType; + existingType = type; + } + LOG.info("For service {} the implementation {} is overridden with {}", serviceLoader.toString(), existingType, + currentType); + return current; + } + + private static boolean isInternalImplementation(String className) { + + return className.startsWith("io.github.mmm."); + } + + /** + * @param type of the service. + * @param serviceLoader the {@link ServiceLoader} that has to be provided from the module declaring the service API + * and holds the {@code uses} statement in its {@code module-info}. + * @param services the {@link Collection} where to add the services from the given {@link ServiceLoader}. + */ + public static final void all(ServiceLoader serviceLoader, Collection services) { + + all(serviceLoader, services, 1); + } + + /** + * @param type of the service. + * @param serviceLoader the {@link ServiceLoader} that has to be provided from the module declaring the service API + * and holds the {@code uses} statement in its {@code module-info}. + * @param services the {@link Collection} where to add the services from the given {@link ServiceLoader}. + * @param min the minimum number of services required. + */ + public static final void all(ServiceLoader serviceLoader, Collection services, int min) { + + int serviceCount = 0; + for (S service : serviceLoader) { + services.add(service); + serviceCount++; + } + if (serviceCount < min) { + throw new IllegalStateException("Required at least " + min + " service(s) for " + serviceLoader); + } + } + + /** + * @param type of the service. + * @param type of the {@link Map} key. + * @param serviceLoader the {@link ServiceLoader} that has to be provided from the module declaring the service API + * and holds the {@code uses} statement in its {@code module-info}. + * @param services the {@link Collection} where to add the services from the given {@link ServiceLoader}. + * @param keyFunction the {@link Function} to get the {@link Map} key from the service. + */ + public static final void all(ServiceLoader serviceLoader, Map services, Function keyFunction) { + + all(serviceLoader, services, keyFunction, 1); + } + + /** + * @param type of the service. + * @param type of the {@link Map} key. + * @param serviceLoader the {@link ServiceLoader} that has to be provided from the module declaring the service API + * and holds the {@code uses} statement in its {@code module-info}. + * @param services the {@link Collection} where to add the services from the given {@link ServiceLoader}. + * @param keyFunction the {@link Function} to get the {@link Map} key from the service. + * @param min the minimum number of services required. + */ + public static final void all(ServiceLoader serviceLoader, Map services, Function keyFunction, + int min) { + + int serviceCount = 0; + for (S service : serviceLoader) { + K key = keyFunction.apply(service); + S duplicate = services.get(key); + if (duplicate == null) { + services.put(key, service); + } else { + S resolved = handleDuplicate(service, duplicate, serviceLoader, false, key); + if (resolved == service) { + services.put(key, service); + } + } + if (duplicate != null) { + throw new DuplicateObjectException(duplicate, key, service); + } + serviceCount++; + } + if (serviceCount < min) + + { + throw new IllegalStateException("Required at least " + min + " service(s) for " + serviceLoader); + } + } + +}