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