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 java.io.*;
016
017import org.apache.juneau.*;
018import org.apache.juneau.annotation.*;
019import org.apache.juneau.http.*;
020import org.apache.juneau.internal.*;
021
022/**
023 * Parent class for all Juneau serializers.
024 *
025 * <h5 class='topic'>Description</h5>
026 *
027 * Base serializer class that serves as the parent class for all serializers.
028 *
029 * <p>
030 * The purpose of this class is:
031 * <ul>
032 *    <li>Maintain a read-only configuration state of a serializer.
033 *    <li>Create session objects used for serializing POJOs (i.e. {@link SerializerSession}).
034 *    <li>Provide convenience methods for serializing POJOs without having to construct session objects.
035 * </ul>
036 *
037 * <p>
038 * Subclasses should extend directly from {@link OutputStreamSerializer} or {@link WriterSerializer} depending on
039 * whether it's a stream or character based serializer.
040 */
041public abstract class Serializer extends BeanTraverseContext {
042
043   //-------------------------------------------------------------------------------------------------------------------
044   // Configurable properties
045   //-------------------------------------------------------------------------------------------------------------------
046
047   private static final String PREFIX = "Serializer.";
048
049   /**
050    * Configuration property:  Add <js>"_type"</js> properties when needed.
051    *
052    * <h5 class='section'>Property:</h5>
053    * <ul>
054    *    <li><b>Name:</b>  <js>"Serializer.addBeanTypes.b"</js>
055    *    <li><b>Data type:</b>  <code>Boolean</code>
056    *    <li><b>Default:</b>  <jk>false</jk>
057    *    <li><b>Session property:</b>  <jk>false</jk>
058    *    <li><b>Methods:</b>
059    *       <ul>
060    *          <li class='jm'>{@link SerializerBuilder#addBeanTypes()}
061    *          <li class='jm'>{@link SerializerBuilder#addBeanTypes(boolean)}
062    *       </ul>
063    * </ul>
064    *
065    * <h5 class='section'>Description:</h5>
066    * <p>
067    * If <jk>true</jk>, then <js>"_type"</js> properties will be added to beans if their type cannot be inferred
068    * through reflection.
069    *
070    * <p>
071    * This is used to recreate the correct objects during parsing if the object types cannot be inferred.
072    * <br>For example, when serializing a <code>Map&lt;String,Object&gt;</code> field where the bean class cannot be determined from
073    * the type of the values.
074    *
075    * <p>
076    * Note the differences between the following settings:
077    * <ul>
078    *    <li class='jf'>{@link #SERIALIZER_addRootType} - Affects whether <js>'_type'</js> is added to root node.
079    *    <li class='jf'>{@link #SERIALIZER_addBeanTypes} - Affects whether <js>'_type'</js> is added to any nodes.
080    * </ul>
081    *
082    * <h5 class='section'>Example:</h5>
083    * <p class='bcode w800'>
084    *    <jc>// Create a serializer that adds _type to nodes.</jc>
085    *    WriterSerializer s = JsonSerializer
086    *       .<jsm>create</jsm>()
087    *       .addBeanTypes()
088    *       .build();
089    *
090    *    <jc>// Same, but use property.</jc>
091    *    WriterSerializer s = JsonSerializer
092    *       .<jsm>create</jsm>()
093    *       .set(<jsf>SERIALIZER_addBeanTypes</jsf>, <jk>true</jk>)
094    *       .build();
095    *
096    *    <jc>// A map of objects we want to serialize.</jc>
097    *    <ja>@Bean</ja>(typeName=<js>"mybean"</js>)
098    *    <jk>public class</jk> MyBean {...}
099    *
100    *    Map&lt;String,Object&gt; m = new HashMap&lt;&gt;();
101    *    m.put(<js>"foo"</js>, <jk>new</jk> MyBean());
102    *
103    *    <jc>// Will contain '_type' attribute.</jc>
104    *    String json = s.serialize(m);
105    * </p>
106    */
107   public static final String SERIALIZER_addBeanTypes = PREFIX + "addBeanTypes.b";
108
109   /**
110    * Configuration property:  Add type attribute to root nodes.
111    *
112    * <h5 class='section'>Property:</h5>
113    * <ul>
114    *    <li><b>Name:</b>  <js>"Serializer.addRootType.b"</js>
115    *    <li><b>Data type:</b>  <code>Boolean</code>
116    *    <li><b>Default:</b>  <jk>false</jk>
117    *    <li><b>Session property:</b>  <jk>false</jk>
118    *    <li><b>Methods:</b>
119    *       <ul>
120    *          <li class='jm'>{@link SerializerBuilder#addRootType(boolean)}
121    *          <li class='jm'>{@link SerializerBuilder#addRootType()}
122    *       </ul>
123    * </ul>
124    *
125    * <h5 class='section'>Description:</h5>
126    * <p>
127    * When disabled, it is assumed that the parser knows the exact Java POJO type being parsed, and therefore top-level
128    * type information that might normally be included to determine the data type will not be serialized.
129    *
130    * <p>
131    * For example, when serializing a top-level POJO with a {@link Bean#typeName() @Bean(typeName)} value, a
132    * <js>'_type'</js> attribute will only be added when this setting is enabled.
133    *
134    * <p>
135    * Note the differences between the following settings:
136    * <ul>
137    *    <li class='jf'>{@link #SERIALIZER_addRootType} - Affects whether <js>'_type'</js> is added to root node.
138    *    <li class='jf'>{@link #SERIALIZER_addBeanTypes} - Affects whether <js>'_type'</js> is added to any nodes.
139    * </ul>
140    *
141    * <h5 class='section'>Example:</h5>
142    * <p class='bcode w800'>
143    *    <jc>// Create a serializer that adds _type to root node.</jc>
144    *    WriterSerializer s = JsonSerializer
145    *       .<jsm>create</jsm>()
146    *       .addRootType()
147    *       .build();
148    *
149    *    <jc>// Same, but use property.</jc>
150    *    WriterSerializer s = JsonSerializer
151    *       .<jsm>create</jsm>()
152    *       .set(<jsf>SERIALIZER_addRootType</jsf>, <jk>true</jk>)
153    *       .build();
154    *
155    *    <jc>// The bean we want to serialize.</jc>
156    *    <ja>@Bean</ja>(typeName=<js>"mybean"</js>)
157    *    <jk>public class</jk> MyBean {...}
158    *
159    *    <jc>// Will contain '_type' attribute.</jc>
160    *    String json = s.serialize(<jk>new</jk> MyBean());
161    * </p>
162    */
163   public static final String SERIALIZER_addRootType = PREFIX + "addRootType.b";
164
165   /**
166    * Configuration property:  Serializer listener.
167    *
168    * <h5 class='section'>Property:</h5>
169    * <ul>
170    *    <li><b>Name:</b>  <js>"Serializer.listener.c"</js>
171    *    <li><b>Data type:</b>  <code>Class&lt;? extends SerializerListener&gt;</code>
172    *    <li><b>Default:</b>  <jk>null</jk>
173    *    <li><b>Session property:</b>  <jk>false</jk>
174    *    <li><b>Methods:</b>
175    *       <ul>
176    *          <li class='jm'>{@link SerializerBuilder#listener(Class)}
177    *       </ul>
178    * </ul>
179    *
180    * <h5 class='section'>Description:</h5>
181    * <p>
182    * Class used to listen for errors and warnings that occur during serialization.
183    *
184    * <h5 class='section'>Example:</h5>
185    * <p class='bcode w800'>
186    *    <jc>// Define our serializer listener.</jc>
187    *    <jc>// Simply captures all errors.</jc>
188    *    <jk>public class</jk> MySerializerListener <jk>extends</jk> SerializerListener {
189    *
190    *       <jc>// A simple property to store our events.</jc>
191    *       <jk>public</jk> List&lt;String&gt; <jf>events</jf> = <jk>new</jk> LinkedList&lt;&gt;();
192    *
193    *       <ja>@Override</ja>
194    *       <jk>public</jk> &lt;T&gt; <jk>void</jk> onError(SerializerSession session, Throwable t, String msg) {
195    *          <jf>events</jf>.add(session.getLastLocation() + <js>","</js> + msg + <js>","</js> + t);
196    *       }
197    *    }
198    *
199    *    <jc>// Create a serializer using our listener.</jc>
200    *    WriterSerializer s = JsonSerializer.
201    *       .<jsm>create</jsm>()
202    *       .listener(MySerializerListener.<jk>class</jk>)
203    *       .build();
204    *
205    *    <jc>// Same, but use property.</jc>
206    *    WriterSerializer s = JsonSerializer.
207    *       .<jsm>create</jsm>()
208    *       .set(<jsf>SERIALIZER_listener</jsf>, MySerializerListener.<jk>class</jk>)
209    *       .build();
210    *
211    *    <jc>// Create a session object.</jc>
212    *    <jc>// Needed because listeners are created per-session.</jc>
213    *    <jk>try</jk> (WriterSerializerSession ss = s.createSession()) {
214    *
215    *       <jc>// Serialize a bean.</jc>
216    *       String json = ss.serialize(<jk>new</jk> MyBean());
217    *
218    *       <jc>// Get the listener.</jc>
219    *       MySerializerListener l = ss.getListener(MySerializerListener.<jk>class</jk>);
220    *
221    *       <jc>// Dump the results to the console.</jc>
222    *       SimpleJsonSerializer.<jsf>DEFAULT</jsf>.println(l.<jf>events</jf>);
223    *    }
224    * </p>
225    */
226   public static final String SERIALIZER_listener = PREFIX + "listener.c";
227
228   /**
229    * Configuration property:  Sort arrays and collections alphabetically.
230    *
231    * <h5 class='section'>Property:</h5>
232    * <ul>
233    *    <li><b>Name:</b>  <js>"Serializer.sortCollections.b"</js>
234    *    <li><b>Data type:</b>  <code>Boolean</code>
235    *    <li><b>Default:</b>  <jk>false</jk>
236    *    <li><b>Session property:</b>  <jk>false</jk>
237    *    <li><b>Methods:</b>
238    *       <ul>
239    *          <li class='jm'>{@link SerializerBuilder#sortCollections(boolean)}
240    *          <li class='jm'>{@link SerializerBuilder#sortCollections()}
241    *       </ul>
242    * </ul>
243    *
244    * <h5 class='section'>Description:</h5>
245    *
246    * <p>
247    * Copies and sorts the contents of arrays and collections before serializing them.
248    *
249    * <p>
250    * Note that this introduces a performance penalty.
251    *
252    * <h5 class='section'>Example:</h5>
253    * <p class='bcode w800'>
254    *    <jc>// Create a serializer that sorts arrays and collections before serialization.</jc>
255    *    WriterSerializer s = JsonSerializer
256    *       .<jsm>create</jsm>()
257    *       .sortCollections()
258    *       .build();
259    *
260    *    <jc>// Same, but use property.</jc>
261    *    WriterSerializer s = JsonSerializer
262    *       .<jsm>create</jsm>()
263    *       .set(<jsf>SERIALIZER_sortCollections</jsf>, <jk>true</jk>)
264    *       .build();
265    * </p>
266    */
267   public static final String SERIALIZER_sortCollections = PREFIX + "sortCollections.b";
268
269   /**
270    * Configuration property:  Sort maps alphabetically.
271    *
272    * <h5 class='section'>Property:</h5>
273    * <ul>
274    *    <li><b>Name:</b>  <js>"Serializer.sortMaps.b"</js>
275    *    <li><b>Data type:</b>  <code>Boolean</code>
276    *    <li><b>Default:</b>  <jk>false</jk>
277    *    <li><b>Session property:</b>  <jk>false</jk>
278    *    <li><b>Methods:</b>
279    *       <ul>
280    *          <li class='jm'>{@link SerializerBuilder#sortMaps(boolean)}
281    *          <li class='jm'>{@link SerializerBuilder#sortMaps()}
282    *       </ul>
283    * </ul>
284    *
285    * <h5 class='section'>Description:</h5>
286    *
287    * <p>
288    * Copies and sorts the contents of maps by their keys before serializing them.
289    *
290    * <p>
291    * Note that this introduces a performance penalty.
292    *
293    * <h5 class='section'>Example:</h5>
294    * <p class='bcode w800'>
295    *    <jc>// Create a serializer that sorts maps before serialization.</jc>
296    *    WriterSerializer s = JsonSerializer
297    *       .<jsm>create</jsm>()
298    *       .sortMaps()
299    *       .build();
300    *
301    *    <jc>// Same, but use property.</jc>
302    *    WriterSerializer s = JsonSerializer
303    *       .<jsm>create</jsm>()
304    *       .set(<jsf>SERIALIZER_sortMaps</jsf>, <jk>true</jk>)
305    *       .build();
306    * </p>
307    */
308   public static final String SERIALIZER_sortMaps = PREFIX + "sortMaps.b";
309
310   /**
311    * Configuration property:  Trim empty lists and arrays.
312    *
313    * <h5 class='section'>Property:</h5>
314    * <ul>
315    *    <li><b>Name:</b>  <js>"Serializer.trimEmptyCollections.b"</js>
316    *    <li><b>Data type:</b>  <code>Boolean</code>
317    *    <li><b>Default:</b>  <jk>false</jk>
318    *    <li><b>Session property:</b>  <jk>false</jk>
319    *    <li><b>Methods:</b>
320    *       <ul>
321    *          <li class='jm'>{@link SerializerBuilder#trimEmptyCollections(boolean)}
322    *          <li class='jm'>{@link SerializerBuilder#trimEmptyCollections()}
323    *       </ul>
324    * </ul>
325    *
326    * <h5 class='section'>Description:</h5>
327    *
328    * <p>
329    * If <jk>true</jk>, empty lists and arrays will not be serialized.
330    *
331    * <p>
332    * Note that enabling this setting has the following effects on parsing:
333    * <ul class='spaced-list'>
334    *    <li>
335    *       Map entries with empty list values will be lost.
336    *    <li>
337    *       Bean properties with empty list values will not be set.
338    * </ul>
339    *
340    * <h5 class='section'>Example:</h5>
341    * <p class='bcode w800'>
342    *    <jc>// Create a serializer that skips empty arrays and collections.</jc>
343    *    WriterSerializer s = JsonSerializer
344    *       .<jsm>create</jsm>()
345    *       .trimEmptyCollections()
346    *       .build();
347    *
348    *    <jc>// Same, but use property.</jc>
349    *    WriterSerializer s = JsonSerializer
350    *       .<jsm>create</jsm>()
351    *       .set(<jsf>SERIALIZER_trimEmptyCollections</jsf>, <jk>true</jk>)
352    *       .build();
353    * </p>
354    */
355   public static final String SERIALIZER_trimEmptyCollections = PREFIX + "trimEmptyCollections.b";
356
357   /**
358    * Configuration property:  Trim empty maps.
359    *
360    * <h5 class='section'>Property:</h5>
361    * <ul>
362    *    <li><b>Name:</b>  <js>"Serializer.trimEmptyMaps.b"</js>
363    *    <li><b>Data type:</b>  <code>Boolean</code>
364    *    <li><b>Default:</b>  <jk>false</jk>
365    *    <li><b>Session property:</b>  <jk>false</jk>
366    *    <li><b>Methods:</b>
367    *       <ul>
368    *          <li class='jm'>{@link SerializerBuilder#trimEmptyMaps(boolean)}
369    *          <li class='jm'>{@link SerializerBuilder#trimEmptyMaps()}
370    *       </ul>
371    * </ul>
372    *
373    * <h5 class='section'>Description:</h5>
374    * <p>
375    * If <jk>true</jk>, empty map values will not be serialized to the output.
376    *
377    * <p>
378    * Note that enabling this setting has the following effects on parsing:
379    * <ul class='spaced-list'>
380    *    <li>
381    *       Bean properties with empty map values will not be set.
382    * </ul>
383    *
384    * <h5 class='section'>Example:</h5>
385    * <p class='bcode w800'>
386    *    <jc>// Create a serializer that skips empty maps.</jc>
387    *    WriterSerializer s = JsonSerializer
388    *       .<jsm>create</jsm>()
389    *       .trimEmptyMaps()
390    *       .build();
391    *
392    *    <jc>// Same, but use property.</jc>
393    *    WriterSerializer s = JsonSerializer
394    *       .<jsm>create</jsm>()
395    *       .set(<jsf>SERIALIZER_trimEmptyMaps</jsf>, <jk>true</jk>)
396    *       .build();
397    * </p>
398    */
399   public static final String SERIALIZER_trimEmptyMaps = PREFIX + "trimEmptyMaps.b";
400
401   /**
402    * Configuration property:  Trim null bean property values.
403    *
404    * <h5 class='section'>Property:</h5>
405    * <ul>
406    *    <li><b>Name:</b>  <js>"Serializer.trimNullProperties.b"</js>
407    *    <li><b>Data type:</b>  <code>Boolean</code>
408    *    <li><b>Default:</b>  <jk>true</jk>
409    *    <li><b>Session property:</b>  <jk>false</jk>
410    *    <li><b>Methods:</b>
411    *       <ul>
412    *          <li class='jm'>{@link SerializerBuilder#trimNullProperties(boolean)}
413    *       </ul>
414    * </ul>
415    *
416    * <h5 class='section'>Description:</h5>
417    * <p>
418    * If <jk>true</jk>, null bean values will not be serialized to the output.
419    *
420    * <p>
421    * Note that enabling this setting has the following effects on parsing:
422    * <ul class='spaced-list'>
423    *    <li>
424    *       Map entries with <jk>null</jk> values will be lost.
425    * </ul>
426    *
427    * <h5 class='section'>Example:</h5>
428    * <p class='bcode w800'>
429    *    <jc>// Create a serializer that serializes null properties.</jc>
430    *    WriterSerializer s = JsonSerializer
431    *       .<jsm>create</jsm>()
432    *       .trimNullProperties(<jk>false</jk>)
433    *       .build();
434    *
435    *    <jc>// Same, but use property.</jc>
436    *    WriterSerializer s = JsonSerializer
437    *       .<jsm>create</jsm>()
438    *       .set(<jsf>SERIALIZER_trimNullProperties</jsf>, <jk>false</jk>)
439    *       .build();
440    * </p>
441    */
442   public static final String SERIALIZER_trimNullProperties = PREFIX + "trimNullProperties.b";
443
444   /**
445    * Configuration property:  Trim strings.
446    *
447    * <h5 class='section'>Property:</h5>
448    * <ul>
449    *    <li><b>Name:</b>  <js>"Serializer.trimStrings.b"</js>
450    *    <li><b>Data type:</b>  <code>Boolean</code>
451    *    <li><b>Default:</b>  <jk>false</jk>
452    *    <li><b>Session property:</b>  <jk>false</jk>
453    *    <li><b>Methods:</b>
454    *       <ul>
455    *          <li class='jm'>{@link SerializerBuilder#trimStrings(boolean)}
456    *          <li class='jm'>{@link SerializerBuilder#trimStrings()}
457    *       </ul>
458    * </ul>
459    *
460    * <h5 class='section'>Description:</h5>
461    * <p>
462    * If <jk>true</jk>, string values will be trimmed of whitespace using {@link String#trim()} before being serialized.
463    *
464    * <h5 class='section'>Example:</h5>
465    * <p class='bcode w800'>
466    *    <jc>// Create a serializer that trims strings before serialization.</jc>
467    *    WriterSerializer s = JsonSerializer
468    *       .<jsm>create</jsm>()
469    *       .trimStrings()
470    *       .build();
471    *
472    *    <jc>// Same, but use property.</jc>
473    *    WriterSerializer s = JsonSerializer
474    *       .<jsm>create</jsm>()
475    *       .set(<jsf>SERIALIZER_trimStrings</jsf>, <jk>true</jk>)
476    *       .build();
477    *
478    *    Map&lt;String,String&gt; m = <jk>new</jk> HashMap&lt;&gt;();
479    *    m.put(<js>" foo "</js>, <js>" bar "</js>);
480    *
481    *    <jc>// Produces "{foo:'bar'}"</jc>
482    *    String json = SimpleJsonSerializer.<jsf>DEFAULT</jsf>.toString(m);
483    * </p>
484    */
485   public static final String SERIALIZER_trimStrings = PREFIX + "trimStrings.b";
486
487   /**
488    * Configuration property:  URI context bean.
489    *
490    * <h5 class='section'>Property:</h5>
491    * <ul>
492    *    <li><b>Name:</b>  <js>"Serializer.uriContext.s"</js>
493    *    <li><b>Data type:</b>  <code>String</code> (JSON object representing a {@link UriContext})
494    *    <li><b>Default:</b>  <js>"{}"</js>
495    *    <li><b>Session property:</b>  <jk>true</jk>
496    *    <li><b>Methods:</b>
497    *       <ul>
498    *          <li class='jm'>{@link SerializerBuilder#uriContext(UriContext)}
499    *          <li class='jm'>{@link SerializerBuilder#uriContext(String)}
500    *       </ul>
501    * </ul>
502    *
503    * <h5 class='section'>Description:</h5>
504    * <p>
505    * Bean used for resolution of URIs to absolute or root-relative form.
506    *
507    * <h5 class='section'>Example:</h5>
508    * <p class='bcode w800'>
509    *    <jc>// Our URI contextual information.</jc>
510    *    String authority = <js>"http://localhost:10000"</js>;
511    *    String contextRoot = <js>"/myContext"</js>;
512    *    String servletPath = <js>"/myServlet"</js>;
513    *    String pathInfo = <js>"/foo"</js>;
514    *
515    *    <jc>// Create a UriContext object.</jc>
516    *    UriContext uriContext = <jk>new</jk> UriContext(authority, contextRoot, servletPath, pathInfo);
517    *
518    *    <jc>// Associate it with our serializer.</jc>
519    *    WriterSerializer s = JsonSerializer
520    *       .<jsm>create</jsm>()
521    *       .uriContext(uriContext)
522    *       .build();
523    *
524    *    <jc>// Same, but specify as a JSON string.</jc>
525    *    WriterSerializer s = JsonSerializer
526    *       .<jsm>create</jsm>()
527    *       .uriContext(<js>"{authority:'http://localhost:10000',contextRoot:'/myContext',servletPath:'/myServlet',pathInfo:'/foo'}"</js>)
528    *       .build();
529    *
530    *    <jc>// Same, but use property.</jc>
531    *    WriterSerializer s = JsonSerializer
532    *       .<jsm>create</jsm>()
533    *       .set(<jsf>SERIALIZER_uriContext</jsf>, uriContext)
534    *       .build();
535    *
536    *    <jc>// Same, but define it on the session args instead.</jc>
537    *    SerializerSessionArgs sessionArgs = <jk>new</jk> SerializerSessionArgs().uriContext(uriContext);
538    *    <jk>try</jk> (WriterSerializerSession session = s.createSession(sessionArgs)) {
539    *       ...
540    *    }
541    * </p>
542    */
543   public static final String SERIALIZER_uriContext = PREFIX + "uriContext.s";
544
545   /**
546    * Configuration property:  URI relativity.
547    *
548    * <h5 class='section'>Property:</h5>
549    * <ul>
550    *    <li><b>Name:</b>  <js>"Serializer.uriRelativity.s"</js>
551    *    <li><b>Data type:</b>  <code>String</code> ({@link UriRelativity})
552    *    <li><b>Default:</b>  <js>"RESOURCE"</js>
553    *    <li><b>Session property:</b>  <jk>false</jk>
554    *    <li><b>Methods:</b>
555    *       <ul>
556    *          <li class='jm'>{@link SerializerBuilder#uriRelativity(UriRelativity)}
557    *          <li class='jm'>{@link SerializerBuilder#uriRelativity(String)}
558    *       </ul>
559    * </ul>
560    *
561    * <h5 class='section'>Description:</h5>
562    * <p>
563    * Defines what relative URIs are relative to when serializing any of the following:
564    * <ul>
565    *    <li>{@link java.net.URI}
566    *    <li>{@link java.net.URL}
567    *    <li>Properties and classes annotated with {@link org.apache.juneau.annotation.URI @URI}
568    * </ul>
569    *
570    * <p>
571    * Possible values are:
572    * <ul>
573    *    <li class='jf'>{@link UriRelativity#RESOURCE}
574    *       - Relative URIs should be considered relative to the servlet URI.
575    *    <li class='jf'>{@link UriRelativity#PATH_INFO}
576    *       - Relative URIs should be considered relative to the request URI.
577    * </ul>
578    *
579    * <h5 class='figure'>Example:</h5>
580    * <p class='bcode w800'>
581    *    <jc>// Define a serializer that converts resource-relative URIs to absolute form.</jc>
582    *    WriterSerializer s = JsonSerializer
583    *       .<jsm>create</jsm>()
584    *       .uriContext(<js>"{authority:'http://localhost:10000',contextRoot:'/myContext',servletPath:'/myServlet',pathInfo:'/foo'}"</js>)
585    *       .uriResolution(<jsf>ABSOLUTE</jsf>)
586    *       .uriRelativity(<jsf>RESOURCE</jsf>)
587    *       .build();
588    * </p>
589    *
590    * <h5 class='section'>See Also:</h5>
591    * <ul>
592    *    <li class='link'>{@doc juneau-marshall.URIs}
593    * </ul>
594    */
595   public static final String SERIALIZER_uriRelativity = PREFIX + "uriRelativity.s";
596
597   /**
598    * Configuration property:  URI resolution.
599    *
600    * <h5 class='section'>Property:</h5>
601    * <ul>
602    *    <li><b>Name:</b>  <js>"Serializer.uriResolution.s"</js>
603    *    <li><b>Data type:</b>  <code>String</code> ({@link UriResolution})
604    *    <li><b>Default:</b>  <js>"NONE"</js>
605    *    <li><b>Session property:</b>  <jk>false</jk>
606    *    <li><b>Methods:</b>
607    *       <ul>
608    *          <li class='jm'>{@link SerializerBuilder#uriResolution(UriResolution)}
609    *          <li class='jm'>{@link SerializerBuilder#uriResolution(String)}
610    *       </ul>
611    * </ul>
612    *
613    * <h5 class='section'>Description:</h5>
614    * <p>
615    * Defines the resolution level for URIs when serializing any of the following:
616    * <ul>
617    *    <li>{@link java.net.URI}
618    *    <li>{@link java.net.URL}
619    *    <li>Properties and classes annotated with {@link org.apache.juneau.annotation.URI @URI}
620    * </ul>
621    *
622    * <p>
623    * Possible values are:
624    * <ul>
625    *    <li class='jf'>{@link UriResolution#ABSOLUTE}
626    *       - Resolve to an absolute URL (e.g. <js>"http://host:port/context-root/servlet-path/path-info"</js>).
627    *    <li class='jf'>{@link UriResolution#ROOT_RELATIVE}
628    *       - Resolve to a root-relative URL (e.g. <js>"/context-root/servlet-path/path-info"</js>).
629    *    <li class='jf'>{@link UriResolution#NONE}
630    *       - Don't do any URL resolution.
631    * </ul>
632    *
633    * <h5 class='figure'>Example:</h5>
634    * <p class='bcode w800'>
635    *    <jc>// Define a serializer that converts resource-relative URIs to absolute form.</jc>
636    *    WriterSerializer s = JsonSerializer
637    *       .<jsm>create</jsm>()
638    *       .uriContext(<js>"{authority:'http://localhost:10000',contextRoot:'/myContext',servletPath:'/myServlet',pathInfo:'/foo'}"</js>)
639    *       .uriResolution(<jsf>ABSOLUTE</jsf>)
640    *       .uriRelativity(<jsf>RESOURCE</jsf>)
641    *       .build();
642    * </p>
643    *
644    * <h5 class='section'>See Also:</h5>
645    * <ul>
646    *    <li class='link'>{@doc juneau-marshall.URIs}
647    * </ul>
648    */
649   public static final String SERIALIZER_uriResolution = PREFIX + "uriResolution.s";
650
651   /**
652    * Configuration property:  Use whitespace.
653    *
654    * <h5 class='section'>Property:</h5>
655    * <ul>
656    *    <li><b>Name:</b>  <js>"Serializer.useWhitespace.b"</js>
657    *    <li><b>Data type:</b>  <code>Boolean</code>
658    *    <li><b>Default:</b>  <jk>false</jk>
659    *    <li><b>Session property:</b>  <jk>true</jk>
660    *    <li><b>Methods:</b>
661    *       <ul>
662    *          <li class='jm'>{@link SerializerBuilder#useWhitespace(boolean)}
663    *          <li class='jm'>{@link SerializerBuilder#useWhitespace()}
664    *          <li class='jm'>{@link SerializerBuilder#ws()}
665    *          <li class='jm'>{@link SerializerSessionArgs#useWhitespace(Boolean)}
666    *       </ul>
667    * </ul>
668    *
669    * <h5 class='section'>Description:</h5>
670    * <p>
671    * If <jk>true</jk>, whitespace is added to the output to improve readability.
672    *
673    * <h5 class='section'>Example:</h5>
674    * <p class='bcode w800'>
675    *    <jc>// Create a serializer with whitespace enabled.</jc>
676    *    WriterSerializer s = JsonSerializer
677    *       .<jsm>create</jsm>()
678    *       .ws()
679    *       .build();
680    *
681    *    <jc>// Same, but use property.</jc>
682    *    WriterSerializer s = JsonSerializer
683    *       .<jsm>create</jsm>()
684    *       .set(<jsf>SERIALIZER_useWhitespace</jsf>, <jk>true</jk>)
685    *       .build();
686    *
687    *    <jc>// Produces "\{\n\t'foo': 'bar'\n\}\n"</jc>
688    *    String json = s.serialize(<jk>new</jk> MyBean());
689    * </p>
690    */
691   public static final String SERIALIZER_useWhitespace = PREFIX + "useWhitespace.b";
692
693
694   static final Serializer DEFAULT = new Serializer(PropertyStore.create().build(), "", "") {
695      @Override
696      public SerializerSession createSession(SerializerSessionArgs args) {
697         throw new NoSuchMethodError();
698      }
699   };
700
701   //-------------------------------------------------------------------------------------------------------------------
702   // Instance
703   //-------------------------------------------------------------------------------------------------------------------
704
705   private final boolean
706      addBeanTypes,
707      trimNullProperties,
708      trimEmptyCollections,
709      trimEmptyMaps,
710      trimStrings,
711      sortCollections,
712      sortMaps,
713      addRootType,
714      useWhitespace;
715   private final UriContext uriContext;
716   private final UriResolution uriResolution;
717   private final UriRelativity uriRelativity;
718   private final Class<? extends SerializerListener> listener;
719
720   private final MediaTypeRange[] accept;
721   private final MediaType[] accepts;
722   private final MediaType produces;
723
724   /**
725    * Constructor
726    *
727    * @param ps
728    *    The property store containing all the settings for this object.
729    * @param produces
730    *    The media type that this serializer produces.
731    * @param accept
732    *    The accept media types that the serializer can handle.
733    *    <p>
734    *    Can contain meta-characters per the <code>media-type</code> specification of {@doc RFC2616.section14.1}
735    *    <p>
736    *    If empty, then assumes the only media type supported is <code>produces</code>.
737    *    <p>
738    *    For example, if this serializer produces <js>"application/json"</js> but should handle media types of
739    *    <js>"application/json"</js> and <js>"text/json"</js>, then the arguments should be:
740    *    <p class='bcode w800'>
741    *    <jk>super</jk>(ps, <js>"application/json"</js>, <js>"application/json,text/json"</js>);
742    *    </p>
743    *    <br>...or...
744    *    <p class='bcode w800'>
745    *    <jk>super</jk>(ps, <js>"application/json"</js>, <js>"*&#8203;/json"</js>);
746    *    </p>
747    * <p>
748    * The accept value can also contain q-values.
749    */
750   protected Serializer(PropertyStore ps, String produces, String accept) {
751      super(ps);
752
753      addBeanTypes = getBooleanProperty(SERIALIZER_addBeanTypes, false);
754      trimNullProperties = getBooleanProperty(SERIALIZER_trimNullProperties, true);
755      trimEmptyCollections = getBooleanProperty(SERIALIZER_trimEmptyCollections, false);
756      trimEmptyMaps = getBooleanProperty(SERIALIZER_trimEmptyMaps, false);
757      trimStrings = getBooleanProperty(SERIALIZER_trimStrings, false);
758      sortCollections = getBooleanProperty(SERIALIZER_sortCollections, false);
759      sortMaps = getBooleanProperty(SERIALIZER_sortMaps, false);
760      addRootType = getBooleanProperty(SERIALIZER_addRootType, false);
761      uriContext = getProperty(SERIALIZER_uriContext, UriContext.class, UriContext.DEFAULT);
762      uriResolution = getProperty(SERIALIZER_uriResolution, UriResolution.class, UriResolution.NONE);
763      uriRelativity = getProperty(SERIALIZER_uriRelativity, UriRelativity.class, UriRelativity.RESOURCE);
764      useWhitespace = getBooleanProperty(SERIALIZER_useWhitespace, false);
765      listener = getClassProperty(SERIALIZER_listener, SerializerListener.class, null);
766
767      this.produces = MediaType.forString(produces);
768      this.accept = accept == null ? MediaTypeRange.parse(produces) : MediaTypeRange.parse(accept);
769      this.accepts = accept == null ? new MediaType[] {this.produces} : MediaType.forStrings(StringUtils.split(accept, ','));
770   }
771
772   @Override /* Context */
773   public SerializerBuilder builder() {
774      return null;
775   }
776
777   //-----------------------------------------------------------------------------------------------------------------
778   // Abstract methods
779   //-----------------------------------------------------------------------------------------------------------------
780
781   /**
782    * Returns <jk>true</jk> if this serializer subclasses from {@link WriterSerializer}.
783    *
784    * @return <jk>true</jk> if this serializer subclasses from {@link WriterSerializer}.
785    */
786   public boolean isWriterSerializer() {
787      return true;
788   }
789
790   /**
791    * Create the session object used for actual serialization of objects.
792    *
793    * @param args
794    *    Runtime arguments.
795    *    These specify session-level information such as locale and URI context.
796    *    It also include session-level properties that override the properties defined on the bean and serializer
797    *    contexts.
798    * @return
799    *    The new session object.
800    */
801   public abstract SerializerSession createSession(SerializerSessionArgs args);
802
803
804   //-----------------------------------------------------------------------------------------------------------------
805   // Convenience methods
806   //-----------------------------------------------------------------------------------------------------------------
807
808   @Override /* Context */
809   public SerializerSession createSession() {
810      return createSession(createDefaultSessionArgs());
811   }
812
813   @Override /* Context */
814   public final SerializerSessionArgs createDefaultSessionArgs() {
815      return new SerializerSessionArgs().mediaType(getResponseContentType());
816   }
817
818   /**
819    * Serializes a POJO to the specified output stream or writer.
820    *
821    * <p>
822    * Equivalent to calling <code>serializer.createSession().serialize(o, output);</code>
823    *
824    * @param o The object to serialize.
825    * @param output
826    *    The output object.
827    *    <br>Character-based serializers can handle the following output class types:
828    *    <ul>
829    *       <li>{@link Writer}
830    *       <li>{@link OutputStream} - Output will be written as UTF-8 encoded stream.
831    *       <li>{@link File} - Output will be written as system-default encoded stream.
832    *       <li>{@link StringBuilder} - Output will be written to the specified string builder.
833    *    </ul>
834    *    <br>Stream-based serializers can handle the following output class types:
835    *    <ul>
836    *       <li>{@link OutputStream}
837    *       <li>{@link File}
838    *    </ul>
839    * @throws SerializeException If a problem occurred trying to convert the output.
840    */
841   public final void serialize(Object o, Object output) throws SerializeException {
842      createSession().serialize(o, output);
843   }
844
845   /**
846    * Shortcut method for serializing objects directly to either a <code>String</code> or <code><jk>byte</jk>[]</code>
847    * depending on the serializer type.
848    *
849    * @param o The object to serialize.
850    * @return
851    *    The serialized object.
852    *    <br>Character-based serializers will return a <code>String</code>
853    *    <br>Stream-based serializers will return a <code><jk>byte</jk>[]</code>
854    * @throws SerializeException If a problem occurred trying to convert the output.
855    */
856   public Object serialize(Object o) throws SerializeException {
857      return createSession().serialize(o);
858   }
859
860   /**
861    * Convenience method for serializing an object to a String.
862    *
863    * <p>
864    * For writer-based serializers, this is identical to calling {@link #serialize(Object)}.
865    * <br>For stream-based serializers, this converts the returned byte array to a string based on
866    * the {@link OutputStreamSerializer#OSSERIALIZER_binaryFormat} setting.
867    *
868    * @param o The object to serialize.
869    * @return The output serialized to a string.
870    * @throws SerializeException If a problem occurred trying to convert the output.
871    */
872   public final String serializeToString(Object o) throws SerializeException {
873      return createSession().serializeToString(o);
874   }
875
876   //-----------------------------------------------------------------------------------------------------------------
877   // Other methods
878   //-----------------------------------------------------------------------------------------------------------------
879
880   /**
881    * Returns the media types handled based on the value of the <code>accept</code> parameter passed into the constructor.
882    *
883    * <p>
884    * Note that the order of these ranges are from high to low q-value.
885    *
886    * @return The list of media types.  Never <jk>null</jk>.
887    */
888   public final MediaTypeRange[] getMediaTypeRanges() {
889      return accept;
890   }
891
892   /**
893    * Returns the first entry in the <code>accept</code> parameter passed into the constructor.
894    *
895    * <p>
896    * This signifies the 'primary' media type for this serializer.
897    *
898    * @return The media type.  Never <jk>null</jk>.
899    */
900   public final MediaType getPrimaryMediaType() {
901      return accepts[0];
902   }
903
904   /**
905    * Returns the media types handled based on the value of the <code>accept</code> parameter passed into the constructor.
906    *
907    * <p>
908    * The order of the media types are the same as those in the <code>accept</code> parameter.
909    *
910    * @return The list of media types.  Never <jk>null</jk>.
911    */
912   public final MediaType[] getAcceptMediaTypes() {
913      return accepts;
914   }
915
916   /**
917    * Optional method that returns the response <code>Content-Type</code> for this serializer if it is different from
918    * the matched media type.
919    *
920    * <p>
921    * This method is specified to override the content type for this serializer.
922    * For example, the {@link org.apache.juneau.json.SimpleJsonSerializer} class returns that it handles media type
923    * <js>"text/json+simple"</js>, but returns <js>"text/json"</js> as the actual content type.
924    * This allows clients to request specific 'flavors' of content using specialized <code>Accept</code> header values.
925    *
926    * <p>
927    * This method is typically meaningless if the serializer is being used stand-alone (i.e. outside of a REST server
928    * or client).
929    *
930    * @return The response content type.  If <jk>null</jk>, then the matched media type is used.
931    */
932   public final MediaType getResponseContentType() {
933      return produces;
934   }
935
936   //-----------------------------------------------------------------------------------------------------------------
937   // Properties
938   //-----------------------------------------------------------------------------------------------------------------
939
940   /**
941    * Configuration property:  Add <js>"_type"</js> properties when needed.
942    *
943    * @see #SERIALIZER_addBeanTypes
944    * @return
945    *    <jk>true</jk> if <js>"_type"</js> properties added to beans if their type cannot be inferred
946    *    through reflection.
947    */
948   protected boolean isAddBeanTypes() {
949      return addBeanTypes;
950   }
951
952   /**
953    * Configuration property:  Trim null bean property values.
954    *
955    * @see #SERIALIZER_trimNullProperties
956    * @return
957    *    <jk>true</jk> if null bean values are not serialized to the output.
958    */
959   protected final boolean isTrimNullProperties() {
960      return trimNullProperties;
961   }
962
963   /**
964    * Configuration property:  Trim empty lists and arrays.
965    *
966    * @see #SERIALIZER_trimEmptyCollections
967    * @return
968    *    <jk>true</jk> if empty lists and arrays are not serialized to the output.
969    */
970   protected final boolean isTrimEmptyCollections() {
971      return trimEmptyCollections;
972   }
973
974   /**
975    * Configuration property:  Trim empty maps.
976    *
977    * @see #SERIALIZER_trimEmptyMaps
978    * @return
979    *    <jk>true</jk> if empty map values are not serialized to the output.
980    */
981   protected final boolean isTrimEmptyMaps() {
982      return trimEmptyMaps;
983   }
984
985   /**
986    * Configuration property:  Trim strings.
987    *
988    * @see #SERIALIZER_trimStrings
989    * @return
990    *    <jk>true</jk> if string values will be trimmed of whitespace using {@link String#trim()} before being serialized.
991    */
992   protected final boolean isTrimStrings() {
993      return trimStrings;
994   }
995
996   /**
997    * Configuration property:  Sort arrays and collections alphabetically.
998    *
999    * @see #SERIALIZER_sortCollections
1000    * @return
1001    *    <jk>true</jk> if arrays and collections are copied and sorted before serialization.
1002    */
1003   protected final boolean isSortCollections() {
1004      return sortCollections;
1005   }
1006
1007   /**
1008    * Configuration property:  Sort maps alphabetically.
1009    *
1010    * @see #SERIALIZER_sortMaps
1011    * @return
1012    *    <jk>true</jk> if maps are copied and sorted before serialization.
1013    */
1014   protected final boolean isSortMaps() {
1015      return sortMaps;
1016   }
1017
1018   /**
1019    * Configuration property:  Add type attribute to root nodes.
1020    *
1021    * @see #SERIALIZER_addRootType
1022    * @return
1023    *    <jk>true</jk> if type property should be added to root node.
1024    */
1025   protected final boolean isAddRootType() {
1026      return addRootType;
1027   }
1028
1029   /**
1030    * Configuration property:  URI context bean.
1031    *
1032    * @see #SERIALIZER_uriContext
1033    * @return
1034    *    Bean used for resolution of URIs to absolute or root-relative form.
1035    */
1036   protected final UriContext getUriContext() {
1037      return uriContext;
1038   }
1039
1040   /**
1041    * Configuration property:  URI resolution.
1042    *
1043    * @see #SERIALIZER_uriResolution
1044    * @return
1045    *    Defines the resolution level for URIs when serializing URIs.
1046    */
1047   protected final UriResolution getUriResolution() {
1048      return uriResolution;
1049   }
1050
1051   /**
1052    * Configuration property:  URI relativity.
1053    *
1054    * @see #SERIALIZER_uriRelativity
1055    * @return
1056    *    Defines what relative URIs are relative to when serializing any of the following:
1057    */
1058   protected final UriRelativity getUriRelativity() {
1059      return uriRelativity;
1060   }
1061
1062   /**
1063    * Configuration property:  Trim strings.
1064    *
1065    * @see #SERIALIZER_trimStrings
1066    * @return
1067    *    <jk>true</jk> if string values will be trimmed of whitespace using {@link String#trim()} before being serialized.
1068    */
1069   protected final boolean isUseWhitespace() {
1070      return useWhitespace;
1071   }
1072
1073   /**
1074    * Configuration property:  Serializer listener.
1075    *
1076    * @see #SERIALIZER_listener
1077    * @return
1078    *    Class used to listen for errors and warnings that occur during serialization.
1079    */
1080   protected final Class<? extends SerializerListener> getListener() {
1081      return listener;
1082   }
1083
1084   @Override /* Context */
1085   public ObjectMap asMap() {
1086      return super.asMap()
1087         .append("Serializer", new ObjectMap()
1088            .append("addBeanTypes", addBeanTypes)
1089            .append("trimNullProperties", trimNullProperties)
1090            .append("trimEmptyCollections", trimEmptyCollections)
1091            .append("trimEmptyMaps", trimEmptyMaps)
1092            .append("trimStrings", trimStrings)
1093            .append("sortCollections", sortCollections)
1094            .append("sortMaps", sortMaps)
1095            .append("addRootType", addRootType)
1096            .append("uriContext", uriContext)
1097            .append("uriResolution", uriResolution)
1098            .append("uriRelativity", uriRelativity)
1099            .append("listener", listener)
1100         );
1101   }
1102
1103   /**
1104    * @deprecated Use {@link #SERIALIZER_addRootType}.
1105    */
1106   @Deprecated
1107   public static final String SERIALIZER_abridged = SERIALIZER_addRootType;
1108
1109   /**
1110    * @deprecated Use {@link #SERIALIZER_addBeanTypes}.
1111    */
1112   @Deprecated
1113   public static final String SERIALIZER_addBeanTypeProperties = SERIALIZER_addBeanTypes;
1114
1115   /**
1116    * @deprecated Use {@link BeanTraverseContext#BEANTRAVERSE_detectRecursions}.
1117    */
1118   @Deprecated
1119   public static final String SERIALIZER_detectRecursions = BeanTraverseContext.BEANTRAVERSE_detectRecursions;
1120
1121   /**
1122    * @deprecated Use {@link BeanTraverseContext#BEANTRAVERSE_ignoreRecursions}.
1123    */
1124   @Deprecated
1125   public static final String SERIALIZER_ignoreRecursions = BeanTraverseContext.BEANTRAVERSE_ignoreRecursions;
1126
1127   /**
1128    * @deprecated Use {@link BeanTraverseContext#BEANTRAVERSE_initialDepth}.
1129    */
1130   @Deprecated
1131   public static final String SERIALIZER_initialDepth = BeanTraverseContext.BEANTRAVERSE_initialDepth;
1132
1133   /**
1134    * @deprecated Use {@link BeanTraverseContext#BEANTRAVERSE_maxDepth}.
1135    */
1136   @Deprecated
1137   public static final String SERIALIZER_maxDepth = BeanTraverseContext.BEANTRAVERSE_maxDepth;
1138
1139   /**
1140    * @deprecated Use {@link BeanTraverseContext#BEANTRAVERSE_maxDepth}.
1141    */
1142   @Deprecated
1143   public static final String SERIALIZER_maxIndent = BeanTraverseContext.BEANTRAVERSE_maxDepth;
1144
1145   /**
1146    * @deprecated Unused.
1147    */
1148   @Deprecated
1149   public static final String SERIALIZER_quoteChar = PREFIX + "quoteChar.s";
1150
1151   /**
1152    * @deprecated Use {@link #getMediaTypeRanges()}
1153    */
1154   @SuppressWarnings("javadoc")
1155   @Deprecated
1156   public final MediaType[] getMediaTypes() {
1157      return null;
1158   }
1159}