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