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'> 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}