From 1155d7775daa471c7e95973ebc65893423277c48 Mon Sep 17 00:00:00 2001 From: Smaarn Date: Sun, 27 Oct 2024 16:22:18 +0100 Subject: [PATCH 01/13] feat: add support for `com.fasterxml.jackson.annotation.JacksonAnnotationsInside` helper annotation --- jsonschema-module-jackson/README.md | 1 + .../jackson/CustomEnumDefinitionProvider.java | 16 ++- .../module/jackson/JacksonHelper.java | 60 +++++++++ .../module/jackson/JacksonModule.java | 23 ++-- ...onIdentityReferenceDefinitionProvider.java | 18 ++- .../module/jackson/JsonPropertySorter.java | 9 +- .../module/jackson/JsonSubTypesResolver.java | 37 ++++-- .../JsonUnwrappedDefinitionProvider.java | 55 ++++++-- .../module/jackson/JacksonHelperTest.java | 121 ++++++++++++++++++ 9 files changed, 288 insertions(+), 52 deletions(-) create mode 100644 jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonHelper.java create mode 100644 jsonschema-module-jackson/src/test/java/com/github/victools/jsonschema/module/jackson/JacksonHelperTest.java diff --git a/jsonschema-module-jackson/README.md b/jsonschema-module-jackson/README.md index cc11c7ec..231015ef 100644 --- a/jsonschema-module-jackson/README.md +++ b/jsonschema-module-jackson/README.md @@ -21,6 +21,7 @@ Module for the [jsonschema-generator](../jsonschema-generator) – deriving JSON 13. Optionally: ignore all methods but those with a `@JsonProperty` annotation, if the `JacksonOption.INCLUDE_ONLY_JSONPROPERTY_ANNOTATED_METHODS` was provided (i.e. this is an "opt-in"). 14. Optionally: respect `@JsonIdentityReference(alwaysAsId=true)` annotation if there is a corresponding `@JsonIdentityInfo` annotation on the type and the `JacksonOption.JSONIDENTITY_REFERENCE_ALWAYS_AS_ID` as provided (i.e., this is an "opt-in") 15. Elevate nested properties to the parent type where members are annotated with `@JsonUnwrapped`. +16. Support `com.fasterxml.jackson.annotation.JacksonAnnotationsInside` annotated combo annotations Schema attributes derived from validation annotations on getter methods are also applied to their associated fields. diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java index 2ddbc0f7..cc277e01 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java @@ -28,6 +28,7 @@ import com.github.victools.jsonschema.generator.impl.AttributeCollector; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.Set; @@ -41,7 +42,6 @@ * definition (e.g. from one of the standard generator {@code Option}s). */ public class CustomEnumDefinitionProvider implements CustomDefinitionProviderV2 { - private final boolean checkForJsonValueAnnotatedMethod; private final boolean checkForJsonPropertyAnnotations; @@ -72,7 +72,7 @@ public CustomDefinition provideCustomSchemaDefinition(ResolvedType javaType, Sch serializedJsonValues = this.getSerializedValuesFromJsonValue(javaType, enumConstants, context); } if (serializedJsonValues == null && this.checkForJsonPropertyAnnotations) { - serializedJsonValues = this.getSerializedValuesFromJsonProperty(javaType, enumConstants); + serializedJsonValues = this.getSerializedValuesFromJsonProperty(javaType, enumConstants, context); } if (serializedJsonValues == null) { return null; @@ -121,7 +121,7 @@ protected ResolvedMethod getJsonValueAnnotatedMethod(ResolvedType javaType, Sche ResolvedMethod[] memberMethods = context.getTypeContext().resolveWithMembers(javaType).getMemberMethods(); Set jsonValueAnnotatedMethods = Stream.of(memberMethods) .filter(method -> method.getArgumentCount() == 0) - .filter(method -> Optional.ofNullable(method.getAnnotations().get(JsonValue.class)).map(JsonValue::value).orElse(false)) + .filter(method -> Optional.ofNullable(context.getTypeContext().getAnnotationFromList(JsonValue.class, method.getAnnotations().asList(), JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER)).map(JsonValue::value).orElse(false)) .collect(Collectors.toSet()); if (jsonValueAnnotatedMethods.size() == 1) { return jsonValueAnnotatedMethods.iterator().next(); @@ -136,14 +136,16 @@ protected ResolvedMethod getJsonValueAnnotatedMethod(ResolvedType javaType, Sche * @param enumConstants non-empty array of enum constants * @return annotated {@link JsonProperty#value()} for each enum constant (or {@code null} if the criteria are not met) */ - protected List getSerializedValuesFromJsonProperty(ResolvedType javaType, Object[] enumConstants) { + protected List getSerializedValuesFromJsonProperty(ResolvedType javaType, Object[] enumConstants, SchemaGenerationContext schemaGenerationContext) { try { List serializedJsonValues = new ArrayList<>(enumConstants.length); for (Object enumConstant : enumConstants) { String enumValueName = ((Enum) enumConstant).name(); - JsonProperty annotation = javaType.getErasedType() - .getDeclaredField(enumValueName) - .getAnnotation(JsonProperty.class); + JsonProperty annotation = schemaGenerationContext.getTypeContext().getAnnotationFromList( + JsonProperty.class, + Arrays.asList(javaType.getErasedType().getDeclaredField(enumValueName).getAnnotations()), + JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + ); if (annotation == null) { // enum constant without @JsonProperty annotation return null; diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonHelper.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonHelper.java new file mode 100644 index 00000000..b80da9bd --- /dev/null +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonHelper.java @@ -0,0 +1,60 @@ +package com.github.victools.jsonschema.module.jackson; + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; + +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +final class JacksonHelper { + + static final Predicate JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER = new JacksonAnnotationsInsideAnnotatedFilter(); + + private static final class JacksonAnnotationsInsideAnnotatedFilter implements Predicate { + JacksonAnnotationsInsideAnnotatedFilter() { + super(); + } + @Override + public boolean test(Annotation annotation) { + return annotation.annotationType().isAnnotationPresent(JacksonAnnotationsInside.class); + } + } + + private JacksonHelper() { + super(); + } + + /** + * Resolves the specified annotation on the given type and resolve indirect jackson annotations. + * + * @param declaringType where to look for the specified annotation + * @param annotationClass the class of the annotation to look for + * @return an empty entry if not found + * @param the generic type of the annotation + */ + static Optional resolveAnnotation(Class declaringType, Class annotationClass) { + final A annotation = declaringType.getAnnotation(annotationClass); + if (annotation != null) { + return Optional.of(annotation); + } + List annotations = extractNestedAnnotations(Arrays.stream(declaringType.getAnnotations())); + while (!annotations.isEmpty()) { + final Optional directAnnotation = annotations.stream().filter(annotationClass::isInstance).findFirst(); + if (directAnnotation.isPresent()) { + return directAnnotation.map(annotationClass::cast); + } + annotations = extractNestedAnnotations(annotations.stream()); + } + return Optional.empty(); + } + + private static List extractNestedAnnotations(Stream annotations) { + return annotations.filter(JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER) + .flatMap(a -> Arrays.stream(a.annotationType().getAnnotations())) + .collect(Collectors.toList()); + } +} diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonModule.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonModule.java index 1d887605..89747106 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonModule.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonModule.java @@ -40,7 +40,6 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; -import java.util.Optional; import java.util.Set; /** @@ -183,7 +182,7 @@ protected String resolveDescription(MemberScope member) { */ protected String resolveDescriptionForType(TypeScope scope) { Class rawType = scope.getType().getErasedType(); - JsonClassDescription classAnnotation = rawType.getAnnotation(JsonClassDescription.class); + JsonClassDescription classAnnotation = scope.getContext().getAnnotationFromList(JsonClassDescription.class, Arrays.asList(rawType.getAnnotations()), JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER); if (classAnnotation != null) { return classAnnotation.value(); } @@ -201,7 +200,7 @@ protected String resolveDescriptionForType(TypeScope scope) { * @return alternative property name (or {@code null}) */ protected String getPropertyNameOverrideBasedOnJsonPropertyAnnotation(MemberScope member) { - JsonProperty annotation = member.getAnnotationConsideringFieldAndGetter(JsonProperty.class); + JsonProperty annotation = member.getAnnotationConsideringFieldAndGetter(JsonProperty.class, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER); if (annotation != null) { String nameOverride = annotation.value(); // check for invalid overrides @@ -237,7 +236,7 @@ protected String getPropertyNameOverrideBasedOnJsonNamingAnnotation(FieldScope f * @return annotated naming strategy instance (or {@code null}) */ private PropertyNamingStrategy getAnnotatedNamingStrategy(Class declaringType) { - return Optional.ofNullable(declaringType.getAnnotation(JsonNaming.class)) + return JacksonHelper.resolveAnnotation(declaringType, JsonNaming.class) .map(JsonNaming::value) .map(strategyType -> { try { @@ -274,11 +273,11 @@ protected final BeanDescription getBeanDescriptionForClass(ResolvedType targetTy * @return whether field should be excluded */ protected boolean shouldIgnoreField(FieldScope field) { - if (field.getAnnotationConsideringFieldAndGetterIfSupported(JsonBackReference.class) != null) { + if (field.getAnnotationConsideringFieldAndGetterIfSupported(JsonBackReference.class, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER) != null) { return true; } // @since 4.32.0 - JsonUnwrapped unwrappedAnnotation = field.getAnnotationConsideringFieldAndGetterIfSupported(JsonUnwrapped.class); + JsonUnwrapped unwrappedAnnotation = field.getAnnotationConsideringFieldAndGetterIfSupported(JsonUnwrapped.class, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER); if (unwrappedAnnotation != null && unwrappedAnnotation.enabled()) { // unwrapped properties should be ignored here, as they are included in their unwrapped form return true; @@ -309,11 +308,11 @@ protected boolean shouldIgnoreField(FieldScope field) { protected boolean shouldIgnoreMethod(MethodScope method) { FieldScope getterField = method.findGetterField(); if (getterField == null) { - if (method.getAnnotationConsideringFieldAndGetterIfSupported(JsonBackReference.class) != null) { + if (method.getAnnotationConsideringFieldAndGetterIfSupported(JsonBackReference.class, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER) != null) { return true; } // @since 4.32.0 - JsonUnwrapped unwrappedAnnotation = method.getAnnotationConsideringFieldAndGetterIfSupported(JsonUnwrapped.class); + JsonUnwrapped unwrappedAnnotation = method.getAnnotationConsideringFieldAndGetterIfSupported(JsonUnwrapped.class, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER); if (unwrappedAnnotation != null && unwrappedAnnotation.enabled()) { // unwrapped properties should be ignored here, as they are included in their unwrapped form return true; @@ -322,7 +321,7 @@ protected boolean shouldIgnoreMethod(MethodScope method) { return true; } return this.options.contains(JacksonOption.INCLUDE_ONLY_JSONPROPERTY_ANNOTATED_METHODS) - && method.getAnnotationConsideringFieldAndGetter(JsonProperty.class) == null; + && method.getAnnotationConsideringFieldAndGetter(JsonProperty.class, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER) == null; } /** @@ -332,7 +331,7 @@ protected boolean shouldIgnoreMethod(MethodScope method) { * @return whether the field should be in the "required" list or not */ protected boolean getRequiredCheckBasedOnJsonPropertyAnnotation(MemberScope member) { - JsonProperty jsonProperty = member.getAnnotationConsideringFieldAndGetterIfSupported(JsonProperty.class) ; + JsonProperty jsonProperty = member.getAnnotationConsideringFieldAndGetterIfSupported(JsonProperty.class, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER) ; return jsonProperty != null && jsonProperty.required(); } @@ -343,7 +342,7 @@ protected boolean getRequiredCheckBasedOnJsonPropertyAnnotation(MemberScope member) { - JsonProperty jsonProperty = member.getAnnotationConsideringFieldAndGetter(JsonProperty.class); + JsonProperty jsonProperty = member.getAnnotationConsideringFieldAndGetter(JsonProperty.class, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER); return jsonProperty != null && jsonProperty.access() == JsonProperty.Access.READ_ONLY; } @@ -354,7 +353,7 @@ protected boolean getReadOnlyCheck(MemberScope member) { * @return whether the field should be marked as write-only */ protected boolean getWriteOnlyCheck(MemberScope member) { - JsonProperty jsonProperty = member.getAnnotationConsideringFieldAndGetter(JsonProperty.class); + JsonProperty jsonProperty = member.getAnnotationConsideringFieldAndGetter(JsonProperty.class, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER); return jsonProperty != null && jsonProperty.access() == JsonProperty.Access.WRITE_ONLY; } } diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonIdentityReferenceDefinitionProvider.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonIdentityReferenceDefinitionProvider.java index 2c50fe31..10ea587a 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonIdentityReferenceDefinitionProvider.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonIdentityReferenceDefinitionProvider.java @@ -28,6 +28,8 @@ import com.github.victools.jsonschema.generator.MemberScope; import com.github.victools.jsonschema.generator.SchemaGenerationContext; import com.github.victools.jsonschema.generator.TypeContext; + +import java.util.Arrays; import java.util.Optional; import java.util.stream.Stream; @@ -73,7 +75,11 @@ public CustomPropertyDefinition provideCustomPropertySchemaDefinition(MemberScop * @return designated type of the applicable identity reference (may be empty) */ public Optional getIdentityReferenceType(ResolvedType javaType, TypeContext typeContext) { - JsonIdentityReference referenceAnnotation = javaType.getErasedType().getAnnotation(JsonIdentityReference.class); + JsonIdentityReference referenceAnnotation = typeContext.getAnnotationFromList( + JsonIdentityReference.class, + Arrays.asList(javaType.getErasedType().getAnnotations()), + JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + ); return this.getIdentityReferenceType(referenceAnnotation, javaType, typeContext); } @@ -86,13 +92,13 @@ public Optional getIdentityReferenceType(ResolvedType javaType, Ty * @return designated type of the applicable identity reference (may be empty) */ public Optional getIdentityReferenceType(MemberScope scope) { - JsonIdentityReference referenceAnnotation = scope.getContainerItemAnnotationConsideringFieldAndGetterIfSupported(JsonIdentityReference.class); + JsonIdentityReference referenceAnnotation = scope.getContainerItemAnnotationConsideringFieldAndGetterIfSupported(JsonIdentityReference.class, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER); if (referenceAnnotation == null) { - referenceAnnotation = scope.getAnnotationConsideringFieldAndGetter(JsonIdentityReference.class); + referenceAnnotation = scope.getAnnotationConsideringFieldAndGetter(JsonIdentityReference.class, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER); } return this.getIdentityReferenceType(referenceAnnotation, scope.getType(), scope.getContext()); } - + /** * If applicable, determine the type of the identity reference that should replace the given actual type, if the * {@code @JsonIdentityReference(alwaysAsId = true)} annotation is present as well as a corresponding {@code @JsonIdentityInfo} annotation on the @@ -110,12 +116,12 @@ private Optional getIdentityReferenceType(JsonIdentityReference re return Optional.empty(); } // additionally, the type itself must have a @JsonIdentityInfo annotation - ResolvedType typeWithIdentityInfoAnnotation = typeContext.getTypeWithAnnotation(javaType, JsonIdentityInfo.class); + ResolvedType typeWithIdentityInfoAnnotation = typeContext.getTypeWithAnnotation(javaType, JsonIdentityInfo.class, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER); if (typeWithIdentityInfoAnnotation == null) { // otherwise, the @JsonIdentityReference annotation is simply ignored return Optional.empty(); } - JsonIdentityInfo identityInfoAnnotation = typeWithIdentityInfoAnnotation.getErasedType().getAnnotation(JsonIdentityInfo.class); + JsonIdentityInfo identityInfoAnnotation = typeContext.getAnnotationFromList(JsonIdentityInfo.class, Arrays.asList(typeWithIdentityInfoAnnotation.getErasedType().getAnnotations()), JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER); // @JsonIdentityInfo annotation declares generator with specific identity type ResolvedType identityTypeFromGenerator = typeContext.getTypeParameterFor(typeContext.resolve(identityInfoAnnotation.generator()), ObjectIdGenerator.class, 0); diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonPropertySorter.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonPropertySorter.java index 3d6c79e5..bf3757c1 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonPropertySorter.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonPropertySorter.java @@ -21,6 +21,7 @@ import com.github.victools.jsonschema.generator.MemberScope; import com.github.victools.jsonschema.generator.MethodScope; import com.github.victools.jsonschema.generator.impl.PropertySortUtils; + import java.util.Arrays; import java.util.Collections; import java.util.Comparator; @@ -98,10 +99,14 @@ protected int getPropertyIndex(MemberScope property) { * @return whether properties that are not specifically mentioned in a {@link JsonPropertyOrder} annotation should be sorted alphabetically */ protected boolean shouldSortPropertiesAlphabetically(Class declaringType) { - return Optional.ofNullable(declaringType.getAnnotation(JsonPropertyOrder.class)) + return resolveJsonPropertyOrder(declaringType) .map(JsonPropertyOrder::alphabetic) .orElse(this.sortAlphabeticallyIfNotAnnotated); } + + private Optional resolveJsonPropertyOrder(Class declaringType) { + return JacksonHelper.resolveAnnotation(declaringType, JsonPropertyOrder.class); + } /** * Lookup the list of specifically sorted property names in the given type based on its {@link JsonPropertyOrder} annotation. @@ -110,7 +115,7 @@ protected boolean shouldSortPropertiesAlphabetically(Class declaringType) { * @return {@link JsonPropertyOrder#value()} or empty list */ private List getAnnotatedPropertyOrder(Class declaringType) { - return Optional.ofNullable(declaringType.getAnnotation(JsonPropertyOrder.class)) + return resolveJsonPropertyOrder(declaringType) .map(JsonPropertyOrder::value) .filter(valueArray -> valueArray.length != 0) .map(Arrays::asList) diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonSubTypesResolver.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonSubTypesResolver.java index aba2eae0..566ca210 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonSubTypesResolver.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonSubTypesResolver.java @@ -34,6 +34,9 @@ import com.github.victools.jsonschema.generator.TypeContext; import com.github.victools.jsonschema.generator.TypeScope; import com.github.victools.jsonschema.generator.impl.AttributeCollector; + +import java.lang.annotation.Annotation; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -109,7 +112,11 @@ public List findSubtypes(ResolvedType declaredType, SchemaGenerati if (this.skipSubtypeResolution(declaredType, context.getTypeContext())) { return null; } - JsonSubTypes subtypesAnnotation = declaredType.getErasedType().getAnnotation(JsonSubTypes.class); + JsonSubTypes subtypesAnnotation = context.getTypeContext().getAnnotationFromList( + JsonSubTypes.class, + Arrays.asList(declaredType.getErasedType().getAnnotations()), + JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + ); return this.lookUpSubtypesFromAnnotation(declaredType, subtypesAnnotation, context.getTypeContext()); } @@ -123,7 +130,7 @@ public List findTargetTypeOverrides(MemberScope property) { if (this.skipSubtypeResolution(property)) { return null; } - JsonSubTypes subtypesAnnotation = property.getAnnotationConsideringFieldAndGetter(JsonSubTypes.class); + JsonSubTypes subtypesAnnotation = property.getAnnotationConsideringFieldAndGetter(JsonSubTypes.class, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER); return this.lookUpSubtypesFromAnnotation(property.getType(), subtypesAnnotation, property.getContext()); } @@ -170,16 +177,18 @@ public CustomDefinition provideCustomSchemaDefinition(ResolvedType javaType, Sch // since 4.37.0: not for void methods return null; } - ResolvedType typeWithTypeInfo = context.getTypeContext().getTypeWithAnnotation(javaType, JsonTypeInfo.class); - if (typeWithTypeInfo == null || javaType.getErasedType().getAnnotation(JsonSubTypes.class) != null - || this.skipSubtypeResolution(javaType, context.getTypeContext())) { + final TypeContext typeContext = context.getTypeContext(); + ResolvedType typeWithTypeInfo = typeContext.getTypeWithAnnotation(javaType, JsonTypeInfo.class, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER); + if (typeWithTypeInfo == null || typeContext.getAnnotationFromList(JsonSubTypes.class, Arrays.asList(javaType.getErasedType().getAnnotations()), JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER) != null + || this.skipSubtypeResolution(javaType, typeContext)) { // no @JsonTypeInfo annotation found or the given javaType is the super type, that should be replaced return null; } Class erasedTypeWithTypeInfo = typeWithTypeInfo.getErasedType(); - JsonTypeInfo typeInfoAnnotation = erasedTypeWithTypeInfo.getAnnotation(JsonTypeInfo.class); - JsonSubTypes subTypesAnnotation = erasedTypeWithTypeInfo.getAnnotation(JsonSubTypes.class); - TypeScope scope = context.getTypeContext().createTypeScope(javaType); + final List annotationsList = Arrays.asList(erasedTypeWithTypeInfo.getAnnotations()); + JsonTypeInfo typeInfoAnnotation = typeContext.getAnnotationFromList(JsonTypeInfo.class, annotationsList, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER); + JsonSubTypes subTypesAnnotation = typeContext.getAnnotationFromList(JsonSubTypes.class, annotationsList, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER); + TypeScope scope = typeContext.createTypeScope(javaType); ObjectNode definition = this.createSubtypeDefinition(scope, typeInfoAnnotation, subTypesAnnotation, context); if (definition == null) { return null; @@ -195,10 +204,10 @@ public CustomDefinition provideCustomSchemaDefinition(ResolvedType javaType, Sch * @return applicable custom per-property override schema definition (may be {@code null}) */ public CustomPropertyDefinition provideCustomPropertySchemaDefinition(MemberScope scope, SchemaGenerationContext context) { - if (this.skipSubtypeResolution(scope) || scope.getType().getErasedType().getDeclaredAnnotation(JsonSubTypes.class) != null) { + if (this.skipSubtypeResolution(scope) || context.getTypeContext().getAnnotationFromList(JsonSubTypes.class, Arrays.asList(scope.getType().getErasedType().getAnnotations()), JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER) != null) { return null; } - JsonTypeInfo typeInfoAnnotation = scope.getAnnotationConsideringFieldAndGetter(JsonTypeInfo.class); + JsonTypeInfo typeInfoAnnotation = scope.getAnnotationConsideringFieldAndGetter(JsonTypeInfo.class, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER); if (typeInfoAnnotation == null) { // the normal per-type behaviour is not being overridden, i.e., no need for an inline custom property schema return null; @@ -210,7 +219,7 @@ public CustomPropertyDefinition provideCustomPropertySchemaDefinition(MemberScop .add(context.createStandardDefinitionReference(scope.getType(), this)); return new CustomPropertyDefinition(definition, CustomDefinition.AttributeInclusion.YES); } - JsonSubTypes subTypesAnnotation = scope.getAnnotationConsideringFieldAndGetter(JsonSubTypes.class); + JsonSubTypes subTypesAnnotation = scope.getAnnotationConsideringFieldAndGetter(JsonSubTypes.class, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER); ObjectNode definition = this.createSubtypeDefinition(scope, typeInfoAnnotation, subTypesAnnotation, context); if (definition == null) { return null; @@ -269,10 +278,14 @@ private static Optional getNameFromSubTypeAnnotation(Class erasedTarg * @return simple class name, with declaring class's unqualified name as prefix for member classes */ private static Optional getNameFromTypeNameAnnotation(Class erasedTargetType) { - return Optional.ofNullable(erasedTargetType.getAnnotation(JsonTypeName.class)) + return lookupJsonTypeName(erasedTargetType) .map(JsonTypeName::value) .filter(name -> !name.isEmpty()); } + + private static Optional lookupJsonTypeName(Class erasedTargetType) { + return JacksonHelper.resolveAnnotation(erasedTargetType, JsonTypeName.class); + } /** * Determine the unqualified name of the given class, e.g., as fall-back value for subtype reference with {@code JsonTypeInfo.Id.NAME}. diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonUnwrappedDefinitionProvider.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonUnwrappedDefinitionProvider.java index a825e811..3d865425 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonUnwrappedDefinitionProvider.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonUnwrappedDefinitionProvider.java @@ -19,6 +19,7 @@ import com.fasterxml.classmate.ResolvedType; import com.fasterxml.classmate.ResolvedTypeWithMembers; import com.fasterxml.classmate.members.ResolvedMember; +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; import com.fasterxml.jackson.annotation.JsonUnwrapped; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; @@ -30,6 +31,9 @@ import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; +import java.util.Deque; +import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Optional; import java.util.stream.Stream; @@ -60,9 +64,9 @@ public CustomDefinition provideCustomSchemaDefinition(ResolvedType javaType, Sch // include each annotated member's type considering the optional prefix and/or suffix Stream.concat(Stream.of(typeWithMembers.getMemberFields()), Stream.of(typeWithMembers.getMemberMethods())) - .filter(member -> Optional.ofNullable(member.getAnnotations().get(JsonUnwrapped.class)) - .filter(JsonUnwrapped::enabled).isPresent()) .map(member -> this.createUnwrappedMemberSchema(member, context)) + .filter(Optional::isPresent) + .map(Optional::get) .forEachOrdered(allOf::add); return new CustomDefinition(definition); @@ -75,14 +79,37 @@ public CustomDefinition provideCustomSchemaDefinition(ResolvedType javaType, Sch * @return whether the given member has an {@code enabled} {@link JsonUnwrapped @JsonUnwrapped} annotation */ private boolean hasJsonUnwrappedAnnotation(ResolvedMember member) { - for (Annotation annotation : member.getAnnotations()) { + return lookupEnabledJsonUnwrappedAnnotation(member.getAnnotations()).isPresent(); + } + + /** + * Returns the first enabled occurrence of the {@link JsonUnwrapped} annotation found in the specified annotations parameter, + * unwrapping "JacksonAnnotationsInside" combo annotations. + * + * @param annotations annotations to crawl through + * @return a present value if, and only if, an enabled instance was found. + */ + private Optional lookupEnabledJsonUnwrappedAnnotation(Iterable annotations) { + Deque> iterators = new LinkedList<>(); + iterators.add(annotations.iterator()); + while (!iterators.isEmpty()) { + Iterator iterator = iterators.peek(); + if (!iterator.hasNext()) { + iterators.remove(); + continue; + } + Annotation annotation = iterator.next(); if (annotation instanceof JsonUnwrapped && ((JsonUnwrapped) annotation).enabled()) { - return true; + return Optional.of((JsonUnwrapped) annotation); + } + final Class annotationClass = annotation.annotationType(); + if (annotationClass.isAnnotationPresent(JacksonAnnotationsInside.class)) { + iterators.addFirst(Arrays.asList(annotationClass.getAnnotations()).iterator()); } } - return false; + return Optional.empty(); } - + /** * Create a schema representing an unwrapped member's type. Contained properties may get a certain prefix and/or suffix applied to their names. * @@ -90,13 +117,15 @@ private boolean hasJsonUnwrappedAnnotation(ResolvedMember member) { * @param context generation context * @return created schema */ - private ObjectNode createUnwrappedMemberSchema(ResolvedMember member, SchemaGenerationContext context) { - ObjectNode definition = context.createStandardDefinition(member.getType(), null); - JsonUnwrapped annotation = member.getAnnotations().get(JsonUnwrapped.class); - if (!annotation.prefix().isEmpty() || !annotation.suffix().isEmpty()) { - this.applyPrefixAndSuffixToPropertyNames(definition, annotation.prefix(), annotation.suffix(), context); - } - return definition; + private Optional createUnwrappedMemberSchema(ResolvedMember member, SchemaGenerationContext context) { + final Optional optAnnotation = lookupEnabledJsonUnwrappedAnnotation(member.getAnnotations()); + return optAnnotation.map(annotation -> { + ObjectNode definition = context.createStandardDefinition(member.getType(), null); + if (!annotation.prefix().isEmpty() || !annotation.suffix().isEmpty()) { + this.applyPrefixAndSuffixToPropertyNames(definition, annotation.prefix(), annotation.suffix(), context); + } + return definition; + }); } /** diff --git a/jsonschema-module-jackson/src/test/java/com/github/victools/jsonschema/module/jackson/JacksonHelperTest.java b/jsonschema-module-jackson/src/test/java/com/github/victools/jsonschema/module/jackson/JacksonHelperTest.java new file mode 100644 index 00000000..64ba586d --- /dev/null +++ b/jsonschema-module-jackson/src/test/java/com/github/victools/jsonschema/module/jackson/JacksonHelperTest.java @@ -0,0 +1,121 @@ +package com.github.victools.jsonschema.module.jackson; + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.fasterxml.jackson.annotation.JsonTypeName; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Optional; + +/** + * Unit test class dedicated to the validation of {@link JacksonHelper}. + * + * @author Antoine Malliarakis + */ +class JacksonHelperTest { + + @JsonTypeName + private static class DirectlyAnnotatedClass { + } + + private static class NonAnnotatedClass { + } + + @UselessFirstComboAnnotation + @UselessSecondComboAnnotation + private static class AnnotatedClassWithUselessAnnotations { + + } + + @Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE}) + @Retention(RetentionPolicy.RUNTIME) + @JacksonAnnotationsInside + private @interface UselessFirstComboAnnotation { + } + + @Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE}) + @Retention(RetentionPolicy.RUNTIME) + @JacksonAnnotationsInside + private @interface UselessSecondComboAnnotation { + } + + @Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE}) + @Retention(RetentionPolicy.RUNTIME) + @JacksonAnnotationsInside + @JsonTypeName("first combo annotation value") + private @interface FirstComboAnnotation { + } + + @Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE}) + @Retention(RetentionPolicy.RUNTIME) + @JacksonAnnotationsInside + @JsonTypeName("second combo annotation value") + private @interface SecondComboAnnotation { + } + + @Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE}) + @Retention(RetentionPolicy.RUNTIME) + @JacksonAnnotationsInside + @SecondComboAnnotation + private @interface ThirdComboAnnotation { + } + + @FirstComboAnnotation + @SecondComboAnnotation + private static class IndirectlyAnnotatedClass { + } + + @JsonTypeName("direct value") + @FirstComboAnnotation + @SecondComboAnnotation + private static class BothDirectAndIndirectlyAnnotatedClass { + } + + @ThirdComboAnnotation + @FirstComboAnnotation + private static class BreadthFirstAnnotatedClass {} + + @Test + void resolveAnnotation_returnsAnEmptyInstanceIfNotAnnotated() { + final Optional result = JacksonHelper.resolveAnnotation(NonAnnotatedClass.class, JsonTypeName.class); + Assertions.assertFalse(result.isPresent()); + } + + @Test + void resolveAnnotation_returnsAnEmptyInstanceIfNotAnnotatedEvenIfThereAreComboAnnotations() { + final Optional result = JacksonHelper.resolveAnnotation(AnnotatedClassWithUselessAnnotations.class, JsonTypeName.class); + Assertions.assertFalse(result.isPresent()); + } + + @Test + void resolveAnnotation_supportsDirectAnnotations() { + final Optional result = JacksonHelper.resolveAnnotation(DirectlyAnnotatedClass.class, JsonTypeName.class); + Assertions.assertTrue(result.isPresent()); + Assertions.assertEquals(result.get().value(), ""); + } + + @Test + void resolveAnnotation_directAnnotationTakesPrecedence() { + final Optional result = JacksonHelper.resolveAnnotation(BothDirectAndIndirectlyAnnotatedClass.class, JsonTypeName.class); + Assertions.assertTrue(result.isPresent()); + Assertions.assertEquals("direct value", result.get().value()); + } + + @Test + void resolveAnnotation_returnsFirstValueFound() { + final Optional result = JacksonHelper.resolveAnnotation(IndirectlyAnnotatedClass.class, JsonTypeName.class); + Assertions.assertTrue(result.isPresent()); + Assertions.assertEquals("first combo annotation value", result.get().value()); + } + + @Test + void resolveAnnotation_usesABreadthFirstlookup() { + final Optional result = JacksonHelper.resolveAnnotation(BreadthFirstAnnotatedClass.class, JsonTypeName.class); + Assertions.assertTrue(result.isPresent()); + Assertions.assertEquals("first combo annotation value", result.get().value()); + } +} \ No newline at end of file From be3838772031dcb4c0e8b3ad643a53784be71c1d Mon Sep 17 00:00:00 2001 From: Smaarn Date: Sun, 27 Oct 2024 16:24:31 +0100 Subject: [PATCH 02/13] revert unsolicited change --- .../jsonschema/module/jackson/CustomEnumDefinitionProvider.java | 1 + 1 file changed, 1 insertion(+) diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java index cc277e01..ee0e0e79 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java @@ -42,6 +42,7 @@ * definition (e.g. from one of the standard generator {@code Option}s). */ public class CustomEnumDefinitionProvider implements CustomDefinitionProviderV2 { + private final boolean checkForJsonValueAnnotatedMethod; private final boolean checkForJsonPropertyAnnotations; From 227f63a9b0505d38b65262918915ebc810cae187 Mon Sep 17 00:00:00 2001 From: Smaarn Date: Sun, 27 Oct 2024 16:38:40 +0100 Subject: [PATCH 03/13] First range of simplifications --- .../jackson/CustomEnumDefinitionProvider.java | 16 ++++++---------- .../jsonschema/module/jackson/JacksonHelper.java | 3 ++- .../jsonschema/module/jackson/JacksonModule.java | 8 +++----- .../JsonIdentityReferenceDefinitionProvider.java | 8 ++------ .../module/jackson/JsonSubTypesResolver.java | 10 +++------- 5 files changed, 16 insertions(+), 29 deletions(-) diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java index ee0e0e79..730e56c9 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java @@ -28,7 +28,6 @@ import com.github.victools.jsonschema.generator.impl.AttributeCollector; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.Set; @@ -73,7 +72,7 @@ public CustomDefinition provideCustomSchemaDefinition(ResolvedType javaType, Sch serializedJsonValues = this.getSerializedValuesFromJsonValue(javaType, enumConstants, context); } if (serializedJsonValues == null && this.checkForJsonPropertyAnnotations) { - serializedJsonValues = this.getSerializedValuesFromJsonProperty(javaType, enumConstants, context); + serializedJsonValues = this.getSerializedValuesFromJsonProperty(javaType, enumConstants); } if (serializedJsonValues == null) { return null; @@ -137,21 +136,18 @@ protected ResolvedMethod getJsonValueAnnotatedMethod(ResolvedType javaType, Sche * @param enumConstants non-empty array of enum constants * @return annotated {@link JsonProperty#value()} for each enum constant (or {@code null} if the criteria are not met) */ - protected List getSerializedValuesFromJsonProperty(ResolvedType javaType, Object[] enumConstants, SchemaGenerationContext schemaGenerationContext) { + protected List getSerializedValuesFromJsonProperty(ResolvedType javaType, Object[] enumConstants) { try { List serializedJsonValues = new ArrayList<>(enumConstants.length); for (Object enumConstant : enumConstants) { String enumValueName = ((Enum) enumConstant).name(); - JsonProperty annotation = schemaGenerationContext.getTypeContext().getAnnotationFromList( - JsonProperty.class, - Arrays.asList(javaType.getErasedType().getDeclaredField(enumValueName).getAnnotations()), - JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER - ); - if (annotation == null) { + Optional annotation = JacksonHelper.resolveAnnotation(javaType.getErasedType().getDeclaredField(enumValueName), JsonProperty.class); + if (!annotation.isPresent()) { // enum constant without @JsonProperty annotation return null; } - serializedJsonValues.add(JsonProperty.USE_DEFAULT_NAME.equals(annotation.value()) ? enumValueName : annotation.value()); + final String value = annotation.get().value(); + serializedJsonValues.add(JsonProperty.USE_DEFAULT_NAME.equals(value) ? enumValueName : value); } return serializedJsonValues; } catch (NoSuchFieldException | SecurityException ex) { diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonHelper.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonHelper.java index b80da9bd..da00cba9 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonHelper.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonHelper.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -36,7 +37,7 @@ private JacksonHelper() { * @return an empty entry if not found * @param the generic type of the annotation */ - static Optional resolveAnnotation(Class declaringType, Class annotationClass) { + static Optional resolveAnnotation(AnnotatedElement declaringType, Class annotationClass) { final A annotation = declaringType.getAnnotation(annotationClass); if (annotation != null) { return Optional.of(annotation); diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonModule.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonModule.java index 89747106..8e869329 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonModule.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonModule.java @@ -40,6 +40,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Optional; import java.util.Set; /** @@ -182,11 +183,8 @@ protected String resolveDescription(MemberScope member) { */ protected String resolveDescriptionForType(TypeScope scope) { Class rawType = scope.getType().getErasedType(); - JsonClassDescription classAnnotation = scope.getContext().getAnnotationFromList(JsonClassDescription.class, Arrays.asList(rawType.getAnnotations()), JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER); - if (classAnnotation != null) { - return classAnnotation.value(); - } - return null; + Optional classAnnotation = JacksonHelper.resolveAnnotation(rawType, JsonClassDescription.class); + return classAnnotation.map(JsonClassDescription::value).orElse(null); } /** diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonIdentityReferenceDefinitionProvider.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonIdentityReferenceDefinitionProvider.java index 10ea587a..d57fc4b0 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonIdentityReferenceDefinitionProvider.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonIdentityReferenceDefinitionProvider.java @@ -75,12 +75,8 @@ public CustomPropertyDefinition provideCustomPropertySchemaDefinition(MemberScop * @return designated type of the applicable identity reference (may be empty) */ public Optional getIdentityReferenceType(ResolvedType javaType, TypeContext typeContext) { - JsonIdentityReference referenceAnnotation = typeContext.getAnnotationFromList( - JsonIdentityReference.class, - Arrays.asList(javaType.getErasedType().getAnnotations()), - JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER - ); - return this.getIdentityReferenceType(referenceAnnotation, javaType, typeContext); + Optional referenceAnnotation = JacksonHelper.resolveAnnotation(javaType.getErasedType(), JsonIdentityReference.class); + return this.getIdentityReferenceType(referenceAnnotation.orElse(null), javaType, typeContext); } /** diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonSubTypesResolver.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonSubTypesResolver.java index 566ca210..9385d47b 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonSubTypesResolver.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonSubTypesResolver.java @@ -112,12 +112,8 @@ public List findSubtypes(ResolvedType declaredType, SchemaGenerati if (this.skipSubtypeResolution(declaredType, context.getTypeContext())) { return null; } - JsonSubTypes subtypesAnnotation = context.getTypeContext().getAnnotationFromList( - JsonSubTypes.class, - Arrays.asList(declaredType.getErasedType().getAnnotations()), - JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER - ); - return this.lookUpSubtypesFromAnnotation(declaredType, subtypesAnnotation, context.getTypeContext()); + Optional subtypesAnnotation = JacksonHelper.resolveAnnotation(declaredType.getErasedType(), JsonSubTypes.class); + return this.lookUpSubtypesFromAnnotation(declaredType, subtypesAnnotation.orElse(null), context.getTypeContext()); } /** @@ -179,7 +175,7 @@ public CustomDefinition provideCustomSchemaDefinition(ResolvedType javaType, Sch } final TypeContext typeContext = context.getTypeContext(); ResolvedType typeWithTypeInfo = typeContext.getTypeWithAnnotation(javaType, JsonTypeInfo.class, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER); - if (typeWithTypeInfo == null || typeContext.getAnnotationFromList(JsonSubTypes.class, Arrays.asList(javaType.getErasedType().getAnnotations()), JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER) != null + if (typeWithTypeInfo == null || JacksonHelper.resolveAnnotation(javaType.getErasedType(), JsonSubTypes.class).isPresent() || this.skipSubtypeResolution(javaType, typeContext)) { // no @JsonTypeInfo annotation found or the given javaType is the super type, that should be replaced return null; From db274e4b9d425d1f6cc2f65ce044e5c8c52e336d Mon Sep 17 00:00:00 2001 From: Smaarn Date: Sun, 27 Oct 2024 18:43:22 +0100 Subject: [PATCH 04/13] Second batch of simplifications and revert of unsolicited changes --- .../jackson/CustomEnumDefinitionProvider.java | 6 ++-- .../module/jackson/JacksonHelper.java | 28 ++++++++++++++++++- ...onIdentityReferenceDefinitionProvider.java | 2 +- .../module/jackson/JsonPropertySorter.java | 1 - .../module/jackson/JsonSubTypesResolver.java | 8 ++---- .../JsonUnwrappedDefinitionProvider.java | 2 +- 6 files changed, 34 insertions(+), 13 deletions(-) diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java index 730e56c9..17a4ee9c 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java @@ -121,7 +121,7 @@ protected ResolvedMethod getJsonValueAnnotatedMethod(ResolvedType javaType, Sche ResolvedMethod[] memberMethods = context.getTypeContext().resolveWithMembers(javaType).getMemberMethods(); Set jsonValueAnnotatedMethods = Stream.of(memberMethods) .filter(method -> method.getArgumentCount() == 0) - .filter(method -> Optional.ofNullable(context.getTypeContext().getAnnotationFromList(JsonValue.class, method.getAnnotations().asList(), JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER)).map(JsonValue::value).orElse(false)) + .filter(method -> JacksonHelper.resolveAnnotation(method.getRawMember(), JsonValue.class).map(JsonValue::value).orElse(false)) .collect(Collectors.toSet()); if (jsonValueAnnotatedMethods.size() == 1) { return jsonValueAnnotatedMethods.iterator().next(); @@ -146,8 +146,8 @@ protected List getSerializedValuesFromJsonProperty(ResolvedType javaType // enum constant without @JsonProperty annotation return null; } - final String value = annotation.get().value(); - serializedJsonValues.add(JsonProperty.USE_DEFAULT_NAME.equals(value) ? enumValueName : value); + final String annotationValue = annotation.get().value(); + serializedJsonValues.add(JsonProperty.USE_DEFAULT_NAME.equals(annotationValue) ? enumValueName : annotationValue); } return serializedJsonValues; } catch (NoSuchFieldException | SecurityException ex) { diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonHelper.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonHelper.java index da00cba9..db08f84b 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonHelper.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonHelper.java @@ -1,5 +1,6 @@ package com.github.victools.jsonschema.module.jackson; +import com.fasterxml.classmate.members.ResolvedMember; import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; import java.lang.annotation.Annotation; @@ -10,6 +11,7 @@ import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; +import java.util.stream.StreamSupport; final class JacksonHelper { @@ -29,9 +31,29 @@ private JacksonHelper() { super(); } + /** + * Resolves the specified annotation on the given resolved member and resolve indirect jackson annotations. + * + *

It uses the same algorithm as {@link com.github.victools.jsonschema.generator.TypeContext#getAnnotationFromList(Class, List, Predicate)}.

+ * + * @param member where to look for the specified annotation + * @param annotationClass the class of the annotation to look for + * @return an empty entry if not found + * @param
the generic type of the annotation + */ + static Optional resolveAnnotation(ResolvedMember member, Class annotationClass) { + final A annotation = member.getAnnotations().get(annotationClass); + if (annotation != null) { + return Optional.of(annotation); + } + return resolveNestedAnnotations(StreamSupport.stream(member.getAnnotations().spliterator(), false), annotationClass); + } + /** * Resolves the specified annotation on the given type and resolve indirect jackson annotations. * + *

It uses the same algorithm as {@link com.github.victools.jsonschema.generator.TypeContext#getAnnotationFromList(Class, List, Predicate)}.

+ * * @param declaringType where to look for the specified annotation * @param annotationClass the class of the annotation to look for * @return an empty entry if not found @@ -42,7 +64,11 @@ static
Optional resolveAnnotation(AnnotatedElement dec if (annotation != null) { return Optional.of(annotation); } - List annotations = extractNestedAnnotations(Arrays.stream(declaringType.getAnnotations())); + return resolveNestedAnnotations(Arrays.stream(declaringType.getAnnotations()), annotationClass); + } + + private static Optional resolveNestedAnnotations(Stream initialAnnotations, Class annotationClass) { + List annotations = extractNestedAnnotations(initialAnnotations); while (!annotations.isEmpty()) { final Optional directAnnotation = annotations.stream().filter(annotationClass::isInstance).findFirst(); if (directAnnotation.isPresent()) { diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonIdentityReferenceDefinitionProvider.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonIdentityReferenceDefinitionProvider.java index d57fc4b0..1b79991d 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonIdentityReferenceDefinitionProvider.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonIdentityReferenceDefinitionProvider.java @@ -94,7 +94,7 @@ public Optional getIdentityReferenceType(MemberScope scope) } return this.getIdentityReferenceType(referenceAnnotation, scope.getType(), scope.getContext()); } - + /** * If applicable, determine the type of the identity reference that should replace the given actual type, if the * {@code @JsonIdentityReference(alwaysAsId = true)} annotation is present as well as a corresponding {@code @JsonIdentityInfo} annotation on the diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonPropertySorter.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonPropertySorter.java index bf3757c1..a37e16be 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonPropertySorter.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonPropertySorter.java @@ -21,7 +21,6 @@ import com.github.victools.jsonschema.generator.MemberScope; import com.github.victools.jsonschema.generator.MethodScope; import com.github.victools.jsonschema.generator.impl.PropertySortUtils; - import java.util.Arrays; import java.util.Collections; import java.util.Comparator; diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonSubTypesResolver.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonSubTypesResolver.java index 9385d47b..652e45e3 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonSubTypesResolver.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonSubTypesResolver.java @@ -200,7 +200,7 @@ public CustomDefinition provideCustomSchemaDefinition(ResolvedType javaType, Sch * @return applicable custom per-property override schema definition (may be {@code null}) */ public CustomPropertyDefinition provideCustomPropertySchemaDefinition(MemberScope scope, SchemaGenerationContext context) { - if (this.skipSubtypeResolution(scope) || context.getTypeContext().getAnnotationFromList(JsonSubTypes.class, Arrays.asList(scope.getType().getErasedType().getAnnotations()), JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER) != null) { + if (this.skipSubtypeResolution(scope) || JacksonHelper.resolveAnnotation(scope.getType().getErasedType(), JsonSubTypes.class).isPresent()) { return null; } JsonTypeInfo typeInfoAnnotation = scope.getAnnotationConsideringFieldAndGetter(JsonTypeInfo.class, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER); @@ -274,14 +274,10 @@ private static Optional getNameFromSubTypeAnnotation(Class erasedTarg * @return simple class name, with declaring class's unqualified name as prefix for member classes */ private static Optional getNameFromTypeNameAnnotation(Class erasedTargetType) { - return lookupJsonTypeName(erasedTargetType) + return JacksonHelper.resolveAnnotation(erasedTargetType, JsonTypeName.class) .map(JsonTypeName::value) .filter(name -> !name.isEmpty()); } - - private static Optional lookupJsonTypeName(Class erasedTargetType) { - return JacksonHelper.resolveAnnotation(erasedTargetType, JsonTypeName.class); - } /** * Determine the unqualified name of the given class, e.g., as fall-back value for subtype reference with {@code JsonTypeInfo.Id.NAME}. diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonUnwrappedDefinitionProvider.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonUnwrappedDefinitionProvider.java index 3d865425..9c6f3ecb 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonUnwrappedDefinitionProvider.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonUnwrappedDefinitionProvider.java @@ -79,7 +79,7 @@ public CustomDefinition provideCustomSchemaDefinition(ResolvedType javaType, Sch * @return whether the given member has an {@code enabled} {@link JsonUnwrapped @JsonUnwrapped} annotation */ private boolean hasJsonUnwrappedAnnotation(ResolvedMember member) { - return lookupEnabledJsonUnwrappedAnnotation(member.getAnnotations()).isPresent(); + return JacksonHelper.resolveAnnotation(member, JsonUnwrapped.class).filter(JsonUnwrapped::enabled).isPresent(); } /** From d99ce3c8c74743a0e1480a5b347222c2f3f224f9 Mon Sep 17 00:00:00 2001 From: Smaarn Date: Sun, 27 Oct 2024 18:51:19 +0100 Subject: [PATCH 05/13] Third batch of simplifications --- .../module/jackson/JsonPropertySorter.java | 8 ++--- .../JsonUnwrappedDefinitionProvider.java | 36 ++----------------- 2 files changed, 4 insertions(+), 40 deletions(-) diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonPropertySorter.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonPropertySorter.java index a37e16be..385fc4cf 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonPropertySorter.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonPropertySorter.java @@ -98,14 +98,10 @@ protected int getPropertyIndex(MemberScope property) { * @return whether properties that are not specifically mentioned in a {@link JsonPropertyOrder} annotation should be sorted alphabetically */ protected boolean shouldSortPropertiesAlphabetically(Class declaringType) { - return resolveJsonPropertyOrder(declaringType) + return JacksonHelper.resolveAnnotation(declaringType, JsonPropertyOrder.class) .map(JsonPropertyOrder::alphabetic) .orElse(this.sortAlphabeticallyIfNotAnnotated); } - - private Optional resolveJsonPropertyOrder(Class declaringType) { - return JacksonHelper.resolveAnnotation(declaringType, JsonPropertyOrder.class); - } /** * Lookup the list of specifically sorted property names in the given type based on its {@link JsonPropertyOrder} annotation. @@ -114,7 +110,7 @@ private Optional resolveJsonPropertyOrder(Class declaringT * @return {@link JsonPropertyOrder#value()} or empty list */ private List getAnnotatedPropertyOrder(Class declaringType) { - return resolveJsonPropertyOrder(declaringType) + return JacksonHelper.resolveAnnotation(declaringType, JsonPropertyOrder.class) .map(JsonPropertyOrder::value) .filter(valueArray -> valueArray.length != 0) .map(Arrays::asList) diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonUnwrappedDefinitionProvider.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonUnwrappedDefinitionProvider.java index 9c6f3ecb..a2eb86c5 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonUnwrappedDefinitionProvider.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonUnwrappedDefinitionProvider.java @@ -19,7 +19,6 @@ import com.fasterxml.classmate.ResolvedType; import com.fasterxml.classmate.ResolvedTypeWithMembers; import com.fasterxml.classmate.members.ResolvedMember; -import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; import com.fasterxml.jackson.annotation.JsonUnwrapped; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; @@ -28,12 +27,9 @@ import com.github.victools.jsonschema.generator.CustomDefinitionProviderV2; import com.github.victools.jsonschema.generator.SchemaGenerationContext; import com.github.victools.jsonschema.generator.SchemaKeyword; -import java.lang.annotation.Annotation; + import java.util.ArrayList; import java.util.Arrays; -import java.util.Deque; -import java.util.Iterator; -import java.util.LinkedList; import java.util.List; import java.util.Optional; import java.util.stream.Stream; @@ -82,34 +78,6 @@ private boolean hasJsonUnwrappedAnnotation(ResolvedMember member) { return JacksonHelper.resolveAnnotation(member, JsonUnwrapped.class).filter(JsonUnwrapped::enabled).isPresent(); } - /** - * Returns the first enabled occurrence of the {@link JsonUnwrapped} annotation found in the specified annotations parameter, - * unwrapping "JacksonAnnotationsInside" combo annotations. - * - * @param annotations annotations to crawl through - * @return a present value if, and only if, an enabled instance was found. - */ - private Optional lookupEnabledJsonUnwrappedAnnotation(Iterable annotations) { - Deque> iterators = new LinkedList<>(); - iterators.add(annotations.iterator()); - while (!iterators.isEmpty()) { - Iterator iterator = iterators.peek(); - if (!iterator.hasNext()) { - iterators.remove(); - continue; - } - Annotation annotation = iterator.next(); - if (annotation instanceof JsonUnwrapped && ((JsonUnwrapped) annotation).enabled()) { - return Optional.of((JsonUnwrapped) annotation); - } - final Class annotationClass = annotation.annotationType(); - if (annotationClass.isAnnotationPresent(JacksonAnnotationsInside.class)) { - iterators.addFirst(Arrays.asList(annotationClass.getAnnotations()).iterator()); - } - } - return Optional.empty(); - } - /** * Create a schema representing an unwrapped member's type. Contained properties may get a certain prefix and/or suffix applied to their names. * @@ -118,7 +86,7 @@ private Optional lookupEnabledJsonUnwrappedAnnotation(Iterable createUnwrappedMemberSchema(ResolvedMember member, SchemaGenerationContext context) { - final Optional optAnnotation = lookupEnabledJsonUnwrappedAnnotation(member.getAnnotations()); + final Optional optAnnotation = JacksonHelper.resolveAnnotation(member, JsonUnwrapped.class).filter(JsonUnwrapped::enabled); return optAnnotation.map(annotation -> { ObjectNode definition = context.createStandardDefinition(member.getType(), null); if (!annotation.prefix().isEmpty() || !annotation.suffix().isEmpty()) { From 49f1a8fd2cdd70b1dc8c52174b60aa3464752ab5 Mon Sep 17 00:00:00 2001 From: Smaarn Date: Sun, 27 Oct 2024 19:08:54 +0100 Subject: [PATCH 06/13] Fix checkstyle and copyrights --- .../jackson/CustomEnumDefinitionProvider.java | 7 +++- .../module/jackson/JacksonHelper.java | 32 +++++++++++--- .../module/jackson/JacksonModule.java | 42 +++++++++++++++---- ...onIdentityReferenceDefinitionProvider.java | 25 ++++++++--- .../module/jackson/JsonPropertySorter.java | 2 +- .../module/jackson/JsonSubTypesResolver.java | 36 ++++++++++++---- .../JsonUnwrappedDefinitionProvider.java | 3 +- 7 files changed, 113 insertions(+), 34 deletions(-) diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java index 17a4ee9c..a68eb535 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 VicTools. + * Copyright 2024 VicTools. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -141,7 +141,10 @@ protected List getSerializedValuesFromJsonProperty(ResolvedType javaType List serializedJsonValues = new ArrayList<>(enumConstants.length); for (Object enumConstant : enumConstants) { String enumValueName = ((Enum) enumConstant).name(); - Optional annotation = JacksonHelper.resolveAnnotation(javaType.getErasedType().getDeclaredField(enumValueName), JsonProperty.class); + Optional annotation = JacksonHelper.resolveAnnotation( + javaType.getErasedType().getDeclaredField(enumValueName), + JsonProperty.class + ); if (!annotation.isPresent()) { // enum constant without @JsonProperty annotation return null; diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonHelper.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonHelper.java index db08f84b..ad1d0a62 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonHelper.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonHelper.java @@ -1,8 +1,23 @@ +/* + * Copyright 2024 VicTools. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.github.victools.jsonschema.module.jackson; import com.fasterxml.classmate.members.ResolvedMember; import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; - import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.util.Arrays; @@ -21,6 +36,7 @@ private static final class JacksonAnnotationsInsideAnnotatedFilter implements Pr JacksonAnnotationsInsideAnnotatedFilter() { super(); } + @Override public boolean test(Annotation annotation) { return annotation.annotationType().isAnnotationPresent(JacksonAnnotationsInside.class); @@ -34,12 +50,14 @@ private JacksonHelper() { /** * Resolves the specified annotation on the given resolved member and resolve indirect jackson annotations. * - *

It uses the same algorithm as {@link com.github.victools.jsonschema.generator.TypeContext#getAnnotationFromList(Class, List, Predicate)}.

+ *

It uses the same algorithm as + * {@link com.github.victools.jsonschema.generator.TypeContext#getAnnotationFromList(Class, List, Predicate)} + * .

* + * @param
the generic type of the annotation * @param member where to look for the specified annotation * @param annotationClass the class of the annotation to look for * @return an empty entry if not found - * @param the generic type of the annotation */ static Optional resolveAnnotation(ResolvedMember member, Class annotationClass) { final A annotation = member.getAnnotations().get(annotationClass); @@ -52,12 +70,14 @@ static Optional resolveAnnotation(ResolvedMember me /** * Resolves the specified annotation on the given type and resolve indirect jackson annotations. * - *

It uses the same algorithm as {@link com.github.victools.jsonschema.generator.TypeContext#getAnnotationFromList(Class, List, Predicate)}.

- * + *

It uses the same algorithm as + * {@link com.github.victools.jsonschema.generator.TypeContext#getAnnotationFromList(Class, List, Predicate)} + * .

+ * + * @param
the generic type of the annotation * @param declaringType where to look for the specified annotation * @param annotationClass the class of the annotation to look for * @return an empty entry if not found - * @param the generic type of the annotation */ static Optional resolveAnnotation(AnnotatedElement declaringType, Class annotationClass) { final A annotation = declaringType.getAnnotation(annotationClass); diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonModule.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonModule.java index 8e869329..928e7a32 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonModule.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonModule.java @@ -198,7 +198,10 @@ protected String resolveDescriptionForType(TypeScope scope) { * @return alternative property name (or {@code null}) */ protected String getPropertyNameOverrideBasedOnJsonPropertyAnnotation(MemberScope member) { - JsonProperty annotation = member.getAnnotationConsideringFieldAndGetter(JsonProperty.class, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER); + JsonProperty annotation = member.getAnnotationConsideringFieldAndGetter( + JsonProperty.class, + JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + ); if (annotation != null) { String nameOverride = annotation.value(); // check for invalid overrides @@ -271,11 +274,16 @@ protected final BeanDescription getBeanDescriptionForClass(ResolvedType targetTy * @return whether field should be excluded */ protected boolean shouldIgnoreField(FieldScope field) { - if (field.getAnnotationConsideringFieldAndGetterIfSupported(JsonBackReference.class, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER) != null) { + if (field.getAnnotationConsideringFieldAndGetterIfSupported( + JsonBackReference.class, + JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER) != null) { return true; } // @since 4.32.0 - JsonUnwrapped unwrappedAnnotation = field.getAnnotationConsideringFieldAndGetterIfSupported(JsonUnwrapped.class, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER); + JsonUnwrapped unwrappedAnnotation = field.getAnnotationConsideringFieldAndGetterIfSupported( + JsonUnwrapped.class, + JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + ); if (unwrappedAnnotation != null && unwrappedAnnotation.enabled()) { // unwrapped properties should be ignored here, as they are included in their unwrapped form return true; @@ -306,11 +314,16 @@ protected boolean shouldIgnoreField(FieldScope field) { protected boolean shouldIgnoreMethod(MethodScope method) { FieldScope getterField = method.findGetterField(); if (getterField == null) { - if (method.getAnnotationConsideringFieldAndGetterIfSupported(JsonBackReference.class, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER) != null) { + if (method.getAnnotationConsideringFieldAndGetterIfSupported( + JsonBackReference.class, + JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER) != null) { return true; } // @since 4.32.0 - JsonUnwrapped unwrappedAnnotation = method.getAnnotationConsideringFieldAndGetterIfSupported(JsonUnwrapped.class, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER); + JsonUnwrapped unwrappedAnnotation = method.getAnnotationConsideringFieldAndGetterIfSupported( + JsonUnwrapped.class, + JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + ); if (unwrappedAnnotation != null && unwrappedAnnotation.enabled()) { // unwrapped properties should be ignored here, as they are included in their unwrapped form return true; @@ -319,7 +332,9 @@ protected boolean shouldIgnoreMethod(MethodScope method) { return true; } return this.options.contains(JacksonOption.INCLUDE_ONLY_JSONPROPERTY_ANNOTATED_METHODS) - && method.getAnnotationConsideringFieldAndGetter(JsonProperty.class, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER) == null; + && method.getAnnotationConsideringFieldAndGetter( + JsonProperty.class, + JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER) == null; } /** @@ -329,7 +344,10 @@ protected boolean shouldIgnoreMethod(MethodScope method) { * @return whether the field should be in the "required" list or not */ protected boolean getRequiredCheckBasedOnJsonPropertyAnnotation(MemberScope member) { - JsonProperty jsonProperty = member.getAnnotationConsideringFieldAndGetterIfSupported(JsonProperty.class, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER) ; + JsonProperty jsonProperty = member.getAnnotationConsideringFieldAndGetterIfSupported( + JsonProperty.class, + JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + ); return jsonProperty != null && jsonProperty.required(); } @@ -340,7 +358,10 @@ protected boolean getRequiredCheckBasedOnJsonPropertyAnnotation(MemberScope member) { - JsonProperty jsonProperty = member.getAnnotationConsideringFieldAndGetter(JsonProperty.class, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER); + JsonProperty jsonProperty = member.getAnnotationConsideringFieldAndGetter( + JsonProperty.class, + JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + ); return jsonProperty != null && jsonProperty.access() == JsonProperty.Access.READ_ONLY; } @@ -351,7 +372,10 @@ protected boolean getReadOnlyCheck(MemberScope member) { * @return whether the field should be marked as write-only */ protected boolean getWriteOnlyCheck(MemberScope member) { - JsonProperty jsonProperty = member.getAnnotationConsideringFieldAndGetter(JsonProperty.class, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER); + JsonProperty jsonProperty = member.getAnnotationConsideringFieldAndGetter( + JsonProperty.class, + JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + ); return jsonProperty != null && jsonProperty.access() == JsonProperty.Access.WRITE_ONLY; } } diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonIdentityReferenceDefinitionProvider.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonIdentityReferenceDefinitionProvider.java index 1b79991d..2991b729 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonIdentityReferenceDefinitionProvider.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonIdentityReferenceDefinitionProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 VicTools. + * Copyright 2024 VicTools. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,6 @@ import com.github.victools.jsonschema.generator.MemberScope; import com.github.victools.jsonschema.generator.SchemaGenerationContext; import com.github.victools.jsonschema.generator.TypeContext; - import java.util.Arrays; import java.util.Optional; import java.util.stream.Stream; @@ -88,9 +87,15 @@ public Optional getIdentityReferenceType(ResolvedType javaType, Ty * @return designated type of the applicable identity reference (may be empty) */ public Optional getIdentityReferenceType(MemberScope scope) { - JsonIdentityReference referenceAnnotation = scope.getContainerItemAnnotationConsideringFieldAndGetterIfSupported(JsonIdentityReference.class, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER); + JsonIdentityReference referenceAnnotation = scope.getContainerItemAnnotationConsideringFieldAndGetterIfSupported( + JsonIdentityReference.class, + JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + ); if (referenceAnnotation == null) { - referenceAnnotation = scope.getAnnotationConsideringFieldAndGetter(JsonIdentityReference.class, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER); + referenceAnnotation = scope.getAnnotationConsideringFieldAndGetter( + JsonIdentityReference.class, + JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + ); } return this.getIdentityReferenceType(referenceAnnotation, scope.getType(), scope.getContext()); } @@ -112,12 +117,20 @@ private Optional getIdentityReferenceType(JsonIdentityReference re return Optional.empty(); } // additionally, the type itself must have a @JsonIdentityInfo annotation - ResolvedType typeWithIdentityInfoAnnotation = typeContext.getTypeWithAnnotation(javaType, JsonIdentityInfo.class, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER); + ResolvedType typeWithIdentityInfoAnnotation = typeContext.getTypeWithAnnotation( + javaType, + JsonIdentityInfo.class, + JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + ); if (typeWithIdentityInfoAnnotation == null) { // otherwise, the @JsonIdentityReference annotation is simply ignored return Optional.empty(); } - JsonIdentityInfo identityInfoAnnotation = typeContext.getAnnotationFromList(JsonIdentityInfo.class, Arrays.asList(typeWithIdentityInfoAnnotation.getErasedType().getAnnotations()), JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER); + JsonIdentityInfo identityInfoAnnotation = typeContext.getAnnotationFromList( + JsonIdentityInfo.class, + Arrays.asList(typeWithIdentityInfoAnnotation.getErasedType().getAnnotations()), + JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + ); // @JsonIdentityInfo annotation declares generator with specific identity type ResolvedType identityTypeFromGenerator = typeContext.getTypeParameterFor(typeContext.resolve(identityInfoAnnotation.generator()), ObjectIdGenerator.class, 0); diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonPropertySorter.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonPropertySorter.java index 385fc4cf..31178e7b 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonPropertySorter.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonPropertySorter.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 VicTools. + * Copyright 2024 VicTools. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonSubTypesResolver.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonSubTypesResolver.java index 652e45e3..74f5ad13 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonSubTypesResolver.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonSubTypesResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 VicTools. + * Copyright 2024 VicTools. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,7 +34,6 @@ import com.github.victools.jsonschema.generator.TypeContext; import com.github.victools.jsonschema.generator.TypeScope; import com.github.victools.jsonschema.generator.impl.AttributeCollector; - import java.lang.annotation.Annotation; import java.util.Arrays; import java.util.Collection; @@ -126,7 +125,10 @@ public List findTargetTypeOverrides(MemberScope property) { if (this.skipSubtypeResolution(property)) { return null; } - JsonSubTypes subtypesAnnotation = property.getAnnotationConsideringFieldAndGetter(JsonSubTypes.class, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER); + JsonSubTypes subtypesAnnotation = property.getAnnotationConsideringFieldAndGetter( + JsonSubTypes.class, + JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + ); return this.lookUpSubtypesFromAnnotation(property.getType(), subtypesAnnotation, property.getContext()); } @@ -174,7 +176,11 @@ public CustomDefinition provideCustomSchemaDefinition(ResolvedType javaType, Sch return null; } final TypeContext typeContext = context.getTypeContext(); - ResolvedType typeWithTypeInfo = typeContext.getTypeWithAnnotation(javaType, JsonTypeInfo.class, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER); + ResolvedType typeWithTypeInfo = typeContext.getTypeWithAnnotation( + javaType, + JsonTypeInfo.class, + JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + ); if (typeWithTypeInfo == null || JacksonHelper.resolveAnnotation(javaType.getErasedType(), JsonSubTypes.class).isPresent() || this.skipSubtypeResolution(javaType, typeContext)) { // no @JsonTypeInfo annotation found or the given javaType is the super type, that should be replaced @@ -182,8 +188,16 @@ public CustomDefinition provideCustomSchemaDefinition(ResolvedType javaType, Sch } Class erasedTypeWithTypeInfo = typeWithTypeInfo.getErasedType(); final List annotationsList = Arrays.asList(erasedTypeWithTypeInfo.getAnnotations()); - JsonTypeInfo typeInfoAnnotation = typeContext.getAnnotationFromList(JsonTypeInfo.class, annotationsList, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER); - JsonSubTypes subTypesAnnotation = typeContext.getAnnotationFromList(JsonSubTypes.class, annotationsList, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER); + JsonTypeInfo typeInfoAnnotation = typeContext.getAnnotationFromList( + JsonTypeInfo.class, + annotationsList, + JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + ); + JsonSubTypes subTypesAnnotation = typeContext.getAnnotationFromList( + JsonSubTypes.class, + annotationsList, + JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + ); TypeScope scope = typeContext.createTypeScope(javaType); ObjectNode definition = this.createSubtypeDefinition(scope, typeInfoAnnotation, subTypesAnnotation, context); if (definition == null) { @@ -203,7 +217,10 @@ public CustomPropertyDefinition provideCustomPropertySchemaDefinition(MemberScop if (this.skipSubtypeResolution(scope) || JacksonHelper.resolveAnnotation(scope.getType().getErasedType(), JsonSubTypes.class).isPresent()) { return null; } - JsonTypeInfo typeInfoAnnotation = scope.getAnnotationConsideringFieldAndGetter(JsonTypeInfo.class, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER); + JsonTypeInfo typeInfoAnnotation = scope.getAnnotationConsideringFieldAndGetter( + JsonTypeInfo.class, + JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + ); if (typeInfoAnnotation == null) { // the normal per-type behaviour is not being overridden, i.e., no need for an inline custom property schema return null; @@ -215,7 +232,10 @@ public CustomPropertyDefinition provideCustomPropertySchemaDefinition(MemberScop .add(context.createStandardDefinitionReference(scope.getType(), this)); return new CustomPropertyDefinition(definition, CustomDefinition.AttributeInclusion.YES); } - JsonSubTypes subTypesAnnotation = scope.getAnnotationConsideringFieldAndGetter(JsonSubTypes.class, JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER); + JsonSubTypes subTypesAnnotation = scope.getAnnotationConsideringFieldAndGetter( + JsonSubTypes.class, + JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + ); ObjectNode definition = this.createSubtypeDefinition(scope, typeInfoAnnotation, subTypesAnnotation, context); if (definition == null) { return null; diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonUnwrappedDefinitionProvider.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonUnwrappedDefinitionProvider.java index a2eb86c5..671e9dcb 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonUnwrappedDefinitionProvider.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonUnwrappedDefinitionProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 VicTools. + * Copyright 2024 VicTools. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,6 @@ import com.github.victools.jsonschema.generator.CustomDefinitionProviderV2; import com.github.victools.jsonschema.generator.SchemaGenerationContext; import com.github.victools.jsonschema.generator.SchemaKeyword; - import java.util.ArrayList; import java.util.Arrays; import java.util.List; From f03489c54d9ef1ff05611f95570d83a1130f8b25 Mon Sep 17 00:00:00 2001 From: Smaarn Date: Thu, 31 Oct 2024 22:53:46 +0100 Subject: [PATCH 07/13] Renamed JacksonHelper -> AnnotationHelper --- ...cksonHelper.java => AnnotationHelper.java} | 4 ++-- .../jackson/CustomEnumDefinitionProvider.java | 4 ++-- .../module/jackson/JacksonModule.java | 22 +++++++++---------- ...onIdentityReferenceDefinitionProvider.java | 13 ++++++----- .../module/jackson/JsonPropertySorter.java | 4 ++-- .../module/jackson/JsonSubTypesResolver.java | 21 +++++++++--------- .../JsonUnwrappedDefinitionProvider.java | 4 ++-- ...perTest.java => AnnotationHelperTest.java} | 16 +++++++------- 8 files changed, 46 insertions(+), 42 deletions(-) rename jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/{JacksonHelper.java => AnnotationHelper.java} (98%) rename jsonschema-module-jackson/src/test/java/com/github/victools/jsonschema/module/jackson/{JacksonHelperTest.java => AnnotationHelperTest.java} (78%) diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonHelper.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/AnnotationHelper.java similarity index 98% rename from jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonHelper.java rename to jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/AnnotationHelper.java index ad1d0a62..d3c55ee5 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonHelper.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/AnnotationHelper.java @@ -28,7 +28,7 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; -final class JacksonHelper { +final class AnnotationHelper { static final Predicate JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER = new JacksonAnnotationsInsideAnnotatedFilter(); @@ -43,7 +43,7 @@ public boolean test(Annotation annotation) { } } - private JacksonHelper() { + private AnnotationHelper() { super(); } diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java index a68eb535..8379daf9 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java @@ -121,7 +121,7 @@ protected ResolvedMethod getJsonValueAnnotatedMethod(ResolvedType javaType, Sche ResolvedMethod[] memberMethods = context.getTypeContext().resolveWithMembers(javaType).getMemberMethods(); Set jsonValueAnnotatedMethods = Stream.of(memberMethods) .filter(method -> method.getArgumentCount() == 0) - .filter(method -> JacksonHelper.resolveAnnotation(method.getRawMember(), JsonValue.class).map(JsonValue::value).orElse(false)) + .filter(method -> AnnotationHelper.resolveAnnotation(method.getRawMember(), JsonValue.class).map(JsonValue::value).orElse(false)) .collect(Collectors.toSet()); if (jsonValueAnnotatedMethods.size() == 1) { return jsonValueAnnotatedMethods.iterator().next(); @@ -141,7 +141,7 @@ protected List getSerializedValuesFromJsonProperty(ResolvedType javaType List serializedJsonValues = new ArrayList<>(enumConstants.length); for (Object enumConstant : enumConstants) { String enumValueName = ((Enum) enumConstant).name(); - Optional annotation = JacksonHelper.resolveAnnotation( + Optional annotation = AnnotationHelper.resolveAnnotation( javaType.getErasedType().getDeclaredField(enumValueName), JsonProperty.class ); diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonModule.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonModule.java index 928e7a32..bc3a759a 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonModule.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonModule.java @@ -183,7 +183,7 @@ protected String resolveDescription(MemberScope member) { */ protected String resolveDescriptionForType(TypeScope scope) { Class rawType = scope.getType().getErasedType(); - Optional classAnnotation = JacksonHelper.resolveAnnotation(rawType, JsonClassDescription.class); + Optional classAnnotation = AnnotationHelper.resolveAnnotation(rawType, JsonClassDescription.class); return classAnnotation.map(JsonClassDescription::value).orElse(null); } @@ -200,7 +200,7 @@ protected String resolveDescriptionForType(TypeScope scope) { protected String getPropertyNameOverrideBasedOnJsonPropertyAnnotation(MemberScope member) { JsonProperty annotation = member.getAnnotationConsideringFieldAndGetter( JsonProperty.class, - JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + AnnotationHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER ); if (annotation != null) { String nameOverride = annotation.value(); @@ -237,7 +237,7 @@ protected String getPropertyNameOverrideBasedOnJsonNamingAnnotation(FieldScope f * @return annotated naming strategy instance (or {@code null}) */ private PropertyNamingStrategy getAnnotatedNamingStrategy(Class declaringType) { - return JacksonHelper.resolveAnnotation(declaringType, JsonNaming.class) + return AnnotationHelper.resolveAnnotation(declaringType, JsonNaming.class) .map(JsonNaming::value) .map(strategyType -> { try { @@ -276,13 +276,13 @@ protected final BeanDescription getBeanDescriptionForClass(ResolvedType targetTy protected boolean shouldIgnoreField(FieldScope field) { if (field.getAnnotationConsideringFieldAndGetterIfSupported( JsonBackReference.class, - JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER) != null) { + AnnotationHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER) != null) { return true; } // @since 4.32.0 JsonUnwrapped unwrappedAnnotation = field.getAnnotationConsideringFieldAndGetterIfSupported( JsonUnwrapped.class, - JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + AnnotationHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER ); if (unwrappedAnnotation != null && unwrappedAnnotation.enabled()) { // unwrapped properties should be ignored here, as they are included in their unwrapped form @@ -316,13 +316,13 @@ protected boolean shouldIgnoreMethod(MethodScope method) { if (getterField == null) { if (method.getAnnotationConsideringFieldAndGetterIfSupported( JsonBackReference.class, - JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER) != null) { + AnnotationHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER) != null) { return true; } // @since 4.32.0 JsonUnwrapped unwrappedAnnotation = method.getAnnotationConsideringFieldAndGetterIfSupported( JsonUnwrapped.class, - JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + AnnotationHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER ); if (unwrappedAnnotation != null && unwrappedAnnotation.enabled()) { // unwrapped properties should be ignored here, as they are included in their unwrapped form @@ -334,7 +334,7 @@ protected boolean shouldIgnoreMethod(MethodScope method) { return this.options.contains(JacksonOption.INCLUDE_ONLY_JSONPROPERTY_ANNOTATED_METHODS) && method.getAnnotationConsideringFieldAndGetter( JsonProperty.class, - JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER) == null; + AnnotationHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER) == null; } /** @@ -346,7 +346,7 @@ protected boolean shouldIgnoreMethod(MethodScope method) { protected boolean getRequiredCheckBasedOnJsonPropertyAnnotation(MemberScope member) { JsonProperty jsonProperty = member.getAnnotationConsideringFieldAndGetterIfSupported( JsonProperty.class, - JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + AnnotationHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER ); return jsonProperty != null && jsonProperty.required(); } @@ -360,7 +360,7 @@ protected boolean getRequiredCheckBasedOnJsonPropertyAnnotation(MemberScope member) { JsonProperty jsonProperty = member.getAnnotationConsideringFieldAndGetter( JsonProperty.class, - JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + AnnotationHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER ); return jsonProperty != null && jsonProperty.access() == JsonProperty.Access.READ_ONLY; } @@ -374,7 +374,7 @@ protected boolean getReadOnlyCheck(MemberScope member) { protected boolean getWriteOnlyCheck(MemberScope member) { JsonProperty jsonProperty = member.getAnnotationConsideringFieldAndGetter( JsonProperty.class, - JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + AnnotationHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER ); return jsonProperty != null && jsonProperty.access() == JsonProperty.Access.WRITE_ONLY; } diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonIdentityReferenceDefinitionProvider.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonIdentityReferenceDefinitionProvider.java index 2991b729..6081ce9f 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonIdentityReferenceDefinitionProvider.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonIdentityReferenceDefinitionProvider.java @@ -74,7 +74,10 @@ public CustomPropertyDefinition provideCustomPropertySchemaDefinition(MemberScop * @return designated type of the applicable identity reference (may be empty) */ public Optional getIdentityReferenceType(ResolvedType javaType, TypeContext typeContext) { - Optional referenceAnnotation = JacksonHelper.resolveAnnotation(javaType.getErasedType(), JsonIdentityReference.class); + Optional referenceAnnotation = AnnotationHelper.resolveAnnotation( + javaType.getErasedType(), + JsonIdentityReference.class + ); return this.getIdentityReferenceType(referenceAnnotation.orElse(null), javaType, typeContext); } @@ -89,12 +92,12 @@ public Optional getIdentityReferenceType(ResolvedType javaType, Ty public Optional getIdentityReferenceType(MemberScope scope) { JsonIdentityReference referenceAnnotation = scope.getContainerItemAnnotationConsideringFieldAndGetterIfSupported( JsonIdentityReference.class, - JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + AnnotationHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER ); if (referenceAnnotation == null) { referenceAnnotation = scope.getAnnotationConsideringFieldAndGetter( JsonIdentityReference.class, - JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + AnnotationHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER ); } return this.getIdentityReferenceType(referenceAnnotation, scope.getType(), scope.getContext()); @@ -120,7 +123,7 @@ private Optional getIdentityReferenceType(JsonIdentityReference re ResolvedType typeWithIdentityInfoAnnotation = typeContext.getTypeWithAnnotation( javaType, JsonIdentityInfo.class, - JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + AnnotationHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER ); if (typeWithIdentityInfoAnnotation == null) { // otherwise, the @JsonIdentityReference annotation is simply ignored @@ -129,7 +132,7 @@ private Optional getIdentityReferenceType(JsonIdentityReference re JsonIdentityInfo identityInfoAnnotation = typeContext.getAnnotationFromList( JsonIdentityInfo.class, Arrays.asList(typeWithIdentityInfoAnnotation.getErasedType().getAnnotations()), - JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + AnnotationHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER ); // @JsonIdentityInfo annotation declares generator with specific identity type ResolvedType identityTypeFromGenerator = typeContext.getTypeParameterFor(typeContext.resolve(identityInfoAnnotation.generator()), diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonPropertySorter.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonPropertySorter.java index 31178e7b..405177ec 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonPropertySorter.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonPropertySorter.java @@ -98,7 +98,7 @@ protected int getPropertyIndex(MemberScope property) { * @return whether properties that are not specifically mentioned in a {@link JsonPropertyOrder} annotation should be sorted alphabetically */ protected boolean shouldSortPropertiesAlphabetically(Class declaringType) { - return JacksonHelper.resolveAnnotation(declaringType, JsonPropertyOrder.class) + return AnnotationHelper.resolveAnnotation(declaringType, JsonPropertyOrder.class) .map(JsonPropertyOrder::alphabetic) .orElse(this.sortAlphabeticallyIfNotAnnotated); } @@ -110,7 +110,7 @@ protected boolean shouldSortPropertiesAlphabetically(Class declaringType) { * @return {@link JsonPropertyOrder#value()} or empty list */ private List getAnnotatedPropertyOrder(Class declaringType) { - return JacksonHelper.resolveAnnotation(declaringType, JsonPropertyOrder.class) + return AnnotationHelper.resolveAnnotation(declaringType, JsonPropertyOrder.class) .map(JsonPropertyOrder::value) .filter(valueArray -> valueArray.length != 0) .map(Arrays::asList) diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonSubTypesResolver.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonSubTypesResolver.java index 74f5ad13..ae8fb0db 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonSubTypesResolver.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonSubTypesResolver.java @@ -111,7 +111,7 @@ public List findSubtypes(ResolvedType declaredType, SchemaGenerati if (this.skipSubtypeResolution(declaredType, context.getTypeContext())) { return null; } - Optional subtypesAnnotation = JacksonHelper.resolveAnnotation(declaredType.getErasedType(), JsonSubTypes.class); + Optional subtypesAnnotation = AnnotationHelper.resolveAnnotation(declaredType.getErasedType(), JsonSubTypes.class); return this.lookUpSubtypesFromAnnotation(declaredType, subtypesAnnotation.orElse(null), context.getTypeContext()); } @@ -127,7 +127,7 @@ public List findTargetTypeOverrides(MemberScope property) { } JsonSubTypes subtypesAnnotation = property.getAnnotationConsideringFieldAndGetter( JsonSubTypes.class, - JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + AnnotationHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER ); return this.lookUpSubtypesFromAnnotation(property.getType(), subtypesAnnotation, property.getContext()); } @@ -179,9 +179,9 @@ public CustomDefinition provideCustomSchemaDefinition(ResolvedType javaType, Sch ResolvedType typeWithTypeInfo = typeContext.getTypeWithAnnotation( javaType, JsonTypeInfo.class, - JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + AnnotationHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER ); - if (typeWithTypeInfo == null || JacksonHelper.resolveAnnotation(javaType.getErasedType(), JsonSubTypes.class).isPresent() + if (typeWithTypeInfo == null || AnnotationHelper.resolveAnnotation(javaType.getErasedType(), JsonSubTypes.class).isPresent() || this.skipSubtypeResolution(javaType, typeContext)) { // no @JsonTypeInfo annotation found or the given javaType is the super type, that should be replaced return null; @@ -191,12 +191,12 @@ public CustomDefinition provideCustomSchemaDefinition(ResolvedType javaType, Sch JsonTypeInfo typeInfoAnnotation = typeContext.getAnnotationFromList( JsonTypeInfo.class, annotationsList, - JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + AnnotationHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER ); JsonSubTypes subTypesAnnotation = typeContext.getAnnotationFromList( JsonSubTypes.class, annotationsList, - JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + AnnotationHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER ); TypeScope scope = typeContext.createTypeScope(javaType); ObjectNode definition = this.createSubtypeDefinition(scope, typeInfoAnnotation, subTypesAnnotation, context); @@ -214,12 +214,13 @@ public CustomDefinition provideCustomSchemaDefinition(ResolvedType javaType, Sch * @return applicable custom per-property override schema definition (may be {@code null}) */ public CustomPropertyDefinition provideCustomPropertySchemaDefinition(MemberScope scope, SchemaGenerationContext context) { - if (this.skipSubtypeResolution(scope) || JacksonHelper.resolveAnnotation(scope.getType().getErasedType(), JsonSubTypes.class).isPresent()) { + if (this.skipSubtypeResolution(scope) + || AnnotationHelper.resolveAnnotation(scope.getType().getErasedType(), JsonSubTypes.class).isPresent()) { return null; } JsonTypeInfo typeInfoAnnotation = scope.getAnnotationConsideringFieldAndGetter( JsonTypeInfo.class, - JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + AnnotationHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER ); if (typeInfoAnnotation == null) { // the normal per-type behaviour is not being overridden, i.e., no need for an inline custom property schema @@ -234,7 +235,7 @@ public CustomPropertyDefinition provideCustomPropertySchemaDefinition(MemberScop } JsonSubTypes subTypesAnnotation = scope.getAnnotationConsideringFieldAndGetter( JsonSubTypes.class, - JacksonHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + AnnotationHelper.JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER ); ObjectNode definition = this.createSubtypeDefinition(scope, typeInfoAnnotation, subTypesAnnotation, context); if (definition == null) { @@ -294,7 +295,7 @@ private static Optional getNameFromSubTypeAnnotation(Class erasedTarg * @return simple class name, with declaring class's unqualified name as prefix for member classes */ private static Optional getNameFromTypeNameAnnotation(Class erasedTargetType) { - return JacksonHelper.resolveAnnotation(erasedTargetType, JsonTypeName.class) + return AnnotationHelper.resolveAnnotation(erasedTargetType, JsonTypeName.class) .map(JsonTypeName::value) .filter(name -> !name.isEmpty()); } diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonUnwrappedDefinitionProvider.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonUnwrappedDefinitionProvider.java index 671e9dcb..274e0ba5 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonUnwrappedDefinitionProvider.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonUnwrappedDefinitionProvider.java @@ -74,7 +74,7 @@ public CustomDefinition provideCustomSchemaDefinition(ResolvedType javaType, Sch * @return whether the given member has an {@code enabled} {@link JsonUnwrapped @JsonUnwrapped} annotation */ private boolean hasJsonUnwrappedAnnotation(ResolvedMember member) { - return JacksonHelper.resolveAnnotation(member, JsonUnwrapped.class).filter(JsonUnwrapped::enabled).isPresent(); + return AnnotationHelper.resolveAnnotation(member, JsonUnwrapped.class).filter(JsonUnwrapped::enabled).isPresent(); } /** @@ -85,7 +85,7 @@ private boolean hasJsonUnwrappedAnnotation(ResolvedMember member) { * @return created schema */ private Optional createUnwrappedMemberSchema(ResolvedMember member, SchemaGenerationContext context) { - final Optional optAnnotation = JacksonHelper.resolveAnnotation(member, JsonUnwrapped.class).filter(JsonUnwrapped::enabled); + final Optional optAnnotation = AnnotationHelper.resolveAnnotation(member, JsonUnwrapped.class).filter(JsonUnwrapped::enabled); return optAnnotation.map(annotation -> { ObjectNode definition = context.createStandardDefinition(member.getType(), null); if (!annotation.prefix().isEmpty() || !annotation.suffix().isEmpty()) { diff --git a/jsonschema-module-jackson/src/test/java/com/github/victools/jsonschema/module/jackson/JacksonHelperTest.java b/jsonschema-module-jackson/src/test/java/com/github/victools/jsonschema/module/jackson/AnnotationHelperTest.java similarity index 78% rename from jsonschema-module-jackson/src/test/java/com/github/victools/jsonschema/module/jackson/JacksonHelperTest.java rename to jsonschema-module-jackson/src/test/java/com/github/victools/jsonschema/module/jackson/AnnotationHelperTest.java index 64ba586d..14b3eed8 100644 --- a/jsonschema-module-jackson/src/test/java/com/github/victools/jsonschema/module/jackson/JacksonHelperTest.java +++ b/jsonschema-module-jackson/src/test/java/com/github/victools/jsonschema/module/jackson/AnnotationHelperTest.java @@ -12,11 +12,11 @@ import java.util.Optional; /** - * Unit test class dedicated to the validation of {@link JacksonHelper}. + * Unit test class dedicated to the validation of {@link AnnotationHelper}. * * @author Antoine Malliarakis */ -class JacksonHelperTest { +class AnnotationHelperTest { @JsonTypeName private static class DirectlyAnnotatedClass { @@ -81,40 +81,40 @@ private static class BreadthFirstAnnotatedClass {} @Test void resolveAnnotation_returnsAnEmptyInstanceIfNotAnnotated() { - final Optional result = JacksonHelper.resolveAnnotation(NonAnnotatedClass.class, JsonTypeName.class); + final Optional result = AnnotationHelper.resolveAnnotation(NonAnnotatedClass.class, JsonTypeName.class); Assertions.assertFalse(result.isPresent()); } @Test void resolveAnnotation_returnsAnEmptyInstanceIfNotAnnotatedEvenIfThereAreComboAnnotations() { - final Optional result = JacksonHelper.resolveAnnotation(AnnotatedClassWithUselessAnnotations.class, JsonTypeName.class); + final Optional result = AnnotationHelper.resolveAnnotation(AnnotatedClassWithUselessAnnotations.class, JsonTypeName.class); Assertions.assertFalse(result.isPresent()); } @Test void resolveAnnotation_supportsDirectAnnotations() { - final Optional result = JacksonHelper.resolveAnnotation(DirectlyAnnotatedClass.class, JsonTypeName.class); + final Optional result = AnnotationHelper.resolveAnnotation(DirectlyAnnotatedClass.class, JsonTypeName.class); Assertions.assertTrue(result.isPresent()); Assertions.assertEquals(result.get().value(), ""); } @Test void resolveAnnotation_directAnnotationTakesPrecedence() { - final Optional result = JacksonHelper.resolveAnnotation(BothDirectAndIndirectlyAnnotatedClass.class, JsonTypeName.class); + final Optional result = AnnotationHelper.resolveAnnotation(BothDirectAndIndirectlyAnnotatedClass.class, JsonTypeName.class); Assertions.assertTrue(result.isPresent()); Assertions.assertEquals("direct value", result.get().value()); } @Test void resolveAnnotation_returnsFirstValueFound() { - final Optional result = JacksonHelper.resolveAnnotation(IndirectlyAnnotatedClass.class, JsonTypeName.class); + final Optional result = AnnotationHelper.resolveAnnotation(IndirectlyAnnotatedClass.class, JsonTypeName.class); Assertions.assertTrue(result.isPresent()); Assertions.assertEquals("first combo annotation value", result.get().value()); } @Test void resolveAnnotation_usesABreadthFirstlookup() { - final Optional result = JacksonHelper.resolveAnnotation(BreadthFirstAnnotatedClass.class, JsonTypeName.class); + final Optional result = AnnotationHelper.resolveAnnotation(BreadthFirstAnnotatedClass.class, JsonTypeName.class); Assertions.assertTrue(result.isPresent()); Assertions.assertEquals("first combo annotation value", result.get().value()); } From f78c48f5e268726d97fe28a0bfd0e8d7c32c048a Mon Sep 17 00:00:00 2001 From: Smaarn Date: Thu, 31 Oct 2024 22:55:57 +0100 Subject: [PATCH 08/13] PR Feedback: turned static inner class into lambda --- .../module/jackson/AnnotationHelper.java | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/AnnotationHelper.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/AnnotationHelper.java index d3c55ee5..563d6ec3 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/AnnotationHelper.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/AnnotationHelper.java @@ -30,18 +30,8 @@ final class AnnotationHelper { - static final Predicate JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER = new JacksonAnnotationsInsideAnnotatedFilter(); - - private static final class JacksonAnnotationsInsideAnnotatedFilter implements Predicate { - JacksonAnnotationsInsideAnnotatedFilter() { - super(); - } - - @Override - public boolean test(Annotation annotation) { - return annotation.annotationType().isAnnotationPresent(JacksonAnnotationsInside.class); - } - } + static final Predicate JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER = (annotation) -> + annotation.annotationType().isAnnotationPresent(JacksonAnnotationsInside.class); private AnnotationHelper() { super(); From f128992e829da119f2167b2e472e6bf8e72aff60 Mon Sep 17 00:00:00 2001 From: Smaarn Date: Thu, 31 Oct 2024 22:59:28 +0100 Subject: [PATCH 09/13] Remove unsolicited @author mention (:facepalm:) --- .../jsonschema/module/jackson/AnnotationHelperTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/jsonschema-module-jackson/src/test/java/com/github/victools/jsonschema/module/jackson/AnnotationHelperTest.java b/jsonschema-module-jackson/src/test/java/com/github/victools/jsonschema/module/jackson/AnnotationHelperTest.java index 14b3eed8..84a7f2f2 100644 --- a/jsonschema-module-jackson/src/test/java/com/github/victools/jsonschema/module/jackson/AnnotationHelperTest.java +++ b/jsonschema-module-jackson/src/test/java/com/github/victools/jsonschema/module/jackson/AnnotationHelperTest.java @@ -13,8 +13,6 @@ /** * Unit test class dedicated to the validation of {@link AnnotationHelper}. - * - * @author Antoine Malliarakis */ class AnnotationHelperTest { From a6cc19e7f439a94a6bb07ee86bf150be6d42da40 Mon Sep 17 00:00:00 2001 From: Smaarn Date: Thu, 31 Oct 2024 22:59:46 +0100 Subject: [PATCH 10/13] Fix copyright years --- .../jsonschema/module/jackson/CustomEnumDefinitionProvider.java | 2 +- .../victools/jsonschema/module/jackson/JacksonModule.java | 2 +- .../module/jackson/JsonIdentityReferenceDefinitionProvider.java | 2 +- .../victools/jsonschema/module/jackson/JsonPropertySorter.java | 2 +- .../jsonschema/module/jackson/JsonSubTypesResolver.java | 2 +- .../module/jackson/JsonUnwrappedDefinitionProvider.java | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java index 8379daf9..a4050228 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 VicTools. + * Copyright 2020-2024 VicTools. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonModule.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonModule.java index bc3a759a..214968cc 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonModule.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JacksonModule.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 VicTools. + * Copyright 2019-2024 VicTools. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonIdentityReferenceDefinitionProvider.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonIdentityReferenceDefinitionProvider.java index 6081ce9f..3072d6cd 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonIdentityReferenceDefinitionProvider.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonIdentityReferenceDefinitionProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 VicTools. + * Copyright 2022-2024 VicTools. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonPropertySorter.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonPropertySorter.java index 405177ec..0074f320 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonPropertySorter.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonPropertySorter.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 VicTools. + * Copyright 2020-2024 VicTools. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonSubTypesResolver.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonSubTypesResolver.java index ae8fb0db..cc292c6c 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonSubTypesResolver.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonSubTypesResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 VicTools. + * Copyright 2020-2024 VicTools. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonUnwrappedDefinitionProvider.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonUnwrappedDefinitionProvider.java index 274e0ba5..7b5bd08b 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonUnwrappedDefinitionProvider.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/JsonUnwrappedDefinitionProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 VicTools. + * Copyright 2023-2024 VicTools. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From fadf84a5aae510567f8c024180a70512bdb02338 Mon Sep 17 00:00:00 2001 From: Smaarn Date: Thu, 31 Oct 2024 23:02:25 +0100 Subject: [PATCH 11/13] PR feedback: remove usage of erased type (let's use the direct type while we're at it) --- .../jsonschema/module/jackson/CustomEnumDefinitionProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java index a4050228..e13c336f 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/CustomEnumDefinitionProvider.java @@ -121,7 +121,7 @@ protected ResolvedMethod getJsonValueAnnotatedMethod(ResolvedType javaType, Sche ResolvedMethod[] memberMethods = context.getTypeContext().resolveWithMembers(javaType).getMemberMethods(); Set jsonValueAnnotatedMethods = Stream.of(memberMethods) .filter(method -> method.getArgumentCount() == 0) - .filter(method -> AnnotationHelper.resolveAnnotation(method.getRawMember(), JsonValue.class).map(JsonValue::value).orElse(false)) + .filter(method -> AnnotationHelper.resolveAnnotation(method, JsonValue.class).map(JsonValue::value).orElse(false)) .collect(Collectors.toSet()); if (jsonValueAnnotatedMethods.size() == 1) { return jsonValueAnnotatedMethods.iterator().next(); From d559d8c11def38a14f39cd34a1cba3dfcf9516a2 Mon Sep 17 00:00:00 2001 From: Smaarn Date: Fri, 1 Nov 2024 11:00:57 +0100 Subject: [PATCH 12/13] PR feedback: extract non "Jackson specific" logic into shared class in the base package --- .../generator/AnnotationHelper.java | 134 ++++++++++++++++++ .../jsonschema/generator/TypeContext.java | 15 +- .../generator/AnnotationHelperTest.java | 122 ++++++++++++++++ .../module/jackson/AnnotationHelper.java | 43 ++---- 4 files changed, 268 insertions(+), 46 deletions(-) create mode 100644 jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/AnnotationHelper.java create mode 100644 jsonschema-generator/src/test/java/com/github/victools/jsonschema/generator/AnnotationHelperTest.java diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/AnnotationHelper.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/AnnotationHelper.java new file mode 100644 index 00000000..e1ac4032 --- /dev/null +++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/AnnotationHelper.java @@ -0,0 +1,134 @@ +/* + * Copyright 2024 VicTools. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.github.victools.jsonschema.generator; + +import com.fasterxml.classmate.members.ResolvedMember; +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +/** + * Helper class providing with standard mechanism to resolve annotations on annotated entities. + * + * @since 4.37.0 + */ +public final class AnnotationHelper { + + private AnnotationHelper() { + super(); + } + + /** + * Resolves the specified annotation on the given resolved member and resolve nested annotations. + * + * @param the generic type of the annotation + * @param member where to look for the specified annotation + * @param annotationClass the class of the annotation to look for + * @param metaAnnotationPredicate the predicate indicating nested annotations + * @return an empty entry if not found + */ + public static Optional resolveAnnotation( + ResolvedMember member, + Class annotationClass, + Predicate metaAnnotationPredicate + ) { + final A annotation = member.getAnnotations().get(annotationClass); + if (annotation != null) { + return Optional.of(annotation); + } + return resolveNestedAnnotations(StreamSupport.stream(member.getAnnotations().spliterator(), false), annotationClass, metaAnnotationPredicate); + } + + /** + * Select the instance of the specified annotation type from the given list. + * + *

Also considering meta annotations (i.e., annotations on annotations) if a meta annotation is + * deemed eligible according to the given {@code Predicate}.

+ * + * @param
the generic type of the annotation + * @param annotationList a list of annotations to look into + * @param annotationClass the class of the annotation to look for + * @param metaAnnotationPredicate the predicate indicating nested annotations + * @return an empty entry if not found + */ + public static Optional resolveAnnotation( + List annotationList, + Class annotationClass, + Predicate metaAnnotationPredicate + ) { + final Optional annotation = annotationList.stream().filter(annotationClass::isInstance).findFirst(); + if (annotation.isPresent()) { + return annotation.map(annotationClass::cast); + } + return resolveNestedAnnotations(annotationList.stream(), annotationClass, metaAnnotationPredicate); + } + + /** + * Select the instance of the specified annotation type from the given annotatedElement's annotations. + * + *

Also considering meta annotations (i.e., annotations on annotations) if a meta annotation is + * deemed eligible according to the given metaAnnotationPredicate.

+ * + * @param
the generic type of the annotation + * @param annotatedElement where to look for the specified annotation + * @param annotationClass the class of the annotation to look for + * @param metaAnnotationPredicate the predicate indicating meta annotations + * @return an empty entry if not found + */ + public static Optional resolveAnnotation( + AnnotatedElement annotatedElement, + Class annotationClass, + Predicate metaAnnotationPredicate + ) { + final A annotation = annotatedElement.getAnnotation(annotationClass); + if (annotation != null) { + return Optional.of(annotation); + } + return resolveNestedAnnotations(Arrays.stream(annotatedElement.getAnnotations()), annotationClass, metaAnnotationPredicate); + } + + private static Optional resolveNestedAnnotations( + Stream initialAnnotations, + Class annotationClass, + Predicate metaAnnotationPredicate + ) { + List annotations = extractAnnotationsFromMetaAnnotations(initialAnnotations, metaAnnotationPredicate); + while (!annotations.isEmpty()) { + final Optional directAnnotation = annotations.stream().filter(annotationClass::isInstance).findFirst(); + if (directAnnotation.isPresent()) { + return directAnnotation.map(annotationClass::cast); + } + annotations = extractAnnotationsFromMetaAnnotations(annotations.stream(), metaAnnotationPredicate); + } + return Optional.empty(); + } + + private static List extractAnnotationsFromMetaAnnotations( + Stream annotations, + Predicate metaAnnotationPredicate + ) { + return annotations.filter(metaAnnotationPredicate) + .flatMap(a -> Arrays.stream(a.annotationType().getAnnotations())) + .collect(Collectors.toList()); + } +} diff --git a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/TypeContext.java b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/TypeContext.java index e0cdc899..e0661432 100644 --- a/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/TypeContext.java +++ b/jsonschema-generator/src/main/java/com/github/victools/jsonschema/generator/TypeContext.java @@ -276,20 +276,7 @@ public ResolvedType getContainerItemType(ResolvedType containerType) { */ public A getAnnotationFromList(Class annotationClass, List annotationList, Predicate considerOtherAnnotation) { - List annotations = annotationList; - while (!annotations.isEmpty()) { - Optional nestedAnnotation = annotations.stream() - .filter(annotationClass::isInstance) - .findFirst(); - if (nestedAnnotation.isPresent()) { - return nestedAnnotation.map(annotationClass::cast).get(); - } - annotations = annotations.stream() - .filter(considerOtherAnnotation) - .flatMap(otherAnnotation -> Stream.of(otherAnnotation.annotationType().getAnnotations())) - .collect(Collectors.toList()); - } - return null; + return AnnotationHelper.resolveAnnotation(annotationList, annotationClass, considerOtherAnnotation).orElse(null); } /** diff --git a/jsonschema-generator/src/test/java/com/github/victools/jsonschema/generator/AnnotationHelperTest.java b/jsonschema-generator/src/test/java/com/github/victools/jsonschema/generator/AnnotationHelperTest.java new file mode 100644 index 00000000..6409229d --- /dev/null +++ b/jsonschema-generator/src/test/java/com/github/victools/jsonschema/generator/AnnotationHelperTest.java @@ -0,0 +1,122 @@ +package com.github.victools.jsonschema.generator; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Arrays; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Stream; + +/** + * Unit test class dedicated to the validation of {@link AnnotationHelper}. + */ +class AnnotationHelperTest { + @Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE}) + @Retention(RetentionPolicy.RUNTIME) + public @interface TargetAnnotation { + String value() default ""; + } + + @Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE}) + @Retention(RetentionPolicy.RUNTIME) + public @interface MetaAnnotation {} + + @TargetAnnotation + private static class DirectlyAnnotatedClass { + } + + private static class NonAnnotatedClass { + } + + @UselessFirstComboAnnotation + @UselessSecondComboAnnotation + private static class AnnotatedClassWithUselessAnnotations { + + } + + @Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE}) + @Retention(RetentionPolicy.RUNTIME) + @MetaAnnotation + private @interface UselessFirstComboAnnotation { + } + + @Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE}) + @Retention(RetentionPolicy.RUNTIME) + @MetaAnnotation + private @interface UselessSecondComboAnnotation { + } + + @Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE}) + @Retention(RetentionPolicy.RUNTIME) + @MetaAnnotation + @TargetAnnotation("first combo annotation value") + private @interface FirstComboAnnotation { + } + + @Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE}) + @Retention(RetentionPolicy.RUNTIME) + @MetaAnnotation + @TargetAnnotation("second combo annotation value") + private @interface SecondComboAnnotation { + } + + @Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE}) + @Retention(RetentionPolicy.RUNTIME) + @MetaAnnotation + @SecondComboAnnotation + private @interface ThirdComboAnnotation { + } + + @FirstComboAnnotation + @SecondComboAnnotation + private static class IndirectlyAnnotatedClass { + } + + @TargetAnnotation("direct value") + @FirstComboAnnotation + @SecondComboAnnotation + private static class BothDirectAndIndirectlyAnnotatedClass { + } + + @ThirdComboAnnotation + @FirstComboAnnotation + private static class BreadthFirstAnnotatedClass {} + + static Stream annotationLookupScenarios() { + return Stream.of( + Arguments.of(NonAnnotatedClass.class, Optional.empty()), + Arguments.of(AnnotatedClassWithUselessAnnotations.class, Optional.empty()), + Arguments.of(DirectlyAnnotatedClass.class, Optional.of("")), + Arguments.of(BothDirectAndIndirectlyAnnotatedClass.class, Optional.of("direct value")), + Arguments.of(IndirectlyAnnotatedClass.class, Optional.of("first combo annotation value")), + Arguments.of(BreadthFirstAnnotatedClass.class, Optional.of("first combo annotation value")) + ); + } + + @ParameterizedTest + @MethodSource("annotationLookupScenarios") + void resolveAnnotation_AnnotatedElement_respects_annotationLookupScenarios(Class annotatedClass, Optional expectedAnnotationValue) { + Optional value = AnnotationHelper.resolveAnnotation(annotatedClass, TargetAnnotation.class, metaAnnotationPredicate()).map(TargetAnnotation::value); + Assertions.assertEquals(expectedAnnotationValue, value); + } + + @ParameterizedTest + @MethodSource("annotationLookupScenarios") + void resolveAnnotation_List_respects_annotationLookupScenarios(Class annotatedClass, Optional expectedAnnotationValue) { + Optional value = AnnotationHelper.resolveAnnotation(Arrays.asList(annotatedClass.getAnnotations()), TargetAnnotation.class, metaAnnotationPredicate()).map(TargetAnnotation::value); + Assertions.assertEquals(expectedAnnotationValue, value); + } + + private static Predicate metaAnnotationPredicate() { + return (annotation) -> annotation.annotationType().isAnnotationPresent(MetaAnnotation.class); + } + +} \ No newline at end of file diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/AnnotationHelper.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/AnnotationHelper.java index 563d6ec3..382af600 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/AnnotationHelper.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/AnnotationHelper.java @@ -20,13 +20,9 @@ import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; -import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.function.Predicate; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; final class AnnotationHelper { @@ -50,11 +46,11 @@ private AnnotationHelper() { * @return an empty entry if not found */ static Optional resolveAnnotation(ResolvedMember member, Class annotationClass) { - final A annotation = member.getAnnotations().get(annotationClass); - if (annotation != null) { - return Optional.of(annotation); - } - return resolveNestedAnnotations(StreamSupport.stream(member.getAnnotations().spliterator(), false), annotationClass); + return com.github.victools.jsonschema.generator.AnnotationHelper.resolveAnnotation( + member, + annotationClass, + JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + ); } /** @@ -70,28 +66,11 @@ static Optional resolveAnnotation(ResolvedMember me * @return an empty entry if not found */ static Optional resolveAnnotation(AnnotatedElement declaringType, Class annotationClass) { - final A annotation = declaringType.getAnnotation(annotationClass); - if (annotation != null) { - return Optional.of(annotation); - } - return resolveNestedAnnotations(Arrays.stream(declaringType.getAnnotations()), annotationClass); - } - - private static Optional resolveNestedAnnotations(Stream initialAnnotations, Class annotationClass) { - List annotations = extractNestedAnnotations(initialAnnotations); - while (!annotations.isEmpty()) { - final Optional directAnnotation = annotations.stream().filter(annotationClass::isInstance).findFirst(); - if (directAnnotation.isPresent()) { - return directAnnotation.map(annotationClass::cast); - } - annotations = extractNestedAnnotations(annotations.stream()); - } - return Optional.empty(); - } - - private static List extractNestedAnnotations(Stream annotations) { - return annotations.filter(JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER) - .flatMap(a -> Arrays.stream(a.annotationType().getAnnotations())) - .collect(Collectors.toList()); + return com.github.victools.jsonschema.generator.AnnotationHelper.resolveAnnotation( + declaringType, + annotationClass, + JACKSON_ANNOTATIONS_INSIDE_ANNOTATED_FILTER + ); } + } From e6e53b831031cce73912f9b4c6e628e3191ad4d2 Mon Sep 17 00:00:00 2001 From: Smaarn Date: Fri, 1 Nov 2024 11:01:35 +0100 Subject: [PATCH 13/13] PR feedback: extract non "Jackson specific" logic into shared class in the base package Remove no longer relevant comment --- .../jsonschema/module/jackson/AnnotationHelper.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/AnnotationHelper.java b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/AnnotationHelper.java index 382af600..31f83668 100644 --- a/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/AnnotationHelper.java +++ b/jsonschema-module-jackson/src/main/java/com/github/victools/jsonschema/module/jackson/AnnotationHelper.java @@ -36,10 +36,6 @@ private AnnotationHelper() { /** * Resolves the specified annotation on the given resolved member and resolve indirect jackson annotations. * - *

It uses the same algorithm as - * {@link com.github.victools.jsonschema.generator.TypeContext#getAnnotationFromList(Class, List, Predicate)} - * .

- * * @param
the generic type of the annotation * @param member where to look for the specified annotation * @param annotationClass the class of the annotation to look for @@ -55,10 +51,6 @@ static Optional resolveAnnotation(ResolvedMember me /** * Resolves the specified annotation on the given type and resolve indirect jackson annotations. - * - *

It uses the same algorithm as - * {@link com.github.victools.jsonschema.generator.TypeContext#getAnnotationFromList(Class, List, Predicate)} - * .

* * @param
the generic type of the annotation * @param declaringType where to look for the specified annotation