/*
* Copyright 2017-2020 original authors
*
* 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
*
* https://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 io.micronaut.core.io.service;
import io.micronaut.core.reflect.ClassUtils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UncheckedIOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.ServiceConfigurationError;
import java.util.function.Predicate;
import java.util.stream.Collectors;
Variation of ServiceLoader
that allows soft loading and conditional loading of META-INF/services classes.
Author: Graeme Rocher Type parameters: - <S> – The service type
Since: 1.0
/**
* <p>Variation of {@link java.util.ServiceLoader} that allows soft loading and conditional loading of
* META-INF/services classes.</p>
*
* @param <S> The service type
* @author Graeme Rocher
* @since 1.0
*/
public final class SoftServiceLoader<S> implements Iterable<ServiceDefinition<S>> {
public static final String META_INF_SERVICES = "META-INF/services";
private final Class<S> serviceType;
private final ClassLoader classLoader;
private final Map<String, ServiceDefinition<S>> loadedServices = new LinkedHashMap<>();
private final Iterator<ServiceDefinition<S>> unloadedServices;
private final Predicate<String> condition;
private SoftServiceLoader(Class<S> serviceType, ClassLoader classLoader) {
this(serviceType, classLoader, (String name) -> true);
}
private SoftServiceLoader(Class<S> serviceType, ClassLoader classLoader, Predicate<String> condition) {
this.serviceType = serviceType;
this.classLoader = classLoader == null ? ClassLoader.getSystemClassLoader() : classLoader;
this.unloadedServices = new ServiceLoaderIterator();
this.condition = condition == null ? (String name) -> true : condition;
}
Creates a new SoftServiceLoader
using the thread context loader by default. Params: - service – The service type
Type parameters: - <S> – The service generic type
Returns: A new service loader
/**
* Creates a new {@link SoftServiceLoader} using the thread context loader by default.
*
* @param service The service type
* @param <S> The service generic type
* @return A new service loader
*/
public static <S> SoftServiceLoader<S> load(Class<S> service) {
return SoftServiceLoader.load(service, SoftServiceLoader.class.getClassLoader());
}
Creates a new SoftServiceLoader
using the given type and class loader. Params: - service – The service type
- loader – The class loader
Type parameters: - <S> – The service generic type
Returns: A new service loader
/**
* Creates a new {@link SoftServiceLoader} using the given type and class loader.
*
* @param service The service type
* @param loader The class loader
* @param <S> The service generic type
* @return A new service loader
*/
public static <S> SoftServiceLoader<S> load(Class<S> service,
ClassLoader loader) {
return new SoftServiceLoader<>(service, loader);
}
Creates a new SoftServiceLoader
using the given type and class loader. Params: - service – The service type
- loader – The class loader to use
- condition – A
Predicate
to use to conditionally load the service. The predicate is passed the service class name
Type parameters: - <S> – The service generic type
Returns: A new service loader
/**
* Creates a new {@link SoftServiceLoader} using the given type and class loader.
*
* @param service The service type
* @param loader The class loader to use
* @param condition A {@link Predicate} to use to conditionally load the service. The predicate is passed the service class name
* @param <S> The service generic type
* @return A new service loader
*/
public static <S> SoftServiceLoader<S> load(Class<S> service,
ClassLoader loader,
Predicate<String> condition) {
return new SoftServiceLoader<>(service, loader, condition);
}
Returns: Return the first such instance
/**
* @return Return the first such instance
*/
public Optional<ServiceDefinition<S>> first() {
Iterator<ServiceDefinition<S>> i = iterator();
if (i.hasNext()) {
return Optional.of(i.next());
}
return Optional.empty();
}
Params: - alternative – An alternative type to use if the this type is not present
- classLoader – The classloader
Returns: Return the first such instance
/**
* @param alternative An alternative type to use if the this type is not present
* @param classLoader The classloader
* @return Return the first such instance
*/
public Optional<ServiceDefinition<S>> firstOr(String alternative, ClassLoader classLoader) {
Iterator<ServiceDefinition<S>> i = iterator();
if (i.hasNext()) {
return Optional.of(i.next());
}
Optional<Class> alternativeClass = ClassUtils.forName(alternative, classLoader);
if (alternativeClass.isPresent()) {
return Optional.of(newService(alternative, alternativeClass));
}
return Optional.empty();
}
Returns: The iterator
/**
* @return The iterator
*/
@Override
public Iterator<ServiceDefinition<S>> iterator() {
return new Iterator<ServiceDefinition<S>>() {
Iterator<ServiceDefinition<S>> loaded = loadedServices.values().iterator();
@Override
public boolean hasNext() {
if (loaded.hasNext()) {
return true;
}
return unloadedServices.hasNext();
}
@Override
public ServiceDefinition<S> next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
if (loaded.hasNext()) {
return loaded.next();
}
if (unloadedServices.hasNext()) {
ServiceDefinition<S> nextService = unloadedServices.next();
loadedServices.put(nextService.getName(), nextService);
return nextService;
}
// should not happen
throw new Error("Bug in iterator");
}
};
}
Params: - name – The name
- loadedClass – The loaded class
Returns: The service definition
/**
* @param name The name
* @param loadedClass The loaded class
* @return The service definition
*/
@SuppressWarnings("unchecked")
protected ServiceDefinition<S> newService(String name, Optional<Class> loadedClass) {
return new DefaultServiceDefinition(name, loadedClass);
}
A service loader iterator implementation.
/**
* A service loader iterator implementation.
*/
private final class ServiceLoaderIterator implements Iterator<ServiceDefinition<S>> {
private Enumeration<URL> serviceConfigs = null;
private Iterator<String> unprocessed = null;
@Override
public boolean hasNext() {
if (serviceConfigs == null) {
String name = serviceType.getName();
try {
serviceConfigs = classLoader.getResources(META_INF_SERVICES + '/' + name);
} catch (IOException e) {
throw new ServiceConfigurationError("Failed to load resources for service: " + name, e);
}
}
while (unprocessed == null || !unprocessed.hasNext()) {
if (!serviceConfigs.hasMoreElements()) {
return false;
}
URL url = serviceConfigs.nextElement();
try {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()))) {
List<String> lines = reader.lines()
.filter(line -> line.length() != 0 && line.charAt(0) != '#')
.filter(condition)
.map(line -> {
int i = line.indexOf('#');
if (i > -1) {
line = line.substring(0, i);
}
return line;
})
.collect(Collectors.toList());
unprocessed = lines.iterator();
}
} catch (IOException | UncheckedIOException e) {
// ignore, can't do anything here and can't log because class used in compiler
}
}
return unprocessed.hasNext();
}
@Override
public ServiceDefinition<S> next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
String nextName = unprocessed.next();
try {
final Class<?> loadedClass = Class.forName(nextName, false, classLoader);
return newService(nextName, Optional.of(loadedClass));
} catch (NoClassDefFoundError | ClassNotFoundException e) {
return newService(nextName, Optional.empty());
}
}
}
}