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