|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package sun.misc; |
|
|
|
import java.io.BufferedReader; |
|
import java.io.IOException; |
|
import java.io.InputStream; |
|
import java.io.InputStreamReader; |
|
import java.net.URL; |
|
import java.util.ArrayList; |
|
import java.util.Enumeration; |
|
import java.util.Iterator; |
|
import java.util.List; |
|
import java.util.NoSuchElementException; |
|
import java.util.Set; |
|
import java.util.TreeSet; |
|
|
|
|
|
/** |
|
* A simple service-provider lookup mechanism. A <i>service</i> is a |
|
* well-known set of interfaces and (usually abstract) classes. A <i>service |
|
* provider</i> is a specific implementation of a service. The classes in a |
|
* provider typically implement the interfaces and subclass the classes defined |
|
* in the service itself. Service providers may be installed in an |
|
* implementation of the Java platform in the form of extensions, that is, jar |
|
* files placed into any of the usual extension directories. Providers may |
|
* also be made available by adding them to the applet or application class |
|
* path or by some other platform-specific means. |
|
* |
|
* <p> In this lookup mechanism a service is represented by an interface or an |
|
* abstract class. (A concrete class may be used, but this is not |
|
* recommended.) A provider of a given service contains one or more concrete |
|
* classes that extend this <i>service class</i> with data and code specific to |
|
* the provider. This <i>provider class</i> will typically not be the entire |
|
* provider itself but rather a proxy that contains enough information to |
|
* decide whether the provider is able to satisfy a particular request together |
|
* with code that can create the actual provider on demand. The details of |
|
* provider classes tend to be highly service-specific; no single class or |
|
* interface could possibly unify them, so no such class has been defined. The |
|
* only requirement enforced here is that provider classes must have a |
|
* zero-argument constructor so that they may be instantiated during lookup. |
|
* |
|
* <p> A service provider identifies itself by placing a provider-configuration |
|
* file in the resource directory <tt>META-INF/services</tt>. The file's name |
|
* should consist of the fully-qualified name of the abstract service class. |
|
* The file should contain a list of fully-qualified concrete provider-class |
|
* names, one per line. Space and tab characters surrounding each name, as |
|
* well as blank lines, are ignored. The comment character is <tt>'#'</tt> |
|
* (<tt>0x23</tt>); on each line all characters following the first comment |
|
* character are ignored. The file must be encoded in UTF-8. |
|
* |
|
* <p> If a particular concrete provider class is named in more than one |
|
* configuration file, or is named in the same configuration file more than |
|
* once, then the duplicates will be ignored. The configuration file naming a |
|
* particular provider need not be in the same jar file or other distribution |
|
* unit as the provider itself. The provider must be accessible from the same |
|
* class loader that was initially queried to locate the configuration file; |
|
* note that this is not necessarily the class loader that found the file. |
|
* |
|
* <p> <b>Example:</b> Suppose we have a service class named |
|
* <tt>java.io.spi.CharCodec</tt>. It has two abstract methods: |
|
* |
|
* <pre> |
|
* public abstract CharEncoder getEncoder(String encodingName); |
|
* public abstract CharDecoder getDecoder(String encodingName); |
|
* </pre> |
|
* |
|
* Each method returns an appropriate object or <tt>null</tt> if it cannot |
|
* translate the given encoding. Typical <tt>CharCodec</tt> providers will |
|
* support more than one encoding. |
|
* |
|
* <p> If <tt>sun.io.StandardCodec</tt> is a provider of the <tt>CharCodec</tt> |
|
* service then its jar file would contain the file |
|
* <tt>META-INF/services/java.io.spi.CharCodec</tt>. This file would contain |
|
* the single line: |
|
* |
|
* <pre> |
|
* sun.io.StandardCodec # Standard codecs for the platform |
|
* </pre> |
|
* |
|
* To locate an encoder for a given encoding name, the internal I/O code would |
|
* do something like this: |
|
* |
|
* <pre> |
|
* CharEncoder getEncoder(String encodingName) { |
|
* Iterator ps = Service.providers(CharCodec.class); |
|
* while (ps.hasNext()) { |
|
* CharCodec cc = (CharCodec)ps.next(); |
|
* CharEncoder ce = cc.getEncoder(encodingName); |
|
* if (ce != null) |
|
* return ce; |
|
* } |
|
* return null; |
|
* } |
|
* </pre> |
|
* |
|
* The provider-lookup mechanism always executes in the security context of the |
|
* caller. Trusted system code should typically invoke the methods in this |
|
* class from within a privileged security context. |
|
* |
|
* @author Mark Reinhold |
|
* @since 1.3 |
|
*/ |
|
|
|
public final class Service<S> { |
|
|
|
private static final String prefix = "META-INF/services/"; |
|
|
|
private Service() { } |
|
|
|
private static void fail(Class<?> service, String msg, Throwable cause) |
|
throws ServiceConfigurationError |
|
{ |
|
ServiceConfigurationError sce |
|
= new ServiceConfigurationError(service.getName() + ": " + msg); |
|
sce.initCause(cause); |
|
throw sce; |
|
} |
|
|
|
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 + ":" + line + ": " + msg); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static int parseLine(Class<?> service, URL u, BufferedReader r, int lc, |
|
List<String> names, Set<String> returned) |
|
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 (!returned.contains(ln)) { |
|
names.add(ln); |
|
returned.add(ln); |
|
} |
|
} |
|
return lc + 1; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static Iterator<String> parse(Class<?> service, URL u, Set<String> returned) |
|
throws ServiceConfigurationError |
|
{ |
|
InputStream in = null; |
|
BufferedReader r = null; |
|
ArrayList<String> names = new ArrayList<>(); |
|
try { |
|
in = u.openStream(); |
|
r = new BufferedReader(new InputStreamReader(in, "utf-8")); |
|
int lc = 1; |
|
while ((lc = parseLine(service, u, r, lc, names, returned)) >= 0); |
|
} catch (IOException x) { |
|
fail(service, ": " + x); |
|
} finally { |
|
try { |
|
if (r != null) r.close(); |
|
if (in != null) in.close(); |
|
} catch (IOException y) { |
|
fail(service, ": " + y); |
|
} |
|
} |
|
return names.iterator(); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private static class LazyIterator<S> implements Iterator<S> { |
|
|
|
Class<S> service; |
|
ClassLoader loader; |
|
Enumeration<URL> configs = null; |
|
Iterator<String> pending = null; |
|
Set<String> returned = new TreeSet<>(); |
|
String nextName = null; |
|
|
|
private LazyIterator(Class<S> service, ClassLoader loader) { |
|
this.service = service; |
|
this.loader = loader; |
|
} |
|
|
|
public boolean hasNext() throws ServiceConfigurationError { |
|
if (nextName != null) { |
|
return true; |
|
} |
|
if (configs == null) { |
|
try { |
|
String fullName = prefix + service.getName(); |
|
if (loader == null) |
|
configs = ClassLoader.getSystemResources(fullName); |
|
else |
|
configs = loader.getResources(fullName); |
|
} catch (IOException x) { |
|
fail(service, ": " + x); |
|
} |
|
} |
|
while ((pending == null) || !pending.hasNext()) { |
|
if (!configs.hasMoreElements()) { |
|
return false; |
|
} |
|
pending = parse(service, configs.nextElement(), returned); |
|
} |
|
nextName = pending.next(); |
|
return true; |
|
} |
|
|
|
public S next() throws ServiceConfigurationError { |
|
if (!hasNext()) { |
|
throw new NoSuchElementException(); |
|
} |
|
String cn = nextName; |
|
nextName = null; |
|
Class<?> c = null; |
|
try { |
|
c = Class.forName(cn, false, loader); |
|
} catch (ClassNotFoundException x) { |
|
fail(service, |
|
"Provider " + cn + " not found"); |
|
} |
|
if (!service.isAssignableFrom(c)) { |
|
fail(service, |
|
"Provider " + cn + " not a subtype"); |
|
} |
|
try { |
|
return service.cast(c.newInstance()); |
|
} catch (Throwable x) { |
|
fail(service, |
|
"Provider " + cn + " could not be instantiated", |
|
x); |
|
} |
|
return null; /* This cannot happen */ |
|
} |
|
|
|
public void remove() { |
|
throw new UnsupportedOperationException(); |
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static <S> Iterator<S> providers(Class<S> service, ClassLoader loader) |
|
throws ServiceConfigurationError |
|
{ |
|
return new LazyIterator<S>(service, loader); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static <S> Iterator<S> providers(Class<S> service) |
|
throws ServiceConfigurationError |
|
{ |
|
ClassLoader cl = Thread.currentThread().getContextClassLoader(); |
|
return Service.providers(service, cl); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static <S> Iterator<S> installedProviders(Class<S> service) |
|
throws ServiceConfigurationError |
|
{ |
|
ClassLoader cl = ClassLoader.getSystemClassLoader(); |
|
ClassLoader prev = null; |
|
while (cl != null) { |
|
prev = cl; |
|
cl = cl.getParent(); |
|
} |
|
return Service.providers(service, prev); |
|
} |
|
|
|
} |