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.jsonschema;
014
015import static org.apache.juneau.http.annotation.AnnotationUtils.*;
016import static org.apache.juneau.internal.StringUtils.*;
017
018import java.util.*;
019
020import org.apache.juneau.*;
021import org.apache.juneau.internal.*;
022import org.apache.juneau.jsonschema.annotation.*;
023import org.apache.juneau.parser.*;
024import org.apache.juneau.utils.*;
025
026/**
027 * Utilities for working with the schema annotations.
028 */
029public class SchemaUtils {
030
031   /**
032    * Converts the specified <ja>@Schema</ja> annotation into a generic map.
033    *
034    * @param a The annotation instance.  Can be <jk>null</jk>.
035    * @return The schema converted to a map, or and empty map if the annotation was null.
036    * @throws ParseException
037    */
038   public static ObjectMap asMap(Schema a) throws ParseException {
039      if (a == null)
040         return ObjectMap.EMPTY_MAP;
041      ObjectMap om = new ObjectMap();
042      if (empty(a))
043         return om;
044      if (a.value().length > 0)
045         om.putAll(parseMap(a.value()));
046   return om
047      .appendSkipEmpty("additionalProperties", toObjectMap(a.additionalProperties()))
048      .appendSkipEmpty("allOf", joinnl(a.allOf()))
049      .appendSkipEmpty("collectionFormat", a.collectionFormat())
050      .appendSkipEmpty("default", joinnl(a._default()))
051      .appendSkipEmpty("discriminator", a.discriminator())
052      .appendSkipEmpty("description", joinnl(a.description()))
053      .appendSkipEmpty("enum", toSet(a._enum()))
054      .appendSkipEmpty("examples", parseMap(a.examples()))
055      .appendSkipFalse("exclusiveMaximum", a.exclusiveMaximum())
056      .appendSkipFalse("exclusiveMinimum", a.exclusiveMinimum())
057      .appendSkipEmpty("externalDocs", merge(om.getObjectMap("externalDocs"), a.externalDocs()))
058      .appendSkipEmpty("format", a.format())
059      .appendSkipEmpty("ignore", a.ignore() ? "true" : null)
060      .appendSkipEmpty("items", merge(om.getObjectMap("items"), a.items()))
061      .appendSkipEmpty("maximum", a.maximum())
062      .appendSkipMinusOne("maxItems", a.maxItems())
063      .appendSkipMinusOne("maxLength", a.maxLength())
064      .appendSkipMinusOne("maxProperties", a.maxProperties())
065      .appendSkipEmpty("minimum", a.minimum())
066      .appendSkipMinusOne("minItems", a.minItems())
067      .appendSkipMinusOne("minLength", a.minLength())
068      .appendSkipMinusOne("minProperties", a.minProperties())
069      .appendSkipEmpty("multipleOf", a.multipleOf())
070      .appendSkipEmpty("pattern", a.pattern())
071      .appendSkipEmpty("properties", toObjectMap(a.properties()))
072      .appendSkipFalse("readOnly", a.readOnly())
073      .appendSkipFalse("required", a.required())
074      .appendSkipEmpty("title", a.title())
075      .appendSkipEmpty("type", a.type())
076      .appendSkipFalse("uniqueItems", a.uniqueItems())
077      .appendSkipEmpty("xml", joinnl(a.xml()))
078      .appendSkipEmpty("x-example", joinnl(a.example()))
079      .appendSkipEmpty("$ref", a.$ref())
080   ;
081   }
082
083   private static ObjectMap toObjectMap(String[] ss) throws ParseException {
084      if (ss.length == 0)
085         return null;
086      String s = joinnl(ss);
087      if (s.isEmpty())
088         return null;
089      if (! isObjectMap(s, true))
090         s = "{" + s + "}";
091      return new ObjectMap(s);
092   }
093
094   private static ObjectMap parseMap(Object o) throws ParseException {
095      if (o == null)
096         return null;
097      if (o instanceof String[])
098         o = joinnl((String[])o);
099      if (o instanceof String) {
100         String s = o.toString();
101         if (s.isEmpty())
102            return null;
103         if ("IGNORE".equalsIgnoreCase(s))
104            return new ObjectMap().append("ignore", true);
105         if (! isObjectMap(s, true))
106            s = "{" + s + "}";
107         return new ObjectMap(s);
108      }
109      if (o instanceof ObjectMap)
110         return (ObjectMap)o;
111      throw new ParseException("Unexpected data type ''{0}''.  Expected ObjectMap or String.", o.getClass().getName());
112   }
113
114   private static Set<String> toSet(String[] ss) throws ParseException {
115      if (ss.length == 0)
116         return null;
117      String s = joinnl(ss);
118      if (s.isEmpty())
119         return null;
120      Set<String> set = new ASet<>();
121      for (Object o : StringUtils.parseListOrCdl(s))
122         set.add(o.toString());
123      return set;
124   }
125
126   private static ObjectMap merge(ObjectMap om, Items a) throws ParseException {
127      if (empty(a))
128         return om;
129      if (a.value().length > 0)
130         om.putAll(parseMap(a.value()));
131      return om
132         .appendSkipEmpty("collectionFormat", a.collectionFormat())
133         .appendSkipEmpty("default", joinnl(a._default()))
134         .appendSkipEmpty("enum", toSet(a._enum()))
135         .appendSkipEmpty("format", a.format())
136         .appendSkipFalse("exclusiveMaximum", a.exclusiveMaximum())
137         .appendSkipFalse("exclusiveMinimum", a.exclusiveMinimum())
138         .appendSkipEmpty("items", merge(om.getObjectMap("items"), a.items()))
139         .appendSkipEmpty("maximum", a.maximum())
140         .appendSkipMinusOne("maxItems", a.maxItems())
141         .appendSkipMinusOne("maxLength", a.maxLength())
142         .appendSkipEmpty("minimum", a.minimum())
143         .appendSkipMinusOne("minItems", a.minItems())
144         .appendSkipMinusOne("minLength", a.minLength())
145         .appendSkipEmpty("multipleOf", a.multipleOf())
146         .appendSkipEmpty("pattern", a.pattern())
147         .appendSkipFalse("uniqueItems", a.uniqueItems())
148         .appendSkipEmpty("type", a.type())
149         .appendSkipEmpty("$ref", a.$ref())
150      ;
151   }
152
153   private static ObjectMap merge(ObjectMap om, SubItems a) throws ParseException {
154      if (empty(a))
155         return om;
156      if (a.value().length > 0)
157         om.putAll(parseMap(a.value()));
158      return om
159         .appendSkipEmpty("collectionFormat", a.collectionFormat())
160         .appendSkipEmpty("default", joinnl(a._default()))
161         .appendSkipEmpty("enum", toSet(a._enum()))
162         .appendSkipFalse("exclusiveMaximum", a.exclusiveMaximum())
163         .appendSkipFalse("exclusiveMinimum", a.exclusiveMinimum())
164         .appendSkipEmpty("format", a.format())
165         .appendSkipEmpty("items", toObjectMap(a.items()))
166         .appendSkipEmpty("maximum", a.maximum())
167         .appendSkipMinusOne("maxItems", a.maxItems())
168         .appendSkipMinusOne("maxLength", a.maxLength())
169         .appendSkipEmpty("minimum", a.minimum())
170         .appendSkipMinusOne("minItems", a.minItems())
171         .appendSkipMinusOne("minLength", a.minLength())
172         .appendSkipEmpty("multipleOf", a.multipleOf())
173         .appendSkipEmpty("pattern", a.pattern())
174         .appendSkipEmpty("type", a.type())
175         .appendSkipFalse("uniqueItems", a.uniqueItems())
176         .appendSkipEmpty("$ref", a.$ref())
177      ;
178   }
179
180   private static ObjectMap merge(ObjectMap om, ExternalDocs a) throws ParseException {
181      if (empty(a))
182         return om;
183      if (a.value().length > 0)
184         om.putAll(parseMap(a.value()));
185      return om
186         .appendSkipEmpty("description", joinnl(a.description()))
187         .appendSkipEmpty("url", a.url())
188      ;
189   }
190}