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