forked from smallrye/smallrye-graphql
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
…h power-jandex-maven-plugin Signed-off-by: Rüdiger zu Dohna <[email protected]>
- Loading branch information
Showing
15 changed files
with
755 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<parent> | ||
<groupId>io.smallrye</groupId> | ||
<artifactId>smallrye-power-annotations-parent</artifactId> | ||
<version>1.0.17-SNAPSHOT</version> | ||
</parent> | ||
|
||
<artifactId>power-annotations</artifactId> | ||
</project> |
52 changes: 52 additions & 0 deletions
52
power-annotations/annotations/src/main/java/com/github/t1/annotations/MixinFor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <em>that you can't change</em>; if you <em>can</em> 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.: | ||
* | ||
* <pre> | ||
* <code> | ||
* public class ImportedClass { | ||
* ... | ||
* } | ||
* </code> | ||
* </pre> | ||
* | ||
* And you need it to be annotated as <code>@SomeAnnotation</code>. Then you can write a mixin (the name is arbitrary): | ||
* | ||
* <pre> | ||
* <code> | ||
* @MixinFor(ImportedClass.class) | ||
* @SomeAnnotation | ||
* public class ImportedClassMixin { | ||
* ... | ||
* } | ||
* </code> | ||
* </pre> | ||
* | ||
* After the Power Annotations have been resolved, the target class looks as if it had another annotation: | ||
* | ||
* <pre> | ||
* <code> | ||
* @SomeAnnotation | ||
* public class ImportedClass { | ||
* ... | ||
* } | ||
* </code> | ||
* </pre> | ||
*/ | ||
@Retention(RUNTIME) | ||
@Target(TYPE) | ||
public @interface MixinFor { | ||
Class<?> value(); | ||
} |
23 changes: 23 additions & 0 deletions
23
power-annotations/annotations/src/main/java/com/github/t1/annotations/Stereotype.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <code>Stereotype</code>) 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 <code>Stereotype</code> 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 <code>javax.enterprise.inject.Stereotype</code>. | ||
*/ | ||
@Retention(RUNTIME) | ||
@Target(ANNOTATION_TYPE) | ||
@Documented | ||
public @interface Stereotype { | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<parent> | ||
<groupId>io.smallrye</groupId> | ||
<artifactId>smallrye-power-annotations-parent</artifactId> | ||
<version>1.0.17-SNAPSHOT</version> | ||
</parent> | ||
|
||
<artifactId>power-annotations-common</artifactId> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>org.jboss</groupId> | ||
<artifactId>jandex</artifactId> | ||
</dependency> | ||
</dependencies> | ||
</project> |
141 changes: 141 additions & 0 deletions
141
power-annotations/common/src/main/java/com/github/t1/powerannotations/common/Jandex.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<Path>() { | ||
@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<DotName, List<AnnotationInstance>> annotations; | ||
private final Map<DotName, List<ClassInfo>> subclasses; | ||
private final Map<DotName, List<ClassInfo>> implementors; | ||
private final Map<DotName, ClassInfo> 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<DotName> 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<DotName, List<AnnotationInstance>> 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(); | ||
} | ||
} |
60 changes: 60 additions & 0 deletions
60
...annotations/common/src/main/java/com/github/t1/powerannotations/common/JandexPrinter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<ClassInfo>() { | ||
@Override | ||
public void accept(ClassInfo classInfo) { | ||
if (!classInfo.name().toString().startsWith("test.")) | ||
return; | ||
System.out.println(classInfo.name() + ":"); | ||
classInfo.methods().forEach(new Consumer<MethodInfo>() { | ||
@Override | ||
public void accept(MethodInfo method) { | ||
System.out.println(" " + method.name() + " [" + method.defaultValue() + "]"); | ||
} | ||
}); | ||
} | ||
}); | ||
System.out.println("------------------------------------------------------------"); | ||
} | ||
} |
5 changes: 5 additions & 0 deletions
5
power-annotations/common/src/main/java/com/github/t1/powerannotations/common/Logger.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package com.github.t1.powerannotations.common; | ||
|
||
public interface Logger { | ||
void info(String message); | ||
} |
Oops, something went wrong.