Home | History | Annotate | Download | only in misc
      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 
    132     private Service() { }
    133 
    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 
    143     private static void fail(Class service, String msg)
    144         throws ServiceConfigurationError
    145     {
    146         throw new ServiceConfigurationError(service.getName() + ": " + msg);
    147     }
    148 
    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      */
    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      */
    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 
    251         private LazyIterator(Class service, ClassLoader loader) {
    252             this.service = service;
    253             this.loader = loader;
    254         }
    255 
    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 
    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 
    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      */
    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      */
    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      */
    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