diff --git a/README.md b/README.md index 21e2443..67d8318 100644 --- a/README.md +++ b/README.md @@ -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(); } ``` @@ -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 @@ -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 @@ -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); ``` diff --git a/domino-auto-api/pom.xml b/domino-auto-api/pom.xml index 98501a0..5242be4 100644 --- a/domino-auto-api/pom.xml +++ b/domino-auto-api/pom.xml @@ -6,7 +6,7 @@ org.dominokit domino-auto - 1.0.0 + 1.0.1 domino-auto-api 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 493e3ad..5e7e2ad 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 @@ -23,5 +23,5 @@ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface DominoAuto { - Class value(); + Class[] blackList(); } diff --git a/domino-auto-processor/pom.xml b/domino-auto-processor/pom.xml index e44c444..91e662d 100644 --- a/domino-auto-processor/pom.xml +++ b/domino-auto-processor/pom.xml @@ -6,7 +6,7 @@ org.dominokit domino-auto - 1.0.0 + 1.0.1 domino-auto-processor 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 b59b89b..443e951 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 @@ -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 getSupportedAnnotationTypes() { + return new HashSet<>(Arrays.asList("*")); + } + + @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<>()); + } + 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 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)); + }); + + 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 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(); } } diff --git a/domino-auto-processor/src/main/java/org/dominokit/auto/DominoAutoProcessorStep.java b/domino-auto-processor/src/main/java/org/dominokit/auto/DominoAutoProcessorStep.java deleted file mode 100644 index ed3f892..0000000 --- a/domino-auto-processor/src/main/java/org/dominokit/auto/DominoAutoProcessorStep.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * #%L - * gwt-websockets-processor - * %% - * Copyright (C) 2011 - 2018 Vertispan LLC - * %% - * 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. - * #L% - */ -package org.dominokit.auto; - -import com.google.auto.common.BasicAnnotationProcessor; -import com.google.common.collect.ImmutableSetMultimap; -import com.google.common.collect.Sets; -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.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.ServiceLoader; -import java.util.Set; -import javax.annotation.processing.Messager; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.Element; -import javax.lang.model.element.Modifier; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; -import javax.tools.Diagnostic; - -public class DominoAutoProcessorStep implements BasicAnnotationProcessor.Step, HasProcessorEnv { - - private final ProcessingEnvironment processingEnv; - private final SourceUtil sourceUtil; - - public DominoAutoProcessorStep(ProcessingEnvironment processingEnv) { - this.processingEnv = processingEnv; - this.sourceUtil = new SourceUtil(this); - } - - @Override - public Set annotations() { - return new HashSet<>(Arrays.asList(DominoAuto.class.getCanonicalName())); - } - - @Override - public Set process( - ImmutableSetMultimap elementsByAnnotation) { - elementsByAnnotation - .get(DominoAuto.class.getCanonicalName()) - .forEach( - element -> { - sourceUtil - .getClassValueFromAnnotation(element, DominoAuto.class, "value") - .ifPresent( - typeMirror -> { - CodeBlock.Builder bodyBuilder = CodeBlock.builder(); - bodyBuilder.addStatement( - "$T<$T> services = new $T()", List.class, typeMirror, ArrayList.class); - - ServiceLoader.load( - DominoAutoService.class, - DominoAutoProcessorStep.class.getClassLoader()) - .stream() - .map(ServiceLoader.Provider::get) - .filter( - dominoAutoService -> - sourceUtil.isAssignableFrom( - typeMirror, dominoAutoService.getClass())) - .forEach( - dominoAutoService -> { - bodyBuilder.addStatement( - "services.add(new $T())", - ClassName.get(dominoAutoService.getClass())); - }); - - bodyBuilder.addStatement("return services"); - TypeSpec classSpec = - TypeSpec.classBuilder( - element.getSimpleName().toString() + "_ServiceLoader") - .addModifiers(Modifier.PUBLIC) - .addMethod( - MethodSpec.methodBuilder("load") - .addModifiers(Modifier.PUBLIC, Modifier.STATIC) - .returns( - ParameterizedTypeName.get( - ClassName.get(List.class), - ClassName.get(typeMirror))) - .addCode(bodyBuilder.build()) - .build()) - .build(); - - try { - JavaFile.builder( - elements().getPackageOf(element).getQualifiedName().toString(), - classSpec) - .build() - .writeTo(processingEnv.getFiler()); - } catch (IOException e) { - processingEnv - .getMessager() - .printMessage( - Diagnostic.Kind.ERROR, "Failed to write service loader", element); - } - }); - }); - - return Sets.newHashSet(); - } - - @Override - public Types types() { - return processingEnv.getTypeUtils(); - } - - @Override - public Elements elements() { - return processingEnv.getElementUtils(); - } - - @Override - public Messager messager() { - return processingEnv.getMessager(); - } -} 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 0dd7581..482798c 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 @@ -16,6 +16,9 @@ package org.dominokit.auto; 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.lang.model.element.AnnotationMirror; @@ -61,6 +64,52 @@ public Optional getClassValueFromAnnotation( return Optional.empty(); } + /** + * Finds a list of type mirrors for classes defined as an annotation parameter. + * + *

For example: + * + *

+   * interface @MyAnnotation {
+   *  Class<?>[] myClasses();
+   * }
+   * 
+ * + *

+ * + * @param element the element + * @param annotation the annotation + * @param paramName the class parameter name + * @return The list of type mirrors for the classes, empty list otherwise + */ + public List getClassArrayValueFromAnnotation( + Element element, Class annotation, String paramName) { + + List values = new ArrayList<>(); + + for (AnnotationMirror am : element.getAnnotationMirrors()) { + if (env.types() + .isSameType( + am.getAnnotationType(), + env.elements().getTypeElement(annotation.getCanonicalName()).asType())) { + for (Map.Entry entry : + am.getElementValues().entrySet()) { + if (paramName.equals(entry.getKey().getSimpleName().toString())) { + List classesTypes = + (List) entry.getValue().getValue(); + Iterator iterator = classesTypes.iterator(); + + while (iterator.hasNext()) { + AnnotationValue next = iterator.next(); + values.add((TypeMirror) next.getValue()); + } + } + } + } + } + return values; + } + /** * isAssignableFrom. checks if a specific {@link TypeMirror} is assignable from a specific {@link * java.lang.Class}. diff --git a/domino-auto-api/src/main/java/org/dominokit/auto/DominoAutoService.java b/domino-auto-processor/src/test/java/org/dominokit/auto/TestService.java similarity index 92% rename from domino-auto-api/src/main/java/org/dominokit/auto/DominoAutoService.java rename to domino-auto-processor/src/test/java/org/dominokit/auto/TestService.java index 959a7a2..cbef1f4 100644 --- a/domino-auto-api/src/main/java/org/dominokit/auto/DominoAutoService.java +++ b/domino-auto-processor/src/test/java/org/dominokit/auto/TestService.java @@ -15,4 +15,6 @@ */ package org.dominokit.auto; -public interface DominoAutoService {} +public interface TestService { + void init(); +} diff --git a/domino-auto-processor/src/test/java/org/dominokit/auto/TestServiceImpl.java b/domino-auto-processor/src/test/java/org/dominokit/auto/TestServiceImpl.java new file mode 100644 index 0000000..7537eb1 --- /dev/null +++ b/domino-auto-processor/src/test/java/org/dominokit/auto/TestServiceImpl.java @@ -0,0 +1,24 @@ +/* + * 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. + */ +package org.dominokit.auto; + +import com.google.auto.service.AutoService; + +@AutoService(TestService.class) +public class TestServiceImpl implements TestService { + @Override + public void init() {} +} diff --git a/pom.xml b/pom.xml index 1987fd3..1680bc4 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.dominokit domino-auto - 1.0.0 + 1.0.1 pom domino-auto @@ -66,7 +66,7 @@ HEAD-SNAPSHOT - 1.0.0 + 1.0.1 11 11 UTF-8