jdk/src/share/classes/sun/misc/Service.java
changeset 22959 2d9d076cee41
parent 22958 273ddc5c37f3
child 22960 436141944957
equal deleted inserted replaced
22958:273ddc5c37f3 22959:2d9d076cee41
     1 /*
       
     2  * Copyright (c) 1999, 2012, 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<S> {
       
   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<String> names, Set<String> 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<String> parse(Class<?> service, URL u, Set<String> returned)
       
   215         throws ServiceConfigurationError
       
   216     {
       
   217         InputStream in = null;
       
   218         BufferedReader r = null;
       
   219         ArrayList<String> 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<S> implements Iterator<S> {
       
   243 
       
   244         Class<S> service;
       
   245         ClassLoader loader;
       
   246         Enumeration<URL> configs = null;
       
   247         Iterator<String> pending = null;
       
   248         Set<String> returned = new TreeSet<>();
       
   249         String nextName = null;
       
   250 
       
   251         private LazyIterator(Class<S> 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, configs.nextElement(), returned);
       
   276             }
       
   277             nextName = pending.next();
       
   278             return true;
       
   279         }
       
   280 
       
   281         public S 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 <S> Iterator<S> providers(Class<S> service, ClassLoader loader)
       
   354         throws ServiceConfigurationError
       
   355     {
       
   356         return new LazyIterator<S>(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 <S> Iterator<S> providers(Class<S> 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 <S> Iterator<S> installedProviders(Class<S> 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 }