[type-utils, weaver-pojo] Introduce a submodule focused on Java types for POJO weaving
This commit is contained in:
@@ -7,6 +7,7 @@ include("tweed5-patchwork")
|
|||||||
include("tweed5-serde-api")
|
include("tweed5-serde-api")
|
||||||
include("tweed5-serde-extension")
|
include("tweed5-serde-extension")
|
||||||
include("tweed5-serde-hjson")
|
include("tweed5-serde-hjson")
|
||||||
|
include("tweed5-type-utils")
|
||||||
include("tweed5-utils")
|
include("tweed5-utils")
|
||||||
include("tweed5-weaver-pojo")
|
include("tweed5-weaver-pojo")
|
||||||
include("tweed5-weaver-pojo-serde-extension")
|
include("tweed5-weaver-pojo-serde-extension")
|
||||||
|
|||||||
3
tweed5-type-utils/build.gradle.kts
Normal file
3
tweed5-type-utils/build.gradle.kts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
plugins {
|
||||||
|
id("de.siphalor.tweed5.base-module")
|
||||||
|
}
|
||||||
@@ -0,0 +1,275 @@
|
|||||||
|
package de.siphalor.tweed5.typeutils.api.type;
|
||||||
|
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.reflect.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a runtime type with the parameters and annotations that are actually in use.
|
||||||
|
*
|
||||||
|
* @param <T> the type represented by this class
|
||||||
|
*/
|
||||||
|
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
|
||||||
|
public class ActualType<T> implements AnnotatedElement {
|
||||||
|
/**
|
||||||
|
* The raw {@link Class} that the type has been originally declared as.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
private final Class<T> declaredType;
|
||||||
|
/**
|
||||||
|
* The {@link AnnotatedType} that represents the type that is actually in use (without parameters).
|
||||||
|
*/
|
||||||
|
@Getter(AccessLevel.PROTECTED)
|
||||||
|
@Nullable
|
||||||
|
private final AnnotatedType usedType;
|
||||||
|
/**
|
||||||
|
* The {@link AnnotatedParameterizedType} that represents the type that is actually in use with parameters.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private final AnnotatedParameterizedType usedParameterizedType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A representation of the layered annotations of this type.
|
||||||
|
* These usually consist of the annotations on {@link #declaredType()} combined with those from {@link #usedType()}
|
||||||
|
*/
|
||||||
|
private final LayeredTypeAnnotations layeredTypeAnnotations;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal cache for the resolved actual type parameters.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private List<@NotNull ActualType<?>> resolvedParameters;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a basic actual type from just a declared class.
|
||||||
|
*/
|
||||||
|
public static <T> ActualType<T> ofClass(Class<T> clazz) {
|
||||||
|
return new ActualType<>(
|
||||||
|
clazz,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
LayeredTypeAnnotations.of(TypeAnnotationLayer.TYPE_DECLARATION, clazz)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an actual type from a Java type usage.
|
||||||
|
*
|
||||||
|
* @throws UnsupportedOperationException when the given annotated type is not yet supported by this class
|
||||||
|
*/
|
||||||
|
public static ActualType<?> ofUsedType(@NotNull AnnotatedType annotatedType) throws UnsupportedOperationException {
|
||||||
|
Class<?> clazz = getDeclaredClassForUsedType(annotatedType);
|
||||||
|
|
||||||
|
LayeredTypeAnnotations layeredTypeAnnotations = new LayeredTypeAnnotations();
|
||||||
|
layeredTypeAnnotations.appendLayerFrom(TypeAnnotationLayer.TYPE_DECLARATION, clazz);
|
||||||
|
layeredTypeAnnotations.appendLayerFrom(TypeAnnotationLayer.TYPE_USE, annotatedType);
|
||||||
|
|
||||||
|
if (annotatedType instanceof AnnotatedParameterizedType) {
|
||||||
|
return new ActualType<>(clazz, annotatedType, (AnnotatedParameterizedType) annotatedType, layeredTypeAnnotations);
|
||||||
|
} else {
|
||||||
|
return new ActualType<>(clazz, annotatedType, null, layeredTypeAnnotations);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the declared {@link Class} of the {@link AnnotatedType} as Java has no generic way to do that.
|
||||||
|
*
|
||||||
|
* @throws UnsupportedOperationException if the given parameter is not supported yet
|
||||||
|
*/
|
||||||
|
private static @NotNull Class<?> getDeclaredClassForUsedType(@NotNull AnnotatedType annotatedType) throws UnsupportedOperationException {
|
||||||
|
if (annotatedType.getType() instanceof Class) {
|
||||||
|
return (Class<?>) annotatedType.getType();
|
||||||
|
} else if (annotatedType.getType() instanceof ParameterizedType) {
|
||||||
|
return (Class<?>) ((ParameterizedType) annotatedType.getType()).getRawType();
|
||||||
|
} else if (annotatedType instanceof AnnotatedWildcardType) {
|
||||||
|
AnnotatedType[] upperBounds = ((AnnotatedWildcardType) annotatedType).getAnnotatedUpperBounds();
|
||||||
|
if (upperBounds.length == 1) {
|
||||||
|
return getDeclaredClassForUsedType(upperBounds[0]);
|
||||||
|
}
|
||||||
|
return Object.class;
|
||||||
|
} else {
|
||||||
|
throw new UnsupportedOperationException(
|
||||||
|
"Failed to resolve raw class of annotated type: " + annotatedType + " (" + annotatedType.getClass() + ")"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <A extends Annotation> A getAnnotation(@NotNull Class<A> annotationClass) {
|
||||||
|
return layeredTypeAnnotations.getAnnotation(annotationClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Annotation @NotNull [] getAnnotations() {
|
||||||
|
return layeredTypeAnnotations.getAnnotations();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Annotation @NotNull [] getDeclaredAnnotations() {
|
||||||
|
return layeredTypeAnnotations.getDeclaredAnnotations();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the type parameters of this type as {@link ActualType}s.
|
||||||
|
*/
|
||||||
|
public @NotNull List<@NotNull ActualType<?>> parameters() {
|
||||||
|
if (resolvedParameters != null) {
|
||||||
|
return resolvedParameters;
|
||||||
|
} else if (usedParameterizedType == null) {
|
||||||
|
int paramCount = declaredType.getTypeParameters().length;
|
||||||
|
if (paramCount == 0) {
|
||||||
|
resolvedParameters = Collections.emptyList();
|
||||||
|
} else {
|
||||||
|
resolvedParameters = new ArrayList<>(paramCount);
|
||||||
|
for (int i = 0; i < paramCount; i++) {
|
||||||
|
resolvedParameters.add(ActualType.ofClass(Object.class));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resolvedParameters = Arrays.stream(usedParameterizedType.getAnnotatedActualTypeArguments())
|
||||||
|
.map(ActualType::ofUsedType)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
return resolvedParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the actual type parameters of a super-class or super-interface of this type.
|
||||||
|
* @param targetClass the class to check
|
||||||
|
* @return the list of type parameters if the given class is assignable from this type or {@code null} if not
|
||||||
|
*/
|
||||||
|
public @Nullable List<ActualType<?>> getTypesOfSuperArguments(@NotNull Class<?> targetClass) {
|
||||||
|
if (targetClass.getTypeParameters().length == 0) {
|
||||||
|
if (targetClass.isAssignableFrom(declaredType)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ActualType<?> superType = getViewOnSuperType(targetClass, this);
|
||||||
|
if (superType == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return superType.parameters();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @Nullable ActualType<?> getViewOnSuperType(
|
||||||
|
Class<?> targetClass,
|
||||||
|
ActualType<?> currentType
|
||||||
|
) {
|
||||||
|
Class<?> currentClass = currentType.declaredType();
|
||||||
|
if (currentClass == targetClass) {
|
||||||
|
return currentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<@NotNull ActualType<?>> currentParameters = currentType.parameters();
|
||||||
|
|
||||||
|
Map<String, AnnotatedType> paramMap;
|
||||||
|
if (currentParameters.isEmpty()) {
|
||||||
|
paramMap = Collections.emptyMap();
|
||||||
|
} else {
|
||||||
|
paramMap = new HashMap<>();
|
||||||
|
for (int i = 0; i < currentParameters.size(); i++) {
|
||||||
|
paramMap.put(currentClass.getTypeParameters()[i].getName(), currentParameters.get(i).usedType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetClass.isInterface()) {
|
||||||
|
for (AnnotatedType annotatedInterface : currentClass.getAnnotatedInterfaces()) {
|
||||||
|
ActualType<?> interfaceType = resolveTypeWithParameters(annotatedInterface, paramMap);
|
||||||
|
@Nullable ActualType<?> resultType = getViewOnSuperType(targetClass, interfaceType);
|
||||||
|
if (resultType != null) {
|
||||||
|
return resultType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currentClass != Object.class && !currentClass.isInterface()) {
|
||||||
|
ActualType<?> superType = resolveTypeWithParameters(currentClass.getAnnotatedSuperclass(), paramMap);
|
||||||
|
@Nullable ActualType<?> resultType = getViewOnSuperType(targetClass, superType);
|
||||||
|
if (resultType != null) {
|
||||||
|
return resultType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ActualType<?> resolveTypeWithParameters(AnnotatedType annotatedType, Map<String, AnnotatedType> parameters) {
|
||||||
|
if (annotatedType instanceof AnnotatedTypeVariable) {
|
||||||
|
ActualType<?> actualType = ofUsedType(parameters.get(annotatedType.getType().getTypeName()));
|
||||||
|
actualType.layeredTypeAnnotations.appendLayerFrom(TypeAnnotationLayer.TYPE_USE, annotatedType);
|
||||||
|
return actualType;
|
||||||
|
} else if (annotatedType instanceof AnnotatedParameterizedType) {
|
||||||
|
List<ActualType<?>> resolvedParameters = Arrays.stream(((AnnotatedParameterizedType) annotatedType).getAnnotatedActualTypeArguments())
|
||||||
|
.map(p -> resolveTypeWithParameters(p, parameters))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
ActualType<?> actualType = ofUsedType(annotatedType);
|
||||||
|
actualType.resolvedParameters = resolvedParameters;
|
||||||
|
return actualType;
|
||||||
|
} else {
|
||||||
|
return ofUsedType(annotatedType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (!(obj instanceof ActualType)) {
|
||||||
|
return false;
|
||||||
|
} else if (usedParameterizedType != null) {
|
||||||
|
return usedParameterizedType.equals(((ActualType<?>) obj).usedParameterizedType);
|
||||||
|
} else if (usedType != null) {
|
||||||
|
return usedType.equals(((ActualType<?>) obj).usedType);
|
||||||
|
} else {
|
||||||
|
return declaredType.equals(((ActualType<?>) obj).declaredType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return getMostSpecificTypeObject().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if (usedType != null) {
|
||||||
|
appendAnnotationsToString(sb, usedType.getAnnotations());
|
||||||
|
}
|
||||||
|
sb.append(declaredType.getName());
|
||||||
|
List<@NotNull ActualType<?>> parameters = parameters();
|
||||||
|
if (!parameters.isEmpty()) {
|
||||||
|
sb.append("<");
|
||||||
|
for (@NotNull ActualType<?> parameter : parameters) {
|
||||||
|
sb.append(parameter);
|
||||||
|
sb.append(", ");
|
||||||
|
}
|
||||||
|
sb.setLength(sb.length() - 2);
|
||||||
|
sb.append(">");
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendAnnotationsToString(@NotNull StringBuilder sb, @NotNull Annotation[] annotations) {
|
||||||
|
for (@NotNull Annotation annotation : annotations) {
|
||||||
|
sb.append(annotation);
|
||||||
|
sb.append(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Object getMostSpecificTypeObject() {
|
||||||
|
if (usedParameterizedType != null) {
|
||||||
|
return usedParameterizedType;
|
||||||
|
} else if (usedType != null) {
|
||||||
|
return usedType;
|
||||||
|
} else {
|
||||||
|
return declaredType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package de.siphalor.tweed5.typeutils.api.type;
|
||||||
|
|
||||||
|
import de.siphalor.tweed5.typeutils.impl.type.AnnotationRepeatTypeResolver;
|
||||||
|
import lombok.Value;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
|
||||||
|
public interface AnnotationRepeatType {
|
||||||
|
static AnnotationRepeatType getType(@NotNull 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
package de.siphalor.tweed5.typeutils.api.type;
|
||||||
|
|
||||||
|
import lombok.Value;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.reflect.AnnotatedElement;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class LayeredTypeAnnotations implements AnnotatedElement {
|
||||||
|
private static final Annotation[] EMPTY_ANNOTATIONS = new Annotation[0];
|
||||||
|
|
||||||
|
public static LayeredTypeAnnotations of(@NotNull TypeAnnotationLayer layer, @NotNull AnnotatedElement annotatedElement) {
|
||||||
|
LayeredTypeAnnotations annotations = new LayeredTypeAnnotations();
|
||||||
|
annotations.layers.add(new Layer(layer, annotatedElement));
|
||||||
|
return annotations;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final List<Layer> layers = new ArrayList<>();
|
||||||
|
|
||||||
|
public void appendLayerFrom(@NotNull TypeAnnotationLayer layer, @NotNull AnnotatedElement annotatedElement) {
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < layers.size(); i++) {
|
||||||
|
if (layer.compareTo(layers.get(i).layer()) > 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layers.add(i, new Layer(layer, annotatedElement));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void prependLayerFrom(@NotNull TypeAnnotationLayer layer, @NotNull AnnotatedElement annotatedElement) {
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < layers.size(); i++) {
|
||||||
|
if (layer.compareTo(layers.get(i).layer()) >= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layers.add(i, new Layer(layer, annotatedElement));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends Annotation> T getAnnotation(@NotNull Class<T> annotationClass) {
|
||||||
|
if (layers.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
} else if (layers.size() == 1) {
|
||||||
|
return layers.get(0).annotatedElement.getAnnotation(annotationClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<? extends Annotation> altAnnotationClass = getRepeatAlternativeAnnotation(annotationClass);
|
||||||
|
|
||||||
|
for (Layer layer : layers) {
|
||||||
|
T annotation = layer.annotatedElement.getAnnotation(annotationClass);
|
||||||
|
if (annotation != null) {
|
||||||
|
return annotation;
|
||||||
|
}
|
||||||
|
if (altAnnotationClass != null && layer.annotatedElement.isAnnotationPresent(altAnnotationClass)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Annotation @NotNull [] getAnnotations() {
|
||||||
|
if (layers.isEmpty()) {
|
||||||
|
return EMPTY_ANNOTATIONS;
|
||||||
|
} else if (layers.size() == 1) {
|
||||||
|
return layers.get(0).annotatedElement.getAnnotations();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<Class<? extends Annotation>, Annotation> annotations = new HashMap<>();
|
||||||
|
for (Layer layer : layers) {
|
||||||
|
for (Annotation layerAnnotation : layer.annotatedElement.getAnnotations()) {
|
||||||
|
Class<? extends Annotation> layerAnnotationClass = layerAnnotation.annotationType();
|
||||||
|
if (annotations.containsKey(layerAnnotationClass)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Class<? extends Annotation> layerAltClass = getRepeatAlternativeAnnotation(layerAnnotationClass);
|
||||||
|
if (annotations.containsKey(layerAltClass)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
annotations.put(layerAnnotationClass, layerAnnotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return annotations.values().toArray(new Annotation[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Annotation @NotNull [] getDeclaredAnnotations() {
|
||||||
|
if (layers.isEmpty()) {
|
||||||
|
return EMPTY_ANNOTATIONS;
|
||||||
|
} else if (layers.size() == 1) {
|
||||||
|
return layers.get(0).annotatedElement.getDeclaredAnnotations();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<Class<? extends Annotation>, Annotation> annotations = new HashMap<>();
|
||||||
|
for (Layer layer : layers) {
|
||||||
|
for (Annotation layerAnnotation : layer.annotatedElement.getDeclaredAnnotations()) {
|
||||||
|
Class<? extends Annotation> layerAnnotationClass = layerAnnotation.annotationType();
|
||||||
|
if (annotations.containsKey(layerAnnotationClass)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Class<? extends Annotation> layerAltClass = getRepeatAlternativeAnnotation(layerAnnotationClass);
|
||||||
|
if (annotations.containsKey(layerAltClass)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
annotations.put(layerAnnotationClass, layerAnnotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return annotations.values().toArray(new Annotation[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T extends Annotation> @Nullable Class<? extends Annotation> getRepeatAlternativeAnnotation(@NotNull 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
|
||||||
|
private static class Layer {
|
||||||
|
TypeAnnotationLayer layer;
|
||||||
|
AnnotatedElement annotatedElement;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package de.siphalor.tweed5.typeutils.api.type;
|
||||||
|
|
||||||
|
public enum TypeAnnotationLayer implements Comparable<TypeAnnotationLayer> {
|
||||||
|
TYPE_DECLARATION,
|
||||||
|
TYPE_PARAMETER,
|
||||||
|
TYPE_USE,
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
@ApiStatus.Internal
|
||||||
|
|
||||||
|
package de.siphalor.tweed5.typeutils.impl;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
package de.siphalor.tweed5.typeutils.impl.type;
|
||||||
|
|
||||||
|
import de.siphalor.tweed5.typeutils.api.type.AnnotationRepeatType;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.annotation.Repeatable;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.locks.ReadWriteLock;
|
||||||
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
|
|
||||||
|
public class AnnotationRepeatTypeResolver {
|
||||||
|
private static final Map<Class<? extends Annotation>, AnnotationRepeatType> CACHE = new HashMap<>();
|
||||||
|
private static final ReadWriteLock CACHE_LOCK = new ReentrantReadWriteLock();
|
||||||
|
|
||||||
|
public static AnnotationRepeatType getType(@NotNull Class<? extends Annotation> annotationClass) {
|
||||||
|
CACHE_LOCK.readLock().lock();
|
||||||
|
try {
|
||||||
|
AnnotationRepeatType cachedValue = CACHE.get(annotationClass);
|
||||||
|
if (cachedValue != null) {
|
||||||
|
return cachedValue;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
CACHE_LOCK.readLock().unlock();
|
||||||
|
}
|
||||||
|
return determineType(annotationClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AnnotationRepeatType determineType(@NotNull Class<? extends Annotation> annotationClass) {
|
||||||
|
Class<? extends Annotation> container = getRepeatableContainerFromComponentAnnotation(annotationClass);
|
||||||
|
if (container != null) {
|
||||||
|
CACHE_LOCK.writeLock().lock();
|
||||||
|
try {
|
||||||
|
AnnotationRepeatType type = new AnnotationRepeatType.Repeatable(container);
|
||||||
|
CACHE.put(annotationClass, type);
|
||||||
|
CACHE.put(container, new AnnotationRepeatType.RepeatableContainer(annotationClass));
|
||||||
|
return type;
|
||||||
|
} finally {
|
||||||
|
CACHE_LOCK.writeLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Class<? extends Annotation> component = getRepeatableComponentFromContainerAnnotation(annotationClass);
|
||||||
|
if (component != null) {
|
||||||
|
CACHE_LOCK.writeLock().lock();
|
||||||
|
try {
|
||||||
|
AnnotationRepeatType type = new AnnotationRepeatType.RepeatableContainer(component);
|
||||||
|
CACHE.put(annotationClass, type);
|
||||||
|
CACHE.put(component, new AnnotationRepeatType.Repeatable(component));
|
||||||
|
return type;
|
||||||
|
} finally {
|
||||||
|
CACHE_LOCK.writeLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CACHE_LOCK.writeLock().lock();
|
||||||
|
try {
|
||||||
|
CACHE.put(annotationClass, AnnotationRepeatType.NonRepeatable.instance());
|
||||||
|
} finally {
|
||||||
|
CACHE_LOCK.writeLock().unlock();
|
||||||
|
}
|
||||||
|
return AnnotationRepeatType.NonRepeatable.instance();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static Class<? extends Annotation> getRepeatableContainerFromComponentAnnotation(
|
||||||
|
Class<? extends Annotation> annotationClass
|
||||||
|
) {
|
||||||
|
Repeatable repeatableDeclaration = annotationClass.getAnnotation(Repeatable.class);
|
||||||
|
if (repeatableDeclaration == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return repeatableDeclaration.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static Class<? extends Annotation> getRepeatableComponentFromContainerAnnotation(
|
||||||
|
Class<? extends Annotation> annotationClass
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
Method method = annotationClass.getMethod("value");
|
||||||
|
Class<?> returnType = method.getReturnType();
|
||||||
|
if (!returnType.isArray()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Class<?> componentType = returnType.getComponentType();
|
||||||
|
if (!componentType.isAnnotation()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Repeatable repeatableDeclaration = componentType.getAnnotation(Repeatable.class);
|
||||||
|
if (repeatableDeclaration == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (repeatableDeclaration.value() != annotationClass) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//noinspection unchecked
|
||||||
|
return (Class<? extends Annotation>) componentType;
|
||||||
|
} catch (NoSuchMethodException ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
package de.siphalor.tweed5.typeutils.api.type;
|
||||||
|
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.CsvSource;
|
||||||
|
import org.junit.jupiter.params.provider.ValueSource;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.InstanceOfAssertFactories.type;
|
||||||
|
|
||||||
|
class ActualTypeTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void ofClass() {
|
||||||
|
assertThat(ActualType.ofClass(TypeTestClass.class))
|
||||||
|
.isNotNull()
|
||||||
|
.satisfies(
|
||||||
|
type -> assertThat(type.declaredType()).isEqualTo(TypeTestClass.class),
|
||||||
|
type -> assertThat(type.getAnnotation(TestAnnotation.class)).isNotNull()
|
||||||
|
.extracting(TestAnnotation::value).isEqualTo("classy"),
|
||||||
|
type -> assertThat(type.usedType()).isNull()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
@Test
|
||||||
|
void ofUsedTypeSimpleAnnotated() {
|
||||||
|
ActualType<?> actualType = ActualType.ofUsedType(
|
||||||
|
TypeTestClass.class.getField("missingParamMap").getAnnotatedType()
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(actualType.declaredType()).isEqualTo(Map.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
@Test
|
||||||
|
void ofUsedTypeParameterized() {
|
||||||
|
ActualType<?> actualType = ActualType.ofUsedType(
|
||||||
|
TypeTestClass.class.getField("wildcardParamMap").getAnnotatedType()
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(actualType.declaredType()).isEqualTo(Map.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
@Test
|
||||||
|
void parameters() {
|
||||||
|
ActualType<?> stringListType = ActualType.ofUsedType(
|
||||||
|
TypeTestClass.class.getField("stringList").getAnnotatedType()
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(stringListType.parameters())
|
||||||
|
.singleElement()
|
||||||
|
.satisfies(
|
||||||
|
type -> assertThat(type.declaredType()).isEqualTo(String.class),
|
||||||
|
type -> assertThat(type.getAnnotation(TestAnnotation.class))
|
||||||
|
.isNotNull()
|
||||||
|
.extracting(TestAnnotation::value)
|
||||||
|
.isEqualTo("hi")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(classes = {List.class, Map.class, Integer.class})
|
||||||
|
void getTypesOfSuperArgumentsNotInherited(Class<?> targetType) {
|
||||||
|
ActualType<String> actualType = ActualType.ofClass(String.class);
|
||||||
|
|
||||||
|
assertThat(actualType.getTypesOfSuperArguments(targetType)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getTypesOfSuperArgumentsInheritedWithoutParameters() {
|
||||||
|
ActualType<String> actualType = ActualType.ofClass(String.class);
|
||||||
|
|
||||||
|
assertThat(actualType.getTypesOfSuperArguments(CharSequence.class)).isNotNull().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(strings = {"missingParamMap", "wildcardParamMap"})
|
||||||
|
void getTypesOfSuperArgumentsMissingParameters(String field) {
|
||||||
|
ActualType<?> actualType = ActualType.ofUsedType(
|
||||||
|
TypeTestClass.class.getField(field).getAnnotatedType()
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(actualType.getTypesOfSuperArguments(Map.class))
|
||||||
|
.satisfiesExactly(
|
||||||
|
t -> assertThat(t.declaredType()).isEqualTo(Object.class),
|
||||||
|
t -> assertThat(t.declaredType()).isEqualTo(Object.class)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(classes = {List.class, AbstractCollection.class, Collection.class, Iterable.class})
|
||||||
|
void getTypesOfSuperArgumentSimpleList(Class<?> targetType) {
|
||||||
|
ActualType<?> stringListType = ActualType.ofUsedType(
|
||||||
|
TypeTestClass.class.getField("stringList").getAnnotatedType()
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(stringListType.getTypesOfSuperArguments(targetType)).singleElement().satisfies(
|
||||||
|
type -> assertThat(type.getAnnotation(TestAnnotation.class))
|
||||||
|
.asInstanceOf(type(TestAnnotation.class))
|
||||||
|
.extracting(TestAnnotation::value)
|
||||||
|
.isEqualTo("hi"),
|
||||||
|
type -> assertThat(type.declaredType()).isEqualTo(String.class)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
@ParameterizedTest
|
||||||
|
@CsvSource({
|
||||||
|
"useMap, elemUse",
|
||||||
|
"declMap, elemDecl",
|
||||||
|
"subValueMap, subType",
|
||||||
|
"valueMap, baseType",
|
||||||
|
})
|
||||||
|
void getTypesOfSuperArgumentMapOverride(String field, String expectedAnnoValue) {
|
||||||
|
ActualType<?> actualType = ActualType.ofUsedType(
|
||||||
|
TypeTestClass.class.getField(field).getAnnotatedType()
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(actualType.getTypesOfSuperArguments(Map.class)).satisfiesExactly(
|
||||||
|
type -> assertThat(type.declaredType()).isEqualTo(String.class),
|
||||||
|
type -> assertThat(type).satisfies(
|
||||||
|
t -> assertThat(t.getAnnotation(TestAnnotation.class))
|
||||||
|
.asInstanceOf(type(TestAnnotation.class))
|
||||||
|
.extracting(TestAnnotation::value)
|
||||||
|
.isEqualTo("list"),
|
||||||
|
t -> assertThat(t.declaredType()).isEqualTo(List.class),
|
||||||
|
t -> assertThat(t.parameters())
|
||||||
|
.singleElement()
|
||||||
|
.extracting(v -> v.getAnnotation(TestAnnotation.class))
|
||||||
|
.as("List element type should have correct annotation")
|
||||||
|
.asInstanceOf(type(TestAnnotation.class))
|
||||||
|
.extracting(TestAnnotation::value)
|
||||||
|
.isEqualTo(expectedAnnoValue)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
@Test
|
||||||
|
void getTypesOfSuperArgumentMapWildcardBounded() {
|
||||||
|
ActualType<?> actualType = ActualType.ofUsedType(
|
||||||
|
TypeTestClass.class.getField("wildcardBoundedParamMap").getAnnotatedType()
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(actualType.getTypesOfSuperArguments(Map.class)).satisfiesExactly(
|
||||||
|
type -> assertThat(type.declaredType()).isEqualTo(CharSequence.class),
|
||||||
|
type -> assertThat(type.declaredType()).isEqualTo(Number.class)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestAnnotation("classy")
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
static class TypeTestClass {
|
||||||
|
public ArrayList<@TestAnnotation("hi") String> stringList;
|
||||||
|
public String2ValueMultimap<@TestAnnotation("elemUse") SubValue> useMap;
|
||||||
|
public String2ValueMultimap<SubValue> declMap;
|
||||||
|
public Map<String, @TestAnnotation("list") List<SubValue>> subValueMap;
|
||||||
|
public Map<String, @TestAnnotation("list") List<Value>> valueMap;
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
public Map missingParamMap;
|
||||||
|
public Map<?, ?> wildcardParamMap;
|
||||||
|
public Map<? extends CharSequence, ? extends Number> wildcardBoundedParamMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface String2ValueMultimap<V extends Value>
|
||||||
|
extends Map<String, @TestAnnotation("list") List<@TestAnnotation("elemDecl") V>> {
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestAnnotation("subType")
|
||||||
|
interface SubValue extends Value {}
|
||||||
|
|
||||||
|
@TestAnnotation("baseType")
|
||||||
|
interface Value {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package de.siphalor.tweed5.typeutils.api.type;
|
||||||
|
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.ValueSource;
|
||||||
|
import org.junit.jupiter.params.provider.ValueSources;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.annotation.Repeatable;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class AnnotationRepeatTypeTest {
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(classes = {Override.class, ParameterizedTest.class})
|
||||||
|
void getTypeSimple(Class<? extends Annotation> annotationClass) {
|
||||||
|
AnnotationRepeatType type = AnnotationRepeatType.getType(annotationClass);
|
||||||
|
assertThat(type).isInstanceOf(AnnotationRepeatType.NonRepeatable.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(classes = {ValueSources.class, Rs.class})
|
||||||
|
void getTypeRepeatableContainer(Class<? extends Annotation> annotationClass) {
|
||||||
|
AnnotationRepeatType type = AnnotationRepeatType.getType(annotationClass);
|
||||||
|
assertThat(type).isInstanceOf(AnnotationRepeatType.RepeatableContainer.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(classes = {ValueSource.class, R.class})
|
||||||
|
void getTypeRepeatableValue(Class<? extends Annotation> annotationClass) {
|
||||||
|
AnnotationRepeatType type = AnnotationRepeatType.getType(annotationClass);
|
||||||
|
assertThat(type).isInstanceOf(AnnotationRepeatType.Repeatable.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Repeatable(Rs.class)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@interface R { }
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@interface Rs {
|
||||||
|
R[] value();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
package de.siphalor.tweed5.typeutils.api.type;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
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 LayeredTypeAnnotationsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void appendInSameLayer() {
|
||||||
|
LayeredTypeAnnotations annotations = new LayeredTypeAnnotations();
|
||||||
|
annotations.appendLayerFrom(TypeAnnotationLayer.TYPE_USE, A.class);
|
||||||
|
annotations.appendLayerFrom(TypeAnnotationLayer.TYPE_USE, B.class);
|
||||||
|
|
||||||
|
assertThat(annotations.getAnnotation(TestAnnotation.class))
|
||||||
|
.isNotNull()
|
||||||
|
.extracting(TestAnnotation::value)
|
||||||
|
.isEqualTo("a");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void appendInDifferentLayer() {
|
||||||
|
LayeredTypeAnnotations annotations = new LayeredTypeAnnotations();
|
||||||
|
annotations.appendLayerFrom(TypeAnnotationLayer.TYPE_DECLARATION, B.class);
|
||||||
|
annotations.appendLayerFrom(TypeAnnotationLayer.TYPE_USE, A.class);
|
||||||
|
annotations.appendLayerFrom(TypeAnnotationLayer.TYPE_DECLARATION, B.class);
|
||||||
|
annotations.appendLayerFrom(TypeAnnotationLayer.TYPE_USE, B.class);
|
||||||
|
|
||||||
|
assertThat(annotations.getAnnotation(TestAnnotation.class))
|
||||||
|
.isNotNull()
|
||||||
|
.extracting(TestAnnotation::value)
|
||||||
|
.isEqualTo("a");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void prependInSameLayer() {
|
||||||
|
LayeredTypeAnnotations annotations = new LayeredTypeAnnotations();
|
||||||
|
annotations.prependLayerFrom(TypeAnnotationLayer.TYPE_USE, A.class);
|
||||||
|
annotations.prependLayerFrom(TypeAnnotationLayer.TYPE_USE, B.class);
|
||||||
|
|
||||||
|
assertThat(annotations.getAnnotation(TestAnnotation.class))
|
||||||
|
.isNotNull()
|
||||||
|
.extracting(TestAnnotation::value)
|
||||||
|
.isEqualTo("b");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void prependInDifferentLayer() {
|
||||||
|
LayeredTypeAnnotations annotations = new LayeredTypeAnnotations();
|
||||||
|
annotations.prependLayerFrom(TypeAnnotationLayer.TYPE_DECLARATION, B.class);
|
||||||
|
annotations.prependLayerFrom(TypeAnnotationLayer.TYPE_USE, B.class);
|
||||||
|
annotations.prependLayerFrom(TypeAnnotationLayer.TYPE_DECLARATION, B.class);
|
||||||
|
annotations.prependLayerFrom(TypeAnnotationLayer.TYPE_USE, A.class);
|
||||||
|
|
||||||
|
assertThat(annotations.getAnnotation(TestAnnotation.class))
|
||||||
|
.isNotNull()
|
||||||
|
.extracting(TestAnnotation::value)
|
||||||
|
.isEqualTo("a");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getAnnotationOverrideRepeatableWins() {
|
||||||
|
LayeredTypeAnnotations annotations = new LayeredTypeAnnotations();
|
||||||
|
annotations.appendLayerFrom(TypeAnnotationLayer.TYPE_DECLARATION, Repeated.class);
|
||||||
|
annotations.appendLayerFrom(TypeAnnotationLayer.TYPE_USE, B.class);
|
||||||
|
|
||||||
|
assertThat(annotations.getAnnotation(TestAnnotation.class))
|
||||||
|
.isNotNull()
|
||||||
|
.extracting(TestAnnotation::value)
|
||||||
|
.isEqualTo("b");
|
||||||
|
assertThat(annotations.getAnnotation(TestAnnotations.class)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getAnnotationOverrideRepeatableContainerWins() {
|
||||||
|
LayeredTypeAnnotations annotations = new LayeredTypeAnnotations();
|
||||||
|
annotations.appendLayerFrom(TypeAnnotationLayer.TYPE_DECLARATION, B.class);
|
||||||
|
annotations.appendLayerFrom(TypeAnnotationLayer.TYPE_USE, Repeated.class);
|
||||||
|
|
||||||
|
assertThat(annotations.getAnnotation(TestAnnotation.class)).isNull();
|
||||||
|
assertThat(annotations.getAnnotation(TestAnnotations.class))
|
||||||
|
.isNotNull()
|
||||||
|
.extracting(TestAnnotations::value)
|
||||||
|
.asInstanceOf(array(TestAnnotation[].class))
|
||||||
|
.satisfiesExactly(
|
||||||
|
a -> assertThat(a.value()).isEqualTo("r1"),
|
||||||
|
a -> assertThat(a.value()).isEqualTo("r2")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getAnnotationsOverrideRepeatableWins() {
|
||||||
|
LayeredTypeAnnotations annotations = new LayeredTypeAnnotations();
|
||||||
|
annotations.appendLayerFrom(TypeAnnotationLayer.TYPE_DECLARATION, Repeated.class);
|
||||||
|
annotations.appendLayerFrom(TypeAnnotationLayer.TYPE_USE, B.class);
|
||||||
|
|
||||||
|
assertThat(annotations.getAnnotations()).singleElement()
|
||||||
|
.asInstanceOf(type(TestAnnotation.class))
|
||||||
|
.extracting(TestAnnotation::value)
|
||||||
|
.isEqualTo("b");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getAnnotationsOverrideRepeatableContainerWins() {
|
||||||
|
LayeredTypeAnnotations annotations = new LayeredTypeAnnotations();
|
||||||
|
annotations.appendLayerFrom(TypeAnnotationLayer.TYPE_DECLARATION, B.class);
|
||||||
|
annotations.appendLayerFrom(TypeAnnotationLayer.TYPE_USE, Repeated.class);
|
||||||
|
|
||||||
|
assertThat(annotations.getAnnotations()).singleElement()
|
||||||
|
.asInstanceOf(type(TestAnnotations.class))
|
||||||
|
.extracting(TestAnnotations::value)
|
||||||
|
.asInstanceOf(array(TestAnnotation[].class))
|
||||||
|
.satisfiesExactly(
|
||||||
|
a -> assertThat(a.value()).isEqualTo("r1"),
|
||||||
|
a -> assertThat(a.value()).isEqualTo("r2")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestAnnotation("r1")
|
||||||
|
@TestAnnotation("r2")
|
||||||
|
private static class Repeated {
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestAnnotation("a")
|
||||||
|
private static class A {
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestAnnotation("b")
|
||||||
|
private static class B {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package de.siphalor.tweed5.typeutils.api.type;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.TYPE_USE)
|
||||||
|
@Repeatable(TestAnnotations.class)
|
||||||
|
public @interface TestAnnotation {
|
||||||
|
String value();
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package de.siphalor.tweed5.typeutils.api.type;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.TYPE_USE)
|
||||||
|
public @interface TestAnnotations {
|
||||||
|
TestAnnotation[] value();
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package de.siphalor.tweed5.typeutils.test;
|
||||||
|
|
||||||
|
import de.siphalor.tweed5.typeutils.api.type.TestAnnotation;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import org.assertj.core.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.lang.reflect.AnnotatedParameterizedType;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.ParameterizedType;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.InstanceOfAssertFactories.type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Various test to demonstrate the workings of Java's reflection type system
|
||||||
|
*/
|
||||||
|
public class JavaReflectionTests {
|
||||||
|
@SneakyThrows
|
||||||
|
@Test
|
||||||
|
void parameterizedType() {
|
||||||
|
Field intsField = TestClass.class.getField("ints");
|
||||||
|
assertThat(intsField.getGenericType())
|
||||||
|
.asInstanceOf(type(ParameterizedType.class))
|
||||||
|
.extracting(ParameterizedType::getRawType)
|
||||||
|
.isInstanceOf(Class.class)
|
||||||
|
.isEqualTo(List.class);
|
||||||
|
assertThat(intsField.getAnnotatedType())
|
||||||
|
.asInstanceOf(type(AnnotatedParameterizedType.class))
|
||||||
|
.satisfies(
|
||||||
|
type -> Assertions.assertThat(type.getAnnotation(TestAnnotation.class)).isNotNull(),
|
||||||
|
type -> assertThat(type.getAnnotatedActualTypeArguments())
|
||||||
|
.singleElement()
|
||||||
|
.isInstanceOf(AnnotatedParameterizedType.class)
|
||||||
|
.satisfies(arg -> Assertions.assertThat(arg.getAnnotation(TestAnnotation.class)).isNull())
|
||||||
|
);
|
||||||
|
assertThat(TestClass.class.getField("string").getGenericType()).isInstanceOf(Class.class)
|
||||||
|
.isEqualTo(String.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void repeatableAnnotation() {
|
||||||
|
assertThat(TestClass.class.getAnnotationsByType(TestAnnotation.class)).hasSize(3);
|
||||||
|
assertThat(TestClass.class.getAnnotations())
|
||||||
|
.doesNotHaveAnyElementsOfTypes(TestAnnotation.class);
|
||||||
|
assertThat(TestClass.class.getAnnotation(TestAnnotation.class)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestAnnotation("a")
|
||||||
|
@TestAnnotation("b")
|
||||||
|
@TestAnnotation("c")
|
||||||
|
static class TestClass {
|
||||||
|
public String string;
|
||||||
|
@TestAnnotation("x")
|
||||||
|
public List<List<@TestAnnotation("y") Integer>> ints;
|
||||||
|
public TestCollection<Long, String> collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TestCollection<A, B> extends Collection<Map<A, B>> {}
|
||||||
|
}
|
||||||
@@ -74,9 +74,13 @@ public class ReadWritePojoPostProcessor implements TweedPojoWeavingPostProcessor
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
readWriteExtension.setEntryReaderWriterDefinition(configEntry, createDefinitionFromEntryConfig(entryConfig, context));
|
EntryReaderWriterDefinition definition = createDefinitionFromEntryConfig(entryConfig, context);
|
||||||
|
if (definition != null) {
|
||||||
|
readWriteExtension.setEntryReaderWriterDefinition(configEntry, definition);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
private EntryReaderWriterDefinition createDefinitionFromEntryConfig(EntryReadWriteConfig entryConfig, WeavingContext context) {
|
private EntryReaderWriterDefinition createDefinitionFromEntryConfig(EntryReadWriteConfig entryConfig, WeavingContext context) {
|
||||||
String readerSpecText = entryConfig.reader().isEmpty() ? entryConfig.value() : entryConfig.reader();
|
String readerSpecText = entryConfig.reader().isEmpty() ? entryConfig.value() : entryConfig.reader();
|
||||||
String writerSpecText = entryConfig.writer().isEmpty() ? entryConfig.value() : entryConfig.writer();
|
String writerSpecText = entryConfig.writer().isEmpty() ? entryConfig.value() : entryConfig.writer();
|
||||||
@@ -90,6 +94,10 @@ public class ReadWritePojoPostProcessor implements TweedPojoWeavingPostProcessor
|
|||||||
writerSpec = specFromText(writerSpecText, context);
|
writerSpec = specFromText(writerSpecText, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (readerSpec == null && writerSpec == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
TweedEntryReader<?, ?> reader = readerSpec == null
|
TweedEntryReader<?, ?> reader = readerSpec == null
|
||||||
? TweedEntryReaderWriterImpls.NOOP_READER_WRITER
|
? TweedEntryReaderWriterImpls.NOOP_READER_WRITER
|
||||||
@@ -165,10 +173,10 @@ public class ReadWritePojoPostProcessor implements TweedPojoWeavingPostProcessor
|
|||||||
private <T> T loadClassIfExists(Class<T> baseClass, String className, T[] arguments) {
|
private <T> T loadClassIfExists(Class<T> baseClass, String className, T[] arguments) {
|
||||||
try {
|
try {
|
||||||
Class<?> clazz = Class.forName(className);
|
Class<?> clazz = Class.forName(className);
|
||||||
Class<?>[] argClassses = new Class<?>[arguments.length];
|
Class<?>[] argClasses = new Class<?>[arguments.length];
|
||||||
Arrays.fill(argClassses, baseClass);
|
Arrays.fill(argClasses, baseClass);
|
||||||
|
|
||||||
Constructor<?> constructor = clazz.getConstructor(argClassses);
|
Constructor<?> constructor = clazz.getConstructor(argClasses);
|
||||||
|
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
return (T) constructor.newInstance((Object[]) arguments);
|
return (T) constructor.newInstance((Object[]) arguments);
|
||||||
|
|||||||
@@ -5,4 +5,5 @@ plugins {
|
|||||||
dependencies {
|
dependencies {
|
||||||
api(project(":tweed5-core"))
|
api(project(":tweed5-core"))
|
||||||
api(project(":tweed5-naming-format"))
|
api(project(":tweed5-naming-format"))
|
||||||
|
api(project(":tweed5-type-utils"))
|
||||||
}
|
}
|
||||||
@@ -11,7 +11,7 @@ import java.lang.annotation.Target;
|
|||||||
* Marks this class as a class that should be woven as a {@link de.siphalor.tweed5.core.api.entry.CompoundConfigEntry}.
|
* Marks this class as a class that should be woven as a {@link de.siphalor.tweed5.core.api.entry.CompoundConfigEntry}.
|
||||||
*/
|
*/
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target({ElementType.TYPE, ElementType.FIELD})
|
@Target({ElementType.TYPE, ElementType.TYPE_USE})
|
||||||
public @interface CompoundWeaving {
|
public @interface CompoundWeaving {
|
||||||
/**
|
/**
|
||||||
* The naming format to use for this POJO.
|
* The naming format to use for this POJO.
|
||||||
|
|||||||
@@ -1,140 +0,0 @@
|
|||||||
package de.siphalor.tweed5.weaver.pojo.api.weaving;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Repeatable;
|
|
||||||
import java.lang.reflect.AnnotatedElement;
|
|
||||||
import java.lang.reflect.Array;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents multi-level annotations across multiple Java elements.
|
|
||||||
* E.g. annotations on a field overriding annotations declared on the field type.
|
|
||||||
*/
|
|
||||||
public class Annotations {
|
|
||||||
private static final List<ElementType> ELEMENT_TYPE_ORDER = Arrays.asList(
|
|
||||||
ElementType.TYPE_USE,
|
|
||||||
ElementType.FIELD,
|
|
||||||
ElementType.CONSTRUCTOR,
|
|
||||||
ElementType.METHOD,
|
|
||||||
ElementType.LOCAL_VARIABLE,
|
|
||||||
ElementType.TYPE_PARAMETER,
|
|
||||||
ElementType.TYPE,
|
|
||||||
ElementType.ANNOTATION_TYPE,
|
|
||||||
ElementType.PACKAGE
|
|
||||||
);
|
|
||||||
private final Map<ElementType, AnnotatedElement> elements = new EnumMap<>(ElementType.class);
|
|
||||||
|
|
||||||
public void addAnnotationsFrom(ElementType elementType, AnnotatedElement element) {
|
|
||||||
elements.put(elementType, element);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
|
|
||||||
for (ElementType elementType : ELEMENT_TYPE_ORDER) {
|
|
||||||
AnnotatedElement annotatedElement = elements.get(elementType);
|
|
||||||
if (annotatedElement != null) {
|
|
||||||
T annotation = annotatedElement.getAnnotation(annotationClass);
|
|
||||||
if (annotation != null) {
|
|
||||||
return annotation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public <T extends Annotation> T getAnnotation(ElementType elementType, Class<T> annotationType) {
|
|
||||||
AnnotatedElement annotatedElement = elements.get(elementType);
|
|
||||||
if (annotatedElement == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return annotatedElement.getAnnotation(annotationType);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public <T extends Annotation> T[] getAnnotationHierarchy(Class<T> annotationClass) {
|
|
||||||
List<T> hierarchy = new ArrayList<>(elements.size());
|
|
||||||
for (ElementType elementType : ELEMENT_TYPE_ORDER) {
|
|
||||||
AnnotatedElement element = elements.get(elementType);
|
|
||||||
if (element != null) {
|
|
||||||
T annotation = element.getAnnotation(annotationClass);
|
|
||||||
if (annotation != null) {
|
|
||||||
hierarchy.add(annotation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//noinspection unchecked
|
|
||||||
return hierarchy.toArray((T[]) Array.newInstance(annotationClass, hierarchy.size()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public <T extends Annotation> T[] getAnnotations(Class<T> annotationClass) {
|
|
||||||
for (ElementType elementType : ELEMENT_TYPE_ORDER) {
|
|
||||||
AnnotatedElement annotatedElement = elements.get(elementType);
|
|
||||||
if (annotatedElement != null) {
|
|
||||||
T[] annotations = annotatedElement.getAnnotationsByType(annotationClass);
|
|
||||||
if (annotations.length != 0) {
|
|
||||||
return annotations;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//noinspection unchecked
|
|
||||||
return (T[]) Array.newInstance(annotationClass, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public <T extends Annotation> T[] getAnnotations(ElementType elementType, Class<T> annotationType) {
|
|
||||||
AnnotatedElement annotatedElement = elements.get(elementType);
|
|
||||||
if (annotatedElement == null) {
|
|
||||||
//noinspection unchecked
|
|
||||||
return (T[]) Array.newInstance(annotationType, 0);
|
|
||||||
}
|
|
||||||
return annotatedElement.getAnnotationsByType(annotationType);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public <T extends Annotation> T[][] getAnnotationsHierachy(Class<T> annotationClass) {
|
|
||||||
List<T[]> hierarchy = new ArrayList<>(ELEMENT_TYPE_ORDER.size());
|
|
||||||
for (ElementType elementType : ELEMENT_TYPE_ORDER) {
|
|
||||||
AnnotatedElement annotatedElement = elements.get(elementType);
|
|
||||||
if (annotatedElement != null) {
|
|
||||||
T[] annotations = annotatedElement.getAnnotationsByType(annotationClass);
|
|
||||||
if (annotations.length != 0) {
|
|
||||||
hierarchy.add(annotations);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//noinspection unchecked
|
|
||||||
return hierarchy.toArray((T[][]) Array.newInstance(annotationClass, 0, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public Annotation[] getAllAnnotations() {
|
|
||||||
Map<Class<? extends Annotation>, Annotation[]> annotations = new HashMap<>();
|
|
||||||
for (ElementType elementType : ELEMENT_TYPE_ORDER) {
|
|
||||||
AnnotatedElement annotatedElement = elements.get(elementType);
|
|
||||||
if (annotatedElement != null) {
|
|
||||||
for (Annotation annotation : annotatedElement.getAnnotations()) {
|
|
||||||
annotations.putIfAbsent(annotation.annotationType(), new Annotation[]{annotation});
|
|
||||||
|
|
||||||
Repeatable repeatable = annotation.annotationType().getAnnotation(Repeatable.class);
|
|
||||||
if (repeatable != null) {
|
|
||||||
annotations.put(repeatable.value(), annotatedElement.getAnnotationsByType(repeatable.value()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (annotations.isEmpty()) {
|
|
||||||
return new Annotation[0];
|
|
||||||
} else if (annotations.size() == 1) {
|
|
||||||
return annotations.values().iterator().next();
|
|
||||||
} else {
|
|
||||||
return annotations.values().stream().flatMap(Arrays::stream).toArray(Annotation[]::new);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,6 +7,9 @@ import de.siphalor.tweed5.namingformat.api.NamingFormatCollector;
|
|||||||
import de.siphalor.tweed5.namingformat.api.NamingFormats;
|
import de.siphalor.tweed5.namingformat.api.NamingFormats;
|
||||||
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.LayeredTypeAnnotations;
|
||||||
|
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;
|
||||||
import de.siphalor.tweed5.weaver.pojo.impl.weaving.PojoWeavingException;
|
import de.siphalor.tweed5.weaver.pojo.impl.weaving.PojoWeavingException;
|
||||||
@@ -15,8 +18,8 @@ import de.siphalor.tweed5.weaver.pojo.impl.weaving.compound.CompoundWeavingConfi
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.invoke.MethodHandle;
|
import java.lang.invoke.MethodHandle;
|
||||||
|
import java.lang.reflect.AnnotatedElement;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -40,7 +43,7 @@ public class CompoundPojoWeaver implements TweedPojoWeaver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable <T> ConfigEntry<T> weaveEntry(Class<T> valueClass, WeavingContext context) {
|
public @Nullable <T> ConfigEntry<T> weaveEntry(ActualType<T> valueType, WeavingContext context) {
|
||||||
if (context.annotations().getAnnotation(CompoundWeaving.class) == null) {
|
if (context.annotations().getAnnotation(CompoundWeaving.class) == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -49,7 +52,7 @@ public class CompoundPojoWeaver implements TweedPojoWeaver {
|
|||||||
WeavingContext.ExtensionsData newExtensionsData = context.extensionsData().copy();
|
WeavingContext.ExtensionsData newExtensionsData = context.extensionsData().copy();
|
||||||
weavingConfigAccess.set(newExtensionsData, weavingConfig);
|
weavingConfigAccess.set(newExtensionsData, weavingConfig);
|
||||||
|
|
||||||
PojoClassIntrospector introspector = PojoClassIntrospector.forClass(valueClass);
|
PojoClassIntrospector introspector = PojoClassIntrospector.forClass(valueType.declaredType());
|
||||||
|
|
||||||
WeavableCompoundConfigEntry<T> compoundEntry = instantiateCompoundEntry(introspector, weavingConfig);
|
WeavableCompoundConfigEntry<T> compoundEntry = instantiateCompoundEntry(introspector, weavingConfig);
|
||||||
|
|
||||||
@@ -62,7 +65,7 @@ public class CompoundPojoWeaver implements TweedPojoWeaver {
|
|||||||
|
|
||||||
return compoundEntry;
|
return compoundEntry;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new PojoWeavingException("Exception occurred trying to weave compound for class " + valueClass.getName(), e);
|
throw new PojoWeavingException("Exception occurred trying to weave compound for class " + valueType, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,27 +85,8 @@ public class CompoundPojoWeaver implements TweedPojoWeaver {
|
|||||||
return CompoundWeavingConfigImpl.withOverrides(parent, local);
|
return CompoundWeavingConfigImpl.withOverrides(parent, local);
|
||||||
}
|
}
|
||||||
|
|
||||||
private WeavingContext createSubContextForProperty(
|
|
||||||
PojoClassIntrospector.Property property,
|
|
||||||
String name,
|
|
||||||
WeavingContext.ExtensionsData newExtensionsData,
|
|
||||||
WeavingContext parentContext
|
|
||||||
) {
|
|
||||||
return parentContext.subContextBuilder(name)
|
|
||||||
.annotations(collectAnnotationsForField(property.field()))
|
|
||||||
.extensionsData(newExtensionsData)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Annotations collectAnnotationsForField(Field field) {
|
|
||||||
Annotations annotations = new Annotations();
|
|
||||||
annotations.addAnnotationsFrom(ElementType.TYPE, field.getType());
|
|
||||||
annotations.addAnnotationsFrom(ElementType.FIELD, field);
|
|
||||||
return annotations;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private CompoundWeavingConfig createWeavingConfigFromAnnotations(Annotations annotations) {
|
private CompoundWeavingConfig createWeavingConfigFromAnnotations(@NotNull AnnotatedElement annotations) {
|
||||||
CompoundWeaving annotation = annotations.getAnnotation(CompoundWeaving.class);
|
CompoundWeaving annotation = annotations.getAnnotation(CompoundWeaving.class);
|
||||||
if (annotation == null) {
|
if (annotation == null) {
|
||||||
return null;
|
return null;
|
||||||
@@ -163,7 +147,7 @@ public class CompoundPojoWeaver implements TweedPojoWeaver {
|
|||||||
// TODO
|
// TODO
|
||||||
throw new UnsupportedOperationException("Final config entries are not supported in weaving yet.");
|
throw new UnsupportedOperationException("Final config entries are not supported in weaving yet.");
|
||||||
} else {
|
} else {
|
||||||
subEntry = subContext.weaveEntry(property.field().getType(), subContext);
|
subEntry = subContext.weaveEntry(ActualType.ofUsedType(property.field().getAnnotatedType()), subContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new StaticPojoCompoundConfigEntry.SubEntry(
|
return new StaticPojoCompoundConfigEntry.SubEntry(
|
||||||
@@ -192,4 +176,24 @@ public class CompoundPojoWeaver implements TweedPojoWeaver {
|
|||||||
}
|
}
|
||||||
return namingFormat;
|
return namingFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private WeavingContext createSubContextForProperty(
|
||||||
|
PojoClassIntrospector.Property property,
|
||||||
|
String name,
|
||||||
|
WeavingContext.ExtensionsData newExtensionsData,
|
||||||
|
WeavingContext parentContext
|
||||||
|
) {
|
||||||
|
return parentContext.subContextBuilder(name)
|
||||||
|
.annotations(collectAnnotationsForField(property.field()))
|
||||||
|
.extensionsData(newExtensionsData)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private AnnotatedElement collectAnnotationsForField(Field field) {
|
||||||
|
LayeredTypeAnnotations annotations = new LayeredTypeAnnotations();
|
||||||
|
annotations.appendLayerFrom(TypeAnnotationLayer.TYPE_DECLARATION, field.getType());
|
||||||
|
annotations.appendLayerFrom(TypeAnnotationLayer.TYPE_USE, field);
|
||||||
|
annotations.appendLayerFrom(TypeAnnotationLayer.TYPE_USE, field.getAnnotatedType());
|
||||||
|
return annotations;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package de.siphalor.tweed5.weaver.pojo.api.weaving;
|
|||||||
|
|
||||||
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||||
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
|
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
|
||||||
|
import de.siphalor.tweed5.typeutils.api.type.ActualType;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
public class TrivialPojoWeaver implements TweedPojoWeaver {
|
public class TrivialPojoWeaver implements TweedPojoWeaver {
|
||||||
@@ -11,7 +12,7 @@ public class TrivialPojoWeaver implements TweedPojoWeaver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable <T> ConfigEntry<T> weaveEntry(Class<T> valueClass, WeavingContext context) {
|
public @Nullable <T> ConfigEntry<T> weaveEntry(ActualType<T> valueType, WeavingContext context) {
|
||||||
return new SimpleConfigEntryImpl<>(valueClass);
|
return new SimpleConfigEntryImpl<>(valueType.declaredType());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package de.siphalor.tweed5.weaver.pojo.api.weaving;
|
package de.siphalor.tweed5.weaver.pojo.api.weaving;
|
||||||
|
|
||||||
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||||
|
import de.siphalor.tweed5.typeutils.api.type.ActualType;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
@@ -12,7 +13,7 @@ public interface TweedPojoWeavingFunction {
|
|||||||
* @return The resulting, sealed config entry or {@code null}, if the weaving function is not applicable to the given parameters.
|
* @return The resulting, sealed config entry or {@code null}, if the weaving function is not applicable to the given parameters.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
<T> ConfigEntry<T> weaveEntry(Class<T> valueClass, WeavingContext context);
|
<T> ConfigEntry<T> weaveEntry(ActualType<T> valueType, WeavingContext context);
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
interface NonNull extends TweedPojoWeavingFunction {
|
interface NonNull extends TweedPojoWeavingFunction {
|
||||||
@@ -24,6 +25,6 @@ public interface TweedPojoWeavingFunction {
|
|||||||
* @throws RuntimeException when a valid config entry could not be resolved.
|
* @throws RuntimeException when a valid config entry could not be resolved.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@NotNull <T> ConfigEntry<T> weaveEntry(Class<T> valueClass, WeavingContext context);
|
@NotNull <T> ConfigEntry<T> weaveEntry(ActualType<T> valueType, WeavingContext context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ package de.siphalor.tweed5.weaver.pojo.api.weaving;
|
|||||||
import de.siphalor.tweed5.core.api.container.ConfigContainer;
|
import de.siphalor.tweed5.core.api.container.ConfigContainer;
|
||||||
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||||
import de.siphalor.tweed5.patchwork.api.Patchwork;
|
import de.siphalor.tweed5.patchwork.api.Patchwork;
|
||||||
|
import de.siphalor.tweed5.typeutils.api.type.ActualType;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.lang.reflect.AnnotatedElement;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
@Value
|
@Value
|
||||||
@@ -24,7 +26,7 @@ public class WeavingContext implements TweedPojoWeavingFunction.NonNull {
|
|||||||
@NotNull
|
@NotNull
|
||||||
ExtensionsData extensionsData;
|
ExtensionsData extensionsData;
|
||||||
@NotNull
|
@NotNull
|
||||||
Annotations annotations;
|
AnnotatedElement annotations;
|
||||||
|
|
||||||
public static Builder builder(TweedPojoWeavingFunction.NonNull weavingFunction, ConfigContainer<?> configContainer) {
|
public static Builder builder(TweedPojoWeavingFunction.NonNull weavingFunction, ConfigContainer<?> configContainer) {
|
||||||
return new Builder(null, weavingFunction, configContainer, new String[0]);
|
return new Builder(null, weavingFunction, configContainer, new String[0]);
|
||||||
@@ -41,8 +43,8 @@ public class WeavingContext implements TweedPojoWeavingFunction.NonNull {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull <T> ConfigEntry<T> weaveEntry(Class<T> valueClass, WeavingContext context) {
|
public @NotNull <T> ConfigEntry<T> weaveEntry(ActualType<T> valueType, WeavingContext context) {
|
||||||
return weavingFunction.weaveEntry(valueClass, context);
|
return weavingFunction.weaveEntry(valueType, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface ExtensionsData extends Patchwork<ExtensionsData> {}
|
public interface ExtensionsData extends Patchwork<ExtensionsData> {}
|
||||||
@@ -57,7 +59,7 @@ public class WeavingContext implements TweedPojoWeavingFunction.NonNull {
|
|||||||
private final ConfigContainer<?> configContainer;
|
private final ConfigContainer<?> configContainer;
|
||||||
private final String[] path;
|
private final String[] path;
|
||||||
private ExtensionsData extensionsData;
|
private ExtensionsData extensionsData;
|
||||||
private Annotations annotations;
|
private AnnotatedElement annotations;
|
||||||
|
|
||||||
public WeavingContext build() {
|
public WeavingContext build() {
|
||||||
return new WeavingContext(
|
return new WeavingContext(
|
||||||
|
|||||||
@@ -8,10 +8,8 @@ import de.siphalor.tweed5.patchwork.api.PatchworkClassCreator;
|
|||||||
import de.siphalor.tweed5.patchwork.impl.PatchworkClass;
|
import de.siphalor.tweed5.patchwork.impl.PatchworkClass;
|
||||||
import de.siphalor.tweed5.patchwork.impl.PatchworkClassGenerator;
|
import de.siphalor.tweed5.patchwork.impl.PatchworkClassGenerator;
|
||||||
import de.siphalor.tweed5.patchwork.impl.PatchworkClassPart;
|
import de.siphalor.tweed5.patchwork.impl.PatchworkClassPart;
|
||||||
import de.siphalor.tweed5.utils.api.collection.ClassToInstancesMultimap;
|
|
||||||
import de.siphalor.tweed5.utils.api.collection.InheritanceMap;
|
|
||||||
import de.siphalor.tweed5.weaver.pojo.api.annotation.PojoWeaving;
|
import de.siphalor.tweed5.weaver.pojo.api.annotation.PojoWeaving;
|
||||||
import de.siphalor.tweed5.weaver.pojo.api.weaving.Annotations;
|
import de.siphalor.tweed5.typeutils.api.type.ActualType;
|
||||||
import de.siphalor.tweed5.weaver.pojo.api.weaving.TweedPojoWeaver;
|
import de.siphalor.tweed5.weaver.pojo.api.weaving.TweedPojoWeaver;
|
||||||
import de.siphalor.tweed5.weaver.pojo.api.weaving.WeavingContext;
|
import de.siphalor.tweed5.weaver.pojo.api.weaving.WeavingContext;
|
||||||
import de.siphalor.tweed5.weaver.pojo.api.weaving.postprocess.TweedPojoWeavingPostProcessor;
|
import de.siphalor.tweed5.weaver.pojo.api.weaving.postprocess.TweedPojoWeavingPostProcessor;
|
||||||
@@ -20,7 +18,6 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.invoke.MethodHandle;
|
import java.lang.invoke.MethodHandle;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
@@ -168,7 +165,7 @@ public class TweedPojoWeaverBootstrapper<T> {
|
|||||||
setupWeavers();
|
setupWeavers();
|
||||||
WeavingContext weavingContext = createWeavingContext();
|
WeavingContext weavingContext = createWeavingContext();
|
||||||
|
|
||||||
ConfigEntry<T> rootEntry = this.weaveEntry(pojoClass, weavingContext);
|
ConfigEntry<T> rootEntry = this.weaveEntry(ActualType.ofClass(pojoClass), weavingContext);
|
||||||
configContainer.attachAndSealTree(rootEntry);
|
configContainer.attachAndSealTree(rootEntry);
|
||||||
|
|
||||||
return configContainer;
|
return configContainer;
|
||||||
@@ -214,19 +211,16 @@ public class TweedPojoWeaverBootstrapper<T> {
|
|||||||
try {
|
try {
|
||||||
WeavingContext.ExtensionsData extensionsData = (WeavingContext.ExtensionsData) contextExtensionsDataClass.constructor().invoke();
|
WeavingContext.ExtensionsData extensionsData = (WeavingContext.ExtensionsData) contextExtensionsDataClass.constructor().invoke();
|
||||||
|
|
||||||
Annotations annotations = new Annotations();
|
|
||||||
annotations.addAnnotationsFrom(ElementType.TYPE, pojoClass);
|
|
||||||
|
|
||||||
return WeavingContext.builder(this::weaveEntry, configContainer)
|
return WeavingContext.builder(this::weaveEntry, configContainer)
|
||||||
.extensionsData(extensionsData)
|
.extensionsData(extensionsData)
|
||||||
.annotations(annotations)
|
.annotations(pojoClass)
|
||||||
.build();
|
.build();
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
throw new PojoWeavingException("Failed to create weaving context's extension data");
|
throw new PojoWeavingException("Failed to create weaving context's extension data");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private <U> ConfigEntry<U> weaveEntry(Class<U> dataClass, WeavingContext context) {
|
private <U> ConfigEntry<U> weaveEntry(ActualType<U> dataClass, WeavingContext context) {
|
||||||
for (TweedPojoWeaver weaver : weavers) {
|
for (TweedPojoWeaver weaver : weavers) {
|
||||||
ConfigEntry<U> configEntry = weaver.weaveEntry(dataClass, context);
|
ConfigEntry<U> configEntry = weaver.weaveEntry(dataClass, context);
|
||||||
if (configEntry != null) {
|
if (configEntry != null) {
|
||||||
@@ -238,7 +232,7 @@ public class TweedPojoWeaverBootstrapper<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new PojoWeavingException("Failed to weave " + dataClass.getName() + ": No matching weavers found");
|
throw new PojoWeavingException("Failed to weave " + dataClass + ": No matching weavers found");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyPostProcessors(ConfigEntry<?> configEntry, WeavingContext context) {
|
private void applyPostProcessors(ConfigEntry<?> configEntry, WeavingContext context) {
|
||||||
|
|||||||
@@ -7,14 +7,13 @@ import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
|
|||||||
import de.siphalor.tweed5.namingformat.api.NamingFormat;
|
import de.siphalor.tweed5.namingformat.api.NamingFormat;
|
||||||
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.weaver.pojo.impl.weaving.compound.CompoundWeavingConfig;
|
import de.siphalor.tweed5.weaver.pojo.impl.weaving.compound.CompoundWeavingConfig;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
|
|
||||||
import static de.siphalor.tweed5.weaver.pojo.test.ConfigEntryAssertions.isCompoundEntryForClassWith;
|
import static de.siphalor.tweed5.weaver.pojo.test.ConfigEntryAssertions.isCompoundEntryForClassWith;
|
||||||
import static de.siphalor.tweed5.weaver.pojo.test.ConfigEntryAssertions.isSimpleEntryForClass;
|
import static de.siphalor.tweed5.weaver.pojo.test.ConfigEntryAssertions.isSimpleEntryForClass;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
@@ -33,28 +32,25 @@ class CompoundPojoWeaverTest {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Annotations annotations = new Annotations();
|
|
||||||
annotations.addAnnotationsFrom(ElementType.TYPE, Compound.class);
|
|
||||||
|
|
||||||
WeavingContext weavingContext = WeavingContext.builder(new TweedPojoWeavingFunction.NonNull() {
|
WeavingContext weavingContext = WeavingContext.builder(new TweedPojoWeavingFunction.NonNull() {
|
||||||
@Override
|
@Override
|
||||||
public @NotNull <T> ConfigEntry<T> weaveEntry(Class<T> valueClass, WeavingContext context) {
|
public @NotNull <T> ConfigEntry<T> weaveEntry(ActualType<T> valueType, WeavingContext context) {
|
||||||
ConfigEntry<T> entry = compoundWeaver.weaveEntry(valueClass, context);
|
ConfigEntry<T> entry = compoundWeaver.weaveEntry(valueType, context);
|
||||||
if (entry != null) {
|
if (entry != null) {
|
||||||
return entry;
|
return entry;
|
||||||
} else {
|
} else {
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
ConfigEntry<T> configEntry = mock((Class<SimpleConfigEntry<T>>) (Class<?>) SimpleConfigEntry.class);
|
ConfigEntry<T> configEntry = mock((Class<SimpleConfigEntry<T>>) (Class<?>) SimpleConfigEntry.class);
|
||||||
when(configEntry.valueClass()).thenReturn(valueClass);
|
when(configEntry.valueClass()).thenReturn(valueType.declaredType());
|
||||||
return configEntry;
|
return configEntry;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, mock(ConfigContainer.class))
|
}, mock(ConfigContainer.class))
|
||||||
.extensionsData(new ExtensionsDataMock(null))
|
.extensionsData(new ExtensionsDataMock(null))
|
||||||
.annotations(annotations)
|
.annotations(Compound.class)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
ConfigEntry<Compound> resultEntry = compoundWeaver.weaveEntry(Compound.class, weavingContext);
|
ConfigEntry<Compound> resultEntry = compoundWeaver.weaveEntry(ActualType.ofClass(Compound.class), weavingContext);
|
||||||
|
|
||||||
assertThat(resultEntry).satisfies(isCompoundEntryForClassWith(Compound.class, compoundEntry -> assertThat(compoundEntry.subEntries())
|
assertThat(resultEntry).satisfies(isCompoundEntryForClassWith(Compound.class, compoundEntry -> assertThat(compoundEntry.subEntries())
|
||||||
.hasEntrySatisfying("an-integer", isSimpleEntryForClass(int.class))
|
.hasEntrySatisfying("an-integer", isSimpleEntryForClass(int.class))
|
||||||
|
|||||||
@@ -12,21 +12,25 @@ import static org.assertj.core.api.InstanceOfAssertFactories.type;
|
|||||||
public class ConfigEntryAssertions {
|
public class ConfigEntryAssertions {
|
||||||
public static Consumer<Object> isSimpleEntryForClass(Class<?> valueClass) {
|
public static Consumer<Object> isSimpleEntryForClass(Class<?> valueClass) {
|
||||||
return object -> assertThat(object)
|
return object -> assertThat(object)
|
||||||
|
.as("Should be a simple config entry for class " + valueClass.getName())
|
||||||
.asInstanceOf(type(SimpleConfigEntry.class))
|
.asInstanceOf(type(SimpleConfigEntry.class))
|
||||||
.extracting(ConfigEntry::valueClass)
|
.extracting(ConfigEntry::valueClass)
|
||||||
.isEqualTo(valueClass);
|
.isEqualTo(valueClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public static <T> Consumer<Object> isCompoundEntryForClassWith(
|
public static <T> Consumer<Object> isCompoundEntryForClassWith(
|
||||||
Class<T> compoundClass,
|
Class<T> compoundClass,
|
||||||
Consumer<CompoundConfigEntry<T>> condition
|
Consumer<CompoundConfigEntry<T>> condition
|
||||||
) {
|
) {
|
||||||
return object -> assertThat(object)
|
return object -> assertThat(object)
|
||||||
|
.as("Should be a compound config entry for class " + compoundClass.getSimpleName())
|
||||||
.asInstanceOf(type(CompoundConfigEntry.class))
|
.asInstanceOf(type(CompoundConfigEntry.class))
|
||||||
.satisfies(compoundEntry -> {
|
.as("Compound entry for class " + compoundClass.getSimpleName())
|
||||||
assertThat(compoundEntry.valueClass()).isEqualTo(compoundClass);
|
.satisfies(
|
||||||
condition.accept(compoundEntry);
|
compoundEntry -> assertThat(compoundEntry.valueClass())
|
||||||
});
|
.as("Value class of compound entry should match")
|
||||||
|
.isEqualTo(compoundClass),
|
||||||
|
condition::accept
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user