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.marshall;
018
019import static org.apache.juneau.commons.utils.SystemUtils.*;
020
021import org.apache.juneau.commons.collections.*;
022
023/**
024 * Parent class for all classes that traverse POJOs.
025 *
026 * <h5 class='topic'>Description</h5>
027 * <p>
028 * Base class that serves as the parent class for all serializers and other classes that traverse POJOs.
029 *
030 * <h5 class='section'>Notes:</h5><ul>
031 *    <li class='note'>This class is thread safe and reusable.
032 * </ul>
033 *
034 */
035@SuppressWarnings({
036   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
037   "rawtypes" // The protected constructor takes the raw Builder type, shared by subclasses that have different self-type parameters
038})
039public abstract class MarshallingTraverseContext extends MarshallingContextable {
040
041   /**
042    * Builder class.
043    */
044   @SuppressWarnings({
045      "java:S119" // 'SELF' (CRTP self-type) is intentional and clearer than a single-letter name.
046   })
047   public abstract static class Builder<SELF extends Builder<SELF>> extends MarshallingContextable.Builder<SELF> {
048
049      private boolean detectRecursions;
050      private boolean ignoreRecursions;
051      private int initialDepth;
052      private int maxDepth;
053
054      /**
055       * Constructor, default settings.
056       */
057      protected Builder() {
058         detectRecursions = env("MarshallingTraverseContext.detectRecursions", false);
059         ignoreRecursions = env("MarshallingTraverseContext.ignoreRecursions", false);
060         initialDepth = env("MarshallingTraverseContext.initialDepth", 0);
061         maxDepth = env("MarshallingTraverseContext.maxDepth", 100);
062      }
063
064      /**
065       * Copy constructor.
066       *
067       * @param copyFrom The bean to copy from.  Cannot be <jk>null</jk>.
068       */
069      protected Builder(MarshallingTraverseContext copyFrom) {
070         super(copyFrom);
071         detectRecursions = copyFrom.getDetectRecursions();
072         ignoreRecursions = copyFrom.getIgnoreRecursions();
073         initialDepth = copyFrom.getInitialDepth();
074         maxDepth = copyFrom.getMaxDepth();
075      }
076
077      /**
078       * Copy constructor.
079       *
080       * @param copyFrom The builder to copy from.  Cannot be <jk>null</jk>.
081       */
082      protected Builder(Builder<?> copyFrom) {
083         super(copyFrom);
084         detectRecursions = copyFrom.detectRecursions;
085         ignoreRecursions = copyFrom.ignoreRecursions;
086         initialDepth = copyFrom.initialDepth;
087         maxDepth = copyFrom.maxDepth;
088      }
089
090      /**
091       * Automatically detect POJO recursions.
092       *
093       * <p>
094       * When enabled, specifies that recursions should be checked for during traversal.
095       *
096       * <p>
097       * Recursions can occur when traversing models that aren't true trees but rather contain loops.
098       * <br>When recursion detection is disabled, such loops are instead bounded by {@link #maxDepth(int) maxDepth}
099       *    (default <c>100</c>): the over-depth branch is silently truncated, producing finite but semantically-incomplete output.
100       * <br>If the stack is exhausted before <c>maxDepth</c> is reached, the resulting {@link StackOverflowError} is
101       *    converted to a {@link MarshallingRecursionException MarshallingRecursionException} with the message <js>"Depth too deep.  Stack overflow occurred."</js>.
102       *
103       * <h5 class='section'>Notes:</h5><ul>
104       *    <li class='note'>
105       *       Checking for recursion can cause a small performance penalty.
106       *    <li class='note'>
107       *       This is the recommended way to fail-fast on cyclic bean graphs such as parent/child
108       *       {@link ParentProperty @ParentProperty} references.  Under the default config such cycles are instead
109       *       silently truncated at {@link #maxDepth(int) maxDepth} (see the {@link ParentProperty} Javadoc).
110       * </ul>
111       *
112       * <h5 class='section'>Example:</h5>
113       * <p class='bjava'>
114       *    <jc>// Create a serializer that automatically checks for recursions.</jc>
115       *    WriterSerializer <jv>serializer</jv> = JsonSerializer
116       *       .<jsm>create</jsm>()
117       *       .detectRecursions()
118       *       .build();
119       *
120       *    <jc>// Create a POJO model with a recursive loop.</jc>
121       *    <jk>public class</jk> MyBean {
122       *       <jk>public</jk> Object <jf>f</jf>;
123       *    }
124       *    MyBean <jv>bean</jv> = <jk>new</jk> MyBean();
125       *    <jv>bean</jv>.<jf>f</jf> = <jv>bean</jv>;
126       *
127       *    <jc>// Throws a SerializeException and not a StackOverflowError</jc>
128       *    String <jv>json</jv> = <jv>serializer</jv>.write(<jv>bean</jv>);
129       * </p>
130       *
131       * @return This object.
132       */
133      public SELF detectRecursions() {
134         return detectRecursions(true);
135      }
136
137      /**
138       * Same as {@link #detectRecursions()} but allows you to explicitly specify the value.
139       *
140       * @param value The value for this setting.
141       * @return This object.
142       */
143      public SELF detectRecursions(boolean value) {
144         detectRecursions = value;
145         return self();
146      }
147
148      @Override /* Overridden from Context.Builder<?> */
149      public HashKey hashKey() {
150         // @formatter:off
151         return HashKey.of(
152            super.hashKey(),
153            detectRecursions,
154            ignoreRecursions,
155            initialDepth,
156            maxDepth
157         );
158         // @formatter:on
159      }
160
161      /**
162       * Ignore recursion errors.
163       *
164       * <p>
165       * When enabled, when we encounter the same object when traversing a tree, we set the value to <jk>null</jk>.
166       *
167       * <p>
168       * For example, if a model contains the links A-&gt;B-&gt;C-&gt;A, then the JSON generated will look like
169       *    the following when this setting is <jk>true</jk>...
170       *
171       * <p class='bjson'>
172       *    {A:{B:{C:<jk>null</jk>}}}
173       * </p>
174       *
175       * <h5 class='section'>Notes:</h5><ul>
176       *    <li class='note'>
177       *       Checking for recursion can cause a small performance penalty.
178       *    <li class='note'>
179       *       Use this to serialize cyclic bean graphs (such as parent/child {@link ParentProperty @ParentProperty}
180       *       references) by emitting the repeated node as <jk>null</jk> so the output round-trips cleanly.
181       * </ul>
182       *
183       * <h5 class='section'>Example:</h5>
184       * <p class='bjava'>
185       *    <jc>// Create a serializer ignores recursions.</jc>
186       *    WriterSerializer <jv>serializer</jv> = JsonSerializer
187       *       .<jsm>create</jsm>()
188       *       .ignoreRecursions()
189       *       .build();
190       *
191       *    <jc>// Create a POJO model with a recursive loop.</jc>
192       *    <jk>public class</jk> MyBean {
193       *       <jk>public</jk> Object <jf>f</jf>;
194       *    }
195       *    MyBean <jv>bean</jv> = <jk>new</jk> MyBean();
196       *    <jv>bean</jv>.<jf>f</jf> = <jv>bean</jv>;
197       *
198       *    <jc>// Produces "{f:null}"</jc>
199       *    String <jv>json</jv> = <jv>serializer</jv>.write(<jv>bean</jv>);
200       * </p>
201       *
202       * @return This object.
203       */
204      public SELF ignoreRecursions() {
205         return ignoreRecursions(true);
206      }
207
208      /**
209       * Same as {@link #ignoreRecursions()} but allows you to explicitly specify the value.
210       *
211       * @param value The value for this setting.
212       * @return This object.
213       */
214      public SELF ignoreRecursions(boolean value) {
215         ignoreRecursions = value;
216         return self();
217      }
218
219      /**
220       * Initial depth.
221       *
222       * <p>
223       * The initial indentation level at the root.
224       *
225       * <p>
226       * Useful when constructing document fragments that need to be indented at a certain level when whitespace is enabled.
227       *
228       * <h5 class='section'>Example:</h5>
229       * <p class='bjava'>
230       *    <jc>// Create a serializer with whitespace enabled and an initial depth of 2.</jc>
231       *    WriterSerializer <jv>serializer</jv> = JsonSerializer
232       *       .<jsm>create</jsm>()
233       *       .ws()
234       *       .initialDepth(2)
235       *       .build();
236       *
237       *    <jc>// Produces "\t\t{\n\t\t\t'foo':'bar'\n\t\t}\n"</jc>
238       *    String <jv>json</jv> = <jv>serializer</jv>.write(<jk>new</jk> MyBean());
239       * </p>
240       *
241       * @param value
242       *    The new value for this setting.
243       *    <br>The default is <c>0</c>.
244       * @return This object.
245       */
246      public SELF initialDepth(int value) {
247         initialDepth = value;
248         return self();
249      }
250
251      /**
252       * Max traversal depth.
253       *
254       * <p>
255       * Specifies the maximum depth traversed in the POJO tree.
256       *
257       * <p>
258       * When this depth is exceeded, the over-depth nodes are silently dropped (truncated) from the output — no
259       * exception is thrown.  This is a size guard that bounds the output of deeply-nested (or cyclic) models; it is
260       * <b>not</b> a cycle detector.  For genuine recursive references, enable {@link #detectRecursions()} to fail-fast
261       * with a {@link MarshallingRecursionException}, or {@link #ignoreRecursions()} to emit repeated nodes as
262       * <jk>null</jk> so the output round-trips cleanly.
263       *
264       * <h5 class='section'>Example:</h5>
265       * <p class='bjava'>
266       *    <jc>// Create a serializer that truncates output beyond a depth of 20.</jc>
267       *    WriterSerializer <jv>serializer</jv> = JsonSerializer
268       *       .<jsm>create</jsm>()
269       *       .maxDepth(20)
270       *       .build();
271       * </p>
272       *
273       * <h5 class='section'>See Also:</h5><ul>
274       *    <li class='jm'>{@link Builder#maxDepth(int)}
275       * </ul>
276       *
277       * @param value
278       *    The new value for this setting.
279       *    <br>The default is <c>100</c>.
280       * @return This object.
281       */
282      public SELF maxDepth(int value) {
283         maxDepth = value;
284         return self();
285      }
286
287
288   }
289
290   private final boolean detectRecursions;
291   private final boolean ignoreRecursions;
292   private final int initialDepth;
293   private final int maxDepth;
294   private final boolean actualDetectRecursions;
295
296   /**
297    * Constructor
298    *
299    * @param builder The builder for this object.  Cannot be <jk>null</jk>.
300    */
301   protected MarshallingTraverseContext(Builder builder) {
302      super(builder);
303
304      detectRecursions = builder.detectRecursions;
305      ignoreRecursions = builder.ignoreRecursions;
306      initialDepth = builder.initialDepth;
307      maxDepth = builder.maxDepth;
308
309      actualDetectRecursions = detectRecursions || ignoreRecursions || super.isDebug();
310   }
311
312   @Override /* Overridden from Context */
313   public abstract Builder<?> copy();
314
315   /**
316    * Initial depth.
317    *
318    * @see Builder#initialDepth(int)
319    * @return
320    *    The initial indentation level at the root.
321    */
322   public final int getInitialDepth() { return initialDepth; }
323
324   /**
325    * Max traversal depth.
326    *
327    * @see Builder#maxDepth(int)
328    * @return
329    *    The depth beyond which nodes in the POJO tree are dropped (truncated) from the output.
330    * <br>Values deeper than this limit are silently omitted rather than causing an exception to be thrown.
331    * <br>This is a size guard, not a cycle detector.
332    */
333   public final int getMaxDepth() { return maxDepth; }
334
335   /**
336    * Automatically detect POJO recursions.
337    *
338    * @see Builder#detectRecursions()
339    * @return
340    *    <jk>true</jk> if recursions should be checked for during traversal.
341    */
342   public final boolean isDetectRecursions() { return detectRecursions; }
343
344   /**
345    * Whether recursion detection is effectively enabled during traversal.
346    * This is {@link #isDetectRecursions()} OR {@link #isIgnoreRecursions()} OR {@link #isDebug()}.
347    */
348   protected final boolean shouldDetectRecursions() { return actualDetectRecursions; }
349
350   /**
351    * Ignore recursion errors.
352    *
353    * @see Builder#ignoreRecursions()
354    * @return
355    *    <jk>true</jk> if when we encounter the same object when traversing a tree, we set the value to <jk>null</jk>.
356    *    <br>Otherwise, an exception is thrown with the message <js>"Recursion occurred, stack=..."</js>.
357    */
358   public final boolean isIgnoreRecursions() { return ignoreRecursions; }
359
360   /**
361    * Detect recursions flag (raw value).
362    *
363    * @return The detect recursions flag.
364    */
365   protected final boolean getDetectRecursions() { return detectRecursions; }
366
367   /**
368    * Ignore recursions flag (raw value).
369    *
370    * @return The ignore recursions flag.
371    */
372   protected final boolean getIgnoreRecursions() { return ignoreRecursions; }
373
374   @Override /* Overridden from MarshallingContextable */
375   protected FluentMap<String,Object> properties() {
376      return super.properties()
377         .a("detectRecursions", detectRecursions)
378         .a("ignoreRecursions", ignoreRecursions)
379         .a("initialDepth", initialDepth)
380         .a("maxDepth", maxDepth);
381   }
382}