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    * Performs a carriage return.
080    *
081    * <p>
082    * Adds a newline and the specified number of tabs (if the {@code useWhitespace} setting is enabled) to the output.
083    *
084    * @param depth The indentation.
085    * @return This object.
086    */
087   @FluentSetter
088   public SerializerWriter cr(int depth) {
089      if (useWhitespace && depth <= maxIndent)
090         return nl(depth).i(depth);
091      return this;
092   }
093
094   /**
095    * Performs a carriage return at the end of a line.
096    *
097    * <p>
098    * Adds a newline and the specified number of tabs (if the {@code useWhitespace} setting is enabled) to the output.
099    *
100    * @param depth The indentation.
101    * @return This object.
102    */
103   @FluentSetter
104   public SerializerWriter cre(int depth) {
105      if (useWhitespace && depth <= maxIndent-1)
106         return nl(depth).i(depth);
107      return this;
108   }
109
110   /**
111    * Writes an indent (if the {@code useWhitespace} setting is enabled), followed by text, followed by a newline
112    * (if the {@code useWhitespace} setting is enabled).
113    *
114    * @param indent The number of tabs to indent.
115    * @param value The text to write.
116    * @return This object.
117    */
118   @FluentSetter
119   public SerializerWriter appendln(int indent, String value) {
120      return append(indent, true, value);
121   }
122
123   /**
124    * Writes the specified text followed by a newline (if the {@code useWhitespace} setting is enabled).
125    *
126    * @param value The text to write.
127    * @return This object.
128    */
129   @FluentSetter
130   public SerializerWriter appendln(String value) {
131      return append(0, true, value);
132   }
133
134   /**
135    * Writes an indent (if the {@code useWhitespace} setting is enabled), followed by text.
136    *
137    * @param indent The number of tabs to indent.
138    * @param value The text to write.
139    * @return This object.
140    */
141   @FluentSetter
142   public SerializerWriter append(int indent, String value) {
143      return append(indent, false, value);
144   }
145
146   /**
147    * Writes an indent (if the {@code useWhitespace} setting is enabled), followed by text.
148    *
149    * @param indent The number of tabs to indent.
150    * @param value The character to write.
151    * @return This object.
152    */
153   @FluentSetter
154   public SerializerWriter append(int indent, char value) {
155      return i(indent).w(value);
156   }
157
158   /**
159    * Writes an indent (if the {@code useWhitespace} setting is enabled), followed by text, optionally followed by a
160    * newline (if the {@code useWhitespace} setting is enabled).
161    *
162    * @param indent The number of tabs to indent.
163    * @param newline If <jk>true</jk>, then a newline is written.
164    * @param value The text to write.
165    * @throws IOException If a problem occurred trying to write to the writer.
166    * @return This object.
167    */
168   private SerializerWriter append(int indent, boolean newline, String value) {
169
170      if (value == null)
171         return this;
172
173      // If text contains newlines, we break it up into lines and indent them separately.
174      if (value.indexOf('\n') != -1 && useWhitespace && indent <= maxIndent) {
175         for (StringTokenizer st = new StringTokenizer(value, "\n"); st.hasMoreTokens();)
176            i(indent).w(st.nextToken()).w("\n");
177      } else {
178         i(indent).w(value);
179      }
180
181      if (newline)
182         nl(indent);
183
184      return this;
185   }
186
187   /**
188    * Appends the specified object as a URI.
189    *
190    * <p>
191    * Object is converted to a <c>String</c> using <c>toString()</c>, so this will work on {@link URL} or
192    * {@link URI} objects, or any other type that returns a URI via it's <c>toString()</c> method.
193    *
194    * <p>
195    * The URI is resolved based on the {@link Serializer.Builder#uriRelativity(UriRelativity)} and
196    * {@link Serializer.Builder#uriResolution(UriResolution)} settings and the {@link UriContext} that's part of the
197    * session.
198    *
199    * @param value The URI to serialize.
200    * @return This object.
201    */
202   @FluentSetter
203   public SerializerWriter appendUri(Object value) {
204      uriResolver.append(this, value);
205      return this;
206   }
207
208   /**
209    * Appends the specified characters to this writer.
210    *
211    * @param value The characters to append to this writer.
212    * @return This object.
213    */
214   @FluentSetter
215   public SerializerWriter append(char[] value) {
216      for (char c : value)
217         w(c);
218      return this;
219   }
220
221   /**
222    * Adds a whitespace character to the output if the {@code useWhitespace} setting is enabled.
223    *
224    * @return This object.
225    */
226   @FluentSetter
227   public SerializerWriter s() {
228      if (useWhitespace)
229         w(' ');
230      return this;
231   }
232
233   /**
234    * Adds the quote character specified by the {@code quoteChar} setting to the output.
235    *
236    * @return This object.
237    */
238   @FluentSetter
239   public SerializerWriter q() {
240      w(quoteChar);
241      return this;
242   }
243
244   /**
245    * Writes an indent to the writer if the {@code useWhitespace} setting is enabled.
246    *
247    * @param indent The number of tabs to indent.
248    * @return This object.
249    */
250   @FluentSetter
251   public SerializerWriter i(int indent) {
252      if (useWhitespace && indent <= maxIndent)
253         for (int i = 0; i < indent; i++)
254            w('\t');
255      return this;
256   }
257
258   /**
259    * Writes an end-of-line 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 ie(int indent) {
266      if (useWhitespace && indent <= maxIndent-1)
267         for (int i = 0; i < indent; i++)
268            w('\t');
269      return this;
270   }
271
272   /**
273    * Writes a newline to the writer if the {@code useWhitespace} setting is enabled.
274    *
275    * @param indent The current indentation level.
276    * @return This object.
277    */
278   @FluentSetter
279   public SerializerWriter nl(int indent) {
280      if (useWhitespace && indent <= maxIndent)
281         w('\n');
282      return this;
283   }
284
285   /**
286    * Writes a space if the boolean expression is <jk>true</jk> and {@code useWhitespace} is false.
287    *
288    * <p>
289    * Intended for cases in XML where text should be separated by either a space or newline.
290    * This ensures the text is separated by a space if whitespace is disabled.
291    *
292    * @param flag The boolean flag.
293    * @return This object.
294    */
295   @FluentSetter
296   public SerializerWriter sIf(boolean flag) {
297      if (flag && ! useWhitespace)
298         w(' ');
299      return this;
300   }
301
302   /**
303    * Writes a newline to the writer if the {@code useWhitespace} setting is enabled and the boolean flag is true.
304    *
305    * @param flag The boolean flag.
306    * @param indent The current indentation level.
307    * @return This object.
308    */
309   @FluentSetter
310   public SerializerWriter nlIf(boolean flag, int indent) {
311      if (flag && useWhitespace && indent <= maxIndent)
312         w('\n');
313      return this;
314   }
315
316   /**
317    * Writes the specified text to the writer if it isn't <jk>null</jk>.
318    *
319    * @param value The text to write.
320    * @return This object.
321    */
322   @FluentSetter
323   public SerializerWriter append(Object value) {
324      w(value == null ? null : value.toString());
325      return this;
326   }
327
328   /**
329    * Writes the specified text to the writer if it isn't <jk>null</jk>.
330    *
331    * @param value The text to write.
332    * @return This object.
333    */
334   @FluentSetter
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   @FluentSetter
349   public SerializerWriter appendIf(boolean flag, String value) {
350      if (flag)
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, char value) {
364      if (flag)
365         w(value);
366      return this;
367   }
368
369   /**
370    * Writes the specified character to the writer.
371    *
372    * @param value The character to write.
373    * @return This object.
374    */
375   @FluentSetter
376   public SerializerWriter w(char value) {
377      try {
378         out.write(value);
379      } catch (IOException e) {
380         throw new SerializeException(e);
381      }
382      return this;
383   }
384
385   /**
386    * Writes the specified string to the writer.
387    *
388    * @param value The string to write.
389    * @return This object.
390    */
391   @FluentSetter
392   public SerializerWriter w(String value) {
393      try {
394         out.write(value);
395      } catch (IOException e) {
396         throw new SerializeException(e);
397      }
398      return this;
399   }
400
401   //-----------------------------------------------------------------------------------------------------------------
402   // Overridden methods
403   //-----------------------------------------------------------------------------------------------------------------
404
405   @Override /* Writer */
406   public SerializerWriter append(char c) {
407      try {
408         out.write(c);
409      } catch (IOException e) {
410         throw new SerializeException(e);
411      }
412      return this;
413   }
414
415   @Override /* Writer */
416   public void write(char[] cbuf, int off, int len) {
417      try {
418         out.write(cbuf, off, len);
419      } catch (IOException e) {
420         throw new SerializeException(e);
421      }
422   }
423
424   @Override /* Writer */
425   public void flush() throws IOException {
426      out.flush();
427   }
428
429   @Override /* Writer */
430   public void close() throws IOException {
431      out.close();
432   }
433
434   // <FluentSetters>
435
436   // </FluentSetters>
437}