Repository: victools/jsonschema-generator
Pull request #13 (2019-11-29 20:46:59)

Annotation convenience function
Introducing convenience function for common concern in modules:
- `MemberScope.getAnnotationConsideringFieldAndGetter(Class)`


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 457303f..f44767e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
 ## [Unreleased]
+### Added
+- Introduce convenience function `MemberScope.getAnnotationConsideringFieldAndGetter(Class)`
 
 ## [3.3.0] - 2019-10-25
 ### Fixed
diff --git a/pom.xml b/pom.xml
index e2adc48..518647b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -8,7 +8,7 @@
 
     <groupId>com.github.victools</groupId>
     <artifactId>jsonschema-generator</artifactId>
-    <version>3.3.1-SNAPSHOT</version>
+    <version>3.4.0-SNAPSHOT</version>
     <packaging>jar</packaging>
 
     <licenses>
diff --git a/src/main/java/com/github/victools/jsonschema/generator/FieldScope.java b/src/main/java/com/github/victools/jsonschema/generator/FieldScope.java
index 6632c90..3b6af98 100644
--- a/src/main/java/com/github/victools/jsonschema/generator/FieldScope.java
+++ b/src/main/java/com/github/victools/jsonschema/generator/FieldScope.java
@@ -20,6 +20,7 @@ import com.fasterxml.classmate.ResolvedType;
 import com.fasterxml.classmate.ResolvedTypeWithMembers;
 import com.fasterxml.classmate.members.ResolvedField;
 import com.fasterxml.classmate.members.ResolvedMethod;
+import java.lang.annotation.Annotation;
 import java.lang.reflect.Field;
 import java.util.stream.Stream;
 
@@ -112,4 +113,14 @@ public class FieldScope extends MemberScope<ResolvedField, Field> {
     public boolean hasGetter() {
         return this.findGetter() != null;
     }
+
+    @Override
+    public <A extends Annotation> A getAnnotationConsideringFieldAndGetter(Class<A> annotationClass) {
+        A annotation = this.getAnnotation(annotationClass);
+        if (annotation == null) {
+            MemberScope<?, ?> associatedGetter = this.findGetter();
+            annotation = associatedGetter == null ? null : associatedGetter.getAnnotation(annotationClass);
+        }
+        return annotation;
+    }
 }
diff --git a/src/main/java/com/github/victools/jsonschema/generator/MemberScope.java b/src/main/java/com/github/victools/jsonschema/generator/MemberScope.java
index d6656a6..eb396f4 100644
--- a/src/main/java/com/github/victools/jsonschema/generator/MemberScope.java
+++ b/src/main/java/com/github/victools/jsonschema/generator/MemberScope.java
@@ -245,6 +245,15 @@ public abstract class MemberScope<M extends ResolvedMember<T>, T extends Member>
         return this.member.get(annotationClass);
     }
 
+    /**
+     * Return the annotation of the given type on the member, if such an annotation is present on either the field or its getter.
+     *
+     * @param <A> type of annotation
+     * @param annotationClass type of annotation
+     * @return annotation instance (or {@code null} if no annotation of the given type is present)
+     */
+    public abstract <A extends Annotation> A getAnnotationConsideringFieldAndGetter(Class<A> annotationClass);
+
     /* ============================================== *
      * Convenience methods for member in this context *
      * ============================================== */
diff --git a/src/main/java/com/github/victools/jsonschema/generator/MethodScope.java b/src/main/java/com/github/victools/jsonschema/generator/MethodScope.java
index 1a955ea..019721a 100644
--- a/src/main/java/com/github/victools/jsonschema/generator/MethodScope.java
+++ b/src/main/java/com/github/victools/jsonschema/generator/MethodScope.java
@@ -19,6 +19,7 @@ package com.github.victools.jsonschema.generator;
 import com.fasterxml.classmate.ResolvedType;
 import com.fasterxml.classmate.ResolvedTypeWithMembers;
 import com.fasterxml.classmate.members.ResolvedMethod;
+import java.lang.annotation.Annotation;
 import java.lang.reflect.Method;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -138,6 +139,16 @@ public class MethodScope extends MemberScope<ResolvedMethod, Method> {
         return this.findGetterField() != null;
     }
 
+    @Override
+    public <A extends Annotation> A getAnnotationConsideringFieldAndGetter(Class<A> annotationClass) {
+        A annotation = this.getAnnotation(annotationClass);
+        if (annotation == null) {
+            MemberScope<?, ?> associatedField = this.findGetterField();
+            annotation = associatedField == null ? null : associatedField.getAnnotation(annotationClass);
+        }
+        return annotation;
+    }
+
     /**
      * Returns the name to be used to reference this method in its parent's "properties".
      *


Test patch:
diff --git a/src/test/java/com/github/victools/jsonschema/generator/FieldScopeTest.java b/src/test/java/com/github/victools/jsonschema/generator/FieldScopeTest.java
index e5bac35..7463124 100644
--- a/src/test/java/com/github/victools/jsonschema/generator/FieldScopeTest.java
+++ b/src/test/java/com/github/victools/jsonschema/generator/FieldScopeTest.java
@@ -16,6 +16,10 @@
 
 package com.github.victools.jsonschema.generator;
 
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
 import org.junit.Assert;
@@ -68,9 +72,28 @@ public class FieldScopeTest extends AbstractTypeAwareTest {
         Assert.assertEquals(expectedResult, result);
     }
 
+    @Test
+    @Parameters({
+        "fieldWithoutGetter, false",
+        "fieldWithPrivateGetter, true",
+        "fieldWithPublicGetter, false",
+        "fieldWithPublicBooleanGetter, true"
+    })
+    public void testGetAnnotationConsideringFieldAndGetter(String fieldName, boolean annotationExpectedToBeFound) {
+        FieldScope field = this.getTestClassField(fieldName);
+        TestAnnotation annotation = field.getAnnotationConsideringFieldAndGetter(TestAnnotation.class);
+
+        if (annotationExpectedToBeFound) {
+            Assert.assertNotNull(annotation);
+        } else {
+            Assert.assertNull(annotation);
+        }
+    }
+
     private static class TestClass {
 
         private String fieldWithoutGetter;
+        @TestAnnotation
         private int fieldWithPrivateGetter;
         private long fieldWithPublicGetter;
         private boolean fieldWithPublicBooleanGetter;
@@ -83,8 +106,14 @@ public class FieldScopeTest extends AbstractTypeAwareTest {
             return this.fieldWithPublicGetter;
         }
 
+        @TestAnnotation
         public boolean isFieldWithPublicBooleanGetter() {
             return this.fieldWithPublicBooleanGetter;
         }
     }
+
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target({ElementType.FIELD, ElementType.METHOD})
+    private static @interface TestAnnotation {
+    }
 }
diff --git a/src/test/java/com/github/victools/jsonschema/generator/MethodScopeTest.java b/src/test/java/com/github/victools/jsonschema/generator/MethodScopeTest.java
index 6bc2923..75169b7 100644
--- a/src/test/java/com/github/victools/jsonschema/generator/MethodScopeTest.java
+++ b/src/test/java/com/github/victools/jsonschema/generator/MethodScopeTest.java
@@ -16,6 +16,10 @@
 
 package com.github.victools.jsonschema.generator;
 
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
 import org.junit.Assert;
@@ -76,12 +80,32 @@ public class MethodScopeTest extends AbstractTypeAwareTest {
         Assert.assertEquals(expectedResult, result);
     }
 
+    @Test
+    @Parameters({
+        "calculateSomething, false",
+        "getFieldWithPrivateGetter, true",
+        "getFieldWithPublicGetter, false",
+        "isFieldWithPublicBooleanGetter, true"
+    })
+    public void testGetAnnotationConsideringFieldAndGetter(String methodName, boolean annotationExpectedToBeFound) {
+        MethodScope method = this.getTestClassMethod(methodName);
+        TestAnnotation annotation = method.getAnnotationConsideringFieldAndGetter(TestAnnotation.class);
+
+        if (annotationExpectedToBeFound) {
+            Assert.assertNotNull(annotation);
+        } else {
+            Assert.assertNull(annotation);
+        }
+    }
+
     private static class TestClass {
 
         private int fieldWithPrivateGetter;
         private long fieldWithPublicGetter;
+        @TestAnnotation
         private boolean fieldWithPublicBooleanGetter;
 
+        @TestAnnotation
         private int getFieldWithPrivateGetter() {
             return this.fieldWithPrivateGetter;
         }
@@ -114,4 +138,9 @@ public class MethodScopeTest extends AbstractTypeAwareTest {
             return 42;
         }
     }
+
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target({ElementType.FIELD, ElementType.METHOD})
+    private static @interface TestAnnotation {
+    }
 }
diff --git a/src/test/java/com/github/victools/jsonschema/generator/SchemaGeneratorTest.java b/src/test/java/com/github/victools/jsonschema/generator/SchemaGeneratorTest.java
index 4a42973..b1981c6 100644
--- a/src/test/java/com/github/victools/jsonschema/generator/SchemaGeneratorTest.java
+++ b/src/test/java/com/github/victools/jsonschema/generator/SchemaGeneratorTest.java
@@ -17,6 +17,7 @@ package com.github.victools.jsonschema.generator;
 
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
 import java.io.IOException;
 import java.io.InputStream;
 import java.math.BigDecimal;
@@ -99,8 +100,15 @@ public class SchemaGeneratorTest {
     }
 
     Object parametersForTestGenerateSchema() {
-        Module neutralModule = configBuilder -> {
-        };
+        Module neutralModule = configBuilder -> configBuilder.with((javaType, context) -> {
+            if (Integer.class == javaType.getErasedType()) {
+                ObjectNode customNode = configBuilder.getObjectMapper()
+                        .createObjectNode()
+                        .put("$comment", "custom definition for Integer.class");
+                return new CustomDefinition(customNode, false);
+            }
+            return null;
+        });
         Module methodModule = configBuilder -> populateConfigPart(configBuilder.forMethods(), "looked-up from method: ");
         Module fieldModule = configBuilder -> populateConfigPart(configBuilder.forFields(), "looked-up from field: ");
         return new Object[][]{
diff --git a/src/test/resources/com/github/victools/jsonschema/generator/testclass1-FULL_DOCUMENTATION.json b/src/test/resources/com/github/victools/jsonschema/generator/testclass1-FULL_DOCUMENTATION.json
index 4f8eda1..d44e278 100644
--- a/src/test/resources/com/github/victools/jsonschema/generator/testclass1-FULL_DOCUMENTATION.json
+++ b/src/test/resources/com/github/victools/jsonschema/generator/testclass1-FULL_DOCUMENTATION.json
@@ -1,4 +1,9 @@
 {
+    "definitions": {
+        "Integer (nullable)": {
+            "$comment": "custom definition for Integer.class"
+        }
+    },
     "type": "object",
     "properties": {
         "CONSTANT": {
@@ -15,7 +20,7 @@
             "type": ["string", "null"]
         },
         "ignoredInternalValue": {
-            "type": ["integer", "null"]
+            "$ref": "#/definitions/Integer (nullable)"
         },
         "primitiveValue": {
             "type": "integer"
diff --git a/src/test/resources/com/github/victools/jsonschema/generator/testclass3-FULL_DOCUMENTATION.json b/src/test/resources/com/github/victools/jsonschema/generator/testclass3-FULL_DOCUMENTATION.json
index 9dd2603..7028ee5 100644
--- a/src/test/resources/com/github/victools/jsonschema/generator/testclass3-FULL_DOCUMENTATION.json
+++ b/src/test/resources/com/github/victools/jsonschema/generator/testclass3-FULL_DOCUMENTATION.json
@@ -1,5 +1,8 @@
 {
     "definitions": {
+        "Integer (nullable)": {
+            "$comment": "custom definition for Integer.class"
+        },
         "Optional<String> (nullable)": {
             "type": ["object", "null"],
             "properties": {
@@ -71,7 +74,7 @@
                     "type": ["string", "null"]
                 },
                 "ignoredInternalValue": {
-                    "type": ["integer", "null"]
+                    "$ref": "#/definitions/Integer (nullable)"
                 },
                 "primitiveValue": {
                     "type": "integer"