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 a972ff62067dfe41ad557124144c84a850d81c00 Author: James Bognar <[email protected]> AuthorDate: Tue May 12 13:24:27 2026 -0400 docs: update TODO-5 plan with Step 8a status and Step 8b breakdown Step 8a is committed as 3a74fcd50a. Update the plan to: - Mark Step 8a [x] with the commit hash. - Split Step 8 into 8a/8b/8c; 8b is the physical git mv, 8c is optional cleanup. - Move the recommended-next-checkpoint pointer to Step 8b. - Spell out the remaining SPI seams that Step 8b has to extract (BeanTypeInfo for ClassMeta, BeanRegistryLookup for BeanRegistry, optional BeanPropertySwap for ObjectSwap) and the design options for each. - Document why moving the 8 types one-at-a-time is not viable (tight cluster of cross-references) — they have to move as a unit. - List the remaining marshalling-side references that survived Step 8a: ClassMeta, BeanRegistry, ObjectSwap, MarshalledFilter, MarshalledProp, Json5Serializer, JsonMap/JsonList — with notes on each one's migration shape. No code changes; documentation/plan update only. Co-authored-by: Cursor <[email protected]> --- todo/TODO-5-bean-runtime-types-to-commons.md | 43 ++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/todo/TODO-5-bean-runtime-types-to-commons.md b/todo/TODO-5-bean-runtime-types-to-commons.md index 4b8b3eb699..56d740ecbd 100644 --- a/todo/TODO-5-bean-runtime-types-to-commons.md +++ b/todo/TODO-5-bean-runtime-types-to-commons.md @@ -4,7 +4,25 @@ This is the remaining work from **Phase 5 of the bean-layer split**. Phase 5a (t --- -## Status (as of Phase 5g checkpoint) +## Status (as of Phase 5h checkpoint) + +**Step 8a complete (SPI-seam extraction).** Commit `3a74fcd50a`. The minimum SPI surface that the 8 target types need from the marshalling layer is now in place: + +- New SPI: `org.apache.juneau.commons.bean.BeanSession`. Captures the session-aware operations (`convertToType`, `convertToMemberType`, `toBeanMap`) that `BeanMap`/`BeanPropertyMeta` need from `MarshallingSession`. The `targetType` parameters are typed as `Object` so `commons.bean` does not need to import `ClassMeta`. The `toBeanMap` method is generic (`<T> Object toBeanMap(T bean)`) so `MarshallingSession`'s existing covariant `<T> BeanMap<T> toBeanMap(T)` satisfies it without a bridge method. +- `MarshallingSession` now implements `BeanSession`. Two new bridge methods (`convertToType(Object,Object)` / `convertToMemberType(Object,Object,Object)`) dispatch `Object`-typed `targetType` arguments into the existing `ClassMeta` / `Class` typed overloads. `IllegalArgumentException` is thrown for unsupported `targetType` kinds. +- `BeanConfigContext` now carries four additional bean-modeling settings that previously only lived on `MarshallingContext`: `beanMapPutReturnsOldValue`, `ignoreInvocationExceptionsOnGetters`, `ignoreInvocationExceptionsOnSetters`, `ignoreUnknownNullBeanProperties`. Defaults match `MarshallingContext`'s historical defaults. Builder, copy() and DEFAULT are wired through. +- `MarshallingContext.buildBeanConfigContext()` populates the four new settings from its own resolved values, so behavior is unchanged. +- `BeanPropertyMeta` reads those four settings through `config.isX()` instead of `bc.isX()`. The corresponding `nn(bc) && bc.isX()` guards from Step 6 are simplified to unconditional `config.isX()` reads. +- `BeanConfigContext_Test` extended to exercise the new defaults, builder setters, and copy() preservation. + +What the 8 target types still pull from `juneau-marshall` (left for Step 8b's SPI work + physical move): +1. `ClassMeta` — used as the type metadata for property `rawTypeMeta` / `typeMeta`, the `BeanMeta.classMeta` field, the `applyChildPropertiesFilter` parameter, and `BeanMap.getBean()`'s Optional-init / constructor-args paths. Needs either: a `BeanTypeInfo` SPI typed in `commons.bean`, or `Object`-typed field with marshalling-side narrowing, or migration of consumption to marshalling-side helpers. +2. `BeanRegistry` — used by `BeanMeta`'s side-map (`Map<BeanPropertyMeta,BeanRegistry> propertyBeanRegistries`) and per-property registry construction inside `BeanMeta` from `Builder.dictionaryClasses`. Needs a `BeanRegistryLookup` SPI (or migrate the side-map to the marshalling-side). +3. `ObjectSwap` — referenced by `BeanPropertyMeta.swap` field and `BeanMap.getBean()`'s Optional handling. The actual swap/unswap calls already route through `readTransform`/`writeTransform` (Step 3); the field itself is just metadata for the defensive double-unswap check. Needs either: a `BeanPropertySwap` SPI, or move the field to a side-map on `BeanMeta`. +4. `MarshalledFilter` — used by `BeanMeta` (composed via `getMarshalledFilter()`), by `BeanMetaFiltered` (passes through to `BeanMeta`'s old constructor), and by `MarshalledProp` annotation reads inside `BeanPropertyMeta.Builder.validate()`. Likely stay-behind on the marshalling side; `BeanMetaFiltered` migrates with `BeanMeta` and the `MarshalledFilter` parameter on the old constructor stays `Object`-typed (or moves to a marshalling-side wrapper). +5. `MarshalledProp` annotation — read in `BeanPropertyMeta.Builder.validate()` to detect filtered properties. Marshalling-side annotation; needs either a `commons.bean` mirror or be lifted out of `validate()` into a marshalling-side post-processor. +6. `Json5Serializer` — used by `BeanProxyInvocationHandler.toString()` only. Trivial to replace with a `BeanSession`-style hook or just inline minimal JSON5-ish formatting. +7. `JsonMap` / `JsonList` — used inside `BeanPropertyMeta`/`BeanMap` for collection/map building during set operations. Likely route through `BeanSession.toBeanMap`/converter callbacks, or accept `Map`/`List` instead. **Step 7 complete (no-op + hardening).** Per-format extension survey confirmed: `ExtendedBeanMeta` (composes `BeanMeta<?>`) lives in `juneau-marshall`; `XmlBeanMeta` and `RdfBeanMeta` extend `ExtendedBeanMeta`; no `HtmlBeanMeta` exists. All three are marshalling-side types (built only from `XmlSerializer`/`XmlParser`/`RdfSerializer`/`RdfParser`, always over a marshalling-built `BeanMeta` where `classMeta` is non-null). They stay in `juneau-marshall` unchanged. @@ -71,16 +89,25 @@ Known limitations of the commons-side path (acceptable for Step 6, scoped for la - [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 [...] - [x] **Step 6** — `BeanMeta.of(Class<T>, BeanConfigContext)` factory + `protected BeanMeta(Class<T>, BeanConfigContext)` constructor wired up. `BeanMeta` now carries a non-null `BeanConfigContext config` facade for all settings reads; the `marshallingContext` and `classMeta` fields are documented-nullable and stay null on the commons-side path. `BeanPropertyMeta.Builder.bc` and `BeanPropertyMeta.bc` similarly nullable; new mirrored `config` field on both. `Builder.validate(...)` accepts [...] - [x] **Step 7** — Per-format extension survey + pre-Step-8 hardening. (a) Survey result: `ExtendedBeanMeta` (composes `BeanMeta<?>`), `XmlBeanMeta` (extends `ExtendedBeanMeta`), `RdfBeanMeta` (extends `ExtendedBeanMeta`); no `HtmlBeanMeta` exists. All marshalling-side, all stay in `juneau-marshall`. (b) Hardened `BeanPropertyMeta.add(BeanMap,String,Object)` / `add(BeanMap,String,String,Object)` / `setArray` / `applyChildPropertiesFilter` (all throw `UnsupportedOperationException` with a [...] -- [ ] **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`). +- [x] **Step 8a** — SPI seams in `commons.bean` (commit `3a74fcd50a`). `BeanSession` interface created, `MarshallingSession` implements it, four marshalling-only-historically settings migrated to `BeanConfigContext` so `BeanPropertyMeta`/`BeanMap` can stop reading them through `MarshallingContext`. See "Status (as of Phase 5h checkpoint)" above for full detail. +- [ ] **Step 8b** — Round out the remaining SPI seams (`BeanTypeInfo` for `ClassMeta`, `BeanRegistryLookup` for `BeanRegistry`, optional `BeanPropertySwap` for `ObjectSwap`), retype the 8 types' fields to use the seams (or `Object`) on the bean-modeling side, then `git mv` all 8 files 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 8c** — (optional) Cleanup pass for anything that comes up during 8b: deprecated bridges, stale imports, package-info docs, etc. - [ ] **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 8 is the recommended next checkpoint** — the physical `git mv` of the eight runtime types into `juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/`. Pre-flight is now clean: per-format `BeanMeta` extensions (`ExtendedBeanMeta`/`XmlBeanMeta`/`RdfBeanMeta`) are confirmed marshalling-side and stay put (Step 7); the previously unguarded [...] - -**Remaining risks/surprises for Step 8:** -- The eight files reference `MarshallingContext`/`MarshallingSession`/`ClassMeta`/`BeanRegistry`/`ObjectSwap` in many places that survived Step 6/7 (the `installSwapAwareTransforms` install path, the `BeanRegistry` side-map on `BeanMeta`, the `MarshallingSession` field on `BeanMap`, the `ClassMeta classMeta` field on `BeanMeta`, etc.). Physically moving the files into `commons.bean` will require either: (a) leaving these fields in place but moving the types, accepting that `juneau-common [...] -- `BeanPropertyMeta.applyChildPropertiesFilter` directly takes a `ClassMeta` parameter — the signature itself encodes a marshalling-side type. If it stays on `BeanPropertyMeta` after the move, `commons.bean` will need a forward reference to `ClassMeta` (which is in `juneau-marshall`). Likely needs to migrate to a side helper in the marshalling layer. -- `BeanMap.getBean(boolean create)`'s constructor-args path uses `session.convertToType(rawVal, cm)` — this entire block is `@BeanCtor` parser-side behavior. Should migrate to a marshalling-side helper after the move (Step 9 or later). +The "incomplete-but-documented over broken-build" rule from Phase 5a still applies. **The recommended next checkpoint is Step 8b** — extracting the remaining SPI seams that the 8 types need from the marshalling layer, then doing the physical `git mv`. Step 8a is the prerequisite (`BeanSession` is in place and `BeanConfigContext` already covers the bean-modeling boolean settings); Step 8b can build on those. + +**Remaining risks/surprises for Step 8b:** +- The 8 target types are tightly clustered: `BeanMap` ↔ `BeanPropertyMeta` ↔ `BeanMeta` ↔ `BeanMapEntry` ↔ `BeanPropertyValue` ↔ `BeanPropertyConsumer` ↔ `BeanMetaFiltered` ↔ `BeanProxyInvocationHandler`. They reference each other in field declarations and method signatures, so they have to move **as a unit**. Moving just the leaf types (e.g. `BeanPropertyConsumer`, `BeanMapEntry`) would create circular cross-module references because they reference `BeanPropertyMeta`/`BeanMap` which sta [...] +- `ClassMeta` is the biggest seam to design. Options: + - **(a)** Introduce `BeanTypeInfo` interface in `commons.bean` (minimum surface: `inner()`, `isPrimitive()`, `isOptional()`, `getOptionalDefault()`, `isAssignableTo`, `isCollection()`, `isMap()`, `isString()`, `isObject()`, `getElementType()`, `getValueType()`, `getKeyType()`, `getInfo()`); have `ClassMeta` implement it. Pro: minimal API churn on the 8 types. Con: large interface to design carefully. + - **(b)** Retype the fields as `Object` on the bean-modeling side; cast to `ClassMeta` in narrow marshalling-side helpers. Pro: zero new SPI surface. Con: stringly-typed; harder to read. + - **(c)** Move the consumption sites (e.g. `BeanMap.getBean()`'s Optional-init, `applyChildPropertiesFilter`) into marshalling-side helpers; leave `BeanMap`/`BeanPropertyMeta` with no `ClassMeta` references at all. Pro: cleanest commons.bean. Con: refactors marshalling-side code along with the move. +- `BeanRegistry` side-map (`Map<BeanPropertyMeta,BeanRegistry> propertyBeanRegistries`) on `BeanMeta` is currently typed against the concrete marshalling-side `BeanRegistry`. Either: keep the map but type its value as `Object` (commons-side narrowing-as-needed), or introduce a `BeanRegistryLookup` interface (`String getTypeName(Class<?>)` + `Class<?> getClassMeta(String)` is probably all that's needed). +- `ObjectSwap` field on `BeanPropertyMeta` is only metadata after Step 3 — the actual swap/unswap calls go through `readTransform`/`writeTransform`. Probably the cleanest move is to retype `swap` as `Object` and let the few callers (`setPropertyValue`'s defensive double-unswap *check*, `BeanMapEntry`'s Javadoc) live with that. +- `BeanMetaFiltered` extends `BeanMeta` and calls `super(innerMeta.getClassMeta(), innerMeta.getMarshalledFilter(), pNames, null)`. The old constructor signature (`BeanMeta(ClassMeta<T>, MarshalledFilter, String[], ClassInfo)`) carries `ClassMeta` and `MarshalledFilter` as parameters. After the move, that constructor either: stays on `BeanMeta` typed against `Object`s, or moves to a marshalling-side factory helper. The new `BeanMeta.of(Class, BeanConfigContext)` factory chain from Step 6 [...] +- `MarshalledProp` annotation read inside `BeanPropertyMeta.Builder.validate()` is the only direct marshalling-annotation reference in the 8 types' source. Either: lift the read out into a marshalling-side post-processor that mutates the `Builder` before `build()`, or move `MarshalledProp` to `commons.bean` (it's already paired with `MarshalledFilter` which is squarely marshalling-side, so the lift-out is cleaner). +- `BeanProxyInvocationHandler.toString()` uses `Json5Serializer.DEFAULT.toString(...)`. Trivially replaceable with `Objects.toString(...)` or routed through a `BeanSession`-style formatter. ---
