All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.mmm.base.service.ServiceHelper Maven / Gradle / Ivy

The newest version!
/* 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);
    }
  }

}