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