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