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