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() && getClassInfo(value).isChildOf(Collection.class))) {
344            if (((Collection<?>)value).isEmpty())
345               return true;
346         }
347      }
348
349      if (isTrimEmptyMaps()) {
350         if (cm.isMap() || (cm.isObject() && getClassInfo(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      return isAddRootType() ? object() : getClassMetaForObject(o);
590   }
591
592   /**
593    * Optional method that specifies HTTP request headers for this serializer.
594    *
595    * <p>
596    * For example, {@link SoapXmlSerializer} needs to set a <c>SOAPAction</c> header.
597    *
598    * <p>
599    * This method is typically meaningless if the serializer is being used stand-alone (i.e. outside of a REST server
600    * or client).
601    *
602    * @return
603    *    The HTTP headers to set on HTTP requests.
604    *    Never <jk>null</jk>.
605    */
606   public Map<String,String> getResponseHeaders() {
607      return Collections.emptyMap();
608   }
609
610   /**
611    * Returns the listener associated with this session.
612    *
613    * @param c The listener class to cast to.
614    * @return The listener associated with this session, or <jk>null</jk> if there is no listener.
615    */
616   @SuppressWarnings("unchecked")
617   public <T extends SerializerListener> T getListener(Class<T> c) {
618      return (T)listener;
619   }
620
621   /**
622    * Resolves any variables in the specified string.
623    *
624    * @param string The string to resolve values in.
625    * @return The string with variables resolved.
626    */
627   public String resolve(String string) {
628      return getVarResolver().resolve(string);
629   }
630
631   /**
632    * Same as {@link #push(String, Object, ClassMeta)} but wraps {@link BeanRecursionException} inside {@link SerializeException}.
633    *
634    * @param attrName The attribute name.
635    * @param o The current object being traversed.
636    * @param eType The expected class type.
637    * @return
638    *    The {@link ClassMeta} of the object so that <c>instanceof</c> operations only need to be performed
639    *    once (since they can be expensive).
640    * @throws SerializeException If recursion occurred.
641    */
642   protected final ClassMeta<?> push2(String attrName, Object o, ClassMeta<?> eType) throws SerializeException {
643      try {
644         return super.push(attrName, o, eType);
645      } catch (BeanRecursionException e) {
646         throw new SerializeException(e);
647      }
648   }
649
650   /**
651    * Invokes the specified swap on the specified object.
652    *
653    * @param swap The swap to invoke.
654    * @param o The input object.
655    * @return The swapped object.
656    * @throws SerializeException If swap method threw an exception.
657    */
658   @SuppressWarnings({ "rawtypes", "unchecked" })
659   protected Object swap(PojoSwap swap, Object o) throws SerializeException {
660      try {
661         return swap.swap(this, o);
662      } catch (Exception e) {
663         throw new SerializeException(e);
664      }
665   }
666
667   //-----------------------------------------------------------------------------------------------------------------
668   // Properties
669   //-----------------------------------------------------------------------------------------------------------------
670
671   /**
672    * Configuration property:  Add <js>"_type"</js> properties when needed.
673    *
674    * @see Serializer#SERIALIZER_addBeanTypes
675    * @return
676    *    <jk>true</jk> if <js>"_type"</js> properties added to beans if their type cannot be inferred
677    *    through reflection.
678    */
679   protected boolean isAddBeanTypes() {
680      return ctx.isAddBeanTypes();
681   }
682
683   /**
684    * Configuration property:  Add type attribute to root nodes.
685    *
686    * @see Serializer#SERIALIZER_addRootType
687    * @return
688    *    <jk>true</jk> if type property should be added to root node.
689    */
690   protected final boolean isAddRootType() {
691      return ctx.isAddRootType();
692   }
693
694   /**
695    * Returns the listener associated with this session.
696    *
697    * @return The listener associated with this session, or <jk>null</jk> if there is no listener.
698    */
699   public SerializerListener getListener() {
700      return listener;
701   }
702
703   /**
704    * Configuration property:  Sort arrays and collections alphabetically.
705    *
706    * @see Serializer#SERIALIZER_sortCollections
707    * @return
708    *    <jk>true</jk> if arrays and collections are copied and sorted before serialization.
709    */
710   protected final boolean isSortCollections() {
711      return ctx.isSortCollections();
712   }
713
714   /**
715    * Configuration property:  Sort maps alphabetically.
716    *
717    * @see Serializer#SERIALIZER_sortMaps
718    * @return
719    *    <jk>true</jk> if maps are copied and sorted before serialization.
720    */
721   protected final boolean isSortMaps() {
722      return ctx.isSortMaps();
723   }
724
725   /**
726    * Configuration property:  Trim empty lists and arrays.
727    *
728    * @see Serializer#SERIALIZER_trimEmptyCollections
729    * @return
730    *    <jk>true</jk> if empty lists and arrays are not serialized to the output.
731    */
732   protected final boolean isTrimEmptyCollections() {
733      return ctx.isTrimEmptyCollections();
734   }
735
736   /**
737    * Configuration property:  Trim empty maps.
738    *
739    * @see Serializer#SERIALIZER_trimEmptyMaps
740    * @return
741    *    <jk>true</jk> if empty map values are not serialized to the output.
742    */
743   protected final boolean isTrimEmptyMaps() {
744      return ctx.isTrimEmptyMaps();
745   }
746
747   /**
748    * Configuration property:  Trim null bean property values.
749    *
750    * @see Serializer#SERIALIZER_trimNullProperties
751    * @return
752    *    <jk>true</jk> if null bean values are not serialized to the output.
753    */
754   protected final boolean isTrimNullProperties() {
755      return ctx.isTrimNullProperties();
756   }
757
758   /**
759    * Configuration property:  Trim strings.
760    *
761    * @see Serializer#SERIALIZER_trimStrings
762    * @return
763    *    <jk>true</jk> if string values will be trimmed of whitespace using {@link String#trim()} before being serialized.
764    */
765   protected boolean isTrimStrings() {
766      return ctx.isTrimStrings();
767   }
768
769   /**
770    * Configuration property:  URI context bean.
771    *
772    * @see Serializer#SERIALIZER_uriContext
773    * @return
774    *    Bean used for resolution of URIs to absolute or root-relative form.
775    */
776   protected final UriContext getUriContext() {
777      return ctx.getUriContext();
778   }
779
780   /**
781    * Configuration property:  URI relativity.
782    *
783    * @see Serializer#SERIALIZER_uriRelativity
784    * @return
785    *    Defines what relative URIs are relative to when serializing any of the following:
786    */
787   protected final UriRelativity getUriRelativity() {
788      return ctx.getUriRelativity();
789   }
790
791   /**
792    * Configuration property:  URI resolution.
793    *
794    * @see Serializer#SERIALIZER_uriResolution
795    * @return
796    *    Defines the resolution level for URIs when serializing URIs.
797    */
798   protected final UriResolution getUriResolution() {
799      return ctx.getUriResolution();
800   }
801
802   //-----------------------------------------------------------------------------------------------------------------
803   // Other methods
804   //-----------------------------------------------------------------------------------------------------------------
805
806   @Override /* Session */
807   public ObjectMap toMap() {
808      return super.toMap()
809         .append("SerializerSession", new DefaultFilteringObjectMap()
810            .append("uriResolver", uriResolver)
811         );
812   }
813}