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