001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.httppart;
014
015import static java.util.Collections.*;
016import static org.apache.juneau.httppart.HttpPartDataType.*;
017import static org.apache.juneau.httppart.HttpPartFormat.*;
018import static org.apache.juneau.internal.StringUtils.*;
019
020import java.lang.annotation.*;
021import java.lang.reflect.*;
022import java.math.*;
023import java.util.*;
024import java.util.concurrent.atomic.*;
025import java.util.regex.*;
026
027import org.apache.juneau.*;
028import org.apache.juneau.collections.*;
029import org.apache.juneau.http.annotation.*;
030import org.apache.juneau.internal.*;
031import org.apache.juneau.parser.*;
032import org.apache.juneau.reflect.*;
033
034/**
035 * Represents an OpenAPI schema definition.
036 *
037 * <p>
038 * The schema definition can be applied to any HTTP parts such as bodies, headers, query/form parameters, and URL path parts.
039 * <br>The API is generic enough to apply to any path part although some attributes may only applicable for certain parts.
040 *
041 * <p>
042 * Schema objects are created via builders instantiated through the {@link #create()} method.
043 *
044 * <p>
045 * This class is thread safe and reusable.
046 *
047 * <ul class='seealso'>
048 *    <li class='link'>{@doc OpenApiDetails}
049 * </ul>
050 */
051public class HttpPartSchema {
052
053   //-------------------------------------------------------------------------------------------------------------------
054   // Predefined instances
055   //-------------------------------------------------------------------------------------------------------------------
056
057   /** Reusable instance of this object, all default settings. */
058   public static final HttpPartSchema DEFAULT = HttpPartSchema.create().allowEmptyValue(true).build();
059
060   /** Boolean type */
061   public static final HttpPartSchema T_BOOLEAN = HttpPartSchema.tBoolean().build();
062
063   /** File type */
064   public static final HttpPartSchema T_FILE = HttpPartSchema.tFile().build();
065
066   /** Integer type */
067   public static final HttpPartSchema T_INTEGER = HttpPartSchema.tInteger().build();
068
069   /** Int32 type */
070   public static final HttpPartSchema T_INT32 = HttpPartSchema.tInt32().build();
071
072   /** Int64 type */
073   public static final HttpPartSchema T_INT64 = HttpPartSchema.tInt64().build();
074
075   /** No type */
076   public static final HttpPartSchema T_NONE = HttpPartSchema.tNone().build();
077
078   /** Number type */
079   public static final HttpPartSchema T_NUMBER = HttpPartSchema.tNumber().build();
080
081   /** Float type */
082   public static final HttpPartSchema T_FLOAT = HttpPartSchema.tFloat().build();
083
084   /** Double type */
085   public static final HttpPartSchema T_DOUBLE = HttpPartSchema.tDouble().build();
086
087   /** String type */
088   public static final HttpPartSchema T_STRING = HttpPartSchema.tString().build();
089
090   /** Byte type */
091   public static final HttpPartSchema T_BYTE = HttpPartSchema.tByte().build();
092
093   /** Binary type */
094   public static final HttpPartSchema T_BINARY = HttpPartSchema.tBinary().build();
095
096   /** Spaced binary type */
097   public static final HttpPartSchema T_BINARY_SPACED = HttpPartSchema.tBinarySpaced().build();
098
099   /** Date type */
100   public static final HttpPartSchema T_DATE = HttpPartSchema.tDate().build();
101
102   /** Date-time type */
103   public static final HttpPartSchema T_DATETIME = HttpPartSchema.tDateTime().build();
104
105   /** UON-formated simple type */
106   public static final HttpPartSchema T_UON = HttpPartSchema.tUon().build();
107
108   /** Array type */
109   public static final HttpPartSchema T_ARRAY = HttpPartSchema.tArray().build();
110
111   /** Comma-delimited array type */
112   public static final HttpPartSchema T_ARRAY_CSV = HttpPartSchema.tArrayCsv().build();
113
114   /** Pipe-delimited array type */
115   public static final HttpPartSchema T_ARRAY_PIPES = HttpPartSchema.tArrayPipes().build();
116
117   /** Space-delimited array type */
118   public static final HttpPartSchema T_ARRAY_SSV = HttpPartSchema.tArraySsv().build();
119
120   /** Tab-delimited array type */
121   public static final HttpPartSchema T_ARRAY_TSV = HttpPartSchema.tArrayTsv().build();
122
123   /** UON-formatted array type */
124   public static final HttpPartSchema T_ARRAY_UON = HttpPartSchema.tArrayUon().build();
125
126   /** Multi-part array type */
127   public static final HttpPartSchema T_ARRAY_MULTI = HttpPartSchema.tArrayMulti().build();
128
129   /** Object type */
130   public static final HttpPartSchema T_OBJECT = HttpPartSchema.tObject().build();
131
132   /** Comma-delimited object type */
133   public static final HttpPartSchema T_OBJECT_CSV = HttpPartSchema.tObjectCsv().build();
134
135   /** Pipe-delimited object type */
136   public static final HttpPartSchema T_OBJECT_PIPES = HttpPartSchema.tObjectPipes().build();
137
138   /** Space-delimited object type */
139   public static final HttpPartSchema T_OBJECT_SSV = HttpPartSchema.tObjectSsv().build();
140
141   /** Tab-delimited object type */
142   public static final HttpPartSchema T_OBJECT_TSV = HttpPartSchema.tObjectTsv().build();
143
144   /** UON-formated object type */
145   public static final HttpPartSchema T_OBJECT_UON = HttpPartSchema.tObjectUon().build();
146
147
148   final String name;
149   final Set<Integer> codes;
150   final String _default;
151   final Set<String> _enum;
152   final Map<String,HttpPartSchema> properties;
153   final boolean allowEmptyValue, exclusiveMaximum, exclusiveMinimum, required, uniqueItems, skipIfEmpty;
154   final HttpPartCollectionFormat collectionFormat;
155   final HttpPartDataType type;
156   final HttpPartFormat format;
157   final Pattern pattern;
158   final HttpPartSchema items, additionalProperties;
159   final Number maximum, minimum, multipleOf;
160   final Long maxLength, minLength, maxItems, minItems, maxProperties, minProperties;
161   final Class<? extends HttpPartParser> parser;
162   final Class<? extends HttpPartSerializer> serializer;
163   final ClassMeta<?> parsedType;
164
165   /**
166    * Instantiates a new builder for this object.
167    *
168    * @return A new builder for this object.
169    */
170   public static HttpPartSchemaBuilder create() {
171      return new HttpPartSchemaBuilder();
172   }
173
174   /**
175    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>BOOLEAN</jsf>)</c>.
176    *
177    * @return A new builder for this object.
178    */
179   public static HttpPartSchemaBuilder tBoolean() {
180      return create().tBoolean();
181   }
182
183   /**
184    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>FILE</jsf>)</c>.
185    *
186    * @return A new builder for this object.
187    */
188   public static HttpPartSchemaBuilder tFile() {
189      return create().tFile();
190   }
191
192   /**
193    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>INTEGER</jsf>)</c>.
194    *
195    * @return A new builder for this object.
196    */
197   public static HttpPartSchemaBuilder tInteger() {
198      return create().tInteger();
199   }
200
201   /**
202    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>INTEGER</jsf>).format(HttpPartFormat.<jsf>INT32</jsf>)</c>.
203    *
204    * @return A new builder for this object.
205    */
206   public static HttpPartSchemaBuilder tInt32() {
207      return create().tInteger().fInt32();
208   }
209
210   /**
211    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>INTEGER</jsf>).format(HttpPartFormat.<jsf>INT64</jsf>)</c>.
212    *
213    * @return A new builder for this object.
214    */
215   public static HttpPartSchemaBuilder tInt64() {
216      return create().tInteger().fInt64();
217   }
218
219   /**
220    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>NONE</jsf>)</c>.
221    *
222    * @return A new builder for this object.
223    */
224   public static HttpPartSchemaBuilder tNone() {
225      return create().tNone();
226   }
227
228   /**
229    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>NUMBER</jsf>)</c>.
230    *
231    * @return A new builder for this object.
232    */
233   public static HttpPartSchemaBuilder tNumber() {
234      return create().tNumber();
235   }
236
237   /**
238    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>NUMBER</jsf>).format(HttpPartFormat.<jsf>FLOAT</jsf>)</c>.
239    *
240    * @return A new builder for this object.
241    */
242   public static HttpPartSchemaBuilder tFloat() {
243      return create().tNumber().fFloat();
244   }
245
246   /**
247    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>NUMBER</jsf>).format(HttpPartFormat.<jsf>DOUBLE</jsf>)</c>.
248    *
249    * @return A new builder for this object.
250    */
251   public static HttpPartSchemaBuilder tDouble() {
252      return create().tNumber().fDouble();
253   }
254
255   /**
256    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>STRING</jsf>)</c>.
257    *
258    * @return A new builder for this object.
259    */
260   public static HttpPartSchemaBuilder tString() {
261      return create().tString();
262   }
263
264   /**
265    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>STRING</jsf>).format(HttpPartFormat.<jsf>BYTE</jsf>)</c>.
266    *
267    * @return A new builder for this object.
268    */
269   public static HttpPartSchemaBuilder tByte() {
270      return create().tString().fByte();
271   }
272
273   /**
274    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>STRING</jsf>).format(HttpPartFormat.<jsf>BINARY</jsf>)</c>.
275    *
276    * @return A new builder for this object.
277    */
278   public static HttpPartSchemaBuilder tBinary() {
279      return create().tString().fBinary();
280   }
281
282   /**
283    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>STRING</jsf>).format(HttpPartFormat.<jsf>BINARY_SPACED</jsf>)</c>.
284    *
285    * @return A new builder for this object.
286    */
287   public static HttpPartSchemaBuilder tBinarySpaced() {
288      return create().tString().fBinarySpaced();
289   }
290
291   /**
292    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>STRING</jsf>).format(HttpPartFormat.<jsf>DATE</jsf>)</c>.
293    *
294    * @return A new builder for this object.
295    */
296   public static HttpPartSchemaBuilder tDate() {
297      return create().tString().fDate();
298   }
299
300   /**
301    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>STRING</jsf>).format(HttpPartFormat.<jsf>DATE_TIME</jsf>)</c>.
302    *
303    * @return A new builder for this object.
304    */
305   public static HttpPartSchemaBuilder tDateTime() {
306      return create().tString().fDateTime();
307   }
308
309   /**
310    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>STRING</jsf>).format(HttpPartFormat.<jsf>UON</jsf>)</c>.
311    *
312    * @return A new builder for this object.
313    */
314   public static HttpPartSchemaBuilder tUon() {
315      return create().tString().fUon();
316   }
317
318   /**
319    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>)</c>.
320    *
321    * @return A new builder for this object.
322    */
323   public static HttpPartSchemaBuilder tArray() {
324      return create().tArray();
325   }
326
327   /**
328    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).items(items)</c>.
329    *
330    * @param items The schema of the array items.
331    * @return A new builder for this object.
332    */
333   public static HttpPartSchemaBuilder tArray(HttpPartSchemaBuilder items) {
334      return create().tArray().items(items);
335   }
336
337   /**
338    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>CSV</jsf>)</c>.
339    *
340    * @return A new builder for this object.
341    */
342   public static HttpPartSchemaBuilder tArrayCsv() {
343      return create().tArray().cfCsv();
344   }
345
346   /**
347    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>CSV</jsf>).items(items)</c>.
348    *
349    * @param items The schema of the array items.
350    * @return A new builder for this object.
351    */
352   public static HttpPartSchemaBuilder tArrayCsv(HttpPartSchemaBuilder items) {
353      return create().tArray().cfCsv().items(items);
354   }
355
356   /**
357    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>PIPES</jsf>)</c>.
358    *
359    * @return A new builder for this object.
360    */
361   public static HttpPartSchemaBuilder tArrayPipes() {
362      return create().tArray().cfPipes();
363   }
364
365   /**
366    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>PIPES</jsf>).items(items)</c>.
367    *
368    * @param items The schema of the array items.
369    * @return A new builder for this object.
370    */
371   public static HttpPartSchemaBuilder tArrayPipes(HttpPartSchemaBuilder items) {
372      return create().tArray().cfPipes().items(items);
373   }
374
375   /**
376    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>SSV</jsf>)</c>.
377    *
378    * @return A new builder for this object.
379    */
380   public static HttpPartSchemaBuilder tArraySsv() {
381      return create().tArray().cfSsv();
382   }
383
384   /**
385    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>SSV</jsf>).items(items)</c>.
386    *
387    * @param items The schema of the array items.
388    * @return A new builder for this object.
389    */
390   public static HttpPartSchemaBuilder tArraySsv(HttpPartSchemaBuilder items) {
391      return create().tArray().cfSsv().items(items);
392   }
393
394   /**
395    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>TSV</jsf>)</c>.
396    *
397    * @return A new builder for this object.
398    */
399   public static HttpPartSchemaBuilder tArrayTsv() {
400      return create().tArray().cfTsv();
401   }
402
403   /**
404    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>TSV</jsf>).items(items)</c>.
405    *
406    * @param items The schema of the array items.
407    * @return A new builder for this object.
408    */
409   public static HttpPartSchemaBuilder tArrayTsv(HttpPartSchemaBuilder items) {
410      return create().tArray().cfTsv().items(items);
411   }
412
413   /**
414    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>UONC</jsf>)</c>.
415    *
416    * @return A new builder for this object.
417    */
418   public static HttpPartSchemaBuilder tArrayUon() {
419      return create().tArray().cfUon();
420   }
421
422   /**
423    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>UONC</jsf>).items(items)</c>.
424    *
425    * @param items The schema of the array items.
426    * @return A new builder for this object.
427    */
428   public static HttpPartSchemaBuilder tArrayUon(HttpPartSchemaBuilder items) {
429      return create().tArray().cfUon().items(items);
430   }
431
432   /**
433    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>MULTI</jsf>)</c>.
434    *
435    * @return A new builder for this object.
436    */
437   public static HttpPartSchemaBuilder tArrayMulti() {
438      return create().tArray().cfMulti();
439   }
440
441   /**
442    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>MULTI</jsf>).items(items)</c>.
443    *
444    * @param items The schema of the array items.
445    * @return A new builder for this object.
446    */
447   public static HttpPartSchemaBuilder tArrayMulti(HttpPartSchemaBuilder items) {
448      return create().tArray().cfMulti().items(items);
449   }
450
451   /**
452    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>OBJECT</jsf>)</c>.
453    *
454    * @return A new builder for this object.
455    */
456   public static HttpPartSchemaBuilder tObject() {
457      return create().tObject();
458   }
459
460   /**
461    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>OBJECT</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>CSV</jsf>)</c>.
462    *
463    * @return A new builder for this object.
464    */
465   public static HttpPartSchemaBuilder tObjectCsv() {
466      return create().tObject().cfCsv();
467   }
468
469   /**
470    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>OBJECT</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>PIPES</jsf>)</c>.
471    *
472    * @return A new builder for this object.
473    */
474   public static HttpPartSchemaBuilder tObjectPipes() {
475      return create().tObject().cfPipes();
476   }
477
478   /**
479    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>OBJECT</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>SSV</jsf>)</c>.
480    *
481    * @return A new builder for this object.
482    */
483   public static HttpPartSchemaBuilder tObjectSsv() {
484      return create().tObject().cfSsv();
485   }
486
487   /**
488    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>OBJECT</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>TSV</jsf>)</c>.
489    *
490    * @return A new builder for this object.
491    */
492   public static HttpPartSchemaBuilder tObjectTsv() {
493      return create().tObject().cfTsv();
494   }
495
496   /**
497    * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>OBJECT</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>UON</jsf>)</c>.
498    *
499    * @return A new builder for this object.
500    */
501   public static HttpPartSchemaBuilder tObjectUon() {
502      return create().tObject().cfUon();
503   }
504
505   /**
506    * Finds the schema information for the specified method parameter.
507    *
508    * <p>
509    * This method will gather all the schema information from the annotations at the following locations:
510    * <ul>
511    *    <li>The method parameter.
512    *    <li>The method parameter class.
513    *    <li>The method parameter parent classes and interfaces.
514    * </ul>
515    *
516    * @param c
517    *    The annotation to look for.
518    *    <br>Valid values:
519    *    <ul>
520    *       <li>{@link Body}
521    *       <li>{@link Header}
522    *       <li>{@link Query}
523    *       <li>{@link FormData}
524    *       <li>{@link Path}
525    *       <li>{@link Response}
526    *       <li>{@link ResponseHeader}
527    *       <li>{@link ResponseBody}
528    *       <li>{@link HasQuery}
529    *       <li>{@link HasFormData}
530    *    </ul>
531    * @param mpi The Java method parameter.
532    * @return The schema information about the parameter.
533    */
534   public static HttpPartSchema create(Class<? extends Annotation> c, ParamInfo mpi) {
535      return create().apply(c, mpi).build();
536   }
537
538   /**
539    * Finds the schema information for the specified method return.
540    *
541    * <p>
542    * This method will gather all the schema information from the annotations at the following locations:
543    * <ul>
544    *    <li>The method.
545    *    <li>The method return class.
546    *    <li>The method return parent classes and interfaces.
547    * </ul>
548    *
549    * @param c
550    *    The annotation to look for.
551    *    <br>Valid values:
552    *    <ul>
553    *       <li>{@link Body}
554    *       <li>{@link Header}
555    *       <li>{@link Query}
556    *       <li>{@link FormData}
557    *       <li>{@link Path}
558    *       <li>{@link Response}
559    *       <li>{@link ResponseHeader}
560    *       <li>{@link HasQuery}
561    *       <li>{@link HasFormData}
562    *    </ul>
563    * @param m
564    *    The Java method with the return type being checked.
565    * @return The schema information about the parameter.
566    */
567   public static HttpPartSchema create(Class<? extends Annotation> c, Method m) {
568      return create().apply(c, m).build();
569   }
570
571   /**
572    * Finds the schema information for the specified class.
573    *
574    * <p>
575    * This method will gather all the schema information from the annotations on the class and all parent classes/interfaces.
576    *
577    * @param c
578    *    The annotation to look for.
579    *    <br>Valid values:
580    *    <ul>
581    *       <li>{@link Body}
582    *       <li>{@link Header}
583    *       <li>{@link Query}
584    *       <li>{@link FormData}
585    *       <li>{@link Path}
586    *       <li>{@link Response}
587    *       <li>{@link ResponseHeader}
588    *       <li>{@link HasQuery}
589    *       <li>{@link HasFormData}
590    *    </ul>
591    * @param t
592    *    The class containing the parameter.
593    * @return The schema information about the parameter.
594    */
595   public static HttpPartSchema create(Class<? extends Annotation> c, java.lang.reflect.Type t) {
596      return create().apply(c, t).build();
597   }
598
599   /**
600    * Shortcut for calling <c>create().type(type);</c>
601    *
602    * @param type The schema type value.
603    * @return A new builder.
604    */
605   public static HttpPartSchemaBuilder create(String type) {
606      return create().type(type);
607   }
608
609   /**
610    * Shortcut for calling <c>create().type(type).format(format);</c>
611    *
612    * @param type The schema type value.
613    * @param format The schema format value.
614    * @return A new builder.
615    */
616   public static HttpPartSchemaBuilder create(String type, String format) {
617      return create().type(type).format(format);
618   }
619
620   /**
621    * Finds the schema information on the specified annotation.
622    *
623    * @param a
624    *    The annotation to find the schema information on..
625    * @return The schema information found on the annotation.
626    */
627   public static HttpPartSchema create(Annotation a) {
628      return create().apply(a).build();
629   }
630
631   /**
632    * Finds the schema information on the specified annotation.
633    *
634    * @param a
635    *    The annotation to find the schema information on..
636    * @param defaultName The default part name if not specified on the annotation.
637    * @return The schema information found on the annotation.
638    */
639   public static HttpPartSchema create(Annotation a, String defaultName) {
640      return create().name(defaultName).apply(a).build();
641   }
642
643   HttpPartSchema(HttpPartSchemaBuilder b) {
644      this.name = b.name;
645      this.codes = copy(b.codes);
646      this._default = b._default;
647      this._enum = copy(b._enum);
648      this.properties = build(b.properties, b.noValidate);
649      this.allowEmptyValue = resolve(b.allowEmptyValue);
650      this.exclusiveMaximum = resolve(b.exclusiveMaximum);
651      this.exclusiveMinimum = resolve(b.exclusiveMinimum);
652      this.required = resolve(b.required);
653      this.uniqueItems = resolve(b.uniqueItems);
654      this.skipIfEmpty = resolve(b.skipIfEmpty);
655      this.collectionFormat = b.collectionFormat;
656      this.type = b.type;
657      this.format = b.format;
658      this.pattern = b.pattern;
659      this.items = build(b.items, b.noValidate);
660      this.additionalProperties = build(b.additionalProperties, b.noValidate);
661      this.maximum = b.maximum;
662      this.minimum = b.minimum;
663      this.multipleOf = b.multipleOf;
664      this.maxItems = b.maxItems;
665      this.maxLength = b.maxLength;
666      this.maxProperties = b.maxProperties;
667      this.minItems = b.minItems;
668      this.minLength = b.minLength;
669      this.minProperties = b.minProperties;
670      this.parser = b.parser;
671      this.serializer = b.serializer;
672
673      // Calculate parse type
674      Class<?> parsedType = Object.class;
675      if (type == ARRAY) {
676         if (items != null)
677            parsedType = Array.newInstance(items.parsedType.getInnerClass(), 0).getClass();
678      } else if (type == BOOLEAN) {
679         parsedType = Boolean.class;
680      } else if (type == INTEGER) {
681         if (format == INT64)
682            parsedType = Long.class;
683         else
684            parsedType = Integer.class;
685      } else if (type == NUMBER) {
686         if (format == DOUBLE)
687            parsedType = Double.class;
688         else
689            parsedType = Float.class;
690      } else if (type == STRING) {
691         if (format == BYTE || format == BINARY || format == BINARY_SPACED)
692            parsedType = byte[].class;
693         else if (format == DATE || format == DATE_TIME)
694            parsedType = Calendar.class;
695         else
696            parsedType = String.class;
697      }
698      this.parsedType = BeanContext.DEFAULT.getClassMeta(parsedType);
699
700      if (b.noValidate)
701         return;
702
703      // Validation.
704      List<String> errors = new ArrayList<>();
705      AList<String> notAllowed = AList.of();
706      boolean invalidFormat = false;
707      switch (type) {
708         case STRING: {
709            notAllowed.aif(properties != null, "properties");
710            notAllowed.aif(additionalProperties != null, "additionalProperties");
711            notAllowed.aif(exclusiveMaximum, "exclusiveMaximum");
712            notAllowed.aif(exclusiveMinimum, "exclusiveMinimum");
713            notAllowed.aif(uniqueItems, "uniqueItems");
714            notAllowed.aif(collectionFormat != HttpPartCollectionFormat.NO_COLLECTION_FORMAT, "collectionFormat");
715            notAllowed.aif(items != null, "items");
716            notAllowed.aif(maximum != null, "maximum");
717            notAllowed.aif(minimum != null, "minimum");
718            notAllowed.aif(multipleOf != null, "multipleOf");
719            notAllowed.aif(maxItems != null, "maxItems");
720            notAllowed.aif(minItems != null, "minItems");
721            notAllowed.aif(minProperties != null, "minProperties");
722            invalidFormat = ! format.isOneOf(HttpPartFormat.BYTE, HttpPartFormat.BINARY, HttpPartFormat.BINARY_SPACED, HttpPartFormat.DATE, HttpPartFormat.DATE_TIME, HttpPartFormat.PASSWORD, HttpPartFormat.UON, HttpPartFormat.NO_FORMAT);
723            break;
724         }
725         case ARRAY: {
726            notAllowed.aif(properties != null, "properties");
727            notAllowed.aif(additionalProperties != null, "additionalProperties");
728            notAllowed.aif(exclusiveMaximum, "exclusiveMaximum");
729            notAllowed.aif(exclusiveMinimum, "exclusiveMinimum");
730            notAllowed.aif(pattern != null, "pattern");
731            notAllowed.aif(maximum != null, "maximum");
732            notAllowed.aif(minimum != null, "minimum");
733            notAllowed.aif(multipleOf != null, "multipleOf");
734            notAllowed.aif(maxLength != null, "maxLength");
735            notAllowed.aif(minLength != null, "minLength");
736            notAllowed.aif(maxProperties != null, "maxProperties");
737            notAllowed.aif(minProperties != null, "minProperties");
738            invalidFormat = ! format.isOneOf(HttpPartFormat.NO_FORMAT, HttpPartFormat.UON);
739            break;
740         }
741         case BOOLEAN: {
742            notAllowed.aif(! _enum.isEmpty(), "_enum");
743            notAllowed.aif(properties != null, "properties");
744            notAllowed.aif(additionalProperties != null, "additionalProperties");
745            notAllowed.aif(exclusiveMaximum, "exclusiveMaximum");
746            notAllowed.aif(exclusiveMinimum, "exclusiveMinimum");
747            notAllowed.aif(uniqueItems, "uniqueItems");
748            notAllowed.aif(collectionFormat != HttpPartCollectionFormat.NO_COLLECTION_FORMAT, "collectionFormat");
749            notAllowed.aif(pattern != null, "pattern");
750            notAllowed.aif(items != null, "items");
751            notAllowed.aif(maximum != null, "maximum");
752            notAllowed.aif(minimum != null, "minimum");
753            notAllowed.aif(multipleOf != null, "multipleOf");
754            notAllowed.aif(maxItems != null, "maxItems");
755            notAllowed.aif(maxLength != null, "maxLength");
756            notAllowed.aif(maxProperties != null, "maxProperties");
757            notAllowed.aif(minItems != null, "minItems");
758            notAllowed.aif(minLength != null, "minLength");
759            notAllowed.aif(minProperties != null, "minProperties");
760            invalidFormat = ! format.isOneOf(HttpPartFormat.NO_FORMAT, HttpPartFormat.UON);
761            break;
762         }
763         case FILE: {
764            break;
765         }
766         case INTEGER: {
767            notAllowed.aif(properties != null, "properties");
768            notAllowed.aif(additionalProperties != null, "additionalProperties");
769            notAllowed.aif(uniqueItems, "uniqueItems");
770            notAllowed.aif(collectionFormat != HttpPartCollectionFormat.NO_COLLECTION_FORMAT, "collectionFormat");
771            notAllowed.aif(pattern != null, "pattern");
772            notAllowed.aif(items != null, "items");
773            notAllowed.aif(maxItems != null, "maxItems");
774            notAllowed.aif(maxLength != null, "maxLength");
775            notAllowed.aif(maxProperties != null, "maxProperties");
776            notAllowed.aif(minItems != null, "minItems");
777            notAllowed.aif(minLength != null, "minLength");
778            notAllowed.aif(minProperties != null, "minProperties");
779            invalidFormat = ! format.isOneOf(HttpPartFormat.NO_FORMAT, HttpPartFormat.UON, HttpPartFormat.INT32, HttpPartFormat.INT64);
780            break;
781         }
782         case NUMBER: {
783            notAllowed.aif(properties != null, "properties");
784            notAllowed.aif(additionalProperties != null, "additionalProperties");
785            notAllowed.aif(uniqueItems, "uniqueItems");
786            notAllowed.aif(collectionFormat != HttpPartCollectionFormat.NO_COLLECTION_FORMAT, "collectionFormat");
787            notAllowed.aif(pattern != null, "pattern");
788            notAllowed.aif(items != null, "items");
789            notAllowed.aif(maxItems != null, "maxItems");
790            notAllowed.aif(maxLength != null, "maxLength");
791            notAllowed.aif(maxProperties != null, "maxProperties");
792            notAllowed.aif(minItems != null, "minItems");
793            notAllowed.aif(minLength != null, "minLength");
794            notAllowed.aif(minProperties != null, "minProperties");
795            invalidFormat = ! format.isOneOf(HttpPartFormat.NO_FORMAT, HttpPartFormat.UON, HttpPartFormat.FLOAT, HttpPartFormat.DOUBLE);
796            break;
797         }
798         case OBJECT: {
799            notAllowed.aif(exclusiveMaximum, "exclusiveMaximum");
800            notAllowed.aif(exclusiveMinimum, "exclusiveMinimum");
801            notAllowed.aif(uniqueItems, "uniqueItems");
802            notAllowed.aif(pattern != null, "pattern");
803            notAllowed.aif(items != null, "items");
804            notAllowed.aif(maximum != null, "maximum");
805            notAllowed.aif(minimum != null, "minimum");
806            notAllowed.aif(multipleOf != null, "multipleOf");
807            notAllowed.aif(maxItems != null, "maxItems");
808            notAllowed.aif(maxLength != null, "maxLength");
809            notAllowed.aif(minItems != null, "minItems");
810            notAllowed.aif(minLength != null, "minLength");
811            invalidFormat = ! format.isOneOf(HttpPartFormat.NO_FORMAT);
812            break;
813         }
814         default:
815            break;
816      }
817
818      if (! notAllowed.isEmpty())
819         errors.add("Attributes not allow for type='"+type+"': " + StringUtils.join(notAllowed, ","));
820      if (invalidFormat)
821         errors.add("Invalid format for type='"+type+"': '"+format+"'");
822      if (exclusiveMaximum && maximum == null)
823         errors.add("Cannot specify exclusiveMaximum with maximum.");
824      if (exclusiveMinimum && minimum == null)
825         errors.add("Cannot specify exclusiveMinimum with minimum.");
826      if (required && _default != null)
827         errors.add("Cannot specify a default value on a required value.");
828      if (minLength != null && maxLength != null && maxLength < minLength)
829         errors.add("maxLength cannot be less than minLength.");
830      if (minimum != null && maximum != null && maximum.doubleValue() < minimum.doubleValue())
831         errors.add("maximum cannot be less than minimum.");
832      if (minItems != null && maxItems != null && maxItems < minItems)
833         errors.add("maxItems cannot be less than minItems.");
834      if (minProperties != null && maxProperties != null && maxProperties < minProperties)
835         errors.add("maxProperties cannot be less than minProperties.");
836      if (minLength != null && minLength < 0)
837         errors.add("minLength cannot be less than zero.");
838      if (maxLength != null && maxLength < 0)
839         errors.add("maxLength cannot be less than zero.");
840      if (minItems != null && minItems < 0)
841         errors.add("minItems cannot be less than zero.");
842      if (maxItems != null && maxItems < 0)
843         errors.add("maxItems cannot be less than zero.");
844      if (minProperties != null && minProperties < 0)
845         errors.add("minProperties cannot be less than zero.");
846      if (maxProperties != null && maxProperties < 0)
847         errors.add("maxProperties cannot be less than zero.");
848      if (type == ARRAY && items != null && items.getType() == OBJECT && (format != UON && format != HttpPartFormat.NO_FORMAT))
849         errors.add("Cannot define an array of objects unless array format is 'uon'.");
850
851      if (! errors.isEmpty())
852         throw new ContextRuntimeException("Schema specification errors: \n\t" + join(errors, "\n\t"), new Object[0]);
853   }
854
855   /**
856    * Returns the default parsed type for this schema.
857    *
858    * @return The default parsed type for this schema.  Never <jk>null</jk>.
859    */
860   public ClassMeta<?> getParsedType() {
861      return parsedType;
862   }
863
864   /**
865    * Returns the name of the object described by this schema, for example the query or form parameter name.
866    *
867    * @return The name, or <jk>null</jk> if not specified.
868    * @see HttpPartSchemaBuilder#name(String)
869    */
870   public String getName() {
871      return name;
872   }
873
874   /**
875    * Returns the HTTP status code or codes defined on a schema.
876    *
877    * @return
878    *    The list of HTTP status codes.
879    *    <br>Never <jk>null</jk>.
880    * @see HttpPartSchemaBuilder#code(int)
881    * @see HttpPartSchemaBuilder#codes(int[])
882    */
883   public Set<Integer> getCodes() {
884      return codes;
885   }
886
887   /**
888    * Returns the HTTP status code or codes defined on a schema.
889    *
890    * @param def The default value if there are no codes defined.
891    * @return
892    *    The list of HTTP status codes.
893    *    <br>A singleton set containing the default value if the set is empty.
894    *    <br>Never <jk>null</jk>.
895    * @see HttpPartSchemaBuilder#code(int)
896    * @see HttpPartSchemaBuilder#codes(int[])
897    */
898   public Set<Integer> getCodes(Integer def) {
899      return codes.isEmpty() ? Collections.singleton(def) : codes;
900   }
901
902   /**
903    * Returns the first HTTP status code on a schema.
904    *
905    * @param def The default value if there are no codes defined.
906    * @return
907    *    The list of HTTP status codes.
908    *    <br>A singleton set containing the default value if the set is empty.
909    *    <br>Never <jk>null</jk>.
910    * @see HttpPartSchemaBuilder#code(int)
911    * @see HttpPartSchemaBuilder#codes(int[])
912    */
913   public Integer getCode(Integer def) {
914      return codes.isEmpty() ? def : codes.iterator().next();
915   }
916
917   /**
918    * Returns the <c>type</c> field of this schema.
919    *
920    * @return The <c>type</c> field of this schema, or <jk>null</jk> if not specified.
921    * @see HttpPartSchemaBuilder#type(String)
922    */
923   public HttpPartDataType getType() {
924      return type;
925   }
926
927   /**
928    * Returns the <c>type</c> field of this schema.
929    *
930    * @param cm
931    *    The class meta of the object.
932    *    <br>Used to auto-detect the type if the type was not specified.
933    * @return The format field of this schema, or <jk>null</jk> if not specified.
934    * @see HttpPartSchemaBuilder#format(String)
935    */
936   public HttpPartDataType getType(ClassMeta<?> cm) {
937      if (type != HttpPartDataType.NO_TYPE)
938         return type;
939      if (cm.isTemporal() || cm.isDateOrCalendar())
940         return HttpPartDataType.STRING;
941      if (cm.isNumber()) {
942         if (cm.isDecimal())
943            return HttpPartDataType.NUMBER;
944         return HttpPartDataType.INTEGER;
945      }
946      if (cm.isBoolean())
947         return HttpPartDataType.BOOLEAN;
948      if (cm.isMapOrBean())
949         return HttpPartDataType.OBJECT;
950      if (cm.isCollectionOrArray())
951         return HttpPartDataType.ARRAY;
952      return HttpPartDataType.STRING;
953   }
954
955   /**
956    * Returns the <c>default</c> field of this schema.
957    *
958    * @return The default value for this schema, or <jk>null</jk> if not specified.
959    * @see HttpPartSchemaBuilder#_default(String)
960    */
961   public String getDefault() {
962      return _default;
963   }
964
965   /**
966    * Returns the <c>collectionFormat</c> field of this schema.
967    *
968    * @return The <c>collectionFormat</c> field of this schema, or <jk>null</jk> if not specified.
969    * @see HttpPartSchemaBuilder#collectionFormat(String)
970    */
971   public HttpPartCollectionFormat getCollectionFormat() {
972      return collectionFormat;
973   }
974
975   /**
976    * Returns the <c>format</c> field of this schema.
977    *
978    * @see HttpPartSchemaBuilder#format(String)
979    * @return The <c>format</c> field of this schema, or <jk>null</jk> if not specified.
980    */
981   public HttpPartFormat getFormat() {
982      return format;
983   }
984
985   /**
986    * Returns the <c>format</c> field of this schema.
987    *
988    * @param cm
989    *    The class meta of the object.
990    *    <br>Used to auto-detect the format if the format was not specified.
991    * @return The <c>format</c> field of this schema, or <jk>null</jk> if not specified.
992    * @see HttpPartSchemaBuilder#format(String)
993    */
994   public HttpPartFormat getFormat(ClassMeta<?> cm) {
995      if (format != HttpPartFormat.NO_FORMAT)
996         return format;
997      if (cm.isNumber()) {
998         if (cm.isDecimal()) {
999            if (cm.isDouble())
1000               return HttpPartFormat.DOUBLE;
1001            return HttpPartFormat.FLOAT;
1002         }
1003         if (cm.isLong())
1004            return HttpPartFormat.INT64;
1005         return HttpPartFormat.INT32;
1006      }
1007      return format;
1008   }
1009
1010   /**
1011    * Returns the <c>maximum</c> field of this schema.
1012    *
1013    * @return The schema for child items of the object represented by this schema, or <jk>null</jk> if not defined.
1014    * @see HttpPartSchemaBuilder#items(HttpPartSchemaBuilder)
1015    */
1016   public HttpPartSchema getItems() {
1017      return items;
1018   }
1019
1020   /**
1021    * Returns the <c>maximum</c> field of this schema.
1022    *
1023    * @return The <c>maximum</c> field of this schema, or <jk>null</jk> if not specified.
1024    * @see HttpPartSchemaBuilder#maximum(Number)
1025    */
1026   public Number getMaximum() {
1027      return maximum;
1028   }
1029
1030   /**
1031    * Returns the <c>minimum</c> field of this schema.
1032    *
1033    * @return The <c>minimum</c> field of this schema, or <jk>null</jk> if not specified.
1034    * @see HttpPartSchemaBuilder#minimum(Number)
1035    */
1036   public Number getMinimum() {
1037      return minimum;
1038   }
1039
1040   /**
1041    * Returns the <c>xxx</c> field of this schema.
1042    *
1043    * @return The <c>xxx</c> field of this schema, or <jk>null</jk> if not specified.
1044    * @see HttpPartSchemaBuilder#multipleOf(Number)
1045    */
1046   public Number getMultipleOf() {
1047      return multipleOf;
1048   }
1049
1050   /**
1051    * Returns the <c>xxx</c> field of this schema.
1052    *
1053    * @return The <c>xxx</c> field of this schema, or <jk>null</jk> if not specified.
1054    * @see HttpPartSchemaBuilder#pattern(String)
1055    */
1056   public Pattern getPattern() {
1057      return pattern;
1058   }
1059
1060   /**
1061    * Returns the <c>xxx</c> field of this schema.
1062    *
1063    * @return The <c>xxx</c> field of this schema, or <jk>null</jk> if not specified.
1064    * @see HttpPartSchemaBuilder#maxLength(Long)
1065    */
1066   public Long getMaxLength() {
1067      return maxLength;
1068   }
1069
1070   /**
1071    * Returns the <c>xxx</c> field of this schema.
1072    *
1073    * @return The <c>xxx</c> field of this schema, or <jk>null</jk> if not specified.
1074    * @see HttpPartSchemaBuilder#minLength(Long)
1075    */
1076   public Long getMinLength() {
1077      return minLength;
1078   }
1079
1080   /**
1081    * Returns the <c>xxx</c> field of this schema.
1082    *
1083    * @return The <c>xxx</c> field of this schema, or <jk>null</jk> if not specified.
1084    * @see HttpPartSchemaBuilder#maxItems(Long)
1085    */
1086   public Long getMaxItems() {
1087      return maxItems;
1088   }
1089
1090   /**
1091    * Returns the <c>xxx</c> field of this schema.
1092    *
1093    * @return The <c>xxx</c> field of this schema, or <jk>null</jk> if not specified.
1094    * @see HttpPartSchemaBuilder#minItems(Long)
1095    */
1096   public Long getMinItems() {
1097      return minItems;
1098   }
1099
1100   /**
1101    * Returns the <c>xxx</c> field of this schema.
1102    *
1103    * @return The <c>xxx</c> field of this schema, or <jk>null</jk> if not specified.
1104    * @see HttpPartSchemaBuilder#maxProperties(Long)
1105    */
1106   public Long getMaxProperties() {
1107      return maxProperties;
1108   }
1109
1110   /**
1111    * Returns the <c>xxx</c> field of this schema.
1112    *
1113    * @return The <c>xxx</c> field of this schema, or <jk>null</jk> if not specified.
1114    * @see HttpPartSchemaBuilder#minProperties(Long)
1115    */
1116   public Long getMinProperties() {
1117      return minProperties;
1118   }
1119
1120   /**
1121    * Returns the <c>exclusiveMaximum</c> field of this schema.
1122    *
1123    * @return The <c>exclusiveMaximum</c> field of this schema.
1124    * @see HttpPartSchemaBuilder#exclusiveMaximum(Boolean)
1125    */
1126   public boolean isExclusiveMaximum() {
1127      return exclusiveMaximum;
1128   }
1129
1130   /**
1131    * Returns the <c>exclusiveMinimum</c> field of this schema.
1132    *
1133    * @return The <c>exclusiveMinimum</c> field of this schema.
1134    * @see HttpPartSchemaBuilder#exclusiveMinimum(Boolean)
1135    */
1136   public boolean isExclusiveMinimum() {
1137      return exclusiveMinimum;
1138   }
1139
1140   /**
1141    * Returns the <c>uniqueItems</c> field of this schema.
1142    *
1143    * @return The <c>uniqueItems</c> field of this schema.
1144    * @see HttpPartSchemaBuilder#uniqueItems(Boolean)
1145    */
1146   public boolean isUniqueItems() {
1147      return uniqueItems;
1148   }
1149
1150   /**
1151    * Returns the <c>required</c> field of this schema.
1152    *
1153    * @return The <c>required</c> field of this schema.
1154    * @see HttpPartSchemaBuilder#required(Boolean)
1155    */
1156   public boolean isRequired() {
1157      return required;
1158   }
1159
1160   /**
1161    * Returns the <c>skipIfEmpty</c> field of this schema.
1162    *
1163    * @return The <c>skipIfEmpty</c> field of this schema.
1164    * @see HttpPartSchemaBuilder#skipIfEmpty(Boolean)
1165    */
1166   public boolean isSkipIfEmpty() {
1167      return skipIfEmpty;
1168   }
1169
1170   /**
1171    * Returns the <c>allowEmptyValue</c> field of this schema.
1172    *
1173    * @return The <c>skipIfEmpty</c> field of this schema.
1174    * @see HttpPartSchemaBuilder#skipIfEmpty(Boolean)
1175    */
1176   public boolean isAllowEmptyValue() {
1177      return allowEmptyValue;
1178   }
1179
1180   /**
1181    * Returns the <c>enum</c> field of this schema.
1182    *
1183    * @return The <c>enum</c> field of this schema, or <jk>null</jk> if not specified.
1184    * @see HttpPartSchemaBuilder#_enum(Set)
1185    */
1186   public Set<String> getEnum() {
1187      return _enum;
1188   }
1189
1190   /**
1191    * Returns the <c>parser</c> field of this schema.
1192    *
1193    * @return The <c>parser</c> field of this schema, or <jk>null</jk> if not specified.
1194    * @see HttpPartSchemaBuilder#parser(Class)
1195    */
1196   public Class<? extends HttpPartParser> getParser() {
1197      return parser;
1198   }
1199
1200   /**
1201    * Returns the <c>serializer</c> field of this schema.
1202    *
1203    * @return The <c>serializer</c> field of this schema, or <jk>null</jk> if not specified.
1204    * @see HttpPartSchemaBuilder#serializer(Class)
1205    */
1206   public Class<? extends HttpPartSerializer> getSerializer() {
1207      return serializer;
1208   }
1209
1210   /**
1211    * Throws a {@link ParseException} if the specified pre-parsed input does not validate against this schema.
1212    *
1213    * @param in The input.
1214    * @return The same object passed in.
1215    * @throws SchemaValidationException if the specified pre-parsed input does not validate against this schema.
1216    */
1217   public String validateInput(String in) throws SchemaValidationException {
1218      if (! isValidRequired(in))
1219         throw new SchemaValidationException("No value specified.");
1220      if (in != null) {
1221         if (! isValidAllowEmpty(in))
1222            throw new SchemaValidationException("Empty value not allowed.");
1223         if (! isValidPattern(in))
1224            throw new SchemaValidationException("Value does not match expected pattern.  Must match pattern: {0}", pattern.pattern());
1225         if (! isValidEnum(in))
1226            throw new SchemaValidationException("Value does not match one of the expected values.  Must be one of the following: {0}", _enum);
1227         if (! isValidMaxLength(in))
1228            throw new SchemaValidationException("Maximum length of value exceeded.");
1229         if (! isValidMinLength(in))
1230            throw new SchemaValidationException("Minimum length of value not met.");
1231      }
1232      return in;
1233   }
1234
1235   /**
1236    * Throws a {@link ParseException} if the specified parsed output does not validate against this schema.
1237    *
1238    * @param o The parsed output.
1239    * @param bc The bean context used to detect POJO types.
1240    * @return The same object passed in.
1241    * @throws SchemaValidationException if the specified parsed output does not validate against this schema.
1242    */
1243   @SuppressWarnings("rawtypes")
1244   public <T> T validateOutput(T o, BeanContext bc) throws SchemaValidationException {
1245      if (o == null) {
1246         if (! isValidRequired(o))
1247            throw new SchemaValidationException("Required value not provided.");
1248         return o;
1249      }
1250      ClassMeta<?> cm = bc.getClassMetaForObject(o);
1251      switch (getType(cm)) {
1252         case ARRAY: {
1253            if (cm.isArray()) {
1254               if (! isValidMinItems(o))
1255                  throw new SchemaValidationException("Minimum number of items not met.");
1256               if (! isValidMaxItems(o))
1257                  throw new SchemaValidationException("Maximum number of items exceeded.");
1258               if (! isValidUniqueItems(o))
1259                  throw new SchemaValidationException("Duplicate items not allowed.");
1260               HttpPartSchema items = getItems();
1261               if (items != null)
1262                  for (int i = 0; i < Array.getLength(o); i++)
1263                     items.validateOutput(Array.get(o, i), bc);
1264            } else if (cm.isCollection()) {
1265               Collection<?> c = (Collection<?>)o;
1266               if (! isValidMinItems(c))
1267                  throw new SchemaValidationException("Minimum number of items not met.");
1268               if (! isValidMaxItems(c))
1269                  throw new SchemaValidationException("Maximum number of items exceeded.");
1270               if (! isValidUniqueItems(c))
1271                  throw new SchemaValidationException("Duplicate items not allowed.");
1272               HttpPartSchema items = getItems();
1273               if (items != null)
1274                  for (Object o2 : c)
1275                     items.validateOutput(o2, bc);
1276            }
1277            break;
1278         }
1279         case INTEGER: {
1280            if (cm.isNumber()) {
1281               Number n = (Number)o;
1282               if (! isValidMinimum(n))
1283                  throw new SchemaValidationException("Minimum value not met.");
1284               if (! isValidMaximum(n))
1285                  throw new SchemaValidationException("Maximum value exceeded.");
1286               if (! isValidMultipleOf(n))
1287                  throw new SchemaValidationException("Multiple-of not met.");
1288            }
1289            break;
1290         }
1291         case NUMBER: {
1292            if (cm.isNumber()) {
1293               Number n = (Number)o;
1294               if (! isValidMinimum(n))
1295                  throw new SchemaValidationException("Minimum value not met.");
1296               if (! isValidMaximum(n))
1297                  throw new SchemaValidationException("Maximum value exceeded.");
1298               if (! isValidMultipleOf(n))
1299                  throw new SchemaValidationException("Multiple-of not met.");
1300            }
1301            break;
1302         }
1303         case OBJECT: {
1304            if (cm.isMapOrBean()) {
1305               Map<?,?> m = cm.isMap() ? (Map<?,?>)o : bc.createSession().toBeanMap(o);
1306               if (! isValidMinProperties(m))
1307                  throw new SchemaValidationException("Minimum number of properties not met.");
1308               if (! isValidMaxProperties(m))
1309                  throw new SchemaValidationException("Maximum number of properties exceeded.");
1310               for (Map.Entry e : m.entrySet()) {
1311                  String key = e.getKey().toString();
1312                  HttpPartSchema s2 = getProperty(key);
1313                  if (s2 != null)
1314                     s2.validateOutput(e.getValue(), bc);
1315               }
1316            } else if (cm.isBean()) {
1317
1318            }
1319            break;
1320         }
1321         case BOOLEAN:
1322         case FILE:
1323         case STRING:
1324         case NO_TYPE:
1325            break;
1326      }
1327      return o;
1328   }
1329
1330   //-----------------------------------------------------------------------------------------------------------------
1331   // Helper methods.
1332   //-----------------------------------------------------------------------------------------------------------------
1333
1334   private boolean isValidRequired(Object x) {
1335      return x != null || ! required;
1336   }
1337
1338   private boolean isValidMinProperties(Map<?,?> x) {
1339      return minProperties == null || x.size() >= minProperties;
1340   }
1341
1342   private boolean isValidMaxProperties(Map<?,?> x) {
1343      return maxProperties == null || x.size() <= maxProperties;
1344   }
1345
1346   private boolean isValidMinimum(Number x) {
1347      if (x instanceof Integer || x instanceof AtomicInteger)
1348         return minimum == null || x.intValue() > minimum.intValue() || (x.intValue() == minimum.intValue() && (! exclusiveMinimum));
1349      if (x instanceof Short || x instanceof Byte)
1350         return minimum == null || x.shortValue() > minimum.shortValue() || (x.intValue() == minimum.shortValue() && (! exclusiveMinimum));
1351      if (x instanceof Long || x instanceof AtomicLong || x instanceof BigInteger)
1352         return minimum == null || x.longValue() > minimum.longValue() || (x.intValue() == minimum.longValue() && (! exclusiveMinimum));
1353      if (x instanceof Float)
1354         return minimum == null || x.floatValue() > minimum.floatValue() || (x.floatValue() == minimum.floatValue() && (! exclusiveMinimum));
1355      if (x instanceof Double || x instanceof BigDecimal)
1356         return minimum == null || x.doubleValue() > minimum.doubleValue() || (x.doubleValue() == minimum.doubleValue() && (! exclusiveMinimum));
1357      return true;
1358   }
1359
1360   private boolean isValidMaximum(Number x) {
1361      if (x instanceof Integer || x instanceof AtomicInteger)
1362         return maximum == null || x.intValue() < maximum.intValue() || (x.intValue() == maximum.intValue() && (! exclusiveMaximum));
1363      if (x instanceof Short || x instanceof Byte)
1364         return maximum == null || x.shortValue() < maximum.shortValue() || (x.intValue() == maximum.shortValue() && (! exclusiveMaximum));
1365      if (x instanceof Long || x instanceof AtomicLong || x instanceof BigInteger)
1366         return maximum == null || x.longValue() < maximum.longValue() || (x.intValue() == maximum.longValue() && (! exclusiveMaximum));
1367      if (x instanceof Float)
1368         return maximum == null || x.floatValue() < maximum.floatValue() || (x.floatValue() == maximum.floatValue() && (! exclusiveMaximum));
1369      if (x instanceof Double || x instanceof BigDecimal)
1370         return maximum == null || x.doubleValue() < maximum.doubleValue() || (x.doubleValue() == maximum.doubleValue() && (! exclusiveMaximum));
1371      return true;
1372   }
1373
1374   private boolean isValidMultipleOf(Number x) {
1375      if (x instanceof Integer || x instanceof AtomicInteger)
1376         return multipleOf == null || x.intValue() % multipleOf.intValue() == 0;
1377      if (x instanceof Short || x instanceof Byte)
1378         return multipleOf == null || x.shortValue() % multipleOf.shortValue() == 0;
1379      if (x instanceof Long || x instanceof AtomicLong || x instanceof BigInteger)
1380         return multipleOf == null || x.longValue() % multipleOf.longValue() == 0;
1381      if (x instanceof Float)
1382         return multipleOf == null || x.floatValue() % multipleOf.floatValue() == 0;
1383      if (x instanceof Double || x instanceof BigDecimal)
1384         return multipleOf == null || x.doubleValue() % multipleOf.doubleValue() == 0;
1385      return true;
1386   }
1387
1388   private boolean isValidAllowEmpty(String x) {
1389      return allowEmptyValue || isNotEmpty(x);
1390   }
1391
1392   private boolean isValidPattern(String x) {
1393      return pattern == null || pattern.matcher(x).matches();
1394   }
1395
1396   private boolean isValidEnum(String x) {
1397      return _enum.isEmpty() || _enum.contains(x);
1398   }
1399
1400   private boolean isValidMinLength(String x) {
1401      return minLength == null || x.length() >= minLength;
1402   }
1403
1404   private boolean isValidMaxLength(String x) {
1405      return maxLength == null || x.length() <= maxLength;
1406   }
1407
1408   private boolean isValidMinItems(Object x) {
1409      return minItems == null || Array.getLength(x) >= minItems;
1410   }
1411
1412   private boolean isValidMaxItems(Object x) {
1413      return maxItems == null || Array.getLength(x) <= maxItems;
1414   }
1415
1416   private boolean isValidUniqueItems(Object x) {
1417      if (uniqueItems) {
1418         Set<Object> s = new HashSet<>();
1419         for (int i = 0; i < Array.getLength(x); i++) {
1420            Object o = Array.get(x, i);
1421            if (! s.add(o))
1422               return false;
1423         }
1424      }
1425      return true;
1426   }
1427
1428   private boolean isValidMinItems(Collection<?> x) {
1429      return minItems == null || x.size() >= minItems;
1430   }
1431
1432   private boolean isValidMaxItems(Collection<?> x) {
1433      return maxItems == null || x.size() <= maxItems;
1434   }
1435
1436   private boolean isValidUniqueItems(Collection<?> x) {
1437      if (uniqueItems && ! (x instanceof Set)) {
1438         Set<Object> s = new HashSet<>();
1439         for (Object o : x)
1440            if (! s.add(o))
1441               return false;
1442      }
1443      return true;
1444   }
1445
1446   /**
1447    * Returns the schema information for the specified property.
1448    *
1449    * @param name The property name.
1450    * @return The schema information for the specified property, or <jk>null</jk> if properties are not defined on this schema.
1451    */
1452   public HttpPartSchema getProperty(String name) {
1453      if (properties != null) {
1454         HttpPartSchema schema = properties.get(name);
1455         if (schema != null)
1456            return schema;
1457      }
1458      return additionalProperties;
1459   }
1460
1461   /**
1462    * Returns <jk>true</jk> if this schema has properties associated with it.
1463    *
1464    * @return <jk>true</jk> if this schema has properties associated with it.
1465    */
1466   public boolean hasProperties() {
1467      return properties != null || additionalProperties != null;
1468   }
1469
1470   private static <T> Set<T> copy(Set<T> in) {
1471      return in == null ? Collections.emptySet() : unmodifiableSet(new LinkedHashSet<>(in));
1472   }
1473
1474   private static Map<String,HttpPartSchema> build(Map<String,Object> in, boolean noValidate) {
1475      if (in == null)
1476         return null;
1477      Map<String,HttpPartSchema> m = new LinkedHashMap<>();
1478      for (Map.Entry<String,Object> e : in.entrySet()) {
1479         Object v = e.getValue();
1480         m.put(e.getKey(), build(v, noValidate));
1481      }
1482      return unmodifiableMap(m);
1483   }
1484
1485   private static HttpPartSchema build(Object in, boolean noValidate) {
1486      if (in == null)
1487         return null;
1488      if (in instanceof HttpPartSchema)
1489         return (HttpPartSchema)in;
1490      return ((HttpPartSchemaBuilder)in).noValidate(noValidate).build();
1491   }
1492
1493   //-----------------------------------------------------------------------------------------------------------------
1494   // Helper methods.
1495   //-----------------------------------------------------------------------------------------------------------------
1496
1497   private boolean resolve(Boolean b) {
1498      return b == null ? false : b;
1499   }
1500
1501   final static Set<String> toSet(String[]...s) {
1502      for (String[] ss : s)
1503         if (ss != null && ss.length > 0)
1504            return toSet(joinnl(ss));
1505      return null;
1506   }
1507
1508   final static Set<String> toSet(String s) {
1509      if (isEmpty(s))
1510         return null;
1511      Set<String> set = ASet.of();
1512      try {
1513         for (Object o : StringUtils.parseListOrCdl(s))
1514            set.add(o.toString());
1515      } catch (ParseException e) {
1516         throw new RuntimeException(e);
1517      }
1518      return set;
1519   }
1520
1521   final static Number toNumber(String...s) {
1522      try {
1523         for (String ss : s)
1524            if (isNotEmpty(ss))
1525               return parseNumber(ss, Number.class);
1526         return null;
1527      } catch (ParseException e) {
1528         throw new RuntimeException(e);
1529      }
1530   }
1531
1532   final static OMap toOMap(String[] ss) {
1533      String s = joinnl(ss);
1534      if (s.isEmpty())
1535         return null;
1536      if (! isJsonObject(s, true))
1537         s = "{" + s + "}";
1538      try {
1539         return OMap.ofJson(s);
1540      } catch (ParseException e) {
1541         throw new RuntimeException(e);
1542      }
1543   }
1544
1545   @Override
1546   public String toString() {
1547      try {
1548         OMap m = new OMap()
1549            .ase("name", name)
1550            .ase("type", type)
1551            .ase("format", format)
1552            .ase("codes", codes)
1553            .ase("default", _default)
1554            .ase("enum", _enum)
1555            .ase("properties", properties)
1556            .asf("allowEmptyValue", allowEmptyValue)
1557            .asf("exclusiveMaximum", exclusiveMaximum)
1558            .asf("exclusiveMinimum", exclusiveMinimum)
1559            .asf("required", required)
1560            .asf("uniqueItems", uniqueItems)
1561            .asf("skipIfEmpty", skipIfEmpty)
1562            .aif(collectionFormat != HttpPartCollectionFormat.NO_COLLECTION_FORMAT, "collectionFormat", collectionFormat)
1563            .ase("pattern", pattern)
1564            .asn("items", items)
1565            .asn("additionalProperties", additionalProperties)
1566            .asmo("maximum", maximum)
1567            .asmo("minimum", minimum)
1568            .asmo("multipleOf", multipleOf)
1569            .asmo("maxLength", maxLength)
1570            .asmo("minLength", minLength)
1571            .asmo("maxItems", maxItems)
1572            .asmo("minItems", minItems)
1573            .asmo("maxProperties", maxProperties)
1574            .asmo("minProperties", minProperties)
1575            .append("parsedType", parsedType)
1576         ;
1577         return m.toString();
1578      } catch (Exception e) {
1579         e.printStackTrace();
1580         return "";
1581      }
1582   }
1583}