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.IoUtils.*; 020import static org.apache.juneau.commons.utils.Shorts.*; 021import static org.apache.juneau.commons.utils.SystemUtils.*; 022import static org.apache.juneau.commons.utils.ThrowableUtils.*; 023 024import java.nio.charset.*; 025 026import org.apache.juneau.commons.collections.*; 027import org.apache.juneau.commons.function.*; 028import org.apache.juneau.commons.logging.*; 029import org.apache.juneau.marshall.json5.*; 030 031/** 032 * Subclass of {@link Serializer} for character-based serializers. 033 * 034 * <h5 class='section'>Notes:</h5><ul> 035 * <li class='note'>This class is thread safe and reusable. 036 * </ul> 037 * 038 * <h5 class='section'>See Also:</h5><ul> 039 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SerializersAndParsers">Serializers and Parsers</a> 040 * </ul> 041 */ 042@SuppressWarnings({ 043 "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants 044 "java:S9149" // Per-format static factories intentionally shadow the parent's. 045}) 046public class WriterSerializer extends Serializer implements ThrowingFunction<Object,String> { 047 048 /** 049 * Builder class. 050 */ 051 @SuppressWarnings({ 052 "java:S119" // 'SELF' (CRTP self-type) is intentional and clearer than a single-letter name. 053 }) 054 public abstract static class Builder<SELF extends Builder<SELF>> extends Serializer.Builder<SELF> { 055 056 private boolean useWhitespace; 057 private Character quoteChar; 058 private Character quoteCharOverride; 059 private Charset streamCharset; 060 private int maxIndent; 061 062 /** 063 * Constructor, default settings. 064 */ 065 protected Builder() { 066 streamCharset = UTF8; 067 maxIndent = env("WriterSerializer.maxIndent", 100); 068 quoteChar = env("WriterSerializer.quoteChar").map(x -> (x.isEmpty() ? null : x.charAt(0))).orElse(null); 069 quoteCharOverride = env("WriterSerializer.quoteCharOverride").map(x -> (x.isEmpty() ? null : x.charAt(0))).orElse(null); 070 useWhitespace = env("WriterSerializer.useWhitespace", false); 071 } 072 073 /** 074 * Copy constructor. 075 * 076 * @param copyFrom The builder to copy from. 077 * <br>Cannot be <jk>null</jk>. 078 */ 079 protected Builder(Builder<?> copyFrom) { 080 super(reqnn("copyFrom", copyFrom)); 081 streamCharset = copyFrom.streamCharset; 082 maxIndent = copyFrom.maxIndent; 083 quoteChar = copyFrom.quoteChar; 084 quoteCharOverride = copyFrom.quoteCharOverride; 085 useWhitespace = copyFrom.useWhitespace; 086 } 087 088 /** 089 * Copy constructor. 090 * 091 * @param copyFrom The bean to copy from. 092 * <br>Cannot be <jk>null</jk>. 093 */ 094 protected Builder(WriterSerializer copyFrom) { 095 super(reqnn("copyFrom", copyFrom)); 096 streamCharset = copyFrom.getStreamCharset(); 097 maxIndent = copyFrom.maxIndent; 098 quoteChar = copyFrom.quoteChar; 099 quoteCharOverride = copyFrom.quoteCharOverride; 100 useWhitespace = copyFrom.useWhitespace; 101 } 102 103 @Override /* Overridden from Context.Builder<?> */ 104 public WriterSerializer build() { 105 return build(WriterSerializer.class); 106 } 107 108 @Override /* Overridden from Context.Builder<?> */ 109 public abstract SELF copy(); 110 111 @Override /* Overridden from Context.Builder<?> */ 112 public HashKey hashKey() { 113 // @formatter:off 114 return HashKey.of( 115 super.hashKey(), 116 streamCharset, 117 maxIndent, 118 quoteChar, 119 quoteCharOverride, 120 useWhitespace 121 ); 122 // @formatter:on 123 } 124 125 /** 126 * Maximum indentation. 127 * 128 * <p> 129 * Specifies the maximum indentation level in the serialized document. 130 * 131 * <h5 class='section'>Notes:</h5><ul> 132 * <li class='note'>This setting does not apply to the RDF serializers. 133 * </ul> 134 * 135 * <h5 class='section'>Example:</h5> 136 * <p class='bjava'> 137 * <jc>// Create a serializer that indents a maximum of 20 tabs.</jc> 138 * WriterSerializer <jv>serializer</jv> = JsonSerializer 139 * .<jsm>create</jsm>() 140 * .ws() <jc>// Enable whitespace</jc> 141 * .maxIndent(20) 142 * .build(); 143 * </p> 144 * 145 * @param value 146 * The new value for this property. 147 * <br>The default is <c>100</c>. 148 * @return This object. 149 */ 150 public SELF maxIndent(int value) { 151 maxIndent = value; 152 return self(); 153 } 154 155 /** 156 * Quote character. 157 * 158 * <p> 159 * Specifies the character to use for quoting attributes and values. 160 * 161 * <h5 class='section'>Notes:</h5><ul> 162 * <li class='note'>This setting does not apply to the RDF serializers. 163 * </ul> 164 * 165 * <h5 class='section'>Example:</h5> 166 * <p class='bjava'> 167 * <jc>// Create a serializer that uses single quotes.</jc> 168 * WriterSerializer <jv>serializer</jv> = JsonSerializer 169 * .<jsm>create</jsm>() 170 * .quoteChar(<js>'\''</js>) 171 * .build(); 172 * 173 * <jc>// A bean with a single property</jc> 174 * <jk>public class</jk> MyBean { 175 * <jk>public</jk> String <jf>foo</jf> = <js>"bar"</js>; 176 * } 177 * 178 * <jc>// Produces {'foo':'bar'}</jc> 179 * String <jv>json</jv> = <jv>serializer</jv>.toString(<jk>new</jk> MyBean()); 180 * </p> 181 * 182 * @param value 183 * The new value for this property. 184 * <br>The default is <js>'"'</js>. 185 * @return This object. 186 */ 187 public SELF quoteChar(char value) { 188 quoteChar = value; 189 return self(); 190 } 191 192 /** 193 * Quote character override. 194 * 195 * <p> 196 * Similar to {@link #quoteChar(char)} but takes precedence over that setting. 197 * 198 * <p> 199 * Allows you to override the quote character even if it's set by a subclass such as {@link Json5Serializer}. 200 * 201 * 202 * @param value 203 * The new value for this property. 204 * <br>The default is <jk>null</jk>. 205 * @return This object. 206 */ 207 public SELF quoteCharOverride(char value) { 208 quoteCharOverride = value; 209 return self(); 210 } 211 212 /** 213 * Quote character. 214 * 215 * <p> 216 * Specifies to use single quotes for quoting attributes and values. 217 * 218 * <h5 class='section'>Notes:</h5><ul> 219 * <li class='note'>This setting does not apply to the RDF serializers. 220 * </ul> 221 * 222 * <h5 class='section'>Example:</h5> 223 * <p class='bjava'> 224 * <jc>// Create a serializer that uses single quotes.</jc> 225 * WriterSerializer <jv>serializer</jv> = JsonSerializer 226 * .<jsm>create</jsm>() 227 * .sq() 228 * .build(); 229 * 230 * <jc>// A bean with a single property</jc> 231 * <jk>public class</jk> MyBean { 232 * <jk>public</jk> String <jf>foo</jf> = <js>"bar"</js>; 233 * } 234 * 235 * <jc>// Produces {'foo':'bar'}</jc> 236 * String <jv>json</jv> = <jv>serializer</jv>.toString(<jk>new</jk> MyBean()); 237 * </p> 238 * 239 * @return This object. 240 */ 241 public SELF sq() { 242 return quoteChar('\''); 243 } 244 245 /** 246 * Output stream charset. 247 * 248 * <p> 249 * The character set to use when writing to <c>OutputStreams</c>. 250 * 251 * <p> 252 * Used when passing in output streams and byte arrays to {@link WriterSerializer#write(Object, Object)}. 253 * 254 * <h5 class='section'>Example:</h5> 255 * <p class='bjava'> 256 * <jc>// Create a serializer that writes UTF-8 files.</jc> 257 * WriterSerializer <jv>serializer</jv> = JsonSerializer 258 * .<jsm>create</jsm>() 259 * .streamCharset(Charset.<jsm>forName</jsm>(<js>"UTF-8"</js>)) 260 * .build(); 261 * 262 * <jc>// Use it to write to a UTF-8 encoded output stream.</jc> 263 * <jv>serializer</jv>.serializer(<jk>new</jk> FileOutputStreamStream(<js>"MyBean.txt"</js>), <jv>myBean</jv>); 264 * </p> 265 * 266 * @param value 267 * The new value for this property. 268 * <br>The default is the system JVM setting. 269 * <br>Can be <jk>null</jk> (defaults to UTF-8). 270 * @return This object. 271 */ 272 public SELF streamCharset(Charset value) { 273 streamCharset = value; 274 return self(); 275 } 276 277 278 /** 279 * Use whitespace. 280 * 281 * <p> 282 * When enabled, whitespace is added to the output to improve readability. 283 * 284 * <h5 class='section'>Example:</h5> 285 * <p class='bjava'> 286 * <jc>// Create a serializer with whitespace enabled.</jc> 287 * WriterSerializer <jv>serializer</jv> = JsonSerializer 288 * .<jsm>create</jsm>() 289 * .useWhitespace() 290 * .build(); 291 * 292 * <jc>// A bean with a single property</jc> 293 * <jk>public class</jk> MyBean { 294 * <jk>public</jk> String <jf>foo</jf> = <js>"bar"</js>; 295 * } 296 * 297 * <jc>// Produces "\{\n\t"foo": "bar"\n\}\n"</jc> 298 * String <jv>json</jv> = <jv>serializer</jv>.write(<jk>new</jk> MyBean()); 299 * </p> 300 * 301 * @return This object. 302 */ 303 public SELF useWhitespace() { 304 return useWhitespace(true); 305 } 306 307 /** 308 * Same as {@link #useWhitespace()} but allows you to explicitly specify the value. 309 * 310 * @param value The value for this setting. 311 * @return This object. 312 */ 313 public SELF useWhitespace(boolean value) { 314 useWhitespace = value; 315 return self(); 316 } 317 318 /** 319 * Use whitespace. 320 * 321 * <p> 322 * When enabled, whitespace is added to the output to improve readability. 323 * 324 * <h5 class='section'>Example:</h5> 325 * <p class='bjava'> 326 * <jc>// Create a serializer with whitespace enabled.</jc> 327 * WriterSerializer <jv>serializer</jv> = JsonSerializer 328 * .<jsm>create</jsm>() 329 * .ws() 330 * .build(); 331 * 332 * <jc>// A bean with a single property</jc> 333 * <jk>public class</jk> MyBean { 334 * <jk>public</jk> String <jf>foo</jf> = <js>"bar"</js>; 335 * } 336 * 337 * <jc>// Produces "\{\n\t"foo": "bar"\n\}\n"</jc> 338 * String <jv>json</jv> = <jv>serializer</jv>.write(<jk>new</jk> MyBean()); 339 * </p> 340 * 341 * @return This object. 342 */ 343 public SELF ws() { 344 return useWhitespace(); 345 } 346 } 347 348 /** 349 * Concrete default builder leaf for the non-subclassed {@link WriterSerializer#create()} / {@link WriterSerializer#copy()} path. 350 */ 351 @SuppressWarnings({ 352 "java:S110" // Inheritance depth follows the serializer builder chain; intentional layered design 353 }) 354 public static final class DefaultBuilder extends Builder<DefaultBuilder> { 355 356 DefaultBuilder() {} 357 358 DefaultBuilder(WriterSerializer copyFrom) { 359 super(copyFrom); 360 } 361 362 DefaultBuilder(Builder<?> copyFrom) { 363 super(copyFrom); 364 } 365 366 @Override /* Overridden from Context.Builder<?> */ 367 public DefaultBuilder copy() { 368 return new DefaultBuilder(this); 369 } 370 } 371 372 /** 373 * Creates a new builder for this object. 374 * 375 * @return A new builder. 376 */ 377 @SuppressWarnings({ 378 "java:S1452" // Builder<?> wildcard return intentional; callers chain via fluent API without needing the concrete type 379 }) 380 public static Builder<?> create() { 381 return new DefaultBuilder(); 382 } 383 384 protected final boolean useWhitespace; 385 protected final Character quoteChar; 386 protected final Character quoteCharOverride; 387 private final Charset streamCharset; 388 protected final int maxIndent; 389 390 private final char quoteCharValue; 391 392 /** 393 * Constructor. 394 * 395 * @param builder 396 * The builder for this object. 397 * <br>Cannot be <jk>null</jk>. 398 */ 399 protected WriterSerializer(Builder<?> builder) { 400 super(builder); 401 402 maxIndent = builder.maxIndent; 403 quoteChar = builder.quoteChar; 404 quoteCharOverride = builder.quoteCharOverride; 405 streamCharset = builder.streamCharset; 406 useWhitespace = builder.useWhitespace; 407 408 if (nn(quoteCharOverride)) { 409 quoteCharValue = quoteCharOverride; 410 } else if (nn(quoteChar)) { 411 quoteCharValue = quoteChar; 412 } else { 413 quoteCharValue = '"'; 414 } 415 } 416 417 @Override /* Overridden from Context */ 418 public Builder<?> copy() { 419 return new DefaultBuilder(this); 420 } 421 422 @Override /* Overridden from Context */ 423 public WriterSerializerSession.Builder<?> createSession() { 424 return WriterSerializerSession.create(this); 425 } 426 427 @Override /* Overridden from Context */ 428 public WriterSerializerSession getSession() { return createSession().build(); } 429 430 @Override /* Overridden from Serializer */ 431 public final boolean isWriterSerializer() { return true; } 432 433 /** 434 * Convenience method for serializing an object and sending it to STDOUT. 435 * 436 * @param o The object to serialize. 437 * @return This object. 438 */ 439 public final WriterSerializer println(Object o) { 440 RichLogger.getLogger(WriterSerializer.class).info(toString(o)); // NOT DEBUG 441 return this; 442 } 443 444 /** 445 * Convenience method for serializing an object to a <c>String</c>. 446 * 447 * @param o The object to serialize. 448 * @return The output serialized to a string. 449 * @throws SerializeException If a problem occurred trying to convert the output. 450 */ 451 @Override /* Overridden from Serializer */ 452 public final String write(Object o) throws SerializeException { 453 return getSession().write(o); 454 } 455 456 /** 457 * Allows this serializer to be used as a {@link ThrowingFunction} that converts an object to its serialized string form. 458 * 459 * <p> 460 * Because {@link WriterSerializer} implements {@link ThrowingFunction ThrowingFunction<Object,String>}, a serializer 461 * instance can be passed directly wherever a {@link java.util.function.Function Function} or {@link ThrowingFunction} is 462 * expected (e.g. assertion transform methods). 463 * 464 * <h5 class='section'>Example:</h5> 465 * <p class='bjava'> 466 * <jsm>assertObject</jsm>(<jv>myPojo</jv>).asString(Json5Serializer.<jsf>DEFAULT</jsf>).is(<js>"{...}"</js>); 467 * </p> 468 * 469 * @param o The object to serialize. 470 * @return The output serialized to a string. 471 * @throws Exception If a problem occurred trying to convert the output. 472 */ 473 @Override /* Overridden from ThrowingFunction */ 474 public final String applyThrows(Object o) throws Exception { 475 return write(o); 476 } 477 478 /** 479 * Identical to {@link #write(Object)} except throws a {@link RuntimeException} instead of a {@link SerializeException}. 480 * 481 * <p> 482 * This is typically good enough for debugging purposes. 483 * 484 * @param o The object to serialize. 485 * @return The serialized object. 486 */ 487 public final String toString(Object o) { 488 try { 489 return write(o); 490 } catch (Exception e) { 491 throw toRex(e); 492 } 493 } 494 495 /** 496 * Maximum indentation. 497 * 498 * @see Builder#maxIndent(int) 499 * @return 500 * The maximum indentation level in the serialized document. 501 */ 502 protected final int getMaxIndent() { return maxIndent; } 503 504 /** 505 * Quote character. 506 * 507 * @see Builder#quoteChar(char) 508 * @return 509 * The character used for quoting attributes and values. 510 */ 511 protected char getQuoteChar() { return quoteCharValue; } 512 513 /** 514 * Output stream charset. 515 * 516 * @see Builder#streamCharset(Charset) 517 * @return 518 * The character set to use when writing to <c>OutputStreams</c> and byte arrays. 519 */ 520 protected final Charset getStreamCharset() { return streamCharset; } 521 522 /** 523 * Use whitespace. 524 * 525 * @see Builder#useWhitespace() 526 * @return 527 * When enabled, whitespace is added to the output to improve readability. 528 */ 529 protected final boolean isUseWhitespace() { return useWhitespace; } 530 531 @Override /* Overridden from Serializer */ 532 protected FluentMap<String,Object> properties() { 533 return super.properties() 534 .a("maxIndent", maxIndent) 535 .a("quoteChar", quoteChar) 536 .a("streamCharset", streamCharset) 537 .a("useWhitespace", useWhitespace); 538 } 539 540 /** 541 * Quote character. 542 * 543 * @see Builder#quoteChar(char) 544 * @return 545 * The character used for quoting attributes and values. 546 */ 547 @SuppressWarnings({ 548 "java:S1845" // Method name intentionally matches field name 549 }) 550 protected Character quoteChar() { 551 return nn(quoteCharOverride) ? quoteCharOverride : quoteChar; 552 } 553}