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.parser.*;
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(boolean)}
054    *          <li class='jm'>{@link org.apache.juneau.BeanTraverseBuilder#detectRecursions()}
055    *       </ul>
056    * </ul>
057    *
058    * <h5 class='section'>Description:</h5>
059    * <p>
060    * 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 ParseException ParseExceptions} with the message <js>"Depth too deep.  Stack overflow occurred."</js>.
066    *
067    * <p>
068    * The behavior when recursions are detected depends on the value for {@link #BEANTRAVERSE_ignoreRecursions}.
069    *
070    * <p>
071    * For example, if a model contains the links A-&gt;B-&gt;C-&gt;A, then the JSON generated will look like
072    *    the following when <jsf>BEANTRAVERSE_ignoreRecursions</jsf> is <jk>true</jk>...
073    *
074    * <p class='bcode w800'>
075    *    {A:{B:{C:<jk>null</jk>}}}
076    * </p>
077    *
078    * <ul class='notes'>
079    *    <li>
080    *       Checking for recursion can cause a small performance penalty.
081    * </ul>
082    *
083    * <h5 class='section'>Example:</h5>
084    * <p class='bcode w800'>
085    *    <jc>// Create a serializer that never adds _type to nodes.</jc>
086    *    WriterSerializer s = JsonSerializer
087    *       .<jsm>create</jsm>()
088    *       .detectRecursions()
089    *       .ignoreRecursions()
090    *       .build();
091    *
092    *    <jc>// Same, but use property.</jc>
093    *    WriterSerializer s = JsonSerializer
094    *       .<jsm>create</jsm>()
095    *       .set(<jsf>BEANTRAVERSE_detectRecursions</jsf>, <jk>true</jk>)
096    *       .set(<jsf>BEANTRAVERSE_ignoreRecursions</jsf>, <jk>true</jk>)
097    *       .build();
098    *
099    *    <jc>// Create a POJO model with a recursive loop.</jc>
100    *    <jk>public class</jk> A {
101    *       <jk>public</jk> Object <jf>f</jf>;
102    *    }
103    *    A a = <jk>new</jk> A();
104    *    a.<jf>f</jf> = a;
105    *
106    *    <jc>// Produces "{f:null}"</jc>
107    *    String json = s.serialize(a);
108    * </p>
109    */
110   public static final String BEANTRAVERSE_detectRecursions = PREFIX + ".detectRecursions.b";
111
112   /**
113    * Configuration property:  Ignore recursion errors.
114    *
115    * <h5 class='section'>Property:</h5>
116    * <ul class='spaced-list'>
117    *    <li><b>ID:</b>  {@link org.apache.juneau.BeanTraverseContext#BEANTRAVERSE_ignoreRecursions BEANTRAVERSE_ignoreRecursions}
118    *    <li><b>Name:</b>  <js>"BeanTraverseContext.ignoreRecursions.b"</js>
119    *    <li><b>Data type:</b>  <jk>boolean</jk>
120    *    <li><b>System property:</b>  <c>BeanTraverseContext.ignoreRecursions</c>
121    *    <li><b>Environment variable:</b>  <c>BEANTRAVERSECONTEXT_IGNORERECURSIONS</c>
122    *    <li><b>Default:</b>  <jk>false</jk>
123    *    <li><b>Session property:</b>  <jk>false</jk>
124    *    <li><b>Annotations:</b>
125    *       <ul>
126    *          <li class='ja'>{@link org.apache.juneau.annotation.BeanConfig#ignoreRecursions()}
127    *       </ul>
128    *    <li><b>Methods:</b>
129    *       <ul>
130    *          <li class='jm'>{@link org.apache.juneau.BeanTraverseBuilder#ignoreRecursions(boolean)}
131    *          <li class='jm'>{@link org.apache.juneau.BeanTraverseBuilder#ignoreRecursions()}
132    *       </ul>
133    * </ul>
134    *
135    * <h5 class='section'>Description:</h5>
136    * <p>
137    * Used in conjunction with {@link #BEANTRAVERSE_detectRecursions}.
138    * <br>Setting is ignored if <jsf>BEANTRAVERSE_detectRecursions</jsf> is <jk>false</jk>.
139    *
140    * <p>
141    * If <jk>true</jk>, when we encounter the same object when traversing a tree, we set the value to <jk>null</jk>.
142    * <br>Otherwise, a {@link BeanRecursionException} is thrown with the message <js>"Recursion occurred, stack=..."</js>.
143    */
144   public static final String BEANTRAVERSE_ignoreRecursions = PREFIX + ".ignoreRecursions.b";
145
146   /**
147    * Configuration property:  Initial depth.
148    *
149    * <h5 class='section'>Property:</h5>
150    * <ul class='spaced-list'>
151    *    <li><b>ID:</b>  {@link org.apache.juneau.BeanTraverseContext#BEANTRAVERSE_initialDepth BEANTRAVERSE_initialDepth}
152    *    <li><b>Name:</b>  <js>"BeanTraverseContext.initialDepth.i"</js>
153    *    <li><b>Data type:</b>  <jk>int</jk>
154    *    <li><b>System property:</b>  <c>BeanTraverseContext.initialDepth</c>
155    *    <li><b>Environment variable:</b>  <c>BEANTRAVERSECONTEXT_INITIALDEPTH</c>
156    *    <li><b>Default:</b>  <c>0</c>
157    *    <li><b>Session property:</b>  <jk>false</jk>
158    *    <li><b>Annotations:</b>
159    *       <ul>
160    *          <li class='ja'>{@link org.apache.juneau.annotation.BeanConfig#initialDepth()}
161    *       </ul>
162    *    <li><b>Methods:</b>
163    *       <ul>
164    *          <li class='jm'>{@link org.apache.juneau.BeanTraverseBuilder#initialDepth(int)}
165    *       </ul>
166    * </ul>
167    *
168    * <h5 class='section'>Description:</h5>
169    * <p>
170    * The initial indentation level at the root.
171    * <br>Useful when constructing document fragments that need to be indented at a certain level.
172    *
173    * <h5 class='section'>Example:</h5>
174    * <p class='bcode w800'>
175    *    <jc>// Create a serializer with whitespace enabled and an initial depth of 2.</jc>
176    *    WriterSerializer s = JsonSerializer
177    *       .<jsm>create</jsm>()
178    *       .ws()
179    *       .initialDepth(2)
180    *       .build();
181    *
182    *    <jc>// Same, but use property.</jc>
183    *    WriterSerializer s = JsonSerializer
184    *       .<jsm>create</jsm>()
185    *       .set(<jsf>BEANTRAVERSE_useWhitespace</jsf>, <jk>true</jk>)
186    *       .set(<jsf>BEANTRAVERSE_initialDepth</jsf>, 2)
187    *       .build();
188    *
189    *    <jc>// Produces "\t\t{\n\t\t\t'foo':'bar'\n\t\t}\n"</jc>
190    *    String json = s.serialize(<jk>new</jk> MyBean());
191    * </p>
192    */
193   public static final String BEANTRAVERSE_initialDepth = PREFIX + ".initialDepth.i";
194
195   /**
196    * Configuration property:  Max traversal depth.
197    *
198    * <h5 class='section'>Property:</h5>
199    * <ul class='spaced-list'>
200    *    <li><b>ID:</b>  {@link org.apache.juneau.BeanTraverseContext#BEANTRAVERSE_maxDepth BEANTRAVERSE_maxDepth}
201    *    <li><b>Name:</b>  <js>"BeanTraverseContext.maxDepth.i"</js>
202    *    <li><b>Data type:</b>  <jk>int</jk>
203    *    <li><b>System property:</b>  <c>BeanTraverseContext.maxDepth</c>
204    *    <li><b>Environment variable:</b>  <c>BEANTRAVERSECONTEXT_MAXDEPTH</c>
205    *    <li><b>Default:</b>  <c>100</c>
206    *    <li><b>Session property:</b>  <jk>false</jk>
207    *    <li><b>Annotations:</b>
208    *       <ul>
209    *          <li class='ja'>{@link org.apache.juneau.annotation.BeanConfig#maxDepth()}
210    *       </ul>
211    *    <li><b>Methods:</b>
212    *       <ul>
213    *          <li class='jm'>{@link org.apache.juneau.BeanTraverseBuilder#maxDepth(int)}
214    *       </ul>
215    * </ul>
216    *
217    * <h5 class='section'>Description:</h5>
218    * <p>
219    * Abort traversal if specified depth is reached in the POJO tree.
220    * <br>If this depth is exceeded, an exception is thrown.
221    *
222    * <h5 class='section'>Example:</h5>
223    * <p class='bcode w800'>
224    *    <jc>// Create a serializer that throws an exception if the depth is greater than 20.</jc>
225    *    WriterSerializer s = JsonSerializer
226    *       .<jsm>create</jsm>()
227    *       .maxDepth(20)
228    *       .build();
229    *
230    *    <jc>// Same, but use property.</jc>
231    *    WriterSerializer s = JsonSerializer
232    *       .<jsm>create</jsm>()
233    *       .set(<jsf>BEANTRAVERSE_maxDepth</jsf>, 20)
234    *       .build();
235    * </p>
236    */
237   public static final String BEANTRAVERSE_maxDepth = PREFIX + ".maxDepth.i";
238
239   //-------------------------------------------------------------------------------------------------------------------
240   // Instance
241   //-------------------------------------------------------------------------------------------------------------------
242
243   private final int initialDepth, maxDepth;
244   private final boolean
245      detectRecursions,
246      ignoreRecursions;
247
248   /**
249    * Constructor
250    *
251    * @param ps
252    *    The property store containing all the settings for this object.
253    */
254   protected BeanTraverseContext(PropertyStore ps) {
255      super(ps);
256
257      maxDepth = getIntegerProperty(BEANTRAVERSE_maxDepth, 100);
258      initialDepth = getIntegerProperty(BEANTRAVERSE_initialDepth, 0);
259      detectRecursions = getBooleanProperty(BEANTRAVERSE_detectRecursions, false);
260      ignoreRecursions = getBooleanProperty(BEANTRAVERSE_ignoreRecursions, false);
261   }
262
263   @Override /* Context */
264   public BeanTraverseBuilder builder() {
265      return null;
266   }
267
268   @Override /* Context */
269   public BeanTraverseSession createSession() {
270      return new BeanTraverseSession(this, createDefaultSessionArgs());
271   }
272
273   @Override /* Context */
274   public BeanTraverseSession createSession(BeanSessionArgs args) {
275      return new BeanTraverseSession(this, args);
276   }
277
278
279   //-----------------------------------------------------------------------------------------------------------------
280   // Properties
281   //-----------------------------------------------------------------------------------------------------------------
282
283   /**
284    * Configuration property:  Automatically detect POJO recursions.
285    *
286    * @see #BEANTRAVERSE_detectRecursions
287    * @return
288    *    <jk>true</jk> if recursions should be checked for during traversal.
289    */
290   protected final boolean isDetectRecursions() {
291      return detectRecursions;
292   }
293
294   /**
295    * Configuration property:  Ignore recursion errors.
296    *
297    * @see #BEANTRAVERSE_ignoreRecursions
298    * @return
299    *    <jk>true</jk> if when we encounter the same object when traversing a tree, we set the value to <jk>null</jk>.
300    *    <br>Otherwise, an exception is thrown with the message <js>"Recursion occurred, stack=..."</js>.
301    */
302   protected final boolean isIgnoreRecursions() {
303      return ignoreRecursions;
304   }
305
306   /**
307    * Configuration property:  Initial depth.
308    *
309    * @see #BEANTRAVERSE_initialDepth
310    * @return
311    *    The initial indentation level at the root.
312    */
313   protected final int getInitialDepth() {
314      return initialDepth;
315   }
316
317   /**
318    * Configuration property:  Max traversal depth.
319    *
320    * @see #BEANTRAVERSE_maxDepth
321    * @return
322    *    The depth at which traversal is aborted if depth is reached in the POJO tree.
323    * <br>If this depth is exceeded, an exception is thrown.
324    */
325   protected final int getMaxDepth() {
326      return maxDepth;
327   }
328
329   //-----------------------------------------------------------------------------------------------------------------
330   // Other methods
331   //-----------------------------------------------------------------------------------------------------------------
332
333   @Override /* Context */
334   public ObjectMap toMap() {
335      return super.toMap()
336         .append("BeanTraverseContext", new DefaultFilteringObjectMap()
337            .append("detectRecursions", detectRecursions)
338            .append("maxDepth", maxDepth)
339            .append("ignoreRecursions", ignoreRecursions)
340            .append("initialDepth", initialDepth)
341         );
342   }
343}