1 /*
2  * Copyright (c) 1999, 2003, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package sun.misc;
27 
28 import java.io.BufferedReader;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.io.InputStreamReader;
32 import java.net.URL;
33 import java.util.ArrayList;
34 import java.util.Enumeration;
35 import java.util.Iterator;
36 import java.util.List;
37 import java.util.NoSuchElementException;
38 import java.util.Set;
39 import java.util.TreeSet;
40 
41 
42 /**
43  * A simple service-provider lookup mechanism.  A <i>service</i> is a
44  * well-known set of interfaces and (usually abstract) classes.  A <i>service
45  * provider</i> is a specific implementation of a service.  The classes in a
46  * provider typically implement the interfaces and subclass the classes defined
47  * in the service itself.  Service providers may be installed in an
48  * implementation of the Java platform in the form of extensions, that is, jar
49  * files placed into any of the usual extension directories.  Providers may
50  * also be made available by adding them to the applet or application class
51  * path or by some other platform-specific means.
52  *
53  * <p> In this lookup mechanism a service is represented by an interface or an
54  * abstract class.  (A concrete class may be used, but this is not
55  * recommended.)  A provider of a given service contains one or more concrete
56  * classes that extend this <i>service class</i> with data and code specific to
57  * the provider.  This <i>provider class</i> will typically not be the entire
58  * provider itself but rather a proxy that contains enough information to
59  * decide whether the provider is able to satisfy a particular request together
60  * with code that can create the actual provider on demand.  The details of
61  * provider classes tend to be highly service-specific; no single class or
62  * interface could possibly unify them, so no such class has been defined.  The
63  * only requirement enforced here is that provider classes must have a
64  * zero-argument constructor so that they may be instantiated during lookup.
65  *
66  * <p> A service provider identifies itself by placing a provider-configuration
67  * file in the resource directory <tt>META-INF/services</tt>.  The file's name
68  * should consist of the fully-qualified name of the abstract service class.
69  * The file should contain a list of fully-qualified concrete provider-class
70  * names, one per line.  Space and tab characters surrounding each name, as
71  * well as blank lines, are ignored.  The comment character is <tt>'#'</tt>
72  * (<tt>0x23</tt>); on each line all characters following the first comment
73  * character are ignored.  The file must be encoded in UTF-8.
74  *
75  * <p> If a particular concrete provider class is named in more than one
76  * configuration file, or is named in the same configuration file more than
77  * once, then the duplicates will be ignored.  The configuration file naming a
78  * particular provider need not be in the same jar file or other distribution
79  * unit as the provider itself.  The provider must be accessible from the same
80  * class loader that was initially queried to locate the configuration file;
81  * note that this is not necessarily the class loader that found the file.
82  *
83  * <p> <b>Example:</b> Suppose we have a service class named
84  * <tt>java.io.spi.CharCodec</tt>.  It has two abstract methods:
85  *
86  * <pre>
87  *   public abstract CharEncoder getEncoder(String encodingName);
88  *   public abstract CharDecoder getDecoder(String encodingName);
89  * </pre>
90  *
91  * Each method returns an appropriate object or <tt>null</tt> if it cannot
92  * translate the given encoding.  Typical <tt>CharCodec</tt> providers will
93  * support more than one encoding.
94  *
95  * <p> If <tt>sun.io.StandardCodec</tt> is a provider of the <tt>CharCodec</tt>
96  * service then its jar file would contain the file
97  * <tt>META-INF/services/java.io.spi.CharCodec</tt>.  This file would contain
98  * the single line:
99  *
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  * @since 1.3
126  */
127 
128 public final class Service {
129 
130     private static final String prefix = "META-INF/services/";
131 
Service()132     private Service() { }
133 
fail(Class service, String msg, Throwable cause)134     private static void fail(Class service, String msg, Throwable cause)
135         throws ServiceConfigurationError
136     {
137         ServiceConfigurationError sce
138             = new ServiceConfigurationError(service.getName() + ": " + msg);
139         sce.initCause(cause);
140         throw sce;
141     }
142 
fail(Class service, String msg)143     private static void fail(Class service, String msg)
144         throws ServiceConfigurationError
145     {
146         throw new ServiceConfigurationError(service.getName() + ": " + msg);
147     }
148 
fail(Class service, URL u, int line, String msg)149     private static void fail(Class service, URL u, int line, String msg)
150         throws ServiceConfigurationError
151     {
152         fail(service, u + ":" + line + ": " + msg);
153     }
154 
155     /**
156      * Parse a single line from the given configuration file, adding the name
157      * on the line to both the names list and the returned set iff the name is
158      * not already a member of the returned set.
159      */
parseLine(Class service, URL u, BufferedReader r, int lc, List names, Set returned)160     private static int parseLine(Class service, URL u, BufferedReader r, int lc,
161                                  List names, Set returned)
162         throws IOException, ServiceConfigurationError
163     {
164         String ln = r.readLine();
165         if (ln == null) {
166             return -1;
167         }
168         int ci = ln.indexOf('#');
169         if (ci >= 0) ln = ln.substring(0, ci);
170         ln = ln.trim();
171         int n = ln.length();
172         if (n != 0) {
173             if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
174                 fail(service, u, lc, "Illegal configuration-file syntax");
175             int cp = ln.codePointAt(0);
176             if (!Character.isJavaIdentifierStart(cp))
177                 fail(service, u, lc, "Illegal provider-class name: " + ln);
178             for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
179                 cp = ln.codePointAt(i);
180                 if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
181                     fail(service, u, lc, "Illegal provider-class name: " + ln);
182             }
183             if (!returned.contains(ln)) {
184                 names.add(ln);
185                 returned.add(ln);
186             }
187         }
188         return lc + 1;
189     }
190 
191     /**
192      * Parse the content of the given URL as a provider-configuration file.
193      *
194      * @param  service
195      *         The service class for which providers are being sought;
196      *         used to construct error detail strings
197      *
198      * @param  url
199      *         The URL naming the configuration file to be parsed
200      *
201      * @param  returned
202      *         A Set containing the names of provider classes that have already
203      *         been returned.  This set will be updated to contain the names
204      *         that will be yielded from the returned <tt>Iterator</tt>.
205      *
206      * @return A (possibly empty) <tt>Iterator</tt> that will yield the
207      *         provider-class names in the given configuration file that are
208      *         not yet members of the returned set
209      *
210      * @throws ServiceConfigurationError
211      *         If an I/O error occurs while reading from the given URL, or
212      *         if a configuration-file format error is detected
213      */
parse(Class service, URL u, Set returned)214     private static Iterator parse(Class service, URL u, Set returned)
215         throws ServiceConfigurationError
216     {
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         } catch (IOException x) {
226             fail(service, ": " + x);
227         } finally {
228             try {
229                 if (r != null) r.close();
230                 if (in != null) in.close();
231             } catch (IOException y) {
232                 fail(service, ": " + y);
233             }
234         }
235         return names.iterator();
236     }
237 
238 
239     /**
240      * Private inner class implementing fully-lazy provider lookup
241      */
242     private static class LazyIterator implements Iterator {
243 
244         Class service;
245         ClassLoader loader;
246         Enumeration configs = null;
247         Iterator pending = null;
248         Set returned = new TreeSet();
249         String nextName = null;
250 
LazyIterator(Class service, ClassLoader loader)251         private LazyIterator(Class service, ClassLoader loader) {
252             this.service = service;
253             this.loader = loader;
254         }
255 
hasNext()256         public boolean hasNext() throws ServiceConfigurationError {
257             if (nextName != null) {
258                 return true;
259             }
260             if (configs == null) {
261                 try {
262                     String fullName = prefix + service.getName();
263                     if (loader == null)
264                         configs = ClassLoader.getSystemResources(fullName);
265                     else
266                         configs = loader.getResources(fullName);
267                 } catch (IOException x) {
268                     fail(service, ": " + x);
269                 }
270             }
271             while ((pending == null) || !pending.hasNext()) {
272                 if (!configs.hasMoreElements()) {
273                     return false;
274                 }
275                 pending = parse(service, (URL)configs.nextElement(), returned);
276             }
277             nextName = (String)pending.next();
278             return true;
279         }
280 
next()281         public Object next() throws ServiceConfigurationError {
282             if (!hasNext()) {
283                 throw new NoSuchElementException();
284             }
285             String cn = nextName;
286             nextName = null;
287             Class<?> c = null;
288             try {
289                 c = Class.forName(cn, false, loader);
290             } catch (ClassNotFoundException x) {
291                 fail(service,
292                      "Provider " + cn + " not found");
293             }
294             if (!service.isAssignableFrom(c)) {
295                 fail(service,
296                      "Provider " + cn  + " not a subtype");
297             }
298             try {
299                 return service.cast(c.newInstance());
300             } catch (Throwable x) {
301                 fail(service,
302                      "Provider " + cn + " could not be instantiated: " + x,
303                      x);
304             }
305             return null;        /* This cannot happen */
306         }
307 
remove()308         public void remove() {
309             throw new UnsupportedOperationException();
310         }
311 
312     }
313 
314 
315     /**
316      * Locates and incrementally instantiates the available providers of a
317      * given service using the given class loader.
318      *
319      * <p> This method transforms the name of the given service class into a
320      * provider-configuration filename as described above and then uses the
321      * <tt>getResources</tt> method of the given class loader to find all
322      * available files with that name.  These files are then read and parsed to
323      * produce a list of provider-class names.  The iterator that is returned
324      * uses the given class loader to lookup and then instantiate each element
325      * of the list.
326      *
327      * <p> Because it is possible for extensions to be installed into a running
328      * Java virtual machine, this method may return different results each time
329      * it is invoked. <p>
330      *
331      * @param  service
332      *         The service's abstract service class
333      *
334      * @param  loader
335      *         The class loader to be used to load provider-configuration files
336      *         and instantiate provider classes, or <tt>null</tt> if the system
337      *         class loader (or, failing that the bootstrap class loader) is to
338      *         be used
339      *
340      * @return An <tt>Iterator</tt> that yields provider objects for the given
341      *         service, in some arbitrary order.  The iterator will throw a
342      *         <tt>ServiceConfigurationError</tt> if a provider-configuration
343      *         file violates the specified format or if a provider class cannot
344      *         be found and instantiated.
345      *
346      * @throws ServiceConfigurationError
347      *         If a provider-configuration file violates the specified format
348      *         or names a provider class that cannot be found and instantiated
349      *
350      * @see #providers(java.lang.Class)
351      * @see #installedProviders(java.lang.Class)
352      */
providers(Class service, ClassLoader loader)353     public static Iterator providers(Class service, ClassLoader loader)
354         throws ServiceConfigurationError
355     {
356         return new LazyIterator(service, loader);
357     }
358 
359 
360     /**
361      * Locates and incrementally instantiates the available providers of a
362      * given service using the context class loader.  This convenience method
363      * is equivalent to
364      *
365      * <pre>
366      *   ClassLoader cl = Thread.currentThread().getContextClassLoader();
367      *   return Service.providers(service, cl);
368      * </pre>
369      *
370      * @param  service
371      *         The service's abstract service class
372      *
373      * @return An <tt>Iterator</tt> that yields provider objects for the given
374      *         service, in some arbitrary order.  The iterator will throw a
375      *         <tt>ServiceConfigurationError</tt> if a provider-configuration
376      *         file violates the specified format or if a provider class cannot
377      *         be found and instantiated.
378      *
379      * @throws ServiceConfigurationError
380      *         If a provider-configuration file violates the specified format
381      *         or names a provider class that cannot be found and instantiated
382      *
383      * @see #providers(java.lang.Class, java.lang.ClassLoader)
384      */
providers(Class service)385     public static Iterator providers(Class service)
386         throws ServiceConfigurationError
387     {
388         ClassLoader cl = Thread.currentThread().getContextClassLoader();
389         return Service.providers(service, cl);
390     }
391 
392 
393     /**
394      * Locates and incrementally instantiates the available providers of a
395      * given service using the extension class loader.  This convenience method
396      * simply locates the extension class loader, call it
397      * <tt>extClassLoader</tt>, and then does
398      *
399      * <pre>
400      *   return Service.providers(service, extClassLoader);
401      * </pre>
402      *
403      * If the extension class loader cannot be found then the system class
404      * loader is used; if there is no system class loader then the bootstrap
405      * class loader is used.
406      *
407      * @param  service
408      *         The service's abstract service class
409      *
410      * @return An <tt>Iterator</tt> that yields provider objects for the given
411      *         service, in some arbitrary order.  The iterator will throw a
412      *         <tt>ServiceConfigurationError</tt> if a provider-configuration
413      *         file violates the specified format or if a provider class cannot
414      *         be found and instantiated.
415      *
416      * @throws ServiceConfigurationError
417      *         If a provider-configuration file violates the specified format
418      *         or names a provider class that cannot be found and instantiated
419      *
420      * @see #providers(java.lang.Class, java.lang.ClassLoader)
421      */
installedProviders(Class service)422     public static Iterator installedProviders(Class service)
423         throws ServiceConfigurationError
424     {
425         ClassLoader cl = ClassLoader.getSystemClassLoader();
426         ClassLoader prev = null;
427         while (cl != null) {
428             prev = cl;
429             cl = cl.getParent();
430         }
431         return Service.providers(service, prev);
432     }
433 
434 }
435