Skip to content

Commit

Permalink
change how we define included/excluded packages to control which load…
Browse files Browse the repository at this point in the history
…er to generate for which services
  • Loading branch information
vegegoku committed Jan 1, 2024
1 parent 5b39d9c commit cdcfb53
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 80 deletions.
66 changes: 55 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,22 @@ Or as s processor path in the compiler plugin :

```xml

<annotationProcessorPaths>
<path>
<groupId>org.dominokit</groupId>
<artifactId>domino-auto-processor</artifactId>
<version>[version]</version>
</path>
</annotationProcessorPaths>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<annotationProcessorPaths>
<!-- path to your annotation processor -->
<path>
<groupId>org.dominokit</groupId>
<artifactId>domino-auto-processor</artifactId>
<version>[version]</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>

```

### Usage
Expand All @@ -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

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<annotationProcessorPaths>
<!-- path to your annotation processor -->
<path>
<groupId>org.dominokit</groupId>
<artifactId>domino-auto-processor</artifactId>
<version>[version]</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>-AdominoAutoInclude=com.dominokit.samples</arg>
</compilerArgs>
</configuration>
</plugin>
```

> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {};
}
6 changes: 6 additions & 0 deletions domino-auto-processor/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@
<artifactId>javapoet</artifactId>
<version>1.13.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.github.classgraph/classgraph -->
<dependency>
<groupId>io.github.classgraph</groupId>
<artifactId>classgraph</artifactId>
<version>4.8.165</version>
</dependency>

</dependencies>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,35 @@
*/
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;
import com.squareup.javapoet.JavaFile;
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;
Expand All @@ -65,80 +69,87 @@ public Set<String> getSupportedAnnotationTypes() {
return new HashSet<>(Arrays.asList("*"));
}

@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
try {
if (roundEnv.processingOver()) {

Set<? extends Element> dominoAutoElements =
roundEnv.getElementsAnnotatedWith(DominoAuto.class);

Set<String> blackList = getBlackListedServices(dominoAutoElements);

Map<String, Set<String>> services = new HashMap<>();

// Load META-INF/services entries from the classpath
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Enumeration<URL> 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<? extends Element> dominoAutoElements =
roundEnv.getElementsAnnotatedWith(DominoAuto.class);

Set<String> includes = new HashSet<>();
Set<String> 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<String, Set<String>> 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<String> 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<String> getBlackListedServices(Set<? extends Element> dominoAutoElements) {
Set<String> 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<String, Set<String>> 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 =
Expand All @@ -158,12 +169,10 @@ private void writeServiceLoaders(Map<String, Set<String>> 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());
}
});
}
Expand Down Expand Up @@ -200,4 +209,14 @@ public Elements elements() {
public Messager messager() {
return env.getMessager();
}

@Override
public Filer getFiler() {
return env.getFiler();
}

@Override
public Map<String, String> options() {
return env.getOptions();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -25,4 +27,8 @@ public interface HasProcessorEnv {
Elements elements();

Messager messager();

Filer getFiler();

Map<String, String> options();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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();
}
}
Loading

0 comments on commit cdcfb53

Please sign in to comment.