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