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

commit b5f5d85f45c51a10f7635215e6de6776231c435b
Author: James Bognar <[email protected]>
AuthorDate: Tue May 12 12:23:21 2026 -0400

    refactor: lift per-property BeanRegistry into BeanMeta side-map (TODO-5 
Step 5)
    
    Co-authored-by: Cursor <[email protected]>
---
 .../src/main/java/org/apache/juneau/BeanMeta.java  | 51 ++++++++++++++++++++--
 .../java/org/apache/juneau/BeanPropertyMeta.java   | 34 +++++++--------
 .../main/java/org/apache/juneau/BeanRegistry.java  | 15 +++++++
 todo/TODO-5-bean-runtime-types-to-commons.md       |  8 ++--
 4 files changed, 83 insertions(+), 25 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 29a8de9d20..6a741cbe70 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
@@ -380,6 +380,7 @@ public class BeanMeta<T> {
        private final ClassInfo stopClass;                                      
    // The stop class for hierarchy traversal.
        private final BeanPropertyMeta typeProperty;                            
   // "_type" mock bean property.
        private final String typePropertyName;                                  
   // "_type" property actual name.
+       private final Map<BeanPropertyMeta,BeanRegistry> 
propertyBeanRegistries;   // Per-property BeanRegistry side-map (Step 5 of 
TODO-5 — keeps BeanRegistry off BeanPropertyMeta itself).
 
        /**
         * Constructor.
@@ -429,6 +430,7 @@ public class BeanMeta<T> {
                var getterPropsMap = CollectionUtils.<Method,String>map();  // 
Convert to MethodInfo keys
                var setterPropsMap = CollectionUtils.<Method,String>map();
                var dynaPropertyValue = Value.<BeanPropertyMeta>empty();
+               var propertyBeanRegistriesTemp = 
CollectionUtils.<BeanPropertyMeta,BeanRegistry>map();  // Per-property 
BeanRegistry side-map (TODO-5 Step 5).
                var unsortedPropertiesTemp = false;
                var ba = ap.find(Marshalled.class, cm);
                var btList = 
ap.find(org.apache.juneau.commons.bean.BeanType.class, cm);
@@ -563,6 +565,10 @@ public class BeanMeta<T> {
                                if (pMeta.isDyna())
                                        dynaPropertyValue.set(pMeta);
                                propertiesValue.get().put(k, pMeta);
+                               // Build the property-level BeanRegistry 
side-map entry from the builder's accumulated
+                               // dictionary classes.  Parents to the 
bean-level registry so @MarshalledProp(dictionary={})
+                               // entries chain on top of bean and global 
dictionaries.
+                               propertyBeanRegistriesTemp.put(pMeta, new 
BeanRegistry(marshallingContext, beanRegistry.get(), v.dictionaryClasses));
                        });
 
                        // If a beanFilter is defined, look for inclusion and 
exclusion lists.
@@ -610,7 +616,12 @@ public class BeanMeta<T> {
                setterProps = u(setterPropsMap);
                dynaProperty = dynaPropertyValue.get();
                unsortedProperties = unsortedPropertiesTemp;
-               typeProperty = BeanPropertyMeta.builder(this, 
typePropertyName).canRead().canWrite().rawMetaType(String.class).beanRegistry(beanRegistry.get()).build();
+               typeProperty = BeanPropertyMeta.builder(this, 
typePropertyName).canRead().canWrite().rawMetaType(String.class).build();
+               // Map the synthetic "_type" property to the bean-level 
BeanRegistry so consumers calling
+               // typeProperty.getBeanRegistry() (currently none in-tree, but 
a public API path) get the same
+               // registry the property previously carried as a field.
+               propertyBeanRegistriesTemp.put(typeProperty, 
beanRegistry.get());
+               propertyBeanRegistries = u(propertyBeanRegistriesTemp);
                dictionaryName = memoize(this::findDictionaryName);
                beanProxyInvocationHandler = memoize(() -> 
marshallingContext.isUseInterfaceProxies() && classInfo.isInterface() ? new 
BeanProxyInvocationHandler<>(this) : null);
                var factoryClassTemp = btList.stream().map(x -> 
x.inner().factory()).filter(x -> x != 
org.apache.juneau.commons.function.BeanFactory.Void.class).findFirst().orElse(null);
@@ -625,7 +636,7 @@ public class BeanMeta<T> {
                        if (p.field == null)
                                
findInnerBeanField(p.name).ifPresent(p::setInnerField);
 
-                       if (p.validate(marshallingContext, beanRegistry.get(), 
typeVarImpls, readOnlyProps, writeOnlyProps)) {
+                       if (p.validate(marshallingContext, typeVarImpls, 
readOnlyProps, writeOnlyProps)) {
 
                                installSwapAwareTransforms(p);
 
@@ -755,6 +766,32 @@ public class BeanMeta<T> {
         */
        public BeanRegistry getBeanRegistry() { return beanRegistry.get(); }
 
+       /**
+        * Returns the per-property {@link BeanRegistry} associated with the 
given {@link BeanPropertyMeta}.
+        *
+        * <p>
+        * As of TODO-5 Step 5, {@link BeanPropertyMeta} no longer carries a 
{@link BeanRegistry} field — the per-property
+        * registry now lives in a side-map on this {@link BeanMeta} keyed by 
the property meta itself.  The serializer
+        * and parser sides still need to look up the property-level registry 
for polymorphic dispatch (see
+        * {@link 
org.apache.juneau.parser.ParserSession#getClassMeta(String,BeanPropertyMeta,ClassMeta)}
 and
+        * {@link org.apache.juneau.serializer.SerializerSession} 
dictionary-name resolution); they now route through
+        * this accessor (directly or via the deprecated-style {@link 
BeanPropertyMeta#getBeanRegistry()} delegate).
+        *
+        * <p>
+        * The returned registry chains the property's
+        * {@link org.apache.juneau.annotation.MarshalledProp#dictionary() 
@MarshalledProp(dictionary)} entries on top of
+        * the bean-level registry, which in turn chains on top of the global
+        * {@link MarshallingContext#getBeanDictionary() bean dictionary}.
+        *
+        * @param p The bean property meta to look up.  Can be a normal 
property, the synthetic <js>"_type"</js> property,
+        *      or any other property meta produced by this bean.
+        * @return The bean registry for the specified property, or 
<jk>null</jk> if none was registered (e.g. the property
+        *      belongs to a different bean meta).
+        */
+       public BeanRegistry getPropertyBeanRegistry(BeanPropertyMeta p) {
+               return propertyBeanRegistries.get(p);
+       }
+
        /**
         * Returns the {@link ClassMeta} of this bean.
         *
@@ -1489,9 +1526,15 @@ public class BeanMeta<T> {
                if (nn(beanFilter) && nn(beanFilter.getTypeName()))
                        return beanFilter.getTypeName();
 
+               // Pure-reflection class identity for 
BeanRegistry.getTypeName(...) — Step 5 of TODO-5 lifted the two
+               // surviving `this.classMeta` references inside this method to 
`classInfo.inner()` so the dictionary
+               // lookup does not require a ClassMeta for the bean's own 
class.  BeanRegistry still lives in
+               // juneau-marshall; it just exposes a raw-Class overload for 
callers that have a ClassInfo.
+               var rawClass = classInfo.inner();
+
                var br = getBeanRegistry();
                if (nn(br)) {
-                       String s = br.getTypeName(this.classMeta);
+                       String s = br.getTypeName(rawClass);
                        if (nn(s))
                                return s;
                }
@@ -1503,7 +1546,7 @@ public class BeanMeta<T> {
                        .map(marshallingContext::getClassMeta)
                        .map(ClassMeta::getBeanRegistry)
                        .filter(Objects::nonNull)
-                       .map(x -> x.getTypeName(this.classMeta))
+                       .map(x -> x.getTypeName(rawClass))
                        .filter(Objects::nonNull)
                        .findFirst()
                        .orElse(null);
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 1c896d48e6..357993e386 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
@@ -96,13 +96,13 @@ public class BeanPropertyMeta implements 
Comparable<BeanPropertyMeta> {
                ObjectSwap swap;  // Package-private for BeanMeta access (used 
to install swap-aware transforms)
                BiFunction<MarshallingSession,Object,Object> readTransform;  // 
Package-private; defaults to identity if null.
                BiFunction<MarshallingSession,Object,Object> writeTransform; // 
Package-private; defaults to identity if null.
+               List<ClassInfo> dictionaryClasses;  // Package-private for 
BeanMeta access; @MarshalledProp(dictionary={}) classes scanned during 
validate().
                private boolean isConstructorArg;
                private boolean isUri;
                private boolean isDyna;
                private boolean isDynaGetterMap;
                private ClassMeta<?> typeMeta;
                private List<String> properties;
-               private BeanRegistry beanRegistry;
                private Object overrideValue;
                private BeanPropertyMeta delegateFor;
                private boolean canRead;
@@ -116,17 +116,6 @@ public class BeanPropertyMeta implements 
Comparable<BeanPropertyMeta> {
                        this.name = name;
                }
 
-               /**
-                * Sets the bean registry to use with this bean property.
-                *
-                * @param value The bean registry to use with this bean 
property.
-                * @return This object.
-                */
-               public Builder beanRegistry(BeanRegistry value) {
-                       beanRegistry = assertArgNotNull(ARG_value, value);
-                       return this;
-               }
-
                /**
                 * @return A new BeanPropertyMeta object using this builder.
                 */
@@ -347,8 +336,13 @@ public class BeanPropertyMeta implements 
Comparable<BeanPropertyMeta> {
                /**
                 * Validates this bean property configuration.
                 *
+                * <p>
+                * After validation succeeds, marshalling-side callers 
(currently {@link BeanMeta}) read the
+                * {@link #dictionaryClasses} field to construct the 
property-level {@link BeanRegistry} and store it in a
+                * side-map keyed by the built {@link BeanPropertyMeta}.  The 
bean-modeling layer itself no longer carries a
+                * {@link BeanRegistry} reference.
+                *
                 * @param bc The bean context.
-                * @param parentBeanRegistry The parent bean registry.
                 * @param typeVarImpls Type variable implementations.
                 * @param bpro Bean properties read-only set.
                 * @param bpwo Bean properties write-only set.
@@ -360,7 +354,7 @@ public class BeanPropertyMeta implements 
Comparable<BeanPropertyMeta> {
                        "java:S112",  // Generic exception thrown; acceptable 
for framework/lifecycle methods
                        "java:S6541"  // Brain Method: validate() intentionally 
consolidates property metadata resolution
                })
-               public boolean validate(MarshallingContext bc, BeanRegistry 
parentBeanRegistry, TypeVariables typeVarImpls, Set<String> bpro, Set<String> 
bpwo) throws Exception {
+               public boolean validate(MarshallingContext bc, TypeVariables 
typeVarImpls, Set<String> bpro, Set<String> bpwo) throws Exception {
 
                        var bdClasses = list();
                        var ap = bc.getAnnotationProvider();
@@ -457,7 +451,7 @@ public class BeanPropertyMeta implements 
Comparable<BeanPropertyMeta> {
                        if (rawTypeMeta == null)
                                return false;
 
-                       beanRegistry = new BeanRegistry(bc, parentBeanRegistry, 
bdClasses.stream().map(ReflectionUtils::info).toList());
+                       dictionaryClasses = 
bdClasses.stream().map(ReflectionUtils::info).toList();
 
                        isDyna = "*".equals(name);
 
@@ -546,7 +540,6 @@ public class BeanPropertyMeta implements 
Comparable<BeanPropertyMeta> {
        private final Supplier<List<AnnotationInfo<?>>> annotations;     // 
Memoized list of all annotations on this property.
        private final MarshallingContext bc;                                    
// The context that created this meta.
        private final BeanMeta<?> beanMeta;                              // The 
bean that this property belongs to.
-       private final BeanRegistry beanRegistry;                         // 
Bean registry for resolving bean types in this property.
        private final boolean canRead;                                   // 
True if this property can be read.
        private final boolean canWrite;                                  // 
True if this property can be written.
        private final BeanPropertyMeta delegateFor;                      // The 
bean property that this meta is a delegate for.
@@ -579,7 +572,6 @@ public class BeanPropertyMeta implements 
Comparable<BeanPropertyMeta> {
                annotations = memoize(this::findAnnotations);
                bc = b.bc;
                beanMeta = b.beanMeta;
-               beanRegistry = b.beanRegistry;
                canRead = b.canRead;
                canWrite = b.canWrite;
                delegateFor = b.delegateFor;
@@ -897,9 +889,15 @@ public class BeanPropertyMeta implements 
Comparable<BeanPropertyMeta> {
         *      <li>Dictionary defined via {@link 
MarshallingContext.Builder#beanDictionary(Class...)}.
         * </ol>
         *
+        * <p>
+        * The per-property {@link BeanRegistry} no longer lives on this 
object; it is stored in a marshalling-side
+        * side-map on {@link BeanMeta} keyed by {@link BeanPropertyMeta}.  
This method delegates to
+        * {@link BeanMeta#getPropertyBeanRegistry(BeanPropertyMeta)} for 
backwards compatibility with existing call sites
+        * in the marshalling layer (parser/serializer sessions, XML 
content-property handling, etc.).
+        *
         * @return The bean dictionary in use for this bean property.  Never 
<jk>null</jk>.
         */
-       public BeanRegistry getBeanRegistry() { return beanRegistry; }
+       public BeanRegistry getBeanRegistry() { return 
beanMeta.getPropertyBeanRegistry(this); }
 
        /**
         * Returns the {@link ClassMeta} of the class of this property.
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanRegistry.java 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanRegistry.java
index 7aad729292..a1197c11bd 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanRegistry.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanRegistry.java
@@ -116,6 +116,21 @@ public class BeanRegistry {
                return isEmpty ? null : reverseMap.get(c.inner());
        }
 
+       /**
+        * Given the specified raw class, return the dictionary name for it.
+        *
+        * <p>
+        * Variant of {@link #getTypeName(ClassMeta)} that takes the raw {@link 
Class} directly so callers in the
+        * bean-modeling layer (which is being decoupled from {@link 
ClassMeta}) can perform a polymorphic-dispatch
+        * lookup without holding a {@link ClassMeta} reference.
+        *
+        * @param c The class to lookup in this registry.
+        * @return The dictionary name for the specified class in this 
registry, or <jk>null</jk> if not found.
+        */
+       public String getTypeName(Class<?> c) {
+               return isEmpty || c == null ? null : reverseMap.get(c);
+       }
+
        /**
         * Returns <jk>true</jk> if this dictionary has an entry for the 
specified type name.
         *
diff --git a/todo/TODO-5-bean-runtime-types-to-commons.md 
b/todo/TODO-5-bean-runtime-types-to-commons.md
index f42ce844f0..f6cdd47a87 100644
--- a/todo/TODO-5-bean-runtime-types-to-commons.md
+++ b/todo/TODO-5-bean-runtime-types-to-commons.md
@@ -4,7 +4,9 @@ This is the remaining work from **Phase 5 of the bean-layer 
split**. Phase 5a (t
 
 ---
 
-## Status (as of Phase 5d checkpoint)
+## Status (as of Phase 5e checkpoint)
+
+**Step 5 complete.** `BeanPropertyMeta` no longer carries a `BeanRegistry` 
field. Picked **Option B** (side-map keyed by `BeanPropertyMeta`) — there are 
only 3 marshalling-side call sites that read the per-property registry 
(`ParserSession.getClassMeta(...)`, `SerializerSession.getBeanTypeName(...)`, 
`XmlParserSession.parseIntoMap-mixed-content`), and they all use the existing 
`pMeta.getBeanRegistry()` public accessor. The accessor stays as a 
backwards-compat delegate that routes to the  [...]
 
 **Step 4 complete.** `BeanMap` no longer takes a `MarshallingSession` in its 
constructor. Picked **Option (c)** (transitional setter) per the plan — 
minimum-disturbance and no behavioral change. The `private final 
MarshallingSession session` field became `private MarshallingSession session` 
(no longer final, defaults to null), the constructor signature dropped to 
`BeanMap(T bean, BeanMeta<T> meta)`, and a new `protected void 
setMarshallingSession(MarshallingSession value)` is called by t [...]
 
@@ -14,14 +16,14 @@ This is the remaining work from **Phase 5 of the bean-layer 
split**. Phase 5a (t
 - [x] **Step 2** — Replaced `ClassMeta` with `ClassInfo` for pure-reflection 
access inside `BeanMeta`. Added a `classInfo` field (a re-typed view of the 
same instance as `classMeta`, since `ClassMeta extends ClassInfoTyped extends 
ClassInfo`) and routed all reflection calls (`inner()`, `isMemberClass()`, 
`isNotStatic()`, `isAnonymousClass()`, `isRecord()`, `isInterface()`, 
`getRecordComponents()`, `getName()`, `getParentsAndInterfaces()`, 
`getPublicConstructors()`, `getDeclaredConstructo [...]
 - [x] **Step 3** — Removed swap-aware `get`/`set` from `BeanPropertyMeta`. 
Picked option (a) (pluggable callbacks). Added 
`BiFunction<MarshallingSession,Object,Object>` `readTransform` / 
`writeTransform` fields with identity defaults; exposed corresponding 
`Builder.readTransform(...)` / `Builder.writeTransform(...)` setters. The 
bean-modeling `get`/`set` paths inside `BeanPropertyMeta` no longer call 
`ObjectSwap.swap` / `ObjectSwap.unswap` directly — instead they invoke the 
installed tra [...]
 - [x] **Step 4** — Removed `MarshallingSession` from the `BeanMap` constructor 
signature (Option c — transitional setter). `BeanMap` now exposes 
`setMarshallingSession(MarshallingSession)` that the marshalling layer wires in 
immediately after construction. The `session` field defaults to null on direct 
construction; only `BeanMap.getBean()` (for read-only beans with constructor 
args), `BeanPropertyMeta.add`/`set`, and child-properties-filter operations 
need it set, and they all go throug [...]
-- [ ] **Step 5** — Remove `BeanRegistry` field from `BeanPropertyMeta`. Lift 
dictionary metadata into a marshalling-side companion (`MarshalledPropertyMeta` 
or a side-map keyed by `BeanPropertyMeta`).
+- [x] **Step 5** — Removed `BeanRegistry` field from `BeanPropertyMeta`. 
Picked **Option B** — side-map keyed by `BeanPropertyMeta` lives on `BeanMeta` 
(`Map<BeanPropertyMeta,BeanRegistry> propertyBeanRegistries`). Per-property 
registries are constructed by `BeanMeta` after `v.build()` from the builder's 
package-private `dictionaryClasses` field (populated during 
`Builder.validate(...)`). `Builder.beanRegistry(...)` public setter, the 
`beanRegistry` builder field, and the `parentBeanRegi [...]
 - [ ] **Step 6** — `BeanMeta` becomes constructible by both `ClassMeta` and 
direct `commons.bean` callers via `BeanMeta.of(MyClass.class, 
BeanConfigContext.DEFAULT)`. `ClassMeta` becomes a *consumer* of `BeanMeta` 
rather than its creator.
 - [ ] **Step 7** — Re-check whether `ExtendedBeanMeta` and per-format 
extensions (`XmlBeanMeta`, `RdfBeanMeta`, `HtmlBeanMeta`) need to follow 
`BeanMeta` to `commons.bean`. Default expectation: they stay in 
`juneau-marshall`.
 - [ ] **Step 8** — `git mv` the eight runtime types into 
`juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/`. 
Verify `juneau-commons` still compiles standalone (`cd 
juneau-core/juneau-commons && mvn clean compile`).
 - [ ] **Step 9** — Reference sweep: 80–120 unique files (mostly inside 
`juneau-marshall`). Update imports, Javadoc `{@link …}` references, 
package-info docs.
 - [ ] **Step 10** — Update `juneau-docs` release notes / migration guide 
(`docs/pages/release-notes/9.5.0.md`, `## Package Moves` section) with the 
bean-runtime relocations.
 
-The "incomplete-but-documented over broken-build" rule from Phase 5a still 
applies. When picking up the next slice of this work, **Step 5 is the 
recommended next checkpoint** — Step 4 dropped the constructor coupling but the 
`BeanMap.session` field is still read by `BeanPropertyMeta.add` / 
`BeanPropertyMeta.set` / `applyChildPropertiesFilter` (for `convertToType` / 
`JsonList(session)` / `JsonMap(session)` construction) and a transitional 
`BeanMap.getMarshallingSession()` accessor still s [...]
+The "incomplete-but-documented over broken-build" rule from Phase 5a still 
applies. When picking up the next slice of this work, **Step 6 is the 
recommended next checkpoint** — Step 5 dropped the `BeanRegistry` field from 
`BeanPropertyMeta` (now on a `BeanMeta` side-map) and lifted two of the 
surviving `this.classMeta` references in `BeanMeta.findDictionaryName(...)` to 
`classInfo.inner()`. The remaining marshalling coupling on `BeanMeta` itself: 
(a) the `marshallingContext` field is rea [...]
 
 ---
 

Reply via email to