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