001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.bean.openapi3;
018
019import static org.apache.juneau.common.utils.Utils.*;
020import static org.apache.juneau.internal.ArrayUtils.contains;
021import static org.apache.juneau.internal.CollectionUtils.*;
022import static org.apache.juneau.internal.CollectionUtils.copyOf;
023import static org.apache.juneau.internal.ConverterUtils.*;
024
025import java.util.*;
026
027import org.apache.juneau.*;
028import org.apache.juneau.annotation.*;
029import org.apache.juneau.collections.*;
030import org.apache.juneau.common.utils.*;
031import org.apache.juneau.internal.*;
032import org.apache.juneau.json.*;
033
034/**
035 * A limited subset of JSON-Schema's items object.
036 *
037 * <p>
038 * The Items Object is a limited subset of JSON-Schema's items object. It is used by parameter definitions that are 
039 * not located in "body" to describe the type of items in an array. This is particularly useful for query parameters, 
040 * path parameters, and header parameters that accept arrays.
041 *
042 * <h5 class='section'>OpenAPI Specification:</h5>
043 * <p>
044 * The Items Object supports the following fields from JSON Schema:
045 * <ul class='spaced-list'>
046 *    <li><c>type</c> (string, REQUIRED) - The data type. Values: <js>"string"</js>, <js>"number"</js>, <js>"integer"</js>, <js>"boolean"</js>, <js>"array"</js>
047 *    <li><c>format</c> (string) - The format modifier (e.g., <js>"int32"</js>, <js>"int64"</js>, <js>"float"</js>, <js>"double"</js>, <js>"date"</js>, <js>"date-time"</js>)
048 *    <li><c>items</c> ({@link Items}) - Required if type is <js>"array"</js>. Describes the type of items in the array
049 *    <li><c>collectionFormat</c> (string) - How multiple values are formatted. Values: <js>"csv"</js>, <js>"ssv"</js>, <js>"tsv"</js>, <js>"pipes"</js>, <js>"multi"</js>
050 *    <li><c>default</c> (any) - The default value
051 *    <li><c>maximum</c> (number), <c>exclusiveMaximum</c> (boolean), <c>minimum</c> (number), <c>exclusiveMinimum</c> (boolean) - Numeric constraints
052 *    <li><c>maxLength</c> (integer), <c>minLength</c> (integer), <c>pattern</c> (string) - String constraints
053 *    <li><c>maxItems</c> (integer), <c>minItems</c> (integer), <c>uniqueItems</c> (boolean) - Array constraints
054 *    <li><c>enum</c> (array) - Possible values for this item
055 *    <li><c>multipleOf</c> (number) - Must be a multiple of this value
056 * </ul>
057 *
058 * <h5 class='section'>Example:</h5>
059 * <p class='bcode'>
060 *    <jc>// Construct using SwaggerBuilder.</jc>
061 *    Items <jv>x</jv> = <jsm>items</jsm>(<js>"string"</js>).minLength(2);
062 *
063 *    <jc>// Serialize using JsonSerializer.</jc>
064 *    String <jv>json</jv> = Json.<jsm>from</jsm>(<jv>x</jv>);
065 *
066 *    <jc>// Or just use toString() which does the same as above.</jc>
067 *    String <jv>json</jv> = <jv>x</jv>.toString();
068 * </p>
069 * <p class='bcode'>
070 *    <jc>// Output</jc>
071 *    {
072 *       <js>"type"</js>: <js>"string"</js>,
073 *       <js>"minLength"</js>: 2
074 *    }
075 * </p>
076 *
077 * <h5 class='section'>See Also:</h5><ul>
078 *    <li class='link'><a class="doclink" href="https://spec.openapis.org/oas/v3.0.0#items-object">OpenAPI Specification &gt; Items Object</a>
079 *    <li class='link'><a class="doclink" href="https://swagger.io/docs/specification/describing-parameters/">OpenAPI Describing Parameters</a>
080 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauBeanOpenApi3">juneau-bean-openapi-v3</a>
081 * </ul>
082 */
083public class Items extends OpenApiElement {
084
085   private static final String[] VALID_TYPES = {"string", "number", "integer", "boolean", "array"};
086   private static final String[] VALID_COLLECTION_FORMATS = {"csv","ssv","tsv","pipes","multi"};
087
088   private String
089      type,
090      format,
091      collectionFormat,
092      pattern,
093      ref;
094   private Number
095      maximum,
096      minimum,
097      multipleOf;
098   private Integer
099      maxLength,
100      minLength,
101      maxItems,
102      minItems;
103   private Boolean
104      exclusiveMaximum,
105      exclusiveMinimum,
106      uniqueItems;
107   private Items items;  // NOSONAR - Intentional naming.
108   private Object _default;  // NOSONAR - Intentional naming.
109   private List<Object> _enum;  // NOSONAR - Intentional naming.
110
111   /**
112    * Default constructor.
113    */
114   public Items() {}
115
116   /**
117    * Copy constructor.
118    *
119    * @param copyFrom The object to copy.
120    */
121   public Items(Items copyFrom) {
122      super(copyFrom);
123
124      this.type = copyFrom.type;
125      this.format = copyFrom.format;
126      this.collectionFormat = copyFrom.collectionFormat;
127      this.pattern = copyFrom.pattern;
128      this.maximum = copyFrom.maximum;
129      this.minimum = copyFrom.minimum;
130      this.multipleOf = copyFrom.multipleOf;
131      this.maxLength = copyFrom.maxLength;
132      this.minLength = copyFrom.minLength;
133      this.maxItems = copyFrom.maxItems;
134      this.minItems = copyFrom.minItems;
135      this.exclusiveMaximum = copyFrom.exclusiveMaximum;
136      this.exclusiveMinimum = copyFrom.exclusiveMinimum;
137      this.uniqueItems = copyFrom.uniqueItems;
138      this.items = copyFrom.items == null ? null : copyFrom.items.copy();
139      this._default = copyFrom._default;
140      this._enum = copyOf(copyFrom._enum);
141      this.ref = copyFrom.ref;
142   }
143
144   /**
145    * Make a deep copy of this object.
146    *
147    * @return A deep copy of this object.
148    */
149   public Items copy() {
150      return new Items(this);
151   }
152
153   @Override /* SwaggerElement */
154   protected Items strict() {
155      super.strict();
156      return this;
157   }
158
159   @Override /* Overridden from OpenApiElement */
160   public Items strict(Object value) {
161      super.strict(value);
162      return this;
163   }
164
165   /**
166    * Bean property getter:  <property>type</property>.
167    *
168    * <p>
169    * The internal type of the array.
170    *
171    * @return The property value, or <jk>null</jk> if it is not set.
172    */
173   public String getType() {
174      return type;
175   }
176
177   /**
178    * Bean property setter:  <property>type</property>.
179    *
180    * <p>
181    * The internal type of the array.
182    *
183    * @param value
184    *    The new value for this property.
185    *    <br>Valid values:
186    *    <ul>
187    *       <li><js>"string"</js>
188    *       <li><js>"number"</js>
189    *       <li><js>"integer"</js>
190    *       <li><js>"boolean"</js>
191    *       <li><js>"array"</js>
192    *    </ul>
193    *    <br>Property value is required.
194    *    <br>Can be <jk>null</jk> to unset the property.
195    * @return This object
196    */
197   public Items setType(String value) {
198      if (isStrict() && ! contains(value, VALID_TYPES))
199         throw new IllegalArgumentException(
200            "Invalid value passed in to setType(String).  Value='"+value+"', valid values="
201            + Json5Serializer.DEFAULT.toString(VALID_TYPES));
202      type = value;
203      return this;
204   }
205
206   /**
207    * Bean property getter:  <property>format</property>.
208    *
209    * <p>
210    * The extending format for the previously mentioned <code>type</code>.
211    *
212    * @return The property value, or <jk>null</jk> if it is not set.
213    */
214   public String getFormat() {
215      return format;
216   }
217
218   /**
219    * Bean property setter:  <property>format</property>.
220    *
221    * <p>
222    * The extending format for the previously mentioned <code>type</code>.
223    *
224    * @param value
225    *    The new value for this property.
226    *    <br>Can be <jk>null</jk> to unset the property.
227    * @return This object
228    */
229   public Items setFormat(String value) {
230      format = value;
231      return this;
232   }
233
234   /**
235    * Bean property getter:  <property>items</property>.
236    *
237    * <p>
238    * Describes the type of items in the array.
239    *
240    * @return The property value, or <jk>null</jk> if it is not set.
241    */
242   public Items getItems() {
243      return items;
244   }
245
246   /**
247    * Bean property setter:  <property>items</property>.
248    *
249    * <p>
250    * Describes the type of items in the array.
251    *
252    * @param value
253    *    The new value for this property.
254    *    <br>Property value is required if <code>type</code> is <js>"array"</js>.
255    *    <br>Can be <jk>null</jk> to unset the property.
256    * @return This object
257    */
258   public Items setItems(Items value) {
259      items = value;
260      return this;
261   }
262
263   /**
264    * Bean property getter:  <property>collectionFormat</property>.
265    *
266    * <p>
267    * Determines the format of the array if type array is used.
268    *
269    * @return The property value, or <jk>null</jk> if it is not set.
270    */
271   public String getCollectionFormat() {
272      return collectionFormat;
273   }
274
275   /**
276    * Bean property setter:  <property>collectionFormat</property>.
277    *
278    * <p>
279    * Determines the format of the array if type array is used.
280    *
281    * @param value
282    *    The new value for this property.
283    *    <br>Valid values:
284    *    <ul>
285    *       <li><js>"csv"</js> (default) - comma separated values <code>foo,bar</code>.
286    *       <li><js>"ssv"</js> - space separated values <code>foo bar</code>.
287    *       <li><js>"tsv"</js> - tab separated values <code>foo\tbar</code>.
288    *       <li><js>"pipes"</js> - pipe separated values <code>foo|bar</code>.
289    *    </ul>
290    *    <br>Can be <jk>null</jk> to unset the property.
291    * @return This object
292    */
293   public Items setCollectionFormat(String value) {
294      if (isStrict() && ! contains(value, VALID_COLLECTION_FORMATS))
295         throw new BasicRuntimeException(
296            "Invalid value passed in to setCollectionFormat(String).  Value=''{0}'', valid values={1}",
297            value, VALID_COLLECTION_FORMATS
298         );
299      collectionFormat = value;
300      return this;
301   }
302
303   /**
304    * Bean property getter:  <property>default</property>.
305    *
306    * <p>
307    * Declares the value of the item that the server will use if none is provided.
308    *
309    * <h5 class='section'>Notes:</h5>
310    * <ul class='spaced-list'>
311    *    <li>
312    *       <js>"default"</js> has no meaning for required items.
313    *    <li>
314    *       Unlike JSON Schema this value MUST conform to the defined <code>type</code> for the data type.
315    * </ul>
316    *
317    * @return The property value, or <jk>null</jk> if it is not set.
318    */
319   public Object getDefault() {
320      return _default;
321   }
322
323   /**
324    * Bean property setter:  <property>default</property>.
325    *
326    * <p>
327    * Declares the value of the item that the server will use if none is provided.
328    *
329    * <h5 class='section'>Notes:</h5>
330    * <ul class='spaced-list'>
331    *    <li>
332    *       <js>"default"</js> has no meaning for required items.
333    *    <li>
334    *       Unlike JSON Schema this value MUST conform to the defined <code>type</code> for the data type.
335    * </ul>
336    *
337    * @param value
338    *    The new value for this property.
339    *    <br>Can be <jk>null</jk> to unset the property.
340    * @return This object
341    */
342   public Items setDefault(Object value) {
343      _default = value;
344      return this;
345   }
346
347   /**
348    * Bean property getter:  <property>maximum</property>.
349    *
350    * @return The property value, or <jk>null</jk> if it is not set.
351    */
352   public Number getMaximum() {
353      return maximum;
354   }
355
356   /**
357    * Bean property setter:  <property>maximum</property>.
358    *
359    * @param value
360    *    The new value for this property.
361    *    <br>Can be <jk>null</jk> to unset the property.
362    * @return This object
363    */
364   public Items setMaximum(Number value) {
365      maximum = value;
366      return this;
367   }
368
369   /**
370    * Bean property getter:  <property>exclusiveMaximum</property>.
371    *
372    * @return The property value, or <jk>null</jk> if it is not set.
373    */
374   public Boolean getExclusiveMaximum() {
375      return exclusiveMaximum;
376   }
377
378   /**
379    * Bean property setter:  <property>exclusiveMaximum</property>.
380    *
381    * @param value
382    *    The new value for this property.
383    *    <br>Can be <jk>null</jk> to unset the property.
384    * @return This object
385    */
386   public Items setExclusiveMaximum(Boolean value) {
387      exclusiveMaximum = value;
388      return this;
389   }
390
391   /**
392    * Bean property getter:  <property>minimum</property>.
393    *
394    * @return The property value, or <jk>null</jk> if it is not set.
395    */
396   public Number getMinimum() {
397      return minimum;
398   }
399
400   /**
401    * Bean property setter:  <property>minimum</property>.
402    *
403    * @param value
404    *    The new value for this property.
405    *    <br>Can be <jk>null</jk> to unset the property.
406    * @return This object
407    */
408   public Items setMinimum(Number value) {
409      minimum = value;
410      return this;
411   }
412
413   /**
414    * Bean property getter:  <property>exclusiveMinimum</property>.
415    *
416    * @return The property value, or <jk>null</jk> if it is not set.
417    */
418   public Boolean getExclusiveMinimum() {
419      return exclusiveMinimum;
420   }
421
422   /**
423    * Bean property setter:  <property>exclusiveMinimum</property>.
424    *
425    * @param value
426    *    The new value for this property.
427    *    <br>Can be <jk>null</jk> to unset the property.
428    * @return This object
429    */
430   public Items setExclusiveMinimum(Boolean value) {
431      exclusiveMinimum = value;
432      return this;
433   }
434
435   /**
436    * Bean property getter:  <property>maxLength</property>.
437    *
438    * @return The property value, or <jk>null</jk> if it is not set.
439    */
440   public Integer getMaxLength() {
441      return maxLength;
442   }
443
444   /**
445    * Bean property setter:  <property>maxLength</property>.
446    *
447    * @param value
448    *    The new value for this property.
449    *    <br>Can be <jk>null</jk> to unset the property.
450    * @return This object
451    */
452   public Items setMaxLength(Integer value) {
453      maxLength = value;
454      return this;
455   }
456
457   /**
458    * Bean property getter:  <property>minLength</property>.
459    *
460    * @return The property value, or <jk>null</jk> if it is not set.
461    */
462   public Integer getMinLength() {
463      return minLength;
464   }
465
466   /**
467    * Bean property setter:  <property>minLength</property>.
468    *
469    * @param value
470    *    The new value for this property.
471    *    <br>Can be <jk>null</jk> to unset the property.
472    * @return This object
473    */
474   public Items setMinLength(Integer value) {
475      minLength = value;
476      return this;
477   }
478
479   /**
480    * Bean property getter:  <property>pattern</property>.
481    *
482    * @return The property value, or <jk>null</jk> if it is not set.
483    */
484   public String getPattern() {
485      return pattern;
486   }
487
488   /**
489    * Bean property setter:  <property>pattern</property>.
490    *
491    * <p>
492    * This string SHOULD be a valid regular expression.
493    *
494    * @param value
495    *    The new value for this property.
496    *    <br>Can be <jk>null</jk> to unset the property.
497    * @return This object
498    */
499   public Items setPattern(String value) {
500      pattern = value;
501      return this;
502   }
503
504   /**
505    * Bean property getter:  <property>maxItems</property>.
506    *
507    * @return The property value, or <jk>null</jk> if it is not set.
508    */
509   public Integer getMaxItems() {
510      return maxItems;
511   }
512
513   /**
514    * Bean property setter:  <property>maxItems</property>.
515    *
516    * @param value
517    *    The new value for this property.
518    *    <br>Can be <jk>null</jk> to unset the property.
519    * @return This object
520    */
521   public Items setMaxItems(Integer value) {
522      maxItems = value;
523      return this;
524   }
525
526   /**
527    * Bean property getter:  <property>minItems</property>.
528    *
529    * @return The property value, or <jk>null</jk> if it is not set.
530    */
531   public Integer getMinItems() {
532      return minItems;
533   }
534
535   /**
536    * Bean property setter:  <property>minItems</property>.
537    *
538    * @param value
539    *    The new value for this property.
540    *    <br>Can be <jk>null</jk> to unset the property.
541    * @return This object
542    */
543   public Items setMinItems(Integer value) {
544      minItems = value;
545      return this;
546   }
547
548   /**
549    * Bean property getter:  <property>uniqueItems</property>.
550    *
551    * @return The property value, or <jk>null</jk> if it is not set.
552    */
553   public Boolean getUniqueItems() {
554      return uniqueItems;
555   }
556
557   /**
558    * Bean property setter:  <property>uniqueItems</property>.
559    *
560    * @param value
561    *    The new value for this property.
562    *    <br>Can be <jk>null</jk> to unset the property.
563    * @return This object
564    */
565   public Items setUniqueItems(Boolean value) {
566      uniqueItems = value;
567      return this;
568   }
569
570   /**
571    * Bean property getter:  <property>enum</property>.
572    *
573    * @return The property value, or <jk>null</jk> if it is not set.
574    */
575   public List<Object> getEnum() {
576      return _enum;
577   }
578
579   /**
580    * Bean property setter:  <property>enum</property>.
581    *
582    * @param value
583    *    The new value for this property.
584    *    <br>Can be <jk>null</jk> to unset the property.
585    * @return This object
586    */
587   public Items setEnum(Collection<Object> value) {
588      _enum = listFrom(value);
589      return this;
590   }
591
592   /**
593    * Adds one or more values to the <property>enum</property> property.
594    *
595    * @param values
596    *    The values to add to this property.
597    *    <br>Ignored if <jk>null</jk>.
598    * @return This object
599    */
600   public Items addEnum(Object...values) {
601      _enum = listBuilder(_enum).elementType(Object.class).sparse().addAny(values).build();
602      return this;
603   }
604
605   /**
606    * Adds one or more values to the <property>enum</property> property.
607    *
608    * @param values
609    *    The values to add to this property.
610    *    <br>Valid types:
611    *    <ul>
612    *       <li><code>Object</code>
613    *       <li><code>Collection&lt;Object&gt;</code>
614    *       <li><code>String</code> - JSON array representation of <code>Collection&lt;Object&gt;</code>
615    *          <h5 class='figure'>Example:</h5>
616    *          <p class='bcode'>
617    *    _enum(<js>"['foo','bar']"</js>);
618    *          </p>
619    *       <li><code>String</code> - Individual values
620    *          <h5 class='figure'>Example:</h5>
621    *          <p class='bcode'>
622    *    _enum(<js>"foo"</js>, <js>"bar"</js>);
623    *          </p>
624    *    </ul>
625    *    <br>Ignored if <jk>null</jk>.
626    * @return This object
627    */
628   public Items setEnum(Object...values) {  // NOSONAR - Intentional naming.
629      _enum = listBuilder(_enum).elementType(Object.class).sparse().addAny(values).build();
630      return this;
631   }
632
633   /**
634    * Bean property getter:  <property>multipleOf</property>.
635    *
636    * @return The property value, or <jk>null</jk> if it is not set.
637    */
638   public Number getMultipleOf() {
639      return multipleOf;
640   }
641
642   /**
643    * Bean property setter:  <property>multipleOf</property>.
644    *
645    * @param value
646    *    The new value for this property.
647    *    <br>Can be <jk>null</jk> to unset the property.
648    * @return This object
649    */
650   public Items setMultipleOf(Number value) {
651      multipleOf = value;
652      return this;
653   }
654
655   /**
656    * Bean property getter:  <property>$ref</property>.
657    *
658    * @return The property value, or <jk>null</jk> if it is not set.
659    */
660   @Beanp("$ref")
661   public String getRef() {
662      return ref;
663   }
664
665   /**
666    * Bean property setter:  <property>$ref</property>.
667    *
668    * @param value
669    *    The new value for this property.
670    *    <br>Can be <jk>null</jk> to unset the property.
671    * @return This object
672    */
673   @Beanp("$ref")
674   public Items setRef(Object value) {
675      ref = Utils.s(value);
676      return this;
677   }
678
679   @Override /* SwaggerElement */
680   public <T> T get(String property, Class<T> type) {
681      assertArgNotNull("property", property);
682      return switch (property) {
683         case "type" -> toType(getType(), type);
684         case "format" -> toType(getFormat(), type);
685         case "items" -> toType(getItems(), type);
686         case "collectionFormat" -> toType(getCollectionFormat(), type);
687         case "default" -> toType(getDefault(), type);
688         case "maximum" -> toType(getMaximum(), type);
689         case "exclusiveMaximum" -> toType(getExclusiveMaximum(), type);
690         case "minimum" -> toType(getMinimum(), type);
691         case "exclusiveMinimum" -> toType(getExclusiveMinimum(), type);
692         case "maxLength" -> toType(getMaxLength(), type);
693         case "minLength" -> toType(getMinLength(), type);
694         case "pattern" -> toType(getPattern(), type);
695         case "maxItems" -> toType(getMaxItems(), type);
696         case "minItems" -> toType(getMinItems(), type);
697         case "uniqueItems" -> toType(getUniqueItems(), type);
698         case "enum" -> toType(getEnum(), type);
699         case "multipleOf" -> toType(getMultipleOf(), type);
700         case "$ref" -> toType(getRef(), type);
701         default -> super.get(property, type);
702      };
703   }
704
705   @Override /* SwaggerElement */
706   public Items set(String property, Object value) {
707      assertArgNotNull("property", property);
708      return switch (property) {
709         case "$ref" -> setRef(value);
710         case "collectionFormat" -> setCollectionFormat(Utils.s(value));
711         case "default" -> setDefault(value);
712         case "enum" -> setEnum(value);
713         case "exclusiveMaximum" -> setExclusiveMaximum(toBoolean(value));
714         case "exclusiveMinimum" -> setExclusiveMinimum(toBoolean(value));
715         case "format" -> setFormat(Utils.s(value));
716         case "items" -> setItems(toType(value, Items.class));
717         case "maxItems" -> setMaxItems(toInteger(value));
718         case "maxLength" -> setMaxLength(toInteger(value));
719         case "maximum" -> setMaximum(toNumber(value));
720         case "minItems" -> setMinItems(toInteger(value));
721         case "minLength" -> setMinLength(toInteger(value));
722         case "minimum" -> setMinimum(toNumber(value));
723         case "multipleOf" -> setMultipleOf(toNumber(value));
724         case "pattern" -> setPattern(Utils.s(value));
725         case "type" -> setType(Utils.s(value));
726         case "uniqueItems" -> setUniqueItems(toBoolean(value));
727         default -> {
728            super.set(property, value);
729            yield this;
730         }
731      };
732   }
733
734   @Override /* SwaggerElement */
735   public Set<String> keySet() {
736      var s = setBuilder(String.class)
737         .addIf(ref != null, "$ref")
738         .addIf(collectionFormat != null, "collectionFormat")
739         .addIf(_default != null, "default")
740         .addIf(_enum != null, "enum")
741         .addIf(exclusiveMaximum != null, "exclusiveMaximum")
742         .addIf(exclusiveMinimum != null, "exclusiveMinimum")
743         .addIf(format != null, "format")
744         .addIf(items != null, "items")
745         .addIf(maxItems != null, "maxItems")
746         .addIf(maxLength != null, "maxLength")
747         .addIf(maximum != null, "maximum")
748         .addIf(minItems != null, "minItems")
749         .addIf(minLength != null, "minLength")
750         .addIf(minimum != null, "minimum")
751         .addIf(multipleOf != null, "multipleOf")
752         .addIf(pattern != null, "pattern")
753         .addIf(type != null, "type")
754         .addIf(uniqueItems != null, "uniqueItems")
755         .build();
756      return new MultiSet<>(s, super.keySet());
757   }
758
759   /**
760    * Resolves any <js>"$ref"</js> attributes in this element.
761    *
762    * @param openApi The swagger document containing the definitions.
763    * @param refStack Keeps track of previously-visited references so that we don't cause recursive loops.
764    * @param maxDepth
765    *    The maximum depth to resolve references.
766    *    <br>After that level is reached, <code>$ref</code> references will be left alone.
767    *    <br>Useful if you have very complex models and you don't want your swagger page to be overly-complex.
768    * @return
769    *    This object with references resolved.
770    *    <br>May or may not be the same object.
771    */
772   public Items resolveRefs(OpenApi openApi, Deque<String> refStack, int maxDepth) {
773
774      if (ref != null) {
775         if (refStack.contains(ref) || refStack.size() >= maxDepth)
776            return this;
777         refStack.addLast(ref);
778         var r = openApi.findRef(ref, Items.class);
779         r = r.resolveRefs(openApi, refStack, maxDepth);
780         refStack.removeLast();
781         return r;
782      }
783
784      set("properties", resolveRefs(get("properties"), openApi, refStack, maxDepth));
785
786      if (items != null)
787         items = items.resolveRefs(openApi, refStack, maxDepth);
788
789      set("example", null);
790
791      return this;
792   }
793
794   /* Resolve references in extra attributes */
795   private Object resolveRefs(Object o, OpenApi openApi, Deque<String> refStack, int maxDepth) {
796      if (o instanceof JsonMap om) {
797         var ref2 = om.get("$ref");
798         if (ref2 instanceof CharSequence) {
799            var sref = ref2.toString();
800            if (refStack.contains(sref) || refStack.size() >= maxDepth)
801               return o;
802            refStack.addLast(sref);
803            var o2 = openApi.findRef(sref, Object.class);
804            o2 = resolveRefs(o2, openApi, refStack, maxDepth);
805            refStack.removeLast();
806            return o2;
807         }
808         for (var e : om.entrySet())
809            e.setValue(resolveRefs(e.getValue(), openApi, refStack, maxDepth));
810      }
811      if (o instanceof JsonList x)
812         for (var li = x.listIterator(); li.hasNext();)
813            li.set(resolveRefs(li.next(), openApi, refStack, maxDepth));
814      return o;
815   }
816}