From cdcfb539ee8081124536a3c59ece4ca3dedf07b3 Mon Sep 17 00:00:00 2001 From: "Ahmad K. Bawaneh" Date: Mon, 1 Jan 2024 19:25:45 +0300 Subject: [PATCH] change how we define included/excluded packages to control which loader to generate for which services --- README.md | 66 ++++++-- .../java/org/dominokit/auto/DominoAuto.java | 6 +- domino-auto-processor/pom.xml | 6 + .../dominokit/auto/DominoAutoProcessor.java | 153 ++++++++++-------- .../org/dominokit/auto/HasProcessorEnv.java | 6 + .../java/org/dominokit/auto/SourceUtil.java | 24 +++ .../java/org/dominokit/auto/package-info.java | 17 ++ 7 files changed, 198 insertions(+), 80 deletions(-) create mode 100644 domino-auto-processor/src/test/java/org/dominokit/auto/package-info.java diff --git a/README.md b/README.md index 67d8318..3eb58f0 100644 --- a/README.md +++ b/README.md @@ -42,13 +42,22 @@ Or as s processor path in the compiler plugin : ```xml - - - org.dominokit - domino-auto-processor - [version] - - + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + + + + org.dominokit + domino-auto-processor + [version] + + + + + ``` ### Usage @@ -59,12 +68,47 @@ for all services defined in the classpath. The generated service loader class name will follow the convention `[Service name]_ServiceLoader`, and will provide a single method `load` that returns a list of that service implementations. -The user can set up a lit of black-listed service to avoid generating service loaders for them, where -the `javax.annotation.processing.Processor` [The annotation processors] are always black-listed. -to add a new entry to the black list create a package-info class or any other class and annotate it with `DominoAuto` and specify the list of black-listed service classes. - +The user needs to specify a white-list of packages that will be included in the generation, the provided white-list +represent the package of the implemented service class not the implementations. the white list can be configured using a +compiler argument `dominoAutoInclude` or using `@DominoAuto` annotation on a type or package-info. ### Example +- Make sure the service package is included in the white-list : + +```xml + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + + + + org.dominokit + domino-auto-processor + [version] + + + + -AdominoAutoInclude=com.dominokit.samples + + + +``` + +> Using this method, make sure in case you are building from the ide to setup the compiler arguments in the ide or make +> the ide delegate the build to maven + +or + +```java +@DominoAuto(include = {"com.dominokit.samples"}) +package com.dominokit.samples; + +import org.dominokit.auto.DominoAuto; +``` + Lets define the desired service interface ```java diff --git a/domino-auto-api/src/main/java/org/dominokit/auto/DominoAuto.java b/domino-auto-api/src/main/java/org/dominokit/auto/DominoAuto.java index 5e7e2ad..1903b6c 100644 --- a/domino-auto-api/src/main/java/org/dominokit/auto/DominoAuto.java +++ b/domino-auto-api/src/main/java/org/dominokit/auto/DominoAuto.java @@ -20,8 +20,10 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -@Target(ElementType.TYPE) +@Target({ElementType.TYPE, ElementType.PACKAGE}) @Retention(RetentionPolicy.RUNTIME) public @interface DominoAuto { - Class[] blackList(); + String[] exclude() default {}; + + String[] include() default {}; } diff --git a/domino-auto-processor/pom.xml b/domino-auto-processor/pom.xml index 5763e3b..5464bc6 100644 --- a/domino-auto-processor/pom.xml +++ b/domino-auto-processor/pom.xml @@ -33,6 +33,12 @@ javapoet 1.13.0 + + + io.github.classgraph + classgraph + 4.8.165 + diff --git a/domino-auto-processor/src/main/java/org/dominokit/auto/DominoAutoProcessor.java b/domino-auto-processor/src/main/java/org/dominokit/auto/DominoAutoProcessor.java index 443e951..7bf440a 100644 --- a/domino-auto-processor/src/main/java/org/dominokit/auto/DominoAutoProcessor.java +++ b/domino-auto-processor/src/main/java/org/dominokit/auto/DominoAutoProcessor.java @@ -15,6 +15,8 @@ */ package org.dominokit.auto; +import static java.util.Objects.nonNull; + import com.google.auto.service.AutoService; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; @@ -22,24 +24,26 @@ import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeSpec; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.URL; +import io.github.classgraph.ClassGraph; +import io.github.classgraph.Resource; +import io.github.classgraph.ScanResult; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; -import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.Filer; import javax.annotation.processing.Messager; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.Processor; import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; @@ -65,80 +69,87 @@ public Set getSupportedAnnotationTypes() { return new HashSet<>(Arrays.asList("*")); } + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { try { - if (roundEnv.processingOver()) { - - Set dominoAutoElements = - roundEnv.getElementsAnnotatedWith(DominoAuto.class); - - Set blackList = getBlackListedServices(dominoAutoElements); - - Map> services = new HashMap<>(); - - // Load META-INF/services entries from the classpath - ClassLoader loader = Thread.currentThread().getContextClassLoader(); - Enumeration resources = loader.getResources("META-INF/services"); - - while (resources.hasMoreElements()) { - URL url = resources.nextElement(); - - try (InputStream is = url.openStream(); - BufferedReader reader = new BufferedReader(new InputStreamReader(is))) { - String line; - while ((line = reader.readLine()) != null) { - if (!blackList.contains(line)) { - URL service = loader.getResource("META-INF/services/" + line); - assert service != null; - try (InputStream serviceStream = service.openStream(); - BufferedReader serviceReader = - new BufferedReader(new InputStreamReader(serviceStream))) { - String serviceLine; - while ((serviceLine = serviceReader.readLine()) != null) { - if (!services.containsKey(line)) { - services.put(line, new HashSet<>()); + + Set dominoAutoElements = + roundEnv.getElementsAnnotatedWith(DominoAuto.class); + + Set includes = new HashSet<>(); + Set exclude = new HashSet<>(); + + if (options().containsKey("dominoAutoInclude")) { + includes.addAll( + Arrays.stream(options().get("dominoAutoInclude").split(",")) + .collect(Collectors.toSet())); + } + + if (options().containsKey("dominoAutoExclude")) { + exclude.addAll( + Arrays.stream(options().get("dominoAutoExclude").split(",")) + .collect(Collectors.toSet())); + } + + dominoAutoElements.forEach( + element -> { + includes.addAll(Arrays.asList(element.getAnnotation(DominoAuto.class).include())); + exclude.addAll(Arrays.asList(element.getAnnotation(DominoAuto.class).exclude())); + }); + + Map> services = new HashMap<>(); + + try (ScanResult scanResult = + new ClassGraph().acceptPathsNonRecursive("META-INF/services").scan()) { + scanResult + .getAllResources() + .forEachByteArrayThrowingIOException( + (Resource res, byte[] content) -> { + String serviceName = res.getPath().replace("META-INF/services/", ""); + + if (includes.stream().anyMatch(serviceName::startsWith) + && exclude.stream().noneMatch(serviceName::startsWith)) { + if (!services.containsKey(serviceName)) { + services.put(serviceName, new HashSet<>()); } - services.get(line).add(serviceLine); + Stream impls = new String(content, StandardCharsets.UTF_8).lines(); + impls + .filter( + impl -> + nonNull(impl) && !impl.trim().isEmpty() && !impl.startsWith("#")) + .forEach( + impl -> { + services.get(serviceName).add(impl); + }); } - } - } - } - } - } - - writeServiceLoaders(services); + }); } + writeServiceLoaders(services); } catch (Exception ex) { + SourceUtil.errorStackTrace(env.getMessager(), ex); env.getMessager().printMessage(Diagnostic.Kind.ERROR, "Failed to generate service loaders."); } return false; } - private Set getBlackListedServices(Set dominoAutoElements) { - Set blackList = new HashSet<>(); - blackList.add(Processor.class.getCanonicalName()); - - dominoAutoElements.forEach( - element -> - sourceUtil - .getClassArrayValueFromAnnotation(element, DominoAuto.class, "blackList") - .forEach( - typeMirror -> - blackList.add(env.getTypeUtils().erasure(typeMirror).toString()))); - return blackList; - } - private void writeServiceLoaders(Map> services) { services.forEach( (key, impls) -> { CodeBlock.Builder bodyBuilder = CodeBlock.builder(); bodyBuilder.addStatement( "$T<$T> services = new $T()", List.class, ClassName.bestGuess(key), ArrayList.class); - impls.forEach( - impl -> { - bodyBuilder.addStatement("services.add(new $T())", ClassName.bestGuess(impl)); - }); + impls.stream() + .forEach( + impl -> { + env.getMessager() + .printMessage(Diagnostic.Kind.WARNING, "Adding service entry : " + impl); + bodyBuilder.addStatement("services.add(new $T())", ClassName.bestGuess(impl)); + }); bodyBuilder.addStatement("return services"); TypeSpec classSpec = @@ -158,12 +169,10 @@ private void writeServiceLoaders(Map> services) { JavaFile.builder(getPackageName(key), classSpec) .build() .writeTo(processingEnv.getFiler()); - } catch (IOException e) { - processingEnv - .getMessager() + } catch (Exception e) { + messager() .printMessage( - Diagnostic.Kind.ERROR, - "Failed to write service loader for service : [" + key + "]"); + Diagnostic.Kind.WARNING, "Failed to write service loader : " + e.getMessage()); } }); } @@ -200,4 +209,14 @@ public Elements elements() { public Messager messager() { return env.getMessager(); } + + @Override + public Filer getFiler() { + return env.getFiler(); + } + + @Override + public Map options() { + return env.getOptions(); + } } diff --git a/domino-auto-processor/src/main/java/org/dominokit/auto/HasProcessorEnv.java b/domino-auto-processor/src/main/java/org/dominokit/auto/HasProcessorEnv.java index 6f0b614..ef33790 100644 --- a/domino-auto-processor/src/main/java/org/dominokit/auto/HasProcessorEnv.java +++ b/domino-auto-processor/src/main/java/org/dominokit/auto/HasProcessorEnv.java @@ -15,6 +15,8 @@ */ package org.dominokit.auto; +import java.util.Map; +import javax.annotation.processing.Filer; import javax.annotation.processing.Messager; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; @@ -25,4 +27,8 @@ public interface HasProcessorEnv { Elements elements(); Messager messager(); + + Filer getFiler(); + + Map options(); } diff --git a/domino-auto-processor/src/main/java/org/dominokit/auto/SourceUtil.java b/domino-auto-processor/src/main/java/org/dominokit/auto/SourceUtil.java index 482798c..b5c6364 100644 --- a/domino-auto-processor/src/main/java/org/dominokit/auto/SourceUtil.java +++ b/domino-auto-processor/src/main/java/org/dominokit/auto/SourceUtil.java @@ -15,18 +15,22 @@ */ package org.dominokit.auto; +import java.io.PrintWriter; +import java.io.StringWriter; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; +import javax.annotation.processing.Messager; 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.type.DeclaredType; import javax.lang.model.type.TypeMirror; +import javax.tools.Diagnostic; public class SourceUtil { @@ -125,4 +129,24 @@ public boolean isAssignableFrom(TypeMirror typeMirror, Class targetClass) { .getDeclaredType(env.elements().getTypeElement(targetClass.getCanonicalName())), typeMirror); } + + public static void errorStackTrace(Messager messager, Exception e) { + StringWriter out = new StringWriter(); + e.printStackTrace(new PrintWriter(out)); + messager.printMessage( + Diagnostic.Kind.ERROR, "error while creating source file " + out.getBuffer().toString()); + } + + public static void warningStackTrace(Messager messager, Exception e) { + StringWriter out = new StringWriter(); + e.printStackTrace(new PrintWriter(out)); + messager.printMessage( + Diagnostic.Kind.WARNING, "error while creating source file " + out.getBuffer().toString()); + } + + public static String errorStackTrace(Exception e) { + StringWriter out = new StringWriter(); + e.printStackTrace(new PrintWriter(out)); + return out.getBuffer().toString(); + } } diff --git a/domino-auto-processor/src/test/java/org/dominokit/auto/package-info.java b/domino-auto-processor/src/test/java/org/dominokit/auto/package-info.java new file mode 100644 index 0000000..5013765 --- /dev/null +++ b/domino-auto-processor/src/test/java/org/dominokit/auto/package-info.java @@ -0,0 +1,17 @@ +/* + * Copyright © 2019 Dominokit + * + * 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. + */ +@DominoAuto(include = {"org.dominokit.auto"}) +package org.dominokit.auto;