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.nio.charset.*; 016 017import org.apache.juneau.*; 018import org.apache.juneau.annotation.*; 019import org.apache.juneau.collections.*; 020import org.apache.juneau.internal.*; 021import org.apache.juneau.utils.*; 022 023/** 024 * Subclass of {@link Serializer} for character-based serializers. 025 */ 026@ConfigurableContext 027public abstract class WriterSerializer extends Serializer { 028 029 //------------------------------------------------------------------------------------------------------------------- 030 // Configurable properties 031 //------------------------------------------------------------------------------------------------------------------- 032 033 static final String PREFIX = "WriterSerializer"; 034 035 /** 036 * Configuration property: File charset. 037 * 038 * <h5 class='section'>Property:</h5> 039 * <ul class='spaced-list'> 040 * <li><b>ID:</b> {@link org.apache.juneau.serializer.WriterSerializer#WSERIALIZER_fileCharset WSERIALIZER_fileCharset} 041 * <li><b>Name:</b> <js>"WriterSerializer.fileCharset.s"</js> 042 * <li><b>Data type:</b> <c>String</c> 043 * <li><b>System property:</b> <c>WriterSerializer.fileCharset</c> 044 * <li><b>Environment variable:</b> <c>WRITERSERIALIZER_FILECHARSET</c> 045 * <li><b>Default:</b> <js>"DEFAULT"</js> 046 * <li><b>Session property:</b> <jk>false</jk> 047 * <li><b>Annotations:</b> 048 * <ul> 049 * <li class='ja'>{@link org.apache.juneau.serializer.annotation.SerializerConfig#fileCharset()} 050 * </ul> 051 * <li><b>Methods:</b> 052 * <ul> 053 * <li class='jm'>{@link org.apache.juneau.serializer.WriterSerializerBuilder#fileCharset(Charset)} 054 * </ul> 055 * </ul> 056 * 057 * <h5 class='section'>Description:</h5> 058 * 059 * <p> 060 * The character set to use for writing <c>Files</c> to the file system. 061 * 062 * <p> 063 * Used when passing in files to {@link Serializer#serialize(Object, Object)}. 064 * 065 * <p> 066 * <js>"DEFAULT"</js> can be used to indicate the JVM default file system charset. 067 * 068 * <h5 class='section'>Example:</h5> 069 * <p class='bcode w800'> 070 * <jc>// Create a serializer that writes UTF-8 files.</jc> 071 * WriterSerializer s = JsonSerializer 072 * .<jsm>create</jsm>() 073 * .fileCharset(Charset.<jsm>forName</jsm>(<js>"UTF-8"</js>)) 074 * .build(); 075 * 076 * <jc>// Same, but use property.</jc> 077 * WriterSerializer s = JsonSerializer 078 * .<jsm>create</jsm>() 079 * .set(<jsf>WSERIALIZER_fileCharset</jsf>, <js>"UTF-8"</js>) 080 * .build(); 081 * 082 * <jc>// Use it to read a UTF-8 encoded file.</jc> 083 * s.serialize(<jk>new</jk> File(<js>"MyBean.txt"</js>), myBean); 084 * </p> 085 */ 086 public static final String WSERIALIZER_fileCharset = PREFIX + ".fileCharset.s"; 087 088 /** 089 * Configuration property: Maximum indentation. 090 * 091 * <h5 class='section'>Property:</h5> 092 * <ul class='spaced-list'> 093 * <li><b>ID:</b> {@link org.apache.juneau.serializer.WriterSerializer#WSERIALIZER_maxIndent WSERIALIZER_maxIndent} 094 * <li><b>Name:</b> <js>"WriterSerializer.maxIndent.i"</js> 095 * <li><b>Data type:</b> <jk>int</jk> 096 * <li><b>System property:</b> <c>WriterSerializer.maxIndent</c> 097 * <li><b>Environment variable:</b> <c>WRITERSERIALIZER_MAXINDENT</c> 098 * <li><b>Default:</b> <c>100</c> 099 * <li><b>Session property:</b> <jk>false</jk> 100 * <li><b>Annotations:</b> 101 * <ul> 102 * <li class='ja'>{@link org.apache.juneau.serializer.annotation.SerializerConfig#maxIndent()} 103 * </ul> 104 * <li><b>Methods:</b> 105 * <ul> 106 * <li class='jm'>{@link org.apache.juneau.serializer.WriterSerializerBuilder#maxIndent(int)} 107 * </ul> 108 * </ul> 109 * 110 * <h5 class='section'>Description:</h5> 111 * 112 * <p> 113 * Specifies the maximum indentation level in the serialized document. 114 * 115 * <ul class='notes'> 116 * <li>This setting does not apply to the RDF serializers. 117 * </ul> 118 * 119 * <h5 class='section'>Example:</h5> 120 * <p class='bcode w800'> 121 * <jc>// Create a serializer that indents a maximum of 20 tabs.</jc> 122 * WriterSerializer s = JsonSerializer 123 * .<jsm>create</jsm>() 124 * .ws() <jc>// Enable whitespace</jc> 125 * .maxIndent(20) 126 * .build(); 127 * 128 * <jc>// Same, but use property.</jc> 129 * WriterSerializer s = JsonSerializer 130 * .<jsm>create</jsm>() 131 * .set(<jsf>WSERIALIZER_maxIndent</jsf>, 20) 132 * .build(); 133 * </p> 134 */ 135 public static final String WSERIALIZER_maxIndent = PREFIX + ".maxIndent.i"; 136 137 /** 138 * Configuration property: Quote character. 139 * 140 * <h5 class='section'>Property:</h5> 141 * <ul class='spaced-list'> 142 * <li><b>ID:</b> {@link org.apache.juneau.serializer.WriterSerializer#WSERIALIZER_quoteChar WSERIALIZER_quoteChar} 143 * <li><b>Name:</b> <js>"WriterSerializer.quoteChar.s"</js> 144 * <li><b>Data type:</b> <c>String</c> 145 * <li><b>System property:</b> <c>WriterSerializer.quoteChar</c> 146 * <li><b>Environment variable:</b> <c>WRITERSERIALIZER_QUOTECHAR</c> 147 * <li><b>Default:</b> <js>"\""</js> 148 * <li><b>Session property:</b> <jk>false</jk> 149 * <li><b>Annotations:</b> 150 * <ul> 151 * <li class='ja'>{@link org.apache.juneau.serializer.annotation.SerializerConfig#quoteChar()} 152 * </ul> 153 * <li><b>Methods:</b> 154 * <ul> 155 * <li class='jm'>{@link org.apache.juneau.serializer.WriterSerializerBuilder#quoteChar(char)} 156 * <li class='jm'>{@link org.apache.juneau.serializer.WriterSerializerBuilder#sq()} 157 * </ul> 158 * </ul> 159 * 160 * <h5 class='section'>Description:</h5> 161 * 162 * <p> 163 * Specifies the character to use for quoting attributes and values. 164 * 165 * <ul class='notes'> 166 * <li>This setting does not apply to the RDF serializers. 167 * </ul> 168 * 169 * <h5 class='section'>Example:</h5> 170 * <p class='bcode w800'> 171 * <jc>// Create a serializer that uses single quotes.</jc> 172 * WriterSerializer s = JsonSerializer 173 * .<jsm>create</jsm>() 174 * .sq() 175 * .build(); 176 * 177 * <jc>// Same, but use property.</jc> 178 * WriterSerializer s = JsonSerializer 179 * .<jsm>create</jsm>() 180 * .set(<jsf>WSERIALIZER_quoteChar</jsf>, <js>'\''</js>) 181 * .build(); 182 * 183 * <jc>// A bean with a single property</jc> 184 * <jk>public class</jk> MyBean { 185 * <jk>public</jk> String <jf>foo</jf> = <js>"bar"</js>; 186 * } 187 * 188 * <jc>// Produces {'foo':'bar'}</jc> 189 * String json = s.toString(<jk>new</jk> MyBean()); 190 * </p> 191 */ 192 public static final String WSERIALIZER_quoteChar = PREFIX + ".quoteChar.s"; 193 194 /** 195 * Configuration property: Output stream charset. 196 * 197 * <h5 class='section'>Property:</h5> 198 * <ul class='spaced-list'> 199 * <li><b>ID:</b> {@link org.apache.juneau.serializer.WriterSerializer#WSERIALIZER_streamCharset WSERIALIZER_streamCharset} 200 * <li><b>Name:</b> <js>"WriterSerializer.streamCharset.s"</js> 201 * <li><b>Data type:</b> <c>String</c> 202 * <li><b>System property:</b> <c>WriterSerializer.streamCharset</c> 203 * <li><b>Environment variable:</b> <c>WRITERSERIALIZER_STREAMCHARSET</c> 204 * <li><b>Default:</b> <js>"UTF-8"</js> 205 * <li><b>Session property:</b> <jk>false</jk> 206 * <li><b>Annotations:</b> 207 * <ul> 208 * <li class='ja'>{@link org.apache.juneau.serializer.annotation.SerializerConfig#streamCharset()} 209 * </ul> 210 * <li><b>Methods:</b> 211 * <ul> 212 * <li class='jm'>{@link org.apache.juneau.serializer.WriterSerializerBuilder#streamCharset(Charset)} 213 * </ul> 214 * </ul> 215 * 216 * <h5 class='section'>Description:</h5> 217 * 218 * <p> 219 * The character set to use when writing to <c>OutputStreams</c>. 220 * 221 * <p> 222 * Used when passing in output streams and byte arrays to {@link WriterSerializer#serialize(Object, Object)}. 223 * 224 * <h5 class='section'>Example:</h5> 225 * <p class='bcode w800'> 226 * <jc>// Create a serializer that writes UTF-8 files.</jc> 227 * WriterSerializer s = JsonSerializer 228 * .<jsm>create</jsm>() 229 * .streamCharset(Charset.<jsm>forName</jsm>(<js>"UTF-8"</js>)) 230 * .build(); 231 * 232 * <jc>// Same, but use property.</jc> 233 * WriterSerializer s = JsonSerializer 234 * .<jsm>create</jsm>() 235 * .set(<jsf>WSERIALIZER_streamCharset</jsf>, <js>"UTF-8"</js>) 236 * .build(); 237 * 238 * <jc>// Use it to write to a UTF-8 encoded output stream.</jc> 239 * s.serializer(<jk>new</jk> FileOutputStreamStream(<js>"MyBean.txt"</js>), myBean); 240 * </p> 241 */ 242 public static final String WSERIALIZER_streamCharset = PREFIX + ".streamCharset.s"; 243 244 /** 245 * Configuration property: Use whitespace. 246 * 247 * <h5 class='section'>Property:</h5> 248 * <ul class='spaced-list'> 249 * <li><b>ID:</b> {@link org.apache.juneau.serializer.WriterSerializer#WSERIALIZER_useWhitespace WSERIALIZER_useWhitespace} 250 * <li><b>Name:</b> <js>"WriterSerializer.useWhitespace.b"</js> 251 * <li><b>Data type:</b> <jk>boolean</jk> 252 * <li><b>System property:</b> <c>WriterSerializer.useWhitespace</c> 253 * <li><b>Environment variable:</b> <c>WRITERSERIALIZER_USEWHITESPACE</c> 254 * <li><b>Default:</b> <jk>false</jk> 255 * <li><b>Session property:</b> <jk>true</jk> 256 * <li><b>Annotations:</b> 257 * <ul> 258 * <li class='ja'>{@link org.apache.juneau.serializer.annotation.SerializerConfig#useWhitespace()} 259 * </ul> 260 * <li><b>Methods:</b> 261 * <ul> 262 * <li class='jm'>{@link org.apache.juneau.serializer.WriterSerializerBuilder#useWhitespace()} 263 * <li class='jm'>{@link org.apache.juneau.serializer.WriterSerializerBuilder#ws()} 264 * </ul> 265 * </ul> 266 * 267 * <h5 class='section'>Description:</h5> 268 * 269 * <p> 270 * When enabled, whitespace is added to the output to improve readability. 271 * 272 * <h5 class='section'>Example:</h5> 273 * <p class='bcode w800'> 274 * <jc>// Create a serializer with whitespace enabled.</jc> 275 * WriterSerializer s = JsonSerializer 276 * .<jsm>create</jsm>() 277 * .ws() 278 * .build(); 279 * 280 * <jc>// Same, but use property.</jc> 281 * WriterSerializer s = JsonSerializer 282 * .<jsm>create</jsm>() 283 * .set(<jsf>WSERIALIZER_useWhitespace</jsf>, <jk>true</jk>) 284 * .build(); 285 * 286 * <jc>// A bean with a single property</jc> 287 * <jk>public class</jk> MyBean { 288 * <jk>public</jk> String <jf>foo</jf> = <js>"bar"</js>; 289 * } 290 * 291 * <jc>// Produces "\{\n\t"foo": "bar"\n\}\n"</jc> 292 * String json = s.serialize(<jk>new</jk> MyBean()); 293 * </p> 294 */ 295 public static final String WSERIALIZER_useWhitespace = PREFIX + ".useWhitespace.b"; 296 297 static final WriterSerializer DEFAULT = new WriterSerializer(PropertyStore.create().build(), "", "") { 298 @Override 299 public WriterSerializerSession createSession(SerializerSessionArgs args) { 300 throw new NoSuchMethodError(); 301 } 302 }; 303 304 //------------------------------------------------------------------------------------------------------------------- 305 // Instance 306 //------------------------------------------------------------------------------------------------------------------- 307 308 private final Charset fileCharset; 309 private final int maxIndent; 310 private final char quoteChar; 311 private final Charset streamCharset; 312 private final boolean useWhitespace; 313 314 /** 315 * Constructor. 316 * 317 * @param ps 318 * The property store containing all the settings for this object. 319 * @param produces 320 * The media type that this serializer produces. 321 * @param accept 322 * The accept media types that the serializer can handle. 323 * <p> 324 * Can contain meta-characters per the <c>media-type</c> specification of {@doc ExtRFC2616.section14.1} 325 * <p> 326 * If empty, then assumes the only media type supported is <c>produces</c>. 327 * <p> 328 * For example, if this serializer produces <js>"application/json"</js> but should handle media types of 329 * <js>"application/json"</js> and <js>"text/json"</js>, then the arguments should be: 330 * <p class='bcode w800'> 331 * <jk>super</jk>(ps, <js>"application/json"</js>, <js>"application/json,text/json"</js>); 332 * </p> 333 * <br>...or... 334 * <p class='bcode w800'> 335 * <jk>super</jk>(ps, <js>"application/json"</js>, <js>"*​/json"</js>); 336 * </p> 337 * <p> 338 * The accept value can also contain q-values. 339 */ 340 protected WriterSerializer(PropertyStore ps, String produces, String accept) { 341 super(ps, produces, accept); 342 343 maxIndent = getIntegerProperty(WSERIALIZER_maxIndent, 100); 344 quoteChar = getStringProperty(WSERIALIZER_quoteChar, "\"").charAt(0); 345 streamCharset = getProperty(WSERIALIZER_streamCharset, Charset.class, IOUtils.UTF8); 346 fileCharset = getProperty(WSERIALIZER_fileCharset, Charset.class, Charset.defaultCharset()); 347 useWhitespace = getBooleanProperty(WSERIALIZER_useWhitespace, false); 348 } 349 350 //----------------------------------------------------------------------------------------------------------------- 351 // Abstract methods 352 //----------------------------------------------------------------------------------------------------------------- 353 354 @Override /* SerializerSession */ 355 public abstract WriterSerializerSession createSession(SerializerSessionArgs args); 356 357 //----------------------------------------------------------------------------------------------------------------- 358 // Other methods 359 //----------------------------------------------------------------------------------------------------------------- 360 361 @Override /* Context */ 362 public WriterSerializerSession createSession() { 363 return createSession(createDefaultSessionArgs()); 364 } 365 366 @Override /* Serializer */ 367 public final boolean isWriterSerializer() { 368 return true; 369 } 370 371 /** 372 * Convenience method for serializing an object to a <c>String</c>. 373 * 374 * @param o The object to serialize. 375 * @return The output serialized to a string. 376 * @throws SerializeException If a problem occurred trying to convert the output. 377 */ 378 @Override /* Serializer */ 379 public final String serialize(Object o) throws SerializeException { 380 return createSession(createDefaultSessionArgs()).serialize(o); 381 } 382 383 /** 384 * Identical to {@link #serialize(Object)} except throws a {@link RuntimeException} instead of a {@link SerializeException}. 385 * 386 * <p> 387 * This is typically good enough for debugging purposes. 388 * 389 * @param o The object to serialize. 390 * @return The serialized object. 391 */ 392 public final String toString(Object o) { 393 try { 394 return serialize(o); 395 } catch (Exception e) { 396 throw new RuntimeException(e); 397 } 398 } 399 400 /** 401 * Wraps the specified object inside a {@link StringObject}. 402 * 403 * @param o The object to wrap. 404 * @return The wrapped object. 405 */ 406 public final StringObject toStringObject(Object o) { 407 return new StringObject(this, o); 408 } 409 410 /** 411 * Convenience method for serializing an object and sending it to STDOUT. 412 * 413 * @param o The object to serialize. 414 * @return This object (for method chaining). 415 */ 416 public final WriterSerializer println(Object o) { 417 System.out.println(toString(o)); // NOT DEBUG 418 return this; 419 } 420 421 //----------------------------------------------------------------------------------------------------------------- 422 // Properties 423 //----------------------------------------------------------------------------------------------------------------- 424 425 /** 426 * File charset. 427 * 428 * @see #WSERIALIZER_fileCharset 429 * @return 430 * The character set to use when writing to <c>Files</c> on the file system. 431 */ 432 protected final Charset getFileCharset() { 433 return fileCharset; 434 } 435 436 /** 437 * Maximum indentation. 438 * 439 * @see #WSERIALIZER_maxIndent 440 * @return 441 * The maximum indentation level in the serialized document. 442 */ 443 protected final int getMaxIndent() { 444 return maxIndent; 445 } 446 447 /** 448 * Quote character. 449 * 450 * @see #WSERIALIZER_quoteChar 451 * @return 452 * The character used for quoting attributes and values. 453 */ 454 protected char getQuoteChar() { 455 return quoteChar; 456 } 457 458 /** 459 * Output stream charset. 460 * 461 * @see #WSERIALIZER_streamCharset 462 * @return 463 * The character set to use when writing to <c>OutputStreams</c> and byte arrays. 464 */ 465 protected final Charset getStreamCharset() { 466 return streamCharset; 467 } 468 469 /** 470 * Trim strings. 471 * 472 * @see #WSERIALIZER_useWhitespace 473 * @return 474 * When enabled, whitespace is added to the output to improve readability. 475 */ 476 protected final boolean isUseWhitespace() { 477 return useWhitespace; 478 } 479 480 //----------------------------------------------------------------------------------------------------------------- 481 // Other methods 482 //----------------------------------------------------------------------------------------------------------------- 483 484 @Override /* Context */ 485 public OMap toMap() { 486 return super.toMap() 487 .a("WriterSerializer", new DefaultFilteringOMap() 488 .a("fileCharset", fileCharset) 489 .a("maxIndent", maxIndent) 490 .a("quoteChar", quoteChar) 491 .a("streamCharset", streamCharset) 492 .a("useWhitespace", useWhitespace) 493 ); 494 } 495}