blob: 1608c1d1a0f8881256477343bf8dbc79b2ff5c01 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 java.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
/**
* A service-provider loader.
*
* <p>A service provider is a factory for creating all known implementations of a particular
* class or interface {@code S}. The known implementations are read from a configuration file in
* {@code META-INF/services/}. The file's name should match the class' binary name (such as
* {@code java.util.Outer$Inner}).
*
* <p>The file format is as follows.
* The file's character encoding must be UTF-8.
* Whitespace is ignored, and {@code #} is used to begin a comment that continues to the
* next newline.
* Lines that are empty after comment removal and whitespace trimming are ignored.
* Otherwise, each line contains the binary name of one implementation class.
* Duplicate entries are ignored, but entries are otherwise returned in order (that is, the file
* is treated as an ordered set).
*
* <p>Given these classes:
* <pre>
* package a.b.c;
* public interface MyService { ... }
* public class MyImpl1 implements MyService { ... }
* public class MyImpl2 implements MyService { ... }
* </pre>
* And this configuration file (stored as {@code META-INF/services/a.b.c.MyService}):
* <pre>
* # Known MyService providers.
* a.b.c.MyImpl1 # The original implementation for handling "bar"s.
* a.b.c.MyImpl2 # A later implementation for "foo"s.
* </pre>
* You might use {@code ServiceProvider} something like this:
* <pre>
* for (MyService service : ServiceLoader<MyService>.load(MyService.class)) {
* if (service.supports(o)) {
* return service.handle(o);
* }
* }
* </pre>
*
* <p>Note that each iteration creates new instances of the various service implementations, so
* any heavily-used code will likely want to cache the known implementations itself and reuse them.
* Note also that the candidate classes are instantiated lazily as you call {@code next} on the
* iterator: construction of the iterator itself does not instantiate any of the providers.
*
* @param <S> the service class or interface
* @since 1.6
*/
public final class ServiceLoader<S> implements Iterable<S> {
private final Class<S> service;
private final ClassLoader classLoader;
private final Set<URL> services;
private ServiceLoader(Class<S> service, ClassLoader classLoader) {
// It makes no sense for service to be null.
// classLoader is null if you want the system class loader.
if (service == null) {
throw new NullPointerException();
}
this.service = service;
this.classLoader = classLoader;
this.services = new HashSet<URL>();
reload();
}
/**
* Invalidates the cache of known service provider class names.
*/
public void reload() {
internalLoad();
}
/**
* Returns an iterator over all the service providers offered by this service loader.
* Note that {@code hasNext} and {@code next} may throw if the configuration is invalid.
*
* <p>Each iterator will return new instances of the classes it iterates over, so callers
* may want to cache the results of a single call to this method rather than call it
* repeatedly.
*
* <p>The returned iterator does not support {@code remove}.
*/
public Iterator<S> iterator() {
return new ServiceIterator(this);
}
/**
* Constructs a service loader. If {@code classLoader} is null, the system class loader
* is used.
*
* @param service the service class or interface
* @param classLoader the class loader
* @return a new ServiceLoader
*/
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader classLoader) {
if (classLoader == null) {
classLoader = ClassLoader.getSystemClassLoader();
}
return new ServiceLoader<S>(service, classLoader);
}
private void internalLoad() {
services.clear();
try {
String name = "META-INF/services/" + service.getName();
services.addAll(Collections.list(classLoader.getResources(name)));
} catch (IOException e) {
return;
}
}
/**
* Constructs a service loader, using the current thread's context class loader.
*
* @param service the service class or interface
* @return a new ServiceLoader
*/
public static <S> ServiceLoader<S> load(Class<S> service) {
return ServiceLoader.load(service, Thread.currentThread().getContextClassLoader());
}
/**
* Constructs a service loader, using the extension class loader.
*
* @param service the service class or interface
* @return a new ServiceLoader
*/
public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {
ClassLoader cl = ClassLoader.getSystemClassLoader();
if (cl != null) {
while (cl.getParent() != null) {
cl = cl.getParent();
}
}
return ServiceLoader.load(service, cl);
}
/**
* Internal API to support built-in SPIs that check a system property first.
* Returns an instance specified by a property with the class' binary name, or null if
* no such property is set.
* @hide
*/
public static <S> S loadFromSystemProperty(final Class<S> service) {
return AccessController.doPrivileged(new PrivilegedAction<S>() {
public S run() {
try {
final String className = System.getProperty(service.getName());
if (className != null) {
Class<?> c = ClassLoader.getSystemClassLoader().loadClass(className);
return (S) c.newInstance();
}
return null;
} catch (Exception e) {
throw new Error(e);
}
}
});
}
@Override
public String toString() {
return "ServiceLoader for " + service.getName();
}
private class ServiceIterator implements Iterator<S> {
private final ClassLoader classLoader;
private final Class<S> service;
private final Set<URL> services;
private boolean isRead = false;
private LinkedList<String> queue = new LinkedList<String>();
public ServiceIterator(ServiceLoader<S> sl) {
this.classLoader = sl.classLoader;
this.service = sl.service;
this.services = sl.services;
}
public boolean hasNext() {
if (!isRead) {
readClass();
}
return (queue != null && !queue.isEmpty());
}
@SuppressWarnings("unchecked")
public S next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
String className = queue.remove();
try {
return service.cast(classLoader.loadClass(className).newInstance());
} catch (Exception e) {
throw new ServiceConfigurationError("Couldn't instantiate class " + className, e);
}
}
private void readClass() {
for (URL url : services) {
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(url.openStream(), "UTF-8"));
String line;
while ((line = reader.readLine()) != null) {
// Strip comments and whitespace...
int commentStart = line.indexOf('#');
if (commentStart != -1) {
line = line.substring(0, commentStart);
}
line = line.trim();
// Ignore empty lines.
if (line.isEmpty()) {
continue;
}
String className = line;
checkValidJavaClassName(className);
if (!queue.contains(className)) {
queue.add(className);
}
}
isRead = true;
} catch (Exception e) {
throw new ServiceConfigurationError("Couldn't read " + url, e);
} finally {
try {
if (reader != null) {
reader.close();
}
} catch (IOException ex) {
}
}
}
}
public void remove() {
throw new UnsupportedOperationException();
}
private void checkValidJavaClassName(String className) {
for (int i = 0; i < className.length(); ++i) {
char ch = className.charAt(i);
if (!Character.isJavaIdentifierPart(ch) && ch != '.') {
throw new ServiceConfigurationError("Bad character '" + ch + "' in class name");
}
}
}
}
}