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