001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.serializer;
014
015import static org.apache.juneau.internal.ClassUtils.*;
016import static org.apache.juneau.internal.StringUtils.*;
017import static org.apache.juneau.serializer.Serializer.*;
018
019import java.io.*;
020import java.lang.reflect.*;
021import java.text.*;
022import java.util.*;
023
024import org.apache.juneau.*;
025import org.apache.juneau.parser.*;
026import org.apache.juneau.reflect.*;
027import org.apache.juneau.soap.*;
028import org.apache.juneau.svl.*;
029import org.apache.juneau.transform.*;
030
031/**
032 * Serializer session that lives for the duration of a single use of {@link Serializer}.
033 *
034 * <p>
035 * Used by serializers for the following purposes:
036 * <ul class='spaced-list'>
037 *    <li>
038 *       Keeping track of how deep it is in a model for indentation purposes.
039 *    <li>
040 *       Ensuring infinite loops don't occur by setting a limit on how deep to traverse a model.
041 *    <li>
042 *       Ensuring infinite loops don't occur from loops in the model (when detectRecursions is enabled.
043 *    <li>
044 *       Allowing serializer properties to be overridden on method calls.
045 * </ul>
046 *
047 * <p>
048 * This class is NOT thread safe.
049 * It is typically discarded after one-time use although it can be reused within the same thread.
050 */
051public abstract class SerializerSession extends BeanTraverseSession {
052
053   private final Serializer ctx;
054   private final UriResolver uriResolver;
055   private VarResolverSession vrs;
056
057   private final Method javaMethod;                                                // Java method that invoked this serializer.
058
059   // Writable properties
060   private final SerializerListener listener;
061
062   /**
063    * Create a new session using properties specified in the context.
064    *
065    * @param ctx
066    *    The context creating this session object.
067    *    The context contains all the configuration settings for this object.
068    *    Can be <jk>null</jk>.
069    * @param args
070    *    Runtime arguments.
071    *    These specify session-level information such as locale and URI context.
072    *    It also include session-level properties that override the properties defined on the bean and
073    *    serializer contexts.
074    */
075   protected SerializerSession(Serializer ctx, SerializerSessionArgs args) {
076      super(ctx, args == null ? SerializerSessionArgs.DEFAULT : args);
077      this.ctx = ctx;
078      args = args == null ? SerializerSessionArgs.DEFAULT : args;
079      this.javaMethod = args.javaMethod;
080      this.uriResolver = new UriResolver(ctx.getUriResolution(), ctx.getUriRelativity(), getProperty(SERIALIZER_uriContext, UriContext.class, ctx.getUriContext()));
081      this.listener = castOrCreate(SerializerListener.class, ctx.getListener());
082      this.vrs = args.resolver;
083   }
084
085   /**
086    * Adds a session object to the {@link VarResolverSession} in this session.
087    *
088    * @param name The session object key.
089    * @param value The session object.
090    * @return This object (for method chaining).
091    */
092   public SerializerSession varSessionObject(String name, Object value) {
093      getVarResolver().sessionObject(name, value);
094      return this;
095   }
096
097   /**
098    * Adds a session object to the {@link VarResolverSession} in this session.
099    *
100    * @return This object (for method chaining).
101    */
102   protected VarResolverSession createDefaultVarResolverSession() {
103      return VarResolver.DEFAULT.createSession();
104   }
105
106   /**
107    * Returns the variable resolver session.
108    *
109    * @return The variable resolver session.
110    */
111   public VarResolverSession getVarResolver() {
112      if (vrs == null)
113         vrs = createDefaultVarResolverSession();
114      return vrs;
115   }
116
117   /**
118    * Default constructor.
119    *
120    * @param args
121    *    Runtime arguments.
122    *    These specify session-level information such as locale and URI context.
123    *    It also include session-level properties that override the properties defined on the bean and
124    *    serializer contexts.
125    */
126   protected SerializerSession(SerializerSessionArgs args) {
127      this(Serializer.DEFAULT, args);
128   }
129
130   //-----------------------------------------------------------------------------------------------------------------
131   // Abstract methods
132   //-----------------------------------------------------------------------------------------------------------------
133
134   /**
135    * Serializes a POJO to the specified output stream or writer.
136    *
137    * <p>
138    * This method should NOT close the context object.
139    *
140    * @param pipe Where to send the output from the serializer.
141    * @param o The object to serialize.
142    * @throws IOException Thrown by underlying stream.
143    * @throws SerializeException Problem occurred trying to serialize object.
144    */
145   protected abstract void doSerialize(SerializerPipe pipe, Object o) throws IOException, SerializeException;
146
147   /**
148    * Shortcut method for serializing objects directly to either a <c>String</c> or <code><jk>byte</jk>[]</code>
149    * depending on the serializer type.
150    *
151    * @param o The object to serialize.
152    * @return
153    *    The serialized object.
154    *    <br>Character-based serializers will return a <c>String</c>.
155    *    <br>Stream-based serializers will return a <code><jk>byte</jk>[]</code>.
156    * @throws SerializeException If a problem occurred trying to convert the output.
157    */
158   public abstract Object serialize(Object o) throws SerializeException;
159
160   /**
161    * Shortcut method for serializing an object to a String.
162    *
163    * @param o The object to serialize.
164    * @return
165    *    The serialized object.
166    *    <br>Character-based serializers will return a <c>String</c>
167    *    <br>Stream-based serializers will return a <code><jk>byte</jk>[]</code> converted to a string based on the {@link OutputStreamSerializer#OSSERIALIZER_binaryFormat} setting.
168    * @throws SerializeException If a problem occurred trying to convert the output.
169    */
170   public abstract String serializeToString(Object o) throws SerializeException;
171
172   /**
173    * Returns <jk>true</jk> if this serializer subclasses from {@link WriterSerializer}.
174    *
175    * @return <jk>true</jk> if this serializer subclasses from {@link WriterSerializer}.
176    */
177   public abstract boolean isWriterSerializer();
178
179   /**
180    * Wraps the specified input object into a {@link ParserPipe} object so that it can be easily converted into
181    * a stream or reader.
182    *
183    * @param output
184    *    The output location.
185    *    <br>For character-based serializers, this can be any of the following types:
186    *    <ul>
187    *       <li>{@link Writer}
188    *       <li>{@link OutputStream} - Output will be written as UTF-8 encoded stream.
189    *       <li>{@link File} - Output will be written as system-default encoded stream.
190    *       <li>{@link StringBuilder}
191    *    </ul>
192    *    <br>For byte-based serializers, this can be any of the following types:
193    *    <ul>
194    *       <li>{@link OutputStream}
195    *       <li>{@link File}
196    *    </ul>
197    * @return
198    *    A new {@link ParserPipe} wrapper around the specified input object.
199    */
200   protected abstract SerializerPipe createPipe(Object output);
201
202   //-----------------------------------------------------------------------------------------------------------------
203   // Other methods
204   //-----------------------------------------------------------------------------------------------------------------
205
206   /**
207    * Serialize the specified object using the specified session.
208    *
209    * @param out Where to send the output from the serializer.
210    * @param o The object to serialize.
211    * @throws SerializeException If a problem occurred trying to convert the output.
212    * @throws IOException Thrown by the underlying stream.
213    */
214   public final void serialize(Object o, Object out) throws SerializeException, IOException {
215      try (SerializerPipe pipe = createPipe(out)) {
216         doSerialize(pipe, o);
217      } catch (SerializeException | IOException e) {
218         throw e;
219      } catch (StackOverflowError e) {
220         throw new SerializeException(this,
221            "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.").initCause(e);
222      } catch (Exception e) {
223         throw new SerializeException(this, e);
224      } finally {
225         checkForWarnings();
226      }
227   }
228
229   /**
230    * Returns the Java method that invoked this serializer.
231    *
232    * <p>
233    * When using the REST API, this is the Java method invoked by the REST call.
234    * Can be used to access annotations defined on the method or class.
235    *
236    * @return The Java method that invoked this serializer.
237   */
238   protected final Method getJavaMethod() {
239      return javaMethod;
240   }
241
242   /**
243    * Returns the URI resolver.
244    *
245    * @return The URI resolver.
246    */
247   protected final UriResolver getUriResolver() {
248      return uriResolver;
249   }
250
251   /**
252    * Specialized warning when an exception is thrown while executing a bean getter.
253    *
254    * @param p The bean map entry representing the bean property.
255    * @param t The throwable that the bean getter threw.
256    */
257   protected final void onBeanGetterException(BeanPropertyMeta p, Throwable t) {
258      if (listener != null)
259         listener.onBeanGetterException(this, t, p);
260      String prefix = (isDebug() ? getStack(false) + ": " : "");
261      addWarning("{0}Could not call getValue() on property ''{1}'' of class ''{2}'', exception = {3}", prefix,
262         p.getName(), p.getBeanMeta().getClassMeta(), t.getLocalizedMessage());
263   }
264
265   /**
266    * Logs a warning message.
267    *
268    * @param t The throwable that was thrown (if there was one).
269    * @param msg The warning message.
270    * @param args Optional {@link MessageFormat}-style arguments.
271    */
272   @Override
273   protected void onError(Throwable t, String msg, Object... args) {
274      if (listener != null)
275         listener.onError(this, t, format(msg, args));
276      super.onError(t, msg, args);
277   }
278
279   /**
280    * Trims the specified string if {@link SerializerSession#isTrimStrings()} returns <jk>true</jk>.
281    *
282    * @param o The input string to trim.
283    * @return The trimmed string, or <jk>null</jk> if the input was <jk>null</jk>.
284    */
285   public final String trim(Object o) {
286      if (o == null)
287         return null;
288      String s = o.toString();
289      if (isTrimStrings())
290         s = s.trim();
291      return s;
292   }
293
294   /**
295    * Generalize the specified object if a POJO swap is associated with it.
296    *
297    * @param o The object to generalize.
298    * @param type The type of object.
299    * @return The generalized object, or <jk>null</jk> if the object is <jk>null</jk>.
300    * @throws SerializeException If a problem occurred trying to convert the output.
301    */
302   @SuppressWarnings({ "rawtypes", "unchecked" })
303   protected final Object generalize(Object o, ClassMeta<?> type) throws SerializeException {
304      try {
305         if (o == null)
306            return null;
307         PojoSwap f = (type == null || type.isObject() ? getClassMeta(o.getClass()).getPojoSwap(this) : type.getPojoSwap(this));
308         if (f == null)
309            return o;
310         return f.swap(this, o);
311      } catch (SerializeException e) {
312         throw e;
313      } catch (Exception e) {
314         throw new SerializeException(e);
315      }
316   }
317
318   /**
319    * Returns <jk>true</jk> if the specified value should not be serialized.
320    *
321    * @param cm The class type of the object being serialized.
322    * @param attrName The bean attribute name, or <jk>null</jk> if this isn't a bean attribute.
323    * @param value The object being serialized.
324    * @return <jk>true</jk> if the specified value should not be serialized.
325    * @throws SerializeException If recursion occurred.
326    */
327   public final boolean canIgnoreValue(ClassMeta<?> cm, String attrName, Object value) throws SerializeException {
328
329      if (isTrimNullProperties() && value == null)
330         return true;
331
332      if (value == null)
333         return false;
334
335      if (cm == null)
336         cm = object();
337
338      if (isTrimEmptyCollections()) {
339         if (cm.isArray() || (cm.isObject() && value.getClass().isArray())) {
340            if (((Object[])value).length == 0)
341               return true;
342         }
343         if (cm.isCollection() || (cm.isObject() && ClassInfo.of(value).isChildOf(Collection.class))) {
344            if (((Collection<?>)value).isEmpty())
345               return true;
346         }
347      }
348
349      if (isTrimEmptyMaps()) {
350         if (cm.isMap() || (cm.isObject() && ClassInfo.of(value).isChildOf(Map.class))) {
351            if (((Map<?,?>)value).isEmpty())
352               return true;
353         }
354      }
355
356      try {
357         if (isTrimNullProperties() && willRecurse(attrName, value, cm))
358            return true;
359      } catch (BeanRecursionException e) {
360         throw new SerializeException(e);
361      }
362
363      return false;
364   }
365
366   /**
367    * Sorts the specified map if {@link SerializerSession#isSortMaps()} returns <jk>true</jk>.
368    *
369    * @param m The map being sorted.
370    * @return A new sorted {@link TreeMap}.
371    */
372   public final <K,V> Map<K,V> sort(Map<K,V> m) {
373      if (isSortMaps() && m != null && (! m.isEmpty()) && m.keySet().iterator().next() instanceof Comparable<?>)
374         return new TreeMap<>(m);
375      return m;
376   }
377
378   /**
379    * Sorts the specified collection if {@link SerializerSession#isSortCollections()} returns <jk>true</jk>.
380    *
381    * @param c The collection being sorted.
382    * @return A new sorted {@link TreeSet}.
383    */
384   public final <E> Collection<E> sort(Collection<E> c) {
385      if (isSortCollections() && c != null && (! c.isEmpty()) && c.iterator().next() instanceof Comparable<?>)
386         return new TreeSet<>(c);
387      return c;
388   }
389
390   /**
391    * Converts the contents of the specified object array to a list.
392    *
393    * <p>
394    * Works on both object and primitive arrays.
395    *
396    * <p>
397    * In the case of multi-dimensional arrays, the outgoing list will contain elements of type n-1 dimension.
398    * i.e. if {@code type} is <code><jk>int</jk>[][]</code> then {@code list} will have entries of type
399    * <code><jk>int</jk>[]</code>.
400    *
401    * @param type The type of array.
402    * @param array The array being converted.
403    * @return The array as a list.
404    */
405   protected static final List<Object> toList(Class<?> type, Object array) {
406      Class<?> componentType = type.getComponentType();
407      if (componentType.isPrimitive()) {
408         int l = Array.getLength(array);
409         List<Object> list = new ArrayList<>(l);
410         for (int i = 0; i < l; i++)
411            list.add(Array.get(array, i));
412         return list;
413      }
414      return Arrays.asList((Object[])array);
415   }
416
417   /**
418    * Converts a String to an absolute URI based on the {@link UriContext} on this session.
419    *
420    * @param uri
421    *    The input URI.
422    *    Can be any of the following:
423    *    <ul>
424    *       <li>{@link java.net.URI}
425    *       <li>{@link java.net.URL}
426    *       <li>{@link CharSequence}
427    *    </ul>
428    *    URI can be any of the following forms:
429    *    <ul>
430    *       <li><js>"foo://foo"</js> - Absolute URI.
431    *       <li><js>"/foo"</js> - Root-relative URI.
432    *       <li><js>"/"</js> - Root URI.
433    *       <li><js>"context:/foo"</js> - Context-root-relative URI.
434    *       <li><js>"context:/"</js> - Context-root URI.
435    *       <li><js>"servlet:/foo"</js> - Servlet-path-relative URI.
436    *       <li><js>"servlet:/"</js> - Servlet-path URI.
437    *       <li><js>"request:/foo"</js> - Request-path-relative URI.
438    *       <li><js>"request:/"</js> - Request-path URI.
439    *       <li><js>"foo"</js> - Path-info-relative URI.
440    *       <li><js>""</js> - Path-info URI.
441    *    </ul>
442    * @return The resolved URI.
443    */
444   public final String resolveUri(Object uri) {
445      return uriResolver.resolve(uri);
446   }
447
448   /**
449    * Opposite of {@link #resolveUri(Object)}.
450    *
451    * <p>
452    * Converts the URI to a value relative to the specified <c>relativeTo</c> parameter.
453    *
454    * <p>
455    * Both parameters can be any of the following:
456    * <ul>
457    *    <li>{@link java.net.URI}
458    *    <li>{@link java.net.URL}
459    *    <li>{@link CharSequence}
460    * </ul>
461    *
462    * <p>
463    * Both URIs can be any of the following forms:
464    * <ul>
465    *    <li><js>"foo://foo"</js> - Absolute URI.
466    *    <li><js>"/foo"</js> - Root-relative URI.
467    *    <li><js>"/"</js> - Root URI.
468    *    <li><js>"context:/foo"</js> - Context-root-relative URI.
469    *    <li><js>"context:/"</js> - Context-root URI.
470    *    <li><js>"servlet:/foo"</js> - Servlet-path-relative URI.
471    *    <li><js>"servlet:/"</js> - Servlet-path URI.
472    *    <li><js>"request:/foo"</js> - Request-path-relative URI.
473    *    <li><js>"request:/"</js> - Request-path URI.
474    *    <li><js>"foo"</js> - Path-info-relative URI.
475    *    <li><js>""</js> - Path-info URI.
476    * </ul>
477    *
478    * @param relativeTo The URI to relativize against.
479    * @param uri The URI to relativize.
480    * @return The relativized URI.
481    */
482   protected final String relativizeUri(Object relativeTo, Object uri) {
483      return uriResolver.relativize(relativeTo, uri);
484   }
485
486   /**
487    * Converts the specified object to a <c>String</c>.
488    *
489    * <p>
490    * Also has the following effects:
491    * <ul>
492    *    <li><c>Class</c> object is converted to a readable name.  See {@link ClassInfo#getFullName()}.
493    *    <li>Whitespace is trimmed if the trim-strings setting is enabled.
494    * </ul>
495    *
496    * @param o The object to convert to a <c>String</c>.
497    * @return The object converted to a String, or <jk>null</jk> if the input was <jk>null</jk>.
498    */
499   public final String toString(Object o) {
500      if (o == null)
501         return null;
502      if (o.getClass() == Class.class)
503         return ClassInfo.of((Class<?>)o).getFullName();
504      if (o.getClass() == ClassInfo.class)
505         return ((ClassInfo)o).getFullName();
506      if (o.getClass().isEnum())
507         return getClassMetaForObject(o).toString(o);
508      String s = o.toString();
509      if (isTrimStrings())
510         s = s.trim();
511      return s;
512   }
513
514   /**
515    * Create a "_type" property that contains the dictionary name of the bean.
516    *
517    * @param m The bean map to create a class property on.
518    * @param typeName The type name of the bean.
519    * @return A new bean property value.
520    */
521   protected static final BeanPropertyValue createBeanTypeNameProperty(BeanMap<?> m, String typeName) {
522      BeanMeta<?> bm = m.getMeta();
523      return new BeanPropertyValue(bm.getTypeProperty(), bm.getTypeProperty().getName(), typeName, null);
524   }
525
526   /**
527    * Resolves the dictionary name for the actual type.
528    *
529    * @param eType The expected type of the bean property.
530    * @param aType The actual type of the bean property.
531    * @param pMeta The current bean property being serialized.
532    * @return The bean dictionary name, or <jk>null</jk> if a name could not be found.
533    */
534   protected final String getBeanTypeName(ClassMeta<?> eType, ClassMeta<?> aType, BeanPropertyMeta pMeta) {
535      if (eType == aType)
536         return null;
537
538      if (! isAddBeanTypes())
539         return null;
540
541      String eTypeTn = eType.getDictionaryName();
542
543      // First see if it's defined on the actual type.
544      String tn = aType.getDictionaryName();
545      if (tn != null && ! tn.equals(eTypeTn)) {
546         return tn;
547      }
548
549      // Then see if it's defined on the expected type.
550      // The expected type might be an interface with mappings for implementation classes.
551      BeanRegistry br = eType.getBeanRegistry();
552      if (br != null) {
553         tn = br.getTypeName(aType);
554         if (tn != null && ! tn.equals(eTypeTn))
555            return tn;
556      }
557
558      // Then look on the bean property.
559      br = pMeta == null ? null : pMeta.getBeanRegistry();
560      if (br != null) {
561         tn = br.getTypeName(aType);
562         if (tn != null && ! tn.equals(eTypeTn))
563            return tn;
564      }
565
566      // Finally look in the session.
567      br = getBeanRegistry();
568      if (br != null) {
569         tn = br.getTypeName(aType);
570         if (tn != null && ! tn.equals(eTypeTn))
571            return tn;
572      }
573
574      return null;
575   }
576
577   /**
578    * Returns the parser-side expected type for the object.
579    *
580    * <p>
581    * The return value depends on the {@link Serializer#SERIALIZER_addRootType} setting.
582    * When disabled, the parser already knows the Java POJO type being parsed, so there is
583    * no reason to add <js>"_type"</js> attributes to the root-level object.
584    *
585    * @param o The object to get the expected type on.
586    * @return The expected type.
587    */
588   protected final ClassMeta<?> getExpectedRootType(Object o) {
589      if (isAddRootType())
590         return object();
591      ClassMeta<?> cm = getClassMetaForObject(o);
592      if (cm != null && cm.isOptional())
593         return cm.getElementType();
594      return cm;
595   }
596
597   /**
598    * Optional method that specifies HTTP request headers for this serializer.
599    *
600    * <p>
601    * For example, {@link SoapXmlSerializer} needs to set a <c>SOAPAction</c> header.
602    *
603    * <p>
604    * This method is typically meaningless if the serializer is being used stand-alone (i.e. outside of a REST server
605    * or client).
606    *
607    * @return
608    *    The HTTP headers to set on HTTP requests.
609    *    Never <jk>null</jk>.
610    */
611   public Map<String,String> getResponseHeaders() {
612      return Collections.emptyMap();
613   }
614
615   /**
616    * Returns the listener associated with this session.
617    *
618    * @param c The listener class to cast to.
619    * @return The listener associated with this session, or <jk>null</jk> if there is no listener.
620    */
621   @SuppressWarnings("unchecked")
622   public <T extends SerializerListener> T getListener(Class<T> c) {
623      return (T)listener;
624   }
625
626   /**
627    * Resolves any variables in the specified string.
628    *
629    * @param string The string to resolve values in.
630    * @return The string with variables resolved.
631    */
632   public String resolve(String string) {
633      return getVarResolver().resolve(string);
634   }
635
636   /**
637    * Same as {@link #push(String, Object, ClassMeta)} but wraps {@link BeanRecursionException} inside {@link SerializeException}.
638    *
639    * @param attrName The attribute name.
640    * @param o The current object being traversed.
641    * @param eType The expected class type.
642    * @return
643    *    The {@link ClassMeta} of the object so that <c>instanceof</c> operations only need to be performed
644    *    once (since they can be expensive).
645    * @throws SerializeException If recursion occurred.
646    */
647   protected final ClassMeta<?> push2(String attrName, Object o, ClassMeta<?> eType) throws SerializeException {
648      try {
649         return super.push(attrName, o, eType);
650      } catch (BeanRecursionException e) {
651         throw new SerializeException(e);
652      }
653   }
654
655   /**
656    * Invokes the specified swap on the specified object.
657    *
658    * @param swap The swap to invoke.
659    * @param o The input object.
660    * @return The swapped object.
661    * @throws SerializeException If swap method threw an exception.
662    */
663   @SuppressWarnings({ "rawtypes", "unchecked" })
664   protected Object swap(PojoSwap swap, Object o) throws SerializeException {
665      try {
666         return swap.swap(this, o);
667      } catch (Exception e) {
668         throw new SerializeException(e);
669      }
670   }
671
672   //-----------------------------------------------------------------------------------------------------------------
673   // Properties
674   //-----------------------------------------------------------------------------------------------------------------
675
676   /**
677    * Configuration property:  Add <js>"_type"</js> properties when needed.
678    *
679    * @see Serializer#SERIALIZER_addBeanTypes
680    * @return
681    *    <jk>true</jk> if <js>"_type"</js> properties added to beans if their type cannot be inferred
682    *    through reflection.
683    */
684   protected boolean isAddBeanTypes() {
685      return ctx.isAddBeanTypes();
686   }
687
688   /**
689    * Configuration property:  Add type attribute to root nodes.
690    *
691    * @see Serializer#SERIALIZER_addRootType
692    * @return
693    *    <jk>true</jk> if type property should be added to root node.
694    */
695   protected final boolean isAddRootType() {
696      return ctx.isAddRootType();
697   }
698
699   /**
700    * Returns the listener associated with this session.
701    *
702    * @return The listener associated with this session, or <jk>null</jk> if there is no listener.
703    */
704   public SerializerListener getListener() {
705      return listener;
706   }
707
708   /**
709    * Configuration property:  Sort arrays and collections alphabetically.
710    *
711    * @see Serializer#SERIALIZER_sortCollections
712    * @return
713    *    <jk>true</jk> if arrays and collections are copied and sorted before serialization.
714    */
715   protected final boolean isSortCollections() {
716      return ctx.isSortCollections();
717   }
718
719   /**
720    * Configuration property:  Sort maps alphabetically.
721    *
722    * @see Serializer#SERIALIZER_sortMaps
723    * @return
724    *    <jk>true</jk> if maps are copied and sorted before serialization.
725    */
726   protected final boolean isSortMaps() {
727      return ctx.isSortMaps();
728   }
729
730   /**
731    * Configuration property:  Trim empty lists and arrays.
732    *
733    * @see Serializer#SERIALIZER_trimEmptyCollections
734    * @return
735    *    <jk>true</jk> if empty lists and arrays are not serialized to the output.
736    */
737   protected final boolean isTrimEmptyCollections() {
738      return ctx.isTrimEmptyCollections();
739   }
740
741   /**
742    * Configuration property:  Trim empty maps.
743    *
744    * @see Serializer#SERIALIZER_trimEmptyMaps
745    * @return
746    *    <jk>true</jk> if empty map values are not serialized to the output.
747    */
748   protected final boolean isTrimEmptyMaps() {
749      return ctx.isTrimEmptyMaps();
750   }
751
752   /**
753    * Configuration property:  Trim null bean property values.
754    *
755    * @see Serializer#SERIALIZER_trimNullProperties
756    * @return
757    *    <jk>true</jk> if null bean values are not serialized to the output.
758    */
759   protected final boolean isTrimNullProperties() {
760      return ctx.isTrimNullProperties();
761   }
762
763   /**
764    * Configuration property:  Trim strings.
765    *
766    * @see Serializer#SERIALIZER_trimStrings
767    * @return
768    *    <jk>true</jk> if string values will be trimmed of whitespace using {@link String#trim()} before being serialized.
769    */
770   protected boolean isTrimStrings() {
771      return ctx.isTrimStrings();
772   }
773
774   /**
775    * Configuration property:  URI context bean.
776    *
777    * @see Serializer#SERIALIZER_uriContext
778    * @return
779    *    Bean used for resolution of URIs to absolute or root-relative form.
780    */
781   protected final UriContext getUriContext() {
782      return ctx.getUriContext();
783   }
784
785   /**
786    * Configuration property:  URI relativity.
787    *
788    * @see Serializer#SERIALIZER_uriRelativity
789    * @return
790    *    Defines what relative URIs are relative to when serializing any of the following:
791    */
792   protected final UriRelativity getUriRelativity() {
793      return ctx.getUriRelativity();
794   }
795
796   /**
797    * Configuration property:  URI resolution.
798    *
799    * @see Serializer#SERIALIZER_uriResolution
800    * @return
801    *    Defines the resolution level for URIs when serializing URIs.
802    */
803   protected final UriResolution getUriResolution() {
804      return ctx.getUriResolution();
805   }
806
807   //-----------------------------------------------------------------------------------------------------------------
808   // Other methods
809   //-----------------------------------------------------------------------------------------------------------------
810
811   @Override /* Session */
812   public ObjectMap toMap() {
813      return super.toMap()
814         .append("SerializerSession", new DefaultFilteringObjectMap()
815            .append("uriResolver", uriResolver)
816         );
817   }
818}