aoqi@0: /* aoqi@0: * Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved. aoqi@0: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. aoqi@0: * aoqi@0: * This code is free software; you can redistribute it and/or modify it aoqi@0: * under the terms of the GNU General Public License version 2 only, as aoqi@0: * published by the Free Software Foundation. Oracle designates this aoqi@0: * particular file as subject to the "Classpath" exception as provided aoqi@0: * by Oracle in the LICENSE file that accompanied this code. aoqi@0: * aoqi@0: * This code is distributed in the hope that it will be useful, but WITHOUT aoqi@0: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or aoqi@0: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License aoqi@0: * version 2 for more details (a copy is included in the LICENSE file that aoqi@0: * accompanied this code). aoqi@0: * aoqi@0: * You should have received a copy of the GNU General Public License version aoqi@0: * 2 along with this work; if not, write to the Free Software Foundation, aoqi@0: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. aoqi@0: * aoqi@0: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA aoqi@0: * or visit www.oracle.com if you need additional information or have any aoqi@0: * questions. aoqi@0: */ aoqi@0: aoqi@0: package com.sun.tools.javac.util; aoqi@0: aoqi@0: import java.io.BufferedReader; aoqi@0: import java.io.IOException; aoqi@0: import java.io.InputStream; aoqi@0: import java.io.InputStreamReader; aoqi@0: import java.net.URL; aoqi@0: import java.net.URLConnection; aoqi@0: import java.util.ArrayList; aoqi@0: import java.util.Enumeration; aoqi@0: import java.util.Iterator; aoqi@0: import java.util.LinkedHashMap; aoqi@0: import java.util.List; aoqi@0: import java.util.Map; aoqi@0: import java.util.NoSuchElementException; aoqi@0: import java.util.Objects; aoqi@0: import java.util.ServiceConfigurationError; aoqi@0: aoqi@0: aoqi@0: /** aoqi@0: * This is a temporary, modified copy of java.util.ServiceLoader, for use by aoqi@0: * javac, to work around bug JDK-8004082. aoqi@0: * aoqi@0: * The bug describes problems in the interaction between ServiceLoader and aoqi@0: * URLClassLoader, such that references to a jar file passed to URLClassLoader aoqi@0: * may be retained after calling URLClassLoader.close(), preventing the jar aoqi@0: * file from being deleted on Windows. aoqi@0: * aoqi@0: *

This is NOT part of any supported API. aoqi@0: * If you write code that depends on this, you do so at your own risk. aoqi@0: * This code and its internal interfaces are subject to change or aoqi@0: * deletion without notice. aoqi@0: */ aoqi@0: aoqi@0: public final class ServiceLoader aoqi@0: implements Iterable aoqi@0: { aoqi@0: aoqi@0: private static final String PREFIX = "META-INF/services/"; aoqi@0: aoqi@0: // The class or interface representing the service being loaded aoqi@0: private Class service; aoqi@0: aoqi@0: // The class loader used to locate, load, and instantiate providers aoqi@0: private ClassLoader loader; aoqi@0: aoqi@0: // Cached providers, in instantiation order aoqi@0: private LinkedHashMap providers = new LinkedHashMap<>(); aoqi@0: aoqi@0: // The current lazy-lookup iterator aoqi@0: private LazyIterator lookupIterator; aoqi@0: aoqi@0: /** aoqi@0: * Clear this loader's provider cache so that all providers will be aoqi@0: * reloaded. aoqi@0: * aoqi@0: *

After invoking this method, subsequent invocations of the {@link aoqi@0: * #iterator() iterator} method will lazily look up and instantiate aoqi@0: * providers from scratch, just as is done by a newly-created loader. aoqi@0: * aoqi@0: *

This method is intended for use in situations in which new providers aoqi@0: * can be installed into a running Java virtual machine. aoqi@0: */ aoqi@0: public void reload() { aoqi@0: providers.clear(); aoqi@0: lookupIterator = new LazyIterator(service, loader); aoqi@0: } aoqi@0: aoqi@0: private ServiceLoader(Class svc, ClassLoader cl) { aoqi@0: service = Objects.requireNonNull(svc, "Service interface cannot be null"); aoqi@0: loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; aoqi@0: reload(); aoqi@0: } aoqi@0: aoqi@0: private static void fail(Class service, String msg, Throwable cause) aoqi@0: throws ServiceConfigurationError aoqi@0: { aoqi@0: throw new ServiceConfigurationError(service.getName() + ": " + msg, aoqi@0: cause); aoqi@0: } aoqi@0: aoqi@0: private static void fail(Class service, String msg) aoqi@0: throws ServiceConfigurationError aoqi@0: { aoqi@0: throw new ServiceConfigurationError(service.getName() + ": " + msg); aoqi@0: } aoqi@0: aoqi@0: private static void fail(Class service, URL u, int line, String msg) aoqi@0: throws ServiceConfigurationError aoqi@0: { aoqi@0: fail(service, u + ":" + line + ": " + msg); aoqi@0: } aoqi@0: aoqi@0: // Parse a single line from the given configuration file, adding the name aoqi@0: // on the line to the names list. aoqi@0: // aoqi@0: private int parseLine(Class service, URL u, BufferedReader r, int lc, aoqi@0: List names) aoqi@0: throws IOException, ServiceConfigurationError aoqi@0: { aoqi@0: String ln = r.readLine(); aoqi@0: if (ln == null) { aoqi@0: return -1; aoqi@0: } aoqi@0: int ci = ln.indexOf('#'); aoqi@0: if (ci >= 0) ln = ln.substring(0, ci); aoqi@0: ln = ln.trim(); aoqi@0: int n = ln.length(); aoqi@0: if (n != 0) { aoqi@0: if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0)) aoqi@0: fail(service, u, lc, "Illegal configuration-file syntax"); aoqi@0: int cp = ln.codePointAt(0); aoqi@0: if (!Character.isJavaIdentifierStart(cp)) aoqi@0: fail(service, u, lc, "Illegal provider-class name: " + ln); aoqi@0: for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) { aoqi@0: cp = ln.codePointAt(i); aoqi@0: if (!Character.isJavaIdentifierPart(cp) && (cp != '.')) aoqi@0: fail(service, u, lc, "Illegal provider-class name: " + ln); aoqi@0: } aoqi@0: if (!providers.containsKey(ln) && !names.contains(ln)) aoqi@0: names.add(ln); aoqi@0: } aoqi@0: return lc + 1; aoqi@0: } aoqi@0: aoqi@0: // Parse the content of the given URL as a provider-configuration file. aoqi@0: // aoqi@0: // @param service aoqi@0: // The service type for which providers are being sought; aoqi@0: // used to construct error detail strings aoqi@0: // aoqi@0: // @param u aoqi@0: // The URL naming the configuration file to be parsed aoqi@0: // aoqi@0: // @return A (possibly empty) iterator that will yield the provider-class aoqi@0: // names in the given configuration file that are not yet members aoqi@0: // of the returned set aoqi@0: // aoqi@0: // @throws ServiceConfigurationError aoqi@0: // If an I/O error occurs while reading from the given URL, or aoqi@0: // if a configuration-file format error is detected aoqi@0: // aoqi@0: private Iterator parse(Class service, URL u) aoqi@0: throws ServiceConfigurationError aoqi@0: { aoqi@0: InputStream in = null; aoqi@0: BufferedReader r = null; aoqi@0: ArrayList names = new ArrayList<>(); aoqi@0: try { aoqi@0: // The problem is that by default, streams opened with aoqi@0: // u.openInputStream use a cached reference to a JarFile, which aoqi@0: // is separate from the reference used by URLClassLoader, and aoqi@0: // which is not closed by URLClassLoader.close(). aoqi@0: // The workaround is to disable caching for this specific jar file, aoqi@0: // so that the reference to the jar file can be closed when the aoqi@0: // file has been read. aoqi@0: // Original code: aoqi@0: // in = u.openStream(); aoqi@0: // Workaround ... aoqi@0: URLConnection uc = u.openConnection(); aoqi@0: uc.setUseCaches(false); aoqi@0: in = uc.getInputStream(); aoqi@0: // ... end of workaround. aoqi@0: r = new BufferedReader(new InputStreamReader(in, "utf-8")); aoqi@0: int lc = 1; aoqi@0: while ((lc = parseLine(service, u, r, lc, names)) >= 0); aoqi@0: } catch (IOException x) { aoqi@0: fail(service, "Error reading configuration file", x); aoqi@0: } finally { aoqi@0: try { aoqi@0: if (r != null) r.close(); aoqi@0: if (in != null) in.close(); aoqi@0: } catch (IOException y) { aoqi@0: fail(service, "Error closing configuration file", y); aoqi@0: } aoqi@0: } aoqi@0: return names.iterator(); aoqi@0: } aoqi@0: aoqi@0: // Private inner class implementing fully-lazy provider lookup aoqi@0: // aoqi@0: private class LazyIterator aoqi@0: implements Iterator aoqi@0: { aoqi@0: aoqi@0: Class service; aoqi@0: ClassLoader loader; aoqi@0: Enumeration configs = null; aoqi@0: Iterator pending = null; aoqi@0: String nextName = null; aoqi@0: aoqi@0: private LazyIterator(Class service, ClassLoader loader) { aoqi@0: this.service = service; aoqi@0: this.loader = loader; aoqi@0: } aoqi@0: aoqi@0: public boolean hasNext() { aoqi@0: if (nextName != null) { aoqi@0: return true; aoqi@0: } aoqi@0: if (configs == null) { aoqi@0: try { aoqi@0: String fullName = PREFIX + service.getName(); aoqi@0: if (loader == null) aoqi@0: configs = ClassLoader.getSystemResources(fullName); aoqi@0: else aoqi@0: configs = loader.getResources(fullName); aoqi@0: } catch (IOException x) { aoqi@0: fail(service, "Error locating configuration files", x); aoqi@0: } aoqi@0: } aoqi@0: while ((pending == null) || !pending.hasNext()) { aoqi@0: if (!configs.hasMoreElements()) { aoqi@0: return false; aoqi@0: } aoqi@0: pending = parse(service, configs.nextElement()); aoqi@0: } aoqi@0: nextName = pending.next(); aoqi@0: return true; aoqi@0: } aoqi@0: aoqi@0: public S next() { aoqi@0: if (!hasNext()) { aoqi@0: throw new NoSuchElementException(); aoqi@0: } aoqi@0: String cn = nextName; aoqi@0: nextName = null; aoqi@0: Class c = null; aoqi@0: try { aoqi@0: c = Class.forName(cn, false, loader); aoqi@0: } catch (ClassNotFoundException x) { aoqi@0: fail(service, aoqi@0: "Provider " + cn + " not found"); aoqi@0: } aoqi@0: if (!service.isAssignableFrom(c)) { aoqi@0: fail(service, aoqi@0: "Provider " + cn + " not a subtype"); aoqi@0: } aoqi@0: try { aoqi@0: S p = service.cast(c.newInstance()); aoqi@0: providers.put(cn, p); aoqi@0: return p; aoqi@0: } catch (Throwable x) { aoqi@0: fail(service, aoqi@0: "Provider " + cn + " could not be instantiated: " + x, aoqi@0: x); aoqi@0: } aoqi@0: throw new Error(); // This cannot happen aoqi@0: } aoqi@0: aoqi@0: public void remove() { aoqi@0: throw new UnsupportedOperationException(); aoqi@0: } aoqi@0: aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Lazily loads the available providers of this loader's service. aoqi@0: * aoqi@0: *

The iterator returned by this method first yields all of the aoqi@0: * elements of the provider cache, in instantiation order. It then lazily aoqi@0: * loads and instantiates any remaining providers, adding each one to the aoqi@0: * cache in turn. aoqi@0: * aoqi@0: *

To achieve laziness the actual work of parsing the available aoqi@0: * provider-configuration files and instantiating providers must be done by aoqi@0: * the iterator itself. Its {@link java.util.Iterator#hasNext hasNext} and aoqi@0: * {@link java.util.Iterator#next next} methods can therefore throw a aoqi@0: * {@link ServiceConfigurationError} if a provider-configuration file aoqi@0: * violates the specified format, or if it names a provider class that aoqi@0: * cannot be found and instantiated, or if the result of instantiating the aoqi@0: * class is not assignable to the service type, or if any other kind of aoqi@0: * exception or error is thrown as the next provider is located and aoqi@0: * instantiated. To write robust code it is only necessary to catch {@link aoqi@0: * ServiceConfigurationError} when using a service iterator. aoqi@0: * aoqi@0: *

If such an error is thrown then subsequent invocations of the aoqi@0: * iterator will make a best effort to locate and instantiate the next aoqi@0: * available provider, but in general such recovery cannot be guaranteed. aoqi@0: * aoqi@0: *

Design Note aoqi@0: * Throwing an error in these cases may seem extreme. The rationale for aoqi@0: * this behavior is that a malformed provider-configuration file, like a aoqi@0: * malformed class file, indicates a serious problem with the way the Java aoqi@0: * virtual machine is configured or is being used. As such it is aoqi@0: * preferable to throw an error rather than try to recover or, even worse, aoqi@0: * fail silently.
aoqi@0: * aoqi@0: *

The iterator returned by this method does not support removal. aoqi@0: * Invoking its {@link java.util.Iterator#remove() remove} method will aoqi@0: * cause an {@link UnsupportedOperationException} to be thrown. aoqi@0: * aoqi@0: * @return An iterator that lazily loads providers for this loader's aoqi@0: * service aoqi@0: */ aoqi@0: public Iterator iterator() { aoqi@0: return new Iterator() { aoqi@0: aoqi@0: Iterator> knownProviders aoqi@0: = providers.entrySet().iterator(); aoqi@0: aoqi@0: public boolean hasNext() { aoqi@0: if (knownProviders.hasNext()) aoqi@0: return true; aoqi@0: return lookupIterator.hasNext(); aoqi@0: } aoqi@0: aoqi@0: public S next() { aoqi@0: if (knownProviders.hasNext()) aoqi@0: return knownProviders.next().getValue(); aoqi@0: return lookupIterator.next(); aoqi@0: } aoqi@0: aoqi@0: public void remove() { aoqi@0: throw new UnsupportedOperationException(); aoqi@0: } aoqi@0: aoqi@0: }; aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Creates a new service loader for the given service type and class aoqi@0: * loader. aoqi@0: * aoqi@0: * @param service aoqi@0: * The interface or abstract class representing the service aoqi@0: * aoqi@0: * @param loader aoqi@0: * The class loader to be used to load provider-configuration files aoqi@0: * and provider classes, or null if the system class aoqi@0: * loader (or, failing that, the bootstrap class loader) is to be aoqi@0: * used aoqi@0: * aoqi@0: * @return A new service loader aoqi@0: */ aoqi@0: public static ServiceLoader load(Class service, aoqi@0: ClassLoader loader) aoqi@0: { aoqi@0: return new ServiceLoader<>(service, loader); aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Creates a new service loader for the given service type, using the aoqi@0: * current thread's {@linkplain java.lang.Thread#getContextClassLoader aoqi@0: * context class loader}. aoqi@0: * aoqi@0: *

An invocation of this convenience method of the form aoqi@0: * aoqi@0: *

aoqi@0:      * ServiceLoader.load(service)
aoqi@0: * aoqi@0: * is equivalent to aoqi@0: * aoqi@0: *
aoqi@0:      * ServiceLoader.load(service,
aoqi@0:      *                    Thread.currentThread().getContextClassLoader())
aoqi@0: * aoqi@0: * @param service aoqi@0: * The interface or abstract class representing the service aoqi@0: * aoqi@0: * @return A new service loader aoqi@0: */ aoqi@0: public static ServiceLoader load(Class service) { aoqi@0: ClassLoader cl = Thread.currentThread().getContextClassLoader(); aoqi@0: return ServiceLoader.load(service, cl); aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Creates a new service loader for the given service type, using the aoqi@0: * extension class loader. aoqi@0: * aoqi@0: *

This convenience method simply locates the extension class loader, aoqi@0: * call it extClassLoader, and then returns aoqi@0: * aoqi@0: *

aoqi@0:      * ServiceLoader.load(service, extClassLoader)
aoqi@0: * aoqi@0: *

If the extension class loader cannot be found then the system class aoqi@0: * loader is used; if there is no system class loader then the bootstrap aoqi@0: * class loader is used. aoqi@0: * aoqi@0: *

This method is intended for use when only installed providers are aoqi@0: * desired. The resulting service will only find and load providers that aoqi@0: * have been installed into the current Java virtual machine; providers on aoqi@0: * the application's class path will be ignored. aoqi@0: * aoqi@0: * @param service aoqi@0: * The interface or abstract class representing the service aoqi@0: * aoqi@0: * @return A new service loader aoqi@0: */ aoqi@0: public static ServiceLoader loadInstalled(Class service) { aoqi@0: ClassLoader cl = ClassLoader.getSystemClassLoader(); aoqi@0: ClassLoader prev = null; aoqi@0: while (cl != null) { aoqi@0: prev = cl; aoqi@0: cl = cl.getParent(); aoqi@0: } aoqi@0: return ServiceLoader.load(service, prev); aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Returns a string describing this service. aoqi@0: * aoqi@0: * @return A descriptive string aoqi@0: */ aoqi@0: public String toString() { aoqi@0: return "java.util.ServiceLoader[" + service.getName() + "]"; aoqi@0: } aoqi@0: aoqi@0: }