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

org.tinylog.configuration.ServiceLoader Maven / Gradle / Ivy

There is a newer version: 2.8.0-M1
Show newest version
/*
 * Copyright 2016 Martin Winandy
 *
 * 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 org.tinylog.configuration;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;
import java.util.regex.Pattern;

import org.tinylog.Level;
import org.tinylog.provider.InternalLogger;
import org.tinylog.runtime.RuntimeProvider;

/**
 * Alternative service loader that supports constructors with arguments in opposite to {@link java.util.ServiceLoader}.
 *
 * @param 
 *            Service interface
 */
public final class ServiceLoader {

	private static final String SERVICE_PREFIX = "META-INF/services/";
	private static final Pattern SPLIT_PATTERN = Pattern.compile(" ");

	private final Class service;
	private final Class[] argumentTypes;

	private final ClassLoader classLoader;
	private final Collection classes;

	/**
	 * @param service
	 *            Service interface
	 * @param argumentTypes
	 *            Expected argument types for constructors
	 */
	public ServiceLoader(final Class service, final Class... argumentTypes) {
		this.service = service;
		this.argumentTypes = argumentTypes;

		String fileName = SERVICE_PREFIX + service.getName();
		for (ClassLoader loader : RuntimeProvider.getClassLoaders()) {
			Enumeration serviceFiles = fetchServiceFiles(loader, fileName);
			if (serviceFiles.hasMoreElements()) {
				classLoader = loader;
				classes = loadClasses(loader, service);
				return;
			}
		}

		this.classLoader = null;
		this.classes = Collections.emptyList();
	}

	/**
	 * Creates a defined service implementation. The name can be either the fully-qualified class name or the simplified
	 * acronym. The acronym is the class name without package and service suffix.
	 *
	 * 

* The acronym for {@code org.tinylog.writers.RollingFileWriter} is for example {@code rolling file}. *

* * @param name * Acronym or class name of service implementation * @param arguments * Arguments for constructor of service implementation * @return A new instance of service or {@code null} if failed to create service */ public T create(final String name, final Object... arguments) { if (name.indexOf('.') == -1) { String expectingClassName = toSimpleClassName(name); for (String className : classes) { int split = className.lastIndexOf('.'); String simpleClassName = split == -1 ? className : className.substring(split + 1); if (expectingClassName.equals(simpleClassName)) { return createInstance(className, arguments); } } InternalLogger.log(Level.ERROR, "Service implementation '" + name + "' not found"); return null; } else { return createInstance(name, arguments); } } /** * Creates service implementations from a comma separated list of names. Names can be either fully-qualified class names or their * simplified acronyms. An acronym is the class name without package and service suffix. Optionally, each name can be enriched by a * string argument. There must be a colon between the name and argument. All service implementations need a constructor that accepts * a string as argument. * * @param list Comma separated list of names with optional arguments * @return All created service implementations (one for each name) */ public List createList(final String list) { List instances = new ArrayList(); for (String entry : list.split(",")) { entry = entry.trim(); if (!entry.isEmpty()) { T instance; int separator = entry.indexOf(':'); if (separator == -1) { instance = create(entry, (Object) null); } else { String name = entry.substring(0, separator).trim(); String argument = entry.substring(separator + 1).trim(); instance = create(name, argument); } if (instance != null) { instances.add(instance); } } } return instances; } /** * Creates all registered service implementations. * * @param arguments * Arguments for constructors of service implementations * @return Instances of all service implementations */ public Collection createAll(final Object... arguments) { Collection instances = new ArrayList(classes.size()); for (String className : classes) { T instance = createInstance(className, arguments); if (instance != null) { instances.add(instance); } } return instances; } private static Enumeration fetchServiceFiles(final ClassLoader classLoader, final String fileName) { try { return classLoader.getResources(fileName); } catch (IOException ex) { InternalLogger.log(Level.ERROR, "Failed loading services from '" + fileName + "'"); return Collections.enumeration(Collections.emptyList()); } } /** * Loads all registered service class names. * * @param * Service interface * @param classLoader * Class loader to use for finding service files * @param service * Service interface * @return Class names */ private static Collection loadClasses(final ClassLoader classLoader, final Class service) { String name = SERVICE_PREFIX + service.getName(); Enumeration urls; try { urls = classLoader.getResources(name); } catch (IOException ex) { InternalLogger.log(Level.ERROR, "Failed loading services from '" + name + "'"); return Collections.emptyList(); } Collection classes = new ArrayList(); while (urls.hasMoreElements()) { URL url = urls.nextElement(); BufferedReader reader = null; try { reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8")); for (String line = reader.readLine(); line != null; line = reader.readLine()) { line = line.trim(); if (line.length() > 0 && line.charAt(0) != '#' && !classes.contains(line)) { classes.add(line); } } } catch (IOException ex) { InternalLogger.log(Level.ERROR, "Failed reading service resource '" + url + "'"); } finally { if (reader != null) { try { reader.close(); } catch (IOException ex) { // Ignore } } } } return classes; } /** * Generates the simple class name from an acronym. A simple class name is the class name without package. * *

* The acronym {@code rolling file}, for example, will be transformed into {@code RollingFileWriter} for the service * interface {@code Writer}. *

* * @param name * Simplified acronym * @return Simple class without package */ public String toSimpleClassName(final String name) { StringBuilder builder = new StringBuilder(name.length()); for (String token : SPLIT_PATTERN.split(name)) { if (!token.isEmpty()) { builder.append(Character.toUpperCase(token.charAt(0))); builder.append(token.substring(1).toLowerCase(Locale.ROOT)); } } builder.append(service.getSimpleName()); return builder.toString(); } /** * Creates a new instance of a class. * * @param className * Fully-qualified class name * @param arguments * Arguments for constructor * @return A new instance of given class or {@code null} if creation failed */ @SuppressWarnings("unchecked") private T createInstance(final String className, final Object... arguments) { try { Class implementation = Class.forName(className, false, classLoader); if (service.isAssignableFrom(implementation)) { return (T) implementation.getDeclaredConstructor(argumentTypes).newInstance(arguments); } else { InternalLogger.log(Level.ERROR, "Class '" + className + "' does not implement service interface '" + service + "'"); } } catch (ClassNotFoundException ex) { InternalLogger.log(Level.ERROR, "Service implementation '" + className + "' not found"); } catch (NoSuchMethodException ex) { InternalLogger.log(Level.ERROR, "Service implementation '" + className + "' has no matching constructor"); } catch (InstantiationException ex) { InternalLogger.log(Level.ERROR, "Service implementation '" + className + "' is not instantiable"); } catch (IllegalAccessException ex) { InternalLogger.log(Level.ERROR, "Constructor of service implementation '" + className + "' is not accessible"); } catch (IllegalArgumentException ex) { InternalLogger.log(Level.ERROR, "Illegal arguments for constructor of service implementation '" + className + "'"); } catch (InvocationTargetException ex) { InternalLogger.log(Level.ERROR, ex.getTargetException(), "Failed creating service implementation '" + className + "'"); } return null; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy