langtools/src/share/classes/com/sun/tools/javac/util/ServiceLoader.java
changeset 17551 03f330c02d97
child 22159 682da512ec17
equal deleted inserted replaced
17550:567df1379253 17551:03f330c02d97
       
     1 /*
       
     2  * Copyright (c) 2005, 2013, 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 com.sun.tools.javac.util;
       
    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.net.URLConnection;
       
    34 import java.util.ArrayList;
       
    35 import java.util.Enumeration;
       
    36 import java.util.Iterator;
       
    37 import java.util.LinkedHashMap;
       
    38 import java.util.List;
       
    39 import java.util.Map;
       
    40 import java.util.NoSuchElementException;
       
    41 import java.util.Objects;
       
    42 import java.util.ServiceConfigurationError;
       
    43 
       
    44 
       
    45 /**
       
    46  * This is a temporary, modified copy of java.util.ServiceLoader, for use by
       
    47  * javac, to work around bug JDK-8004082.
       
    48  *
       
    49  * The bug describes problems in the interaction between ServiceLoader and
       
    50  * URLClassLoader, such that references to a jar file passed to URLClassLoader
       
    51  * may be retained after calling URLClassLoader.close(), preventing the jar
       
    52  * file from being deleted on Windows.
       
    53  *
       
    54  *  <p><b>This is NOT part of any supported API.
       
    55  *  If you write code that depends on this, you do so at your own risk.
       
    56  *  This code and its internal interfaces are subject to change or
       
    57  *  deletion without notice.</b>
       
    58  */
       
    59 
       
    60 public final class ServiceLoader<S>
       
    61     implements Iterable<S>
       
    62 {
       
    63 
       
    64     private static final String PREFIX = "META-INF/services/";
       
    65 
       
    66     // The class or interface representing the service being loaded
       
    67     private Class<S> service;
       
    68 
       
    69     // The class loader used to locate, load, and instantiate providers
       
    70     private ClassLoader loader;
       
    71 
       
    72     // Cached providers, in instantiation order
       
    73     private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
       
    74 
       
    75     // The current lazy-lookup iterator
       
    76     private LazyIterator lookupIterator;
       
    77 
       
    78     /**
       
    79      * Clear this loader's provider cache so that all providers will be
       
    80      * reloaded.
       
    81      *
       
    82      * <p> After invoking this method, subsequent invocations of the {@link
       
    83      * #iterator() iterator} method will lazily look up and instantiate
       
    84      * providers from scratch, just as is done by a newly-created loader.
       
    85      *
       
    86      * <p> This method is intended for use in situations in which new providers
       
    87      * can be installed into a running Java virtual machine.
       
    88      */
       
    89     public void reload() {
       
    90         providers.clear();
       
    91         lookupIterator = new LazyIterator(service, loader);
       
    92     }
       
    93 
       
    94     private ServiceLoader(Class<S> svc, ClassLoader cl) {
       
    95         service = Objects.requireNonNull(svc, "Service interface cannot be null");
       
    96         loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
       
    97         reload();
       
    98     }
       
    99 
       
   100     private static void fail(Class<?> service, String msg, Throwable cause)
       
   101         throws ServiceConfigurationError
       
   102     {
       
   103         throw new ServiceConfigurationError(service.getName() + ": " + msg,
       
   104                                             cause);
       
   105     }
       
   106 
       
   107     private static void fail(Class<?> service, String msg)
       
   108         throws ServiceConfigurationError
       
   109     {
       
   110         throw new ServiceConfigurationError(service.getName() + ": " + msg);
       
   111     }
       
   112 
       
   113     private static void fail(Class<?> service, URL u, int line, String msg)
       
   114         throws ServiceConfigurationError
       
   115     {
       
   116         fail(service, u + ":" + line + ": " + msg);
       
   117     }
       
   118 
       
   119     // Parse a single line from the given configuration file, adding the name
       
   120     // on the line to the names list.
       
   121     //
       
   122     private int parseLine(Class<?> service, URL u, BufferedReader r, int lc,
       
   123                           List<String> names)
       
   124         throws IOException, ServiceConfigurationError
       
   125     {
       
   126         String ln = r.readLine();
       
   127         if (ln == null) {
       
   128             return -1;
       
   129         }
       
   130         int ci = ln.indexOf('#');
       
   131         if (ci >= 0) ln = ln.substring(0, ci);
       
   132         ln = ln.trim();
       
   133         int n = ln.length();
       
   134         if (n != 0) {
       
   135             if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
       
   136                 fail(service, u, lc, "Illegal configuration-file syntax");
       
   137             int cp = ln.codePointAt(0);
       
   138             if (!Character.isJavaIdentifierStart(cp))
       
   139                 fail(service, u, lc, "Illegal provider-class name: " + ln);
       
   140             for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
       
   141                 cp = ln.codePointAt(i);
       
   142                 if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
       
   143                     fail(service, u, lc, "Illegal provider-class name: " + ln);
       
   144             }
       
   145             if (!providers.containsKey(ln) && !names.contains(ln))
       
   146                 names.add(ln);
       
   147         }
       
   148         return lc + 1;
       
   149     }
       
   150 
       
   151     // Parse the content of the given URL as a provider-configuration file.
       
   152     //
       
   153     // @param  service
       
   154     //         The service type for which providers are being sought;
       
   155     //         used to construct error detail strings
       
   156     //
       
   157     // @param  u
       
   158     //         The URL naming the configuration file to be parsed
       
   159     //
       
   160     // @return A (possibly empty) iterator that will yield the provider-class
       
   161     //         names in the given configuration file that are not yet members
       
   162     //         of the returned set
       
   163     //
       
   164     // @throws ServiceConfigurationError
       
   165     //         If an I/O error occurs while reading from the given URL, or
       
   166     //         if a configuration-file format error is detected
       
   167     //
       
   168     private Iterator<String> parse(Class<?> service, URL u)
       
   169         throws ServiceConfigurationError
       
   170     {
       
   171         InputStream in = null;
       
   172         BufferedReader r = null;
       
   173         ArrayList<String> names = new ArrayList<>();
       
   174         try {
       
   175             // The problem is that by default, streams opened with
       
   176             // u.openInputStream use a cached reference to a JarFile, which
       
   177             // is separate from the reference used by URLClassLoader, and
       
   178             // which is not closed by URLClassLoader.close().
       
   179             // The workaround is to disable caching for this specific jar file,
       
   180             // so that the reference to the jar file can be closed when the
       
   181             // file has been read.
       
   182             // Original code:
       
   183             // in = u.openStream();
       
   184             // Workaround ...
       
   185             URLConnection uc = u.openConnection();
       
   186             uc.setUseCaches(false);
       
   187             in = uc.getInputStream();
       
   188             // ... end of workaround.
       
   189             r = new BufferedReader(new InputStreamReader(in, "utf-8"));
       
   190             int lc = 1;
       
   191             while ((lc = parseLine(service, u, r, lc, names)) >= 0);
       
   192         } catch (IOException x) {
       
   193             fail(service, "Error reading configuration file", x);
       
   194         } finally {
       
   195             try {
       
   196                 if (r != null) r.close();
       
   197                 if (in != null) in.close();
       
   198             } catch (IOException y) {
       
   199                 fail(service, "Error closing configuration file", y);
       
   200             }
       
   201         }
       
   202         return names.iterator();
       
   203     }
       
   204 
       
   205     // Private inner class implementing fully-lazy provider lookup
       
   206     //
       
   207     private class LazyIterator
       
   208         implements Iterator<S>
       
   209     {
       
   210 
       
   211         Class<S> service;
       
   212         ClassLoader loader;
       
   213         Enumeration<URL> configs = null;
       
   214         Iterator<String> pending = null;
       
   215         String nextName = null;
       
   216 
       
   217         private LazyIterator(Class<S> service, ClassLoader loader) {
       
   218             this.service = service;
       
   219             this.loader = loader;
       
   220         }
       
   221 
       
   222         public boolean hasNext() {
       
   223             if (nextName != null) {
       
   224                 return true;
       
   225             }
       
   226             if (configs == null) {
       
   227                 try {
       
   228                     String fullName = PREFIX + service.getName();
       
   229                     if (loader == null)
       
   230                         configs = ClassLoader.getSystemResources(fullName);
       
   231                     else
       
   232                         configs = loader.getResources(fullName);
       
   233                 } catch (IOException x) {
       
   234                     fail(service, "Error locating configuration files", x);
       
   235                 }
       
   236             }
       
   237             while ((pending == null) || !pending.hasNext()) {
       
   238                 if (!configs.hasMoreElements()) {
       
   239                     return false;
       
   240                 }
       
   241                 pending = parse(service, configs.nextElement());
       
   242             }
       
   243             nextName = pending.next();
       
   244             return true;
       
   245         }
       
   246 
       
   247         public S next() {
       
   248             if (!hasNext()) {
       
   249                 throw new NoSuchElementException();
       
   250             }
       
   251             String cn = nextName;
       
   252             nextName = null;
       
   253             Class<?> c = null;
       
   254             try {
       
   255                 c = Class.forName(cn, false, loader);
       
   256             } catch (ClassNotFoundException x) {
       
   257                 fail(service,
       
   258                      "Provider " + cn + " not found");
       
   259             }
       
   260             if (!service.isAssignableFrom(c)) {
       
   261                 fail(service,
       
   262                      "Provider " + cn  + " not a subtype");
       
   263             }
       
   264             try {
       
   265                 S p = service.cast(c.newInstance());
       
   266                 providers.put(cn, p);
       
   267                 return p;
       
   268             } catch (Throwable x) {
       
   269                 fail(service,
       
   270                      "Provider " + cn + " could not be instantiated: " + x,
       
   271                      x);
       
   272             }
       
   273             throw new Error();          // This cannot happen
       
   274         }
       
   275 
       
   276         public void remove() {
       
   277             throw new UnsupportedOperationException();
       
   278         }
       
   279 
       
   280     }
       
   281 
       
   282     /**
       
   283      * Lazily loads the available providers of this loader's service.
       
   284      *
       
   285      * <p> The iterator returned by this method first yields all of the
       
   286      * elements of the provider cache, in instantiation order.  It then lazily
       
   287      * loads and instantiates any remaining providers, adding each one to the
       
   288      * cache in turn.
       
   289      *
       
   290      * <p> To achieve laziness the actual work of parsing the available
       
   291      * provider-configuration files and instantiating providers must be done by
       
   292      * the iterator itself.  Its {@link java.util.Iterator#hasNext hasNext} and
       
   293      * {@link java.util.Iterator#next next} methods can therefore throw a
       
   294      * {@link ServiceConfigurationError} if a provider-configuration file
       
   295      * violates the specified format, or if it names a provider class that
       
   296      * cannot be found and instantiated, or if the result of instantiating the
       
   297      * class is not assignable to the service type, or if any other kind of
       
   298      * exception or error is thrown as the next provider is located and
       
   299      * instantiated.  To write robust code it is only necessary to catch {@link
       
   300      * ServiceConfigurationError} when using a service iterator.
       
   301      *
       
   302      * <p> If such an error is thrown then subsequent invocations of the
       
   303      * iterator will make a best effort to locate and instantiate the next
       
   304      * available provider, but in general such recovery cannot be guaranteed.
       
   305      *
       
   306      * <blockquote style="font-size: smaller; line-height: 1.2"><span
       
   307      * style="padding-right: 1em; font-weight: bold">Design Note</span>
       
   308      * Throwing an error in these cases may seem extreme.  The rationale for
       
   309      * this behavior is that a malformed provider-configuration file, like a
       
   310      * malformed class file, indicates a serious problem with the way the Java
       
   311      * virtual machine is configured or is being used.  As such it is
       
   312      * preferable to throw an error rather than try to recover or, even worse,
       
   313      * fail silently.</blockquote>
       
   314      *
       
   315      * <p> The iterator returned by this method does not support removal.
       
   316      * Invoking its {@link java.util.Iterator#remove() remove} method will
       
   317      * cause an {@link UnsupportedOperationException} to be thrown.
       
   318      *
       
   319      * @return  An iterator that lazily loads providers for this loader's
       
   320      *          service
       
   321      */
       
   322     public Iterator<S> iterator() {
       
   323         return new Iterator<S>() {
       
   324 
       
   325             Iterator<Map.Entry<String,S>> knownProviders
       
   326                 = providers.entrySet().iterator();
       
   327 
       
   328             public boolean hasNext() {
       
   329                 if (knownProviders.hasNext())
       
   330                     return true;
       
   331                 return lookupIterator.hasNext();
       
   332             }
       
   333 
       
   334             public S next() {
       
   335                 if (knownProviders.hasNext())
       
   336                     return knownProviders.next().getValue();
       
   337                 return lookupIterator.next();
       
   338             }
       
   339 
       
   340             public void remove() {
       
   341                 throw new UnsupportedOperationException();
       
   342             }
       
   343 
       
   344         };
       
   345     }
       
   346 
       
   347     /**
       
   348      * Creates a new service loader for the given service type and class
       
   349      * loader.
       
   350      *
       
   351      * @param  service
       
   352      *         The interface or abstract class representing the service
       
   353      *
       
   354      * @param  loader
       
   355      *         The class loader to be used to load provider-configuration files
       
   356      *         and provider classes, or <tt>null</tt> if the system class
       
   357      *         loader (or, failing that, the bootstrap class loader) is to be
       
   358      *         used
       
   359      *
       
   360      * @return A new service loader
       
   361      */
       
   362     public static <S> ServiceLoader<S> load(Class<S> service,
       
   363                                             ClassLoader loader)
       
   364     {
       
   365         return new ServiceLoader<>(service, loader);
       
   366     }
       
   367 
       
   368     /**
       
   369      * Creates a new service loader for the given service type, using the
       
   370      * current thread's {@linkplain java.lang.Thread#getContextClassLoader
       
   371      * context class loader}.
       
   372      *
       
   373      * <p> An invocation of this convenience method of the form
       
   374      *
       
   375      * <blockquote><pre>
       
   376      * ServiceLoader.load(<i>service</i>)</pre></blockquote>
       
   377      *
       
   378      * is equivalent to
       
   379      *
       
   380      * <blockquote><pre>
       
   381      * ServiceLoader.load(<i>service</i>,
       
   382      *                    Thread.currentThread().getContextClassLoader())</pre></blockquote>
       
   383      *
       
   384      * @param  service
       
   385      *         The interface or abstract class representing the service
       
   386      *
       
   387      * @return A new service loader
       
   388      */
       
   389     public static <S> ServiceLoader<S> load(Class<S> service) {
       
   390         ClassLoader cl = Thread.currentThread().getContextClassLoader();
       
   391         return ServiceLoader.load(service, cl);
       
   392     }
       
   393 
       
   394     /**
       
   395      * Creates a new service loader for the given service type, using the
       
   396      * extension class loader.
       
   397      *
       
   398      * <p> This convenience method simply locates the extension class loader,
       
   399      * call it <tt><i>extClassLoader</i></tt>, and then returns
       
   400      *
       
   401      * <blockquote><pre>
       
   402      * ServiceLoader.load(<i>service</i>, <i>extClassLoader</i>)</pre></blockquote>
       
   403      *
       
   404      * <p> If the extension class loader cannot be found then the system class
       
   405      * loader is used; if there is no system class loader then the bootstrap
       
   406      * class loader is used.
       
   407      *
       
   408      * <p> This method is intended for use when only installed providers are
       
   409      * desired.  The resulting service will only find and load providers that
       
   410      * have been installed into the current Java virtual machine; providers on
       
   411      * the application's class path will be ignored.
       
   412      *
       
   413      * @param  service
       
   414      *         The interface or abstract class representing the service
       
   415      *
       
   416      * @return A new service loader
       
   417      */
       
   418     public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {
       
   419         ClassLoader cl = ClassLoader.getSystemClassLoader();
       
   420         ClassLoader prev = null;
       
   421         while (cl != null) {
       
   422             prev = cl;
       
   423             cl = cl.getParent();
       
   424         }
       
   425         return ServiceLoader.load(service, prev);
       
   426     }
       
   427 
       
   428     /**
       
   429      * Returns a string describing this service.
       
   430      *
       
   431      * @return  A descriptive string
       
   432      */
       
   433     public String toString() {
       
   434         return "java.util.ServiceLoader[" + service.getName() + "]";
       
   435     }
       
   436 
       
   437 }