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