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.jsonschema;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020import static org.apache.juneau.commons.utils.ThrowableUtils.*;
021
022import java.lang.reflect.*;
023
024import org.apache.juneau.marshall.collections.*;
025import org.apache.juneau.marshall.json.*;
026import org.apache.juneau.marshall.jsonschema.*;
027import org.apache.juneau.marshall.marshaller.Json;
028import org.apache.juneau.marshall.parser.*;
029
030/**
031 * Bridge for generating typed {@link JsonSchema} beans from Java types.
032 *
033 * <p>
034 * {@link JsonSchemaGenerator} in <c>juneau-marshall</c> produces <c>JsonMap</c> output.  This class wraps that
035 * generator and converts the generated map into typed <c>JsonSchema</c> beans in <c>juneau-bean-jsonschema</c>.
036 *
037 * <h5 class='section'>Example:</h5>
038 * <p class='bjava'>
039 *    <jc>// Default generation.</jc>
040 *    JsonSchema <jv>s1</jv> = JsonSchemaBeanGenerator.<jsf>DEFAULT</jsf>.generate(MyBean.<jk>class</jk>);
041 *
042 *    <jc>// Custom generation with bean defs and descriptions.</jc>
043 *    JsonSchema <jv>s2</jv> = JsonSchemaBeanGenerator.<jsm>create</jsm>()
044 *       .useBeanDefs()
045 *       .addDescriptionsTo(TypeCategory.<jsf>ANY</jsf>)
046 *       .build()
047 *       .generate(MyBean.<jk>class</jk>);
048 * </p>
049 */
050@SuppressWarnings({
051   "java:S1452" // generate() and toBean() return JsonSchema<?> because the concrete SELF type is unknown to callers.
052})
053public final class JsonSchemaBeanGenerator {
054
055   /** Reusable default instance. */
056   public static final JsonSchemaBeanGenerator DEFAULT = create().build();
057
058   /** Creates a new builder. */
059   public static Builder create() {
060      return new Builder();
061   }
062
063   private final JsonSchemaGenerator generator;
064
065   private JsonSchemaBeanGenerator(Builder builder) {
066      this.generator = builder.generatorBuilder.build();
067   }
068
069   /**
070    * Generates a schema bean from a Java type.
071    *
072    * @param type The Java type.  Must not be <jk>null</jk>, or an {@link IllegalArgumentException} is thrown.
073    * @return The generated schema bean, or <jk>null</jk> if a schema could not be generated for the type.
074    */
075   public JsonSchema<?> generate(Type type) {
076      reqnn("type", type);
077      try {
078         var session = generator.getSession();
079         var root = session.getSchema(type);
080         if (root == null)
081            return null;
082         var defs = session.getBeanDefs();
083         if (defs != null && ! defs.isEmpty())
084            root.append("$defs", defs);
085         return toBean(root);
086      } catch (Exception e) {
087         throw toRex(e);
088      }
089   }
090
091   /**
092    * Generates a schema bean from a Java class.
093    *
094    * @param type The Java class.  Must not be <jk>null</jk>, or an {@link IllegalArgumentException} is thrown.
095    * @return The generated schema bean, or <jk>null</jk> if a schema could not be generated for the class.
096    */
097   public JsonSchema<?> generate(Class<?> type) {
098      return generate((Type)type);
099   }
100
101   /**
102    * Generates a schema bean from an object.
103    *
104    * <p>
105    * The value can be a POJO or a <c>Class</c>/<c>Type</c>.
106    *
107    * @param o The value to infer a schema from.  Must not be <jk>null</jk>, or an {@link IllegalArgumentException} is thrown.
108    * @return The generated schema bean, or <jk>null</jk> if a schema could not be generated for the value.
109    */
110   public JsonSchema<?> generate(Object o) {
111      reqnn("o", o);
112      try {
113         var session = generator.getSession();
114         var root = session.getSchema(o);
115         if (root == null)
116            return null;
117         var defs = session.getBeanDefs();
118         if (defs != null && ! defs.isEmpty())
119            root.append("$defs", defs);
120         return toBean(root);
121      } catch (Exception e) {
122         throw toRex(e);
123      }
124   }
125
126   /**
127    * Converts a generated schema map into a typed bean.
128    *
129    * <p>
130    * Conversion uses a JSON roundtrip to ensure the swaps declared on {@link JsonSchema} are honored.
131    *
132    * @param schemaMap The generated schema map.  Must not be <jk>null</jk>, or an {@link IllegalArgumentException} is thrown.
133    * @return The typed schema bean.
134    */
135   public static JsonSchema<?> toBean(JsonMap schemaMap) {
136      reqnn("schemaMap", schemaMap);
137      try {
138         var json = Json.of(schemaMap);
139         return JsonParser.create().ignoreUnknownBeanProperties().build().read(json, JsonSchema.class);
140      } catch (ParseException e) {
141         throw toRex(e);
142      }
143   }
144
145   /** Builder for {@link JsonSchemaBeanGenerator}. */
146   public static final class Builder {
147      private final JsonSchemaGenerator.Builder generatorBuilder = JsonSchemaGenerator.create();
148
149      private Builder() {}
150
151      /** Mirrors {@link org.apache.juneau.marshall.jsonschema.JsonSchemaGenerator.Builder#addDescriptionsTo(TypeCategory...) JsonSchemaGenerator.Builder#addDescriptionsTo(TypeCategory...)}. */
152      public Builder addDescriptionsTo(TypeCategory...values) {
153         generatorBuilder.addDescriptionsTo(values);
154         return this;
155      }
156
157      /** Mirrors {@link org.apache.juneau.marshall.jsonschema.JsonSchemaGenerator.Builder#addExamplesTo(TypeCategory...) JsonSchemaGenerator.Builder#addExamplesTo(TypeCategory...)}. */
158      public Builder addExamplesTo(TypeCategory...values) {
159         generatorBuilder.addExamplesTo(values);
160         return this;
161      }
162
163      /** Mirrors {@link org.apache.juneau.marshall.jsonschema.JsonSchemaGenerator.Builder#allowNestedDescriptions() JsonSchemaGenerator.Builder#allowNestedDescriptions()}. */
164      public Builder allowNestedDescriptions() {
165         generatorBuilder.allowNestedDescriptions();
166         return this;
167      }
168
169      /** Mirrors {@link org.apache.juneau.marshall.jsonschema.JsonSchemaGenerator.Builder#allowNestedDescriptions(boolean) JsonSchemaGenerator.Builder#allowNestedDescriptions(boolean)}. */
170      public Builder allowNestedDescriptions(boolean value) {
171         generatorBuilder.allowNestedDescriptions(value);
172         return this;
173      }
174
175      /** Mirrors {@link org.apache.juneau.marshall.jsonschema.JsonSchemaGenerator.Builder#allowNestedExamples() JsonSchemaGenerator.Builder#allowNestedExamples()}. */
176      public Builder allowNestedExamples() {
177         generatorBuilder.allowNestedExamples();
178         return this;
179      }
180
181      /** Mirrors {@link org.apache.juneau.marshall.jsonschema.JsonSchemaGenerator.Builder#allowNestedExamples(boolean) JsonSchemaGenerator.Builder#allowNestedExamples(boolean)}. */
182      public Builder allowNestedExamples(boolean value) {
183         generatorBuilder.allowNestedExamples(value);
184         return this;
185      }
186
187      /** Mirrors {@link org.apache.juneau.marshall.jsonschema.JsonSchemaGenerator.Builder#beanDefMapper(Class) JsonSchemaGenerator.Builder#beanDefMapper(Class)}. */
188      public Builder beanDefMapper(Class<? extends MarshallingDefMapper> value) {
189         generatorBuilder.beanDefMapper(value);
190         return this;
191      }
192
193      /** Mirrors {@link org.apache.juneau.marshall.jsonschema.JsonSchemaGenerator.Builder#ignoreTypes(String...) JsonSchemaGenerator.Builder#ignoreTypes(String...)}. */
194      public Builder ignoreTypes(String...values) {
195         generatorBuilder.ignoreTypes(values);
196         return this;
197      }
198
199      /** Mirrors {@link org.apache.juneau.marshall.jsonschema.JsonSchemaGenerator.Builder#useBeanDefs() JsonSchemaGenerator.Builder#useBeanDefs()}. */
200      public Builder useBeanDefs() {
201         generatorBuilder.useBeanDefs();
202         return this;
203      }
204
205      /** Mirrors {@link org.apache.juneau.marshall.jsonschema.JsonSchemaGenerator.Builder#useBeanDefs(boolean) JsonSchemaGenerator.Builder#useBeanDefs(boolean)}. */
206      public Builder useBeanDefs(boolean value) {
207         generatorBuilder.useBeanDefs(value);
208         return this;
209      }
210
211      /** Builds a new generator. */
212      public JsonSchemaBeanGenerator build() {
213         return new JsonSchemaBeanGenerator(this);
214      }
215   }
216}