Repository: victools/jsonschema-generator
Pull request #30 (2020-02-23 17:42:20)

Offer safer/convenient type parameter look-up
The goal is to enable simpler configurations by already taking care of some edge cases like a generic type (e.g. Map<K, V>` being used without type parameters (i.e. as raw `Map`), by simply returning `Object` as lower boundary then.

For simplicity's sake, the actual look-up of boundaries of the generic type itself is being left-out here, especially since the handling of union types is not that straightforward.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7b8ddb9..a47f5b5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ## [Unreleased]
 ### Added
 - Support for "additionalProperties" property via `SchemaGeneratorConfigPart.withAdditionalPropertiesResolver()`
+- Offer `TypeScope.getTypeParameterFor()` and `TypeContext.getTypeParameterFor()` convenience methods
 
 ## [4.1.0] - 2020-02-18
 ### Added
diff --git a/README.md b/README.md
index d0efdb0..61dab2c 100644
--- a/README.md
+++ b/README.md
@@ -137,7 +137,7 @@ configBuilder.forTypesInGeneral()
     .withAdditionalPropertiesResolver((scope) -> {
         if (scope.getType().isInstanceOf(Map.class)) {
             // within a Map<Key, Value> allow additionalProperties of the Value type
-            return scope.getType().typeParametersFor(Map.class).get(1);
+            return scope.getTypeParameterFor(Map.class, 1);
         }
         if (scope.getType().isPrimitive()
                 || scope.getType().isInstanceOf(Number.class)
diff --git a/src/main/java/com/github/victools/jsonschema/generator/TypeContext.java b/src/main/java/com/github/victools/jsonschema/generator/TypeContext.java
index bb6833d..1d5a367 100644
--- a/src/main/java/com/github/victools/jsonschema/generator/TypeContext.java
+++ b/src/main/java/com/github/victools/jsonschema/generator/TypeContext.java
@@ -102,6 +102,30 @@ public class TypeContext {
         return new TypeScope(type, this);
     }
 
+    /**
+     * Find type parameterization for the specified (super) type at return the type parameter at the given index.
+     *
+     * @param type type to find type parameter for
+     * @param erasedSuperType (super) type to find declared type parameter for
+     * @param parameterIndex index of the single type parameter's declared type to return
+     * @return declared parameter type; or Object.class if no parameters are defined; or null if the given type or index are invalid
+     * @see ResolvedType#typeParametersFor(Class)
+     */
+    public ResolvedType getTypeParameterFor(ResolvedType type, Class<?> erasedSuperType, int parameterIndex) {
+        List<ResolvedType> typeParameters = type.typeParametersFor(erasedSuperType);
+        if (typeParameters == null
+                || (!typeParameters.isEmpty() && typeParameters.size() <= parameterIndex)
+                || (typeParameters.isEmpty() && erasedSuperType.getTypeParameters().length <= parameterIndex)) {
+            // given type is not a super type of the type in scope or given index is out of bounds
+            return null;
+        }
+        if (typeParameters.isEmpty()) {
+            // no type parameters are defined, for simplicity's sake not trying to resolve declared boundaries
+            return this.resolve(Object.class);
+        }
+        return typeParameters.get(parameterIndex);
+    }
+
     /**
      * Determine whether a given type should be treated as "{@value SchemaConstants#TAG_TYPE_ARRAY}" in the generated schema.
      *
@@ -122,7 +146,7 @@ public class TypeContext {
     public ResolvedType getContainerItemType(ResolvedType containerType) {
         ResolvedType itemType = containerType.getArrayElementType();
         if (itemType == null && this.isContainerType(containerType)) {
-            itemType = containerType.typeParametersFor(Iterable.class).get(0);
+            itemType = this.getTypeParameterFor(containerType, Iterable.class, 0);
         }
         return itemType;
     }
diff --git a/src/main/java/com/github/victools/jsonschema/generator/TypeScope.java b/src/main/java/com/github/victools/jsonschema/generator/TypeScope.java
index 60a13a0..9812ec3 100644
--- a/src/main/java/com/github/victools/jsonschema/generator/TypeScope.java
+++ b/src/main/java/com/github/victools/jsonschema/generator/TypeScope.java
@@ -58,6 +58,18 @@ public class TypeScope {
     /* ===================================== *
      * Convenience methods for targeted type *
      * ===================================== */
+    /**
+     * Find type parameterization for the specified (super) type at return the type parameter at the given index.
+     *
+     * @param erasedSuperType (super) type to find declared type parameter for
+     * @param parameterIndex index of the single type parameter's declared type to return
+     * @return declared parameter type; or Object.class if no parameters are defined; or null if the given type or index are invalid
+     * @see TypeContext#getTypeParameterFor(ResolvedType, Class, int)
+     */
+    public ResolvedType getTypeParameterFor(Class<?> erasedSuperType, int parameterIndex) {
+        return this.context.getTypeParameterFor(this.type, erasedSuperType, parameterIndex);
+    }
+
     /**
      * Determine whether this targeted type should be treated as "{@value SchemaConstants#TAG_TYPE_ARRAY}" in the generated schema.
      * <br>
diff --git a/src/main/java/com/github/victools/jsonschema/generator/impl/module/FlattenedOptionalModule.java b/src/main/java/com/github/victools/jsonschema/generator/impl/module/FlattenedOptionalModule.java
index 20f6313..c3ad804 100644
--- a/src/main/java/com/github/victools/jsonschema/generator/impl/module/FlattenedOptionalModule.java
+++ b/src/main/java/com/github/victools/jsonschema/generator/impl/module/FlattenedOptionalModule.java
@@ -38,8 +38,7 @@ public class FlattenedOptionalModule implements Module {
      * @return the wrapped component type (or null if it is not an {@link Optional} (sub) type
      */
     private ResolvedType resolveOptionalComponentType(MemberScope<?, ?> fieldOrMethod) {
-        ResolvedType javaType = fieldOrMethod.getType();
-        return FlattenedOptionalModule.isOptional(javaType) ? javaType.typeParametersFor(Optional.class).get(0) : null;
+        return FlattenedOptionalModule.isOptional(fieldOrMethod.getType()) ? fieldOrMethod.getTypeParameterFor(Optional.class, 0) : null;
     }
 
     @Override


Test patch:
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 ddee2c0..8e02ba2 100644
--- a/src/test/java/com/github/victools/jsonschema/generator/SchemaGeneratorTest.java
+++ b/src/test/java/com/github/victools/jsonschema/generator/SchemaGeneratorTest.java
@@ -177,7 +177,7 @@ public class SchemaGeneratorTest {
             return Object.class;
         }
         if (scope.getType().isInstanceOf(TestClass4.class)) {
-            return scope.getType().typeParametersFor(TestClass4.class).get(1);
+            return scope.getTypeParameterFor(TestClass4.class, 1);
         }
         return Void.class;
     }