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.dto.jsonschema;
014
015import static org.apache.juneau.common.internal.StringUtils.*;
016import static org.apache.juneau.internal.ClassUtils.*;
017import static org.apache.juneau.internal.CollectionUtils.*;
018
019import java.net.*;
020import java.util.*;
021
022import org.apache.juneau.*;
023import org.apache.juneau.annotation.*;
024import org.apache.juneau.json.*;
025import org.apache.juneau.parser.*;
026import org.apache.juneau.serializer.*;
027import org.apache.juneau.swap.*;
028
029/**
030 * Represents a top-level schema object bean in the JSON-Schema core specification.
031 */
032@Bean(typeName="schema",
033   properties="id,$schema,$ref, title,description,type,definitions,properties,"
034      + "patternProperties,dependencies,items,multipleOf,maximum,exclusiveMaximum,"
035      + "minimum,exclusiveMinimum,maxLength,minLength,pattern,additionalItems,"
036      + "maxItems,minItems,uniqueItems,maxProperties,minProperties,required,"
037      + "additionalProperties,enum,allOf,anyOf,oneOf,not"
038)
039public class JsonSchema {
040   private String name;                                   // Property name.  Not serialized.
041   private URI id;
042   private URI schemaVersion;
043   private String title;
044   private String description;
045   private JsonType typeJsonType;                         // JsonType representation of type
046   private JsonTypeArray typeJsonTypeArray;               // JsonTypeArray representation of type
047   private Map<String,JsonSchema> definitions;
048   private Map<String,JsonSchema> properties;
049   private Map<String,JsonSchema> patternProperties;
050   private Map<String,JsonSchema> dependencies;
051   private JsonSchema itemsSchema;                            // JsonSchema representation of items
052   private JsonSchemaArray itemsSchemaArray;                  // JsonSchemaArray representation of items
053   private Number multipleOf;
054   private Number maximum;
055   private Boolean exclusiveMaximum;
056   private Number minimum;
057   private Boolean exclusiveMinimum;
058   private Integer maxLength;
059   private Integer minLength;
060   private String pattern;
061   private Boolean additionalItemsBoolean;                // Boolean representation of additionalItems
062   private JsonSchemaArray additionalItemsSchemaArray;        // JsonSchemaArray representation of additionalItems
063   private Integer maxItems;
064   private Integer minItems;
065   private Boolean uniqueItems;
066   private Integer maxProperties;
067   private Integer minProperties;
068   private List<String> required;
069   private Boolean additionalPropertiesBoolean;           // Boolean representation of additionalProperties
070   private JsonSchema additionalPropertiesSchema;             // JsonSchema representation of additionalProperties
071   private List<String> _enum;
072   private List<JsonSchema> allOf;
073   private List<JsonSchema> anyOf;
074   private List<JsonSchema> oneOf;
075   private JsonSchema not;
076   private URI ref;
077   private JsonSchemaMap schemaMap;
078   private JsonSchema master = this;
079
080   /**
081    * Default constructor.
082    */
083   public JsonSchema() {}
084
085
086   //-----------------------------------------------------------------------------------------------------------------
087   // Bean properties
088   //-----------------------------------------------------------------------------------------------------------------
089
090   /**
091    * Bean property getter:  <property>name</property>.
092    *
093    * @return The value of the <property>name</property> property on this bean, or <jk>null</jk> if it is not set.
094    */
095   public String getName() {
096      return name;
097   }
098
099   /**
100    * Bean property setter:  <property>name</property>.
101    *
102    * @param name The new value for the <property>name</property> property on this bean.
103    * @return This object.
104    */
105   public JsonSchema setName(String name) {
106      this.name = name;
107      return this;
108   }
109
110   /**
111    * Bean property getter:  <property>id</property>.
112    *
113    * @return The value of the <property>id</property> property on this bean, or <jk>null</jk> if it is not set.
114    */
115   public URI getId() {
116      return id;
117   }
118
119   /**
120    * Bean property setter:  <property>id</property>.
121    *
122    * <p>
123    * The value can be of any of the following types: {@link URI}, {@link URL}, {@link String}.
124    * Strings must be valid URIs.
125    *
126    * <p>
127    * URIs defined by {@link UriResolver} can be used for values.
128    *
129    * @param id The new value for the <property>id</property> property on this bean.
130    * @return This object.
131    */
132   public JsonSchema setId(Object id) {
133      this.id = toURI(id);
134      return this;
135   }
136
137   /**
138    * Bean property getter:  <property>$schema</property>.
139    *
140    * @return The value of the <property>$schema</property> property on this bean, or <jk>null</jk> if it is not set.
141    */
142   @Beanp("$schema")
143   public URI getSchemaVersionUri() {
144      return schemaVersion;
145   }
146
147   /**
148    * Bean property setter:  <property>$schema</property>.
149    *
150    * <p>
151    * The value can be of any of the following types: {@link URI}, {@link URL}, {@link String}.
152    * Strings must be valid URIs.
153    *
154    * <p>
155    * URIs defined by {@link UriResolver} can be used for values.
156    *
157    * @param schemaVersion The new value for the <property>schemaVersion</property> property on this bean.
158    * @return This object.
159    */
160   @Beanp("$schema")
161   public JsonSchema setSchemaVersionUri(Object schemaVersion) {
162      this.schemaVersion = toURI(schemaVersion);
163      return this;
164   }
165
166   /**
167    * Bean property getter:  <property>title</property>.
168    *
169    * @return The value of the <property>title</property> property, or <jk>null</jk> if it is not set.
170    */
171   public String getTitle() {
172      return title;
173   }
174
175   /**
176    * Bean property setter:  <property>title</property>.
177    *
178    * @param title The new value for the <property>title</property> property on this bean.
179    * @return This object.
180    */
181   public JsonSchema setTitle(String title) {
182      this.title = title;
183      return this;
184   }
185
186   /**
187    * Bean property getter:  <property>description</property>.
188    *
189    * @return The value of the <property>description</property> property, or <jk>null</jk> if it is not set.
190    */
191   public String getDescription() {
192      return description;
193   }
194
195   /**
196    * Bean property setter:  <property>description</property>.
197    *
198    * @param description The new value for the <property>description</property> property on this bean.
199    * @return This object.
200    */
201   public JsonSchema setDescription(String description) {
202      this.description = description;
203      return this;
204   }
205
206   /**
207    * Bean property getter:  <property>type</property>.
208    *
209    * @return
210    *    The value of the <property>type</property> property on this bean, or <jk>null</jk> if it is not set.
211    *    Can be either a {@link JsonType} or {@link JsonTypeArray} depending on what value was used to set it.
212    */
213   @Swap(JsonTypeOrJsonTypeArraySwap.class)
214   public Object getType() {
215      if (typeJsonType != null)
216         return typeJsonType;
217      return typeJsonTypeArray;
218   }
219
220   /**
221    * Bean property getter:  <property>type</property>.
222    *
223    * <p>
224    * Convenience method for returning the <property>type</property> property when it is a {@link JsonType} value.
225    *
226    * @return
227    *    The currently set value, or <jk>null</jk> if the property is not set, or is set as a {@link JsonTypeArray}.
228    */
229   public JsonType getTypeAsJsonType() {
230      return typeJsonType;
231   }
232
233   /**
234    * Bean property getter:  <property>type</property>.
235    *
236    * <p>
237    * Convenience method for returning the <property>type</property> property when it is a {@link JsonTypeArray} value.
238    *
239    * @return The currently set value, or <jk>null</jk> if the property is not set, or is set as a {@link JsonType}.
240    */
241   public JsonTypeArray getTypeAsJsonTypeArray() {
242      return typeJsonTypeArray;
243   }
244
245   /**
246    * Bean property setter:  <property>type</property>.
247    *
248    * @param type
249    *    The new value for the <property>type</property> property on this bean.
250    *    This object must be of type {@link JsonType} or {@link JsonTypeArray}.
251    * @return This object.
252    * @throws BeanRuntimeException If invalid object type passed in.
253    */
254   public JsonSchema setType(Object type) {
255      this.typeJsonType = null;
256      this.typeJsonTypeArray = null;
257      if (type != null) {
258         if (type instanceof JsonType)
259            this.typeJsonType = (JsonType)type;
260         else if (type instanceof JsonTypeArray)
261            this.typeJsonTypeArray = (JsonTypeArray)type;
262         else
263            throw new BeanRuntimeException(JsonSchemaProperty.class, "Invalid attribute type ''{0}'' passed in.  Must be one of the following:  SimpleType, SimpleTypeArray", className(type));
264      }
265      return this;
266   }
267
268   /**
269    * Bean property appender:  <property>type</property>.
270    *
271    * @param types The list of items to append to the <property>type</property> property on this bean.
272    * @return This object.
273    */
274   public JsonSchema addTypes(JsonType...types) {
275      if (this.typeJsonTypeArray == null)
276         this.typeJsonTypeArray = new JsonTypeArray();
277      this.typeJsonTypeArray.addAll(types);
278      return this;
279   }
280
281   /**
282    * Used during parsing to convert the <property>type</property> property to the correct class type.
283    *
284    * <ul class='spaced-list'>
285    *    <li>
286    *       If parsing a JSON-array, converts to a {@link JsonTypeArray}.
287    *    <li>
288    *       If parsing a JSON-object, converts to a {@link JsonType}.
289    * </ul>
290    *
291    * <p>
292    * Serialization method is a no-op.
293    */
294   public static class JsonTypeOrJsonTypeArraySwap extends ObjectSwap<Object,Object> {
295
296      @Override /* ObjectSwap */
297      public Object swap(BeanSession session, Object o) throws SerializeException {
298         return o;
299      }
300
301      @Override /* ObjectSwap */
302      public Object unswap(BeanSession session, Object o, ClassMeta<?> hint) throws ParseException {
303         ClassMeta<?> cm = (
304            o instanceof Collection
305            ? session.getClassMeta(JsonTypeArray.class)
306            : session.getClassMeta(JsonType.class)
307         );
308         return session.convertToType(o, cm);
309      }
310   }
311
312   /**
313    * Bean property getter:  <property>definitions</property>.
314    *
315    * @return
316    *    The value of the <property>definitions</property> property on this bean, or <jk>null</jk> if it is not set.
317    */
318   public Map<String,JsonSchema> getDefinitions() {
319      return definitions;
320   }
321
322   /**
323    * Bean property setter:  <property>definitions</property>.
324    *
325    * @param definitions The new value for the <property>definitions</property> property on this bean.
326    * @return This object.
327    */
328   public JsonSchema setDefinitions(Map<String,JsonSchema> definitions) {
329      this.definitions = definitions;
330      if (definitions != null)
331         setMasterOn(definitions.values());
332      return this;
333   }
334
335   /**
336    * Bean property appender:  <property>definitions</property>.
337    *
338    * @param name The key in the definitions map entry.
339    * @param definition The value in the definitions map entry.
340    * @return This object.
341    */
342   public JsonSchema addDefinition(String name, JsonSchema definition) {
343      if (this.definitions == null)
344         this.definitions = map();
345      this.definitions.put(name, definition);
346      setMasterOn(definition);
347      return this;
348   }
349
350   /**
351    * Bean property getter:  <property>properties</property>.
352    *
353    * @return The value of the <property>properties</property> property on this bean, or <jk>null</jk> if it is not set.
354    */
355   public Map<String,JsonSchema> getProperties() {
356      return properties;
357   }
358
359   /**
360    * Returns the property with the specified name.
361    *
362    * <p>
363    * This is equivalent to calling <property>getProperty(name, <jk>false</jk>)</property>.
364    *
365    * @param name The property name.
366    * @return The property with the specified name, or <jk>null</jk> if no property is specified.
367    */
368   public JsonSchema getProperty(String name) {
369      return getProperty(name, false);
370   }
371
372   /**
373    * Returns the property with the specified name.
374    *
375    * <p>
376    * If <property>resolve</property> is <jk>true</jk>, the property object will automatically be  resolved by calling
377    * {@link #resolve()}.
378    * Therefore, <property>getProperty(name, <jk>true</jk>)</property> is equivalent to calling
379    * <property>getProperty(name).resolve()</property>, except it's safe from a potential
380    * <property>NullPointerException</property>.
381    *
382    * @param name The property name.
383    * @param resolve If <jk>true</jk>, calls {@link #resolve()} on object before returning.
384    * @return The property with the specified name, or <jk>null</jk> if no property is specified.
385    */
386   public JsonSchema getProperty(String name, boolean resolve) {
387      if (properties == null)
388         return null;
389      JsonSchema s = properties.get(name);
390      if (s == null)
391         return null;
392      if (resolve)
393         s = s.resolve();
394      return s;
395   }
396
397   /**
398    * Bean property setter:  <property>properties</property>.
399    *
400    * @param properties The new value for the <property>properties</property> property on this bean.
401    * @return This object.
402    */
403   public JsonSchema setProperties(Map<String,JsonSchema> properties) {
404      this.properties = properties;
405      if (properties != null) {
406         properties.entrySet().forEach(x -> {
407            JsonSchema value = x.getValue();
408            setMasterOn(value);
409            value.setName(x.getKey());
410         });
411      }
412      return this;
413   }
414
415   /**
416    * Bean property appender:  <property>properties</property>.
417    *
418    * <p>
419    * Properties must have their <property>name</property> property set on them when using this method.
420    *
421    * @param properties The list of items to append to the <property>properties</property> property on this bean.
422    * @return This object.
423    * @throws BeanRuntimeException If property is found without a set <property>name</property> property.
424    */
425   public JsonSchema addProperties(JsonSchema...properties) {
426      if (this.properties == null)
427         this.properties = map();
428      for (JsonSchema p : properties) {
429         if (p.getName() == null)
430            throw new BeanRuntimeException(JsonSchema.class,
431               "Invalid property passed to JsonSchema.addProperties().  Property name was null.");
432         setMasterOn(p);
433         this.properties.put(p.getName(), p);
434      }
435      return this;
436   }
437
438   /**
439    * Bean property getter:  <property>patternProperties</property>.
440    *
441    * @return
442    *    The value of the <property>patternProperties</property> property on this bean, or <jk>null</jk> if it is
443    *    not set.
444    */
445   public Map<String,JsonSchema> getPatternProperties() {
446      return patternProperties;
447   }
448
449   /**
450    * Bean property setter:  <property>patternProperties</property>.
451    *
452    * @param patternProperties The new value for the <property>patternProperties</property> property on this bean.
453    * @return This object.
454    */
455   public JsonSchema setPatternProperties(Map<String,JsonSchema> patternProperties) {
456      this.patternProperties = patternProperties;
457      if (patternProperties != null) {
458         patternProperties.entrySet().forEach(x -> {
459            JsonSchema s = x.getValue();
460            setMasterOn(s);
461            s.setName(x.getKey());
462         });
463      }
464      return this;
465   }
466
467   /**
468    * Bean property appender:  <property>patternProperties</property>.
469    *
470    * <p>
471    * Properties must have their <property>name</property> property set to the pattern string when using this method.
472    *
473    * @param properties The list of items to append to the <property>patternProperties</property> property on this bean.
474    * @return This object.
475    * @throws BeanRuntimeException If property is found without a set <property>name</property> property.
476    */
477   public JsonSchema addPatternProperties(JsonSchemaProperty...properties) {
478      if (this.patternProperties == null)
479         this.patternProperties = map();
480      for (JsonSchema p : properties) {
481         if (p.getName() == null)
482            throw new BeanRuntimeException(JsonSchema.class,
483               "Invalid property passed to JsonSchema.addProperties().  Property name was null.");
484         setMasterOn(p);
485         this.patternProperties.put(p.getName(), p);
486      }
487      return this;
488   }
489
490   /**
491    * Bean property getter:  <property>dependencies</property>.
492    *
493    * @return
494    *    The value of the <property>dependencies</property> property on this bean, or <jk>null</jk> if it is not set.
495    */
496   public Map<String,JsonSchema> getDependencies() {
497      return dependencies;
498   }
499
500   /**
501    * Bean property setter:  <property>dependencies</property>.
502    *
503    * @param dependencies The new value for the <property>dependencies</property> property on this bean.
504    * @return This object.
505    */
506   public JsonSchema setDependencies(Map<String,JsonSchema> dependencies) {
507      this.dependencies = dependencies;
508      if (dependencies != null)
509         setMasterOn(dependencies.values());
510      return this;
511   }
512
513   /**
514    * Bean property appender:  <property>dependencies</property>.
515    *
516    * @param name The key of the entry in the dependencies map.
517    * @param dependency The value of the entry in the dependencies map.
518    * @return This object.
519    */
520   public JsonSchema addDependency(String name, JsonSchema dependency) {
521      if (this.dependencies == null)
522         this.dependencies = map();
523      this.dependencies.put(name, dependency);
524      setMasterOn(dependency);
525      return this;
526   }
527
528   /**
529    * Bean property getter:  <property>items</property>.
530    *
531    * @return
532    *    The value of the <property>items</property> property on this bean, or <jk>null</jk> if it is not set.
533    *    Can be either a {@link JsonSchema} or {@link JsonSchemaArray} depending on what value was used to set it.
534    */
535   @Swap(JsonSchemaOrSchemaArraySwap.class)
536   public Object getItems() {
537      if (itemsSchema != null)
538         return itemsSchema;
539      return itemsSchemaArray;
540   }
541
542   /**
543    * Bean property getter:  <property>items</property>.
544    *
545    * <p>
546    * Convenience method for returning the <property>items</property> property when it is a {@link JsonSchema} value.
547    *
548    * @return The currently set value, or <jk>null</jk> if the property is not set, or is set as a {@link JsonSchemaArray}.
549    */
550   public JsonSchema getItemsAsSchema() {
551      return itemsSchema;
552   }
553
554   /**
555    * Bean property getter:  <property>items</property>.
556    *
557    * <p>
558    * Convenience method for returning the <property>items</property> property when it is a {@link JsonSchemaArray} value.
559    *
560    * @return The currently set value, or <jk>null</jk> if the property is not set, or is set as a {@link JsonSchema}.
561    */
562   public JsonSchemaArray getItemsAsSchemaArray() {
563      return itemsSchemaArray;
564   }
565
566   /**
567    * Used during parsing to convert the <property>items</property> property to the correct class type.
568    *
569    * <ul class='spaced-list'>
570    *    <li>
571    *       If parsing a JSON-array, converts to a {@link JsonSchemaArray}.
572    *    <li>
573    *       If parsing a JSON-object, converts to a {@link JsonSchema}.
574    * </ul>
575    *
576    * <p>
577    * Serialization method is a no-op.
578    */
579   public static class JsonSchemaOrSchemaArraySwap extends ObjectSwap<Object,Object> {
580
581      @Override /* ObjectSwap */
582      public Object swap(BeanSession session, Object o) throws SerializeException {
583         return o;
584      }
585
586      @Override /* ObjectSwap */
587      public Object unswap(BeanSession session, Object o, ClassMeta<?> hint) throws ParseException {
588         ClassMeta<?> cm = (
589            o instanceof Collection
590            ? session.getClassMeta(JsonSchemaArray.class)
591            : session.getClassMeta(JsonSchema.class)
592         );
593         return session.convertToType(o, cm);
594      }
595   }
596
597   /**
598    * Bean property setter:  <property>items</property>.
599    *
600    * @param
601    *    items The new value for the <property>items</property> property on this bean.
602    *    This object must be of type {@link JsonSchema} or {@link JsonSchemaArray}.
603    * @return This object.
604    * @throws BeanRuntimeException If invalid object type passed in.
605    */
606   public JsonSchema setItems(Object items) {
607      this.itemsSchema = null;
608      this.itemsSchemaArray = null;
609      if (items != null) {
610         if (items instanceof JsonSchema) {
611            this.itemsSchema = (JsonSchema)items;
612            setMasterOn(this.itemsSchema);
613         } else if (items instanceof JsonSchemaArray) {
614            this.itemsSchemaArray = (JsonSchemaArray)items;
615            setMasterOn(this.itemsSchemaArray);
616         } else {
617            throw new BeanRuntimeException(JsonSchemaProperty.class, "Invalid attribute type ''{0}'' passed in.  Must be one of the following:  JsonSchema, JsonSchemaArray", className(items));
618         }
619      }
620      return this;
621   }
622
623   /**
624    * Bean property appender:  <property>items</property>.
625    *
626    * @param items The list of items to append to the <property>items</property> property on this bean.
627    * @return This object.
628    */
629   public JsonSchema addItems(JsonSchema...items) {
630      if (this.itemsSchemaArray == null)
631         this.itemsSchemaArray = new JsonSchemaArray();
632      this.itemsSchemaArray.addAll(items);
633      setMasterOn(items);
634      return this;
635   }
636
637   /**
638    * Bean property getter:  <property>multipleOf</property>.
639    *
640    * @return The value of the <property>multipleOf</property> property on this bean, or <jk>null</jk> if it is not set.
641    */
642   public Number getMultipleOf() {
643      return multipleOf;
644   }
645
646   /**
647    * Bean property setter:  <property>multipleOf</property>.
648    *
649    * @param multipleOf The new value for the <property>multipleOf</property> property on this bean.
650    * @return This object.
651    */
652   public JsonSchema setMultipleOf(Number multipleOf) {
653      this.multipleOf = multipleOf;
654      return this;
655   }
656
657   /**
658    * Bean property getter:  <property>maximum</property>.
659    *
660    * @return The value of the <property>maximum</property> property on this bean, or <jk>null</jk> if it is not set.
661    */
662   public Number getMaximum() {
663      return maximum;
664   }
665
666   /**
667    * Bean property setter:  <property>maximum</property>.
668    *
669    * @param maximum The new value for the <property>maximum</property> property on this bean.
670    * @return This object.
671    */
672   public JsonSchema setMaximum(Number maximum) {
673      this.maximum = maximum;
674      return this;
675   }
676
677   /**
678    * Bean property getter:  <property>exclusiveMaximum</property>.
679    *
680    * @return
681    *    The value of the <property>exclusiveMaximum</property> property on this bean, or <jk>null</jk> if it is
682    *    not set.
683    */
684   public Boolean isExclusiveMaximum() {
685      return exclusiveMaximum;
686   }
687
688   /**
689    * Bean property setter:  <property>exclusiveMaximum</property>.
690    *
691    * @param exclusiveMaximum The new value for the <property>exclusiveMaximum</property> property on this bean.
692    * @return This object.
693    */
694   public JsonSchema setExclusiveMaximum(Boolean exclusiveMaximum) {
695      this.exclusiveMaximum = exclusiveMaximum;
696      return this;
697   }
698
699   /**
700    * Bean property getter:  <property>minimum</property>.
701    *
702    * @return The value of the <property>minimum</property> property on this bean, or <jk>null</jk> if it is not set.
703    */
704   public Number getMinimum() {
705      return minimum;
706   }
707
708   /**
709    * Bean property setter:  <property>minimum</property>.
710    *
711    * @param minimum The new value for the <property>minimum</property> property on this bean.
712    * @return This object.
713    */
714   public JsonSchema setMinimum(Number minimum) {
715      this.minimum = minimum;
716      return this;
717   }
718
719   /**
720    * Bean property getter:  <property>exclusiveMinimum</property>.
721    *
722    * @return
723    *    The value of the <property>exclusiveMinimum</property> property on this bean, or <jk>null</jk> if it is
724    *    not set.
725    */
726   public Boolean isExclusiveMinimum() {
727      return exclusiveMinimum;
728   }
729
730   /**
731    * Bean property setter:  <property>exclusiveMinimum</property>.
732    *
733    * @param exclusiveMinimum The new value for the <property>exclusiveMinimum</property> property on this bean.
734    * @return This object.
735    */
736   public JsonSchema setExclusiveMinimum(Boolean exclusiveMinimum) {
737      this.exclusiveMinimum = exclusiveMinimum;
738      return this;
739   }
740
741   /**
742    * Bean property getter:  <property>maxLength</property>.
743    *
744    * @return The value of the <property>maxLength</property> property on this bean, or <jk>null</jk> if it is not set.
745    */
746   public Integer getMaxLength() {
747      return maxLength;
748   }
749
750   /**
751    * Bean property setter:  <property>maxLength</property>.
752    *
753    * @param maxLength The new value for the <property>maxLength</property> property on this bean.
754    * @return This object.
755    */
756   public JsonSchema setMaxLength(Integer maxLength) {
757      this.maxLength = maxLength;
758      return this;
759   }
760
761   /**
762    * Bean property getter:  <property>minLength</property>.
763    *
764    * @return The value of the <property>minLength</property> property on this bean, or <jk>null</jk> if it is not set.
765    */
766   public Integer getMinLength() {
767      return minLength;
768   }
769
770   /**
771    * Bean property setter:  <property>minLength</property>.
772    *
773    * @param minLength The new value for the <property>minLength</property> property on this bean.
774    * @return This object.
775    */
776   public JsonSchema setMinLength(Integer minLength) {
777      this.minLength = minLength;
778      return this;
779   }
780
781   /**
782    * Bean property getter:  <property>pattern</property>.
783    *
784    * @return The value of the <property>pattern</property> property on this bean, or <jk>null</jk> if it is not set.
785    */
786   public String getPattern() {
787      return pattern;
788   }
789
790   /**
791    * Bean property setter:  <property>pattern</property>.
792    *
793    * @param pattern The new value for the <property>pattern</property> property on this bean.
794    * @return This object.
795    */
796   public JsonSchema setPattern(String pattern) {
797      this.pattern = pattern;
798      return this;
799   }
800
801   /**
802    * Bean property getter:  <property>additionalItems</property>.
803    *
804    * @return
805    *    The value of the <property>additionalItems</property> property on this bean, or <jk>null</jk> if it is
806    *    not set.
807    *    Can be either a {@link Boolean} or {@link JsonSchemaArray} depending on what value was used to set it.
808    */
809   @Swap(BooleanOrSchemaArraySwap.class)
810   public Object getAdditionalItems() {
811      if (additionalItemsBoolean != null)
812         return additionalItemsBoolean;
813      return additionalItemsSchemaArray;
814   }
815
816   /**
817    * Bean property getter:  <property>additionalItems</property>.
818    *
819    * <p>
820    * Convenience method for returning the <property>additionalItems</property> property when it is a {@link Boolean}
821    * value.
822    *
823    * @return The currently set value, or <jk>null</jk> if the property is not set, or is set as a {@link JsonSchemaArray}.
824    */
825   public Boolean getAdditionalItemsAsBoolean() {
826      return additionalItemsBoolean;
827   }
828
829   /**
830    * Bean property getter:  <property>additionalItems</property>.
831    *
832    * <p>
833    * Convenience method for returning the <property>additionalItems</property> property when it is a
834    * {@link JsonSchemaArray} value.
835    *
836    * @return The currently set value, or <jk>null</jk> if the property is not set, or is set as a {@link Boolean}.
837    */
838   public List<JsonSchema> getAdditionalItemsAsSchemaArray() {
839      return additionalItemsSchemaArray;
840   }
841
842   /**
843    * Bean property setter:  <property>additionalItems</property>.
844    *
845    * @param additionalItems
846    *    The new value for the <property>additionalItems</property> property on this bean.
847    *    This object must be of type {@link Boolean} or {@link JsonSchemaArray}.
848    * @return This object.
849    * @throws BeanRuntimeException If invalid object type passed in.
850    */
851   public JsonSchema setAdditionalItems(Object additionalItems) {
852      this.additionalItemsBoolean = null;
853      this.additionalItemsSchemaArray = null;
854      if (additionalItems != null) {
855         if (additionalItems instanceof Boolean)
856            this.additionalItemsBoolean = (Boolean)additionalItems;
857         else if (additionalItems instanceof JsonSchemaArray) {
858            this.additionalItemsSchemaArray = (JsonSchemaArray)additionalItems;
859            setMasterOn(this.additionalItemsSchemaArray);
860         } else {
861            throw new BeanRuntimeException(JsonSchemaProperty.class, "Invalid attribute type ''{0}'' passed in.  Must be one of the following:  Boolean, JsonSchemaArray", className(additionalItems));
862         }
863      }
864      return this;
865   }
866
867   /**
868    * Bean property appender:  <property>additionalItems</property>.
869    *
870    * @param additionalItems
871    *    The list of items to append to the <property>additionalItems</property> property on this bean.
872    * @return This object.
873    */
874   public JsonSchema addAdditionalItems(JsonSchema...additionalItems) {
875      if (this.additionalItemsSchemaArray == null)
876         this.additionalItemsSchemaArray = new JsonSchemaArray();
877      this.additionalItemsSchemaArray.addAll(additionalItems);
878      setMasterOn(additionalItems);
879      return this;
880   }
881
882   /**
883    * Used during parsing to convert the <property>additionalItems</property> property to the correct class type.
884    *
885    * <ul class='spaced-list'>
886    *    <li>
887    *       If parsing a JSON-array, converts to a {@link JsonSchemaArray}.
888    *    <li>
889    *       If parsing a JSON-boolean, converts to a {@link Boolean}.
890    * </ul>
891    *
892    * <p>
893    * Serialization method is a no-op.
894    */
895   public static class BooleanOrSchemaArraySwap extends ObjectSwap<Object,Object> {
896
897      @Override /* ObjectSwap */
898      public Object swap(BeanSession session, Object o) throws SerializeException {
899         return o;
900      }
901
902      @Override /* ObjectSwap */
903      public Object unswap(BeanSession session, Object o, ClassMeta<?> hint) throws ParseException {
904         ClassMeta<?> cm = (
905            o instanceof Collection
906            ? session.getClassMeta(JsonSchemaArray.class)
907            : session.getClassMeta(Boolean.class)
908         );
909         return session.convertToType(o, cm);
910      }
911   }
912
913   /**
914    * Bean property getter:  <property>maxItems</property>.
915    *
916    * @return The value of the <property>maxItems</property> property on this bean, or <jk>null</jk> if it is not set.
917    */
918   public Integer getMaxItems() {
919      return maxItems;
920   }
921
922   /**
923    * Bean property setter:  <property>maxItems</property>.
924    *
925    * @param maxItems The new value for the <property>maxItems</property> property on this bean.
926    * @return This object.
927    */
928   public JsonSchema setMaxItems(Integer maxItems) {
929      this.maxItems = maxItems;
930      return this;
931   }
932
933   /**
934    * Bean property getter:  <property>minItems</property>.
935    *
936    * @return The value of the <property>minItems</property> property on this bean, or <jk>null</jk> if it is not set.
937    */
938   public Integer getMinItems() {
939      return minItems;
940   }
941
942   /**
943    * Bean property setter:  <property>minItems</property>.
944    *
945    * @param minItems The new value for the <property>minItems</property> property on this bean.
946    * @return This object.
947    */
948   public JsonSchema setMinItems(Integer minItems) {
949      this.minItems = minItems;
950      return this;
951   }
952
953   /**
954    * Bean property getter:  <property>uniqueItems</property>.
955    *
956    * @return
957    *    The value of the <property>uniqueItems</property> property on this bean, or <jk>null</jk> if it is not set.
958    */
959   public Boolean getUniqueItems() {
960      return uniqueItems;
961   }
962
963   /**
964    * Bean property setter:  <property>uniqueItems</property>.
965    *
966    * @param uniqueItems The new value for the <property>uniqueItems</property> property on this bean.
967    * @return This object.
968    */
969   public JsonSchema setUniqueItems(Boolean uniqueItems) {
970      this.uniqueItems = uniqueItems;
971      return this;
972   }
973
974   /**
975    * Bean property getter:  <property>maxProperties</property>.
976    *
977    * @return
978    *    The value of the <property>maxProperties</property> property on this bean, or <jk>null</jk> if it is not set.
979    */
980   public Integer getMaxProperties() {
981      return maxProperties;
982   }
983
984   /**
985    * Bean property setter:  <property>maxProperties</property>.
986    *
987    * @param maxProperties The new value for the <property>maxProperties</property> property on this bean.
988    * @return This object.
989    */
990   public JsonSchema setMaxProperties(Integer maxProperties) {
991      this.maxProperties = maxProperties;
992      return this;
993   }
994
995   /**
996    * Bean property getter:  <property>minProperties</property>.
997    *
998    * @return
999    *    The value of the <property>minProperties</property> property on this bean, or <jk>null</jk> if it is not set.
1000    */
1001   public Integer getMinProperties() {
1002      return minProperties;
1003   }
1004
1005   /**
1006    * Bean property setter:  <property>minProperties</property>.
1007    *
1008    * @param minProperties The new value for the <property>minProperties</property> property on this bean.
1009    * @return This object.
1010    */
1011   public JsonSchema setMinProperties(Integer minProperties) {
1012      this.minProperties = minProperties;
1013      return this;
1014   }
1015
1016   /**
1017    * Bean property getter:  <property>required</property>.
1018    *
1019    * @return The value of the <property>required</property> property on this bean, or <jk>null</jk> if it is not set.
1020    */
1021   public List<String> getRequired() {
1022      return required;
1023   }
1024
1025   /**
1026    * Bean property setter:  <property>required</property>.
1027    *
1028    * @param required The new value for the <property>required</property> property on this bean.
1029    * @return This object.
1030    */
1031   public JsonSchema setRequired(List<String> required) {
1032      this.required = required;
1033      return this;
1034   }
1035
1036   /**
1037    * Bean property appender:  <property>required</property>.
1038    *
1039    * @param required The list of items to append to the <property>required</property> property on this bean.
1040    * @return This object.
1041    */
1042   public JsonSchema addRequired(List<String> required) {
1043      if (this.required == null)
1044         this.required = new LinkedList<>();
1045      required.forEach(x -> this.required.add(x));
1046      return this;
1047   }
1048
1049   /**
1050    * Bean property appender:  <property>required</property>.
1051    *
1052    * @param required The list of items to append to the <property>required</property> property on this bean.
1053    * @return This object.
1054    */
1055   public JsonSchema addRequired(String...required) {
1056      if (this.required == null)
1057         this.required = new LinkedList<>();
1058      for (String r : required)
1059         this.required.add(r);
1060      return this;
1061   }
1062
1063   /**
1064    * Bean property appender:  <property>required</property>.
1065    *
1066    * @param properties The list of items to append to the <property>required</property> property on this bean.
1067    * @return This object.
1068    */
1069   public JsonSchema addRequired(JsonSchemaProperty...properties) {
1070      if (this.required == null)
1071         this.required = new LinkedList<>();
1072      for (JsonSchemaProperty p : properties)
1073         this.required.add(p.getName());
1074      return this;
1075   }
1076
1077   /**
1078    * Bean property getter:  <property>additionalProperties</property>.
1079    *
1080    * @return
1081    *    The value of the <property>additionalProperties</property> property on this bean, or <jk>null</jk> if it
1082    *    is not set.
1083    *    Can be either a {@link Boolean} or {@link JsonSchemaArray} depending on what value was used to set it.
1084    */
1085   @Swap(BooleanOrSchemaSwap.class)
1086   public Object getAdditionalProperties() {
1087      if (additionalPropertiesBoolean != null)
1088         return additionalItemsBoolean;
1089      return additionalPropertiesSchema;
1090   }
1091
1092   /**
1093    * Bean property getter:  <property>additionalProperties</property>.
1094    *
1095    * <p>
1096    * Convenience method for returning the <property>additionalProperties</property> property when it is a
1097    * {@link Boolean} value.
1098    *
1099    * @return The currently set value, or <jk>null</jk> if the property is not set, or is set as a {@link JsonSchema}.
1100    */
1101   public Boolean getAdditionalPropertiesAsBoolean() {
1102      return additionalPropertiesBoolean;
1103   }
1104
1105   /**
1106    * Bean property getter:  <property>additionalProperties</property>.
1107    *
1108    * <p>
1109    * Convenience method for returning the <property>additionalProperties</property> property when it is a
1110    * {@link JsonSchema} value.
1111    *
1112    * @return The currently set value, or <jk>null</jk> if the property is not set, or is set as a {@link Boolean}.
1113    */
1114   public JsonSchema getAdditionalPropertiesAsSchema() {
1115      return additionalPropertiesSchema;
1116   }
1117
1118   /**
1119    * Bean property setter:  <property>additionalProperties</property>.
1120    *
1121    * @param additionalProperties
1122    *    The new value for the <property>additionalProperties</property> property on this bean.
1123    *    This object must be of type {@link Boolean} or {@link JsonSchema}.
1124    * @return This object.
1125    * @throws BeanRuntimeException If invalid object type passed in.
1126    */
1127   @Beanp(dictionary={JsonSchema.class})
1128   public JsonSchema setAdditionalProperties(Object additionalProperties) {
1129      this.additionalPropertiesBoolean = null;
1130      this.additionalPropertiesSchema = null;
1131      if (additionalProperties != null) {
1132         if (additionalProperties instanceof Boolean)
1133            this.additionalPropertiesBoolean = (Boolean)additionalProperties;
1134         else if (additionalProperties instanceof JsonSchema) {
1135            this.additionalPropertiesSchema = (JsonSchema)additionalProperties;
1136            setMasterOn(this.additionalPropertiesSchema);
1137         } else
1138            throw new BeanRuntimeException(JsonSchemaProperty.class,
1139               "Invalid attribute type ''{0}'' passed in.  Must be one of the following:  Boolean, JsonSchema",
1140               className(additionalProperties));
1141      }
1142      return this;
1143   }
1144
1145   /**
1146    * Used during parsing to convert the <property>additionalProperties</property> property to the correct class type.
1147    *
1148    * <ul class='spaced-list'>
1149    *    <li>
1150    *       If parsing a JSON-object, converts to a {@link JsonSchema}.
1151    *    <li>
1152    *       If parsing a JSON-boolean, converts to a {@link Boolean}.
1153    * </ul>
1154    *
1155    * <p>
1156    * Serialization method is a no-op.
1157    */
1158   public static class BooleanOrSchemaSwap extends ObjectSwap<Object,Object> {
1159
1160      @Override /* ObjectSwap */
1161      public Object swap(BeanSession session, Object o) throws SerializeException {
1162         return o;
1163      }
1164
1165      @Override /* ObjectSwap */
1166      public Object unswap(BeanSession session, Object o, ClassMeta<?> hint) throws ParseException {
1167         ClassMeta<?> cm = (
1168            o instanceof Boolean
1169            ? session.getClassMeta(Boolean.class)
1170            : session.getClassMeta(JsonSchema.class)
1171         );
1172         return session.convertToType(o, cm);
1173      }
1174   }
1175
1176   /**
1177    * Bean property getter:  <property>enum</property>.
1178    *
1179    * @return The value of the <property>enum</property> property on this bean, or <jk>null</jk> if it is not set.
1180    */
1181   public List<String> getEnum() {
1182      return _enum;
1183   }
1184
1185   /**
1186    * Bean property setter:  <property>enum</property>.
1187    *
1188    * @param _enum The new value for the <property>enum</property> property on this bean.
1189    * @return This object.
1190    */
1191   public JsonSchema setEnum(List<String> _enum) {
1192      this._enum = _enum;
1193      return this;
1194   }
1195
1196   /**
1197    * Bean property appender:  <property>enum</property>.
1198    *
1199    * @param _enum The list of items to append to the <property>enum</property> property on this bean.
1200    * @return This object.
1201    */
1202   public JsonSchema addEnum(String..._enum) {
1203      if (this._enum == null)
1204         this._enum = new LinkedList<>();
1205      for (String e : _enum)
1206         this._enum.add(e);
1207      return this;
1208   }
1209
1210   /**
1211    * Bean property getter:  <property>allOf</property>.
1212    *
1213    * @return The value of the <property>allOf</property> property on this bean, or <jk>null</jk> if it is not set.
1214    */
1215   public List<JsonSchema> getAllOf() {
1216      return allOf;
1217   }
1218
1219   /**
1220    * Bean property setter:  <property>allOf</property>.
1221    *
1222    * @param allOf The new value for the <property>allOf</property> property on this bean.
1223    * @return This object.
1224    */
1225   public JsonSchema setAllOf(List<JsonSchema> allOf) {
1226      this.allOf = allOf;
1227      setMasterOn(allOf);
1228      return this;
1229   }
1230
1231   /**
1232    * Bean property appender:  <property>allOf</property>.
1233    *
1234    * @param allOf The list of items to append to the <property>allOf</property> property on this bean.
1235    * @return This object.
1236    */
1237   public JsonSchema addAllOf(JsonSchema...allOf) {
1238      setMasterOn(allOf);
1239      this.allOf = addAll(this.allOf, allOf);
1240      return this;
1241   }
1242
1243   /**
1244    * Bean property getter:  <property>anyOf</property>.
1245    *
1246    * @return The value of the <property>anyOf</property> property on this bean, or <jk>null</jk> if it is not set.
1247    */
1248   public List<JsonSchema> getAnyOf() {
1249      return anyOf;
1250   }
1251
1252   /**
1253    * Bean property setter:  <property>anyOf</property>.
1254    *
1255    * @param anyOf The new value of the <property>anyOf</property> property on this bean.
1256    * @return This object.
1257    */
1258   public JsonSchema setAnyOf(List<JsonSchema> anyOf) {
1259      this.anyOf = anyOf;
1260      setMasterOn(anyOf);
1261      return this;
1262   }
1263
1264   /**
1265    * Bean property appender:  <property>anyOf</property>.
1266    *
1267    * @param anyOf The list of items to append to the <property>anyOf</property> property on this bean.
1268    * @return This object.
1269    */
1270   public JsonSchema addAnyOf(JsonSchema...anyOf) {
1271      if (this.anyOf == null)
1272         this.anyOf = new LinkedList<>();
1273      setMasterOn(anyOf);
1274      for (JsonSchema s : anyOf)
1275         this.anyOf.add(s);
1276      return this;
1277   }
1278
1279   /**
1280    * Bean property getter:  <property>oneOf</property>.
1281    *
1282    * @return The value of the <property>oneOf</property> property on this bean, or <jk>null</jk> if it is not set.
1283    */
1284   public List<JsonSchema> getOneOf() {
1285      return oneOf;
1286   }
1287
1288   /**
1289    * Bean property setter:  <property>oneOf</property>.
1290    *
1291    * @param oneOf The new value for the <property>oneOf</property> property on this bean.
1292    * @return This object.
1293    */
1294   public JsonSchema setOneOf(List<JsonSchema> oneOf) {
1295      this.oneOf = oneOf;
1296      setMasterOn(oneOf);
1297      return this;
1298   }
1299
1300   /**
1301    * Bean property appender:  <property>oneOf</property>.
1302    *
1303    * @param oneOf The list of items to append to the <property>oneOf</property> property on this bean.
1304    * @return This object.
1305    */
1306   public JsonSchema addOneOf(JsonSchema...oneOf) {
1307      if (this.oneOf == null)
1308         this.oneOf = new LinkedList<>();
1309      setMasterOn(oneOf);
1310      for (JsonSchema s : oneOf)
1311         this.oneOf.add(s);
1312      return this;
1313   }
1314
1315   /**
1316    * Bean property getter:  <property>not</property>.
1317    *
1318    * @return The value of the <property>not</property> property on this bean, or <jk>null</jk> if it is not set.
1319    */
1320   public JsonSchema getNot() {
1321      return not;
1322   }
1323
1324   /**
1325    * Bean property setter:  <property>not</property>.
1326    *
1327    * @param not The new value for the <property>not</property> property on this bean.
1328    * @return This object.
1329    */
1330   public JsonSchema setNot(JsonSchema not) {
1331      this.not = not;
1332      setMasterOn(not);
1333      return this;
1334   }
1335
1336   /**
1337    * Bean property getter:  <property>$ref</property>.
1338    *
1339    * @return The value of the <property>$ref</property> property on this bean, or <jk>null</jk> if it is not set.
1340    */
1341   @Beanp("$ref")
1342   public URI getRef() {
1343      return ref;
1344   }
1345
1346   /**
1347    * Bean property setter:  <property>$ref</property>.
1348    *
1349    * <p>
1350    * The value can be of any of the following types: {@link URI}, {@link URL}, {@link String}.
1351    * Strings must be valid URIs.
1352    *
1353    * <p>
1354    * URIs defined by {@link UriResolver} can be used for values.
1355    *
1356    * @param ref The new value for the <property>$ref</property> property on this bean.
1357    * @return This object.
1358    */
1359   @Beanp("$ref")
1360   public JsonSchema setRef(Object ref) {
1361      this.ref = toURI(ref);
1362      return this;
1363   }
1364
1365   private void setMasterOn(JsonSchema s) {
1366      if (s != null)
1367         s.setMaster(this);
1368   }
1369
1370   private void setMasterOn(JsonSchema[] ss) {
1371      if (ss != null)
1372         for (JsonSchema s : ss)
1373            setMasterOn(s);
1374   }
1375
1376   private void setMasterOn(Collection<JsonSchema> ss) {
1377      if (ss != null)
1378         ss.forEach(x -> setMasterOn(x));
1379   }
1380
1381   private void setMasterOn(JsonSchemaArray ss) {
1382      if (ss != null)
1383         ss.forEach(x -> setMasterOn(x));
1384   }
1385
1386   /**
1387    * Sets the master schema for this schema and all child schema objects.
1388    *
1389    * <p>
1390    * All child elements in a schema should point to a single "master" schema in order to locate registered JsonSchemaMap
1391    * objects for resolving external schemas.
1392    *
1393    * @param master The master schema to associate on this and all children.  Can be <jk>null</jk>.
1394    */
1395   protected void setMaster(JsonSchema master) {
1396      this.master = master;
1397      if (definitions != null)
1398         definitions.values().forEach(x -> x.setMaster(master));
1399      if (properties != null)
1400         properties.values().forEach(x -> x.setMaster(master));
1401      if (patternProperties != null)
1402         patternProperties.values().forEach(x -> x.setMaster(master));
1403      if (dependencies != null)
1404         dependencies.values().forEach(x -> x.setMaster(master));
1405      if (itemsSchema != null)
1406         itemsSchema.setMaster(master);
1407      if (itemsSchemaArray != null)
1408         itemsSchemaArray.forEach(x -> x.setMaster(master));
1409      if (additionalItemsSchemaArray != null)
1410         additionalItemsSchemaArray.forEach(x -> x.setMaster(master));
1411      if (additionalPropertiesSchema != null)
1412         additionalPropertiesSchema.setMaster(master);
1413      if (allOf != null)
1414         allOf.forEach(x -> x.setMaster(master));
1415      if (anyOf != null)
1416         anyOf.forEach(x -> x.setMaster(master));
1417      if (oneOf != null)
1418         oneOf.forEach(x -> x.setMaster(master));
1419      if (not != null)
1420         not.setMaster(master);
1421   }
1422
1423   /**
1424    * Resolve schema if reference.
1425    *
1426    * <p>
1427    * If this schema is a reference to another schema (has its <property>$ref</property> property set), this
1428    * method will retrieve the referenced schema from the schema map registered with this schema.
1429    *
1430    * <p>
1431    * If this schema is not a reference, or no schema map is registered with this schema, this method is a no-op and
1432    * simply returns this object.
1433    *
1434    * @return The referenced schema, or <jk>null</jk>.
1435    */
1436   public JsonSchema resolve() {
1437      if (ref == null || master.schemaMap == null)
1438         return this;
1439      return master.schemaMap.get(ref);
1440   }
1441
1442   /**
1443    * Associates a schema map with this schema for resolving other schemas identified through <property>$ref</property>
1444    * properties.
1445    *
1446    * @param schemaMap The schema map to associate with this schema.  Can be <jk>null</jk>.
1447    * @return This object.
1448    */
1449   public JsonSchema setSchemaMap(JsonSchemaMap schemaMap) {
1450      this.schemaMap = schemaMap;
1451      return this;
1452   }
1453
1454   @Override /* Object */
1455   public String toString() {
1456      return JsonSerializer.DEFAULT.toString(this);
1457   }
1458}