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;
018
019import static org.apache.juneau.commons.utils.Utils.*;
020
021import java.lang.annotation.*;
022import java.util.*;
023
024import org.apache.juneau.commons.collections.*;
025import org.apache.juneau.commons.function.*;
026import org.apache.juneau.commons.reflect.*;
027
028/**
029 * Parent class for all classes that traverse POJOs.
030 *
031 * <h5 class='topic'>Description</h5>
032 * <p>
033 * Base class that serves as the parent class for all serializers and other classes that traverse POJOs.
034 *
035 * <h5 class='section'>Notes:</h5><ul>
036 *    <li class='note'>This class is thread safe and reusable.
037 * </ul>
038 *
039 */
040public abstract class BeanTraverseContext extends BeanContextable {
041   /**
042    * Builder class.
043    */
044   public abstract static class Builder extends BeanContextable.Builder {
045
046      private boolean detectRecursions;
047      private boolean ignoreRecursions;
048      private int initialDepth;
049      private int maxDepth;
050
051      /**
052       * Constructor, default settings.
053       */
054      protected Builder() {
055         detectRecursions = env("BeanTraverseContext.detectRecursions", false);
056         ignoreRecursions = env("BeanTraverseContext.ignoreRecursions", false);
057         initialDepth = env("BeanTraverseContext.initialDepth", 0);
058         maxDepth = env("BeanTraverseContext.maxDepth", 100);
059      }
060
061      /**
062       * Copy constructor.
063       *
064       * @param copyFrom The bean to copy from.
065       */
066      protected Builder(BeanTraverseContext copyFrom) {
067         super(copyFrom);
068         detectRecursions = copyFrom.getDetectRecursions();
069         ignoreRecursions = copyFrom.getIgnoreRecursions();
070         initialDepth = copyFrom.getInitialDepth();
071         maxDepth = copyFrom.getMaxDepth();
072      }
073
074      /**
075       * Copy constructor.
076       *
077       * @param copyFrom The builder to copy from.
078       */
079      protected Builder(Builder copyFrom) {
080         super(copyFrom);
081         detectRecursions = copyFrom.detectRecursions;
082         ignoreRecursions = copyFrom.ignoreRecursions;
083         initialDepth = copyFrom.initialDepth;
084         maxDepth = copyFrom.maxDepth;
085      }
086
087      @Override /* Overridden from Builder */
088      public Builder annotations(Annotation...values) {
089         super.annotations(values);
090         return this;
091      }
092
093      @Override /* Overridden from Builder */
094      public Builder apply(AnnotationWorkList work) {
095         super.apply(work);
096         return this;
097      }
098
099      @Override /* Overridden from Builder */
100      public Builder applyAnnotations(Class<?>...from) {
101         super.applyAnnotations(from);
102         return this;
103      }
104
105      @Override /* Overridden from Builder */
106      public Builder applyAnnotations(Object...from) {
107         super.applyAnnotations(from);
108         return this;
109      }
110
111      @Override /* Overridden from Builder */
112      public Builder beanClassVisibility(Visibility value) {
113         super.beanClassVisibility(value);
114         return this;
115      }
116
117      @Override /* Overridden from Builder */
118      public Builder beanConstructorVisibility(Visibility value) {
119         super.beanConstructorVisibility(value);
120         return this;
121      }
122
123      @Override /* Overridden from Builder */
124      public Builder beanContext(BeanContext value) {
125         super.beanContext(value);
126         return this;
127      }
128
129      @Override /* Overridden from Builder */
130      public Builder beanContext(BeanContext.Builder value) {
131         super.beanContext(value);
132         return this;
133      }
134
135      @Override /* Overridden from Builder */
136      public Builder beanDictionary(java.lang.Class<?>...values) {
137         super.beanDictionary(values);
138         return this;
139      }
140
141      @Override /* Overridden from Builder */
142      public Builder beanFieldVisibility(Visibility value) {
143         super.beanFieldVisibility(value);
144         return this;
145      }
146
147      @Override /* Overridden from Builder */
148      public Builder beanInterceptor(Class<?> on, Class<? extends org.apache.juneau.swap.BeanInterceptor<?>> value) {
149         super.beanInterceptor(on, value);
150         return this;
151      }
152
153      @Override /* Overridden from Builder */
154      public Builder beanMapPutReturnsOldValue() {
155         super.beanMapPutReturnsOldValue();
156         return this;
157      }
158
159      @Override /* Overridden from Builder */
160      public Builder beanMethodVisibility(Visibility value) {
161         super.beanMethodVisibility(value);
162         return this;
163      }
164
165      @Override /* Overridden from Builder */
166      public Builder beanProperties(Class<?> beanClass, String properties) {
167         super.beanProperties(beanClass, properties);
168         return this;
169      }
170
171      @Override /* Overridden from Builder */
172      public Builder beanProperties(Map<String,Object> values) {
173         super.beanProperties(values);
174         return this;
175      }
176
177      @Override /* Overridden from Builder */
178      public Builder beanProperties(String beanClassName, String properties) {
179         super.beanProperties(beanClassName, properties);
180         return this;
181      }
182
183      @Override /* Overridden from Builder */
184      public Builder beanPropertiesExcludes(Class<?> beanClass, String properties) {
185         super.beanPropertiesExcludes(beanClass, properties);
186         return this;
187      }
188
189      @Override /* Overridden from Builder */
190      public Builder beanPropertiesExcludes(Map<String,Object> values) {
191         super.beanPropertiesExcludes(values);
192         return this;
193      }
194
195      @Override /* Overridden from Builder */
196      public Builder beanPropertiesExcludes(String beanClassName, String properties) {
197         super.beanPropertiesExcludes(beanClassName, properties);
198         return this;
199      }
200
201      @Override /* Overridden from Builder */
202      public Builder beanPropertiesReadOnly(Class<?> beanClass, String properties) {
203         super.beanPropertiesReadOnly(beanClass, properties);
204         return this;
205      }
206
207      @Override /* Overridden from Builder */
208      public Builder beanPropertiesReadOnly(Map<String,Object> values) {
209         super.beanPropertiesReadOnly(values);
210         return this;
211      }
212
213      @Override /* Overridden from Builder */
214      public Builder beanPropertiesReadOnly(String beanClassName, String properties) {
215         super.beanPropertiesReadOnly(beanClassName, properties);
216         return this;
217      }
218
219      @Override /* Overridden from Builder */
220      public Builder beanPropertiesWriteOnly(Class<?> beanClass, String properties) {
221         super.beanPropertiesWriteOnly(beanClass, properties);
222         return this;
223      }
224
225      @Override /* Overridden from Builder */
226      public Builder beanPropertiesWriteOnly(Map<String,Object> values) {
227         super.beanPropertiesWriteOnly(values);
228         return this;
229      }
230
231      @Override /* Overridden from Builder */
232      public Builder beanPropertiesWriteOnly(String beanClassName, String properties) {
233         super.beanPropertiesWriteOnly(beanClassName, properties);
234         return this;
235      }
236
237      @Override /* Overridden from Builder */
238      public Builder beansRequireDefaultConstructor() {
239         super.beansRequireDefaultConstructor();
240         return this;
241      }
242
243      @Override /* Overridden from Builder */
244      public Builder beansRequireSerializable() {
245         super.beansRequireSerializable();
246         return this;
247      }
248
249      @Override /* Overridden from Builder */
250      public Builder beansRequireSettersForGetters() {
251         super.beansRequireSettersForGetters();
252         return this;
253      }
254
255      @Override /* Overridden from Builder */
256      public Builder cache(Cache<HashKey,? extends org.apache.juneau.Context> value) {
257         super.cache(value);
258         return this;
259      }
260
261      @Override /* Overridden from Context.Builder */
262      public abstract Builder copy();
263
264      @Override /* Overridden from Builder */
265      public Builder debug() {
266         super.debug();
267         return this;
268      }
269
270      @Override /* Overridden from Builder */
271      public Builder debug(boolean value) {
272         super.debug(value);
273         return this;
274      }
275
276      /**
277       * Automatically detect POJO recursions.
278       *
279       * <p>
280       * When enabled, specifies that recursions should be checked for during traversal.
281       *
282       * <p>
283       * Recursions can occur when traversing models that aren't true trees but rather contain loops.
284       * <br>In general, unchecked recursions cause stack-overflow-errors.
285       * <br>These show up as {@link BeanRecursionException BeanRecursionException} with the message <js>"Depth too deep.  Stack overflow occurred."</js>.
286       *
287       * <h5 class='section'>Notes:</h5><ul>
288       *    <li class='note'>
289       *       Checking for recursion can cause a small performance penalty.
290       * </ul>
291       *
292       * <h5 class='section'>Example:</h5>
293       * <p class='bjava'>
294       *    <jc>// Create a serializer that automatically checks for recursions.</jc>
295       *    WriterSerializer <jv>serializer</jv> = JsonSerializer
296       *       .<jsm>create</jsm>()
297       *       .detectRecursions()
298       *       .build();
299       *
300       *    <jc>// Create a POJO model with a recursive loop.</jc>
301       *    <jk>public class</jk> MyBean {
302       *       <jk>public</jk> Object <jf>f</jf>;
303       *    }
304       *    MyBean <jv>bean</jv> = <jk>new</jk> MyBean();
305       *    <jv>bean</jv>.<jf>f</jf> = <jv>bean</jv>;
306       *
307       *    <jc>// Throws a SerializeException and not a StackOverflowError</jc>
308       *    String <jv>json</jv> = <jv>serializer</jv>.serialize(<jv>bean</jv>);
309       * </p>
310       *
311       * @return This object.
312       */
313      public Builder detectRecursions() {
314         return detectRecursions(true);
315      }
316
317      /**
318       * Same as {@link #detectRecursions()} but allows you to explicitly specify the value.
319       *
320       * @param value The value for this setting.
321       * @return This object.
322       */
323      public Builder detectRecursions(boolean value) {
324         detectRecursions = value;
325         return this;
326      }
327
328      @Override /* Overridden from Builder */
329      public Builder dictionaryOn(Class<?> on, java.lang.Class<?>...values) {
330         super.dictionaryOn(on, values);
331         return this;
332      }
333
334      @Override /* Overridden from Builder */
335      public Builder disableBeansRequireSomeProperties() {
336         super.disableBeansRequireSomeProperties();
337         return this;
338      }
339
340      @Override /* Overridden from Builder */
341      public Builder disableIgnoreMissingSetters() {
342         super.disableIgnoreMissingSetters();
343         return this;
344      }
345
346      @Override /* Overridden from Builder */
347      public Builder disableIgnoreTransientFields() {
348         super.disableIgnoreTransientFields();
349         return this;
350      }
351
352      @Override /* Overridden from Builder */
353      public Builder disableIgnoreUnknownNullBeanProperties() {
354         super.disableIgnoreUnknownNullBeanProperties();
355         return this;
356      }
357
358      @Override /* Overridden from Builder */
359      public Builder disableInterfaceProxies() {
360         super.disableInterfaceProxies();
361         return this;
362      }
363
364      @Override /* Overridden from Builder */
365      public <T> Builder example(Class<T> pojoClass, String json) {
366         super.example(pojoClass, json);
367         return this;
368      }
369
370      @Override /* Overridden from Builder */
371      public <T> Builder example(Class<T> pojoClass, T o) {
372         super.example(pojoClass, o);
373         return this;
374      }
375
376      @Override /* Overridden from Builder */
377      public Builder findFluentSetters() {
378         super.findFluentSetters();
379         return this;
380      }
381
382      @Override /* Overridden from Builder */
383      public Builder findFluentSetters(Class<?> on) {
384         super.findFluentSetters(on);
385         return this;
386      }
387
388      @Override /* Overridden from Context.Builder */
389      public HashKey hashKey() {
390         // @formatter:off
391         return HashKey.of(
392            super.hashKey(),
393            detectRecursions,
394            ignoreRecursions,
395            initialDepth,
396            maxDepth
397         );
398         // @formatter:on
399      }
400
401      @Override /* Overridden from Builder */
402      public Builder ignoreInvocationExceptionsOnGetters() {
403         super.ignoreInvocationExceptionsOnGetters();
404         return this;
405      }
406
407      @Override /* Overridden from Builder */
408      public Builder ignoreInvocationExceptionsOnSetters() {
409         super.ignoreInvocationExceptionsOnSetters();
410         return this;
411      }
412
413      /**
414       * Ignore recursion errors.
415       *
416       * <p>
417       * When enabled, when we encounter the same object when traversing a tree, we set the value to <jk>null</jk>.
418       *
419       * <p>
420       * For example, if a model contains the links A-&gt;B-&gt;C-&gt;A, then the JSON generated will look like
421       *    the following when this setting is <jk>true</jk>...
422       *
423       * <p class='bjson'>
424       *    {A:{B:{C:<jk>null</jk>}}}
425       * </p>
426       *
427       * <h5 class='section'>Notes:</h5><ul>
428       *    <li class='note'>
429       *       Checking for recursion can cause a small performance penalty.
430       * </ul>
431       *
432       * <h5 class='section'>Example:</h5>
433       * <p class='bjava'>
434       *    <jc>// Create a serializer ignores recursions.</jc>
435       *    WriterSerializer <jv>serializer</jv> = JsonSerializer
436       *       .<jsm>create</jsm>()
437       *       .ignoreRecursions()
438       *       .build();
439       *
440       *    <jc>// Create a POJO model with a recursive loop.</jc>
441       *    <jk>public class</jk> MyBean {
442       *       <jk>public</jk> Object <jf>f</jf>;
443       *    }
444       *    MyBean <jv>bean</jv> = <jk>new</jk> MyBean();
445       *    <jv>bean</jv>.<jf>f</jf> = <jv>bean</jv>;
446       *
447       *    <jc>// Produces "{f:null}"</jc>
448       *    String <jv>json</jv> = <jv>serializer</jv>.serialize(<jv>bean</jv>);
449       * </p>
450       *
451       * @return This object.
452       */
453      public Builder ignoreRecursions() {
454         return ignoreRecursions(true);
455      }
456
457      /**
458       * Same as {@link #ignoreRecursions()} but allows you to explicitly specify the value.
459       *
460       * @param value The value for this setting.
461       * @return This object.
462       */
463      public Builder ignoreRecursions(boolean value) {
464         ignoreRecursions = value;
465         return this;
466      }
467
468      @Override /* Overridden from Builder */
469      public Builder ignoreUnknownBeanProperties() {
470         super.ignoreUnknownBeanProperties();
471         return this;
472      }
473
474      @Override /* Overridden from Builder */
475      public Builder ignoreUnknownEnumValues() {
476         super.ignoreUnknownEnumValues();
477         return this;
478      }
479
480      @Override /* Overridden from Builder */
481      public Builder impl(Context value) {
482         super.impl(value);
483         return this;
484      }
485
486      @Override /* Overridden from Builder */
487      public Builder implClass(Class<?> interfaceClass, Class<?> implClass) {
488         super.implClass(interfaceClass, implClass);
489         return this;
490      }
491
492      @Override /* Overridden from Builder */
493      public Builder implClasses(Map<Class<?>,Class<?>> values) {
494         super.implClasses(values);
495         return this;
496      }
497
498      /**
499       * Initial depth.
500       *
501       * <p>
502       * The initial indentation level at the root.
503       *
504       * <p>
505       * Useful when constructing document fragments that need to be indented at a certain level when whitespace is enabled.
506       *
507       * <h5 class='section'>Example:</h5>
508       * <p class='bjava'>
509       *    <jc>// Create a serializer with whitespace enabled and an initial depth of 2.</jc>
510       *    WriterSerializer <jv>serializer</jv> = JsonSerializer
511       *       .<jsm>create</jsm>()
512       *       .ws()
513       *       .initialDepth(2)
514       *       .build();
515       *
516       *    <jc>// Produces "\t\t{\n\t\t\t'foo':'bar'\n\t\t}\n"</jc>
517       *    String <jv>json</jv> = <jv>serializer</jv>.serialize(<jk>new</jk> MyBean());
518       * </p>
519       *
520       * @param value
521       *    The new value for this setting.
522       *    <br>The default is <c>0</c>.
523       * @return This object.
524       */
525      public Builder initialDepth(int value) {
526         initialDepth = value;
527         return this;
528      }
529
530      @Override /* Overridden from Builder */
531      public Builder interfaceClass(Class<?> on, Class<?> value) {
532         super.interfaceClass(on, value);
533         return this;
534      }
535
536      @Override /* Overridden from Builder */
537      public Builder interfaces(java.lang.Class<?>...value) {
538         super.interfaces(value);
539         return this;
540      }
541
542      @Override /* Overridden from Builder */
543      public Builder locale(Locale value) {
544         super.locale(value);
545         return this;
546      }
547
548      /**
549       * Max traversal depth.
550       *
551       * <p>
552       * When enabled, abort traversal if specified depth is reached in the POJO tree.
553       *
554       * <p>
555       * If this depth is exceeded, an exception is thrown.
556       *
557       * <p>
558       * This prevents stack overflows from occurring when trying to traverse models with recursive references.
559       *
560       * <h5 class='section'>Example:</h5>
561       * <p class='bjava'>
562       *    <jc>// Create a serializer that throws an exception if the depth reaches greater than 20.</jc>
563       *    WriterSerializer <jv>serializer</jv> = JsonSerializer
564       *       .<jsm>create</jsm>()
565       *       .maxDepth(20)
566       *       .build();
567       * </p>
568       *
569       * <h5 class='section'>See Also:</h5><ul>
570       *    <li class='jm'>{@link Builder#maxDepth(int)}
571       * </ul>
572       *
573       * @param value
574       *    The new value for this setting.
575       *    <br>The default is <c>100</c>.
576       * @return This object.
577       */
578      public Builder maxDepth(int value) {
579         maxDepth = value;
580         return this;
581      }
582
583      @Override /* Overridden from Builder */
584      public Builder mediaType(MediaType value) {
585         super.mediaType(value);
586         return this;
587      }
588
589      @Override /* Overridden from Builder */
590      public Builder notBeanClasses(java.lang.Class<?>...values) {
591         super.notBeanClasses(values);
592         return this;
593      }
594
595      @Override /* Overridden from Builder */
596      public Builder notBeanPackages(String...values) {
597         super.notBeanPackages(values);
598         return this;
599      }
600
601      @Override /* Overridden from Builder */
602      public Builder propertyNamer(Class<?> on, Class<? extends org.apache.juneau.PropertyNamer> value) {
603         super.propertyNamer(on, value);
604         return this;
605      }
606
607      @Override /* Overridden from Builder */
608      public Builder propertyNamer(Class<? extends org.apache.juneau.PropertyNamer> value) {
609         super.propertyNamer(value);
610         return this;
611      }
612
613      @Override /* Overridden from Builder */
614      public Builder sortProperties() {
615         super.sortProperties();
616         return this;
617      }
618
619      @Override /* Overridden from Builder */
620      public Builder sortProperties(java.lang.Class<?>...on) {
621         super.sortProperties(on);
622         return this;
623      }
624
625      @Override /* Overridden from Builder */
626      public Builder stopClass(Class<?> on, Class<?> value) {
627         super.stopClass(on, value);
628         return this;
629      }
630
631      @Override /* Overridden from Builder */
632      public <T,S> Builder swap(Class<T> normalClass, Class<S> swappedClass, ThrowingFunction<T,S> swapFunction) {
633         super.swap(normalClass, swappedClass, swapFunction);
634         return this;
635      }
636
637      @Override /* Overridden from Builder */
638      public <T,S> Builder swap(Class<T> normalClass, Class<S> swappedClass, ThrowingFunction<T,S> swapFunction, ThrowingFunction<S,T> unswapFunction) {
639         super.swap(normalClass, swappedClass, swapFunction, unswapFunction);
640         return this;
641      }
642
643      @Override /* Overridden from Builder */
644      public Builder swaps(Class<?>...values) {
645         super.swaps(values);
646         return this;
647      }
648
649      @Override /* Overridden from Builder */
650      public Builder swaps(Object...values) {
651         super.swaps(values);
652         return this;
653      }
654
655      @Override /* Overridden from Builder */
656      public Builder timeZone(TimeZone value) {
657         super.timeZone(value);
658         return this;
659      }
660
661      @Override /* Overridden from Builder */
662      public Builder type(Class<? extends org.apache.juneau.Context> value) {
663         super.type(value);
664         return this;
665      }
666
667      @Override /* Overridden from Builder */
668      public Builder typeName(Class<?> on, String value) {
669         super.typeName(on, value);
670         return this;
671      }
672
673      @Override /* Overridden from Builder */
674      public Builder typePropertyName(Class<?> on, String value) {
675         super.typePropertyName(on, value);
676         return this;
677      }
678
679      @Override /* Overridden from Builder */
680      public Builder typePropertyName(String value) {
681         super.typePropertyName(value);
682         return this;
683      }
684
685      @Override /* Overridden from Builder */
686      public Builder useEnumNames() {
687         super.useEnumNames();
688         return this;
689      }
690
691      @Override /* Overridden from Builder */
692      public Builder useJavaBeanIntrospector() {
693         super.useJavaBeanIntrospector();
694         return this;
695      }
696   }
697
698   private final boolean detectRecursions;
699   private final boolean ignoreRecursions;
700   private final int initialDepth;
701   private final int maxDepth;
702   private final boolean actualDetectRecursions;
703
704   /**
705    * Constructor
706    *
707    * @param builder The builder for this object.
708    */
709   protected BeanTraverseContext(Builder builder) {
710      super(builder);
711
712      detectRecursions = builder.detectRecursions;
713      ignoreRecursions = builder.ignoreRecursions;
714      initialDepth = builder.initialDepth;
715      maxDepth = builder.maxDepth;
716
717      actualDetectRecursions = detectRecursions || ignoreRecursions || super.isDebug();
718   }
719
720   @Override /* Overridden from Context */
721   public abstract Builder copy();
722
723   /**
724    * Initial depth.
725    *
726    * @see Builder#initialDepth(int)
727    * @return
728    *    The initial indentation level at the root.
729    */
730   public final int getInitialDepth() { return initialDepth; }
731
732   /**
733    * Max traversal depth.
734    *
735    * @see Builder#maxDepth(int)
736    * @return
737    *    The depth at which traversal is aborted if depth is reached in the POJO tree.
738    * <br>If this depth is exceeded, an exception is thrown.
739    */
740   public final int getMaxDepth() { return maxDepth; }
741
742   /**
743    * Automatically detect POJO recursions.
744    *
745    * @see Builder#detectRecursions()
746    * @return
747    *    <jk>true</jk> if recursions should be checked for during traversal.
748    */
749   public final boolean isDetectRecursions() { return actualDetectRecursions; }
750
751   /**
752    * Ignore recursion errors.
753    *
754    * @see Builder#ignoreRecursions()
755    * @return
756    *    <jk>true</jk> if when we encounter the same object when traversing a tree, we set the value to <jk>null</jk>.
757    *    <br>Otherwise, an exception is thrown with the message <js>"Recursion occurred, stack=..."</js>.
758    */
759   public final boolean isIgnoreRecursions() { return ignoreRecursions; }
760
761   /**
762    * Detect recursions flag (raw value).
763    *
764    * @return The detect recursions flag.
765    */
766   protected final boolean getDetectRecursions() { return detectRecursions; }
767
768   /**
769    * Ignore recursions flag (raw value).
770    *
771    * @return The ignore recursions flag.
772    */
773   protected final boolean getIgnoreRecursions() { return ignoreRecursions; }
774
775   @Override /* Overridden from BeanContextable */
776   protected FluentMap<String,Object> properties() {
777      return super.properties()
778         .a("detectRecursions", detectRecursions)
779         .a("ignoreRecursions", ignoreRecursions)
780         .a("initialDepth", initialDepth)
781         .a("maxDepth", maxDepth);
782   }
783}