OSDN Git Service

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