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 */
17package java.util;
18
19import java.io.BufferedReader;
20import java.io.IOException;
21import java.io.InputStreamReader;
22import java.net.URL;
23import 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 */
72public 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
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     */
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     */
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     */
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
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     */
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     */
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     */
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
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
194        public ServiceIterator(ServiceLoader<S> sl) {
195            this.classLoader = sl.classLoader;
196            this.service = sl.service;
197            this.services = sl.services;
198        }
199
200        public boolean hasNext() {
201            if (!isRead) {
202                readClass();
203            }
204            return (queue != null && !queue.isEmpty());
205        }
206
207        @SuppressWarnings("unchecked")
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
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
252        public void remove() {
253            throw new UnsupportedOperationException();
254        }
255
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