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
+
+