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