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.nio.charset.*;
016
017import org.apache.juneau.*;
018import org.apache.juneau.annotation.*;
019import org.apache.juneau.collections.*;
020import org.apache.juneau.internal.*;
021import org.apache.juneau.utils.*;
022
023/**
024 * Subclass of {@link Serializer} for character-based serializers.
025 */
026@ConfigurableContext
027public abstract class WriterSerializer extends Serializer {
028
029   //-------------------------------------------------------------------------------------------------------------------
030   // Configurable properties
031   //-------------------------------------------------------------------------------------------------------------------
032
033   static final String PREFIX = "WriterSerializer";
034
035   /**
036    * Configuration property:  File charset.
037    *
038    * <h5 class='section'>Property:</h5>
039    * <ul class='spaced-list'>
040    *    <li><b>ID:</b>  {@link org.apache.juneau.serializer.WriterSerializer#WSERIALIZER_fileCharset WSERIALIZER_fileCharset}
041    *    <li><b>Name:</b>  <js>"WriterSerializer.fileCharset.s"</js>
042    *    <li><b>Data type:</b>  <c>String</c>
043    *    <li><b>System property:</b>  <c>WriterSerializer.fileCharset</c>
044    *    <li><b>Environment variable:</b>  <c>WRITERSERIALIZER_FILECHARSET</c>
045    *    <li><b>Default:</b>  <js>"DEFAULT"</js>
046    *    <li><b>Session property:</b>  <jk>false</jk>
047    *    <li><b>Annotations:</b>
048    *       <ul>
049    *          <li class='ja'>{@link org.apache.juneau.serializer.annotation.SerializerConfig#fileCharset()}
050    *       </ul>
051    *    <li><b>Methods:</b>
052    *       <ul>
053    *          <li class='jm'>{@link org.apache.juneau.serializer.WriterSerializerBuilder#fileCharset(Charset)}
054    *       </ul>
055    * </ul>
056    *
057    * <h5 class='section'>Description:</h5>
058    *
059    * <p>
060    * The character set to use for writing <c>Files</c> to the file system.
061    *
062    * <p>
063    * Used when passing in files to {@link Serializer#serialize(Object, Object)}.
064    *
065    * <p>
066    * <js>"DEFAULT"</js> can be used to indicate the JVM default file system charset.
067    *
068    * <h5 class='section'>Example:</h5>
069    * <p class='bcode w800'>
070    *    <jc>// Create a serializer that writes UTF-8 files.</jc>
071    *    WriterSerializer s = JsonSerializer
072    *       .<jsm>create</jsm>()
073    *       .fileCharset(Charset.<jsm>forName</jsm>(<js>"UTF-8"</js>))
074    *       .build();
075    *
076    *    <jc>// Same, but use property.</jc>
077    *    WriterSerializer s = JsonSerializer
078    *       .<jsm>create</jsm>()
079    *       .set(<jsf>WSERIALIZER_fileCharset</jsf>, <js>"UTF-8"</js>)
080    *       .build();
081    *
082    *    <jc>// Use it to read a UTF-8 encoded file.</jc>
083    *    s.serialize(<jk>new</jk> File(<js>"MyBean.txt"</js>), myBean);
084    * </p>
085    */
086   public static final String WSERIALIZER_fileCharset = PREFIX + ".fileCharset.s";
087
088   /**
089    * Configuration property:  Maximum indentation.
090    *
091    * <h5 class='section'>Property:</h5>
092    * <ul class='spaced-list'>
093    *    <li><b>ID:</b>  {@link org.apache.juneau.serializer.WriterSerializer#WSERIALIZER_maxIndent WSERIALIZER_maxIndent}
094    *    <li><b>Name:</b>  <js>"WriterSerializer.maxIndent.i"</js>
095    *    <li><b>Data type:</b>  <jk>int</jk>
096    *    <li><b>System property:</b>  <c>WriterSerializer.maxIndent</c>
097    *    <li><b>Environment variable:</b>  <c>WRITERSERIALIZER_MAXINDENT</c>
098    *    <li><b>Default:</b>  <c>100</c>
099    *    <li><b>Session property:</b>  <jk>false</jk>
100    *    <li><b>Annotations:</b>
101    *       <ul>
102    *          <li class='ja'>{@link org.apache.juneau.serializer.annotation.SerializerConfig#maxIndent()}
103    *       </ul>
104    *    <li><b>Methods:</b>
105    *       <ul>
106    *          <li class='jm'>{@link org.apache.juneau.serializer.WriterSerializerBuilder#maxIndent(int)}
107    *       </ul>
108    * </ul>
109    *
110    * <h5 class='section'>Description:</h5>
111    *
112    * <p>
113    * Specifies the maximum indentation level in the serialized document.
114    *
115    * <ul class='notes'>
116    *    <li>This setting does not apply to the RDF serializers.
117    * </ul>
118    *
119    * <h5 class='section'>Example:</h5>
120    * <p class='bcode w800'>
121    *    <jc>// Create a serializer that indents a maximum of 20 tabs.</jc>
122    *    WriterSerializer s = JsonSerializer
123    *       .<jsm>create</jsm>()
124    *       .ws()  <jc>// Enable whitespace</jc>
125    *       .maxIndent(20)
126    *       .build();
127    *
128    *    <jc>// Same, but use property.</jc>
129    *    WriterSerializer s = JsonSerializer
130    *       .<jsm>create</jsm>()
131    *       .set(<jsf>WSERIALIZER_maxIndent</jsf>, 20)
132    *       .build();
133    * </p>
134    */
135   public static final String WSERIALIZER_maxIndent = PREFIX + ".maxIndent.i";
136
137   /**
138    * Configuration property:  Quote character.
139    *
140    * <h5 class='section'>Property:</h5>
141    * <ul class='spaced-list'>
142    *    <li><b>ID:</b>  {@link org.apache.juneau.serializer.WriterSerializer#WSERIALIZER_quoteChar WSERIALIZER_quoteChar}
143    *    <li><b>Name:</b>  <js>"WriterSerializer.quoteChar.s"</js>
144    *    <li><b>Data type:</b>  <c>String</c>
145    *    <li><b>System property:</b>  <c>WriterSerializer.quoteChar</c>
146    *    <li><b>Environment variable:</b>  <c>WRITERSERIALIZER_QUOTECHAR</c>
147    *    <li><b>Default:</b>  <js>"\""</js>
148    *    <li><b>Session property:</b>  <jk>false</jk>
149    *    <li><b>Annotations:</b>
150    *       <ul>
151    *          <li class='ja'>{@link org.apache.juneau.serializer.annotation.SerializerConfig#quoteChar()}
152    *       </ul>
153    *    <li><b>Methods:</b>
154    *       <ul>
155    *          <li class='jm'>{@link org.apache.juneau.serializer.WriterSerializerBuilder#quoteChar(char)}
156    *          <li class='jm'>{@link org.apache.juneau.serializer.WriterSerializerBuilder#sq()}
157    *       </ul>
158    * </ul>
159    *
160    * <h5 class='section'>Description:</h5>
161    *
162    * <p>
163    * Specifies the character to use for quoting attributes and values.
164    *
165    * <ul class='notes'>
166    *    <li>This setting does not apply to the RDF serializers.
167    * </ul>
168    *
169    * <h5 class='section'>Example:</h5>
170    * <p class='bcode w800'>
171    *    <jc>// Create a serializer that uses single quotes.</jc>
172    *    WriterSerializer s = JsonSerializer
173    *       .<jsm>create</jsm>()
174    *       .sq()
175    *       .build();
176    *
177    *    <jc>// Same, but use property.</jc>
178    *    WriterSerializer s = JsonSerializer
179    *       .<jsm>create</jsm>()
180    *       .set(<jsf>WSERIALIZER_quoteChar</jsf>, <js>'\''</js>)
181    *       .build();
182    *
183    *    <jc>// A bean with a single property</jc>
184    *    <jk>public class</jk> MyBean {
185    *       <jk>public</jk> String <jf>foo</jf> = <js>"bar"</js>;
186    *    }
187    *
188    *    <jc>// Produces {'foo':'bar'}</jc>
189    *    String json = s.toString(<jk>new</jk> MyBean());
190    * </p>
191    */
192   public static final String WSERIALIZER_quoteChar = PREFIX + ".quoteChar.s";
193
194   /**
195    * Configuration property:  Output stream charset.
196    *
197    * <h5 class='section'>Property:</h5>
198    * <ul class='spaced-list'>
199    *    <li><b>ID:</b>  {@link org.apache.juneau.serializer.WriterSerializer#WSERIALIZER_streamCharset WSERIALIZER_streamCharset}
200    *    <li><b>Name:</b>  <js>"WriterSerializer.streamCharset.s"</js>
201    *    <li><b>Data type:</b>  <c>String</c>
202    *    <li><b>System property:</b>  <c>WriterSerializer.streamCharset</c>
203    *    <li><b>Environment variable:</b>  <c>WRITERSERIALIZER_STREAMCHARSET</c>
204    *    <li><b>Default:</b>  <js>"UTF-8"</js>
205    *    <li><b>Session property:</b>  <jk>false</jk>
206    *    <li><b>Annotations:</b>
207    *       <ul>
208    *          <li class='ja'>{@link org.apache.juneau.serializer.annotation.SerializerConfig#streamCharset()}
209    *       </ul>
210    *    <li><b>Methods:</b>
211    *       <ul>
212    *          <li class='jm'>{@link org.apache.juneau.serializer.WriterSerializerBuilder#streamCharset(Charset)}
213    *       </ul>
214    * </ul>
215    *
216    * <h5 class='section'>Description:</h5>
217    *
218    * <p>
219    * The character set to use when writing to <c>OutputStreams</c>.
220    *
221    * <p>
222    * Used when passing in output streams and byte arrays to {@link WriterSerializer#serialize(Object, Object)}.
223    *
224    * <h5 class='section'>Example:</h5>
225    * <p class='bcode w800'>
226    *    <jc>// Create a serializer that writes UTF-8 files.</jc>
227    *    WriterSerializer s = JsonSerializer
228    *       .<jsm>create</jsm>()
229    *       .streamCharset(Charset.<jsm>forName</jsm>(<js>"UTF-8"</js>))
230    *       .build();
231    *
232    *    <jc>// Same, but use property.</jc>
233    *    WriterSerializer s = JsonSerializer
234    *       .<jsm>create</jsm>()
235    *       .set(<jsf>WSERIALIZER_streamCharset</jsf>, <js>"UTF-8"</js>)
236    *       .build();
237    *
238    *    <jc>// Use it to write to a UTF-8 encoded output stream.</jc>
239    *    s.serializer(<jk>new</jk> FileOutputStreamStream(<js>"MyBean.txt"</js>), myBean);
240    * </p>
241    */
242   public static final String WSERIALIZER_streamCharset = PREFIX + ".streamCharset.s";
243
244   /**
245    * Configuration property:  Use whitespace.
246    *
247    * <h5 class='section'>Property:</h5>
248    * <ul class='spaced-list'>
249    *    <li><b>ID:</b>  {@link org.apache.juneau.serializer.WriterSerializer#WSERIALIZER_useWhitespace WSERIALIZER_useWhitespace}
250    *    <li><b>Name:</b>  <js>"WriterSerializer.useWhitespace.b"</js>
251    *    <li><b>Data type:</b>  <jk>boolean</jk>
252    *    <li><b>System property:</b>  <c>WriterSerializer.useWhitespace</c>
253    *    <li><b>Environment variable:</b>  <c>WRITERSERIALIZER_USEWHITESPACE</c>
254    *    <li><b>Default:</b>  <jk>false</jk>
255    *    <li><b>Session property:</b>  <jk>true</jk>
256    *    <li><b>Annotations:</b>
257    *       <ul>
258    *          <li class='ja'>{@link org.apache.juneau.serializer.annotation.SerializerConfig#useWhitespace()}
259    *       </ul>
260    *    <li><b>Methods:</b>
261    *       <ul>
262    *          <li class='jm'>{@link org.apache.juneau.serializer.WriterSerializerBuilder#useWhitespace()}
263    *          <li class='jm'>{@link org.apache.juneau.serializer.WriterSerializerBuilder#ws()}
264    *       </ul>
265    * </ul>
266    *
267    * <h5 class='section'>Description:</h5>
268    *
269    * <p>
270    * When enabled, whitespace is added to the output to improve readability.
271    *
272    * <h5 class='section'>Example:</h5>
273    * <p class='bcode w800'>
274    *    <jc>// Create a serializer with whitespace enabled.</jc>
275    *    WriterSerializer s = JsonSerializer
276    *       .<jsm>create</jsm>()
277    *       .ws()
278    *       .build();
279    *
280    *    <jc>// Same, but use property.</jc>
281    *    WriterSerializer s = JsonSerializer
282    *       .<jsm>create</jsm>()
283    *       .set(<jsf>WSERIALIZER_useWhitespace</jsf>, <jk>true</jk>)
284    *       .build();
285    *
286    *    <jc>// A bean with a single property</jc>
287    *    <jk>public class</jk> MyBean {
288    *       <jk>public</jk> String <jf>foo</jf> = <js>"bar"</js>;
289    *    }
290    *
291    *    <jc>// Produces "\{\n\t"foo": "bar"\n\}\n"</jc>
292    *    String json = s.serialize(<jk>new</jk> MyBean());
293    * </p>
294    */
295   public static final String WSERIALIZER_useWhitespace = PREFIX + ".useWhitespace.b";
296
297   static final WriterSerializer DEFAULT = new WriterSerializer(PropertyStore.create().build(), "", "") {
298      @Override
299      public WriterSerializerSession createSession(SerializerSessionArgs args) {
300         throw new NoSuchMethodError();
301      }
302   };
303
304   //-------------------------------------------------------------------------------------------------------------------
305   // Instance
306   //-------------------------------------------------------------------------------------------------------------------
307
308   private final Charset fileCharset;
309   private final int maxIndent;
310   private final char quoteChar;
311   private final Charset streamCharset;
312   private final boolean useWhitespace;
313
314   /**
315    * Constructor.
316    *
317    * @param ps
318    *    The property store containing all the settings for this object.
319    * @param produces
320    *    The media type that this serializer produces.
321    * @param accept
322    *    The accept media types that the serializer can handle.
323    *    <p>
324    *    Can contain meta-characters per the <c>media-type</c> specification of {@doc ExtRFC2616.section14.1}
325    *    <p>
326    *    If empty, then assumes the only media type supported is <c>produces</c>.
327    *    <p>
328    *    For example, if this serializer produces <js>"application/json"</js> but should handle media types of
329    *    <js>"application/json"</js> and <js>"text/json"</js>, then the arguments should be:
330    *    <p class='bcode w800'>
331    *    <jk>super</jk>(ps, <js>"application/json"</js>, <js>"application/json,text/json"</js>);
332    *    </p>
333    *    <br>...or...
334    *    <p class='bcode w800'>
335    *    <jk>super</jk>(ps, <js>"application/json"</js>, <js>"*&#8203;/json"</js>);
336    *    </p>
337    * <p>
338    * The accept value can also contain q-values.
339    */
340   protected WriterSerializer(PropertyStore ps, String produces, String accept) {
341      super(ps, produces, accept);
342
343      maxIndent = getIntegerProperty(WSERIALIZER_maxIndent, 100);
344      quoteChar = getStringProperty(WSERIALIZER_quoteChar, "\"").charAt(0);
345      streamCharset = getProperty(WSERIALIZER_streamCharset, Charset.class, IOUtils.UTF8);
346      fileCharset = getProperty(WSERIALIZER_fileCharset, Charset.class, Charset.defaultCharset());
347      useWhitespace = getBooleanProperty(WSERIALIZER_useWhitespace, false);
348   }
349
350   //-----------------------------------------------------------------------------------------------------------------
351   // Abstract methods
352   //-----------------------------------------------------------------------------------------------------------------
353
354   @Override /* SerializerSession */
355   public abstract WriterSerializerSession createSession(SerializerSessionArgs args);
356
357   //-----------------------------------------------------------------------------------------------------------------
358   // Other methods
359   //-----------------------------------------------------------------------------------------------------------------
360
361   @Override /* Context */
362   public WriterSerializerSession createSession() {
363      return createSession(createDefaultSessionArgs());
364   }
365
366   @Override /* Serializer */
367   public final boolean isWriterSerializer() {
368      return true;
369   }
370
371   /**
372    * Convenience method for serializing an object to a <c>String</c>.
373    *
374    * @param o The object to serialize.
375    * @return The output serialized to a string.
376    * @throws SerializeException If a problem occurred trying to convert the output.
377    */
378   @Override /* Serializer */
379   public final String serialize(Object o) throws SerializeException {
380      return createSession(createDefaultSessionArgs()).serialize(o);
381   }
382
383   /**
384    * Identical to {@link #serialize(Object)} except throws a {@link RuntimeException} instead of a {@link SerializeException}.
385    *
386    * <p>
387    * This is typically good enough for debugging purposes.
388    *
389    * @param o The object to serialize.
390    * @return The serialized object.
391    */
392   public final String toString(Object o) {
393      try {
394         return serialize(o);
395      } catch (Exception e) {
396         throw new RuntimeException(e);
397      }
398   }
399
400   /**
401    * Wraps the specified object inside a {@link StringObject}.
402    *
403    * @param o The object to wrap.
404    * @return The wrapped object.
405    */
406   public final StringObject toStringObject(Object o) {
407      return new StringObject(this, o);
408   }
409
410   /**
411    * Convenience method for serializing an object and sending it to STDOUT.
412    *
413    * @param o The object to serialize.
414    * @return This object (for method chaining).
415    */
416   public final WriterSerializer println(Object o) {
417      System.out.println(toString(o));  // NOT DEBUG
418      return this;
419   }
420
421   //-----------------------------------------------------------------------------------------------------------------
422   // Properties
423   //-----------------------------------------------------------------------------------------------------------------
424
425   /**
426    * File charset.
427    *
428    * @see #WSERIALIZER_fileCharset
429    * @return
430    *    The character set to use when writing to <c>Files</c> on the file system.
431    */
432   protected final Charset getFileCharset() {
433      return fileCharset;
434   }
435
436   /**
437    * Maximum indentation.
438    *
439    * @see #WSERIALIZER_maxIndent
440    * @return
441    *    The maximum indentation level in the serialized document.
442    */
443   protected final int getMaxIndent() {
444      return maxIndent;
445   }
446
447   /**
448    * Quote character.
449    *
450    * @see #WSERIALIZER_quoteChar
451    * @return
452    *    The character used for quoting attributes and values.
453    */
454   protected char getQuoteChar() {
455      return quoteChar;
456   }
457
458   /**
459    * Output stream charset.
460    *
461    * @see #WSERIALIZER_streamCharset
462    * @return
463    *    The character set to use when writing to <c>OutputStreams</c> and byte arrays.
464    */
465   protected final Charset getStreamCharset() {
466      return streamCharset;
467   }
468
469   /**
470    * Trim strings.
471    *
472    * @see #WSERIALIZER_useWhitespace
473    * @return
474    *    When enabled, whitespace is added to the output to improve readability.
475    */
476   protected final boolean isUseWhitespace() {
477      return useWhitespace;
478   }
479
480   //-----------------------------------------------------------------------------------------------------------------
481   // Other methods
482   //-----------------------------------------------------------------------------------------------------------------
483
484   @Override /* Context */
485   public OMap toMap() {
486      return super.toMap()
487         .a("WriterSerializer", new DefaultFilteringOMap()
488            .a("fileCharset", fileCharset)
489            .a("maxIndent", maxIndent)
490            .a("quoteChar", quoteChar)
491            .a("streamCharset", streamCharset)
492            .a("useWhitespace", useWhitespace)
493         );
494   }
495}