1616
1717package com .github .victools .jsonschema .module .javax .validation ;
1818
19- import com .github .victools .jsonschema .generator .JavaType ;
19+ import com .github .victools .jsonschema .generator .FieldScope ;
20+ import com .github .victools .jsonschema .generator .MemberScope ;
21+ import com .github .victools .jsonschema .generator .MethodScope ;
2022import com .github .victools .jsonschema .generator .Module ;
2123import com .github .victools .jsonschema .generator .SchemaGeneratorConfigBuilder ;
2224import com .github .victools .jsonschema .generator .SchemaGeneratorConfigPart ;
23- import com .github .victools .jsonschema .generator .impl .ReflectionTypeUtils ;
24- import java .lang .reflect .AccessibleObject ;
25+ import java .lang .annotation .Annotation ;
2526import java .math .BigDecimal ;
2627import javax .validation .constraints .DecimalMax ;
2728import javax .validation .constraints .DecimalMin ;
3839import javax .validation .constraints .Size ;
3940
4041/**
41- * With this module, the base assumption for the nullable indication is that all fields and methods are nullable unless annotated otherwise.
42+ * JSON Schema Generation Module: based on annotations from the {@code javax.validation.constraints} package.
43+ * <ul>
44+ * <li>Determine whether a member is not nullable, base assumption being that all fields and method return values are nullable if not annotated.</li>
45+ * <li>Populate "minItems" and "maxItems" for containers (i.e. arrays and collections).</li>
46+ * <li>Populate "minLength" and "maxLength" for strings.</li>
47+ * <li>Populate "minimum"/"exclusiveMinimum" and "maximum"/"exclusiveMaximum" for numbers.</li>
48+ * </ul>
4249 */
4350public class JavaxValidationModule implements Module {
4451
@@ -53,7 +60,7 @@ public void applyToConfigBuilder(SchemaGeneratorConfigBuilder builder) {
5360 *
5461 * @param configPart config builder part to add configurations to
5562 */
56- private void applyToConfigPart (SchemaGeneratorConfigPart <? extends AccessibleObject > configPart ) {
63+ private void applyToConfigPart (SchemaGeneratorConfigPart <?> configPart ) {
5764 configPart .withNullableCheck (this ::isNullable );
5865 configPart .withArrayMinItemsResolver (this ::resolveArrayMinItems );
5966 configPart .withArrayMaxItemsResolver (this ::resolveArrayMaxItems );
@@ -65,21 +72,47 @@ private void applyToConfigPart(SchemaGeneratorConfigPart<? extends AccessibleObj
6572 configPart .withNumberExclusiveMaximumResolver (this ::resolveNumberExclusiveMaximum );
6673 }
6774
75+ /**
76+ * Retrieves the annotation instance of the given type, either from the field it self or (if not present) from its getter.
77+ *
78+ * @param <A> type of annotation
79+ * @param member field or method to retrieve annotation instance from (or from a field's getter or getter method's field)
80+ * @param annotationClass type of annotation
81+ * @return annotation instance (or {@code null})
82+ * @see MemberScope#getAnnotation(Class)
83+ * @see FieldScope#findGetter()
84+ * @see MethodScope#findGetterField()
85+ */
86+ protected <A extends Annotation > A getAnnotationFromFieldOrGetter (MemberScope <?, ?> member , Class <A > annotationClass ) {
87+ A annotation = member .getAnnotation (annotationClass );
88+ if (annotation == null ) {
89+ MemberScope <?, ?> associatedGetterOrField ;
90+ if (member instanceof FieldScope ) {
91+ associatedGetterOrField = ((FieldScope ) member ).findGetter ();
92+ } else if (member instanceof MethodScope ) {
93+ associatedGetterOrField = ((MethodScope ) member ).findGetterField ();
94+ } else {
95+ associatedGetterOrField = null ;
96+ }
97+ annotation = associatedGetterOrField == null ? null : associatedGetterOrField .getAnnotation (annotationClass );
98+ }
99+ return annotation ;
100+ }
101+
68102 /**
69103 * Determine whether a given field or method is annotated to be not nullable.
70104 *
71- * @param fieldOrMethod the field or method to check
72- * @param type field's or method return value's type
73- * @return whether the field/method is specifically annotated as nullable or not (returns null if not specified: assumption it is nullable then)
105+ * @param member the field or method to check
106+ * @return whether member is annotated as nullable or not (returns null if not specified: assumption it is nullable then)
74107 */
75- private Boolean isNullable (AccessibleObject fieldOrMethod , JavaType type ) {
108+ protected Boolean isNullable (MemberScope <?, ?> member ) {
76109 Boolean result ;
77- if (fieldOrMethod . isAnnotationPresent ( NotNull .class )
78- || fieldOrMethod . isAnnotationPresent ( NotBlank .class )
79- || fieldOrMethod . isAnnotationPresent ( NotEmpty .class )) {
110+ if (this . getAnnotationFromFieldOrGetter ( member , NotNull .class ) != null
111+ || this . getAnnotationFromFieldOrGetter ( member , NotBlank .class ) != null
112+ || this . getAnnotationFromFieldOrGetter ( member , NotEmpty .class ) != null ) {
80113 // field is specifically NOT nullable
81114 result = Boolean .FALSE ;
82- } else if (fieldOrMethod . isAnnotationPresent ( Null .class )) {
115+ } else if (this . getAnnotationFromFieldOrGetter ( member , Null .class ) != null ) {
83116 // field is specifically null (and thereby nullable)
84117 result = Boolean .TRUE ;
85118 } else {
@@ -91,19 +124,18 @@ private Boolean isNullable(AccessibleObject fieldOrMethod, JavaType type) {
91124 /**
92125 * Determine a given array type's minimum number of items.
93126 *
94- * @param fieldOrMethod the field or method to check
95- * @param type field's or method return value's type
127+ * @param member the field or method to check
96128 * @return specified minimum number of array items (or null)
97129 * @see Size
98130 */
99- private Integer resolveArrayMinItems (AccessibleObject fieldOrMethod , JavaType type ) {
100- if (ReflectionTypeUtils . isArrayType ( type )) {
101- Size sizeAnnotation = fieldOrMethod . getAnnotation ( Size .class );
131+ protected Integer resolveArrayMinItems (MemberScope <?, ?> member ) {
132+ if (member . isContainerType ( )) {
133+ Size sizeAnnotation = this . getAnnotationFromFieldOrGetter ( member , Size .class );
102134 if (sizeAnnotation != null && sizeAnnotation .min () > 0 ) {
103135 // minimum length greater than the default 0 was specified
104136 return sizeAnnotation .min ();
105137 }
106- if (fieldOrMethod . isAnnotationPresent ( NotEmpty .class )) {
138+ if (this . getAnnotationFromFieldOrGetter ( member , NotEmpty .class ) != null ) {
107139 return 1 ;
108140 }
109141 }
@@ -113,14 +145,13 @@ private Integer resolveArrayMinItems(AccessibleObject fieldOrMethod, JavaType ty
113145 /**
114146 * Determine a given array type's maximum number of items.
115147 *
116- * @param fieldOrMethod the field or method to check
117- * @param type field's or method return value's type
148+ * @param member the field or method to check
118149 * @return specified maximum number of array items (or null)
119150 * @see Size
120151 */
121- private Integer resolveArrayMaxItems (AccessibleObject fieldOrMethod , JavaType type ) {
122- if (ReflectionTypeUtils . isArrayType ( type )) {
123- Size sizeAnnotation = fieldOrMethod . getAnnotation ( Size .class );
152+ protected Integer resolveArrayMaxItems (MemberScope <?, ?> member ) {
153+ if (member . isContainerType ( )) {
154+ Size sizeAnnotation = this . getAnnotationFromFieldOrGetter ( member , Size .class );
124155 if (sizeAnnotation != null && sizeAnnotation .max () < 2147483647 ) {
125156 // maximum length below the default 2147483647 was specified
126157 return sizeAnnotation .max ();
@@ -132,23 +163,21 @@ private Integer resolveArrayMaxItems(AccessibleObject fieldOrMethod, JavaType ty
132163 /**
133164 * Determine a given text type's minimum number of characters.
134165 *
135- * @param fieldOrMethod the field or method to check
136- * @param type field's or method return value's type
166+ * @param member the field or method to check
137167 * @return specified minimum number of characters (or null)
138168 * @see Size
139169 * @see NotEmpty
140170 * @see NotBlank
141171 */
142- private Integer resolveStringMinLength (AccessibleObject fieldOrMethod , JavaType type ) {
143- Class <?> rawType = ReflectionTypeUtils .getRawType (type .getResolvedType ());
144- if (CharSequence .class .isAssignableFrom (rawType )) {
145- Size sizeAnnotation = fieldOrMethod .getAnnotation (Size .class );
172+ protected Integer resolveStringMinLength (MemberScope <?, ?> member ) {
173+ if (member .getType ().isInstanceOf (CharSequence .class )) {
174+ Size sizeAnnotation = this .getAnnotationFromFieldOrGetter (member , Size .class );
146175 if (sizeAnnotation != null && sizeAnnotation .min () > 0 ) {
147176 // minimum length greater than the default 0 was specified
148177 return sizeAnnotation .min ();
149178 }
150- if (fieldOrMethod . isAnnotationPresent ( NotEmpty .class )
151- || fieldOrMethod . isAnnotationPresent ( NotBlank .class )) {
179+ if (this . getAnnotationFromFieldOrGetter ( member , NotEmpty .class ) != null
180+ || this . getAnnotationFromFieldOrGetter ( member , NotBlank .class ) != null ) {
152181 return 1 ;
153182 }
154183 }
@@ -158,15 +187,13 @@ private Integer resolveStringMinLength(AccessibleObject fieldOrMethod, JavaType
158187 /**
159188 * Determine a given text type's maximum number of characters.
160189 *
161- * @param fieldOrMethod the field or method to check
162- * @param type field's or method return value's type
190+ * @param member the field or method to check
163191 * @return specified minimum number of characters (or null)
164192 * @see Size
165193 */
166- private Integer resolveStringMaxLength (AccessibleObject fieldOrMethod , JavaType type ) {
167- Class <?> rawType = ReflectionTypeUtils .getRawType (type .getResolvedType ());
168- if (CharSequence .class .isAssignableFrom (rawType )) {
169- Size sizeAnnotation = fieldOrMethod .getAnnotation (Size .class );
194+ protected Integer resolveStringMaxLength (MemberScope <?, ?> member ) {
195+ if (member .getType ().isInstanceOf (CharSequence .class )) {
196+ Size sizeAnnotation = this .getAnnotationFromFieldOrGetter (member , Size .class );
170197 if (sizeAnnotation != null && sizeAnnotation .max () < 2147483647 ) {
171198 // maximum length below the default 2147483647 was specified
172199 return sizeAnnotation .max ();
@@ -178,23 +205,22 @@ private Integer resolveStringMaxLength(AccessibleObject fieldOrMethod, JavaType
178205 /**
179206 * Determine a number type's minimum (inclusive) value.
180207 *
181- * @param fieldOrMethod the field or method to check
182- * @param type field's or method return value's type
208+ * @param member the field or method to check
183209 * @return specified inclusive minimum value (or null)
184210 * @see Min
185211 * @see DecimalMin
186212 * @see PositiveOrZero
187213 */
188- private BigDecimal resolveNumberInclusiveMinimum (AccessibleObject fieldOrMethod , JavaType type ) {
189- Min minAnnotation = fieldOrMethod . getAnnotation ( Min .class );
214+ protected BigDecimal resolveNumberInclusiveMinimum (MemberScope <?, ?> member ) {
215+ Min minAnnotation = this . getAnnotationFromFieldOrGetter ( member , Min .class );
190216 if (minAnnotation != null ) {
191217 return new BigDecimal (minAnnotation .value ());
192218 }
193- DecimalMin decimalMinAnnotation = fieldOrMethod . getAnnotation ( DecimalMin .class );
219+ DecimalMin decimalMinAnnotation = this . getAnnotationFromFieldOrGetter ( member , DecimalMin .class );
194220 if (decimalMinAnnotation != null && decimalMinAnnotation .inclusive ()) {
195221 return new BigDecimal (decimalMinAnnotation .value ());
196222 }
197- PositiveOrZero positiveAnnotation = fieldOrMethod . getAnnotation ( PositiveOrZero .class );
223+ PositiveOrZero positiveAnnotation = this . getAnnotationFromFieldOrGetter ( member , PositiveOrZero .class );
198224 if (positiveAnnotation != null ) {
199225 return BigDecimal .ZERO ;
200226 }
@@ -204,18 +230,17 @@ private BigDecimal resolveNumberInclusiveMinimum(AccessibleObject fieldOrMethod,
204230 /**
205231 * Determine a number type's minimum (exclusive) value.
206232 *
207- * @param fieldOrMethod the field or method to check
208- * @param type field's or method return value's type
233+ * @param member the field or method to check
209234 * @return specified exclusive minimum value (or null)
210235 * @see DecimalMin
211236 * @see Positive
212237 */
213- private BigDecimal resolveNumberExclusiveMinimum (AccessibleObject fieldOrMethod , JavaType type ) {
214- DecimalMin decimalMinAnnotation = fieldOrMethod . getAnnotation ( DecimalMin .class );
238+ protected BigDecimal resolveNumberExclusiveMinimum (MemberScope <?, ?> member ) {
239+ DecimalMin decimalMinAnnotation = this . getAnnotationFromFieldOrGetter ( member , DecimalMin .class );
215240 if (decimalMinAnnotation != null && !decimalMinAnnotation .inclusive ()) {
216241 return new BigDecimal (decimalMinAnnotation .value ());
217242 }
218- Positive positiveAnnotation = fieldOrMethod . getAnnotation ( Positive .class );
243+ Positive positiveAnnotation = this . getAnnotationFromFieldOrGetter ( member , Positive .class );
219244 if (positiveAnnotation != null ) {
220245 return BigDecimal .ZERO ;
221246 }
@@ -225,23 +250,22 @@ private BigDecimal resolveNumberExclusiveMinimum(AccessibleObject fieldOrMethod,
225250 /**
226251 * Determine a number type's maximum (inclusive) value.
227252 *
228- * @param fieldOrMethod the field or method to check
229- * @param type field's or method return value's type
253+ * @param member the field or method to check
230254 * @return specified inclusive maximum value (or null)
231255 * @see Max
232256 * @see DecimalMax#inclusive()
233257 * @see NegativeOrZero
234258 */
235- private BigDecimal resolveNumberInclusiveMaximum (AccessibleObject fieldOrMethod , JavaType type ) {
236- Max maxAnnotation = fieldOrMethod . getAnnotation ( Max .class );
259+ protected BigDecimal resolveNumberInclusiveMaximum (MemberScope <?, ?> member ) {
260+ Max maxAnnotation = this . getAnnotationFromFieldOrGetter ( member , Max .class );
237261 if (maxAnnotation != null ) {
238262 return new BigDecimal (maxAnnotation .value ());
239263 }
240- DecimalMax decimalMaxAnnotation = fieldOrMethod . getAnnotation ( DecimalMax .class );
264+ DecimalMax decimalMaxAnnotation = this . getAnnotationFromFieldOrGetter ( member , DecimalMax .class );
241265 if (decimalMaxAnnotation != null && decimalMaxAnnotation .inclusive ()) {
242266 return new BigDecimal (decimalMaxAnnotation .value ());
243267 }
244- NegativeOrZero negativeAnnotation = fieldOrMethod . getAnnotation ( NegativeOrZero .class );
268+ NegativeOrZero negativeAnnotation = this . getAnnotationFromFieldOrGetter ( member , NegativeOrZero .class );
245269 if (negativeAnnotation != null ) {
246270 return BigDecimal .ZERO ;
247271 }
@@ -251,18 +275,17 @@ private BigDecimal resolveNumberInclusiveMaximum(AccessibleObject fieldOrMethod,
251275 /**
252276 * Determine a number type's maximum (exclusive) value.
253277 *
254- * @param fieldOrMethod the field or method to check
255- * @param type field's or method return value's type
278+ * @param member the field or method to check
256279 * @return specified exclusive maximum value (or null)
257280 * @see DecimalMax#inclusive()
258281 * @see Negative
259282 */
260- private BigDecimal resolveNumberExclusiveMaximum (AccessibleObject fieldOrMethod , JavaType type ) {
261- DecimalMax decimalMaxAnnotation = fieldOrMethod . getAnnotation ( DecimalMax .class );
283+ protected BigDecimal resolveNumberExclusiveMaximum (MemberScope <?, ?> member ) {
284+ DecimalMax decimalMaxAnnotation = this . getAnnotationFromFieldOrGetter ( member , DecimalMax .class );
262285 if (decimalMaxAnnotation != null && !decimalMaxAnnotation .inclusive ()) {
263286 return new BigDecimal (decimalMaxAnnotation .value ());
264287 }
265- Negative negativeAnnotation = fieldOrMethod . getAnnotation ( Negative .class );
288+ Negative negativeAnnotation = this . getAnnotationFromFieldOrGetter ( member , Negative .class );
266289 if (negativeAnnotation != null ) {
267290 return BigDecimal .ZERO ;
268291 }
0 commit comments