/*
 * Copyright The OpenTelemetry Authors
 * SPDX-License-Identifier: Apache-2.0
 */

package io.opentelemetry.javaagent.tooling;

import static java.util.logging.Level.FINE;

import io.opentelemetry.sdk.autoconfigure.spi.Ordered;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.ServiceLoader;
import java.util.logging.Logger;

public final class SafeServiceLoader {

  private static final Logger logger = Logger.getLogger(SafeServiceLoader.class.getName());

  /**
   * Delegates to {@link ServiceLoader#load(Class, ClassLoader)} and then eagerly iterates over
   * returned {@code Iterable}, ignoring any potential {@link UnsupportedClassVersionError}.
   *
   * <p>Those errors can happen when some classes returned by {@code ServiceLoader} were compiled
   * for later java version than is used by currently running JVM. During normal course of business
   * this should not happen. Please read CONTRIBUTING.md, section "Testing - Java versions" for a
   * background info why this is Ok.
   */
  // Because we want to catch exception per iteration
  @SuppressWarnings("ForEachIterable")
  public static <T> List<T> load(Class<T> serviceClass, ClassLoader classLoader) {
    List<T> result = new ArrayList<>();
    ServiceLoader<T> services = ServiceLoader.load(serviceClass, classLoader);
    for (Iterator<T> iterator = new SafeIterator<>(services.iterator()); iterator.hasNext(); ) {
      T service = iterator.next();
      if (service != null) {
        result.add(service);
      }
    }
    return result;
  }

  /**
   * Same as {@link #load(Class, ClassLoader)}, but also orders the returned implementations by
   * comparing their {@link Ordered#order()}.
   */
  public static <T extends Ordered> List<T> loadOrdered(
      Class<T> serviceClass, ClassLoader classLoader) {
    List<T> result = load(serviceClass, classLoader);
    result.sort(Comparator.comparing(Ordered::order));
    return result;
  }

  private SafeServiceLoader() {}

  private static class SafeIterator<T> implements Iterator<T> {
    private final Iterator<T> delegate;

    SafeIterator(Iterator<T> iterator) {
      delegate = iterator;
    }

    private static void handleUnsupportedClassVersionError(
        UnsupportedClassVersionError unsupportedClassVersionError) {
      logger.log(
          FINE,
          "Unable to load instrumentation class: {0}",
          unsupportedClassVersionError.getMessage());
    }

    @Override
    public boolean hasNext() {
      // jdk9 and newer throw UnsupportedClassVersionError in hasNext()
      while (true) {
        try {
          return delegate.hasNext();
        } catch (UnsupportedClassVersionError unsupportedClassVersionError) {
          handleUnsupportedClassVersionError(unsupportedClassVersionError);
        }
      }
    }

    @Override
    public T next() {
      // jdk8 throws UnsupportedClassVersionError in next()
      try {
        return delegate.next();
      } catch (UnsupportedClassVersionError unsupportedClassVersionError) {
        handleUnsupportedClassVersionError(unsupportedClassVersionError);
        return null;
      }
    }
  }
}
