diff --git a/pom.xml b/pom.xml index b41353c7b..513544f1b 100644 --- a/pom.xml +++ b/pom.xml @@ -56,6 +56,7 @@ server client tools + power-annotations diff --git a/power-annotations/README.adoc b/power-annotations/README.adoc new file mode 100644 index 000000000..563d703e2 --- /dev/null +++ b/power-annotations/README.adoc @@ -0,0 +1,55 @@ += Power-Annotations + +Power-Annotations is actually independent of GraphQL. It's a generic mechanism for meta annotations. It consists of annotations you can put on your code and (for now only) implementation using a maven plugin to build a Jandex index file. + +== Annotations + +=== Stereotypes + +The simplest use-case for Stereotypes is renaming annotations, e.g. `@Property` instead of `@JsonbProperty` (assuming your JBON-B implementation supported Power Annotations). + +[source,java] +---- +@Retention(RUNTIME) +@Stereotype +@JsonbProperty +public @interface Property {} +---- + +It gets much more interesting, when you add more annotations from other (supporting) frameworks to your stereotype, e.g. `@XmlAttribute` from JAX-B. Now you have one annotation instead of two with both having the same semantics. + +Properly used, stereotypes are shortcuts describing the role the annotated element has; functionally as well as a documentation. + +This is exactly what https://jakarta.ee/specifications/cdi/2.0/cdi-spec-2.0.html#stereotypes[CDI-Stereotypes] do, but not restricted to CDI, i.e. the annotations used by any framework that supports Power Annotations can be used with stereotypes. We have our own `Stereotype` class; but as we don't want to depend on CDI and still not exclude CDI, any annotation named `Stereotype` will work. + + +// TODO === Resolve From Class +// +//This is a very common pattern: annotations on a class are considered as a fallback for member annotations (i.e. on fields or methods), if +// +//* the member is not annotated with the same type or the annotation is repeatable, and +//* the annotation is annotated to be an _explicitly_ allowed `@Target` for `FIELD`/`METHOD`. + + +// TODO === Inheritance +// +//When annotating a super class or interface, the annotation is valid also for the sub class or interface. This is also true for annotations on overridden or implemented methods. +// +//In Java reflection, this only works for super classes and only if the annotation is annotated as `@Inherited`. As this generally violates the https://en.wikipedia.org/wiki/Liskov_substitution_principle[LSP], power-annotations always resolves these annotations. We may add a mechanism to _not_ inherit annotations later, if the need actually arises. + +=== Mixins + +Say you have a class you want to add annotations to, but you can't; e.g., because it's a class from some library or even from the JDK. You can create your own class (or interface) and annotate it as `@MixinFor` the target class. The annotations you put on your mixin class will work as if they were on the target class (if your framework supports Power Annotations). + +This also works for annotations: say we're developing an application packed with annotations from JPA, which doesn't support mixins (yet). The application also uses a library that supports mixins but doesn't know about JPA, e.g. a future MP GraphQL. We want all JPA `@Id` annotations to be recognized as synonyms for GraphQL `@Id` annotations. We could create a simple mixin for the JPA annotation: + +[source,java] +---- +@MixinFor(javax.persistence.Id.class) +@org.eclipse.microprofile.graphql.Id +public class PersistenceIdMixin {} +---- + +Voila! MP GraphQL would work as if all JPA `@Id` annotations were it's own. + +NOTE: Mixins are a very powerful kind of magic: use them with caution and only when strictly necessary. Otherwise, the readers of your code will have a hard time to find out why something behaves as if an annotation was there, but it's clearly not. If you can, use Stereotypes instead. diff --git a/power-annotations/annotations/pom.xml b/power-annotations/annotations/pom.xml new file mode 100644 index 000000000..1464b3f30 --- /dev/null +++ b/power-annotations/annotations/pom.xml @@ -0,0 +1,14 @@ + + + 4.0.0 + + + io.smallrye + smallrye-power-annotations-parent + 1.0.17-SNAPSHOT + + + power-annotations + diff --git a/power-annotations/annotations/src/main/java/com/github/t1/annotations/MixinFor.java b/power-annotations/annotations/src/main/java/com/github/t1/annotations/MixinFor.java new file mode 100644 index 000000000..bd796f41c --- /dev/null +++ b/power-annotations/annotations/src/main/java/com/github/t1/annotations/MixinFor.java @@ -0,0 +1,52 @@ +package com.github.t1.annotations; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Add annotations to another class that you can't change; if you can change that class, + * use explicit {@link Stereotype}s instead. Using Mixins is very implicit and difficult to trace back; + * it is often better to actually copy the target class, so you can directly add your annotations, + * instead of working with mixins. You have been warned! + * + * If you have a class that you don't control, e.g.: + * + *
+ * 
+ *     public class ImportedClass {
+ *         ...
+ *     }
+ * 
+ * 
+ * + * And you need it to be annotated as @SomeAnnotation. Then you can write a mixin (the name is arbitrary): + * + *
+ * 
+ *     @MixinFor(ImportedClass.class)
+ *     @SomeAnnotation
+ *     public class ImportedClassMixin {
+ *         ...
+ *     }
+ * 
+ * 
+ * + * After the Power Annotations have been resolved, the target class looks as if it had another annotation: + * + *
+ * 
+ *     @SomeAnnotation
+ *     public class ImportedClass {
+ *         ...
+ *     }
+ * 
+ * 
+ */ +@Retention(RUNTIME) +@Target(TYPE) +public @interface MixinFor { + Class value(); +} diff --git a/power-annotations/annotations/src/main/java/com/github/t1/annotations/Stereotype.java b/power-annotations/annotations/src/main/java/com/github/t1/annotations/Stereotype.java new file mode 100644 index 000000000..4eeb3daf7 --- /dev/null +++ b/power-annotations/annotations/src/main/java/com/github/t1/annotations/Stereotype.java @@ -0,0 +1,23 @@ +package com.github.t1.annotations; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Marks an annotation to be a meta annotation, i.e. the annotations on the stereotype (i.e. the annotation that is + * annotated as Stereotype) are logically copied to all targets (i.e. the classes/fields/methods that are + * annotated with the stereotype). + * + * Power Annotations doesn't depend on CDI, so it handles all classes named Stereotype the same. + * You can use this class if you don't have another stereotype class on your classpath, + * but in JEE/MP applications you should have javax.enterprise.inject.Stereotype. + */ +@Retention(RUNTIME) +@Target(ANNOTATION_TYPE) +@Documented +public @interface Stereotype { +} diff --git a/power-annotations/common/pom.xml b/power-annotations/common/pom.xml new file mode 100644 index 000000000..156f8ebba --- /dev/null +++ b/power-annotations/common/pom.xml @@ -0,0 +1,21 @@ + + + 4.0.0 + + + io.smallrye + smallrye-power-annotations-parent + 1.0.17-SNAPSHOT + + + power-annotations-common + + + + org.jboss + jandex + + + diff --git a/power-annotations/common/src/main/java/com/github/t1/powerannotations/common/Jandex.java b/power-annotations/common/src/main/java/com/github/t1/powerannotations/common/Jandex.java new file mode 100644 index 000000000..c8e787887 --- /dev/null +++ b/power-annotations/common/src/main/java/com/github/t1/powerannotations/common/Jandex.java @@ -0,0 +1,141 @@ +package com.github.t1.powerannotations.common; + +import static java.nio.file.FileVisitResult.CONTINUE; +import static java.nio.file.Files.createDirectories; +import static java.nio.file.Files.newInputStream; +import static java.nio.file.Files.newOutputStream; +import static java.nio.file.Files.walkFileTree; +import static org.jboss.jandex.JandexBackdoor.annotations; +import static org.jboss.jandex.JandexBackdoor.classes; +import static org.jboss.jandex.JandexBackdoor.implementors; +import static org.jboss.jandex.JandexBackdoor.newAnnotationInstance; +import static org.jboss.jandex.JandexBackdoor.subclasses; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.FileVisitResult; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget; +import org.jboss.jandex.AnnotationValue; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; +import org.jboss.jandex.FieldInfo; +import org.jboss.jandex.Index; +import org.jboss.jandex.IndexWriter; +import org.jboss.jandex.Indexer; +import org.jboss.jandex.JandexBackdoor; +import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.Type; + +public class Jandex { + + public static Jandex scan(Path path) { + return new Jandex(path, scanIndex(path)); + } + + private static Index scanIndex(Path path) { + final Indexer indexer = new Indexer(); + try { + walkFileTree(path, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (file.toString().endsWith(".class")) { + try (InputStream inputStream = newInputStream(file)) { + indexer.index(inputStream); + } + } + return CONTINUE; + } + }); + return indexer.complete(); + } catch (IOException e) { + throw new RuntimeException("failed to index", e); + } + } + + private final Path path; + final Index index; + + private final Map> annotations; + private final Map> subclasses; + private final Map> implementors; + private final Map classes; + + public Jandex(Path path, Index index) { + this.path = path; + this.index = index; + + this.annotations = annotations(index); + this.subclasses = subclasses(index); + this.implementors = implementors(index); + this.classes = classes(index); + } + + public Set allAnnotationNames() { + return annotations.keySet(); + } + + public void copyClassAnnotation(AnnotationInstance original, DotName className) { + ClassInfo classInfo = classes.get(className); + AnnotationInstance copy = copyAnnotationInstance(original, classInfo); + add(copy, annotations(classInfo)); + } + + public void copyFieldAnnotation(AnnotationInstance original, DotName className, String fieldName) { + ClassInfo classInfo = classes.get(className); + FieldInfo field = classInfo.field(fieldName); + AnnotationInstance annotationInstance = copyAnnotationInstance(original, field); + JandexBackdoor.add(annotationInstance, field); + add(annotationInstance, annotations(classInfo)); + } + + public void copyMethodAnnotation(AnnotationInstance original, DotName className, String methodName, Type... parameters) { + ClassInfo classInfo = classes.get(className); + MethodInfo method = classInfo.method(methodName, parameters); + AnnotationInstance annotationInstance = copyAnnotationInstance(original, method); + JandexBackdoor.add(annotationInstance, method); + add(annotationInstance, annotations(classInfo)); + } + + private AnnotationInstance copyAnnotationInstance(AnnotationInstance original, AnnotationTarget annotationTarget) { + return createAnnotationInstance(original.name(), annotationTarget, original.values().toArray(new AnnotationValue[0])); + } + + private AnnotationInstance createAnnotationInstance(DotName annotationName, AnnotationTarget target, + AnnotationValue... values) { + AnnotationInstance annotation = newAnnotationInstance(annotationName, target, values); + add(annotation, annotations); + return annotation; + } + + private void add(AnnotationInstance instance, Map> map) { + if (!map.containsKey(instance.name())) + map.put(instance.name(), new ArrayList<>()); + map.get(instance.name()).add(instance); + } + + public void write() { + Path filePath = path.resolve("META-INF/jandex.idx"); + try { + createDirectories(filePath.getParent()); + OutputStream outputStream = newOutputStream(filePath); + new IndexWriter(outputStream).write(index); + outputStream.close(); + } catch (IOException e) { + throw new RuntimeException("can't write jandex to " + filePath, e); + } + } + + public void print() { + new JandexPrinter(index).run(); + } +} diff --git a/power-annotations/common/src/main/java/com/github/t1/powerannotations/common/JandexPrinter.java b/power-annotations/common/src/main/java/com/github/t1/powerannotations/common/JandexPrinter.java new file mode 100644 index 000000000..af3ce9bf1 --- /dev/null +++ b/power-annotations/common/src/main/java/com/github/t1/powerannotations/common/JandexPrinter.java @@ -0,0 +1,60 @@ +package com.github.t1.powerannotations.common; + +import static java.nio.file.Files.newInputStream; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.function.Consumer; + +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.Index; +import org.jboss.jandex.IndexReader; +import org.jboss.jandex.IndexView; +import org.jboss.jandex.MethodInfo; + +public class JandexPrinter { + + private final IndexView index; + + public JandexPrinter(Path indexFile) { + this(load(indexFile)); + } + + public JandexPrinter(IndexView index) { + this.index = index; + } + + private static IndexView load(Path indexFile) { + System.out.println("load from " + indexFile); + try (InputStream inputStream = new BufferedInputStream(newInputStream(indexFile))) { + return new IndexReader(inputStream).read(); + } catch (IOException e) { + throw new RuntimeException("can't load Jandex index file", e); + } + } + + public void run() { + System.out.println("------------------------------------------------------------"); + ((Index) index).printAnnotations(); + System.out.println("------------------------------------------------------------"); + ((Index) index).printSubclasses(); + System.out.println("------------------------------------------------------------"); + index.getKnownClasses().forEach(new Consumer() { + @Override + public void accept(ClassInfo classInfo) { + if (!classInfo.name().toString().startsWith("test.")) + return; + System.out.println(classInfo.name() + ":"); + classInfo.methods().forEach(new Consumer() { + @Override + public void accept(MethodInfo method) { + System.out.println(" " + method.name() + " [" + method.defaultValue() + "]"); + } + }); + } + }); + System.out.println("------------------------------------------------------------"); + } +} diff --git a/power-annotations/common/src/main/java/com/github/t1/powerannotations/common/Logger.java b/power-annotations/common/src/main/java/com/github/t1/powerannotations/common/Logger.java new file mode 100644 index 000000000..e9292cac6 --- /dev/null +++ b/power-annotations/common/src/main/java/com/github/t1/powerannotations/common/Logger.java @@ -0,0 +1,5 @@ +package com.github.t1.powerannotations.common; + +public interface Logger { + void info(String message); +} diff --git a/power-annotations/common/src/main/java/com/github/t1/powerannotations/common/PowerAnnotations.java b/power-annotations/common/src/main/java/com/github/t1/powerannotations/common/PowerAnnotations.java new file mode 100644 index 000000000..58ab319c8 --- /dev/null +++ b/power-annotations/common/src/main/java/com/github/t1/powerannotations/common/PowerAnnotations.java @@ -0,0 +1,117 @@ +package com.github.t1.powerannotations.common; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; +import org.jboss.jandex.Type; + +public class PowerAnnotations { + private static final DotName MIXIN = DotName.createSimple("com.github.t1.annotations.MixinFor"); + private static final DotName RETENTION = DotName.createSimple("java.lang.annotation.Retention"); + + private final Jandex jandex; + private final Logger log; + + public PowerAnnotations(Jandex jandex, Logger log) { + this.jandex = jandex; + this.log = log; + } + + public void resolveAnnotations() { + resolveMixins(); + resolveStereotypes(); + } + + private void resolveMixins() { + for (AnnotationInstance mixin : getAnnotationInstances(MIXIN)) { + Type mixinTarget = mixin.value().asClass(); + log.info("mix " + mixin.target() + " into " + mixinTarget); + Map> annotations = mixin.target().asClass().annotations(); + for (DotName annotationName : annotations.keySet()) { + if (MIXIN.equals(annotationName)) + continue; + for (AnnotationInstance annotationInstance : annotations.get(annotationName)) { + AnnotationTarget annotationTarget = annotationInstance.target(); + log.info("- " + annotationInstance + " -> " + annotationTarget.kind().name().toLowerCase() + + " " + annotationInstance.target()); + switch (annotationTarget.kind()) { + case CLASS: + jandex.copyClassAnnotation(annotationInstance, mixinTarget.name()); + continue; + case FIELD: + jandex.copyFieldAnnotation(annotationInstance, mixinTarget.name(), + annotationInstance.target().asField().name()); + continue; + case METHOD: + jandex.copyMethodAnnotation(annotationInstance, mixinTarget.name(), + annotationInstance.target().asMethod().name(), + annotationInstance.target().asMethod().parameters().toArray(new Type[0])); + continue; + case TYPE: + case METHOD_PARAMETER: + break; + } + throw new RuntimeException("don't know how to handle a " + annotationTarget.kind() + ": " + + annotationInstance); + } + } + } + } + + private void resolveStereotypes() { + for (DotName stereotypeTypeName : jandex.allAnnotationNames()) { + if (!stereotypeTypeName.withoutPackagePrefix().equals("Stereotype")) + continue; + log.info("stereotype type " + stereotypeTypeName); + for (AnnotationInstance stereotypeAnnotationInstance : new ArrayList<>( + getAnnotationInstances(stereotypeTypeName))) { + ClassInfo stereotype = stereotypeAnnotationInstance.target().asClass(); + log.info("stereotype " + stereotype); + for (AnnotationInstance stereotypeTargetAnnotationInstance : new ArrayList<>( + getAnnotationInstances(stereotype.asClass().name()))) { + AnnotationTarget annotationTarget = stereotypeTargetAnnotationInstance.target(); + log.info("-> " + annotationTarget.kind().name().toLowerCase() + " " + annotationTarget); + Map> annotations = stereotype.annotations(); + for (DotName annotationName : annotations.keySet()) { + if (stereotypeTypeName.equals(annotationName) || RETENTION.equals(annotationName)) + continue; + for (AnnotationInstance annotationInstance : annotations.get(annotationName)) { + log.info(" - " + annotationInstance); + switch (annotationTarget.kind()) { + case CLASS: + jandex.copyClassAnnotation(annotationInstance, + annotationTarget.asClass().name()); + continue; + case FIELD: + jandex.copyFieldAnnotation(annotationInstance, + annotationTarget.asField().declaringClass().name(), + annotationTarget.asField().name()); + continue; + case METHOD: + jandex.copyMethodAnnotation(annotationInstance, + annotationTarget.asMethod().declaringClass().name(), + annotationTarget.asMethod().name(), + annotationTarget.asMethod().parameters().toArray(new Type[0])); + continue; + case TYPE: + case METHOD_PARAMETER: + break; + } + throw new RuntimeException("don't know how to copy to a " + annotationTarget.kind() + ": " + + stereotypeTargetAnnotationInstance); + } + } + } + } + } + } + + private List getAnnotationInstances(DotName annotationName) { + return jandex.index.getAnnotations(annotationName); + } +} diff --git a/power-annotations/common/src/main/java/org/jboss/jandex/JandexBackdoor.java b/power-annotations/common/src/main/java/org/jboss/jandex/JandexBackdoor.java new file mode 100644 index 000000000..fcdc3899c --- /dev/null +++ b/power-annotations/common/src/main/java/org/jboss/jandex/JandexBackdoor.java @@ -0,0 +1,75 @@ +package org.jboss.jandex; + +import static org.jboss.jandex.ReflectionUtils.get; +import static org.jboss.jandex.ReflectionUtils.isArraysArrayList; +import static org.jboss.jandex.ReflectionUtils.isUnmodifiable; +import static org.jboss.jandex.ReflectionUtils.modifiable; +import static org.jboss.jandex.ReflectionUtils.set; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * A lot of methods or fields in Jandex are package private. This is a jump host, + * which is much better than using reflection or putting all of our own stuff in the same package. + * Sometimes even that is not enough and we need reflection. + */ +public class JandexBackdoor { + private JandexBackdoor() { + } + + public static Map> annotations(Index index) { + return modifiable(index.annotations); + } + + public static Map> subclasses(Index index) { + return index.subclasses; + } + + public static Map> implementors(Index index) { + return index.implementors; + } + + public static Map classes(Index index) { + return modifiable(index.classes); + } + + public static Map> annotations(ClassInfo classInfo) { + Map> map = get(classInfo, "annotations"); + if (isUnmodifiable(map)) + map = modifiable(map); + return map; + } + + public static void add(AnnotationInstance annotationInstance, MethodInfo method) { + add(annotationInstance, method.methodInternal()); + } + + private static void add(AnnotationInstance instance, MethodInternal method) { + ArrayList instances = new ArrayList<>(method.annotations()); + instances.add(instance); + method.setAnnotations(instances); + } + + public static void add(AnnotationInstance instance, FieldInfo field) { + ArrayList instances = new ArrayList<>(field.annotations()); + instances.add(instance); + field.setAnnotations(instances); + } + + public static List annotations(MethodInfo methodInfo) { + List list = methodInfo.methodInternal().annotations(); + if (isUnmodifiable(list)) + list = modifiable(list); + if (isArraysArrayList(list)) { + list = new ArrayList<>(list); + set(methodInfo.methodInternal(), "annotations", list); + } + return list; + } + + public static AnnotationInstance newAnnotationInstance(DotName name, AnnotationTarget target, AnnotationValue... values) { + return new AnnotationInstance(name, target, values); + } +} diff --git a/power-annotations/common/src/main/java/org/jboss/jandex/ReflectionUtils.java b/power-annotations/common/src/main/java/org/jboss/jandex/ReflectionUtils.java new file mode 100644 index 000000000..0211a6005 --- /dev/null +++ b/power-annotations/common/src/main/java/org/jboss/jandex/ReflectionUtils.java @@ -0,0 +1,81 @@ +package org.jboss.jandex; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +class ReflectionUtils { + private ReflectionUtils() { + } + + static T get(Object object, String fieldName) { + return get(object.getClass(), object, fieldName); + } + + static T get(Class type, Object object, String fieldName) { + try { + Field field = type.getDeclaredField(fieldName); + field.setAccessible(true); + @SuppressWarnings("unchecked") + T value = (T) field.get(object); + return value; + } catch (ReflectiveOperationException e) { + throw new RuntimeException("can't get field '" + fieldName + "'", e); + } + } + + static void set(Object object, String fieldName, Object value) { + set(object.getClass(), object, fieldName, value); + } + + static void set(Class type, Object object, String fieldName, Object value) { + try { + Field field = type.getDeclaredField(fieldName); + field.setAccessible(true); + field.set(object, value); + } catch (ReflectiveOperationException e) { + throw new RuntimeException("can't get field '" + fieldName + "'", e); + } + } + + static boolean isUnmodifiable(Map> map) { + return UNMODIFIABLE_MAP.equals(map.getClass()); + } + + static Map modifiable(Map map) { + return get(map, "m"); + } + + static boolean isUnmodifiable(List list) { + return UNMODIFIABLE_LIST.equals(list.getClass()) || UNMODIFIABLE_RANDOM_ACCESS_LIST.equals(list.getClass()); + } + + static List modifiable(List list) { + // UnmodifiableRandomAccessList is a subclass of UnmodifiableList + list = get(UNMODIFIABLE_LIST, list, "list"); + return list; + } + + static boolean isArraysArrayList(List list) { + return ARRAY_LIST.equals(list.getClass()); + } + + private static final Class UNMODIFIABLE_MAP = unmodifiableCollectionClass("Map"); + private static final Class UNMODIFIABLE_LIST = unmodifiableCollectionClass("List"); + private static final Class UNMODIFIABLE_RANDOM_ACCESS_LIST = unmodifiableCollectionClass("RandomAccessList"); + private static final Class ARRAY_LIST = classForName(Arrays.class.getName() + "$ArrayList"); + + private static Class unmodifiableCollectionClass(String type) { + return classForName(Collections.class.getName() + "$Unmodifiable" + type); + } + + private static Class classForName(String className) { + try { + return Class.forName(className); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } +} diff --git a/power-annotations/maven-plugin/pom.xml b/power-annotations/maven-plugin/pom.xml new file mode 100644 index 000000000..50018273d --- /dev/null +++ b/power-annotations/maven-plugin/pom.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + + + io.smallrye + smallrye-power-annotations-parent + 1.0.17-SNAPSHOT + + + power-jandex-maven-plugin + maven-plugin + + + 3.5 + + + + 1.7 + 1.7 + true + + 3.6.3 + + + + + org.apache.maven.plugin-tools + maven-plugin-annotations + 3.6.0 + provided + + + org.apache.maven + maven-plugin-api + ${maven.version} + provided + + + org.apache.maven + maven-core + ${maven.version} + provided + + + + io.smallrye + power-annotations-common + ${project.version} + + + diff --git a/power-annotations/maven-plugin/src/main/java/com/github/t1/powerjandex/PowerJandexMojo.java b/power-annotations/maven-plugin/src/main/java/com/github/t1/powerjandex/PowerJandexMojo.java new file mode 100644 index 000000000..9c25cef3d --- /dev/null +++ b/power-annotations/maven-plugin/src/main/java/com/github/t1/powerjandex/PowerJandexMojo.java @@ -0,0 +1,37 @@ +package com.github.t1.powerjandex; + +import static org.apache.maven.plugins.annotations.LifecyclePhase.PROCESS_CLASSES; + +import javax.inject.Inject; + +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.project.MavenProject; + +import com.github.t1.powerannotations.common.Jandex; +import com.github.t1.powerannotations.common.Logger; +import com.github.t1.powerannotations.common.PowerAnnotations; + +@Mojo(name = "power-jandex", defaultPhase = PROCESS_CLASSES, threadSafe = true) +public class PowerJandexMojo extends AbstractMojo { + + @Inject + @SuppressWarnings("CdiInjectionPointsInspection") + MavenProject project; + + @Override + public void execute() { + Jandex jandex = Jandex.scan(project.getBasedir().toPath().resolve("target/classes")); + + new PowerAnnotations(jandex, new MojoLogger()).resolveAnnotations(); + + jandex.write(); + } + + private class MojoLogger implements Logger { + @Override + public void info(String message) { + getLog().info(message); + } + } +} diff --git a/power-annotations/pom.xml b/power-annotations/pom.xml new file mode 100644 index 000000000..12c5dacb7 --- /dev/null +++ b/power-annotations/pom.xml @@ -0,0 +1,21 @@ + + + 4.0.0 + + + io.smallrye + smallrye-graphql-parent + 1.0.17-SNAPSHOT + + + smallrye-power-annotations-parent + pom + + Power Annotations + + + annotations + common + maven-plugin + +