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