[annotation-inheritance, type-utils] Implement module for annotation inheritance
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
[versions]
|
[versions]
|
||||||
assertj = "3.26.3"
|
assertj = "3.26.3"
|
||||||
|
asm = "9.7"
|
||||||
autoservice = "1.1.1"
|
autoservice = "1.1.1"
|
||||||
acl = "1.3.5"
|
acl = "1.3.5"
|
||||||
java-main = "8"
|
java-main = "8"
|
||||||
@@ -17,6 +18,8 @@ lombok = { id = "io.freefair.lombok", version = "8.13.1" }
|
|||||||
[libraries]
|
[libraries]
|
||||||
acl = { group = "commons-logging", name = "commons-logging", version.ref = "acl" }
|
acl = { group = "commons-logging", name = "commons-logging", version.ref = "acl" }
|
||||||
assertj = { group = "org.assertj", name = "assertj-core", version.ref = "assertj" }
|
assertj = { group = "org.assertj", name = "assertj-core", version.ref = "assertj" }
|
||||||
|
asm-commons = { group = "org.ow2.asm", name = "asm-commons", version.ref = "asm" }
|
||||||
|
asm-core = { group = "org.ow2.asm", name = "asm", version.ref = "asm" }
|
||||||
autoservice-annotations = { group = "com.google.auto.service", name = "auto-service-annotations", version.ref = "autoservice" }
|
autoservice-annotations = { group = "com.google.auto.service", name = "auto-service-annotations", version.ref = "autoservice" }
|
||||||
autoservice-processor = { group = "com.google.auto.service", name = "auto-service", version.ref = "autoservice" }
|
autoservice-processor = { group = "com.google.auto.service", name = "auto-service", version.ref = "autoservice" }
|
||||||
jetbrains-annotations = { group = "org.jetbrains", name = "annotations", version.ref = "jetbrains-annotations" }
|
jetbrains-annotations = { group = "org.jetbrains", name = "annotations", version.ref = "jetbrains-annotations" }
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
rootProject.name = "tweed5"
|
rootProject.name = "tweed5"
|
||||||
|
|
||||||
include("test-utils")
|
include("test-utils")
|
||||||
|
include("tweed5-annotation-inheritance")
|
||||||
include("tweed5-construct")
|
include("tweed5-construct")
|
||||||
include("tweed5-core")
|
include("tweed5-core")
|
||||||
include("tweed5-default-extensions")
|
include("tweed5-default-extensions")
|
||||||
|
|||||||
9
tweed5-annotation-inheritance/build.gradle.kts
Normal file
9
tweed5-annotation-inheritance/build.gradle.kts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
plugins {
|
||||||
|
id("de.siphalor.tweed5.base-module")
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":tweed5-utils"))
|
||||||
|
implementation(project(":tweed5-type-utils"))
|
||||||
|
implementation(libs.asm.core)
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package de.siphalor.tweed5.annotationinheritance.api;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
@Target(ElementType.ANNOTATION_TYPE)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface AnnotationInheritance {
|
||||||
|
Class<? extends Annotation>[] passOn() default {};
|
||||||
|
Class<? extends Annotation>[] override() default {};
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package de.siphalor.tweed5.annotationinheritance.api;
|
||||||
|
|
||||||
|
import de.siphalor.tweed5.annotationinheritance.impl.AnnotationInheritanceResolver;
|
||||||
|
import de.siphalor.tweed5.typeutils.api.annotations.AnnotationRepeatType;
|
||||||
|
import de.siphalor.tweed5.utils.api.collection.ClassToInstanceMap;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.jspecify.annotations.NonNull;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.reflect.AnnotatedElement;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AnnotationInheritanceAwareAnnotatedElement implements AnnotatedElement {
|
||||||
|
private final AnnotatedElement inner;
|
||||||
|
private @Nullable ClassToInstanceMap<Annotation> resolvedAnnotations;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable <T extends Annotation> T getAnnotation(@NonNull Class<T> annotationClass) {
|
||||||
|
if (resolvedAnnotations != null) {
|
||||||
|
return resolvedAnnotations.get(annotationClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
Annotation[] annotations = inner.getAnnotations();
|
||||||
|
boolean metaEncountered = false;
|
||||||
|
T foundAnnotation = null;
|
||||||
|
|
||||||
|
for (Annotation annotation : annotations) {
|
||||||
|
if (annotation.annotationType().equals(annotationClass)) {
|
||||||
|
//noinspection unchecked
|
||||||
|
foundAnnotation = (T) annotation;
|
||||||
|
} else if (!metaEncountered) {
|
||||||
|
metaEncountered = annotation.annotationType().isAnnotationPresent(AnnotationInheritance.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundAnnotation != null) {
|
||||||
|
AnnotationRepeatType repeatType = AnnotationRepeatType.getType(annotationClass);
|
||||||
|
if (repeatType instanceof AnnotationRepeatType.NonRepeatable) {
|
||||||
|
return foundAnnotation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!metaEncountered) {
|
||||||
|
return foundAnnotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getOrResolveAnnotations().get(annotationClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Annotation[] getAnnotations() {
|
||||||
|
return getOrResolveAnnotations().values().toArray(new Annotation[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Annotation[] getDeclaredAnnotations() {
|
||||||
|
return inner.getDeclaredAnnotations();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClassToInstanceMap<Annotation> getOrResolveAnnotations() {
|
||||||
|
if (resolvedAnnotations == null) {
|
||||||
|
resolvedAnnotations = new AnnotationInheritanceResolver(inner).resolve();
|
||||||
|
}
|
||||||
|
return resolvedAnnotations;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
@NullMarked
|
||||||
|
package de.siphalor.tweed5.annotationinheritance.api;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
package de.siphalor.tweed5.annotationinheritance.impl;
|
||||||
|
|
||||||
|
import de.siphalor.tweed5.annotationinheritance.api.AnnotationInheritance;
|
||||||
|
import de.siphalor.tweed5.typeutils.api.annotations.AnnotationRepeatType;
|
||||||
|
import de.siphalor.tweed5.utils.api.collection.ClassToInstanceMap;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.Value;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
import java.lang.reflect.AnnotatedElement;
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AnnotationInheritanceResolver {
|
||||||
|
private final AnnotatedElement main;
|
||||||
|
private final Map<Class<? extends Annotation>, Aggregator> aggregators = new LinkedHashMap<>();
|
||||||
|
private static final Set<Class<? extends Annotation>> IGNORED_META_ANNOTATIONS = new CopyOnWriteArraySet<>(Arrays.asList(
|
||||||
|
Target.class,
|
||||||
|
Retention.class,
|
||||||
|
AnnotationInheritance.class
|
||||||
|
));
|
||||||
|
|
||||||
|
public ClassToInstanceMap<Annotation> resolve() {
|
||||||
|
resolve(main, Collections.emptySet());
|
||||||
|
|
||||||
|
ClassToInstanceMap<Annotation> resolvedAnnotations = new ClassToInstanceMap<>();
|
||||||
|
|
||||||
|
List<Aggregator> aggregatorList = new ArrayList<>(aggregators.values());
|
||||||
|
for (int i = aggregatorList.size() - 1; i >= 0; i--) {
|
||||||
|
Aggregator aggregator = aggregatorList.get(i);
|
||||||
|
if (aggregator.annotations.size() == 1) {
|
||||||
|
//noinspection unchecked
|
||||||
|
resolvedAnnotations.put(
|
||||||
|
(Class<Annotation>) aggregator.repeatType.annotationClass(),
|
||||||
|
aggregator.annotations.iterator().next()
|
||||||
|
);
|
||||||
|
} else if (!aggregator.annotations.isEmpty()) {
|
||||||
|
Annotation[] annotations = (Annotation[]) Array.newInstance(
|
||||||
|
aggregator.repeatType.annotationClass(),
|
||||||
|
aggregator.annotations.size()
|
||||||
|
);
|
||||||
|
int j = aggregator.annotations.size() - 1;
|
||||||
|
for (Annotation annotation : aggregator.annotations) {
|
||||||
|
annotations[j--] = annotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
Annotation containerAnnotation = RepeatableAnnotationContainerHelper.createContainer(annotations);
|
||||||
|
//noinspection unchecked
|
||||||
|
resolvedAnnotations.put((Class<Annotation>) containerAnnotation.annotationType(), containerAnnotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolvedAnnotations;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resolve(AnnotatedElement annotatedElement, Set<Class<? extends Annotation>> overriden) {
|
||||||
|
AnnotationInheritance inheritanceConfig = annotatedElement.getAnnotation(AnnotationInheritance.class);
|
||||||
|
|
||||||
|
Set<Class<? extends Annotation>> passOnAnnotations = null;
|
||||||
|
Set<Class<? extends Annotation>> overridenOnwards = new HashSet<>(overriden);
|
||||||
|
if (annotatedElement != main) {
|
||||||
|
if (inheritanceConfig == null || inheritanceConfig.passOn().length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
passOnAnnotations = new HashSet<>(inheritanceConfig.passOn().length + 5);
|
||||||
|
for (Class<? extends Annotation> passOn : inheritanceConfig.passOn()) {
|
||||||
|
passOnAnnotations.add(passOn);
|
||||||
|
AnnotationRepeatType repeatType = AnnotationRepeatType.getType(passOn);
|
||||||
|
if (repeatType instanceof AnnotationRepeatType.Repeatable) {
|
||||||
|
passOnAnnotations.add(((AnnotationRepeatType.Repeatable) repeatType).containerAnnotationClass());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (inheritanceConfig != null) {
|
||||||
|
for (Class<? extends Annotation> override : inheritanceConfig.override()) {
|
||||||
|
overridenOnwards.add(override);
|
||||||
|
AnnotationRepeatType repeatType = AnnotationRepeatType.getType(override);
|
||||||
|
if (repeatType instanceof AnnotationRepeatType.Repeatable) {
|
||||||
|
overridenOnwards.add(((AnnotationRepeatType.Repeatable) repeatType).containerAnnotationClass());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Annotation[] annotations = annotatedElement.getAnnotations();
|
||||||
|
for (int i = annotations.length - 1; i >= 0; i--) {
|
||||||
|
Annotation annotation = annotations[i];
|
||||||
|
if ((passOnAnnotations != null && !passOnAnnotations.contains(annotation.annotationType()))
|
||||||
|
|| IGNORED_META_ANNOTATIONS.contains(annotation.annotationType())
|
||||||
|
|| overriden.contains(annotation.annotationType())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Aggregator aggregator = aggregators.get(annotation.annotationType());
|
||||||
|
AnnotationRepeatType repeatType;
|
||||||
|
if (aggregator != null) {
|
||||||
|
repeatType = aggregator.repeatType;
|
||||||
|
if (repeatType instanceof AnnotationRepeatType.Repeatable) {
|
||||||
|
aggregator.annotations.add(annotation);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
repeatType = AnnotationRepeatType.getType(annotation.annotationType());
|
||||||
|
if (repeatType instanceof AnnotationRepeatType.NonRepeatable) {
|
||||||
|
aggregator = new Aggregator(repeatType, Collections.singleton(annotation));
|
||||||
|
aggregators.put(annotation.annotationType(), aggregator);
|
||||||
|
overridenOnwards.add(annotation.annotationType());
|
||||||
|
} else if (repeatType instanceof AnnotationRepeatType.Repeatable) {
|
||||||
|
ArrayList<Annotation> repeatableAnnotations = new ArrayList<>();
|
||||||
|
repeatableAnnotations.add(annotation);
|
||||||
|
aggregator = new Aggregator(repeatType, repeatableAnnotations);
|
||||||
|
aggregators.put(annotation.annotationType(), aggregator);
|
||||||
|
} else if (repeatType instanceof AnnotationRepeatType.RepeatableContainer) {
|
||||||
|
AnnotationRepeatType.RepeatableContainer containerRepeatType
|
||||||
|
= (AnnotationRepeatType.RepeatableContainer) repeatType;
|
||||||
|
Class<? extends Annotation> elementAnnotationType = containerRepeatType.elementAnnotationClass();
|
||||||
|
|
||||||
|
Annotation[] elements = containerRepeatType.elements(annotation);
|
||||||
|
|
||||||
|
aggregator = aggregators.get(elementAnnotationType);
|
||||||
|
if (aggregator != null) {
|
||||||
|
for (int j = elements.length - 1; j >= 0; j--) {
|
||||||
|
aggregator.annotations.add(elements[j]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
List<Annotation> repeatedAnnotations = new ArrayList<>(elements.length);
|
||||||
|
for (int e = elements.length - 1; e >= 0; e--) {
|
||||||
|
repeatedAnnotations.add(elements[e]);
|
||||||
|
}
|
||||||
|
aggregators.put(
|
||||||
|
containerRepeatType.elementAnnotationClass(),
|
||||||
|
new Aggregator(containerRepeatType.elementRepeatType(), repeatedAnnotations)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (repeatType instanceof AnnotationRepeatType.NonRepeatable && annotation.annotationType()
|
||||||
|
.isAnnotationPresent(AnnotationInheritance.class)) {
|
||||||
|
resolve(annotation.annotationType(), overridenOnwards);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Value
|
||||||
|
private static class Aggregator {
|
||||||
|
AnnotationRepeatType repeatType;
|
||||||
|
Collection<Annotation> annotations;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package de.siphalor.tweed5.annotationinheritance.impl;
|
||||||
|
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
|
class ByteArrayClassLoader extends ClassLoader {
|
||||||
|
public static Class<?> loadClass(@Nullable String binaryClassName, byte[] byteCode) {
|
||||||
|
return new ByteArrayClassLoader(ByteArrayClassLoader.class.getClassLoader()).createClass(
|
||||||
|
binaryClassName,
|
||||||
|
byteCode
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ByteArrayClassLoader(ClassLoader parent) {
|
||||||
|
super(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<?> createClass(@Nullable String binaryClassName, byte[] byteCode) {
|
||||||
|
Class<?> clazz = defineClass(binaryClassName, byteCode, 0, byteCode.length);
|
||||||
|
resolveClass(clazz);
|
||||||
|
return clazz;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,387 @@
|
|||||||
|
package de.siphalor.tweed5.annotationinheritance.impl;
|
||||||
|
|
||||||
|
import de.siphalor.tweed5.typeutils.api.annotations.AnnotationRepeatType;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.objectweb.asm.*;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.invoke.MethodHandle;
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.lang.invoke.MethodType;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.locks.ReadWriteLock;
|
||||||
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public class RepeatableAnnotationContainerHelper {
|
||||||
|
/**
|
||||||
|
* Class version to use for generation (Java 8)
|
||||||
|
*/
|
||||||
|
private static final int CLASS_VERSION = Opcodes.V1_8;
|
||||||
|
private static final String GENERATED_PACKAGE = RepeatableAnnotationContainerHelper.class.getPackage().getName()
|
||||||
|
+ ".generated";
|
||||||
|
|
||||||
|
private static String generateUniqueIdentifier() {
|
||||||
|
UUID uuid = UUID.randomUUID();
|
||||||
|
return uuid.toString().replace("-", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Map<Class<? extends Annotation>, Function<Annotation[], Annotation>> CACHE = new HashMap<>();
|
||||||
|
private static final ReadWriteLock CACHE_LOCK = new ReentrantReadWriteLock();
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static <R extends Annotation, C extends Annotation> C createContainer(R[] elements) {
|
||||||
|
if (elements.length == 0) {
|
||||||
|
throw new IllegalArgumentException("elements must not be empty");
|
||||||
|
}
|
||||||
|
Class<R> repeatableClass = (Class<R>) elements[0].annotationType();
|
||||||
|
AnnotationRepeatType repeatType = AnnotationRepeatType.getType(repeatableClass);
|
||||||
|
if (!(repeatType instanceof AnnotationRepeatType.Repeatable)) {
|
||||||
|
throw new IllegalArgumentException(repeatableClass.getName() + " is not a repeatable");
|
||||||
|
}
|
||||||
|
Class<? extends Annotation> containerClass
|
||||||
|
= ((AnnotationRepeatType.Repeatable) repeatType).containerAnnotationClass();
|
||||||
|
|
||||||
|
CACHE_LOCK.readLock().lock();
|
||||||
|
try {
|
||||||
|
Function<Annotation[], Annotation> constructor = CACHE.get(containerClass);
|
||||||
|
if (constructor != null) {
|
||||||
|
return (C) constructor.apply(elements);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
CACHE_LOCK.readLock().unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
Function<R[], ? extends Annotation> constructor = createContainerClassConstructor(
|
||||||
|
containerClass,
|
||||||
|
repeatableClass
|
||||||
|
);
|
||||||
|
CACHE_LOCK.writeLock().lock();
|
||||||
|
try {
|
||||||
|
//noinspection rawtypes
|
||||||
|
CACHE.put(containerClass, (Function) constructor);
|
||||||
|
} finally {
|
||||||
|
CACHE_LOCK.writeLock().unlock();
|
||||||
|
}
|
||||||
|
return (C) constructor.apply(elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static <R extends Annotation, C extends Annotation> Function<R[], C> createContainerClassConstructor(
|
||||||
|
Class<C> containerClass,
|
||||||
|
Class<R> repeatableClass
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
if (!containerClass.isAnnotation()) {
|
||||||
|
throw new IllegalArgumentException(containerClass.getName() + " is not a container annotation");
|
||||||
|
}
|
||||||
|
|
||||||
|
String generatedClassName = GENERATED_PACKAGE + ".RepeatableContainer$" + generateUniqueIdentifier();
|
||||||
|
byte[] bytes = createContainerClassBytes(generatedClassName, containerClass, repeatableClass);
|
||||||
|
Class<?>
|
||||||
|
generatedClass
|
||||||
|
= new ByteArrayClassLoader(RepeatableAnnotationContainerHelper.class.getClassLoader())
|
||||||
|
.createClass(generatedClassName, bytes);
|
||||||
|
|
||||||
|
MethodHandle constructorHandle = MethodHandles.lookup().findConstructor(
|
||||||
|
generatedClass,
|
||||||
|
MethodType.methodType(void.class, repeatableClass.arrayType())
|
||||||
|
);
|
||||||
|
|
||||||
|
return (repeatedValues) -> {
|
||||||
|
try {
|
||||||
|
return (C) constructorHandle.invoke((Object) repeatedValues);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw new RuntimeException("Failed to instantiate generated container annotation", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalStateException("Class generation failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static <R extends Annotation, C extends Annotation> byte[] createContainerClassBytes(
|
||||||
|
String generatedClassName,
|
||||||
|
Class<C> containerClass,
|
||||||
|
Class<R> repeatableClass
|
||||||
|
) {
|
||||||
|
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
|
||||||
|
String generatedClassNameInternal = generatedClassName.replace('.', '/');
|
||||||
|
classWriter.visit(
|
||||||
|
CLASS_VERSION,
|
||||||
|
Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC,
|
||||||
|
generatedClassNameInternal,
|
||||||
|
null,
|
||||||
|
"java/lang/Object",
|
||||||
|
new String[]{containerClass.getName().replace('.', '/')}
|
||||||
|
);
|
||||||
|
Class<?> repeatableArrayClass = repeatableClass.arrayType();
|
||||||
|
|
||||||
|
classWriter.visitField(Opcodes.ACC_PRIVATE, "values", repeatableArrayClass.descriptorString(), null, null);
|
||||||
|
|
||||||
|
appendConstructor(classWriter, repeatableArrayClass, generatedClassNameInternal);
|
||||||
|
appendValueMethod(classWriter, repeatableArrayClass, generatedClassNameInternal);
|
||||||
|
appendEqualsMethod(classWriter, repeatableArrayClass, containerClass, generatedClassNameInternal);
|
||||||
|
appendHashCodeMethod(classWriter, repeatableArrayClass, generatedClassNameInternal);
|
||||||
|
appendToStringMethod(classWriter, repeatableArrayClass, containerClass, generatedClassNameInternal);
|
||||||
|
appendAnnotationTypeMethod(classWriter, containerClass);
|
||||||
|
|
||||||
|
classWriter.visitEnd();
|
||||||
|
|
||||||
|
return classWriter.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void appendConstructor(
|
||||||
|
ClassWriter classWriter,
|
||||||
|
Class<?> repeatableArrayClass,
|
||||||
|
String generatedClassNameInternal
|
||||||
|
) {
|
||||||
|
MethodVisitor methodWriter = classWriter.visitMethod(
|
||||||
|
Opcodes.ACC_PUBLIC,
|
||||||
|
"<init>",
|
||||||
|
"(" + repeatableArrayClass.descriptorString() + ")V",
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
methodWriter.visitParameter("values", 0);
|
||||||
|
methodWriter.visitCode();
|
||||||
|
methodWriter.visitVarInsn(Opcodes.ALOAD, 0);
|
||||||
|
methodWriter.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
|
||||||
|
methodWriter.visitVarInsn(Opcodes.ALOAD, 0);
|
||||||
|
methodWriter.visitVarInsn(Opcodes.ALOAD, 1);
|
||||||
|
methodWriter.visitFieldInsn(
|
||||||
|
Opcodes.PUTFIELD,
|
||||||
|
generatedClassNameInternal,
|
||||||
|
"values",
|
||||||
|
repeatableArrayClass.descriptorString()
|
||||||
|
);
|
||||||
|
methodWriter.visitInsn(Opcodes.RETURN);
|
||||||
|
methodWriter.visitMaxs(2, 2);
|
||||||
|
methodWriter.visitEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void appendValueMethod(
|
||||||
|
ClassWriter classWriter,
|
||||||
|
Class<?> repeatableArrayClass,
|
||||||
|
String generatedClassNameInternal
|
||||||
|
) {
|
||||||
|
MethodVisitor methodWriter = classWriter.visitMethod(
|
||||||
|
Opcodes.ACC_PUBLIC,
|
||||||
|
"value",
|
||||||
|
"()" + repeatableArrayClass.descriptorString(),
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
methodWriter.visitCode();
|
||||||
|
methodWriter.visitVarInsn(Opcodes.ALOAD, 0);
|
||||||
|
methodWriter.visitFieldInsn(
|
||||||
|
Opcodes.GETFIELD,
|
||||||
|
generatedClassNameInternal,
|
||||||
|
"values",
|
||||||
|
repeatableArrayClass.descriptorString()
|
||||||
|
);
|
||||||
|
methodWriter.visitInsn(Opcodes.ARETURN);
|
||||||
|
methodWriter.visitMaxs(1, 1);
|
||||||
|
methodWriter.visitEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void appendEqualsMethod(
|
||||||
|
ClassWriter classWriter,
|
||||||
|
Class<?> repeatableArrayClass,
|
||||||
|
Class<?> containerAnnotationClass,
|
||||||
|
String generatedClassNameInternal
|
||||||
|
) {
|
||||||
|
MethodVisitor methodWriter = classWriter.visitMethod(
|
||||||
|
Opcodes.ACC_PUBLIC,
|
||||||
|
"equals",
|
||||||
|
"(Ljava/lang/Object;)Z",
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
methodWriter.visitParameter("other", 0);
|
||||||
|
Label falseLabel = new Label();
|
||||||
|
|
||||||
|
methodWriter.visitCode();
|
||||||
|
methodWriter.visitVarInsn(Opcodes.ALOAD, 1);
|
||||||
|
String containerAnnotationClassBinaryName = Type.getInternalName(containerAnnotationClass);
|
||||||
|
methodWriter.visitTypeInsn(Opcodes.INSTANCEOF, containerAnnotationClassBinaryName);
|
||||||
|
methodWriter.visitJumpInsn(Opcodes.IFEQ, falseLabel);
|
||||||
|
|
||||||
|
methodWriter.visitVarInsn(Opcodes.ALOAD, 0);
|
||||||
|
methodWriter.visitFieldInsn(
|
||||||
|
Opcodes.GETFIELD,
|
||||||
|
generatedClassNameInternal,
|
||||||
|
"values",
|
||||||
|
repeatableArrayClass.descriptorString()
|
||||||
|
);
|
||||||
|
methodWriter.visitVarInsn(Opcodes.ALOAD, 1);
|
||||||
|
methodWriter.visitMethodInsn(
|
||||||
|
Opcodes.INVOKEINTERFACE,
|
||||||
|
containerAnnotationClassBinaryName,
|
||||||
|
"value",
|
||||||
|
"()" + repeatableArrayClass.descriptorString(),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
methodWriter.visitMethodInsn(
|
||||||
|
Opcodes.INVOKESTATIC,
|
||||||
|
"java/util/Arrays",
|
||||||
|
"equals",
|
||||||
|
"([Ljava/lang/Object;[Ljava/lang/Object;)Z",
|
||||||
|
false
|
||||||
|
);
|
||||||
|
methodWriter.visitInsn(Opcodes.IRETURN);
|
||||||
|
|
||||||
|
methodWriter.visitLabel(falseLabel);
|
||||||
|
methodWriter.visitLdcInsn(false);
|
||||||
|
methodWriter.visitInsn(Opcodes.IRETURN);
|
||||||
|
|
||||||
|
methodWriter.visitMaxs(0, 0);
|
||||||
|
methodWriter.visitEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void appendHashCodeMethod(
|
||||||
|
ClassWriter classWriter,
|
||||||
|
Class<?> repeatableArrayClass,
|
||||||
|
String generatedClassNameInternal
|
||||||
|
) {
|
||||||
|
MethodVisitor methodWriter = classWriter.visitMethod(
|
||||||
|
Opcodes.ACC_PUBLIC,
|
||||||
|
"hashCode",
|
||||||
|
"()I",
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
final int keyHashCode = "value".hashCode() * 127;
|
||||||
|
methodWriter.visitCode();
|
||||||
|
methodWriter.visitVarInsn(Opcodes.ALOAD, 0);
|
||||||
|
methodWriter.visitFieldInsn(
|
||||||
|
Opcodes.GETFIELD,
|
||||||
|
generatedClassNameInternal,
|
||||||
|
"values",
|
||||||
|
repeatableArrayClass.descriptorString()
|
||||||
|
);
|
||||||
|
methodWriter.visitMethodInsn(
|
||||||
|
Opcodes.INVOKESTATIC,
|
||||||
|
"java/util/Arrays",
|
||||||
|
"hashCode",
|
||||||
|
"([Ljava/lang/Object;)I",
|
||||||
|
false
|
||||||
|
);
|
||||||
|
methodWriter.visitLdcInsn(keyHashCode);
|
||||||
|
methodWriter.visitInsn(Opcodes.IXOR);
|
||||||
|
methodWriter.visitInsn(Opcodes.IRETURN);
|
||||||
|
methodWriter.visitMaxs(2, 2);
|
||||||
|
methodWriter.visitEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void appendToStringMethod(
|
||||||
|
ClassWriter classWriter,
|
||||||
|
Class<?> repeatableArrayClass,
|
||||||
|
Class<?> containerAnnotationClass,
|
||||||
|
String generatedClassNameInternal
|
||||||
|
) {
|
||||||
|
MethodVisitor methodWriter = classWriter.visitMethod(
|
||||||
|
Opcodes.ACC_PUBLIC,
|
||||||
|
"toString",
|
||||||
|
"()Ljava/lang/String;",
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
String prefix = "@" + containerAnnotationClass.getName() + "(value=";
|
||||||
|
String suffix = ")";
|
||||||
|
String stringBuilderBinaryName = StringBuilder.class.getName().replace('.', '/');
|
||||||
|
methodWriter.visitCode();
|
||||||
|
methodWriter.visitVarInsn(Opcodes.ALOAD, 0);
|
||||||
|
methodWriter.visitFieldInsn(
|
||||||
|
Opcodes.GETFIELD,
|
||||||
|
generatedClassNameInternal,
|
||||||
|
"values",
|
||||||
|
repeatableArrayClass.descriptorString()
|
||||||
|
);
|
||||||
|
methodWriter.visitMethodInsn(
|
||||||
|
Opcodes.INVOKESTATIC,
|
||||||
|
"java/util/Arrays",
|
||||||
|
"toString",
|
||||||
|
"([Ljava/lang/Object;)Ljava/lang/String;",
|
||||||
|
false
|
||||||
|
);
|
||||||
|
methodWriter.visitInsn(Opcodes.DUP);
|
||||||
|
methodWriter.visitMethodInsn(
|
||||||
|
Opcodes.INVOKEVIRTUAL,
|
||||||
|
"java/lang/String",
|
||||||
|
"length",
|
||||||
|
"()I",
|
||||||
|
false
|
||||||
|
);
|
||||||
|
methodWriter.visitLdcInsn(prefix.length() + suffix.length());
|
||||||
|
methodWriter.visitInsn(Opcodes.IADD);
|
||||||
|
methodWriter.visitTypeInsn(Opcodes.NEW, stringBuilderBinaryName);
|
||||||
|
methodWriter.visitInsn(Opcodes.DUP_X1); // S, I, SB -> S, SB, I, SB
|
||||||
|
methodWriter.visitInsn(Opcodes.SWAP); // S, SB, I, SB -> S, SB, SB, I
|
||||||
|
methodWriter.visitMethodInsn(
|
||||||
|
Opcodes.INVOKESPECIAL,
|
||||||
|
stringBuilderBinaryName,
|
||||||
|
"<init>",
|
||||||
|
"(I)V",
|
||||||
|
false
|
||||||
|
);
|
||||||
|
methodWriter.visitLdcInsn(prefix);
|
||||||
|
methodWriter.visitMethodInsn(
|
||||||
|
Opcodes.INVOKEVIRTUAL,
|
||||||
|
stringBuilderBinaryName,
|
||||||
|
"append",
|
||||||
|
"(Ljava/lang/CharSequence;)L" + stringBuilderBinaryName + ";",
|
||||||
|
false
|
||||||
|
);
|
||||||
|
methodWriter.visitInsn(Opcodes.SWAP); // S, SB -> SB, S
|
||||||
|
methodWriter.visitMethodInsn(
|
||||||
|
Opcodes.INVOKEVIRTUAL,
|
||||||
|
stringBuilderBinaryName,
|
||||||
|
"append",
|
||||||
|
"(Ljava/lang/String;)L" + stringBuilderBinaryName + ";",
|
||||||
|
false
|
||||||
|
);
|
||||||
|
methodWriter.visitLdcInsn(suffix);
|
||||||
|
methodWriter.visitMethodInsn(
|
||||||
|
Opcodes.INVOKEVIRTUAL,
|
||||||
|
stringBuilderBinaryName,
|
||||||
|
"append",
|
||||||
|
"(Ljava/lang/CharSequence;)L" + stringBuilderBinaryName + ";",
|
||||||
|
false
|
||||||
|
);
|
||||||
|
methodWriter.visitMethodInsn(
|
||||||
|
Opcodes.INVOKEVIRTUAL,
|
||||||
|
stringBuilderBinaryName,
|
||||||
|
"toString",
|
||||||
|
"()Ljava/lang/String;",
|
||||||
|
false
|
||||||
|
);
|
||||||
|
methodWriter.visitInsn(Opcodes.ARETURN);
|
||||||
|
methodWriter.visitMaxs(0, 0);
|
||||||
|
methodWriter.visitEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void appendAnnotationTypeMethod(
|
||||||
|
ClassWriter classWriter,
|
||||||
|
Class<?> containerAnnotationClass
|
||||||
|
) {
|
||||||
|
MethodVisitor methodWriter = classWriter.visitMethod(
|
||||||
|
Opcodes.ACC_PUBLIC,
|
||||||
|
"annotationType",
|
||||||
|
"()Ljava/lang/Class;",
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
methodWriter.visitCode();
|
||||||
|
methodWriter.visitLdcInsn(Type.getType(containerAnnotationClass));
|
||||||
|
methodWriter.visitInsn(Opcodes.ARETURN);
|
||||||
|
methodWriter.visitMaxs(1, 1);
|
||||||
|
methodWriter.visitEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
/**
|
||||||
|
* Package for on-the-fly-generated classes.
|
||||||
|
*/
|
||||||
|
package de.siphalor.tweed5.annotationinheritance.impl.generated;
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
@ApiStatus.Internal
|
||||||
|
@NullMarked
|
||||||
|
package de.siphalor.tweed5.annotationinheritance.impl;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
package de.siphalor.tweed5.annotationinheritance.api;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.InstanceOfAssertFactories.array;
|
||||||
|
import static org.assertj.core.api.InstanceOfAssertFactories.type;
|
||||||
|
|
||||||
|
class AnnotationInheritanceAwareAnnotatedElementTest {
|
||||||
|
@Test
|
||||||
|
void getAnnotationAForTarget1() {
|
||||||
|
var element = new AnnotationInheritanceAwareAnnotatedElement(Target1.class);
|
||||||
|
assertThat(element.getAnnotation(A.class))
|
||||||
|
.isNotNull()
|
||||||
|
.extracting(A::value)
|
||||||
|
.isEqualTo(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getAnnotationBForTarget1() {
|
||||||
|
var element = new AnnotationInheritanceAwareAnnotatedElement(Target1.class);
|
||||||
|
assertThat(element.getAnnotation(B.class))
|
||||||
|
.isNotNull()
|
||||||
|
.extracting(B::value)
|
||||||
|
.isEqualTo(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getAnnotationCForTarget1() {
|
||||||
|
var element = new AnnotationInheritanceAwareAnnotatedElement(Target1.class);
|
||||||
|
assertThat(element.getAnnotation(C.class))
|
||||||
|
.isNotNull()
|
||||||
|
.extracting(C::value)
|
||||||
|
.isEqualTo(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getAnnotationRForTarget1() {
|
||||||
|
var element = new AnnotationInheritanceAwareAnnotatedElement(Target1.class);
|
||||||
|
assertThat(element.getAnnotation(R.class)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getAnnotationRsForTarget1() {
|
||||||
|
var element = new AnnotationInheritanceAwareAnnotatedElement(Target1.class);
|
||||||
|
Rs rs = element.getAnnotation(Rs.class);
|
||||||
|
assertThat(rs)
|
||||||
|
.isNotNull()
|
||||||
|
.extracting(Rs::value)
|
||||||
|
.asInstanceOf(array(R[].class))
|
||||||
|
.satisfiesExactly(
|
||||||
|
r -> assertThat(r.value()).isEqualTo(4),
|
||||||
|
r -> assertThat(r.value()).isEqualTo(2),
|
||||||
|
r -> assertThat(r.value()).isEqualTo(3),
|
||||||
|
r -> assertThat(r.value()).isEqualTo(10)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getAnnotationsForTarget1() {
|
||||||
|
var element = new AnnotationInheritanceAwareAnnotatedElement(Target1.class);
|
||||||
|
assertThat(element.getAnnotations())
|
||||||
|
.satisfiesExactlyInAnyOrder(
|
||||||
|
a -> assertThat(a).isInstanceOf(BequeatherThree.class),
|
||||||
|
a -> assertThat(a).isInstanceOf(BequeatherTwo.class),
|
||||||
|
a -> assertThat(a).isInstanceOf(BequeatherOne.class),
|
||||||
|
a -> assertThat(a).asInstanceOf(type(A.class)).extracting(A::value).isEqualTo(1),
|
||||||
|
a -> assertThat(a).asInstanceOf(type(B.class)).extracting(B::value).isEqualTo(2),
|
||||||
|
a -> assertThat(a).asInstanceOf(type(C.class)).extracting(C::value).isEqualTo(10),
|
||||||
|
a -> assertThat(a).asInstanceOf(type(Rs.class))
|
||||||
|
.extracting(Rs::value)
|
||||||
|
.asInstanceOf(array(R[].class))
|
||||||
|
.satisfiesExactly(
|
||||||
|
r -> assertThat(r).extracting(R::value).isEqualTo(4),
|
||||||
|
r -> assertThat(r).extracting(R::value).isEqualTo(2),
|
||||||
|
r -> assertThat(r).extracting(R::value).isEqualTo(3),
|
||||||
|
r -> assertThat(r).extracting(R::value).isEqualTo(10)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
|
||||||
|
public @interface A {
|
||||||
|
int value();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
|
||||||
|
public @interface B {
|
||||||
|
int value();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
|
||||||
|
public @interface C {
|
||||||
|
int value();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
|
||||||
|
@Repeatable(Rs.class)
|
||||||
|
public @interface R {
|
||||||
|
int value();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
|
||||||
|
public @interface Rs {
|
||||||
|
R[] value();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
|
||||||
|
@A(1)
|
||||||
|
@B(1)
|
||||||
|
@C(1)
|
||||||
|
@R(1)
|
||||||
|
@R(2)
|
||||||
|
@AnnotationInheritance(passOn = {A.class, B.class, C.class, R.class})
|
||||||
|
public @interface BequeatherOne {}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@BequeatherOne
|
||||||
|
@B(2)
|
||||||
|
@R(2)
|
||||||
|
@R(3)
|
||||||
|
@AnnotationInheritance(passOn = {BequeatherOne.class, B.class, R.class}, override = R.class)
|
||||||
|
public @interface BequeatherTwo {}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@R(4)
|
||||||
|
@AnnotationInheritance(passOn = {R.class, A.class})
|
||||||
|
public @interface BequeatherThree {}
|
||||||
|
|
||||||
|
@BequeatherThree
|
||||||
|
@BequeatherTwo
|
||||||
|
@C(10)
|
||||||
|
@R(10)
|
||||||
|
public static class Target1 {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package de.siphalor.tweed5.annotationinheritance.impl;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import org.junit.jupiter.api.Disabled;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.annotation.Repeatable;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.InstanceOfAssertFactories.type;
|
||||||
|
|
||||||
|
@RepeatableAnnotationContainerHelperTest.R(1)
|
||||||
|
@RepeatableAnnotationContainerHelperTest.R(2)
|
||||||
|
@RepeatableAnnotationContainerHelperTest.R(3)
|
||||||
|
class RepeatableAnnotationContainerHelperTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Disabled("Dumping the class is only for debugging purposes")
|
||||||
|
@SneakyThrows
|
||||||
|
void dumpClass() {
|
||||||
|
byte[] bytes = RepeatableAnnotationContainerHelper.createContainerClassBytes(
|
||||||
|
"de.siphalor.tweed5.annotationinheritance.impl.generated.DumpIt",
|
||||||
|
Rs.class,
|
||||||
|
R.class
|
||||||
|
);
|
||||||
|
Path path = Path.of(getClass().getSimpleName() + ".class");
|
||||||
|
Files.write(path, bytes);
|
||||||
|
System.out.println("Dumped to " + path.toAbsolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void test() {
|
||||||
|
R[] elements = {new RImpl(1), new RImpl(2), new RImpl(3)};
|
||||||
|
Annotation result = RepeatableAnnotationContainerHelper.createContainer(elements);
|
||||||
|
assertThat(result).asInstanceOf(type(Rs.class))
|
||||||
|
.extracting(Rs::value)
|
||||||
|
.isEqualTo(elements);
|
||||||
|
assertThat(result.annotationType()).isEqualTo(Rs.class);
|
||||||
|
|
||||||
|
assertThat(result.toString()).containsSubsequence("Rs(value=", "1", "2", "3");
|
||||||
|
|
||||||
|
Rs ref = RepeatableAnnotationContainerHelperTest.class.getAnnotation(Rs.class);
|
||||||
|
assertThat(result.equals(ref)).isTrue();
|
||||||
|
assertThat(result.hashCode()).isEqualTo(ref.hashCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public static class RImpl implements R {
|
||||||
|
private final int value;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
} else if (obj instanceof R) {
|
||||||
|
return ((R) obj).value() == this.value();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return (127 * "value".hashCode()) ^ Integer.valueOf(value).hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.valueOf(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends Annotation> annotationType() {
|
||||||
|
return R.class;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Repeatable(Rs.class)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface R {
|
||||||
|
int value();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface Rs {
|
||||||
|
R[] value();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
package de.siphalor.tweed5.typeutils.api.annotations;
|
||||||
|
|
||||||
|
import de.siphalor.tweed5.typeutils.impl.annotations.AnnotationRepeatTypeResolver;
|
||||||
|
import lombok.*;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.invoke.MethodHandle;
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.lang.invoke.MethodType;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
@Getter
|
||||||
|
@EqualsAndHashCode
|
||||||
|
@ToString
|
||||||
|
public abstract class AnnotationRepeatType {
|
||||||
|
public static AnnotationRepeatType getType(Class<? extends Annotation> annotationClass) {
|
||||||
|
return AnnotationRepeatTypeResolver.getType(annotationClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Class<? extends Annotation> annotationClass;
|
||||||
|
|
||||||
|
public abstract @Nullable Class<? extends Annotation> alternativeAnnotationClass();
|
||||||
|
|
||||||
|
public static class NonRepeatable extends AnnotationRepeatType {
|
||||||
|
public NonRepeatable(Class<? extends Annotation> annotationClass) {
|
||||||
|
super(annotationClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable Class<? extends Annotation> alternativeAnnotationClass() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@ToString(callSuper = true)
|
||||||
|
public static class Repeatable extends AnnotationRepeatType {
|
||||||
|
private final Class<? extends Annotation> containerAnnotationClass;
|
||||||
|
|
||||||
|
public Repeatable(
|
||||||
|
Class<? extends Annotation> annotationClass,
|
||||||
|
Class<? extends Annotation> containerAnnotationClass
|
||||||
|
) {
|
||||||
|
super(annotationClass);
|
||||||
|
this.containerAnnotationClass = containerAnnotationClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AnnotationRepeatType.RepeatableContainer containerRepeatType() {
|
||||||
|
return new RepeatableContainer(containerAnnotationClass, annotationClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable Class<? extends Annotation> alternativeAnnotationClass() {
|
||||||
|
return containerAnnotationClass;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@ToString(callSuper = true)
|
||||||
|
public static class RepeatableContainer extends AnnotationRepeatType {
|
||||||
|
@Getter
|
||||||
|
private final Class<? extends Annotation> elementAnnotationClass;
|
||||||
|
@EqualsAndHashCode.Exclude
|
||||||
|
@ToString.Exclude
|
||||||
|
@Nullable
|
||||||
|
private MethodHandle valueHandle;
|
||||||
|
|
||||||
|
public RepeatableContainer(
|
||||||
|
Class<? extends Annotation> annotationClass,
|
||||||
|
Class<? extends Annotation> elementAnnotationClass
|
||||||
|
) {
|
||||||
|
super(annotationClass);
|
||||||
|
this.elementAnnotationClass = elementAnnotationClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AnnotationRepeatType.Repeatable elementRepeatType() {
|
||||||
|
return new Repeatable(elementAnnotationClass, annotationClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable Class<? extends Annotation> alternativeAnnotationClass() {
|
||||||
|
return elementAnnotationClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <C extends Annotation, E extends Annotation> E[] elements(C containerAnnotation) {
|
||||||
|
if (valueHandle == null) {
|
||||||
|
try {
|
||||||
|
valueHandle = MethodHandles.lookup().findVirtual(
|
||||||
|
annotationClass(),
|
||||||
|
"value",
|
||||||
|
MethodType.methodType(elementAnnotationClass.arrayType())
|
||||||
|
);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Failed to resolve value method of container annotation: " + containerAnnotation
|
||||||
|
+ " (" + containerAnnotation.getClass().getName() + ")",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
//noinspection unchecked
|
||||||
|
return (E[]) valueHandle.invoke(containerAnnotation);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Unexpected exception when calling value method of container annotation: "
|
||||||
|
+ containerAnnotation + "( " + containerAnnotation.getClass().getName() + ")",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package de.siphalor.tweed5.typeutils.api.type;
|
package de.siphalor.tweed5.typeutils.api.annotations;
|
||||||
|
|
||||||
|
import de.siphalor.tweed5.typeutils.api.type.TypeAnnotationLayer;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
import org.jspecify.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.lang.reflect.AnnotatedElement;
|
import java.lang.reflect.AnnotatedElement;
|
||||||
@@ -46,7 +46,8 @@ public class LayeredTypeAnnotations implements AnnotatedElement {
|
|||||||
return layers.get(0).annotatedElement.getAnnotation(annotationClass);
|
return layers.get(0).annotatedElement.getAnnotation(annotationClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
Class<? extends Annotation> altAnnotationClass = getRepeatAlternativeAnnotation(annotationClass);
|
Class<? extends Annotation> altAnnotationClass = AnnotationRepeatType.getType(annotationClass)
|
||||||
|
.alternativeAnnotationClass();
|
||||||
|
|
||||||
for (Layer layer : layers) {
|
for (Layer layer : layers) {
|
||||||
T annotation = layer.annotatedElement.getAnnotation(annotationClass);
|
T annotation = layer.annotatedElement.getAnnotation(annotationClass);
|
||||||
@@ -75,7 +76,8 @@ public class LayeredTypeAnnotations implements AnnotatedElement {
|
|||||||
if (annotations.containsKey(layerAnnotationClass)) {
|
if (annotations.containsKey(layerAnnotationClass)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Class<? extends Annotation> layerAltClass = getRepeatAlternativeAnnotation(layerAnnotationClass);
|
Class<? extends Annotation> layerAltClass = AnnotationRepeatType.getType(layerAnnotationClass)
|
||||||
|
.alternativeAnnotationClass();
|
||||||
if (annotations.containsKey(layerAltClass)) {
|
if (annotations.containsKey(layerAltClass)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -100,7 +102,8 @@ public class LayeredTypeAnnotations implements AnnotatedElement {
|
|||||||
if (annotations.containsKey(layerAnnotationClass)) {
|
if (annotations.containsKey(layerAnnotationClass)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Class<? extends Annotation> layerAltClass = getRepeatAlternativeAnnotation(layerAnnotationClass);
|
Class<? extends Annotation> layerAltClass = AnnotationRepeatType.getType(layerAnnotationClass)
|
||||||
|
.alternativeAnnotationClass();
|
||||||
if (annotations.containsKey(layerAltClass)) {
|
if (annotations.containsKey(layerAltClass)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -110,17 +113,6 @@ public class LayeredTypeAnnotations implements AnnotatedElement {
|
|||||||
return annotations.values().toArray(new Annotation[0]);
|
return annotations.values().toArray(new Annotation[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T extends Annotation> @Nullable Class<? extends Annotation> getRepeatAlternativeAnnotation(Class<T> annotationClass) {
|
|
||||||
AnnotationRepeatType annotationRepeatType = AnnotationRepeatType.getType(annotationClass);
|
|
||||||
Class<? extends Annotation> altAnnotationClass = null;
|
|
||||||
if (annotationRepeatType instanceof AnnotationRepeatType.Repeatable) {
|
|
||||||
altAnnotationClass = ((AnnotationRepeatType.Repeatable) annotationRepeatType).containerAnnotation();
|
|
||||||
} else if (annotationRepeatType instanceof AnnotationRepeatType.RepeatableContainer) {
|
|
||||||
altAnnotationClass = ((AnnotationRepeatType.RepeatableContainer) annotationRepeatType).componentAnnotation();
|
|
||||||
}
|
|
||||||
return altAnnotationClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Value
|
@Value
|
||||||
private static class Layer {
|
private static class Layer {
|
||||||
TypeAnnotationLayer layer;
|
TypeAnnotationLayer layer;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
@NullMarked
|
||||||
|
package de.siphalor.tweed5.typeutils.api.annotations;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package de.siphalor.tweed5.typeutils.api.type;
|
package de.siphalor.tweed5.typeutils.api.type;
|
||||||
|
|
||||||
|
import de.siphalor.tweed5.typeutils.api.annotations.LayeredTypeAnnotations;
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
package de.siphalor.tweed5.typeutils.api.type;
|
|
||||||
|
|
||||||
import de.siphalor.tweed5.typeutils.impl.type.AnnotationRepeatTypeResolver;
|
|
||||||
import lombok.Value;
|
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
|
||||||
|
|
||||||
public interface AnnotationRepeatType {
|
|
||||||
static AnnotationRepeatType getType(Class<? extends Annotation> annotationClass) {
|
|
||||||
return AnnotationRepeatTypeResolver.getType(annotationClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
class NonRepeatable implements AnnotationRepeatType {
|
|
||||||
private static final NonRepeatable INSTANCE = new NonRepeatable();
|
|
||||||
|
|
||||||
public static NonRepeatable instance() {
|
|
||||||
return INSTANCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
private NonRepeatable() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Value
|
|
||||||
class Repeatable implements AnnotationRepeatType {
|
|
||||||
Class<? extends Annotation> containerAnnotation;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Value
|
|
||||||
class RepeatableContainer implements AnnotationRepeatType {
|
|
||||||
Class<? extends Annotation> componentAnnotation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package de.siphalor.tweed5.typeutils.impl.type;
|
package de.siphalor.tweed5.typeutils.impl.annotations;
|
||||||
|
|
||||||
import de.siphalor.tweed5.typeutils.api.type.AnnotationRepeatType;
|
import de.siphalor.tweed5.typeutils.api.annotations.AnnotationRepeatType;
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
@@ -33,9 +33,9 @@ public class AnnotationRepeatTypeResolver {
|
|||||||
if (container != null) {
|
if (container != null) {
|
||||||
CACHE_LOCK.writeLock().lock();
|
CACHE_LOCK.writeLock().lock();
|
||||||
try {
|
try {
|
||||||
AnnotationRepeatType type = new AnnotationRepeatType.Repeatable(container);
|
AnnotationRepeatType type = new AnnotationRepeatType.Repeatable(annotationClass, container);
|
||||||
CACHE.put(annotationClass, type);
|
CACHE.put(annotationClass, type);
|
||||||
CACHE.put(container, new AnnotationRepeatType.RepeatableContainer(annotationClass));
|
CACHE.put(container, new AnnotationRepeatType.RepeatableContainer(container, annotationClass));
|
||||||
return type;
|
return type;
|
||||||
} finally {
|
} finally {
|
||||||
CACHE_LOCK.writeLock().unlock();
|
CACHE_LOCK.writeLock().unlock();
|
||||||
@@ -45,22 +45,24 @@ public class AnnotationRepeatTypeResolver {
|
|||||||
if (component != null) {
|
if (component != null) {
|
||||||
CACHE_LOCK.writeLock().lock();
|
CACHE_LOCK.writeLock().lock();
|
||||||
try {
|
try {
|
||||||
AnnotationRepeatType type = new AnnotationRepeatType.RepeatableContainer(component);
|
AnnotationRepeatType type = new AnnotationRepeatType.RepeatableContainer(annotationClass, component);
|
||||||
CACHE.put(annotationClass, type);
|
CACHE.put(annotationClass, type);
|
||||||
CACHE.put(component, new AnnotationRepeatType.Repeatable(component));
|
CACHE.put(component, new AnnotationRepeatType.Repeatable(component, annotationClass));
|
||||||
return type;
|
return type;
|
||||||
} finally {
|
} finally {
|
||||||
CACHE_LOCK.writeLock().unlock();
|
CACHE_LOCK.writeLock().unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AnnotationRepeatType.NonRepeatable type = new AnnotationRepeatType.NonRepeatable(annotationClass);
|
||||||
|
|
||||||
CACHE_LOCK.writeLock().lock();
|
CACHE_LOCK.writeLock().lock();
|
||||||
try {
|
try {
|
||||||
CACHE.put(annotationClass, AnnotationRepeatType.NonRepeatable.instance());
|
CACHE.put(annotationClass, type);
|
||||||
} finally {
|
} finally {
|
||||||
CACHE_LOCK.writeLock().unlock();
|
CACHE_LOCK.writeLock().unlock();
|
||||||
}
|
}
|
||||||
return AnnotationRepeatType.NonRepeatable.instance();
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static @Nullable Class<? extends Annotation> getRepeatableContainerFromComponentAnnotation(
|
private static @Nullable Class<? extends Annotation> getRepeatableContainerFromComponentAnnotation(
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
@ApiStatus.Internal
|
||||||
|
@NullMarked
|
||||||
|
package de.siphalor.tweed5.typeutils.impl.annotations;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package de.siphalor.tweed5.typeutils.api.type;
|
package de.siphalor.tweed5.typeutils.api.annotations;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.ValueSource;
|
import org.junit.jupiter.params.provider.ValueSource;
|
||||||
import org.junit.jupiter.params.provider.ValueSources;
|
import org.junit.jupiter.params.provider.ValueSources;
|
||||||
@@ -17,21 +18,19 @@ class AnnotationRepeatTypeTest {
|
|||||||
@ValueSource(classes = {Override.class, ParameterizedTest.class})
|
@ValueSource(classes = {Override.class, ParameterizedTest.class})
|
||||||
void getTypeSimple(Class<? extends Annotation> annotationClass) {
|
void getTypeSimple(Class<? extends Annotation> annotationClass) {
|
||||||
AnnotationRepeatType type = AnnotationRepeatType.getType(annotationClass);
|
AnnotationRepeatType type = AnnotationRepeatType.getType(annotationClass);
|
||||||
assertThat(type).isInstanceOf(AnnotationRepeatType.NonRepeatable.class);
|
assertThat(type).isEqualTo(new AnnotationRepeatType.NonRepeatable(annotationClass));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
@Test
|
||||||
@ValueSource(classes = {ValueSources.class, Rs.class})
|
void getTypeRepeatableContainer() {
|
||||||
void getTypeRepeatableContainer(Class<? extends Annotation> annotationClass) {
|
AnnotationRepeatType type = AnnotationRepeatType.getType(Rs.class);
|
||||||
AnnotationRepeatType type = AnnotationRepeatType.getType(annotationClass);
|
assertThat(type).isEqualTo(new AnnotationRepeatType.RepeatableContainer(Rs.class, R.class));
|
||||||
assertThat(type).isInstanceOf(AnnotationRepeatType.RepeatableContainer.class);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
@Test
|
||||||
@ValueSource(classes = {ValueSource.class, R.class})
|
void getTypeRepeatableValue() {
|
||||||
void getTypeRepeatableValue(Class<? extends Annotation> annotationClass) {
|
AnnotationRepeatType type = AnnotationRepeatType.getType(R.class);
|
||||||
AnnotationRepeatType type = AnnotationRepeatType.getType(annotationClass);
|
assertThat(type).isEqualTo(new AnnotationRepeatType.Repeatable(R.class, Rs.class));
|
||||||
assertThat(type).isInstanceOf(AnnotationRepeatType.Repeatable.class);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Repeatable(Rs.class)
|
@Repeatable(Rs.class)
|
||||||
@@ -42,4 +41,4 @@ class AnnotationRepeatTypeTest {
|
|||||||
@interface Rs {
|
@interface Rs {
|
||||||
R[] value();
|
R[] value();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
package de.siphalor.tweed5.typeutils.api.type;
|
package de.siphalor.tweed5.typeutils.api.annotations;
|
||||||
|
|
||||||
|
import de.siphalor.tweed5.typeutils.test.TestAnnotation;
|
||||||
|
import de.siphalor.tweed5.typeutils.test.TestAnnotations;
|
||||||
|
import de.siphalor.tweed5.typeutils.api.type.TypeAnnotationLayer;
|
||||||
|
import org.assertj.core.api.Assertions;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
@@ -14,7 +18,7 @@ class LayeredTypeAnnotationsTest {
|
|||||||
annotations.appendLayerFrom(TypeAnnotationLayer.TYPE_USE, A.class);
|
annotations.appendLayerFrom(TypeAnnotationLayer.TYPE_USE, A.class);
|
||||||
annotations.appendLayerFrom(TypeAnnotationLayer.TYPE_USE, B.class);
|
annotations.appendLayerFrom(TypeAnnotationLayer.TYPE_USE, B.class);
|
||||||
|
|
||||||
assertThat(annotations.getAnnotation(TestAnnotation.class))
|
Assertions.assertThat(annotations.getAnnotation(TestAnnotation.class))
|
||||||
.isNotNull()
|
.isNotNull()
|
||||||
.extracting(TestAnnotation::value)
|
.extracting(TestAnnotation::value)
|
||||||
.isEqualTo("a");
|
.isEqualTo("a");
|
||||||
@@ -71,6 +75,10 @@ class LayeredTypeAnnotationsTest {
|
|||||||
.extracting(TestAnnotation::value)
|
.extracting(TestAnnotation::value)
|
||||||
.isEqualTo("b");
|
.isEqualTo("b");
|
||||||
assertThat(annotations.getAnnotation(TestAnnotations.class)).isNull();
|
assertThat(annotations.getAnnotation(TestAnnotations.class)).isNull();
|
||||||
|
assertThat(annotations.getAnnotationsByType(TestAnnotation.class))
|
||||||
|
.singleElement()
|
||||||
|
.extracting(TestAnnotation::value)
|
||||||
|
.isEqualTo("b");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -88,6 +96,7 @@ class LayeredTypeAnnotationsTest {
|
|||||||
a -> assertThat(a.value()).isEqualTo("r1"),
|
a -> assertThat(a.value()).isEqualTo("r1"),
|
||||||
a -> assertThat(a.value()).isEqualTo("r2")
|
a -> assertThat(a.value()).isEqualTo("r2")
|
||||||
);
|
);
|
||||||
|
assertThat(annotations.getAnnotationsByType(TestAnnotation.class)).hasSize(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -100,6 +109,10 @@ class LayeredTypeAnnotationsTest {
|
|||||||
.asInstanceOf(type(TestAnnotation.class))
|
.asInstanceOf(type(TestAnnotation.class))
|
||||||
.extracting(TestAnnotation::value)
|
.extracting(TestAnnotation::value)
|
||||||
.isEqualTo("b");
|
.isEqualTo("b");
|
||||||
|
assertThat(annotations.getAnnotationsByType(TestAnnotation.class))
|
||||||
|
.singleElement()
|
||||||
|
.extracting(TestAnnotation::value)
|
||||||
|
.isEqualTo("b");;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -116,6 +129,7 @@ class LayeredTypeAnnotationsTest {
|
|||||||
a -> assertThat(a.value()).isEqualTo("r1"),
|
a -> assertThat(a.value()).isEqualTo("r1"),
|
||||||
a -> assertThat(a.value()).isEqualTo("r2")
|
a -> assertThat(a.value()).isEqualTo("r2")
|
||||||
);
|
);
|
||||||
|
assertThat(annotations.getAnnotationsByType(TestAnnotation.class)).hasSize(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestAnnotation("r1")
|
@TestAnnotation("r1")
|
||||||
@@ -130,4 +144,4 @@ class LayeredTypeAnnotationsTest {
|
|||||||
@TestAnnotation("b")
|
@TestAnnotation("b")
|
||||||
private static class B {
|
private static class B {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package de.siphalor.tweed5.typeutils.api.type;
|
package de.siphalor.tweed5.typeutils.api.type;
|
||||||
|
|
||||||
|
import de.siphalor.tweed5.typeutils.test.TestAnnotation;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
@@ -178,4 +179,4 @@ class ActualTypeTest {
|
|||||||
|
|
||||||
@TestAnnotation("baseType")
|
@TestAnnotation("baseType")
|
||||||
interface Value {}
|
interface Value {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package de.siphalor.tweed5.typeutils.test;
|
package de.siphalor.tweed5.typeutils.test;
|
||||||
|
|
||||||
import de.siphalor.tweed5.typeutils.api.type.TestAnnotation;
|
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import org.assertj.core.api.Assertions;
|
import org.assertj.core.api.Assertions;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@@ -16,7 +15,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
import static org.assertj.core.api.InstanceOfAssertFactories.type;
|
import static org.assertj.core.api.InstanceOfAssertFactories.type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Various test to demonstrate the workings of Java's reflection type system
|
* Various test to demonstrate the workings of Java's reflection annotations system
|
||||||
*/
|
*/
|
||||||
public class JavaReflectionTests {
|
public class JavaReflectionTests {
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
@@ -31,7 +30,7 @@ public class JavaReflectionTests {
|
|||||||
assertThat(intsField.getAnnotatedType())
|
assertThat(intsField.getAnnotatedType())
|
||||||
.asInstanceOf(type(AnnotatedParameterizedType.class))
|
.asInstanceOf(type(AnnotatedParameterizedType.class))
|
||||||
.satisfies(
|
.satisfies(
|
||||||
type -> Assertions.assertThat(type.getAnnotation(TestAnnotation.class)).isNotNull(),
|
type -> assertThat(type.getAnnotation(TestAnnotation.class)).isNotNull(),
|
||||||
type -> assertThat(type.getAnnotatedActualTypeArguments())
|
type -> assertThat(type.getAnnotatedActualTypeArguments())
|
||||||
.singleElement()
|
.singleElement()
|
||||||
.isInstanceOf(AnnotatedParameterizedType.class)
|
.isInstanceOf(AnnotatedParameterizedType.class)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package de.siphalor.tweed5.typeutils.api.type;
|
package de.siphalor.tweed5.typeutils.test;
|
||||||
|
|
||||||
import java.lang.annotation.*;
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package de.siphalor.tweed5.typeutils.api.type;
|
package de.siphalor.tweed5.typeutils.test;
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
@@ -43,7 +43,11 @@ public class ClassToInstanceMap<T extends @NonNull Object> implements Iterable<T
|
|||||||
}
|
}
|
||||||
|
|
||||||
public <V extends T> @Nullable V put(V value) {
|
public <V extends T> @Nullable V put(V value) {
|
||||||
return (V) delegate.put((Class<? extends T>) value.getClass(), value);
|
return put((Class<V>) value.getClass(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <V extends T, U extends V> @Nullable V put(Class<V> key, U value) {
|
||||||
|
return (V) delegate.put(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public <V extends T> @Nullable V remove(Class<V> key) {
|
public <V extends T> @Nullable V remove(Class<V> key) {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
|
|||||||
import de.siphalor.tweed5.weaver.pojo.api.annotation.CompoundWeaving;
|
import de.siphalor.tweed5.weaver.pojo.api.annotation.CompoundWeaving;
|
||||||
import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableCompoundConfigEntry;
|
import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableCompoundConfigEntry;
|
||||||
import de.siphalor.tweed5.typeutils.api.type.ActualType;
|
import de.siphalor.tweed5.typeutils.api.type.ActualType;
|
||||||
import de.siphalor.tweed5.typeutils.api.type.LayeredTypeAnnotations;
|
import de.siphalor.tweed5.typeutils.api.annotations.LayeredTypeAnnotations;
|
||||||
import de.siphalor.tweed5.typeutils.api.type.TypeAnnotationLayer;
|
import de.siphalor.tweed5.typeutils.api.type.TypeAnnotationLayer;
|
||||||
import de.siphalor.tweed5.weaver.pojo.impl.entry.StaticPojoCompoundConfigEntry;
|
import de.siphalor.tweed5.weaver.pojo.impl.entry.StaticPojoCompoundConfigEntry;
|
||||||
import de.siphalor.tweed5.weaver.pojo.impl.weaving.PojoClassIntrospector;
|
import de.siphalor.tweed5.weaver.pojo.impl.weaving.PojoClassIntrospector;
|
||||||
|
|||||||
Reference in New Issue
Block a user