|
|
|
@ -0,0 +1,235 @@ |
|
|
|
|
/* |
|
|
|
|
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria)
|
|
|
|
|
* |
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
|
|
|
* you may not use this file except in compliance with the License. |
|
|
|
|
* You may obtain a copy of the License at |
|
|
|
|
* |
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
* |
|
|
|
|
* Unless required by applicable law or agreed to in writing, software |
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS, |
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
|
|
|
* See the License for the specific language governing permissions and |
|
|
|
|
* limitations under the License. |
|
|
|
|
*/ |
|
|
|
|
package com.arialyy.aria.util; |
|
|
|
|
|
|
|
|
|
import java.io.BufferedReader; |
|
|
|
|
import java.io.IOException; |
|
|
|
|
import java.io.InputStream; |
|
|
|
|
import java.io.InputStreamReader; |
|
|
|
|
import java.net.URL; |
|
|
|
|
import java.util.ArrayList; |
|
|
|
|
import java.util.Enumeration; |
|
|
|
|
import java.util.Iterator; |
|
|
|
|
import java.util.List; |
|
|
|
|
import java.util.ServiceConfigurationError; |
|
|
|
|
import java.util.ServiceLoader; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* aria 服务加载器 |
|
|
|
|
* 参考{@link ServiceLoader} |
|
|
|
|
*/ |
|
|
|
|
public class AriaServiceLoader<S> { |
|
|
|
|
|
|
|
|
|
private static final String PREFIX = "META-INF/services/"; |
|
|
|
|
|
|
|
|
|
// The class or interface representing the service being loaded
|
|
|
|
|
private final Class<S> service; |
|
|
|
|
|
|
|
|
|
// The class loader used to locate, load, and instantiate providers
|
|
|
|
|
private final ClassLoader loader; |
|
|
|
|
|
|
|
|
|
// The access control context taken when the ServiceLoader is created
|
|
|
|
|
// Android-changed: do not use legacy security code.
|
|
|
|
|
// private final AccessControlContext acc;
|
|
|
|
|
|
|
|
|
|
private LazyLoader lazyLoader; |
|
|
|
|
|
|
|
|
|
public void reload() { |
|
|
|
|
lazyLoader = new LazyLoader(service, loader); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private AriaServiceLoader(Class<S> svc, ClassLoader cl) { |
|
|
|
|
service = svc; |
|
|
|
|
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; |
|
|
|
|
// Android-changed: Do not use legacy security code.
|
|
|
|
|
// On Android, System.getSecurityManager() is always null.
|
|
|
|
|
// acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
|
|
|
|
|
reload(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public static <S> AriaServiceLoader<S> load(Class<S> service, ClassLoader loader) { |
|
|
|
|
return new AriaServiceLoader<>(service, loader); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public static <S> AriaServiceLoader<S> load(Class<S> service) { |
|
|
|
|
Thread.currentThread().setContextClassLoader(AriaServiceLoader.class.getClassLoader()); |
|
|
|
|
ClassLoader cl = Thread.currentThread().getContextClassLoader(); |
|
|
|
|
return AriaServiceLoader.load(service, cl); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public S getService(String serviceName) { |
|
|
|
|
return lazyLoader.loadService(serviceName); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private class LazyLoader { |
|
|
|
|
private Class<S> service; |
|
|
|
|
private ClassLoader loader; |
|
|
|
|
private Enumeration<URL> configs = null; |
|
|
|
|
private Iterator<String> pending = null; |
|
|
|
|
|
|
|
|
|
private LazyLoader(Class<S> service, ClassLoader loader) { |
|
|
|
|
this.service = service; |
|
|
|
|
this.loader = loader; |
|
|
|
|
parseConfig(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Parse the content of the given URL as a provider-configuration file.
|
|
|
|
|
//
|
|
|
|
|
// @param service
|
|
|
|
|
// The service type for which providers are being sought;
|
|
|
|
|
// used to construct error detail strings
|
|
|
|
|
//
|
|
|
|
|
// @param u
|
|
|
|
|
// The URL naming the configuration file to be parsed
|
|
|
|
|
//
|
|
|
|
|
// @return A (possibly empty) iterator that will yield the provider-class
|
|
|
|
|
// names in the given configuration file that are not yet members
|
|
|
|
|
// of the returned set
|
|
|
|
|
//
|
|
|
|
|
// @throws ServiceConfigurationError
|
|
|
|
|
// If an I/O error occurs while reading from the given URL, or
|
|
|
|
|
// if a configuration-file format error is detected
|
|
|
|
|
//
|
|
|
|
|
private Iterator<String> parse(Class<?> service, URL u) throws ServiceConfigurationError { |
|
|
|
|
InputStream in = null; |
|
|
|
|
BufferedReader r = null; |
|
|
|
|
ArrayList<String> names = new ArrayList<>(); |
|
|
|
|
try { |
|
|
|
|
in = u.openStream(); |
|
|
|
|
r = new BufferedReader(new InputStreamReader(in, "utf-8")); |
|
|
|
|
int lc = 1; |
|
|
|
|
while ((lc = parseLine(service, u, r, lc, names)) >= 0) ; |
|
|
|
|
} catch (IOException x) { |
|
|
|
|
fail(service, "Error reading configuration file", x); |
|
|
|
|
} finally { |
|
|
|
|
try { |
|
|
|
|
if (r != null) r.close(); |
|
|
|
|
if (in != null) in.close(); |
|
|
|
|
} catch (IOException y) { |
|
|
|
|
fail(service, "Error closing configuration file", y); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return names.iterator(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void fail(Class<?> service, String msg, Throwable cause) |
|
|
|
|
throws ServiceConfigurationError { |
|
|
|
|
throw new ServiceConfigurationError(service.getName() + ": " + msg, |
|
|
|
|
cause); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void fail(Class<?> service, String msg) |
|
|
|
|
throws ServiceConfigurationError { |
|
|
|
|
throw new ServiceConfigurationError(service.getName() + ": " + msg); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void fail(Class<?> service, URL u, int line, String msg) |
|
|
|
|
throws ServiceConfigurationError { |
|
|
|
|
fail(service, u + ":" + line + ": " + msg); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Parse a single line from the given configuration file, adding the name
|
|
|
|
|
// on the line to the names list.
|
|
|
|
|
//
|
|
|
|
|
private int parseLine(Class<?> service, URL u, BufferedReader r, int lc, |
|
|
|
|
List<String> names) |
|
|
|
|
throws IOException, ServiceConfigurationError { |
|
|
|
|
String ln = r.readLine(); |
|
|
|
|
if (ln == null) { |
|
|
|
|
return -1; |
|
|
|
|
} |
|
|
|
|
int ci = ln.indexOf('#'); |
|
|
|
|
if (ci >= 0) ln = ln.substring(0, ci); |
|
|
|
|
ln = ln.trim(); |
|
|
|
|
int n = ln.length(); |
|
|
|
|
if (n != 0) { |
|
|
|
|
if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0)) { |
|
|
|
|
fail(service, u, lc, "Illegal configuration-file syntax"); |
|
|
|
|
} |
|
|
|
|
int cp = ln.codePointAt(0); |
|
|
|
|
if (!Character.isJavaIdentifierStart(cp)) { |
|
|
|
|
fail(service, u, lc, "Illegal provider-class name: " + ln); |
|
|
|
|
} |
|
|
|
|
for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) { |
|
|
|
|
cp = ln.codePointAt(i); |
|
|
|
|
if (!Character.isJavaIdentifierPart(cp) && (cp != '.')) { |
|
|
|
|
fail(service, u, lc, "Illegal provider-class name: " + ln); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (!names.contains(ln)) { |
|
|
|
|
names.add(ln); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return lc + 1; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private S loadService(String serviceName) { |
|
|
|
|
while (pending.hasNext()) { |
|
|
|
|
if (pending.next().equals(serviceName)) { |
|
|
|
|
Class<?> c = null; |
|
|
|
|
try { |
|
|
|
|
c = Class.forName(serviceName, false, loader); |
|
|
|
|
} catch (ClassNotFoundException x) { |
|
|
|
|
fail(service, |
|
|
|
|
// Android-changed: Let the ServiceConfigurationError have a cause.
|
|
|
|
|
"Provider " + serviceName + " not found", x); |
|
|
|
|
// "Provider " + cn + " not found");
|
|
|
|
|
} |
|
|
|
|
if (!service.isAssignableFrom(c)) { |
|
|
|
|
// Android-changed: Let the ServiceConfigurationError have a cause.
|
|
|
|
|
ClassCastException cce = new ClassCastException( |
|
|
|
|
service.getCanonicalName() + " is not assignable from " + c.getCanonicalName()); |
|
|
|
|
fail(service, |
|
|
|
|
"Provider " + serviceName + " not a subtype", cce); |
|
|
|
|
// fail(service,
|
|
|
|
|
// "Provider " + cn + " not a subtype");
|
|
|
|
|
} |
|
|
|
|
try { |
|
|
|
|
return service.cast(c.newInstance()); |
|
|
|
|
} catch (Throwable x) { |
|
|
|
|
fail(service, "Provider " + serviceName + " could not be instantiated", x); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
throw new Error(); // This cannot happen
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 解析配置文件 |
|
|
|
|
*/ |
|
|
|
|
private void parseConfig() { |
|
|
|
|
if (configs == null) { |
|
|
|
|
try { |
|
|
|
|
String fullName = PREFIX + service.getName(); |
|
|
|
|
if (loader == null) { |
|
|
|
|
configs = ClassLoader.getSystemResources(fullName); |
|
|
|
|
} else { |
|
|
|
|
configs = loader.getResources(fullName); |
|
|
|
|
} |
|
|
|
|
} catch (IOException x) { |
|
|
|
|
fail(service, "Error locating configuration files", x); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
while ((pending == null) || !pending.hasNext()) { |
|
|
|
|
if (!configs.hasMoreElements()) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
pending = parse(service, configs.nextElement()); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |