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 d09e1bac9d Marshall module improvements
d09e1bac9d is described below
commit d09e1bac9d77dcc7c96819f600db7fdc8d700029
Author: James Bognar <[email protected]>
AuthorDate: Wed Dec 10 10:54:54 2025 -0500
Marshall module improvements
---
.../src/main/java/org/apache/juneau/BeanMeta.java | 589 +++++++++++++++------
1 file changed, 435 insertions(+), 154 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 b3d18a4595..f297af346c 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
@@ -67,6 +67,25 @@ import org.apache.juneau.commons.utils.*;
*/
public class BeanMeta<T> {
+ /**
+ * Represents the result of creating a BeanMeta, including the bean
metadata and any reason why it's not a bean.
+ *
+ * @param <T> The bean type.
+ * @param beanMeta The bean metadata, or <jk>null</jk> if the class is
not a bean.
+ * @param notABeanReason The reason why the class is not a bean, or
<jk>null</jk> if it is a bean.
+ */
+ record BeanMetaValue<T>(BeanMeta<T> beanMeta, String notABeanReason) {
+ Optional<BeanMeta<T>> optBeanMeta() { return opt(beanMeta()); }
+ Optional<String> optNotABeanReason() { return
opt(notABeanReason()); }
+ }
+
+ /**
+ * Possible property method types.
+ */
+ enum MethodType {
+ UNKNOWN, GETTER, SETTER, EXTRAKEYS;
+ }
+
/*
* Temporary getter/setter method struct used for calculating bean
methods.
*/
@@ -132,25 +151,6 @@ public class BeanMeta<T> {
*/
private record BeanConstructor(Optional<ConstructorInfo> constructor,
List<String> args) {}
- /**
- * Represents the result of creating a BeanMeta, including the bean
metadata and any reason why it's not a bean.
- *
- * @param <T> The bean type.
- * @param beanMeta The bean metadata, or <jk>null</jk> if the class is
not a bean.
- * @param notABeanReason The reason why the class is not a bean, or
<jk>null</jk> if it is a bean.
- */
- record BeanMetaValue<T>(BeanMeta<T> beanMeta, String notABeanReason) {
- Optional<BeanMeta<T>> optBeanMeta() { return opt(beanMeta()); }
- Optional<String> optNotABeanReason() { return
opt(notABeanReason()); }
- }
-
- /**
- * Possible property method types.
- */
- enum MethodType {
- UNKNOWN, GETTER, SETTER, EXTRAKEYS;
- }
-
/**
* Creates a {@link BeanMeta} instance for the specified class metadata.
*
@@ -222,7 +222,7 @@ public class BeanMeta<T> {
if ((!
bc.getBeanClassVisibility().isVisible(cm.getModifiers()) ||
cm.isAnonymousClass()) && ! ap.has(Bean.class, cm))
return notABean("Class is not public");
- var bm = new BeanMeta<>(cm, findBeanFilter(cm), null,
opt(implClass).map(x -> x.getPublicConstructor(x2 ->
x2.hasNumParameters(0)).orElse(null)).orElse(null));
+ var bm = new BeanMeta<>(cm, findBeanFilter(cm), null,
implClass);
if (nn(bm.notABeanReason))
return notABean(bm.notABeanReason);
@@ -233,13 +233,6 @@ public class BeanMeta<T> {
}
}
- /*
- * Shortcut for creating a BeanMetaValue with a not-a-bean reason.
- */
- private static <T> BeanMetaValue<T> notABean(String reason) {
- return new BeanMetaValue<>(null, reason);
- }
-
/*
* Extracts the property name from {@link Beanp @Beanp} or {@link Name
@Name} annotations.
*
@@ -341,13 +334,20 @@ public class BeanMeta<T> {
return null;
}
+ /*
+ * Shortcut for creating a BeanMetaValue with a not-a-bean reason.
+ */
+ private static <T> BeanMetaValue<T> notABean(String reason) {
+ return new BeanMetaValue<>(null, reason);
+ }
+
private final BeanConstructor beanConstructor;
// The constructor for this bean.
+ private final BeanContext beanContext;
// The bean context that created this metadata object.
private final BeanFilter beanFilter;
// Optional bean filter associated with the target class.
private final OptionalSupplier<InvocationHandler>
beanProxyInvocationHandler; // The invocation handler for this bean (if it's
an interface).
private final Supplier<BeanRegistry> beanRegistry;
// The bean registry for this bean.
private final Supplier<List<ClassInfo>> classHierarchy;
// List of all classes traversed in the class hierarchy.
private final ClassMeta<T> classMeta;
// The target class type that this meta object describes.
- private final BeanContext beanContext;
// The bean context that created this metadata object.
private final Supplier<String> dictionaryName;
// The @Bean(typeName) annotation defined on this bean class.
private final BeanPropertyMeta dynaProperty;
// "extras" property.
private final boolean fluentSetters;
// Whether fluent setters are enabled.
@@ -365,18 +365,32 @@ public class BeanMeta<T> {
/**
* Constructor.
*
- * @param cm The target class.
- * @param beanContext The bean context that created this object.
- * @param bf Optional bean filter associated with the target class.
Can be <jk>null</jk>.
- * @param pNames Explicit list of property names and order of
properties. If <jk>null</jk>, determine automatically.
- * @param implClassConstructor The constructor to use if one cannot be
found. Can be <jk>null</jk>.
+ * <p>
+ * Creates a new {@link BeanMeta} instance for the specified class
metadata. This constructor performs
+ * introspection to discover all bean properties, methods, and fields
in the class hierarchy.
+ *
+ * <p>
+ * The bean metadata is built by:
+ * <ul>
+ * <li>Finding all bean fields in the class hierarchy
+ * <li>Finding all bean methods (getters, setters, extraKeys) in
the class hierarchy
+ * <li>Determining the appropriate constructor for bean
instantiation
+ * <li>Building the class hierarchy for property discovery
+ * <li>Creating the bean registry for dictionary name resolution
+ * <li>Validating and filtering properties based on bean filter
settings
+ * </ul>
+ *
+ * @param cm The class metadata for the bean class.
+ * @param bf Optional bean filter to apply. Can be <jk>null</jk>.
+ * @param pNames Explicit list of property names and order. If
<jk>null</jk>, properties are determined automatically.
+ * @param implClass Optional implementation class constructor to use if
one cannot be found. Can be <jk>null</jk>.
*/
@SuppressWarnings("rawtypes")
- protected BeanMeta(ClassMeta<T> cm, BeanFilter bf, String[] pNames,
ConstructorInfo implClassConstructor) {
+ protected BeanMeta(ClassMeta<T> cm, BeanFilter bf, String[] pNames,
ClassInfo implClass) {
classMeta = cm;
beanContext = cm.getBeanContext();
beanFilter = bf;
- this.implClassConstructor = implClassConstructor;
+ implClassConstructor = opt(implClass).map(x ->
x.getPublicConstructor(x2 -> x2.hasNumParameters(0)).orElse(null)).orElse(null);
fluentSetters = beanContext.isFindFluentSetters() || (nn(bf) &&
bf.isFluentSetters());
stopClass = opt(bf).map(x ->
(Class)x.getStopClass()).orElse(Object.class);
beanRegistry = memoize(()->findBeanRegistry());
@@ -407,9 +421,6 @@ public class BeanMeta<T> {
var fixedBeanProps = bfo.map(x ->
x.getProperties()).orElse(sete());
try {
- var mVis = beanContext.getBeanMethodVisibility();
- var fVis = beanContext.getBeanFieldVisibility();
-
Map<String,BeanPropertyMeta.Builder> normalProps =
map(); // NOAI
// First populate the properties with those specified
in the bean annotation to
@@ -431,7 +442,7 @@ public class BeanMeta<T> {
} else /* Use 'better' introspection */ {
- findBeanFields(fVis).forEach(x -> {
+ findBeanFields().forEach(x -> {
var name = ap.find(x).stream()
.filter(x2 ->
x2.isType(Beanp.class) || x2.isType(Name.class))
.map(x2 -> name(x2))
@@ -443,7 +454,7 @@ public class BeanMeta<T> {
}
});
- var bms = findBeanMethods(mVis, propertyNamer,
fluentSetters);
+ var bms = findBeanMethods();
// Iterate through all the getters.
bms.forEach(x -> {
@@ -620,21 +631,21 @@ public class BeanMeta<T> {
*
* @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(); }
+ public BeanRegistry getBeanRegistry() { return beanRegistry.get(); }
/**
* Returns the {@link ClassMeta} of this bean.
*
* @return The {@link ClassMeta} of this bean.
*/
- public final ClassMeta<T> getClassMeta() { return classMeta; }
+ public ClassMeta<T> getClassMeta() { return classMeta; }
/**
* Returns the dictionary name for this bean as defined through the
{@link Bean#typeName() @Bean(typeName)} annotation.
*
* @return The dictionary name for this bean, or <jk>null</jk> if it
has no dictionary name defined.
*/
- public final String getDictionaryName() { return dictionaryName.get(); }
+ public String getDictionaryName() { return dictionaryName.get(); }
/**
* Returns a map of all properties on this bean.
@@ -660,7 +671,7 @@ public class BeanMeta<T> {
* @return The metadata about the property, or <jk>null</jk> if no such
property exists on this bean.
*/
public BeanPropertyMeta getPropertyMeta(String name) {
- BeanPropertyMeta bpm = properties.get(name);
+ var bpm = properties.get(name);
if (bpm == null)
bpm = hiddenProperties.get(name);
if (bpm == null)
@@ -674,7 +685,7 @@ public class BeanMeta<T> {
*
* @return The type name property.
*/
- public final BeanPropertyMeta getTypeProperty() { return typeProperty; }
+ public BeanPropertyMeta getTypeProperty() { return typeProperty; }
/**
* Returns the type property name for this bean.
@@ -697,42 +708,7 @@ public class BeanMeta<T> {
* 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 context that created this metadata object.
- *
- * @return The bean context.
- */
- protected final BeanContext getBeanContext() { return beanContext; }
-
- /**
- * Returns the "extras" property for dynamic bean properties.
- *
- * @return The dynamic property, or <jk>null</jk> if not present.
- */
- protected final BeanPropertyMeta getDynaProperty() { return
dynaProperty; }
-
- /**
- * Returns the map of getter methods to property names.
- *
- * @return The getter properties map.
- */
- protected final Map<Method,String> getGetterProps() { return
getterProps; }
-
- /**
- * Returns the map of setter methods to property names.
- *
- * @return The setter properties map.
- */
- protected final Map<Method,String> getSetterProps() { return
setterProps; }
-
- /**
- * Returns whether properties should be sorted for this bean.
- *
- * @return <jk>true</jk> if properties should be sorted.
- */
- protected final boolean isSortProperties() { return sortProperties; }
+ public String getTypePropertyName() { return typePropertyName; }
@Override /* Overridden from Object */
public int hashCode() {
@@ -778,6 +754,191 @@ public class BeanMeta<T> {
return sb.toString();
}
+ /**
+ * Returns the bean context that created this metadata object.
+ *
+ * @return The bean context.
+ */
+ protected BeanContext getBeanContext() { return beanContext; }
+
+ /**
+ * Returns the constructor for this bean, if one was found.
+ *
+ * <p>
+ * The constructor is determined by {@link #findBeanConstructor()} and
may be:
+ * <ul>
+ * <li>A constructor annotated with {@link Beanc @Beanc}
+ * <li>An implementation class constructor (if provided)
+ * <li>A no-argument constructor
+ * <li><jk>null</jk> if no suitable constructor was found
+ * </ul>
+ *
+ * @return The constructor for this bean, or <jk>null</jk> if no
constructor is available.
+ * @see #getConstructorArgs()
+ * @see #hasConstructor()
+ */
+ protected ConstructorInfo getConstructor() {
+ return beanConstructor.constructor().orElse(null);
+ }
+
+ /**
+ * Returns the list of property names that correspond to the
constructor parameters.
+ *
+ * <p>
+ * The property names are in the same order as the constructor
parameters. These names are used to map
+ * parsed property values to constructor arguments when creating bean
instances.
+ *
+ * <p>
+ * The property names are determined from:
+ * <ul>
+ * <li>The {@link Beanc#properties() properties()} value in the
{@link Beanc @Beanc} annotation (if present)
+ * <li>Otherwise, the parameter names from the constructor (if
available in bytecode)
+ * </ul>
+ *
+ * <p>
+ * If the bean has no constructor or uses a no-argument constructor,
this list will be empty.
+ *
+ * @return A list of property names corresponding to constructor
parameters, in parameter order.
+ * @see #getConstructor()
+ * @see #hasConstructor()
+ */
+ protected List<String> getConstructorArgs() {
+ return beanConstructor.args();
+ }
+
+ /**
+ * Returns the "extras" property for dynamic bean properties.
+ *
+ * @return The dynamic property, or <jk>null</jk> if not present.
+ */
+ protected BeanPropertyMeta getDynaProperty() { return dynaProperty; }
+
+ /**
+ * Returns the map of getter methods to property names.
+ *
+ * @return The getter properties map.
+ */
+ protected Map<Method,String> getGetterProps() { return getterProps; }
+
+ /**
+ * Returns the map of hidden properties on this bean.
+ *
+ * <p>
+ * Hidden properties are properties that exist on the bean but are not
included in the normal property list.
+ * These properties are typically excluded from serialization but may
still be accessible programmatically.
+ *
+ * <p>
+ * Hidden properties can be defined through:
+ * <ul>
+ * <li>{@link BeanFilter#getExcludeProperties() Bean filter
exclude properties}
+ * <li>Properties that fail validation during bean metadata
creation
+ * </ul>
+ *
+ * @return A map of hidden property names to their metadata. The map is
unmodifiable.
+ * @see #getProperties()
+ */
+ protected Map<String,BeanPropertyMeta> getHiddenProperties() {
+ return hiddenProperties;
+ }
+
+ /**
+ * Returns the map of setter methods to property names.
+ *
+ * @return The setter properties map.
+ */
+ protected Map<Method,String> getSetterProps() { return setterProps; }
+
+ /**
+ * Returns whether this bean has a constructor available for
instantiation.
+ *
+ * <p>
+ * A bean has a constructor if {@link #findBeanConstructor()} was able
to find a suitable constructor,
+ * which may be:
+ * <ul>
+ * <li>A constructor annotated with {@link Beanc @Beanc}
+ * <li>An implementation class constructor (if provided)
+ * <li>A no-argument constructor
+ * </ul>
+ *
+ * <p>
+ * If this method returns <jk>false</jk>, the bean cannot be
instantiated using {@link #newBean(Object)},
+ * and may need to be created through other means (e.g., interface
proxies).
+ *
+ * @return <jk>true</jk> if a constructor is available, <jk>false</jk>
otherwise.
+ * @see #getConstructor()
+ * @see #newBean(Object)
+ */
+ protected boolean hasConstructor() {
+ return beanConstructor.constructor().isPresent();
+ }
+
+ /**
+ * Returns whether properties should be sorted for this bean.
+ *
+ * @return <jk>true</jk> if properties should be sorted.
+ */
+ protected boolean isSortProperties() { return sortProperties; }
+
+ /**
+ * Creates a new instance of this bean.
+ *
+ * @param outer The outer object if bean class is a non-static inner
member class.
+ * @return A new instance of this bean if possible, or <jk>null</jk> if
not.
+ * @throws ExecutableException Exception occurred on invoked
constructor/method/field.
+ */
+ @SuppressWarnings("unchecked")
+ protected T newBean(Object outer) throws ExecutableException {
+ if (classMeta.isMemberClass() && classMeta.isNotStatic()) {
+ if (hasConstructor())
+ return getConstructor().<T>newInstance(outer);
+ } else {
+ if (hasConstructor())
+ return getConstructor().<T>newInstance();
+ var h = classMeta.getProxyInvocationHandler();
+ if (nn(h)) {
+ var cl = classMeta.getClassLoader();
+ return (T)Proxy.newProxyInstance(cl,
a(classMeta.inner(), java.io.Serializable.class), h);
+ }
+ }
+ return null;
+ }
+
+ /*
+ * Finds the appropriate constructor for this bean and determines the
property names for constructor arguments.
+ *
+ * <p>
+ * This method searches for a constructor in the following order of
precedence:
+ * <ol>
+ * <li><b>{@link Beanc @Beanc} annotated constructor:</b> If a
constructor is annotated with {@link Beanc @Beanc},
+ * it is used. The property names are determined from:
+ * <ul>
+ * <li>The {@link Beanc#properties() properties()}
value in the annotation, if specified
+ * <li>Otherwise, the parameter names from the
constructor (if available in bytecode)
+ * </ul>
+ * If multiple constructors are annotated with {@link
Beanc @Beanc}, an exception is thrown.
+ * <li><b>Implementation class constructor:</b> If an {@link
#implClassConstructor} was provided during bean
+ * metadata creation, it is used with an empty property
list.
+ * <li><b>No-arg constructor:</b> Searches for a no-argument
constructor. The visibility required depends on
+ * whether the class has a {@link Bean @Bean} annotation:
+ * <ul>
+ * <li>If {@link Bean @Bean} is present, private
constructors are allowed
+ * <li>Otherwise, the visibility is determined by
{@link BeanContext#getBeanConstructorVisibility()}
+ * </ul>
+ * <li><b>No constructor:</b> Returns an empty {@link Optional} if
no suitable constructor is found.
+ * </ol>
+ *
+ * <p>
+ * The returned {@link BeanConstructor} contains:
+ * <ul>
+ * <li>The constructor (if found), wrapped in an {@link Optional}
+ * <li>A list of property names that correspond to the constructor
parameters, in order
+ * </ul>
+ *
+ * @return A {@link BeanConstructor} containing the found constructor
and its associated property names.
+ * @throws BeanRuntimeException If multiple constructors are annotated
with {@link Beanc @Beanc}, or if
+ * the number of properties specified in {@link Beanc @Beanc}
doesn't match the number of constructor parameters,
+ * or if parameter names cannot be determined from the bytecode.
+ */
private BeanConstructor findBeanConstructor() {
var ap = beanContext.getAnnotationProvider();
var vis = beanContext.getBeanConstructorVisibility();
@@ -815,18 +976,87 @@ public class BeanMeta<T> {
}
/*
- * Find all the bean methods on this class.
+ * Finds all bean fields in the class hierarchy.
*
- * @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.
+ * <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>
+ *
+ * @return A collection of all bean fields found in the class hierarchy.
*/
- private final List<BeanMethod> findBeanMethods(Visibility v,
PropertyNamer pn, boolean fluentSetters) {
+ private Collection<FieldInfo> findBeanFields() {
+ var v = beanContext.getBeanFieldVisibility();
+ var noIgnoreTransients = !
beanContext.isIgnoreTransientFields();
+ var ap = beanContext.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
+ }
+
+ /*
+ * Finds all bean methods (getters, setters, and extraKeys) in the
class hierarchy.
+ *
+ * <p>
+ * Traverses the complete class hierarchy (as defined by {@link
#classHierarchy}) and identifies methods that
+ * represent bean properties. Methods are identified as:
+ * <ul>
+ * <li><b>Getters:</b> Methods with no parameters that return a
value, matching patterns like:
+ * <ul>
+ * <li><c>getX()</c> - standard getter pattern
+ * <li><c>isX()</c> - boolean getter pattern
(returns boolean or Boolean)
+ * <li>Methods annotated with {@link Beanp @Beanp}
or {@link Name @Name}
+ * <li>Methods with {@link Beanp @Beanp}
annotation with value <js>"*"</js> that return a Map
+ * </ul>
+ * <li><b>Setters:</b> Methods with one parameter that match
patterns like:
+ * <ul>
+ * <li><c>setX(value)</c> - standard setter pattern
+ * <li><c>withX(value)</c> - fluent setter pattern
(returns the bean type)
+ * <li>Methods annotated with {@link Beanp @Beanp}
or {@link Name @Name}
+ * <li>Methods with {@link Beanp @Beanp}
annotation with value <js>"*"</js> that accept a Map
+ * <li>Fluent setters (if enabled) - methods that
return the bean type and accept one parameter
+ * </ul>
+ * <li><b>ExtraKeys:</b> Methods with {@link Beanp @Beanp}
annotation with value <js>"*"</js> that return a Collection
+ * </ul>
+ *
+ * <p>
+ * Methods are filtered based on:
+ * <ul>
+ * <li>Not static, not bridge methods
+ * <li>Parameter count ≤ 2
+ * <li>Not annotated with {@link BeanIgnore @BeanIgnore}
+ * <li>Not annotated with {@link Transient @Transient}
+ * <li>Visible according to the specified visibility level, or
annotated with {@link Beanp @Beanp} or {@link Name @Name}
+ * </ul>
+ *
+ * <p>
+ * Property names are determined from:
+ * <ul>
+ * <li>{@link Beanp @Beanp} or {@link Name @Name} annotations (if
present)
+ * <li>Otherwise, derived from the method name using the provided
{@link PropertyNamer}
+ * </ul>
+ *
+ * @return A list of {@link BeanMethod} objects representing all found
bean methods.
+ */
+ private List<BeanMethod> findBeanMethods() {
var l = new LinkedList<BeanMethod>();
var ap = beanContext.getAnnotationProvider();
var ci = classMeta;
+ var v = beanContext.getBeanMethodVisibility();
+ var pn = opt(beanFilter).map(x ->
x.getPropertyNamer()).orElse(beanContext.getPropertyNamer());
classHierarchy.get().stream().forEach(c2 -> {
for (var m : c2.getDeclaredMethods()) {
@@ -934,6 +1164,29 @@ public class BeanMeta<T> {
return l;
}
+ /*
+ * Creates a bean registry for this bean class.
+ *
+ * <p>
+ * The bean registry is used to resolve dictionary names (type names)
to actual class types. This is essential
+ * for polymorphic bean serialization and parsing, where a dictionary
name in the serialized form needs to be
+ * mapped back to the correct subclass.
+ *
+ * <p>
+ * The registry is built from:
+ * <ul>
+ * <li><b>Bean filter dictionary:</b> Classes specified in the
{@link BeanFilter#getBeanDictionary() bean filter's dictionary}
+ * (if a bean filter is present)
+ * <li><b>{@link Bean @Bean} annotation:</b> If the class has a
{@link Bean @Bean} annotation with a non-empty
+ * {@link Bean#typeName() typeName()}, the class itself is
added to the dictionary
+ * </ul>
+ *
+ * <p>
+ * The registry is used by parsers to determine which class to
instantiate when deserializing polymorphic beans.
+ *
+ * @return A new {@link BeanRegistry} containing the dictionary classes
for this bean, or an empty registry if
+ * no dictionary classes are found.
+ */
private BeanRegistry findBeanRegistry() {
// Bean dictionary on bean filter.
List<Class<?>> beanDictionaryClasses = nn(beanFilter) ?
copyOf(beanFilter.getBeanDictionary()) : list();
@@ -945,6 +1198,32 @@ public class BeanMeta<T> {
return new BeanRegistry(beanContext, null,
beanDictionaryClasses.toArray(new Class<?>[beanDictionaryClasses.size()]));
}
+ /*
+ * Builds a list of all classes in the class hierarchy for this bean.
+ *
+ * <p>
+ * Traverses the complete inheritance hierarchy (classes and
interfaces) starting from the bean class (or the
+ * interface class specified in the bean filter) and collects all
classes up to (but not including) the stop class.
+ *
+ * <p>
+ * The traversal order follows a depth-first approach:
+ * <ol>
+ * <li>First, recursively traverses the superclass hierarchy
+ * <li>Then, recursively traverses all implemented interfaces
+ * <li>Finally, adds the current class itself
+ * </ol>
+ *
+ * <p>
+ * If a {@link BeanFilter#getInterfaceClass() bean filter interface
class} is specified, the traversal starts
+ * from that interface class instead of the bean class itself. This
allows beans to use properties defined on
+ * a parent interface rather than the concrete implementation class.
+ *
+ * <p>
+ * The resulting list is used to find bean properties, methods, and
fields across the entire class hierarchy.
+ *
+ * @return An unmodifiable list of {@link ClassInfo} objects
representing all classes in the hierarchy, in
+ * traversal order (superclasses first, then interfaces, then the
class itself).
+ */
private List<ClassInfo> findClassHierarchy() {
var result = new LinkedList<ClassInfo>();
// If @Bean.interfaceClass is specified on the parent class,
then we want
@@ -954,6 +1233,24 @@ public class BeanMeta<T> {
return u(result);
}
+ /*
+ * Recursively traverses the class hierarchy and invokes the consumer
for each class found.
+ *
+ * <p>
+ * This is a helper method that performs a depth-first traversal of the
class hierarchy:
+ * <ol>
+ * <li>Recursively processes the superclass (if present and not
the stop class)
+ * <li>Recursively processes all implemented interfaces
+ * <li>Invokes the consumer with the current class
+ * </ol>
+ *
+ * <p>
+ * The traversal stops when it reaches the stop class (which is not
included in the traversal).
+ *
+ * @param c The class to start traversal from.
+ * @param stopClass The class to stop traversal at (exclusive).
Traversal will not proceed beyond this class.
+ * @param consumer The consumer to invoke for each class in the
hierarchy.
+ */
private void findClassHierarchy(ClassInfo c, Class<?> stopClass,
Consumer<ClassInfo> consumer) {
var sc = c.getSuperclass();
if (nn(sc) && ! sc.is(stopClass))
@@ -962,6 +1259,35 @@ public class BeanMeta<T> {
consumer.accept(c);
}
+ /*
+ * Finds the dictionary name (type name) for this bean class.
+ *
+ * <p>
+ * The dictionary name is used in serialized forms to identify the
specific type of a bean instance, enabling
+ * polymorphic serialization and deserialization. This is especially
important when serializing/parsing beans
+ * that are part of an inheritance hierarchy.
+ *
+ * <p>
+ * The dictionary name is determined by searching in the following
order of precedence:
+ * <ol>
+ * <li><b>Bean filter type name:</b> If a bean filter is present
and has a type name specified via
+ * {@link BeanFilter#getTypeName()}, that value is used.
+ * <li><b>Bean registry lookup:</b> If a bean registry exists for
this bean, it is queried for the type name
+ * of this class.
+ * <li><b>Parent class registry lookup:</b> Searches through
parent classes and interfaces (starting from the
+ * second one, skipping the class itself) and checks if
any of their bean registries contain a type name
+ * for this class.
+ * <li><b>{@link Bean @Bean} annotation:</b> If the class has a
{@link Bean @Bean} annotation with a non-empty
+ * {@link Bean#typeName() typeName()}, that value is used.
+ * <li><b>No dictionary name:</b> Returns <jk>null</jk> if no
dictionary name is found.
+ * </ol>
+ *
+ * <p>
+ * If a dictionary name is found, it will be used in serialized output
(typically as a special property like
+ * <js>"_type"</js>) so that parsers can determine the correct class to
instantiate when deserializing.
+ *
+ * @return The dictionary name for this bean, or <jk>null</jk> if no
dictionary name is defined.
+ */
private String findDictionaryName() {
if (nn(beanFilter) && nn(beanFilter.getTypeName()))
return beanFilter.getTypeName();
@@ -997,79 +1323,34 @@ public class BeanMeta<T> {
.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.
+ /*
+ * Finds a bean field by name in the class hierarchy.
*
- * @param outer The outer object if bean class is a non-static inner
member class.
- * @return A new instance of this bean if possible, or <jk>null</jk> if
not.
- * @throws ExecutableException Exception occurred on invoked
constructor/method/field.
- */
- @SuppressWarnings("unchecked")
- protected T newBean(Object outer) throws ExecutableException {
- if (classMeta.isMemberClass() && classMeta.isNotStatic()) {
- if (hasConstructor())
- return getConstructor().<T>newInstance(outer);
- } else {
- if (hasConstructor())
- return getConstructor().<T>newInstance();
- var h = classMeta.getProxyInvocationHandler();
- if (nn(h)) {
- var cl = classMeta.getClassLoader();
- return (T)Proxy.newProxyInstance(cl,
a(classMeta.inner(), java.io.Serializable.class), h);
- }
- }
- return null;
- }
-
- /**
- * Finds all bean fields in the class hierarchy.
+ * <p>
+ * Searches through the complete class hierarchy (as defined by {@link
#classHierarchy}) to find a field
+ * with the specified name. The search is performed in the order
classes appear in the hierarchy, and the
+ * first matching field is returned.
*
* <p>
- * Traverses the complete class hierarchy (as defined by {@link
#classHierarchy}) and collects all fields that
- * meet the bean field criteria:
+ * A field is considered a match if it:
* <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}
+ * <li>Has the exact name specified
+ * <li>Is not static
+ * <li>Is not transient (unless transient fields are not ignored)
+ * <li>Is not annotated with {@link Transient @Transient} (unless
transient fields are not ignored)
+ * <li>Is not annotated with {@link BeanIgnore @BeanIgnore}
* </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.
+ * <p>
+ * This method is used to find fields for bean properties when a field
reference is needed but wasn't
+ * discovered during the initial property discovery phase (e.g., for
properties defined only through
+ * getters/setters).
+ *
+ * @param name The name of the field to find.
+ * @return The {@link FieldInfo} for the field if found, or
<jk>null</jk> if no matching field exists
+ * in the class hierarchy.
*/
- final Collection<FieldInfo> findBeanFields(Visibility v) {
- var noIgnoreTransients = !
beanContext.isIgnoreTransientFields();
- var ap = beanContext.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) {
+ private FieldInfo findInnerBeanField(String name) {
var noIgnoreTransients = !
beanContext.isIgnoreTransientFields();
var ap = beanContext.getAnnotationProvider();