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