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