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