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.commons.bean; 018 019import static java.util.Collections.*; 020import static org.apache.juneau.commons.function.Suppliers.*; 021import static org.apache.juneau.commons.reflect.AnnotationTraversal.*; 022import static org.apache.juneau.commons.reflect.ReflectionUtils.*; 023import static org.apache.juneau.commons.utils.ClassUtils.*; 024import static org.apache.juneau.commons.utils.CollectionUtils.*; 025import static org.apache.juneau.commons.utils.ObjectUtils.*; 026import static org.apache.juneau.commons.utils.Shorts.*; 027import static org.apache.juneau.commons.utils.Shorts.eq; 028import static org.apache.juneau.commons.utils.ThrowableUtils.*; 029 030import java.lang.annotation.*; 031import java.lang.reflect.*; 032import java.net.*; 033import java.util.*; 034import java.util.concurrent.atomic.*; 035import java.util.function.*; 036import java.util.stream.*; 037 038import org.apache.juneau.commons.collections.*; 039import org.apache.juneau.commons.inject.*; 040import org.apache.juneau.commons.lang.*; 041import org.apache.juneau.commons.reflect.*; 042 043/** 044 * Contains metadata about a bean property. 045 * 046 * <p> 047 * Contains information such as type of property (e.g. field/getter/setter), class type of property value, and whether 048 * any transforms are associated with this property. 049 * 050 * <p> 051 * Developers will typically not need access to this class. The information provided by it is already exposed through 052 * several methods on the {@link BeanMap} API. 053 * 054 * <h5 class='topic'>Thread safety</h5> 055 * 056 * Instances are immutable after construction and are safe for concurrent read access. 057 * 058 */ 059@SuppressWarnings({ 060 "java:S115", // Constants use UPPER_snakeCase convention (e.g., MSG_getterOrFieldNotDefined) 061 "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants 062 "java:S1452", // Wildcard required - BeanMap<?>, ClassMeta<?> for property metadata 063 "java:S3776", // setPropertyValue(), add() and set() branch over every property kind (array, collection, map, bean, swap) and are kept as single flows 064 "java:S6541", // setPropertyValue() (about 150 lines) is the single dispatch point for assigning a value of any property kind 065 "rawtypes", // Raw types necessary for generic type handling 066 "unchecked" // Type erasure requires unchecked casts 067}) 068public class BeanPropertyMeta implements Comparable<BeanPropertyMeta> { 069 070 /** 071 * Sentinel value that, when returned from {@link BeanPropertyMeta.Builder#writeTransform} during 072 * {@link #set(BeanMap, String, Object)}, causes the set to be skipped entirely — neither the configured setter 073 * is invoked nor the property cache updated. 074 * 075 * <p> 076 * Provided as a generic SPI hook so the marshalling layer (or any other transform-installing extension) can 077 * implement "leave the bean's pre-existing value in place" semantics — used by the per-property null-coercion 078 * {@code SKIP} mode added to the marshalling layer in 10.0.0. 079 * 080 * @since 10.0.0 081 */ 082 public static final Object SKIP_VALUE = new Object(); 083 084 // Error message constants 085 private static final String MSG_getterOrFieldNotDefined = "Getter or public field not defined on property '%s'"; 086 087 /** 088 * BeanPropertyMeta builder class. 089 */ 090 @SuppressWarnings({ 091 "java:S1104" // Public mutable fields are an intentional cross-module SPI seam: the marshalling-side post-processor (different package) reads/writes them after validate(), so they cannot be final or package-private. 092 }) 093 public static class Builder { 094 BeanMeta<?> beanMeta; // Package-private for BeanMeta access 095 BeanTypeResolver bc; // The bean-modeling SPI seam to the marshalling-side type resolver. Null when the owning BeanMeta was built via the commons-side path. 096 BeanConfigContext config; // Package-private for BeanMeta access. Always non-null — sourced from the owning BeanMeta. 097 public String name; // Public so marshalling-side post-processors can use it when constructing diagnostic messages (e.g. MarshalledPropertyPostProcessor's schema-validation transform). 098 FieldInfo field; // Package-private for BeanMeta access 099 public FieldInfo innerField; // Package-private for BeanMeta access 100 public MethodInfo getter; // Package-private for BeanMeta access 101 public MethodInfo setter; // Package-private for BeanMeta access 102 MethodInfo extraKeys; // Package-private for BeanMeta access 103 public BeanInfo<?> rawTypeMeta; // Package-private for BeanMeta access (used to install swap-aware transforms). Null on commons-side path (no type resolution). Concrete instances are always {@code ClassMeta} since it's the only in-tree implementation; the field is typed against the bean-modeling SPI seam so the field can live in commons.bean. 104 public Object swap; // Object-typed so the field can live in commons.bean; cast to ObjectSwap by marshalling-side consumers. Set only via MarshalledPropertyPostProcessor (marshalling-side post-processor). 105 public BiFunction<BeanSession,Object,Object> readTransform; // Package-private; defaults to identity if null. Typed against the commons.bean SPI seam; marshalling-side installers cast the session argument back to {@code MarshallingSession} where needed (see {@code MarshalledPropertyPostProcessor#installSwapAwareTransforms}). 106 public BiFunction<BeanSession,Object,Object> writeTransform; // Package-private; defaults to identity if null. Typed against the commons.bean SPI seam (see readTransform note). 107 public List<ClassInfo> dictionaryClasses; // Package-private for BeanMeta access; @MarshalledProp(dictionary={}) classes scanned during validate(). 108 public Set<String> views; // Named views this property belongs to; null = untagged (in all views when defaultViewInclusion is enabled). Set via MarshalledPropertyPostProcessor. 109 private boolean isConstructorArg; 110 public boolean isUri; // Package-private so MarshalledPropertyPostProcessor can set @Uri-derived flag. Mirrors rawTypeMeta.isUri() plus @Uri annotation reads on field/getter/setter. 111 boolean isDyna; 112 boolean isDynaGetterMap; 113 public BeanInfo<?> typeMeta; // Package-private so the marshalling-side post-processor can override after @Swap/@MarshalledProp detection. Concrete instances are always {@code ClassMeta}; typed against the bean-modeling SPI seam. 114 private Object overrideValue; 115 private BeanPropertyMeta delegateFor; 116 private boolean canRead; 117 private boolean canWrite; 118 private boolean readOnly; 119 private boolean writeOnly; 120 boolean required; // @BeanProp(required=true) on the field, getter or setter (OR-merged). 121 122 Builder(BeanMeta<?> beanMeta, String name) { 123 this.beanMeta = beanMeta; 124 this.bc = beanMeta.getBeanTypeResolver(); // Returns null on commons-side path. 125 this.config = beanMeta.getConfig(); 126 this.name = name; 127 } 128 129 /** 130 * @return A new BeanPropertyMeta object using this builder. 131 */ 132 public BeanPropertyMeta build() { 133 return new BeanPropertyMeta(this); 134 } 135 136 /** 137 * Sets the original bean property that this one is overriding. 138 * 139 * @param value The original bean property that this one is overriding. Must not be <jk>null</jk>. 140 * @return This object. 141 */ 142 public Builder delegateFor(BeanPropertyMeta value) { 143 delegateFor = reqnn("value", value); 144 return this; 145 } 146 147 /** 148 * Installs the read-side value transform for this property. 149 * 150 * <p> 151 * Applied to the raw getter result by {@link BeanPropertyMeta#get(BeanMap,String)} before it is returned to the 152 * caller. Defaults to identity (raw value passes through unchanged). 153 * 154 * <p> 155 * Used by the marshalling layer to install {@code ObjectSwap}-aware behavior at bean-meta construction time; 156 * the bean-modeling layer itself only invokes the function and does not directly reference 157 * {@code ObjectSwap}. 158 * 159 * @param value The transform function. Must not be <jk>null</jk>. 160 * @return This object. 161 */ 162 public Builder readTransform(BiFunction<BeanSession,Object,Object> value) { 163 readTransform = reqnn("value", value); 164 return this; 165 } 166 167 /** 168 * Installs the write-side value transform for this property. 169 * 170 * <p> 171 * Applied to the incoming value by {@link BeanPropertyMeta#set(BeanMap,String,Object)} before the raw setter is 172 * invoked. Defaults to identity (raw value passes through unchanged). 173 * 174 * <p> 175 * Used by the marshalling layer to install {@code ObjectSwap}-aware behavior at bean-meta construction time; 176 * the bean-modeling layer itself only invokes the function and does not directly reference 177 * {@code ObjectSwap}. 178 * 179 * @param value The transform function. Must not be <jk>null</jk>. 180 * @return This object. 181 */ 182 public Builder writeTransform(BiFunction<BeanSession,Object,Object> value) { 183 writeTransform = reqnn("value", value); 184 return this; 185 } 186 187 /** 188 * Sets the overridden value of this bean property. 189 * 190 * @param value The overridden value of this bean property. Can be <jk>null</jk>, in which case no override is applied. 191 * @return This object. 192 */ 193 public Builder overrideValue(Object value) { 194 overrideValue = value; 195 return this; 196 } 197 198 /** 199 * Sets the raw metadata type for this bean property. 200 * 201 * @param value The raw metadata type for this bean property. Must not be <jk>null</jk>. 202 * @return This object. 203 */ 204 public Builder rawMetaType(BeanInfo<?> value) { 205 rawTypeMeta = reqnn("value", value); 206 typeMeta = rawTypeMeta; 207 return this; 208 } 209 210 /** 211 * Sets the raw metadata type for this bean property from a {@link Class}. 212 * 213 * <p> 214 * Convenience overload that resolves the supplied class via the property's 215 * {@link BeanTypeResolver}, allowing callers from the bean-modeling layer to seed the type without 216 * holding a {@link BeanInfo} reference. 217 * 218 * <p> 219 * When the owning {@link BeanMeta} was built via the commons-side path 220 * ({@link BeanMeta#of(Class, BeanConfigContext)}), no resolver is available, so 221 * {@code rawTypeMeta}/{@code typeMeta} are left <jk>null</jk> and the property runs in raw-reflection mode. 222 * 223 * @param value The raw metadata type for this bean property. Must not be <jk>null</jk>. 224 * @return This object. 225 */ 226 public Builder rawMetaType(Class<?> value) { 227 reqnn("value", value); 228 if (bc == null) 229 return this; 230 rawTypeMeta = bc.resolveType(null, info(value), null); 231 typeMeta = rawTypeMeta; 232 return this; 233 } 234 235 /** 236 * Marks this property as readable. 237 * 238 * @return This object. 239 */ 240 public Builder canRead() { 241 canRead = true; 242 return this; 243 } 244 245 /** 246 * Marks this property as writable. 247 * 248 * @return This object. 249 */ 250 public Builder canWrite() { 251 canWrite = true; 252 return this; 253 } 254 255 /** 256 * Marks this property as a constructor argument. 257 * 258 * @return This object. 259 */ 260 public Builder setAsConstructorArg() { 261 isConstructorArg = true; 262 return this; 263 } 264 265 /** 266 * Sets the extra keys method for this bean property. 267 * 268 * @param value The method info that returns extra keys for this property. Must not be <jk>null</jk>. 269 * @return This object. 270 */ 271 public Builder setExtraKeys(MethodInfo value) { 272 reqnn("value", value); 273 extraKeys = value.accessible(); 274 return this; 275 } 276 277 /** 278 * Sets the field for this bean property. 279 * 280 * @param value The field info for this bean property. Must not be <jk>null</jk>. 281 * @return This object. 282 */ 283 public Builder setField(FieldInfo value) { 284 reqnn("value", value); 285 field = value.accessible(); 286 innerField = field; 287 return this; 288 } 289 290 /** 291 * Sets the getter method for this bean property. 292 * 293 * @param value The getter method info for this bean property. Must not be <jk>null</jk>. 294 * @return This object. 295 */ 296 public Builder setGetter(MethodInfo value) { 297 reqnn("value", value); 298 getter = value.accessible(); 299 return this; 300 } 301 302 /** 303 * Sets the inner field for this bean property from a {@link FieldInfo}. 304 * 305 * @param value The field info containing the inner field. Must not be <jk>null</jk>. 306 * @return This object. 307 */ 308 public Builder setInnerField(FieldInfo value) { 309 innerField = reqnn("innerField", value); 310 return this; 311 } 312 313 /** 314 * Sets the setter method for this bean property. 315 * 316 * @param value The setter method info for this bean property. Must not be <jk>null</jk>. 317 * @return This object. 318 */ 319 public Builder setSetter(MethodInfo value) { 320 reqnn("value", value); 321 setter = value.accessible(); 322 return this; 323 } 324 325 /** 326 * Validates this bean property configuration. 327 * 328 * <p> 329 * After validation succeeds, marshalling-side callers (currently {@link BeanMeta}) read the 330 * {@link #dictionaryClasses} field to construct the property-level registry lookup and store it in a 331 * side-map keyed by the built {@link BeanPropertyMeta}. The bean-modeling layer itself no longer carries a 332 * concrete registry reference. 333 * 334 * <p> 335 * When {@code bc} is <jk>null</jk> (commons-side path), this method runs in raw-reflection mode: 336 * annotation reads are routed through {@link BeanConfigContext#getAnnotationProvider()} but no 337 * {@code ClassMeta} resolution or {@code ObjectSwap} discovery is performed; the property's 338 * {@code rawTypeMeta}/{@code typeMeta} stay <jk>null</jk> and the resulting {@link BeanPropertyMeta} 339 * exposes raw getter/setter invocation only. 340 * 341 * @param bc The bean context. May be <jk>null</jk> for the commons-side path. 342 * @param typeVarImpls Type variable implementations. Ignored when {@code bc} is <jk>null</jk>. 343 * @param bpro Bean properties read-only set. Must not be <jk>null</jk> (pass an empty set for none). 344 * @param bpwo Bean properties write-only set. Must not be <jk>null</jk> (pass an empty set for none). 345 * @return <jk>true</jk> if this property is valid, <jk>false</jk> otherwise. 346 * @throws Exception If validation fails. 347 */ 348 @SuppressWarnings({ 349 "java:S112" // Generic exception thrown; acceptable for framework/lifecycle methods 350 }) 351 public boolean validate(BeanTypeResolver bc, TypeVariables typeVarImpls, Set<String> bpro, Set<String> bpwo) throws Exception { 352 353 var ap = nn(bc) ? bc.getAnnotationProvider() : config.getAnnotationProvider(); 354 355 if (field == null && getter == null && setter == null) 356 return false; 357 358 // Settings reads route through BeanConfigContext so the commons-side path works without a MarshallingContext. 359 if (field == null && setter == null && config.isBeansRequireSettersForGetters() && ! isConstructorArg) 360 return false; 361 362 canRead |= (nn(field) || nn(getter)); 363 canWrite |= (nn(field) || nn(setter) || isConstructorArg); 364 365 var ifi = innerField; 366 var gi = getter; 367 var si = setter; 368 369 // @MarshalledProp / @Swap annotation reads have been lifted out to the marshalling-side post-processor 370 // (see {@link org.apache.juneau.MarshalledPropertyPostProcessor#process}). They are processed by {@link BeanMeta} 371 // after this method returns. The post-processor mutates {@link #swap}, {@link #properties}, 372 // {@link #dictionaryClasses}, and {@link #typeMeta} (when a swap is detected) on this builder. 373 dictionaryClasses = emptyList(); 374 375 if (nn(innerField)) { 376 var lbp = ap.find(BeanProp.class, ifi); 377 if (nn(bc) && (nn(field) || ine(lbp))) { 378 // Only use field type if it's a bean property or has @BeanProp annotation. 379 // Otherwise, we want to infer the type from the getter or setter. 380 rawTypeMeta = bc.resolveType(o(last(lbp)).orElse(null), innerField.getFieldType(), typeVarImpls); 381 isUri |= (rawTypeMeta.isUri()); 382 } 383 lbp.forEach(x -> { 384 var beanp = x.inner(); 385 if (ine(beanp.ro())) 386 readOnly = b(beanp.ro()); 387 if (ine(beanp.wo())) 388 writeOnly = b(beanp.wo()); 389 required |= beanp.required(); 390 }); 391 } 392 393 if (nn(getter)) { 394 var lbp = ap.find(BeanProp.class, gi); 395 if (nn(bc) && rawTypeMeta == null) 396 rawTypeMeta = bc.resolveType(o(last(lbp)).orElse(null), getter.getReturnType(), typeVarImpls); 397 if (nn(rawTypeMeta)) 398 isUri |= rawTypeMeta.isUri(); 399 lbp.forEach(x -> { 400 var beanp = x.inner(); 401 if (ine(beanp.ro())) 402 readOnly = b(beanp.ro()); 403 if (ine(beanp.wo())) 404 writeOnly = b(beanp.wo()); 405 required |= beanp.required(); 406 }); 407 } 408 409 if (nn(setter)) { 410 var lbp = ap.find(BeanProp.class, si); 411 if (nn(bc) && rawTypeMeta == null) 412 rawTypeMeta = bc.resolveType(o(last(lbp)).orElse(null), setter.getParameterTypes().get(0), typeVarImpls); 413 if (nn(rawTypeMeta)) 414 isUri |= rawTypeMeta.isUri(); 415 lbp.forEach(x -> { 416 var beanp = x.inner(); 417 if (ine(beanp.ro())) 418 readOnly = b(beanp.ro()); 419 if (ine(beanp.wo())) 420 writeOnly = b(beanp.wo()); 421 required |= beanp.required(); 422 }); 423 } 424 425 // On the commons-side path (bc == null), rawTypeMeta stays null and validate() accepts the property 426 // in raw-reflection mode. The marshalling-side path still requires a resolvable type. 427 if (nn(bc) && rawTypeMeta == null) 428 return false; 429 430 isDyna = eq(name, "*"); 431 432 // Do some annotation validation. Type-aware validation requires rawTypeMeta — on the commons-side 433 // path we skip it entirely. 434 if (nn(rawTypeMeta)) { 435 var ci = rawTypeMeta; 436 if (nn(getter)) { 437 var pt = getter.getParameterTypes(); 438 if (isDyna) { 439 if (ci.isAssignableTo(Map.class) && ie(pt)) { 440 isDynaGetterMap = true; 441 } else if (pt.size() == 1 && pt.get(0).is(String.class)) { 442 // OK. 443 } else { 444 return false; 445 } 446 } else { 447 if (! ci.isAssignableTo(getter.getReturnType())) 448 return false; 449 } 450 } 451 if (nn(setter)) { 452 var pt = setter.getParameterTypes(); 453 if (isDyna) { 454 if (pt.size() == 2 && pt.get(0).is(String.class)) { 455 // OK. 456 } else { 457 return false; 458 } 459 } else { 460 if (pt.size() != 1 || ! ci.isAssignableTo(pt.get(0).inner())) 461 return false; 462 } 463 } 464 if (nn(field)) { 465 if (isDyna) { 466 if (! field.getFieldType().isAssignableTo(Map.class)) 467 return false; 468 } else { 469 if (! ci.isAssignableTo(field.getFieldType())) 470 return false; 471 } 472 } 473 474 if (isDyna) { 475 rawTypeMeta = rawTypeMeta.getValueType(); 476 if (rawTypeMeta == null && nn(bc)) 477 rawTypeMeta = bc.objectType(); 478 } 479 480 if (typeMeta == null) { 481 if (rawTypeMeta == null && nn(bc)) 482 typeMeta = bc.objectType(); 483 else 484 typeMeta = rawTypeMeta; 485 } 486 if (typeMeta == null) 487 typeMeta = rawTypeMeta; 488 } 489 490 if (bpro.contains(name) || bpro.contains("*")) 491 readOnly = true; 492 if (bpwo.contains(name) || bpwo.contains("*")) 493 writeOnly = true; 494 495 return true; 496 } 497 498 /** 499 * Returns whether this property is marked {@code @BeanProp(required=true)}. 500 * 501 * @return <jk>true</jk> if required. 502 */ 503 boolean isRequired() { return required; } 504 505 /** 506 * Returns whether input can ever set this property, which a required property must allow. 507 * 508 * <p> 509 * Call only after {@link #validate} and after constructor-argument marking. 510 * 511 * @return <jk>true</jk> if the property has a field, a setter, or is a constructor argument, and is not read-only or dynamic. 512 */ 513 boolean canSatisfyRequired() { 514 return ! isDyna && ! readOnly && (nn(field) || nn(setter) || isConstructorArg); 515 } 516 517 } 518 519 /** 520 * Creates a builder for {@link #BeanPropertyMeta} objects. 521 * 522 * @param beanMeta The metadata on the bean. Must not be <jk>null</jk>. 523 * @param name The bean property name. 524 * @return A new builder. 525 */ 526 public static Builder builder(BeanMeta<?> beanMeta, String name) { 527 return new Builder(beanMeta, name); 528 } 529 530 private final AnnotationProvider ap; // Annotation provider for finding annotations on this property. Sourced from bc (marshalling-side) or beanMeta.getConfig() (commons-side). 531 private final Supplier<List<AnnotationInfo<?>>> annotations; // Memoized list of all annotations on this property. 532 private final BeanTypeResolver bc; // The bean-modeling SPI seam to the marshalling-side type resolver. Null when the owning BeanMeta was built via the commons-side path. 533 private final BeanConfigContext config; // Bean-modeling settings facade — always non-null. Mirrors the BeanMeta's config. 534 private final BeanMeta<?> beanMeta; // The bean that this property belongs to. 535 private final boolean canRead; // True if this property can be read. 536 private final boolean canWrite; // True if this property can be written. 537 private final BeanPropertyMeta delegateFor; // The bean property that this meta is a delegate for. 538 private final MethodInfo extraKeys; // The bean property extraKeys method. 539 private final FieldInfo field; // The bean property field (if it has one). 540 private final MethodInfo getter; // The bean property getter. 541 private final int hashCode; // Cached hash code for this property meta. 542 private final FieldInfo innerField; // The bean property field even if private (if it has one). 543 private final boolean isDyna; // True if this is a dyna property (i.e. name="*"). 544 private final boolean isDynaGetterMap; // True if this is a dyna property where the getter returns a Map directly. 545 private final boolean isUri; // True if this is a URL/URI or annotated with @URI. 546 private final String name; // The name of the property. 547 private final Object overrideValue; // The bean property value (if it's an overridden delegate). 548 private final BeanInfo<?> rawTypeMeta; // The real class type of the bean property. Concrete instances are always {@code ClassMeta}; typed against the bean-modeling SPI seam for the eventual move to commons.bean. 549 private final BiFunction<BeanSession,Object,Object> readTransform; // Applied to raw getter result; identity by default. Typed against the commons.bean SPI seam. 550 private final boolean required; // True if this property is marked @BeanProp(required=true). 551 private final boolean readOnly; // True if this property is read-only. 552 private final MethodInfo setter; // The bean property setter. 553 private final BeanInfo<?> typeMeta; // The transformed class type of the bean property. Concrete instances are always {@code ClassMeta}; typed against the bean-modeling SPI seam. 554 private final BiFunction<BeanSession,Object,Object> writeTransform; // Applied to incoming value before raw setter; identity by default. Typed against the commons.bean SPI seam. 555 private final AtomicReference<Supplier<Collection<?>>> abstractCollectionFactory = new AtomicReference<>(); // Memoized factory for abstract/interface collection property types. 556 private final AtomicReference<Supplier<Collection<?>>> concreteCollectionFactory = new AtomicReference<>(); // Memoized factory for concrete collection property types. 557 private final AtomicReference<Supplier<Map<?,?>>> mapFactory = new AtomicReference<>(); // Memoized factory for concrete map property types. 558 private final boolean writeOnly; // True if this property is write-only. 559 private final Set<String> views; // Named views this property belongs to; null = untagged (included in all views when defaultViewInclusion is enabled). 560 561 /** 562 * Creates a new BeanPropertyMeta using the contents of the specified builder. 563 * 564 * @param b The builder to copy fields from. Must not be <jk>null</jk>. 565 */ 566 protected BeanPropertyMeta(Builder b) { 567 annotations = memoize(this::findAnnotations); 568 bc = b.bc; 569 config = b.config; 570 beanMeta = b.beanMeta; 571 canRead = b.canRead; 572 canWrite = b.canWrite; 573 delegateFor = b.delegateFor; 574 extraKeys = b.extraKeys; 575 field = b.field; 576 getter = b.getter; 577 innerField = b.innerField; 578 isDyna = b.isDyna; 579 isDynaGetterMap = b.isDynaGetterMap; 580 isUri = b.isUri; 581 name = b.name; 582 overrideValue = b.overrideValue; 583 rawTypeMeta = b.rawTypeMeta; 584 readOnly = b.readOnly; 585 required = b.required; 586 setter = b.setter; 587 typeMeta = b.typeMeta; 588 writeOnly = b.writeOnly; 589 views = b.views != null ? u(b.views) : null; 590 readTransform = b.readTransform != null ? b.readTransform : (session, o) -> o; 591 writeTransform = b.writeTransform != null ? b.writeTransform : (session, o) -> o; 592 593 ap = nn(bc) ? bc.getAnnotationProvider() : b.config.getAnnotationProvider(); 594 hashCode = h(beanMeta, name); 595 } 596 597 /** 598 * Adds a value to a {@link Collection} or array property. 599 * 600 * <p> 601 * Note that adding values to an array property is inefficient for large arrays since it must copy the array into a 602 * larger array on each operation. 603 * 604 * <p> 605 * <b>Marshalling-only path.</b> Requires a property built via the marshalling-side construction path 606 * (i.e. with a non-null {@link #getBeanInfo() rawTypeMeta} and a non-null backing 607 * {@code MarshallingSession} on the supplied {@link BeanMap}). When the owning {@link BeanMeta} was built via 608 * {@link BeanMeta#of(Class, BeanConfigContext)}, this method throws 609 * {@link UnsupportedOperationException} because adding into a Collection/array property requires 610 * type-aware element conversion (the marshalling session's {@code convertToType}) that is not available in the 611 * bean-modeling-only path. 612 * 613 * @param m The bean of the field being set. 614 * @param pName 615 * The property name if this is a dyna property (i.e. <js>"*"</js>). 616 * <br>Otherwise can be <jk>null</jk>. 617 * @param value The value to add to the field. Can be <jk>null</jk>. 618 * @throws BeanRuntimeException If field is not a collection or array. 619 * @throws UnsupportedOperationException If this property was built via the bean-modeling-only path 620 * ({@link BeanMeta#of(Class, BeanConfigContext)}). 621 */ 622 public void add(BeanMap<?> m, String pName, Object value) throws BeanRuntimeException { 623 624 if (rawTypeMeta == null) 625 throw uoex("Property '%s' was built via the bean-modeling-only path; Collection/array add operations require a marshalling context.", name); 626 627 m.recordPresent(name); 628 629 // Read-only beans get their properties stored in a cache. 630 if (m.bean == null) { 631 if (! m.propertyCache.containsKey(name)) 632 m.propertyCache.put(name, l()); 633 ((List)m.propertyCache.get(name)).add(value); 634 return; 635 } 636 637 var session = m.getBeanSession(); 638 639 var isCollection = rawTypeMeta.isCollection(); 640 var isArray = rawTypeMeta.isArray(); 641 642 if (! (isCollection || isArray)) 643 throw brex(beanMeta.getBeanInfo(), "Attempt to add element to property '%s' which is not a collection or array", name); 644 645 var bean = m.getBean(true); 646 647 var elementType = rawTypeMeta.getElementType(); 648 649 try { 650 var v = session.convertToType(value, elementType); 651 652 if (isCollection) { 653 Collection c = (Collection)invokeGetter(bean, pName); 654 655 Collection c2 = null; 656 if (nn(c)) { 657 if (canAddTo(c)) { 658 c.add(v); 659 return; 660 } 661 c2 = c; 662 } 663 664 if (rawTypeMeta.canCreateNewInstance()) 665 c = (Collection)rawTypeMeta.newInstance(); 666 else 667 c = l(); 668 669 if (c2 != null) 670 c.addAll(c2); 671 672 c.add(v); 673 674 invokeSetter(bean, pName, c); 675 676 } else /* isArray() */ { 677 678 if (m.arrayPropertyCache == null) 679 m.arrayPropertyCache = new TreeMap<>(); 680 681 List l = m.arrayPropertyCache.get(name); 682 if (l == null) { 683 l = new LinkedList(); // ArrayLists and LinkLists appear to perform equally. 684 m.arrayPropertyCache.put(name, l); 685 686 // Copy any existing array values into the temporary list. 687 var oldArray = invokeGetter(bean, pName); 688 copyArrayToList(oldArray, l); 689 } 690 691 // Add new entry to our array. 692 l.add(v); 693 } 694 695 } catch (BeanRuntimeException e) { 696 throw e; 697 } catch (Exception e) { 698 throw brex(e); 699 } 700 } 701 702 /** 703 * Adds a value to a {@link Map} or bean property. 704 * 705 * <p> 706 * <b>Marshalling-only path.</b> Requires a property built via the marshalling-side construction path 707 * (i.e. with a non-null {@link #getBeanInfo() rawTypeMeta} and a non-null backing 708 * {@code MarshallingSession} on the supplied {@link BeanMap}). When the owning {@link BeanMeta} was built via 709 * {@link BeanMeta#of(Class, BeanConfigContext)}, this method throws 710 * {@link UnsupportedOperationException} because adding into a Map/bean property requires type-aware 711 * value conversion (the marshalling session's {@code convertToType} / {@code toBeanMap}) that is not available 712 * in the bean-modeling-only path. 713 * 714 * @param m The bean of the field being set. 715 * @param pName 716 * The property name if this is a dyna property (i.e. <js>"*"</js>). 717 * <br>Otherwise can be <jk>null</jk>. 718 * @param key The key to add to the field. 719 * @param value The value to add to the field. Can be <jk>null</jk>. 720 * @throws BeanRuntimeException If field is not a map or array. 721 * @throws UnsupportedOperationException If this property was built via the bean-modeling-only path 722 * ({@link BeanMeta#of(Class, BeanConfigContext)}). 723 */ 724 public void add(BeanMap<?> m, String pName, String key, Object value) throws BeanRuntimeException { 725 726 if (rawTypeMeta == null) 727 throw uoex("Property '%s' was built via the bean-modeling-only path; Map/bean add operations require a marshalling context.", name); 728 729 m.recordPresent(name); 730 731 // Read-only beans get their properties stored in a cache. 732 if (m.bean == null) { 733 if (! m.propertyCache.containsKey(name)) 734 m.propertyCache.put(name, m()); 735 ((Map)m.propertyCache.get(name)).put(key, value); 736 return; 737 } 738 739 var session = m.getBeanSession(); 740 741 var isMap = rawTypeMeta.isMap(); 742 var isBean = rawTypeMeta.isBean(); 743 744 if (! (isBean || isMap)) 745 throw brex(beanMeta.getBeanInfo(), "Attempt to add key/value to property '%s' which is not a map or bean", name); 746 747 var bean = m.getBean(true); 748 749 try { 750 var v = session.convertToType(value, rawTypeMeta.getElementType()); 751 752 if (isMap) { 753 var map = (Map)invokeGetter(bean, pName); 754 755 if (nn(map)) { 756 map.put(key, v); 757 return; 758 } 759 760 if (rawTypeMeta.canCreateNewInstance()) 761 map = (Map)rawTypeMeta.newInstance(); 762 else 763 map = m(); 764 765 map.put(key, v); 766 767 invokeSetter(bean, pName, map); 768 769 } else /* isBean() */ { 770 771 var b = invokeGetter(bean, pName); 772 773 if (nn(b)) { 774 ((Map) session.toBeanMap(b)).put(key, v); 775 return; 776 } 777 778 if (rawTypeMeta.canCreateNewInstance(m.getBean(false))) { 779 b = rawTypeMeta.newInstance(); 780 ((Map) session.toBeanMap(b)).put(key, v); 781 } 782 783 invokeSetter(bean, pName, b); 784 } 785 786 } catch (BeanRuntimeException e) { 787 throw e; 788 } catch (Exception e) { 789 throw brex(e); 790 } 791 } 792 793 /** 794 * Returns <jk>true</jk> if this property can be read. 795 * 796 * <p> 797 * Write-only properties (see {@link #isWriteOnly()}) are never readable, so serializers, tabular column sets and 798 * schemas skip them. They remain settable through {@link #canWrite()} and parsing. 799 * 800 * @return <jk>true</jk> if this property can be read. 801 */ 802 public boolean canRead() { 803 return canRead && ! writeOnly; 804 } 805 806 /** 807 * Returns <jk>true</jk> if this property can be written. 808 * 809 * @return <jk>true</jk> if this property can be written. 810 */ 811 public boolean canWrite() { 812 return canWrite; 813 } 814 815 @Override /* Overridden from Comparable */ 816 public int compareTo(BeanPropertyMeta o) { 817 return cmp(name, o.name); 818 } 819 820 @Override /* Overridden from Object */ 821 public boolean equals(Object o) { 822 return (o instanceof BeanPropertyMeta o2) && eq(this, o2, (x, y) -> eq(x.name, y.name) && eq(x.beanMeta, y.beanMeta)); 823 } 824 825 /** 826 * Returns all annotations on this property (field, getter, and setter). 827 * 828 * <p> 829 * The annotations are found on: 830 * <ul> 831 * <li>The field (if present) 832 * <li>The getter method (if present) - including annotations on the method, its return type, and package 833 * <li>The setter method (if present) - including annotations on the method, its return type, and package 834 * </ul> 835 * 836 * <p> 837 * The result is memoized and computed only once. 838 * 839 * @return A list of all annotation infos on this property. Never <jk>null</jk>. 840 */ 841 public List<AnnotationInfo> getAnnotations() { 842 return (List)annotations.get(); 843 } 844 845 /** 846 * Returns a stream of annotations of the specified type on this property. 847 * 848 * <p> 849 * The annotations are found on: 850 * <ul> 851 * <li>The field (if present) 852 * <li>The getter method (if present) - including annotations on the method, its return type, and package 853 * <li>The setter method (if present) - including annotations on the method, its return type, and package 854 * </ul> 855 * 856 * @param <A> The annotation type to find. 857 * @param a The annotation class to find. Can be <jk>null</jk>, in which case an empty stream is returned. 858 * @return A stream of annotation infos of the specified type. Never <jk>null</jk>. 859 */ 860 public <A extends Annotation> Stream<AnnotationInfo<A>> getAnnotations(Class<A> a) { 861 if (a == null) 862 return Stream.empty(); 863 return annotations.get().stream() 864 .filter(x -> x.isType(a)) 865 .map(x -> (AnnotationInfo<A>)x); 866 } 867 868 /** 869 * Helper method to find all annotations on this property. 870 * 871 * @return A list of all annotation infos found on the field, getter, and setter. 872 */ 873 private List<AnnotationInfo<?>> findAnnotations() { 874 var result = new ArrayList<AnnotationInfo<?>>(); 875 if (nn(field)) 876 result.addAll(ap.find(field)); 877 if (nn(getter)) 878 result.addAll(ap.find(getter, SELF, MATCHING_METHODS, RETURN_TYPE, PACKAGE)); 879 if (nn(setter)) 880 result.addAll(ap.find(setter, SELF, MATCHING_METHODS, RETURN_TYPE, PACKAGE)); 881 if (nn(extraKeys)) 882 result.addAll(ap.find(extraKeys, SELF, MATCHING_METHODS, RETURN_TYPE, PACKAGE)); 883 return u(result); 884 } 885 886 /** 887 * Equivalent to calling {@link BeanMap#get(Object)}, but is faster since it avoids looking up the property meta. 888 * 889 * @param m The bean map to get the transformed value from. 890 * @param pName 891 * The property name if this is a dyna property (i.e. <js>"*"</js>). 892 * <br>Otherwise can be <jk>null</jk>. 893 * @return 894 * The property value. 895 * <br>Returns <jk>null</jk> if this is a write-only property. 896 */ 897 public Object get(BeanMap<?> m, String pName) { 898 return m.meta.onReadProperty(m.bean, pName, getInner(m, pName)); 899 } 900 901 /** 902 * Returns the bean meta that this property belongs to. 903 * 904 * @return The bean meta that this property belongs to. 905 */ 906 public BeanMeta<?> getBeanMeta() { return beanMeta; } 907 908 /** 909 * Returns the bean dictionary in use for this bean property. 910 * 911 * <p> 912 * The order of lookup for the dictionary is as follows: 913 * <ol> 914 * <li>Dictionary defined via {@code @MarshalledProp(dictionary)}. 915 * <li>Dictionary defined via {@code MarshallingContext.Builder<?>#beanDictionary(Class...)}. 916 * </ol> 917 * 918 * <p> 919 * The per-property registry no longer lives on this object; it is stored in a marshalling-side 920 * side-map on {@link BeanMeta} keyed by {@link BeanPropertyMeta}. This method delegates to 921 * {@link BeanMeta#getPropertyBeanRegistry(BeanPropertyMeta)} for backwards compatibility with existing call sites 922 * in the marshalling layer (parser/serializer sessions, XML content-property handling, etc.). 923 * 924 * <p> 925 * Returns the bean-modeling-side SPI type ({@link BeanRegistryLookup}). Marshalling-side callers that need 926 * implementation-specific behavior cast at the call site. 927 * 928 * @return The bean dictionary in use for this bean property, or <jk>null</jk> if no registry was registered. 929 */ 930 public BeanRegistryLookup getBeanRegistry() { return beanMeta.getPropertyBeanRegistry(this); } 931 932 /** 933 * Returns the {@link BeanInfo} of the class of this property. 934 * 935 * <p> 936 * If this property or the property type class has a {@code ObjectSwap} associated with it, this method returns the 937 * transformed class meta. 938 * This matches the class type that is used by the {@link #get(BeanMap,String)} and 939 * {@link #set(BeanMap,String,Object)} methods. 940 * 941 * <p> 942 * Returns the bean-modeling-side SPI type ({@link BeanInfo}). Marshalling-side callers that need the 943 * {@code ClassMeta} narrowing must cast — the concrete instance in-tree is always a {@code ClassMeta}. 944 * 945 * @return The {@link BeanInfo} of the class of this property, or <jk>null</jk> if this property was built via 946 * the bean-modeling-only path. 947 */ 948 public BeanInfo<?> getBeanInfo() { return typeMeta; } 949 950 /** 951 * Returns the metadata on the property that this metadata is a delegate for. 952 * 953 * @return the metadata on the property that this metadata is a delegate for, or this object if it's not a delegate. 954 */ 955 public BeanPropertyMeta getDelegateFor() { return coalesce(delegateFor, this); } 956 957 /** 958 * Returns the {@link Map} object returned by the DynaBean getter. 959 * 960 * <p> 961 * The DynaBean property is the property whose name is <js>"*"</js> and returns a map of "extra" properties on the 962 * bean. 963 * 964 * @param bean The bean. 965 * @return 966 * The map returned by the getter, or an empty map if the getter returned <jk>null</jk> or this isn't a DynaBean 967 * property. 968 * @throws IllegalArgumentException Thrown by method invocation. 969 * @throws IllegalAccessException Thrown by method invocation. 970 * @throws InvocationTargetException Thrown by method invocation. 971 */ 972 public Map<String,Object> getDynaMap(Object bean) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { 973 if (isDyna) { 974 if (nn(extraKeys) && nn(getter) && ! isDynaGetterMap) { 975 Map<String,Object> m = map(); 976 ((Collection<String>)extraKeys.invoke(bean)).forEach(x -> safe(() -> m.put(x, getter.invoke(bean, x)))); 977 return m; 978 } 979 if (nn(getter) && isDynaGetterMap) 980 return (Map)getter.invoke(bean); 981 if (nn(field)) 982 return (Map)field.get(bean); 983 throw brex(beanMeta.getBeanInfo(), MSG_getterOrFieldNotDefined, name); 984 } 985 return emptyMap(); 986 } 987 988 /** 989 * Returns the field for this property. 990 * 991 * @return The field info for this bean property, or <jk>null</jk> if there is no field associated with this bean property. 992 */ 993 public FieldInfo getField() { return field; } 994 995 /** 996 * Returns the getter method for this property. 997 * 998 * @return The getter method info for this bean property, or <jk>null</jk> if there is no getter method. 999 */ 1000 public MethodInfo getGetter() { return getter; } 1001 1002 /** 1003 * Returns the field for this property even if the field is private. 1004 * 1005 * @return The field info for this bean property, or <jk>null</jk> if there is no field associated with this bean property. 1006 */ 1007 public FieldInfo getInnerField() { return innerField; } 1008 1009 /** 1010 * Returns the name of this bean property. 1011 * 1012 * @return The name of the bean property. 1013 */ 1014 public String getName() { return name; } 1015 1016 /** 1017 * Equivalent to calling {@link BeanMap#getRaw(Object)}, but is faster since it avoids looking up the property meta. 1018 * 1019 * @param m The bean map to get the transformed value from. 1020 * @param pName 1021 * The property name if this is a dyna property (i.e. <js>"*"</js>). 1022 * <br>Otherwise can be <jk>null</jk>. 1023 * @return The raw property value. 1024 */ 1025 public Object getRaw(BeanMap<?> m, String pName) { 1026 try { 1027 // Read-only beans have their properties stored in a cache until getBean() is called. 1028 var bean = m.bean; 1029 if (bean == null) 1030 return m.propertyCache.get(name); 1031 1032 return invokeGetter(bean, pName); 1033 1034 } catch (Exception e) { 1035 if (config.isIgnoreInvocationExceptionsOnGetters()) { 1036 if (nn(rawTypeMeta) && rawTypeMeta.isPrimitive()) 1037 return rawTypeMeta.getPrimitiveDefault(); 1038 return null; 1039 } 1040 throw brex(e, beanMeta.getClassInfo(), "Exception occurred while getting property '%s'", name); 1041 } 1042 } 1043 1044 /** 1045 * Returns the setter method for this property. 1046 * 1047 * @return The setter method info for this bean property, or <jk>null</jk> if there is no setter method. 1048 */ 1049 public MethodInfo getSetter() { return setter; } 1050 1051 @Override /* Overridden from Object */ 1052 public int hashCode() { 1053 return hashCode; 1054 } 1055 1056 /** 1057 * Returns <jk>true</jk> if this bean property is named <js>"*"</js>. 1058 * 1059 * @return <jk>true</jk> if this bean property is named <js>"*"</js>. 1060 */ 1061 public boolean isDyna() { return isDyna; } 1062 1063 /** 1064 * Returns whether this property is marked {@code @BeanProp(required=true)}. 1065 * 1066 * <p> 1067 * Required properties must be present in parser input (or in the source map of a {@code Map}-to-bean conversion); 1068 * see {@link BeanProp#required()}. 1069 * 1070 * @return <jk>true</jk> if this property is required. 1071 */ 1072 public boolean isRequired() { return required; } 1073 1074 /** 1075 * Returns <jk>true</jk> if this property is read-only. 1076 * 1077 * <p> 1078 * This implies the property MIGHT be writable, but that parsers should not set a value for it. 1079 * 1080 * @return <jk>true</jk> if this property is read-only. 1081 */ 1082 public boolean isReadOnly() { return readOnly; } 1083 1084 /** 1085 * Returns <jk>true</jk> if this bean property is a URI. 1086 * 1087 * <p> 1088 * A bean property can be considered a URI if any of the following are true: 1089 * <ul> 1090 * <li>Property class type is {@link URL} or {@link URI}. 1091 * <li>Property class type is annotated with {@code @Uri}. 1092 * <li>Property getter, setter, or field is annotated with {@code @Uri}. 1093 * </ul> 1094 * 1095 * @return <jk>true</jk> if this bean property is a URI. 1096 */ 1097 public boolean isUri() { return isUri; } 1098 1099 /** 1100 * Returns the set of named views this property belongs to, or <jk>null</jk> if the property is untagged. 1101 * 1102 * <p> 1103 * An untagged property (one with no {@code @MarshalledProp(view=...)} annotation) follows the 1104 * default-view-inclusion policy: it is included under every active view when the policy is enabled 1105 * (the default behavior), or excluded from all views when the policy is disabled. 1106 * 1107 * <p> 1108 * A tagged property is included only when the active view name is contained in this set. 1109 * 1110 * @return The set of named views, or <jk>null</jk> if no view membership was declared. 1111 */ 1112 public Set<String> getViews() { return views; } 1113 1114 /** 1115 * Equivalent to calling {@link BeanMap#put(String, Object)}, but is faster since it avoids looking up the property 1116 * meta. 1117 * 1118 * <p> 1119 * This is a no-op on a read-only property. 1120 * 1121 * @param m The bean map to set the property value on. 1122 * @param pName 1123 * The property name if this is a dyna property (i.e. <js>"*"</js>). 1124 * <br>Otherwise can be <jk>null</jk>. 1125 * @param value The value to set. Can be <jk>null</jk> to set the property to <jk>null</jk>. 1126 * @return The previous property value. 1127 * @throws BeanRuntimeException If property could not be set. 1128 */ 1129 public Object set(BeanMap<?> m, String pName, Object value) throws BeanRuntimeException { 1130 Object value1 = m.meta.onWriteProperty(m.bean, pName, value); 1131 if (! isDyna) 1132 m.recordPresent(name); 1133 try { 1134 1135 if (readOnly) 1136 return null; 1137 1138 var session = m.getBeanSession(); 1139 1140 // Apply the install-time write transform (identity by default; swap-aware in the marshalling layer). 1141 value1 = writeTransform.apply(session, value1); 1142 1143 // Generic SPI hook: a transform that returns SKIP_VALUE causes the set to be skipped entirely so the 1144 // bean's pre-existing value (typically a field-initializer) is preserved. Used by the marshalling 1145 // layer's per-property null-coercion SKIP mode. 1146 if (value1 == SKIP_VALUE) 1147 return null; 1148 1149 if (m.bean == null) { 1150 1151 // Read-only beans get their properties stored in a cache. 1152 if (nn(m.propertyCache)) 1153 return m.propertyCache.put(name, value1); 1154 1155 throw brex("Non-existent bean instance on bean."); 1156 } 1157 1158 // Raw-reflection path: when the owning BeanMeta was built via the commons-side path, rawTypeMeta and 1159 // bc are null and there is no marshalling-aware type conversion to perform. Just invoke the setter 1160 // (or write the field) directly. 1161 if (rawTypeMeta == null) { 1162 var bean = m.getBean(true); 1163 var old = (nn(getter) || nn(field)) ? get(m, pName) : null; 1164 invokeSetter(bean, pName, value1); 1165 return old; 1166 } 1167 1168 var isMap = rawTypeMeta.isMap(); 1169 var isCollection = rawTypeMeta.isCollection(); 1170 1171 if ((! isDyna) && field == null && setter == null && ! (isMap || isCollection)) { 1172 if ((value1 == null && config.isIgnoreUnknownNullBeanProperties()) || config.isIgnoreMissingSetters()) 1173 return null; 1174 throw brex(beanMeta.getClassInfo(), "Setter or public field not defined on property '%s'", name); 1175 } 1176 1177 var bean = m.getBean(true); // Don't use getBean() because it triggers array creation! 1178 1179 return setPropertyValue(m, pName, value1, bean, isMap, isCollection, session); 1180 } catch (org.apache.juneau.commons.BasicRuntimeException e2) { 1181 // Preserve previous behavior: marshalling-side BasicRuntimeException (notably ParseException raised by 1182 // session.parseToMap/parseToList or writeTransform) gets wrapped in BeanRuntimeException so callers can 1183 // uniformly catch BeanRuntimeException. BeanRuntimeException itself does not extend BasicRuntimeException 1184 // so it propagates unchanged. 1185 throw brex(e2); 1186 } 1187 } 1188 1189 private Object setPropertyValue(BeanMap<?> m, String pName, Object value1, Object bean, boolean isMap, boolean isCollection, BeanSession session) { 1190 try { 1191 var r = (config.isBeanMapPutReturnsOldValue() || isMap || isCollection) && (nn(getter) || nn(field)) ? get(m, pName) : null; 1192 var propertyClass = rawTypeMeta.inner(); 1193 var pcInfo = rawTypeMeta; 1194 1195 if (value1 == null && (isMap || isCollection)) { 1196 invokeSetter(bean, pName, null); 1197 return r; 1198 } 1199 1200 var vc = value1 == null ? null : value1.getClass(); 1201 1202 if (isMap && (setter == null || ! pcInfo.isAssignableFrom(vc))) { 1203 1204 if (! (value1 instanceof Map)) { 1205 if (value1 instanceof CharSequence value12) 1206 value1 = session.parseToMap(value12); 1207 else 1208 throw brex(beanMeta.getBeanInfo(), "Cannot set property '%s' of type '%s' to object of type '%s'", name, propertyClass.getName(), cn(value1)); 1209 } 1210 1211 var valueMap = (Map)value1; 1212 var propMap = (Map)r; 1213 var keyType = rawTypeMeta.getKeyType(); 1214 var valueType = rawTypeMeta.getValueType(); 1215 1216 // If the property type is abstract, then we either need to reuse the existing 1217 // map (if it's not null), or try to assign the value directly. 1218 if (! rawTypeMeta.canCreateNewInstance()) { 1219 if (propMap == null) { 1220 if (setter == null && field == null) 1221 throw brex(beanMeta.getBeanInfo(), 1222 "Cannot set property '%s' of type '%s' to object of type '%s' because no setter or public field is defined, and the current value is null", name, 1223 propertyClass.getName(), cn(value1)); 1224 1225 if (propertyClass.isInstance(valueMap)) { 1226 if (! (keyType.isObject() && valueType.isObject())) { 1227 var needsConversion = Flag.create(); 1228 valueMap.forEach((k, v2) -> { 1229 if (nn(k) && ! keyType.isObject() && ! keyType.isInstance(k)) 1230 needsConversion.set(); 1231 if (nn(v2) && ! valueType.isInstance(v2)) { 1232 needsConversion.set(); 1233 } 1234 }); 1235 if (needsConversion.isSet()) 1236 valueMap = (Map)session.convertToType(valueMap, rawTypeMeta); 1237 } 1238 invokeSetter(bean, pName, valueMap); 1239 return r; 1240 } 1241 throw brex(beanMeta.getBeanInfo(), 1242 "Cannot set property '%1$s' of type '%3$s' to object of type '%3$s' because the assigned map cannot be converted to the specified type because the property type is abstract, and the property value is currently null", 1243 name, propertyClass.getName(), cn(value1)); 1244 } 1245 } else { 1246 if (propMap == null) { 1247 propMap = mapFactory().get(); 1248 } else { 1249 propMap.clear(); 1250 } 1251 } 1252 1253 // Set the values. 1254 for (var e : ((Set<Map.Entry>)valueMap.entrySet())) { 1255 var k1 = e.getKey(); 1256 var v1 = e.getValue(); 1257 if (! keyType.isObject()) 1258 k1 = session.convertToType(k1, keyType); 1259 if (! valueType.isObject()) 1260 v1 = session.convertToType(v1, valueType); 1261 propMap.put(k1, v1); 1262 } 1263 if (nn(setter) || nn(field)) 1264 invokeSetter(bean, pName, propMap); 1265 1266 } else if (isCollection && (setter == null || ! pcInfo.isAssignableFrom(vc))) { 1267 1268 if (! (value1 instanceof Collection)) { 1269 if (value1 instanceof CharSequence value12) 1270 value1 = session.parseToList(value12); 1271 else 1272 throw brex(beanMeta.getBeanInfo(), "Cannot set property '%s' of type '%s' to object of type '%s'", name, propertyClass.getName(), cn(value1)); 1273 } 1274 1275 var valueList = (Collection)value1; 1276 var propList = (Collection)r; 1277 var elementType = rawTypeMeta.getElementType(); 1278 1279 // If the property type is abstract, then we either need to reuse the existing 1280 // collection (if it's not null), or try to assign the value directly. 1281 if (! rawTypeMeta.canCreateNewInstance()) { 1282 if (propList == null) { 1283 if (setter == null && field == null) 1284 throw brex(beanMeta.getBeanInfo(), 1285 "Cannot set property '%s' of type '%s' to object of type '%s' because no setter or public field is defined, and the current value is null", name, 1286 propertyClass.getName(), cn(value1)); 1287 1288 if (propertyClass.isInstance(valueList) || (nn(setter) && setter.getParameterTypes().get(0).is(Collection.class))) { 1289 if (! elementType.isObject()) { 1290 // Element coercion is required. Build the coerced elements into a concrete collection 1291 // whose shape is assignable to the (abstract) property type — never a bare ArrayList — 1292 // so Deque/Queue/SortedSet fields aren't handed an incompatible container. (The source 1293 // value can incidentally satisfy propertyClass.isInstance(...) — e.g. the parser's 1294 // generic list is a LinkedList, which is-a Deque/Queue — yet still need coercion.) 1295 Collection coerced = abstractCollectionFactory(propertyClass).get(); 1296 for (var v : valueList) { 1297 var needsConversion = v == null ? elementType.isOptional() : ! elementType.isInstance(v); 1298 coerced.add(needsConversion ? session.convertToType(v, elementType) : v); 1299 } 1300 valueList = coerced; 1301 } 1302 invokeSetter(bean, pName, valueList); 1303 return r; 1304 } 1305 1306 // Robust best-effort-then-fallback materialization — never returns null (see helper Javadoc). 1307 propList = abstractCollectionFactory(propertyClass).get(); 1308 invokeSetter(bean, pName, propList); 1309 } 1310 propList.clear(); 1311 } else { 1312 if (propList == null) { 1313 propList = concreteCollectionFactory().get(); 1314 invokeSetter(bean, pName, propList); 1315 } else { 1316 propList.clear(); 1317 } 1318 } 1319 1320 // Set the values. 1321 for (var x : valueList) { 1322 if (! elementType.isObject()) 1323 x = session.convertToType(x, elementType); 1324 propList.add(x); 1325 } 1326 1327 } else { 1328 value1 = session.convertToMemberType(bean, value1, rawTypeMeta); 1329 invokeSetter(bean, pName, value1); 1330 } 1331 1332 return r; 1333 1334 } catch (BeanRuntimeException e) { 1335 throw e; 1336 } catch (Exception e1) { 1337 if (config.isIgnoreInvocationExceptionsOnSetters()) { 1338 if (rawTypeMeta.isPrimitive()) 1339 return rawTypeMeta.getPrimitiveDefault(); 1340 return null; 1341 } 1342 throw brex(e1, beanMeta.getBeanInfo(), "Error occurred trying to set property '%s'", name); 1343 } 1344 } 1345 1346 protected FluentMap<String,Object> properties() { 1347 // @formatter:off 1348 return filteredBeanPropertyMap() 1349 .a("field", field) 1350 .a("getter", getter) 1351 .a("name", name) 1352 .a("setter", setter) 1353 .a("type", cn(rawTypeMeta)); 1354 // @formatter:on 1355 } 1356 1357 @Override /* Overridden from Object */ 1358 public String toString() { 1359 return r(properties()); 1360 } 1361 1362 private Object getInner(BeanMap<?> m, String pName) { 1363 try { 1364 1365 if (writeOnly) 1366 return null; 1367 1368 if (nn(overrideValue)) 1369 return overrideValue; 1370 1371 // Read-only beans have their properties stored in a cache until getBean() is called. 1372 var bean = m.bean; 1373 if (bean == null) 1374 return m.propertyCache.get(name); 1375 1376 var session = m.getBeanSession(); 1377 var o = getRaw(m, pName); 1378 1379 return readTransform.apply(session, o); 1380 1381 } catch (Exception e) { 1382 if (config.isIgnoreInvocationExceptionsOnGetters()) { 1383 if (nn(rawTypeMeta) && rawTypeMeta.isPrimitive()) 1384 return rawTypeMeta.getPrimitiveDefault(); 1385 return null; 1386 } 1387 throw brex(e, beanMeta.getClassInfo(), "Exception occurred while getting property '%s'", name); 1388 } 1389 } 1390 1391 private Object invokeGetter(Object bean, String pName) throws IllegalArgumentException { 1392 if (isDyna) { 1393 Map m = null; 1394 if (nn(getter)) { 1395 if (! isDynaGetterMap) 1396 return getter.invoke(bean, pName); 1397 m = (Map)getter.invoke(bean); 1398 } else if (nn(field)) 1399 m = (Map)field.get(bean); 1400 else 1401 throw brex(beanMeta.getClassInfo(), MSG_getterOrFieldNotDefined, name); 1402 return (m == null ? null : m.get(pName)); 1403 } 1404 if (nn(getter)) 1405 return getter.invoke(bean); 1406 if (nn(field)) 1407 return field.get(bean); 1408 throw brex(beanMeta.getClassInfo(), MSG_getterOrFieldNotDefined, name); 1409 } 1410 1411 private Object invokeSetter(Object bean, String pName, Object val) throws IllegalArgumentException { 1412 if (isDyna) { 1413 if (nn(setter)) 1414 return setter.invoke(bean, pName, val); 1415 Map m = null; 1416 if (nn(field)) 1417 m = (Map<String,Object>)field.get(bean); 1418 else if (nn(getter)) 1419 m = (Map<String,Object>)getter.invoke(bean); 1420 else 1421 throw brex(beanMeta.getClassInfo(), "Cannot set property '%s' of type '%s' to object of type '%s' because no setter is defined on this property, and the existing property value is null", 1422 name, classNameForError(), cn(val)); 1423 return (m == null ? null : m.put(pName, val)); 1424 } 1425 if (nn(setter)) 1426 return setter.invoke(bean, val); 1427 if (nn(field)) { 1428 field.set(bean, val); 1429 return null; 1430 } 1431 throw brex(beanMeta.getClassInfo(), "Cannot set property '%s' of type '%s' to object of type '%s' because no setter is defined on this property, and the existing property value is null", name, 1432 classNameForError(), cn(val)); 1433 } 1434 1435 private String classNameForError() { 1436 var cm = getBeanInfo(); 1437 if (nn(cm)) 1438 return cm.getName(); 1439 return beanMeta.getClassInfo().getName(); 1440 } 1441 1442 /** 1443 * Returns a concrete {@link Collection} instance suitable for populating an abstract collection-typed bean 1444 * property that has no setter and no {@code @BeanProp(type=...)} override. 1445 * 1446 * <p> 1447 * Resolution is <b>best-effort-then-fallback</b>: 1448 * <ol> 1449 * <li><b>Best-effort materialization of the declared type.</b> First attempts to instantiate 1450 * {@code propertyClass} directly via {@link BeanInstantiator} (preferring a zero-arg constructor). This 1451 * succeeds when the declared type is itself instantiable — e.g. a concrete {@link AbstractSet} / 1452 * {@link AbstractList} subclass with an accessible no-arg constructor, or any type 1453 * {@code BeanInstantiator} can otherwise construct. The attempt never throws; on any failure it silently 1454 * degrades to the shape-based fallback below. 1455 * <li><b>Shape-based fallback.</b> When the declared type is genuinely not instantiable (a {@link Set} / 1456 * {@link List} / {@link Queue} interface, a truly abstract class, or a class with no usable constructor), 1457 * a default concrete type is chosen by collection shape: 1458 * <ul> 1459 * <li>{@link SortedSet} / {@link NavigableSet} → {@link TreeSet} 1460 * <li>{@link Set} → {@link LinkedHashSet} (preserves insertion order) 1461 * <li>{@link Deque} / {@link Queue} → {@link ArrayDeque} 1462 * <li>{@link List} / raw {@link Collection} (and any other collection shape) → {@link ArrayList} 1463 * </ul> 1464 * </ol> 1465 * 1466 * <p> 1467 * The choice between the two is made once per property and memoized; each call to the returned supplier 1468 * creates a new, empty, concrete {@link Collection} instance (never <jk>null</jk>). 1469 * 1470 * @param propertyClass The declared (abstract or interface) collection type of the property. 1471 * @return The memoized factory for new collections of this property. 1472 */ 1473 private Supplier<Collection<?>> abstractCollectionFactory(Class<?> propertyClass) { 1474 var f = abstractCollectionFactory.get(); 1475 if (f == null) { 1476 // Best-effort: try to materialize the declared type itself (handles concrete AbstractSet/AbstractList 1477 // subclasses, registered defaults, etc.). Never throws — degrades to the shape-based fallback on failure. 1478 Supplier<Collection<?>> declared = () -> BeanInstantiator.of(Collection.class).type(info(propertyClass)).preferZeroArgConstructor().fallback(() -> null).run(); 1479 if (nn(declared.get())) 1480 f = declared; 1481 else 1482 f = shapeDefaultCollectionFactory(propertyClass); 1483 abstractCollectionFactory.set(f); 1484 } 1485 return f; 1486 } 1487 1488 private static Supplier<Collection<?>> shapeDefaultCollectionFactory(Class<?> propertyClass) { 1489 // Shape-based fallback for genuinely non-instantiable declared types. 1490 if (SortedSet.class.isAssignableFrom(propertyClass) || NavigableSet.class.isAssignableFrom(propertyClass)) 1491 return TreeSet::new; 1492 if (Set.class.isAssignableFrom(propertyClass)) 1493 return LinkedHashSet::new; 1494 if (Deque.class.isAssignableFrom(propertyClass) || Queue.class.isAssignableFrom(propertyClass)) 1495 return ArrayDeque::new; 1496 return ArrayList::new; // List or raw Collection (and any other collection shape). 1497 } 1498 1499 /** 1500 * Returns the memoized factory used to create a new collection for a concrete (instantiable) property type. 1501 * 1502 * <p> 1503 * Resolved once per property: a public no-arg constructor is cached when the type has one, otherwise each 1504 * call goes through the full {@link BeanInstantiator} pipeline. 1505 */ 1506 private Supplier<Collection<?>> concreteCollectionFactory() { 1507 var f = concreteCollectionFactory.get(); 1508 if (f == null) { 1509 var ctor = publicNoArgConstructor(rawTypeMeta); 1510 if (nn(ctor)) 1511 f = ctor::newInstance; 1512 else 1513 f = () -> BeanInstantiator.of(Collection.class).type(rawTypeMeta).preferZeroArgConstructor().run(); 1514 concreteCollectionFactory.set(f); 1515 } 1516 return f; 1517 } 1518 1519 /** 1520 * Same as {@link #concreteCollectionFactory()} but for map-typed properties. 1521 */ 1522 private Supplier<Map<?,?>> mapFactory() { 1523 var f = mapFactory.get(); 1524 if (f == null) { 1525 var ctor = publicNoArgConstructor(rawTypeMeta); 1526 if (nn(ctor)) 1527 f = ctor::newInstance; 1528 else 1529 f = () -> BeanInstantiator.of(Map.class).type(rawTypeMeta).preferZeroArgConstructor().run(); 1530 mapFactory.set(f); 1531 } 1532 return f; 1533 } 1534 1535 private static ConstructorInfo publicNoArgConstructor(ClassInfo ci) { 1536 if (ci.isAbstract() || ci.isInterface()) 1537 return null; 1538 return ci.getPublicConstructors().stream() 1539 .filter(x -> x.isAll(ElementFlag.NOT_DEPRECATED)) 1540 .filter(x -> x.isDeclaringClass(ci)) 1541 .filter(x -> x.getParameterCount() == 0) 1542 .findFirst() 1543 .map(ConstructorInfo::accessible) 1544 .orElse(null); 1545 } 1546 1547 /** 1548 * Returns <jk>true</jk> if this property is write-only. 1549 * 1550 * <p> 1551 * This implies the property MIGHT be readable, but that serializers should not serialize it. 1552 * 1553 * @return <jk>true</jk> if this property is write-only. 1554 */ 1555 public boolean isWriteOnly() { return writeOnly; } 1556 1557 /** 1558 * Sets an array field on this bean. 1559 * 1560 * <p> 1561 * Works on both <c>Object</c> and primitive arrays. 1562 * 1563 * <p> 1564 * <b>Marshalling-only path.</b> Requires a property built via the marshalling-side construction path 1565 * (i.e. with a non-null {@link #getBeanInfo() rawTypeMeta}). When the owning {@link BeanMeta} was built via 1566 * {@link BeanMeta#of(Class, BeanConfigContext)}, this method throws 1567 * {@link UnsupportedOperationException} because the array element type is only known via {@code rawTypeMeta}, 1568 * which is not populated in the bean-modeling-only path. 1569 * 1570 * @param bean The bean of the field. 1571 * @param l The collection to use to set the array field. 1572 * @throws IllegalArgumentException Thrown by method invocation. 1573 * @throws IllegalAccessException Thrown by method invocation. 1574 * @throws InvocationTargetException Thrown by method invocation. 1575 * @throws UnsupportedOperationException If this property was built via the bean-modeling-only path 1576 * ({@link BeanMeta#of(Class, BeanConfigContext)}). 1577 */ 1578 protected void setArray(Object bean, List l) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { 1579 if (rawTypeMeta == null) 1580 throw uoex("Property '%s' was built via the bean-modeling-only path; setArray requires a marshalling context.", name); 1581 var array = toArray(l, this.rawTypeMeta.getElementType().inner()); 1582 invokeSetter(bean, name, array); 1583 } 1584}