src/share/classes/com/sun/tools/javac/util/ServiceLoader.java

changeset 0
959103a6100f
equal deleted inserted replaced
-1:000000000000 0:959103a6100f
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 }

mercurial