/*
 * Copyright (c) 2021, 2024 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Distribution License v. 1.0, which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

package jakarta.activation;

import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;

class FactoryFinder {

    private static final Logger logger = Logger.getLogger("jakarta.activation");

    private static final ServiceLoaderUtil.ExceptionHandler<RuntimeException> EXCEPTION_HANDLER =
            new ServiceLoaderUtil.ExceptionHandler<>() {
                @Override
                public RuntimeException createException(Throwable throwable, String message) {
                    return new IllegalStateException(message, throwable);
                }
            };

    /**
     * Finds the implementation {@code Class} object for the given
     * factory type.
     * <p>
     * This method is package private so that this code can be shared.
     *
     * @param factoryClass factory abstract class or interface to be found
     * @return the {@code Class} object of the specified message factory;
     * may not be {@code null}
     * @throws IllegalStateException if there is no factory found
     */
    static <T> T find(Class<T> factoryClass) throws RuntimeException {
        for (ClassLoader l : getClassLoaders(Thread.class, FactoryFinder.class, System.class)) {
            T f = find(factoryClass, l);
            if (f != null) {
                return f;
            }
        }
        throw EXCEPTION_HANDLER.createException(null, "Provider for " + factoryClass.getName() + " cannot be found");
    }

    static <T> T find(Class<T> factoryClass, ClassLoader loader) throws RuntimeException {
        String className = fromSystemProperty(factoryClass.getName());
        if (className != null) {
            T result = newInstance(className, factoryClass, loader);
            if (result != null) {
                return result;
            }
        }
        return ServiceLoaderUtil.firstByServiceLoader(factoryClass, loader, logger, EXCEPTION_HANDLER);
    }

    private static <T> T newInstance(String className,
                                     Class<? extends T> service, ClassLoader loader)
            throws RuntimeException {
        return ServiceLoaderUtil.newInstance(className, service, loader, EXCEPTION_HANDLER);
    }

    private static String fromSystemProperty(String factoryId) {
        return getSystemProperty(factoryId);
    }

    private static String getSystemProperty(final String property) {
        logger.log(Level.FINE, "Checking system property {0}", property);
        String value = System.getProperty(property);
        logFound(value);
        return value;
    }

    private static void logFound(String value) {
        if (value != null) {
            logger.log(Level.FINE, "  found {0}", value);
        } else {
            logger.log(Level.FINE, "  not found");
        }
    }

    private static ClassLoader[] getClassLoaders(final Class<?>... classes) {
        ClassLoader[] loaders = new ClassLoader[classes.length];
        int w = 0;
        for (Class<?> k : classes) {
            ClassLoader cl = null;
            if (k == Thread.class) {
                cl = Thread.currentThread().getContextClassLoader();
            } else if (k == System.class) {
                cl = ClassLoader.getSystemClassLoader();
            } else {
                cl = k.getClassLoader();
            }
            if (cl != null) {
                loaders[w++] = cl;
            }
        }
        if (loaders.length != w) {
            loaders = Arrays.copyOf(loaders, w);
        }
        return loaders;
    }
}
