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} &rarr; {@link TreeSet}
1460    *          <li>{@link Set} &rarr; {@link LinkedHashSet} (preserves insertion order)
1461    *          <li>{@link Deque} / {@link Queue} &rarr; {@link ArrayDeque}
1462    *          <li>{@link List} / raw {@link Collection} (and any other collection shape) &rarr; {@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}