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.io.*; 016 017import org.apache.juneau.*; 018import org.apache.juneau.annotation.*; 019import org.apache.juneau.http.*; 020import org.apache.juneau.parser.*; 021 022/** 023 * Parent class for all Juneau serializers. 024 * 025 * <h5 class='topic'>Description</h5> 026 * 027 * Base serializer class that serves as the parent class for all serializers. 028 * 029 * <p> 030 * The purpose of this class is: 031 * <ul> 032 * <li>Maintain a read-only configuration state of a serializer. 033 * <li>Create session objects used for serializing POJOs (i.e. {@link SerializerSession}). 034 * <li>Provide convenience methods for serializing POJOs without having to construct session objects. 035 * </ul> 036 * 037 * <p> 038 * Subclasses should extend directly from {@link OutputStreamSerializer} or {@link WriterSerializer} depending on 039 * whether it's a stream or character based serializer. 040 */ 041public abstract class Serializer extends BeanContext { 042 043 //------------------------------------------------------------------------------------------------------------------- 044 // Configurable properties 045 //------------------------------------------------------------------------------------------------------------------- 046 047 private static final String PREFIX = "Serializer."; 048 049 /** 050 * Configuration property: Abridged output. 051 * 052 * <h5 class='section'>Property:</h5> 053 * <ul> 054 * <li><b>Name:</b> <js>"Serializer.abridged.b"</js> 055 * <li><b>Data type:</b> <code>Boolean</code> 056 * <li><b>Default:</b> <jk>false</jk> 057 * <li><b>Session-overridable:</b> <jk>true</jk> 058 * <li><b>Methods:</b> 059 * <ul> 060 * <li class='jm'>{@link SerializerBuilder#abridged(boolean)} 061 * <li class='jm'>{@link SerializerBuilder#abridged()} 062 * </ul> 063 * </ul> 064 * 065 * <h5 class='section'>Description:</h5> 066 * <p> 067 * When enabled, it is assumed that the parser knows the exact Java POJO type being parsed, and therefore top-level 068 * type information that might normally be included to determine the data type will not be serialized. 069 * 070 * <p> 071 * For example, when serializing a top-level POJO with a {@link Bean#typeName() @Bean.typeName()} value, a 072 * <js>'_type'</js> attribute will only be added when this setting is enabled. 073 * 074 * <p> 075 * Note the differences between the following settings: 076 * <ul> 077 * <li class='jf'>{@link #SERIALIZER_abridged} - Affects whether <js>'_type'</js> is added to root node. 078 * <li class='jf'>{@link #SERIALIZER_addBeanTypeProperties} - Affects whether <js>'_type'</js> is added to any nodes. 079 * </ul> 080 * 081 * <h5 class='section'>Example:</h5> 082 * <p class='bcode'> 083 * <jc>// Create a serializer that doesn't add _type to root node.</jc> 084 * WriterSerializer s = JsonSerializer 085 * .<jsm>create</jsm>() 086 * .abridged() 087 * .build(); 088 * 089 * <jc>// Same, but use property.</jc> 090 * WriterSerializer s = JsonSerializer 091 * .<jsm>create</jsm>() 092 * .set(<jsf>SERIALIZER_abridged</jsf>, <jk>true</jk>) 093 * .build(); 094 * 095 * <jc>// The bean we want to serialize.</jc> 096 * <ja>@Bean</ja>(typeName=<js>"mybean"</js>) 097 * <jk>public class</jk> MyBean {...} 098 * 099 * <jc>// Will not contain '_type' attribute even though there's a type name on the bean.</jc> 100 * String json = s.serialize(<jk>new</jk> MyBean()); 101 * 102 * <jc>// '_type' wasn't needed on the parse side because we know the type being parsed.</jc> 103 * MyBean myBean = JsonParser.<jsf>DEFAULT</jsf>.parse(json, MyBean.<jk>class</jk>); 104 * </p> 105 */ 106 public static final String SERIALIZER_abridged = PREFIX + "abridged.b"; 107 108 /** 109 * Configuration property: Add <js>"_type"</js> properties when needed. 110 * 111 * <h5 class='section'>Property:</h5> 112 * <ul> 113 * <li><b>Name:</b> <js>"Serializer.addBeanTypeProperties.b"</js> 114 * <li><b>Data type:</b> <code>Boolean</code> 115 * <li><b>Default:</b> <jk>true</jk> 116 * <li><b>Session-overridable:</b> <jk>true</jk> 117 * <li><b>Methods:</b> 118 * <ul> 119 * <li class='jm'>{@link SerializerBuilder#addBeanTypeProperties(boolean)} 120 * </ul> 121 * </ul> 122 * 123 * <h5 class='section'>Description:</h5> 124 * <p> 125 * If <jk>true</jk>, then <js>"_type"</js> properties will be added to beans if their type cannot be inferred 126 * through reflection. 127 * 128 * <p> 129 * This is used to recreate the correct objects during parsing if the object types cannot be inferred. 130 * <br>For example, when serializing a <code>Map<String,Object></code> field where the bean class cannot be determined from 131 * the type of the values. 132 * 133 * <p> 134 * Note the differences between the following settings: 135 * <ul> 136 * <li class='jf'>{@link #SERIALIZER_abridged} - Affects whether <js>'_type'</js> is added to root node. 137 * <li class='jf'>{@link #SERIALIZER_addBeanTypeProperties} - Affects whether <js>'_type'</js> is added to any nodes. 138 * </ul> 139 * 140 * <h5 class='section'>Example:</h5> 141 * <p class='bcode'> 142 * <jc>// Create a serializer that never adds _type to nodes.</jc> 143 * WriterSerializer s = JsonSerializer 144 * .<jsm>create</jsm>() 145 * .addBeanTypeProperties(<jk>false</jk>) 146 * .build(); 147 * 148 * <jc>// Same, but use property.</jc> 149 * WriterSerializer s = JsonSerializer 150 * .<jsm>create</jsm>() 151 * .set(<jsf>SERIALIZER_addBeanTypeProperties</jsf>, <jk>false</jk>) 152 * .build(); 153 * 154 * <jc>// A map of objects we want to serialize.</jc> 155 * <ja>@Bean</ja>(typeName=<js>"mybean"</js>) 156 * <jk>public class</jk> MyBean {...} 157 * 158 * Map<String,Object> m = new HashMap<>(); 159 * m.put(<js>"foo"</js>, <jk>new</jk> MyBean()); 160 * 161 * <jc>// Will not contain '_type' attribute even though type name is on bean and we're serializing</jc> 162 * <jc>// a map of generic objects.</jc> 163 * String json = s.serialize(m); 164 * </p> 165 */ 166 public static final String SERIALIZER_addBeanTypeProperties = PREFIX + "addBeanTypeProperties.b"; 167 168 /** 169 * Configuration property: Automatically detect POJO recursions. 170 * 171 * <h5 class='section'>Property:</h5> 172 * <ul> 173 * <li><b>Name:</b> <js>"Serializer.detectRecursions.b"</js> 174 * <li><b>Data type:</b> <code>Boolean</code> 175 * <li><b>Default:</b> <jk>false</jk> 176 * <li><b>Session-overridable:</b> <jk>true</jk> 177 * <li><b>Methods:</b> 178 * <ul> 179 * <li class='jm'>{@link SerializerBuilder#detectRecursions(boolean)} 180 * <li class='jm'>{@link SerializerBuilder#detectRecursions()} 181 * </ul> 182 * </ul> 183 * 184 * <h5 class='section'>Description:</h5> 185 * <p> 186 * Specifies that recursions should be checked for during serialization. 187 * 188 * <p> 189 * Recursions can occur when serializing models that aren't true trees but rather contain loops. 190 * <br>In general, unchecked recursions cause stack-overflow-errors. 191 * <br>These show up as {@link ParseException ParseExceptions} with the message <js>"Depth too deep. Stack overflow occurred."</js>. 192 * 193 * <p> 194 * The behavior when recursions are detected depends on the value for {@link #SERIALIZER_ignoreRecursions}. 195 * 196 * <p> 197 * For example, if a model contains the links A->B->C->A, then the JSON generated will look like 198 * the following when <jsf>SERIALIZER_ignoreRecursions</jsf> is <jk>true</jk>... 199 * 200 * <p class='bcode'> 201 * {A:{B:{C:<jk>null</jk>}}} 202 * </p> 203 * 204 * <h5 class='section'>Notes:</h5> 205 * <ul class='spaced-list'> 206 * <li> 207 * Checking for recursion can cause a small performance penalty. 208 * </ul> 209 * 210 * <h5 class='section'>Example:</h5> 211 * <p class='bcode'> 212 * <jc>// Create a serializer that never adds _type to nodes.</jc> 213 * WriterSerializer s = JsonSerializer 214 * .<jsm>create</jsm>() 215 * .detectRecursions() 216 * .ignoreRecursions() 217 * .build(); 218 * 219 * <jc>// Same, but use property.</jc> 220 * WriterSerializer s = JsonSerializer 221 * .<jsm>create</jsm>() 222 * .set(<jsf>SERIALIZER_detectRecursions</jsf>, <jk>true</jk>) 223 * .set(<jsf>SERIALIZER_ignoreRecursions</jsf>, <jk>true</jk>) 224 * .build(); 225 * 226 * <jc>// Create a POJO model with a recursive loop.</jc> 227 * <jk>public class</jk> A { 228 * <jk>public</jk> Object <jf>f</jf>; 229 * } 230 * A a = <jk>new</jk> A(); 231 * a.<jf>f</jf> = a; 232 * 233 * <jc>// Produces "{f:null}"</jc> 234 * String json = s.serialize(a); 235 * </p> 236 */ 237 public static final String SERIALIZER_detectRecursions = PREFIX + "detectRecursions.b"; 238 239 /** 240 * Configuration property: Ignore recursion errors. 241 * 242 * <h5 class='section'>Property:</h5> 243 * <ul> 244 * <li><b>Name:</b> <js>"Serializer.ignoreRecursions.b"</js> 245 * <li><b>Data type:</b> <code>Boolean</code> 246 * <li><b>Default:</b> <jk>false</jk> 247 * <li><b>Session-overridable:</b> <jk>true</jk> 248 * <li><b>Methods:</b> 249 * <ul> 250 * <li class='jm'>{@link SerializerBuilder#ignoreRecursions(boolean)} 251 * <li class='jm'>{@link SerializerBuilder#ignoreRecursions()} 252 * </ul> 253 * </ul> 254 * 255 * <h5 class='section'>Description:</h5> 256 * <p> 257 * Used in conjunction with {@link #SERIALIZER_detectRecursions}. 258 * <br>Setting is ignored if <jsf>SERIALIZER_detectRecursions</jsf> is <jk>false</jk>. 259 * 260 * <p> 261 * If <jk>true</jk>, when we encounter the same object when serializing a tree, we set the value to <jk>null</jk>. 262 * <br>Otherwise, a {@link SerializeException} is thrown with the message <js>"Recursion occurred, stack=..."</js>. 263 */ 264 public static final String SERIALIZER_ignoreRecursions = PREFIX + "ignoreRecursions.b"; 265 266 /** 267 * Configuration property: Initial depth. 268 * 269 * <h5 class='section'>Property:</h5> 270 * <ul> 271 * <li><b>Name:</b> <js>"Serializer.initialDepth.i"</js> 272 * <li><b>Data type:</b> <code>Integer</code> 273 * <li><b>Default:</b> <code>0</code> 274 * <li><b>Session-overridable:</b> <jk>true</jk> 275 * <li><b>Methods:</b> 276 * <ul> 277 * <li class='jm'>{@link SerializerBuilder#initialDepth(int)} 278 * </ul> 279 * </ul> 280 * 281 * <h5 class='section'>Description:</h5> 282 * <p> 283 * The initial indentation level at the root. 284 * <br>Useful when constructing document fragments that need to be indented at a certain level. 285 * 286 * <h5 class='section'>Example:</h5> 287 * <p class='bcode'> 288 * <jc>// Create a serializer with whitespace enabled and an initial depth of 2.</jc> 289 * WriterSerializer s = JsonSerializer 290 * .<jsm>create</jsm>() 291 * .ws() 292 * .initialDepth(2) 293 * .build(); 294 * 295 * <jc>// Same, but use property.</jc> 296 * WriterSerializer s = JsonSerializer 297 * .<jsm>create</jsm>() 298 * .set(<jsf>SERIALIZER_useWhitespace</jsf>, <jk>true</jk>) 299 * .set(<jsf>SERIALIZER_initialDepth</jsf>, 2) 300 * .build(); 301 * 302 * <jc>// Produces "\t\t{\n\t\t\t'foo':'bar'\n\t\t}\n"</jc> 303 * String json = s.serialize(<jk>new</jk> MyBean()); 304 * </p> 305 */ 306 public static final String SERIALIZER_initialDepth = PREFIX + "initialDepth.i"; 307 308 /** 309 * Configuration property: Serializer listener. 310 * 311 * <h5 class='section'>Property:</h5> 312 * <ul> 313 * <li><b>Name:</b> <js>"Serializer.listener.c"</js> 314 * <li><b>Data type:</b> <code>Class<? extends SerializerListener></code> 315 * <li><b>Default:</b> <jk>null</jk> 316 * <li><b>Session-overridable:</b> <jk>true</jk> 317 * <li><b>Methods:</b> 318 * <ul> 319 * <li class='jm'>{@link SerializerBuilder#listener(Class)} 320 * </ul> 321 * </ul> 322 * 323 * <h5 class='section'>Description:</h5> 324 * <p> 325 * Class used to listen for errors and warnings that occur during serialization. 326 * 327 * <h5 class='section'>Example:</h5> 328 * <p class='bcode'> 329 * <jc>// Define our serializer listener.</jc> 330 * <jc>// Simply captures all errors.</jc> 331 * <jk>public class</jk> MySerializerListener <jk>extends</jk> SerializerListener { 332 * 333 * <jc>// A simple property to store our events.</jc> 334 * <jk>public</jk> List<String> <jf>events</jf> = <jk>new</jk> LinkedList<>(); 335 * 336 * <ja>@Override</ja> 337 * <jk>public</jk> <T> <jk>void</jk> onError(SerializerSession session, Throwable t, String msg) { 338 * <jf>events</jf>.add(session.getLastLocation() + <js>","</js> + msg + <js>","</js> + t); 339 * } 340 * } 341 * 342 * <jc>// Create a serializer using our listener.</jc> 343 * WriterSerializer s = JsonSerializer. 344 * .<jsm>create</jsm>() 345 * .listener(MySerializerListener.<jk>class</jk>) 346 * .build(); 347 * 348 * <jc>// Same, but use property.</jc> 349 * WriterSerializer s = JsonSerializer. 350 * .<jsm>create</jsm>() 351 * .set(<jsf>SERIALIZER_listener</jsf>, MySerializerListener.<jk>class</jk>) 352 * .build(); 353 * 354 * <jc>// Create a session object.</jc> 355 * <jc>// Needed because listeners are created per-session.</jc> 356 * <jk>try</jk> (WriterSerializerSession ss = s.createSession()) { 357 * 358 * <jc>// Serialize a bean.</jc> 359 * String json = ss.serialize(<jk>new</jk> MyBean()); 360 * 361 * <jc>// Get the listener.</jc> 362 * MySerializerListener l = ss.getListener(MySerializerListener.<jk>class</jk>); 363 * 364 * <jc>// Dump the results to the console.</jc> 365 * JsonSerializer.<jsf>DEFAULT_LAX</jsf>.println(l.<jf>events</jf>); 366 * } 367 * </p> 368 */ 369 public static final String SERIALIZER_listener = PREFIX + "listener.c"; 370 371 /** 372 * Configuration property: Max serialization depth. 373 * 374 * <h5 class='section'>Property:</h5> 375 * <ul> 376 * <li><b>Name:</b> <js>"Serializer.maxDepth.i"</js> 377 * <li><b>Data type:</b> <code>Integer</code> 378 * <li><b>Default:</b> <code>100</code> 379 * <li><b>Session-overridable:</b> <jk>true</jk> 380 * <li><b>Methods:</b> 381 * <ul> 382 * <li class='jm'>{@link SerializerBuilder#maxDepth(int)} 383 * </ul> 384 * </ul> 385 * 386 * <h5 class='section'>Description:</h5> 387 * <p> 388 * Abort serialization if specified depth is reached in the POJO tree. 389 * <br>If this depth is exceeded, an exception is thrown. 390 * 391 * <h5 class='section'>Example:</h5> 392 * <p class='bcode'> 393 * <jc>// Create a serializer that throws an exception if the depth is greater than 20.</jc> 394 * WriterSerializer s = JsonSerializer 395 * .<jsm>create</jsm>() 396 * .maxDepth(20) 397 * .build(); 398 * 399 * <jc>// Same, but use property.</jc> 400 * WriterSerializer s = JsonSerializer 401 * .<jsm>create</jsm>() 402 * .set(<jsf>SERIALIZER_maxDepth</jsf>, 20) 403 * .build(); 404 * </p> 405 */ 406 public static final String SERIALIZER_maxDepth = PREFIX + "maxDepth.i"; 407 408 /** 409 * Configuration property: Maximum indentation. 410 * 411 * <h5 class='section'>Property:</h5> 412 * <ul> 413 * <li><b>Name:</b> <js>"Serializer.maxIndent.i"</js> 414 * <li><b>Data type:</b> <code>Integer</code> 415 * <li><b>Default:</b> <code>100</code> 416 * <li><b>Session-overridable:</b> <jk>true</jk> 417 * <li><b>Methods:</b> 418 * <ul> 419 * <li class='jm'>{@link SerializerBuilder#maxIndent(int)} 420 * </ul> 421 * </ul> 422 * 423 * <h5 class='section'>Description:</h5> 424 * <p> 425 * Specifies the maximum indentation level in the serialized document. 426 * 427 * <p> 428 * This setting does not apply to the MessagePack or RDF serializers. 429 * 430 * <h5 class='section'>Example:</h5> 431 * <p class='bcode'> 432 * <jc>// Create a serializer that indents a maximum of 20 tabs.</jc> 433 * WriterSerializer s = JsonSerializer 434 * .<jsm>create</jsm>() 435 * .maxIndent(20) 436 * .build(); 437 * 438 * <jc>// Same, but use property.</jc> 439 * WriterSerializer s = JsonSerializer 440 * .<jsm>create</jsm>() 441 * .set(<jsf>SERIALIZER_maxIndent</jsf>, 20) 442 * .build(); 443 * </p> 444 */ 445 public static final String SERIALIZER_maxIndent = PREFIX + "maxIndent.i"; 446 447 /** 448 * Configuration property: Quote character. 449 * 450 * <h5 class='section'>Property:</h5> 451 * <ul> 452 * <li><b>Name:</b> <js>"Serializer.quoteChar.s"</js> 453 * <li><b>Data type:</b> <code>String</code> 454 * <li><b>Default:</b> <js>"\""</js> 455 * <li><b>Session-overridable:</b> <jk>true</jk> 456 * <li><b>Methods:</b> 457 * <ul> 458 * <li class='jm'>{@link SerializerBuilder#quoteChar(char)} 459 * <li class='jm'>{@link SerializerBuilder#sq()} 460 * </ul> 461 * </ul> 462 * 463 * <h5 class='section'>Description:</h5> 464 * <p> 465 * This is the character used for quoting attributes and values. 466 * 467 * <p> 468 * This setting does not apply to the MessagePack or RDF serializers. 469 * 470 * <h5 class='section'>Example:</h5> 471 * <p class='bcode'> 472 * <jc>// Create a serializer that uses single quotes.</jc> 473 * WriterSerializer s = JsonSerializer 474 * .<jsm>create</jsm>() 475 * .sq() 476 * .build(); 477 * 478 * <jc>// Same, but use property.</jc> 479 * WriterSerializer s = JsonSerializer 480 * .<jsm>create</jsm>() 481 * .set(<jsf>SERIALIZER_quoteChar</jsf>, <js>'\''</js>) 482 * .build(); 483 * </p> 484 */ 485 public static final String SERIALIZER_quoteChar = PREFIX + "quoteChar.s"; 486 487 /** 488 * Configuration property: Sort arrays and collections alphabetically. 489 * 490 * <h5 class='section'>Property:</h5> 491 * <ul> 492 * <li><b>Name:</b> <js>"Serializer.sortCollections.b"</js> 493 * <li><b>Data type:</b> <code>Boolean</code> 494 * <li><b>Default:</b> <jk>false</jk> 495 * <li><b>Session-overridable:</b> <jk>true</jk> 496 * <li><b>Methods:</b> 497 * <ul> 498 * <li class='jm'>{@link SerializerBuilder#sortCollections(boolean)} 499 * <li class='jm'>{@link SerializerBuilder#sortCollections()} 500 * </ul> 501 * </ul> 502 * 503 * <h5 class='section'>Description:</h5> 504 * 505 * <p> 506 * Copies and sorts the contents of arrays and collections before serializing them. 507 * 508 * <p> 509 * Note that this introduces a performance penalty. 510 * 511 * <h5 class='section'>Example:</h5> 512 * <p class='bcode'> 513 * <jc>// Create a serializer that sorts arrays and collections before serialization.</jc> 514 * WriterSerializer s = JsonSerializer 515 * .<jsm>create</jsm>() 516 * .sortCollections() 517 * .build(); 518 * 519 * <jc>// Same, but use property.</jc> 520 * WriterSerializer s = JsonSerializer 521 * .<jsm>create</jsm>() 522 * .set(<jsf>SERIALIZER_sortCollections</jsf>, <jk>true</jk>) 523 * .build(); 524 * </p> 525 */ 526 public static final String SERIALIZER_sortCollections = PREFIX + "sortCollections.b"; 527 528 /** 529 * Configuration property: Sort maps alphabetically. 530 * 531 * <h5 class='section'>Property:</h5> 532 * <ul> 533 * <li><b>Name:</b> <js>"Serializer.sortMaps.b"</js> 534 * <li><b>Data type:</b> <code>Boolean</code> 535 * <li><b>Default:</b> <jk>false</jk> 536 * <li><b>Session-overridable:</b> <jk>true</jk> 537 * <li><b>Methods:</b> 538 * <ul> 539 * <li class='jm'>{@link SerializerBuilder#sortMaps(boolean)} 540 * <li class='jm'>{@link SerializerBuilder#sortMaps()} 541 * </ul> 542 * </ul> 543 * 544 * <h5 class='section'>Description:</h5> 545 * 546 * <p> 547 * Copies and sorts the contents of maps by their keys before serializing them. 548 * 549 * <p> 550 * Note that this introduces a performance penalty. 551 * 552 * <h5 class='section'>Example:</h5> 553 * <p class='bcode'> 554 * <jc>// Create a serializer that sorts maps before serialization.</jc> 555 * WriterSerializer s = JsonSerializer 556 * .<jsm>create</jsm>() 557 * .sortMaps() 558 * .build(); 559 * 560 * <jc>// Same, but use property.</jc> 561 * WriterSerializer s = JsonSerializer 562 * .<jsm>create</jsm>() 563 * .set(<jsf>SERIALIZER_sortMaps</jsf>, <jk>true</jk>) 564 * .build(); 565 * </p> 566 */ 567 public static final String SERIALIZER_sortMaps = PREFIX + "sortMaps.b"; 568 569 /** 570 * Configuration property: Trim empty lists and arrays. 571 * 572 * <h5 class='section'>Property:</h5> 573 * <ul> 574 * <li><b>Name:</b> <js>"Serializer.trimEmptyCollections.b"</js> 575 * <li><b>Data type:</b> <code>Boolean</code> 576 * <li><b>Default:</b> <jk>false</jk> 577 * <li><b>Session-overridable:</b> <jk>true</jk> 578 * <li><b>Methods:</b> 579 * <ul> 580 * <li class='jm'>{@link SerializerBuilder#trimEmptyCollections(boolean)} 581 * <li class='jm'>{@link SerializerBuilder#trimEmptyCollections()} 582 * </ul> 583 * </ul> 584 * 585 * <h5 class='section'>Description:</h5> 586 * 587 * <p> 588 * If <jk>true</jk>, empty lists and arrays will not be serialized. 589 * 590 * <p> 591 * Note that enabling this setting has the following effects on parsing: 592 * <ul class='spaced-list'> 593 * <li> 594 * Map entries with empty list values will be lost. 595 * <li> 596 * Bean properties with empty list values will not be set. 597 * </ul> 598 * 599 * <h5 class='section'>Example:</h5> 600 * <p class='bcode'> 601 * <jc>// Create a serializer that skips empty arrays and collections.</jc> 602 * WriterSerializer s = JsonSerializer 603 * .<jsm>create</jsm>() 604 * .trimEmptyCollections() 605 * .build(); 606 * 607 * <jc>// Same, but use property.</jc> 608 * WriterSerializer s = JsonSerializer 609 * .<jsm>create</jsm>() 610 * .set(<jsf>SERIALIZER_trimEmptyCollections</jsf>, <jk>true</jk>) 611 * .build(); 612 * </p> 613 */ 614 public static final String SERIALIZER_trimEmptyCollections = PREFIX + "trimEmptyCollections.b"; 615 616 /** 617 * Configuration property: Trim empty maps. 618 * 619 * <h5 class='section'>Property:</h5> 620 * <ul> 621 * <li><b>Name:</b> <js>"Serializer.trimEmptyMaps.b"</js> 622 * <li><b>Data type:</b> <code>Boolean</code> 623 * <li><b>Default:</b> <jk>false</jk> 624 * <li><b>Session-overridable:</b> <jk>true</jk> 625 * <li><b>Methods:</b> 626 * <ul> 627 * <li class='jm'>{@link SerializerBuilder#trimEmptyMaps(boolean)} 628 * <li class='jm'>{@link SerializerBuilder#trimEmptyMaps()} 629 * </ul> 630 * </ul> 631 * 632 * <h5 class='section'>Description:</h5> 633 * <p> 634 * If <jk>true</jk>, empty map values will not be serialized to the output. 635 * 636 * <p> 637 * Note that enabling this setting has the following effects on parsing: 638 * <ul class='spaced-list'> 639 * <li> 640 * Bean properties with empty map values will not be set. 641 * </ul> 642 * 643 * <h5 class='section'>Example:</h5> 644 * <p class='bcode'> 645 * <jc>// Create a serializer that skips empty maps.</jc> 646 * WriterSerializer s = JsonSerializer 647 * .<jsm>create</jsm>() 648 * .trimEmptyMaps() 649 * .build(); 650 * 651 * <jc>// Same, but use property.</jc> 652 * WriterSerializer s = JsonSerializer 653 * .<jsm>create</jsm>() 654 * .set(<jsf>SERIALIZER_trimEmptyMaps</jsf>, <jk>true</jk>) 655 * .build(); 656 * </p> 657 */ 658 public static final String SERIALIZER_trimEmptyMaps = PREFIX + "trimEmptyMaps.b"; 659 660 /** 661 * Configuration property: Trim null bean property values. 662 * 663 * <h5 class='section'>Property:</h5> 664 * <ul> 665 * <li><b>Name:</b> <js>"Serializer.trimNullProperties.b"</js> 666 * <li><b>Data type:</b> <code>Boolean</code> 667 * <li><b>Default:</b> <jk>true</jk> 668 * <li><b>Session-overridable:</b> <jk>true</jk> 669 * <li><b>Methods:</b> 670 * <ul> 671 * <li class='jm'>{@link SerializerBuilder#trimNullProperties(boolean)} 672 * </ul> 673 * </ul> 674 * 675 * <h5 class='section'>Description:</h5> 676 * <p> 677 * If <jk>true</jk>, null bean values will not be serialized to the output. 678 * 679 * <p> 680 * Note that enabling this setting has the following effects on parsing: 681 * <ul class='spaced-list'> 682 * <li> 683 * Map entries with <jk>null</jk> values will be lost. 684 * </ul> 685 * 686 * <h5 class='section'>Example:</h5> 687 * <p class='bcode'> 688 * <jc>// Create a serializer that serializes null properties.</jc> 689 * WriterSerializer s = JsonSerializer 690 * .<jsm>create</jsm>() 691 * .trimNullProperties(<jk>false</jk>) 692 * .build(); 693 * 694 * <jc>// Same, but use property.</jc> 695 * WriterSerializer s = JsonSerializer 696 * .<jsm>create</jsm>() 697 * .set(<jsf>SERIALIZER_trimNullProperties</jsf>, <jk>false</jk>) 698 * .build(); 699 * </p> 700 */ 701 public static final String SERIALIZER_trimNullProperties = PREFIX + "trimNullProperties.b"; 702 703 /** 704 * Configuration property: Trim strings. 705 * 706 * <h5 class='section'>Property:</h5> 707 * <ul> 708 * <li><b>Name:</b> <js>"Serializer.trimStrings.b"</js> 709 * <li><b>Data type:</b> <code>Boolean</code> 710 * <li><b>Default:</b> <jk>false</jk> 711 * <li><b>Session-overridable:</b> <jk>true</jk> 712 * <li><b>Methods:</b> 713 * <ul> 714 * <li class='jm'>{@link SerializerBuilder#trimStrings(boolean)} 715 * <li class='jm'>{@link SerializerBuilder#trimStrings()} 716 * </ul> 717 * </ul> 718 * 719 * <h5 class='section'>Description:</h5> 720 * <p> 721 * If <jk>true</jk>, string values will be trimmed of whitespace using {@link String#trim()} before being serialized. 722 * 723 * <h5 class='section'>Example:</h5> 724 * <p class='bcode'> 725 * <jc>// Create a serializer that trims strings before serialization.</jc> 726 * WriterSerializer s = JsonSerializer 727 * .<jsm>create</jsm>() 728 * .trimStrings() 729 * .build(); 730 * 731 * <jc>// Same, but use property.</jc> 732 * WriterSerializer s = JsonSerializer 733 * .<jsm>create</jsm>() 734 * .set(<jsf>SERIALIZER_trimStrings</jsf>, <jk>true</jk>) 735 * .build(); 736 * 737 * Map<String,String> m = <jk>new</jk> HashMap<>(); 738 * m.put(<js>" foo "</js>, <js>" bar "</js>); 739 * 740 * <jc>// Produces "{foo:'bar'}"</jc> 741 * String json = JsonSerializer.<jsf>DEFAULT_LAX</jsf>.toString(m); 742 * </p> 743 */ 744 public static final String SERIALIZER_trimStrings = PREFIX + "trimStrings.b"; 745 746 /** 747 * Configuration property: URI context bean. 748 * 749 * <h5 class='section'>Property:</h5> 750 * <ul> 751 * <li><b>Name:</b> <js>"Serializer.uriContext.s"</js> 752 * <li><b>Data type:</b> <code>String</code> (JSON object representing a {@link UriContext}) 753 * <li><b>Default:</b> <js>"{}"</js> 754 * <li><b>Session-overridable:</b> <jk>true</jk> 755 * <li><b>Methods:</b> 756 * <ul> 757 * <li class='jm'>{@link SerializerBuilder#uriContext(UriContext)} 758 * <li class='jm'>{@link SerializerBuilder#uriContext(String)} 759 * </ul> 760 * </ul> 761 * 762 * <h5 class='section'>Description:</h5> 763 * <p> 764 * Bean used for resolution of URIs to absolute or root-relative form. 765 * 766 * <h5 class='section'>Example:</h5> 767 * <p class='bcode'> 768 * <jc>// Our URI contextual information.</jc> 769 * String authority = <js>"http://localhost:10000"</js>; 770 * String contextRoot = <js>"/myContext"</js>; 771 * String servletPath = <js>"/myServlet"</js>; 772 * String pathInfo = <js>"/foo"</js>; 773 * 774 * <jc>// Create a UriContext object.</jc> 775 * UriContext uriContext = <jk>new</jk> UriContext(authority, contextRoot, servletPath, pathInfo); 776 * 777 * <jc>// Associate it with our serializer.</jc> 778 * WriterSerializer s = JsonSerializer 779 * .<jsm>create</jsm>() 780 * .uriContext(uriContext) 781 * .build(); 782 * 783 * <jc>// Same, but specify as a JSON string.</jc> 784 * WriterSerializer s = JsonSerializer 785 * .<jsm>create</jsm>() 786 * .uriContext(<js>"{authority:'http://localhost:10000',contextRoot:'/myContext',servletPath:'/myServlet',pathInfo:'/foo'}"</js>) 787 * .build(); 788 * 789 * <jc>// Same, but use property.</jc> 790 * WriterSerializer s = JsonSerializer 791 * .<jsm>create</jsm>() 792 * .set(<jsf>SERIALIZER_uriContext</jsf>, uriContext) 793 * .build(); 794 * 795 * <jc>// Same, but define it on the session args instead.</jc> 796 * SerializerSessionArgs sessionArgs = <jk>new</jk> SerializerSessionArgs().uriContext(uriContext); 797 * <jk>try</jk> (WriterSerializerSession session = s.createSession(sessionArgs)) { 798 * ... 799 * } 800 * </p> 801 * 802 * <h5 class='section'>See Also:</h5> 803 * <ul> 804 * <li class='link'><a class="doclink" href="../../../../overview-summary.html#juneau-marshall.URIs">Overview > juneau-marshall > URIs</a> 805 * </ul> 806 */ 807 public static final String SERIALIZER_uriContext = PREFIX + "uriContext.s"; 808 809 /** 810 * Configuration property: URI relativity. 811 * 812 * <h5 class='section'>Property:</h5> 813 * <ul> 814 * <li><b>Name:</b> <js>"Serializer.uriRelativity.s"</js> 815 * <li><b>Data type:</b> <code>String</code> ({@link UriRelativity}) 816 * <li><b>Default:</b> <js>"RESOURCE"</js> 817 * <li><b>Session-overridable:</b> <jk>true</jk> 818 * <li><b>Methods:</b> 819 * <ul> 820 * <li class='jm'>{@link SerializerBuilder#uriRelativity(UriRelativity)} 821 * <li class='jm'>{@link SerializerBuilder#uriRelativity(String)} 822 * </ul> 823 * </ul> 824 * 825 * <h5 class='section'>Description:</h5> 826 * <p> 827 * Defines what relative URIs are relative to when serializing any of the following: 828 * <ul> 829 * <li>{@link java.net.URI} 830 * <li>{@link java.net.URL} 831 * <li>Properties and classes annotated with {@link org.apache.juneau.annotation.URI @URI} 832 * </ul> 833 * 834 * <p> 835 * Possible values are: 836 * <ul> 837 * <li class='jf'>{@link UriRelativity#RESOURCE} 838 * - Relative URIs should be considered relative to the servlet URI. 839 * <li class='jf'>{@link UriRelativity#PATH_INFO} 840 * - Relative URIs should be considered relative to the request URI. 841 * </ul> 842 * 843 * <h5 class='figure'>Example:</h5> 844 * <p class='bcode'> 845 * <jc>// Define a serializer that converts resource-relative URIs to absolute form.</jc> 846 * WriterSerializer s = JsonSerializer 847 * .<jsm>create</jsm>() 848 * .uriContext(<js>"{authority:'http://localhost:10000',contextRoot:'/myContext',servletPath:'/myServlet',pathInfo:'/foo'}"</js>) 849 * .uriResolution(<jsf>ABSOLUTE</jsf>) 850 * .uriRelativity(<jsf>RESOURCE</jsf>) 851 * .build(); 852 * </p> 853 * 854 * <h5 class='section'>See Also:</h5> 855 * <ul> 856 * <li class='link'><a class="doclink" href="../../../../overview-summary.html#juneau-marshall.URIs">Overview > juneau-marshall > URIs</a> 857 * </ul> 858 */ 859 public static final String SERIALIZER_uriRelativity = PREFIX + "uriRelativity.s"; 860 861 /** 862 * Configuration property: URI resolution. 863 * 864 * <h5 class='section'>Property:</h5> 865 * <ul> 866 * <li><b>Name:</b> <js>"Serializer.uriResolution.s"</js> 867 * <li><b>Data type:</b> <code>String</code> ({@link UriResolution}) 868 * <li><b>Default:</b> <js>"NONE"</js> 869 * <li><b>Session-overridable:</b> <jk>true</jk> 870 * <li><b>Methods:</b> 871 * <ul> 872 * <li class='jm'>{@link SerializerBuilder#uriResolution(UriResolution)} 873 * <li class='jm'>{@link SerializerBuilder#uriResolution(String)} 874 * </ul> 875 * </ul> 876 * 877 * <h5 class='section'>Description:</h5> 878 * <p> 879 * Defines the resolution level for URIs when serializing any of the following: 880 * <ul> 881 * <li>{@link java.net.URI} 882 * <li>{@link java.net.URL} 883 * <li>Properties and classes annotated with {@link org.apache.juneau.annotation.URI @URI} 884 * </ul> 885 * 886 * <p> 887 * Possible values are: 888 * <ul> 889 * <li class='jf'>{@link UriResolution#ABSOLUTE} 890 * - Resolve to an absolute URL (e.g. <js>"http://host:port/context-root/servlet-path/path-info"</js>). 891 * <li class='jf'>{@link UriResolution#ROOT_RELATIVE} 892 * - Resolve to a root-relative URL (e.g. <js>"/context-root/servlet-path/path-info"</js>). 893 * <li class='jf'>{@link UriResolution#NONE} 894 * - Don't do any URL resolution. 895 * </ul> 896 * 897 * <h5 class='figure'>Example:</h5> 898 * <p class='bcode'> 899 * <jc>// Define a serializer that converts resource-relative URIs to absolute form.</jc> 900 * WriterSerializer s = JsonSerializer 901 * .<jsm>create</jsm>() 902 * .uriContext(<js>"{authority:'http://localhost:10000',contextRoot:'/myContext',servletPath:'/myServlet',pathInfo:'/foo'}"</js>) 903 * .uriResolution(<jsf>ABSOLUTE</jsf>) 904 * .uriRelativity(<jsf>RESOURCE</jsf>) 905 * .build(); 906 * </p> 907 * 908 * <h5 class='section'>See Also:</h5> 909 * <ul> 910 * <li class='link'><a class="doclink" href="../../../../overview-summary.html#juneau-marshall.URIs">Overview > juneau-marshall > URIs</a> 911 * </ul> 912 */ 913 public static final String SERIALIZER_uriResolution = PREFIX + "uriResolution.s"; 914 915 /** 916 * Configuration property: Use whitespace. 917 * 918 * <h5 class='section'>Property:</h5> 919 * <ul> 920 * <li><b>Name:</b> <js>"Serializer.useWhitespace.b"</js> 921 * <li><b>Data type:</b> <code>Boolean</code> 922 * <li><b>Default:</b> <jk>false</jk> 923 * <li><b>Session-overridable:</b> <jk>true</jk> 924 * <li><b>Methods:</b> 925 * <ul> 926 * <li class='jm'>{@link SerializerBuilder#useWhitespace(boolean)} 927 * <li class='jm'>{@link SerializerBuilder#useWhitespace()} 928 * <li class='jm'>{@link SerializerBuilder#ws()} 929 * </ul> 930 * </ul> 931 * 932 * <h5 class='section'>Description:</h5> 933 * <p> 934 * If <jk>true</jk>, whitespace is added to the output to improve readability. 935 * 936 * <p> 937 * This setting does not apply to the MessagePack serializer. 938 * 939 * <h5 class='section'>Example:</h5> 940 * <p class='bcode'> 941 * <jc>// Create a serializer with whitespace enabled.</jc> 942 * WriterSerializer s = JsonSerializer 943 * .<jsm>create</jsm>() 944 * .ws() 945 * .build(); 946 * 947 * <jc>// Same, but use property.</jc> 948 * WriterSerializer s = JsonSerializer 949 * .<jsm>create</jsm>() 950 * .set(<jsf>SERIALIZER_useWhitespace</jsf>, <jk>true</jk>) 951 * .build(); 952 * 953 * <jc>// Produces "\{\n\t'foo': 'bar'\n\}\n"</jc> 954 * String json = s.serialize(<jk>new</jk> MyBean()); 955 * </p> 956 */ 957 public static final String SERIALIZER_useWhitespace = PREFIX + "useWhitespace.b"; 958 959 960 static final Serializer DEFAULT = new Serializer(PropertyStore.create().build(), "") { 961 @Override 962 public SerializerSession createSession(SerializerSessionArgs args) { 963 throw new NoSuchMethodError(); 964 } 965 }; 966 967 //------------------------------------------------------------------------------------------------------------------- 968 // Instance 969 //------------------------------------------------------------------------------------------------------------------- 970 971 final int maxDepth, initialDepth, maxIndent; 972 final boolean 973 detectRecursions, 974 ignoreRecursions, 975 useWhitespace, 976 addBeanTypeProperties, 977 trimNulls, 978 trimEmptyCollections, 979 trimEmptyMaps, 980 trimStrings, 981 sortCollections, 982 sortMaps, 983 abridged; 984 final char quoteChar; 985 final UriContext uriContext; 986 final UriResolution uriResolution; 987 final UriRelativity uriRelativity; 988 final Class<? extends SerializerListener> listener; 989 990 private final MediaType[] accept; 991 private final MediaType produces; 992 993 // Hidden constructors to force subclass from OuputStreamSerializer or WriterSerializer. 994 Serializer(PropertyStore ps, String produces, String...accept) { 995 super(ps); 996 997 maxDepth = getIntegerProperty(SERIALIZER_maxDepth, 100); 998 initialDepth = getIntegerProperty(SERIALIZER_initialDepth, 0); 999 detectRecursions = getBooleanProperty(SERIALIZER_detectRecursions, false); 1000 ignoreRecursions = getBooleanProperty(SERIALIZER_ignoreRecursions, false); 1001 useWhitespace = getBooleanProperty(SERIALIZER_useWhitespace, false); 1002 maxIndent = getIntegerProperty(SERIALIZER_maxIndent, 100); 1003 addBeanTypeProperties = getBooleanProperty(SERIALIZER_addBeanTypeProperties, true); 1004 trimNulls = getBooleanProperty(SERIALIZER_trimNullProperties, true); 1005 trimEmptyCollections = getBooleanProperty(SERIALIZER_trimEmptyCollections, false); 1006 trimEmptyMaps = getBooleanProperty(SERIALIZER_trimEmptyMaps, false); 1007 trimStrings = getBooleanProperty(SERIALIZER_trimStrings, false); 1008 sortCollections = getBooleanProperty(SERIALIZER_sortCollections, false); 1009 sortMaps = getBooleanProperty(SERIALIZER_sortMaps, false); 1010 abridged = getBooleanProperty(SERIALIZER_abridged, false); 1011 quoteChar = getStringProperty(SERIALIZER_quoteChar, "\"").charAt(0); 1012 uriContext = getProperty(SERIALIZER_uriContext, UriContext.class, UriContext.DEFAULT); 1013 uriResolution = getProperty(SERIALIZER_uriResolution, UriResolution.class, UriResolution.NONE); 1014 uriRelativity = getProperty(SERIALIZER_uriRelativity, UriRelativity.class, UriRelativity.RESOURCE); 1015 listener = getClassProperty(SERIALIZER_listener, SerializerListener.class, null); 1016 1017 this.produces = MediaType.forString(produces); 1018 if (accept.length == 0) { 1019 this.accept = new MediaType[]{this.produces}; 1020 } else { 1021 this.accept = new MediaType[accept.length]; 1022 for (int i = 0; i < accept.length; i++) { 1023 this.accept[i] = MediaType.forString(accept[i]); 1024 } 1025 } 1026 } 1027 1028 @Override /* Context */ 1029 public SerializerBuilder builder() { 1030 return null; 1031 } 1032 1033 //-------------------------------------------------------------------------------- 1034 // Abstract methods 1035 //-------------------------------------------------------------------------------- 1036 1037 /** 1038 * Returns <jk>true</jk> if this serializer subclasses from {@link WriterSerializer}. 1039 * 1040 * @return <jk>true</jk> if this serializer subclasses from {@link WriterSerializer}. 1041 */ 1042 public boolean isWriterSerializer() { 1043 return true; 1044 } 1045 1046 /** 1047 * Create the session object used for actual serialization of objects. 1048 * 1049 * @param args 1050 * Runtime arguments. 1051 * These specify session-level information such as locale and URI context. 1052 * It also include session-level properties that override the properties defined on the bean and serializer 1053 * contexts. 1054 * @return 1055 * The new session object. 1056 */ 1057 public abstract SerializerSession createSession(SerializerSessionArgs args); 1058 1059 1060 //-------------------------------------------------------------------------------- 1061 // Convenience methods 1062 //-------------------------------------------------------------------------------- 1063 1064 @Override /* Context */ 1065 public final SerializerSession createSession() { 1066 return createSession(createDefaultSessionArgs()); 1067 } 1068 1069 @Override /* Context */ 1070 public final SerializerSessionArgs createDefaultSessionArgs() { 1071 return new SerializerSessionArgs(ObjectMap.EMPTY_MAP, null, null, null, getResponseContentType(), null); 1072 } 1073 1074 /** 1075 * Serializes a POJO to the specified output stream or writer. 1076 * 1077 * <p> 1078 * Equivalent to calling <code>serializer.createSession().serialize(o, output);</code> 1079 * 1080 * @param o The object to serialize. 1081 * @param output 1082 * The output object. 1083 * <br>Character-based serializers can handle the following output class types: 1084 * <ul> 1085 * <li>{@link Writer} 1086 * <li>{@link OutputStream} - Output will be written as UTF-8 encoded stream. 1087 * <li>{@link File} - Output will be written as system-default encoded stream. 1088 * <li>{@link StringBuilder} - Output will be written to the specified string builder. 1089 * </ul> 1090 * <br>Stream-based serializers can handle the following output class types: 1091 * <ul> 1092 * <li>{@link OutputStream} 1093 * <li>{@link File} 1094 * </ul> 1095 * @throws SerializeException If a problem occurred trying to convert the output. 1096 */ 1097 public final void serialize(Object o, Object output) throws SerializeException { 1098 createSession().serialize(o, output); 1099 } 1100 1101 /** 1102 * Shortcut method for serializing objects directly to either a <code>String</code> or <code><jk>byte</jk>[]</code> 1103 * depending on the serializer type. 1104 * 1105 * @param o The object to serialize. 1106 * @return 1107 * The serialized object. 1108 * <br>Character-based serializers will return a <code>String</code> 1109 * <br>Stream-based serializers will return a <code><jk>byte</jk>[]</code> 1110 * @throws SerializeException If a problem occurred trying to convert the output. 1111 */ 1112 public Object serialize(Object o) throws SerializeException { 1113 return createSession().serialize(o); 1114 } 1115 1116 //-------------------------------------------------------------------------------- 1117 // Other methods 1118 //-------------------------------------------------------------------------------- 1119 1120 /** 1121 * Returns the media types handled based on the value of the <code>accept</code> parameter passed into the constructor. 1122 * 1123 * @return The list of media types. Never <jk>null</jk>. 1124 */ 1125 public final MediaType[] getMediaTypes() { 1126 return accept; 1127 } 1128 1129 /** 1130 * Optional method that returns the response <code>Content-Type</code> for this serializer if it is different from 1131 * the matched media type. 1132 * 1133 * <p> 1134 * This method is specified to override the content type for this serializer. 1135 * For example, the {@link org.apache.juneau.json.JsonSerializer.Simple} class returns that it handles media type 1136 * <js>"text/json+simple"</js>, but returns <js>"text/json"</js> as the actual content type. 1137 * This allows clients to request specific 'flavors' of content using specialized <code>Accept</code> header values. 1138 * 1139 * <p> 1140 * This method is typically meaningless if the serializer is being used stand-alone (i.e. outside of a REST server 1141 * or client). 1142 * 1143 * @return The response content type. If <jk>null</jk>, then the matched media type is used. 1144 */ 1145 public final MediaType getResponseContentType() { 1146 return produces; 1147 } 1148 1149 @Override /* Context */ 1150 public ObjectMap asMap() { 1151 return super.asMap() 1152 .append("Serializer", new ObjectMap() 1153 .append("maxDepth", maxDepth) 1154 .append("initialDepth", initialDepth) 1155 .append("detectRecursions", detectRecursions) 1156 .append("ignoreRecursions", ignoreRecursions) 1157 .append("useWhitespace", useWhitespace) 1158 .append("maxIndent", maxIndent) 1159 .append("addBeanTypeProperties", addBeanTypeProperties) 1160 .append("trimNulls", trimNulls) 1161 .append("trimEmptyCollections", trimEmptyCollections) 1162 .append("trimEmptyMaps", trimEmptyMaps) 1163 .append("trimStrings", trimStrings) 1164 .append("sortCollections", sortCollections) 1165 .append("sortMaps", sortMaps) 1166 .append("parserKnowsRootTypes", abridged) 1167 .append("quoteChar", quoteChar) 1168 .append("uriContext", uriContext) 1169 .append("uriResolution", uriResolution) 1170 .append("uriRelativity", uriRelativity) 1171 .append("listener", listener) 1172 ); 1173 } 1174}