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; 018 019import static org.apache.juneau.BeanMeta.MethodType.*; 020import static org.apache.juneau.commons.reflect.AnnotationTraversal.*; 021import static org.apache.juneau.commons.reflect.ReflectionUtils.*; 022import static org.apache.juneau.commons.utils.CollectionUtils.*; 023import static org.apache.juneau.commons.utils.StringUtils.*; 024import static org.apache.juneau.commons.utils.ThrowableUtils.*; 025import static org.apache.juneau.commons.utils.Utils.*; 026 027import java.beans.*; 028import java.io.*; 029import java.lang.reflect.*; 030import java.util.*; 031import java.util.function.*; 032 033import org.apache.juneau.annotation.*; 034import org.apache.juneau.commons.collections.*; 035import org.apache.juneau.commons.function.OptionalSupplier; 036import org.apache.juneau.commons.lang.*; 037import org.apache.juneau.commons.reflect.*; 038import org.apache.juneau.commons.reflect.Visibility; 039import org.apache.juneau.commons.utils.*; 040 041/** 042 * Encapsulates all access to the properties of a bean class (like a souped-up {@link java.beans.BeanInfo}). 043 * 044 * <h5 class='topic'>Description</h5> 045 * 046 * Uses introspection to find all the properties associated with this class. If the {@link Bean @Bean} annotation 047 * is present on the class, then that information is used to determine the properties on the class. 048 * Otherwise, the {@code BeanInfo} functionality in Java is used to determine the properties on the class. 049 * 050 * <h5 class='topic'>Bean property ordering</h5> 051 * 052 * The order of the properties are as follows: 053 * <ul class='spaced-list'> 054 * <li> 055 * If {@link Bean @Bean} annotation is specified on class, then the order is the same as the list of properties 056 * in the annotation. 057 * <li> 058 * If {@link Bean @Bean} annotation is not specified on the class, then the order is based on the following. 059 * <ul> 060 * <li>Public fields (same order as {@code Class.getFields()}). 061 * <li>Properties returned by {@code BeanInfo.getPropertyDescriptors()}. 062 * <li>Non-standard getters/setters with {@link Beanp @Beanp} annotation defined on them. 063 * </ul> 064 * </ul> 065 * 066 * 067 * @param <T> The class type that this metadata applies to. 068 */ 069public class BeanMeta<T> { 070 071 /** 072 * Represents the result of creating a BeanMeta, including the bean metadata and any reason why it's not a bean. 073 * 074 * @param <T> The bean type. 075 * @param beanMeta The bean metadata, or <jk>null</jk> if the class is not a bean. 076 * @param notABeanReason The reason why the class is not a bean, or <jk>null</jk> if it is a bean. 077 */ 078 record BeanMetaValue<T>(BeanMeta<T> beanMeta, String notABeanReason) { 079 Optional<BeanMeta<T>> optBeanMeta() { return opt(beanMeta()); } 080 Optional<String> optNotABeanReason() { return opt(notABeanReason()); } 081 } 082 083 /** 084 * Possible property method types. 085 */ 086 enum MethodType { 087 UNKNOWN, GETTER, SETTER, EXTRAKEYS; 088 } 089 090 /* 091 * Temporary getter/setter method struct used for calculating bean methods. 092 */ 093 private static class BeanMethod { 094 private String propertyName; 095 private MethodType methodType; 096 private Method method; 097 private ClassInfo type; 098 099 private BeanMethod(String propertyName, MethodType type, Method method) { 100 this.propertyName = propertyName; 101 this.methodType = type; 102 this.method = method; 103 this.type = info(type == SETTER ? method.getParameterTypes()[0] : method.getReturnType()); 104 } 105 106 @Override /* Overridden from Object */ 107 public String toString() { 108 return method.toString(); 109 } 110 111 /* 112 * Returns true if this method matches the class type of the specified property. 113 * Only meant to be used for setters. 114 */ 115 private boolean matchesPropertyType(BeanPropertyMeta.Builder b) { 116 if (b == null) 117 return false; 118 119 // Don't do further validation if this is the "*" bean property. 120 if ("*".equals(b.name)) 121 return true; 122 123 // Get the bean property type from the getter/field. 124 var pt = (Class<?>)null; // TODO - Convert to ClassInfo 125 if (nn(b.getter)) 126 pt = b.getter.getReturnType().inner(); 127 else if (nn(b.field)) 128 pt = b.field.inner().getType(); 129 130 // Matches if only a setter is defined. 131 if (pt == null) 132 return true; 133 134 // Doesn't match if not same type or super type as getter/field. 135 if (! type.isParentOf(pt)) 136 return false; 137 138 // If a setter was previously set, only use this setter if it's a closer 139 // match (e.g. prev type is a superclass of this type). 140 if (b.setter == null) 141 return true; 142 143 return type.isStrictChildOf(b.setter.getParameterTypes().get(0).inner()); 144 } 145 } 146 147 /* 148 * Represents a bean constructor with its associated property names. 149 * 150 * @param constructor The constructor information. 151 * @param propertyNames The list of property names that correspond to the constructor parameters. 152 */ 153 private record BeanConstructor(Optional<ConstructorInfo> constructor, List<String> args) {} 154 155 /** 156 * Creates a {@link BeanMeta} instance for the specified class metadata. 157 * 158 * <p> 159 * This is a factory method that attempts to create bean metadata for a class. If the class is determined to be a bean, 160 * the returned {@link BeanMetaValue} will contain the {@link BeanMeta} instance and a <jk>null</jk> reason. 161 * If the class is not a bean, the returned value will contain <jk>null</jk> for the bean metadata and a non-null 162 * string explaining why it's not a bean. 163 * 164 * <h5 class='section'>Parameters:</h5> 165 * <ul class='spaced-list'> 166 * <li><b>cm</b> - The class metadata for the class to create bean metadata for. 167 * <li><b>bf</b> - Optional bean filter to apply. Can be <jk>null</jk>. 168 * <li><b>pNames</b> - Explicit list of property names and order. If <jk>null</jk>, properties are determined automatically. 169 * <li><b>implClassConstructor</b> - Optional constructor to use if one cannot be found. Can be <jk>null</jk>. 170 * </ul> 171 * 172 * <h5 class='section'>Return Value:</h5> 173 * <p> 174 * Returns a {@link BeanMetaValue} containing: 175 * <ul> 176 * <li><b>beanMeta</b> - The bean metadata if the class is a bean, or <jk>null</jk> if it's not. 177 * <li><b>notABeanReason</b> - A string explaining why the class is not a bean, or <jk>null</jk> if it is a bean. 178 * </ul> 179 * 180 * <h5 class='section'>Exception Handling:</h5> 181 * <p> 182 * If a {@link RuntimeException} is thrown during bean metadata creation, it is caught and the exception message 183 * is returned as the <c>notABeanReason</c> with <jk>null</jk> for the bean metadata. 184 * 185 * <h5 class='section'>Example:</h5> 186 * <p class='bjava'> 187 * <jc>// Create bean metadata for a class</jc> 188 * ClassMeta<Person> <jv>cm</jv> = <jv>beanContext</jv>.getClassMeta(Person.<jk>class</jk>); 189 * BeanMetaValue<Person> <jv>result</jv> = BeanMeta.<jsm>create</jsm>(<jv>cm</jv>, <jk>null</jk>, <jk>null</jk>, <jk>null</jk>); 190 * 191 * <jc>// Check if it's a bean</jc> 192 * <jk>if</jk> (<jv>result</jv>.beanMeta() != <jk>null</jk>) { 193 * BeanMeta<Person> <jv>bm</jv> = <jv>result</jv>.beanMeta(); 194 * <jc>// Use the bean metadata...</jc> 195 * } <jk>else</jk> { 196 * String <jv>reason</jv> = <jv>result</jv>.notABeanReason(); 197 * <jc>// Handle the case where it's not a bean...</jc> 198 * } 199 * </p> 200 * 201 * @param <T> The class type. 202 * @param cm The class metadata for the class to create bean metadata for. 203 * @param implClass The implementation class info. 204 * @return A {@link BeanMetaValue} containing the bean metadata (if successful) or a reason why it's not a bean. 205 */ 206 public static <T> BeanMetaValue<T> create(ClassMeta<T> cm, ClassInfo implClass) { 207 try { 208 var bc = cm.getBeanContext(); 209 var ap = bc.getAnnotationProvider(); 210 211 // Sanity checks first. 212 if (bc.isNotABean(cm)) 213 return notABean("Class matches exclude-class list"); 214 215 if (bc.isBeansRequireSerializable() && ! cm.isChildOf(Serializable.class) && ! ap.has(Bean.class, cm)) 216 return notABean("Class is not serializable"); 217 218 if (ap.has(BeanIgnore.class, cm)) 219 return notABean("Class is annotated with @BeanIgnore"); 220 221 if ((! bc.getBeanClassVisibility().isVisible(cm.getModifiers()) || cm.isAnonymousClass()) && ! ap.has(Bean.class, cm)) 222 return notABean("Class is not public"); 223 224 var bm = new BeanMeta<>(cm, findBeanFilter(cm), null, implClass); 225 226 if (nn(bm.notABeanReason)) 227 return notABean(bm.notABeanReason); 228 229 return new BeanMetaValue<>(bm, null); 230 } catch (RuntimeException e) { 231 return new BeanMetaValue<>(null, e.getMessage()); 232 } 233 } 234 235 /* 236 * Extracts the property name from {@link Beanp @Beanp} or {@link Name @Name} annotations. 237 * 238 * <p> 239 * If {@link Name @Name} annotations are present, returns the value from the last one. 240 * Otherwise, searches through {@link Beanp @Beanp} annotations and returns the first non-empty 241 * {@link Beanp#value() value()} or {@link Beanp#name() name()} found. 242 * 243 * @param p List of {@link Beanp @Beanp} annotations. 244 * @param n List of {@link Name @Name} annotations. 245 * @return The property name, or <jk>null</jk> if no name is found. 246 */ 247 private static String bpName(List<Beanp> p, List<Name> n) { 248 if (p.isEmpty() && n.isEmpty()) 249 return null; 250 if (! n.isEmpty()) 251 return last(n).value(); 252 253 var name = Value.of(p.isEmpty() ? null : ""); 254 p.forEach(x -> { 255 if (! x.value().isEmpty()) 256 name.set(x.value()); 257 if (! x.name().isEmpty()) 258 name.set(x.name()); 259 }); 260 261 return name.orElse(null); 262 } 263 264 /* 265 * Finds and creates the bean filter for the specified class metadata. 266 * 267 * <p> 268 * Searches for {@link Bean @Bean} annotations on the class and its parent classes/interfaces. If found, creates a 269 * {@link BeanFilter} that applies the configuration from those annotations. 270 * 271 * <p> 272 * When multiple {@link Bean @Bean} annotations are found (e.g., on a parent class and a child class), they are 273 * applied in reverse order (parent classes first, then child classes). This ensures that child class annotations 274 * override parent class annotations, allowing child classes to customize or extend the bean configuration. 275 * 276 * <p> 277 * The bean filter controls various aspects of bean serialization and parsing, such as: 278 * <ul> 279 * <li>Property inclusion/exclusion lists 280 * <li>Property ordering and sorting 281 * <li>Type name mapping for dictionary lookups 282 * <li>Fluent setter detection 283 * <li>Read-only and write-only property definitions 284 * </ul> 285 * 286 * @param <T> The class type. 287 * @param cm The class metadata to find the filter for. 288 * @return The bean filter, or <jk>null</jk> if no {@link Bean @Bean} annotations are found on the class or its hierarchy. 289 * @see Bean 290 * @see BeanFilter 291 */ 292 private static <T> BeanFilter findBeanFilter(ClassMeta<T> cm) { 293 var ap = cm.getBeanContext().getAnnotationProvider(); 294 var l = ap.find(Bean.class, cm); 295 if (l.isEmpty()) 296 return null; 297 return BeanFilter.create(cm).applyAnnotations(reverse(l.stream().map(AnnotationInfo::inner).toList())).build(); 298 } 299 300 /* 301 * Extracts the property name from a single {@link Beanp @Beanp} or {@link Name @Name} annotation. 302 * 303 * <p> 304 * For {@link Beanp @Beanp} annotations, returns the first non-empty value found in the following order: 305 * <ol> 306 * <li>{@link Beanp#name() name()} 307 * <li>{@link Beanp#value() value()} 308 * </ol> 309 * 310 * <p> 311 * For {@link Name @Name} annotations, returns the {@link Name#value() value()}. 312 * 313 * <p> 314 * This method is used to extract property names from individual annotations, typically when processing 315 * annotation lists in stream operations. 316 * 317 * @param ai The annotation info containing either a {@link Beanp @Beanp} or {@link Name @Name} annotation. 318 * @return The property name extracted from the annotation, or <jk>null</jk> if no name is found. 319 * @see #bpName(List, List) 320 */ 321 private static String name(AnnotationInfo<?> ai) { 322 if (ai.isType(Beanp.class)) { 323 var p = ai.cast(Beanp.class).inner(); 324 if (ne(p.name())) 325 return p.name(); 326 if (ne(p.value())) 327 return p.value(); 328 } else { 329 var n = ai.cast(Name.class).inner(); 330 if (ne(n.value())) 331 return n.value(); 332 } 333 return null; 334 } 335 336 /* 337 * Shortcut for creating a BeanMetaValue with a not-a-bean reason. 338 */ 339 private static <T> BeanMetaValue<T> notABean(String reason) { 340 return new BeanMetaValue<>(null, reason); 341 } 342 343 private final BeanConstructor beanConstructor; // The constructor for this bean. 344 private final BeanContext beanContext; // The bean context that created this metadata object. 345 private final BeanFilter beanFilter; // Optional bean filter associated with the target class. 346 private final OptionalSupplier<InvocationHandler> beanProxyInvocationHandler; // The invocation handler for this bean (if it's an interface). 347 private final Supplier<BeanRegistry> beanRegistry; // The bean registry for this bean. 348 private final Supplier<List<ClassInfo>> classHierarchy; // List of all classes traversed in the class hierarchy. 349 private final ClassMeta<T> classMeta; // The target class type that this meta object describes. 350 private final Supplier<String> dictionaryName; // The @Bean(typeName) annotation defined on this bean class. 351 private final BeanPropertyMeta dynaProperty; // "extras" property. 352 private final boolean fluentSetters; // Whether fluent setters are enabled. 353 private final Map<Method,String> getterProps; // The getter properties on the target class. 354 private final Map<String,BeanPropertyMeta> hiddenProperties; // The hidden properties on the target class. 355 private final ConstructorInfo implClassConstructor; // Optional constructor to use if one cannot be found. 356 private final String notABeanReason; // Readable string explaining why this class wasn't a bean. 357 private final Map<String,BeanPropertyMeta> properties; // The properties on the target class. 358 private final Map<Method,String> setterProps; // The setter properties on the target class. 359 private final boolean sortProperties; // Whether properties should be sorted. 360 private final ClassInfo stopClass; // The stop class for hierarchy traversal. 361 private final BeanPropertyMeta typeProperty; // "_type" mock bean property. 362 private final String typePropertyName; // "_type" property actual name. 363 364 /** 365 * Constructor. 366 * 367 * <p> 368 * Creates a new {@link BeanMeta} instance for the specified class metadata. This constructor performs 369 * introspection to discover all bean properties, methods, and fields in the class hierarchy. 370 * 371 * <p> 372 * The bean metadata is built by: 373 * <ul> 374 * <li>Finding all bean fields in the class hierarchy 375 * <li>Finding all bean methods (getters, setters, extraKeys) in the class hierarchy 376 * <li>Determining the appropriate constructor for bean instantiation 377 * <li>Building the class hierarchy for property discovery 378 * <li>Creating the bean registry for dictionary name resolution 379 * <li>Validating and filtering properties based on bean filter settings 380 * </ul> 381 * 382 * @param cm The class metadata for the bean class. 383 * @param bf Optional bean filter to apply. Can be <jk>null</jk>. 384 * @param pNames Explicit list of property names and order. If <jk>null</jk>, properties are determined automatically. 385 * @param implClass Optional implementation class constructor to use if one cannot be found. Can be <jk>null</jk>. 386 */ 387 protected BeanMeta(ClassMeta<T> cm, BeanFilter bf, String[] pNames, ClassInfo implClass) { 388 classMeta = cm; 389 beanContext = cm.getBeanContext(); 390 beanFilter = bf; 391 implClassConstructor = opt(implClass).map(x -> x.getPublicConstructor(x2 -> x2.hasNumParameters(0)).orElse(null)).orElse(null); 392 fluentSetters = beanContext.isFindFluentSetters() || (nn(bf) && bf.isFluentSetters()); 393 stopClass = opt(bf).map(x -> x.getStopClass()).orElse(info(Object.class)); 394 beanRegistry = mem(()->findBeanRegistry()); 395 classHierarchy = mem(()->findClassHierarchy()); 396 beanConstructor = findBeanConstructor(); 397 398 // Local variables for initialization 399 var ap = beanContext.getAnnotationProvider(); 400 var c = cm.inner(); 401 var ci = cm; 402 var _notABeanReason = (String)null; 403 var _properties = Value.<Map<String,BeanPropertyMeta>>empty(); 404 var _hiddenProperties = CollectionUtils.<String,BeanPropertyMeta>map(); 405 var _getterProps = CollectionUtils.<Method,String>map(); // Convert to MethodInfo keys 406 var _setterProps = CollectionUtils.<Method,String>map(); 407 var _dynaProperty = Value.<BeanPropertyMeta>empty(); 408 var _sortProperties = false; 409 var ba = ap.find(Bean.class, cm); 410 var propertyNamer = opt(bf).map(x -> x.getPropertyNamer()).orElse(beanContext.getPropertyNamer()); 411 412 this.typePropertyName = ba.stream().map(x -> x.inner().typePropertyName()).filter(Utils::ne).findFirst().orElseGet(() -> beanContext.getBeanTypePropertyName()); 413 414 // Check if constructor is required but not found 415 if (! beanConstructor.constructor().isPresent() && bf == null && beanContext.isBeansRequireDefaultConstructor()) 416 _notABeanReason = "Class does not have the required no-arg constructor"; 417 418 var bfo = opt(bf); 419 var fixedBeanProps = bfo.map(x -> x.getProperties()).orElse(sete()); 420 421 try { 422 Map<String,BeanPropertyMeta.Builder> normalProps = map(); // NOAI 423 424 // First populate the properties with those specified in the bean annotation to 425 // ensure that ordering first. 426 fixedBeanProps.forEach(x -> normalProps.put(x, BeanPropertyMeta.builder(this, x))); 427 428 if (beanContext.isUseJavaBeanIntrospector()) { 429 var c2 = bfo.map(x -> x.getInterfaceClass()).filter(Objects::nonNull).orElse(classMeta); 430 var bi = (BeanInfo)null; 431 if (! c2.isInterface()) 432 bi = Introspector.getBeanInfo(c2.inner(), stopClass.inner()); 433 else 434 bi = Introspector.getBeanInfo(c2.inner(), null); 435 if (nn(bi)) { 436 for (var pd : bi.getPropertyDescriptors()) { 437 var builder = normalProps.computeIfAbsent(pd.getName(), n -> BeanPropertyMeta.builder(this, n)); 438 if (pd.getReadMethod() != null) 439 builder.setGetter(info(pd.getReadMethod())); 440 if (pd.getWriteMethod() != null) 441 builder.setSetter(info(pd.getWriteMethod())); 442 } 443 } 444 445 } else /* Use 'better' introspection */ { 446 447 findBeanFields().forEach(x -> { 448 var name = ap.find(x).stream() 449 .filter(x2 -> x2.isType(Beanp.class) || x2.isType(Name.class)) 450 .map(x2 -> name(x2)) 451 .filter(Objects::nonNull) 452 .findFirst() 453 .orElse(propertyNamer.getPropertyName(x.getName())); 454 if (nn(name)) { 455 normalProps.computeIfAbsent(name, n->BeanPropertyMeta.builder(this, n)).setField(x); 456 } 457 }); 458 459 var bms = findBeanMethods(); 460 461 // Iterate through all the getters. 462 bms.forEach(x -> { 463 var pn = x.propertyName; 464 var m = x.method; // TODO - Convert to MethodInfo 465 var mi = info(m); 466 var bpm = normalProps.computeIfAbsent(pn, k -> new BeanPropertyMeta.Builder(this, k)); 467 468 if (x.methodType == GETTER) { 469 // Two getters. Pick the best. 470 if (nn(bpm.getter)) { 471 if (! ap.has(Beanp.class, mi) && ap.has(Beanp.class, bpm.getter)) { 472 m = bpm.getter.inner(); // @Beanp annotated method takes precedence. 473 } else if (m.getName().startsWith("is") && bpm.getter.getSimpleName().startsWith("get")) { 474 m = bpm.getter.inner(); // getX() overrides isX(). 475 } 476 } 477 bpm.setGetter(info(m)); 478 } 479 }); 480 481 // Now iterate through all the setters. 482 bms.stream().filter(x -> eq(x.methodType, SETTER)).forEach(x -> { 483 var bpm = normalProps.get(x.propertyName); 484 if (x.matchesPropertyType(bpm)) 485 bpm.setSetter(info(x.method)); 486 }); 487 488 // Now iterate through all the extraKeys. 489 bms.stream().filter(x -> eq(x.methodType, EXTRAKEYS)).forEach(x -> normalProps.get(x.propertyName).setExtraKeys(info(x.method))); 490 } 491 492 var typeVarImpls = TypeVariables.of(c); 493 494 // Eliminate invalid properties, and set the contents of getterProps and setterProps. 495 var readOnlyProps = bfo.map(x -> x.getReadOnlyProperties()).orElse(sete()); 496 var writeOnlyProps = bfo.map(x -> x.getWriteOnlyProperties()).orElse(sete()); 497 for (var i = normalProps.values().iterator(); i.hasNext();) { 498 var p = i.next(); 499 try { 500 if (p.field == null) 501 findInnerBeanField(p.name).ifPresent(x -> p.setInnerField(x)); 502 503 if (p.validate(beanContext, beanRegistry.get(), typeVarImpls, readOnlyProps, writeOnlyProps)) { 504 505 if (nn(p.getter)) 506 _getterProps.put(p.getter.inner(), p.name); 507 508 if (nn(p.setter)) 509 _setterProps.put(p.setter.inner(), p.name); 510 511 } else { 512 i.remove(); 513 } 514 } catch (ClassNotFoundException e) { 515 throw bex(c, lm(e)); 516 } 517 } 518 519 // Check for missing properties. 520 fixedBeanProps.stream().filter(x -> ! normalProps.containsKey(x)).findFirst().ifPresent(x -> { throw bex(c, "The property ''{0}'' was defined on the @Bean(properties=X) annotation of class ''{1}'' but was not found on the class definition.", x, ci.getNameSimple()); }); 521 522 // Mark constructor arg properties. 523 for (var fp : beanConstructor.args()) { 524 var m = normalProps.get(fp); 525 if (m == null) 526 throw bex(c, "The property ''{0}'' was defined on the @Beanc(properties=X) annotation but was not found on the class definition.", fp); 527 m.setAsConstructorArg(); 528 } 529 530 // Make sure at least one property was found. 531 if (bf == null && beanContext.isBeansRequireSomeProperties() && normalProps.isEmpty()) 532 _notABeanReason = "No properties detected on bean class"; 533 534 _sortProperties = beanContext.isSortProperties() || bfo.map(x -> x.isSortProperties()).orElse(false) && fixedBeanProps.isEmpty(); 535 536 _properties.set(_sortProperties ? sortedMap() : map()); 537 538 normalProps.forEach((k, v) -> { 539 var pMeta = v.build(); 540 if (pMeta.isDyna()) 541 _dynaProperty.set(pMeta); 542 _properties.get().put(k, pMeta); 543 }); 544 545 // If a beanFilter is defined, look for inclusion and exclusion lists. 546 if (bf != null) { 547 548 // Eliminated excluded properties if BeanFilter.excludeKeys is specified. 549 var bfbpi = bf.getProperties(); 550 var bfbpx = bf.getExcludeProperties(); 551 var p = _properties.get(); 552 553 if (! bfbpi.isEmpty()) { 554 // Only include specified properties if BeanFilter.includeKeys is specified. 555 // Note that the order must match includeKeys. 556 Map<String,BeanPropertyMeta> p2 = map(); // NOAI 557 bfbpi.stream().filter(x -> p.containsKey(x)).forEach(x -> p2.put(x, p.remove(x))); 558 _hiddenProperties.putAll(p); 559 _properties.set(p2); 560 } 561 562 bfbpx.forEach(x -> _hiddenProperties.put(x, _properties.get().remove(x))); 563 } 564 565 if (nn(pNames)) { 566 var p = _properties.get(); 567 Map<String,BeanPropertyMeta> p2 = map(); 568 for (var k : pNames) { 569 if (p.containsKey(k)) 570 p2.put(k, p.get(k)); 571 else 572 _hiddenProperties.put(k, p.get(k)); 573 } 574 _properties.set(p2); 575 } 576 577 } catch (BeanRuntimeException e) { 578 throw e; 579 } catch (Exception e) { 580 _notABeanReason = "Exception: " + getStackTrace(e); 581 } 582 583 notABeanReason = _notABeanReason; 584 properties = u(_properties.get()); 585 hiddenProperties = u(_hiddenProperties); 586 getterProps = u(_getterProps); 587 setterProps = u(_setterProps); 588 dynaProperty = _dynaProperty.get(); 589 sortProperties = _sortProperties; 590 typeProperty = BeanPropertyMeta.builder(this, typePropertyName).canRead().canWrite().rawMetaType(beanContext.string()).beanRegistry(beanRegistry.get()).build(); 591 dictionaryName = mem(()->findDictionaryName()); 592 beanProxyInvocationHandler = mem(()->beanContext.isUseInterfaceProxies() && c.isInterface() ? new BeanProxyInvocationHandler<>(this) : null); 593 } 594 595 @Override /* Overridden from Object */ 596 public boolean equals(Object o) { 597 return (o instanceof BeanMeta<?> o2) && eq(this, o2, (x, y) -> eq(x.classMeta, y.classMeta)); 598 } 599 600 /** 601 * Returns the bean filter associated with this bean. 602 * 603 * <p> 604 * Bean filters are used to control aspects of how beans are handled during serialization and parsing, such as 605 * property inclusion/exclusion, property ordering, and type name mapping. 606 * 607 * <p> 608 * The bean filter is typically created from the {@link Bean @Bean} annotation on the class. If no {@link Bean @Bean} 609 * annotation is present, this method returns <jk>null</jk>. 610 * 611 * @return The bean filter for this bean, or <jk>null</jk> if no bean filter is associated with this bean. 612 * @see Bean 613 */ 614 public BeanFilter getBeanFilter() { 615 return beanFilter; 616 } 617 618 /** 619 * Returns the proxy invocation handler for this bean if it's an interface. 620 * 621 * @return The invocation handler, or <jk>null</jk> if this is not an interface or interface proxies are disabled. 622 */ 623 public InvocationHandler getBeanProxyInvocationHandler() { 624 return beanProxyInvocationHandler.get(); 625 } 626 627 /** 628 * Returns the bean registry for this bean. 629 * 630 * <p> 631 * The bean registry is used to resolve dictionary names to class types. It's created when a bean class has a 632 * {@link Bean#dictionary() @Bean(dictionary)} annotation that specifies a list of possible subclasses. 633 * 634 * @return The bean registry for this bean, or <jk>null</jk> if no bean registry is associated with it. 635 */ 636 public BeanRegistry getBeanRegistry() { return beanRegistry.get(); } 637 638 /** 639 * Returns the {@link ClassMeta} of this bean. 640 * 641 * @return The {@link ClassMeta} of this bean. 642 */ 643 public ClassMeta<T> getClassMeta() { return classMeta; } 644 645 /** 646 * Returns the dictionary name for this bean as defined through the {@link Bean#typeName() @Bean(typeName)} annotation. 647 * 648 * @return The dictionary name for this bean, or <jk>null</jk> if it has no dictionary name defined. 649 */ 650 public String getDictionaryName() { return dictionaryName.get(); } 651 652 /** 653 * Returns a map of all properties on this bean. 654 * 655 * <p> 656 * The map is keyed by property name and contains {@link BeanPropertyMeta} objects that provide metadata about each 657 * property, including its type, getter/setter methods, field information, and serialization/parsing behavior. 658 * 659 * <p> 660 * This map contains only the normal (non-hidden) properties of the bean. Hidden properties can be accessed via 661 * {@link #getHiddenProperties()}. 662 * 663 * @return A map of property names to their metadata. The map is unmodifiable. 664 * @see #getPropertyMeta(String) 665 * @see #getHiddenProperties() 666 */ 667 public Map<String,BeanPropertyMeta> getProperties() { return properties; } 668 669 /** 670 * Returns metadata about the specified property. 671 * 672 * @param name The name of the property on this bean. 673 * @return The metadata about the property, or <jk>null</jk> if no such property exists on this bean. 674 */ 675 public BeanPropertyMeta getPropertyMeta(String name) { 676 var bpm = properties.get(name); 677 if (bpm == null) 678 bpm = hiddenProperties.get(name); 679 if (bpm == null) 680 bpm = dynaProperty; 681 return bpm; 682 } 683 684 /** 685 * Returns a mock bean property that resolves to the name <js>"_type"</js> and whose value always resolves to the 686 * dictionary name of the bean. 687 * 688 * @return The type name property. 689 */ 690 public BeanPropertyMeta getTypeProperty() { return typeProperty; } 691 692 /** 693 * Returns the type property name for this bean. 694 * 695 * <p> 696 * This is the name of the bean property used to store the dictionary name of a bean type so that the parser knows 697 * the data type to reconstruct. 698 * 699 * <p> 700 * If <jk>null</jk>, <js>"_type"</js> should be assumed. 701 * 702 * <p> 703 * The value is determined from: 704 * <ul> 705 * <li>The {@link Bean#typePropertyName() @Bean(typePropertyName)} annotation on the class, if present. 706 * <li>Otherwise, the default value from {@link BeanContext#getBeanTypePropertyName()}. 707 * </ul> 708 * 709 * @return 710 * The type property name associated with this bean, or <jk>null</jk> if the default <js>"_type"</js> should be used. 711 * @see BeanContext#getBeanTypePropertyName() 712 */ 713 public String getTypePropertyName() { return typePropertyName; } 714 715 @Override /* Overridden from Object */ 716 public int hashCode() { 717 return classMeta.hashCode(); 718 } 719 720 /** 721 * Property read interceptor. 722 * 723 * <p> 724 * Called immediately after calling the getter to allow the value to be overridden. 725 * 726 * @param bean The bean from which the property was read. 727 * @param name The property name. 728 * @param value The value just extracted from calling the bean getter. 729 * @return The value to serialize. Default is just to return the existing value. 730 */ 731 public Object onReadProperty(Object bean, String name, Object value) { 732 return beanFilter == null ? value : beanFilter.readProperty(bean, name, value); 733 } 734 735 /** 736 * Property write interceptor. 737 * 738 * <p> 739 * Called immediately before calling theh setter to allow value to be overwridden. 740 * 741 * @param bean The bean from which the property was read. 742 * @param name The property name. 743 * @param value The value just parsed. 744 * @return The value to serialize. Default is just to return the existing value. 745 */ 746 public Object onWriteProperty(Object bean, String name, Object value) { 747 return beanFilter == null ? value : beanFilter.writeProperty(bean, name, value); 748 } 749 750 protected FluentMap<String,Object> properties() { 751 // @formatter:off 752 return filteredBeanPropertyMap() 753 .a("class", classMeta.getName()) 754 .a("properties", properties); 755 // @formatter:on 756 } 757 758 @Override /* Overridden from Object */ 759 public String toString() { 760 return r(properties()); 761 } 762 763 /** 764 * Returns the bean context that created this metadata object. 765 * 766 * @return The bean context. 767 */ 768 protected BeanContext getBeanContext() { return beanContext; } 769 770 /** 771 * Returns the constructor for this bean, if one was found. 772 * 773 * <p> 774 * The constructor is determined by {@link #findBeanConstructor()} and may be: 775 * <ul> 776 * <li>A constructor annotated with {@link Beanc @Beanc} 777 * <li>An implementation class constructor (if provided) 778 * <li>A no-argument constructor 779 * <li><jk>null</jk> if no suitable constructor was found 780 * </ul> 781 * 782 * @return The constructor for this bean, or <jk>null</jk> if no constructor is available. 783 * @see #getConstructorArgs() 784 * @see #hasConstructor() 785 */ 786 protected ConstructorInfo getConstructor() { 787 return beanConstructor.constructor().orElse(null); 788 } 789 790 /** 791 * Returns the list of property names that correspond to the constructor parameters. 792 * 793 * <p> 794 * The property names are in the same order as the constructor parameters. These names are used to map 795 * parsed property values to constructor arguments when creating bean instances. 796 * 797 * <p> 798 * The property names are determined from: 799 * <ul> 800 * <li>The {@link Beanc#properties() properties()} value in the {@link Beanc @Beanc} annotation (if present) 801 * <li>Otherwise, the parameter names from the constructor (if available in bytecode) 802 * </ul> 803 * 804 * <p> 805 * If the bean has no constructor or uses a no-argument constructor, this list will be empty. 806 * 807 * @return A list of property names corresponding to constructor parameters, in parameter order. 808 * @see #getConstructor() 809 * @see #hasConstructor() 810 */ 811 protected List<String> getConstructorArgs() { 812 return beanConstructor.args(); 813 } 814 815 /** 816 * Returns the "extras" property for dynamic bean properties. 817 * 818 * @return The dynamic property, or <jk>null</jk> if not present. 819 */ 820 protected BeanPropertyMeta getDynaProperty() { return dynaProperty; } 821 822 /** 823 * Returns the map of getter methods to property names. 824 * 825 * @return The getter properties map. 826 */ 827 protected Map<Method,String> getGetterProps() { return getterProps; } 828 829 /** 830 * Returns the map of hidden properties on this bean. 831 * 832 * <p> 833 * Hidden properties are properties that exist on the bean but are not included in the normal property list. 834 * These properties are typically excluded from serialization but may still be accessible programmatically. 835 * 836 * <p> 837 * Hidden properties can be defined through: 838 * <ul> 839 * <li>{@link BeanFilter#getExcludeProperties() Bean filter exclude properties} 840 * <li>Properties that fail validation during bean metadata creation 841 * </ul> 842 * 843 * @return A map of hidden property names to their metadata. The map is unmodifiable. 844 * @see #getProperties() 845 */ 846 protected Map<String,BeanPropertyMeta> getHiddenProperties() { 847 return hiddenProperties; 848 } 849 850 /** 851 * Returns the map of setter methods to property names. 852 * 853 * @return The setter properties map. 854 */ 855 protected Map<Method,String> getSetterProps() { return setterProps; } 856 857 /** 858 * Returns whether this bean has a constructor available for instantiation. 859 * 860 * <p> 861 * A bean has a constructor if {@link #findBeanConstructor()} was able to find a suitable constructor, 862 * which may be: 863 * <ul> 864 * <li>A constructor annotated with {@link Beanc @Beanc} 865 * <li>An implementation class constructor (if provided) 866 * <li>A no-argument constructor 867 * </ul> 868 * 869 * <p> 870 * If this method returns <jk>false</jk>, the bean cannot be instantiated using {@link #newBean(Object)}, 871 * and may need to be created through other means (e.g., interface proxies). 872 * 873 * @return <jk>true</jk> if a constructor is available, <jk>false</jk> otherwise. 874 * @see #getConstructor() 875 * @see #newBean(Object) 876 */ 877 protected boolean hasConstructor() { 878 return beanConstructor.constructor().isPresent(); 879 } 880 881 /** 882 * Returns whether properties should be sorted for this bean. 883 * 884 * @return <jk>true</jk> if properties should be sorted. 885 */ 886 protected boolean isSortProperties() { return sortProperties; } 887 888 /** 889 * Creates a new instance of this bean. 890 * 891 * @param outer The outer object if bean class is a non-static inner member class. 892 * @return A new instance of this bean if possible, or <jk>null</jk> if not. 893 * @throws ExecutableException Exception occurred on invoked constructor/method/field. 894 */ 895 @SuppressWarnings("unchecked") 896 protected T newBean(Object outer) throws ExecutableException { 897 if (classMeta.isMemberClass() && classMeta.isNotStatic()) { 898 if (hasConstructor()) 899 return getConstructor().<T>newInstance(outer); 900 } else { 901 if (hasConstructor()) 902 return getConstructor().<T>newInstance(); 903 var h = classMeta.getProxyInvocationHandler(); 904 if (nn(h)) { 905 var cl = classMeta.getClassLoader(); 906 return (T)Proxy.newProxyInstance(cl, a(classMeta.inner(), java.io.Serializable.class), h); 907 } 908 } 909 return null; 910 } 911 912 /* 913 * Finds the appropriate constructor for this bean and determines the property names for constructor arguments. 914 * 915 * <p> 916 * This method searches for a constructor in the following order of precedence: 917 * <ol> 918 * <li><b>{@link Beanc @Beanc} annotated constructor:</b> If a constructor is annotated with {@link Beanc @Beanc}, 919 * it is used. The property names are determined from: 920 * <ul> 921 * <li>The {@link Beanc#properties() properties()} value in the annotation, if specified 922 * <li>Otherwise, the parameter names from the constructor (if available in bytecode) 923 * </ul> 924 * If multiple constructors are annotated with {@link Beanc @Beanc}, an exception is thrown. 925 * <li><b>Implementation class constructor:</b> If an {@link #implClassConstructor} was provided during bean 926 * metadata creation, it is used with an empty property list. 927 * <li><b>No-arg constructor:</b> Searches for a no-argument constructor. The visibility required depends on 928 * whether the class has a {@link Bean @Bean} annotation: 929 * <ul> 930 * <li>If {@link Bean @Bean} is present, private constructors are allowed 931 * <li>Otherwise, the visibility is determined by {@link BeanContext#getBeanConstructorVisibility()} 932 * </ul> 933 * <li><b>No constructor:</b> Returns an empty {@link Optional} if no suitable constructor is found. 934 * </ol> 935 * 936 * <p> 937 * The returned {@link BeanConstructor} contains: 938 * <ul> 939 * <li>The constructor (if found), wrapped in an {@link Optional} 940 * <li>A list of property names that correspond to the constructor parameters, in order 941 * </ul> 942 * 943 * @return A {@link BeanConstructor} containing the found constructor and its associated property names. 944 * @throws BeanRuntimeException If multiple constructors are annotated with {@link Beanc @Beanc}, or if 945 * the number of properties specified in {@link Beanc @Beanc} doesn't match the number of constructor parameters, 946 * or if parameter names cannot be determined from the bytecode. 947 */ 948 private BeanConstructor findBeanConstructor() { 949 var ap = beanContext.getAnnotationProvider(); 950 var vis = beanContext.getBeanConstructorVisibility(); 951 var ci = classMeta; 952 953 var l = ci.getPublicConstructors().stream().filter(x -> ap.has(Beanc.class, x)).toList(); 954 if (l.isEmpty()) 955 l = ci.getDeclaredConstructors().stream().filter(x -> ap.has(Beanc.class, x)).toList(); 956 if (l.size() > 1) 957 throw bex(ci, "Multiple instances of '@Beanc' found."); 958 if (l.size() == 1) { 959 var con = l.get(0).accessible(); 960 var args = ap.find(Beanc.class, con).stream().map(x -> x.inner().properties()).filter(StringUtils::isNotBlank).map(x -> split(x)).findFirst().orElse(liste()); 961 if (! con.hasNumParameters(args.size())) { 962 if (ne(args)) 963 throw bex(ci, "Number of properties defined in '@Beanc' annotation does not match number of parameters in constructor."); 964 args = con.getParameters().stream().map(x -> x.getName()).toList(); 965 for (int i = 0; i < args.size(); i++) { 966 if (isBlank(args.get(i))) 967 throw bex(ci, "Could not find name for parameter #{0} of constructor ''{1}''", i, con.getFullName()); 968 } 969 } 970 return new BeanConstructor(opt(con), args); 971 } 972 973 if (implClassConstructor != null) 974 return new BeanConstructor(opt(implClassConstructor.accessible()), liste()); 975 976 var ba = ap.find(Bean.class, classMeta); 977 var con = ci.getNoArgConstructor(! ba.isEmpty() ? Visibility.PRIVATE : vis).orElse(null); 978 if (con != null) 979 return new BeanConstructor(opt(con.accessible()), liste()); 980 981 return new BeanConstructor(opte(), liste()); 982 } 983 984 /* 985 * Finds all bean fields in the class hierarchy. 986 * 987 * <p> 988 * Traverses the complete class hierarchy (as defined by {@link #classHierarchy}) and collects all fields that 989 * meet the bean field criteria: 990 * <ul> 991 * <li>Not static 992 * <li>Not transient (unless transient fields are not ignored) 993 * <li>Not annotated with {@link Transient @Transient} (unless transient fields are not ignored) 994 * <li>Not annotated with {@link BeanIgnore @BeanIgnore} 995 * <li>Visible according to the specified visibility level, or annotated with {@link Beanp @Beanp} 996 * </ul> 997 * 998 * @return A collection of all bean fields found in the class hierarchy. 999 */ 1000 private Collection<FieldInfo> findBeanFields() { 1001 var v = beanContext.getBeanFieldVisibility(); 1002 var noIgnoreTransients = ! beanContext.isIgnoreTransientFields(); 1003 var ap = beanContext.getAnnotationProvider(); 1004 // @formatter:off 1005 return classHierarchy.get().stream() 1006 .flatMap(c2 -> c2.getDeclaredFields().stream()) 1007 .filter(x -> x.isNotStatic() 1008 && (x.isNotTransient() || noIgnoreTransients) 1009 && (! x.hasAnnotation(Transient.class) || noIgnoreTransients) 1010 && ! ap.has(BeanIgnore.class, x) 1011 && (v.isVisible(x.inner()) || ap.has(Beanp.class, x))) 1012 .toList(); 1013 // @formatter:on 1014 } 1015 1016 /* 1017 * Finds all bean methods (getters, setters, and extraKeys) in the class hierarchy. 1018 * 1019 * <p> 1020 * Traverses the complete class hierarchy (as defined by {@link #classHierarchy}) and identifies methods that 1021 * represent bean properties. Methods are identified as: 1022 * <ul> 1023 * <li><b>Getters:</b> Methods with no parameters that return a value, matching patterns like: 1024 * <ul> 1025 * <li><c>getX()</c> - standard getter pattern 1026 * <li><c>isX()</c> - boolean getter pattern (returns boolean or Boolean) 1027 * <li>Methods annotated with {@link Beanp @Beanp} or {@link Name @Name} 1028 * <li>Methods with {@link Beanp @Beanp} annotation with value <js>"*"</js> that return a Map 1029 * </ul> 1030 * <li><b>Setters:</b> Methods with one parameter that match patterns like: 1031 * <ul> 1032 * <li><c>setX(value)</c> - standard setter pattern 1033 * <li><c>withX(value)</c> - fluent setter pattern (returns the bean type) 1034 * <li>Methods annotated with {@link Beanp @Beanp} or {@link Name @Name} 1035 * <li>Methods with {@link Beanp @Beanp} annotation with value <js>"*"</js> that accept a Map 1036 * <li>Fluent setters (if enabled) - methods that return the bean type and accept one parameter 1037 * </ul> 1038 * <li><b>ExtraKeys:</b> Methods with {@link Beanp @Beanp} annotation with value <js>"*"</js> that return a Collection 1039 * </ul> 1040 * 1041 * <p> 1042 * Methods are filtered based on: 1043 * <ul> 1044 * <li>Not static, not bridge methods 1045 * <li>Parameter count ≤ 2 1046 * <li>Not annotated with {@link BeanIgnore @BeanIgnore} 1047 * <li>Not annotated with {@link Transient @Transient} 1048 * <li>Visible according to the specified visibility level, or annotated with {@link Beanp @Beanp} or {@link Name @Name} 1049 * </ul> 1050 * 1051 * <p> 1052 * Property names are determined from: 1053 * <ul> 1054 * <li>{@link Beanp @Beanp} or {@link Name @Name} annotations (if present) 1055 * <li>Otherwise, derived from the method name using the provided {@link PropertyNamer} 1056 * </ul> 1057 * 1058 * @return A list of {@link BeanMethod} objects representing all found bean methods. 1059 */ 1060 private List<BeanMethod> findBeanMethods() { 1061 var l = new LinkedList<BeanMethod>(); 1062 var ap = beanContext.getAnnotationProvider(); 1063 var ci = classMeta; 1064 var v = beanContext.getBeanMethodVisibility(); 1065 var pn = opt(beanFilter).map(x -> x.getPropertyNamer()).orElse(beanContext.getPropertyNamer()); 1066 1067 classHierarchy.get().stream().forEach(c2 -> { 1068 for (var m : c2.getDeclaredMethods()) { 1069 1070 if (m.isStatic() || m.isBridge() || m.getParameterCount() > 2) 1071 continue; 1072 1073 var mm = m.getMatchingMethods(); 1074 1075 if (mm.stream().anyMatch(m2 -> ap.has(BeanIgnore.class, m2, SELF))) 1076 continue; 1077 1078 if (mm.stream().anyMatch(m2 -> ap.find(Transient.class, m2, SELF).stream().map(x -> x.inner().value()).findFirst().orElse(false))) 1079 continue; 1080 1081 var beanps = ap.find(Beanp.class, m).stream().map(AnnotationInfo::inner).toList(); 1082 var names = ap.find(Name.class, m).stream().map(AnnotationInfo::inner).toList(); 1083 1084 if (! (m.isVisible(v) || ne(beanps) || ne(names))) 1085 continue; 1086 1087 var n = m.getSimpleName(); 1088 1089 var params = m.getParameters(); 1090 var rt = m.getReturnType(); 1091 var methodType = UNKNOWN; 1092 var bpName = bpName(beanps, names); 1093 1094 if (params.isEmpty()) { 1095 if ("*".equals(bpName)) { 1096 if (rt.isChildOf(Collection.class)) { 1097 methodType = EXTRAKEYS; 1098 } else if (rt.isChildOf(Map.class)) { 1099 methodType = GETTER; 1100 } 1101 n = bpName; 1102 } else if (n.startsWith("get") && (! rt.is(Void.TYPE))) { 1103 methodType = GETTER; 1104 n = n.substring(3); 1105 } else if (n.startsWith("is") && (rt.is(Boolean.TYPE) || rt.is(Boolean.class))) { 1106 methodType = GETTER; 1107 n = n.substring(2); 1108 } else if (nn(bpName)) { 1109 methodType = GETTER; 1110 if (bpName.isEmpty()) { 1111 if (n.startsWith("get")) 1112 n = n.substring(3); 1113 else if (n.startsWith("is")) 1114 n = n.substring(2); 1115 bpName = n; 1116 } else { 1117 n = bpName; 1118 } 1119 } 1120 } else if (params.size() == 1) { 1121 if ("*".equals(bpName)) { 1122 if (params.get(0).getParameterType().isChildOf(Map.class)) { 1123 methodType = SETTER; 1124 n = bpName; 1125 } else if (params.get(0).getParameterType().is(String.class)) { 1126 methodType = GETTER; 1127 n = bpName; 1128 } 1129 } else if (n.startsWith("set") && (rt.isParentOf(ci) || rt.is(Void.TYPE))) { 1130 methodType = SETTER; 1131 n = n.substring(3); 1132 } else if (n.startsWith("with") && (rt.isParentOf(ci))) { 1133 methodType = SETTER; 1134 n = n.substring(4); 1135 } else if (nn(bpName)) { 1136 methodType = SETTER; 1137 if (bpName.isEmpty()) { 1138 if (n.startsWith("set")) 1139 n = n.substring(3); 1140 bpName = n; 1141 } else { 1142 n = bpName; 1143 } 1144 } else if (fluentSetters && rt.isParentOf(ci)) { 1145 methodType = SETTER; 1146 } 1147 } else if (params.size() == 2) { 1148 if ("*".equals(bpName) && params.get(0).getParameterType().is(String.class)) { 1149 if (n.startsWith("set") && (rt.isParentOf(ci) || rt.is(Void.TYPE))) { 1150 methodType = SETTER; 1151 } else { 1152 methodType = GETTER; 1153 } 1154 n = bpName; 1155 } 1156 } 1157 n = pn.getPropertyName(n); 1158 1159 if ("*".equals(bpName) && methodType == UNKNOWN) 1160 throw bex(ci, "Found @Beanp(\"*\") but could not determine method type on method ''{0}''.", m.getSimpleName()); 1161 1162 if (methodType != UNKNOWN) { 1163 if (nn(bpName) && ! bpName.isEmpty()) 1164 n = bpName; 1165 if (nn(n)) 1166 l.add(new BeanMethod(n, methodType, m.inner())); 1167 } 1168 } 1169 }); 1170 return l; 1171 } 1172 1173 /* 1174 * Creates a bean registry for this bean class. 1175 * 1176 * <p> 1177 * The bean registry is used to resolve dictionary names (type names) to actual class types. This is essential 1178 * for polymorphic bean serialization and parsing, where a dictionary name in the serialized form needs to be 1179 * mapped back to the correct subclass. 1180 * 1181 * <p> 1182 * The registry is built from: 1183 * <ul> 1184 * <li><b>Bean filter dictionary:</b> Classes specified in the {@link BeanFilter#getBeanDictionary() bean filter's dictionary} 1185 * (if a bean filter is present) 1186 * <li><b>{@link Bean @Bean} annotation:</b> If the class has a {@link Bean @Bean} annotation with a non-empty 1187 * {@link Bean#typeName() typeName()}, the class itself is added to the dictionary 1188 * </ul> 1189 * 1190 * <p> 1191 * The registry is used by parsers to determine which class to instantiate when deserializing polymorphic beans. 1192 * 1193 * @return A new {@link BeanRegistry} containing the dictionary classes for this bean, or an empty registry if 1194 * no dictionary classes are found. 1195 */ 1196 private BeanRegistry findBeanRegistry() { 1197 // Bean dictionary on bean filter. 1198 var beanDictionaryClasses = opt(beanFilter).map(x -> new ArrayList<>(x.getBeanDictionary())).orElse(new ArrayList<>()); 1199 1200 // Bean dictionary from @Bean(typeName) annotation. 1201 var ba = beanContext.getAnnotationProvider().find(Bean.class, classMeta); 1202 ba.stream().map(x -> x.inner().typeName()).filter(Utils::ne).findFirst().ifPresent(x -> beanDictionaryClasses.add(classMeta)); 1203 1204 return new BeanRegistry(beanContext, null, beanDictionaryClasses); 1205 } 1206 1207 /* 1208 * Builds a list of all classes in the class hierarchy for this bean. 1209 * 1210 * <p> 1211 * Traverses the complete inheritance hierarchy (classes and interfaces) starting from the bean class (or the 1212 * interface class specified in the bean filter) and collects all classes up to (but not including) the stop class. 1213 * 1214 * <p> 1215 * The traversal order follows a depth-first approach: 1216 * <ol> 1217 * <li>First, recursively traverses the superclass hierarchy 1218 * <li>Then, recursively traverses all implemented interfaces 1219 * <li>Finally, adds the current class itself 1220 * </ol> 1221 * 1222 * <p> 1223 * If a {@link BeanFilter#getInterfaceClass() bean filter interface class} is specified, the traversal starts 1224 * from that interface class instead of the bean class itself. This allows beans to use properties defined on 1225 * a parent interface rather than the concrete implementation class. 1226 * 1227 * <p> 1228 * The resulting list is used to find bean properties, methods, and fields across the entire class hierarchy. 1229 * 1230 * @return An unmodifiable list of {@link ClassInfo} objects representing all classes in the hierarchy, in 1231 * traversal order (superclasses first, then interfaces, then the class itself). 1232 */ 1233 private List<ClassInfo> findClassHierarchy() { 1234 var result = new LinkedList<ClassInfo>(); 1235 // If @Bean.interfaceClass is specified on the parent class, then we want 1236 // to use the properties defined on that class, not the subclass. 1237 var c2 = (nn(beanFilter) && nn(beanFilter.getInterfaceClass()) ? beanFilter.getInterfaceClass() : classMeta); 1238 findClassHierarchy(c2, stopClass, result::add); 1239 return u(result); 1240 } 1241 1242 /* 1243 * Recursively traverses the class hierarchy and invokes the consumer for each class found. 1244 * 1245 * <p> 1246 * This is a helper method that performs a depth-first traversal of the class hierarchy: 1247 * <ol> 1248 * <li>Recursively processes the superclass (if present and not the stop class) 1249 * <li>Recursively processes all implemented interfaces 1250 * <li>Invokes the consumer with the current class 1251 * </ol> 1252 * 1253 * <p> 1254 * The traversal stops when it reaches the stop class (which is not included in the traversal). 1255 * 1256 * @param c The class to start traversal from. 1257 * @param stopClass The class to stop traversal at (exclusive). Traversal will not proceed beyond this class. 1258 * @param consumer The consumer to invoke for each class in the hierarchy. 1259 */ 1260 private void findClassHierarchy(ClassInfo c, ClassInfo stopClass, Consumer<ClassInfo> consumer) { 1261 var sc = c.getSuperclass(); 1262 if (nn(sc) && ! sc.is(stopClass.inner())) 1263 findClassHierarchy(sc, stopClass, consumer); 1264 c.getInterfaces().forEach(x -> findClassHierarchy(x, stopClass, consumer)); 1265 consumer.accept(c); 1266 } 1267 1268 /* 1269 * Finds the dictionary name (type name) for this bean class. 1270 * 1271 * <p> 1272 * The dictionary name is used in serialized forms to identify the specific type of a bean instance, enabling 1273 * polymorphic serialization and deserialization. This is especially important when serializing/parsing beans 1274 * that are part of an inheritance hierarchy. 1275 * 1276 * <p> 1277 * The dictionary name is determined by searching in the following order of precedence: 1278 * <ol> 1279 * <li><b>Bean filter type name:</b> If a bean filter is present and has a type name specified via 1280 * {@link BeanFilter#getTypeName()}, that value is used. 1281 * <li><b>Bean registry lookup:</b> If a bean registry exists for this bean, it is queried for the type name 1282 * of this class. 1283 * <li><b>Parent class registry lookup:</b> Searches through parent classes and interfaces (starting from the 1284 * second one, skipping the class itself) and checks if any of their bean registries contain a type name 1285 * for this class. 1286 * <li><b>{@link Bean @Bean} annotation:</b> If the class has a {@link Bean @Bean} annotation with a non-empty 1287 * {@link Bean#typeName() typeName()}, that value is used. 1288 * <li><b>No dictionary name:</b> Returns <jk>null</jk> if no dictionary name is found. 1289 * </ol> 1290 * 1291 * <p> 1292 * If a dictionary name is found, it will be used in serialized output (typically as a special property like 1293 * <js>"_type"</js>) so that parsers can determine the correct class to instantiate when deserializing. 1294 * 1295 * @return The dictionary name for this bean, or <jk>null</jk> if no dictionary name is defined. 1296 */ 1297 private String findDictionaryName() { 1298 if (nn(beanFilter) && nn(beanFilter.getTypeName())) 1299 return beanFilter.getTypeName(); 1300 1301 var br = getBeanRegistry(); 1302 if (nn(br)) { 1303 String s = br.getTypeName(this.classMeta); 1304 if (nn(s)) 1305 return s; 1306 } 1307 1308 var n = classMeta 1309 .getParentsAndInterfaces() 1310 .stream() 1311 .skip(1) 1312 .map(x -> beanContext.getClassMeta(x)) 1313 .map(x -> x.getBeanRegistry()) 1314 .filter(Objects::nonNull) 1315 .map(x -> x.getTypeName(this.classMeta)) 1316 .filter(Objects::nonNull) 1317 .findFirst() 1318 .orElse(null); 1319 1320 if (n != null) 1321 return n; 1322 1323 return classMeta.getBeanContext().getAnnotationProvider().find(Bean.class, classMeta) 1324 .stream() 1325 .map(AnnotationInfo::inner) 1326 .filter(x -> ! x.typeName().isEmpty()) 1327 .map(x -> x.typeName()) 1328 .findFirst() 1329 .orElse(null); 1330 } 1331 1332 /* 1333 * Finds a bean field by name in the class hierarchy. 1334 * 1335 * <p> 1336 * Searches through the complete class hierarchy (as defined by {@link #classHierarchy}) to find a field 1337 * with the specified name. The search is performed in the order classes appear in the hierarchy, and the 1338 * first matching field is returned. 1339 * 1340 * <p> 1341 * A field is considered a match if it: 1342 * <ul> 1343 * <li>Has the exact name specified 1344 * <li>Is not static 1345 * <li>Is not transient (unless transient fields are not ignored) 1346 * <li>Is not annotated with {@link Transient @Transient} (unless transient fields are not ignored) 1347 * <li>Is not annotated with {@link BeanIgnore @BeanIgnore} 1348 * </ul> 1349 * 1350 * <p> 1351 * This method is used to find fields for bean properties when a field reference is needed but wasn't 1352 * discovered during the initial property discovery phase (e.g., for properties defined only through 1353 * getters/setters). 1354 * 1355 * @param name The name of the field to find. 1356 * @return The {@link FieldInfo} for the field if found, or <jk>null</jk> if no matching field exists 1357 * in the class hierarchy. 1358 */ 1359 private Optional<FieldInfo> findInnerBeanField(String name) { 1360 var noIgnoreTransients = ! beanContext.isIgnoreTransientFields(); 1361 var ap = beanContext.getAnnotationProvider(); 1362 1363 // @formatter:off 1364 return classHierarchy.get().stream() 1365 .flatMap(c2 -> c2.getDeclaredField( 1366 x -> x.isNotStatic() 1367 && (x.isNotTransient() || noIgnoreTransients) 1368 && (! x.hasAnnotation(Transient.class) || noIgnoreTransients) 1369 && ! ap.has(BeanIgnore.class, x) 1370 && x.hasName(name) 1371 ).stream()) 1372 .findFirst(); 1373 // @formatter:on 1374 } 1375}