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.net.*;
023import java.util.*;
024
025import org.apache.juneau.marshall.*;
026
027/**
028 * Simple wrapper around a standard {@link Writer} with additional methods.
029 *
030 * <p>
031 * Modeled after the Java ProcessBuilder class so that you can chain commands to reduce the need for string
032 * concatenation for performance reasons.
033 *
034 * <h5 class='section'>Example:</h5>
035 * <p class='bjava'>
036 *    <jv>writer</jv>.append(<js>"foo"</js>).nl().i(5).append(<js>"bar"</js>);
037 * </p>
038 *
039 * <h5 class='section'>See Also:</h5><ul>
040 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SerializersAndParsers">Serializers and Parsers</a>
041 * </ul>
042 */
043@SuppressWarnings({
044   "java:S119", // 'SELF' (CRTP self-type) is intentional and clearer than a single-letter name.
045   "resource" // SerializerWriter manages Closeable resources
046})
047public abstract class SerializerWriter<SELF extends SerializerWriter<SELF>> extends Writer {
048
049   @SuppressWarnings({
050      "unchecked" // The (SELF) this cast in self() is the standard CRTP self-type cast.
051   })
052   protected final SELF self() {
053      return (SELF) this;
054   }
055
056   /** The underlying writer. */
057   protected final Writer out;
058
059   /** Use-whitespace flag. */
060   protected final boolean useWhitespace;
061
062   /** Max indentation levels. */
063   protected final int maxIndent;
064
065   /** Trim strings flag. */
066   protected final boolean trimStrings;
067
068   /** The quote character being used by this writer. */
069   protected final char quoteChar;
070
071   /** The URI resolver of the request. */
072   protected final UriResolver uriResolver;
073
074   /**
075    * Copy Constructor
076    *
077    * @param w Writer being copied.
078    *    <br>Must not be <jk>null</jk>.
079    */
080   protected SerializerWriter(SerializerWriter<?> w) {
081      this.out = w.out;
082      this.useWhitespace = w.useWhitespace;
083      this.maxIndent = w.maxIndent;
084      this.trimStrings = w.trimStrings;
085      this.quoteChar = w.quoteChar;
086      this.uriResolver = w.uriResolver;
087   }
088
089   /**
090    * @param out The writer being wrapped.
091    *    <br>Must not be <jk>null</jk>.
092    * @param useWhitespace
093    *    If <jk>true</jk>, calling {@link #cr(int)} will create an indentation and calling {@link #s()} will write a
094    *    space character.
095    * @param maxIndent The maximum indentation level.
096    * @param trimStrings If <jk>true</jk>, strings should be trimmed before they're serialized.
097    * @param quoteChar The character to write when {@link #q()} is called.
098    * @param uriResolver The URI resolver for resolving URIs to absolute or root-relative form.
099    */
100   protected SerializerWriter(Writer out, boolean useWhitespace, int maxIndent, boolean trimStrings, char quoteChar, UriResolver uriResolver) {
101      this.out = out;
102      this.useWhitespace = useWhitespace;
103      this.maxIndent = maxIndent;
104      this.trimStrings = trimStrings;
105      this.quoteChar = quoteChar;
106      this.uriResolver = uriResolver;
107   }
108
109   @Override /* Overridden from Writer */
110   public SELF append(char c) {
111      try {
112         out.write(c);
113      } catch (IOException e) {
114         throw new SerializeException(e);
115      }
116      return self();
117   }
118
119   /**
120    * Appends the specified characters to this writer.
121    *
122    * @param value The characters to append to this writer.
123    *    <br>Must not be <jk>null</jk>.
124    * @return This object.
125    */
126   public SELF append(char[] value) {
127      for (var c : value)
128         w(c);
129      return self();
130   }
131
132   /**
133    * Writes an indent (if the {@code useWhitespace} setting is enabled), followed by text.
134    *
135    * @param indent The number of tabs to indent.
136    * @param value The character to write.
137    * @return This object.
138    */
139   public SELF append(int indent, char value) {
140      return i(indent).w(value);
141   }
142
143   /**
144    * Writes an indent (if the {@code useWhitespace} setting is enabled), followed by text.
145    *
146    * @param indent The number of tabs to indent.
147    * @param value The text to write.
148    * @return This object.
149    */
150   public SELF append(int indent, String value) {
151      return append(indent, false, value);
152   }
153
154   /**
155    * Writes the string representation of the specified object to the writer.
156    *
157    * @param value The object to write.
158    *    <br>Must not be <jk>null</jk>.
159    * @return This object.
160    */
161   public SELF append(Object value) {
162      w(value == null ? null : value.toString());
163      return self();
164   }
165
166   /**
167    * Writes the specified text to the writer if it isn't <jk>null</jk>.
168    *
169    * @param value The text to write.
170    *    <br>Can be <jk>null</jk> (ignored).
171    * @return This object.
172    */
173   public SELF append(String value) {
174      if (nn(value))
175         w(value);
176      return self();
177   }
178
179   /**
180    * Writes the specified text to the writer if b is true.
181    *
182    * @param flag Boolean flag.
183    * @param value The text to write.
184    * @return This object.
185    */
186   public SELF appendIf(boolean flag, char value) {
187      if (flag)
188         w(value);
189      return self();
190   }
191
192   /**
193    * Writes the specified text to the writer if b is true.
194    *
195    * @param flag Boolean flag.
196    * @param value The text to write.
197    * @return This object.
198    */
199   public SELF appendIf(boolean flag, String value) {
200      if (flag)
201         w(value);
202      return self();
203   }
204
205   /**
206    * Writes an indent (if the {@code useWhitespace} setting is enabled), followed by text, followed by a newline
207    * (if the {@code useWhitespace} setting is enabled).
208    *
209    * @param indent The number of tabs to indent.
210    * @param value The text to write.
211    * @return This object.
212    */
213   public SELF appendln(int indent, String value) {
214      return append(indent, true, value);
215   }
216
217   /**
218    * Writes the specified text followed by a newline (if the {@code useWhitespace} setting is enabled).
219    *
220    * @param value The text to write.
221    * @return This object.
222    */
223   public SELF appendln(String value) {
224      return append(0, true, value);
225   }
226
227   /**
228    * Appends the specified object as a URI.
229    *
230    * <p>
231    * Object is converted to a <c>String</c> using <c>toString()</c>, so this will work on {@link URL} or
232    * {@link URI} objects, or any other type that returns a URI via it's <c>toString()</c> method.
233    *
234    * <p>
235    * The URI is resolved based on the {@link Serializer.Builder#uriRelativity(UriRelativity)} and
236    * {@link Serializer.Builder#uriResolution(UriResolution)} settings and the {@link UriContext} that's part of the
237    * session.
238    *
239    * @param value The URI to serialize.
240    *    <br>Can be <jk>null</jk>.  The effect is implementation-dependent — see {@link UriResolver#append(Appendable, Object)}.
241    * @return This object.
242    */
243   public SELF appendUri(Object value) {
244      uriResolver.append(this, value);
245      return self();
246   }
247
248   @Override /* Overridden from Writer */
249   public void close() throws IOException {
250      out.close();
251   }
252
253   /**
254    * Performs a carriage return.
255    *
256    * <p>
257    * Adds a newline and the specified number of tabs (if the {@code useWhitespace} setting is enabled) to the output.
258    *
259    * @param depth The indentation.
260    * @return This object.
261    */
262   public SELF cr(int depth) {
263      if (useWhitespace && depth <= maxIndent)
264         return nl(depth).i(depth);
265      return self();
266   }
267
268   /**
269    * Performs a carriage return at the end of a line.
270    *
271    * <p>
272    * Adds a newline and the specified number of tabs (if the {@code useWhitespace} setting is enabled) to the output.
273    *
274    * @param depth The indentation.
275    * @return This object.
276    */
277   public SELF cre(int depth) {
278      if (useWhitespace && depth <= maxIndent - 1)
279         return nl(depth).i(depth);
280      return self();
281   }
282
283   @Override /* Overridden from Writer */
284   public void flush() throws IOException {
285      out.flush();
286   }
287
288   /**
289    * Writes an indent to the writer if the {@code useWhitespace} setting is enabled.
290    *
291    * @param indent The number of tabs to indent.
292    * @return This object.
293    */
294   public SELF i(int indent) {
295      if (useWhitespace && indent <= maxIndent)
296         for (var i = 0; i < indent; i++)
297            w('\t');
298      return self();
299   }
300
301   /**
302    * Writes an end-of-line indent to the writer if the {@code useWhitespace} setting is enabled.
303    *
304    * @param indent The number of tabs to indent.
305    * @return This object.
306    */
307   public SELF ie(int indent) {
308      if (useWhitespace && indent <= maxIndent - 1)
309         for (var i = 0; i < indent; i++)
310            w('\t');
311      return self();
312   }
313
314   /**
315    * Writes a newline to the writer if the {@code useWhitespace} setting is enabled.
316    *
317    * @param indent The current indentation level.
318    * @return This object.
319    */
320   public SELF nl(int indent) {
321      if (useWhitespace && indent <= maxIndent)
322         w('\n');
323      return self();
324   }
325
326   /**
327    * Writes a newline to the writer if the {@code useWhitespace} setting is enabled and the boolean flag is true.
328    *
329    * @param flag The boolean flag.
330    * @param indent The current indentation level.
331    * @return This object.
332    */
333   public SELF nlIf(boolean flag, int indent) {
334      if (flag && useWhitespace && indent <= maxIndent)
335         w('\n');
336      return self();
337   }
338
339   /**
340    * Adds the quote character specified by the {@code quoteChar} setting to the output.
341    *
342    * @return This object.
343    */
344   public SELF q() {
345      w(quoteChar);
346      return self();
347   }
348
349   /**
350    * Adds a whitespace character to the output if the {@code useWhitespace} setting is enabled.
351    *
352    * @return This object.
353    */
354   public SELF s() {
355      if (useWhitespace)
356         w(' ');
357      return self();
358   }
359
360   /**
361    * Writes a space if the boolean expression is <jk>true</jk> and {@code useWhitespace} is false.
362    *
363    * <p>
364    * Intended for cases in XML where text should be separated by either a space or newline.
365    * This ensures the text is separated by a space if whitespace is disabled.
366    *
367    * @param flag The boolean flag.
368    * @return This object.
369    */
370   public SELF sIf(boolean flag) {
371      if (flag && ! useWhitespace)
372         w(' ');
373      return self();
374   }
375
376   /**
377    * Writes the specified character to the writer.
378    *
379    * @param value The character to write.
380    * @return This object.
381    */
382   public SELF w(char value) {
383      try {
384         out.write(value);
385      } catch (IOException e) {
386         throw new SerializeException(e);
387      }
388      return self();
389   }
390
391   /**
392    * Writes the specified string to the writer.
393    *
394    * @param value The string to write.
395    *    <br>Must not be <jk>null</jk>.
396    * @return This object.
397    */
398   public SELF w(String value) {
399      try {
400         out.write(value);
401      } catch (IOException e) {
402         throw new SerializeException(e);
403      }
404      return self();
405   }
406
407   @Override /* Overridden from Writer */
408   public void write(char[] cbuf, int off, int len) {
409      try {
410         out.write(cbuf, off, len);
411      } catch (IOException e) {
412         throw new SerializeException(e);
413      }
414   }
415
416   /**
417    * Writes an indent (if the {@code useWhitespace} setting is enabled), followed by text, optionally followed by a
418    * newline (if the {@code useWhitespace} setting is enabled).
419    *
420    * @param indent The number of tabs to indent.
421    * @param newline If <jk>true</jk>, then a newline is written.
422    * @param value The text to write.
423    * @throws IOException If a problem occurred trying to write to the writer.
424    * @return This object.
425    */
426   private SELF append(int indent, boolean newline, String value) {
427
428      if (value == null)
429         return self();
430
431      // If text contains newlines, we break it up into lines and indent them separately.
432      if (value.indexOf('\n') != -1 && useWhitespace && indent <= maxIndent) {
433         for (StringTokenizer st = new StringTokenizer(value, "\n"); st.hasMoreTokens();)
434            i(indent).w(st.nextToken()).w("\n");
435      } else {
436         i(indent).w(value);
437      }
438
439      if (newline)
440         nl(indent);
441
442      return self();
443   }
444}