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 [...] ---
