001: /*
002: * @(#)Service.java 1.16 06/10/10
003: *
004: * Copyright 1990-2006 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026:
027: package sun.misc;
028:
029: import java.io.BufferedReader;
030: import java.io.IOException;
031: import java.io.InputStream;
032: import java.io.InputStreamReader;
033: import java.net.URL;
034: import java.util.ArrayList;
035: import java.util.Enumeration;
036: import java.util.Iterator;
037: import java.util.List;
038: import java.util.NoSuchElementException;
039: import java.util.Set;
040: import java.util.TreeSet;
041:
042: /**
043: * A simple service-provider lookup mechanism. A <i>service</i> is a
044: * well-known set of interfaces and (usually abstract) classes. A <i>service
045: * provider</i> is a specific implementation of a service. The classes in a
046: * provider typically implement the interfaces and subclass the classes defined
047: * in the service itself. Service providers may be installed in an
048: * implementation of the Java platform in the form of extensions, that is, jar
049: * files placed into any of the usual extension directories. Providers may
050: * also be made available by adding them to the applet or application class
051: * path or by some other platform-specific means.
052: *
053: * <p> In this lookup mechanism a service is represented by an interface or an
054: * abstract class. (A concrete class may be used, but this is not
055: * recommended.) A provider of a given service contains one or more concrete
056: * classes that extend this <i>service class</i> with data and code specific to
057: * the provider. This <i>provider class</i> will typically not be the entire
058: * provider itself but rather a proxy that contains enough information to
059: * decide whether the provider is able to satisfy a particular request together
060: * with code that can create the actual provider on demand. The details of
061: * provider classes tend to be highly service-specific; no single class or
062: * interface could possibly unify them, so no such class has been defined. The
063: * only requirement enforced here is that provider classes must have a
064: * zero-argument constructor so that they may be instantiated during lookup.
065: *
066: * <p> A service provider identifies itself by placing a provider-configuration
067: * file in the resource directory <tt>META-INF/services</tt>. The file's name
068: * should consist of the fully-qualified name of the abstract service class.
069: * The file should contain a list of fully-qualified concrete provider-class
070: * names, one per line. Space and tab characters surrounding each name, as
071: * well as blank lines, are ignored. The comment character is <tt>'#'</tt>
072: * (<tt>0x23</tt>); on each line all characters following the first comment
073: * character are ignored. The file must be encoded in UTF-8.
074: *
075: * <p> If a particular concrete provider class is named in more than one
076: * configuration file, or is named in the same configuration file more than
077: * once, then the duplicates will be ignored. The configuration file naming a
078: * particular provider need not be in the same jar file or other distribution
079: * unit as the provider itself. The provider must be accessible from the same
080: * class loader that was initially queried to locate the configuration file;
081: * note that this is not necessarily the class loader that found the file.
082: *
083: * <p> <b>Example:</b> Suppose we have a service class named
084: * <tt>java.io.spi.CharCodec</tt>. It has two abstract methods:
085: *
086: * <pre>
087: * public abstract CharEncoder getEncoder(String encodingName);
088: * public abstract CharDecoder getDecoder(String encodingName);
089: * </pre>
090: *
091: * Each method returns an appropriate object or <tt>null</tt> if it cannot
092: * translate the given encoding. Typical <tt>CharCodec</tt> providers will
093: * support more than one encoding.
094: *
095: * <p> If <tt>sun.io.StandardCodec</tt> is a provider of the <tt>CharCodec</tt>
096: * service then its jar file would contain the file
097: * <tt>META-INF/services/java.io.spi.CharCodec</tt>. This file would contain
098: * the single line:
099: *
100: * <pre>
101: * sun.io.StandardCodec # Standard codecs for the platform
102: * </pre>
103: *
104: * To locate an encoder for a given encoding name, the internal I/O code would
105: * do something like this:
106: *
107: * <pre>
108: * CharEncoder getEncoder(String encodingName) {
109: * Iterator ps = Service.providers(CharCodec.class);
110: * while (ps.hasNext()) {
111: * CharCodec cc = (CharCodec)ps.next();
112: * CharEncoder ce = cc.getEncoder(encodingName);
113: * if (ce != null)
114: * return ce;
115: * }
116: * return null;
117: * }
118: * </pre>
119: *
120: * The provider-lookup mechanism always executes in the security context of the
121: * caller. Trusted system code should typically invoke the methods in this
122: * class from within a privileged security context.
123: *
124: * @author Mark Reinhold
125: * @version 1.10, 02/08/19
126: * @since 1.3
127: */
128:
129: public final class Service {
130: private static final String prefix = "META-INF/services/";
131:
132: private Service() {
133: }
134:
135: private static void fail(Class service, String msg, Throwable cause)
136: throws ServiceConfigurationError {
137: ServiceConfigurationError sce = new ServiceConfigurationError(
138: service.getName() + ": " + msg);
139: sce.initCause(cause);
140: throw sce;
141: }
142:
143: private static void fail(Class service, String msg)
144: throws ServiceConfigurationError {
145: throw new ServiceConfigurationError(service.getName() + ": "
146: + msg);
147: }
148:
149: private static void fail(Class service, URL u, int line, String msg)
150: throws ServiceConfigurationError {
151: fail(service, u + ":" + line + ": " + msg);
152: }
153:
154: /**
155: * Parse a single line from the given configuration file, adding the name
156: * on the line to both the names list and the returned set iff the name is
157: * not already a member of the returned set.
158: */
159: private static int parseLine(Class service, URL u,
160: BufferedReader r, int lc, List names, Set returned)
161: throws IOException, ServiceConfigurationError {
162: String ln = r.readLine();
163: if (ln == null) {
164: return -1;
165: }
166: int ci = ln.indexOf('#');
167: if (ci >= 0)
168: ln = ln.substring(0, ci);
169: ln = ln.trim();
170: int n = ln.length();
171: if (n != 0) {
172: if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
173: fail(service, u, lc,
174: "Illegal configuration-file syntax");
175: if (!Character.isJavaIdentifierStart(ln.charAt(0)))
176: fail(service, u, lc, "Illegal provider-class name: "
177: + ln);
178: for (int i = 1; i < n; i++) {
179: char c = ln.charAt(i);
180: if (!Character.isJavaIdentifierPart(c) && (c != '.'))
181: fail(service, u, lc,
182: "Illegal provider-class name: " + ln);
183: }
184: if (!returned.contains(ln)) {
185: names.add(ln);
186: returned.add(ln);
187: }
188: }
189: return lc + 1;
190: }
191:
192: /**
193: * Parse the content of the given URL as a provider-configuration file.
194: *
195: * @param service
196: * The service class for which providers are being sought;
197: * used to construct error detail strings
198: *
199: * @param url
200: * The URL naming the configuration file to be parsed
201: *
202: * @param returned
203: * A Set containing the names of provider classes that have already
204: * been returned. This set will be updated to contain the names
205: * that will be yielded from the returned <tt>Iterator</tt>.
206: *
207: * @return A (possibly empty) <tt>Iterator</tt> that will yield the
208: * provider-class names in the given configuration file that are
209: * not yet members of the returned set
210: *
211: * @throws ServiceConfigurationError
212: * If an I/O error occurs while reading from the given URL, or
213: * if a configuration-file format error is detected
214: */
215: private static Iterator parse(Class service, URL u, Set returned)
216: throws ServiceConfigurationError {
217: InputStream in = null;
218: BufferedReader r = null;
219: ArrayList names = new ArrayList();
220: try {
221: in = u.openStream();
222: r = new BufferedReader(new InputStreamReader(in, "utf-8"));
223: int lc = 1;
224: while ((lc = parseLine(service, u, r, lc, names, returned)) >= 0)
225: ;
226: } catch (IOException x) {
227: fail(service, ": " + x);
228: } finally {
229: try {
230: if (r != null)
231: r.close();
232: if (in != null)
233: in.close();
234: } catch (IOException y) {
235: fail(service, ": " + y);
236: }
237: }
238: return names.iterator();
239: }
240:
241: /**
242: * Private inner class implementing fully-lazy provider lookup
243: */
244: private static class LazyIterator implements Iterator {
245: Class service;
246: ClassLoader loader;
247: Enumeration configs = null;
248: Iterator pending = null;
249: Set returned = new TreeSet();
250: String nextName = null;
251:
252: private LazyIterator(Class service, ClassLoader loader) {
253: this .service = service;
254: this .loader = loader;
255: }
256:
257: public boolean hasNext() throws ServiceConfigurationError {
258: if (nextName != null) {
259: return true;
260: }
261: if (configs == null) {
262: try {
263: String fullName = prefix + service.getName();
264: if (loader == null)
265: configs = ClassLoader
266: .getSystemResources(fullName);
267: else
268: configs = loader.getResources(fullName);
269: } catch (IOException x) {
270: fail(service, ": " + x);
271: }
272: }
273: while ((pending == null) || !pending.hasNext()) {
274: if (!configs.hasMoreElements()) {
275: return false;
276: }
277: pending = parse(service, (URL) configs.nextElement(),
278: returned);
279: }
280: nextName = (String) pending.next();
281: return true;
282: }
283:
284: public Object next() throws ServiceConfigurationError {
285: if (!hasNext()) {
286: throw new NoSuchElementException();
287: }
288: String cn = nextName;
289: nextName = null;
290: try {
291: return Class.forName(cn, true, loader).newInstance();
292: } catch (ClassNotFoundException x) {
293: fail(service, "Provider " + cn + " not found");
294: } catch (Exception x) {
295: fail(service, "Provider " + cn
296: + " could not be instantiated: " + x);
297: }
298: return null; /* This cannot happen */
299: }
300:
301: public void remove() {
302: throw new UnsupportedOperationException();
303: }
304:
305: }
306:
307: /**
308: * Locates and incrementally instantiates the available providers of a
309: * given service using the given class loader.
310: *
311: * <p> This method transforms the name of the given service class into a
312: * provider-configuration filename as described above and then uses the
313: * <tt>getResources</tt> method of the given class loader to find all
314: * available files with that name. These files are then read and parsed to
315: * produce a list of provider-class names. The iterator that is returned
316: * uses the given class loader to lookup and then instantiate each element
317: * of the list.
318: *
319: * <p> Because it is possible for extensions to be installed into a running
320: * Java virtual machine, this method may return different results each time
321: * it is invoked. <p>
322: *
323: * @param service
324: * The service's abstract service class
325: *
326: * @param loader
327: * The class loader to be used to load provider-configuration files
328: * and instantiate provider classes, or <tt>null</tt> if the system
329: * class loader (or, failing that the bootstrap class loader) is to
330: * be used
331: *
332: * @return An <tt>Iterator</tt> that yields provider objects for the given
333: * service, in some arbitrary order. The iterator will throw a
334: * <tt>ServiceConfigurationError</tt> if a provider-configuration
335: * file violates the specified format or if a provider class cannot
336: * be found and instantiated.
337: *
338: * @throws ServiceConfigurationError
339: * If a provider-configuration file violates the specified format
340: * or names a provider class that cannot be found and instantiated
341: *
342: * @see #providers(java.lang.Class)
343: * @see #installedProviders(java.lang.Class)
344: */
345: public static Iterator providers(Class service, ClassLoader loader)
346: throws ServiceConfigurationError {
347: return new LazyIterator(service, loader);
348: }
349:
350: /**
351: * Locates and incrementally instantiates the available providers of a
352: * given service using the context class loader. This convenience method
353: * is equivalent to
354: *
355: * <pre>
356: * ClassLoader cl = Thread.currentThread().getContextClassLoader();
357: * return Service.providers(service, cl);
358: * </pre>
359: *
360: * @param service
361: * The service's abstract service class
362: *
363: * @return An <tt>Iterator</tt> that yields provider objects for the given
364: * service, in some arbitrary order. The iterator will throw a
365: * <tt>ServiceConfigurationError</tt> if a provider-configuration
366: * file violates the specified format or if a provider class cannot
367: * be found and instantiated.
368: *
369: * @throws ServiceConfigurationError
370: * If a provider-configuration file violates the specified format
371: * or names a provider class that cannot be found and instantiated
372: *
373: * @see #providers(java.lang.Class, java.lang.ClassLoader)
374: */
375: public static Iterator providers(Class service)
376: throws ServiceConfigurationError {
377: ClassLoader cl = Thread.currentThread().getContextClassLoader();
378: return Service.providers(service, cl);
379: }
380:
381: /**
382: * Locates and incrementally instantiates the available providers of a
383: * given service using the extension class loader. This convenience method
384: * simply locates the extension class loader, call it
385: * <tt>extClassLoader</tt>, and then does
386: *
387: * <pre>
388: * return Service.providers(service, extClassLoader);
389: * </pre>
390: *
391: * If the extension class loader cannot be found then the system class
392: * loader is used; if there is no system class loader then the bootstrap
393: * class loader is used.
394: *
395: * @param service
396: * The service's abstract service class
397: *
398: * @return An <tt>Iterator</tt> that yields provider objects for the given
399: * service, in some arbitrary order. The iterator will throw a
400: * <tt>ServiceConfigurationError</tt> if a provider-configuration
401: * file violates the specified format or if a provider class cannot
402: * be found and instantiated.
403: *
404: * @throws ServiceConfigurationError
405: * If a provider-configuration file violates the specified format
406: * or names a provider class that cannot be found and instantiated
407: *
408: * @see #providers(java.lang.Class, java.lang.ClassLoader)
409: */
410: public static Iterator installedProviders(Class service)
411: throws ServiceConfigurationError {
412: ClassLoader cl = ClassLoader.getSystemClassLoader();
413: ClassLoader prev = null;
414: while (cl != null) {
415: prev = cl;
416: cl = cl.getParent();
417: }
418: return Service.providers(service, prev);
419: }
420: }
|