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 java.util.Collections.*;
020import static org.apache.juneau.commons.utils.ClassUtils.*;
021import static org.apache.juneau.commons.utils.Shorts.*;
022import static org.apache.juneau.commons.utils.ThrowableUtils.*;
023
024import java.util.*;
025import java.util.function.*;
026
027import org.apache.juneau.commons.bean.*;
028import org.apache.juneau.commons.collections.*;
029import org.apache.juneau.commons.utils.*;
030import org.apache.juneau.marshall.collections.*;
031
032/**
033 * ContextSession that lives for the duration of a single use of {@link MarshallingTraverseContext}.
034 *
035 * <p>
036 * Used by serializers and other classes that traverse POJOs for the following purposes:
037 * <ul class='spaced-list'>
038 *    <li>
039 *       Keeping track of how deep it is in a model for indentation purposes.
040 *    <li>
041 *       Ensuring infinite loops don't occur by setting a limit on how deep to traverse a model.
042 *    <li>
043 *       Ensuring infinite loops don't occur from loops in the model (when detectRecursions is enabled.
044 * </ul>
045 *
046 * <h5 class='section'>Notes:</h5><ul>
047 *    <li class='warn'>This class is not thread safe and is typically discarded after one use.
048 * </ul>
049 *
050 */
051@SuppressWarnings({
052   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
053   "java:S1452" // Wildcard required - ClassMeta<?>, ObjectSwap<?,?> for traversal metadata
054})
055public class MarshallingTraverseSession extends MarshallingSession {
056
057   /**
058    * Builder class.
059    */
060   @SuppressWarnings({
061      "java:S119" // 'SELF' (CRTP self-type) is intentional and clearer than a single-letter name.
062   })
063   public abstract static class Builder<SELF extends Builder<SELF>> extends MarshallingSession.Builder<SELF> {
064
065      private final MarshallingTraverseContext ctx;
066      private int initialDepth;
067
068      /**
069       * Constructor
070       *
071       * @param ctx The context creating this session.
072       *    <br>Cannot be <jk>null</jk>.
073       */
074      protected Builder(MarshallingTraverseContext ctx) {
075         super(reqnn("ctx", ctx).getMarshallingContext());
076         this.ctx = ctx;
077         initialDepth = ctx.getInitialDepth();
078      }
079
080
081      /**
082       * Initial depth.
083       *
084       * @param value The new value for this property.
085       * @return This object.
086       */
087      public SELF initialDepth(int value) {
088         initialDepth = value;
089         return self();
090      }
091
092      @Override /* Overridden from Builder */
093      public SELF property(String key, Object value) {
094         if (key == null) {
095            super.property(key, value);
096            return self();
097         }
098         switch (key) {
099            case "initialDepth", "MarshallingTraverseSession.initialDepth":
100               return initialDepth(cvt(value, Integer.class));
101            default:
102               super.property(key, value);
103               return self();
104         }
105      }
106
107   }
108
109   private class StackElement {
110      final int level;
111      final String name;
112      final Object o;
113      final ClassMeta<?> aType;
114
115      StackElement(int level, String name, Object o, ClassMeta<?> aType) {
116         this.level = level;
117         this.name = name;
118         this.o = o;
119         this.aType = aType;
120      }
121
122      String toString(boolean simple) {
123         var sb = new StringBuilder().append('[').append(level).append(']').append(' ');
124         sb.append(ie(name) ? "<noname>" : name).append(':');
125         sb.append(aType.toString(simple));
126         if (aType != aType.getSerializedClassMeta(MarshallingTraverseSession.this))
127            sb.append('/').append(aType.getSerializedClassMeta(MarshallingTraverseSession.this).toString(simple));
128         return sb.toString();
129      }
130   }
131
132   private final MarshallingTraverseContext ctx;
133   private final LinkedList<StackElement> stack = new LinkedList<>();              // Contains the current objects in the current branch of the model.
134   private final Map<Object,Object> set;                                           // Contains the current objects in the current branch of the model.
135   private BeanPropertyMeta currentProperty;
136   private ClassMeta<?> currentClass;
137   private boolean isBottom;                                                       // If 'true', then we're at a leaf in the model (i.e. a String, Number, Boolean, or null).
138   /** The current indentation depth into the model. */
139   @SuppressWarnings({
140      "java:S1104" // Public field accessed externally (e.g., session.indent = 6)
141   })
142   public int indent;
143   private int depth;
144
145   /**
146    * Constructor.
147    *
148    * @param builder The builder for this object.  Cannot be <jk>null</jk>.
149    */
150   protected MarshallingTraverseSession(Builder<?> builder) {
151      super(builder);
152      ctx = builder.ctx;
153      indent = builder.initialDepth;
154      if (isDetectRecursions() || isDebug()) {
155         set = new IdentityHashMap<>();
156      } else {
157         set = emptyMap();
158      }
159   }
160
161   /**
162    * Initial depth.
163    *
164    * @see MarshallingTraverseContext.Builder#initialDepth(int)
165    * @return
166    *    The initial indentation level at the root.
167    */
168   public final int getInitialDepth() { return ctx.getInitialDepth(); }
169
170   /**
171    * Returns information used to determine at what location in the parse a failure occurred.
172    *
173    * @return A map, typically containing something like <c>{line:123,column:456,currentProperty:"foobar"}</c>
174    */
175   public final JsonMap getLastLocation() {
176      Predicate<Object> nn = Shorts::nn;
177      Predicate<Collection<?>> nec = Shorts::ine;
178      // @formatter:off
179      return JsonMap
180         .create()
181         .appendIf(nn, "currentClass", currentClass)
182         .appendIf(nn, "currentProperty", currentProperty)
183         .appendIf(nec, "stack", stack);
184      // @formatter:on
185   }
186
187   /**
188    * Max traversal depth.
189    *
190    * @see MarshallingTraverseContext.Builder#maxDepth(int)
191    * @return
192    *    The depth beyond which nodes in the POJO tree are dropped (truncated) from the output.
193    * <br>Values deeper than this limit are silently omitted rather than causing an exception to be thrown.
194    * <br>This is a size guard, not a cycle detector.
195    */
196   public final int getMaxDepth() { return ctx.getMaxDepth(); }
197
198   /**
199    * Automatically detect POJO recursions.
200    *
201    * @see MarshallingTraverseContext.Builder#detectRecursions()
202    * @return
203    *    <jk>true</jk> if recursions should be checked for during traversal.
204    */
205   public final boolean isDetectRecursions() { return ctx.shouldDetectRecursions(); }
206
207   /**
208    * Ignore recursion errors.
209    *
210    * @see MarshallingTraverseContext.Builder#ignoreRecursions()
211    * @return
212    *    <jk>true</jk> if when we encounter the same object when traversing a tree, we set the value to <jk>null</jk>.
213    *    <br>Otherwise, a {@link MarshallingRecursionException} is thrown with the message <js>"Recursion occurred, stack=..."</js>.
214    */
215   public final boolean isIgnoreRecursions() { return ctx.isIgnoreRecursions(); }
216
217   /**
218    * Returns the inner type of an {@link Optional}.
219    *
220    * @param cm The meta to check.  Must not be <jk>null</jk>.
221    * @return The inner type of an {@link Optional}.
222    */
223   protected final ClassMeta<?> getOptionalType(ClassMeta<?> cm) {
224      if (cm.isOptional())
225         return getOptionalType(cm.getElementType());
226      return cm;
227   }
228
229   /**
230    * If the specified object is an {@link Optional}, returns the inner object.
231    *
232    * @param o The object to check.  Can be <jk>null</jk> (returns <jk>null</jk>).
233    * @return The inner object if it's an {@link Optional}, <jk>null</jk> if it's <jk>null</jk>, or else the same object.
234    */
235   protected final Object getOptionalValue(Object o) {
236      if (o == null)
237         return null;
238      if (o instanceof Optional<?> o2)
239         return getOptionalValue(o2.orElse(null));
240      if (o instanceof OptionalInt o2)
241         return o2.isPresent() ? Integer.valueOf(o2.getAsInt()) : null;
242      if (o instanceof OptionalLong o2)
243         return o2.isPresent() ? Long.valueOf(o2.getAsLong()) : null;
244      if (o instanceof OptionalDouble o2)
245         return o2.isPresent() ? Double.valueOf(o2.getAsDouble()) : null;
246      return o;
247   }
248
249   /**
250    * Returns the current stack trace.
251    *
252    * @param full
253    *    If <jk>true</jk>, returns a full stack trace.
254    * @return The current stack trace.
255    */
256   protected String getStack(boolean full) {
257      var sb = new StringBuilder();
258      stack.forEach(x -> {
259         if (full) {
260            sb.append("\n\t");
261            for (var i = 1; i < x.level; i++)
262               sb.append("  ");
263            if (x.level > 0)
264               sb.append("->");
265            sb.append(x.toString(false));
266         } else {
267            sb.append(" > ").append(x.toString(true));
268         }
269      });
270      return sb.toString();
271   }
272
273   /**
274    * Same as {@link ClassMeta#isOptional()} but gracefully handles a null {@link ClassMeta}.
275    *
276    * @param cm The meta to check.  Can be <jk>null</jk> (returns <jk>false</jk>).
277    * @return <jk>true</jk> if the specified meta is an {@link Optional}.
278    */
279   protected static final boolean isOptional(ClassMeta<?> cm) {
280      return (nn(cm) && cm.isOptional());
281   }
282
283   /**
284    * Returns <jk>true</jk> if we're processing the root node.
285    *
286    * <p>
287    * Must be called after {@link #push(String, Object, ClassMeta)} and before {@link #pop()}.
288    *
289    * @return <jk>true</jk> if we're processing the root node.
290    */
291   protected final boolean isRoot() { return depth == 1; }
292
293   /**
294    * Logs a warning message.
295    *
296    * @param t The throwable that was thrown (if there was one).
297    * @param msg The warning message.
298    * @param args Optional {@link String#format(String, Object...) String.format}-style arguments.
299    */
300   protected void onError(Throwable t, String msg, Object...args) {
301      // Honor the throwable per the @param contract: fold its localized message into the recorded warning when present.
302      if (t == null)
303         super.addWarning(msg, args);
304      else
305         super.addWarning("%s%n\tCaused by: %s", f(msg, args), localizedMessage(t));
306   }
307
308   /**
309    * Pop an object off the stack.
310    */
311   protected final void pop() {
312      indent--;
313      depth--;
314      if ((isDetectRecursions() || isDebug()) && ! isBottom) {
315         Object o = stack.removeLast().o;
316         Object o2 = set.remove(o);
317         if (o2 == null)
318            onError(null, "Couldn't remove object of type '%s' on attribute '%s' from object stack.", cn(o), stack);
319      }
320      isBottom = false;
321   }
322
323   @Override /* Overridden from MarshallingSession */
324   protected FluentMap<String,Object> properties() {
325      return super.properties()
326         .a("indent", indent)
327         .a("depth", depth);
328   }
329
330   /**
331    * Push the specified object onto the stack.
332    *
333    * <p>
334    * When the current depth exceeds {@link #getMaxDepth() maxDepth}, this method returns <jk>null</jk> so that the
335    * over-depth node is dropped (truncated) from the output rather than traversed further.  Exceeding <c>maxDepth</c>
336    * does <b>not</b> throw an exception — it is a size guard that truncates deeply-nested (or cyclic) models, not a
337    * cycle detector.  To fail-fast on genuine cycles, enable
338    * {@link MarshallingTraverseContext.Builder#detectRecursions() detectRecursions}; to omit repeated nodes as
339    * <jk>null</jk>, enable {@link MarshallingTraverseContext.Builder#ignoreRecursions() ignoreRecursions}.
340    *
341    * @param attrName The attribute name.  Can be <jk>null</jk> for unnamed entries (e.g. array elements); used only for stack-trace labeling.
342    * @param o The current object being traversed.  Can be <jk>null</jk> (returns <jk>null</jk>).
343    * @param eType The expected class type.  Can be <jk>null</jk>, in which case the object's actual runtime type is used instead.
344    * @return
345    *    The {@link ClassMeta} of the object so that <c>instanceof</c> operations only need to be performed
346    *    once (since they can be expensive).
347    *    <br>Returns <jk>null</jk> when the object is <jk>null</jk>, when <c>maxDepth</c> is exceeded (over-depth node
348    *    dropped), or when a recursion is detected under {@link MarshallingTraverseContext.Builder#ignoreRecursions() ignoreRecursions}.
349    * @throws MarshallingRecursionException If recursion occurred while
350    *    {@link MarshallingTraverseContext.Builder#detectRecursions() detectRecursions} is enabled without
351    *    {@link MarshallingTraverseContext.Builder#ignoreRecursions() ignoreRecursions}.
352    */
353   protected final ClassMeta<?> push(String attrName, Object o, ClassMeta<?> eType) throws MarshallingRecursionException {
354      indent++;
355      depth++;
356      isBottom = true;
357      if (o == null)
358         return null;
359      var c = o.getClass();
360      ClassMeta<?> cm;
361      if (nn(eType) && c == eType.inner()) {
362         cm = eType;
363      } else if (o instanceof ClassMeta) {
364         cm = (ClassMeta<?>)o;
365      } else {
366         // Unwrap recognized proxies (e.g. Hibernate $HibernateProxy$) so we serialize the entity, not the proxy subclass.
367         var unwrapped = getProxyFor(o);
368         Class<?> k = unwrapped != null ? unwrapped : c;
369         cm = getClassMeta(k);
370      }
371      if (cm.isCharSequence() || cm.isNumber() || cm.isBoolean())
372         return cm;
373      if (depth > getMaxDepth())
374         return null;
375      if (isDetectRecursions() || isDebug()) {
376         if (willRecurse(attrName, o, cm))
377            return null;
378         isBottom = false;
379         stack.add(new StackElement(stack.size(), attrName, o, cm));
380         set.put(o, o);
381      }
382      return cm;
383   }
384
385   /**
386    * Sets the current class being traversed for proper error messages.
387    *
388    * @param currentClass The current class being traversed.
389    */
390   protected final void setCurrentClass(ClassMeta<?> currentClass) { this.currentClass = currentClass; }
391
392   /**
393    * Sets the current bean property being traversed for proper error messages.
394    *
395    * @param currentProperty The current property being traversed.
396    */
397   protected final void setCurrentProperty(BeanPropertyMeta currentProperty) { this.currentProperty = currentProperty; }
398
399   /**
400    * Returns <jk>true</jk> if we're about to exceed the max depth for the document.
401    *
402    * <p>
403    * When this returns <jk>true</jk>, the over-depth value is dropped (truncated) from the output rather than
404    * causing an exception to be thrown — exceeding {@link #getMaxDepth() maxDepth} is a size guard, not a cycle detector.
405    *
406    * @return <jk>true</jk> if we're about to exceed the max depth for the document.
407    */
408   protected final boolean willExceedDepth() {
409      return (depth >= getMaxDepth());
410   }
411
412   /**
413    * Returns <jk>true</jk> if {@link MarshallingTraverseContext.Builder#detectRecursions()} is enabled, and the specified
414    * object is already higher up in the traversal chain.
415    *
416    * @param attrName The bean property attribute name, or some other identifier.
417    * @param o The object to check for recursion.
418    * @param cm The metadata on the object class.
419    * @return <jk>true</jk> if recursion detected.
420    * @throws MarshallingRecursionException If recursion occurred.
421    */
422   protected final boolean willRecurse(String attrName, Object o, ClassMeta<?> cm) throws MarshallingRecursionException {
423      if (! (isDetectRecursions() || isDebug()) || ! set.containsKey(o))
424         return false;
425      if (isIgnoreRecursions() && ! isDebug())
426         return true;
427
428      stack.add(new StackElement(stack.size(), attrName, o, cm));
429      throw new MarshallingRecursionException("Recursion occurred, stack=%s", getStack(true));
430   }
431}