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