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

nextflow.util.ServiceDiscover.groovy Maven / Gradle / Ivy

Go to download

A DSL modelled around the UNIX pipe concept, that simplifies writing parallel and scalable pipelines in a portable manner

There is a newer version: 24.08.0-edge
Show newest version
/*
 * Copyright 2013-2024, Seqera Labs
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package nextflow.util

import groovy.transform.CompileStatic

/**
 * A service loader inspired to {@link ServiceLoader}
 * that allows to load only service names or classes without instantiating
 * the actual instances
 *
 * @author Paolo Di Tommaso 
 */
@CompileStatic
class ServiceDiscover {

    private static final String PREFIX = "META-INF/services/";

    private Class service

    private ClassLoader classLoader

    static  List> load(Class service) {
        new ServiceDiscover(service).load()
    }

    ServiceDiscover(Class service) {
        assert service
        this.service = service
        this.classLoader = Thread.currentThread().getContextClassLoader()
    }

    ServiceDiscover(Class service, ClassLoader classLoader) {
        assert service
        assert classLoader
        this.service = service
        this.classLoader = classLoader
    }

    private int parseLine(Class service, URL u, BufferedReader r, int lc, List names)
            throws IOException, ServiceConfigurationError
    {
        String ln = r.readLine();
        if (ln == null) {
            return -1
        }

        int ci = ln.indexOf('#')
        if (ci >= 0)
            ln = ln.substring(0, ci)

        ln = ln.trim()
        int n = ln.length()

        if (n != 0) {
            if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
                fail(service, u, lc, "Illegal configuration file syntax")

            int cp = ln.codePointAt(0)
            if (!Character.isJavaIdentifierStart(cp))
                fail(service, u, lc, "Illegal provider-class name: " + ln)

            for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
                cp = ln.codePointAt(i)
                if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
                    fail(service, u, lc, "Illegal provider-class name: " + ln)
            }

            if ( !names.contains(ln) )
                names.add(ln)
        }

        return lc + 1
    }


    private List parse(Class service, URL u) throws ServiceConfigurationError
    {
        InputStream stream = null;
        BufferedReader reader = null;
        ArrayList names = new ArrayList<>();
        try {
            stream = u.openStream();
            reader = new BufferedReader(new InputStreamReader(stream, "utf-8"));
            int lc = 1;
            while ((lc = parseLine(service, u, reader, lc, names)) >= 0) { /*empty*/ }
        }
        catch (IOException x) {
            fail(service, "Error reading configuration file", x);
        }
        finally {
            try {
                if (reader != null) reader.close();
                if (stream != null) stream.close();
            }
            catch (IOException y) {
                fail(service, "Error closing configuration file", y);
            }
        }
        return names
    }

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

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

    private static void fail(Class service, URL u, int line, String msg)
            throws ServiceConfigurationError
    {
        fail(service, u.toString() + ":" + line + ": " + msg);
    }

    private Class classForName(String clazzName) {

        Class result = null
        try {
            result = Class.forName(clazzName, false, classLoader);
        }
        catch (ClassNotFoundException x) {
            fail(service, "Provider $clazzName not found");
        }
        if (!service.isAssignableFrom(result)) {
            fail(service, "Provider $clazzName not a subtype");
        }
        try {
            return (Class)result;
        }
        catch (Throwable x) {
            fail(service, "Provider $clazzName could not be instantiated: " + x, x);
        }
        throw new Error()
    }

    List names() {

        Enumeration configs = null

        try {
            configs = classLoader.getResources(PREFIX + service.getName())

        } catch (IOException x) {
            fail(service, "Error locating configuration files", x);
        }

        List result = []
        for( URL url : configs ) {
            for( String name : parse(service, url) ) {
                result << name
            }
        }
        return result
    }

    List> load() {
        names().collect{ classForName(it) }
    }


}