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