silentbalanceyh/vertx-zero

View on GitHub
vertx-gaia/vertx-co/src/main/environment/io/aeon/runtime/channel/AeonService.java

Summary

Maintainability
A
3 hrs
Test Coverage
package io.aeon.runtime.channel;

import io.aeon.runtime.CRunning;
import io.horizon.eon.VValue;
import io.horizon.util.HUt;
import io.vertx.up.util.Ut;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.*;

/**
 * @author <a href="http://www.origin-x.cn">Lang</a>
 */
public class AeonService {
    private static final String PREFIX = "META-INF/services/aeon/";
    private final Class<?> serviceCls;
    private final ClassLoader loader;
    private final Set<String> providerSet = new HashSet<>();
    private Enumeration<URL> configs;

    private AeonService(final Class<?> serviceCls, final ClassLoader loader) {
        this.serviceCls = serviceCls;
        this.loader = loader;
    }

    private static void fail(final Class<?> service, final String msg, final Throwable cause)
        throws ServiceConfigurationError {
        throw new ServiceConfigurationError(service.getName() + ": " + msg,
            cause);
    }

    private static void fail(final Class<?> service, final String msg)
        throws ServiceConfigurationError {
        throw new ServiceConfigurationError(service.getName() + ": " + msg);
    }

    private static void fail(final Class<?> service, final URL u, final int line, final String msg)
        throws ServiceConfigurationError {
        fail(service, u + ":" + line + ": " + msg);
    }

    @SuppressWarnings("unchecked")
    public static <T> T load(final Class<?> interfaceCls, final ClassLoader loader) {
        if (Objects.isNull(interfaceCls) || !interfaceCls.isInterface()) {
            return null;
        }
        return (T) CRunning.CC_SPI.pick(() -> {
            // AeonService instance
            final AeonService service = new AeonService(interfaceCls, loader);
            return service.service();
        }, interfaceCls);
    }

    @SuppressWarnings("unchecked")
    private <T> T service() {
        // Configuration
        this.aeonConfigure();
        // Load: /META-INF/services/aeon
        final Class<?> implCls = this.aeonSearch();
        if (Objects.nonNull(implCls)) {
            return Ut.instance(implCls);
        }
        // Load: /META-INF/services
        T reference = HUt.service((Class<T>) this.serviceCls, this.loader);
        if (Objects.isNull(reference)) {
            // Fix Liquibase Issue:
            /*
             * [ HED ] Missed `HED` component in service loader: META-INF/services/io.vertx.up.experiment.mixture.HED
             * This issue happened only when run `mvn liquibase:update`, because the class runtime is standalone
             */
            reference = HUt.service((Class<T>) this.serviceCls, this.serviceCls.getClassLoader());
        }
        return reference;
    }

    //    private <T> T service(final Class<T> serviceCls, final ClassLoader classLoader) {
    //        /*
    //         * Service Loader for lookup input interface implementation
    //         * This configuration must be configured in
    //         * META-INF/services/<interfaceCls Name> file
    //         */
    //        final ServiceLoader<T> loader = ServiceLoader.load(serviceCls, classLoader);
    //        /*
    //         * New data structure to put interface class into LEXEME_MAP
    //         * In current version, it support one to one only
    //         *
    //         * 1) The key is interface class name
    //         * 2) The found class is implementation name
    //         */
    //        T reference = null;
    //        for (final T t : loader) {
    //            reference = t;
    //            break;
    //        }
    //        return reference;
    //    }

    private void aeonConfigure() {
        try {
            final String fullName = PREFIX + this.serviceCls.getName();
            if (null == this.loader) {
                this.configs = ClassLoader.getSystemResources(fullName);
                /*
                 * Ignore following because of internal jdk
                 * else if (loader == ClassLoaders.platformClassLoader()) {
                            // The platform classloader doesn't have a class path,
                            // but the boot loader might.
                            if (BootLoader.hasClassPath()) {
                                configs = BootLoader.findResources(fullName);
                            } else {
                                configs = Collections.emptyEnumeration();
                            }
                        }
                 */
            } else {
                this.configs = this.loader.getResources(fullName);
            }
        } catch (final IOException ex) {
            fail(this.serviceCls, "Configuration Error", ex);
        }
    }

    private Class<?> aeonSearch() {
        if (!this.configs.hasMoreElements()) {
            return null;
        }
        Iterator<String> pending = null;
        while ((pending == null) || !pending.hasNext()) {
            pending = this.aeonParse(this.configs.nextElement());
        }
        final String className = pending.next();
        return Ut.clazz(className, null);
    }

    private int aeonParseLine(final URL u, final BufferedReader r, final int lc, final Set<String> names)
        throws IOException {
        String ln = r.readLine();
        if (ln == null) {
            return -1;
        }
        final int ci = ln.indexOf('#');
        if (ci >= 0) {
            ln = ln.substring(0, ci);
        }
        ln = ln.trim();
        final int n = ln.length();
        if (n != 0) {
            if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0)) {
                fail(this.serviceCls, u, lc, "Illegal configuration-file syntax");
            }
            int cp = ln.codePointAt(0);
            if (!Character.isJavaIdentifierStart(cp)) {
                fail(this.serviceCls, u, lc, "Illegal provider-class name: " + ln);
            }
            final int start = Character.charCount(cp);
            for (int i = start; i < n; i += Character.charCount(cp)) {
                cp = ln.codePointAt(i);
                if (!Character.isJavaIdentifierPart(cp) && (cp != '.')) {
                    fail(this.serviceCls, u, lc, "Illegal provider-class name: " + ln);
                }
            }
            if (this.providerSet.add(ln)) {
                names.add(ln);
            }
        }
        return lc + 1;
    }

    @SuppressWarnings("all")
    private Iterator<String> aeonParse(final URL u) {
        final Set<String> names = new LinkedHashSet<>(); // preserve insertion order
        try {
            final URLConnection uc = u.openConnection();
            uc.setUseCaches(false);
            try (final InputStream in = uc.getInputStream();
                 final BufferedReader r = new BufferedReader(new InputStreamReader(in, VValue.DFT.CHARSET))) {
                int lc = 1;
                while ((lc = this.aeonParseLine(u, r, lc, names)) >= 0) {
                    ;
                }
            }
        } catch (final IOException x) {
            fail(this.serviceCls, "Error accessing configuration file", x);
        }
        return names.iterator();
    }
}