All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.polaris.core.service.ServiceLoader Maven / Gradle / Ivy

There is a newer version: 3.2.1
Show newest version
package io.polaris.core.service;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import io.polaris.core.log.ILogger;
import io.polaris.core.log.ILoggers;

/**
 * @author Qt
 * @since 1.8
 */
public class ServiceLoader implements Iterable> {
	private static final ILogger log = ILoggers.of(ServiceLoader.class);
	public static final String[] PREFIX = {"META-INF/services/"};
	private final Class type;
	private final ClassLoader loader;
	private final AtomicBoolean loaded = new AtomicBoolean(false);
	private List> providers;
	private List> wrappers;
	private Map> namings;

	public ServiceLoader(final Class type, final ClassLoader loader) {
		this.type = type;
		this.loader = loader == null ? defaultClassLoader() : loader;
	}

	public ServiceLoader(final Class type) {
		this(type, defaultClassLoader());
	}

	public static  ServiceLoader of(Class type) {
		return new ServiceLoader<>(type);
	}

	public static  ServiceLoader of(Class type, ClassLoader loader) {
		return new ServiceLoader<>(type, loader);
	}

	public static ClassLoader defaultClassLoader() {
		ClassLoader cl = null;
		try {
			cl = Thread.currentThread().getContextClassLoader();
		} catch (Throwable ignored) {
		}
		if (cl == null) {
			cl = ServiceLoader.class.getClassLoader();
			if (cl == null) {
				try {
					cl = ClassLoader.getSystemClassLoader();
				} catch (Throwable ignored) {
				}
			}
		}
		return cl;
	}

	public List> getProviders() {
		load();
		return providers;
	}

	public Map> getNamings() {
		load();
		return namings;
	}

	@Override
	public Iterator> iterator() {
		load();
		return providers.iterator();
	}

	private void load() {
		if (!loaded.get()) {
			synchronized (this) {
				if (!loaded.get()) {
					loadClasses();
					loaded.set(true);
				}
			}
		}
	}

	private void loadClasses() {
		final List> classes = new ArrayList<>();
		loadDirectory(classes);
		List> services = new ArrayList<>();
		List> wrappers = new ArrayList<>();
		Map> namings = new HashMap<>();
		if (!classes.isEmpty()) {
			List> ordered = new ArrayList<>();
			List> others = new ArrayList<>();
			List> defaults = new ArrayList<>();

			Function wrapperBuilder = instance -> {
				if (this.wrappers != null && !this.wrappers.isEmpty()) {
					int count = this.wrappers.size();
					for (int i = count - 1; i >= 0; i--) {
						Service wrapper = this.wrappers.get(i);
						instance = wrapper.newPureInstance(new Class[]{type}, new Object[]{instance});
					}
				}
				return instance;
			};
			for (Class o : classes) {
				Class clazz = (Class) o;
				Map properties = new HashMap<>();
				String serviceName = null;

				if (clazz.isAnnotationPresent(ServiceName.class)) {
					serviceName = clazz.getAnnotation(ServiceName.class).value();
					properties.put("name", serviceName);
				}
				if (clazz.isAnnotationPresent(ServiceProperties.class)) {
					ServiceProperties annos = clazz.getAnnotation(ServiceProperties.class);
					for (ServiceProperty anno : annos.value()) {
						properties.put(anno.name(), anno.value());
					}
				}
				if (clazz.isAnnotationPresent(ServiceProperty.class)) {
					ServiceProperty anno = clazz.getAnnotation(ServiceProperty.class);
					properties.put(anno.name(), anno.value());
				}

				properties = properties.isEmpty() ? null : Collections.unmodifiableMap(properties);
				if (clazz.isAnnotationPresent(ServiceOrder.class)) {
					int order = clazz.getAnnotation(ServiceOrder.class).value();
					ordered.add(new Service(clazz, properties, serviceName, order, wrapperBuilder));
				} else if (clazz.isAnnotationPresent(ServiceDefault.class)) {
					int order = clazz.getAnnotation(ServiceDefault.class).value();
					defaults.add(new Service(clazz, properties, serviceName, order, wrapperBuilder));
				} else {
					others.add(new Service(clazz, properties, serviceName, 0, wrapperBuilder));
				}
			}
			Collections.sort(ordered, Service.defaultComparator);
			Collections.sort(others, Service.defaultComparator);
			Collections.sort(defaults, Service.defaultComparator);

			for (List> list : new List[]{ordered, others, defaults}) {
				for (Service service : list) {
					if (isWrapper(service.getServiceClass())) {
						wrappers.add(service);
					} else {
						services.add(service);
						if (service.getServiceName() != null) {
							namings.putIfAbsent(service.getServiceName(), service);
						}
					}
				}
			}

		}
		this.providers = Collections.unmodifiableList(services);
		this.wrappers = Collections.unmodifiableList(wrappers);
		this.namings = Collections.unmodifiableMap(namings);
	}

	private void loadDirectory(List> classes) {
		String typeName = type.getName();
		for (String dir : PREFIX) {
			String filename = dir + typeName;
			try {
				Enumeration urls = loader.getResources(filename);
				while (urls.hasMoreElements()) {
					URL url = urls.nextElement();
					loadResources(classes, url);
				}
			} catch (Throwable ignored) {
			}
		}
	}

	private boolean isWrapper(Class clazz) {
		Constructor[] constructors = clazz.getConstructors();
		for (Constructor constructor : constructors) {
			if (constructor.getParameterTypes().length == 1 && constructor.getParameterTypes()[0] == type) {
				return true;
			}
		}
		return false;
	}

	private void loadResources(final List> classes, final URL url) throws IOException {
		try (InputStream in = url.openStream();) {
			BufferedReader br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
			for (String line = br.readLine(); line != null; line = br.readLine()) {
				line = line.trim().replaceAll("#.*", "");
				if (line.length() > 0 && !line.startsWith("#")) {
					try {
						Class clazz = Class.forName(line, true, loader);
						if (type.isAssignableFrom(clazz)) {
							classes.add((Class) clazz);
						} else {
							log.warn("加载服务类失败,类型不匹配:" + line);
						}
					} catch (Throwable e) {
						log.warn("加载服务类失败:" + line, e);
					}
				}
			}
		} catch (Throwable e) {
			log.warn("加载服务类失败,资源读取错误:" + url, e);
		}
	}

	@Nullable
	private S getSingleton(@Nullable Service s) {
		return Optional.ofNullable(s).map(Service::getSingleton).orElse(null);
	}

	@Nullable
	private S getPureSingleton(@Nullable Service s) {
		return Optional.ofNullable(s).map(Service::getPureSingleton).orElse(null);
	}

	@Nullable
	public Service get() {
		load();
		return providers.isEmpty() ? null : providers.get(0);
	}

	@Nullable
	public S getSingleton() {
		return getSingleton(get());
	}

	@Nullable
	public S getPureSingleton() {
		return getPureSingleton(get());
	}

	@Nullable
	public Service get(String name) {
		load();
		return this.namings.get(name);
	}

	@Nullable
	public S getSingleton(String name) {
		return getSingleton(get(name));
	}

	@Nullable
	public S getPureSingleton(String name) {
		return getPureSingleton(get(name));
	}

	@Nullable
	public Service get(String propertyName, String propertyValue) {
		load();
		for (Service service : providers) {
			String value = service.getProperty(propertyName);
			if (Objects.equals(propertyValue, value)) {
				return service;
			}
		}
		return null;
	}

	@Nullable
	public S getSingleton(String propertyName, String propertyValue) {
		return getSingleton(get(propertyName, propertyValue));
	}

	@Nullable
	public S getPureSingleton(String propertyName, String propertyValue) {
		return getPureSingleton(get(propertyName, propertyValue));
	}

	@Nullable
	public Service get(@Nonnull Function, Boolean> matcher) {
		load();
		for (Service service : providers) {
			if (matcher.apply(service)) {
				return service;
			}
		}
		return null;
	}

	@Nullable
	public S getSingleton(@Nonnull Function, Boolean> matcher) {
		return getSingleton(get(matcher));
	}

	@Nullable
	public S getPureSingleton(@Nonnull Function, Boolean> matcher) {
		return getPureSingleton(get(matcher));
	}

	public void reload() {
		loaded.set(false);
		load();
	}
}