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