• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Licensed to the Apache Software Foundation (ASF) under one or more
3  *  contributor license agreements.  See the NOTICE file distributed with
4  *  this work for additional information regarding copyright ownership.
5  *  The ASF licenses this file to You under the Apache License, Version 2.0
6  *  (the "License"); you may not use this file except in compliance with
7  *  the License.  You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  */
17 package java.util;
18 
19 import java.io.BufferedReader;
20 import java.io.IOException;
21 import java.io.InputStreamReader;
22 import java.net.URL;
23 import libcore.io.IoUtils;
24 
25 /**
26  * A service-provider loader.
27  *
28  * <p>A service provider is a factory for creating all known implementations of a particular
29  * class or interface {@code S}. The known implementations are read from a configuration file in
30  * {@code META-INF/services/}. The file's name should match the class' binary name (such as
31  * {@code java.util.Outer$Inner}).
32  *
33  * <p>The file format is as follows.
34  * The file's character encoding must be UTF-8.
35  * Whitespace is ignored, and {@code #} is used to begin a comment that continues to the
36  * next newline.
37  * Lines that are empty after comment removal and whitespace trimming are ignored.
38  * Otherwise, each line contains the binary name of one implementation class.
39  * Duplicate entries are ignored, but entries are otherwise returned in order (that is, the file
40  * is treated as an ordered set).
41  *
42  * <p>Given these classes:
43  * <pre>
44  * package a.b.c;
45  * public interface MyService { ... }
46  * public class MyImpl1 implements MyService { ... }
47  * public class MyImpl2 implements MyService { ... }
48  * </pre>
49  * And this configuration file (stored as {@code META-INF/services/a.b.c.MyService}):
50  * <pre>
51  * # Known MyService providers.
52  * a.b.c.MyImpl1  # The original implementation for handling "bar"s.
53  * a.b.c.MyImpl2  # A later implementation for "foo"s.
54  * </pre>
55  * You might use {@code ServiceProvider} something like this:
56  * <pre>
57  *   for (MyService service : ServiceLoader<MyService>.load(MyService.class)) {
58  *     if (service.supports(o)) {
59  *       return service.handle(o);
60  *     }
61  *   }
62  * </pre>
63  *
64  * <p>Note that each iteration creates new instances of the various service implementations, so
65  * any heavily-used code will likely want to cache the known implementations itself and reuse them.
66  * Note also that the candidate classes are instantiated lazily as you call {@code next} on the
67  * iterator: construction of the iterator itself does not instantiate any of the providers.
68  *
69  * @param <S> the service class or interface
70  * @since 1.6
71  */
72 public final class ServiceLoader<S> implements Iterable<S> {
73     private final Class<S> service;
74     private final ClassLoader classLoader;
75     private final Set<URL> services;
76 
ServiceLoader(Class<S> service, ClassLoader classLoader)77     private ServiceLoader(Class<S> service, ClassLoader classLoader) {
78         // It makes no sense for service to be null.
79         // classLoader is null if you want the system class loader.
80         if (service == null) {
81             throw new NullPointerException("service == null");
82         }
83         this.service = service;
84         this.classLoader = classLoader;
85         this.services = new HashSet<URL>();
86         reload();
87     }
88 
89     /**
90      * Invalidates the cache of known service provider class names.
91      */
reload()92     public void reload() {
93         internalLoad();
94     }
95 
96     /**
97      * Returns an iterator over all the service providers offered by this service loader.
98      * Note that {@code hasNext} and {@code next} may throw if the configuration is invalid.
99      *
100      * <p>Each iterator will return new instances of the classes it iterates over, so callers
101      * may want to cache the results of a single call to this method rather than call it
102      * repeatedly.
103      *
104      * <p>The returned iterator does not support {@code remove}.
105      */
iterator()106     public Iterator<S> iterator() {
107         return new ServiceIterator(this);
108     }
109 
110     /**
111      * Constructs a service loader. If {@code classLoader} is null, the system class loader
112      * is used.
113      *
114      * @param service the service class or interface
115      * @param classLoader the class loader
116      * @return a new ServiceLoader
117      */
load(Class<S> service, ClassLoader classLoader)118     public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader classLoader) {
119         if (classLoader == null) {
120             classLoader = ClassLoader.getSystemClassLoader();
121         }
122         return new ServiceLoader<S>(service, classLoader);
123     }
124 
internalLoad()125     private void internalLoad() {
126         services.clear();
127         try {
128             String name = "META-INF/services/" + service.getName();
129             services.addAll(Collections.list(classLoader.getResources(name)));
130         } catch (IOException e) {
131             return;
132         }
133     }
134 
135     /**
136      * Constructs a service loader, using the current thread's context class loader.
137      *
138      * @param service the service class or interface
139      * @return a new ServiceLoader
140      */
load(Class<S> service)141     public static <S> ServiceLoader<S> load(Class<S> service) {
142         return ServiceLoader.load(service, Thread.currentThread().getContextClassLoader());
143     }
144 
145     /**
146      * Constructs a service loader, using the extension class loader.
147      *
148      * @param service the service class or interface
149      * @return a new ServiceLoader
150      */
loadInstalled(Class<S> service)151     public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {
152         ClassLoader cl = ClassLoader.getSystemClassLoader();
153         if (cl != null) {
154             while (cl.getParent() != null) {
155                 cl = cl.getParent();
156             }
157         }
158         return ServiceLoader.load(service, cl);
159     }
160 
161     /**
162      * Internal API to support built-in SPIs that check a system property first.
163      * Returns an instance specified by a property with the class' binary name, or null if
164      * no such property is set.
165      * @hide
166      */
loadFromSystemProperty(final Class<S> service)167     public static <S> S loadFromSystemProperty(final Class<S> service) {
168         try {
169             final String className = System.getProperty(service.getName());
170             if (className != null) {
171                 Class<?> c = ClassLoader.getSystemClassLoader().loadClass(className);
172                 return (S) c.newInstance();
173             }
174             return null;
175         } catch (Exception e) {
176             throw new Error(e);
177         }
178     }
179 
180     @Override
toString()181     public String toString() {
182         return "ServiceLoader for " + service.getName();
183     }
184 
185     private class ServiceIterator implements Iterator<S> {
186         private final ClassLoader classLoader;
187         private final Class<S> service;
188         private final Set<URL> services;
189 
190         private boolean isRead = false;
191 
192         private LinkedList<String> queue = new LinkedList<String>();
193 
ServiceIterator(ServiceLoader<S> sl)194         public ServiceIterator(ServiceLoader<S> sl) {
195             this.classLoader = sl.classLoader;
196             this.service = sl.service;
197             this.services = sl.services;
198         }
199 
hasNext()200         public boolean hasNext() {
201             if (!isRead) {
202                 readClass();
203             }
204             return (queue != null && !queue.isEmpty());
205         }
206 
207         @SuppressWarnings("unchecked")
next()208         public S next() {
209             if (!hasNext()) {
210                 throw new NoSuchElementException();
211             }
212             String className = queue.remove();
213             try {
214                 return service.cast(classLoader.loadClass(className).newInstance());
215             } catch (Exception e) {
216                 throw new ServiceConfigurationError("Couldn't instantiate class " + className, e);
217             }
218         }
219 
readClass()220         private void readClass() {
221             for (URL url : services) {
222                 BufferedReader reader = null;
223                 try {
224                     reader = new BufferedReader(new InputStreamReader(url.openStream(), "UTF-8"));
225                     String line;
226                     while ((line = reader.readLine()) != null) {
227                         // Strip comments and whitespace...
228                         int commentStart = line.indexOf('#');
229                         if (commentStart != -1) {
230                             line = line.substring(0, commentStart);
231                         }
232                         line = line.trim();
233                         // Ignore empty lines.
234                         if (line.isEmpty()) {
235                             continue;
236                         }
237                         String className = line;
238                         checkValidJavaClassName(className);
239                         if (!queue.contains(className)) {
240                             queue.add(className);
241                         }
242                     }
243                     isRead = true;
244                 } catch (Exception e) {
245                     throw new ServiceConfigurationError("Couldn't read " + url, e);
246                 } finally {
247                     IoUtils.closeQuietly(reader);
248                 }
249             }
250         }
251 
remove()252         public void remove() {
253             throw new UnsupportedOperationException();
254         }
255 
checkValidJavaClassName(String className)256         private void checkValidJavaClassName(String className) {
257             for (int i = 0; i < className.length(); ++i) {
258                 char ch = className.charAt(i);
259                 if (!Character.isJavaIdentifierPart(ch) && ch != '.') {
260                     throw new ServiceConfigurationError("Bad character '" + ch + "' in class name");
261                 }
262             }
263         }
264     }
265 }
266