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.reflect.ReflectionUtils.*; 020import static org.apache.juneau.commons.utils.ClassUtils.*; 021import static org.apache.juneau.commons.utils.CollectionUtils.*; 022import static org.apache.juneau.commons.utils.Shorts.*; 023import static org.apache.juneau.commons.utils.ThrowableUtils.*; 024 025import java.io.*; 026import java.lang.reflect.*; 027import java.math.*; 028import java.net.*; 029import java.time.*; 030import java.time.Duration; 031import java.time.temporal.*; 032import java.util.*; 033import java.util.function.*; 034import java.util.stream.*; 035 036import javax.xml.datatype.*; 037 038import org.apache.juneau.commons.bean.*; 039import org.apache.juneau.commons.collections.*; 040import org.apache.juneau.commons.function.*; 041import org.apache.juneau.commons.inject.*; 042import org.apache.juneau.commons.reflect.*; 043import org.apache.juneau.commons.svl.*; 044import org.apache.juneau.marshall.*; 045import org.apache.juneau.marshall.DateFormat; 046import org.apache.juneau.marshall.httppart.*; 047import org.apache.juneau.marshall.soap.*; 048import org.apache.juneau.marshall.swap.spi.*; 049import org.apache.juneau.marshall.utils.*; 050 051/** 052 * Serializer session that lives for the duration of a single use of {@link Serializer}. 053 * 054 * <p> 055 * Used by serializers for the following purposes: 056 * <ul class='spaced-list'> 057 * <li> 058 * Keeping track of how deep it is in a model for indentation purposes. 059 * <li> 060 * Ensuring infinite loops don't occur by setting a limit on how deep to traverse a model. 061 * <li> 062 * Ensuring infinite loops don't occur from loops in the model (when detectRecursions is enabled. 063 * <li> 064 * Allowing serializer properties to be overridden on method calls. 065 * </ul> 066 * 067 * <h5 class='section'>Notes:</h5><ul> 068 * <li class='warn'>This class is not thread safe and is typically discarded after one use. 069 * </ul> 070 * 071 * <h5 class='section'>See Also:</h5><ul> 072 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SerializersAndParsers">Serializers and Parsers</a> 073 074 * </ul> 075 */ 076@SuppressWarnings({ 077 "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants 078 "java:S1452", // create() returning Builder<?> and getExpectedRootType()/push2() returning ClassMeta<?> intentionally expose wildcard types. 079 "java:S3776", // Branching is inherent to canIgnoreValue() and getBeanTypeName(), which check many null/empty/default combinations. 080 "java:S6539", // Monster class; SerializerSession is intentionally the core per-run serialization session base, aggregating traversal/formatting state 081 "rawtypes", // Raw types necessary for generic type handling throughout serializer session 082 "resource", // Internal helpers return Closeables wired into pipe lifecycle; Eclipse JDT @Owning warning is by design. 083 "unchecked" // Type erasure requires unchecked casts throughout serializer session 084}) 085public class SerializerSession extends MarshallingTraverseSession { 086 087 /** 088 * Builder class. 089 */ 090 @SuppressWarnings({ 091 "java:S119" // 'SELF' (CRTP self-type) is intentional and clearer than a single-letter name. 092 }) 093 public abstract static class Builder<SELF extends Builder<SELF>> extends MarshallingTraverseSession.Builder<SELF> { 094 095 private HttpPartSchema schema; 096 private Method javaMethod; 097 private final Serializer ctx; 098 private UriContext uriContext; 099 private VarResolverSession resolver; 100 private boolean keepNullProperties; 101 private boolean nonDefault; 102 private boolean trimStrings; 103 private boolean addBeanTypes; 104 private boolean addRootType; 105 private boolean sortCollections; 106 private boolean sortMaps; 107 private boolean trimEmptyCollections; 108 private boolean trimEmptyMaps; 109 110 /** 111 * Constructor 112 * 113 * @param ctx The context creating this session. 114 * <br>Cannot be <jk>null</jk>. 115 */ 116 protected Builder(Serializer ctx) { 117 super(reqnn("ctx", ctx)); 118 this.ctx = ctx; 119 mediaTypeDefault(ctx.getResponseContentType()); 120 uriContext = ctx.getUriContext(); 121 keepNullProperties = ctx.isKeepNullProperties(); 122 nonDefault = ctx.isNonDefault(); 123 trimStrings = ctx.isTrimStrings(); 124 addBeanTypes = ctx.isAddBeanTypes(); 125 addRootType = ctx.isAddRootType(); 126 sortCollections = ctx.isSortCollections(); 127 sortMaps = ctx.isSortMaps(); 128 trimEmptyCollections = ctx.isTrimEmptyCollections(); 129 trimEmptyMaps = ctx.isTrimEmptyMaps(); 130 } 131 132 @Override 133 public SerializerSession build() { 134 return new SerializerSession(this); 135 } 136 137 /** 138 * The java method that called this serializer, usually the method in a REST servlet. 139 * 140 * @param value 141 * The new property value. 142 * <br>Can be <jk>null</jk> (value will not be set, existing value will be kept). 143 * @return This object. 144 */ 145 public SELF javaMethod(Method value) { 146 if (nn(value)) 147 javaMethod = value; 148 return self(); 149 } 150 151 @Override /* Overridden from Builder */ 152 public SELF property(String key, Object value) { 153 if (key == null) { 154 super.property(key, value); 155 return self(); 156 } 157 switch (key) { 158 case "javaMethod", "SerializerSession.javaMethod": 159 return javaMethod(cvt(value, Method.class)); 160 case "resolver", "SerializerSession.resolver": 161 return resolver(cvt(value, VarResolverSession.class)); 162 case "schema", "SerializerSession.schema": 163 return schema(cvt(value, HttpPartSchema.class)); 164 case "uriContext", "SerializerSession.uriContext": 165 return uriContext(cvt(value, UriContext.class)); 166 case "keepNullProperties", "SerializerSession.keepNullProperties": 167 return keepNullProperties(cvt(value, Boolean.class)); 168 case "nonDefault", "SerializerSession.nonDefault": 169 return nonDefault(cvt(value, Boolean.class)); 170 case "trimStrings", "SerializerSession.trimStrings": 171 return trimStrings(cvt(value, Boolean.class)); 172 case "addBeanTypes", "SerializerSession.addBeanTypes": 173 return addBeanTypes(cvt(value, Boolean.class)); 174 case "addRootType", "SerializerSession.addRootType": 175 return addRootType(cvt(value, Boolean.class)); 176 case "sortCollections", "SerializerSession.sortCollections": 177 return sortCollections(cvt(value, Boolean.class)); 178 case "sortMaps", "SerializerSession.sortMaps": 179 return sortMaps(cvt(value, Boolean.class)); 180 case "trimEmptyCollections", "SerializerSession.trimEmptyCollections": 181 return trimEmptyCollections(cvt(value, Boolean.class)); 182 case "trimEmptyMaps", "SerializerSession.trimEmptyMaps": 183 return trimEmptyMaps(cvt(value, Boolean.class)); 184 default: 185 super.property(key, value); 186 return self(); 187 } 188 } 189 190 /** 191 * String variable resolver. 192 * 193 * <p> 194 * If not specified, defaults to session created by {@link VarResolver#DEFAULT}. 195 * 196 * @param value 197 * The new property value. 198 * <br>Can be <jk>null</jk> (value will not be set, defaults to session created by {@link VarResolver#DEFAULT} when accessed). 199 * @return This object. 200 */ 201 public SELF resolver(VarResolverSession value) { 202 if (nn(value)) 203 resolver = value; 204 return self(); 205 } 206 207 /** 208 * HTTP-part schema. 209 * 210 * <p> 211 * Used for schema-based serializers and parsers to define additional formatting. 212 * 213 * @param value 214 * The new value for this property. 215 * <br>Can be <jk>null</jk> (value will not be set, existing value will be kept). 216 * @return This object. 217 */ 218 public SELF schema(HttpPartSchema value) { 219 if (nn(value)) 220 schema = value; 221 return self(); 222 } 223 224 /** 225 * Same as {@link #schema(HttpPartSchema)} but doesn't overwrite the value if it is already set. 226 * 227 * @param value 228 * The new value for this property. 229 * <br>Can be <jk>null</jk> (value will not be set). 230 * @return This object. 231 */ 232 public SELF schemaDefault(HttpPartSchema value) { 233 if (nn(value) && schema == null) 234 schema = value; 235 return self(); 236 } 237 238 /** 239 * URI context bean. 240 * 241 * <p> 242 * Bean used for resolution of URIs to absolute or root-relative form. 243 * 244 * <p> 245 * If not specified, defaults to {@link Serializer.Builder#uriContext(UriContext)}. 246 * 247 * @param value 248 * The new property value. 249 * <br>Can be <jk>null</jk> (value will not be set, defaults to {@link Serializer.Builder#uriContext(UriContext)} from context). 250 * @return This object. 251 */ 252 public SELF uriContext(UriContext value) { 253 if (nn(value)) 254 uriContext = value; 255 return self(); 256 } 257 258 /** 259 * Keep null bean property values. 260 * 261 * @param value The new value for this property. 262 * @return This object. 263 */ 264 public SELF keepNullProperties(boolean value) { 265 keepNullProperties = value; 266 return self(); 267 } 268 269 /** 270 * Non-default inclusion. 271 * 272 * @param value The new value for this property. 273 * @return This object. 274 * @since 10.0.0 275 */ 276 public SELF nonDefault(boolean value) { 277 nonDefault = value; 278 return self(); 279 } 280 281 /** 282 * Trim strings. 283 * 284 * @param value The new value for this property. 285 * @return This object. 286 */ 287 public SELF trimStrings(boolean value) { 288 trimStrings = value; 289 return self(); 290 } 291 292 /** 293 * Add bean types. 294 * 295 * @param value The new value for this property. 296 * @return This object. 297 */ 298 public SELF addBeanTypes(boolean value) { 299 addBeanTypes = value; 300 return self(); 301 } 302 303 /** 304 * Add root type. 305 * 306 * @param value The new value for this property. 307 * @return This object. 308 */ 309 public SELF addRootType(boolean value) { 310 addRootType = value; 311 return self(); 312 } 313 314 /** 315 * Sort collections. 316 * 317 * @param value The new value for this property. 318 * @return This object. 319 */ 320 public SELF sortCollections(boolean value) { 321 sortCollections = value; 322 return self(); 323 } 324 325 /** 326 * Sort maps. 327 * 328 * @param value The new value for this property. 329 * @return This object. 330 */ 331 public SELF sortMaps(boolean value) { 332 sortMaps = value; 333 return self(); 334 } 335 336 /** 337 * Trim empty collections. 338 * 339 * @param value The new value for this property. 340 * @return This object. 341 */ 342 public SELF trimEmptyCollections(boolean value) { 343 trimEmptyCollections = value; 344 return self(); 345 } 346 347 /** 348 * Trim empty maps. 349 * 350 * @param value The new value for this property. 351 * @return This object. 352 */ 353 public SELF trimEmptyMaps(boolean value) { 354 trimEmptyMaps = value; 355 return self(); 356 } 357 } 358 359 /** 360 * Concrete default builder leaf for the non-subclassed {@code create()} path (CRTP terminal). 361 */ 362 public static final class DefaultBuilder extends Builder<DefaultBuilder> { 363 364 DefaultBuilder(Serializer ctx) { 365 super(ctx); 366 } 367 } 368 369 /** 370 * Creates a new builder for this object. 371 * 372 * @param ctx The context creating this session. 373 * <br>Cannot be <jk>null</jk>. 374 * @return A new builder. 375 */ 376 public static Builder<?> create(Serializer ctx) { 377 return new DefaultBuilder(reqnn("ctx", ctx)); 378 } 379 380 /** 381 * Create a "_type" property that contains the dictionary name of the bean. 382 * 383 * @param m The bean map to create a class property on. 384 * <br>Must not be <jk>null</jk>. 385 * @param typeName The type name of the bean. 386 * <br>Can be <jk>null</jk> (the created property's value is <jk>null</jk>). 387 * @return A new bean property value. 388 */ 389 protected static final BeanPropertyValue createBeanTypeNameProperty(BeanMap<?> m, String typeName) { 390 BeanMeta<?> bm = m.getMeta(); 391 return new BeanPropertyValue(bm.getTypeProperty(), bm.getTypeProperty().getName(), typeName, null); 392 } 393 394 /** 395 * Converts the specified throwable to either a {@link RuntimeException} or {@link SerializeException}. 396 * 397 * @param <T> The throwable type. 398 * @param causedBy The exception to cast or wrap. 399 * <br>Must not be <jk>null</jk>. 400 */ 401 protected static <T extends Throwable> void handleThrown(T causedBy) { 402 if (causedBy instanceof StackOverflowError) 403 throw new SerializeException( 404 "Stack overflow occurred. This can occur when trying to serialize models containing loops. It's recommended you use the MarshallingTraverseContext.BEANTRAVERSE_detectRecursions setting to help locate the loop."); 405 if (causedBy instanceof Error causedBy2) 406 throw causedBy2; 407 if (causedBy instanceof RuntimeException causedBy2) 408 throw causedBy2; 409 if (causedBy instanceof SerializeException causedBy2) 410 throw causedBy2; 411 throw new SerializeException(causedBy); 412 } 413 414 /** 415 * Converts the contents of the specified object array to a list. 416 * 417 * <p> 418 * Works on both object and primitive arrays. 419 * 420 * <p> 421 * In the case of multi-dimensional arrays, the outgoing list will contain elements of type n-1 dimension. 422 * i.e. if {@code type} is <code><jk>int</jk>[][]</code> then {@code list} will have entries of type 423 * <code><jk>int</jk>[]</code>. 424 * 425 * @param type The type of array. 426 * <br>Must not be <jk>null</jk>. 427 * @param array The array being converted. 428 * <br>Must not be <jk>null</jk>. 429 * @return The array as a list. 430 */ 431 protected static final List<Object> toList(Class<?> type, Object array) { 432 var componentType = type.getComponentType(); 433 if (componentType.isPrimitive()) { 434 var l = Array.getLength(array); 435 var list = new ArrayList<>(l); 436 for (var i = 0; i < l; i++) 437 list.add(Array.get(array, i)); 438 return list; 439 } 440 return l((Object[])array); 441 } 442 443 private final HttpPartSchema schema; 444 private final Method javaMethod; // Java method that invoked this serializer. 445 private final Serializer ctx; 446 private final SerializerListener listener; 447 private final UriResolver uriResolver; 448 private VarResolverSession vrs; 449 private final boolean keepNullProperties; 450 private final boolean nonDefault; 451 private final boolean trimStrings; 452 private final boolean addBeanTypes; 453 private final boolean addRootType; 454 private final boolean sortCollections; 455 private final boolean sortMaps; 456 private final boolean trimEmptyCollections; 457 private final boolean trimEmptyMaps; 458 459 /** 460 * Constructor. 461 * 462 * @param builder The builder for this object. 463 * <br>Cannot be <jk>null</jk>. 464 */ 465 protected SerializerSession(Builder<?> builder) { 466 super(builder); 467 ctx = builder.ctx; 468 javaMethod = builder.javaMethod; 469 schema = builder.schema; 470 UriContext uriContext = builder.uriContext; 471 uriResolver = UriResolver.of(ctx.getUriResolution(), ctx.getUriRelativity(), uriContext); 472 vrs = builder.resolver; 473 listener = BeanInstantiator.createOrNull(ctx.getListener()); 474 keepNullProperties = builder.keepNullProperties; 475 nonDefault = builder.nonDefault; 476 trimStrings = builder.trimStrings; 477 addBeanTypes = builder.addBeanTypes; 478 addRootType = builder.addRootType; 479 sortCollections = builder.sortCollections; 480 sortMaps = builder.sortMaps; 481 trimEmptyCollections = builder.trimEmptyCollections; 482 trimEmptyMaps = builder.trimEmptyMaps; 483 } 484 485 /** 486 * Adds a session object to the {@link VarResolverSession} in this session. 487 * 488 * @param <T> The bean type. 489 * @param c The bean type being added. 490 * @param value The bean being added. 491 * @return This object. 492 */ 493 public <T> SerializerSession addVarBean(Class<T> c, T value) { 494 getVarResolver().bean(c, value); 495 return this; 496 } 497 498 /** 499 * Returns <jk>true</jk> if the specified value should not be serialized. 500 * 501 * <p> 502 * A value is also omitted (dropped) when it would exceed {@link #getMaxDepth() maxDepth} (via 503 * {@link #willExceedDepth()}) — over-depth values are silently truncated from the output rather than throwing. 504 * Exceeding <c>maxDepth</c> is a size guard, not a cycle detector; when 505 * {@link org.apache.juneau.marshall.MarshallingTraverseContext.Builder#detectRecursions() detectRecursions} is enabled, an actual recursion 506 * instead throws a {@link SerializeException}. 507 * 508 * @param cm The class type of the object being serialized. 509 * <br>Can be <jk>null</jk> (treated as the {@code Object} type). 510 * @param attrName The bean attribute name, or <jk>null</jk> if this isn't a bean attribute. 511 * @param value The object being serialized. 512 * <br>Can be <jk>null</jk>, in which case the result depends on {@link Serializer.Builder#keepNullProperties() keepNullProperties} (ignored unless that setting is enabled). 513 * @return <jk>true</jk> if the specified value should not be serialized. 514 * @throws SerializeException If recursion occurred. 515 */ 516 public final boolean canIgnoreValue(ClassMeta<?> cm, String attrName, Object value) throws SerializeException { 517 518 if (value == null && ! isKeepNullProperties()) 519 return true; 520 521 if (value == null) 522 return false; 523 524 if (cm == null) 525 cm = object(); 526 527 if (isTrimEmptyCollections()) { 528 if ((cm.isArray() || (cm.isObject() && isArray(value))) && ((Object[])value).length == 0) 529 return true; 530 if ((cm.isCollection() || (cm.isObject() && info(value).isAssignableTo(Collection.class))) && ((Collection<?>)value).isEmpty()) 531 return true; 532 } 533 534 if (isTrimEmptyMaps() && (cm.isMap() || (cm.isObject() && info(value).isAssignableTo(Map.class))) && ((Map<?,?>)value).isEmpty()) 535 return true; 536 537 try { 538 if ((! isKeepNullProperties()) && (willRecurse(attrName, value, cm) || willExceedDepth())) 539 return true; 540 } catch (MarshallingRecursionException e) { 541 throw new SerializeException(e); 542 } 543 544 return false; 545 } 546 547 /** 548 * Per-bean-property variant of {@link #canIgnoreValue(ClassMeta, String, Object)}. 549 * 550 * <p> 551 * Applies the same standard inclusion rules ({@link Serializer.Builder#keepNullProperties() keepNullProperties}, 552 * {@link Serializer.Builder#trimEmptyCollections() trimEmptyCollections} / 553 * {@link Serializer.Builder#trimEmptyMaps() trimEmptyMaps}, 554 * recursion detection), then additionally: 555 * <ul> 556 * <li>Treats {@link Optional#empty()} (and its primitive cousins 557 * {@link OptionalInt}/{@link OptionalLong}/{@link OptionalDouble}) as a 558 * <jk>null</jk> property value for inclusion purposes — the shared Optional/absent contract. 559 * <li>Applies {@link Serializer.Builder#nonDefault() nonDefault} by comparing the surviving value against a 560 * cached <i>reference instance</i> of the property's containing bean. When no reference instance can be 561 * built (no accessible no-arg ctor / instantiation throws), {@code nonDefault} is silently skipped for 562 * that bean — the property is emitted normally rather than failing. 563 * </ul> 564 * 565 * <p> 566 * Precedence (first "omit" wins): (1) <jk>null</jk> / empty Optional, (2) trimEmpty*, (3) value-equals-default. 567 * 568 * @param pMeta The bean property meta for the value being considered. Must not be <jk>null</jk>. 569 * @param attrName The bean attribute name. Must not be <jk>null</jk>. 570 * @param value The current property value. 571 * <br>Can be <jk>null</jk> (treated as an absent value; whether it's omitted depends on {@link Serializer.Builder#keepNullProperties() keepNullProperties}). 572 * @return <jk>true</jk> if the property should be omitted from the output. 573 * @throws SerializeException If recursion occurred. 574 * @since 10.0.0 575 */ 576 public final boolean canIgnoreValue(BeanPropertyMeta pMeta, String attrName, Object value) throws SerializeException { 577 var cMeta = (ClassMeta<?>) pMeta.getBeanInfo(); 578 // Resolve Optional<empty> / OptionalInt/Long/Double.isPresent()==false to null for the inclusion check. 579 var resolved = unwrapEmptyOptional(value); 580 if (canIgnoreValue(cMeta, attrName, resolved)) 581 return true; 582 if (isNonDefault() && resolved != null) 583 return equalsBeanPropertyDefault(pMeta, attrName, resolved); 584 return false; 585 } 586 587 /** 588 * Resolves an {@link Optional#empty()} (or the empty form of an {@link OptionalInt}/{@link OptionalLong}/ 589 * {@link OptionalDouble}) to <jk>null</jk>, leaving all other values unchanged. 590 * 591 * <p> 592 * Nested {@code Optional<Optional<X>>} values are unwrapped recursively. 593 * 594 * @param value The value to resolve. 595 * @return The resolved value (may be <jk>null</jk>). 596 */ 597 private static Object unwrapEmptyOptional(Object value) { 598 if (value == null) 599 return null; 600 if (value instanceof Optional<?> value2) 601 return value2.isEmpty() ? null : unwrapEmptyOptional(value2.orElse(null)); 602 if (value instanceof OptionalInt value2) 603 return value2.isEmpty() ? null : Integer.valueOf(value2.getAsInt()); 604 if (value instanceof OptionalLong value2) 605 return value2.isEmpty() ? null : Long.valueOf(value2.getAsLong()); 606 if (value instanceof OptionalDouble value2) 607 return value2.isEmpty() ? null : Double.valueOf(value2.getAsDouble()); 608 return value; 609 } 610 611 /** 612 * Returns <jk>true</jk> when {@code value} equals the property's bean-constructed default per the 613 * {@code nonDefault} contract. 614 * 615 * <p> 616 * Resolves (and caches per-{@link ClassMeta}) the containing bean's reference instance. Returns 617 * <jk>false</jk> when no reference instance can be built (which signals "skip nonDefault for this bean" per 618 * the documented contract). 619 * 620 * @param pMeta The bean-property meta. 621 * @param attrName The bean attribute name. 622 * @param value The surviving property value (non-null). 623 * @return <jk>true</jk> if the value equals the property's default. 624 */ 625 private boolean equalsBeanPropertyDefault(BeanPropertyMeta pMeta, String attrName, Object value) { 626 var owning = pMeta.getBeanMeta(); 627 if (owning == null) 628 return false; 629 var ownerClass = owning.getClassInfo(); 630 if (ownerClass == null) 631 return false; 632 var ownerMeta = getClassMeta(ownerClass.inner()); 633 var refValueHolder = ownerMeta.getProperty(NON_DEFAULT_PROPERTY_CACHE_KEY, cm -> { 634 var instance = BeanInstantiator.createOrNull(cm.inner()); 635 return instance == null ? NO_REFERENCE_INSTANCE : instance; 636 }); 637 var ref = refValueHolder.orElse(null); 638 if (ref == null || ref == NO_REFERENCE_INSTANCE) 639 return false; 640 // Resolve the default value via the bean's accessor surface so it composes with @MarshalledProp 641 // transforms. Wrap any throw in a guard — a bean whose getter throws on a fresh instance is just 642 // treated as "no default available", which is safer than aborting the serialize. 643 Object defaultValue; 644 try { 645 defaultValue = pMeta.get(toBeanMap(ref), attrName); 646 } catch (RuntimeException e) { // HTT — defensive: getter on reference instance throws. 647 return false; 648 } 649 var resolvedDefault = unwrapEmptyOptional(defaultValue); 650 return defaultEquals(value, resolvedDefault); 651 } 652 653 /** 654 * {@code Objects.equals} with a numeric-value shortcut so {@code 1.0} equals {@code 1}, 655 * {@code BigDecimal("1.0")} equals {@code BigDecimal("1")}, and {@code 0.0}/{@code -0.0} equal each other. 656 * 657 * @param a First value. 658 * @param b Second value. 659 * @return <jk>true</jk> if the values are equal under the {@code nonDefault} contract. 660 */ 661 @SuppressWarnings({ 662 "java:S1244" // Float equality is intentional: caller has already narrowed to numeric short-circuit; BigDecimal compareTo handles the value-equality semantics. 663 }) 664 private static boolean defaultEquals(Object a, Object b) { 665 if (a == b) 666 return true; 667 if (a == null || b == null) 668 return false; 669 if (a instanceof Number an && b instanceof Number bn) { 670 // Treat NaN as equal to NaN (Objects.equals semantics) so two NaN-typed defaults match. 671 var anan = (an instanceof Double d && Double.isNaN(d)) || (an instanceof Float f && Float.isNaN(f)); 672 var bnan = (bn instanceof Double d && Double.isNaN(d)) || (bn instanceof Float f && Float.isNaN(f)); 673 if (anan || bnan) 674 return anan && bnan; 675 try { 676 return toBigDecimal(an).compareTo(toBigDecimal(bn)) == 0; 677 } catch (NumberFormatException e) { // HTT — defensive: a Number subclass whose toString() isn't a valid decimal. 678 return eq(a, b); 679 } 680 } 681 return eq(a, b); 682 } 683 684 private static BigDecimal toBigDecimal(Number n) { 685 if (n instanceof BigDecimal n2) 686 return n2; 687 if (n instanceof BigInteger n2) 688 return new BigDecimal(n2); 689 if (n instanceof Float || n instanceof Double) 690 return BigDecimal.valueOf(n.doubleValue()); 691 return BigDecimal.valueOf(n.longValue()); 692 } 693 694 /** Cache key under which the per-{@link ClassMeta} reference instance is memoized for {@code nonDefault}. */ 695 private static final String NON_DEFAULT_PROPERTY_CACHE_KEY = "SerializerSession.nonDefault.referenceInstance"; 696 /** Sentinel stored when a reference instance could not be built (cached so we don't retry on every call). */ 697 private static final Object NO_REFERENCE_INSTANCE = new Object(); 698 699 /** 700 * Consumes each entry in the list. 701 * 702 * @param <E> The element type. 703 * @param c The collection being sorted. 704 * @param consumer The entry consumer. 705 */ 706 public final <E> void forEachEntry(Collection<E> c, Consumer<E> consumer) { 707 if (isEmpty(c)) 708 return; 709 if (isSortCollections() && !(c instanceof SortedSet) && isSortable(c)) 710 c.stream().sorted().forEach(consumer); 711 else 712 c.forEach(consumer); 713 } 714 715 /** 716 * Consumes each map entry in the map. 717 * 718 * @param <K> The key type. 719 * @param <V> The value type. 720 * @param m The map being consumed. 721 * @param consumer The map entry consumer. 722 */ 723 @SuppressWarnings({ 724 "cast" // Cast required for generic type safety 725 }) 726 public final <K,V> void forEachEntry(Map<K,V> m, Consumer<Map.Entry<K,V>> consumer) { 727 if (isEmpty(m)) 728 return; 729 if (isSortMaps() && !(m instanceof SortedMap) && isSortable(m.keySet())) 730 ((Map)m).entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(x -> consumer.accept((Map.Entry<K,V>)x)); 731 else 732 m.entrySet().forEach(consumer); 733 } 734 735 /** 736 * Iterates over a streamable object (Iterator, Iterable, or Stream), consuming each entry. 737 * 738 * <p> 739 * For collections, delegates to {@link #forEachEntry(Collection, Consumer)} which supports sorting. 740 * For other streamable types, iterates lazily without materializing into a List. 741 * 742 * @param o The streamable object. 743 * @param type The class meta for the object. 744 * @param consumer The entry consumer. 745 * @since 9.2.1 746 */ 747 public final void forEachStreamableEntry(Object o, ClassMeta<?> type, Consumer consumer) { 748 if (o == null) 749 return; 750 if (type.isCollection()) { 751 forEachEntry((Collection)o, consumer); 752 } else if (type.isIterable()) { 753 if (o instanceof BeanSupplier o2) { 754 try { 755 drainBeanSupplier(o2, consumer); 756 } catch (RuntimeException e) { 757 throw e; 758 } catch (Exception e) { 759 throw new BasicRuntimeException(e); 760 } 761 } else { 762 ((Iterable)o).forEach(consumer); 763 } 764 } else if (type.isIterator()) { 765 if (o instanceof Enumeration o2) 766 o2.asIterator().forEachRemaining(consumer); 767 else 768 ((Iterator)o).forEachRemaining(consumer); 769 } else if (type.isStream()) { 770 try (var stream = (Stream)o) { 771 stream.forEach(consumer); 772 } 773 } 774 } 775 776 private static void drainBeanSupplier(BeanSupplier bs, Consumer consumer) throws Exception { 777 bs.begin(); 778 try { 779 bs.iterator().forEachRemaining(consumer); 780 } catch (Exception e) { 781 bs.onError(e); 782 } finally { 783 bs.complete(); 784 } 785 } 786 787 /** 788 * Converts a streamable object (Iterator, Iterable, or Stream) to a List. 789 * 790 * <p> 791 * Used by serializers that need to know the collection size or inspect elements before serializing 792 * (e.g. MsgPack, HTML, CSV). 793 * 794 * @param o The streamable object. 795 * @param type The class meta for the object. 796 * @return A new list containing all elements. 797 * @since 9.2.1 798 */ 799 public final List<Object> toListFromStreamable(Object o, ClassMeta<?> type) { 800 var list = l(); 801 forEachStreamableEntry(o, type, list::add); 802 return list; 803 } 804 805 /** 806 * Returns the listener associated with this session. 807 * 808 * @return The listener associated with this session, or <jk>null</jk> if there is no listener. 809 */ 810 public SerializerListener getListener() { return listener; } 811 812 /** 813 * Returns the listener associated with this session. 814 * 815 * @param <T> The listener type. 816 * @param c The listener class to cast to. 817 * @return The listener associated with this session, or <jk>null</jk> if there is no listener. 818 */ 819 public <T extends SerializerListener> T getListener(Class<T> c) { 820 return (T)listener; 821 } 822 823 /** 824 * Optional method that specifies HTTP request headers for this serializer. 825 * 826 * <p> 827 * For example, {@link SoapXmlSerializer} needs to set a <c>SOAPAction</c> header. 828 * 829 * <p> 830 * This method is typically meaningless if the serializer is being used stand-alone (i.e. outside of a REST server 831 * or client). 832 * 833 * <p> 834 * The default implementation of this method simply calls {@link Serializer#getResponseHeaders(SerializerSession)}. 835 * 836 * @return 837 * The HTTP headers to set on HTTP requests. 838 * Never <jk>null</jk>. 839 */ 840 public Map<String,String> getResponseHeaders() { return ctx.getResponseHeaders(this); } 841 842 /** 843 * HTTP part schema of object being serialized. 844 * 845 * @return HTTP part schema of object being serialized, or <jk>null</jk> if not specified. 846 */ 847 public final HttpPartSchema getSchema() { return schema; } 848 849 /** 850 * Returns the variable resolver session. 851 * 852 * @return The variable resolver session. 853 */ 854 public VarResolverSession getVarResolver() { 855 if (vrs == null) 856 vrs = createDefaultVarResolverSession(); 857 return vrs; 858 } 859 860 /** 861 * Returns <jk>true</jk> if this serializer subclasses from {@link WriterSerializer}. 862 * 863 * @return <jk>true</jk> if this serializer subclasses from {@link WriterSerializer}. 864 */ 865 public boolean isWriterSerializer() { return false; } 866 867 /** 868 * Resolves any variables in the specified string. 869 * 870 * @param string The string to resolve values in. 871 * @return The string with variables resolved. 872 */ 873 public String resolve(String string) { 874 return getVarResolver().resolve(string); 875 } 876 877 /** 878 * Converts a String to an absolute URI based on the {@link UriContext} on this session. 879 * 880 * @param uri 881 * The input URI. 882 * Can be any of the following: 883 * <ul> 884 * <li>{@link URI} 885 * <li>{@link URL} 886 * <li>{@link CharSequence} 887 * </ul> 888 * URI can be any of the following forms: 889 * <ul> 890 * <li><js>"foo://foo"</js> - Absolute URI. 891 * <li><js>"/foo"</js> - Root-relative URI. 892 * <li><js>"/"</js> - Root URI. 893 * <li><js>"context:/foo"</js> - Context-root-relative URI. 894 * <li><js>"context:/"</js> - Context-root URI. 895 * <li><js>"servlet:/foo"</js> - Servlet-path-relative URI. 896 * <li><js>"servlet:/"</js> - Servlet-path URI. 897 * <li><js>"request:/foo"</js> - Request-path-relative URI. 898 * <li><js>"request:/"</js> - Request-path URI. 899 * <li><js>"foo"</js> - Path-info-relative URI. 900 * <li><js>""</js> - Path-info URI. 901 * </ul> 902 * @return The resolved URI. 903 */ 904 public final String resolveUri(Object uri) { 905 return uriResolver.resolve(uri); 906 } 907 908 /** 909 * Shortcut method for serializing objects directly to either a <c>String</c> or <code><jk>byte</jk>[]</code> 910 * depending on the serializer type. 911 * 912 * @param o The object to serialize. 913 * @return 914 * The serialized object. 915 * <br>Character-based serializers will return a <c>String</c>. 916 * <br>Stream-based serializers will return a <code><jk>byte</jk>[]</code>. 917 * @throws SerializeException If a problem occurred trying to convert the output. 918 */ 919 public Object write(Object o) throws SerializeException { 920 throw uoex(); 921 } 922 923 /** 924 * Serialize the specified object using the specified session. 925 * 926 * @param out Where to send the output from the serializer. 927 * @param o The object to serialize. 928 * @throws SerializeException If a problem occurred trying to convert the output. 929 * @throws IOException Thrown by the underlying stream. 930 */ 931 public final void write(Object o, Object out) throws SerializeException, IOException { 932 try (SerializerPipe pipe = createPipe(out)) { 933 var unwrapped = unwrapSupplier(o, 0); 934 if (unwrapped instanceof BeanConsumer && !(unwrapped instanceof BeanChannel)) 935 throw new SerializeException(this, 936 "BeanConsumer cannot be used as a serializer source. Use BeanSupplier or BeanChannel for round-trip support."); 937 doWrite(pipe, unwrapped); 938 } catch (SerializeException | IOException e) { 939 throw e; 940 } catch (@SuppressWarnings("unused") StackOverflowError e) { 941 throw new SerializeException(this, 942 "Stack overflow occurred. This can occur when trying to serialize models containing loops. It's recommended you use the MarshallingTraverseContext.BEANTRAVERSE_detectRecursions setting to help locate the loop."); 943 } catch (Exception e) { 944 throw new SerializeException(this, e); 945 } finally { 946 checkForWarnings(); 947 } 948 } 949 950 /** 951 * Recursively unwraps nested {@link Supplier} chains to their underlying value. 952 * 953 * <p> 954 * Supports chains of the form {@code Supplier<Supplier<T>>} → {@code T} with a maximum depth of 10 955 * to prevent infinite loops. Note that {@link BeanSupplier} instances are NOT unwrapped here — 956 * they are treated as {@link Iterable} sequences by the serializer. 957 * 958 * @param o The object to unwrap. 959 * @param depth The current recursion depth (must be 0 on initial call). 960 * @return The unwrapped value, or the original object if it is not a {@code Supplier}. 961 * @throws SerializeException If the {@link Supplier} chain exceeds 10 levels. 962 */ 963 protected final Object unwrapSupplier(Object o, int depth) throws SerializeException { 964 if (! (o instanceof Supplier<?>) || o instanceof BeanSupplier) 965 return o; 966 if (depth > 10) 967 throw new SerializeException(this, "Supplier chain exceeds maximum unwrap depth of 10."); 968 return unwrapSupplier(((Supplier<?>)o).get(), depth + 1); 969 } 970 971 /** 972 * Shortcut method for serializing an object to a String. 973 * 974 * @param o The object to serialize. 975 * @return 976 * The serialized object. 977 * <br>Character-based serializers will return a <c>String</c> 978 * <br>Stream-based serializers will return a <code><jk>byte</jk>[]</code> converted to a string based on the {@link OutputStreamSerializer.Builder#binaryFormat(BinaryFormat)} setting. 979 * @throws SerializeException If a problem occurred trying to convert the output. 980 */ 981 public String writeToString(Object o) throws SerializeException { 982 throw uoex(); 983 } 984 985 /** 986 * Sorts the specified collection if {@link SerializerSession#isSortCollections()} returns <jk>true</jk>. 987 * 988 * @param <E> The element type. 989 * @param c The collection being sorted. 990 * <br>Can be <jk>null</jk> (returns <jk>null</jk> unchanged). 991 * @return The sorted collection, or the input collection unchanged if sorting is disabled or the collection isn't sortable. 992 */ 993 public final <E> Collection<E> sort(Collection<E> c) { 994 if (isEmpty(c) || c instanceof SortedSet) 995 return c; 996 if (isSortCollections() && isSortable(c)) 997 return c.stream().sorted().toList(); 998 return c; 999 } 1000 1001 /** 1002 * Sorts the specified collection if {@link SerializerSession#isSortCollections()} returns <jk>true</jk>. 1003 * 1004 * @param <E> The element type. 1005 * @param c The collection being sorted. 1006 * <br>Can be <jk>null</jk> (returns <jk>null</jk> unchanged). 1007 * @return The sorted list, or the input list unchanged if sorting is disabled or the list isn't sortable. 1008 */ 1009 public final <E> List<E> sort(List<E> c) { 1010 if (isEmpty(c)) 1011 return c; 1012 if (isSortCollections() && isSortable(c)) 1013 return c.stream().sorted().toList(); 1014 return c; 1015 } 1016 1017 /** 1018 * Sorts the specified map if {@link SerializerSession#isSortMaps()} returns <jk>true</jk>. 1019 * 1020 * @param <K> The key type. 1021 * @param <V> The value type. 1022 * @param m The map being sorted. 1023 * <br>Can be <jk>null</jk> (returns <jk>null</jk> unchanged). 1024 * @return A new sorted {@link TreeMap}, or the input map unchanged if sorting is disabled or the keys aren't sortable. 1025 */ 1026 public final <K,V> Map<K,V> sort(Map<K,V> m) { 1027 if (isEmpty(m) || m instanceof SortedMap) 1028 return m; 1029 if (isSortMaps() && isSortable(m.keySet())) 1030 return new TreeMap<>(m); 1031 return m; 1032 } 1033 1034 /** 1035 * Converts the specified object to a <c>String</c>. 1036 * 1037 * <p> 1038 * Also has the following effects: 1039 * <ul> 1040 * <li><c>Class</c> object is converted to a readable name. See {@link ClassInfo#getNameFull()}. 1041 * <li>Whitespace is trimmed if the trim-strings setting is enabled. 1042 * </ul> 1043 * 1044 * @param o The object to convert to a <c>String</c>. 1045 * @return The object converted to a String, or <jk>null</jk> if the input was <jk>null</jk>. 1046 */ 1047 public final String toString(Object o) { 1048 if (o == null) 1049 return null; 1050 if (o.getClass() == Class.class) 1051 return info((Class<?>)o).getNameFull(); 1052 if (o.getClass() == ClassInfo.class) 1053 return ((ClassInfo)o).getNameFull(); 1054 if (o.getClass().isEnum()) 1055 return getClassMetaForObject(o).toString(o); 1056 var cm = getClassMetaForObject(o); 1057 if (cm.isDate()) 1058 return writeDate((Date)o, cm); 1059 if (cm.isCalendar()) 1060 return writeCalendar(o, cm); 1061 if (cm.isTemporal()) 1062 return writeTemporal((TemporalAccessor)o, cm); 1063 if (cm.isDuration()) 1064 return writeDuration((Duration)o); 1065 if (cm.isPeriod()) 1066 return writePeriod((Period)o); 1067 var s = o.toString(); 1068 if (isTrimStrings()) 1069 s = s.trim(); 1070 return s; 1071 } 1072 1073 /** 1074 * Formats a {@link Duration} value using this session's configured 1075 * {@linkplain #getDurationFormat() duration format}. 1076 * 1077 * @param d The value to format. <jk>null</jk> returns <jk>null</jk>. 1078 * @return The formatted value, or <jk>null</jk> if {@code d} is <jk>null</jk>. 1079 */ 1080 protected final String writeDuration(Duration d) { 1081 return Iso8601Utils.formatDuration(d, getDurationFormat()); 1082 } 1083 1084 /** 1085 * Formats a {@link Period} value using this session's configured 1086 * {@linkplain #getPeriodFormat() period format}. 1087 * 1088 * @param p The value to format. <jk>null</jk> returns <jk>null</jk>. 1089 * @return The formatted value, or <jk>null</jk> if {@code p} is <jk>null</jk>. 1090 */ 1091 protected final String writePeriod(Period p) { 1092 return Iso8601Utils.formatPeriod(p, getPeriodFormat()); 1093 } 1094 1095 /** 1096 * Formats a {@link Date} value using this session's configured 1097 * {@linkplain #getDateFormat() date format} and {@linkplain #getTimeZone() time zone}. 1098 * 1099 * @param d The value to format. <jk>null</jk> returns <jk>null</jk>. 1100 * @param cm The class metadata for the value (kept for symmetry with {@link Iso8601Utils#formatDate}). 1101 * @return The formatted value, or <jk>null</jk> if {@code d} is <jk>null</jk>. 1102 */ 1103 protected final String writeDate(Date d, ClassMeta<?> cm) { 1104 return Iso8601Utils.formatDate(d, cm, getDateFormat(), getTimeZone()); 1105 } 1106 1107 /** 1108 * Formats a {@link Calendar} or {@link XMLGregorianCalendar} value using this 1109 * session's configured {@linkplain #getCalendarFormat() calendar format} and 1110 * {@linkplain #getTimeZone() time zone}. 1111 * 1112 * <p> 1113 * {@link XMLGregorianCalendar} values always emit their 1114 * {@link XMLGregorianCalendar#toXMLFormat()} form regardless of the configured 1115 * format, mirroring the parse-side rule. 1116 * 1117 * @param c The value to format. Must be a {@link Calendar} or {@link XMLGregorianCalendar}. 1118 * <jk>null</jk> returns <jk>null</jk>. 1119 * @param cm The class metadata for the value (kept for symmetry with {@link Iso8601Utils#formatCalendar}). 1120 * @return The formatted value, or <jk>null</jk> if {@code c} is <jk>null</jk>. 1121 */ 1122 protected final String writeCalendar(Object c, ClassMeta<?> cm) { 1123 return Iso8601Utils.formatCalendar(c, cm, getCalendarFormat(), getTimeZone()); 1124 } 1125 1126 /** 1127 * Formats a {@link TemporalAccessor} value using this session's configured 1128 * {@linkplain #getTemporalFormat() temporal format} and {@linkplain #getTimeZone() time zone}. 1129 * 1130 * @param t The value to format. Must be a {@link Temporal} subtype. 1131 * <jk>null</jk> returns <jk>null</jk>. 1132 * @param cm The class metadata for the value (kept for symmetry with {@link Iso8601Utils#formatTemporal}). 1133 * @return The formatted value, or <jk>null</jk> if {@code t} is <jk>null</jk>. 1134 */ 1135 protected final String writeTemporal(TemporalAccessor t, ClassMeta<?> cm) { 1136 return Iso8601Utils.formatTemporal(t, cm, getTemporalFormat(), getTimeZone()); 1137 } 1138 1139 /** 1140 * Trims the specified string if {@link SerializerSession#isTrimStrings()} returns <jk>true</jk>. 1141 * 1142 * @param o The input string to trim. 1143 * @return The trimmed string, or <jk>null</jk> if the input was <jk>null</jk>. 1144 */ 1145 public final String trim(Object o) { 1146 if (o == null) 1147 return null; 1148 var s = o.toString(); 1149 if (isTrimStrings()) 1150 s = s.trim(); 1151 return s; 1152 } 1153 1154 private static boolean isSortable(Collection<?> c) { 1155 if (c == null) 1156 return false; 1157 for (var o : c) 1158 if (! (o instanceof Comparable)) 1159 return false; 1160 return true; 1161 } 1162 1163 /** 1164 * Adds a session object to the {@link VarResolverSession} in this session. 1165 * 1166 * @return This object. 1167 */ 1168 protected VarResolverSession createDefaultVarResolverSession() { 1169 return VarResolver.DEFAULT.createSession(); 1170 } 1171 1172 /** 1173 * Wraps the specified output object into a {@link SerializerPipe} object so that it can be easily converted into 1174 * a stream or writer. 1175 * 1176 * @param output 1177 * The output location. 1178 * <br>For character-based serializers, this can be any of the following types: 1179 * <ul> 1180 * <li>{@link Writer} 1181 * <li>{@link OutputStream} - Output will be written as UTF-8 encoded stream. 1182 * </ul> 1183 * <br>For byte-based serializers, this must be an {@link OutputStream}. 1184 * @return 1185 * A new {@link SerializerPipe} wrapper around the specified output object. 1186 */ 1187 protected SerializerPipe createPipe(Object output) { 1188 return new SerializerPipe(output); 1189 } 1190 1191 /** 1192 * Serializes a POJO to the specified pipe. 1193 * 1194 * <p> 1195 * This method should NOT close the context object. 1196 * 1197 * <p> 1198 * The default implementation of this method simply calls {@link Serializer#doWrite(SerializerSession,SerializerPipe,Object)}. 1199 * 1200 * @param pipe Where to send the output from the serializer. 1201 * @param o The object to serialize. 1202 * @throws IOException Thrown by underlying stream. 1203 * @throws SerializeException Problem occurred trying to serialize object. 1204 */ 1205 protected void doWrite(SerializerPipe pipe, Object o) throws IOException, SerializeException { 1206 ctx.doWrite(this, pipe, o); 1207 } 1208 1209 /** 1210 * Generalize the specified object if a POJO swap is associated with it. 1211 * 1212 * @param o The object to generalize. 1213 * <br>Can be <jk>null</jk> (returns <jk>null</jk>). 1214 * @param type The type of object. 1215 * <br>Can be <jk>null</jk> (the object's actual runtime class's swap, if any, is used instead). 1216 * @return The generalized object, or <jk>null</jk> if the object is <jk>null</jk>. 1217 * @throws SerializeException If a problem occurred trying to convert the output. 1218 */ 1219 protected final Object generalize(Object o, ClassMeta<?> type) throws SerializeException { 1220 try { 1221 if (o == null) 1222 return null; 1223 ObjectSwap f = (type == null || type.isObject() || type.isString() ? getClassMeta(o.getClass()).getSwap(this) : type.getSwap(this)); 1224 if (f == null) 1225 return o; 1226 return f.swap(this, o); 1227 } catch (SerializeException e) { 1228 throw e; 1229 } catch (Exception e) { 1230 throw new SerializeException(e); 1231 } 1232 } 1233 1234 /** 1235 * Resolves the dictionary name for the actual type. 1236 * 1237 * @param session The current serializer session. 1238 * @param eType The expected type of the bean property. 1239 * @param aType The actual type of the bean property. 1240 * @param pMeta The current bean property being serialized. 1241 * <br>Can be <jk>null</jk> (the bean-property-level dictionary lookup tier is skipped). 1242 * @return The bean dictionary name, or <jk>null</jk> if a name could not be found. 1243 */ 1244 @SuppressWarnings({ 1245 "null" // Null analysis handled by runtime checks 1246 }) 1247 protected final String getBeanTypeName(SerializerSession session, ClassMeta<?> eType, ClassMeta<?> aType, BeanPropertyMeta pMeta) { 1248 if (eType == aType || ! (isAddBeanTypes() || (session.isRoot() && isAddRootType()))) 1249 return null; 1250 1251 String eTypeTn = eType.getBeanDictionaryName(); 1252 1253 // First see if it's defined on the actual type. 1254 String tn = aType.getBeanDictionaryName(); 1255 if (nn(tn) && ! tn.equals(eTypeTn)) { 1256 return tn; 1257 } 1258 1259 // Then see if it's defined on the expected type. 1260 // The expected type might be an interface with mappings for implementation classes. 1261 BeanRegistry br = eType.getBeanRegistry(); 1262 if (nn(br)) { 1263 tn = br.getTypeName(aType); 1264 if (nn(tn) && ! tn.equals(eTypeTn)) 1265 return tn; 1266 } 1267 1268 // Then look on the bean property. 1269 br = pMeta == null ? null : (BeanRegistry) pMeta.getBeanRegistry(); 1270 if (nn(br)) { 1271 tn = br.getTypeName(aType); 1272 if (nn(tn) && ! tn.equals(eTypeTn)) 1273 return tn; 1274 } 1275 1276 // Finally look in the session. 1277 br = getBeanRegistry(); 1278 if (nn(br)) { 1279 tn = br.getTypeName(aType); 1280 if (nn(tn) && ! tn.equals(eTypeTn)) 1281 return tn; 1282 } 1283 1284 return null; 1285 } 1286 1287 /** 1288 * Returns the parser-side expected type for the object. 1289 * 1290 * <p> 1291 * The return value depends on the {@link Serializer.Builder#addRootType()} setting. 1292 * When disabled, the parser already knows the Java POJO type being parsed, so there is 1293 * no reason to add <js>"_type"</js> attributes to the root-level object. 1294 * 1295 * @param o The object to get the expected type on. 1296 * <br>Can be <jk>null</jk> (returns <jk>null</jk> unless root typing is enabled, in which case the generic {@link Object} type is returned). 1297 * @return The expected type, or <jk>null</jk> if the object was <jk>null</jk> and root typing is disabled. 1298 */ 1299 protected final ClassMeta<?> getExpectedRootType(Object o) { 1300 if (isAddRootType()) 1301 return object(); 1302 var cm = getClassMetaForObject(o); 1303 if (nn(cm) && cm.isOptional()) 1304 return cm.getElementType(); 1305 return cm; 1306 } 1307 1308 /** 1309 * Returns the Java method that invoked this serializer. 1310 * 1311 * <p> 1312 * When using the REST API, this is the Java method invoked by the REST call. 1313 * Can be used to access annotations defined on the method or class. 1314 * 1315 * @return The Java method that invoked this serializer, or <jk>null</jk> if not specified. 1316 */ 1317 protected final Method getJavaMethod() { return javaMethod; } 1318 1319 /** 1320 * URI context bean. 1321 * 1322 * @see Serializer.Builder#uriContext(UriContext) 1323 * @return 1324 * Bean used for resolution of URIs to absolute or root-relative form. 1325 */ 1326 protected final UriContext getUriContext() { return ctx.getUriContext(); } 1327 1328 /** 1329 * URI relativity. 1330 * 1331 * @see Serializer.Builder#uriRelativity(UriRelativity) 1332 * @return 1333 * Defines what relative URIs are relative to when serializing any of the following: 1334 */ 1335 protected final UriRelativity getUriRelativity() { return ctx.getUriRelativity(); } 1336 1337 /** 1338 * URI resolution. 1339 * 1340 * @see Serializer.Builder#uriResolution(UriResolution) 1341 * @return 1342 * Defines the resolution level for URIs when serializing URIs. 1343 */ 1344 protected final UriResolution getUriResolution() { return ctx.getUriResolution(); } 1345 1346 /** 1347 * Returns the configured default duration wire format. 1348 * 1349 * @return The configured default duration wire format. 1350 */ 1351 protected final DurationFormat getDurationFormat() { return ctx.getMarshallingContext().getDurationFormat(); } 1352 1353 /** 1354 * Returns the configured default period wire format. 1355 * 1356 * @return The configured default period wire format. 1357 */ 1358 protected final PeriodFormat getPeriodFormat() { return ctx.getMarshallingContext().getPeriodFormat(); } 1359 1360 /** 1361 * Returns the configured default calendar wire format. 1362 * 1363 * @return The configured default calendar wire format. 1364 */ 1365 protected final CalendarFormat getCalendarFormat() { return ctx.getMarshallingContext().getCalendarFormat(); } 1366 1367 /** 1368 * Returns the configured default date wire format. 1369 * 1370 * @return The configured default date wire format. 1371 */ 1372 protected final DateFormat getDateFormat() { return ctx.getMarshallingContext().getDateFormat(); } 1373 1374 /** 1375 * Returns the configured default temporal wire format. 1376 * 1377 * @return The configured default temporal wire format. 1378 */ 1379 protected final TemporalFormat getTemporalFormat() { return ctx.getMarshallingContext().getTemporalFormat(); } 1380 1381 /** 1382 * Returns the configured default time-zone wire format. 1383 * 1384 * @return The configured default time-zone wire format. 1385 */ 1386 protected final TimeZoneFormat getTimeZoneFormat() { return ctx.getMarshallingContext().getTimeZoneFormat(); } 1387 1388 /** 1389 * Returns the configured default locale wire format. 1390 * 1391 * @return The configured default locale wire format. 1392 */ 1393 protected final LocaleFormat getLocaleFormat() { return ctx.getMarshallingContext().getLocaleFormat(); } 1394 1395 /** 1396 * Returns the URI resolver. 1397 * 1398 * @return The URI resolver. 1399 */ 1400 protected final UriResolver getUriResolver() { return uriResolver; } 1401 1402 /** 1403 * Add <js>"_type"</js> properties when needed. 1404 * 1405 * @see Serializer.Builder#addBeanTypes() 1406 * @return 1407 * <jk>true</jk> if <js>"_type"</js> properties added to beans if their type cannot be inferred 1408 * through reflection. 1409 */ 1410 protected boolean isAddBeanTypes() { return addBeanTypes; } 1411 1412 /** 1413 * Add type attribute to root nodes. 1414 * 1415 * @see Serializer.Builder#addRootType() 1416 * @return 1417 * <jk>true</jk> if type property should be added to root node. 1418 */ 1419 protected final boolean isAddRootType() { return addRootType; } 1420 1421 /** 1422 * Don't trim null bean property values. 1423 * 1424 * @see Serializer.Builder#keepNullProperties() 1425 * @return 1426 * <jk>true</jk> if null bean values are serialized to the output. 1427 */ 1428 protected final boolean isKeepNullProperties() { return keepNullProperties; } 1429 1430 /** 1431 * Don't serialize bean properties whose values equal the default for their type. 1432 * 1433 * @see Serializer.Builder#nonDefault() 1434 * @return 1435 * <jk>true</jk> if bean properties whose value equals the type / bean-constructed default are omitted. 1436 * @since 10.0.0 1437 */ 1438 protected final boolean isNonDefault() { return nonDefault; } 1439 1440 /** 1441 * Sort arrays and collections alphabetically. 1442 * 1443 * @see Serializer.Builder#sortCollections() 1444 * @return 1445 * <jk>true</jk> if arrays and collections are copied and sorted before serialization. 1446 */ 1447 protected final boolean isSortCollections() { return sortCollections; } 1448 1449 /** 1450 * Sort maps alphabetically. 1451 * 1452 * @see Serializer.Builder#sortMaps() 1453 * @return 1454 * <jk>true</jk> if maps are copied and sorted before serialization. 1455 */ 1456 protected final boolean isSortMaps() { return sortMaps; } 1457 1458 /** 1459 * Trim empty lists and arrays. 1460 * 1461 * @see Serializer.Builder#trimEmptyCollections() 1462 * @return 1463 * <jk>true</jk> if empty lists and arrays are not serialized to the output. 1464 */ 1465 protected final boolean isTrimEmptyCollections() { return trimEmptyCollections; } 1466 1467 /** 1468 * Trim empty maps. 1469 * 1470 * @see Serializer.Builder#trimEmptyMaps() 1471 * @return 1472 * <jk>true</jk> if empty map values are not serialized to the output. 1473 */ 1474 protected final boolean isTrimEmptyMaps() { return trimEmptyMaps; } 1475 1476 /** 1477 * Trim strings. 1478 * 1479 * @see Serializer.Builder#trimStrings() 1480 * @return 1481 * <jk>true</jk> if string values will be trimmed of whitespace using {@link String#trim()} before being serialized. 1482 */ 1483 protected boolean isTrimStrings() { return trimStrings; } 1484 1485 /** 1486 * Specialized warning when an exception is thrown while executing a bean getter. 1487 * 1488 * @param p The bean map entry representing the bean property. 1489 * <br>Must not be <jk>null</jk>. 1490 * @param t The throwable that the bean getter threw. 1491 * @throws SerializeException Thrown if ignoreInvocationExceptionOnGetters is false. 1492 */ 1493 protected final void onBeanGetterException(BeanPropertyMeta p, Throwable t) throws SerializeException { 1494 // If it's a StackOverflowError, let it propagate so it can be caught by the write() method 1495 // and converted to a proper SerializeException with recursion detection message 1496 if (t instanceof StackOverflowError t2) 1497 throw t2; 1498 1499 if (nn(listener)) 1500 listener.onBeanGetterException(this, t, p); 1501 String prefix = (isDebug() ? getStack(false) + ": " : ""); 1502 addWarning("%sCould not call getValue() on property '%s' of class '%s', exception = %s", prefix, p.getName(), p.getBeanMeta().getBeanInfo(), localizedMessage(t)); 1503 if (! isIgnoreInvocationExceptionsOnGetters()) 1504 throw new SerializeException(this, "%sCould not call getValue() on property '%s' of class '%s', exception = %s", prefix, p.getName(), p.getBeanMeta().getBeanInfo(), localizedMessage(t)) 1505 .initCause(t); 1506 } 1507 1508 /** 1509 * Logs a warning message. 1510 * 1511 * @param t The throwable that was thrown (if there was one). 1512 * @param msg The warning message. 1513 * @param args Optional {@link String#format(String, Object...) String.format}-style arguments. 1514 */ 1515 @Override 1516 protected void onError(Throwable t, String msg, Object...args) { 1517 if (nn(listener)) 1518 listener.onError(this, t, f(msg, args)); 1519 super.onError(t, msg, args); 1520 } 1521 1522 @Override /* Overridden from MarshallingTraverseSession */ 1523 protected FluentMap<String,Object> properties() { 1524 return super.properties() 1525 .a("uriResolver", uriResolver); 1526 } 1527 1528 /** 1529 * Same as {@link #push(String, Object, ClassMeta)} but wraps {@link MarshallingRecursionException} inside {@link SerializeException}. 1530 * 1531 * @param attrName The attribute name. 1532 * <br>Can be <jk>null</jk> for unnamed entries (e.g. array elements); used only for stack-trace labeling. 1533 * @param o The current object being traversed. 1534 * <br>Can be <jk>null</jk> (returns <jk>null</jk>). 1535 * @param eType The expected class type. 1536 * <br>Can be <jk>null</jk>, in which case the object's actual runtime type is used instead. 1537 * @return 1538 * The {@link ClassMeta} of the object so that <c>instanceof</c> operations only need to be performed 1539 * once (since they can be expensive). 1540 * @throws SerializeException If recursion occurred. 1541 */ 1542 protected final ClassMeta<?> push2(String attrName, Object o, ClassMeta<?> eType) throws SerializeException { 1543 try { 1544 return super.push(attrName, o, eType); 1545 } catch (MarshallingRecursionException e) { 1546 throw new SerializeException(e); 1547 } 1548 } 1549 1550 /** 1551 * Opposite of {@link #resolveUri(Object)}. 1552 * 1553 * <p> 1554 * Converts the URI to a value relative to the specified <c>relativeTo</c> parameter. 1555 * 1556 * <p> 1557 * Both parameters can be any of the following: 1558 * <ul> 1559 * <li>{@link URI} 1560 * <li>{@link URL} 1561 * <li>{@link CharSequence} 1562 * </ul> 1563 * 1564 * <p> 1565 * Both URIs can be any of the following forms: 1566 * <ul> 1567 * <li><js>"foo://foo"</js> - Absolute URI. 1568 * <li><js>"/foo"</js> - Root-relative URI. 1569 * <li><js>"/"</js> - Root URI. 1570 * <li><js>"context:/foo"</js> - Context-root-relative URI. 1571 * <li><js>"context:/"</js> - Context-root URI. 1572 * <li><js>"servlet:/foo"</js> - Servlet-path-relative URI. 1573 * <li><js>"servlet:/"</js> - Servlet-path URI. 1574 * <li><js>"request:/foo"</js> - Request-path-relative URI. 1575 * <li><js>"request:/"</js> - Request-path URI. 1576 * <li><js>"foo"</js> - Path-info-relative URI. 1577 * <li><js>""</js> - Path-info URI. 1578 * </ul> 1579 * 1580 * @param relativeTo The URI to relativize against. 1581 * @param uri The URI to relativize. 1582 * @return The relativized URI. 1583 */ 1584 protected final String relativizeUri(Object relativeTo, Object uri) { 1585 return uriResolver.relativize(relativeTo, uri); 1586 } 1587 1588 /** 1589 * Invokes the specified swap on the specified object if the swap is not null. 1590 * 1591 * @param swap The swap to invoke. Can be <jk>null</jk> (no-op; the input object is returned unchanged). 1592 * @param o The input object. 1593 * @return The swapped object. 1594 * @throws SerializeException If swap method threw an exception. 1595 */ 1596 protected Object swap(ObjectSwap swap, Object o) throws SerializeException { 1597 try { 1598 if (swap == null) 1599 return o; 1600 return swap.swap(this, o); 1601 } catch (Exception e) { 1602 throw new SerializeException(e); 1603 } 1604 } 1605}