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