io.smallrye.graphql.client.model.Annotations Maven / Gradle / Ivy
package io.smallrye.graphql.client.model;
import static java.util.Collections.emptyMap;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationTarget.Kind;
import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.FieldInfo;
import org.jboss.jandex.MethodInfo;
import org.jboss.jandex.MethodParameterInfo;
import org.jboss.jandex.Type;
public class Annotations {
// todo: maybe use some of the helper methods in this class
private final Map annotationsMap;
public final Map parentAnnotations;
/**
* Get used when creating operations.
* Operation only have methods (no properties)
*
* @param methodInfo the java method
* @return Annotations for this method and its return-type
*/
public static Annotations getAnnotationsForMethod(MethodInfo methodInfo) {
Map annotationMap = new HashMap<>();
for (AnnotationInstance annotationInstance : methodInfo.annotations()) {
DotName name = annotationInstance.name();
Kind kind = annotationInstance.target().kind();
if (kind.equals(Kind.METHOD)) {
annotationMap.put(name, annotationInstance);
}
}
final Type type = methodInfo.returnType();
if (Classes.isParameterized(type)) {
Type wrappedType = type.asParameterizedType().arguments().get(0);
for (final AnnotationInstance annotationInstance : wrappedType.annotations()) {
DotName name = annotationInstance.name();
annotationMap.put(name, annotationInstance);
}
}
Map parentAnnotations = getParentAnnotations(methodInfo.declaringClass());
return new Annotations(annotationMap, parentAnnotations);
}
private static Map getParentAnnotations(FieldInfo fieldInfo, MethodInfo methodInfo) {
ClassInfo declaringClass = fieldInfo != null ? fieldInfo.declaringClass() : methodInfo.declaringClass();
return getParentAnnotations(declaringClass);
}
private static Map getParentAnnotations(ClassInfo classInfo) {
Map parentAnnotations = new HashMap<>();
for (AnnotationInstance classAnnotation : classInfo.declaredAnnotations()) {
parentAnnotations.putIfAbsent(classAnnotation.name(), classAnnotation);
}
Map packageAnnotations = getPackageAnnotations(classInfo);
for (DotName dotName : packageAnnotations.keySet()) {
parentAnnotations.putIfAbsent(dotName, packageAnnotations.get(dotName));
}
return parentAnnotations;
}
private static Map getPackageAnnotations(ClassInfo classInfo) {
Map packageAnnotations = new HashMap<>();
DotName packageName = packageInfo(classInfo);
if (packageName != null) {
ClassInfo packageInfo = ScanningContext.getIndex().getClassByName(packageName);
if (packageInfo != null) {
for (AnnotationInstance packageAnnotation : packageInfo.declaredAnnotations()) {
packageAnnotations.putIfAbsent(packageAnnotation.name(), packageAnnotation);
}
}
}
return packageAnnotations;
}
private static DotName packageInfo(ClassInfo classInfo) {
String className = classInfo.name().toString();
int index = className.lastIndexOf('.');
if (index == -1) {
return null;
}
return DotName.createSimple(className.substring(0, index) + ".package-info");
}
/**
* Get used when we create types and references to them
*
* Class level annotation for type creation.
*
* @param classInfo the java class
* @return annotation for this class
*/
public static Annotations getAnnotationsForClass(ClassInfo classInfo) {
Map annotationMap = new HashMap<>();
for (AnnotationInstance annotationInstance : classInfo.declaredAnnotations()) {
DotName name = annotationInstance.name();
annotationMap.put(name, annotationInstance);
}
Map packageAnnotations = getPackageAnnotations(classInfo);
for (DotName dotName : packageAnnotations.keySet()) {
annotationMap.putIfAbsent(dotName, packageAnnotations.get(dotName));
}
return new Annotations(annotationMap, packageAnnotations);
}
/**
* Get used when creating arrays.
*
* This will contains the annotation on the collection field and method
*
* @param typeInCollection the field java type
* @param methodTypeInCollection the method java type
* @return the annotation for this array
*/
public static Annotations getAnnotationsForArray(Type typeInCollection,
Type methodTypeInCollection) {
Map annotationMap = getAnnotationsForType(typeInCollection);
annotationMap.putAll(getAnnotationsForType(methodTypeInCollection));
return new Annotations(annotationMap);
}
//
/**
* Used when we are creating operation and arguments for these operations
*
* @param methodInfo the java method
* @param pos the argument position
* @return annotation for this argument
*/
public static Annotations getAnnotationsForArgument(MethodInfo methodInfo, short pos) {
if (pos >= methodInfo.parametersCount()) {
throw new IndexOutOfBoundsException(
"Parameter at position " + pos + " not found on method " + methodInfo.name());
}
final Type parameterType = methodInfo.parameterType(pos);
Map annotationMap = getAnnotations(parameterType);
for (AnnotationInstance anno : methodInfo.annotations()) {
if (anno.target().kind().equals(Kind.METHOD_PARAMETER)) {
MethodParameterInfo methodParameter = anno.target().asMethodParameter();
short position = methodParameter.position();
if (position == pos) {
annotationMap.put(anno.name(), anno);
}
}
}
final Map parentAnnotations = getParentAnnotations(methodInfo.declaringClass());
return new Annotations(annotationMap, parentAnnotations);
}
public static boolean isJsonBAnnotation(AnnotationInstance instance) {
return instance.name().toString().startsWith(JAKARTA_JSONB) || instance.name().toString().startsWith(JAVAX_JSONB);
}
// ------- All static creators done, now the actual class --------
private Annotations(Map annotations) {
this(annotations, new HashMap<>());
}
/**
* Create the annotations, mapped by name
*
* @param annotations the annotation
*/
private Annotations(Map annotations, Map parentAnnotations) {
this.annotationsMap = annotations;
this.parentAnnotations = parentAnnotations;
}
public Set getAnnotationNames() {
return annotationsMap.keySet();
}
public Annotations removeAnnotations(DotName... annotations) {
Map newAnnotationsMap = new HashMap<>(annotationsMap);
for (DotName annotation : annotations) {
newAnnotationsMap.remove(annotation);
}
return new Annotations(newAnnotationsMap, this.parentAnnotations);
}
/**
* Get a specific annotation
*
* @param annotation the annotation you want
* @return the annotation value or null
*/
public AnnotationValue getAnnotationValue(DotName annotation) {
return this.annotationsMap.get(annotation).value();
}
/**
* Get a specific annotation
*
* @param annotation the annotation you want
* @param name the name of the field that you want the value of
* @return the annotation value or null
*/
public AnnotationValue getAnnotationValue(DotName annotation, String name) {
return this.annotationsMap.get(annotation).value(name);
}
/**
* Check if there is an annotation and it has a valid value
*
* @param annotation the annotation we are checking
* @return true if valid value
*/
public boolean containsKeyAndValidValue(DotName annotation) {
return this.annotationsMap.containsKey(annotation) && this.annotationsMap.get(annotation).value() != null;
}
/**
* Check if one of these annotations is present
*
* @param annotations the annotations to check
* @return true if it does
*/
public boolean containsOneOfTheseAnnotations(DotName... annotations) {
for (DotName name : annotations) {
if (this.annotationsMap.containsKey(name)) {
return true;
}
}
return false;
}
public boolean containsOneOfTheseInheritableAnnotations(DotName... annotations) {
for (DotName name : annotations) {
if (this.parentAnnotations.containsKey(name)) {
return true;
}
}
return false;
}
/**
* Get on of these annotations
*
* @param annotations the annotations to check (in order)
* @return the annotation potentially or empty if not found
*/
public Optional getOneOfTheseAnnotations(DotName... annotations) {
for (DotName name : annotations) {
if (this.annotationsMap.containsKey(name)) {
return Optional.of(this.annotationsMap.get(name));
}
}
return Optional.empty();
}
/**
* This go through a list of annotations and find the first one that has a valid value.
* If it could not find one, it return empty
*
* @param annotations the annotations in order
* @return the valid annotation value or default value
*/
public Optional getOneOfTheseAnnotationsValue(DotName... annotations) {
for (DotName dotName : annotations) {
if (dotName != null && containsKeyAndValidValue(dotName)) {
return getStringValue(dotName);
}
}
return Optional.empty();
}
/**
* This go through a list of method annotations and find the first one that has a valid value.
* If it could not find one, it return the default value.
*
* @param annotations the annotations in order
* @return the valid annotation value or empty
*/
public Optional getOneOfTheseMethodAnnotationsValue(DotName... annotations) {
for (DotName dotName : annotations) {
if (dotName != null && hasValidMethodAnnotation(dotName)) {
return getStringValue(dotName);
}
}
return Optional.empty();
}
/**
* This go through a list of method parameter annotations and find the first one that has a valid value.
* If it could not find one, it return the default value.
*
* @param annotations the annotations in order
* @return the valid annotation value or empty
*/
public Optional getOneOfTheseMethodParameterAnnotationsValue(DotName... annotations) {
for (DotName dotName : annotations) {
if (dotName != null && hasValidMethodParameterAnnotation(dotName)) {
return getStringValue(dotName);
}
}
return Optional.empty();
}
/**
* Get a stream of that annotation, maybe empty if not present, maybe a stream of one, or maybe several, if it's repeatable.
*
* @param name dotname of the annotation
*/
public Stream resolve(DotName name) {
var annotationInstance = annotationsMap.get(name);
if (annotationInstance == null) {
var repeatableType = ScanningContext.getIndex().getClassByName(name);
if (repeatableType.hasAnnotation(REPEATABLE)) {
DotName containerName = repeatableType.annotation(REPEATABLE).value().asClass().name();
AnnotationInstance containerAnnotation = annotationsMap.get(containerName);
if (containerAnnotation != null) {
return Stream.of(containerAnnotation.value().asNestedArray());
}
}
return Stream.of();
}
return Stream.of(annotationInstance);
}
@Override
public String toString() {
return annotationsMap.toString();
}
private boolean hasValidMethodAnnotation(DotName annotation) {
return containsKeyAndValidValue(annotation) && isMethodAnnotation(getAnnotation(annotation));
}
private boolean hasValidMethodParameterAnnotation(DotName annotation) {
return containsKeyAndValidValue(annotation) && isMethodParameterAnnotation(getAnnotation(annotation));
}
private AnnotationInstance getAnnotation(DotName key) {
return this.annotationsMap.get(key);
}
private Optional getStringValue(DotName annotation) {
AnnotationInstance annotationInstance = getAnnotation(annotation);
if (annotationInstance != null) {
return getStringValue(annotationInstance);
}
return Optional.empty();
}
private Optional getStringValue(AnnotationInstance annotationInstance) {
AnnotationValue value = annotationInstance.value();
if (value != null) {
return getStringValue(value);
}
return Optional.empty();
}
private Optional getStringValue(AnnotationValue annotationValue) {
String value;
if (annotationValue != null) {
value = annotationValue.asString();
if (value != null && !value.isEmpty()) {
return Optional.of(value);
}
}
return Optional.empty();
}
// Private static methods use by the static initializers
private static boolean isMethodAnnotation(AnnotationInstance instance) {
return instance.target().kind().equals(Kind.METHOD);
}
private static boolean isMethodParameterAnnotation(AnnotationInstance instance) {
return instance.target().kind().equals(Kind.METHOD_PARAMETER);
}
private static Annotations getAnnotationsForInputField(FieldInfo fieldInfo, MethodInfo methodInfo) {
Map annotationsForField = getAnnotationsForField(fieldInfo, methodInfo);
if (fieldInfo != null) {
annotationsForField.putAll(getTypeUseAnnotations(fieldInfo.type()));
}
if (methodInfo != null) {
List parameters = methodInfo.parameterTypes();
if (!parameters.isEmpty()) {
Type param = parameters.get(ZERO);
annotationsForField.putAll(getTypeUseAnnotations(param));
}
}
final Map parentAnnotations = getParentAnnotations(fieldInfo, methodInfo);
return new Annotations(annotationsForField, parentAnnotations);
}
private static Annotations getAnnotationsForOutputField(FieldInfo fieldInfo, MethodInfo methodInfo) {
Map annotationsForField = getAnnotationsForField(fieldInfo, methodInfo);
if (fieldInfo != null) {
annotationsForField.putAll(getTypeUseAnnotations(fieldInfo.type()));
}
if (methodInfo != null) {
Type returnType = methodInfo.returnType();
if (returnType != null) {
annotationsForField.putAll(getTypeUseAnnotations(methodInfo.returnType()));
}
}
Map parentAnnotations = getParentAnnotations(fieldInfo, methodInfo);
return new Annotations(annotationsForField, parentAnnotations);
}
private static Map getTypeUseAnnotations(Type type) {
if (type != null) {
return getAnnotationsWithFilter(type,
Annotations.DATE_FORMAT,
Annotations.NUMBER_FORMAT);
}
return emptyMap();
}
private static Map getAnnotations(Type type) {
Map annotationMap = new HashMap<>();
if (type.kind().equals(Type.Kind.PARAMETERIZED_TYPE)) {
Type typeInCollection = type.asParameterizedType().arguments().get(0);
annotationMap.putAll(getAnnotations(typeInCollection));
} else {
List annotations = type.annotations();
for (AnnotationInstance annotationInstance : annotations) {
annotationMap.put(annotationInstance.name(), annotationInstance);
}
}
return annotationMap;
}
private static Map getAnnotationsForType(Type type) {
Map annotationMap = new HashMap<>();
for (AnnotationInstance annotationInstance : type.annotations()) {
DotName name = annotationInstance.name();
annotationMap.put(name, annotationInstance);
}
return annotationMap;
}
private static Map getAnnotationsForField(FieldInfo fieldInfo, MethodInfo methodInfo) {
Map annotationMap = new HashMap<>();
if (fieldInfo != null)
annotationMap.putAll(listToMap(fieldInfo.annotations().stream()
.filter(ai -> ai.target().kind() == Kind.FIELD)
.collect(Collectors.toList())));
if (methodInfo != null)
annotationMap.putAll(listToMap(methodInfo.annotations().stream()
.filter(ai -> ai.target().kind() == Kind.METHOD || ai.target().kind() == Kind.METHOD_PARAMETER)
.collect(Collectors.toList())));
return annotationMap;
}
private static Map listToMap(List annotationInstances) {
Map annotationMap = new HashMap<>();
for (AnnotationInstance annotationInstance : annotationInstances) {
DotName name = annotationInstance.name();
annotationMap.put(name, annotationInstance);
}
return annotationMap;
}
private static Map getAnnotationsWithFilter(Type type, DotName... filter) {
Map annotationMap = new HashMap<>();
if (type.kind().equals(Type.Kind.PARAMETERIZED_TYPE)) {
Type typeInCollection = type.asParameterizedType().arguments().get(0);
annotationMap.putAll(getAnnotationsWithFilter(typeInCollection, filter));
} else {
List annotations = type.annotations();
for (AnnotationInstance annotationInstance : annotations) {
if (Arrays.asList(filter).contains(annotationInstance.name())) {
annotationMap.put(annotationInstance.name(), annotationInstance);
}
}
}
return annotationMap;
}
private static final short ZERO = 0;
public static final DotName REPEATABLE = DotName.createSimple("java.lang.annotation.Repeatable");
// SmallRye Common Annotations
public static final DotName BLOCKING = DotName.createSimple("io.smallrye.common.annotation.Blocking");
public static final DotName NON_BLOCKING = DotName.createSimple("io.smallrye.common.annotation.NonBlocking");
// SmallRye GraphQL Annotations (Experimental)
public static final DotName TO_SCALAR = DotName.createSimple("io.smallrye.graphql.api.ToScalar"); // TODO: Remove
public static final DotName ADAPT_TO_SCALAR = DotName.createSimple("io.smallrye.graphql.api.AdaptToScalar");
public static final DotName ADAPT_WITH = DotName.createSimple("io.smallrye.graphql.api.AdaptWith");
public static final DotName ERROR_CODE = DotName.createSimple("io.smallrye.graphql.api.ErrorCode");
public static final DotName DATAFETCHER = DotName.createSimple("io.smallrye.graphql.api.DataFetcher");
public static final DotName SUBCRIPTION = DotName.createSimple("io.smallrye.graphql.api.Subscription");
public static final DotName DIRECTIVE = DotName.createSimple("io.smallrye.graphql.api.Directive");
public static final DotName DEFAULT_NON_NULL = DotName.createSimple("io.smallrye.graphql.api.DefaultNonNull");
public static final DotName NULLABLE = DotName.createSimple("io.smallrye.graphql.api.Nullable");
public static final DotName KOTLIN_METADATA = DotName.createSimple("kotlin.Metadata");
// MicroProfile GraphQL Annotations
public static final DotName GRAPHQL_CLIENT_API = DotName
.createSimple("io.smallrye.graphql.client.typesafe.api.GraphQLClientApi");
public static final DotName QUERY = DotName.createSimple("org.eclipse.microprofile.graphql.Query");
public static final DotName MUTATION = DotName.createSimple("org.eclipse.microprofile.graphql.Mutation");
public static final DotName INPUT = DotName.createSimple("org.eclipse.microprofile.graphql.Input");
public static final DotName TYPE = DotName.createSimple("org.eclipse.microprofile.graphql.Type");
public static final DotName INTERFACE = DotName.createSimple("org.eclipse.microprofile.graphql.Interface");
public static final DotName UNION = DotName.createSimple("io.smallrye.graphql.api.Union");
public static final DotName MULTIPLE = DotName.createSimple("io.smallrye.graphql.client.typesafe.api.Multiple");
public static final DotName NESTED_PARAMETER = DotName
.createSimple("io.smallrye.graphql.client.typesafe.api.NestedParameter");
public static final DotName ENUM = DotName.createSimple("org.eclipse.microprofile.graphql.Enum");
public static final DotName ID = DotName.createSimple("org.eclipse.microprofile.graphql.Id");
public static final DotName DESCRIPTION = DotName.createSimple("org.eclipse.microprofile.graphql.Description");
public static final DotName DATE_FORMAT = DotName.createSimple("org.eclipse.microprofile.graphql.DateFormat");
public static final DotName NUMBER_FORMAT = DotName.createSimple("org.eclipse.microprofile.graphql.NumberFormat");
public static final DotName DEFAULT_VALUE = DotName.createSimple("org.eclipse.microprofile.graphql.DefaultValue");
public static final DotName IGNORE = DotName.createSimple("org.eclipse.microprofile.graphql.Ignore");
public static final DotName NON_NULL = DotName.createSimple("org.eclipse.microprofile.graphql.NonNull");
public static final DotName NAME = DotName.createSimple("org.eclipse.microprofile.graphql.Name");
public static final DotName SOURCE = DotName.createSimple("org.eclipse.microprofile.graphql.Source");
// Json-B Annotations
public static final String JAVAX_JSONB = "javax.json.bind.annotation.";
public static final DotName JAVAX_JSONB_DATE_FORMAT = DotName.createSimple(JAVAX_JSONB + "JsonbDateFormat");
public static final DotName JAVAX_JSONB_NUMBER_FORMAT = DotName.createSimple(JAVAX_JSONB + "JsonbNumberFormat");
public static final DotName JAVAX_JSONB_PROPERTY = DotName.createSimple(JAVAX_JSONB + "JsonbProperty");
public static final DotName JAVAX_JSONB_TRANSIENT = DotName.createSimple(JAVAX_JSONB + "JsonbTransient");
public static final DotName JAVAX_JSONB_CREATOR = DotName.createSimple(JAVAX_JSONB + "JsonbCreator");
public static final DotName JAVAX_JSONB_TYPE_ADAPTER = DotName.createSimple(JAVAX_JSONB + "JsonbTypeAdapter");
public static final String JAKARTA_JSONB = "jakarta.json.bind.annotation.";
public static final DotName JAKARTA_JSONB_DATE_FORMAT = DotName.createSimple(JAKARTA_JSONB + "JsonbDateFormat");
public static final DotName JAKARTA_JSONB_NUMBER_FORMAT = DotName.createSimple(JAKARTA_JSONB + "JsonbNumberFormat");
public static final DotName JAKARTA_JSONB_PROPERTY = DotName.createSimple(JAKARTA_JSONB + "JsonbProperty");
public static final DotName JAKARTA_JSONB_TRANSIENT = DotName.createSimple(JAKARTA_JSONB + "JsonbTransient");
public static final DotName JAKARTA_JSONB_CREATOR = DotName.createSimple(JAKARTA_JSONB + "JsonbCreator");
public static final DotName JAKARTA_JSONB_TYPE_ADAPTER = DotName.createSimple(JAKARTA_JSONB + "JsonbTypeAdapter");
// Jackson Annotations
public static final DotName JACKSON_IGNORE = DotName.createSimple("com.fasterxml.jackson.annotation.JsonIgnore");
public static final DotName JACKSON_PROPERTY = DotName.createSimple("com.fasterxml.jackson.annotation.JsonProperty");
public static final DotName JACKSON_CREATOR = DotName.createSimple("com.fasterxml.jackson.annotation.JsonCreator");
public static final DotName JACKSON_FORMAT = DotName.createSimple("com.fasterxml.jackson.annotation.JsonFormat");
// Bean Validation Annotations (SmallRye extra, not part of the spec)
public static final DotName JAVAX_BEAN_VALIDATION_NOT_NULL = DotName.createSimple("javax.validation.constraints.NotNull");
public static final DotName JAVAX_BEAN_VALIDATION_NOT_EMPTY = DotName.createSimple("javax.validation.constraints.NotEmpty");
public static final DotName JAVAX_BEAN_VALIDATION_NOT_BLANK = DotName.createSimple("javax.validation.constraints.NotBlank");
public static final DotName JAKARTA_BEAN_VALIDATION_NOT_NULL = DotName
.createSimple("jakarta.validation.constraints.NotNull");
public static final DotName JAKARTA_BEAN_VALIDATION_NOT_EMPTY = DotName
.createSimple("jakarta.validation.constraints.NotEmpty");
public static final DotName JAKARTA_BEAN_VALIDATION_NOT_BLANK = DotName
.createSimple("jakarta.validation.constraints.NotBlank");
public static final DotName JAKARTA_NON_NULL = DotName.createSimple("jakarta.validation.constraints.NotNull");
//Kotlin NotNull
public static final DotName KOTLIN_NOT_NULL = DotName.createSimple("org.jetbrains.annotations.NotNull");
}