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;
014
015
016import org.apache.juneau.annotation.*;
017import org.apache.juneau.collections.*;
018
019/**
020 * Parent class for all classes that traverse POJOs.
021 *
022 * <h5 class='topic'>Description</h5>
023 *
024 * Base class that serves as the parent class for all serializers and other classes that traverse POJOs.
025 */
026@ConfigurableContext
027public abstract class BeanTraverseContext extends BeanContext {
028
029   //-------------------------------------------------------------------------------------------------------------------
030   // Configurable properties
031   //-------------------------------------------------------------------------------------------------------------------
032
033   static final String PREFIX = "BeanTraverseContext";
034
035   /**
036    * Configuration property:  Automatically detect POJO recursions.
037    *
038    * <h5 class='section'>Property:</h5>
039    * <ul class='spaced-list'>
040    *    <li><b>ID:</b>  {@link org.apache.juneau.BeanTraverseContext#BEANTRAVERSE_detectRecursions BEANTRAVERSE_detectRecursions}
041    *    <li><b>Name:</b>  <js>"BeanTraverseContext.detectRecursions.b"</js>
042    *    <li><b>Data type:</b>  <jk>boolean</jk>
043    *    <li><b>System property:</b>  <c>BeanTraverseContext.detectRecursions</c>
044    *    <li><b>Environment variable:</b>  <c>BEANTRAVERSECONTEXT_DETECTRECURSIONS</c>
045    *    <li><b>Default:</b>  <jk>false</jk>
046    *    <li><b>Session property:</b>  <jk>false</jk>
047    *    <li><b>Annotations:</b>
048    *       <ul>
049    *          <li class='ja'>{@link org.apache.juneau.annotation.BeanConfig#detectRecursions()}
050    *       </ul>
051    *    <li><b>Methods:</b>
052    *       <ul>
053    *          <li class='jm'>{@link org.apache.juneau.BeanTraverseBuilder#detectRecursions()}
054    *       </ul>
055    * </ul>
056    *
057    * <h5 class='section'>Description:</h5>
058    *
059    * <p>
060    * When enabled, specifies that recursions should be checked for during traversal.
061    *
062    * <p>
063    * Recursions can occur when traversing models that aren't true trees but rather contain loops.
064    * <br>In general, unchecked recursions cause stack-overflow-errors.
065    * <br>These show up as {@link BeanRecursionException BeanRecursionException} with the message <js>"Depth too deep.  Stack overflow occurred."</js>.
066    *
067    * <ul class='notes'>
068    *    <li>
069    *       Checking for recursion can cause a small performance penalty.
070    * </ul>
071    *
072    * <h5 class='section'>Example:</h5>
073    * <p class='bcode w800'>
074    *    <jc>// Create a serializer that automatically checks for recursions.</jc>
075    *    WriterSerializer s = JsonSerializer
076    *       .<jsm>create</jsm>()
077    *       .set(<jsf>BEANTRAVERSE_detectRecursions</jsf>, <jk>true</jk>)
078    *       .build();
079    *
080    *    <jc>// Create a POJO model with a recursive loop.</jc>
081    *    <jk>public class</jk> A {
082    *       <jk>public</jk> Object <jf>f</jf>;
083    *    }
084    *    A a = <jk>new</jk> A();
085    *    a.<jf>f</jf> = a;
086    *
087    *    <jc>// Throws a SerializeException</jc>
088    *    String json = s.serialize(a);
089    * </p>
090    */
091   public static final String BEANTRAVERSE_detectRecursions = PREFIX + ".detectRecursions.b";
092
093   /**
094    * Configuration property:  Ignore recursion errors.
095    *
096    * <h5 class='section'>Property:</h5>
097    * <ul class='spaced-list'>
098    *    <li><b>ID:</b>  {@link org.apache.juneau.BeanTraverseContext#BEANTRAVERSE_ignoreRecursions BEANTRAVERSE_ignoreRecursions}
099    *    <li><b>Name:</b>  <js>"BeanTraverseContext.ignoreRecursions.b"</js>
100    *    <li><b>Data type:</b>  <jk>boolean</jk>
101    *    <li><b>System property:</b>  <c>BeanTraverseContext.ignoreRecursions</c>
102    *    <li><b>Environment variable:</b>  <c>BEANTRAVERSECONTEXT_IGNORERECURSIONS</c>
103    *    <li><b>Default:</b>  <jk>false</jk>
104    *    <li><b>Session property:</b>  <jk>false</jk>
105    *    <li><b>Annotations:</b>
106    *       <ul>
107    *          <li class='ja'>{@link org.apache.juneau.annotation.BeanConfig#ignoreRecursions()}
108    *       </ul>
109    *    <li><b>Methods:</b>
110    *       <ul>
111    *          <li class='jm'>{@link org.apache.juneau.BeanTraverseBuilder#ignoreRecursions()}
112    *       </ul>
113    * </ul>
114    *
115    * <h5 class='section'>Description:</h5>
116    *
117    * <p>
118    * When enabled, when we encounter the same object when traversing a tree, we set the value to <jk>null</jk>.
119    *
120    * <p>
121    * For example, if a model contains the links A-&gt;B-&gt;C-&gt;A, then the JSON generated will look like
122    *    the following when <jsf>BEANTRAVERSE_ignoreRecursions</jsf> is <jk>true</jk>...
123    *
124    * <ul class='notes'>
125    *    <li>
126    *       Checking for recursion can cause a small performance penalty.
127    * </ul>
128    *
129    * <p class='bcode w800'>
130    *    {A:{B:{C:<jk>null</jk>}}}
131    * </p>
132    *
133    * <h5 class='section'>Example:</h5>
134    * <p class='bcode w800'>
135    *    <jc>// Create a serializer ignores recursions.</jc>
136    *    WriterSerializer s = JsonSerializer
137    *       .<jsm>create</jsm>()
138    *       .set(<jsf>BEANTRAVERSE_ignoreRecursions</jsf>, <jk>true</jk>)
139    *       .build();
140    *
141    *    <jc>// Create a POJO model with a recursive loop.</jc>
142    *    <jk>public class</jk> A {
143    *       <jk>public</jk> Object <jf>f</jf>;
144    *    }
145    *    A a = <jk>new</jk> A();
146    *    a.<jf>f</jf> = a;
147    *
148    *    <jc>// Produces "{f:null}"</jc>
149    *    String json = s.serialize(a);
150    * </p>
151    */
152   public static final String BEANTRAVERSE_ignoreRecursions = PREFIX + ".ignoreRecursions.b";
153
154   /**
155    * Configuration property:  Initial depth.
156    *
157    * <h5 class='section'>Property:</h5>
158    * <ul class='spaced-list'>
159    *    <li><b>ID:</b>  {@link org.apache.juneau.BeanTraverseContext#BEANTRAVERSE_initialDepth BEANTRAVERSE_initialDepth}
160    *    <li><b>Name:</b>  <js>"BeanTraverseContext.initialDepth.i"</js>
161    *    <li><b>Data type:</b>  <jk>int</jk>
162    *    <li><b>System property:</b>  <c>BeanTraverseContext.initialDepth</c>
163    *    <li><b>Environment variable:</b>  <c>BEANTRAVERSECONTEXT_INITIALDEPTH</c>
164    *    <li><b>Default:</b>  <c>0</c>
165    *    <li><b>Session property:</b>  <jk>false</jk>
166    *    <li><b>Annotations:</b>
167    *       <ul>
168    *          <li class='ja'>{@link org.apache.juneau.annotation.BeanConfig#initialDepth()}
169    *       </ul>
170    *    <li><b>Methods:</b>
171    *       <ul>
172    *          <li class='jm'>{@link org.apache.juneau.BeanTraverseBuilder#initialDepth(int)}
173    *       </ul>
174    * </ul>
175    *
176    * <h5 class='section'>Description:</h5>
177    *
178    * <p>
179    * The initial indentation level at the root.
180    *
181    * <p>
182    * Useful when constructing document fragments that need to be indented at a certain level when whitespace is enabled.
183    *
184    * <h5 class='section'>Example:</h5>
185    * <p class='bcode w800'>
186    *    <jc>// Create a serializer with whitespace enabled and an initial depth of 2.</jc>
187    *    WriterSerializer s = JsonSerializer
188    *       .<jsm>create</jsm>()
189    *       .ws()
190    *       .initialDepth(2)
191    *       .build();
192    *
193    *    <jc>// Same, but use property.</jc>
194    *    WriterSerializer s = JsonSerializer
195    *       .<jsm>create</jsm>()
196    *       .set(<jsf>BEANTRAVERSE_useWhitespace</jsf>, <jk>true</jk>)
197    *       .set(<jsf>BEANTRAVERSE_initialDepth</jsf>, 2)
198    *       .build();
199    *
200    *    <jc>// Produces "\t\t{\n\t\t\t'foo':'bar'\n\t\t}\n"</jc>
201    *    String json = s.serialize(<jk>new</jk> MyBean());
202    * </p>
203    */
204   public static final String BEANTRAVERSE_initialDepth = PREFIX + ".initialDepth.i";
205
206   /**
207    * Configuration property:  Max traversal depth.
208    *
209    * <h5 class='section'>Property:</h5>
210    * <ul class='spaced-list'>
211    *    <li><b>ID:</b>  {@link org.apache.juneau.BeanTraverseContext#BEANTRAVERSE_maxDepth BEANTRAVERSE_maxDepth}
212    *    <li><b>Name:</b>  <js>"BeanTraverseContext.maxDepth.i"</js>
213    *    <li><b>Data type:</b>  <jk>int</jk>
214    *    <li><b>System property:</b>  <c>BeanTraverseContext.maxDepth</c>
215    *    <li><b>Environment variable:</b>  <c>BEANTRAVERSECONTEXT_MAXDEPTH</c>
216    *    <li><b>Default:</b>  <c>100</c>
217    *    <li><b>Session property:</b>  <jk>false</jk>
218    *    <li><b>Annotations:</b>
219    *       <ul>
220    *          <li class='ja'>{@link org.apache.juneau.annotation.BeanConfig#maxDepth()}
221    *       </ul>
222    *    <li><b>Methods:</b>
223    *       <ul>
224    *          <li class='jm'>{@link org.apache.juneau.BeanTraverseBuilder#maxDepth(int)}
225    *       </ul>
226    * </ul>
227    *
228    * <h5 class='section'>Description:</h5>
229    *
230    * <p>
231    * When enabled, abort traversal if specified depth is reached in the POJO tree.
232    *
233    * <p>
234    * If this depth is exceeded, an exception is thrown.
235    *
236    * <p>
237    * This prevents stack overflows from occurring when trying to traverse models with recursive references.
238    *
239    * <h5 class='section'>Example:</h5>
240    * <p class='bcode w800'>
241    *    <jc>// Create a serializer that throws an exception if the depth reaches greater than 20.</jc>
242    *    WriterSerializer s = JsonSerializer
243    *       .<jsm>create</jsm>()
244    *       .maxDepth(20)
245    *       .build();
246    *
247    *    <jc>// Same, but use property.</jc>
248    *    WriterSerializer s = JsonSerializer
249    *       .<jsm>create</jsm>()
250    *       .set(<jsf>BEANTRAVERSE_maxDepth</jsf>, 20)
251    *       .build();
252    * </p>
253    */
254   public static final String BEANTRAVERSE_maxDepth = PREFIX + ".maxDepth.i";
255
256   //-------------------------------------------------------------------------------------------------------------------
257   // Instance
258   //-------------------------------------------------------------------------------------------------------------------
259
260   private final int initialDepth, maxDepth;
261   private final boolean
262      detectRecursions,
263      ignoreRecursions;
264
265   /**
266    * Constructor
267    *
268    * @param ps
269    *    The property store containing all the settings for this object.
270    */
271   protected BeanTraverseContext(PropertyStore ps) {
272      super(ps);
273
274      maxDepth = getIntegerProperty(BEANTRAVERSE_maxDepth, 100);
275      initialDepth = getIntegerProperty(BEANTRAVERSE_initialDepth, 0);
276      ignoreRecursions = getBooleanProperty(BEANTRAVERSE_ignoreRecursions, false);
277      detectRecursions = getBooleanProperty(BEANTRAVERSE_detectRecursions, ignoreRecursions);
278   }
279
280   @Override /* Context */
281   public BeanTraverseBuilder builder() {
282      return null;
283   }
284
285   @Override /* Context */
286   public BeanTraverseSession createSession() {
287      return new BeanTraverseSession(this, createDefaultSessionArgs());
288   }
289
290   @Override /* Context */
291   public BeanTraverseSession createSession(BeanSessionArgs args) {
292      return new BeanTraverseSession(this, args);
293   }
294
295
296   //-----------------------------------------------------------------------------------------------------------------
297   // Properties
298   //-----------------------------------------------------------------------------------------------------------------
299
300   /**
301    * Automatically detect POJO recursions.
302    *
303    * @see #BEANTRAVERSE_detectRecursions
304    * @return
305    *    <jk>true</jk> if recursions should be checked for during traversal.
306    */
307   protected final boolean isDetectRecursions() {
308      return detectRecursions;
309   }
310
311   /**
312    * Ignore recursion errors.
313    *
314    * @see #BEANTRAVERSE_ignoreRecursions
315    * @return
316    *    <jk>true</jk> if when we encounter the same object when traversing a tree, we set the value to <jk>null</jk>.
317    *    <br>Otherwise, an exception is thrown with the message <js>"Recursion occurred, stack=..."</js>.
318    */
319   protected final boolean isIgnoreRecursions() {
320      return ignoreRecursions;
321   }
322
323   /**
324    * Initial depth.
325    *
326    * @see #BEANTRAVERSE_initialDepth
327    * @return
328    *    The initial indentation level at the root.
329    */
330   protected final int getInitialDepth() {
331      return initialDepth;
332   }
333
334   /**
335    * Max traversal depth.
336    *
337    * @see #BEANTRAVERSE_maxDepth
338    * @return
339    *    The depth at which traversal is aborted if depth is reached in the POJO tree.
340    * <br>If this depth is exceeded, an exception is thrown.
341    */
342   protected final int getMaxDepth() {
343      return maxDepth;
344   }
345
346   //-----------------------------------------------------------------------------------------------------------------
347   // Other methods
348   //-----------------------------------------------------------------------------------------------------------------
349
350   @Override /* Context */
351   public OMap toMap() {
352      return super.toMap()
353         .a("BeanTraverseContext", new DefaultFilteringOMap()
354            .a("detectRecursions", detectRecursions)
355            .a("maxDepth", maxDepth)
356            .a("ignoreRecursions", ignoreRecursions)
357            .a("initialDepth", initialDepth)
358         );
359   }
360}