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.serializer;
018
019import static org.apache.juneau.commons.reflect.ReflectionUtils.*;
020import static org.apache.juneau.commons.utils.AssertionUtils.*;
021import static org.apache.juneau.commons.utils.CollectionUtils.*;
022import static org.apache.juneau.commons.utils.ThrowableUtils.*;
023import static org.apache.juneau.commons.utils.Utils.*;
024
025import java.io.*;
026import java.lang.reflect.*;
027import java.text.*;
028import java.util.*;
029import java.util.function.*;
030import java.util.stream.*;
031
032import org.apache.juneau.*;
033import org.apache.juneau.commons.collections.FluentMap;
034import org.apache.juneau.commons.reflect.*;
035import org.apache.juneau.cp.*;
036import org.apache.juneau.httppart.*;
037import org.apache.juneau.parser.*;
038import org.apache.juneau.soap.*;
039import org.apache.juneau.svl.*;
040import org.apache.juneau.swap.*;
041
042/**
043 * Serializer session that lives for the duration of a single use of {@link Serializer}.
044 *
045 * <p>
046 * Used by serializers for the following purposes:
047 * <ul class='spaced-list'>
048 *    <li>
049 *       Keeping track of how deep it is in a model for indentation purposes.
050 *    <li>
051 *       Ensuring infinite loops don't occur by setting a limit on how deep to traverse a model.
052 *    <li>
053 *       Ensuring infinite loops don't occur from loops in the model (when detectRecursions is enabled.
054 *    <li>
055 *       Allowing serializer properties to be overridden on method calls.
056 * </ul>
057 *
058 * <h5 class='section'>Notes:</h5><ul>
059 *    <li class='warn'>This class is not thread safe and is typically discarded after one use.
060 * </ul>
061 *
062 * <h5 class='section'>See Also:</h5><ul>
063 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SerializersAndParsers">Serializers and Parsers</a>
064
065 * </ul>
066 */
067public class SerializerSession extends BeanTraverseSession {
068   /**
069    * Builder class.
070    */
071   public static class Builder extends BeanTraverseSession.Builder {
072
073      private HttpPartSchema schema;
074      private Method javaMethod;
075      private Serializer ctx;
076      private UriContext uriContext;
077      private VarResolverSession resolver;
078
079      /**
080       * Constructor
081       *
082       * @param ctx The context creating this session.
083       *    <br>Cannot be <jk>null</jk>.
084       */
085      protected Builder(Serializer ctx) {
086         super(assertArgNotNull("ctx", ctx));
087         this.ctx = ctx;
088         mediaTypeDefault(ctx.getResponseContentType());
089         uriContext = ctx.getUriContext();
090      }
091
092      @Override /* Overridden from Builder */
093      public <T> Builder apply(Class<T> type, Consumer<T> apply) {
094         super.apply(type, apply);
095         return this;
096      }
097
098      @Override
099      public SerializerSession build() {
100         return new SerializerSession(this);
101      }
102
103      @Override /* Overridden from Builder */
104      public Builder debug(Boolean value) {
105         super.debug(value);
106         return this;
107      }
108
109      /**
110       * The java method that called this serializer, usually the method in a REST servlet.
111       *
112       * @param value
113       *    The new property value.
114       *    <br>Can be <jk>null</jk> (value will not be set, existing value will be kept).
115       * @return This object.
116       */
117      public Builder javaMethod(Method value) {
118         if (nn(value))
119            javaMethod = value;
120         return this;
121      }
122
123      @Override /* Overridden from Builder */
124      public Builder locale(Locale value) {
125         super.locale(value);
126         return this;
127      }
128
129      @Override /* Overridden from Builder */
130      public Builder mediaType(MediaType value) {
131         super.mediaType(value);
132         return this;
133      }
134
135      @Override /* Overridden from Builder */
136      public Builder mediaTypeDefault(MediaType value) {
137         super.mediaTypeDefault(value);
138         return this;
139      }
140
141      @Override /* Overridden from Builder */
142      public Builder properties(Map<String,Object> value) {
143         super.properties(value);
144         return this;
145      }
146
147      @Override /* Overridden from Builder */
148      public Builder property(String key, Object value) {
149         super.property(key, value);
150         return this;
151      }
152
153      /**
154       * String variable resolver.
155       *
156       * <p>
157       * If not specified, defaults to session created by {@link VarResolver#DEFAULT}.
158       *
159       * @param value
160       *    The new property value.
161       *    <br>Can be <jk>null</jk> (value will not be set, defaults to session created by {@link VarResolver#DEFAULT} when accessed).
162       * @return This object.
163       */
164      public Builder resolver(VarResolverSession value) {
165         if (nn(value))
166            resolver = value;
167         return this;
168      }
169
170      /**
171       * HTTP-part schema.
172       *
173       * <p>
174       * Used for schema-based serializers and parsers to define additional formatting.
175       *
176       * @param value
177       *    The new value for this property.
178       *    <br>Can be <jk>null</jk> (value will not be set, existing value will be kept).
179       * @return This object.
180       */
181      public Builder schema(HttpPartSchema value) {
182         if (nn(value))
183            schema = value;
184         return this;
185      }
186
187      /**
188       * Same as {@link #schema(HttpPartSchema)} but doesn't overwrite the value if it is already set.
189       *
190       * @param value
191       *    The new value for this property.
192       *    <br>If <jk>null</jk>, then the locale defined on the context is used.
193       * @return This object.
194       */
195      public Builder schemaDefault(HttpPartSchema value) {
196         if (nn(value) && schema == null)
197            schema = value;
198         return this;
199      }
200
201      @Override /* Overridden from Builder */
202      public Builder timeZone(TimeZone value) {
203         super.timeZone(value);
204         return this;
205      }
206
207      @Override /* Overridden from Builder */
208      public Builder timeZoneDefault(TimeZone value) {
209         super.timeZoneDefault(value);
210         return this;
211      }
212
213      @Override /* Overridden from Builder */
214      public Builder unmodifiable() {
215         super.unmodifiable();
216         return this;
217      }
218
219      /**
220       * URI context bean.
221       *
222       * <p>
223       * Bean used for resolution of URIs to absolute or root-relative form.
224       *
225       * <p>
226       * If not specified, defaults to {@link Serializer.Builder#uriContext(UriContext)}.
227       *
228       * @param value
229       *    The new property value.
230       *    <br>Can be <jk>null</jk> (value will not be set, defaults to {@link Serializer.Builder#uriContext(UriContext)} from context).
231       * @return This object.
232       */
233      public Builder uriContext(UriContext value) {
234         if (nn(value))
235            uriContext = value;
236         return this;
237      }
238   }
239
240   /**
241    * Creates a new builder for this object.
242    *
243    * @param ctx The context creating this session.
244    *    <br>Cannot be <jk>null</jk>.
245    * @return A new builder.
246    */
247   public static Builder create(Serializer ctx) {
248      return new Builder(assertArgNotNull("ctx", ctx));
249   }
250
251   /**
252    * Create a "_type" property that contains the dictionary name of the bean.
253    *
254    * @param m The bean map to create a class property on.
255    * @param typeName The type name of the bean.
256    * @return A new bean property value.
257    */
258   protected static final BeanPropertyValue createBeanTypeNameProperty(BeanMap<?> m, String typeName) {
259      BeanMeta<?> bm = m.getMeta();
260      return new BeanPropertyValue(bm.getTypeProperty(), bm.getTypeProperty().getName(), typeName, null);
261   }
262
263   /**
264    * Converts the specified throwable to either a {@link RuntimeException} or {@link SerializeException}.
265    *
266    * @param <T> The throwable type.
267    * @param causedBy The exception to cast or wrap.
268    */
269   protected static <T extends Throwable> void handleThrown(T causedBy) {
270      if (causedBy instanceof Error)
271         throw (Error)causedBy;
272      if (causedBy instanceof RuntimeException)
273         throw (RuntimeException)causedBy;
274      if (causedBy instanceof StackOverflowError)
275         throw new SerializeException(
276            "Stack overflow occurred.  This can occur when trying to serialize models containing loops.  It's recommended you use the BeanTraverseContext.BEANTRAVERSE_detectRecursions setting to help locate the loop.");
277      if (causedBy instanceof SerializeException)
278         throw (SerializeException)causedBy;
279      throw new SerializeException(causedBy);
280   }
281
282   /**
283    * Converts the contents of the specified object array to a list.
284    *
285    * <p>
286    * Works on both object and primitive arrays.
287    *
288    * <p>
289    * In the case of multi-dimensional arrays, the outgoing list will contain elements of type n-1 dimension.
290    * i.e. if {@code type} is <code><jk>int</jk>[][]</code> then {@code list} will have entries of type
291    * <code><jk>int</jk>[]</code>.
292    *
293    * @param type The type of array.
294    * @param array The array being converted.
295    * @return The array as a list.
296    */
297   protected static final List<Object> toList(Class<?> type, Object array) {
298      var componentType = type.getComponentType();
299      if (componentType.isPrimitive()) {
300         var l = Array.getLength(array);
301         var list = new ArrayList<>(l);
302         for (var i = 0; i < l; i++)
303            list.add(Array.get(array, i));
304         return list;
305      }
306      return l((Object[])array);
307   }
308
309   private final HttpPartSchema schema;
310   private final Method javaMethod;                                                // Java method that invoked this serializer.
311   private final Serializer ctx;
312   private final SerializerListener listener;
313   private final UriResolver uriResolver;
314   private VarResolverSession vrs;
315
316   /**
317    * Constructor.
318    *
319    * @param builder The builder for this object.
320    */
321   protected SerializerSession(Builder builder) {
322      super(builder);
323      ctx = builder.ctx;
324      javaMethod = builder.javaMethod;
325      schema = builder.schema;
326      UriContext uriContext = builder.uriContext;
327      uriResolver = UriResolver.of(ctx.getUriResolution(), ctx.getUriRelativity(), uriContext);
328      vrs = builder.resolver;
329      listener = BeanCreator.of(SerializerListener.class).type(ctx.getListener()).orElse(null);
330   }
331
332   /**
333    * Adds a session object to the {@link VarResolverSession} in this session.
334    *
335    * @param <T> The bean type.
336    * @param c The bean type being added.
337    * @param value The bean being added.
338    * @return This object.
339    */
340   public <T> SerializerSession addVarBean(Class<T> c, T value) {
341      getVarResolver().bean(c, value);
342      return this;
343   }
344
345   /**
346    * Returns <jk>true</jk> if the specified value should not be serialized.
347    *
348    * @param cm The class type of the object being serialized.
349    * @param attrName The bean attribute name, or <jk>null</jk> if this isn't a bean attribute.
350    * @param value The object being serialized.
351    * @return <jk>true</jk> if the specified value should not be serialized.
352    * @throws SerializeException If recursion occurred.
353    */
354   public final boolean canIgnoreValue(ClassMeta<?> cm, String attrName, Object value) throws SerializeException {
355
356      if (value == null && ! isKeepNullProperties())
357         return true;
358
359      if (value == null)
360         return false;
361
362      if (cm == null)
363         cm = object();
364
365      if (isTrimEmptyCollections()) {
366         if (cm.isArray() || (cm.isObject() && isArray(value))) {
367            if (((Object[])value).length == 0)
368               return true;
369         }
370         if (cm.isCollection() || (cm.isObject() && info(value).isChildOf(Collection.class))) {
371            if (((Collection<?>)value).isEmpty())
372               return true;
373         }
374      }
375
376      if (isTrimEmptyMaps()) {
377         if (cm.isMap() || (cm.isObject() && info(value).isChildOf(Map.class))) {
378            if (((Map<?,?>)value).isEmpty())
379               return true;
380         }
381      }
382
383      try {
384         if ((! isKeepNullProperties()) && (willRecurse(attrName, value, cm) || willExceedDepth()))
385            return true;
386      } catch (BeanRecursionException e) {
387         throw new SerializeException(e);
388      }
389
390      return false;
391   }
392
393   /**
394    * Consumes each entry in the list.
395    *
396    * @param <E> The element type.
397    * @param c The collection being sorted.
398    * @param consumer The entry consumer.
399    */
400   public final <E> void forEachEntry(Collection<E> c, Consumer<E> consumer) {
401      if (c == null || c.isEmpty())
402         return;
403      if (isSortCollections() && ! SortedSet.class.isInstance(c) && isSortable(c))
404         c.stream().sorted().forEach(consumer);
405      else
406         c.forEach(consumer);
407   }
408
409   /**
410    * Consumes each map entry in the map.
411    *
412    * @param <K> The key type.
413    * @param <V> The value type.
414    * @param m The map being consumed.
415    * @param consumer The map entry consumer.
416    */
417   @SuppressWarnings({ "unchecked", "rawtypes", "cast" })
418   public final <K,V> void forEachEntry(Map<K,V> m, Consumer<Map.Entry<K,V>> consumer) {
419      if (m == null || m.isEmpty())
420         return;
421      if (isSortMaps() && ! SortedMap.class.isInstance(m) && isSortable(m.keySet()))
422         ((Map)m).entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(x -> consumer.accept((Map.Entry<K,V>)x));
423      else
424         m.entrySet().forEach(consumer);
425   }
426
427   /**
428    * Returns the listener associated with this session.
429    *
430    * @return The listener associated with this session, or <jk>null</jk> if there is no listener.
431    */
432   public SerializerListener getListener() { return listener; }
433
434   /**
435    * Returns the listener associated with this session.
436    *
437    * @param <T> The listener type.
438    * @param c The listener class to cast to.
439    * @return The listener associated with this session, or <jk>null</jk> if there is no listener.
440    */
441   @SuppressWarnings("unchecked")
442   public <T extends SerializerListener> T getListener(Class<T> c) {
443      return (T)listener;
444   }
445
446   /**
447    * Optional method that specifies HTTP request headers for this serializer.
448    *
449    * <p>
450    * For example, {@link SoapXmlSerializer} needs to set a <c>SOAPAction</c> header.
451    *
452    * <p>
453    * This method is typically meaningless if the serializer is being used stand-alone (i.e. outside of a REST server
454    * or client).
455    *
456    * <p>
457    * The default implementation of this method simply calls {@link Serializer#getResponseHeaders(SerializerSession)}.
458    *
459    * @return
460    *    The HTTP headers to set on HTTP requests.
461    *    Never <jk>null</jk>.
462    */
463   public Map<String,String> getResponseHeaders() { return ctx.getResponseHeaders(this); }
464
465   /**
466    * HTTP part schema of object being serialized.
467    *
468    * @return HTTP part schema of object being serialized, or <jk>null</jk> if not specified.
469    */
470   public final HttpPartSchema getSchema() { return schema; }
471
472   /**
473    * Returns the variable resolver session.
474    *
475    * @return The variable resolver session.
476    */
477   public VarResolverSession getVarResolver() {
478      if (vrs == null)
479         vrs = createDefaultVarResolverSession();
480      return vrs;
481   }
482
483   /**
484    * Returns <jk>true</jk> if this serializer subclasses from {@link WriterSerializer}.
485    *
486    * @return <jk>true</jk> if this serializer subclasses from {@link WriterSerializer}.
487    */
488   public boolean isWriterSerializer() { return false; }
489
490   /**
491    * Resolves any variables in the specified string.
492    *
493    * @param string The string to resolve values in.
494    * @return The string with variables resolved.
495    */
496   public String resolve(String string) {
497      return getVarResolver().resolve(string);
498   }
499
500   /**
501    * Converts a String to an absolute URI based on the {@link UriContext} on this session.
502    *
503    * @param uri
504    *    The input URI.
505    *    Can be any of the following:
506    *    <ul>
507    *       <li>{@link java.net.URI}
508    *       <li>{@link java.net.URL}
509    *       <li>{@link CharSequence}
510    *    </ul>
511    *    URI can be any of the following forms:
512    *    <ul>
513    *       <li><js>"foo://foo"</js> - Absolute URI.
514    *       <li><js>"/foo"</js> - Root-relative URI.
515    *       <li><js>"/"</js> - Root URI.
516    *       <li><js>"context:/foo"</js> - Context-root-relative URI.
517    *       <li><js>"context:/"</js> - Context-root URI.
518    *       <li><js>"servlet:/foo"</js> - Servlet-path-relative URI.
519    *       <li><js>"servlet:/"</js> - Servlet-path URI.
520    *       <li><js>"request:/foo"</js> - Request-path-relative URI.
521    *       <li><js>"request:/"</js> - Request-path URI.
522    *       <li><js>"foo"</js> - Path-info-relative URI.
523    *       <li><js>""</js> - Path-info URI.
524    *    </ul>
525    * @return The resolved URI.
526    */
527   public final String resolveUri(Object uri) {
528      return uriResolver.resolve(uri);
529   }
530
531   /**
532    * Shortcut method for serializing objects directly to either a <c>String</c> or <code><jk>byte</jk>[]</code>
533    * depending on the serializer type.
534    *
535    * @param o The object to serialize.
536    * @return
537    *    The serialized object.
538    *    <br>Character-based serializers will return a <c>String</c>.
539    *    <br>Stream-based serializers will return a <code><jk>byte</jk>[]</code>.
540    * @throws SerializeException If a problem occurred trying to convert the output.
541    */
542   public Object serialize(Object o) throws SerializeException {
543      throw unsupportedOp();
544   }
545
546   /**
547    * Serialize the specified object using the specified session.
548    *
549    * @param out Where to send the output from the serializer.
550    * @param o The object to serialize.
551    * @throws SerializeException If a problem occurred trying to convert the output.
552    * @throws IOException Thrown by the underlying stream.
553    */
554   public final void serialize(Object o, Object out) throws SerializeException, IOException {
555      try (SerializerPipe pipe = createPipe(out)) {
556         doSerialize(pipe, o);
557      } catch (SerializeException | IOException e) {
558         throw e;
559      } catch (@SuppressWarnings("unused") StackOverflowError e) {
560         throw new SerializeException(this,
561            "Stack overflow occurred.  This can occur when trying to serialize models containing loops.  It's recommended you use the BeanTraverseContext.BEANTRAVERSE_detectRecursions setting to help locate the loop.");
562      } catch (Exception e) {
563         throw new SerializeException(this, e);
564      } finally {
565         checkForWarnings();
566      }
567   }
568
569   /**
570    * Shortcut method for serializing an object to a String.
571    *
572    * @param o The object to serialize.
573    * @return
574    *    The serialized object.
575    *    <br>Character-based serializers will return a <c>String</c>
576    *    <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.
577    * @throws SerializeException If a problem occurred trying to convert the output.
578    */
579   public String serializeToString(Object o) throws SerializeException {
580      throw unsupportedOp();
581   }
582
583   /**
584    * Sorts the specified collection if {@link SerializerSession#isSortCollections()} returns <jk>true</jk>.
585    *
586    * @param <E> The element type.
587    * @param c The collection being sorted.
588    * @return A new sorted {@link TreeSet}.
589    */
590   public final <E> Collection<E> sort(Collection<E> c) {
591      if (c == null || c.isEmpty() || SortedSet.class.isInstance(c))
592         return c;
593      if (isSortCollections() && isSortable(c))
594         return c.stream().sorted().collect(Collectors.toList());
595      return c;
596   }
597
598   /**
599    * Sorts the specified collection if {@link SerializerSession#isSortCollections()} returns <jk>true</jk>.
600    *
601    * @param <E> The element type.
602    * @param c The collection being sorted.
603    * @return A new sorted {@link TreeSet}.
604    */
605   public final <E> List<E> sort(List<E> c) {
606      if (c == null || c.isEmpty())
607         return c;
608      if (isSortCollections() && isSortable(c))
609         return c.stream().sorted().collect(Collectors.toList());
610      return c;
611   }
612
613   /**
614    * Sorts the specified map if {@link SerializerSession#isSortMaps()} returns <jk>true</jk>.
615    *
616    * @param <K> The key type.
617    * @param <V> The value type.
618    * @param m The map being sorted.
619    * @return A new sorted {@link TreeMap}.
620    */
621   public final <K,V> Map<K,V> sort(Map<K,V> m) {
622      if (m == null || m.isEmpty() || SortedMap.class.isInstance(m))
623         return m;
624      if (isSortMaps() && isSortable(m.keySet()))
625         return new TreeMap<>(m);
626      return m;
627   }
628
629   /**
630    * Converts the specified object to a <c>String</c>.
631    *
632    * <p>
633    * Also has the following effects:
634    * <ul>
635    *    <li><c>Class</c> object is converted to a readable name.  See {@link ClassInfo#getNameFull()}.
636    *    <li>Whitespace is trimmed if the trim-strings setting is enabled.
637    * </ul>
638    *
639    * @param o The object to convert to a <c>String</c>.
640    * @return The object converted to a String, or <jk>null</jk> if the input was <jk>null</jk>.
641    */
642   public final String toString(Object o) {
643      if (o == null)
644         return null;
645      if (o.getClass() == Class.class)
646         return info((Class<?>)o).getNameFull();
647      if (o.getClass() == ClassInfo.class)
648         return ((ClassInfo)o).getNameFull();
649      if (o.getClass().isEnum())
650         return getClassMetaForObject(o).toString(o);
651      var s = o.toString();
652      if (isTrimStrings())
653         s = s.trim();
654      return s;
655   }
656
657   /**
658    * Trims the specified string if {@link SerializerSession#isTrimStrings()} returns <jk>true</jk>.
659    *
660    * @param o The input string to trim.
661    * @return The trimmed string, or <jk>null</jk> if the input was <jk>null</jk>.
662    */
663   public final String trim(Object o) {
664      if (o == null)
665         return null;
666      var s = o.toString();
667      if (isTrimStrings())
668         s = s.trim();
669      return s;
670   }
671
672   private static boolean isSortable(Collection<?> c) {
673      if (c == null)
674         return false;
675      for (var o : c)
676         if (! (o instanceof Comparable))
677            return false;
678      return true;
679   }
680
681   /**
682    * Adds a session object to the {@link VarResolverSession} in this session.
683    *
684    * @return This object.
685    */
686   protected VarResolverSession createDefaultVarResolverSession() {
687      return VarResolver.DEFAULT.createSession();
688   }
689
690   /**
691    * Wraps the specified input object into a {@link ParserPipe} object so that it can be easily converted into
692    * a stream or reader.
693    *
694    * @param output
695    *    The output location.
696    *    <br>For character-based serializers, this can be any of the following types:
697    *    <ul>
698    *       <li>{@link Writer}
699    *       <li>{@link OutputStream} - Output will be written as UTF-8 encoded stream.
700    *       <li>{@link File} - Output will be written as system-default encoded stream.
701    *       <li>{@link StringBuilder}
702    *    </ul>
703    *    <br>For byte-based serializers, this can be any of the following types:
704    *    <ul>
705    *       <li>{@link OutputStream}
706    *       <li>{@link File}
707    *    </ul>
708    * @return
709    *    A new {@link ParserPipe} wrapper around the specified input object.
710    */
711   protected SerializerPipe createPipe(Object output) {
712      return new SerializerPipe(output);
713   }
714
715   /**
716    * Serializes a POJO to the specified pipe.
717    *
718    * <p>
719    * This method should NOT close the context object.
720    *
721    * <p>
722    * The default implementation of this method simply calls {@link Serializer#doSerialize(SerializerSession,SerializerPipe,Object)}.
723    *
724    * @param pipe Where to send the output from the serializer.
725    * @param o The object to serialize.
726    * @throws IOException Thrown by underlying stream.
727    * @throws SerializeException Problem occurred trying to serialize object.
728    */
729   protected void doSerialize(SerializerPipe pipe, Object o) throws IOException, SerializeException {
730      ctx.doSerialize(this, pipe, o);
731   }
732
733   /**
734    * Generalize the specified object if a POJO swap is associated with it.
735    *
736    * @param o The object to generalize.
737    * @param type The type of object.
738    * @return The generalized object, or <jk>null</jk> if the object is <jk>null</jk>.
739    * @throws SerializeException If a problem occurred trying to convert the output.
740    */
741   @SuppressWarnings({ "rawtypes", "unchecked" })
742   protected final Object generalize(Object o, ClassMeta<?> type) throws SerializeException {
743      try {
744         if (o == null)
745            return null;
746         ObjectSwap f = (type == null || type.isObject() || type.isString() ? getClassMeta(o.getClass()).getSwap(this) : type.getSwap(this));
747         if (f == null)
748            return o;
749         return f.swap(this, o);
750      } catch (SerializeException e) {
751         throw e;
752      } catch (Exception e) {
753         throw new SerializeException(e);
754      }
755   }
756
757   /**
758    * Resolves the dictionary name for the actual type.
759    *
760    * @param session The current serializer session.
761    * @param eType The expected type of the bean property.
762    * @param aType The actual type of the bean property.
763    * @param pMeta The current bean property being serialized.
764    * @return The bean dictionary name, or <jk>null</jk> if a name could not be found.
765    */
766   @SuppressWarnings("null")
767   protected final String getBeanTypeName(SerializerSession session, ClassMeta<?> eType, ClassMeta<?> aType, BeanPropertyMeta pMeta) {
768      if (eType == aType || ! (isAddBeanTypes() || (session.isRoot() && isAddRootType())))
769         return null;
770
771      String eTypeTn = eType.getBeanDictionaryName();
772
773      // First see if it's defined on the actual type.
774      String tn = aType.getBeanDictionaryName();
775      if (nn(tn) && ! tn.equals(eTypeTn)) {
776         return tn;
777      }
778
779      // Then see if it's defined on the expected type.
780      // The expected type might be an interface with mappings for implementation classes.
781      BeanRegistry br = eType.getBeanRegistry();
782      if (nn(br)) {
783         tn = br.getTypeName(aType);
784         if (nn(tn) && ! tn.equals(eTypeTn))
785            return tn;
786      }
787
788      // Then look on the bean property.
789      br = pMeta == null ? null : pMeta.getBeanRegistry();
790      if (nn(br)) {
791         tn = br.getTypeName(aType);
792         if (nn(tn) && ! tn.equals(eTypeTn))
793            return tn;
794      }
795
796      // Finally look in the session.
797      br = getBeanRegistry();
798      if (nn(br)) {
799         tn = br.getTypeName(aType);
800         if (nn(tn) && ! tn.equals(eTypeTn))
801            return tn;
802      }
803
804      return null;
805   }
806
807   /**
808    * Returns the parser-side expected type for the object.
809    *
810    * <p>
811    * The return value depends on the {@link Serializer.Builder#addRootType()} setting.
812    * When disabled, the parser already knows the Java POJO type being parsed, so there is
813    * no reason to add <js>"_type"</js> attributes to the root-level object.
814    *
815    * @param o The object to get the expected type on.
816    * @return The expected type.
817    */
818   protected final ClassMeta<?> getExpectedRootType(Object o) {
819      if (isAddRootType())
820         return object();
821      var cm = getClassMetaForObject(o);
822      if (nn(cm) && cm.isOptional())
823         return cm.getElementType();
824      return cm;
825   }
826
827   /**
828    * Returns the Java method that invoked this serializer.
829    *
830    * <p>
831    * When using the REST API, this is the Java method invoked by the REST call.
832    * Can be used to access annotations defined on the method or class.
833    *
834    * @return The Java method that invoked this serializer.
835   */
836   protected final Method getJavaMethod() { return javaMethod; }
837
838   /**
839    * URI context bean.
840    *
841    * @see Serializer.Builder#uriContext(UriContext)
842    * @return
843    *    Bean used for resolution of URIs to absolute or root-relative form.
844    */
845   protected final UriContext getUriContext() { return ctx.getUriContext(); }
846
847   /**
848    * URI relativity.
849    *
850    * @see Serializer.Builder#uriRelativity(UriRelativity)
851    * @return
852    *    Defines what relative URIs are relative to when serializing any of the following:
853    */
854   protected final UriRelativity getUriRelativity() { return ctx.getUriRelativity(); }
855
856   /**
857    * URI resolution.
858    *
859    * @see Serializer.Builder#uriResolution(UriResolution)
860    * @return
861    *    Defines the resolution level for URIs when serializing URIs.
862    */
863   protected final UriResolution getUriResolution() { return ctx.getUriResolution(); }
864
865   /**
866    * Returns the URI resolver.
867    *
868    * @return The URI resolver.
869    */
870   protected final UriResolver getUriResolver() { return uriResolver; }
871
872   /**
873    * Add <js>"_type"</js> properties when needed.
874    *
875    * @see Serializer.Builder#addBeanTypes()
876    * @return
877    *    <jk>true</jk> if <js>"_type"</js> properties added to beans if their type cannot be inferred
878    *    through reflection.
879    */
880   protected boolean isAddBeanTypes() { return ctx.isAddBeanTypes(); }
881
882   /**
883    * Add type attribute to root nodes.
884    *
885    * @see Serializer.Builder#addRootType()
886    * @return
887    *    <jk>true</jk> if type property should be added to root node.
888    */
889   protected final boolean isAddRootType() { return ctx.isAddRootType(); }
890
891   /**
892    * Don't trim null bean property values.
893    *
894    * @see Serializer.Builder#keepNullProperties()
895    * @return
896    *    <jk>true</jk> if null bean values are serialized to the output.
897    */
898   protected final boolean isKeepNullProperties() { return ctx.isKeepNullProperties(); }
899
900   /**
901    * Sort arrays and collections alphabetically.
902    *
903    * @see Serializer.Builder#sortCollections()
904    * @return
905    *    <jk>true</jk> if arrays and collections are copied and sorted before serialization.
906    */
907   protected final boolean isSortCollections() { return ctx.isSortCollections(); }
908
909   /**
910    * Sort maps alphabetically.
911    *
912    * @see Serializer.Builder#sortMaps()
913    * @return
914    *    <jk>true</jk> if maps are copied and sorted before serialization.
915    */
916   protected final boolean isSortMaps() { return ctx.isSortMaps(); }
917
918   /**
919    * Trim empty lists and arrays.
920    *
921    * @see Serializer.Builder#trimEmptyCollections()
922    * @return
923    *    <jk>true</jk> if empty lists and arrays are not serialized to the output.
924    */
925   protected final boolean isTrimEmptyCollections() { return ctx.isTrimEmptyCollections(); }
926
927   /**
928    * Trim empty maps.
929    *
930    * @see Serializer.Builder#trimEmptyMaps()
931    * @return
932    *    <jk>true</jk> if empty map values are not serialized to the output.
933    */
934   protected final boolean isTrimEmptyMaps() { return ctx.isTrimEmptyMaps(); }
935
936   /**
937    * Trim strings.
938    *
939    * @see Serializer.Builder#trimStrings()
940    * @return
941    *    <jk>true</jk> if string values will be trimmed of whitespace using {@link String#trim()} before being serialized.
942    */
943   protected boolean isTrimStrings() { return ctx.isTrimStrings(); }
944
945   /**
946    * Specialized warning when an exception is thrown while executing a bean getter.
947    *
948    * @param p The bean map entry representing the bean property.
949    * @param t The throwable that the bean getter threw.
950    * @throws SerializeException Thrown if ignoreInvocationExceptionOnGetters is false.
951    */
952   protected final void onBeanGetterException(BeanPropertyMeta p, Throwable t) throws SerializeException {
953      if (nn(listener))
954         listener.onBeanGetterException(this, t, p);
955      String prefix = (isDebug() ? getStack(false) + ": " : "");
956      addWarning("{0}Could not call getValue() on property ''{1}'' of class ''{2}'', exception = {3}", prefix, p.getName(), p.getBeanMeta().getClassMeta(), lm(t));
957      if (! isIgnoreInvocationExceptionsOnGetters())
958         throw new SerializeException(this, "{0}Could not call getValue() on property ''{1}'' of class ''{2}'', exception = {3}", prefix, p.getName(), p.getBeanMeta().getClassMeta(), lm(t))
959            .initCause(t);
960   }
961
962   /**
963    * Logs a warning message.
964    *
965    * @param t The throwable that was thrown (if there was one).
966    * @param msg The warning message.
967    * @param args Optional {@link MessageFormat}-style arguments.
968    */
969   @Override
970   protected void onError(Throwable t, String msg, Object...args) {
971      if (nn(listener))
972         listener.onError(this, t, f(msg, args));
973      super.onError(t, msg, args);
974   }
975
976   @Override /* Overridden from BeanTraverseSession */
977   protected FluentMap<String,Object> properties() {
978      return super.properties()
979         .a("uriResolver", uriResolver);
980   }
981
982   /**
983    * Same as {@link #push(String, Object, ClassMeta)} but wraps {@link BeanRecursionException} inside {@link SerializeException}.
984    *
985    * @param attrName The attribute name.
986    * @param o The current object being traversed.
987    * @param eType The expected class type.
988    * @return
989    *    The {@link ClassMeta} of the object so that <c>instanceof</c> operations only need to be performed
990    *    once (since they can be expensive).
991    * @throws SerializeException If recursion occurred.
992    */
993   protected final ClassMeta<?> push2(String attrName, Object o, ClassMeta<?> eType) throws SerializeException {
994      try {
995         return super.push(attrName, o, eType);
996      } catch (BeanRecursionException e) {
997         throw new SerializeException(e);
998      }
999   }
1000
1001   /**
1002    * Opposite of {@link #resolveUri(Object)}.
1003    *
1004    * <p>
1005    * Converts the URI to a value relative to the specified <c>relativeTo</c> parameter.
1006    *
1007    * <p>
1008    * Both parameters can be any of the following:
1009    * <ul>
1010    *    <li>{@link java.net.URI}
1011    *    <li>{@link java.net.URL}
1012    *    <li>{@link CharSequence}
1013    * </ul>
1014    *
1015    * <p>
1016    * Both URIs can be any of the following forms:
1017    * <ul>
1018    *    <li><js>"foo://foo"</js> - Absolute URI.
1019    *    <li><js>"/foo"</js> - Root-relative URI.
1020    *    <li><js>"/"</js> - Root URI.
1021    *    <li><js>"context:/foo"</js> - Context-root-relative URI.
1022    *    <li><js>"context:/"</js> - Context-root URI.
1023    *    <li><js>"servlet:/foo"</js> - Servlet-path-relative URI.
1024    *    <li><js>"servlet:/"</js> - Servlet-path URI.
1025    *    <li><js>"request:/foo"</js> - Request-path-relative URI.
1026    *    <li><js>"request:/"</js> - Request-path URI.
1027    *    <li><js>"foo"</js> - Path-info-relative URI.
1028    *    <li><js>""</js> - Path-info URI.
1029    * </ul>
1030    *
1031    * @param relativeTo The URI to relativize against.
1032    * @param uri The URI to relativize.
1033    * @return The relativized URI.
1034    */
1035   protected final String relativizeUri(Object relativeTo, Object uri) {
1036      return uriResolver.relativize(relativeTo, uri);
1037   }
1038
1039   /**
1040    * Invokes the specified swap on the specified object if the swap is not null.
1041    *
1042    * @param swap The swap to invoke.  Can be <jk>null</jk>.
1043    * @param o The input object.
1044    * @return The swapped object.
1045    * @throws SerializeException If swap method threw an exception.
1046    */
1047   @SuppressWarnings({ "rawtypes", "unchecked" })
1048   protected Object swap(ObjectSwap swap, Object o) throws SerializeException {
1049      try {
1050         if (swap == null)
1051            return o;
1052         return swap.swap(this, o);
1053      } catch (Exception e) {
1054         throw new SerializeException(e);
1055      }
1056   }
1057}