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.bean.BeanMeta.MethodType.*; 021import static org.apache.juneau.commons.function.Suppliers.*; 022import static org.apache.juneau.commons.reflect.AnnotationTraversal.*; 023import static org.apache.juneau.commons.reflect.ReflectionUtils.*; 024import static org.apache.juneau.commons.utils.CollectionUtils.*; 025import static org.apache.juneau.commons.utils.Shorts.*; 026import static org.apache.juneau.commons.utils.Shorts.neq; 027import static org.apache.juneau.commons.utils.Shorts.eq; 028import static org.apache.juneau.commons.utils.StringUtils.*; 029import static org.apache.juneau.commons.utils.ThrowableUtils.*; 030 031import java.beans.*; 032import java.io.*; 033import java.lang.reflect.*; 034import java.util.*; 035import java.util.function.*; 036 037import org.apache.juneau.commons.collections.*; 038import org.apache.juneau.commons.function.*; 039import org.apache.juneau.commons.inject.*; 040import org.apache.juneau.commons.lang.*; 041import org.apache.juneau.commons.reflect.*; 042import org.apache.juneau.commons.reflect.Visibility; 043import org.apache.juneau.commons.utils.*; 044 045/** 046 * Encapsulates all access to the properties of a bean class (like a souped-up {@link BeanInfo}). 047 * 048 * <h5 class='topic'>Description</h5> 049 * 050 * Uses introspection to find all the properties associated with this class. If the 051 * {@link BeanType @BeanType} annotation is present on the class (or the marshalling-side 052 * {@code @Marshalled} sibling), that information is used to determine the properties on the class. 053 * Otherwise, the {@code BeanInfo} functionality in Java is used to determine the properties on the class. 054 * 055 * <h5 class='topic'>Bean property ordering</h5> 056 * 057 * The order of the properties is as follows: 058 * <ul class='spaced-list'> 059 * <li> 060 * If {@link BeanType#properties() @BeanType(properties)} is specified on the class, the order matches the 061 * list of properties in the annotation. 062 * <li> 063 * Otherwise, by default, properties are sorted alphabetically. 064 * <li> 065 * When unsorted properties are enabled ({@link BeanConfigContext#isUnsortedProperties()}, 066 * {@link BeanType#unsorted() @BeanType(unsorted=true)}, or a bean filter), properties use declaration order: 067 * <ul> 068 * <li>Records: record component order. 069 * <li>Other classes: field declaration order, superclass fields first. Getter/setter properties are ranked 070 * by their same-named field (even a private one), which is used as an ordering hint only. 071 * <li>Method-only properties (no backing field) follow, alphabetically. 072 * </ul> 073 * <li> 074 * {@link BeanConfigContext#isRecordComponentOrder()} gives records component order even when other beans are 075 * sorted. 076 * </ul> 077 * 078 * <p> 079 * Field order comes from {@link Class#getDeclaredFields()}, which is declaration order on HotSpot and OpenJ9 but is 080 * not guaranteed by the JLS. 081 * 082 * <h5 class='topic'>Thread safety</h5> 083 * 084 * Instances are effectively immutable after construction and are safe for concurrent read access. 085 * Construction is not thread-safe and should be completed before sharing an instance across threads. 086 * 087 * 088 * @param <T> The class type that this metadata applies to. 089 */ 090@SuppressWarnings({ 091 "java:S107", // The private constructor and validateAndRegisterProperty() thread the fixed introspection context (8 parameters each); a holder object would only add indirection 092 "java:S135", // The property-discovery loops in findBeanMethods() and the constructor use independent continue guard clauses to skip ineligible members 093 "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants 094 "java:S1200", // Central bean-introspection type; high coupling to annotations/reflect/utils is inherent to its role 095 "java:S3776", // findBeanMethods() and the BeanMeta constructor apply the bean getter/setter/field discovery rules in a single pass; splitting them would scatter those rules 096 "java:S6539", // Monster class; BeanMeta is intentionally a single cohesive bean-introspection metadata cache 097 "javabugs:S2259", // The annotation lookups are guarded by isType()/hasConstructor() checks that flow analysis does not track. 098 "rawtypes", // Raw Class is used in the BeanFactory instantiation ((Class)fc) and in Class[]::new for record constructor lookup 099 "unchecked" // Casts to T/BeanFactory in the factory-create, Proxy.newProxyInstance and BeanInstantiator paths are guaranteed by the bean class being instantiated 100}) 101public class BeanMeta<T> { 102 103 /** 104 * Represents the result of creating a BeanMeta, including the bean metadata and any reason why it's not a bean. 105 * 106 * @param <T> The bean type. 107 * @param beanMeta The bean metadata, or <jk>null</jk> if the class is not a bean. 108 * @param notABeanReason The reason why the class is not a bean, or <jk>null</jk> if it is a bean. 109 */ 110 public record BeanMetaValue<T>(BeanMeta<T> beanMeta, String notABeanReason) { 111 public Optional<BeanMeta<T>> optBeanMeta() { return o(beanMeta()); } 112 Optional<String> optNotABeanReason() { return o(notABeanReason()); } 113 } 114 115 /** 116 * Possible property method types. 117 */ 118 enum MethodType { 119 UNKNOWN, GETTER, SETTER, EXTRAKEYS; 120 } 121 122 /* 123 * Temporary getter/setter method struct used for calculating bean methods. 124 */ 125 private static class BeanMethod { 126 private final String propertyName; 127 private final MethodType methodType; 128 private final Method method; 129 private final ClassInfo type; 130 131 private BeanMethod(String propertyName, MethodType type, Method method) { 132 this.propertyName = propertyName; 133 this.methodType = type; 134 this.method = method; 135 this.type = info(type == SETTER ? method.getParameterTypes()[0] : method.getReturnType()); 136 } 137 138 @Override /* Overridden from Object */ 139 public String toString() { 140 return method.toString(); 141 } 142 143 /* 144 * Returns true if this method matches the class type of the specified property. 145 * Only meant to be used for setters. 146 */ 147 private boolean matchesPropertyType(BeanPropertyMeta.Builder b) { 148 if (b == null) 149 return false; 150 151 // Don't do further validation if this is the "*" bean property. 152 if (eq(b.name, "*")) 153 return true; 154 155 // Get the bean property type from the getter/field. 156 Class<?> pt = null; // Uses raw Class<?> here because assignment checks are performed against Class. 157 if (nn(b.getter)) 158 pt = b.getter.getReturnType().inner(); 159 else if (nn(b.field)) 160 pt = b.field.inner().getType(); 161 162 // Matches if only a setter is defined. 163 if (pt == null) 164 return true; 165 166 // Doesn't match if not same type or super type as getter/field. 167 if (! type.isAssignableFrom(pt)) 168 return false; 169 170 // If a setter was previously set, only use this setter if it's a closer 171 // match (e.g. prev type is a superclass of this type). 172 if (b.setter == null) 173 return true; 174 175 return type.isStrictChildOf(b.setter.getParameterTypes().get(0).inner()); 176 } 177 } 178 179 /** 180 * Represents a bean constructor with its associated property names. 181 * 182 * @param constructor The constructor information. 183 * @param propertyNames The list of property names that correspond to the constructor parameters. 184 */ 185 private record BeanConstructor(Optional<ConstructorInfo> constructor, List<String> args) {} 186 187 /** 188 * Creates a {@link BeanMeta} instance for the specified bean type info. 189 * 190 * <p> 191 * This is the marshalling-side factory entry point. It attempts to create bean metadata for a class; if the class is 192 * determined to be a bean, the returned {@link BeanMetaValue} carries the {@link BeanMeta} instance and a 193 * <jk>null</jk> reason. Otherwise it carries <jk>null</jk> for the bean metadata and a non-empty string explaining 194 * why the class is not a bean. 195 * 196 * <p> 197 * The bean filter is resolved internally from the {@code @Marshalled} / {@link BeanType @BeanType} annotations on the 198 * class via {@link BeanMetaInitializer#buildBeanFilter(BeanInfo)}. See {@link #of(Class, BeanConfigContext)} for 199 * the pure bean-modeling-side entry point that does not require a {@link BeanInfo}. 200 * 201 * <h5 class='section'>Exception handling:</h5> 202 * <p> 203 * If a {@link RuntimeException} is thrown during bean metadata creation, it is caught and the exception message 204 * is returned as the {@code notABeanReason} with <jk>null</jk> for the bean metadata. 205 * 206 * <h5 class='section'>Example:</h5> 207 * <p class='bjava'> 208 * <jc>// Create bean metadata for a class via the marshalling-side context.</jc> 209 * BeanInfo<Person> <jv>cm</jv> = <jv>marshallingContext</jv>.getClassMeta(Person.<jk>class</jk>); 210 * BeanMetaHolder<Person> <jv>result</jv> = BeanMeta.<jsm>create</jsm>(<jv>cm</jv>, <jk>null</jk>); 211 * 212 * <jc>// Check if it's a bean</jc> 213 * <jk>if</jk> (<jv>result</jv>.beanMeta() != <jk>null</jk>) { 214 * BeanMeta<Person> <jv>bm</jv> = <jv>result</jv>.beanMeta(); 215 * <jc>// Use the bean metadata...</jc> 216 * } <jk>else</jk> { 217 * String <jv>reason</jv> = <jv>result</jv>.notABeanReason(); 218 * <jc>// Handle the case where it's not a bean...</jc> 219 * } 220 * </p> 221 * 222 * @param <T> The class type. 223 * @param cm The bean type info for the class to create bean metadata for. Must not be <jk>null</jk>. 224 * @param implClass Optional implementation class info to use when looking for a no-arg constructor. Can be <jk>null</jk> (the bean class's own no-arg constructor is used instead). 225 * @return A {@link BeanMetaValue} containing the bean metadata (if successful) or a reason why it's not a bean. 226 */ 227 public static <T> BeanMetaValue<T> create(BeanInfo<T> cm, ClassInfo implClass) { 228 return create(cm, implClass, null); 229 } 230 231 /** 232 * Same as {@link #create(BeanInfo, ClassInfo)} but allows an explicit {@link PropertyNamer} to override the one 233 * that would otherwise be derived from the bean filter or {@link BeanConfigContext#getPropertyNamer()}. 234 * 235 * <p> 236 * The returned {@link BeanMeta} is <b>not</b> cached anywhere — callers that need a namer-specific view (for 237 * example, {@code MarshallingSession#toBeanMap(Object, PropertyNamer)}) build one on demand so the shared, 238 * {@code Class}-keyed default {@link BeanMeta} cache is never polluted with alternate-namer property keys. 239 * 240 * @param <T> The class type. 241 * @param cm The bean type info for the class to create bean metadata for. Must not be <jk>null</jk>. 242 * @param implClass Optional implementation class info to use when looking for a no-arg constructor. Can be <jk>null</jk> (the bean class's own no-arg constructor is used instead). 243 * @param propertyNamer Optional property namer to use for deriving property names. If <jk>null</jk>, the namer is 244 * resolved the normal way (bean filter, then {@link BeanConfigContext#getPropertyNamer()}). 245 * @return A {@link BeanMetaValue} containing the bean metadata (if successful) or a reason why it's not a bean. 246 */ 247 public static <T> BeanMetaValue<T> create(BeanInfo<T> cm, ClassInfo implClass, PropertyNamer propertyNamer) { 248 try { 249 var cfg = cm.getBeanConfigContext(); 250 var ap = cfg.getAnnotationProvider(); 251 var bmi = cfg.getBeanMetaInitializer(); 252 253 // Sanity checks first. 254 if (cfg.isNotABean(cm)) 255 return notABean("Class matches exclude-class list"); 256 257 if (cfg.isBeansRequireSerializable() && ! cm.isAssignableTo(Serializable.class) && ! bmi.hasBeanRegistrationAnnotation(cfg, cm)) 258 return notABean("Class is not serializable"); 259 260 if (ap.has(BeanIgnore.class, cm)) 261 return notABean("Class is annotated with @BeanIgnore"); 262 263 if ((! cfg.getBeanClassVisibility().isVisible(cm.getModifiers()) || cm.isAnonymousClass()) && ! bmi.hasBeanRegistrationAnnotation(cfg, cm)) 264 return notABean("Class is not public"); 265 266 var bm = new BeanMeta<>(cm, bmi.buildBeanFilter(cm), null, implClass, propertyNamer); 267 268 if (nn(bm.notABeanReason)) 269 return notABean(bm.notABeanReason); 270 271 return new BeanMetaValue<>(bm, null); 272 } catch (RuntimeException e) { 273 return new BeanMetaValue<>(null, e.getMessage()); 274 } 275 } 276 277 /** 278 * Returns the property namer that should be used to derive property names for this bean. 279 * 280 * <p> 281 * Precedence: an explicit per-instance {@link #propertyNamerOverride} (used by the 282 * {@code toBeanMap(Object, PropertyNamer)} path) wins over everything; otherwise the bean filter's namer, then the 283 * {@link BeanConfigContext#getPropertyNamer() configured default}. Preserving this fallback order keeps the 284 * default-cache construction path behaviorally identical to before the override was introduced. 285 */ 286 private PropertyNamer effectivePropertyNamer() { 287 if (nn(propertyNamerOverride)) 288 return propertyNamerOverride; 289 return o(beanFilter).map(x -> x.getPropertyNamer()).orElse(config.getPropertyNamer()); 290 } 291 292 /** 293 * Extracts the property name from {@link BeanProp @BeanProp} or {@link Name @Name} annotations. 294 * 295 * <p> 296 * If {@link Name @Name} annotations are present, returns the value from the last one. 297 * Otherwise, searches through {@link BeanProp @BeanProp} annotations and returns the first non-empty 298 * {@link BeanProp#value() value()} or {@link BeanProp#name() name()} found. 299 * 300 * @param p List of {@link BeanProp @BeanProp} annotations. 301 * @param n List of {@link Name @Name} annotations. 302 * @return The property name, or <jk>null</jk> if no name is found. 303 */ 304 private static String bpName(List<BeanProp> p, List<Name> n) { 305 if (p.isEmpty() && n.isEmpty()) 306 return null; 307 if (! n.isEmpty()) 308 return last(n).value(); 309 310 var name = Holder.of(p.isEmpty() ? null : ""); 311 p.forEach(x -> { 312 if (! x.value().isEmpty()) 313 name.set(x.value()); 314 if (! x.name().isEmpty()) 315 name.set(x.name()); 316 }); 317 318 return name.orElse(null); 319 } 320 321 /** 322 * Extracts the property name from a single {@link BeanProp @BeanProp} or {@link Name @Name} annotation. 323 * 324 * <p> 325 * For {@link BeanProp @BeanProp} annotations, returns the first non-empty value found in the following order: 326 * <ol> 327 * <li>{@link BeanProp#name() name()} 328 * <li>{@link BeanProp#value() value()} 329 * </ol> 330 * 331 * <p> 332 * For {@link Name @Name} annotations, returns the {@link Name#value() value()}. 333 * 334 * <p> 335 * This method is used to extract property names from individual annotations, typically when processing 336 * annotation lists in stream operations. 337 * 338 * @param ai The annotation info containing either a {@link BeanProp @BeanProp} or {@link Name @Name} annotation. 339 * @return The property name extracted from the annotation, or <jk>null</jk> if no name is found. 340 * @see #bpName(List, List) 341 */ 342 private static String name(AnnotationInfo<?> ai) { 343 if (ai.isType(BeanProp.class)) { 344 var p = ai.cast(BeanProp.class).inner(); 345 if (ine(p.name())) 346 return p.name(); 347 if (ine(p.value())) 348 return p.value(); 349 } else { 350 var n = ai.cast(Name.class).inner(); 351 if (ine(n.value())) 352 return n.value(); 353 } 354 return null; 355 } 356 357 /* 358 * Shortcut for creating a BeanMetaValue with a not-a-bean reason. 359 */ 360 private static <T> BeanMetaValue<T> notABean(String reason) { 361 return new BeanMetaValue<>(null, reason); 362 } 363 364 private BeanConstructor beanConstructor; // The constructor for this bean. 365 private final Object marshallingContext; // MarshallingContext, but Object-typed so the field can live in commons.bean. Cast to MarshallingContext at marshalling-side use sites. Null when constructed via {@link #of(Class, BeanConfigContext)}. 366 private final BeanConfigContext config; // Bean-modeling settings facade — always non-null. Sources: marshallingContext.getBeanConfigContext() (marshalling-side) or the explicit BeanConfigContext (commons-side). 367 private final BeanFilter beanFilter; // Optional bean filter associated with the target class. Typed as the bean-modeling-side SPI seam; marshalling-side callers narrow back to MarshalledFilter (the only concrete in-tree implementation). 368 private final NullableSupplier<InvocationHandler> beanProxyInvocationHandler; // The invocation handler for this bean (if it's an interface). 369 private final Supplier<BeanRegistryLookup> beanRegistry; // The bean registry for this bean. Exposed through the BeanRegistryLookup SPI seam. 370 private final Supplier<List<ClassInfo>> classHierarchy; // List of all classes traversed in the class hierarchy. 371 private final BeanInfo<T> classMeta; // The target class type that this meta object describes. Null when constructed via {@link #of(Class, BeanConfigContext)}. Typed against the bean-modeling SPI seam. 372 private final ClassInfo classInfo; // Pure-reflection view of the bean class that decouples bean modeling from ClassMeta. Always non-null. 373 private final Supplier<String> dictionaryName; // The @Marshalled(typeName) annotation defined on this bean class. 374 private final BeanPropertyMeta dynaProperty; // "extras" property. 375 private final Class<? extends BeanFactory> factoryClass; // @BeanType(factory=X.class) — null means no factory. 376 private final boolean fluentSetters; // Whether fluent setters are enabled. 377 private final Map<Method,String> getterProps; // The getter properties on the target class. 378 private final Map<String,BeanPropertyMeta> hiddenProperties; // The hidden properties on the target class. 379 private final ConstructorInfo implClassConstructor; // Optional constructor to use if one cannot be found. 380 final String notABeanReason; // Readable string explaining why this class wasn't a bean. 381 private final BeanPropertyMeta[] propertyArray; // The properties as an array, for allocation-free iteration. 382 private final Map<String,BeanPropertyMeta> properties; // The properties on the target class. 383 private final Map<Method,String> setterProps; // The setter properties on the target class. 384 private final List<String> requiredPropertyNames; // Names of @BeanProp(required=true) properties, in property order. 385 private final boolean unsortedProperties; // Whether properties use declaration order instead of alphabetical order. 386 private final PropertyNamer propertyNamerOverride; // Optional per-instance namer override. Null means resolve the normal way (bean filter, then config). Non-null takes precedence over both. 387 private final ClassInfo stopClass; // The stop class for hierarchy traversal. 388 private final BeanPropertyMeta typeProperty; // "_type" mock bean property. 389 private final String typePropertyName; // "_type" property actual name. 390 private final Map<BeanPropertyMeta,BeanRegistryLookup> propertyBeanRegistries; // Per-property bean-registry side-map that keeps registry implementation details off BeanPropertyMeta itself. Typed against the commons.bean SPI seam. 391 392 /** 393 * Creates a {@link BeanMeta} for the specified class using the supplied {@link BeanConfigContext}. 394 * 395 * <p> 396 * This is the bean-modeling entry point — it constructs a {@link BeanMeta} purely from a {@link Class} 397 * and a {@link BeanConfigContext}, without touching any marshalling-side type infrastructure. 398 * The returned {@link BeanMeta} carries enough information to do raw getter/setter invocation and property 399 * iteration; marshalling-aware reads ({@link #getBeanInfo()}, type-resolution on each 400 * {@link BeanPropertyMeta#getBeanInfo()}, the per-property registry lookup) remain <jk>null</jk>. 401 * 402 * <h5 class='section'>Example:</h5> 403 * <p class='bjava'> 404 * BeanMeta<Person> <jv>bm</jv> = BeanMeta.<jsm>of</jsm>(Person.<jk>class</jk>, BeanConfigContext.<jsf>DEFAULT</jsf>); 405 * BeanPropertyMeta <jv>name</jv> = <jv>bm</jv>.getProperties().get(<js>"name"</js>); 406 * </p> 407 * 408 * @param <T> The bean class type. 409 * @param beanClass The class to build a {@link BeanMeta} for. Must not be <jk>null</jk>. 410 * @param config The bean-modeling configuration to apply. Must not be <jk>null</jk>. 411 * @return A new {@link BeanMeta} for the specified class. 412 */ 413 public static <T> BeanMeta<T> of(Class<T> beanClass, BeanConfigContext config) { 414 return new BeanMeta<>(beanClass, config); 415 } 416 417 /** 418 * Same as {@link #of(Class, BeanConfigContext)} using {@link BeanConfigContext#DEFAULT}. 419 * 420 * @param <T> The bean class type. 421 * @param beanClass The class to build a {@link BeanMeta} for. Must not be <jk>null</jk>. 422 * @return A new {@link BeanMeta} for the specified class. 423 */ 424 public static <T> BeanMeta<T> of(Class<T> beanClass) { 425 return of(beanClass, BeanConfigContext.DEFAULT); 426 } 427 428 /** 429 * Constructor. 430 * 431 * <p> 432 * Creates a new {@link BeanMeta} instance for the specified class metadata. This constructor performs 433 * introspection to discover all bean properties, methods, and fields in the class hierarchy. 434 * 435 * <p> 436 * The bean metadata is built by: 437 * <ul> 438 * <li>Finding all bean fields in the class hierarchy 439 * <li>Finding all bean methods (getters, setters, extraKeys) in the class hierarchy 440 * <li>Determining the appropriate constructor for bean instantiation 441 * <li>Building the class hierarchy for property discovery 442 * <li>Creating the bean registry for dictionary name resolution 443 * <li>Validating and filtering properties based on bean filter settings 444 * </ul> 445 * 446 * @param cm The class metadata for the bean class. Must not be <jk>null</jk>. 447 * @param bf Optional bean filter to apply. Can be <jk>null</jk> (no bean filter customizations are applied; the default stop class of {@code Object} is used). 448 * @param pNames Explicit list of property names and order. If <jk>null</jk>, properties are determined automatically. 449 * @param implClass Optional implementation class constructor to use if one cannot be found. Can be <jk>null</jk> (the bean class's own no-arg constructor is used instead). 450 */ 451 protected BeanMeta(BeanInfo<T> cm, BeanFilter bf, String[] pNames, ClassInfo implClass) { 452 this(cm, cm, cm.getBeanConfigContext(), cm.getMarshallingContext(), bf, pNames, implClass, null); 453 } 454 455 /** 456 * Same as {@link #BeanMeta(BeanInfo, BeanFilter, String[], ClassInfo)} but allows an explicit 457 * {@link PropertyNamer} override. 458 * 459 * @param cm The class metadata for the bean class. Must not be <jk>null</jk>. 460 * @param bf Optional bean filter to apply. Can be <jk>null</jk> (no bean filter customizations are applied; the default stop class of {@code Object} is used). 461 * @param pNames Explicit list of property names and order. If <jk>null</jk>, properties are determined automatically. 462 * @param implClass Optional implementation class constructor to use if one cannot be found. Can be <jk>null</jk> (the bean class's own no-arg constructor is used instead). 463 * @param propertyNamerOverride Optional namer that, when non-<jk>null</jk>, takes precedence over the bean filter 464 * and {@link BeanConfigContext#getPropertyNamer()} for deriving property names. 465 */ 466 protected BeanMeta(BeanInfo<T> cm, BeanFilter bf, String[] pNames, ClassInfo implClass, PropertyNamer propertyNamerOverride) { 467 this(cm, cm, cm.getBeanConfigContext(), cm.getMarshallingContext(), bf, pNames, implClass, propertyNamerOverride); 468 } 469 470 /** 471 * Bean-modeling constructor — builds a {@link BeanMeta} without marshalling context state. 472 * 473 * <p> 474 * See {@link #of(Class, BeanConfigContext)} for the public entry point. The {@link #getBeanInfo()}, 475 * {@link #getBeanTypeResolver() bean type resolver}, and per-property registry lookups are 476 * left <jk>null</jk>; the per-property {@link BeanPropertyMeta#getBeanInfo() rawTypeMeta}/{@code typeMeta} 477 * fields are also <jk>null</jk> (no type-resolution is performed in this path). 478 * 479 * @param beanClass The bean class. Must not be <jk>null</jk>. 480 * @param config The bean-modeling configuration. Must not be <jk>null</jk>. 481 */ 482 protected BeanMeta(Class<T> beanClass, BeanConfigContext config) { 483 this(null, info(reqnn("beanClass", beanClass)), reqnn("config", config), null, null, null, null, null); 484 } 485 486 private BeanMeta(BeanInfo<T> cm, ClassInfo ci0, BeanConfigContext config, Object mc, BeanFilter bf, String[] pNames, ClassInfo implClass, PropertyNamer propertyNamerOverride) { 487 classMeta = cm; 488 classInfo = ci0; 489 this.config = config; 490 marshallingContext = mc; 491 beanFilter = bf; 492 this.propertyNamerOverride = propertyNamerOverride; 493 implClassConstructor = o(implClass).map(x -> x.getPublicConstructor(x2 -> x2.hasNumParameters(0)).orElse(null)).orElse(null); 494 fluentSetters = config.isFindFluentSetters() || (nn(bf) && bf.isFluentSetters()); 495 stopClass = o(bf).map(x -> x.getStopClass()).orElse(info(Object.class)); 496 beanRegistry = memoize(this::findBeanRegistry); 497 classHierarchy = memoize(this::findClassHierarchy); 498 beanConstructor = findBeanConstructor(); 499 500 // Local variables for initialization 501 var ap = config.getAnnotationProvider(); 502 var c = classInfo.inner(); 503 var ci = classInfo; 504 String notABeanReasonTemp = null; 505 var propertiesValue = Holder.<Map<String,BeanPropertyMeta>>empty(); 506 var hiddenPropertiesMap = CollectionUtils.<String,BeanPropertyMeta>map(); 507 var getterPropsMap = CollectionUtils.<Method,String>map(); // Convert to MethodInfo keys 508 var setterPropsMap = CollectionUtils.<Method,String>map(); 509 var dynaPropertyValue = Holder.<BeanPropertyMeta>empty(); 510 var propertyBeanRegistriesTemp = CollectionUtils.<BeanPropertyMeta,BeanRegistryLookup>map(); // Per-property BeanRegistry side-map. 511 var unsortedPropertiesTemp = false; 512 var btList = ap.find(BeanType.class, classInfo); 513 var propertyNamer = effectivePropertyNamer(); 514 515 // resolveTypePropertyName may return null on the commons-side NOOP path; fall back to the configured default. 516 var resolvedTypePropertyName = config.getBeanMetaInitializer().resolveTypePropertyName(config, classInfo); 517 this.typePropertyName = nn(resolvedTypePropertyName) ? resolvedTypePropertyName : config.getBeanTypePropertyName(); 518 519 // Check if constructor is required but not found (records always resolve their canonical constructor above, so they never reach this) 520 if (! beanConstructor.constructor().isPresent() && bf == null && config.isBeansRequireDefaultConstructor()) 521 notABeanReasonTemp = "Class does not have the required no-arg constructor"; 522 523 var bfo = o(bf); 524 var fixedBeanProps = bfo.map(x -> x.getProperties()).orElse(emptySet()); 525 526 try { 527 Map<String,BeanPropertyMeta.Builder> normalProps = map(); // NOAI 528 529 // First populate the properties with those specified in the bean annotation to 530 // ensure that ordering first. 531 fixedBeanProps.forEach(x -> normalProps.put(x, BeanPropertyMeta.builder(this, x))); 532 533 if (config.isUseJavaBeanIntrospector()) { 534 var c2 = bfo.map(x -> x.getInterfaceClass()).filter(Objects::nonNull).orElse(classInfo); 535 java.beans.BeanInfo bi = null; 536 if (! c2.isInterface()) 537 bi = Introspector.getBeanInfo(c2.inner(), stopClass.inner()); 538 else 539 bi = Introspector.getBeanInfo(c2.inner(), null); 540 if (nn(bi)) 541 mergeJavaBeanPropertyDescriptorsIntoNormalProps(bi, normalProps, propertyNamer); 542 543 } else /* Use 'better' introspection */ { 544 545 findBeanFields().forEach(x -> { 546 var name = ap.find(x).stream() 547 .filter(x2 -> x2.isType(BeanProp.class) || x2.isType(Name.class)) 548 .map(BeanMeta::name) 549 .filter(Objects::nonNull) 550 .findFirst() 551 .orElse(propertyNamer.getPropertyName(x.getName())); 552 name = resolveBeanFieldPropertyName(x, name, propertyNamer); 553 if (nn(name)) { 554 normalProps.computeIfAbsent(name, n->BeanPropertyMeta.builder(this, n)).setField(x); 555 } 556 }); 557 558 var bms = findBeanMethods(); 559 560 // Iterate through all the getters. 561 bms.forEach(x -> { 562 var pn = x.propertyName; 563 var m = x.method; // Method is read first for name/annotation precedence checks, then wrapped as MethodInfo. 564 var mi = info(m); 565 var bpm = normalProps.computeIfAbsent(pn, k -> new BeanPropertyMeta.Builder(this, k)); 566 567 if (x.methodType == GETTER) { 568 // Two getters. Pick the best. 569 if (nn(bpm.getter) 570 && ((! ap.has(BeanProp.class, mi) && ap.has(BeanProp.class, bpm.getter)) 571 || (m.getName().startsWith("is") && bpm.getter.getNameSimple().startsWith("get")))) { 572 // @BeanProp on existing getter takes precedence; else getX() overrides isX(). 573 m = bpm.getter.inner(); 574 } 575 bpm.setGetter(info(m)); 576 } 577 }); 578 579 // Now iterate through all the setters. 580 bms.stream().filter(x -> eq(x.methodType, SETTER)).forEach(x -> { 581 var bpm = normalProps.get(x.propertyName); 582 if (x.matchesPropertyType(bpm)) 583 bpm.setSetter(info(x.method)); 584 }); 585 586 // Now iterate through all the extraKeys. 587 bms.stream().filter(x -> eq(x.methodType, EXTRAKEYS)).forEach(x -> normalProps.get(x.propertyName).setExtraKeys(info(x.method))); 588 } 589 590 var typeVarImpls = TypeVariables.of(c); 591 592 // Eliminate invalid properties, and set the contents of getterProps and setterProps. 593 var readOnlyProps = bfo.map(x -> x.getReadOnlyProperties()).orElse(emptySet()); 594 var writeOnlyProps = bfo.map(x -> x.getWriteOnlyProperties()).orElse(emptySet()); 595 for (var i = normalProps.values().iterator(); i.hasNext();) { 596 var p = i.next(); 597 validateAndRegisterProperty(p, c, typeVarImpls, readOnlyProps, writeOnlyProps, i, getterPropsMap, setterPropsMap); 598 } 599 600 // Check for missing properties. 601 fixedBeanProps.stream().filter(x -> ! normalProps.containsKey(x)).findFirst().ifPresent(x -> { throw brex(c, "The property '%s' was defined on the @BeanType(properties=X) annotation of class '%s' but was not found on the class definition.", x, ci.getNameSimple()); }); 602 603 // For records with renamed properties, remap constructor args to use the actual property names. 604 if (ci.isRecord() && ine(beanConstructor.args())) { 605 var components = ci.getRecordComponents(); 606 var remappedArgs = new ArrayList<String>(beanConstructor.args().size()); 607 for (int idx = 0; idx < beanConstructor.args().size(); idx++) { 608 var componentName = beanConstructor.args().get(idx); 609 if (normalProps.containsKey(componentName)) { 610 remappedArgs.add(componentName); 611 } else { 612 var rcName = idx < components.size() ? components.get(idx).getName() : componentName; 613 var found = normalProps.entrySet().stream() 614 .filter(e -> (e.getValue().field != null && e.getValue().field.hasName(rcName)) 615 || (e.getValue().getter != null && e.getValue().getter.hasName(rcName))) 616 .map(Map.Entry::getKey) 617 .findFirst() 618 .orElse(componentName); 619 remappedArgs.add(found); 620 } 621 } 622 beanConstructor = new BeanConstructor(beanConstructor.constructor(), remappedArgs); 623 } 624 625 // Mark constructor arg properties. 626 for (var fp : beanConstructor.args()) { 627 var m = normalProps.get(fp); 628 if (m == null) 629 throw brex(c, "The property '%s' was defined on the @BeanCtor(properties=X) annotation but was not found on the class definition.", fp); 630 m.setAsConstructorArg(); 631 } 632 633 // @BeanProp(required=true) must be satisfiable from input. 634 for (var p : normalProps.values()) 635 if (p.isRequired() && ! p.canSatisfyRequired()) 636 throw brex(c, "Property '%s' on class '%s' is marked @BeanProp(required=true) but cannot be written (read-only, dynamic, or no field, setter or constructor argument).", p.name, ci.getNameSimple()); 637 638 // Make sure at least one property was found (records with no components are exempt). 639 if (bf == null && config.isBeansRequireSomeProperties() && normalProps.isEmpty() && ! ci.isRecord()) 640 notABeanReasonTemp = "No properties detected on bean class"; 641 642 // Records opt into component order via recordComponentOrder(); that is unsorted mode scoped to records. 643 var recordComponentOrder = ci.isRecord() && config.isRecordComponentOrder(); 644 unsortedPropertiesTemp = recordComponentOrder || config.isUnsortedProperties() || bfo.map(x -> x.isUnsortedProperties()).orElse(false) || !fixedBeanProps.isEmpty(); 645 646 // Declaration-order seeding. Skipped when an explicit @BeanType(properties) list exists 647 // (that list keeps absolute precedence) and on the java.beans.Introspector path. 648 if (unsortedPropertiesTemp && fixedBeanProps.isEmpty() && ! config.isUseJavaBeanIntrospector()) 649 orderByDeclaration(normalProps); 650 651 propertiesValue.set(unsortedPropertiesTemp ? map() : sortedMap()); 652 653 normalProps.forEach((k, v) -> { 654 var pMeta = v.build(); 655 if (pMeta.isDyna()) 656 dynaPropertyValue.set(pMeta); 657 propertiesValue.get().put(k, pMeta); 658 // Build the property-level BeanRegistry side-map entry from the builder's accumulated 659 // dictionary classes. Parents to the bean-level registry so @MarshalledProp(dictionary={}) 660 // entries chain on top of bean and global dictionaries. Skipped on the commons-side path 661 // (no marshallingContext means no BeanRegistry construction). 662 if (nn(marshallingContext) && nn(v.dictionaryClasses)) 663 propertyBeanRegistriesTemp.put(pMeta, config.getBeanMetaInitializer().buildPropertyBeanRegistry(marshallingContext, beanRegistry.get(), v.dictionaryClasses)); 664 }); 665 666 // If a beanFilter is defined, look for inclusion and exclusion lists. 667 if (bf != null) { 668 669 // Eliminated excluded properties if MarshalledFilter.excludeKeys is specified. 670 var bfbpi = bf.getProperties(); 671 var bfbpx = bf.getExcludeProperties(); 672 var p = propertiesValue.get(); 673 674 if (! bfbpi.isEmpty()) { 675 // Only include specified properties if MarshalledFilter.includeKeys is specified. 676 // Note that the order must match includeKeys. 677 Map<String,BeanPropertyMeta> p2 = map(); // NOAI 678 bfbpi.stream().filter(p::containsKey).forEach(x -> p2.put(x, p.remove(x))); 679 hiddenPropertiesMap.putAll(p); 680 propertiesValue.set(p2); 681 } 682 683 bfbpx.forEach(x -> hiddenPropertiesMap.put(x, propertiesValue.get().remove(x))); 684 } 685 686 if (nn(pNames)) { 687 var p = propertiesValue.get(); 688 Map<String,BeanPropertyMeta> p2 = map(); 689 for (var k : pNames) { 690 if (p.containsKey(k)) 691 p2.put(k, p.get(k)); 692 else 693 hiddenPropertiesMap.put(k, p.get(k)); 694 } 695 propertiesValue.set(p2); 696 } 697 698 } catch (BeanRuntimeException e) { 699 throw e; 700 } catch (Exception e) { 701 notABeanReasonTemp = "Exception: " + getStackTrace(e); 702 } 703 704 notABeanReason = notABeanReasonTemp; 705 properties = u(propertiesValue.get()); 706 propertyArray = properties == null ? new BeanPropertyMeta[0] : properties.values().toArray(new BeanPropertyMeta[0]); 707 hiddenProperties = u(hiddenPropertiesMap); 708 getterProps = u(getterPropsMap); 709 setterProps = u(setterPropsMap); 710 dynaProperty = dynaPropertyValue.get(); 711 unsortedProperties = unsortedPropertiesTemp; 712 requiredPropertyNames = properties == null ? List.<String>of() : properties.values().stream().filter(BeanPropertyMeta::isRequired).map(BeanPropertyMeta::getName).toList(); 713 typeProperty = BeanPropertyMeta.builder(this, typePropertyName).canRead().canWrite().rawMetaType(String.class).build(); 714 // Map the synthetic "_type" property to the bean-level BeanRegistry so consumers calling 715 // typeProperty.getBeanRegistry() (currently none in-tree, but a public API path) get the same 716 // registry the property previously carried as a field. Skipped on the commons-side path. 717 if (nn(marshallingContext)) 718 propertyBeanRegistriesTemp.put(typeProperty, beanRegistry.get()); 719 propertyBeanRegistries = u(propertyBeanRegistriesTemp); 720 dictionaryName = memoize(this::findDictionaryName); 721 beanProxyInvocationHandler = memoize(() -> config.isUseInterfaceProxies() && classInfo.isInterface() ? new BeanProxyInvocationHandler<>(this) : null); 722 var factoryClassTemp = btList.stream().map(x -> x.inner().factory()).filter(x -> x != BeanFactory.Void.class).findFirst().orElse(null); 723 factoryClass = factoryClassTemp; 724 } 725 726 private void validateAndRegisterProperty(BeanPropertyMeta.Builder p, Class<?> c, TypeVariables typeVarImpls, Set<String> readOnlyProps, Set<String> writeOnlyProps, Iterator<BeanPropertyMeta.Builder> i, Map<Method,String> getterProps, Map<Method,String> setterProps) { 727 try { 728 if (p.field == null) 729 findInnerBeanField(p.name).ifPresent(p::setInnerField); 730 731 // When constructed via the commons-side path, marshallingContext is null and validate() runs in 732 // raw-reflection mode (no ClassMeta/ObjectSwap/BeanRegistry resolution). 733 if (p.validate((BeanTypeResolver) marshallingContext, typeVarImpls, readOnlyProps, writeOnlyProps)) { 734 735 // Marshalling-side post-processor — applies @MarshalledProp / @Swap annotation effects 736 // (swap detection, properties override, dictionary classes) and installs swap-aware 737 // read/write transforms. Skipped on the commons-side path: those annotations require a 738 // MarshallingContext to resolve ObjectSwap/StringFormatSwap/Surrogate types and the swap 739 // class meta. 740 if (nn(marshallingContext)) 741 config.getBeanPropertyPostProcessor().process(marshallingContext, p); 742 743 if (nn(p.getter)) 744 getterProps.put(p.getter.inner(), p.name); 745 746 if (nn(p.setter)) 747 setterProps.put(p.setter.inner(), p.name); 748 749 } else { 750 i.remove(); 751 } 752 } catch (Exception e) { 753 throw brex(c, localizedMessage(e)); 754 } 755 } 756 757 /** 758 * Re-keys the discovered properties into declaration order. 759 * 760 * <ul class='spaced-list'> 761 * <li>Records: record component order ({@link ClassInfo#getRecordComponents()}, defined by the JLS). A component 762 * is matched to its property through the property's field or accessor name, so renamed components keep 763 * their position. 764 * <li>Other classes: the position of the property's backing field (the visible field, or the same-named private 765 * field recorded as {@code innerField}) in a superclass-first walk of 766 * {@link ClassInfo#getDeclaredFieldsInDeclarationOrder()}. 767 * <li>Properties with no backing field or component (method-only) go after all ranked properties, alphabetically. 768 * </ul> 769 * 770 * <p> 771 * Visibility is unchanged: private fields only <i>rank</i> properties, they never become properties. 772 * 773 * @param props The discovered properties. Re-ordered in place. 774 */ 775 private void orderByDeclaration(Map<String,BeanPropertyMeta.Builder> props) { 776 var rank = new HashMap<String,Integer>(); 777 if (classInfo.isRecord()) { 778 var components = classInfo.getRecordComponents(); 779 for (var i = 0; i < components.size(); i++) { 780 var rcName = components.get(i).getName(); 781 for (var e : props.entrySet()) { 782 var p = e.getValue(); 783 if ((nn(p.field) && p.field.hasName(rcName)) || (nn(p.getter) && p.getter.hasName(rcName))) 784 rank.putIfAbsent(e.getKey(), i); 785 } 786 } 787 } else { 788 var fieldRank = new HashMap<Field,Integer>(); 789 for (var c2 : classHierarchy.get()) 790 for (var f : c2.getDeclaredFieldsInDeclarationOrder()) 791 fieldRank.putIfAbsent(f.inner(), fieldRank.size()); 792 props.forEach((k, p) -> { 793 var f = nn(p.field) ? p.field : p.innerField; 794 if (nn(f) && fieldRank.containsKey(f.inner())) 795 rank.put(k, fieldRank.get(f.inner())); 796 }); 797 } 798 var keys = new ArrayList<>(props.keySet()); 799 keys.sort(Comparator.<String>comparingInt(k -> rank.getOrDefault(k, Integer.MAX_VALUE)).thenComparing(Comparator.naturalOrder())); 800 var copy = new LinkedHashMap<>(props); 801 props.clear(); 802 keys.forEach(k -> props.put(k, copy.get(k))); 803 } 804 805 @Override /* Overridden from Object */ 806 public boolean equals(Object o) { 807 return (o instanceof BeanMeta<?> o2) && eq(this, o2, (x, y) -> eq(x.classInfo, y.classInfo)); 808 } 809 810 /** 811 * Returns the bean filter associated with this bean. 812 * 813 * <p> 814 * Bean filters are used to control aspects of how beans are handled during serialization and parsing, such as 815 * property inclusion/exclusion, property ordering, and type name mapping. 816 * 817 * <p> 818 * On the marshalling-side construction path, the bean filter is typically built from the {@code @Marshalled} and 819 * {@link BeanType @BeanType} annotations on the class. If neither annotation is present (or this {@link BeanMeta} 820 * was built via the commons-side path), this method returns <jk>null</jk>. 821 * 822 * @return The bean filter for this bean, or <jk>null</jk> if no bean filter is associated with this bean. 823 */ 824 public BeanFilter getBeanFilter() { 825 // The field is typed as the bean-modeling-side BeanFilter SPI; marshalling-side callers 826 // narrow back to MarshalledFilter (the only concrete implementation in-tree). 827 return beanFilter; 828 } 829 830 /** 831 * Returns the proxy invocation handler for this bean if it's an interface. 832 * 833 * @return The invocation handler, or <jk>null</jk> if this is not an interface or interface proxies are disabled. 834 */ 835 public InvocationHandler getBeanProxyInvocationHandler() { 836 return beanProxyInvocationHandler.get(); 837 } 838 839 /** 840 * Returns the bean registry for this bean. 841 * 842 * <p> 843 * The bean registry is used to resolve dictionary names to class types. It's created when a bean class has a 844 * {@code @Marshalled(dictionary)} annotation that specifies a list of possible subclasses. 845 * 846 * <p> 847 * Returns the bean-modeling-side SPI type ({@link BeanRegistryLookup}). 848 * 849 * @return The bean registry for this bean, or <jk>null</jk> if no bean registry is associated with it. 850 */ 851 public BeanRegistryLookup getBeanRegistry() { return beanRegistry.get(); } 852 853 /** 854 * Returns the per-property registry lookup associated with the given {@link BeanPropertyMeta}. 855 * 856 * <p> 857 * {@link BeanPropertyMeta} no longer carries a concrete registry field — the per-property 858 * registry now lives in a side-map on this {@link BeanMeta} keyed by the property meta itself. The serializer 859 * and parser sides still need to look up the property-level registry for polymorphic dispatch; they now route through 860 * this accessor (directly or via the deprecated-style {@link BeanPropertyMeta#getBeanRegistry()} delegate). 861 * 862 * <p> 863 * The returned registry chains the property's 864 * {@code @MarshalledProp(dictionary)} entries on top of 865 * the bean-level registry, which in turn chains on top of the global bean dictionary. 866 * 867 * @param p The bean property meta to look up. Can be a normal property, the synthetic <js>"_type"</js> property, 868 * or any other property meta produced by this bean. 869 * @return The bean registry for the specified property, or <jk>null</jk> if none was registered (e.g. the property 870 * belongs to a different bean meta). 871 */ 872 public BeanRegistryLookup getPropertyBeanRegistry(BeanPropertyMeta p) { 873 return propertyBeanRegistries.get(p); 874 } 875 876 /** 877 * Returns the {@link BeanInfo} of this bean. 878 * 879 * <p> 880 * Returns <jk>null</jk> when this {@link BeanMeta} was constructed via the commons-side path 881 * ({@link #of(Class, BeanConfigContext)}) — in that case {@link #getClassInfo()} carries the 882 * pure-reflection view of the bean class. 883 * 884 * <p> 885 * Returns the bean-modeling-side SPI type ({@link BeanInfo}). 886 * 887 * @return The {@link BeanInfo} of this bean, or <jk>null</jk> for bean-modeling-only construction. 888 */ 889 public BeanInfo<T> getBeanInfo() { return classMeta; } 890 891 /** 892 * Returns the {@link ClassInfo} of this bean. 893 * 894 * <p> 895 * Pure-reflection view of the bean class. Always non-null, regardless of construction path. Use this in 896 * preference to {@link #getBeanInfo()} for any bean-modeling read that does not depend on marshalling-aware 897 * type metadata; the commons-side construction path leaves {@link #getBeanInfo()} <jk>null</jk>. 898 * 899 * @return The class info for this bean. Never <jk>null</jk>. 900 */ 901 public ClassInfo getClassInfo() { return classInfo; } 902 903 /** 904 * Returns the bean-modeling configuration snapshot used to build this {@link BeanMeta}. 905 * 906 * <p> 907 * For marshalling-side construction, this is sourced from the owning marshalling context. 908 * For commons-side construction (via {@link #of(Class, BeanConfigContext)}), this is the {@link BeanConfigContext} 909 * passed to the factory. Always non-null. 910 * 911 * @return The bean-modeling configuration. Never <jk>null</jk>. 912 */ 913 public BeanConfigContext getConfig() { return config; } 914 915 /** 916 * Returns the dictionary name for this bean as defined through the {@code @Marshalled(typeName)} annotation. 917 * 918 * @return The dictionary name for this bean, or <jk>null</jk> if it has no dictionary name defined. 919 */ 920 public String getDictionaryName() { return dictionaryName.get(); } 921 922 /** 923 * Returns a map of all properties on this bean. 924 * 925 * <p> 926 * The map is keyed by property name and contains {@link BeanPropertyMeta} objects that provide metadata about each 927 * property, including its type, getter/setter methods, field information, and serialization/parsing behavior. 928 * 929 * <p> 930 * This map contains only the normal (non-hidden) properties of the bean. Hidden properties can be accessed via 931 * {@link #getHiddenProperties()}. 932 * 933 * @return A map of property names to their metadata. The map is unmodifiable. 934 * @see #getPropertyMeta(String) 935 * @see #getHiddenProperties() 936 */ 937 public Map<String,BeanPropertyMeta> getProperties() { return properties; } 938 939 /** 940 * Returns the properties as an array in property order. 941 * 942 * <p> 943 * The returned array is shared and must not be modified. 944 * 945 * @return The properties array. 946 */ 947 BeanPropertyMeta[] getPropertyArray() { return propertyArray; } 948 949 /** 950 * Returns metadata about the specified property. 951 * 952 * @param name The name of the property on this bean. Can be <jk>null</jk> to return the dynamic (<js>"*"</js>) property. 953 * @return The metadata about the property, or <jk>null</jk> if no such property exists on this bean. 954 */ 955 public BeanPropertyMeta getPropertyMeta(String name) { 956 if (name == null) 957 return dynaProperty; 958 var bpm = properties.get(name); 959 if (bpm == null) 960 bpm = hiddenProperties.get(name); 961 if (bpm == null) 962 bpm = dynaProperty; 963 return bpm; 964 } 965 966 /** 967 * Returns a mock bean property that resolves to the name <js>"_type"</js> and whose value always resolves to the 968 * dictionary name of the bean. 969 * 970 * @return The type name property. 971 */ 972 public BeanPropertyMeta getTypeProperty() { return typeProperty; } 973 974 /** 975 * Returns the type property name for this bean. 976 * 977 * <p> 978 * This is the name of the bean property used to store the dictionary name of a bean type so that the parser knows 979 * the data type to reconstruct. 980 * 981 * <p> 982 * If <jk>null</jk>, <js>"_type"</js> should be assumed. 983 * 984 * <p> 985 * The value is determined from: 986 * <ul> 987 * <li>The marshalling-side {@code @Marshalled(typePropertyName)} on the class, if present 988 * (resolved via {@link BeanMetaInitializer#resolveTypePropertyName(BeanConfigContext, ClassInfo)}). 989 * <li>Otherwise, the default from {@link BeanConfigContext#getBeanTypePropertyName()}. 990 * </ul> 991 * 992 * @return 993 * The type property name associated with this bean, or <jk>null</jk> if the default <js>"_type"</js> should be used. 994 * @see BeanConfigContext#getBeanTypePropertyName() 995 */ 996 public String getTypePropertyName() { return typePropertyName; } 997 998 @Override /* Overridden from Object */ 999 public int hashCode() { 1000 return classInfo.hashCode(); 1001 } 1002 1003 /** 1004 * Property read interceptor. 1005 * 1006 * <p> 1007 * Called immediately after calling the getter to allow the value to be overridden. 1008 * 1009 * @param bean The bean from which the property was read. 1010 * @param name The property name. 1011 * @param value The value just extracted from calling the bean getter. 1012 * @return The value to serialize. Default is just to return the existing value. 1013 */ 1014 public Object onReadProperty(Object bean, String name, Object value) { 1015 return beanFilter == null ? value : beanFilter.readProperty(bean, name, value); 1016 } 1017 1018 /** 1019 * Property write interceptor. 1020 * 1021 * <p> 1022 * Called immediately before calling theh setter to allow value to be overwridden. 1023 * 1024 * @param bean The bean from which the property was read. 1025 * @param name The property name. 1026 * @param value The value just parsed. 1027 * @return The value to serialize. Default is just to return the existing value. 1028 */ 1029 public Object onWriteProperty(Object bean, String name, Object value) { 1030 return beanFilter == null ? value : beanFilter.writeProperty(bean, name, value); 1031 } 1032 1033 protected FluentMap<String,Object> properties() { 1034 // @formatter:off 1035 return filteredBeanPropertyMap() 1036 .a("class", classInfo.getName()) 1037 .a("properties", properties); 1038 // @formatter:on 1039 } 1040 1041 @Override /* Overridden from Object */ 1042 public String toString() { 1043 return r(properties()); 1044 } 1045 1046 /** 1047 * Returns the bean-modeling {@link BeanTypeResolver} that this metadata was constructed against. 1048 * 1049 * <p> 1050 * On the marshalling-side construction path, the underlying object is the marshalling context (which 1051 * implements {@link BeanTypeResolver}); marshalling-side callers that need the concrete narrowing must cast 1052 * (e.g. {@code (MarshallingContext) bm.getBeanTypeResolver()}). 1053 * 1054 * <p> 1055 * Returns <jk>null</jk> when this {@link BeanMeta} was constructed via the commons-side path 1056 * ({@link #of(Class, BeanConfigContext)}). Use {@link #getConfig()} for the always-non-null bean-modeling 1057 * configuration facade. 1058 * 1059 * @return The bean-modeling type resolver, or <jk>null</jk> for bean-modeling-only construction. 1060 */ 1061 protected BeanTypeResolver getBeanTypeResolver() { return (BeanTypeResolver) marshallingContext; } 1062 1063 /** 1064 * Returns the constructor for this bean, if one was found. 1065 * 1066 * <p> 1067 * The constructor is determined by {@link #findBeanConstructor()} and may be: 1068 * <ul> 1069 * <li>A constructor annotated with {@link BeanCtor @BeanCtor} 1070 * <li>An implementation class constructor (if provided) 1071 * <li>A no-argument constructor 1072 * <li><jk>null</jk> if no suitable constructor was found 1073 * </ul> 1074 * 1075 * @return The constructor for this bean, or <jk>null</jk> if no constructor is available. 1076 * @see #getConstructorArgs() 1077 * @see #hasConstructor() 1078 */ 1079 public ConstructorInfo getConstructor() { 1080 return beanConstructor.constructor().orElse(null); 1081 } 1082 1083 /** 1084 * Returns the list of property names that correspond to the constructor parameters. 1085 * 1086 * <p> 1087 * The property names are in the same order as the constructor parameters. These names are used to map 1088 * parsed property values to constructor arguments when creating bean instances. 1089 * 1090 * <p> 1091 * The property names are determined from: 1092 * <ul> 1093 * <li>The {@link BeanCtor#properties() properties()} value in the {@link BeanCtor @BeanCtor} annotation (if present) 1094 * <li>Otherwise, the parameter names from the constructor (if available in bytecode) 1095 * </ul> 1096 * 1097 * <p> 1098 * If the bean has no constructor or uses a no-argument constructor, this list will be empty. 1099 * 1100 * @return A list of property names corresponding to constructor parameters, in parameter order. 1101 * @see #getConstructor() 1102 * @see #hasConstructor() 1103 */ 1104 public List<String> getConstructorArgs() { 1105 return beanConstructor.args(); 1106 } 1107 1108 /** 1109 * Returns the "extras" property for dynamic bean properties. 1110 * 1111 * @return The dynamic property, or <jk>null</jk> if not present. 1112 */ 1113 protected BeanPropertyMeta getDynaProperty() { return dynaProperty; } 1114 1115 /** 1116 * Returns the map of getter methods to property names. 1117 * 1118 * @return The getter properties map. 1119 */ 1120 protected Map<Method,String> getGetterProps() { return getterProps; } 1121 1122 /** 1123 * Returns the map of hidden properties on this bean. 1124 * 1125 * <p> 1126 * Hidden properties are properties that exist on the bean but are not included in the normal property list. 1127 * These properties are typically excluded from serialization but may still be accessible programmatically. 1128 * 1129 * <p> 1130 * Hidden properties can be defined through: 1131 * <ul> 1132 * <li>{@link BeanFilter#getExcludeProperties() Bean filter exclude properties} 1133 * <li>Properties that fail validation during bean metadata creation 1134 * </ul> 1135 * 1136 * @return A map of hidden property names to their metadata. The map is unmodifiable. 1137 * @see #getProperties() 1138 */ 1139 protected Map<String,BeanPropertyMeta> getHiddenProperties() { 1140 return hiddenProperties; 1141 } 1142 1143 /** 1144 * Returns the map of setter methods to property names. 1145 * 1146 * @return The setter properties map. 1147 */ 1148 protected Map<Method,String> getSetterProps() { return setterProps; } 1149 1150 /** 1151 * Returns whether this bean has a constructor available for instantiation. 1152 * 1153 * <p> 1154 * A bean has a constructor if {@link #findBeanConstructor()} was able to find a suitable constructor, 1155 * which may be: 1156 * <ul> 1157 * <li>A constructor annotated with {@link BeanCtor @BeanCtor} 1158 * <li>An implementation class constructor (if provided) 1159 * <li>A no-argument constructor 1160 * </ul> 1161 * 1162 * <p> 1163 * If this method returns <jk>false</jk>, the bean cannot be instantiated using {@link #newBean(Object)}, 1164 * and may need to be created through other means (e.g., interface proxies). 1165 * 1166 * @return <jk>true</jk> if a constructor is available, <jk>false</jk> otherwise. 1167 * @see #getConstructor() 1168 * @see #newBean(Object) 1169 */ 1170 public boolean hasConstructor() { 1171 return beanConstructor.constructor().isPresent(); 1172 } 1173 1174 /** 1175 * Returns whether any property of this bean is marked {@code @BeanProp(required=true)}. 1176 * 1177 * @return <jk>true</jk> if at least one property is required. 1178 */ 1179 public boolean hasRequiredProperties() { return ! requiredPropertyNames.isEmpty(); } 1180 1181 /** 1182 * Returns the names of the properties marked {@code @BeanProp(required=true)}. 1183 * 1184 * <p> 1185 * Hidden and excluded properties are not included, since input can never set them. 1186 * 1187 * <h5 class='section'>Example:</h5> 1188 * <p class='bjava'> 1189 * <jk>public record</jk> Person(<ja>@BeanProp</ja>(required=<jk>true</jk>) String <jv>name</jv>, Integer <jv>age</jv>) {} 1190 * 1191 * <jc>// [name]</jc> 1192 * List<String> <jv>required</jv> = BeanMeta.<jsm>of</jsm>(Person.<jk>class</jk>, BeanConfigContext.<jsf>DEFAULT</jsf>).getRequiredPropertyNames(); 1193 * </p> 1194 * 1195 * @return An unmodifiable list of property names, in property order. Never <jk>null</jk>. 1196 */ 1197 public List<String> getRequiredPropertyNames() { return requiredPropertyNames; } 1198 1199 /** 1200 * Returns whether this bean has opted out of alphabetical property sorting. 1201 * 1202 * @return <jk>true</jk> if properties use declaration order (see the class Javadoc). 1203 */ 1204 protected boolean isUnsortedProperties() { return unsortedProperties; } 1205 1206 /** 1207 * Creates a new instance of this bean. 1208 * 1209 * @param outer The outer object if bean class is a non-static inner member class. Can be <jk>null</jk> if not applicable. 1210 * @return A new instance of this bean if possible, or <jk>null</jk> if not. 1211 * @throws ExecutableException Exception occurred on invoked constructor/method/field. 1212 */ 1213 public T newBean(Object outer) throws ExecutableException { 1214 if (factoryClass != null) { 1215 try { 1216 BeanFactory factory = resolveFactory(factoryClass); 1217 return (T) factory.create(); 1218 } catch (ExecutableException e) { 1219 throw e; 1220 } catch (Exception e) { 1221 throw new ExecutableException(e); 1222 } 1223 } 1224 if (classInfo.isMemberClass() && classInfo.isNotStatic()) { 1225 if (hasConstructor()) 1226 return getConstructor().<T>newInstance(outer); 1227 } else { 1228 if (hasConstructor()) 1229 return getConstructor().<T>newInstance(); 1230 var h = beanProxyInvocationHandler.get(); 1231 if (nn(h)) { 1232 var inner = classInfo.inner(); 1233 return (T)Proxy.newProxyInstance(inner.getClassLoader(), a(inner, Serializable.class), h); 1234 } 1235 } 1236 return null; 1237 } 1238 1239 private BeanFactory resolveFactory(Class<? extends BeanFactory> fc) { 1240 var bs = config.getBeanStore(); 1241 if (bs != null) { 1242 var opt = bs.getBean(fc); 1243 if (opt.isPresent()) 1244 return opt.get(); 1245 } 1246 return (BeanFactory) BeanInstantiator.of((Class)fc).run(); 1247 } 1248 1249 /** 1250 * Finds the appropriate constructor for this bean and determines the property names for constructor arguments. 1251 * 1252 * <p> 1253 * This method searches for a constructor in the following order of precedence: 1254 * <ol> 1255 * <li><b>{@link BeanCtor @BeanCtor} annotated constructor:</b> If a constructor is annotated with {@link BeanCtor @BeanCtor}, 1256 * it is used. The property names are determined from: 1257 * <ul> 1258 * <li>The {@link BeanCtor#properties() properties()} value in the annotation, if specified 1259 * <li>Otherwise, the parameter names from the constructor (if available in bytecode) 1260 * </ul> 1261 * If multiple constructors are annotated with {@link BeanCtor @BeanCtor}, an exception is thrown. 1262 * <li><b>Implementation class constructor:</b> If an {@link #implClassConstructor} was provided during bean 1263 * metadata creation, it is used with an empty property list. 1264 * <li><b>No-arg constructor:</b> Searches for a no-argument constructor. The visibility required depends on 1265 * whether the class carries a bean-registration annotation (e.g. {@link BeanType @BeanType} or the 1266 * marshalling-side {@code @Marshalled}) — see 1267 * {@link BeanMetaInitializer#hasBeanRegistrationAnnotation(BeanConfigContext, ClassInfo)}: 1268 * <ul> 1269 * <li>If a registration annotation is present, private constructors are allowed 1270 * <li>Otherwise, the visibility is determined by {@link BeanConfigContext#getBeanConstructorVisibility()} 1271 * </ul> 1272 * <li><b>No constructor:</b> Returns an empty {@link Optional} if no suitable constructor is found. 1273 * </ol> 1274 * 1275 * <p> 1276 * The returned {@link BeanConstructor} contains: 1277 * <ul> 1278 * <li>The constructor (if found), wrapped in an {@link Optional} 1279 * <li>A list of property names that correspond to the constructor parameters, in order 1280 * </ul> 1281 * 1282 * @return A {@link BeanConstructor} containing the found constructor and its associated property names. 1283 * @throws BeanRuntimeException If multiple constructors are annotated with {@link BeanCtor @BeanCtor}, or if 1284 * the number of properties specified in {@link BeanCtor @BeanCtor} doesn't match the number of constructor parameters, 1285 * or if parameter names cannot be determined from the bytecode. 1286 */ 1287 private BeanConstructor findBeanConstructor() { 1288 var ap = config.getAnnotationProvider(); 1289 var vis = config.getBeanConstructorVisibility(); 1290 var ci = classInfo; 1291 1292 var l = ci.getPublicConstructors().stream().filter(x -> ap.has(BeanCtor.class, x)).toList(); 1293 if (l.isEmpty()) 1294 l = ci.getDeclaredConstructors().stream().filter(x -> ap.has(BeanCtor.class, x)).toList(); 1295 if (l.size() > 1) 1296 throw brex(ci, "Multiple instances of '@BeanCtor' found."); 1297 if (l.size() == 1) { 1298 var con = first(l).orElseThrow(() -> brex(ci, "No constructor found.")).accessible(); 1299 var args = ap.find(BeanCtor.class, con).stream().map(x -> x.inner().properties()).filter(StringUtils::isNotBlank).map(x -> split(x)).findFirst().orElse(emptyList()); 1300 if (! con.hasNumParameters(args.size())) { 1301 if (ine(args)) 1302 throw brex(ci, "Number of properties defined in '@BeanCtor' annotation does not match number of parameters in constructor."); 1303 args = con.getParameters().stream().map(x -> x.getName()).toList(); 1304 for (int i = 0; i < args.size(); i++) { 1305 if (isBlank(args.get(i))) 1306 throw brex(ci, "Could not find name for parameter #%s of constructor '%s'", i, con.getNameFull()); 1307 } 1308 } 1309 return new BeanConstructor(o(con), args); 1310 } 1311 1312 if (ci.isRecord()) { 1313 var components = ci.getRecordComponents(); 1314 var paramTypes = components.stream().map(RecordComponent::getType).toArray(Class[]::new); 1315 // A record's canonical constructor has the same access as the record class (JLS 8.10.4), so look it up 1316 // among the declared constructors; the bean-class visibility check already governs the record itself. 1317 var rcon = ci.getDeclaredConstructor(x -> x.hasParameterTypes(paramTypes)).orElse(null); 1318 if (rcon != null) 1319 return new BeanConstructor(o(makeRecordConstructorAccessible(ci, rcon)), components.stream().map(RecordComponent::getName).toList()); 1320 } 1321 1322 if (implClassConstructor != null) 1323 return new BeanConstructor(o(implClassConstructor.accessible()), emptyList()); 1324 1325 var con = ci.getNoArgConstructor(config.getBeanMetaInitializer().hasBeanRegistrationAnnotation(config, classInfo) ? Visibility.PRIVATE : vis).orElse(null); 1326 if (con != null) 1327 return new BeanConstructor(o(con.accessible()), emptyList()); 1328 1329 return new BeanConstructor(oe(), emptyList()); 1330 } 1331 1332 /** 1333 * Resolves the bean property name for a field when {@link BeanProp @BeanProp} or {@link Name @Name} supplies 1334 * <js>"*"</js>. 1335 * 1336 * <p> 1337 * On a {@link Map} field, <js>"*"</js> remains the dyna-property name. On any other field type, <js>"*"</js> is 1338 * treated like an unnamed {@code @BeanProp} (apply other attributes) but the property name is taken from the field 1339 * (via {@link PropertyNamer#getPropertyName(String)}). 1340 * </p> 1341 * 1342 * @param x Field being registered. 1343 * @param nameFromAnnotations Name from {@code @BeanProp}/{@code @Name}, or already-resolved default from the field name. 1344 * @param propertyNamer Namer for raw field names. 1345 * @return Property key to use in {@link BeanMeta}. 1346 */ 1347 private static String resolveBeanFieldPropertyName(FieldInfo x, String nameFromAnnotations, PropertyNamer propertyNamer) { 1348 if (neq(nameFromAnnotations, "*")) 1349 return nameFromAnnotations; 1350 if (x.getFieldType().isAssignableTo(Map.class)) 1351 return "*"; 1352 return propertyNamer.getPropertyName(x.getName()); 1353 } 1354 1355 /** 1356 * Finds all bean fields in the class hierarchy. 1357 * 1358 * <p> 1359 * Traverses the complete class hierarchy (as defined by {@link #classHierarchy}) and collects all fields that 1360 * meet the bean field criteria: 1361 * <ul> 1362 * <li>Not static 1363 * <li>Not transient (unless transient fields are not ignored) 1364 * <li>Not annotated with {@link Transient @Transient} (unless transient fields are not ignored) 1365 * <li>Not annotated with {@link BeanIgnore @BeanIgnore} 1366 * <li>Visible according to the specified visibility level, or annotated with {@link BeanProp @BeanProp} 1367 * </ul> 1368 * 1369 * @return A collection of all bean fields found in the class hierarchy. 1370 */ 1371 private Collection<FieldInfo> findBeanFields() { 1372 var v = config.getBeanFieldVisibility(); 1373 var noIgnoreTransients = ! config.isIgnoreTransientFields(); 1374 var ap = config.getAnnotationProvider(); 1375 var isRecord = classInfo.isRecord(); 1376 var recordComponentNames = isRecord 1377 ? classInfo.getRecordComponents().stream().map(RecordComponent::getName).collect(java.util.stream.Collectors.toSet()) 1378 : Set.<String>of(); 1379 // @formatter:off 1380 return classHierarchy.get().stream() 1381 .flatMap(c2 -> c2.getDeclaredFields().stream()) 1382 .filter(x -> x.isNotStatic() 1383 && (x.isNotTransient() || noIgnoreTransients) 1384 && (! x.hasAnnotation(Transient.class) || noIgnoreTransients) 1385 && ! ap.has(BeanIgnore.class, x) 1386 && (v.isVisible(x.inner()) || ap.has(BeanProp.class, x) 1387 || (isRecord && recordComponentNames.contains(x.getName())))) 1388 .toList(); 1389 // @formatter:on 1390 } 1391 1392 /** 1393 * Finds all bean methods (getters, setters, and extraKeys) in the class hierarchy. 1394 * 1395 * <p> 1396 * Traverses the complete class hierarchy (as defined by {@link #classHierarchy}) and identifies methods that 1397 * represent bean properties. Methods are identified as: 1398 * <ul> 1399 * <li><b>Getters:</b> Methods with no parameters that return a value, matching patterns like: 1400 * <ul> 1401 * <li><c>getX()</c> - standard getter pattern 1402 * <li><c>isX()</c> - boolean getter pattern (returns boolean or Boolean) 1403 * <li>Methods annotated with {@link BeanProp @BeanProp} or {@link Name @Name} 1404 * <li>Methods with {@link BeanProp @BeanProp} annotation with value <js>"*"</js> that return a Map 1405 * </ul> 1406 * <li><b>Setters:</b> Methods with one parameter that match patterns like: 1407 * <ul> 1408 * <li><c>setX(value)</c> - standard setter pattern 1409 * <li><c>withX(value)</c> - fluent setter pattern (returns the bean type) 1410 * <li>Methods annotated with {@link BeanProp @BeanProp} or {@link Name @Name} 1411 * <li>Methods with {@link BeanProp @BeanProp} annotation with value <js>"*"</js> that accept a Map 1412 * <li>Fluent setters (if enabled) - methods that return the bean type and accept one parameter 1413 * </ul> 1414 * <li><b>ExtraKeys:</b> Methods with {@link BeanProp @BeanProp} annotation with value <js>"*"</js> that return a Collection 1415 * </ul> 1416 * 1417 * <p> 1418 * Methods are filtered based on: 1419 * <ul> 1420 * <li>Not static, not bridge methods 1421 * <li>Parameter count ≤ 2 1422 * <li>Not annotated with {@link BeanIgnore @BeanIgnore} 1423 * <li>Not annotated with {@link Transient @Transient} 1424 * <li>Visible according to the specified visibility level, or annotated with {@link BeanProp @BeanProp} or {@link Name @Name} 1425 * </ul> 1426 * 1427 * <p> 1428 * Property names are determined from: 1429 * <ul> 1430 * <li>{@link BeanProp @BeanProp} or {@link Name @Name} annotations (if present) 1431 * <li>Otherwise, derived from the method name using the provided {@link PropertyNamer} 1432 * </ul> 1433 * 1434 * @return A list of {@link BeanMethod} objects representing all found bean methods. 1435 */ 1436 private List<BeanMethod> findBeanMethods() { 1437 var l = new LinkedList<BeanMethod>(); 1438 var ap = config.getAnnotationProvider(); 1439 var ci = classInfo; 1440 var v = config.getBeanMethodVisibility(); 1441 var pn = effectivePropertyNamer(); 1442 var suppressedFromBeanIgnoredFields = findSuppressedPropertyNamesFromIgnoredFields(pn); 1443 1444 classHierarchy.get().forEach(c2 -> { 1445 for (var m : c2.getDeclaredMethods()) { 1446 var mm = m.getMatchingMethods(); 1447 var beanps = ap.find(BeanProp.class, m).stream().map(AnnotationInfo::inner).toList(); 1448 var names = ap.find(Name.class, m).stream().map(AnnotationInfo::inner).toList(); 1449 // Skip static, bridge, or methods with >2 params; skip if ignored, transient, or not visible 1450 if (m.isStatic() || m.isBridge() || m.getParameterCount() > 2 1451 || mm.stream().anyMatch(m2 -> ap.has(BeanIgnore.class, m2, SELF)) 1452 || mm.stream().anyMatch(m2 -> ap.find(Transient.class, m2, SELF).stream().map(x -> x.inner().value()).findFirst().orElse(false)) 1453 || ! (m.isVisible(v) || ine(beanps) || ine(names))) 1454 continue; 1455 1456 var n = m.getNameSimple(); 1457 1458 var params = m.getParameters(); 1459 var rt = m.getReturnType(); 1460 var methodType = UNKNOWN; 1461 var bpName = bpName(beanps, names); 1462 1463 if (params.isEmpty()) { 1464 if (eq(bpName, "*")) { 1465 if (rt.isAssignableTo(Collection.class)) { 1466 methodType = EXTRAKEYS; 1467 } else if (rt.isAssignableTo(Map.class)) { 1468 methodType = GETTER; 1469 } 1470 n = bpName; 1471 } else if (n.startsWith("get") && (! rt.is(Void.TYPE))) { 1472 methodType = GETTER; 1473 n = n.substring(3); 1474 } else if (n.startsWith("is") && (rt.is(Boolean.TYPE) || rt.is(Boolean.class))) { 1475 methodType = GETTER; 1476 n = n.substring(2); 1477 } else if (ci.isRecord() && isRecordAccessor(m, ci)) { 1478 methodType = GETTER; 1479 } else if (nn(bpName)) { 1480 methodType = GETTER; 1481 if (bpName.isEmpty()) { 1482 if (n.startsWith("get")) 1483 n = n.substring(3); 1484 else if (n.startsWith("is")) 1485 n = n.substring(2); 1486 bpName = n; 1487 } else { 1488 n = bpName; 1489 } 1490 } 1491 } else if (params.size() == 1) { 1492 if (eq(bpName, "*")) { 1493 if (params.get(0).getParameterType().isAssignableTo(Map.class)) { 1494 methodType = SETTER; 1495 n = bpName; 1496 } else if (params.get(0).getParameterType().is(String.class)) { 1497 methodType = GETTER; 1498 n = bpName; 1499 } 1500 } else if (n.startsWith("set") && (rt.isAssignableFrom(ci) || rt.is(Void.TYPE))) { 1501 methodType = SETTER; 1502 n = n.substring(3); 1503 } else if (n.startsWith("with") && (rt.isAssignableFrom(ci))) { 1504 methodType = SETTER; 1505 n = n.substring(4); 1506 } else if (nn(bpName)) { 1507 methodType = SETTER; 1508 if (bpName.isEmpty()) { 1509 if (n.startsWith("set")) 1510 n = n.substring(3); 1511 bpName = n; 1512 } else { 1513 n = bpName; 1514 } 1515 } else if (fluentSetters && rt.isAssignableFrom(ci)) { 1516 methodType = SETTER; 1517 } 1518 } else if (params.size() == 2) { 1519 if (eq(bpName, "*") && params.get(0).getParameterType().is(String.class) && n.startsWith("set") && (rt.isAssignableFrom(ci) || rt.is(Void.TYPE))) { 1520 methodType = SETTER; 1521 } else { 1522 methodType = GETTER; 1523 } 1524 n = bpName; 1525 } 1526 n = pn.getPropertyName(n); 1527 1528 if (eq(bpName, "*") && methodType == UNKNOWN) 1529 throw brex(ci, "Found @BeanProp(\"*\") but could not determine method type on method '%s'.", m.getNameSimple()); 1530 1531 if (methodType != UNKNOWN) { 1532 if (nn(bpName) && ! bpName.isEmpty()) 1533 n = bpName; 1534 if (nn(n) && ! suppressedFromBeanIgnoredFields.contains(n)) 1535 l.add(new BeanMethod(n, methodType, m.inner())); 1536 } 1537 } 1538 }); 1539 return l; 1540 } 1541 1542 /** 1543 * Makes a record's canonical constructor accessible, failing with a clear message if the module system refuses. 1544 * 1545 * @param ci The record class. 1546 * @param rcon The canonical constructor. 1547 * @return The same constructor. 1548 * @throws BeanRuntimeException If the record is non-public and its package is not opened to Juneau. 1549 */ 1550 static ConstructorInfo makeRecordConstructorAccessible(ClassInfo ci, ConstructorInfo rcon) { 1551 return makeRecordConstructorAccessible(ci, rcon, ConstructorInfo::setAccessible); 1552 } 1553 1554 // Package-private seam so tests can force setAccessible() to report failure. 1555 static ConstructorInfo makeRecordConstructorAccessible(ClassInfo ci, ConstructorInfo rcon, Predicate<ConstructorInfo> accessor) { 1556 // Always attempt to make the constructor accessible, since a public record nested in a non-public class still needs it. 1557 // Only fail loudly when the record is not effectively public. 1558 var ok = accessor.test(rcon); 1559 if (! ok && ! (rcon.isPublic() && isEffectivelyPublic(ci))) { 1560 var pkg = ci.getPackage() == null ? "" : ci.getPackage().getName(); 1561 throw brex(ci, "Could not make the canonical constructor of non-public record '%s' accessible. The package '%s' must be opened to Juneau, e.g. 'opens %s to org.apache.juneau.commons;' in module-info or '--add-opens <module>/%s=ALL-UNNAMED'.", ci.getNameFull(), pkg, pkg, pkg); 1562 } 1563 return rcon; 1564 } 1565 1566 private static boolean isEffectivelyPublic(ClassInfo ci) { 1567 for (Class<?> c = ci.inner(); c != null; c = c.getEnclosingClass()) 1568 if (! Modifier.isPublic(c.getModifiers())) 1569 return false; 1570 return true; 1571 } 1572 1573 private static boolean isRecordAccessor(MethodInfo m, ClassInfo ci) { 1574 return ci.getRecordComponents().stream() 1575 .anyMatch(rc -> rc.getName().equals(m.getNameSimple()) 1576 && rc.getType().equals(m.getReturnType().inner())); 1577 } 1578 1579 /** 1580 * Creates a bean registry for this bean class. 1581 * 1582 * <p> 1583 * The bean registry is used to resolve dictionary names (type names) to actual class types. This is essential 1584 * for polymorphic bean serialization and parsing, where a dictionary name in the serialized form needs to be 1585 * mapped back to the correct subclass. 1586 * 1587 * <p> 1588 * The registry is built from: 1589 * <ul> 1590 * <li><b>Bean filter dictionary:</b> Classes specified in the {@link BeanFilter#getBeanDictionary() bean filter's dictionary} 1591 * (if a bean filter is present) 1592 * <li><b>{@code @Marshalled} annotation:</b> If the class has a {@code @Marshalled} annotation with a non-empty 1593 * {@code typeName()}, the class itself is added to the dictionary 1594 * </ul> 1595 * 1596 * <p> 1597 * The registry is used by parsers to determine which class to instantiate when deserializing polymorphic beans. 1598 * 1599 * @return A new registry lookup containing the dictionary classes for this bean, or an empty lookup if 1600 * no dictionary classes are found. 1601 */ 1602 private BeanRegistryLookup findBeanRegistry() { 1603 // BeanRegistry is a marshalling-side concern (polymorphic dispatch via @Marshalled(typeName)/dictionary). 1604 // Lifted out to the BeanMetaInitializer SPI carried on BeanConfigContext so this class no longer references 1605 // MarshalledBeanMetaInitializer directly. Returns null on the commons-side path (no marshallingContext). 1606 return config.getBeanMetaInitializer().buildBeanRegistry(marshallingContext, beanFilter, classInfo, config); 1607 } 1608 1609 /** 1610 * Builds a list of all classes in the class hierarchy for this bean. 1611 * 1612 * <p> 1613 * Traverses the complete inheritance hierarchy (classes and interfaces) starting from the bean class (or the 1614 * interface class specified in the bean filter) and collects all classes up to (but not including) the stop class. 1615 * 1616 * <p> 1617 * The traversal order follows a depth-first approach: 1618 * <ol> 1619 * <li>First, recursively traverses the superclass hierarchy 1620 * <li>Then, recursively traverses all implemented interfaces 1621 * <li>Finally, adds the current class itself 1622 * </ol> 1623 * 1624 * <p> 1625 * If a {@link BeanFilter#getInterfaceClass() bean filter interface class} is specified, the traversal starts 1626 * from that interface class instead of the bean class itself. This allows beans to use properties defined on 1627 * a parent interface rather than the concrete implementation class. 1628 * 1629 * <p> 1630 * The resulting list is used to find bean properties, methods, and fields across the entire class hierarchy. 1631 * 1632 * @return An unmodifiable list of {@link ClassInfo} objects representing all classes in the hierarchy, in 1633 * traversal order (superclasses first, then interfaces, then the class itself). 1634 */ 1635 private List<ClassInfo> findClassHierarchy() { 1636 var result = new LinkedList<ClassInfo>(); 1637 // If @BeanType(interfaceClass) is specified on the parent class, then we want 1638 // to use the properties defined on that class, not the subclass. 1639 var c2 = (nn(beanFilter) && nn(beanFilter.getInterfaceClass()) ? beanFilter.getInterfaceClass() : classInfo); 1640 findClassHierarchy(c2, stopClass, result::add); 1641 return u(result); 1642 } 1643 1644 /** 1645 * Recursively traverses the class hierarchy and invokes the consumer for each class found. 1646 * 1647 * <p> 1648 * This is a helper method that performs a depth-first traversal of the class hierarchy: 1649 * <ol> 1650 * <li>Recursively processes the superclass (if present and not the stop class) 1651 * <li>Recursively processes all implemented interfaces 1652 * <li>Invokes the consumer with the current class 1653 * </ol> 1654 * 1655 * <p> 1656 * The traversal stops when it reaches the stop class (which is not included in the traversal). 1657 * 1658 * @param c The class to start traversal from. 1659 * @param stopClass The class to stop traversal at (exclusive). Traversal will not proceed beyond this class. 1660 * @param consumer The consumer to invoke for each class in the hierarchy. 1661 */ 1662 private void findClassHierarchy(ClassInfo c, ClassInfo stopClass, Consumer<ClassInfo> consumer) { 1663 var sc = c.getSuperclass(); 1664 if (nn(sc) && ! sc.is(stopClass.inner())) 1665 findClassHierarchy(sc, stopClass, consumer); 1666 c.getInterfaces().forEach(x -> findClassHierarchy(x, stopClass, consumer)); 1667 consumer.accept(c); 1668 } 1669 1670 /** 1671 * Finds the dictionary name (type name) for this bean class. 1672 * 1673 * <p> 1674 * The dictionary name is used in serialized forms to identify the specific type of a bean instance, enabling 1675 * polymorphic serialization and deserialization. This is especially important when serializing/parsing beans 1676 * that are part of an inheritance hierarchy. 1677 * 1678 * <p> 1679 * The dictionary name is determined by searching in the following order of precedence: 1680 * <ol> 1681 * <li><b>Bean filter type name:</b> If a bean filter is present and has a type name specified via 1682 * {@link BeanFilter#getTypeName()}, that value is used. 1683 * <li><b>Bean registry lookup:</b> If a bean registry exists for this bean, it is queried for the type name 1684 * of this class. 1685 * <li><b>Parent class registry lookup:</b> Searches through parent classes and interfaces (starting from the 1686 * second one, skipping the class itself) and checks if any of their bean registries contain a type name 1687 * for this class. 1688 * <li><b>{@code @Marshalled} annotation:</b> If the class has a {@code @Marshalled} annotation with a non-empty 1689 * {@code typeName()}, that value is used. 1690 * <li><b>No dictionary name:</b> Returns <jk>null</jk> if no dictionary name is found. 1691 * </ol> 1692 * 1693 * <p> 1694 * If a dictionary name is found, it will be used in serialized output (typically as a special property like 1695 * <js>"_type"</js>) so that parsers can determine the correct class to instantiate when deserializing. 1696 * 1697 * @return The dictionary name for this bean, or <jk>null</jk> if no dictionary name is defined. 1698 */ 1699 private String findDictionaryName() { 1700 if (nn(beanFilter) && nn(beanFilter.getTypeName())) 1701 return beanFilter.getTypeName(); 1702 1703 // Pure-reflection class identity for BeanRegistry.getTypeName(...) — this method now uses 1704 // surviving `this.classMeta` references inside this method to `classInfo.inner()` so the dictionary 1705 // lookup does not require a ClassMeta for the bean's own class. BeanRegistry still lives in 1706 // juneau-marshall; it just exposes a raw-Class overload for callers that have a ClassInfo. 1707 var rawClass = classInfo.inner(); 1708 1709 var br = getBeanRegistry(); 1710 if (nn(br)) { 1711 String s = br.getTypeName(rawClass); 1712 if (nn(s)) 1713 return s; 1714 } 1715 1716 // Parent-class BeanRegistry lookup is a marshalling-side concern: it walks parents and interfaces to 1717 // see if any of THEIR ClassMeta-backed BeanRegistries declares a typeName for our raw class. Lifted 1718 // out to the BeanMetaInitializer SPI carried on BeanConfigContext. Returns null on the commons-side path. 1719 var bmi = config.getBeanMetaInitializer(); 1720 var n = bmi.findTypeNameInParents(marshallingContext, classInfo, rawClass); 1721 if (n != null) 1722 return n; 1723 1724 return bmi.findMarshalledTypeName(config, classInfo); 1725 } 1726 1727 /** 1728 * Merges standard JavaBeans {@link BeanInfo} property descriptors into {@code normalProps}, skipping the class 1729 * pseudo-property and logical names suppressed when {@link BeanIgnore#ignoreAccessors()} is <jk>true</jk> on a field. 1730 */ 1731 private void mergeJavaBeanPropertyDescriptorsIntoNormalProps(java.beans.BeanInfo bi, Map<String,BeanPropertyMeta.Builder> normalProps, 1732 PropertyNamer propertyNamer) { 1733 var suppressedFromBeanIgnoredFields = findSuppressedPropertyNamesFromIgnoredFields(propertyNamer); 1734 for (var pd : bi.getPropertyDescriptors()) { 1735 if (eq(pd.getName(), "class")) 1736 continue; 1737 if (suppressedFromBeanIgnoredFields.contains(pd.getName())) 1738 continue; 1739 var builder = normalProps.computeIfAbsent(pd.getName(), n -> BeanPropertyMeta.builder(this, n)); 1740 if (pd.getReadMethod() != null) 1741 builder.setGetter(info(pd.getReadMethod())); 1742 if (pd.getWriteMethod() != null) 1743 builder.setSetter(info(pd.getWriteMethod())); 1744 } 1745 } 1746 1747 /** 1748 * Property names suppressed from getter/setter discovery because a non-static field with that logical name is 1749 * annotated with {@link BeanIgnore @BeanIgnore} and {@link BeanIgnore#ignoreAccessors()} is <jk>true</jk>. 1750 * 1751 * <p> 1752 * When {@link BeanIgnore#ignoreAccessors()} is <jk>false</jk> (the default), ignored fields do not suppress 1753 * JavaBean accessors so patterns such as {@code @BeanIgnore} on a private field with a public {@code getX()} still 1754 * expose {@code x} when field visibility excludes the field. 1755 */ 1756 private Set<String> findSuppressedPropertyNamesFromIgnoredFields(PropertyNamer propertyNamer) { 1757 var s = new HashSet<String>(); 1758 var ap = config.getAnnotationProvider(); 1759 for (var c2 : classHierarchy.get()) { 1760 for (var x : c2.getDeclaredFields()) { 1761 if (! x.isNotStatic() || ! ap.has(BeanIgnore.class, x)) 1762 continue; 1763 if (! fieldBeanIgnoreIgnoresAccessors(x)) 1764 continue; 1765 var name = ap.find(x).stream() 1766 .filter(x2 -> x2.isType(BeanProp.class) || x2.isType(Name.class)) 1767 .map(BeanMeta::name) 1768 .filter(Objects::nonNull) 1769 .findFirst() 1770 .orElse(propertyNamer.getPropertyName(x.getName())); 1771 name = resolveBeanFieldPropertyName(x, name, propertyNamer); 1772 if (nn(name)) 1773 s.add(name); 1774 } 1775 } 1776 return s; 1777 } 1778 1779 private static boolean fieldBeanIgnoreIgnoresAccessors(FieldInfo x) { 1780 for (var bi : x.inner().getAnnotationsByType(BeanIgnore.class)) 1781 if (bi.ignoreAccessors()) 1782 return true; 1783 return false; 1784 } 1785 1786 /** 1787 * Finds a bean field by name in the class hierarchy. 1788 * 1789 * <p> 1790 * Searches through the complete class hierarchy (as defined by {@link #classHierarchy}) to find a field 1791 * with the specified name. The search is performed in the order classes appear in the hierarchy, and the 1792 * first matching field is returned. 1793 * 1794 * <p> 1795 * A field is considered a match if it: 1796 * <ul> 1797 * <li>Has the exact name specified 1798 * <li>Is not static 1799 * <li>Is not transient (unless transient fields are not ignored) 1800 * <li>Is not annotated with {@link Transient @Transient} (unless transient fields are not ignored) 1801 * <li>Is not annotated with {@link BeanIgnore @BeanIgnore} 1802 * </ul> 1803 * 1804 * <p> 1805 * This method is used to find fields for bean properties when a field reference is needed but wasn't 1806 * discovered during the initial property discovery phase (e.g., for properties defined only through 1807 * getters/setters). 1808 * 1809 * @param name The name of the field to find. 1810 * @return The {@link FieldInfo} for the field if found, or <jk>null</jk> if no matching field exists 1811 * in the class hierarchy. 1812 */ 1813 private Optional<FieldInfo> findInnerBeanField(String name) { 1814 var noIgnoreTransients = ! config.isIgnoreTransientFields(); 1815 var ap = config.getAnnotationProvider(); 1816 1817 // @formatter:off 1818 return classHierarchy.get().stream() 1819 .flatMap(c2 -> c2.getDeclaredField( 1820 x -> x.isNotStatic() 1821 && (x.isNotTransient() || noIgnoreTransients) 1822 && (! x.hasAnnotation(Transient.class) || noIgnoreTransients) 1823 && ! ap.has(BeanIgnore.class, x) 1824 && x.hasName(name) 1825 ).stream()) 1826 .findFirst(); 1827 // @formatter:on 1828 } 1829}