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

org.macrocloud.kernel.auto.service.AutoServiceProcessor Maven / Gradle / Ivy

There is a newer version: 1.1.0-RELEASE
Show newest version
package org.macrocloud.kernel.auto.service;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.stream.Collectors;

import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedOptions;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.SimpleAnnotationValueVisitor8;
import javax.lang.model.util.Types;
import javax.tools.FileObject;
import javax.tools.StandardLocation;

import org.macrocloud.kernel.auto.common.AbstractBaseProcessor;
import org.macrocloud.kernel.auto.common.MultiSetMap;
import org.macrocloud.kernel.auto.common.Sets;
import org.macrocloud.kernel.auto.common.TypeHelper;

/**
 * java spi 服务自动处理器 参考:google auto
 *
 * @author macro
 */
@SupportedOptions("debug")
public class AutoServiceProcessor extends AbstractBaseProcessor {
	/**
	 * spi 服务集合,key 接口 -> value 实现列表
	 */
	private final MultiSetMap providers = new MultiSetMap<>();
	private TypeHelper typeHelper;

	@Override
	public synchronized void init(ProcessingEnvironment env) {
		super.init(env);
		this.typeHelper = new TypeHelper(env);
	}

	@Override
	public Set getSupportedAnnotationTypes() {
		return Sets.ofImmutableSet(AutoService.class.getName());
	}

	@Override
	protected boolean processImpl(Set annotations, RoundEnvironment roundEnv) {
		if (roundEnv.processingOver()) {
			generateConfigFiles();
		} else {
			processAnnotations(annotations, roundEnv);
		}
		return true;
	}

	private void processAnnotations(Set annotations, RoundEnvironment roundEnv) {
		Set elements = roundEnv.getElementsAnnotatedWith(AutoService.class);

		log(annotations.toString());
		log(elements.toString());

		for (Element e : elements) {
			TypeElement providerImplementer = (TypeElement) e;
			AnnotationMirror annotationMirror = getAnnotationMirror(e, AutoService.class);
			if (annotationMirror == null) {
				continue;
			}
			Set typeMirrors = getValueFieldOfClasses(annotationMirror);
			if (typeMirrors.isEmpty()) {
				error("No service interfaces provided for element!", e, annotationMirror);
				continue;
			}
			for (TypeMirror typeMirror : typeMirrors) {
				String providerInterfaceName = typeHelper.getType(typeMirror);
				Name providerImplementerName = providerImplementer.getQualifiedName();

				log("provider interface: " + providerInterfaceName);
				log("provider implementer: " + providerImplementerName);

				if (checkImplementer(providerImplementer, typeMirror)) {
					providers.put(providerInterfaceName, typeHelper.getType(providerImplementer));
				} else {
					String message = "ServiceProviders must implement their service provider interface. "
							+ providerImplementerName + " does not implement " + providerInterfaceName;
					error(message, e, annotationMirror);
				}
			}
		}
	}

	private void generateConfigFiles() {
		Filer filer = processingEnv.getFiler();

		for (String providerInterface : providers.keySet()) {
			String resourceFile = "META-INF/services/" + providerInterface;
			log("Working on resource file: " + resourceFile);
			try {
				SortedSet allServices = new TreeSet<>();
				try {
					FileObject existingFile = filer.getResource(StandardLocation.CLASS_OUTPUT, "", resourceFile);
					log("Looking for existing resource file at " + existingFile.toUri());
					Set oldServices = ServicesFiles.readServiceFile(existingFile.openInputStream());
					log("Existing service entries: " + oldServices);
					allServices.addAll(oldServices);
				} catch (IOException e) {
					log("Resource file did not already exist.");
				}

				Set newServices = new HashSet<>(providers.get(providerInterface));
				if (allServices.containsAll(newServices)) {
					log("No new service entries being added.");
					return;
				}

				allServices.addAll(newServices);
				log("New service file contents: " + allServices);
				FileObject fileObject = filer.createResource(StandardLocation.CLASS_OUTPUT, "", resourceFile);
				OutputStream out = fileObject.openOutputStream();
				ServicesFiles.writeServiceFile(allServices, out);
				out.close();
				log("Wrote to: " + fileObject.toUri());
			} catch (IOException e) {
				fatalError("Unable to create " + resourceFile + ", " + e);
				return;
			}
		}
	}

	/**
	 * Verifies {@link java.util.spi.LocaleServiceProvider} constraints on the
	 * concrete provider class. Note that these constraints are enforced at runtime
	 * via the ServiceLoader, we're just checking them at compile time to be extra
	 * nice to our users.
	 */
	private boolean checkImplementer(TypeElement providerImplementer, TypeMirror providerType) {
		// TODO: We're currently only enforcing the subtype relationship
		// constraint. It would be nice to enforce them all.
		Types types = processingEnv.getTypeUtils();

		return types.isSubtype(providerImplementer.asType(), providerType);
	}

	/**
	 * 读取 AutoService 上的 value 值
	 *
	 * @param annotationMirror AnnotationMirror
	 * @return value 集合
	 */
	private Set getValueFieldOfClasses(AnnotationMirror annotationMirror) {
		return getAnnotationValue(annotationMirror, "value")
				.accept(new SimpleAnnotationValueVisitor8, Void>() {
					@Override
					public Set visitType(TypeMirror typeMirror, Void v) {
						Set declaredTypeSet = new HashSet<>(1);
						declaredTypeSet.add(typeMirror);
						return Collections.unmodifiableSet(declaredTypeSet);
					}

					@Override
					public Set visitArray(List values, Void v) {
						return values.stream().flatMap(value -> value.accept(this, null).stream())
								.collect(Collectors.toSet());
					}
				}, null);
	}

	/**
	 * Returns a {@link ExecutableElement} and its associated
	 * {@link AnnotationValue} if such an element was either declared in the usage
	 * represented by the provided {@link AnnotationMirror}, or if such an element
	 * was defined with a default.
	 *
	 * @param annotationMirror AnnotationMirror
	 * @param elementName      elementName
	 * @return AnnotationValue map
	 * @throws IllegalArgumentException if no element is defined with the given
	 *                                  elementName.
	 */
	public AnnotationValue getAnnotationValue(AnnotationMirror annotationMirror, String elementName) {
		Objects.requireNonNull(annotationMirror);
		Objects.requireNonNull(elementName);
		for (Map.Entry entry : getAnnotationValuesWithDefaults(annotationMirror)
				.entrySet()) {
			if (entry.getKey().getSimpleName().contentEquals(elementName)) {
				return entry.getValue();
			}
		}
		String name = typeHelper.getType(annotationMirror);
		throw new IllegalArgumentException(String.format("@%s does not define an element %s()", name, elementName));
	}

	/**
	 * Returns the {@link AnnotationMirror}'s map of {@link AnnotationValue} indexed
	 * by {@link ExecutableElement}, supplying default values from the annotation if
	 * the annotation property has not been set. This is equivalent to
	 * {@link Elements#getElementValuesWithDefaults(AnnotationMirror)} but can be
	 * called statically without an {@link Elements} instance.
	 *
	 * 

* The iteration order of elements of the returned map will be the order in * which the {@link ExecutableElement}s are defined in {@code annotation}'s * {@linkplain AnnotationMirror#getAnnotationType() type}. * * @param annotation AnnotationMirror * @return AnnotationValue Map */ public Map getAnnotationValuesWithDefaults(AnnotationMirror annotation) { Map values = new HashMap<>(32); Map declaredValues = annotation.getElementValues(); for (ExecutableElement method : ElementFilter .methodsIn(annotation.getAnnotationType().asElement().getEnclosedElements())) { // Must iterate and put in this order, to ensure consistency in generated code. if (declaredValues.containsKey(method)) { values.put(method, declaredValues.get(method)); } else if (method.getDefaultValue() != null) { values.put(method, method.getDefaultValue()); } else { String name = typeHelper.getType(method); throw new IllegalStateException("Unset annotation value without default should never happen: " + name + '.' + method.getSimpleName() + "()"); } } return Collections.unmodifiableMap(values); } public AnnotationMirror getAnnotationMirror(Element element, Class annotationClass) { String annotationClassName = annotationClass.getCanonicalName(); for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) { String name = typeHelper.getType(annotationMirror); if (name.contentEquals(annotationClassName)) { return annotationMirror; } } return null; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy