Skip to content

Commit

Permalink
Merge branch 'release/1.0.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
vegegoku committed Dec 24, 2023
2 parents 096463b + 18628bc commit 9442664
Show file tree
Hide file tree
Showing 10 changed files with 274 additions and 177 deletions.
30 changes: 14 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,24 @@ Or as s processor path in the compiler plugin :

### Usage

For a service to be utilized by this tool, it must adhere to the `DominoAutoService` marker interface and possess a
default constructor without arguments. Additionally, these services need to be declared as a `DominoAutoService` service
within the project's `META-INF`. When a service meets these criteria, you can use the `@DominoAuto` annotation on a
class or interface to specify the desired service interface. The processor then automatically generates a class that
aggregates all instances of the specified service.
Adding the dependencies should be all you need to start using the tool; it will automatically generate service loaders
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.

### Example

Lets define the desired service interface

```java
public interface SampleService extends DominoAutoService {
package com.dominokit.samples;

public interface SampleService {
void init();
}
```
Expand Down Expand Up @@ -92,7 +98,7 @@ public class BarSampleServiceImpl implements SampleService {
```
The project `META-INF` we register both implementations as services :

Create a file named `org.dominokit.auto.DominoAutoService` under the following path
Create a file named `com.dominokit.samples.SampleService` under the following path
`project root folder -> src -> main -> resources -> META-INF -> services`

In the file list the services with the full qualified class name
Expand All @@ -102,14 +108,6 @@ com.dominokit.samples.FooSampleServiceImpl
com.dominokit.samples.BarSampleServiceImpl
```

Now in the client module from where you need to load the services, you create a class or interface and annotate it with `@DominoAuto`

```java
@DominoAuto(SampleService.class)
public interface SampleServiceAuto {
}
```

This will generate the following code :

```java
Expand All @@ -126,7 +124,7 @@ public class SampleServiceAuto_ServiceLoader {
Then we can use it like this :

```java
SampleServiceAuto_ServiceLoader.load()
SampleService_ServiceLoader.load()
.forEach(SampleService::init);
```

Expand Down
2 changes: 1 addition & 1 deletion domino-auto-api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>org.dominokit</groupId>
<artifactId>domino-auto</artifactId>
<version>1.0.0</version>
<version>1.0.1</version>
</parent>

<artifactId>domino-auto-api</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DominoAuto {
Class<? extends DominoAutoService> value();
Class<?>[] blackList();
}
2 changes: 1 addition & 1 deletion domino-auto-processor/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>org.dominokit</groupId>
<artifactId>domino-auto</artifactId>
<version>1.0.0</version>
<version>1.0.1</version>
</parent>

<artifactId>domino-auto-processor</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,39 +1,203 @@
/*
* #%L
* gwt-websockets-processor
* %%
* Copyright (C) 2011 - 2018 Vertispan LLC
* %%
* 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
* 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.
* #L%
*/
package org.dominokit.auto;

import com.google.auto.common.BasicAnnotationProcessor;
import com.google.auto.service.AutoService;
import java.util.Collections;
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 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 javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.lang.model.SourceVersion;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;

@AutoService(Processor.class)
public class DominoAutoProcessor extends BasicAnnotationProcessor {
public class DominoAutoProcessor extends AbstractProcessor implements HasProcessorEnv {

private ProcessingEnvironment env;
private SourceUtil sourceUtil;

@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
this.env = processingEnv;
this.sourceUtil = new SourceUtil(this);
}

@Override
public Set<String> getSupportedAnnotationTypes() {
return new HashSet<>(Arrays.asList("*"));
}

@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<>());
}
services.get(line).add(serviceLine);
}
}
}
}
}
}

writeServiceLoaders(services);
}
} catch (Exception 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));
});

bodyBuilder.addStatement("return services");
TypeSpec classSpec =
TypeSpec.classBuilder(getClassName(key) + "_ServiceLoader")
.addModifiers(Modifier.PUBLIC)
.addMethod(
MethodSpec.methodBuilder("load")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(
ParameterizedTypeName.get(
ClassName.get(List.class), ClassName.bestGuess(key)))
.addCode(bodyBuilder.build())
.build())
.build();

try {
JavaFile.builder(getPackageName(key), classSpec)
.build()
.writeTo(processingEnv.getFiler());
} catch (IOException e) {
processingEnv
.getMessager()
.printMessage(
Diagnostic.Kind.ERROR,
"Failed to write service loader for service : [" + key + "]");
}
});
}

private String getPackageName(String qualifiedName) {
int lastDotIndex = qualifiedName.lastIndexOf('.');
// Check for default package or no dot present
if (lastDotIndex == -1) {
return "";
}
return qualifiedName.substring(0, lastDotIndex);
}

private String getClassName(String qualifiedName) {
int lastDotIndex = qualifiedName.lastIndexOf('.');
// Check if no dot is present, meaning the entire string is the class name
if (lastDotIndex == -1) {
return qualifiedName;
}
return qualifiedName.substring(lastDotIndex + 1);
}

@Override
public Types types() {
return env.getTypeUtils();
}

@Override
protected Iterable<? extends Step> steps() {
return Collections.singletonList(new DominoAutoProcessorStep(processingEnv));
public Elements elements() {
return env.getElementUtils();
}

@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
public Messager messager() {
return env.getMessager();
}
}
Loading

0 comments on commit 9442664

Please sign in to comment.