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.serializer;
018
019import static org.apache.juneau.commons.reflect.ReflectionUtils.*;
020import static org.apache.juneau.commons.utils.ClassUtils.*;
021import static org.apache.juneau.commons.utils.CollectionUtils.*;
022import static org.apache.juneau.commons.utils.Shorts.*;
023import static org.apache.juneau.commons.utils.ThrowableUtils.*;
024
025import java.io.*;
026import java.lang.reflect.*;
027import java.math.*;
028import java.net.*;
029import java.time.*;
030import java.time.Duration;
031import java.time.temporal.*;
032import java.util.*;
033import java.util.function.*;
034import java.util.stream.*;
035
036import javax.xml.datatype.*;
037
038import org.apache.juneau.commons.bean.*;
039import org.apache.juneau.commons.collections.*;
040import org.apache.juneau.commons.function.*;
041import org.apache.juneau.commons.inject.*;
042import org.apache.juneau.commons.reflect.*;
043import org.apache.juneau.commons.svl.*;
044import org.apache.juneau.marshall.*;
045import org.apache.juneau.marshall.DateFormat;
046import org.apache.juneau.marshall.httppart.*;
047import org.apache.juneau.marshall.soap.*;
048import org.apache.juneau.marshall.swap.spi.*;
049import org.apache.juneau.marshall.utils.*;
050
051/**
052 * Serializer session that lives for the duration of a single use of {@link Serializer}.
053 *
054 * <p>
055 * Used by serializers for the following purposes:
056 * <ul class='spaced-list'>
057 *    <li>
058 *       Keeping track of how deep it is in a model for indentation purposes.
059 *    <li>
060 *       Ensuring infinite loops don't occur by setting a limit on how deep to traverse a model.
061 *    <li>
062 *       Ensuring infinite loops don't occur from loops in the model (when detectRecursions is enabled.
063 *    <li>
064 *       Allowing serializer properties to be overridden on method calls.
065 * </ul>
066 *
067 * <h5 class='section'>Notes:</h5><ul>
068 *    <li class='warn'>This class is not thread safe and is typically discarded after one use.
069 * </ul>
070 *
071 * <h5 class='section'>See Also:</h5><ul>
072 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SerializersAndParsers">Serializers and Parsers</a>
073
074 * </ul>
075 */
076@SuppressWarnings({
077   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
078   "java:S1452", // create() returning Builder<?> and getExpectedRootType()/push2() returning ClassMeta<?> intentionally expose wildcard types.
079   "java:S3776", // Branching is inherent to canIgnoreValue() and getBeanTypeName(), which check many null/empty/default combinations.
080   "java:S6539", // Monster class; SerializerSession is intentionally the core per-run serialization session base, aggregating traversal/formatting state
081   "rawtypes", // Raw types necessary for generic type handling throughout serializer session
082   "resource", // Internal helpers return Closeables wired into pipe lifecycle; Eclipse JDT @Owning warning is by design.
083   "unchecked" // Type erasure requires unchecked casts throughout serializer session
084})
085public class SerializerSession extends MarshallingTraverseSession {
086
087   /**
088    * Builder class.
089    */
090   @SuppressWarnings({
091      "java:S119" // 'SELF' (CRTP self-type) is intentional and clearer than a single-letter name.
092   })
093   public abstract static class Builder<SELF extends Builder<SELF>> extends MarshallingTraverseSession.Builder<SELF> {
094
095      private HttpPartSchema schema;
096      private Method javaMethod;
097      private final Serializer ctx;
098      private UriContext uriContext;
099      private VarResolverSession resolver;
100      private boolean keepNullProperties;
101      private boolean nonDefault;
102      private boolean trimStrings;
103      private boolean addBeanTypes;
104      private boolean addRootType;
105      private boolean sortCollections;
106      private boolean sortMaps;
107      private boolean trimEmptyCollections;
108      private boolean trimEmptyMaps;
109
110      /**
111       * Constructor
112       *
113       * @param ctx The context creating this session.
114       *    <br>Cannot be <jk>null</jk>.
115       */
116      protected Builder(Serializer ctx) {
117         super(reqnn("ctx", ctx));
118         this.ctx = ctx;
119         mediaTypeDefault(ctx.getResponseContentType());
120         uriContext = ctx.getUriContext();
121         keepNullProperties = ctx.isKeepNullProperties();
122         nonDefault = ctx.isNonDefault();
123         trimStrings = ctx.isTrimStrings();
124         addBeanTypes = ctx.isAddBeanTypes();
125         addRootType = ctx.isAddRootType();
126         sortCollections = ctx.isSortCollections();
127         sortMaps = ctx.isSortMaps();
128         trimEmptyCollections = ctx.isTrimEmptyCollections();
129         trimEmptyMaps = ctx.isTrimEmptyMaps();
130      }
131
132      @Override
133      public SerializerSession build() {
134         return new SerializerSession(this);
135      }
136
137      /**
138       * The java method that called this serializer, usually the method in a REST servlet.
139       *
140       * @param value
141       *    The new property value.
142       *    <br>Can be <jk>null</jk> (value will not be set, existing value will be kept).
143       * @return This object.
144       */
145      public SELF javaMethod(Method value) {
146         if (nn(value))
147            javaMethod = value;
148         return self();
149      }
150
151      @Override /* Overridden from Builder */
152      public SELF property(String key, Object value) {
153         if (key == null) {
154            super.property(key, value);
155            return self();
156         }
157         switch (key) {
158            case "javaMethod", "SerializerSession.javaMethod":
159               return javaMethod(cvt(value, Method.class));
160            case "resolver", "SerializerSession.resolver":
161               return resolver(cvt(value, VarResolverSession.class));
162            case "schema", "SerializerSession.schema":
163               return schema(cvt(value, HttpPartSchema.class));
164            case "uriContext", "SerializerSession.uriContext":
165               return uriContext(cvt(value, UriContext.class));
166            case "keepNullProperties", "SerializerSession.keepNullProperties":
167               return keepNullProperties(cvt(value, Boolean.class));
168            case "nonDefault", "SerializerSession.nonDefault":
169               return nonDefault(cvt(value, Boolean.class));
170            case "trimStrings", "SerializerSession.trimStrings":
171               return trimStrings(cvt(value, Boolean.class));
172            case "addBeanTypes", "SerializerSession.addBeanTypes":
173               return addBeanTypes(cvt(value, Boolean.class));
174            case "addRootType", "SerializerSession.addRootType":
175               return addRootType(cvt(value, Boolean.class));
176            case "sortCollections", "SerializerSession.sortCollections":
177               return sortCollections(cvt(value, Boolean.class));
178            case "sortMaps", "SerializerSession.sortMaps":
179               return sortMaps(cvt(value, Boolean.class));
180            case "trimEmptyCollections", "SerializerSession.trimEmptyCollections":
181               return trimEmptyCollections(cvt(value, Boolean.class));
182            case "trimEmptyMaps", "SerializerSession.trimEmptyMaps":
183               return trimEmptyMaps(cvt(value, Boolean.class));
184            default:
185               super.property(key, value);
186               return self();
187         }
188      }
189
190      /**
191       * String variable resolver.
192       *
193       * <p>
194       * If not specified, defaults to session created by {@link VarResolver#DEFAULT}.
195       *
196       * @param value
197       *    The new property value.
198       *    <br>Can be <jk>null</jk> (value will not be set, defaults to session created by {@link VarResolver#DEFAULT} when accessed).
199       * @return This object.
200       */
201      public SELF resolver(VarResolverSession value) {
202         if (nn(value))
203            resolver = value;
204         return self();
205      }
206
207      /**
208       * HTTP-part schema.
209       *
210       * <p>
211       * Used for schema-based serializers and parsers to define additional formatting.
212       *
213       * @param value
214       *    The new value for this property.
215       *    <br>Can be <jk>null</jk> (value will not be set, existing value will be kept).
216       * @return This object.
217       */
218      public SELF schema(HttpPartSchema value) {
219         if (nn(value))
220            schema = value;
221         return self();
222      }
223
224      /**
225       * Same as {@link #schema(HttpPartSchema)} but doesn't overwrite the value if it is already set.
226       *
227       * @param value
228       *    The new value for this property.
229       *    <br>Can be <jk>null</jk> (value will not be set).
230       * @return This object.
231       */
232      public SELF schemaDefault(HttpPartSchema value) {
233         if (nn(value) && schema == null)
234            schema = value;
235         return self();
236      }
237
238      /**
239       * URI context bean.
240       *
241       * <p>
242       * Bean used for resolution of URIs to absolute or root-relative form.
243       *
244       * <p>
245       * If not specified, defaults to {@link Serializer.Builder#uriContext(UriContext)}.
246       *
247       * @param value
248       *    The new property value.
249       *    <br>Can be <jk>null</jk> (value will not be set, defaults to {@link Serializer.Builder#uriContext(UriContext)} from context).
250       * @return This object.
251       */
252      public SELF uriContext(UriContext value) {
253         if (nn(value))
254            uriContext = value;
255         return self();
256      }
257
258      /**
259       * Keep null bean property values.
260       *
261       * @param value The new value for this property.
262       * @return This object.
263       */
264      public SELF keepNullProperties(boolean value) {
265         keepNullProperties = value;
266         return self();
267      }
268
269      /**
270       * Non-default inclusion.
271       *
272       * @param value The new value for this property.
273       * @return This object.
274       * @since 10.0.0
275       */
276      public SELF nonDefault(boolean value) {
277         nonDefault = value;
278         return self();
279      }
280
281      /**
282       * Trim strings.
283       *
284       * @param value The new value for this property.
285       * @return This object.
286       */
287      public SELF trimStrings(boolean value) {
288         trimStrings = value;
289         return self();
290      }
291
292      /**
293       * Add bean types.
294       *
295       * @param value The new value for this property.
296       * @return This object.
297       */
298      public SELF addBeanTypes(boolean value) {
299         addBeanTypes = value;
300         return self();
301      }
302
303      /**
304       * Add root type.
305       *
306       * @param value The new value for this property.
307       * @return This object.
308       */
309      public SELF addRootType(boolean value) {
310         addRootType = value;
311         return self();
312      }
313
314      /**
315       * Sort collections.
316       *
317       * @param value The new value for this property.
318       * @return This object.
319       */
320      public SELF sortCollections(boolean value) {
321         sortCollections = value;
322         return self();
323      }
324
325      /**
326       * Sort maps.
327       *
328       * @param value The new value for this property.
329       * @return This object.
330       */
331      public SELF sortMaps(boolean value) {
332         sortMaps = value;
333         return self();
334      }
335
336      /**
337       * Trim empty collections.
338       *
339       * @param value The new value for this property.
340       * @return This object.
341       */
342      public SELF trimEmptyCollections(boolean value) {
343         trimEmptyCollections = value;
344         return self();
345      }
346
347      /**
348       * Trim empty maps.
349       *
350       * @param value The new value for this property.
351       * @return This object.
352       */
353      public SELF trimEmptyMaps(boolean value) {
354         trimEmptyMaps = value;
355         return self();
356      }
357   }
358
359   /**
360    * Concrete default builder leaf for the non-subclassed {@code create()} path (CRTP terminal).
361    */
362   public static final class DefaultBuilder extends Builder<DefaultBuilder> {
363
364      DefaultBuilder(Serializer ctx) {
365         super(ctx);
366      }
367   }
368
369   /**
370    * Creates a new builder for this object.
371    *
372    * @param ctx The context creating this session.
373    *    <br>Cannot be <jk>null</jk>.
374    * @return A new builder.
375    */
376   public static Builder<?> create(Serializer ctx) {
377      return new DefaultBuilder(reqnn("ctx", ctx));
378   }
379
380   /**
381    * Create a "_type" property that contains the dictionary name of the bean.
382    *
383    * @param m The bean map to create a class property on.
384    *    <br>Must not be <jk>null</jk>.
385    * @param typeName The type name of the bean.
386    *    <br>Can be <jk>null</jk> (the created property's value is <jk>null</jk>).
387    * @return A new bean property value.
388    */
389   protected static final BeanPropertyValue createBeanTypeNameProperty(BeanMap<?> m, String typeName) {
390      BeanMeta<?> bm = m.getMeta();
391      return new BeanPropertyValue(bm.getTypeProperty(), bm.getTypeProperty().getName(), typeName, null);
392   }
393
394   /**
395    * Converts the specified throwable to either a {@link RuntimeException} or {@link SerializeException}.
396    *
397    * @param <T> The throwable type.
398    * @param causedBy The exception to cast or wrap.
399    *    <br>Must not be <jk>null</jk>.
400    */
401   protected static <T extends Throwable> void handleThrown(T causedBy) {
402      if (causedBy instanceof StackOverflowError)
403         throw new SerializeException(
404            "Stack overflow occurred.  This can occur when trying to serialize models containing loops.  It's recommended you use the MarshallingTraverseContext.BEANTRAVERSE_detectRecursions setting to help locate the loop.");
405      if (causedBy instanceof Error causedBy2)
406         throw causedBy2;
407      if (causedBy instanceof RuntimeException causedBy2)
408         throw causedBy2;
409      if (causedBy instanceof SerializeException causedBy2)
410         throw causedBy2;
411      throw new SerializeException(causedBy);
412   }
413
414   /**
415    * Converts the contents of the specified object array to a list.
416    *
417    * <p>
418    * Works on both object and primitive arrays.
419    *
420    * <p>
421    * In the case of multi-dimensional arrays, the outgoing list will contain elements of type n-1 dimension.
422    * i.e. if {@code type} is <code><jk>int</jk>[][]</code> then {@code list} will have entries of type
423    * <code><jk>int</jk>[]</code>.
424    *
425    * @param type The type of array.
426    *    <br>Must not be <jk>null</jk>.
427    * @param array The array being converted.
428    *    <br>Must not be <jk>null</jk>.
429    * @return The array as a list.
430    */
431   protected static final List<Object> toList(Class<?> type, Object array) {
432      var componentType = type.getComponentType();
433      if (componentType.isPrimitive()) {
434         var l = Array.getLength(array);
435         var list = new ArrayList<>(l);
436         for (var i = 0; i < l; i++)
437            list.add(Array.get(array, i));
438         return list;
439      }
440      return l((Object[])array);
441   }
442
443   private final HttpPartSchema schema;
444   private final Method javaMethod;                                                // Java method that invoked this serializer.
445   private final Serializer ctx;
446   private final SerializerListener listener;
447   private final UriResolver uriResolver;
448   private VarResolverSession vrs;
449   private final boolean keepNullProperties;
450   private final boolean nonDefault;
451   private final boolean trimStrings;
452   private final boolean addBeanTypes;
453   private final boolean addRootType;
454   private final boolean sortCollections;
455   private final boolean sortMaps;
456   private final boolean trimEmptyCollections;
457   private final boolean trimEmptyMaps;
458
459   /**
460    * Constructor.
461    *
462    * @param builder The builder for this object.
463    *    <br>Cannot be <jk>null</jk>.
464    */
465   protected SerializerSession(Builder<?> builder) {
466      super(builder);
467      ctx = builder.ctx;
468      javaMethod = builder.javaMethod;
469      schema = builder.schema;
470      UriContext uriContext = builder.uriContext;
471      uriResolver = UriResolver.of(ctx.getUriResolution(), ctx.getUriRelativity(), uriContext);
472      vrs = builder.resolver;
473      listener = BeanInstantiator.createOrNull(ctx.getListener());
474      keepNullProperties = builder.keepNullProperties;
475      nonDefault = builder.nonDefault;
476      trimStrings = builder.trimStrings;
477      addBeanTypes = builder.addBeanTypes;
478      addRootType = builder.addRootType;
479      sortCollections = builder.sortCollections;
480      sortMaps = builder.sortMaps;
481      trimEmptyCollections = builder.trimEmptyCollections;
482      trimEmptyMaps = builder.trimEmptyMaps;
483   }
484
485   /**
486    * Adds a session object to the {@link VarResolverSession} in this session.
487    *
488    * @param <T> The bean type.
489    * @param c The bean type being added.
490    * @param value The bean being added.
491    * @return This object.
492    */
493   public <T> SerializerSession addVarBean(Class<T> c, T value) {
494      getVarResolver().bean(c, value);
495      return this;
496   }
497
498   /**
499    * Returns <jk>true</jk> if the specified value should not be serialized.
500    *
501    * <p>
502    * A value is also omitted (dropped) when it would exceed {@link #getMaxDepth() maxDepth} (via
503    * {@link #willExceedDepth()}) — over-depth values are silently truncated from the output rather than throwing.
504    * Exceeding <c>maxDepth</c> is a size guard, not a cycle detector; when
505    * {@link org.apache.juneau.marshall.MarshallingTraverseContext.Builder#detectRecursions() detectRecursions} is enabled, an actual recursion
506    * instead throws a {@link SerializeException}.
507    *
508    * @param cm The class type of the object being serialized.
509    *    <br>Can be <jk>null</jk> (treated as the {@code Object} type).
510    * @param attrName The bean attribute name, or <jk>null</jk> if this isn't a bean attribute.
511    * @param value The object being serialized.
512    *    <br>Can be <jk>null</jk>, in which case the result depends on {@link Serializer.Builder#keepNullProperties() keepNullProperties} (ignored unless that setting is enabled).
513    * @return <jk>true</jk> if the specified value should not be serialized.
514    * @throws SerializeException If recursion occurred.
515    */
516   public final boolean canIgnoreValue(ClassMeta<?> cm, String attrName, Object value) throws SerializeException {
517
518      if (value == null && ! isKeepNullProperties())
519         return true;
520
521      if (value == null)
522         return false;
523
524      if (cm == null)
525         cm = object();
526
527      if (isTrimEmptyCollections()) {
528         if ((cm.isArray() || (cm.isObject() && isArray(value))) && ((Object[])value).length == 0)
529            return true;
530         if ((cm.isCollection() || (cm.isObject() && info(value).isAssignableTo(Collection.class))) && ((Collection<?>)value).isEmpty())
531            return true;
532      }
533
534      if (isTrimEmptyMaps() && (cm.isMap() || (cm.isObject() && info(value).isAssignableTo(Map.class))) && ((Map<?,?>)value).isEmpty())
535         return true;
536
537      try {
538         if ((! isKeepNullProperties()) && (willRecurse(attrName, value, cm) || willExceedDepth()))
539            return true;
540      } catch (MarshallingRecursionException e) {
541         throw new SerializeException(e);
542      }
543
544      return false;
545   }
546
547   /**
548    * Per-bean-property variant of {@link #canIgnoreValue(ClassMeta, String, Object)}.
549    *
550    * <p>
551    * Applies the same standard inclusion rules ({@link Serializer.Builder#keepNullProperties() keepNullProperties},
552    * {@link Serializer.Builder#trimEmptyCollections() trimEmptyCollections} /
553    * {@link Serializer.Builder#trimEmptyMaps() trimEmptyMaps},
554    * recursion detection), then additionally:
555    * <ul>
556    *    <li>Treats {@link Optional#empty()} (and its primitive cousins
557    *       {@link OptionalInt}/{@link OptionalLong}/{@link OptionalDouble}) as a
558    *       <jk>null</jk> property value for inclusion purposes — the shared Optional/absent contract.
559    *    <li>Applies {@link Serializer.Builder#nonDefault() nonDefault} by comparing the surviving value against a
560    *       cached <i>reference instance</i> of the property's containing bean.  When no reference instance can be
561    *       built (no accessible no-arg ctor / instantiation throws), {@code nonDefault} is silently skipped for
562    *       that bean — the property is emitted normally rather than failing.
563    * </ul>
564    *
565    * <p>
566    * Precedence (first "omit" wins): (1) <jk>null</jk> / empty Optional, (2) trimEmpty*, (3) value-equals-default.
567    *
568    * @param pMeta The bean property meta for the value being considered.  Must not be <jk>null</jk>.
569    * @param attrName The bean attribute name.  Must not be <jk>null</jk>.
570    * @param value The current property value.
571    *    <br>Can be <jk>null</jk> (treated as an absent value; whether it's omitted depends on {@link Serializer.Builder#keepNullProperties() keepNullProperties}).
572    * @return <jk>true</jk> if the property should be omitted from the output.
573    * @throws SerializeException If recursion occurred.
574    * @since 10.0.0
575    */
576   public final boolean canIgnoreValue(BeanPropertyMeta pMeta, String attrName, Object value) throws SerializeException {
577      var cMeta = (ClassMeta<?>) pMeta.getBeanInfo();
578      // Resolve Optional<empty> / OptionalInt/Long/Double.isPresent()==false to null for the inclusion check.
579      var resolved = unwrapEmptyOptional(value);
580      if (canIgnoreValue(cMeta, attrName, resolved))
581         return true;
582      if (isNonDefault() && resolved != null)
583         return equalsBeanPropertyDefault(pMeta, attrName, resolved);
584      return false;
585   }
586
587   /**
588    * Resolves an {@link Optional#empty()} (or the empty form of an {@link OptionalInt}/{@link OptionalLong}/
589    * {@link OptionalDouble}) to <jk>null</jk>, leaving all other values unchanged.
590    *
591    * <p>
592    * Nested {@code Optional<Optional<X>>} values are unwrapped recursively.
593    *
594    * @param value The value to resolve.
595    * @return The resolved value (may be <jk>null</jk>).
596    */
597   private static Object unwrapEmptyOptional(Object value) {
598      if (value == null)
599         return null;
600      if (value instanceof Optional<?> value2)
601         return value2.isEmpty() ? null : unwrapEmptyOptional(value2.orElse(null));
602      if (value instanceof OptionalInt value2)
603         return value2.isEmpty() ? null : Integer.valueOf(value2.getAsInt());
604      if (value instanceof OptionalLong value2)
605         return value2.isEmpty() ? null : Long.valueOf(value2.getAsLong());
606      if (value instanceof OptionalDouble value2)
607         return value2.isEmpty() ? null : Double.valueOf(value2.getAsDouble());
608      return value;
609   }
610
611   /**
612    * Returns <jk>true</jk> when {@code value} equals the property's bean-constructed default per the
613    * {@code nonDefault} contract.
614    *
615    * <p>
616    * Resolves (and caches per-{@link ClassMeta}) the containing bean's reference instance.  Returns
617    * <jk>false</jk> when no reference instance can be built (which signals "skip nonDefault for this bean" per
618    * the documented contract).
619    *
620    * @param pMeta The bean-property meta.
621    * @param attrName The bean attribute name.
622    * @param value The surviving property value (non-null).
623    * @return <jk>true</jk> if the value equals the property's default.
624    */
625   private boolean equalsBeanPropertyDefault(BeanPropertyMeta pMeta, String attrName, Object value) {
626      var owning = pMeta.getBeanMeta();
627      if (owning == null)
628         return false;
629      var ownerClass = owning.getClassInfo();
630      if (ownerClass == null)
631         return false;
632      var ownerMeta = getClassMeta(ownerClass.inner());
633      var refValueHolder = ownerMeta.getProperty(NON_DEFAULT_PROPERTY_CACHE_KEY, cm -> {
634         var instance = BeanInstantiator.createOrNull(cm.inner());
635         return instance == null ? NO_REFERENCE_INSTANCE : instance;
636      });
637      var ref = refValueHolder.orElse(null);
638      if (ref == null || ref == NO_REFERENCE_INSTANCE)
639         return false;
640      // Resolve the default value via the bean's accessor surface so it composes with @MarshalledProp
641      // transforms.  Wrap any throw in a guard — a bean whose getter throws on a fresh instance is just
642      // treated as "no default available", which is safer than aborting the serialize.
643      Object defaultValue;
644      try {
645         defaultValue = pMeta.get(toBeanMap(ref), attrName);
646      } catch (RuntimeException e) {  // HTT — defensive: getter on reference instance throws.
647         return false;
648      }
649      var resolvedDefault = unwrapEmptyOptional(defaultValue);
650      return defaultEquals(value, resolvedDefault);
651   }
652
653   /**
654    * {@code Objects.equals} with a numeric-value shortcut so {@code 1.0} equals {@code 1},
655    * {@code BigDecimal("1.0")} equals {@code BigDecimal("1")}, and {@code 0.0}/{@code -0.0} equal each other.
656    *
657    * @param a First value.
658    * @param b Second value.
659    * @return <jk>true</jk> if the values are equal under the {@code nonDefault} contract.
660    */
661   @SuppressWarnings({
662      "java:S1244" // Float equality is intentional: caller has already narrowed to numeric short-circuit; BigDecimal compareTo handles the value-equality semantics.
663   })
664   private static boolean defaultEquals(Object a, Object b) {
665      if (a == b)
666         return true;
667      if (a == null || b == null)
668         return false;
669      if (a instanceof Number an && b instanceof Number bn) {
670         // Treat NaN as equal to NaN (Objects.equals semantics) so two NaN-typed defaults match.
671         var anan = (an instanceof Double d && Double.isNaN(d)) || (an instanceof Float f && Float.isNaN(f));
672         var bnan = (bn instanceof Double d && Double.isNaN(d)) || (bn instanceof Float f && Float.isNaN(f));
673         if (anan || bnan)
674            return anan && bnan;
675         try {
676            return toBigDecimal(an).compareTo(toBigDecimal(bn)) == 0;
677         } catch (NumberFormatException e) {  // HTT — defensive: a Number subclass whose toString() isn't a valid decimal.
678            return eq(a, b);
679         }
680      }
681      return eq(a, b);
682   }
683
684   private static BigDecimal toBigDecimal(Number n) {
685      if (n instanceof BigDecimal n2)
686         return n2;
687      if (n instanceof BigInteger n2)
688         return new BigDecimal(n2);
689      if (n instanceof Float || n instanceof Double)
690         return BigDecimal.valueOf(n.doubleValue());
691      return BigDecimal.valueOf(n.longValue());
692   }
693
694   /** Cache key under which the per-{@link ClassMeta} reference instance is memoized for {@code nonDefault}. */
695   private static final String NON_DEFAULT_PROPERTY_CACHE_KEY = "SerializerSession.nonDefault.referenceInstance";
696   /** Sentinel stored when a reference instance could not be built (cached so we don't retry on every call). */
697   private static final Object NO_REFERENCE_INSTANCE = new Object();
698
699   /**
700    * Consumes each entry in the list.
701    *
702    * @param <E> The element type.
703    * @param c The collection being sorted.
704    * @param consumer The entry consumer.
705    */
706   public final <E> void forEachEntry(Collection<E> c, Consumer<E> consumer) {
707      if (isEmpty(c))
708         return;
709      if (isSortCollections() && !(c instanceof SortedSet) && isSortable(c))
710         c.stream().sorted().forEach(consumer);
711      else
712         c.forEach(consumer);
713   }
714
715   /**
716    * Consumes each map entry in the map.
717    *
718    * @param <K> The key type.
719    * @param <V> The value type.
720    * @param m The map being consumed.
721    * @param consumer The map entry consumer.
722    */
723   @SuppressWarnings({
724      "cast" // Cast required for generic type safety
725   })
726   public final <K,V> void forEachEntry(Map<K,V> m, Consumer<Map.Entry<K,V>> consumer) {
727      if (isEmpty(m))
728         return;
729      if (isSortMaps() && !(m instanceof SortedMap) && isSortable(m.keySet()))
730         ((Map)m).entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(x -> consumer.accept((Map.Entry<K,V>)x));
731      else
732         m.entrySet().forEach(consumer);
733   }
734
735   /**
736    * Iterates over a streamable object (Iterator, Iterable, or Stream), consuming each entry.
737    *
738    * <p>
739    * For collections, delegates to {@link #forEachEntry(Collection, Consumer)} which supports sorting.
740    * For other streamable types, iterates lazily without materializing into a List.
741    *
742    * @param o The streamable object.
743    * @param type The class meta for the object.
744    * @param consumer The entry consumer.
745    * @since 9.2.1
746    */
747   public final void forEachStreamableEntry(Object o, ClassMeta<?> type, Consumer consumer) {
748      if (o == null)
749         return;
750      if (type.isCollection()) {
751         forEachEntry((Collection)o, consumer);
752      } else if (type.isIterable()) {
753         if (o instanceof BeanSupplier o2) {
754            try {
755               drainBeanSupplier(o2, consumer);
756            } catch (RuntimeException e) {
757               throw e;
758            } catch (Exception e) {
759               throw new BasicRuntimeException(e);
760            }
761         } else {
762            ((Iterable)o).forEach(consumer);
763         }
764      } else if (type.isIterator()) {
765         if (o instanceof Enumeration o2)
766            o2.asIterator().forEachRemaining(consumer);
767         else
768            ((Iterator)o).forEachRemaining(consumer);
769      } else if (type.isStream()) {
770         try (var stream = (Stream)o) {
771            stream.forEach(consumer);
772         }
773      }
774   }
775
776   private static void drainBeanSupplier(BeanSupplier bs, Consumer consumer) throws Exception {
777      bs.begin();
778      try {
779         bs.iterator().forEachRemaining(consumer);
780      } catch (Exception e) {
781         bs.onError(e);
782      } finally {
783         bs.complete();
784      }
785   }
786
787   /**
788    * Converts a streamable object (Iterator, Iterable, or Stream) to a List.
789    *
790    * <p>
791    * Used by serializers that need to know the collection size or inspect elements before serializing
792    * (e.g. MsgPack, HTML, CSV).
793    *
794    * @param o The streamable object.
795    * @param type The class meta for the object.
796    * @return A new list containing all elements.
797    * @since 9.2.1
798    */
799   public final List<Object> toListFromStreamable(Object o, ClassMeta<?> type) {
800      var list = l();
801      forEachStreamableEntry(o, type, list::add);
802      return list;
803   }
804
805   /**
806    * Returns the listener associated with this session.
807    *
808    * @return The listener associated with this session, or <jk>null</jk> if there is no listener.
809    */
810   public SerializerListener getListener() { return listener; }
811
812   /**
813    * Returns the listener associated with this session.
814    *
815    * @param <T> The listener type.
816    * @param c The listener class to cast to.
817    * @return The listener associated with this session, or <jk>null</jk> if there is no listener.
818    */
819   public <T extends SerializerListener> T getListener(Class<T> c) {
820      return (T)listener;
821   }
822
823   /**
824    * Optional method that specifies HTTP request headers for this serializer.
825    *
826    * <p>
827    * For example, {@link SoapXmlSerializer} needs to set a <c>SOAPAction</c> header.
828    *
829    * <p>
830    * This method is typically meaningless if the serializer is being used stand-alone (i.e. outside of a REST server
831    * or client).
832    *
833    * <p>
834    * The default implementation of this method simply calls {@link Serializer#getResponseHeaders(SerializerSession)}.
835    *
836    * @return
837    *    The HTTP headers to set on HTTP requests.
838    *    Never <jk>null</jk>.
839    */
840   public Map<String,String> getResponseHeaders() { return ctx.getResponseHeaders(this); }
841
842   /**
843    * HTTP part schema of object being serialized.
844    *
845    * @return HTTP part schema of object being serialized, or <jk>null</jk> if not specified.
846    */
847   public final HttpPartSchema getSchema() { return schema; }
848
849   /**
850    * Returns the variable resolver session.
851    *
852    * @return The variable resolver session.
853    */
854   public VarResolverSession getVarResolver() {
855      if (vrs == null)
856         vrs = createDefaultVarResolverSession();
857      return vrs;
858   }
859
860   /**
861    * Returns <jk>true</jk> if this serializer subclasses from {@link WriterSerializer}.
862    *
863    * @return <jk>true</jk> if this serializer subclasses from {@link WriterSerializer}.
864    */
865   public boolean isWriterSerializer() { return false; }
866
867   /**
868    * Resolves any variables in the specified string.
869    *
870    * @param string The string to resolve values in.
871    * @return The string with variables resolved.
872    */
873   public String resolve(String string) {
874      return getVarResolver().resolve(string);
875   }
876
877   /**
878    * Converts a String to an absolute URI based on the {@link UriContext} on this session.
879    *
880    * @param uri
881    *    The input URI.
882    *    Can be any of the following:
883    *    <ul>
884    *       <li>{@link URI}
885    *       <li>{@link URL}
886    *       <li>{@link CharSequence}
887    *    </ul>
888    *    URI can be any of the following forms:
889    *    <ul>
890    *       <li><js>"foo://foo"</js> - Absolute URI.
891    *       <li><js>"/foo"</js> - Root-relative URI.
892    *       <li><js>"/"</js> - Root URI.
893    *       <li><js>"context:/foo"</js> - Context-root-relative URI.
894    *       <li><js>"context:/"</js> - Context-root URI.
895    *       <li><js>"servlet:/foo"</js> - Servlet-path-relative URI.
896    *       <li><js>"servlet:/"</js> - Servlet-path URI.
897    *       <li><js>"request:/foo"</js> - Request-path-relative URI.
898    *       <li><js>"request:/"</js> - Request-path URI.
899    *       <li><js>"foo"</js> - Path-info-relative URI.
900    *       <li><js>""</js> - Path-info URI.
901    *    </ul>
902    * @return The resolved URI.
903    */
904   public final String resolveUri(Object uri) {
905      return uriResolver.resolve(uri);
906   }
907
908   /**
909    * Shortcut method for serializing objects directly to either a <c>String</c> or <code><jk>byte</jk>[]</code>
910    * depending on the serializer type.
911    *
912    * @param o The object to serialize.
913    * @return
914    *    The serialized object.
915    *    <br>Character-based serializers will return a <c>String</c>.
916    *    <br>Stream-based serializers will return a <code><jk>byte</jk>[]</code>.
917    * @throws SerializeException If a problem occurred trying to convert the output.
918    */
919   public Object write(Object o) throws SerializeException {
920      throw uoex();
921   }
922
923   /**
924    * Serialize the specified object using the specified session.
925    *
926    * @param out Where to send the output from the serializer.
927    * @param o The object to serialize.
928    * @throws SerializeException If a problem occurred trying to convert the output.
929    * @throws IOException Thrown by the underlying stream.
930    */
931   public final void write(Object o, Object out) throws SerializeException, IOException {
932      try (SerializerPipe pipe = createPipe(out)) {
933         var unwrapped = unwrapSupplier(o, 0);
934         if (unwrapped instanceof BeanConsumer && !(unwrapped instanceof BeanChannel))
935            throw new SerializeException(this,
936               "BeanConsumer cannot be used as a serializer source. Use BeanSupplier or BeanChannel for round-trip support.");
937         doWrite(pipe, unwrapped);
938      } catch (SerializeException | IOException e) {
939         throw e;
940      } catch (@SuppressWarnings("unused") StackOverflowError e) {
941         throw new SerializeException(this,
942            "Stack overflow occurred.  This can occur when trying to serialize models containing loops.  It's recommended you use the MarshallingTraverseContext.BEANTRAVERSE_detectRecursions setting to help locate the loop.");
943      } catch (Exception e) {
944         throw new SerializeException(this, e);
945      } finally {
946         checkForWarnings();
947      }
948   }
949
950   /**
951    * Recursively unwraps nested {@link Supplier} chains to their underlying value.
952    *
953    * <p>
954    * Supports chains of the form {@code Supplier<Supplier<T>>} → {@code T} with a maximum depth of 10
955    * to prevent infinite loops. Note that {@link BeanSupplier} instances are NOT unwrapped here —
956    * they are treated as {@link Iterable} sequences by the serializer.
957    *
958    * @param o The object to unwrap.
959    * @param depth The current recursion depth (must be 0 on initial call).
960    * @return The unwrapped value, or the original object if it is not a {@code Supplier}.
961    * @throws SerializeException If the {@link Supplier} chain exceeds 10 levels.
962    */
963   protected final Object unwrapSupplier(Object o, int depth) throws SerializeException {
964      if (! (o instanceof Supplier<?>) || o instanceof BeanSupplier)
965         return o;
966      if (depth > 10)
967         throw new SerializeException(this, "Supplier chain exceeds maximum unwrap depth of 10.");
968      return unwrapSupplier(((Supplier<?>)o).get(), depth + 1);
969   }
970
971   /**
972    * Shortcut method for serializing an object to a String.
973    *
974    * @param o The object to serialize.
975    * @return
976    *    The serialized object.
977    *    <br>Character-based serializers will return a <c>String</c>
978    *    <br>Stream-based serializers will return a <code><jk>byte</jk>[]</code> converted to a string based on the {@link OutputStreamSerializer.Builder#binaryFormat(BinaryFormat)} setting.
979    * @throws SerializeException If a problem occurred trying to convert the output.
980    */
981   public String writeToString(Object o) throws SerializeException {
982      throw uoex();
983   }
984
985   /**
986    * Sorts the specified collection if {@link SerializerSession#isSortCollections()} returns <jk>true</jk>.
987    *
988    * @param <E> The element type.
989    * @param c The collection being sorted.
990    *    <br>Can be <jk>null</jk> (returns <jk>null</jk> unchanged).
991    * @return The sorted collection, or the input collection unchanged if sorting is disabled or the collection isn't sortable.
992    */
993   public final <E> Collection<E> sort(Collection<E> c) {
994      if (isEmpty(c) || c instanceof SortedSet)
995         return c;
996      if (isSortCollections() && isSortable(c))
997         return c.stream().sorted().toList();
998      return c;
999   }
1000
1001   /**
1002    * Sorts the specified collection if {@link SerializerSession#isSortCollections()} returns <jk>true</jk>.
1003    *
1004    * @param <E> The element type.
1005    * @param c The collection being sorted.
1006    *    <br>Can be <jk>null</jk> (returns <jk>null</jk> unchanged).
1007    * @return The sorted list, or the input list unchanged if sorting is disabled or the list isn't sortable.
1008    */
1009   public final <E> List<E> sort(List<E> c) {
1010      if (isEmpty(c))
1011         return c;
1012      if (isSortCollections() && isSortable(c))
1013         return c.stream().sorted().toList();
1014      return c;
1015   }
1016
1017   /**
1018    * Sorts the specified map if {@link SerializerSession#isSortMaps()} returns <jk>true</jk>.
1019    *
1020    * @param <K> The key type.
1021    * @param <V> The value type.
1022    * @param m The map being sorted.
1023    *    <br>Can be <jk>null</jk> (returns <jk>null</jk> unchanged).
1024    * @return A new sorted {@link TreeMap}, or the input map unchanged if sorting is disabled or the keys aren't sortable.
1025    */
1026   public final <K,V> Map<K,V> sort(Map<K,V> m) {
1027      if (isEmpty(m) || m instanceof SortedMap)
1028         return m;
1029      if (isSortMaps() && isSortable(m.keySet()))
1030         return new TreeMap<>(m);
1031      return m;
1032   }
1033
1034   /**
1035    * Converts the specified object to a <c>String</c>.
1036    *
1037    * <p>
1038    * Also has the following effects:
1039    * <ul>
1040    *    <li><c>Class</c> object is converted to a readable name.  See {@link ClassInfo#getNameFull()}.
1041    *    <li>Whitespace is trimmed if the trim-strings setting is enabled.
1042    * </ul>
1043    *
1044    * @param o The object to convert to a <c>String</c>.
1045    * @return The object converted to a String, or <jk>null</jk> if the input was <jk>null</jk>.
1046    */
1047   public final String toString(Object o) {
1048      if (o == null)
1049         return null;
1050      if (o.getClass() == Class.class)
1051         return info((Class<?>)o).getNameFull();
1052      if (o.getClass() == ClassInfo.class)
1053         return ((ClassInfo)o).getNameFull();
1054      if (o.getClass().isEnum())
1055         return getClassMetaForObject(o).toString(o);
1056      var cm = getClassMetaForObject(o);
1057      if (cm.isDate())
1058         return writeDate((Date)o, cm);
1059      if (cm.isCalendar())
1060         return writeCalendar(o, cm);
1061      if (cm.isTemporal())
1062         return writeTemporal((TemporalAccessor)o, cm);
1063      if (cm.isDuration())
1064         return writeDuration((Duration)o);
1065      if (cm.isPeriod())
1066         return writePeriod((Period)o);
1067      var s = o.toString();
1068      if (isTrimStrings())
1069         s = s.trim();
1070      return s;
1071   }
1072
1073   /**
1074    * Formats a {@link Duration} value using this session's configured
1075    * {@linkplain #getDurationFormat() duration format}.
1076    *
1077    * @param d The value to format. <jk>null</jk> returns <jk>null</jk>.
1078    * @return The formatted value, or <jk>null</jk> if {@code d} is <jk>null</jk>.
1079    */
1080   protected final String writeDuration(Duration d) {
1081      return Iso8601Utils.formatDuration(d, getDurationFormat());
1082   }
1083
1084   /**
1085    * Formats a {@link Period} value using this session's configured
1086    * {@linkplain #getPeriodFormat() period format}.
1087    *
1088    * @param p The value to format. <jk>null</jk> returns <jk>null</jk>.
1089    * @return The formatted value, or <jk>null</jk> if {@code p} is <jk>null</jk>.
1090    */
1091   protected final String writePeriod(Period p) {
1092      return Iso8601Utils.formatPeriod(p, getPeriodFormat());
1093   }
1094
1095   /**
1096    * Formats a {@link Date} value using this session's configured
1097    * {@linkplain #getDateFormat() date format} and {@linkplain #getTimeZone() time zone}.
1098    *
1099    * @param d The value to format. <jk>null</jk> returns <jk>null</jk>.
1100    * @param cm The class metadata for the value (kept for symmetry with {@link Iso8601Utils#formatDate}).
1101    * @return The formatted value, or <jk>null</jk> if {@code d} is <jk>null</jk>.
1102    */
1103   protected final String writeDate(Date d, ClassMeta<?> cm) {
1104      return Iso8601Utils.formatDate(d, cm, getDateFormat(), getTimeZone());
1105   }
1106
1107   /**
1108    * Formats a {@link Calendar} or {@link XMLGregorianCalendar} value using this
1109    * session's configured {@linkplain #getCalendarFormat() calendar format} and
1110    * {@linkplain #getTimeZone() time zone}.
1111    *
1112    * <p>
1113    * {@link XMLGregorianCalendar} values always emit their
1114    * {@link XMLGregorianCalendar#toXMLFormat()} form regardless of the configured
1115    * format, mirroring the parse-side rule.
1116    *
1117    * @param c The value to format. Must be a {@link Calendar} or {@link XMLGregorianCalendar}.
1118    *    <jk>null</jk> returns <jk>null</jk>.
1119    * @param cm The class metadata for the value (kept for symmetry with {@link Iso8601Utils#formatCalendar}).
1120    * @return The formatted value, or <jk>null</jk> if {@code c} is <jk>null</jk>.
1121    */
1122   protected final String writeCalendar(Object c, ClassMeta<?> cm) {
1123      return Iso8601Utils.formatCalendar(c, cm, getCalendarFormat(), getTimeZone());
1124   }
1125
1126   /**
1127    * Formats a {@link TemporalAccessor} value using this session's configured
1128    * {@linkplain #getTemporalFormat() temporal format} and {@linkplain #getTimeZone() time zone}.
1129    *
1130    * @param t The value to format. Must be a {@link Temporal} subtype.
1131    *    <jk>null</jk> returns <jk>null</jk>.
1132    * @param cm The class metadata for the value (kept for symmetry with {@link Iso8601Utils#formatTemporal}).
1133    * @return The formatted value, or <jk>null</jk> if {@code t} is <jk>null</jk>.
1134    */
1135   protected final String writeTemporal(TemporalAccessor t, ClassMeta<?> cm) {
1136      return Iso8601Utils.formatTemporal(t, cm, getTemporalFormat(), getTimeZone());
1137   }
1138
1139   /**
1140    * Trims the specified string if {@link SerializerSession#isTrimStrings()} returns <jk>true</jk>.
1141    *
1142    * @param o The input string to trim.
1143    * @return The trimmed string, or <jk>null</jk> if the input was <jk>null</jk>.
1144    */
1145   public final String trim(Object o) {
1146      if (o == null)
1147         return null;
1148      var s = o.toString();
1149      if (isTrimStrings())
1150         s = s.trim();
1151      return s;
1152   }
1153
1154   private static boolean isSortable(Collection<?> c) {
1155      if (c == null)
1156         return false;
1157      for (var o : c)
1158         if (! (o instanceof Comparable))
1159            return false;
1160      return true;
1161   }
1162
1163   /**
1164    * Adds a session object to the {@link VarResolverSession} in this session.
1165    *
1166    * @return This object.
1167    */
1168   protected VarResolverSession createDefaultVarResolverSession() {
1169      return VarResolver.DEFAULT.createSession();
1170   }
1171
1172   /**
1173    * Wraps the specified output object into a {@link SerializerPipe} object so that it can be easily converted into
1174    * a stream or writer.
1175    *
1176    * @param output
1177    *    The output location.
1178    *    <br>For character-based serializers, this can be any of the following types:
1179    *    <ul>
1180    *       <li>{@link Writer}
1181    *       <li>{@link OutputStream} - Output will be written as UTF-8 encoded stream.
1182    *    </ul>
1183    *    <br>For byte-based serializers, this must be an {@link OutputStream}.
1184    * @return
1185    *    A new {@link SerializerPipe} wrapper around the specified output object.
1186    */
1187   protected SerializerPipe createPipe(Object output) {
1188      return new SerializerPipe(output);
1189   }
1190
1191   /**
1192    * Serializes a POJO to the specified pipe.
1193    *
1194    * <p>
1195    * This method should NOT close the context object.
1196    *
1197    * <p>
1198    * The default implementation of this method simply calls {@link Serializer#doWrite(SerializerSession,SerializerPipe,Object)}.
1199    *
1200    * @param pipe Where to send the output from the serializer.
1201    * @param o The object to serialize.
1202    * @throws IOException Thrown by underlying stream.
1203    * @throws SerializeException Problem occurred trying to serialize object.
1204    */
1205   protected void doWrite(SerializerPipe pipe, Object o) throws IOException, SerializeException {
1206      ctx.doWrite(this, pipe, o);
1207   }
1208
1209   /**
1210    * Generalize the specified object if a POJO swap is associated with it.
1211    *
1212    * @param o The object to generalize.
1213    *    <br>Can be <jk>null</jk> (returns <jk>null</jk>).
1214    * @param type The type of object.
1215    *    <br>Can be <jk>null</jk> (the object's actual runtime class's swap, if any, is used instead).
1216    * @return The generalized object, or <jk>null</jk> if the object is <jk>null</jk>.
1217    * @throws SerializeException If a problem occurred trying to convert the output.
1218    */
1219   protected final Object generalize(Object o, ClassMeta<?> type) throws SerializeException {
1220      try {
1221         if (o == null)
1222            return null;
1223         ObjectSwap f = (type == null || type.isObject() || type.isString() ? getClassMeta(o.getClass()).getSwap(this) : type.getSwap(this));
1224         if (f == null)
1225            return o;
1226         return f.swap(this, o);
1227      } catch (SerializeException e) {
1228         throw e;
1229      } catch (Exception e) {
1230         throw new SerializeException(e);
1231      }
1232   }
1233
1234   /**
1235    * Resolves the dictionary name for the actual type.
1236    *
1237    * @param session The current serializer session.
1238    * @param eType The expected type of the bean property.
1239    * @param aType The actual type of the bean property.
1240    * @param pMeta The current bean property being serialized.
1241    *    <br>Can be <jk>null</jk> (the bean-property-level dictionary lookup tier is skipped).
1242    * @return The bean dictionary name, or <jk>null</jk> if a name could not be found.
1243    */
1244   @SuppressWarnings({
1245      "null" // Null analysis handled by runtime checks
1246   })
1247   protected final String getBeanTypeName(SerializerSession session, ClassMeta<?> eType, ClassMeta<?> aType, BeanPropertyMeta pMeta) {
1248      if (eType == aType || ! (isAddBeanTypes() || (session.isRoot() && isAddRootType())))
1249         return null;
1250
1251      String eTypeTn = eType.getBeanDictionaryName();
1252
1253      // First see if it's defined on the actual type.
1254      String tn = aType.getBeanDictionaryName();
1255      if (nn(tn) && ! tn.equals(eTypeTn)) {
1256         return tn;
1257      }
1258
1259      // Then see if it's defined on the expected type.
1260      // The expected type might be an interface with mappings for implementation classes.
1261      BeanRegistry br = eType.getBeanRegistry();
1262      if (nn(br)) {
1263         tn = br.getTypeName(aType);
1264         if (nn(tn) && ! tn.equals(eTypeTn))
1265            return tn;
1266      }
1267
1268      // Then look on the bean property.
1269      br = pMeta == null ? null : (BeanRegistry) pMeta.getBeanRegistry();
1270      if (nn(br)) {
1271         tn = br.getTypeName(aType);
1272         if (nn(tn) && ! tn.equals(eTypeTn))
1273            return tn;
1274      }
1275
1276      // Finally look in the session.
1277      br = getBeanRegistry();
1278      if (nn(br)) {
1279         tn = br.getTypeName(aType);
1280         if (nn(tn) && ! tn.equals(eTypeTn))
1281            return tn;
1282      }
1283
1284      return null;
1285   }
1286
1287   /**
1288    * Returns the parser-side expected type for the object.
1289    *
1290    * <p>
1291    * The return value depends on the {@link Serializer.Builder#addRootType()} setting.
1292    * When disabled, the parser already knows the Java POJO type being parsed, so there is
1293    * no reason to add <js>"_type"</js> attributes to the root-level object.
1294    *
1295    * @param o The object to get the expected type on.
1296    *    <br>Can be <jk>null</jk> (returns <jk>null</jk> unless root typing is enabled, in which case the generic {@link Object} type is returned).
1297    * @return The expected type, or <jk>null</jk> if the object was <jk>null</jk> and root typing is disabled.
1298    */
1299   protected final ClassMeta<?> getExpectedRootType(Object o) {
1300      if (isAddRootType())
1301         return object();
1302      var cm = getClassMetaForObject(o);
1303      if (nn(cm) && cm.isOptional())
1304         return cm.getElementType();
1305      return cm;
1306   }
1307
1308   /**
1309    * Returns the Java method that invoked this serializer.
1310    *
1311    * <p>
1312    * When using the REST API, this is the Java method invoked by the REST call.
1313    * Can be used to access annotations defined on the method or class.
1314    *
1315    * @return The Java method that invoked this serializer, or <jk>null</jk> if not specified.
1316   */
1317   protected final Method getJavaMethod() { return javaMethod; }
1318
1319   /**
1320    * URI context bean.
1321    *
1322    * @see Serializer.Builder#uriContext(UriContext)
1323    * @return
1324    *    Bean used for resolution of URIs to absolute or root-relative form.
1325    */
1326   protected final UriContext getUriContext() { return ctx.getUriContext(); }
1327
1328   /**
1329    * URI relativity.
1330    *
1331    * @see Serializer.Builder#uriRelativity(UriRelativity)
1332    * @return
1333    *    Defines what relative URIs are relative to when serializing any of the following:
1334    */
1335   protected final UriRelativity getUriRelativity() { return ctx.getUriRelativity(); }
1336
1337   /**
1338    * URI resolution.
1339    *
1340    * @see Serializer.Builder#uriResolution(UriResolution)
1341    * @return
1342    *    Defines the resolution level for URIs when serializing URIs.
1343    */
1344   protected final UriResolution getUriResolution() { return ctx.getUriResolution(); }
1345
1346   /**
1347    * Returns the configured default duration wire format.
1348    *
1349    * @return The configured default duration wire format.
1350    */
1351   protected final DurationFormat getDurationFormat() { return ctx.getMarshallingContext().getDurationFormat(); }
1352
1353   /**
1354    * Returns the configured default period wire format.
1355    *
1356    * @return The configured default period wire format.
1357    */
1358   protected final PeriodFormat getPeriodFormat() { return ctx.getMarshallingContext().getPeriodFormat(); }
1359
1360   /**
1361    * Returns the configured default calendar wire format.
1362    *
1363    * @return The configured default calendar wire format.
1364    */
1365   protected final CalendarFormat getCalendarFormat() { return ctx.getMarshallingContext().getCalendarFormat(); }
1366
1367   /**
1368    * Returns the configured default date wire format.
1369    *
1370    * @return The configured default date wire format.
1371    */
1372   protected final DateFormat getDateFormat() { return ctx.getMarshallingContext().getDateFormat(); }
1373
1374   /**
1375    * Returns the configured default temporal wire format.
1376    *
1377    * @return The configured default temporal wire format.
1378    */
1379   protected final TemporalFormat getTemporalFormat() { return ctx.getMarshallingContext().getTemporalFormat(); }
1380
1381   /**
1382    * Returns the configured default time-zone wire format.
1383    *
1384    * @return The configured default time-zone wire format.
1385    */
1386   protected final TimeZoneFormat getTimeZoneFormat() { return ctx.getMarshallingContext().getTimeZoneFormat(); }
1387
1388   /**
1389    * Returns the configured default locale wire format.
1390    *
1391    * @return The configured default locale wire format.
1392    */
1393   protected final LocaleFormat getLocaleFormat() { return ctx.getMarshallingContext().getLocaleFormat(); }
1394
1395   /**
1396    * Returns the URI resolver.
1397    *
1398    * @return The URI resolver.
1399    */
1400   protected final UriResolver getUriResolver() { return uriResolver; }
1401
1402   /**
1403    * Add <js>"_type"</js> properties when needed.
1404    *
1405    * @see Serializer.Builder#addBeanTypes()
1406    * @return
1407    *    <jk>true</jk> if <js>"_type"</js> properties added to beans if their type cannot be inferred
1408    *    through reflection.
1409    */
1410   protected boolean isAddBeanTypes() { return addBeanTypes; }
1411
1412   /**
1413    * Add type attribute to root nodes.
1414    *
1415    * @see Serializer.Builder#addRootType()
1416    * @return
1417    *    <jk>true</jk> if type property should be added to root node.
1418    */
1419   protected final boolean isAddRootType() { return addRootType; }
1420
1421   /**
1422    * Don't trim null bean property values.
1423    *
1424    * @see Serializer.Builder#keepNullProperties()
1425    * @return
1426    *    <jk>true</jk> if null bean values are serialized to the output.
1427    */
1428   protected final boolean isKeepNullProperties() { return keepNullProperties; }
1429
1430   /**
1431    * Don't serialize bean properties whose values equal the default for their type.
1432    *
1433    * @see Serializer.Builder#nonDefault()
1434    * @return
1435    *    <jk>true</jk> if bean properties whose value equals the type / bean-constructed default are omitted.
1436    * @since 10.0.0
1437    */
1438   protected final boolean isNonDefault() { return nonDefault; }
1439
1440   /**
1441    * Sort arrays and collections alphabetically.
1442    *
1443    * @see Serializer.Builder#sortCollections()
1444    * @return
1445    *    <jk>true</jk> if arrays and collections are copied and sorted before serialization.
1446    */
1447   protected final boolean isSortCollections() { return sortCollections; }
1448
1449   /**
1450    * Sort maps alphabetically.
1451    *
1452    * @see Serializer.Builder#sortMaps()
1453    * @return
1454    *    <jk>true</jk> if maps are copied and sorted before serialization.
1455    */
1456   protected final boolean isSortMaps() { return sortMaps; }
1457
1458   /**
1459    * Trim empty lists and arrays.
1460    *
1461    * @see Serializer.Builder#trimEmptyCollections()
1462    * @return
1463    *    <jk>true</jk> if empty lists and arrays are not serialized to the output.
1464    */
1465   protected final boolean isTrimEmptyCollections() { return trimEmptyCollections; }
1466
1467   /**
1468    * Trim empty maps.
1469    *
1470    * @see Serializer.Builder#trimEmptyMaps()
1471    * @return
1472    *    <jk>true</jk> if empty map values are not serialized to the output.
1473    */
1474   protected final boolean isTrimEmptyMaps() { return trimEmptyMaps; }
1475
1476   /**
1477    * Trim strings.
1478    *
1479    * @see Serializer.Builder#trimStrings()
1480    * @return
1481    *    <jk>true</jk> if string values will be trimmed of whitespace using {@link String#trim()} before being serialized.
1482    */
1483   protected boolean isTrimStrings() { return trimStrings; }
1484
1485   /**
1486    * Specialized warning when an exception is thrown while executing a bean getter.
1487    *
1488    * @param p The bean map entry representing the bean property.
1489    *    <br>Must not be <jk>null</jk>.
1490    * @param t The throwable that the bean getter threw.
1491    * @throws SerializeException Thrown if ignoreInvocationExceptionOnGetters is false.
1492    */
1493   protected final void onBeanGetterException(BeanPropertyMeta p, Throwable t) throws SerializeException {
1494      // If it's a StackOverflowError, let it propagate so it can be caught by the write() method
1495      // and converted to a proper SerializeException with recursion detection message
1496      if (t instanceof StackOverflowError t2)
1497         throw t2;
1498
1499      if (nn(listener))
1500         listener.onBeanGetterException(this, t, p);
1501      String prefix = (isDebug() ? getStack(false) + ": " : "");
1502      addWarning("%sCould not call getValue() on property '%s' of class '%s', exception = %s", prefix, p.getName(), p.getBeanMeta().getBeanInfo(), localizedMessage(t));
1503      if (! isIgnoreInvocationExceptionsOnGetters())
1504         throw new SerializeException(this, "%sCould not call getValue() on property '%s' of class '%s', exception = %s", prefix, p.getName(), p.getBeanMeta().getBeanInfo(), localizedMessage(t))
1505            .initCause(t);
1506   }
1507
1508   /**
1509    * Logs a warning message.
1510    *
1511    * @param t The throwable that was thrown (if there was one).
1512    * @param msg The warning message.
1513    * @param args Optional {@link String#format(String, Object...) String.format}-style arguments.
1514    */
1515   @Override
1516   protected void onError(Throwable t, String msg, Object...args) {
1517      if (nn(listener))
1518         listener.onError(this, t, f(msg, args));
1519      super.onError(t, msg, args);
1520   }
1521
1522   @Override /* Overridden from MarshallingTraverseSession */
1523   protected FluentMap<String,Object> properties() {
1524      return super.properties()
1525         .a("uriResolver", uriResolver);
1526   }
1527
1528   /**
1529    * Same as {@link #push(String, Object, ClassMeta)} but wraps {@link MarshallingRecursionException} inside {@link SerializeException}.
1530    *
1531    * @param attrName The attribute name.
1532    *    <br>Can be <jk>null</jk> for unnamed entries (e.g. array elements); used only for stack-trace labeling.
1533    * @param o The current object being traversed.
1534    *    <br>Can be <jk>null</jk> (returns <jk>null</jk>).
1535    * @param eType The expected class type.
1536    *    <br>Can be <jk>null</jk>, in which case the object's actual runtime type is used instead.
1537    * @return
1538    *    The {@link ClassMeta} of the object so that <c>instanceof</c> operations only need to be performed
1539    *    once (since they can be expensive).
1540    * @throws SerializeException If recursion occurred.
1541    */
1542   protected final ClassMeta<?> push2(String attrName, Object o, ClassMeta<?> eType) throws SerializeException {
1543      try {
1544         return super.push(attrName, o, eType);
1545      } catch (MarshallingRecursionException e) {
1546         throw new SerializeException(e);
1547      }
1548   }
1549
1550   /**
1551    * Opposite of {@link #resolveUri(Object)}.
1552    *
1553    * <p>
1554    * Converts the URI to a value relative to the specified <c>relativeTo</c> parameter.
1555    *
1556    * <p>
1557    * Both parameters can be any of the following:
1558    * <ul>
1559    *    <li>{@link URI}
1560    *    <li>{@link URL}
1561    *    <li>{@link CharSequence}
1562    * </ul>
1563    *
1564    * <p>
1565    * Both URIs can be any of the following forms:
1566    * <ul>
1567    *    <li><js>"foo://foo"</js> - Absolute URI.
1568    *    <li><js>"/foo"</js> - Root-relative URI.
1569    *    <li><js>"/"</js> - Root URI.
1570    *    <li><js>"context:/foo"</js> - Context-root-relative URI.
1571    *    <li><js>"context:/"</js> - Context-root URI.
1572    *    <li><js>"servlet:/foo"</js> - Servlet-path-relative URI.
1573    *    <li><js>"servlet:/"</js> - Servlet-path URI.
1574    *    <li><js>"request:/foo"</js> - Request-path-relative URI.
1575    *    <li><js>"request:/"</js> - Request-path URI.
1576    *    <li><js>"foo"</js> - Path-info-relative URI.
1577    *    <li><js>""</js> - Path-info URI.
1578    * </ul>
1579    *
1580    * @param relativeTo The URI to relativize against.
1581    * @param uri The URI to relativize.
1582    * @return The relativized URI.
1583    */
1584   protected final String relativizeUri(Object relativeTo, Object uri) {
1585      return uriResolver.relativize(relativeTo, uri);
1586   }
1587
1588   /**
1589    * Invokes the specified swap on the specified object if the swap is not null.
1590    *
1591    * @param swap The swap to invoke.  Can be <jk>null</jk> (no-op; the input object is returned unchanged).
1592    * @param o The input object.
1593    * @return The swapped object.
1594    * @throws SerializeException If swap method threw an exception.
1595    */
1596   protected Object swap(ObjectSwap swap, Object o) throws SerializeException {
1597      try {
1598         if (swap == null)
1599            return o;
1600         return swap.swap(this, o);
1601      } catch (Exception e) {
1602         throw new SerializeException(e);
1603      }
1604   }
1605}