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