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