001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.marshall.httppart;
018
019import static java.util.Collections.*;
020import static org.apache.juneau.commons.httppart.HttpPartDataType.*;
021import static org.apache.juneau.commons.httppart.HttpPartFormat.*;
022import static org.apache.juneau.commons.reflect.ReflectionUtils.*;
023import static org.apache.juneau.commons.utils.ClassUtils.*;
024import static org.apache.juneau.commons.utils.CollectionUtils.*;
025import static org.apache.juneau.commons.utils.CollectionUtils.list;
026import static org.apache.juneau.commons.utils.ObjectUtils.*;
027import static org.apache.juneau.commons.utils.Shorts.*;
028import static org.apache.juneau.commons.utils.Shorts.neq;
029import static org.apache.juneau.commons.utils.Shorts.eq;
030import static org.apache.juneau.commons.utils.StringUtils.*;
031import static org.apache.juneau.commons.utils.StringUtils.isEmpty;
032import static org.apache.juneau.commons.utils.ThrowableUtils.*;
033import static org.apache.juneau.marshall.Constants.*;
034
035import java.lang.annotation.*;
036import java.lang.reflect.*;
037import java.math.*;
038import java.util.*;
039import java.util.concurrent.atomic.*;
040import java.util.function.*;
041import java.util.regex.*;
042
043import org.apache.juneau.commons.*;
044import org.apache.juneau.commons.BasicRuntimeException;
045import org.apache.juneau.commons.collections.*;
046import org.apache.juneau.commons.httppart.*;
047import org.apache.juneau.commons.lang.*;
048import org.apache.juneau.commons.reflect.*;
049import org.apache.juneau.commons.utils.*;
050import org.apache.juneau.marshall.*;
051import org.apache.juneau.marshall.collections.*;
052import org.apache.juneau.marshall.parser.*;
053
054/**
055 * Represents an OpenAPI schema definition.
056 *
057 * <p>
058 * The schema definition can be applied to any HTTP parts such as bodies, headers, query/form parameters, and URL path parts.
059 * <br>The API is generic enough to apply to any path part although some attributes may only applicable for certain parts.
060 *
061 * <p>
062 * Schema objects are created via builders instantiated through the {@link #create()} method.
063 *
064 * <h5 class='section'>Jakarta Bean Validation Support:</h5>
065 * <p>
066 * As of 9.2.0, this class supports Jakarta Bean Validation constraint annotations (e.g., <c>@NotNull</c>, <c>@Size</c>, <c>@Min</c>, <c>@Max</c>).
067 * When these annotations are encountered during schema building, they are automatically mapped to corresponding OpenAPI schema properties:
068 * <ul>
069 *    <li><c>@NotNull</c> → <c>required(true)</c>
070 *    <li><c>@Size(min=x, max=y)</c> → <c>minLength/maxLength</c> and <c>minItems/maxItems</c>
071 *    <li><c>@Min(value)</c> → <c>minimum(value)</c>
072 *    <li><c>@Max(value)</c> → <c>maximum(value)</c>
073 *    <li><c>@Pattern(regexp)</c> → <c>pattern(regexp)</c>
074 *    <li><c>@Email</c> → <c>format("email")</c>
075 *    <li><c>@Positive/@PositiveOrZero/@Negative/@NegativeOrZero</c> → Corresponding min/max constraints
076 *    <li><c>@NotEmpty</c> → <c>required(true) + minLength(1)/minItems(1)</c>
077 *    <li><c>@NotBlank</c> → <c>required(true) + minLength(1) + pattern</c>
078 *    <li><c>@DecimalMin/@DecimalMax</c> → <c>minimum/maximum</c> with optional <c>exclusiveMinimum/exclusiveMaximum</c>
079 * </ul>
080 * <p>
081 * This integration uses pure reflection and does not require <c>jakarta.validation-api</c> as a dependency.
082 * The annotations are detected and processed automatically when present.
083 *
084 * <h5 class='section'>Notes:</h5><ul>
085 *    <li class='note'>This class is thread safe and reusable.
086 * </ul>
087 *
088 * <h5 class='section'>See Also:</h5><ul>
089 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/OpenApiSupport">OpenApi</a>
090 * </ul>
091 */
092@SuppressWarnings({
093   "java:S100", // Builder methods const_, default_ and enum_ use a trailing underscore to avoid Java keyword clashes with the OpenAPI keywords
094   "java:S115", // Constants use UPPER_camelCase convention (e.g., ANN_inclusive)
095   "java:S116", // Field names use trailing underscores (default_, enum_, const_) to avoid Java keyword conflicts
096   "java:S1168", // Builder helpers (toNumber, toSet, joinnlOrNull, getAnnotationString) return null as the absent-value sentinel that callers test for
097   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
098   "java:S1452", // Wildcard required for parsed type metadata
099   "java:S3776", // The HttpPartSchema(Builder) constructor and annotation-applying code branch over every OpenAPI type/format/constraint combination
100   "java:S5843", // Complex regex patterns needed for RFC compliance (IPv6, ISO 8601 duration, date-time formats)
101   "java:S6539", // Monster class; HttpPartSchema is intentionally a single cohesive OpenAPI/HTTP-part schema model+validator
102   "java:S6541", // The HttpPartSchema(Builder) constructor runs the full per-type validation switch in one place
103   "unused" // Method kept for future use or API completeness
104})
105public class HttpPartSchema {
106
107   private static final AnnotationProvider AP = AnnotationProvider.INSTANCE;
108
109   /**
110    * Builder class.
111    */
112   public static class Builder {
113      String name;
114      String default_;
115      Set<Integer> codes;
116      Set<String> enum_;
117      Boolean allowEmptyValue;
118      Boolean exclusiveMaximum;
119      Boolean exclusiveMinimum;
120      Boolean required;
121      Boolean uniqueItems;
122      Boolean skipIfEmpty;
123      HttpPartCollectionFormat collectionFormat = HttpPartCollectionFormat.NO_COLLECTION_FORMAT;
124      HttpPartDataType type = HttpPartDataType.NO_TYPE;
125      HttpPartFormat format = HttpPartFormat.NO_FORMAT;
126      Pattern pattern;
127      Number maximum;
128      Number minimum;
129      Number multipleOf;
130      Long maxLength;
131      Long minLength;
132      Long maxItems;
133      Long minItems;
134      Long maxProperties;
135      Long minProperties;
136      Map<String,Object> properties;
137      Object items;
138      Object additionalProperties;
139      boolean noValidate;
140      Class<?> parser;
141      Class<?> serializer;
142      // JSON Schema Draft 2020-12 properties
143      String const_;
144      String[] examples;
145      Boolean deprecated;
146      Number exclusiveMaximumValue;
147      Number exclusiveMinimumValue;
148
149      /**
150       * <mk>const</mk> field (JSON Schema Draft 2020-12).
151       *
152       * <p>
153       * Defines a constant value for this schema.
154       * The instance must be equal to this value to validate.
155       *
156       * @param value
157       *    The new value for this property.
158       * @return This object.
159       */
160      public Builder const_(String value) {
161         const_ = value;
162         return this;
163      }
164
165      /**
166       * <mk>default</mk> field.
167       *
168       * <p>
169       * Declares the value of the parameter that the server will use if none is provided, for example a "count" to control the number of results per page might default to 100 if not supplied by the client in the request.
170       * <br>(Note: "default" has no meaning for required parameters.)
171       *
172       * <p>
173       * Applicable to the following Swagger schema objects:
174       * <ul>
175       *    <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a>
176       *    <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a>
177       *    <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a>
178       *    <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a>
179       * </ul>
180       *
181       * @param value
182       *    The new value for this property.
183       *    <br>Ignored if value is <jk>null</jk>.
184       * @return This object.
185       */
186      public Builder default_(String value) {
187         if (ine(value))
188            default_ = value;
189         return this;
190      }
191
192      /**
193       * <mk>enum</mk> field.
194       *
195       * <p>
196       * If specified, the input validates successfully if it is equal to one of the elements in this array.
197       *
198       * <p>
199       * Applicable to the following Swagger schema objects:
200       * <ul>
201       *    <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a>
202       *    <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a>
203       *    <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a>
204       *    <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a>
205       * </ul>
206       *
207       * @param value
208       *    The new value for this property.
209       *    <br>Ignored if value is <jk>null</jk> or an empty set.
210       * @return This object.
211       */
212      public Builder enum_(Set<String> value) {
213         if (nn(value) && ! value.isEmpty())
214            enum_ = value;
215         return this;
216      }
217
218      /**
219       * <mk>enum</mk> field.
220       *
221       * <p>
222       * Same as {@link #enum_(Set)} but takes in a var-args array.
223       *
224       * @param values
225       *    The new values for this property.
226       *    <br>Ignored if value is empty.
227       * @return This object.
228       */
229      public Builder enum_(String...values) {
230         return enum_(set(values));
231      }
232
233      /**
234       * <mk>additionalProperties</mk> field.
235       *
236       * <p>
237       * Applicable to the following Swagger schema objects:
238       * <ul>
239       *    <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a>
240       * </ul>
241       *
242       * @param value
243       *    The new value for this property.
244       *    <br>Ignored if value is <jk>null</jk> or empty.
245       * @return This object.
246       */
247      public Builder additionalProperties(Builder value) {
248         if (nn(value))
249            additionalProperties = value;
250         return this;
251      }
252
253      /**
254       * <mk>additionalProperties</mk> field.
255       *
256       * <p>
257       * Applicable to the following Swagger schema objects:
258       * <ul>
259       *    <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a>
260       * </ul>
261       *
262       * @param value
263       *    The new value for this property.
264       *    <br>Ignored if value is <jk>null</jk> or empty.
265       * @return This object.
266       */
267      public Builder additionalProperties(HttpPartSchema value) {
268         if (nn(value))
269            additionalProperties = value;
270         return this;
271      }
272
273      /**
274       * Synonym for {@link #allowEmptyValue()}.
275       *
276       * @return This object.
277       */
278      public Builder aev() {
279         return allowEmptyValue(true);
280      }
281
282      /**
283       * Synonym for {@link #allowEmptyValue(Boolean)}.
284       *
285       * @param value
286       *    The new value for this property.
287       * @return This object.
288       */
289      public Builder aev(Boolean value) {
290         return allowEmptyValue(value);
291      }
292
293      /**
294       * Synonym for {@link #allowEmptyValue(String)}.
295       *
296       * @param value
297       *    The new value for this property.
298       * @return This object.
299       */
300      public Builder aev(String value) {
301         return allowEmptyValue(value);
302      }
303
304      /**
305       * <mk>allowEmptyValue</mk> field.
306       *
307       * <p>
308       * Shortcut for calling <code>allowEmptyValue(<jk>true</jk>);</code>.
309       *
310       * @return This object.
311       */
312      public Builder allowEmptyValue() {
313         return allowEmptyValue(true);
314      }
315
316      /**
317       * <mk>allowEmptyValue</mk> field.
318       *
319       * <p>
320       * Sets the ability to pass empty-valued parameters.
321       * <br>This is valid only for either query or formData parameters and allows you to send a parameter with a name only or an empty value.
322       * <br>The default value is <jk>false</jk>.
323       *
324       * <p>
325       * Applicable to the following Swagger schema objects:
326       * <ul>
327       *    <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a>
328       * </ul>
329       *
330       * @param value
331       *    The new value for this property.
332       *    <br>Ignored if value is <jk>null</jk>.
333       * @return This object.
334       */
335      public Builder allowEmptyValue(Boolean value) {
336         allowEmptyValue = resolve(value, allowEmptyValue);
337         return this;
338      }
339
340      /**
341       * <mk>allowEmptyValue</mk> field.
342       *
343       * <p>
344       * Same as {@link #allowEmptyValue(Boolean)} but takes in a string boolean value.
345       *
346       * @param value
347       *    The new value for this property.
348       *    <br>Ignored if value is <jk>null</jk> or empty.
349       * @return This object.
350       */
351      public Builder allowEmptyValue(String value) {
352         allowEmptyValue = resolve(value, allowEmptyValue);
353         return this;
354      }
355
356      /**
357       * Shortcut for <c>additionalProperties(value)</c>
358       *
359       * <p>
360       * Applicable to the following Swagger schema objects:
361       * <ul>
362       *    <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a>
363       * </ul>
364       *
365       * @param value
366       *    The new value for this property.
367       *    <br>Ignored if value is <jk>null</jk> or empty.
368       * @return This object.
369       */
370      public Builder ap(Builder value) {
371         return additionalProperties(value);
372      }
373
374      /**
375       * Shortcut for <c>additionalProperties(value)</c>
376       *
377       * <p>
378       * Applicable to the following Swagger schema objects:
379       * <ul>
380       *    <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a>
381       * </ul>
382       *
383       * @param value
384       *    The new value for this property.
385       *    <br>Ignored if value is <jk>null</jk> or empty.
386       * @return This object.
387       */
388      public Builder ap(HttpPartSchema value) {
389         return additionalProperties(value);
390      }
391
392      /**
393       * Apply the specified annotation to this schema.
394       *
395       * @param a The annotation to apply.  Must not be <jk>null</jk>.
396       * @return This object.
397      */
398      public Builder apply(Annotation a) {
399         if (a instanceof Schema a2)
400            return apply(a2);
401         if (a instanceof Items a2)
402            return apply(a2);
403         if (a instanceof SubItems a2)
404            return apply(a2);
405         if (a instanceof HttpPartMarshalling a2)
406            return apply(a2);
407         if (cn(a.annotationType()).startsWith("jakarta.validation.constraints.")) {
408            applyJakartaValidation(a);
409            return this;
410         }
411         return applyHttpAnnotation(a);
412      }
413
414      /**
415       * Instantiates a new {@link HttpPartSchema} object based on the configuration of this builder.
416       *
417       * <p>
418       * This method can be called multiple times to produce new schema objects.
419       *
420       * @return
421       *    A new {@link HttpPartSchema} object.
422       *    <br>Never <jk>null</jk>.
423       */
424      public HttpPartSchema build() {
425         return new HttpPartSchema(this);
426      }
427
428      /**
429       * Synonym for {@link #collectionFormat(HttpPartCollectionFormat)}.
430       *
431       * @param value
432       *    The new value for this property.
433       * @return This object.
434       */
435      public Builder cf(HttpPartCollectionFormat value) {
436         return collectionFormat(value);
437      }
438
439      /**
440       * Synonym for {@link #collectionFormat(String)}.
441       *
442       * @param value
443       *    The new value for this property.
444       * @return This object.
445       */
446      public Builder cf(String value) {
447         return collectionFormat(value);
448      }
449
450      /**
451       * Shortcut for <c>collectionFormat(HttpPartCollectionFormat.CSV)</c>.
452       *
453       * @return This object.
454       */
455      public Builder cfCsv() {
456         return collectionFormat(HttpPartCollectionFormat.CSV);
457      }
458
459      /**
460       * Shortcut for <c>collectionFormat(HttpPartCollectionFormat.MULTI)</c>.
461       *
462       * @return This object.
463       */
464      public Builder cfMulti() {
465         return collectionFormat(HttpPartCollectionFormat.MULTI);
466      }
467
468      /**
469       * Shortcut for <c>collectionFormat(HttpPartCollectionFormat.NO_COLLECTION_FORMAT)</c>.
470       *
471       * @return This object.
472       */
473      public Builder cfNone() {
474         return collectionFormat(HttpPartCollectionFormat.NO_COLLECTION_FORMAT);
475      }
476
477      /**
478       * Shortcut for <c>collectionFormat(HttpPartCollectionFormat.PIPES)</c>.
479       *
480       * @return This object.
481       */
482      public Builder cfPipes() {
483         return collectionFormat(HttpPartCollectionFormat.PIPES);
484      }
485
486      /**
487       * Shortcut for <c>collectionFormat(HttpPartCollectionFormat.SSV)</c>.
488       *
489       * @return This object.
490       */
491      public Builder cfSsv() {
492         return collectionFormat(HttpPartCollectionFormat.SSV);
493      }
494
495      /**
496       * Shortcut for <c>collectionFormat(HttpPartCollectionFormat.TSV)</c>.
497       *
498       * @return This object.
499       */
500      public Builder cfTsv() {
501         return collectionFormat(HttpPartCollectionFormat.TSV);
502      }
503
504      /**
505       * Shortcut for <c>collectionFormat(HttpPartCollectionFormat.UONC)</c>.
506       *
507       * @return This object.
508       */
509      public Builder cfUon() {
510         return collectionFormat(HttpPartCollectionFormat.UONC);
511      }
512
513      /**
514       * <mk>httpStatusCode</mk> key.
515       *
516       * <p>
517       * Applicable to the following Swagger schema objects:
518       * <ul>
519       *    <li><a class="doclink" href="https://swagger.io/specification/v2#responsesObject">Responses</a>
520       * </ul>
521       *
522       * @param value
523       *    The new value for this property.
524       *    <br>Ignored if value is <c>0</c>.
525       * @return This object.
526       */
527      public Builder code(int value) {
528         if (value != 0) {
529            if (codes == null)
530               codes = new TreeSet<>();
531            codes.add(value);
532         }
533         return this;
534      }
535
536      /**
537       * <mk>httpStatusCode</mk> key.
538       *
539       * <p>
540       * Applicable to the following Swagger schema objects:
541       * <ul>
542       *    <li><a class="doclink" href="https://swagger.io/specification/v2#responsesObject">Responses</a>
543       * </ul>
544       *
545       * @param value
546       *    The new value for this property.
547       *    <br>Ignored if <jk>null</jk> or an empty array.
548       * @return This object.
549       */
550      public Builder codes(int[] value) {
551         if (nn(value) && value.length != 0)
552            for (var v : value)
553               code(v);
554         return this;
555      }
556
557      /**
558       * <mk>collectionFormat</mk> field.
559       *
560       * <p>
561       * Determines the format of the array if <c>type</c> <js>"array"</js> is used.
562       * <br>Can only be used if <c>type</c> is <js>"array"</js>.
563       *
564       * <p>
565       * Applicable to the following Swagger schema objects:
566       * <ul>
567       *    <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a>
568       *    <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a>
569       *    <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a>
570       * </ul>
571       *
572       * <p>
573       * Note that for collections/arrays parameters with POJO element types, the input is broken into a string array before being converted into POJO elements.
574       *
575       * <ul class='values javatree'>
576       *    <ul class='jc'>{@link HttpPartCollectionFormat}
577       *    <ul>
578       *       <li>
579       *          {@link HttpPartCollectionFormat#CSV CSV} (default) - Comma-separated values (e.g. <js>"foo,bar"</js>).
580       *       <li>
581       *          {@link HttpPartCollectionFormat#SSV SSV} - Space-separated values (e.g. <js>"foo bar"</js>).
582       *       <li>
583       *          {@link HttpPartCollectionFormat#TSV TSV} - Tab-separated values (e.g. <js>"foo\tbar"</js>).
584       *       <li>
585       *          {@link HttpPartCollectionFormat#PIPES PIPES} - Pipe-separated values (e.g. <js>"foo|bar"</js>).
586       *       <li>
587       *          {@link HttpPartCollectionFormat#MULTI MULTI} - Corresponds to multiple parameter instances instead of multiple values for a single instance (e.g. <js>"foo=bar&amp;foo=baz"</js>).
588       *       <li>
589       *          {@link HttpPartCollectionFormat#UONC UONC} - UON collection notation (e.g. <js>"@(foo,bar)"</js>).
590       *    </ul>
591       * </ul>
592       *
593       * @param value
594       *    The new value for this property.
595       * @return This object.
596       */
597      public Builder collectionFormat(HttpPartCollectionFormat value) {
598         // Guard null for parity with the String overload (which ine()-guards); a null must not overwrite the field.
599         if (value != null)
600            collectionFormat = value;
601         return this;
602      }
603
604      /**
605       * <mk>collectionFormat</mk> field.
606       *
607       * <p>
608       * Determines the format of the array if <c>type</c> <js>"array"</js> is used.
609       * <br>Can only be used if <c>type</c> is <js>"array"</js>.
610       *
611       * <p>
612       * Applicable to the following Swagger schema objects:
613       * <ul>
614       *    <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a>
615       *    <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a>
616       *    <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a>
617       * </ul>
618       *
619       * <p>
620       * Note that for collections/arrays parameters with POJO element types, the input is broken into a string array before being converted into POJO elements.
621       *
622       * <ul class='values'>
623       *    <li>
624       *       <js>"csv"</js> (default) - Comma-separated values (e.g. <js>"foo,bar"</js>).
625       *    <li>
626       *       <js>"ssv"</js> - Space-separated values (e.g. <js>"foo bar"</js>).
627       *    <li>
628       *       <js>"tsv"</js> - Tab-separated values (e.g. <js>"foo\tbar"</js>).
629       *    <li>
630       *       <js>"pipes</js> - Pipe-separated values (e.g. <js>"foo|bar"</js>).
631       *    <li>
632       *       <js>"multi"</js> - Corresponds to multiple parameter instances instead of multiple values for a single instance (e.g. <js>"foo=bar&amp;foo=baz"</js>).
633       *    <li>
634       *       <js>"uon"</js> - UON notation (e.g. <js>"@(foo,bar)"</js>).
635       *    <li>
636       * </ul>
637       *
638       * @param value
639       *    The new value for this property.
640       *    <br>Ignored if value is <jk>null</jk> or empty.
641       * @return This object.
642       */
643      public Builder collectionFormat(String value) {
644         try {
645            if (ine(value))
646               this.collectionFormat = HttpPartCollectionFormat.fromString(value);
647         } catch (Exception e) {
648            throw new BasicRuntimeException(e, "Invalid value '%s' passed in as collectionFormat value.  Valid values: %s", value, HttpPartCollectionFormat.values());
649         }
650         return this;
651      }
652
653      /**
654       * <mk>deprecated</mk> field (JSON Schema Draft 2020-12).
655       *
656       * <p>
657       * Indicates that applications should refrain from usage of this property.
658       * This is used for documentation purposes only and does not affect validation.
659       *
660       * @param value
661       *    The new value for this property.
662       * @return This object.
663       */
664      public Builder deprecated(Boolean value) {
665         deprecated = resolve(value, deprecated);
666         return this;
667      }
668
669      /**
670       * Synonym for {@link #default_(String)}.
671       *
672       * @param value
673       *    The new value for this property.
674       * @return This object.
675       */
676      public Builder df(String value) {
677         return default_(value);
678      }
679
680      /**
681       * Synonym for {@link #enum_(Set)}.
682       *
683       * @param value
684       *    The new value for this property.
685       * @return This object.
686       */
687      public Builder e(Set<String> value) {
688         return enum_(value);
689      }
690
691      /**
692       * Synonym for {@link #enum_(String...)}.
693       *
694       * @param values
695       *    The new values for this property.
696       * @return This object.
697       */
698      public Builder e(String...values) {
699         return enum_(values);
700      }
701
702      /**
703       * Synonym for {@link #exclusiveMaximum()}.
704       *
705       * @return This object.
706       */
707      public Builder emax() {
708         return exclusiveMaximum();
709      }
710
711      /**
712       * Synonym for {@link #exclusiveMaximum(Boolean)}.
713       *
714       * @param value
715       *    The new value for this property.
716       * @return This object.
717       */
718      public Builder emax(Boolean value) {
719         return exclusiveMaximum(value);
720      }
721
722      /**
723       * Synonym for {@link #exclusiveMaximum(String)}.
724       *
725       * @param value
726       *    The new value for this property.
727       * @return This object.
728       */
729      public Builder emax(String value) {
730         return exclusiveMaximum(value);
731      }
732
733      /**
734       * Synonym for {@link #exclusiveMinimum()}.
735       *
736       * @return This object.
737       */
738      public Builder emin() {
739         return exclusiveMinimum();
740      }
741
742      /**
743       * Synonym for {@link #exclusiveMinimum(Boolean)}.
744       *
745       * @param value
746       *    The new value for this property.
747       * @return This object.
748       */
749      public Builder emin(Boolean value) {
750         return exclusiveMinimum(value);
751      }
752
753      /**
754       * Synonym for {@link #exclusiveMinimum(String)}.
755       *
756       * @param value
757       *    The new value for this property.
758       * @return This object.
759       */
760      public Builder emin(String value) {
761         return exclusiveMinimum(value);
762      }
763
764      /**
765       * <mk>examples</mk> field (JSON Schema Draft 2020-12).
766       *
767       * <p>
768       * An array of example values.
769       * This is used for documentation purposes only and does not affect validation.
770       *
771       * @param value
772       *    The new value for this property.
773       * @return This object.
774       */
775      public Builder examples(String...value) {
776         examples = value;
777         return this;
778      }
779
780      /**
781       * <mk>exclusiveMaximum</mk> field.
782       *
783       * <p>
784       * Shortcut for calling <code>exclusiveMaximum(<jk>true</jk>);</code>.
785       *
786       * @return This object.
787       */
788      public Builder exclusiveMaximum() {
789         return exclusiveMaximum(true);
790      }
791
792      /**
793       * <mk>exclusiveMaximum</mk> field.
794       *
795       * <p>
796       * Defines whether the maximum is matched exclusively.
797       *
798       * <p>
799       * Only allowed for the following types: <js>"integer"</js>, <js>"number"</js>.
800       * <br>If <jk>true</jk>, must be accompanied with <c>maximum</c>.
801       *
802       * <p>
803       * Applicable to the following Swagger schema objects:
804       * <ul>
805       *    <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a>
806       *    <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a>
807       *    <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a>
808       *    <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a>
809       * </ul>
810       *
811       * @param value
812       *    The new value for this property.
813       *    <br>Ignored if value is <jk>null</jk>.
814       * @return This object.
815       */
816      public Builder exclusiveMaximum(Boolean value) {
817         exclusiveMaximum = resolve(value, exclusiveMaximum);
818         return this;
819      }
820
821      /**
822       * <mk>exclusiveMaximum</mk> field.
823       *
824       * <p>
825       * Same as {@link #exclusiveMaximum(Boolean)} but takes in a string boolean value.
826       *
827       * @param value
828       *    The new value for this property.
829       *    <br>Ignored if value is <jk>null</jk> or empty.
830       * @return This object.
831       */
832      public Builder exclusiveMaximum(String value) {
833         exclusiveMaximum = resolve(value, exclusiveMaximum);
834         return this;
835      }
836
837      /**
838       * <mk>exclusiveMaximum</mk> field with numeric value (JSON Schema Draft 2020-12).
839       *
840       * <p>
841       * Defines the exclusive maximum value for numeric types.
842       * The instance is valid if it is strictly less than (not equal to) this value.
843       *
844       * <p>
845       * This is the Draft 2020-12 version that uses a numeric value instead of a boolean flag.
846       * If this is set, it takes precedence over the boolean {@link #exclusiveMaximum(Boolean)} property.
847       *
848       * @param value
849       *    The new value for this property.
850       * @return This object.
851       */
852      public Builder exclusiveMaximumValue(Number value) {
853         exclusiveMaximumValue = value;
854         return this;
855      }
856
857      /**
858       * <mk>exclusiveMinimum</mk> field.
859       *
860       * <p>
861       * Shortcut for calling <code>exclusiveMinimum(<jk>true</jk>);</code>.
862       *
863       * @return This object.
864       */
865      public Builder exclusiveMinimum() {
866         return exclusiveMinimum(true);
867      }
868
869      /**
870       * <mk>exclusiveMinimum</mk> field.
871       *
872       * <p>
873       * Defines whether the minimum is matched exclusively.
874       *
875       * <p>
876       * Only allowed for the following types: <js>"integer"</js>, <js>"number"</js>.
877       * <br>If <jk>true</jk>, must be accompanied with <c>minimum</c>.
878       *
879       * <p>
880       * Applicable to the following Swagger schema objects:
881       * <ul>
882       *    <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a>
883       *    <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a>
884       *    <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a>
885       *    <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a>
886       * </ul>
887       *
888       * @param value
889       *    The new value for this property.
890       *    <br>Ignored if value is <jk>null</jk>.
891       * @return This object.
892       */
893      public Builder exclusiveMinimum(Boolean value) {
894         exclusiveMinimum = resolve(value, exclusiveMinimum);
895         return this;
896      }
897
898      /**
899       * <mk>exclusiveMinimum</mk> field.
900       *
901       * <p>
902       * Same as {@link #exclusiveMinimum(Boolean)} but takes in a string boolean value.
903       *
904       * @param value
905       *    The new value for this property.
906       *    <br>Ignored if value is <jk>null</jk> or empty.
907       * @return This object.
908       */
909      public Builder exclusiveMinimum(String value) {
910         exclusiveMinimum = resolve(value, exclusiveMinimum);
911         return this;
912      }
913
914      /**
915       * <mk>exclusiveMinimum</mk> field with numeric value (JSON Schema Draft 2020-12).
916       *
917       * <p>
918       * Defines the exclusive minimum value for numeric types.
919       * The instance is valid if it is strictly greater than (not equal to) this value.
920       *
921       * <p>
922       * This is the Draft 2020-12 version that uses a numeric value instead of a boolean flag.
923       * If this is set, it takes precedence over the boolean {@link #exclusiveMinimum(Boolean)} property.
924       *
925       * @param value
926       *    The new value for this property.
927       * @return This object.
928       */
929      public Builder exclusiveMinimumValue(Number value) {
930         exclusiveMinimumValue = value;
931         return this;
932      }
933
934      /**
935       * Synonym for {@link #format(HttpPartFormat)}.
936       *
937       * @param value
938       *    The new value for this property.
939       * @return This object.
940       */
941      public Builder f(HttpPartFormat value) {
942         return format(value);
943      }
944
945      /**
946       * Synonym for {@link #format(String)}.
947       *
948       * @param value
949       *    The new value for this property.
950       * @return This object.
951       */
952      public Builder f(String value) {
953         return format(value);
954      }
955
956      /**
957       * Shortcut for <c>format(HttpPartFormat.BINARY)</c>.
958       *
959       * @return This object.
960       */
961      public Builder fBinary() {
962         format = HttpPartFormat.BINARY;
963         return this;
964      }
965
966      /**
967       * Shortcut for <c>format(HttpPartFormat.BINARY_SPACED)</c>.
968       *
969       * @return This object.
970       */
971      public Builder fBinarySpaced() {
972         format = HttpPartFormat.BINARY_SPACED;
973         return this;
974      }
975
976      /**
977       * Shortcut for <c>format(HttpPartFormat.BYTE)</c>.
978       *
979       * @return This object.
980       */
981      public Builder fByte() {
982         format = HttpPartFormat.BYTE;
983         return this;
984      }
985
986      /**
987       * Shortcut for <c>format(HttpPartFormat.DATE)</c>.
988       *
989       * @return This object.
990       */
991      public Builder fDate() {
992         format = HttpPartFormat.DATE;
993         return this;
994      }
995
996      /**
997       * Shortcut for <c>format(HttpPartFormat.DATE_TIME)</c>.
998       *
999       * @return This object.
1000       */
1001      public Builder fDateTime() {
1002         format = HttpPartFormat.DATE_TIME;
1003         return this;
1004      }
1005
1006      /**
1007       * Shortcut for <c>format(HttpPartFormat.DOUBLE)</c>.
1008       *
1009       * @return This object.
1010       */
1011      public Builder fDouble() {
1012         format = HttpPartFormat.DOUBLE;
1013         return this;
1014      }
1015
1016      /**
1017       * Shortcut for <c>format(HttpPartFormat.FLOAT)</c>.
1018       *
1019       * @return This object.
1020       */
1021      public Builder fFloat() {
1022         format = HttpPartFormat.FLOAT;
1023         return this;
1024      }
1025
1026      /**
1027       * Shortcut for <c>format(HttpPartFormat.INT32)</c>.
1028       *
1029       * @return This object.
1030       */
1031      public Builder fInt32() {
1032         format = HttpPartFormat.INT32;
1033         return this;
1034      }
1035
1036      /**
1037       * Shortcut for <c>format(HttpPartFormat.INT64)</c>.
1038       *
1039       * @return This object.
1040       */
1041      public Builder fInt64() {
1042         format = HttpPartFormat.INT64;
1043         return this;
1044      }
1045
1046      /**
1047       * Shortcut for <c>format(HttpPartFormat.NO_FORMAT)</c>.
1048       *
1049       * @return This object.
1050       */
1051      public Builder fNone() {
1052         format = HttpPartFormat.NO_FORMAT;
1053         return this;
1054      }
1055
1056      /**
1057       * <mk>format</mk> field.
1058       *
1059       * <p>
1060       * The extending format for the previously mentioned <a class="doclink" href="https://swagger.io/specification/v2#parameterType">parameter type</a>.
1061       *
1062       * <p>
1063       * Applicable to the following Swagger schema objects:
1064       * <ul>
1065       *    <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a>
1066       *    <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a>
1067       *    <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a>
1068       *    <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a>
1069       * </ul>
1070       *
1071       * <ul class='values javatree'>
1072       *    <ul class='jc'>{@link HttpPartFormat}
1073       *    <ul>
1074       *       <li class='jf'>
1075       *          {@link HttpPartFormat#INT32 INT32} - Signed 32 bits.
1076       *          <br>Only valid with type <js>"integer"</js>.
1077       *       <li class='jf'>
1078       *          {@link HttpPartFormat#INT64 INT64} - Signed 64 bits.
1079       *          <br>Only valid with type <js>"integer"</js>.
1080       *       <li class='jf'>
1081       *          {@link HttpPartFormat#FLOAT FLOAT} - 32-bit floating point number.
1082       *          <br>Only valid with type <js>"number"</js>.
1083       *       <li class='jf'>
1084       *          {@link HttpPartFormat#DOUBLE DOUBLE} - 64-bit floating point number.
1085       *          <br>Only valid with type <js>"number"</js>.
1086       *       <li class='jf'>
1087       *          {@link HttpPartFormat#BYTE BYTE} - BASE-64 encoded characters.
1088       *          <br>Only valid with type <js>"string"</js>.
1089       *          <br>Parameters of type POJO convertible from string are converted after the string has been decoded.
1090       *       <li class='jf'>
1091       *          {@link HttpPartFormat#BINARY BINARY} - Hexadecimal encoded octets (e.g. <js>"00FF"</js>).
1092       *          <br>Only valid with type <js>"string"</js>.
1093       *          <br>Parameters of type POJO convertible from string are converted after the string has been decoded.
1094       *       <li class='jf'>
1095       *          {@link HttpPartFormat#BINARY_SPACED BINARY_SPACED} - Hexadecimal encoded octets, spaced (e.g. <js>"00 FF"</js>).
1096       *          <br>Only valid with type <js>"string"</js>.
1097       *          <br>Parameters of type POJO convertible from string are converted after the string has been decoded.
1098       *       <li class='jf'>
1099       *          {@link HttpPartFormat#DATE DATE} - An <a href='http://xml2rfc.ietf.org/public/rfc/html/rfc3339.html#anchor14'>RFC3339 full-date</a>.
1100       *          <br>Only valid with type <js>"string"</js>.
1101       *       <li class='jf'>
1102       *          {@link HttpPartFormat#DATE_TIME DATE_TIME} - An <a href='http://xml2rfc.ietf.org/public/rfc/html/rfc3339.html#anchor14'>RFC3339 date-time</a>.
1103       *          <br>Only valid with type <js>"string"</js>.
1104       *       <li class='jf'>
1105       *          {@link HttpPartFormat#PASSWORD PASSWORD} - Used to hint UIs the input needs to be obscured.
1106       *          <br>This format does not affect the serialization or parsing of the parameter.
1107       *       <li class='jf'>
1108       *          {@link HttpPartFormat#UON UON} - UON notation (e.g. <js>"(foo=bar,baz=@(qux,123))"</js>).
1109       *          <br>Only valid with type <js>"object"</js>.
1110       *          <br>If not specified, then the input is interpreted as plain-text and is converted to a POJO directly.
1111       *    </ul>
1112       * </ul>
1113       *
1114       * <h5 class='section'>See Also:</h5><ul>
1115       *    <li class='extlink'><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Swagger Schema Object</a>
1116       * </ul>
1117       *
1118       * @param value
1119       *    The new value for this property.
1120       * @return This object.
1121       */
1122      public Builder format(HttpPartFormat value) {
1123         // Guard null for parity with the String overload (which ine()-guards); a null must not overwrite the field.
1124         if (value != null)
1125            format = value;
1126         return this;
1127      }
1128
1129      /**
1130       * <mk>format</mk> field.
1131       *
1132       * <p>
1133       * The extending format for the previously mentioned <a class="doclink" href="https://swagger.io/specification/v2#parameterType">parameter type</a>.
1134       *
1135       * <p>
1136       * Applicable to the following Swagger schema objects:
1137       * <ul>
1138       *    <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a>
1139       *    <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a>
1140       *    <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a>
1141       *    <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a>
1142       * </ul>
1143       *
1144       * <ul class='values'>
1145       *    <li>
1146       *       <js>"int32"</js> - Signed 32 bits.
1147       *       <br>Only valid with type <js>"integer"</js>.
1148       *    <li>
1149       *       <js>"int64"</js> - Signed 64 bits.
1150       *       <br>Only valid with type <js>"integer"</js>.
1151       *    <li>
1152       *       <js>"float"</js> - 32-bit floating point number.
1153       *       <br>Only valid with type <js>"number"</js>.
1154       *    <li>
1155       *       <js>"double"</js> - 64-bit floating point number.
1156       *       <br>Only valid with type <js>"number"</js>.
1157       *    <li>
1158       *       <js>"byte"</js> - BASE-64 encoded characters.
1159       *       <br>Only valid with type <js>"string"</js>.
1160       *       <br>Parameters of type POJO convertible from string are converted after the string has been decoded.
1161       *    <li>
1162       *       <js>"binary"</js> - Hexadecimal encoded octets (e.g. <js>"00FF"</js>).
1163       *       <br>Only valid with type <js>"string"</js>.
1164       *       <br>Parameters of type POJO convertible from string are converted after the string has been decoded.
1165       *    <li>
1166       *       <js>"binary-spaced"</js> - Hexadecimal encoded octets, spaced (e.g. <js>"00 FF"</js>).
1167       *       <br>Only valid with type <js>"string"</js>.
1168       *       <br>Parameters of type POJO convertible from string are converted after the string has been decoded.
1169       *    <li>
1170       *       <js>"date"</js> - An <a href='http://xml2rfc.ietf.org/public/rfc/html/rfc3339.html#anchor14'>RFC3339 full-date</a>.
1171       *       <br>Only valid with type <js>"string"</js>.
1172       *    <li>
1173       *       <js>"date-time"</js> - An <a href='http://xml2rfc.ietf.org/public/rfc/html/rfc3339.html#anchor14'>RFC3339 date-time</a>.
1174       *       <br>Only valid with type <js>"string"</js>.
1175       *    <li>
1176       *       <js>"password"</js> - Used to hint UIs the input needs to be obscured.
1177       *       <br>This format does not affect the serialization or parsing of the parameter.
1178       *    <li>
1179       *       <js>"uon"</js> - UON notation (e.g. <js>"(foo=bar,baz=@(qux,123))"</js>).
1180       *       <br>Only valid with type <js>"object"</js>.
1181       *       <br>If not specified, then the input is interpreted as plain-text and is converted to a POJO directly.
1182       * </ul>
1183       *
1184       * <h5 class='section'>See Also:</h5><ul>
1185       *    <li class='extlink'><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Swagger Schema Object</a>
1186       * </ul>
1187       *
1188       * @param value
1189       *    The new value for this property.
1190       *    <br>Ignored if value is <jk>null</jk> or an empty string.
1191       * @return This object.
1192       */
1193      public Builder format(String value) {
1194         try {
1195            if (ine(value))
1196               format = HttpPartFormat.fromString(value);
1197         } catch (Exception e) {
1198            throw new BasicRuntimeException(e, "Invalid value '%s' passed in as format value.  Valid values: %s", value, HttpPartFormat.values());
1199         }
1200         return this;
1201      }
1202
1203      /**
1204       * Shortcut for <c>format(HttpPartFormat.PASSWORD)</c>.
1205       *
1206       * @return This object.
1207       */
1208      public Builder fPassword() {
1209         format = HttpPartFormat.PASSWORD;
1210         return this;
1211      }
1212
1213      /**
1214       * Shortcut for <c>format(HttpPartFormat.UON)</c>.
1215       *
1216       * @return This object.
1217       */
1218      public Builder fUon() {
1219         format = HttpPartFormat.UON;
1220         return this;
1221      }
1222
1223      /**
1224       * Synonym for {@link #items(HttpPartSchema.Builder)}.
1225       *
1226       * @param value
1227       *    The new value for this property.
1228       * @return This object.
1229       */
1230      public Builder i(Builder value) {
1231         return items(value);
1232      }
1233
1234      /**
1235       * Synonym for {@link #items(HttpPartSchema)}.
1236       *
1237       * @param value
1238       *    The new value for this property.
1239       * @return This object.
1240       */
1241      public Builder i(HttpPartSchema value) {
1242         return items(value);
1243      }
1244
1245      /**
1246       * <mk>items</mk> field.
1247       *
1248       * <p>
1249       * Describes the type of items in the array.
1250       * <p>
1251       * Required if <c>type</c> is <js>"array"</js>.
1252       * <br>Can only be used if <c>type</c> is <js>"array"</js>.
1253       *
1254       * <p>
1255       * Applicable to the following Swagger schema objects:
1256       * <ul>
1257       *    <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a>
1258       *    <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a>
1259       *    <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a>
1260       *    <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a>
1261       * </ul>
1262       *
1263       * @param value
1264       *    The new value for this property.
1265       *    <br>Ignored if value is <jk>null</jk> or empty.
1266       * @return This object.
1267       */
1268      public Builder items(Builder value) {
1269         if (nn(value))
1270            items = value;
1271         return this;
1272      }
1273
1274      /**
1275       * <mk>items</mk> field.
1276       *
1277       * <p>
1278       * Describes the type of items in the array.
1279       * <p>
1280       * Required if <c>type</c> is <js>"array"</js>.
1281       * <br>Can only be used if <c>type</c> is <js>"array"</js>.
1282       *
1283       * <p>
1284       * Applicable to the following Swagger schema objects:
1285       * <ul>
1286       *    <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a>
1287       *    <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a>
1288       *    <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a>
1289       *    <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a>
1290       * </ul>
1291       *
1292       * @param value
1293       *    The new value for this property.
1294       *    <br>Ignored if value is <jk>null</jk> or empty.
1295       * @return This object.
1296       */
1297      public Builder items(HttpPartSchema value) {
1298         if (nn(value))
1299            items = value;
1300         return this;
1301      }
1302
1303      /**
1304       * Synonym for {@link #maximum(Number)}.
1305       *
1306       * @param value
1307       *    The new value for this property.
1308       * @return This object.
1309       */
1310      public Builder max(Number value) {
1311         return maximum(value);
1312      }
1313
1314      /**
1315       * Synonym for {@link #maxItems(Long)}.
1316       *
1317       * @param value
1318       *    The new value for this property.
1319       * @return This object.
1320       */
1321      public Builder maxi(Long value) {
1322         return maxItems(value);
1323      }
1324
1325      /**
1326       * Synonym for {@link #maxItems(String)}.
1327       *
1328       * @param value
1329       *    The new value for this property.
1330       * @return This object.
1331       */
1332      public Builder maxi(String value) {
1333         return maxItems(value);
1334      }
1335
1336      /**
1337       * <mk>maximum</mk> field.
1338       *
1339       * <p>
1340       * Defines the maximum value for a parameter of numeric types.
1341       *
1342       * <p>
1343       * Only allowed for the following types: <js>"integer"</js>, <js>"number"</js>.
1344       *
1345       * <p>
1346       * Applicable to the following Swagger schema objects:
1347       * <ul>
1348       *    <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a>
1349       *    <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a>
1350       *    <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a>
1351       *    <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a>
1352       * </ul>
1353       *
1354       * @param value
1355       *    The new value for this property.
1356       *    <br>Ignored if value is <jk>null</jk>.
1357       * @return This object.
1358       */
1359      public Builder maximum(Number value) {
1360         if (nn(value))
1361            maximum = value;
1362         return this;
1363      }
1364
1365      /**
1366       * <mk>maxItems</mk> field.
1367       *
1368       * <p>
1369       * An array or collection is valid if its size is less than, or equal to, the value of this keyword.
1370       *
1371       * <p>
1372       * Only allowed for the following types: <js>"array"</js>.
1373       *
1374       * <p>
1375       * Applicable to the following Swagger schema objects:
1376       * <ul>
1377       *    <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a>
1378       *    <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a>
1379       *    <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a>
1380       *    <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a>
1381       * </ul>
1382       *
1383       * @param value
1384       *    The new value for this property.
1385       *    <br>Ignored if value is <jk>null</jk> or <c>-1</c>.
1386       * @return This object.
1387       */
1388      public Builder maxItems(Long value) {
1389         maxItems = resolve(value, maxItems);
1390         return this;
1391      }
1392
1393      /**
1394       * <mk>maxItems</mk> field.
1395       *
1396       * <p>
1397       * Same as {@link #maxItems(Long)} but takes in a string number.
1398       *
1399       * @param value
1400       *    The new value for this property.
1401       *    <br>Ignored if value is <jk>null</jk> or empty.
1402       * @return This object.
1403       */
1404      public Builder maxItems(String value) {
1405         maxItems = resolve(value, maxItems);
1406         return this;
1407      }
1408
1409      /**
1410       * Synonym for {@link #maxLength(Long)}.
1411       *
1412       * @param value
1413       *    The new value for this property.
1414       * @return This object.
1415       */
1416      public Builder maxl(Long value) {
1417         return maxLength(value);
1418      }
1419
1420      /**
1421       * Synonym for {@link #maxLength(String)}.
1422       *
1423       * @param value
1424       *    The new value for this property.
1425       * @return This object.
1426       */
1427      public Builder maxl(String value) {
1428         return maxLength(value);
1429      }
1430
1431      /**
1432       * <mk>maxLength</mk> field.
1433       *
1434       * <p>
1435       * A string instance is valid against this keyword if its length is less than, or equal to, the value of this keyword.
1436       * <br>The length of a string instance is defined as the number of its characters as defined by <a href='https://tools.ietf.org/html/rfc4627'>RFC 4627</a>.
1437       *
1438       * <p>
1439       * Only allowed for the following types: <js>"string"</js>.
1440       *
1441       * <p>
1442       * Applicable to the following Swagger schema objects:
1443       * <ul>
1444       *    <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a>
1445       *    <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a>
1446       *    <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a>
1447       *    <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a>
1448       * </ul>
1449       *
1450       * @param value
1451       *    The new value for this property.
1452       *    <br>Ignored if value is <jk>null</jk> or <c>-1</c>.
1453       * @return This object.
1454       */
1455      public Builder maxLength(Long value) {
1456         maxLength = resolve(value, maxLength);
1457         return this;
1458      }
1459
1460      /**
1461       * <mk>maxLength</mk> field.
1462       *
1463       * <p>
1464       * Same as {@link #maxLength(Long)} but takes in a string number.
1465       *
1466       * @param value
1467       *    The new value for this property.
1468       *    <br>Ignored if value is <jk>null</jk> or empty.
1469       * @return This object.
1470       */
1471      public Builder maxLength(String value) {
1472         maxLength = resolve(value, maxLength);
1473         return this;
1474      }
1475
1476      /**
1477       * Synonym for {@link #maxProperties(Long)}.
1478       *
1479       * @param value
1480       *    The new value for this property.
1481       * @return This object.
1482       */
1483      public Builder maxp(Long value) {
1484         return maxProperties(value);
1485      }
1486
1487      /**
1488       * Synonym for {@link #maxProperties(String)}.
1489       *
1490       * @param value
1491       *    The new value for this property.
1492       * @return This object.
1493       */
1494      public Builder maxp(String value) {
1495         return maxProperties(value);
1496      }
1497
1498      /**
1499       * <mk>mapProperties</mk> field.
1500       *
1501       * <p>
1502       * Applicable to the following Swagger schema objects:
1503       * <ul>
1504       *    <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a>
1505       * </ul>
1506       *
1507       * @param value
1508       *    The new value for this property.
1509       *    <br>Ignored if value is <jk>null</jk> or <c>-1</c>.
1510       * @return This object.
1511       */
1512      public Builder maxProperties(Long value) {
1513         maxProperties = resolve(value, maxProperties);
1514         return this;
1515      }
1516
1517      /**
1518       * <mk>mapProperties</mk> field.
1519       *
1520       * <p>
1521       * Same as {@link #maxProperties(Long)} but takes in a string number.
1522       *
1523       * @param value
1524       *    The new value for this property.
1525       *    <br>Ignored if value is <jk>null</jk> or empty.
1526       * @return This object.
1527       */
1528      public Builder maxProperties(String value) {
1529         maxProperties = resolve(value, maxProperties);
1530         return this;
1531      }
1532
1533      /**
1534       * Synonym for {@link #minimum(Number)}.
1535       *
1536       * @param value
1537       *    The new value for this property.
1538       * @return This object.
1539       */
1540      public Builder min(Number value) {
1541         return minimum(value);
1542      }
1543
1544      /**
1545       * Synonym for {@link #minItems(Long)}.
1546       *
1547       * @param value
1548       *    The new value for this property.
1549       * @return This object.
1550       */
1551      public Builder mini(Long value) {
1552         return minItems(value);
1553      }
1554
1555      /**
1556       * Synonym for {@link #minItems(String)}.
1557       *
1558       * @param value
1559       *    The new value for this property.
1560       * @return This object.
1561       */
1562      public Builder mini(String value) {
1563         return minItems(value);
1564      }
1565
1566      /**
1567       * <mk>minimum</mk> field.
1568       *
1569       * <p>
1570       * Defines the minimum value for a parameter of numeric types.
1571       *
1572       * <p>
1573       * Only allowed for the following types: <js>"integer"</js>, <js>"number"</js>.
1574       *
1575       * <p>
1576       * Applicable to the following Swagger schema objects:
1577       * <ul>
1578       *    <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a>
1579       *    <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a>
1580       *    <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a>
1581       *    <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a>
1582       * </ul>
1583       *
1584       * @param value
1585       *    The new value for this property.
1586       *    <br>Ignored if value is <jk>null</jk>.
1587       * @return This object.
1588       */
1589      public Builder minimum(Number value) {
1590         if (nn(value))
1591            minimum = value;
1592         return this;
1593      }
1594
1595      /**
1596       * <mk>minItems</mk> field.
1597       *
1598       * <p>
1599       * An array or collection is valid if its size is greater than, or equal to, the value of this keyword.
1600       *
1601       * <p>
1602       * Only allowed for the following types: <js>"array"</js>.
1603       *
1604       * <p>
1605       * Applicable to the following Swagger schema objects:
1606       * <ul>
1607       *    <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a>
1608       *    <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a>
1609       *    <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a>
1610       *    <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a>
1611       * </ul>
1612       *
1613       * @param value
1614       *    The new value for this property.
1615       *    <br>Ignored if value is <jk>null</jk> or <c>-1</c>.
1616       * @return This object.
1617       */
1618      public Builder minItems(Long value) {
1619         minItems = resolve(value, minItems);
1620         return this;
1621      }
1622
1623      /**
1624       * <mk>minItems</mk> field.
1625       *
1626       * <p>
1627       * Same as {@link #minItems(Long)} but takes in a string number.
1628       *
1629       * @param value
1630       *    The new value for this property.
1631       *    <br>Ignored if value is <jk>null</jk> or empty.
1632       * @return This object.
1633       */
1634      public Builder minItems(String value) {
1635         minItems = resolve(value, minItems);
1636         return this;
1637      }
1638
1639      /**
1640       * Synonym for {@link #minLength(Long)}.
1641       *
1642       * @param value
1643       *    The new value for this property.
1644       * @return This object.
1645       */
1646      public Builder minl(Long value) {
1647         return minLength(value);
1648      }
1649
1650      /**
1651       * Synonym for {@link #minLength(String)}.
1652       *
1653       * @param value
1654       *    The new value for this property.
1655       * @return This object.
1656       */
1657      public Builder minl(String value) {
1658         return minLength(value);
1659      }
1660
1661      /**
1662       * <mk>minLength</mk> field.
1663       *
1664       * <p>
1665       * A string instance is valid against this keyword if its length is greater than, or equal to, the value of this keyword.
1666       * <br>The length of a string instance is defined as the number of its characters as defined by <a href='https://tools.ietf.org/html/rfc4627'>RFC 4627</a>.
1667       *
1668       * <p>
1669       * Only allowed for the following types: <js>"string"</js>.
1670       *
1671       * <p>
1672       * Applicable to the following Swagger schema objects:
1673       * <ul>
1674       *    <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a>
1675       *    <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a>
1676       *    <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a>
1677       *    <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a>
1678       * </ul>
1679       *
1680       * @param value
1681       *    The new value for this property.
1682       *    <br>Ignored if value is <jk>null</jk> or <c>-1</c>.
1683       * @return This object.
1684       */
1685      public Builder minLength(Long value) {
1686         minLength = resolve(value, minLength);
1687         return this;
1688      }
1689
1690      /**
1691       * <mk>minLength</mk> field.
1692       *
1693       * <p>
1694       * Same as {@link #minLength(Long)} but takes in a string number.
1695       *
1696       * @param value
1697       *    The new value for this property.
1698       *    <br>Ignored if value is <jk>null</jk> or empty.
1699       * @return This object.
1700       */
1701      public Builder minLength(String value) {
1702         minLength = resolve(value, minLength);
1703         return this;
1704      }
1705
1706      /**
1707       * Synonym for {@link #minProperties(Long)}.
1708       *
1709       * @param value
1710       *    The new value for this property.
1711       * @return This object.
1712       */
1713      public Builder minp(Long value) {
1714         return minProperties(value);
1715      }
1716
1717      /**
1718       * Synonym for {@link #minProperties(String)}.
1719       *
1720       * @param value
1721       *    The new value for this property.
1722       * @return This object.
1723       */
1724      public Builder minp(String value) {
1725         return minProperties(value);
1726      }
1727
1728      /**
1729       * <mk>minProperties</mk> field.
1730       *
1731       * <p>
1732       * Applicable to the following Swagger schema objects:
1733       * <ul>
1734       *    <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a>
1735       * </ul>
1736       *
1737       * @param value
1738       *    The new value for this property.
1739       *    <br>Ignored if value is <jk>null</jk>.
1740       * @return This object.
1741       */
1742      public Builder minProperties(Long value) {
1743         minProperties = resolve(value, minProperties);
1744         return this;
1745      }
1746
1747      /**
1748       * <mk>minProperties</mk> field.
1749       *
1750       * <p>
1751       * Same as {@link #minProperties(Long)} but takes in a string boolean.
1752       *
1753       * @param value
1754       *    The new value for this property.
1755       *    <br>Ignored if value is <jk>null</jk> or empty.
1756       * @return This object.
1757       */
1758      public Builder minProperties(String value) {
1759         minProperties = resolve(value, minProperties);
1760         return this;
1761      }
1762
1763      /**
1764       * Synonym for {@link #multipleOf(Number)}.
1765       *
1766       * @param value
1767       *    The new value for this property.
1768       * @return This object.
1769       */
1770      public Builder mo(Number value) {
1771         return multipleOf(value);
1772      }
1773
1774      /**
1775       * <mk>multipleOf</mk> field.
1776       *
1777       * <p>
1778       * A numeric instance is valid if the result of the division of the instance by this keyword's value is an integer.
1779       *
1780       * <p>
1781       * Only allowed for the following types: <js>"integer"</js>, <js>"number"</js>.
1782       *
1783       * <p>
1784       * Applicable to the following Swagger schema objects:
1785       * <ul>
1786       *    <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a>
1787       *    <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a>
1788       *    <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a>
1789       *    <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a>
1790       * </ul>
1791       *
1792       * @param value
1793       *    The new value for this property.
1794       *    <br>Ignored if value is <jk>null</jk>.
1795       * @return This object.
1796       */
1797      public Builder multipleOf(Number value) {
1798         if (nn(value))
1799            multipleOf = value;
1800         return this;
1801      }
1802
1803      /**
1804       * Synonym for {@link #name(String)}.
1805       *
1806       * @param value
1807       *    The new value for this property.
1808       * @return This object.
1809       */
1810      public Builder n(String value) {
1811         return name(value);
1812      }
1813
1814      /**
1815       * <mk>name</mk> field.
1816       *
1817       * <p>
1818       * Applicable to the following Swagger schema objects:
1819       * <ul>
1820       *    <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a>
1821       *    <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a>
1822       * </ul>
1823       *
1824       * @param value
1825       *    The new value for this property.
1826       * @return This object.
1827       */
1828      public Builder name(String value) {
1829         if (ine(value))
1830            name = value;
1831         return this;
1832      }
1833
1834      /**
1835       * Disables Swagger schema usage validation checking.
1836       *
1837       * <p>
1838       * Shortcut for calling <code>noValidate(<jk>true</jk>);</code>.
1839       *
1840       * @return This object.
1841       */
1842      public Builder noValidate() {
1843         return noValidate(true);
1844      }
1845
1846      /**
1847       * Disables Swagger schema usage validation checking.
1848       *
1849       * @param value Specify <jk>true</jk> to prevent {@link BasicRuntimeException} from being thrown if invalid Swagger usage was detected.
1850       * @return This object.
1851       */
1852      public Builder noValidate(Boolean value) {
1853         if (nn(value))
1854            noValidate = value;
1855         return this;
1856      }
1857
1858      /**
1859       * Synonym for {@link #pattern(String)}.
1860       *
1861       * @param value
1862       *    The new value for this property.
1863       * @return This object.
1864       */
1865      public Builder p(String value) {
1866         return pattern(value);
1867      }
1868
1869      /**
1870       * Shortcut for <c>property(key, value)</c>.
1871       *
1872       * <p>
1873       * Applicable to the following Swagger schema objects:
1874       * <ul>
1875       *    <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a>
1876       * </ul>
1877       *
1878       * @param key
1879       * The property name.
1880       * @param value
1881       *    The new value for this property.
1882       *    <br>Ignored if value is <jk>null</jk>.
1883       * @return This object.
1884       */
1885      public Builder p(String key, Builder value) {
1886         return property(key, value);
1887      }
1888
1889      /**
1890       * Shortcut for <c>property(key, value)</c>.
1891       *
1892       * <p>
1893       * Applicable to the following Swagger schema objects:
1894       * <ul>
1895       *    <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a>
1896       * </ul>
1897       *
1898       * @param key
1899       * The property name.
1900       * @param value
1901       *    The new value for this property.
1902       *    <br>Ignored if value is <jk>null</jk>.
1903       * @return This object.
1904       */
1905      public Builder p(String key, HttpPartSchema value) {
1906         return property(key, value);
1907      }
1908
1909      /**
1910       * Identifies the part parser to use for parsing this part.
1911       *
1912       * @param value
1913       *    The new value for this property.
1914       *    <br>Ignored if value is <jk>null</jk> or has the simple name "Void".
1915       * @return This object.
1916       */
1917      public Builder parser(Class<?> value) {
1918         if (isNotVoid(value))
1919            parser = value;
1920         return this;
1921      }
1922
1923      /**
1924       * <mk>pattern</mk> field.
1925       *
1926       * <p>
1927       * A string input is valid if it matches the specified regular expression pattern.
1928       *
1929       * <p>
1930       * Only allowed for the following types: <js>"string"</js>.
1931       *
1932       * <p>
1933       * Applicable to the following Swagger schema objects:
1934       * <ul>
1935       *    <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a>
1936       *    <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a>
1937       *    <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a>
1938       *    <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a>
1939       * </ul>
1940       *
1941       * @param value
1942       *    The new value for this property.
1943       *    <br>Ignored if value is <jk>null</jk> or empty.
1944       * @return This object.
1945       */
1946      public Builder pattern(String value) {
1947         try {
1948            if (ine(value))
1949               this.pattern = Pattern.compile(value);
1950         } catch (Exception e) {
1951            throw new BasicRuntimeException(e, "Invalid value %s passed in as pattern value.  Must be a valid regular expression.", value);
1952         }
1953         return this;
1954      }
1955
1956      /**
1957       * <mk>properties</mk> field.
1958       *
1959       * <p>
1960       * Applicable to the following Swagger schema objects:
1961       * <ul>
1962       *    <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a>
1963       * </ul>
1964       *
1965       * @param key
1966       * The property name.
1967       * @param value
1968       *    The new value for this property.
1969       *    <br>Ignored if value is <jk>null</jk>.
1970       * @return This object.
1971       */
1972      public Builder property(String key, Builder value) {
1973         if (nn(key) && nn(value)) {
1974            if (properties == null)
1975               properties = map();
1976            properties.put(key, value);
1977         }
1978         return this;
1979      }
1980
1981      /**
1982       * <mk>properties</mk> field.
1983       *
1984       * <p>
1985       * Applicable to the following Swagger schema objects:
1986       * <ul>
1987       *    <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a>
1988       * </ul>
1989       *
1990       * @param key
1991       * The property name.
1992       * @param value
1993       *    The new value for this property.
1994       *    <br>Ignored if value is <jk>null</jk>.
1995       * @return This object.
1996       */
1997      public Builder property(String key, HttpPartSchema value) {
1998         if (nn(key) && nn(value)) {
1999            if (properties == null)
2000               properties = map();
2001            properties.put(key, value);
2002         }
2003         return this;
2004      }
2005
2006      /**
2007       * Synonym for {@link #required()}.
2008       *
2009       * @return This object.
2010       */
2011      public Builder r() {
2012         return required();
2013      }
2014
2015      /**
2016       * Synonym for {@link #required(Boolean)}.
2017       *
2018       * @param value
2019       *    The new value for this property.
2020       * @return This object.
2021       */
2022      public Builder r(Boolean value) {
2023         return required(value);
2024      }
2025
2026      /**
2027       * Synonym for {@link #required(String)}.
2028       *
2029       * @param value
2030       *    The new value for this property.
2031       * @return This object.
2032       */
2033      public Builder r(String value) {
2034         return required(value);
2035      }
2036
2037      /**
2038       * <mk>required</mk> field.
2039       *
2040       * <p>
2041       * Shortcut for calling <code>required(<jk>true</jk>);</code>.
2042       *
2043       * @return This object.
2044       */
2045      public Builder required() {
2046         return required(true);
2047      }
2048
2049      /**
2050       * <mk>required</mk> field.
2051       *
2052       * <p>
2053       * Determines whether the parameter is mandatory.
2054       *
2055       * <p>
2056       * Applicable to the following Swagger schema objects:
2057       * <ul>
2058       *    <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a>
2059       *    <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a>
2060       * </ul>
2061       *
2062       * @param value
2063       *    The new value for this property.
2064       *    <br>Ignored if value is <jk>null</jk>.
2065       * @return This object.
2066       */
2067      public Builder required(Boolean value) {
2068         required = resolve(value, required);
2069         return this;
2070      }
2071
2072      /**
2073       * <mk>required</mk> field.
2074       *
2075       * <p>
2076       * Determines whether the parameter is mandatory.
2077       *
2078       * <p>
2079       * Same as {@link #required(Boolean)} but takes in a boolean value as a string.
2080       *
2081       * @param value
2082       *    The new value for this property.
2083       *    <br>Ignored if value is <jk>null</jk> or empty.
2084       * @return This object.
2085       */
2086      public Builder required(String value) {
2087         required = resolve(value, required);
2088         return this;
2089      }
2090
2091      /**
2092       * Identifies the part serializer to use for serializing this part.
2093       *
2094       * @param value
2095       *    The new value for this property.
2096       *    <br>Ignored if value is <jk>null</jk> or has the simple name "Void".
2097       * @return This object.
2098       */
2099      public Builder serializer(Class<?> value) {
2100         if (isNotVoid(value))
2101            serializer = value;
2102         return this;
2103      }
2104
2105      /**
2106       * Synonym for {@link #skipIfEmpty()}.
2107       *
2108       * @return This object.
2109       */
2110      public Builder sie() {
2111         return skipIfEmpty();
2112      }
2113
2114      /**
2115       * Synonym for {@link #skipIfEmpty(Boolean)}.
2116       *
2117       * @param value
2118       *    The new value for this property.
2119       * @return This object.
2120       */
2121      public Builder sie(Boolean value) {
2122         return skipIfEmpty(value);
2123      }
2124
2125      /**
2126       * Synonym for {@link #skipIfEmpty(String)}.
2127       *
2128       * @param value
2129       *    The new value for this property.
2130       * @return This object.
2131       */
2132      public Builder sie(String value) {
2133         return skipIfEmpty(value);
2134      }
2135
2136      /**
2137       * Identifies whether an item should be skipped if it's empty.
2138       *
2139       * <p>
2140       * Shortcut for calling <code>skipIfEmpty(<jk>true</jk>);</code>.
2141       *
2142       * @return This object.
2143       */
2144      public Builder skipIfEmpty() {
2145         return skipIfEmpty(true);
2146      }
2147
2148      /**
2149       * <mk>skipIfEmpty</mk> field.
2150       *
2151       * <p>
2152       * Identifies whether an item should be skipped during serialization if it's empty.
2153       *
2154       * @param value
2155       *    The new value for this property.
2156       *    <br>Ignored if value is <jk>null</jk>.
2157       * @return This object.
2158       */
2159      public Builder skipIfEmpty(Boolean value) {
2160         skipIfEmpty = resolve(value, skipIfEmpty);
2161         return this;
2162      }
2163
2164      /**
2165       * <mk>skipIfEmpty</mk> field.
2166       *
2167       * <p>
2168       * Same as {@link #skipIfEmpty(Boolean)} but takes in a string boolean.
2169       *
2170       * @param value
2171       *    The new value for this property.
2172       *    <br>Ignored if value is <jk>null</jk> or empty.
2173       * @return This object.
2174       */
2175      public Builder skipIfEmpty(String value) {
2176         skipIfEmpty = resolve(value, skipIfEmpty);
2177         return this;
2178      }
2179
2180      /**
2181       * Synonym for {@link #type(HttpPartDataType)}.
2182       *
2183       * @param value
2184       *    The new value for this property.
2185       * @return This object.
2186       */
2187      public Builder t(HttpPartDataType value) {
2188         return type(value);
2189      }
2190
2191      /**
2192       * Synonym for {@link #type(String)}.
2193       *
2194       * @param value
2195       *    The new value for this property.
2196       * @return This object.
2197       */
2198      public Builder t(String value) {
2199         return type(value);
2200      }
2201
2202      /**
2203       * Shortcut for <c>type(HttpPartDataType.ARRAY)</c>.
2204       *
2205       * @return This object.
2206       */
2207      public Builder tArray() {
2208         type = HttpPartDataType.ARRAY;
2209         return this;
2210      }
2211
2212      /**
2213       * Shortcut for <c>type(HttpPartDataType.BOOLEAN)</c>.
2214       *
2215       * @return This object.
2216       */
2217      public Builder tBoolean() {
2218         type = HttpPartDataType.BOOLEAN;
2219         return this;
2220      }
2221
2222      /**
2223       * Shortcut for <c>type(HttpPartDataType.FILE)</c>.
2224       *
2225       * @return This object.
2226       */
2227      public Builder tFile() {
2228         type = HttpPartDataType.FILE;
2229         return this;
2230      }
2231
2232      /**
2233       * Shortcut for <c>type(HttpPartDataType.INTEGER)</c>.
2234       *
2235       * @return This object.
2236       */
2237      public Builder tInteger() {
2238         type = HttpPartDataType.INTEGER;
2239         return this;
2240      }
2241
2242      /**
2243       * Shortcut for <c>type(HttpPartDataType.NO_TYPE)</c>.
2244       *
2245       * @return This object.
2246       */
2247      public Builder tNone() {
2248         type = HttpPartDataType.NO_TYPE;
2249         return this;
2250      }
2251
2252      /**
2253       * Shortcut for <c>type(HttpPartDataType.NUMBER)</c>.
2254       *
2255       * @return This object.
2256       */
2257      public Builder tNumber() {
2258         type = HttpPartDataType.NUMBER;
2259         return this;
2260      }
2261
2262      /**
2263       * Shortcut for <c>type(HttpPartDataType.OBJECT)</c>.
2264       *
2265       * @return This object.
2266       */
2267      public Builder tObject() {
2268         type = HttpPartDataType.OBJECT;
2269         return this;
2270      }
2271
2272      /**
2273       * Shortcut for <c>type(HttpPartDataType.STRING)</c>.
2274       *
2275       * @return This object.
2276       */
2277      public Builder tString() {
2278         type = HttpPartDataType.STRING;
2279         return this;
2280      }
2281
2282      /**
2283       * <mk>type</mk> field.
2284       *
2285       * <p>
2286       * The type of the parameter.
2287       *
2288       * <p>
2289       * If the type is not specified, it will be auto-detected based on the parameter class type.
2290       *
2291       * <p>
2292       * Applicable to the following Swagger schema objects:
2293       * <ul>
2294       *    <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a>
2295       *    <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a>
2296       *    <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a>
2297       *    <li><a class="doclink" href="https://swagger.io/specification/v2#securitySchemeObject">SecurityScheme</a>
2298       * </ul>
2299       *
2300       * <ul class='values javatree'>
2301       *    <li class='jc'>{@link HttpPartDataType}
2302       *    <ul>
2303       *       <li class='jf'>
2304       *          {@link HttpPartDataType#STRING STRING}
2305       *          <br>Parameter must be a string or a POJO convertible from a string.
2306       *          <li>
2307       *          {@link HttpPartDataType#NUMBER NUMBER}
2308       *          <br>Parameter must be a number primitive or number object.
2309       *          <br>If parameter is <c>Object</c>, creates either a <c>Float</c> or <c>Double</c> depending on the size of the number.
2310       *       <li class='jf'>
2311       *          {@link HttpPartDataType#INTEGER INTEGER}
2312       *          <br>Parameter must be a integer/long primitive or integer/long object.
2313       *          <br>If parameter is <c>Object</c>, creates either a <c>Short</c>, <c>Integer</c>, or <c>Long</c> depending on the size of the number.
2314       *       <li class='jf'>
2315       *          {@link HttpPartDataType#BOOLEAN BOOLEAN}
2316       *          <br>Parameter must be a boolean primitive or object.
2317       *       <li class='jf'>
2318       *          {@link HttpPartDataType#ARRAY ARRAY}
2319       *          <br>Parameter must be an array or collection.
2320       *          <br>Elements must be strings or POJOs convertible from strings.
2321       *          <br>If parameter is <c>Object</c>, creates an {@link JsonList}.
2322       *       <li class='jf'>
2323       *          {@link HttpPartDataType#OBJECT OBJECT}
2324       *          <br>Parameter must be a map or bean.
2325       *          <br>If parameter is <c>Object</c>, creates a map.
2326       *          <br>Note that this is an extension of the OpenAPI schema as Juneau allows for arbitrarily-complex POJOs to be serialized as HTTP parts.
2327       *       <li class='jf'>
2328       *          {@link HttpPartDataType#FILE FILE}
2329       *          <br>This type is currently not supported.
2330       *    </ul>
2331       * </ul>
2332       *
2333       * <h5 class='section'>See Also:</h5><ul>
2334       *    <li class='extlink'><a class="doclink" href="https://swagger.io/specification#dataTypes">Swagger Data Types</a>
2335       * </ul>
2336       *
2337       * @param value
2338       *    The new value for this property.
2339       * @return This object.
2340       */
2341      public Builder type(HttpPartDataType value) {
2342         // Guard null for parity with the String overload (which ine()-guards); a null must not overwrite the field.
2343         if (value != null)
2344            type = value;
2345         return this;
2346      }
2347
2348      /**
2349       * <mk>type</mk> field.
2350       *
2351       * <p>
2352       * The type of the parameter.
2353       *
2354       * <p>
2355       * If the type is not specified, it will be auto-detected based on the parameter class type.
2356       *
2357       * <p>
2358       * Applicable to the following Swagger schema objects:
2359       * <ul>
2360       *    <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a>
2361       *    <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a>
2362       *    <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a>
2363       *    <li><a class="doclink" href="https://swagger.io/specification/v2#securitySchemeObject">SecurityScheme</a>
2364       * </ul>
2365       *
2366       * <ul class='values'>
2367       *    <li>
2368       *       <js>"string"</js>
2369       *       <br>Parameter must be a string or a POJO convertible from a string.
2370       *    <li>
2371       *       <js>"number"</js>
2372       *       <br>Parameter must be a number primitive or number object.
2373       *       <br>If parameter is <c>Object</c>, creates either a <c>Float</c> or <c>Double</c> depending on the size of the number.
2374       *    <li>
2375       *       <js>"integer"</js>
2376       *       <br>Parameter must be a integer/long primitive or integer/long object.
2377       *       <br>If parameter is <c>Object</c>, creates either a <c>Short</c>, <c>Integer</c>, or <c>Long</c> depending on the size of the number.
2378       *    <li>
2379       *       <js>"boolean"</js>
2380       *       <br>Parameter must be a boolean primitive or object.
2381       *    <li>
2382       *       <js>"array"</js>
2383       *       <br>Parameter must be an array or collection.
2384       *       <br>Elements must be strings or POJOs convertible from strings.
2385       *       <br>If parameter is <c>Object</c>, creates an {@link JsonList}.
2386       *    <li>
2387       *       <js>"object"</js>
2388       *       <br>Parameter must be a map or bean.
2389       *       <br>If parameter is <c>Object</c>, creates a map.
2390       *       <br>Note that this is an extension of the OpenAPI schema as Juneau allows for arbitrarily-complex POJOs to be serialized as HTTP parts.
2391       *    <li>
2392       *       <js>"file"</js>
2393       *       <br>This type is currently not supported.
2394       * </ul>
2395       *
2396       * <h5 class='section'>See Also:</h5><ul>
2397       *    <li class='extlink'><a class="doclink" href="https://swagger.io/specification#dataTypes">Swagger Data Types</a>
2398       * </ul>
2399       *
2400       * @param value
2401       *    The new value for this property.
2402       *    <br>Ignored if value is <jk>null</jk> or empty.
2403       * @return This object.
2404       */
2405      public Builder type(String value) {
2406         try {
2407            if (ine(value))
2408               type = HttpPartDataType.fromString(value);
2409         } catch (Exception e) {
2410            throw new BasicRuntimeException(e, "Invalid value '%s' passed in as type value.  Valid values: %s", value, HttpPartDataType.values());
2411         }
2412         return this;
2413      }
2414
2415      /**
2416       * Synonym for {@link #uniqueItems()}.
2417       *
2418       * @return This object.
2419       */
2420      public Builder ui() {
2421         return uniqueItems();
2422      }
2423
2424      /**
2425       * Synonym for {@link #uniqueItems(Boolean)}.
2426       *
2427       * @param value
2428       *    The new value for this property.
2429       * @return This object.
2430       */
2431      public Builder ui(Boolean value) {
2432         return uniqueItems(value);
2433      }
2434
2435      /**
2436       * Synonym for {@link #uniqueItems(String)}.
2437       *
2438       * @param value
2439       *    The new value for this property.
2440       * @return This object.
2441       */
2442      public Builder ui(String value) {
2443         return uniqueItems(value);
2444      }
2445
2446      /**
2447       * <mk>uniqueItems</mk> field.
2448       *
2449       * <p>
2450       * Shortcut for calling <code>uniqueItems(<jk>true</jk>);</code>.
2451       *
2452       * @return This object.
2453       */
2454      public Builder uniqueItems() {
2455         return uniqueItems(true);
2456      }
2457
2458      /**
2459       * <mk>uniqueItems</mk> field.
2460       *
2461       * <p>
2462       * If <jk>true</jk>, the input validates successfully if all of its elements are unique.
2463       *
2464       * <p>
2465       * <br>If the parameter type is a subclass of {@link Set}, this validation is skipped (since a set can only contain unique items anyway).
2466       * <br>Otherwise, the collection or array is checked for duplicate items.
2467       *
2468       * <p>
2469       * Only allowed for the following types: <js>"array"</js>.
2470       *
2471       * <p>
2472       * Applicable to the following Swagger schema objects:
2473       * <ul>
2474       *    <li><a class="doclink" href="https://swagger.io/specification/v2#parameterObject">Parameter</a>
2475       *    <li><a class="doclink" href="https://swagger.io/specification/v2#schemaObject">Schema</a>
2476       *    <li><a class="doclink" href="https://swagger.io/specification/v2#itemsObject">Items</a>
2477       *    <li><a class="doclink" href="https://swagger.io/specification/v2#headerObject">Header</a>
2478       * </ul>
2479       *
2480       * @param value
2481       *    The new value for this property.
2482       *    <br>Ignored if value is <jk>null</jk>.
2483       * @return This object.
2484       */
2485      public Builder uniqueItems(Boolean value) {
2486         uniqueItems = resolve(value, uniqueItems);
2487         return this;
2488      }
2489
2490      /**
2491       * <mk>uniqueItems</mk> field.
2492       *
2493       * <p>
2494       * Same as {@link #uniqueItems(Boolean)} but takes in a string boolean.
2495       *
2496       * @param value
2497       *    The new value for this property.
2498       *    <br>Ignored if value is <jk>null</jk> or empty..
2499       * @return This object.
2500       */
2501      public Builder uniqueItems(String value) {
2502         uniqueItems = resolve(value, uniqueItems);
2503         return this;
2504      }
2505
2506      private static Long firstNmo(Long...l) {
2507         for (var ll : l)
2508            if (nn(ll) && ll != -1)
2509               return ll;
2510         return null;
2511      }
2512
2513      /**
2514       * Helper method to safely get annotation attribute values via reflection.
2515       *
2516       * <p>
2517       * This allows reading Jakarta Validation annotations without requiring them on the classpath.
2518       *
2519       * @param <T> The expected return type.
2520       * @param a The annotation to read from.
2521       * @param attributeName The attribute name to read.
2522       * @param type The expected type of the attribute value.
2523       * @return The attribute value, or <jk>null</jk> if not found or not of the expected type.
2524       */
2525      private static <T> T getAnnotationValue(Annotation a, String attributeName, Class<T> type) {
2526         try {
2527            Method m = a.annotationType().getDeclaredMethod(attributeName);
2528            Object value = m.invoke(a);
2529            return type.isInstance(value) ? type.cast(value) : null;
2530         } catch (@SuppressWarnings("unused") Exception e) {
2531            return null;
2532         }
2533      }
2534
2535      private static String joinnlOrNull(String[]...s) {
2536         for (var ss : s)
2537            if (ss.length > 0)
2538               return StringUtils.joinnl(ss);
2539         return null;
2540      }
2541
2542      private static Boolean resolve(Boolean newValue, Boolean oldValue) {
2543         return newValue == null ? oldValue : newValue;
2544      }
2545
2546      private static Long resolve(Long newValue, Long oldValue) {
2547         return (newValue == null || newValue == -1) ? oldValue : newValue;
2548      }
2549
2550      private static Boolean resolve(String newValue, Boolean oldValue) {
2551         return isEmpty(newValue) ? oldValue : b(newValue);
2552      }
2553
2554      private static Long resolve(String newValue, Long oldValue) {
2555         return isEmpty(newValue) ? oldValue : Long.parseLong(newValue);
2556      }
2557
2558      private static Number toNumber(String...s) {
2559         return HttpPartSchema.toNumber(s);
2560      }
2561
2562      private static Set<String> toSet(String[]...s) {
2563         return HttpPartSchema.toSet(s);
2564      }
2565
2566      Builder apply(Class<? extends Annotation> c, Type t) {
2567         if (t instanceof Class<?> c2) {
2568            rstream(AP.find(c, info(c2))).forEach(x -> apply(x.inner()));
2569         } else if (Holder.isType(t)) {
2570            apply(c, Holder.getParameterType(t));
2571         }
2572         return this;
2573      }
2574
2575      Builder apply(Class<? extends Annotation> c, Method m) {
2576         apply(c, m.getGenericReturnType());
2577         Annotation a = m.getAnnotation(c);
2578         if (nn(a))
2579            return apply(a);
2580         return this;
2581      }
2582
2583      Builder apply(Class<? extends Annotation> c, ParameterInfo mpi) {
2584         apply(c, mpi.getParameterType().innerType());
2585         mpi.getAnnotations(c).forEach(x -> apply(x.inner()));
2586         return this;
2587      }
2588
2589      Builder apply(HttpPartMarshalling a) {
2590         parser(a.parser());
2591         serializer(a.serializer());
2592         return this;
2593      }
2594
2595      private Builder applyHttpAnnotation(Annotation a) {
2596         var annotationName = a.annotationType().getSimpleName();
2597
2598         var schema = getAnnotationValue(a, "schema", Schema.class);
2599         if (schema != null && (eq(annotationName, "Response") || !SchemaAnnotation.empty(schema)))
2600            apply(schema);
2601
2602         var aName = getAnnotationString(a, "name");
2603         var aValue = getAnnotationString(a, "value");
2604
2605         switch (annotationName) {
2606            case "PathRemainder" -> {
2607               name("/*");
2608               default_(getAnnotationString(a, "def"));
2609               allowEmptyValue();
2610               required(false);
2611            }
2612            case "Path" -> {
2613               name(firstNonEmpty(aName, aValue));
2614               var def = getAnnotationString(a, "def");
2615               if (def != null && neq(NONE, def))
2616                  default_ = def;
2617               if (startsWith(name, '/')) {
2618                  allowEmptyValue();
2619                  required(false);
2620               } else if (required == null) {
2621                  required(def == null || eq(NONE, def));
2622               }
2623            }
2624            case "Response" -> {
2625               allowEmptyValue(true);
2626               required(false);
2627            }
2628            case "StatusCode" -> codes(getAnnotationIntArray(a, "value"));
2629            default -> {
2630               name(firstNonEmpty(aName, aValue));
2631               default_(getAnnotationString(a, "def"));
2632            }
2633         }
2634         return this;
2635      }
2636
2637      private static String getAnnotationString(Annotation a, String methodName) {
2638         try {
2639            var m = a.annotationType().getMethod(methodName);
2640            if (m.getReturnType() == String.class)
2641               return (String) m.invoke(a);
2642         } catch (@SuppressWarnings("unused") NoSuchMethodException e) {
2643            // Attribute doesn't exist on this annotation -- ignore
2644         } catch (Exception e) {
2645            throw rex(e);
2646         }
2647         return null;
2648      }
2649
2650      private static int[] getAnnotationIntArray(Annotation a, String methodName) {
2651         try {
2652            var m = a.annotationType().getMethod(methodName);
2653            if (m.getReturnType() == int[].class) {
2654               var arr = (int[]) m.invoke(a);
2655               return arr == null ? new int[0] : arr;
2656            }
2657         } catch (@SuppressWarnings("unused") NoSuchMethodException e) {
2658            // Attribute doesn't exist on this annotation -- ignore
2659         } catch (Exception e) {
2660            throw rex(e);
2661         }
2662         return new int[0];
2663      }
2664
2665      Builder apply(Items a) {
2666         default_(joinnlOrNull(a.default_(), a.df()));
2667         enum_(toSet(a.enum_(), a.e()));
2668         collectionFormat(firstNonEmpty(a.collectionFormat(), a.cf()));
2669         exclusiveMaximum(a.exclusiveMaximum() || a.emax());
2670         exclusiveMinimum(a.exclusiveMinimum() || a.emin());
2671         format(firstNonEmpty(a.format(), a.f()));
2672         items(a.items());
2673         maximum(toNumber(a.maximum(), a.max()));
2674         maxItems(firstNmo(a.maxItems(), a.maxi()));
2675         maxLength(firstNmo(a.maxLength(), a.maxl()));
2676         minimum(toNumber(a.minimum(), a.min()));
2677         minItems(firstNmo(a.minItems(), a.mini()));
2678         minLength(firstNmo(a.minLength(), a.minl()));
2679         multipleOf(toNumber(a.multipleOf(), a.mo()));
2680         pattern(firstNonEmpty(a.pattern(), a.p()));
2681         type(firstNonEmpty(a.type(), a.t()));
2682         uniqueItems(a.uniqueItems() || a.ui());
2683         return this;
2684      }
2685
2686      // -----------------------------------------------------------------------------------------------------------------
2687      // JSON Schema Draft 2020-12 property setters
2688      // -----------------------------------------------------------------------------------------------------------------
2689
2690      // -----------------------------------------------------------------------------------------------------------------
2691      // Other
2692      // -----------------------------------------------------------------------------------------------------------------
2693
2694      Builder apply(Schema a) {
2695         default_(joinnlOrNull(a.default_(), a.df()));
2696         enum_(toSet(a.enum_(), a.e()));
2697         allowEmptyValue(a.allowEmptyValue() || a.aev());
2698         collectionFormat(firstNonEmpty(a.collectionFormat(), a.cf()));
2699
2700         // Handle exclusiveMaximum with fallback from Draft 2020-12 to Draft 04
2701         String exMaxVal = a.exclusiveMaximumValue();
2702         if (ine(exMaxVal)) {
2703            exclusiveMaximumValue(toNumber(exMaxVal));
2704         } else if (a.emax()) {
2705            exclusiveMaximum(true);
2706         }
2707
2708         // Handle exclusiveMinimum with fallback from Draft 2020-12 to Draft 04
2709         String exMinVal = a.exclusiveMinimumValue();
2710         if (ine(exMinVal)) {
2711            exclusiveMinimumValue(toNumber(exMinVal));
2712         } else if (a.emin()) {
2713            exclusiveMinimum(true);
2714         }
2715
2716         format(firstNonEmpty(a.format(), a.f()));
2717         items(a.items());
2718         maximum(toNumber(a.maximum(), a.max()));
2719         maxItems(firstNmo(a.maxItems(), a.maxi()));
2720         maxLength(firstNmo(a.maxLength(), a.maxl()));
2721         maxProperties(firstNmo(a.maxProperties(), a.maxp()));
2722         minimum(toNumber(a.minimum(), a.min()));
2723         minItems(firstNmo(a.minItems(), a.mini()));
2724         minLength(firstNmo(a.minLength(), a.minl()));
2725         minProperties(firstNmo(a.minProperties(), a.minp()));
2726         multipleOf(toNumber(a.multipleOf(), a.mo()));
2727         pattern(firstNonEmpty(a.pattern(), a.p()));
2728         required(a.required() || a.r());
2729         skipIfEmpty(a.skipIfEmpty() || a.sie());
2730         type(firstNonEmpty(a.type(), a.t()));
2731         uniqueItems(a.uniqueItems() || a.ui());
2732
2733         // JSON Schema Draft 2020-12 properties
2734         const_(joinnlOrNull(a.const_()));
2735         examples(a.examples());
2736         deprecated(a.deprecated_());
2737
2738         return this;
2739      }
2740
2741      Builder apply(SubItems a) {
2742         default_(joinnlOrNull(a.default_(), a.df()));
2743         enum_(toSet(a.enum_(), a.e()));
2744         collectionFormat(firstNonEmpty(a.collectionFormat(), a.cf()));
2745         exclusiveMaximum(a.exclusiveMaximum() || a.emax());
2746         exclusiveMinimum(a.exclusiveMinimum() || a.emin());
2747         format(firstNonEmpty(a.format(), a.f()));
2748         maximum(toNumber(a.maximum(), a.max()));
2749         maxItems(firstNmo(a.maxItems(), a.maxi()));
2750         maxLength(firstNmo(a.maxLength(), a.maxl()));
2751         minimum(toNumber(a.minimum(), a.min()));
2752         minItems(firstNmo(a.minItems(), a.mini()));
2753         minLength(firstNmo(a.minLength(), a.minl()));
2754         multipleOf(toNumber(a.multipleOf(), a.mo()));
2755         pattern(firstNonEmpty(a.pattern(), a.p()));
2756         type(firstNonEmpty(a.type(), a.t()));
2757         uniqueItems(a.uniqueItems() || a.ui());
2758         return this;
2759      }
2760
2761      public Builder applyAll(Class<? extends Annotation> c, Type t) {
2762         return apply(Schema.class, t).apply(c, t);
2763      }
2764
2765      public Builder applyAll(Class<? extends Annotation> c, Method m) {
2766         return apply(Schema.class, m).apply(c, m);
2767      }
2768
2769      public Builder applyAll(Class<? extends Annotation> c, ParameterInfo mpi) {
2770         return apply(Schema.class, mpi).apply(c, mpi);
2771      }
2772
2773      /**
2774       * Apply Jakarta Bean Validation constraints to this schema.
2775       *
2776       * <p>
2777       * This method uses pure reflection to read constraint annotations, so no jakarta.validation-api
2778       * dependency is required. The constraints are mapped to OpenAPI schema properties where applicable.
2779       *
2780       * <p>
2781       * Supported constraints:
2782       * <ul>
2783       *    <li><c>@NotNull</c> → <c>required(true)</c>
2784       *    <li><c>@Size</c> → <c>minLength/maxLength</c> or <c>minItems/maxItems</c>
2785       *    <li><c>@Min</c> → <c>minimum</c>
2786       *    <li><c>@Max</c> → <c>maximum</c>
2787       *    <li><c>@DecimalMin</c> → <c>minimum + exclusiveMinimum</c>
2788       *    <li><c>@DecimalMax</c> → <c>maximum + exclusiveMaximum</c>
2789       *    <li><c>@Pattern</c> → <c>pattern</c>
2790       *    <li><c>@Email</c> → <c>format("email")</c>
2791       *    <li><c>@Positive</c> → <c>minimum(0) + exclusiveMinimum(true)</c>
2792       *    <li><c>@PositiveOrZero</c> → <c>minimum(0)</c>
2793       *    <li><c>@Negative</c> → <c>maximum(0) + exclusiveMaximum(true)</c>
2794       *    <li><c>@NegativeOrZero</c> → <c>maximum(0)</c>
2795       *    <li><c>@NotEmpty</c> → <c>required(true) + minLength(1)/minItems(1)</c>
2796       *    <li><c>@NotBlank</c> → <c>required(true) + minLength(1) + pattern</c>
2797       * </ul>
2798       *
2799       * @param a The Jakarta Validation constraint annotation.
2800       * @return This object.
2801       * @since 9.2.0
2802       */
2803      Builder applyJakartaValidation(Annotation a) {
2804         String simpleName = cns(a.annotationType());
2805
2806         try {
2807            switch (simpleName) {
2808               case "NotNull":
2809                  required(true);
2810                  break;
2811               case "Size":
2812                  Integer min = getAnnotationValue(a, "min", Integer.class);
2813                  Integer max = getAnnotationValue(a, "max", Integer.class);
2814                  if (nn(min) && min > 0) {
2815                     minLength(min.longValue());
2816                     minItems(min.longValue());
2817                  }
2818                  if (nn(max) && max < Integer.MAX_VALUE) {
2819                     maxLength(max.longValue());
2820                     maxItems(max.longValue());
2821                  }
2822                  break;
2823               case "Min":
2824                  Long minValue = getAnnotationValue(a, "value", Long.class);
2825                  if (nn(minValue))
2826                     minimum(minValue);
2827                  break;
2828               case "Max":
2829                  Long maxValue = getAnnotationValue(a, "value", Long.class);
2830                  if (nn(maxValue))
2831                     maximum(maxValue);
2832                  break;
2833               case "Pattern":
2834                  String regexp = getAnnotationValue(a, "regexp", String.class);
2835                  if (nn(regexp))
2836                     pattern(regexp);
2837                  break;
2838               case "Email":
2839                  format("email");
2840                  break;
2841               case "Positive":
2842                  minimum(0);
2843                  exclusiveMinimum(true);
2844                  break;
2845               case "PositiveOrZero":
2846                  minimum(0);
2847                  break;
2848               case "Negative":
2849                  maximum(0);
2850                  exclusiveMaximum(true);
2851                  break;
2852               case "NegativeOrZero":
2853                  maximum(0);
2854                  break;
2855               case "NotEmpty":
2856                  required(true);
2857                  minLength(1L);
2858                  minItems(1L);
2859                  break;
2860               case "NotBlank":
2861                  required(true);
2862                  minLength(1L);
2863                  pattern(".*\\S.*"); // Contains at least one non-whitespace character
2864                  break;
2865               case "DecimalMin":
2866                  String minVal = getAnnotationValue(a, "value", String.class);
2867                  Boolean minInclusive = getAnnotationValue(a, "inclusive", Boolean.class);
2868                  if (nn(minVal)) {
2869                     minimum(toNumber(minVal));
2870                     if (isFalse(minInclusive))
2871                        exclusiveMinimum(true);
2872                  }
2873                  break;
2874               case "DecimalMax":
2875                  String maxVal = getAnnotationValue(a, "value", String.class);
2876                  Boolean maxInclusive = getAnnotationValue(a, "inclusive", Boolean.class);
2877                  if (nn(maxVal)) {
2878                     maximum(toNumber(maxVal));
2879                     if (isFalse(maxInclusive))
2880                        exclusiveMaximum(true);
2881                  }
2882                  break;
2883               default:
2884                  // Silently ignore other validation annotations we don't support yet
2885            }
2886         } catch (@SuppressWarnings("unused") Exception e) {
2887            // If reflection fails, just skip this annotation - it's optional
2888         }
2889         return this;
2890      }
2891
2892      Builder items(Items value) {
2893         if (! ItemsAnnotation.empty(value))
2894            items = HttpPartSchema.create().apply(value);
2895         return this;
2896      }
2897
2898      Builder items(SubItems value) {
2899         if (! SubItemsAnnotation.empty(value))
2900            items = HttpPartSchema.create().apply(value);
2901         return this;
2902      }
2903   }
2904
2905   /** Reusable instance of this object, all default settings. */
2906   public static final HttpPartSchema DEFAULT = HttpPartSchema.create().allowEmptyValue(true).build();
2907
2908   /** Boolean type */
2909   public static final HttpPartSchema T_BOOLEAN = HttpPartSchema.tBoolean().build();
2910
2911   /** File type */
2912   public static final HttpPartSchema T_FILE = HttpPartSchema.tFile().build();
2913
2914   /** Integer type */
2915   public static final HttpPartSchema T_INTEGER = HttpPartSchema.tInteger().build();
2916
2917   /** Int32 type */
2918   public static final HttpPartSchema T_INT32 = HttpPartSchema.tInt32().build();
2919
2920   /** Int64 type */
2921   public static final HttpPartSchema T_INT64 = HttpPartSchema.tInt64().build();
2922
2923   /** No type */
2924   public static final HttpPartSchema T_NONE = HttpPartSchema.tNone().build();
2925
2926   /** Number type */
2927   public static final HttpPartSchema T_NUMBER = HttpPartSchema.tNumber().build();
2928
2929   /** Float type */
2930   public static final HttpPartSchema T_FLOAT = HttpPartSchema.tFloat().build();
2931
2932   /** Double type */
2933   public static final HttpPartSchema T_DOUBLE = HttpPartSchema.tDouble().build();
2934
2935   /** String type */
2936   public static final HttpPartSchema T_STRING = HttpPartSchema.tString().build();
2937
2938   /** Byte type */
2939   public static final HttpPartSchema T_BYTE = HttpPartSchema.tByte().build();
2940
2941   /** Binary type */
2942   public static final HttpPartSchema T_BINARY = HttpPartSchema.tBinary().build();
2943
2944   /** Spaced binary type */
2945   public static final HttpPartSchema T_BINARY_SPACED = HttpPartSchema.tBinarySpaced().build();
2946
2947   /** Date type */
2948   public static final HttpPartSchema T_DATE = HttpPartSchema.tDate().build();
2949
2950   /** Date-time type */
2951   public static final HttpPartSchema T_DATETIME = HttpPartSchema.tDateTime().build();
2952
2953   /** UON-formated simple type */
2954   public static final HttpPartSchema T_UON = HttpPartSchema.tUon().build();
2955
2956   /** Array type */
2957   public static final HttpPartSchema T_ARRAY = HttpPartSchema.tArray().build();
2958
2959   /** Comma-delimited array type */
2960   public static final HttpPartSchema T_ARRAY_CSV = HttpPartSchema.tArrayCsv().build();
2961
2962   /** Pipe-delimited array type */
2963   public static final HttpPartSchema T_ARRAY_PIPES = HttpPartSchema.tArrayPipes().build();
2964
2965   /** Space-delimited array type */
2966   public static final HttpPartSchema T_ARRAY_SSV = HttpPartSchema.tArraySsv().build();
2967
2968   /** Tab-delimited array type */
2969   public static final HttpPartSchema T_ARRAY_TSV = HttpPartSchema.tArrayTsv().build();
2970
2971   /** UON-formatted array type */
2972   public static final HttpPartSchema T_ARRAY_UON = HttpPartSchema.tArrayUon().build();
2973
2974   /** Multi-part array type */
2975   public static final HttpPartSchema T_ARRAY_MULTI = HttpPartSchema.tArrayMulti().build();
2976
2977   /** Object type */
2978   public static final HttpPartSchema T_OBJECT = HttpPartSchema.tObject().build();
2979
2980   /** UON-formated object type */
2981   public static final HttpPartSchema T_OBJECT_UON = HttpPartSchema.tObjectUon().build();
2982
2983   /**
2984    * Instantiates a new builder for this object.
2985    *
2986    * @return A new builder for this object.
2987    */
2988   public static Builder create() {
2989      return new Builder();
2990   }
2991
2992   /**
2993    * Finds the schema information on the specified annotation.
2994    *
2995    * @param a
2996    *    The annotation to find the schema information on..
2997    *    <br>Must not be <jk>null</jk>.
2998    * @return The schema information found on the annotation.  Never <jk>null</jk>.
2999    */
3000   public static HttpPartSchema create(Annotation a) {
3001      return create().apply(a).build();
3002   }
3003
3004   /**
3005    * Finds the schema information on the specified annotation.
3006    *
3007    * @param a
3008    *    The annotation to find the schema information on..
3009    *    <br>Must not be <jk>null</jk>.
3010    * @param defaultName The default part name if not specified on the annotation.  Can be <jk>null</jk> (the name from the annotation, if any, is left unchanged).
3011    * @return The schema information found on the annotation.  Never <jk>null</jk>.
3012    */
3013   public static HttpPartSchema create(Annotation a, String defaultName) {
3014      return create().name(defaultName).apply(a).build();
3015   }
3016
3017   /**
3018    * Finds the schema information for the specified class.
3019    *
3020    * <p>
3021    * This method will gather all the schema information from the annotations on the class and all parent classes/interfaces.
3022    *
3023    * @param c
3024    *    The annotation to look for.
3025    *    <br>Valid values:
3026    *    <ul>
3027    *       <li><ja>@Content</ja>
3028    *       <li><ja>@Header</ja>
3029    *       <li><ja>@Query</ja>
3030    *       <li><ja>@FormData</ja>
3031    *       <li><ja>@Path</ja>
3032    *       <li><ja>@Response</ja>
3033    *       <li><ja>@HasQuery</ja>
3034    *       <li><ja>@HasFormData</ja>
3035    *    </ul>
3036    * @param t
3037    *    The class containing the parameter.
3038    *    <br>Can be <jk>null</jk> (no type-level annotations are applied).
3039    * @return The schema information about the parameter.  Never <jk>null</jk>.
3040    */
3041   public static HttpPartSchema create(Class<? extends Annotation> c, Type t) {
3042      return create().applyAll(c, t).build();
3043   }
3044
3045   /**
3046    * Finds the schema information for the specified method return.
3047    *
3048    * <p>
3049    * This method will gather all the schema information from the annotations at the following locations:
3050    * <ul>
3051    *    <li>The method.
3052    *    <li>The method return class.
3053    *    <li>The method return parent classes and interfaces.
3054    * </ul>
3055    *
3056    * @param c
3057    *    The annotation to look for.
3058    *    <br>Valid values:
3059    *    <ul>
3060    *       <li><ja>@Content</ja>
3061    *       <li><ja>@Header</ja>
3062    *       <li><ja>@Query</ja>
3063    *       <li><ja>@FormData</ja>
3064    *       <li><ja>@Path</ja>
3065    *       <li><ja>@Response</ja>
3066    *       <li><ja>@HasQuery</ja>
3067    *       <li><ja>@HasFormData</ja>
3068    *    </ul>
3069    * @param m
3070    *    The Java method with the return type being checked.
3071    *    <br>Must not be <jk>null</jk>.
3072    * @return The schema information about the parameter.  Never <jk>null</jk>.
3073    */
3074   public static HttpPartSchema create(Class<? extends Annotation> c, Method m) {
3075      return create().applyAll(c, m).build();
3076   }
3077
3078   /**
3079    * Finds the schema information for the specified method parameter.
3080    *
3081    * <p>
3082    * This method will gather all the schema information from the annotations at the following locations:
3083    * <ul>
3084    *    <li>The method parameter.
3085    *    <li>The method parameter class.
3086    *    <li>The method parameter parent classes and interfaces.
3087    * </ul>
3088    *
3089    * @param c
3090    *    The annotation to look for.
3091    *    <br>Valid values:
3092    *    <ul>
3093    *       <li><ja>@Content</ja>
3094    *       <li><ja>@Header</ja>
3095    *       <li><ja>@Query</ja>
3096    *       <li><ja>@FormData</ja>
3097    *       <li><ja>@Path</ja>
3098    *       <li><ja>@Response</ja>
3099    *       <li><ja>@HasQuery</ja>
3100    *       <li><ja>@HasFormData</ja>
3101    *    </ul>
3102    * @param mpi The Java method parameter.  Must not be <jk>null</jk>.
3103    * @return The schema information about the parameter.  Never <jk>null</jk>.
3104    */
3105   public static HttpPartSchema create(Class<? extends Annotation> c, ParameterInfo mpi) {
3106      return create().applyAll(c, mpi).build();
3107   }
3108
3109   /**
3110    * Shortcut for calling <c>create().type(type);</c>
3111    *
3112    * @param type The schema type value.
3113    * @return A new builder.
3114    */
3115   public static Builder create(String type) {
3116      return create().type(type);
3117   }
3118
3119   /**
3120    * Shortcut for calling <c>create().type(type).format(format);</c>
3121    *
3122    * @param type The schema type value.
3123    * @param format The schema format value.
3124    * @return A new builder.
3125    */
3126   public static Builder create(String type, String format) {
3127      return create().type(type).format(format);
3128   }
3129
3130   /**
3131    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>)</c>.
3132    *
3133    * @return A new builder for this object.
3134    */
3135   public static Builder tArray() {
3136      return create().tArray();
3137   }
3138
3139   /**
3140    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).items(items)</c>.
3141    *
3142    * @param items The schema of the array items.
3143    * @return A new builder for this object.
3144    */
3145   public static Builder tArray(Builder items) {
3146      return create().tArray().items(items);
3147   }
3148
3149   /**
3150    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>CSV</jsf>)</c>.
3151    *
3152    * @return A new builder for this object.
3153    */
3154   public static Builder tArrayCsv() {
3155      return create().tArray().cfCsv();
3156   }
3157
3158   /**
3159    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>CSV</jsf>).items(items)</c>.
3160    *
3161    * @param items The schema of the array items.
3162    * @return A new builder for this object.
3163    */
3164   public static Builder tArrayCsv(Builder items) {
3165      return create().tArray().cfCsv().items(items);
3166   }
3167
3168   /**
3169    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>MULTI</jsf>)</c>.
3170    *
3171    * @return A new builder for this object.
3172    */
3173   public static Builder tArrayMulti() {
3174      return create().tArray().cfMulti();
3175   }
3176
3177   /**
3178    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>MULTI</jsf>).items(items)</c>.
3179    *
3180    * @param items The schema of the array items.
3181    * @return A new builder for this object.
3182    */
3183   public static Builder tArrayMulti(Builder items) {
3184      return create().tArray().cfMulti().items(items);
3185   }
3186
3187   /**
3188    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>PIPES</jsf>)</c>.
3189    *
3190    * @return A new builder for this object.
3191    */
3192   public static Builder tArrayPipes() {
3193      return create().tArray().cfPipes();
3194   }
3195
3196   /**
3197    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>PIPES</jsf>).items(items)</c>.
3198    *
3199    * @param items The schema of the array items.
3200    * @return A new builder for this object.
3201    */
3202   public static Builder tArrayPipes(Builder items) {
3203      return create().tArray().cfPipes().items(items);
3204   }
3205
3206   /**
3207    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>SSV</jsf>)</c>.
3208    *
3209    * @return A new builder for this object.
3210    */
3211   public static Builder tArraySsv() {
3212      return create().tArray().cfSsv();
3213   }
3214
3215   /**
3216    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>SSV</jsf>).items(items)</c>.
3217    *
3218    * @param items The schema of the array items.
3219    * @return A new builder for this object.
3220    */
3221   public static Builder tArraySsv(Builder items) {
3222      return create().tArray().cfSsv().items(items);
3223   }
3224
3225   /**
3226    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>TSV</jsf>)</c>.
3227    *
3228    * @return A new builder for this object.
3229    */
3230   public static Builder tArrayTsv() {
3231      return create().tArray().cfTsv();
3232   }
3233
3234   /**
3235    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>TSV</jsf>).items(items)</c>.
3236    *
3237    * @param items The schema of the array items.
3238    * @return A new builder for this object.
3239    */
3240   public static Builder tArrayTsv(Builder items) {
3241      return create().tArray().cfTsv().items(items);
3242   }
3243
3244   /**
3245    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>UONC</jsf>)</c>.
3246    *
3247    * @return A new builder for this object.
3248    */
3249   public static Builder tArrayUon() {
3250      return create().tArray().cfUon();
3251   }
3252
3253   /**
3254    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>UONC</jsf>).items(items)</c>.
3255    *
3256    * @param items The schema of the array items.
3257    * @return A new builder for this object.
3258    */
3259   public static Builder tArrayUon(Builder items) {
3260      return create().tArray().cfUon().items(items);
3261   }
3262
3263   /**
3264    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>STRING</jsf>).format(HttpPartFormat.<jsf>BINARY</jsf>)</c>.
3265    *
3266    * @return A new builder for this object.
3267    */
3268   public static Builder tBinary() {
3269      return create().tString().fBinary();
3270   }
3271
3272   /**
3273    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>STRING</jsf>).format(HttpPartFormat.<jsf>BINARY_SPACED</jsf>)</c>.
3274    *
3275    * @return A new builder for this object.
3276    */
3277   public static Builder tBinarySpaced() {
3278      return create().tString().fBinarySpaced();
3279   }
3280
3281   /**
3282    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>BOOLEAN</jsf>)</c>.
3283    *
3284    * @return A new builder for this object.
3285    */
3286   public static Builder tBoolean() {
3287      return create().tBoolean();
3288   }
3289
3290   /**
3291    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>STRING</jsf>).format(HttpPartFormat.<jsf>BYTE</jsf>)</c>.
3292    *
3293    * @return A new builder for this object.
3294    */
3295   public static Builder tByte() {
3296      return create().tString().fByte();
3297   }
3298
3299   /**
3300    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>STRING</jsf>).format(HttpPartFormat.<jsf>DATE</jsf>)</c>.
3301    *
3302    * @return A new builder for this object.
3303    */
3304   public static Builder tDate() {
3305      return create().tString().fDate();
3306   }
3307
3308   /**
3309    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>STRING</jsf>).format(HttpPartFormat.<jsf>DATE_TIME</jsf>)</c>.
3310    *
3311    * @return A new builder for this object.
3312    */
3313   public static Builder tDateTime() {
3314      return create().tString().fDateTime();
3315   }
3316
3317   /**
3318    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>NUMBER</jsf>).format(HttpPartFormat.<jsf>DOUBLE</jsf>)</c>.
3319    *
3320    * @return A new builder for this object.
3321    */
3322   public static Builder tDouble() {
3323      return create().tNumber().fDouble();
3324   }
3325
3326   /**
3327    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>FILE</jsf>)</c>.
3328    *
3329    * @return A new builder for this object.
3330    */
3331   public static Builder tFile() {
3332      return create().tFile();
3333   }
3334
3335   /**
3336    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>NUMBER</jsf>).format(HttpPartFormat.<jsf>FLOAT</jsf>)</c>.
3337    *
3338    * @return A new builder for this object.
3339    */
3340   public static Builder tFloat() {
3341      return create().tNumber().fFloat();
3342   }
3343
3344   /**
3345    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>INTEGER</jsf>).format(HttpPartFormat.<jsf>INT32</jsf>)</c>.
3346    *
3347    * @return A new builder for this object.
3348    */
3349   public static Builder tInt32() {
3350      return create().tInteger().fInt32();
3351   }
3352
3353   /**
3354    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>INTEGER</jsf>).format(HttpPartFormat.<jsf>INT64</jsf>)</c>.
3355    *
3356    * @return A new builder for this object.
3357    */
3358   public static Builder tInt64() {
3359      return create().tInteger().fInt64();
3360   }
3361
3362   /**
3363    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>INTEGER</jsf>)</c>.
3364    *
3365    * @return A new builder for this object.
3366    */
3367   public static Builder tInteger() {
3368      return create().tInteger();
3369   }
3370
3371   /**
3372    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>NONE</jsf>)</c>.
3373    *
3374    * @return A new builder for this object.
3375    */
3376   public static Builder tNone() {
3377      return create().tNone();
3378   }
3379
3380   /**
3381    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>NUMBER</jsf>)</c>.
3382    *
3383    * @return A new builder for this object.
3384    */
3385   public static Builder tNumber() {
3386      return create().tNumber();
3387   }
3388
3389   /**
3390    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>OBJECT</jsf>)</c>.
3391    *
3392    * @return A new builder for this object.
3393    */
3394   public static Builder tObject() {
3395      return create().tObject();
3396   }
3397
3398   /**
3399    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>OBJECT</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>UON</jsf>)</c>.
3400    *
3401    * @return A new builder for this object.
3402    */
3403   public static Builder tObjectUon() {
3404      return create().tObject().cfUon();
3405   }
3406
3407   /**
3408    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>STRING</jsf>)</c>.
3409    *
3410    * @return A new builder for this object.
3411    */
3412   public static Builder tString() {
3413      return create().tString();
3414   }
3415
3416   /**
3417    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>STRING</jsf>).format(HttpPartFormat.<jsf>UON</jsf>)</c>.
3418    *
3419    * @return A new builder for this object.
3420    */
3421   public static Builder tUon() {
3422      return create().tString().fUon();
3423   }
3424
3425   private static Map<String,HttpPartSchema> build(Map<String,Object> in, boolean noValidate) {
3426      if (in == null)
3427         return null;
3428      Map<String,HttpPartSchema> m = map();
3429      in.forEach((k, v) -> m.put(k, build(v, noValidate)));
3430      return u(m);
3431   }
3432
3433   private static HttpPartSchema build(Object in, boolean noValidate) {
3434      if (in == null)
3435         return null;
3436      if (in instanceof HttpPartSchema in2)
3437         return in2;
3438      return ((Builder)in).noValidate(noValidate).build();
3439   }
3440
3441   private static <T> Set<T> copy(Set<T> in) {
3442      return in == null ? emptySet() : u(cp(in));
3443   }
3444
3445   static final Number toNumber(String...s) {
3446      try {
3447         for (var ss : s)
3448            if (ine(ss))
3449               return parseNumber(ss, Number.class);
3450         return null;
3451      } catch (ParseException e) {
3452         throw toRex(e);
3453      }
3454   }
3455
3456   static final Set<String> toSet(String s) {
3457      if (isEmpty(s))
3458         return null;
3459      Set<String> set = set();
3460      StringUtils.split(s, set::add);
3461      return set;
3462   }
3463
3464   static final Set<String> toSet(String[]...s) {
3465      var isNotEmpty = false;
3466      for (var ss : s)
3467         isNotEmpty |= ss.length > 0;
3468      if (! isNotEmpty)
3469         return null;
3470      Set<String> set = set();
3471      for (var ss : s)
3472         if (nn(ss))
3473            for (var ss2 : ss)
3474               StringUtils.split(ss2, set::add);
3475      return set.isEmpty() ? null : set;
3476   }
3477
3478   final String name;
3479   final String default_;
3480   final Set<String> enum_;
3481   final Map<String,HttpPartSchema> properties;
3482   final boolean allowEmptyValue;
3483   final boolean exclusiveMaximum;
3484   final boolean exclusiveMinimum;
3485   final boolean required;
3486   final boolean uniqueItems;
3487   final boolean skipIfEmpty;
3488   final HttpPartCollectionFormat collectionFormat;
3489   final HttpPartDataType type;
3490   final HttpPartFormat format;
3491   final Pattern pattern;
3492   final HttpPartSchema items;
3493   final HttpPartSchema additionalProperties;
3494   final Number maximum;
3495   final Number minimum;
3496   final Number multipleOf;
3497   final Long maxLength;
3498   final Long minLength;
3499   final Long maxItems;
3500   final Long minItems;
3501   final Long maxProperties;
3502   final Long minProperties;
3503
3504   final Class<?> parser;
3505
3506   final Class<?> serializer;
3507
3508   final Class<?> parsedType;
3509
3510   // JSON Schema Draft 2020-12 fields
3511   final String const_;
3512
3513   final String[] examples;
3514
3515   final boolean deprecated;
3516
3517   final Number exclusiveMaximumValue;
3518   final Number exclusiveMinimumValue;
3519
3520   HttpPartSchema(Builder b) {
3521      name = b.name;
3522      default_ = b.default_;
3523      enum_ = copy(b.enum_);
3524      properties = build(b.properties, b.noValidate);
3525      allowEmptyValue = resolve(b.allowEmptyValue);
3526      exclusiveMaximum = resolve(b.exclusiveMaximum);
3527      exclusiveMinimum = resolve(b.exclusiveMinimum);
3528      required = resolve(b.required);
3529      uniqueItems = resolve(b.uniqueItems);
3530      skipIfEmpty = resolve(b.skipIfEmpty);
3531      collectionFormat = b.collectionFormat;
3532      type = b.type;
3533      format = b.format;
3534      pattern = b.pattern;
3535      items = build(b.items, b.noValidate);
3536      additionalProperties = build(b.additionalProperties, b.noValidate);
3537      maximum = b.maximum;
3538      minimum = b.minimum;
3539      multipleOf = b.multipleOf;
3540      maxItems = b.maxItems;
3541      maxLength = b.maxLength;
3542      maxProperties = b.maxProperties;
3543      minItems = b.minItems;
3544      minLength = b.minLength;
3545      minProperties = b.minProperties;
3546      parser = b.parser;
3547      serializer = b.serializer;
3548      // JSON Schema Draft 2020-12 fields
3549      const_ = b.const_;
3550      examples = b.examples;
3551      deprecated = resolve(b.deprecated);
3552      exclusiveMaximumValue = b.exclusiveMaximumValue;
3553      exclusiveMinimumValue = b.exclusiveMinimumValue;
3554
3555      // Calculate parse type
3556      Class<?> parsedType2 = Object.class;
3557      if (type == ARRAY) {
3558         if (nn(items) && nn(items.parsedType))
3559            parsedType2 = Array.newInstance(items.parsedType, 0).getClass();
3560      } else if (type == BOOLEAN) {
3561         parsedType2 = Boolean.class;
3562      } else if (type == INTEGER) {
3563         if (format == INT64)
3564            parsedType2 = Long.class;
3565         else
3566            parsedType2 = Integer.class;
3567      } else if (type == NUMBER) {
3568         if (format == DOUBLE)
3569            parsedType2 = Double.class;
3570         else
3571            parsedType2 = Float.class;
3572      } else if (type == STRING) {
3573         if (format == BYTE || format == BINARY || format == BINARY_SPACED)
3574            parsedType2 = byte[].class;
3575         else if (format == DATE || format == DATE_TIME)
3576            parsedType2 = Calendar.class;
3577         else
3578            parsedType2 = String.class;
3579      }
3580      this.parsedType = parsedType2;
3581
3582      if (b.noValidate)
3583         return;
3584
3585      // Validation.
3586      var errors = list();
3587      var notAllowed = lb(String.class);
3588      var invalidFormat = false;
3589
3590      // @formatter:off
3591      switch (type) {
3592         case STRING: {
3593            notAllowed
3594               .addIf(nn(properties), "properties")
3595               .addIf(nn(additionalProperties), "additionalProperties")
3596               .addIf(exclusiveMaximum, "exclusiveMaximum")
3597               .addIf(exclusiveMinimum, "exclusiveMinimum")
3598               .addIf(uniqueItems, "uniqueItems")
3599               .addIf(collectionFormat != HttpPartCollectionFormat.NO_COLLECTION_FORMAT, "collectionFormat")
3600               .addIf(nn(items), "items")
3601               .addIf(nn(maximum), "maximum")
3602               .addIf(nn(minimum), "minimum")
3603               .addIf(nn(multipleOf), "multipleOf")
3604               .addIf(nn(maxItems), "maxItems")
3605               .addIf(nn(minItems), "minItems")
3606               .addIf(nn(minProperties), "minProperties");
3607            invalidFormat = ! format.isOneOf(HttpPartFormat.BYTE, HttpPartFormat.BINARY, HttpPartFormat.BINARY_SPACED, HttpPartFormat.DATE, HttpPartFormat.DATE_TIME, HttpPartFormat.PASSWORD, HttpPartFormat.UON, HttpPartFormat.NO_FORMAT);
3608            break;
3609         }
3610         case ARRAY: {
3611            notAllowed.addIf(nn(properties), "properties")
3612               .addIf(nn(additionalProperties), "additionalProperties")
3613               .addIf(exclusiveMaximum, "exclusiveMaximum")
3614               .addIf(exclusiveMinimum, "exclusiveMinimum")
3615               .addIf(nn(pattern), "pattern")
3616               .addIf(nn(maximum), "maximum")
3617               .addIf(nn(minimum), "minimum")
3618               .addIf(nn(multipleOf), "multipleOf")
3619               .addIf(nn(maxLength), "maxLength")
3620               .addIf(nn(minLength), "minLength")
3621               .addIf(nn(maxProperties), "maxProperties")
3622               .addIf(nn(minProperties), "minProperties");
3623            invalidFormat = ! format.isOneOf(HttpPartFormat.NO_FORMAT, HttpPartFormat.UON);
3624            break;
3625         }
3626         case BOOLEAN: {
3627            notAllowed.addIf(! enum_.isEmpty(), "enum")
3628               .addIf(nn(properties), "properties")
3629               .addIf(nn(additionalProperties), "additionalProperties")
3630               .addIf(exclusiveMaximum, "exclusiveMaximum")
3631               .addIf(exclusiveMinimum, "exclusiveMinimum")
3632               .addIf(uniqueItems, "uniqueItems")
3633               .addIf(collectionFormat != HttpPartCollectionFormat.NO_COLLECTION_FORMAT, "collectionFormat")
3634               .addIf(nn(pattern), "pattern")
3635               .addIf(nn(items), "items")
3636               .addIf(nn(maximum), "maximum")
3637               .addIf(nn(minimum), "minimum")
3638               .addIf(nn(multipleOf), "multipleOf")
3639               .addIf(nn(maxItems), "maxItems")
3640               .addIf(nn(maxLength), "maxLength")
3641               .addIf(nn(maxProperties), "maxProperties")
3642               .addIf(nn(minItems), "minItems")
3643               .addIf(nn(minLength), "minLength")
3644               .addIf(nn(minProperties), "minProperties");
3645            invalidFormat = ! format.isOneOf(HttpPartFormat.NO_FORMAT, HttpPartFormat.UON);
3646            break;
3647         }
3648         case FILE: {
3649            break;
3650         }
3651         case INTEGER: {
3652            notAllowed.addIf(nn(properties), "properties")
3653               .addIf(nn(additionalProperties), "additionalProperties")
3654               .addIf(uniqueItems, "uniqueItems")
3655               .addIf(collectionFormat != HttpPartCollectionFormat.NO_COLLECTION_FORMAT, "collectionFormat")
3656               .addIf(nn(pattern), "pattern")
3657               .addIf(nn(items), "items")
3658               .addIf(nn(maxItems), "maxItems")
3659               .addIf(nn(maxLength), "maxLength")
3660               .addIf(nn(maxProperties), "maxProperties")
3661               .addIf(nn(minItems), "minItems")
3662               .addIf(nn(minLength), "minLength")
3663               .addIf(nn(minProperties), "minProperties");
3664            invalidFormat = ! format.isOneOf(HttpPartFormat.NO_FORMAT, HttpPartFormat.UON, HttpPartFormat.INT32, HttpPartFormat.INT64);
3665            break;
3666         }
3667         case NUMBER: {
3668            notAllowed.addIf(nn(properties), "properties")
3669               .addIf(nn(additionalProperties), "additionalProperties")
3670               .addIf(uniqueItems, "uniqueItems")
3671               .addIf(collectionFormat != HttpPartCollectionFormat.NO_COLLECTION_FORMAT, "collectionFormat")
3672               .addIf(nn(pattern), "pattern")
3673               .addIf(nn(items), "items")
3674               .addIf(nn(maxItems), "maxItems")
3675               .addIf(nn(maxLength), "maxLength")
3676               .addIf(nn(maxProperties), "maxProperties")
3677               .addIf(nn(minItems), "minItems")
3678               .addIf(nn(minLength), "minLength")
3679               .addIf(nn(minProperties), "minProperties");
3680            invalidFormat = ! format.isOneOf(HttpPartFormat.NO_FORMAT, HttpPartFormat.UON, HttpPartFormat.FLOAT, HttpPartFormat.DOUBLE);
3681            break;
3682         }
3683         case OBJECT: {
3684            notAllowed.addIf(exclusiveMaximum, "exclusiveMaximum")
3685               .addIf(exclusiveMinimum, "exclusiveMinimum")
3686               .addIf(uniqueItems, "uniqueItems")
3687               .addIf(nn(pattern), "pattern")
3688               .addIf(nn(items), "items")
3689               .addIf(nn(maximum), "maximum")
3690               .addIf(nn(minimum), "minimum")
3691               .addIf(nn(multipleOf), "multipleOf")
3692               .addIf(nn(maxItems), "maxItems")
3693               .addIf(nn(maxLength), "maxLength")
3694               .addIf(nn(minItems), "minItems")
3695               .addIf(nn(minLength), "minLength");
3696            invalidFormat = ! format.isOneOf(HttpPartFormat.NO_FORMAT);
3697            break;
3698         }
3699         default:
3700            break;
3701      }
3702      // @formatter:on
3703
3704      List<String> notAllowed2 = notAllowed.build();
3705      if (! notAllowed2.isEmpty())
3706         errors.add("Attributes not allow for type='" + type + "': " + StringUtils.join(notAllowed2, ","));
3707      if (invalidFormat)
3708         errors.add("Invalid format for type='" + type + "': '" + format + "'");
3709      if (exclusiveMaximum && maximum == null)
3710         errors.add("Cannot specify exclusiveMaximum with maximum.");
3711      if (exclusiveMinimum && minimum == null)
3712         errors.add("Cannot specify exclusiveMinimum with minimum.");
3713      if (required && nn(default_))
3714         errors.add("Cannot specify a default value on a required value.");
3715      if (nn(minLength) && nn(maxLength) && maxLength < minLength)
3716         errors.add("maxLength cannot be less than minLength.");
3717      if (nn(minimum) && nn(maximum) && maximum.doubleValue() < minimum.doubleValue())
3718         errors.add("maximum cannot be less than minimum.");
3719      if (nn(minItems) && nn(maxItems) && maxItems < minItems)
3720         errors.add("maxItems cannot be less than minItems.");
3721      if (nn(minProperties) && nn(maxProperties) && maxProperties < minProperties)
3722         errors.add("maxProperties cannot be less than minProperties.");
3723      if (nn(minLength) && minLength < 0)
3724         errors.add("minLength cannot be less than zero.");
3725      if (nn(maxLength) && maxLength < 0)
3726         errors.add("maxLength cannot be less than zero.");
3727      if (nn(minItems) && minItems < 0)
3728         errors.add("minItems cannot be less than zero.");
3729      if (nn(maxItems) && maxItems < 0)
3730         errors.add("maxItems cannot be less than zero.");
3731      if (nn(minProperties) && minProperties < 0)
3732         errors.add("minProperties cannot be less than zero.");
3733      if (nn(maxProperties) && maxProperties < 0)
3734         errors.add("maxProperties cannot be less than zero.");
3735      if (type == ARRAY && nn(items) && items.getType() == OBJECT && (format != UON && format != HttpPartFormat.NO_FORMAT))
3736         errors.add("Cannot define an array of objects unless array format is 'uon'.");
3737
3738      if (! errors.isEmpty())
3739         throw new BasicRuntimeException("Schema specification errors: \n\t" + StringUtils.join(errors, "\n\t"));
3740   }
3741
3742   /**
3743    * Returns the <c>collectionFormat</c> field of this schema.
3744    *
3745    * @return The <c>collectionFormat</c> field of this schema.  Defaults to {@link HttpPartCollectionFormat#NO_COLLECTION_FORMAT} if not specified.
3746    * @see HttpPartSchema.Builder#collectionFormat(String)
3747    */
3748   public HttpPartCollectionFormat getCollectionFormat() { return collectionFormat; }
3749
3750   /**
3751    * Returns the <c>default</c> field of this schema.
3752    *
3753    * @return The default value for this schema, or <jk>null</jk> if not specified.
3754    * @see HttpPartSchema.Builder#default_(String)
3755    */
3756   public String getDefault() { return default_; }
3757
3758   /**
3759    * Returns the <c>enum</c> field of this schema.
3760    *
3761    * @return The <c>enum</c> field of this schema.  Never <jk>null</jk> - returns an empty set if not specified.
3762    * @see HttpPartSchema.Builder#enum_(Set)
3763    */
3764   public Set<String> getEnum() { return enum_; }
3765
3766   /**
3767    * Returns the <c>format</c> field of this schema.
3768    *
3769    * @see HttpPartSchema.Builder#format(String)
3770    * @return The <c>format</c> field of this schema.  Defaults to {@link HttpPartFormat#NO_FORMAT} if not specified.
3771    */
3772   public HttpPartFormat getFormat() { return format; }
3773
3774   /**
3775    * Returns the <c>format</c> field of this schema.
3776    *
3777    * @param cm
3778    *    The class info of the object.
3779    *    <br>Used to auto-detect the format if the format was not specified.
3780    *    <br>Must not be <jk>null</jk>.
3781    * @return The <c>format</c> field of this schema, or the format auto-detected from the class info, or {@link HttpPartFormat#NO_FORMAT} if not specified and not auto-detectable.
3782    * @see HttpPartSchema.Builder#format(String)
3783    */
3784   public HttpPartFormat getFormat(ClassInfo cm) {
3785      if (format != HttpPartFormat.NO_FORMAT)
3786         return format;
3787      if (cm.isNumber()) {
3788         if (cm.isDecimal()) {
3789            if (cm.isDouble())
3790               return HttpPartFormat.DOUBLE;
3791            return HttpPartFormat.FLOAT;
3792         }
3793         if (cm.isLong())
3794            return HttpPartFormat.INT64;
3795         return HttpPartFormat.INT32;
3796      }
3797      return format;
3798   }
3799
3800   /**
3801    * Returns the <c>items</c> field of this schema.
3802    *
3803    * @return The schema for child items of the object represented by this schema, or <jk>null</jk> if not defined.
3804    * @see HttpPartSchema.Builder#items(HttpPartSchema.Builder)
3805    */
3806   public HttpPartSchema getItems() { return items; }
3807
3808   /**
3809    * Returns the <c>maximum</c> field of this schema.
3810    *
3811    * @return The <c>maximum</c> field of this schema, or <jk>null</jk> if not specified.
3812    * @see HttpPartSchema.Builder#maximum(Number)
3813    */
3814   public Number getMaximum() { return maximum; }
3815
3816   /**
3817    * Returns the <c>maxItems</c> field of this schema.
3818    *
3819    * @return The <c>maxItems</c> field of this schema, or <jk>null</jk> if not specified.
3820    * @see HttpPartSchema.Builder#maxItems(Long)
3821    */
3822   public Long getMaxItems() { return maxItems; }
3823
3824   /**
3825    * Returns the <c>maxLength</c> field of this schema.
3826    *
3827    * @return The <c>maxLength</c> field of this schema, or <jk>null</jk> if not specified.
3828    * @see HttpPartSchema.Builder#maxLength(Long)
3829    */
3830   public Long getMaxLength() { return maxLength; }
3831
3832   /**
3833    * Returns the <c>maxProperties</c> field of this schema.
3834    *
3835    * @return The <c>maxProperties</c> field of this schema, or <jk>null</jk> if not specified.
3836    * @see HttpPartSchema.Builder#maxProperties(Long)
3837    */
3838   public Long getMaxProperties() { return maxProperties; }
3839
3840   /**
3841    * Returns the <c>minimum</c> field of this schema.
3842    *
3843    * @return The <c>minimum</c> field of this schema, or <jk>null</jk> if not specified.
3844    * @see HttpPartSchema.Builder#minimum(Number)
3845    */
3846   public Number getMinimum() { return minimum; }
3847
3848   /**
3849    * Returns the <c>minItems</c> field of this schema.
3850    *
3851    * @return The <c>minItems</c> field of this schema, or <jk>null</jk> if not specified.
3852    * @see HttpPartSchema.Builder#minItems(Long)
3853    */
3854   public Long getMinItems() { return minItems; }
3855
3856   /**
3857    * Returns the <c>minLength</c> field of this schema.
3858    *
3859    * @return The <c>minLength</c> field of this schema, or <jk>null</jk> if not specified.
3860    * @see HttpPartSchema.Builder#minLength(Long)
3861    */
3862   public Long getMinLength() { return minLength; }
3863
3864   /**
3865    * Returns the <c>minProperties</c> field of this schema.
3866    *
3867    * @return The <c>minProperties</c> field of this schema, or <jk>null</jk> if not specified.
3868    * @see HttpPartSchema.Builder#minProperties(Long)
3869    */
3870   public Long getMinProperties() { return minProperties; }
3871
3872   /**
3873    * Returns the <c>multipleOf</c> field of this schema.
3874    *
3875    * @return The <c>multipleOf</c> field of this schema, or <jk>null</jk> if not specified.
3876    * @see HttpPartSchema.Builder#multipleOf(Number)
3877    */
3878   public Number getMultipleOf() { return multipleOf; }
3879
3880   /**
3881    * Returns the name of the object described by this schema, for example the query or form parameter name.
3882    *
3883    * @return The name, or <jk>null</jk> if not specified.
3884    * @see HttpPartSchema.Builder#name(String)
3885    */
3886   public String getName() { return name; }
3887
3888   /**
3889    * Returns the default parsed type for this schema.
3890    *
3891    * @return The default parsed type for this schema.  Never <jk>null</jk>.
3892    */
3893   public Class<?> getParsedType() { return parsedType; }
3894
3895   /**
3896    * Returns the <c>parser</c> field of this schema.
3897    *
3898    * @return The <c>parser</c> field of this schema, or <jk>null</jk> if not specified.
3899    * @see HttpPartSchema.Builder#parser(Class)
3900    */
3901   public Class<?> getParser() { return parser; }
3902
3903   /**
3904    * Returns the <c>pattern</c> field of this schema.
3905    *
3906    * @return The <c>pattern</c> field of this schema, or <jk>null</jk> if not specified.
3907    * @see HttpPartSchema.Builder#pattern(String)
3908    */
3909   public Pattern getPattern() { return pattern; }
3910
3911   /**
3912    * Returns the schema information for the specified property.
3913    *
3914    * @param name The property name.  Can be <jk>null</jk> (treated as an unmatched name; falls back to the <c>additionalProperties</c> schema).
3915    * @return The schema information for the specified property, or the <c>additionalProperties</c> schema if no matching property is defined, or <jk>null</jk> if neither is defined.
3916    */
3917   public HttpPartSchema getProperty(String name) {
3918      if (nn(properties)) {
3919         HttpPartSchema schema = properties.get(name);
3920         if (nn(schema))
3921            return schema;
3922      }
3923      return additionalProperties;
3924   }
3925
3926   /**
3927    * Returns the <c>serializer</c> field of this schema.
3928    *
3929    * @return The <c>serializer</c> field of this schema, or <jk>null</jk> if not specified.
3930    * @see HttpPartSchema.Builder#serializer(Class)
3931    */
3932   public Class<?> getSerializer() { return serializer; }
3933
3934   /**
3935    * Returns the <c>type</c> field of this schema.
3936    *
3937    * @return The <c>type</c> field of this schema.  Defaults to {@link HttpPartDataType#NO_TYPE} if not specified.
3938    * @see HttpPartSchema.Builder#type(String)
3939    */
3940   public HttpPartDataType getType() { return type; }
3941
3942   /**
3943    * Returns the <c>type</c> field of this schema.
3944    *
3945    * @param cm
3946    *    The class info of the object.
3947    *    <br>Used to auto-detect the type if the type was not specified.
3948    *    <br>Must not be <jk>null</jk>.
3949    * @return The <c>type</c> field of this schema, or the type auto-detected from the class info if not specified.
3950    * @see HttpPartSchema.Builder#type(String)
3951    */
3952   public HttpPartDataType getType(ClassInfo cm) {
3953      if (type != HttpPartDataType.NO_TYPE)
3954         return type;
3955      if (cm.isTemporal() || cm.isDateOrCalendar())
3956         return HttpPartDataType.STRING;
3957      if (cm.isNumber()) {
3958         if (cm.isDecimal())
3959            return HttpPartDataType.NUMBER;
3960         return HttpPartDataType.INTEGER;
3961      }
3962      if (cm.isBoolean())
3963         return HttpPartDataType.BOOLEAN;
3964      if (cm.isMap() || isObjectType(cm))
3965         return HttpPartDataType.OBJECT;
3966      if (cm.isCollectionOrArray())
3967         return HttpPartDataType.ARRAY;
3968      return HttpPartDataType.STRING;
3969   }
3970
3971   private static boolean isObjectType(ClassInfo cm) {
3972      if (cm instanceof ClassMeta<?> cm2)
3973         return cm2.isBean();
3974      return false;
3975   }
3976
3977   /**
3978    * Returns <jk>true</jk> if this schema has properties associated with it.
3979    *
3980    * @return <jk>true</jk> if this schema has properties associated with it.
3981    */
3982   public boolean hasProperties() {
3983      return nn(properties) || nn(additionalProperties);
3984   }
3985
3986   /**
3987    * Returns the <c>allowEmptyValue</c> field of this schema.
3988    *
3989    * @return The <c>allowEmptyValue</c> field of this schema.
3990    * @see HttpPartSchema.Builder#allowEmptyValue(Boolean)
3991    */
3992   public boolean isAllowEmptyValue() { return allowEmptyValue; }
3993
3994   /**
3995    * Returns the <c>exclusiveMaximum</c> field of this schema.
3996    *
3997    * @return The <c>exclusiveMaximum</c> field of this schema.
3998    * @see HttpPartSchema.Builder#exclusiveMaximum(Boolean)
3999    */
4000   public boolean isExclusiveMaximum() { return exclusiveMaximum; }
4001
4002   /**
4003    * Returns the <c>exclusiveMinimum</c> field of this schema.
4004    *
4005    * @return The <c>exclusiveMinimum</c> field of this schema.
4006    * @see HttpPartSchema.Builder#exclusiveMinimum(Boolean)
4007    */
4008   public boolean isExclusiveMinimum() { return exclusiveMinimum; }
4009
4010   /**
4011    * Returns the <c>required</c> field of this schema.
4012    *
4013    * @return The <c>required</c> field of this schema.
4014    * @see HttpPartSchema.Builder#required(Boolean)
4015    */
4016   public boolean isRequired() { return required; }
4017
4018   /**
4019    * Returns the <c>skipIfEmpty</c> field of this schema.
4020    *
4021    * @return The <c>skipIfEmpty</c> field of this schema.
4022    * @see HttpPartSchema.Builder#skipIfEmpty(Boolean)
4023    */
4024   public boolean isSkipIfEmpty() { return skipIfEmpty; }
4025
4026   /**
4027    * Returns the <c>uniqueItems</c> field of this schema.
4028    *
4029    * @return The <c>uniqueItems</c> field of this schema.
4030    * @see HttpPartSchema.Builder#uniqueItems(Boolean)
4031    */
4032   public boolean isUniqueItems() { return uniqueItems; }
4033
4034   protected FluentMap<String,Object> properties() {
4035      // @formatter:off
4036      Predicate<Object> ne = x -> ine(s(x));
4037      Predicate<Object> nf = x -> x instanceof Boolean x2 && x2;
4038      Predicate<Object> nm1 = x -> x instanceof Number x2 && x2.intValue() != -1;
4039      Predicate<Object> nn = Shorts::nn;
4040      return mapb_so().sorted().buildFluent()
4041         .ai(ne, "name", name)
4042         .ai(ne, "type", type)
4043         .ai(ne, "format", format)
4044         .ai(ne, "default", default_)
4045         .ai(ne, "enum", enum_)
4046         .ai(ne, "properties", properties)
4047         .ai(nf, "allowEmptyValue", allowEmptyValue)
4048         .ai(nf, "exclusiveMaximum", exclusiveMaximum)
4049         .ai(nf, "exclusiveMinimum", exclusiveMinimum)
4050         .ai(nf, "required", required)
4051         .ai(nf, "uniqueItems", uniqueItems)
4052         .ai(nf, "skipIfEmpty", skipIfEmpty)
4053         .ai(x -> x != HttpPartCollectionFormat.NO_COLLECTION_FORMAT, "collectionFormat", collectionFormat)
4054         .ai(ne, "pattern", pattern)
4055         .ai(nn, "items", items)
4056         .ai(nn, "additionalProperties", additionalProperties)
4057         .ai(nm1, "maximum", maximum)
4058         .ai(nm1, "minimum", minimum)
4059         .ai(nm1, "multipleOf", multipleOf)
4060         .ai(nm1, "maxLength", maxLength)
4061         .ai(nm1, "minLength", minLength)
4062         .ai(nm1, "maxItems", maxItems)
4063         .ai(nm1, "minItems", minItems)
4064         .ai(nm1, "maxProperties", maxProperties)
4065         .ai(nm1, "minProperties", minProperties)
4066         .a("parsedType", parsedType);
4067      // @formatter:on
4068   }
4069
4070   @Override /* Overridden from Object */
4071   public String toString() {
4072      return r(properties());
4073   }
4074
4075   /**
4076    * Throws a {@link SchemaValidationException} if the specified pre-parsed input does not validate against this schema.
4077    *
4078    * @param in The input.  Can be <jk>null</jk> (only the <c>required</c> check is performed; format/pattern/length checks are skipped).
4079    * @return The same object passed in.
4080    * @throws SchemaValidationException if the specified pre-parsed input does not validate against this schema.
4081    */
4082   public String validateInput(String in) throws SchemaValidationException {
4083      if (! isValidRequired(in))
4084         throw new SchemaValidationException("No value specified.");
4085      if (nn(in)) {
4086         if (! isValidAllowEmpty(in))
4087            throw new SchemaValidationException("Empty value not allowed.");
4088         if (! isValidConst(in))
4089            throw new SchemaValidationException("Value does not match constant.  Must be: %s", const_);
4090         if (! isValidPattern(in))
4091            throw new SchemaValidationException("Value does not match expected pattern.  Must match pattern: %s", pattern.pattern());
4092         if (! isValidEnum(in))
4093            throw new SchemaValidationException("Value does not match one of the expected values.  Must be one of the following:  %s", toCdl(enum_));
4094         if (! isValidMaxLength(in))
4095            throw new SchemaValidationException("Maximum length of value exceeded.");
4096         if (! isValidMinLength(in))
4097            throw new SchemaValidationException("Minimum length of value not met.");
4098         if (! isValidFormat(in))
4099            throw new SchemaValidationException("Value does not match expected format: %s", format);
4100      }
4101      return in;
4102   }
4103
4104   /**
4105    * Validates the specified parsed output against this schema.
4106    *
4107    * @param <T> The return type.
4108    * @param o The parsed output.  Can be <jk>null</jk> (only the <c>required</c> check is performed; type-specific validation is skipped).
4109    * @return The same object passed in.
4110    * @throws SchemaValidationException if the specified parsed output does not validate against this schema.
4111    */
4112   public <T> T validateOutput(T o) throws SchemaValidationException {
4113      if (o == null) {
4114         if (! isValidRequired(o))
4115            throw new SchemaValidationException("Required value not provided.");
4116         return o;
4117      }
4118      var ci = ClassInfo.of(o);
4119      switch (getType(ci)) {
4120         case ARRAY: {
4121            if (ci.isArray()) {
4122               if (! isValidMinItems(o))
4123                  throw new SchemaValidationException("Minimum number of items not met.");
4124               if (! isValidMaxItems(o))
4125                  throw new SchemaValidationException("Maximum number of items exceeded.");
4126               if (! isValidUniqueItems(o))
4127                  throw new SchemaValidationException("Duplicate items not allowed.");
4128               HttpPartSchema items2 = getItems();
4129               if (nn(items2))
4130                  for (var i = 0; i < Array.getLength(o); i++)
4131                     items2.validateOutput(Array.get(o, i));
4132            } else if (ci.isCollection()) {
4133               Collection<?> c = (Collection<?>)o;
4134               if (! isValidMinItems(c))
4135                  throw new SchemaValidationException("Minimum number of items not met.");
4136               if (! isValidMaxItems(c))
4137                  throw new SchemaValidationException("Maximum number of items exceeded.");
4138               if (! isValidUniqueItems(c))
4139                  throw new SchemaValidationException("Duplicate items not allowed.");
4140               HttpPartSchema items2 = getItems();
4141               if (nn(items2))
4142                  c.forEach(items2::validateOutput);
4143            }
4144            break;
4145         }
4146         case INTEGER: {
4147            if (ci.isNumber()) {
4148               Number n = (Number)o;
4149               if (! isValidMinimum(n))
4150                  throw new SchemaValidationException("Minimum value not met.");
4151               if (! isValidMaximum(n))
4152                  throw new SchemaValidationException("Maximum value exceeded.");
4153               if (! isValidMultipleOf(n))
4154                  throw new SchemaValidationException("Multiple-of not met.");
4155            }
4156            break;
4157         }
4158         case NUMBER: {
4159            if (ci.isNumber()) {
4160               Number n = (Number)o;
4161               if (! isValidMinimum(n))
4162                  throw new SchemaValidationException("Minimum value not met.");
4163               if (! isValidMaximum(n))
4164                  throw new SchemaValidationException("Maximum value exceeded.");
4165               if (! isValidMultipleOf(n))
4166                  throw new SchemaValidationException("Multiple-of not met.");
4167            }
4168            break;
4169         }
4170         case OBJECT: {
4171            if (ci.isMap()) {
4172               Map<?,?> m = (Map<?,?>)o;
4173               if (! isValidMinProperties(m))
4174                  throw new SchemaValidationException("Minimum number of properties not met.");
4175               if (! isValidMaxProperties(m))
4176                  throw new SchemaValidationException("Maximum number of properties exceeded.");
4177               m.forEach((k, v) -> {
4178                  var key = k.toString();
4179                  HttpPartSchema s2 = getProperty(key);
4180                  if (nn(s2))
4181                     s2.validateOutput(v);
4182               });
4183            }
4184            break;
4185         }
4186         case STRING: {
4187            if (ci.isCharSequence()) {
4188               var s = o.toString();
4189               if (! isValidMinLength(s))
4190                  throw new SchemaValidationException("Minimum length of value not met.");
4191               if (! isValidMaxLength(s))
4192                  throw new SchemaValidationException("Maximum length of value exceeded.");
4193               if (! isValidPattern(s))
4194                  throw new SchemaValidationException("Value does not match expected pattern.  Must match pattern: %s", pattern.pattern());
4195               if (! isValidFormat(s))
4196                  throw new SchemaValidationException("Value does not match expected format: %s", format);
4197            }
4198            break;
4199         }
4200         case BOOLEAN, FILE, NO_TYPE:
4201            break;
4202         default:
4203            break;
4204      }
4205      return o;
4206   }
4207
4208   private boolean isValidAllowEmpty(String x) {
4209      return allowEmptyValue || ine(x);
4210   }
4211
4212   private boolean isValidConst(String x) {
4213      return const_ == null || const_.equals(x);
4214   }
4215
4216   private static boolean isValidDate(String x) {
4217      // RFC 3339 full-date: YYYY-MM-DD (relaxed to allow various date formats) (programmatic to avoid regex recompilation per HTTP-part value).
4218      // Mirrors prefix match ^\d{4}[-/]\d{1,2}[-/]\d{1,2}.* (\d = ASCII [0-9]; trailing .* is a non-line-terminator prefix check, not a full match).
4219      if (x.length() < 4)
4220         return false;
4221      for (var i = 0; i < 4; i++)
4222         if (! isAsciiDigit(x.charAt(i)))
4223            return false;
4224      var i = scanSepAnd1or2Digits(x, 4);
4225      if (i < 0)
4226         return false;
4227      i = scanSepAnd1or2Digits(x, i);
4228      if (i < 0)
4229         return false;
4230      return hasNoLineTerminator(x, i);
4231   }
4232
4233   private static boolean isValidDateTime(String x) {
4234      // RFC 3339 date-time (relaxed to allow various datetime formats) (programmatic to avoid regex recompilation per HTTP-part value).
4235      // Mirrors prefix match ^\d{4}[-/]\d{1,2}[-/]\d{1,2}[T\s]\d{1,2}:\d{1,2}.* (\s = ASCII whitespace; trailing .* is a non-line-terminator prefix check).
4236      if (x.length() < 4)
4237         return false;
4238      for (var i = 0; i < 4; i++)
4239         if (! isAsciiDigit(x.charAt(i)))
4240            return false;
4241      var i = scanSepAnd1or2Digits(x, 4);
4242      if (i < 0)
4243         return false;
4244      i = scanSepAnd1or2Digits(x, i);
4245      if (i < 0)
4246         return false;
4247      if (i >= x.length() || ! (x.charAt(i) == 'T' || isRegexWhitespace(x.charAt(i))))
4248         return false;
4249      i = scan1or2Digits(x, i + 1);
4250      if (i < 0)
4251         return false;
4252      if (! charIs(x, i, ':'))
4253         return false;
4254      i = scan1or2Digits(x, i + 1);
4255      if (i < 0)
4256         return false;
4257      return hasNoLineTerminator(x, i);
4258   }
4259
4260   private static boolean isValidDateTimeZone(String x) {
4261      // RFC 3339 date-time with time zone (programmatic to avoid regex recompilation per HTTP-part value).
4262      // Mirrors full match ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?[+-]\d{2}:\d{2}$ (\d = ASCII [0-9]).
4263      var len = x.length();
4264      var i = scanDateTimeCore(x);
4265      if (i < 0)
4266         return false;
4267      i = scanOptionalFractionalSeconds(x, i, len);
4268      if (i < 0)
4269         return false;
4270      return scanTimezoneOffset(x, i, len) == len;
4271   }
4272
4273   // Mirrors \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2} - the date/time core consumed by isValidDateTimeZone. Returns
4274   // the index after the seconds field, or -1 if any component or separator is missing/malformed.
4275   private static int scanDateTimeCore(String x) {
4276      var i = scanDigits(x, 0, 4);
4277      if (i < 0 || ! charIs(x, i, '-'))
4278         return -1;
4279      i = scanDigits(x, i + 1, 2);
4280      if (i < 0 || ! charIs(x, i, '-'))
4281         return -1;
4282      i = scanDigits(x, i + 1, 2);
4283      if (i < 0 || ! charIs(x, i, 'T'))
4284         return -1;
4285      i = scanDigits(x, i + 1, 2);
4286      if (i < 0 || ! charIs(x, i, ':'))
4287         return -1;
4288      i = scanDigits(x, i + 1, 2);
4289      if (i < 0 || ! charIs(x, i, ':'))
4290         return -1;
4291      return scanDigits(x, i + 1, 2);
4292   }
4293
4294   // Mirrors the optional group (\.\d+)? shared by isValidDateTimeZone and isValidTime: consumes '.' followed
4295   // by one-or-more ASCII digits.  Returns i unchanged if no '.' is present at i, the index after the digits
4296   // if one is, or -1 if '.' is present with no following digit.
4297   private static int scanOptionalFractionalSeconds(String x, int i, int len) {
4298      if (i < len && x.charAt(i) == '.') {
4299         var f = i + 1;
4300         var j = f;
4301         while (j < len && isAsciiDigit(x.charAt(j)))
4302            j++;
4303         if (j == f)
4304            return -1;
4305         return j;
4306      }
4307      return i;
4308   }
4309
4310   // Mirrors [+-]\d{2}:\d{2}, the mandatory zone-offset suffix of isValidDateTimeZone and the offset branch of
4311   // isValidTime's optional zone group.  Returns the index after the offset, or -1 if the sign, digits, or
4312   // separator are missing/malformed.
4313   private static int scanTimezoneOffset(String x, int i, int len) {
4314      if (i >= len || (x.charAt(i) != '+' && x.charAt(i) != '-'))
4315         return -1;
4316      i = scanDigits(x, i + 1, 2);
4317      if (i < 0 || ! charIs(x, i, ':'))
4318         return -1;
4319      return scanDigits(x, i + 1, 2);
4320   }
4321
4322   private static boolean isValidDuration(String x) {
4323      // RFC 3339 Appendix A duration (ISO 8601) (programmatic to avoid regex recompilation per HTTP-part value).
4324      // Mirrors full match ^P(?:\d+Y)?(?:\d+M)?(?:\d+D)?(?:T(?:\d+H)?(?:\d+M)?(?:\d+(?:\.\d+)?S)?)?$ (\d = ASCII [0-9]).
4325      var len = x.length();
4326      if (len == 0 || x.charAt(0) != 'P')
4327         return false;
4328      var i = scanDurationUnit(x, 1, 'Y');
4329      i = scanDurationUnit(x, i, 'M');
4330      i = scanDurationUnit(x, i, 'D');
4331      if (i < len && x.charAt(i) == 'T') {
4332         i = scanDurationUnit(x, i + 1, 'H');
4333         i = scanDurationUnit(x, i, 'M');
4334         i = scanDurationSeconds(x, i);
4335      }
4336      return i == len;
4337   }
4338
4339   private static boolean isValidEmail(String x) {
4340      // RFC 5321 simplified email validation (programmatic to avoid regex super-linear backtracking on large inputs).
4341      // Mirrors ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
4342      var at = x.indexOf('@');
4343      if (at < 1 || at != x.lastIndexOf('@'))
4344         return false;
4345      for (var i = 0; i < at; i++)
4346         if (! isEmailLocalChar(x.charAt(i)))
4347            return false;
4348      var dot = x.lastIndexOf('.');
4349      if (dot < at + 2)  // Need at least one domain-body char between '@' and the final '.'.
4350         return false;
4351      for (var i = at + 1; i < dot; i++)
4352         if (! isEmailDomainChar(x.charAt(i)))
4353            return false;
4354      if (x.length() - dot - 1 < 2)  // TLD must be 2+ characters.
4355         return false;
4356      for (var i = dot + 1; i < x.length(); i++)
4357         if (! isAsciiLetter(x.charAt(i)))
4358            return false;
4359      return true;
4360   }
4361
4362   private static boolean isEmailLocalChar(char c) {
4363      return isAsciiAlphanumeric(c) || c == '.' || c == '_' || c == '%' || c == '+' || c == '-';
4364   }
4365
4366   private static boolean isEmailDomainChar(char c) {
4367      return isAsciiAlphanumeric(c) || c == '.' || c == '-';
4368   }
4369
4370   private static boolean isAsciiLetter(char c) {
4371      return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
4372   }
4373
4374   private boolean isValidEnum(String x) {
4375      return enum_.isEmpty() || enum_.contains(x);
4376   }
4377
4378   private boolean isValidFormat(String x) {
4379      if (format == null || format == HttpPartFormat.NO_FORMAT)
4380         return true;
4381
4382      // Skip validation for literal "null" string
4383      if (eq(x, "null"))
4384         return true;
4385
4386      try {
4387         return switch (format) {
4388            case EMAIL -> isValidEmail(x);
4389            case IDN_EMAIL -> isValidIdnEmail(x);
4390            case HOSTNAME -> isValidHostname(x);
4391            case IDN_HOSTNAME -> isValidIdnHostname(x);
4392            case IPV4 -> isValidIpv4(x);
4393            case IPV6 -> isValidIpv6(x);
4394            case URI -> isValidUri(x);
4395            case URI_REFERENCE -> isValidUriReference(x);
4396            case IRI -> isValidIri(x);
4397            case IRI_REFERENCE -> isValidIriReference(x);
4398            case UUID -> isValidUuid(x);
4399            case URI_TEMPLATE -> isValidUriTemplate(x);
4400            case JSON_POINTER -> isValidJsonPointer(x);
4401            case RELATIVE_JSON_POINTER -> isValidRelativeJsonPointer(x);
4402            case REGEX -> isValidRegex(x);
4403            case DATE -> isValidDate(x);
4404            case DATE_TIME -> isValidDateTime(x);
4405            case DATE_TIME_ZONE -> isValidDateTimeZone(x);
4406            case TIME -> isValidTime(x);
4407            case DURATION -> isValidDuration(x);
4408            case BYTE, BINARY, BINARY_SPACED -> true; // These are transformation formats, not validation formats
4409            case PASSWORD -> true; // Password format is just a UI hint
4410            case INT32, INT64, FLOAT, DOUBLE -> true; // Numeric formats are validated during parsing
4411            case UON -> true; // UON format is validated during parsing
4412            default -> true;
4413         };
4414      } catch (@SuppressWarnings("unused") Exception e) {
4415         return false;
4416      }
4417   }
4418
4419   private static boolean isValidHostname(String x) {
4420      // RFC 1123 hostname validation (programmatic to avoid regex stack overflow on large inputs)
4421      if (x.isEmpty())
4422         return false;
4423      int labelStart = 0;
4424      for (int i = 0; i <= x.length(); i++) {
4425         if (i == x.length() || x.charAt(i) == '.') {
4426            if (! isValidHostnameLabel(x, labelStart, i))
4427               return false;
4428            labelStart = i + 1;
4429         }
4430      }
4431      return true;
4432   }
4433
4434   private static boolean isValidHostnameLabel(String x, int start, int end) {
4435      int len = end - start;
4436      if (len == 0 || len > 63)
4437         return false;
4438      if (! isAsciiAlphanumeric(x.charAt(start)) || ! isAsciiAlphanumeric(x.charAt(end - 1)))
4439         return false;
4440      for (int i = start + 1; i < end - 1; i++) {
4441         char c = x.charAt(i);
4442         if (! isAsciiAlphanumeric(c) && c != '-')
4443            return false;
4444      }
4445      return true;
4446   }
4447
4448   private static boolean isAsciiAlphanumeric(char c) {
4449      return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9');
4450   }
4451
4452   private static boolean isAsciiDigit(char c) {
4453      // Java regex \d (ASCII [0-9] only).
4454      return c >= '0' && c <= '9';
4455   }
4456
4457   private static boolean isAsciiHexDigit(char c) {
4458      return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
4459   }
4460
4461   private static boolean isUriSchemeChar(char c) {
4462      // Matches the regex class [a-zA-Z0-9+.-].
4463      return isAsciiAlphanumeric(c) || c == '+' || c == '.' || c == '-';
4464   }
4465
4466   private static boolean isRegexLineTerminator(char c) {
4467      // Chars NOT matched by regex '.' in default (non-DOTALL) mode.
4468      return c == '\n' || c == '\r' || c == '\u0085' || c == '\u2028' || c == '\u2029';
4469   }
4470
4471   private static boolean charIs(String x, int i, char c) {
4472      return i < x.length() && x.charAt(i) == c;
4473   }
4474
4475   // Returns the index after consuming exactly n ASCII digits starting at i, or -1 if fewer than n digits are present.
4476   private static int scanDigits(String x, int i, int n) {
4477      if (i < 0 || i + n > x.length())
4478         return -1;
4479      for (var k = 0; k < n; k++)
4480         if (! isAsciiDigit(x.charAt(i + k)))
4481            return -1;
4482      return i + n;
4483   }
4484
4485   // Mirrors \d{1,2} - consumes 1 mandatory then 1 optional ASCII digit (greedy). Returns new index or -1.
4486   private static int scan1or2Digits(String x, int i) {
4487      var len = x.length();
4488      if (i < 0 || i >= len || ! isAsciiDigit(x.charAt(i)))
4489         return -1;
4490      i++;
4491      if (i < len && isAsciiDigit(x.charAt(i)))
4492         i++;
4493      return i;
4494   }
4495
4496   // Mirrors [-/]\d{1,2}. Returns new index or -1.
4497   private static int scanSepAnd1or2Digits(String x, int i) {
4498      if (i < 0 || i >= x.length() || (x.charAt(i) != '-' && x.charAt(i) != '/'))
4499         return -1;
4500      return scan1or2Digits(x, i + 1);
4501   }
4502
4503   // Mirrors '.*' / '.+' tail consumption: returns true iff every char from i onward is a non-line-terminator (i.e. matchable by regex '.').
4504   private static boolean hasNoLineTerminator(String x, int i) {
4505      for (var j = i; j < x.length(); j++)
4506         if (isRegexLineTerminator(x.charAt(j)))
4507            return false;
4508      return true;
4509   }
4510
4511   // Mirrors the optional group (?:\d+<unit>)?: consumes a run of >=1 ASCII digits followed by the unit char if present; otherwise leaves i unchanged.
4512   private static int scanDurationUnit(String x, int i, char unit) {
4513      var len = x.length();
4514      var j = i;
4515      while (j < len && isAsciiDigit(x.charAt(j)))
4516         j++;
4517      if (j > i && j < len && x.charAt(j) == unit)
4518         return j + 1;
4519      return i;
4520   }
4521
4522   // Mirrors the optional group (?:\d+(?:\.\d+)?S)?: consumes seconds (with optional fraction) followed by 'S' if present; otherwise leaves i unchanged.
4523   private static int scanDurationSeconds(String x, int i) {
4524      var len = x.length();
4525      var j = i;
4526      while (j < len && isAsciiDigit(x.charAt(j)))
4527         j++;
4528      if (j == i)
4529         return i;  // \d+ requires at least one digit
4530      if (j < len && x.charAt(j) == '.') {
4531         var f = j + 1;
4532         var k = f;
4533         while (k < len && isAsciiDigit(x.charAt(k)))
4534            k++;
4535         if (k == f)
4536            return i;  // '.' with no following digit -> group cannot match -> consume nothing
4537         j = k;
4538      }
4539      if (j < len && x.charAt(j) == 'S')
4540         return j + 1;
4541      return i;  // no 'S' -> group matches empty
4542   }
4543
4544   // Mirrors ^[a-zA-Z][a-zA-Z0-9+.-]*:<rest> where <rest> is .+ (nonEmptyRest=true) or .* (nonEmptyRest=false), all non-line-terminators.
4545   private static boolean isValidScheme(String x, boolean nonEmptyRest) {
4546      var len = x.length();
4547      if (len == 0 || ! isAsciiLetter(x.charAt(0)))
4548         return false;
4549      var i = 1;
4550      while (i < len && isUriSchemeChar(x.charAt(i)))
4551         i++;
4552      if (i >= len || x.charAt(i) != ':')
4553         return false;
4554      i++;
4555      if (nonEmptyRest && i >= len)
4556         return false;
4557      return hasNoLineTerminator(x, i);
4558   }
4559
4560   // Consumes up to 4 ASCII hex digits starting at i (bounded by end). Mirrors [0-9a-fA-F]{0,4}.
4561   private static int scanHex4(String x, int i, int end) {
4562      var n = 0;
4563      while (i < end && n < 4 && isAsciiHexDigit(x.charAt(i))) {
4564         i++;
4565         n++;
4566      }
4567      return i;
4568   }
4569
4570   // Mirrors ^([0-9a-fA-F]{0,4}+:){7}[0-9a-fA-F]{0,4}$ - exactly 8 hextet groups separated by 7 colons.
4571   private static boolean isIpv6Full(String x) {
4572      var len = x.length();
4573      var i = 0;
4574      for (var g = 0; g < 7; g++) {
4575         i = scanHex4(x, i, len);
4576         if (i >= len || x.charAt(i) != ':')
4577            return false;
4578         i++;
4579      }
4580      i = scanHex4(x, i, len);
4581      return i == len;
4582   }
4583
4584   // Mirrors ^::([0-9a-fA-F]{0,4}+:){0,6}[0-9a-fA-F]{0,4}$.
4585   private static boolean isIpv6LeadingDoubleColon(String x) {
4586      var len = x.length();
4587      if (len < 2 || x.charAt(0) != ':' || x.charAt(1) != ':')
4588         return false;
4589      var i = 2;
4590      var groups = 0;
4591      while (groups < 6) {
4592         var j = scanHex4(x, i, len);
4593         if (j < len && x.charAt(j) == ':') {
4594            i = j + 1;
4595            groups++;
4596         } else {
4597            break;
4598         }
4599      }
4600      i = scanHex4(x, i, len);
4601      return i == len;
4602   }
4603
4604   // Mirrors ^([0-9a-fA-F]{0,4}+:){1,7}:$ - every such match ends in ':', and the body (minus the final ':') is 1..7 hextet+colon groups.
4605   private static boolean isIpv6TrailingColon(String x) {
4606      var len = x.length();
4607      if (len == 0 || x.charAt(len - 1) != ':')
4608         return false;
4609      var body = len - 1;
4610      var i = 0;
4611      var groups = 0;
4612      while (i < body) {
4613         var j = scanHex4(x, i, body);
4614         if (j < body && x.charAt(j) == ':') {
4615            i = j + 1;
4616            groups++;
4617         } else {
4618            return false;
4619         }
4620      }
4621      return groups >= 1 && groups <= 7 && i == body;
4622   }
4623
4624   private static boolean isValidIdnEmail(String x) {
4625      // RFC 6531 - allows international characters (programmatic to avoid regex super-linear backtracking on large inputs).
4626      // Mirrors ^[^@\s]+@[^@\s]+\.[^@\s]+$
4627      var at = x.indexOf('@');
4628      if (at < 1 || at != x.lastIndexOf('@'))
4629         return false;
4630      for (var i = 0; i < at; i++)
4631         if (isRegexWhitespace(x.charAt(i)))
4632            return false;
4633      var len = x.length();
4634      var interiorDot = false;
4635      for (var i = at + 1; i < len; i++) {
4636         var c = x.charAt(i);
4637         if (isRegexWhitespace(c))
4638            return false;
4639         if (c == '.' && i > at + 1 && i < len - 1)
4640            interiorDot = true;
4641      }
4642      return interiorDot;
4643   }
4644
4645   private static boolean isRegexWhitespace(char c) {
4646      // Matches Java regex \s (ASCII whitespace only).
4647      return c == ' ' || c == '\t' || c == '\n' || c == '\u000B' || c == '\f' || c == '\r';
4648   }
4649
4650   private static boolean isValidIdnHostname(String x) {
4651      // RFC 5890 - allows international characters (programmatic to avoid regex stack overflow on large inputs)
4652      if (x.isEmpty())
4653         return false;
4654      for (int i = 0; i < x.length(); i++)
4655         if (Character.isWhitespace(x.charAt(i)))
4656            return false;
4657      return true;
4658   }
4659
4660   private static boolean isValidIpv4(String x) {
4661      // RFC 2673 IPv4 validation
4662      var parts = x.split("\\.");
4663      if (parts.length != 4)
4664         return false;
4665      for (var part : parts) {
4666         try {
4667            int val = Integer.parseInt(part);
4668            if (val < 0 || val > 255)
4669               return false;
4670         } catch (@SuppressWarnings("unused") NumberFormatException e) {
4671            return false;
4672         }
4673      }
4674      return true;
4675   }
4676
4677   private static boolean isValidIpv6(String x) {
4678      // RFC 4291 IPv6 validation (simplified) (programmatic to avoid regex recompilation per HTTP-part value).
4679      // Mirrors the three full-match alternatives:
4680      //   ^([0-9a-fA-F]{0,4}+:){7}[0-9a-fA-F]{0,4}$ | ^::([0-9a-fA-F]{0,4}+:){0,6}[0-9a-fA-F]{0,4}$ | ^([0-9a-fA-F]{0,4}+:){1,7}:$
4681      return isIpv6Full(x) || isIpv6LeadingDoubleColon(x) || isIpv6TrailingColon(x);
4682   }
4683
4684   private static boolean isValidIri(String x) {
4685      // RFC 3987 IRI validation (allows international characters) (programmatic to avoid regex recompilation per HTTP-part value).
4686      // Mirrors prefix match ^[a-zA-Z][a-zA-Z0-9+.-]*:.+ (.+ requires at least one trailing non-line-terminator char).
4687      return isValidScheme(x, true);
4688   }
4689
4690   private static boolean isValidIriReference(String x) {
4691      // RFC 3987 IRI reference (allows international characters)
4692      return !x.isEmpty();
4693   }
4694
4695   private static boolean isValidJsonPointer(String x) {
4696      // RFC 6901 JSON Pointer validation
4697      return x.isEmpty() || x.charAt(0) == '/';
4698   }
4699
4700   private boolean isValidMaximum(Number x) {
4701      // Check Draft 2020-12 exclusiveMaximumValue first (takes precedence)
4702      if (nn(exclusiveMaximumValue)) {
4703         if (x instanceof Integer || x instanceof AtomicInteger)
4704            return x.intValue() < exclusiveMaximumValue.intValue();
4705         if (x instanceof Short || x instanceof Byte)
4706            return x.shortValue() < exclusiveMaximumValue.shortValue();
4707         if (x instanceof Long || x instanceof AtomicLong || x instanceof BigInteger)
4708            return x.longValue() < exclusiveMaximumValue.longValue();
4709         if (x instanceof Float)
4710            return x.floatValue() < exclusiveMaximumValue.floatValue();
4711         if (x instanceof Double || x instanceof BigDecimal)
4712            return x.doubleValue() < exclusiveMaximumValue.doubleValue();
4713      }
4714      // Fall back to Draft 04 boolean exclusiveMaximum with maximum
4715      if (x instanceof Integer || x instanceof AtomicInteger)
4716         return maximum == null || x.intValue() < maximum.intValue() || (x.intValue() == maximum.intValue() && (! exclusiveMaximum));
4717      if (x instanceof Short || x instanceof Byte)
4718         return maximum == null || x.shortValue() < maximum.shortValue() || (x.intValue() == maximum.shortValue() && (! exclusiveMaximum));
4719      if (x instanceof Long || x instanceof AtomicLong || x instanceof BigInteger)
4720         return maximum == null || x.longValue() < maximum.longValue() || (x.intValue() == maximum.longValue() && (! exclusiveMaximum));
4721      if (x instanceof Float)
4722         return maximum == null || x.floatValue() < maximum.floatValue() || (x.floatValue() == maximum.floatValue() && (! exclusiveMaximum));
4723      if (x instanceof Double || x instanceof BigDecimal)
4724         return maximum == null || x.doubleValue() < maximum.doubleValue() || (x.doubleValue() == maximum.doubleValue() && (! exclusiveMaximum));
4725      return true;
4726   }
4727
4728   private boolean isValidMaxItems(Collection<?> x) {
4729      return maxItems == null || x.size() <= maxItems;
4730   }
4731
4732   private boolean isValidMaxItems(Object x) {
4733      return maxItems == null || Array.getLength(x) <= maxItems;
4734   }
4735
4736   private boolean isValidMaxLength(String x) {
4737      return maxLength == null || x.length() <= maxLength;
4738   }
4739
4740   private boolean isValidMaxProperties(Map<?,?> x) {
4741      return maxProperties == null || x.size() <= maxProperties;
4742   }
4743
4744   private boolean isValidMinimum(Number x) {
4745      // Check Draft 2020-12 exclusiveMinimumValue first (takes precedence)
4746      if (nn(exclusiveMinimumValue)) {
4747         if (x instanceof Integer || x instanceof AtomicInteger)
4748            return x.intValue() > exclusiveMinimumValue.intValue();
4749         if (x instanceof Short || x instanceof Byte)
4750            return x.shortValue() > exclusiveMinimumValue.shortValue();
4751         if (x instanceof Long || x instanceof AtomicLong || x instanceof BigInteger)
4752            return x.longValue() > exclusiveMinimumValue.longValue();
4753         if (x instanceof Float)
4754            return x.floatValue() > exclusiveMinimumValue.floatValue();
4755         if (x instanceof Double || x instanceof BigDecimal)
4756            return x.doubleValue() > exclusiveMinimumValue.doubleValue();
4757      }
4758      // Fall back to Draft 04 boolean exclusiveMinimum with minimum
4759      if (x instanceof Integer || x instanceof AtomicInteger)
4760         return minimum == null || x.intValue() > minimum.intValue() || (x.intValue() == minimum.intValue() && (! exclusiveMinimum));
4761      if (x instanceof Short || x instanceof Byte)
4762         return minimum == null || x.shortValue() > minimum.shortValue() || (x.intValue() == minimum.shortValue() && (! exclusiveMinimum));
4763      if (x instanceof Long || x instanceof AtomicLong || x instanceof BigInteger)
4764         return minimum == null || x.longValue() > minimum.longValue() || (x.intValue() == minimum.longValue() && (! exclusiveMinimum));
4765      if (x instanceof Float)
4766         return minimum == null || x.floatValue() > minimum.floatValue() || (x.floatValue() == minimum.floatValue() && (! exclusiveMinimum));
4767      if (x instanceof Double || x instanceof BigDecimal)
4768         return minimum == null || x.doubleValue() > minimum.doubleValue() || (x.doubleValue() == minimum.doubleValue() && (! exclusiveMinimum));
4769      return true;
4770   }
4771
4772   private boolean isValidMinItems(Collection<?> x) {
4773      return minItems == null || x.size() >= minItems;
4774   }
4775
4776   private boolean isValidMinItems(Object x) {
4777      return minItems == null || Array.getLength(x) >= minItems;
4778   }
4779
4780   private boolean isValidMinLength(String x) {
4781      return minLength == null || x.length() >= minLength;
4782   }
4783
4784   private boolean isValidMinProperties(Map<?,?> x) {
4785      return minProperties == null || x.size() >= minProperties;
4786   }
4787
4788   private boolean isValidMultipleOf(Number x) {
4789      if (x instanceof Integer || x instanceof AtomicInteger)
4790         return multipleOf == null || x.intValue() % multipleOf.intValue() == 0;
4791      if (x instanceof Short || x instanceof Byte)
4792         return multipleOf == null || x.shortValue() % multipleOf.shortValue() == 0;
4793      if (x instanceof Long || x instanceof AtomicLong || x instanceof BigInteger)
4794         return multipleOf == null || x.longValue() % multipleOf.longValue() == 0;
4795      if (x instanceof Float)
4796         return multipleOf == null || x.floatValue() % multipleOf.floatValue() == 0;
4797      if (x instanceof Double || x instanceof BigDecimal)
4798         return multipleOf == null || x.doubleValue() % multipleOf.doubleValue() == 0;
4799      return true;
4800   }
4801
4802   private boolean isValidPattern(String x) {
4803      return pattern == null || pattern.matcher(x).matches();
4804   }
4805
4806   // Upper bound on the length of a value validated as a regular expression.  The value here is intentionally
4807   // a caller-authored regex (JSON-Schema "format":"regex"), so it must be compiled rather than quoted; the
4808   // compiled pattern is immediately discarded and never matched against anything.  This cap bounds the cost
4809   // of that throwaway compilation so an over-long or deeply-nested pattern can't be used as a DoS vector.
4810   private static final int MAX_REGEX_VALIDATION_LENGTH = 1000;
4811
4812   private static boolean isValidRegex(String x) {
4813      // ECMA-262 regex validation.  Reject over-long values before compiling (the compiled Pattern is
4814      // discarded and never used for matching, so bounding length is sufficient to cap the work done here).
4815      if (x.length() > MAX_REGEX_VALIDATION_LENGTH)
4816         return false;
4817      try {
4818         Pattern.compile(x);
4819         return true;
4820      } catch (@SuppressWarnings("unused") Exception e) {
4821         return false;
4822      }
4823   }
4824
4825   private static boolean isValidRelativeJsonPointer(String x) {
4826      // Relative JSON Pointer validation (programmatic to avoid regex stack overflow on large inputs)
4827      int i = 0;
4828      int len = x.length();
4829      if (i >= len)
4830         return false;
4831      if (x.charAt(i) == '0') {
4832         i++;
4833      } else if (x.charAt(i) >= '1' && x.charAt(i) <= '9') {
4834         i++;
4835         while (i < len && x.charAt(i) >= '0' && x.charAt(i) <= '9')
4836            i++;
4837      } else {
4838         return false;
4839      }
4840      if (i >= len)
4841         return true;
4842      if (x.charAt(i) == '#')
4843         return i + 1 == len;
4844      return x.charAt(i) == '/';
4845   }
4846
4847   private boolean isValidRequired(Object x) {
4848      return nn(x) || ! required;
4849   }
4850
4851   private static boolean isValidTime(String x) {
4852      // RFC 3339 time (programmatic to avoid regex recompilation per HTTP-part value).
4853      // Mirrors full match ^\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?$ (\d = ASCII [0-9]).
4854      var len = x.length();
4855      var i = scanTimeCore(x);
4856      if (i < 0)
4857         return false;
4858      i = scanOptionalFractionalSeconds(x, i, len);
4859      if (i < 0)
4860         return false;
4861      if (i == len)
4862         return true;  // (Z|[+-]\d{2}:\d{2})? matched empty
4863      if (x.charAt(i) == 'Z')
4864         return i + 1 == len;
4865      return scanTimezoneOffset(x, i, len) == len;
4866   }
4867
4868   // Mirrors \d{2}:\d{2}:\d{2} - the time core consumed by isValidTime. Returns the index after the seconds
4869   // field, or -1 if any component or separator is missing/malformed.
4870   private static int scanTimeCore(String x) {
4871      var i = scanDigits(x, 0, 2);
4872      if (i < 0 || ! charIs(x, i, ':'))
4873         return -1;
4874      i = scanDigits(x, i + 1, 2);
4875      if (i < 0 || ! charIs(x, i, ':'))
4876         return -1;
4877      return scanDigits(x, i + 1, 2);
4878   }
4879
4880   private boolean isValidUniqueItems(Collection<?> x) {
4881      if (uniqueItems && ! (x instanceof Set)) {
4882         var s = new HashSet<>();
4883         for (var o : x)
4884            if (! s.add(o))
4885               return false;
4886      }
4887      return true;
4888   }
4889
4890   private boolean isValidUniqueItems(Object x) {
4891      if (uniqueItems) {
4892         var s = new HashSet<>();
4893         for (var i = 0; i < Array.getLength(x); i++) {
4894            var o = Array.get(x, i);
4895            if (! s.add(o))
4896               return false;
4897         }
4898      }
4899      return true;
4900   }
4901
4902   private static boolean isValidUri(String x) {
4903      // RFC 3986 URI validation
4904      try {
4905         new java.net.URI(x);
4906         // Mirrors prefix match ^[a-zA-Z][a-zA-Z0-9+.-]*:.* (.* permits an empty trailing, non-line-terminator part).
4907         return isValidScheme(x, false);
4908      } catch (Exception e) {
4909         return false;
4910      }
4911   }
4912
4913   private static boolean isValidUriReference(String x) {
4914      // RFC 3986 URI reference (can be relative)
4915      try {
4916         new java.net.URI(x);
4917         return true;
4918      } catch (Exception e) {
4919         return false;
4920      }
4921   }
4922
4923   private static boolean isValidUriTemplate(String x) {
4924      // RFC 6570 URI Template validation (simplified) (programmatic to avoid regex recompilation per HTTP-part value).
4925      // Mirrors full match ^[^\s]*$ - every char must be a non-whitespace char (\s = ASCII whitespace).
4926      for (var i = 0; i < x.length(); i++)
4927         if (isRegexWhitespace(x.charAt(i)))
4928            return false;
4929      return true;
4930   }
4931
4932   private static boolean isValidUuid(String x) {
4933      // RFC 4122 UUID validation (programmatic to avoid regex recompilation per HTTP-part value).
4934      // Mirrors full match ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$.
4935      if (x.length() != 36)
4936         return false;
4937      for (var i = 0; i < 36; i++) {
4938         var c = x.charAt(i);
4939         if (i == 8 || i == 13 || i == 18 || i == 23) {
4940            if (c != '-')
4941               return false;
4942         } else if (! isAsciiHexDigit(c)) {
4943            return false;
4944         }
4945      }
4946      return true;
4947   }
4948
4949   private static boolean resolve(Boolean b) {
4950      return b != null && b;
4951   }
4952}