This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new 5b99505646 Marshall module improvements
5b99505646 is described below

commit 5b99505646fbc60ae6c52aec73cceaa0179010e8
Author: James Bognar <[email protected]>
AuthorDate: Tue Dec 9 16:52:20 2025 -0500

    Marshall module improvements
---
 .../src/main/java/org/apache/juneau/BeanMeta.java  | 906 +++++++++++----------
 .../java/org/apache/juneau/BeanPropertyMeta.java   |   5 +
 2 files changed, 474 insertions(+), 437 deletions(-)

diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java
index 8b46440fac..88a1337dfb 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java
@@ -23,7 +23,6 @@ import static 
org.apache.juneau.commons.utils.CollectionUtils.*;
 import static org.apache.juneau.commons.utils.StringUtils.*;
 import static org.apache.juneau.commons.utils.ThrowableUtils.*;
 import static org.apache.juneau.commons.utils.Utils.*;
-import static org.apache.juneau.BeanMeta.MethodType.*;
 
 import java.beans.*;
 import java.io.*;
@@ -68,6 +67,63 @@ import org.apache.juneau.commons.utils.*;
  */
 public class BeanMeta<T> {
 
+       /*
+        * Temporary getter/setter method struct.
+        */
+       private static class BeanMethod {
+               String propertyName;
+               MethodType methodType;
+               Method method;
+               ClassInfo type;
+
+               BeanMethod(String propertyName, MethodType type, Method method) 
{
+                       this.propertyName = propertyName;
+                       this.methodType = type;
+                       this.method = method;
+                       this.type = info(type == SETTER ? 
method.getParameterTypes()[0] : method.getReturnType());
+               }
+
+               @Override /* Overridden from Object */
+               public String toString() {
+                       return method.toString();
+               }
+
+               /*
+                * Returns true if this method matches the class type of the 
specified property.
+                * Only meant to be used for setters.
+                */
+               boolean matchesPropertyType(BeanPropertyMeta.Builder b) {
+                       if (b == null)
+                               return false;
+
+                       // Don't do further validation if this is the "*" bean 
property.
+                       if ("*".equals(b.name))
+                               return true;
+
+                       // Get the bean property type from the getter/field.
+                       var pt = (Class<?>)null;
+                       if (nn(b.getter))
+                               pt = b.getter.getReturnType();
+                       else if (nn(b.field))
+                               pt = b.field.getType();
+
+                       // Matches if only a setter is defined.
+                       if (pt == null)
+                               return true;
+
+                       // Doesn't match if not same type or super type as 
getter/field.
+                       if (! type.isParentOf(pt))
+                               return false;
+
+                       // If a setter was previously set, only use this setter 
if it's a closer
+                       // match (e.g. prev type is a superclass of this type).
+                       if (b.setter == null)
+                               return true;
+
+                       return 
type.isStrictChildOf(b.setter.getParameterTypes()[0]);
+               }
+       }
+
        /**
         * Represents the result of creating a BeanMeta, including the bean 
metadata and any reason why it's not a bean.
         *
@@ -81,18 +137,18 @@ public class BeanMeta<T> {
        }
 
        /**
-        * Finds the bean filter for the specified class metadata.
+        * Represents a bean constructor with its associated property names.
         *
-        * @param <T> The class type.
-        * @param cm The class metadata to find the filter for.
-        * @return The bean filter, or <jk>null</jk> if no filter is found.
+        * @param constructor The constructor information.
+        * @param propertyNames The list of property names that correspond to 
the constructor parameters.
         */
-       static <T> BeanFilter findBeanFilter(ClassMeta<T> cm) {
-               var ap = cm.getBeanContext().getAnnotationProvider();
-               var l = ap.find(Bean.class, cm);
-               if (l.isEmpty())
-                       return null;
-               return 
BeanFilter.create(cm).applyAnnotations(reverse(l.stream().map(AnnotationInfo::inner).toList())).build();
+       record BeanConstructor(Optional<ConstructorInfo> constructor, 
List<String> args) {}
+
+       /**
+        * Possible property method types.
+        */
+       enum MethodType {
+               UNKNOWN, GETTER, SETTER, EXTRAKEYS;
        }
 
        /**
@@ -174,81 +230,16 @@ public class BeanMeta<T> {
                }
        }
 
-       private static <T> BeanMetaValue<T> notABean(String reason) {
-               return new BeanMetaValue<>(null, reason);
-       }
-
-       /**
-        * Represents a bean constructor with its associated property names.
-        *
-        * @param constructor The constructor information.
-        * @param propertyNames The list of property names that correspond to 
the constructor parameters.
-        */
-       record BeanConstructor(Optional<ConstructorInfo> constructor, 
List<String> args) {}
-
-       /*
-        * Temporary getter/setter method struct.
-        */
-       private static class BeanMethod {
-               String propertyName;
-               MethodType methodType;
-               Method method;
-               ClassInfo type;
-
-               BeanMethod(String propertyName, MethodType type, Method method) 
{
-                       this.propertyName = propertyName;
-                       this.methodType = type;
-                       this.method = method;
-                       this.type = info(type == SETTER ? 
method.getParameterTypes()[0] : method.getReturnType());
-               }
-
-               @Override /* Overridden from Object */
-               public String toString() {
-                       return method.toString();
-               }
-
-               /*
-                * Returns true if this method matches the class type of the 
specified property.
-                * Only meant to be used for setters.
-                */
-               boolean matchesPropertyType(BeanPropertyMeta.Builder b) {
-                       if (b == null)
-                               return false;
-
-                       // Don't do further validation if this is the "*" bean 
property.
-                       if ("*".equals(b.name))
-                               return true;
-
-                       // Get the bean property type from the getter/field.
-                       var pt = (Class<?>)null;
-                       if (nn(b.getter))
-                               pt = b.getter.getReturnType();
-                       else if (nn(b.field))
-                               pt = b.field.getType();
-
-                       // Matches if only a setter is defined.
-                       if (pt == null)
-                               return true;
-
-                       // Doesn't match if not same type or super type as 
getter/field.
-                       if (! type.isParentOf(pt))
-                               return false;
-
-                       // If a setter was previously set, only use this setter 
if it's a closer
-                       // match (e.g. prev type is a superclass of this type).
-                       if (b.setter == null)
-                               return true;
-
-                       return 
type.isStrictChildOf(b.setter.getParameterTypes()[0]);
-               }
+       private static void forEachClass(ClassInfo c, Class<?> stopClass, 
Consumer<ClassInfo> consumer) {
+               var sc = c.getSuperclass();
+               if (nn(sc) && ! sc.is(stopClass))
+                       forEachClass(sc, stopClass, consumer);
+               c.getInterfaces().forEach(x -> forEachClass(x, stopClass, 
consumer));
+               consumer.accept(c);
        }
 
-
-       /**
-        * Possible property method types.
-        */
-       enum MethodType {
-               UNKNOWN, GETTER, SETTER, EXTRAKEYS;
+       private static <T> BeanMetaValue<T> notABean(String reason) {
+               return new BeanMetaValue<>(null, reason);
        }
 
        static final String bpName(List<Beanp> p, List<Name> n) {
@@ -268,6 +259,21 @@ public class BeanMeta<T> {
                return name.orElse(null);
        }
 
+       /**
+        * Finds the bean filter for the specified class metadata.
+        *
+        * @param <T> The class type.
+        * @param cm The class metadata to find the filter for.
+        * @return The bean filter, or <jk>null</jk> if no filter is found.
+        */
+       static <T> BeanFilter findBeanFilter(ClassMeta<T> cm) {
+               var ap = cm.getBeanContext().getAnnotationProvider();
+               var l = ap.find(Bean.class, cm);
+               if (l.isEmpty())
+                       return null;
+               return 
BeanFilter.create(cm).applyAnnotations(reverse(l.stream().map(AnnotationInfo::inner).toList())).build();
+       }
+
        static String name(AnnotationInfo<?> ai) {
                if (ai.isType(Beanp.class)) {
                        Beanp p = ai.cast(Beanp.class).inner();
@@ -283,226 +289,44 @@ public class BeanMeta<T> {
                return null;
        }
 
-       /**
-        * Finds all bean fields in the class hierarchy.
-        *
-        * <p>
-        * Traverses the complete class hierarchy (as defined by {@link 
#classHierarchy}) and collects all fields that
-        * meet the bean field criteria:
-        * <ul>
-        *      <li>Not static
-        *      <li>Not transient (unless transient fields are not ignored)
-        *      <li>Not annotated with {@link Transient @Transient} (unless 
transient fields are not ignored)
-        *      <li>Not annotated with {@link BeanIgnore @BeanIgnore}
-        *      <li>Visible according to the specified visibility level, or 
annotated with {@link Beanp @Beanp}
-        * </ul>
-        *
-        * @param v The minimum visibility level required for fields to be 
included.
-        * @return A collection of all bean fields found in the class hierarchy.
-        */
-       final Collection<Field> findBeanFields(Visibility v) {
-               var noIgnoreTransients = ! ctx.isIgnoreTransientFields();
-               var ap = ctx.getAnnotationProvider();
-               // @formatter:off
-               return classHierarchy.get().stream()
-                       .flatMap(c2 -> c2.getDeclaredFields().stream())
-                       .filter(x -> x.isNotStatic()
-                               && (x.isNotTransient() || noIgnoreTransients)
-                               && (! x.hasAnnotation(Transient.class) || 
noIgnoreTransients)
-                               && ! ap.has(BeanIgnore.class, x)
-                               && (v.isVisible(x.inner()) || 
ap.has(Beanp.class, x)))
-                       .map(FieldInfo::inner)
-                       .toList();
-               // @formatter:on
-       }
+       /** The target class type that this meta object describes. */
+       protected final ClassMeta<T> classMeta;
 
-       /*
-        * Find all the bean methods on this class.
-        *
-        * @param c The transformed class.
-        * @param stopClass Don't look above this class in the hierarchy.
-        * @param v The minimum method visibility.
-        * @param fixedBeanProps Only include methods whose properties are in 
this list.
-        * @param pn Use this property namer to determine property names from 
the method names.
-        */
-       private final List<BeanMethod> findBeanMethods(Visibility v, 
PropertyNamer pn, boolean fluentSetters) {
-               var l = new LinkedList<BeanMethod>();
-               var ap = ctx.getAnnotationProvider();
+       /** The target class that this meta object describes. */
+       protected final Class<T> c;
 
-               classHierarchy.get().stream().forEach(c2 -> {
-                       for (var m : c2.getDeclaredMethods()) {
+       /** The properties on the target class. */
+       protected final Map<String,BeanPropertyMeta> properties;
 
-                               if (m.isStatic() || m.isBridge() || 
m.getParameterCount() > 2)
-                                       continue;
+       /** The hidden properties on the target class. */
+       protected final Map<String,BeanPropertyMeta> hiddenProperties;
 
-                               var mm = m.getMatchingMethods();
+       /** The getter properties on the target class. */
+       protected final Map<Method,String> getterProps;
+       /** The setter properties on the target class. */
+       protected final Map<Method,String> setterProps;
+       /** The bean context that created this metadata object. */
+       protected final BeanContext ctx;
+       /** Optional bean filter associated with the target class. */
+       protected final BeanFilter beanFilter;
+       private final Class<?> stopClass;
+       /** The constructor for this bean. */
+       private final BeanConstructor beanConstructor;
 
-                               if (mm.stream().anyMatch(m2 -> 
ap.has(BeanIgnore.class, m2, SELF)))
-                                       continue;
+       /** Optional constructor to use if one cannot be found. */
+       private final ConstructorInfo implClassConstructor;
 
-                               if (mm.stream().anyMatch(m2 -> 
ap.find(Transient.class, m2, SELF).stream().map(x -> 
x.inner().value()).findFirst().orElse(false)))
-                                       continue;
+       // Other fields
+       final String typePropertyName;                         // "_type" 
property actual name.
 
-                               var beanps = ap.find(Beanp.class, 
m).stream().map(AnnotationInfo::inner).toList();
-                               var names = ap.find(Name.class, 
m).stream().map(AnnotationInfo::inner).toList();
+       private final BeanPropertyMeta typeProperty;           // "_type" mock 
bean property.
 
-                               if (! (m.isVisible(v) || isNotEmpty(beanps) || 
isNotEmpty(names)))
-                                       continue;
+       final BeanPropertyMeta dynaProperty;                   // "extras" 
property.
 
-                               var n = m.getSimpleName();
-
-                               var params = m.getParameters();
-                               var rt = m.getReturnType();
-                               var methodType = UNKNOWN;
-                               var bpName = bpName(beanps, names);
-
-                               if (params.isEmpty()) {
-                                       if ("*".equals(bpName)) {
-                                               if 
(rt.isChildOf(Collection.class)) {
-                                                       methodType = EXTRAKEYS;
-                                               } else if 
(rt.isChildOf(Map.class)) {
-                                                       methodType = GETTER;
-                                               }
-                                               n = bpName;
-                                       } else if (n.startsWith("get") && (! 
rt.is(Void.TYPE))) {
-                                               methodType = GETTER;
-                                               n = n.substring(3);
-                                       } else if (n.startsWith("is") && 
(rt.is(Boolean.TYPE) || rt.is(Boolean.class))) {
-                                               methodType = GETTER;
-                                               n = n.substring(2);
-                                       } else if (nn(bpName)) {
-                                               methodType = GETTER;
-                                               if (bpName.isEmpty()) {
-                                                       if (n.startsWith("get"))
-                                                               n = 
n.substring(3);
-                                                       else if 
(n.startsWith("is"))
-                                                               n = 
n.substring(2);
-                                                       bpName = n;
-                                               } else {
-                                                       n = bpName;
-                                               }
-                                       }
-                               } else if (params.size() == 1) {
-                                       if ("*".equals(bpName)) {
-                                               if 
(params.get(0).getParameterType().isChildOf(Map.class)) {
-                                                       methodType = SETTER;
-                                                       n = bpName;
-                                               } else if 
(params.get(0).getParameterType().is(String.class)) {
-                                                       methodType = GETTER;
-                                                       n = bpName;
-                                               }
-                                       } else if (n.startsWith("set") && 
(rt.isParentOf(c) || rt.is(Void.TYPE))) {
-                                               methodType = SETTER;
-                                               n = n.substring(3);
-                                       } else if (n.startsWith("with") && 
(rt.isParentOf(c))) {
-                                               methodType = SETTER;
-                                               n = n.substring(4);
-                                       } else if (nn(bpName)) {
-                                               methodType = SETTER;
-                                               if (bpName.isEmpty()) {
-                                                       if (n.startsWith("set"))
-                                                               n = 
n.substring(3);
-                                                       bpName = n;
-                                               } else {
-                                                       n = bpName;
-                                               }
-                                       } else if (fluentSetters && 
rt.isParentOf(c)) {
-                                               methodType = SETTER;
-                                       }
-                               } else if (params.size() == 2) {
-                                       if ("*".equals(bpName) && 
params.get(0).getParameterType().is(String.class)) {
-                                               if (n.startsWith("set") && 
(rt.isParentOf(c) || rt.is(Void.TYPE))) {
-                                                       methodType = SETTER;
-                                               } else {
-                                                       methodType = GETTER;
-                                               }
-                                               n = bpName;
-                                       }
-                               }
-                               n = pn.getPropertyName(n);
-
-                               if ("*".equals(bpName) && methodType == UNKNOWN)
-                                       throw bex(c, "Found @Beanp(\"*\") but 
could not determine method type on method ''{0}''.", m.getSimpleName());
-
-                               if (methodType != UNKNOWN) {
-                                       if (nn(bpName) && ! bpName.isEmpty())
-                                               n = bpName;
-                                       if (nn(n))
-                                               l.add(new BeanMethod(n, 
methodType, m.inner()));
-                               }
-                       }
-               });
-               return l;
-       }
-
-       final Field findInnerBeanField(String name) {
-               var noIgnoreTransients = ! ctx.isIgnoreTransientFields();
-               var value = Value.<Field>empty();
-               classHierarchy.get().stream().forEach(c2 -> {
-                       // @formatter:off
-                       c2.getDeclaredField(
-                               x -> x.isNotStatic()
-                               && (x.isNotTransient() || noIgnoreTransients)
-                               && (! x.hasAnnotation(Transient.class) || 
noIgnoreTransients)
-                               && ! 
ctx.getAnnotationProvider().has(BeanIgnore.class, x)
-                               && x.hasName(name))
-                       .ifPresent(f -> value.set(f.inner()));
-                       // @formatter:on
-               });
-               return value.get();
-       }
-
-       /** The target class type that this meta object describes. */
-       protected final ClassMeta<T> classMeta;
-
-       /** The target class that this meta object describes. */
-       protected final Class<T> c;
-
-       /** The properties on the target class. */
-       protected final Map<String,BeanPropertyMeta> properties;
-       /** The hidden properties on the target class. */
-       protected final Map<String,BeanPropertyMeta> hiddenProperties;
-       /** The getter properties on the target class. */
-       protected final Map<Method,String> getterProps;
-       /** The setter properties on the target class. */
-       protected final Map<Method,String> setterProps;
-       /** The bean context that created this metadata object. */
-       protected final BeanContext ctx;
-       /** Optional bean filter associated with the target class. */
-       protected final BeanFilter beanFilter;
-
-       private final Class<?> stopClass;
-
-       public BeanFilter getBeanFilter() {
-               return beanFilter;
-       }
-
-       /** The constructor for this bean. */
-       private final BeanConstructor beanConstructor;
-
-       /** Optional constructor to use if one cannot be found. */
-       private final ConstructorInfo implClassConstructor;
-
-       // Other fields
-       final String typePropertyName;                         // "_type" 
property actual name.
-
-       private final BeanPropertyMeta typeProperty;           // "_type" mock 
bean property.
-
-       final BeanPropertyMeta dynaProperty;                   // "extras" 
property.
-
-       private final Supplier<String> dictionaryName2;                   // 
The @Bean(typeName) annotation defined on this bean class.
+       private final Supplier<String> dictionaryName2;                   // 
The @Bean(typeName) annotation defined on this bean class.
 
        private final OptionalSupplier<InvocationHandler> 
beanProxyInvocationHandler;  // The invocation handler for this bean (if it's 
an interface).
 
-       /**
-        * Returns the proxy invocation handler for this bean if it's an 
interface.
-        *
-        * @return The invocation handler, or <jk>null</jk> if this is not an 
interface or interface proxies are disabled.
-        */
-       public InvocationHandler getBeanProxyInvocationHandler() {
-               return beanProxyInvocationHandler.get();
-       }
-
        final String notABeanReason;                           // Readable 
string explaining why this class wasn't a bean.
 
        private final Supplier<BeanRegistry> beanRegistry;
@@ -513,105 +337,6 @@ public class BeanMeta<T> {
 
        final boolean fluentSetters;
 
-       private BeanRegistry findBeanRegistry() {
-               // Bean dictionary on bean filter.
-               List<Class<?>> beanDictionaryClasses = nn(beanFilter) ? 
copyOf(beanFilter.getBeanDictionary()) : list();
-
-               // Bean dictionary from @Bean(typeName) annotation.
-               var ba = ctx.getAnnotationProvider().find(Bean.class, 
classMeta);
-               ba.stream().map(x -> 
x.inner().typeName()).filter(Utils::isNotEmpty).findFirst().ifPresent(x -> 
beanDictionaryClasses.add(classMeta.inner()));
-
-               return new BeanRegistry(ctx, null, 
beanDictionaryClasses.toArray(new Class<?>[beanDictionaryClasses.size()]));
-       }
-
-       private List<ClassInfo> findClassHierarchy() {
-               var result = new LinkedList<ClassInfo>();
-               // If @Bean.interfaceClass is specified on the parent class, 
then we want
-               // to use the properties defined on that class, not the 
subclass.
-               var c2 = (nn(beanFilter) && nn(beanFilter.getInterfaceClass()) 
? beanFilter.getInterfaceClass() : c);
-               forEachClass(info(c2), stopClass, result::add);
-               return u(result);
-       }
-
-       private static void forEachClass(ClassInfo c, Class<?> stopClass, 
Consumer<ClassInfo> consumer) {
-               var sc = c.getSuperclass();
-               if (nn(sc) && ! sc.is(stopClass))
-                       forEachClass(sc, stopClass, consumer);
-               c.getInterfaces().forEach(x -> forEachClass(x, stopClass, 
consumer));
-               consumer.accept(c);
-       }
-
-       private BeanConstructor findBeanConstructor() {
-               var ap = ctx.getAnnotationProvider();
-               var vis = ctx.getBeanConstructorVisibility();
-               var ci = classMeta;
-
-               var l = ci.getPublicConstructors().stream().filter(x -> 
ap.has(Beanc.class, x)).toList();
-               if (l.isEmpty())
-                       l = ci.getDeclaredConstructors().stream().filter(x -> 
ap.has(Beanc.class, x)).toList();
-               if (l.size() > 1)
-                       throw bex(c, "Multiple instances of '@Beanc' found.");
-               if (l.size() == 1) {
-                       var con = l.get(0).accessible();
-                       var args = ap.find(Beanc.class, con).stream().map(x -> 
x.inner().properties()).filter(StringUtils::isNotBlank).map(x -> 
split(x)).findFirst().orElse(liste());
-                       if (! con.hasNumParameters(args.size())) {
-                               if (isNotEmpty(args))
-                                       throw bex(c, "Number of properties 
defined in '@Beanc' annotation does not match number of parameters in 
constructor.");
-                               args = con.getParameters().stream().map(x -> 
x.getName()).toList();
-                               for (int i = 0; i < args.size(); i++) {
-                                       if (isBlank(args.get(i)))
-                                               throw bex(c, "Could not find 
name for parameter #{0} of constructor ''{1}''", i, con.getFullName());
-                               }
-                       }
-                       return new BeanConstructor(opt(con), args);
-               }
-
-               if (implClassConstructor != null)
-                       return new 
BeanConstructor(opt(implClassConstructor.accessible()), liste());
-
-               var ba = ap.find(Bean.class, classMeta);
-               var con = ci.getNoArgConstructor(! ba.isEmpty() ? 
Visibility.PRIVATE : vis).orElse(null);
-               if (con != null)
-                       return new BeanConstructor(opt(con.accessible()), 
liste());
-
-               return new BeanConstructor(opte(), liste());
-       }
-
-       private String findDictionaryName() {
-               if (nn(beanFilter) && nn(beanFilter.getTypeName()))
-                       return beanFilter.getTypeName();
-
-               var br = getBeanRegistry();
-               if (nn(br)) {
-                       String s = br.getTypeName(this.classMeta);
-                       if (nn(s))
-                               return s;
-               }
-
-               var n = classMeta
-                       .getParentsAndInterfaces()
-                       .stream()
-                       .skip(1)
-                       .map(x -> ctx.getClassMeta(x))
-                       .map(x -> x.getBeanRegistry())
-                       .filter(Objects::nonNull)
-                       .map(x -> x.getTypeName(this.classMeta))
-                       .filter(Objects::nonNull)
-                       .findFirst()
-                       .orElse(null);
-
-               if (n != null)
-                       return n;
-
-               return 
classMeta.getBeanContext().getAnnotationProvider().find(Bean.class, classMeta)
-                       .stream()
-                       .map(AnnotationInfo::inner)
-                       .filter(x -> ! x.typeName().isEmpty())
-                       .map(x -> x.typeName())
-                       .findFirst()
-                       .orElse(null);
-       }
-
        /**
         * Constructor.
         *
@@ -685,9 +410,14 @@ public class BeanMeta<T> {
                        } else /* Use 'better' introspection */ {
 
                                findBeanFields(fVis).forEach(x -> {
-                                       var name = 
ap.find(info(x)).stream().filter(x2 -> x2.isType(Beanp.class) || 
x2.isType(Name.class)).map(x2 -> 
name(x2)).filter(Objects::nonNull).findFirst().orElse(propertyNamer.getPropertyName(x.getName()));
+                                       var name = ap.find(x).stream()
+                                               .filter(x2 -> 
x2.isType(Beanp.class) || x2.isType(Name.class))
+                                               .map(x2 -> name(x2))
+                                               .filter(Objects::nonNull)
+                                               .findFirst()
+                                               
.orElse(propertyNamer.getPropertyName(x.getName()));
                                        if (nn(name)) {
-                                               
normalProps.computeIfAbsent(name, n->BeanPropertyMeta.builder(this, 
n)).setField(x);
+                                               
normalProps.computeIfAbsent(name, n->BeanPropertyMeta.builder(this, 
n)).setField(x.inner());
                                        }
                                });
 
@@ -842,30 +572,48 @@ public class BeanMeta<T> {
                beanProxyInvocationHandler = 
memoize(()->ctx.isUseInterfaceProxies() && c.isInterface() ? new 
BeanProxyInvocationHandler<>(this) : null);
        }
 
-       public Map<String,BeanPropertyMeta> getProperties() {
-               return properties;
+       @Override /* Overridden from Object */
+       public boolean equals(Object o) {
+               return (o instanceof BeanMeta<?> o2) && eq(this, o2, (x, y) -> 
eq(x.classMeta, y.classMeta));
        }
 
-       protected Map<String,BeanPropertyMeta> getHiddenProperties() {
-               return hiddenProperties;
+       /**
+        * Returns the bean filter associated with this bean.
+        *
+        * <p>
+        * Bean filters are used to control aspects of how beans are handled 
during serialization and parsing, such as
+        * property inclusion/exclusion, property ordering, and type name 
mapping.
+        *
+        * <p>
+        * The bean filter is typically created from the {@link Bean @Bean} 
annotation on the class. If no {@link Bean @Bean}
+        * annotation is present, this method returns <jk>null</jk>.
+        *
+        * @return The bean filter for this bean, or <jk>null</jk> if no bean 
filter is associated with this bean.
+        * @see Bean
+        */
+       public BeanFilter getBeanFilter() {
+               return beanFilter;
        }
 
-       protected boolean hasConstructor() {
-               return beanConstructor.constructor().isPresent();
-       }
-
-       protected ConstructorInfo getConstructor() {
-               return beanConstructor.constructor().orElse(null);
-       }
-
-       protected List<String> getConstructorArgs() {
-               return beanConstructor.args();
+       /**
+        * Returns the proxy invocation handler for this bean if it's an 
interface.
+        *
+        * @return The invocation handler, or <jk>null</jk> if this is not an 
interface or interface proxies are disabled.
+        */
+       public InvocationHandler getBeanProxyInvocationHandler() {
+               return beanProxyInvocationHandler.get();
        }
 
-       @Override /* Overridden from Object */
-       public boolean equals(Object o) {
-               return (o instanceof BeanMeta<?> o2) && eq(this, o2, (x, y) -> 
eq(x.classMeta, y.classMeta));
-       }
+       /**
+        * Returns the bean registry for this bean.
+        *
+        * <p>
+        * The bean registry is used to resolve dictionary names to class 
types. It's created when a bean class has a
+        * {@link Bean#dictionary() @Bean(dictionary)} annotation that 
specifies a list of possible subclasses.
+        *
+        * @return The bean registry for this bean, or <jk>null</jk> if no bean 
registry is associated with it.
+        */
+       public final BeanRegistry getBeanRegistry() { return 
beanRegistry.get(); }
 
        /**
         * Returns the {@link ClassMeta} of this bean.
@@ -882,38 +630,23 @@ public class BeanMeta<T> {
        public final String getDictionaryName() { return dictionaryName2.get(); 
}
 
        /**
-        * Returns the type property name for this bean.
-        *
-        * <p>
-        * This is the name of the bean property used to store the dictionary 
name of a bean type so that the parser knows
-        * the data type to reconstruct.
-        *
-        * <p>
-        * If <jk>null</jk>, <js>"_type"</js> should be assumed.
+        * Returns a map of all properties on this bean.
         *
         * <p>
-        * The value is determined from:
-        * <ul>
-        *      <li>The {@link Bean#typePropertyName() @Bean(typePropertyName)} 
annotation on the class, if present.
-        *      <li>Otherwise, the default value from {@link 
BeanContext#getBeanTypePropertyName()}.
-        * </ul>
-        *
-        * @return
-        *      The type property name associated with this bean, or 
<jk>null</jk> if the default <js>"_type"</js> should be used.
-        * @see BeanContext#getBeanTypePropertyName()
-        */
-       public final String getTypePropertyName() { return typePropertyName; }
-
-       /**
-        * Returns the bean registry for this bean.
+        * The map is keyed by property name and contains {@link 
BeanPropertyMeta} objects that provide metadata about each
+        * property, including its type, getter/setter methods, field 
information, and serialization/parsing behavior.
         *
         * <p>
-        * The bean registry is used to resolve dictionary names to class 
types. It's created when a bean class has a
-        * {@link Bean#dictionary() @Bean(dictionary)} annotation that 
specifies a list of possible subclasses.
+        * This map contains only the normal (non-hidden) properties of the 
bean. Hidden properties can be accessed via
+        * {@link #getHiddenProperties()}.
         *
-        * @return The bean registry for this bean, or <jk>null</jk> if no bean 
registry is associated with it.
+        * @return A map of property names to their metadata. The map is 
unmodifiable.
+        * @see #getPropertyMeta(String)
+        * @see #getHiddenProperties()
         */
-       public final BeanRegistry getBeanRegistry() { return 
beanRegistry.get(); }
+       public Map<String,BeanPropertyMeta> getProperties() {
+               return properties;
+       }
 
        /**
         * Returns metadata about the specified property.
@@ -938,6 +671,29 @@ public class BeanMeta<T> {
         */
        public final BeanPropertyMeta getTypeProperty() { return typeProperty; }
 
+       /**
+        * Returns the type property name for this bean.
+        *
+        * <p>
+        * This is the name of the bean property used to store the dictionary 
name of a bean type so that the parser knows
+        * the data type to reconstruct.
+        *
+        * <p>
+        * If <jk>null</jk>, <js>"_type"</js> should be assumed.
+        *
+        * <p>
+        * The value is determined from:
+        * <ul>
+        *      <li>The {@link Bean#typePropertyName() @Bean(typePropertyName)} 
annotation on the class, if present.
+        *      <li>Otherwise, the default value from {@link 
BeanContext#getBeanTypePropertyName()}.
+        * </ul>
+        *
+        * @return
+        *      The type property name associated with this bean, or 
<jk>null</jk> if the default <js>"_type"</js> should be used.
+        * @see BeanContext#getBeanTypePropertyName()
+        */
+       public final String getTypePropertyName() { return typePropertyName; }
+
        @Override /* Overridden from Object */
        public int hashCode() {
                return classMeta.hashCode();
@@ -982,6 +738,232 @@ public class BeanMeta<T> {
                return sb.toString();
        }
 
+       private BeanConstructor findBeanConstructor() {
+               var ap = ctx.getAnnotationProvider();
+               var vis = ctx.getBeanConstructorVisibility();
+               var ci = classMeta;
+
+               var l = ci.getPublicConstructors().stream().filter(x -> 
ap.has(Beanc.class, x)).toList();
+               if (l.isEmpty())
+                       l = ci.getDeclaredConstructors().stream().filter(x -> 
ap.has(Beanc.class, x)).toList();
+               if (l.size() > 1)
+                       throw bex(c, "Multiple instances of '@Beanc' found.");
+               if (l.size() == 1) {
+                       var con = l.get(0).accessible();
+                       var args = ap.find(Beanc.class, con).stream().map(x -> 
x.inner().properties()).filter(StringUtils::isNotBlank).map(x -> 
split(x)).findFirst().orElse(liste());
+                       if (! con.hasNumParameters(args.size())) {
+                               if (isNotEmpty(args))
+                                       throw bex(c, "Number of properties 
defined in '@Beanc' annotation does not match number of parameters in 
constructor.");
+                               args = con.getParameters().stream().map(x -> 
x.getName()).toList();
+                               for (int i = 0; i < args.size(); i++) {
+                                       if (isBlank(args.get(i)))
+                                               throw bex(c, "Could not find 
name for parameter #{0} of constructor ''{1}''", i, con.getFullName());
+                               }
+                       }
+                       return new BeanConstructor(opt(con), args);
+               }
+
+               if (implClassConstructor != null)
+                       return new 
BeanConstructor(opt(implClassConstructor.accessible()), liste());
+
+               var ba = ap.find(Bean.class, classMeta);
+               var con = ci.getNoArgConstructor(! ba.isEmpty() ? 
Visibility.PRIVATE : vis).orElse(null);
+               if (con != null)
+                       return new BeanConstructor(opt(con.accessible()), 
liste());
+
+               return new BeanConstructor(opte(), liste());
+       }
+
+       /*
+        * Find all the bean methods on this class.
+        *
+        * @param c The transformed class.
+        * @param stopClass Don't look above this class in the hierarchy.
+        * @param v The minimum method visibility.
+        * @param fixedBeanProps Only include methods whose properties are in 
this list.
+        * @param pn Use this property namer to determine property names from 
the method names.
+        */
+       private final List<BeanMethod> findBeanMethods(Visibility v, 
PropertyNamer pn, boolean fluentSetters) {
+               var l = new LinkedList<BeanMethod>();
+               var ap = ctx.getAnnotationProvider();
+
+               classHierarchy.get().stream().forEach(c2 -> {
+                       for (var m : c2.getDeclaredMethods()) {
+
+                               if (m.isStatic() || m.isBridge() || 
m.getParameterCount() > 2)
+                                       continue;
+
+                               var mm = m.getMatchingMethods();
+
+                               if (mm.stream().anyMatch(m2 -> 
ap.has(BeanIgnore.class, m2, SELF)))
+                                       continue;
+
+                               if (mm.stream().anyMatch(m2 -> 
ap.find(Transient.class, m2, SELF).stream().map(x -> 
x.inner().value()).findFirst().orElse(false)))
+                                       continue;
+
+                               var beanps = ap.find(Beanp.class, 
m).stream().map(AnnotationInfo::inner).toList();
+                               var names = ap.find(Name.class, 
m).stream().map(AnnotationInfo::inner).toList();
+
+                               if (! (m.isVisible(v) || isNotEmpty(beanps) || 
isNotEmpty(names)))
+                                       continue;
+
+                               var n = m.getSimpleName();
+
+                               var params = m.getParameters();
+                               var rt = m.getReturnType();
+                               var methodType = UNKNOWN;
+                               var bpName = bpName(beanps, names);
+
+                               if (params.isEmpty()) {
+                                       if ("*".equals(bpName)) {
+                                               if 
(rt.isChildOf(Collection.class)) {
+                                                       methodType = EXTRAKEYS;
+                                               } else if 
(rt.isChildOf(Map.class)) {
+                                                       methodType = GETTER;
+                                               }
+                                               n = bpName;
+                                       } else if (n.startsWith("get") && (! 
rt.is(Void.TYPE))) {
+                                               methodType = GETTER;
+                                               n = n.substring(3);
+                                       } else if (n.startsWith("is") && 
(rt.is(Boolean.TYPE) || rt.is(Boolean.class))) {
+                                               methodType = GETTER;
+                                               n = n.substring(2);
+                                       } else if (nn(bpName)) {
+                                               methodType = GETTER;
+                                               if (bpName.isEmpty()) {
+                                                       if (n.startsWith("get"))
+                                                               n = 
n.substring(3);
+                                                       else if 
(n.startsWith("is"))
+                                                               n = 
n.substring(2);
+                                                       bpName = n;
+                                               } else {
+                                                       n = bpName;
+                                               }
+                                       }
+                               } else if (params.size() == 1) {
+                                       if ("*".equals(bpName)) {
+                                               if 
(params.get(0).getParameterType().isChildOf(Map.class)) {
+                                                       methodType = SETTER;
+                                                       n = bpName;
+                                               } else if 
(params.get(0).getParameterType().is(String.class)) {
+                                                       methodType = GETTER;
+                                                       n = bpName;
+                                               }
+                                       } else if (n.startsWith("set") && 
(rt.isParentOf(c) || rt.is(Void.TYPE))) {
+                                               methodType = SETTER;
+                                               n = n.substring(3);
+                                       } else if (n.startsWith("with") && 
(rt.isParentOf(c))) {
+                                               methodType = SETTER;
+                                               n = n.substring(4);
+                                       } else if (nn(bpName)) {
+                                               methodType = SETTER;
+                                               if (bpName.isEmpty()) {
+                                                       if (n.startsWith("set"))
+                                                               n = 
n.substring(3);
+                                                       bpName = n;
+                                               } else {
+                                                       n = bpName;
+                                               }
+                                       } else if (fluentSetters && 
rt.isParentOf(c)) {
+                                               methodType = SETTER;
+                                       }
+                               } else if (params.size() == 2) {
+                                       if ("*".equals(bpName) && 
params.get(0).getParameterType().is(String.class)) {
+                                               if (n.startsWith("set") && 
(rt.isParentOf(c) || rt.is(Void.TYPE))) {
+                                                       methodType = SETTER;
+                                               } else {
+                                                       methodType = GETTER;
+                                               }
+                                               n = bpName;
+                                       }
+                               }
+                               n = pn.getPropertyName(n);
+
+                               if ("*".equals(bpName) && methodType == UNKNOWN)
+                                       throw bex(c, "Found @Beanp(\"*\") but 
could not determine method type on method ''{0}''.", m.getSimpleName());
+
+                               if (methodType != UNKNOWN) {
+                                       if (nn(bpName) && ! bpName.isEmpty())
+                                               n = bpName;
+                                       if (nn(n))
+                                               l.add(new BeanMethod(n, 
methodType, m.inner()));
+                               }
+                       }
+               });
+               return l;
+       }
+
+       private BeanRegistry findBeanRegistry() {
+               // Bean dictionary on bean filter.
+               List<Class<?>> beanDictionaryClasses = nn(beanFilter) ? 
copyOf(beanFilter.getBeanDictionary()) : list();
+
+               // Bean dictionary from @Bean(typeName) annotation.
+               var ba = ctx.getAnnotationProvider().find(Bean.class, 
classMeta);
+               ba.stream().map(x -> 
x.inner().typeName()).filter(Utils::isNotEmpty).findFirst().ifPresent(x -> 
beanDictionaryClasses.add(classMeta.inner()));
+
+               return new BeanRegistry(ctx, null, 
beanDictionaryClasses.toArray(new Class<?>[beanDictionaryClasses.size()]));
+       }
+
+       private List<ClassInfo> findClassHierarchy() {
+               var result = new LinkedList<ClassInfo>();
+               // If @Bean.interfaceClass is specified on the parent class, 
then we want
+               // to use the properties defined on that class, not the 
subclass.
+               var c2 = (nn(beanFilter) && nn(beanFilter.getInterfaceClass()) 
? beanFilter.getInterfaceClass() : c);
+               forEachClass(info(c2), stopClass, result::add);
+               return u(result);
+       }
+
+       private String findDictionaryName() {
+               if (nn(beanFilter) && nn(beanFilter.getTypeName()))
+                       return beanFilter.getTypeName();
+
+               var br = getBeanRegistry();
+               if (nn(br)) {
+                       String s = br.getTypeName(this.classMeta);
+                       if (nn(s))
+                               return s;
+               }
+
+               var n = classMeta
+                       .getParentsAndInterfaces()
+                       .stream()
+                       .skip(1)
+                       .map(x -> ctx.getClassMeta(x))
+                       .map(x -> x.getBeanRegistry())
+                       .filter(Objects::nonNull)
+                       .map(x -> x.getTypeName(this.classMeta))
+                       .filter(Objects::nonNull)
+                       .findFirst()
+                       .orElse(null);
+
+               if (n != null)
+                       return n;
+
+               return 
classMeta.getBeanContext().getAnnotationProvider().find(Bean.class, classMeta)
+                       .stream()
+                       .map(AnnotationInfo::inner)
+                       .filter(x -> ! x.typeName().isEmpty())
+                       .map(x -> x.typeName())
+                       .findFirst()
+                       .orElse(null);
+       }
+
+       protected ConstructorInfo getConstructor() {
+               return beanConstructor.constructor().orElse(null);
+       }
+
+       protected List<String> getConstructorArgs() {
+               return beanConstructor.args();
+       }
+
+       protected Map<String,BeanPropertyMeta> getHiddenProperties() {
+               return hiddenProperties;
+       }
+
+       protected boolean hasConstructor() {
+               return beanConstructor.constructor().isPresent();
+       }
+
        /**
         * Creates a new instance of this bean.
         *
@@ -1005,4 +987,54 @@ public class BeanMeta<T> {
                }
                return null;
        }
+
+       /**
+        * Finds all bean fields in the class hierarchy.
+        *
+        * <p>
+        * Traverses the complete class hierarchy (as defined by {@link 
#classHierarchy}) and collects all fields that
+        * meet the bean field criteria:
+        * <ul>
+        *      <li>Not static
+        *      <li>Not transient (unless transient fields are not ignored)
+        *      <li>Not annotated with {@link Transient @Transient} (unless 
transient fields are not ignored)
+        *      <li>Not annotated with {@link BeanIgnore @BeanIgnore}
+        *      <li>Visible according to the specified visibility level, or 
annotated with {@link Beanp @Beanp}
+        * </ul>
+        *
+        * @param v The minimum visibility level required for fields to be 
included.
+        * @return A collection of all bean fields found in the class hierarchy.
+        */
+       final Collection<FieldInfo> findBeanFields(Visibility v) {
+               var noIgnoreTransients = ! ctx.isIgnoreTransientFields();
+               var ap = ctx.getAnnotationProvider();
+               // @formatter:off
+               return classHierarchy.get().stream()
+                       .flatMap(c2 -> c2.getDeclaredFields().stream())
+                       .filter(x -> x.isNotStatic()
+                               && (x.isNotTransient() || noIgnoreTransients)
+                               && (! x.hasAnnotation(Transient.class) || 
noIgnoreTransients)
+                               && ! ap.has(BeanIgnore.class, x)
+                               && (v.isVisible(x.inner()) || 
ap.has(Beanp.class, x)))
+                       .toList();
+               // @formatter:on
+       }
+
+       final FieldInfo findInnerBeanField(String name) {
+               var noIgnoreTransients = ! ctx.isIgnoreTransientFields();
+               var ap = ctx.getAnnotationProvider();
+
+               // @formatter:off
+               return classHierarchy.get().stream()
+                       .flatMap(c2 -> c2.getDeclaredField(
+                               x -> x.isNotStatic()
+                                       && (x.isNotTransient() || 
noIgnoreTransients)
+                                       && (! x.hasAnnotation(Transient.class) 
|| noIgnoreTransients)
+                                       && ! ap.has(BeanIgnore.class, x)
+                                       && x.hasName(name)
+                       ).stream())
+                       .findFirst()
+                       .orElse(null);
+               // @formatter:on
+       }
 }
\ No newline at end of file
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanPropertyMeta.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanPropertyMeta.java
index 71f162b9a7..8478b62530 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanPropertyMeta.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanPropertyMeta.java
@@ -193,6 +193,11 @@ public class BeanPropertyMeta implements 
Comparable<BeanPropertyMeta> {
                        return this;
                }
 
+               BeanPropertyMeta.Builder setInnerField(FieldInfo innerField) {
+                       this.innerField = innerField == null ? null : 
innerField.inner();
+                       return this;
+               }
+
                BeanPropertyMeta.Builder setInnerField(Field innerField) {
                        this.innerField = innerField;
                        return this;


Reply via email to