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.collections.JsonMap.*;
016import static org.apache.juneau.common.internal.StringUtils.*;
017import static org.apache.juneau.common.internal.ThrowableUtils.*;
018import static org.apache.juneau.internal.CollectionUtils.*;
019import static java.util.Collections.*;
020
021import java.lang.annotation.*;
022import java.lang.reflect.*;
023import java.util.*;
024import java.util.concurrent.*;
025import java.util.regex.*;
026
027import org.apache.juneau.*;
028import org.apache.juneau.annotation.*;
029import org.apache.juneau.collections.*;
030import org.apache.juneau.internal.*;
031import org.apache.juneau.json.*;
032import org.apache.juneau.utils.*;
033
034/**
035 * Generates JSON-schema metadata about POJOs.
036 *
037 * <h5 class='section'>Notes:</h5><ul>
038 *    <li class='note'>This class is thread safe and reusable.
039 * </ul>
040 *
041 * <p>
042 * <h5 class='section'>See Also:</h5><ul>
043 *    <li class='link'><a class="doclink" href="../../../../index.html#jm.JsonSchemaDetails">JSON-Schema Support</a>
044 * </ul>
045 */
046public class JsonSchemaGenerator extends BeanTraverseContext implements JsonSchemaMetaProvider {
047
048   //-------------------------------------------------------------------------------------------------------------------
049   // Static
050   //-------------------------------------------------------------------------------------------------------------------
051
052   /** Default serializer, all default settings.*/
053   public static final JsonSchemaGenerator DEFAULT = new JsonSchemaGenerator(create());
054
055   /**
056    * Creates a new builder for this object.
057    *
058    * @return A new builder.
059    */
060   public static Builder create() {
061      return new Builder();
062   }
063
064   //-------------------------------------------------------------------------------------------------------------------
065   // Builder
066   //-------------------------------------------------------------------------------------------------------------------
067
068   /**
069    * Builder class.
070    */
071   @FluentSetters
072   public static class Builder extends BeanTraverseContext.Builder {
073
074      private static final Cache<HashKey,JsonSchemaGenerator> CACHE = Cache.of(HashKey.class, JsonSchemaGenerator.class).build();
075
076      final JsonSerializer.Builder jsonSerializerBuilder;
077      final JsonParser.Builder jsonParserBuilder;
078
079      SortedSet<TypeCategory> addDescriptionsTo, addExamplesTo;
080      boolean allowNestedDescriptions, allowNestedExamples, useBeanDefs;
081      Class<? extends BeanDefMapper> beanDefMapper;
082      SortedSet<String> ignoreTypes;
083
084      /**
085       * Constructor, default settings.
086       */
087      protected Builder() {
088         BeanContext.Builder bc = beanContext();
089         jsonSerializerBuilder = JsonSerializer.create().beanContext(bc);
090         jsonParserBuilder = JsonParser.create().beanContext(bc);
091         registerBuilders(jsonSerializerBuilder, jsonParserBuilder);
092         addDescriptionsTo = null;
093         addExamplesTo = null;
094         allowNestedDescriptions = env("JsonSchemaGenerator.allowNestedDescriptions", false);
095         allowNestedExamples = env("JsonSchemaGenerator.allowNestedExamples", false);
096         useBeanDefs = env("JsonSchemaGenerator.useBeanDefs", false);
097         beanDefMapper = BasicBeanDefMapper.class;
098         ignoreTypes = null;
099      }
100
101      /**
102       * Copy constructor.
103       *
104       * @param copyFrom The bean to copy from.
105       */
106      protected Builder(JsonSchemaGenerator copyFrom) {
107         super(copyFrom);
108         BeanContext.Builder bc = beanContext();
109         jsonSerializerBuilder = copyFrom.jsonSerializer.copy().beanContext(bc);
110         jsonParserBuilder = copyFrom.jsonParser.copy().beanContext(bc);
111         registerBuilders(jsonSerializerBuilder, jsonParserBuilder);
112         addDescriptionsTo = copyFrom.addDescriptionsTo.isEmpty() ? null : new TreeSet<>(copyFrom.addDescriptionsTo);
113         addExamplesTo = copyFrom.addExamplesTo.isEmpty() ? null : new TreeSet<>(copyFrom.addExamplesTo);
114         allowNestedDescriptions = copyFrom.allowNestedDescriptions;
115         allowNestedExamples = copyFrom.allowNestedExamples;
116         useBeanDefs = copyFrom.useBeanDefs;
117         beanDefMapper = copyFrom.beanDefMapper;
118         ignoreTypes = copyFrom.ignoreTypes.isEmpty() ? null : new TreeSet<>(copyFrom.ignoreTypes);
119      }
120
121      /**
122       * Copy constructor.
123       *
124       * @param copyFrom The builder to copy from.
125       */
126      protected Builder(Builder copyFrom) {
127         super(copyFrom);
128         BeanContext.Builder bc = beanContext();
129         jsonSerializerBuilder = copyFrom.jsonSerializerBuilder.copy().beanContext(bc);
130         jsonParserBuilder = copyFrom.jsonParserBuilder.copy().beanContext(bc);
131         registerBuilders(jsonSerializerBuilder, jsonParserBuilder);
132         addDescriptionsTo = copyFrom.addDescriptionsTo == null ? null : new TreeSet<>(copyFrom.addDescriptionsTo);
133         addExamplesTo = copyFrom.addExamplesTo == null ? null : new TreeSet<>(copyFrom.addExamplesTo);
134         allowNestedDescriptions = copyFrom.allowNestedDescriptions;
135         allowNestedExamples = copyFrom.allowNestedExamples;
136         useBeanDefs = copyFrom.useBeanDefs;
137         beanDefMapper = copyFrom.beanDefMapper;
138         ignoreTypes = copyFrom.ignoreTypes == null ? null : new TreeSet<>(copyFrom.ignoreTypes);
139      }
140
141      @Override /* Context.Builder */
142      public Builder copy() {
143         return new Builder(this);
144      }
145
146      @Override /* Context.Builder */
147      public JsonSchemaGenerator build() {
148         return cache(CACHE).build(JsonSchemaGenerator.class);
149      }
150
151      @Override /* Context.Builder */
152      public HashKey hashKey() {
153         return HashKey.of(
154            super.hashKey(),
155            jsonSerializerBuilder.hashKey(),
156            jsonParserBuilder.hashKey(),
157            addDescriptionsTo,
158            addExamplesTo,
159            allowNestedDescriptions,
160            allowNestedExamples,
161            useBeanDefs,
162            beanDefMapper,
163            ignoreTypes
164         );
165      }
166
167      //-----------------------------------------------------------------------------------------------------------------
168      // Properties
169      //-----------------------------------------------------------------------------------------------------------------
170
171      /**
172       * Add descriptions.
173       *
174       * <p>
175       * Identifies which categories of types that descriptions should be automatically added to generated schemas.
176       * The description is the result of calling {@link ClassMeta#getFullName()}.
177       * The format is a comma-delimited list of any of the following values:
178       *
179       * <ul class='javatree'>
180       *    <li class='jf'>{@link TypeCategory#BEAN BEAN}
181       *    <li class='jf'>{@link TypeCategory#COLLECTION COLLECTION}
182       *    <li class='jf'>{@link TypeCategory#ARRAY ARRAY}
183       *    <li class='jf'>{@link TypeCategory#MAP MAP}
184       *    <li class='jf'>{@link TypeCategory#STRING STRING}
185       *    <li class='jf'>{@link TypeCategory#NUMBER NUMBER}
186       *    <li class='jf'>{@link TypeCategory#BOOLEAN BOOLEAN}
187       *    <li class='jf'>{@link TypeCategory#ANY ANY}
188       *    <li class='jf'>{@link TypeCategory#OTHER OTHER}
189       * </ul>
190       *
191       * @param values
192       *    The values to add to this setting.
193       *    <br>The default is an empty string.
194       * @return This object.
195       */
196      @FluentSetter
197      public Builder addDescriptionsTo(TypeCategory...values) {
198         addDescriptionsTo = addAll(addDescriptionsTo, values);
199         return this;
200      }
201
202      /**
203       * Add examples.
204       *
205       * <p>
206       * Identifies which categories of types that examples should be automatically added to generated schemas.
207       * <p>
208       * The examples come from calling {@link ClassMeta#getExample(BeanSession,JsonParserSession)} which in turn gets examples
209       * from the following:
210       * <ul class='javatree'>
211       *    <li class='ja'>{@link Example}
212       *    <li class='ja'>{@link Marshalled#example() Marshalled(example)}
213       * </ul>
214       *
215       * <p>
216       * The format is a comma-delimited list of any of the following values:
217       *
218       * <ul class='javatree'>
219       *    <li class='jf'>{@link TypeCategory#BEAN BEAN}
220       *    <li class='jf'>{@link TypeCategory#COLLECTION COLLECTION}
221       *    <li class='jf'>{@link TypeCategory#ARRAY ARRAY}
222       *    <li class='jf'>{@link TypeCategory#MAP MAP}
223       *    <li class='jf'>{@link TypeCategory#STRING STRING}
224       *    <li class='jf'>{@link TypeCategory#NUMBER NUMBER}
225       *    <li class='jf'>{@link TypeCategory#BOOLEAN BOOLEAN}
226       *    <li class='jf'>{@link TypeCategory#ANY ANY}
227       *    <li class='jf'>{@link TypeCategory#OTHER OTHER}
228       * </ul>
229       *
230       * @param values
231       *    The values to add to this setting.
232       *    <br>The default is an empty string.
233       * @return This object.
234       */
235      @FluentSetter
236      public Builder addExamplesTo(TypeCategory...values) {
237         addExamplesTo = addAll(addExamplesTo, values);
238         return this;
239      }
240
241      /**
242       * Allow nested descriptions.
243       *
244       * <p>
245       * Identifies whether nested descriptions are allowed in schema definitions.
246       *
247       * @return This object.
248       */
249      @FluentSetter
250      public Builder allowNestedDescriptions() {
251         return allowNestedDescriptions(true);
252      }
253
254      /**
255       * Same as {@link #allowNestedDescriptions()} but allows you to explicitly specify the value.
256       *
257       * @param value The value for this setting.
258       * @return This object.
259       */
260      @FluentSetter
261      public Builder allowNestedDescriptions(boolean value) {
262         allowNestedDescriptions = value;
263         return this;
264      }
265
266      /**
267       * Allow nested examples.
268       *
269       * <p>
270       * Identifies whether nested examples are allowed in schema definitions.
271       *
272       * @return This object.
273       */
274      @FluentSetter
275      public Builder allowNestedExamples() {
276         return allowNestedExamples(true);
277      }
278
279      /**
280       * Same as {@link #allowNestedExamples()} but allows you to explicitly specify the value.
281       *
282       * @param value The value for this setting.
283       * @return This object.
284       */
285      @FluentSetter
286      public Builder allowNestedExamples(boolean value) {
287         allowNestedExamples = value;
288         return this;
289      }
290
291      /**
292       * Schema definition mapper.
293       *
294       * <p>
295       * Interface to use for converting Bean classes to definition IDs and URIs.
296       * <p>
297       * Used primarily for defining common definition sections for beans in Swagger JSON.
298       * <p>
299       * This setting is ignored if {@link JsonSchemaGenerator.Builder#useBeanDefs()} is not enabled.
300       *
301       * @param value
302       *    The new value for this setting.
303       *    <br>The default is {@link org.apache.juneau.jsonschema.BasicBeanDefMapper}.
304       * @return This object.
305       */
306      @FluentSetter
307      public Builder beanDefMapper(Class<? extends BeanDefMapper> value) {
308         beanDefMapper = value;
309         return this;
310      }
311
312      /**
313       * Ignore types from schema definitions.
314       *
315       * <h5 class='section'>Description:</h5>
316       * <p>
317       * Defines class name patterns that should be ignored when generating schema definitions in the generated
318       * Swagger documentation.
319       *
320       * <h5 class='section'>Example:</h5>
321       * <p class='bjava'>
322       *    <jc>// Don't generate schema for any prototype packages or the class named 'Swagger'.</jc>
323       *    <ja>@JsonSchemaConfig</ja>(
324       *       ignoreTypes=<js>"Swagger,*.proto.*"</js>
325       *    )
326       *    <jk>public class</jk> MyResource {...}
327       * </p>
328       *
329       * @param values
330       *    The values to add.
331       * @return This object.
332       */
333      @FluentSetter
334      public Builder ignoreTypes(String...values) {
335         ignoreTypes = addAll(ignoreTypes, values);
336         return this;
337      }
338
339      /**
340       * Use bean definitions.
341       *
342       * <p>
343       * When enabled, schemas on beans will be serialized as the following:
344       * <p class='bjson'>
345       *    {
346       *       type: <js>'object'</js>,
347       *       <js>'$ref'</js>: <js>'#/definitions/TypeId'</js>
348       *    }
349       * </p>
350       *
351       * <p>
352       * The definitions can then be retrieved from the session using {@link JsonSchemaGeneratorSession#getBeanDefs()}.
353       * <p>
354       * Definitions can also be added programmatically using {@link JsonSchemaGeneratorSession#addBeanDef(String, JsonMap)}.
355       *
356       * @return This object.
357       */
358      @FluentSetter
359      public Builder useBeanDefs() {
360         return useBeanDefs(true);
361      }
362
363      /**
364       * Same as {@link #useBeanDefs()} but allows you to explicitly specify the value.
365       *
366       * @param value The value for this setting.
367       * @return This object.
368       */
369      @FluentSetter
370      public Builder useBeanDefs(boolean value) {
371         useBeanDefs = value;
372         return this;
373      }
374
375      /**
376       * Gives access to the inner JSON serializer builder if you want to modify the serializer settings.
377       *
378       * @return The JSON serializer builder.
379       */
380      public JsonSerializer.Builder getJsonSerializerBuilder() {
381         return jsonSerializerBuilder;
382      }
383
384      /**
385       * Gives access to the inner JSON parser builder if you want to modify the parser settings.
386       *
387       * @return The JSON serializer builder.
388       */
389      public JsonParser.Builder getJsonParserBuilder() {
390         return jsonParserBuilder;
391      }
392
393      // <FluentSetters>
394
395      @Override /* GENERATED - org.apache.juneau.Context.Builder */
396      public Builder annotations(Annotation...values) {
397         super.annotations(values);
398         return this;
399      }
400
401      @Override /* GENERATED - org.apache.juneau.Context.Builder */
402      public Builder apply(AnnotationWorkList work) {
403         super.apply(work);
404         return this;
405      }
406
407      @Override /* GENERATED - org.apache.juneau.Context.Builder */
408      public Builder applyAnnotations(java.lang.Class<?>...fromClasses) {
409         super.applyAnnotations(fromClasses);
410         return this;
411      }
412
413      @Override /* GENERATED - org.apache.juneau.Context.Builder */
414      public Builder applyAnnotations(Method...fromMethods) {
415         super.applyAnnotations(fromMethods);
416         return this;
417      }
418
419      @Override /* GENERATED - org.apache.juneau.Context.Builder */
420      public Builder cache(Cache<HashKey,? extends org.apache.juneau.Context> value) {
421         super.cache(value);
422         return this;
423      }
424
425      @Override /* GENERATED - org.apache.juneau.Context.Builder */
426      public Builder debug() {
427         super.debug();
428         return this;
429      }
430
431      @Override /* GENERATED - org.apache.juneau.Context.Builder */
432      public Builder debug(boolean value) {
433         super.debug(value);
434         return this;
435      }
436
437      @Override /* GENERATED - org.apache.juneau.Context.Builder */
438      public Builder impl(Context value) {
439         super.impl(value);
440         return this;
441      }
442
443      @Override /* GENERATED - org.apache.juneau.Context.Builder */
444      public Builder type(Class<? extends org.apache.juneau.Context> value) {
445         super.type(value);
446         return this;
447      }
448
449      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
450      public Builder beanClassVisibility(Visibility value) {
451         super.beanClassVisibility(value);
452         return this;
453      }
454
455      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
456      public Builder beanConstructorVisibility(Visibility value) {
457         super.beanConstructorVisibility(value);
458         return this;
459      }
460
461      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
462      public Builder beanContext(BeanContext value) {
463         super.beanContext(value);
464         return this;
465      }
466
467      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
468      public Builder beanContext(BeanContext.Builder value) {
469         super.beanContext(value);
470         return this;
471      }
472
473      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
474      public Builder beanDictionary(java.lang.Class<?>...values) {
475         super.beanDictionary(values);
476         return this;
477      }
478
479      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
480      public Builder beanFieldVisibility(Visibility value) {
481         super.beanFieldVisibility(value);
482         return this;
483      }
484
485      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
486      public Builder beanInterceptor(Class<?> on, Class<? extends org.apache.juneau.swap.BeanInterceptor<?>> value) {
487         super.beanInterceptor(on, value);
488         return this;
489      }
490
491      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
492      public Builder beanMapPutReturnsOldValue() {
493         super.beanMapPutReturnsOldValue();
494         return this;
495      }
496
497      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
498      public Builder beanMethodVisibility(Visibility value) {
499         super.beanMethodVisibility(value);
500         return this;
501      }
502
503      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
504      public Builder beanProperties(Map<String,Object> values) {
505         super.beanProperties(values);
506         return this;
507      }
508
509      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
510      public Builder beanProperties(Class<?> beanClass, String properties) {
511         super.beanProperties(beanClass, properties);
512         return this;
513      }
514
515      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
516      public Builder beanProperties(String beanClassName, String properties) {
517         super.beanProperties(beanClassName, properties);
518         return this;
519      }
520
521      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
522      public Builder beanPropertiesExcludes(Map<String,Object> values) {
523         super.beanPropertiesExcludes(values);
524         return this;
525      }
526
527      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
528      public Builder beanPropertiesExcludes(Class<?> beanClass, String properties) {
529         super.beanPropertiesExcludes(beanClass, properties);
530         return this;
531      }
532
533      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
534      public Builder beanPropertiesExcludes(String beanClassName, String properties) {
535         super.beanPropertiesExcludes(beanClassName, properties);
536         return this;
537      }
538
539      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
540      public Builder beanPropertiesReadOnly(Map<String,Object> values) {
541         super.beanPropertiesReadOnly(values);
542         return this;
543      }
544
545      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
546      public Builder beanPropertiesReadOnly(Class<?> beanClass, String properties) {
547         super.beanPropertiesReadOnly(beanClass, properties);
548         return this;
549      }
550
551      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
552      public Builder beanPropertiesReadOnly(String beanClassName, String properties) {
553         super.beanPropertiesReadOnly(beanClassName, properties);
554         return this;
555      }
556
557      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
558      public Builder beanPropertiesWriteOnly(Map<String,Object> values) {
559         super.beanPropertiesWriteOnly(values);
560         return this;
561      }
562
563      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
564      public Builder beanPropertiesWriteOnly(Class<?> beanClass, String properties) {
565         super.beanPropertiesWriteOnly(beanClass, properties);
566         return this;
567      }
568
569      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
570      public Builder beanPropertiesWriteOnly(String beanClassName, String properties) {
571         super.beanPropertiesWriteOnly(beanClassName, properties);
572         return this;
573      }
574
575      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
576      public Builder beansRequireDefaultConstructor() {
577         super.beansRequireDefaultConstructor();
578         return this;
579      }
580
581      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
582      public Builder beansRequireSerializable() {
583         super.beansRequireSerializable();
584         return this;
585      }
586
587      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
588      public Builder beansRequireSettersForGetters() {
589         super.beansRequireSettersForGetters();
590         return this;
591      }
592
593      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
594      public Builder dictionaryOn(Class<?> on, java.lang.Class<?>...values) {
595         super.dictionaryOn(on, values);
596         return this;
597      }
598
599      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
600      public Builder disableBeansRequireSomeProperties() {
601         super.disableBeansRequireSomeProperties();
602         return this;
603      }
604
605      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
606      public Builder disableIgnoreMissingSetters() {
607         super.disableIgnoreMissingSetters();
608         return this;
609      }
610
611      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
612      public Builder disableIgnoreTransientFields() {
613         super.disableIgnoreTransientFields();
614         return this;
615      }
616
617      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
618      public Builder disableIgnoreUnknownNullBeanProperties() {
619         super.disableIgnoreUnknownNullBeanProperties();
620         return this;
621      }
622
623      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
624      public Builder disableInterfaceProxies() {
625         super.disableInterfaceProxies();
626         return this;
627      }
628
629      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
630      public <T> Builder example(Class<T> pojoClass, T o) {
631         super.example(pojoClass, o);
632         return this;
633      }
634
635      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
636      public <T> Builder example(Class<T> pojoClass, String json) {
637         super.example(pojoClass, json);
638         return this;
639      }
640
641      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
642      public Builder findFluentSetters() {
643         super.findFluentSetters();
644         return this;
645      }
646
647      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
648      public Builder findFluentSetters(Class<?> on) {
649         super.findFluentSetters(on);
650         return this;
651      }
652
653      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
654      public Builder ignoreInvocationExceptionsOnGetters() {
655         super.ignoreInvocationExceptionsOnGetters();
656         return this;
657      }
658
659      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
660      public Builder ignoreInvocationExceptionsOnSetters() {
661         super.ignoreInvocationExceptionsOnSetters();
662         return this;
663      }
664
665      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
666      public Builder ignoreUnknownBeanProperties() {
667         super.ignoreUnknownBeanProperties();
668         return this;
669      }
670
671      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
672      public Builder ignoreUnknownEnumValues() {
673         super.ignoreUnknownEnumValues();
674         return this;
675      }
676
677      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
678      public Builder implClass(Class<?> interfaceClass, Class<?> implClass) {
679         super.implClass(interfaceClass, implClass);
680         return this;
681      }
682
683      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
684      public Builder implClasses(Map<Class<?>,Class<?>> values) {
685         super.implClasses(values);
686         return this;
687      }
688
689      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
690      public Builder interfaceClass(Class<?> on, Class<?> value) {
691         super.interfaceClass(on, value);
692         return this;
693      }
694
695      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
696      public Builder interfaces(java.lang.Class<?>...value) {
697         super.interfaces(value);
698         return this;
699      }
700
701      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
702      public Builder locale(Locale value) {
703         super.locale(value);
704         return this;
705      }
706
707      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
708      public Builder mediaType(MediaType value) {
709         super.mediaType(value);
710         return this;
711      }
712
713      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
714      public Builder notBeanClasses(java.lang.Class<?>...values) {
715         super.notBeanClasses(values);
716         return this;
717      }
718
719      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
720      public Builder notBeanPackages(String...values) {
721         super.notBeanPackages(values);
722         return this;
723      }
724
725      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
726      public Builder propertyNamer(Class<? extends org.apache.juneau.PropertyNamer> value) {
727         super.propertyNamer(value);
728         return this;
729      }
730
731      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
732      public Builder propertyNamer(Class<?> on, Class<? extends org.apache.juneau.PropertyNamer> value) {
733         super.propertyNamer(on, value);
734         return this;
735      }
736
737      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
738      public Builder sortProperties() {
739         super.sortProperties();
740         return this;
741      }
742
743      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
744      public Builder sortProperties(java.lang.Class<?>...on) {
745         super.sortProperties(on);
746         return this;
747      }
748
749      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
750      public Builder stopClass(Class<?> on, Class<?> value) {
751         super.stopClass(on, value);
752         return this;
753      }
754
755      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
756      public <T, S> Builder swap(Class<T> normalClass, Class<S> swappedClass, ThrowingFunction<T,S> swapFunction) {
757         super.swap(normalClass, swappedClass, swapFunction);
758         return this;
759      }
760
761      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
762      public <T, S> Builder swap(Class<T> normalClass, Class<S> swappedClass, ThrowingFunction<T,S> swapFunction, ThrowingFunction<S,T> unswapFunction) {
763         super.swap(normalClass, swappedClass, swapFunction, unswapFunction);
764         return this;
765      }
766
767      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
768      public Builder swaps(java.lang.Class<?>...values) {
769         super.swaps(values);
770         return this;
771      }
772
773      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
774      public Builder timeZone(TimeZone value) {
775         super.timeZone(value);
776         return this;
777      }
778
779      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
780      public Builder typeName(Class<?> on, String value) {
781         super.typeName(on, value);
782         return this;
783      }
784
785      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
786      public Builder typePropertyName(String value) {
787         super.typePropertyName(value);
788         return this;
789      }
790
791      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
792      public Builder typePropertyName(Class<?> on, String value) {
793         super.typePropertyName(on, value);
794         return this;
795      }
796
797      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
798      public Builder useEnumNames() {
799         super.useEnumNames();
800         return this;
801      }
802
803      @Override /* GENERATED - org.apache.juneau.BeanContextable.Builder */
804      public Builder useJavaBeanIntrospector() {
805         super.useJavaBeanIntrospector();
806         return this;
807      }
808
809      @Override /* GENERATED - org.apache.juneau.BeanTraverseContext.Builder */
810      public Builder detectRecursions() {
811         super.detectRecursions();
812         return this;
813      }
814
815      @Override /* GENERATED - org.apache.juneau.BeanTraverseContext.Builder */
816      public Builder detectRecursions(boolean value) {
817         super.detectRecursions(value);
818         return this;
819      }
820
821      @Override /* GENERATED - org.apache.juneau.BeanTraverseContext.Builder */
822      public Builder ignoreRecursions() {
823         super.ignoreRecursions();
824         return this;
825      }
826
827      @Override /* GENERATED - org.apache.juneau.BeanTraverseContext.Builder */
828      public Builder ignoreRecursions(boolean value) {
829         super.ignoreRecursions(value);
830         return this;
831      }
832
833      @Override /* GENERATED - org.apache.juneau.BeanTraverseContext.Builder */
834      public Builder initialDepth(int value) {
835         super.initialDepth(value);
836         return this;
837      }
838
839      @Override /* GENERATED - org.apache.juneau.BeanTraverseContext.Builder */
840      public Builder maxDepth(int value) {
841         super.maxDepth(value);
842         return this;
843      }
844
845      // </FluentSetters>
846   }
847
848   //-------------------------------------------------------------------------------------------------------------------
849   // Instance
850   //-------------------------------------------------------------------------------------------------------------------
851
852   final boolean useBeanDefs, allowNestedExamples, allowNestedDescriptions;
853   final Set<TypeCategory> addExamplesTo, addDescriptionsTo;
854   final Class<? extends BeanDefMapper> beanDefMapper;
855   final Set<String> ignoreTypes;
856
857   private final BeanDefMapper beanDefMapperBean;
858   final JsonSerializer jsonSerializer;
859   final JsonParser jsonParser;
860   private final Pattern[] ignoreTypePatterns;
861   private final Map<ClassMeta<?>,JsonSchemaClassMeta> jsonSchemaClassMetas = new ConcurrentHashMap<>();
862   private final Map<BeanPropertyMeta,JsonSchemaBeanPropertyMeta> jsonSchemaBeanPropertyMetas = new ConcurrentHashMap<>();
863
864   /**
865    * Constructor.
866    *
867    * @param builder The builder for this object.
868    */
869   public JsonSchemaGenerator(Builder builder) {
870      super(builder.detectRecursions().ignoreRecursions());
871
872      useBeanDefs = builder.useBeanDefs;
873      allowNestedExamples = builder.allowNestedExamples;
874      allowNestedDescriptions = builder.allowNestedDescriptions;
875      beanDefMapper = builder.beanDefMapper;
876      addExamplesTo = builder.addExamplesTo == null ? emptySet() : new TreeSet<>(builder.addExamplesTo);
877      addDescriptionsTo = builder.addDescriptionsTo == null ? emptySet() : new TreeSet<>(builder.addDescriptionsTo);
878      ignoreTypes = builder.ignoreTypes == null ? emptySet() : new TreeSet<>(builder.ignoreTypes);
879
880      Set<Pattern> ignoreTypePatterns = set();
881      ignoreTypes.forEach(y -> split(y, x -> ignoreTypePatterns.add(Pattern.compile(x.replace(".", "\\.").replace("*", ".*")))));
882      this.ignoreTypePatterns = ignoreTypePatterns.toArray(new Pattern[ignoreTypePatterns.size()]);
883
884      try {
885         beanDefMapperBean = beanDefMapper.getDeclaredConstructor().newInstance();
886      } catch (Exception e) {
887         throw asRuntimeException(e);
888      }
889
890      jsonSerializer = builder.jsonSerializerBuilder.build();
891      jsonParser = builder.jsonParserBuilder.beanContext(getBeanContext()).build();
892   }
893
894   @Override /* Context */
895   public Builder copy() {
896      return new Builder(this);
897   }
898
899   @Override /* Context */
900   public JsonSchemaGeneratorSession.Builder createSession() {
901      return JsonSchemaGeneratorSession.create(this);
902   }
903
904   @Override /* Context */
905   public JsonSchemaGeneratorSession getSession() {
906      return createSession().build();
907   }
908
909   JsonSerializer getJsonSerializer() {
910      return jsonSerializer;
911   }
912
913   JsonParser getJsonParser() {
914      return jsonParser;
915   }
916
917   //-----------------------------------------------------------------------------------------------------------------
918   // Properties
919   //-----------------------------------------------------------------------------------------------------------------
920
921   /**
922    * Add descriptions to types.
923    *
924    * @see Builder#addDescriptionsTo(TypeCategory...)
925    * @return
926    *    Set of categories of types that descriptions should be automatically added to generated schemas.
927    */
928   protected final Set<TypeCategory> getAddDescriptionsTo() {
929      return addDescriptionsTo;
930   }
931
932   /**
933    * Add examples.
934    *
935    * @see Builder#addExamplesTo(TypeCategory...)
936    * @return
937    *    Set of categories of types that examples should be automatically added to generated schemas.
938    */
939   protected final Set<TypeCategory> getAddExamplesTo() {
940      return addExamplesTo;
941   }
942
943   /**
944    * Allow nested descriptions.
945    *
946    * @see Builder#allowNestedDescriptions()
947    * @return
948    *    <jk>true</jk> if nested descriptions are allowed in schema definitions.
949    */
950   protected final boolean isAllowNestedDescriptions() {
951      return allowNestedDescriptions;
952   }
953
954   /**
955    * Allow nested examples.
956    *
957    * @see Builder#allowNestedExamples()
958    * @return
959    *    <jk>true</jk> if nested examples are allowed in schema definitions.
960    */
961   protected final boolean isAllowNestedExamples() {
962      return allowNestedExamples;
963   }
964
965   /**
966    * Bean schema definition mapper.
967    *
968    * @see Builder#beanDefMapper(Class)
969    * @return
970    *    Interface to use for converting Bean classes to definition IDs and URIs.
971    */
972   protected final BeanDefMapper getBeanDefMapper() {
973      return beanDefMapperBean;
974   }
975
976   /**
977    * Ignore types from schema definitions.
978    *
979    * @see Builder#ignoreTypes(String...)
980    * @return
981    *    Custom schema information for particular class types.
982    */
983   public List<Pattern> getIgnoreTypes() {
984      return ulist(ignoreTypePatterns);
985   }
986
987   /**
988    * Use bean definitions.
989    *
990    * @see Builder#useBeanDefs()
991    * @return
992    *    <jk>true</jk> if schemas on beans will be serialized with <js>'$ref'</js> tags.
993    */
994   protected final boolean isUseBeanDefs() {
995      return useBeanDefs;
996   }
997
998   //-----------------------------------------------------------------------------------------------------------------
999   // Extended metadata
1000   //-----------------------------------------------------------------------------------------------------------------
1001
1002   @Override
1003   public JsonSchemaClassMeta getJsonSchemaClassMeta(ClassMeta<?> cm) {
1004      JsonSchemaClassMeta m = jsonSchemaClassMetas.get(cm);
1005      if (m == null) {
1006         m = new JsonSchemaClassMeta(cm, this);
1007         jsonSchemaClassMetas.put(cm, m);
1008      }
1009      return m;
1010   }
1011
1012   @Override
1013   public JsonSchemaBeanPropertyMeta getJsonSchemaBeanPropertyMeta(BeanPropertyMeta bpm) {
1014      JsonSchemaBeanPropertyMeta m = jsonSchemaBeanPropertyMetas.get(bpm);
1015      if (m == null) {
1016         m = new JsonSchemaBeanPropertyMeta(bpm, this);
1017         jsonSchemaBeanPropertyMetas.put(bpm, m);
1018      }
1019      return m;
1020   }
1021
1022   //-----------------------------------------------------------------------------------------------------------------
1023   // Other methods
1024   //-----------------------------------------------------------------------------------------------------------------
1025
1026   /**
1027    * Returns <jk>true</jk> if the specified type is ignored.
1028    *
1029    * <p>
1030    * The type is ignored if it's specified in the {@link Builder#ignoreTypes(String...)} setting.
1031    * <br>Ignored types return <jk>null</jk> on the call to {@link JsonSchemaGeneratorSession#getSchema(ClassMeta)}.
1032    *
1033    * @param cm The type to check.
1034    * @return <jk>true</jk> if the specified type is ignored.
1035    */
1036   public boolean isIgnoredType(ClassMeta<?> cm) {
1037      for (Pattern p : ignoreTypePatterns)
1038         if (p.matcher(cm.getSimpleName()).matches() || p.matcher(cm.getName()).matches())
1039            return true;
1040      return false;
1041   }
1042
1043   //-----------------------------------------------------------------------------------------------------------------
1044   // Other methods
1045   //-----------------------------------------------------------------------------------------------------------------
1046
1047   @Override /* Context */
1048   protected JsonMap properties() {
1049      return filteredMap()
1050         .append("useBeanDefs", useBeanDefs)
1051         .append("allowNestedExamples", allowNestedExamples)
1052         .append("allowNestedDescriptions", allowNestedDescriptions)
1053         .append("beanDefMapper", beanDefMapper)
1054         .append("addExamplesTo", addExamplesTo)
1055         .append("addDescriptionsTo", addDescriptionsTo)
1056         .append("ignoreTypes", ignoreTypes);
1057   }
1058}