001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.marshall.serializer;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import java.io.*;
022import java.nio.charset.*;
023
024import org.apache.juneau.commons.collections.*;
025import org.apache.juneau.commons.io.*;
026
027/**
028 * Subclass of {@link SerializerSession} for character-based serializers.
029 *
030 * <h5 class='topic'>Description</h5>
031 *
032 * This class is typically the parent class of all character-based serializers.
033 * <br>It has 1 abstract method to implement...
034 * <ul class='spaced-list'>
035 *    <li>
036 *       {@link #doWrite(SerializerPipe, Object)}
037 * </ul>
038 *
039 * <h5 class='section'>Notes:</h5><ul>
040 *    <li class='warn'>This class is not thread safe and is typically discarded after one use.
041 * </ul>
042 *
043 * <h5 class='section'>See Also:</h5><ul>
044 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SerializersAndParsers">Serializers and Parsers</a>
045 * </ul>
046 */
047@SuppressWarnings({
048   "java:S110", // Deep inheritance inherent to the serializer/parser session hierarchy
049   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
050   "resource" // Internal helpers return Closeables wired into pipe lifecycle; Eclipse JDT @Owning warning is by design.
051})
052public class WriterSerializerSession extends SerializerSession {
053
054   /**
055    * Builder class.
056    */
057   @SuppressWarnings({
058      "java:S119" // 'SELF' (CRTP self-type) is intentional and clearer than a single-letter name.
059   })
060   public abstract static class Builder<SELF extends Builder<SELF>> extends SerializerSession.Builder<SELF> {
061
062      private boolean useWhitespace;
063      private Charset streamCharset;
064      private int maxIndent;
065      private char quoteChar;
066
067      /**
068       * Constructor
069       *
070       * @param ctx The context creating this session.
071       *    <br>Cannot be <jk>null</jk>.
072       */
073      protected Builder(WriterSerializer ctx) {
074         super(reqnn("ctx", ctx));
075         streamCharset = ctx.getStreamCharset();
076         useWhitespace = ctx.useWhitespace;
077         maxIndent = ctx.getMaxIndent();
078         quoteChar = ctx.getQuoteChar();
079      }
080
081      @Override
082      public WriterSerializerSession build() {
083         return new WriterSerializerSession(this);
084      }
085
086      /**
087       * Maximum indentation.
088       *
089       * @param value The new value for this property.
090       * @return This object.
091       */
092      public SELF maxIndent(int value) {
093         maxIndent = value;
094         return self();
095      }
096
097      @Override /* Overridden from Builder */
098      public SELF property(String key, Object value) {
099         if (key == null) {
100            super.property(key, value);
101            return self();
102         }
103         switch (key) {
104            case "streamCharset", "WriterSerializerSession.streamCharset":
105               return streamCharset(cvt(value, Charset.class));
106            case "useWhitespace", "WriterSerializerSession.useWhitespace":
107               return useWhitespace(cvt(value, Boolean.class));
108            case "maxIndent", "WriterSerializerSession.maxIndent":
109               return maxIndent(cvt(value, Integer.class));
110            case "quoteChar", "WriterSerializerSession.quoteChar":
111               return quoteChar(cvt(value, Character.class));
112            default:
113               super.property(key, value);
114               return self();
115         }
116      }
117
118      /**
119       * Quote character.
120       *
121       * @param value The new value for this property.
122       * @return This object.
123       */
124      public SELF quoteChar(char value) {
125         quoteChar = value;
126         return self();
127      }
128
129      /**
130       * Output stream charset.
131       *
132       * <p>
133       * The character set to use when writing to OutputStreams.
134       *
135       * <p>
136       * Used when passing in output streams and byte arrays to {@link WriterSerializer#write(Object, Object)}.
137       *
138       * <p>
139       * If not specified, defaults to UTF-8.
140       *
141       * @param value
142       *    The new property value.
143       *    <br>Can be <jk>null</jk> (value will not be set, defaults to UTF-8).
144       * @return This object.
145       */
146      public SELF streamCharset(Charset value) {
147         if (nn(value))
148            streamCharset = value;
149         return self();
150      }
151
152      /**
153       * Use whitespace.
154       *
155       * <p>
156       * If true, whitespace is added to the output to improve readability.
157       *
158       * @param value
159       *    The new property value.
160       *    <br>Can be <jk>null</jk> (value will not be set, existing value from context will be kept).
161       * @return This object.
162       */
163      public SELF useWhitespace(Boolean value) {
164         if (nn(value))
165            useWhitespace = value;
166         return self();
167      }
168   }
169
170   /**
171    * Concrete default builder leaf for the non-subclassed {@code create()} path (CRTP terminal).
172    */
173   public static final class DefaultBuilder extends Builder<DefaultBuilder> {
174
175      DefaultBuilder(WriterSerializer ctx) {
176         super(ctx);
177      }
178   }
179
180   /**
181    * Creates a new builder for this object.
182    *
183    * @param ctx The context creating this session.
184    *    <br>Cannot be <jk>null</jk>.
185    * @return A new builder.
186    */
187   @SuppressWarnings({
188      "java:S1452" // Builder<?> wildcard return intentional; callers use it to construct session instances polymorphically
189   })
190   public static Builder<?> create(WriterSerializer ctx) {
191      return new DefaultBuilder(reqnn("ctx", ctx));
192   }
193
194   private final boolean useWhitespace;
195   private final Charset streamCharset;
196   private final int maxIndent;
197   private final char quoteChar;
198
199   /**
200    * Constructor.
201    *
202    * @param builder The builder for this object.
203    *    <br>Cannot be <jk>null</jk>.
204    */
205   protected WriterSerializerSession(Builder<?> builder) {
206      super(builder);
207      streamCharset = builder.streamCharset;
208      useWhitespace = builder.useWhitespace;
209      maxIndent = builder.maxIndent;
210      quoteChar = builder.quoteChar;
211   }
212
213   /**
214    * Returns the stream charset defined on this session.
215    *
216    * @return the stream charset defined on this session.
217    */
218   public Charset getStreamCharset() { return streamCharset; }
219
220   @Override /* Overridden from SerializerSession */
221   public final boolean isWriterSerializer() { return true; }
222
223   /**
224    * Convenience method for serializing an object to a <c>String</c>.
225    *
226    * @param o The object to serialize.
227    * @return The output serialized to a string.
228    * @throws SerializeException If a problem occurred trying to convert the output.
229    */
230   @Override /* Overridden from SerializerSession */
231   public final String write(Object o) throws SerializeException {
232      var w = new StringBuilderWriter();
233      try {
234         write(o, w);
235      } catch (IOException e) {
236         throw new SerializeException(e); // Shouldn't happen.
237      }
238      return w.toString();
239   }
240
241   @Override /* Overridden from SerializerSession */
242   public final String writeToString(Object o) throws SerializeException {
243      return write(o);
244   }
245
246   @Override /* Overridden from SerializerSession */
247   protected SerializerPipe createPipe(Object output) {
248      return new SerializerPipe(output, streamCharset);
249   }
250
251   /**
252    * Maximum indentation.
253    *
254    * @see WriterSerializer.Builder#maxIndent(int)
255    * @return
256    *    The maximum indentation level in the serialized document.
257    */
258   protected final int getMaxIndent() { return maxIndent; }
259
260   /**
261    * Quote character.
262    *
263    * @see WriterSerializer.Builder#quoteChar(char)
264    * @return
265    *    The character used for quoting attributes and values.
266    */
267   protected char getQuoteChar() { return quoteChar; }
268
269   /**
270    * Use whitespace.
271    *
272    * @see WriterSerializer.Builder#useWhitespace()
273    * @return
274    *    <jk>true</jk> if whitespace is added to the output to improve readability.
275    */
276   protected final boolean isUseWhitespace() { return useWhitespace; }
277
278   @Override /* Overridden from SerializerSession */
279   protected FluentMap<String,Object> properties() {
280      return super.properties()
281         .a("streamCharset", streamCharset)
282         .a("useWhitespace", useWhitespace)
283         .a("maxIndent", maxIndent)
284         .a("quoteChar", quoteChar);
285   }
286}