You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2026/05/13 21:30:05 UTC
(juneau) 13/21: refactor: lift BeanMeta constructor body + CharSequence parsing to commons SPI (TODO-5 Step 8b-ii Phase C tasks 3 + 4-deferred)
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 4f2f61c4cb677ed52c016ed61348a24c3dc7af32
Author: James Bognar <ja...@jamesbog-ltmjqf2.internal.salesforce.com>
AuthorDate: Wed May 13 08:36:32 2026 -0400
refactor: lift BeanMeta constructor body + CharSequence parsing to commons SPI (TODO-5 Step 8b-ii Phase C tasks 3 + 4-deferred)
Co-authored-by: Cursor <cu...@cursor.com>
---
.../apache/juneau/commons/bean/BeanSession.java | 32 +++
.../src/main/java/org/apache/juneau/BeanMeta.java | 210 +++---------------
.../java/org/apache/juneau/BeanPropertyMeta.java | 5 +-
.../juneau/MarshalledBeanMetaInitializer.java | 242 +++++++++++++++++++++
.../juneau/MarshalledPropertyPostProcessor.java | 76 +++++++
.../java/org/apache/juneau/MarshallingSession.java | 34 +++
todo/TODO-5-bean-runtime-types-to-commons.md | 72 ++++--
7 files changed, 478 insertions(+), 193 deletions(-)
diff --git a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanSession.java b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanSession.java
index 8b4b511079..5823355100 100644
--- a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanSession.java
+++ b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanSession.java
@@ -70,6 +70,38 @@ public interface BeanSession {
*/
Object convertToMemberType(Object outer, Object value, Object targetType);
+ /**
+ * Parses the specified JSON-formatted character sequence into a {@link java.util.Map}.
+ *
+ * <p>
+ * Used by {@code BeanPropertyMeta.setPropertyValue} when a {@link CharSequence} value is supplied for a
+ * {@code Map}-typed property — the bean-modeling layer cannot reference the marshalling-side JSON parser
+ * directly, so the parse is delegated to the session via this SPI.
+ *
+ * <p>
+ * Implementations typically route through {@code JsonMap.ofJson(value).session(this)}.
+ *
+ * @param value The JSON-formatted character sequence to parse. Must not be <jk>null</jk>.
+ * @return The parsed map.
+ */
+ java.util.Map<?,?> parseToMap(CharSequence value);
+
+ /**
+ * Parses the specified JSON-formatted character sequence into a {@link java.util.Collection}.
+ *
+ * <p>
+ * Used by {@code BeanPropertyMeta.setPropertyValue} when a {@link CharSequence} value is supplied for a
+ * {@code Collection}-typed property — the bean-modeling layer cannot reference the marshalling-side JSON
+ * parser directly, so the parse is delegated to the session via this SPI.
+ *
+ * <p>
+ * Implementations typically route through {@code new JsonList(value).setBeanSession(this)}.
+ *
+ * @param value The JSON-formatted character sequence to parse. Must not be <jk>null</jk>.
+ * @return The parsed collection.
+ */
+ java.util.Collection<?> parseToList(CharSequence value);
+
/**
* Wraps the specified bean in a {@code BeanMap}.
*
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 ae900b20f8..1136b1e611 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
@@ -40,9 +40,6 @@ import org.apache.juneau.commons.reflect.Visibility;
import org.apache.juneau.commons.utils.*;
import org.apache.juneau.commons.inject.*;
import org.apache.juneau.commons.bean.*;
-import org.apache.juneau.parser.*;
-import org.apache.juneau.serializer.*;
-import org.apache.juneau.swap.*;
/**
* Encapsulates all access to the properties of a bean class (like a souped-up {@link java.beans.BeanInfo}).
@@ -226,16 +223,16 @@ public class BeanMeta<T> {
if (bc.isNotABean(cm))
return notABean("Class matches exclude-class list");
- if (bc.isBeansRequireSerializable() && ! cm.isAssignableTo(Serializable.class) && ! ap.has(Marshalled.class, cm) && ! ap.has(org.apache.juneau.commons.bean.BeanType.class, cm))
+ if (bc.isBeansRequireSerializable() && ! cm.isAssignableTo(Serializable.class) && ! ap.has(Marshalled.class, cm) && ! ap.has(BeanType.class, cm))
return notABean("Class is not serializable");
if (ap.has(BeanIgnore.class, cm))
return notABean("Class is annotated with @BeanIgnore");
- if ((! bc.getBeanClassVisibility().isVisible(cm.getModifiers()) || cm.isAnonymousClass()) && ! ap.has(Marshalled.class, cm) && ! ap.has(org.apache.juneau.commons.bean.BeanType.class, cm))
+ if ((! bc.getBeanClassVisibility().isVisible(cm.getModifiers()) || cm.isAnonymousClass()) && ! ap.has(Marshalled.class, cm) && ! ap.has(BeanType.class, cm))
return notABean("Class is not public");
- var bm = new BeanMeta<>(cm, findMarshalledFilter(cm), null, implClass);
+ var bm = new BeanMeta<>(cm, MarshalledBeanMetaInitializer.findMarshalledFilter(cm), null, implClass);
if (nn(bm.notABeanReason))
return notABean(bm.notABeanReason);
@@ -275,46 +272,6 @@ public class BeanMeta<T> {
return name.orElse(null);
}
- /*
- * Finds and creates the bean filter for the specified class metadata.
- *
- * <p>
- * Searches for {@link Marshalled @Marshalled} annotations on the class and its parent classes/interfaces. If found, creates a
- * {@link MarshalledFilter} that applies the configuration from those annotations.
- *
- * <p>
- * When multiple {@link Marshalled @Marshalled} annotations are found (e.g., on a parent class and a child class), they are
- * applied in reverse order (parent classes first, then child classes). This ensures that child class annotations
- * override parent class annotations, allowing child classes to customize or extend the bean configuration.
- *
- * <p>
- * The bean filter controls various aspects of bean serialization and parsing, such as:
- * <ul>
- * <li>Property inclusion/exclusion lists
- * <li>Property ordering and sorting
- * <li>Type name mapping for dictionary lookups
- * <li>Fluent setter detection
- * <li>Read-only and write-only property definitions
- * </ul>
- *
- * @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 {@link Marshalled @Marshalled} annotations are found on the class or its hierarchy.
- * @see Marshalled
- * @see MarshalledFilter
- */
- private static <T> MarshalledFilter findMarshalledFilter(ClassMeta<T> cm) {
- var ap = cm.getMarshallingContext().getAnnotationProvider();
- var l = ap.find(Marshalled.class, cm);
- var bt = ap.find(org.apache.juneau.commons.bean.BeanType.class, cm);
- if (l.isEmpty() && bt.isEmpty())
- return null;
- return MarshalledFilter.create(cm)
- .applyAnnotations(reverse(l.stream().map(AnnotationInfo::inner).toList()))
- .applyBeanTypeAnnotations(reverse(bt.stream().map(AnnotationInfo::inner).toList()))
- .build();
- }
-
/*
* Extracts the property name from a single {@link BeanProp @BeanProp} or {@link Name @Name} annotation.
*
@@ -359,7 +316,7 @@ public class BeanMeta<T> {
}
private BeanConstructor beanConstructor; // The constructor for this bean.
- private final MarshallingContext marshallingContext; // The bean context that created this metadata object. Null when constructed via {@link #of(Class, BeanConfigContext)}.
+ private final Object marshallingContext; // MarshallingContext, but Object-typed so the field can live in commons.bean. Cast to MarshallingContext at marshalling-side use sites. Null when constructed via {@link #of(Class, BeanConfigContext)}.
private final BeanConfigContext config; // Bean-modeling settings facade — always non-null. Sources: marshallingContext.getBeanConfigContext() (marshalling-side) or the explicit BeanConfigContext (commons-side).
private final BeanFilter beanFilter; // Optional bean filter associated with the target class. Typed as the bean-modeling-side SPI seam; marshalling-side callers cast back to {@link MarshalledFilter} via {@link #getMarshalledFilter()}.
private final NullableSupplier<InvocationHandler> beanProxyInvocationHandler; // The invocation handler for this bean (if it's an interface).
@@ -445,7 +402,7 @@ public class BeanMeta<T> {
* @param implClass Optional implementation class constructor to use if one cannot be found. Can be <jk>null</jk>.
*/
protected BeanMeta(BeanTypeInfo<T> cm, BeanFilter bf, String[] pNames, ClassInfo implClass) {
- this((ClassMeta<T>) cm, (ClassMeta<T>) cm, ((ClassMeta<T>) cm).getMarshallingContext().getBeanConfigContext(), ((ClassMeta<T>) cm).getMarshallingContext(), bf, pNames, implClass);
+ this(cm, MarshalledBeanMetaInitializer.classInfoOf(cm), MarshalledBeanMetaInitializer.configOf(cm), MarshalledBeanMetaInitializer.contextOf(cm), bf, pNames, implClass);
}
/**
@@ -468,7 +425,7 @@ public class BeanMeta<T> {
"java:S3776", // Cognitive complexity acceptable for bean metadata initialization
"java:S107" // 7 parameters needed to support both construction paths
})
- private BeanMeta(ClassMeta<T> cm, ClassInfo ci0, BeanConfigContext config, MarshallingContext mc, BeanFilter bf, String[] pNames, ClassInfo implClass) {
+ private BeanMeta(BeanTypeInfo<T> cm, ClassInfo ci0, BeanConfigContext config, Object mc, BeanFilter bf, String[] pNames, ClassInfo implClass) {
classMeta = cm;
classInfo = ci0;
this.config = config;
@@ -621,18 +578,18 @@ public class BeanMeta<T> {
propertiesValue.set(unsortedPropertiesTemp ? map() : sortedMap());
- normalProps.forEach((k, v) -> {
- var pMeta = v.build();
- 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. Skipped on the commons-side path
- // (no marshallingContext means no BeanRegistry construction).
- if (nn(marshallingContext) && nn(v.dictionaryClasses))
- propertyBeanRegistriesTemp.put(pMeta, new BeanRegistry(marshallingContext, (BeanRegistry) beanRegistry.get(), v.dictionaryClasses));
- });
+ normalProps.forEach((k, v) -> {
+ var pMeta = v.build();
+ 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. Skipped on the commons-side path
+ // (no marshallingContext means no BeanRegistry construction).
+ if (nn(marshallingContext) && nn(v.dictionaryClasses))
+ propertyBeanRegistriesTemp.put(pMeta, MarshalledBeanMetaInitializer.buildPropertyBeanRegistry(marshallingContext, beanRegistry.get(), v.dictionaryClasses));
+ });
// If a beanFilter is defined, look for inclusion and exclusion lists.
if (bf != null) {
@@ -702,16 +659,15 @@ public class BeanMeta<T> {
// When constructed via the commons-side path, marshallingContext is null and validate() runs in
// raw-reflection mode (no ClassMeta/ObjectSwap/BeanRegistry resolution).
- if (p.validate(marshallingContext, typeVarImpls, readOnlyProps, writeOnlyProps)) {
+ if (p.validate((BeanTypeResolver) marshallingContext, typeVarImpls, readOnlyProps, writeOnlyProps)) {
// Marshalling-side post-processor — applies @MarshalledProp / @Swap annotation effects
- // (swap detection, properties override, dictionary classes) after validate() succeeds.
- // Skipped on the commons-side path: those annotations require a MarshallingContext to
- // resolve ObjectSwap/StringFormatSwap/Surrogate types and the swap class meta.
+ // (swap detection, properties override, dictionary classes) and installs swap-aware
+ // read/write transforms. Skipped on the commons-side path: those annotations require a
+ // MarshallingContext to resolve ObjectSwap/StringFormatSwap/Surrogate types and the swap
+ // class meta.
if (nn(marshallingContext))
- MarshalledPropertyPostProcessor.process(marshallingContext, p);
-
- installSwapAwareTransforms(p);
+ MarshalledPropertyPostProcessor.process((MarshallingContext) marshallingContext, p);
if (nn(p.getter))
getterProps.put(p.getter.inner(), p.name);
@@ -727,75 +683,6 @@ public class BeanMeta<T> {
}
}
- /**
- * Installs swap-aware read/write transforms on a {@link BeanPropertyMeta.Builder} after validation.
- *
- * <p>
- * After {@link BeanPropertyMeta.Builder#validate validate()} succeeds, the builder's {@code swap} and
- * {@code rawTypeMeta} fields describe whether the property has a configured {@link ObjectSwap} (via
- * {@link org.apache.juneau.annotation.MarshalledProp @MarshalledProp(format=...)} or
- * {@link org.apache.juneau.annotation.Swap @Swap}) and whether the property's raw type has child swaps registered
- * on it. This method packages those concerns into install-time closures so the marshalling-side swap behavior is
- * established as data on the {@link BeanPropertyMeta} rather than executed by the bean-modeling
- * {@link BeanPropertyMeta#get get}/{@link BeanPropertyMeta#set set} methods themselves.
- *
- * <p>
- * If neither a property-level swap nor a child swap on the raw type meta is present, no transforms are installed
- * and the property's {@code get}/{@code set} fall through to identity (raw access).
- *
- * @param p The builder to attach swap-aware transforms to.
- */
- @SuppressWarnings({
- "rawtypes", // ObjectSwap used raw to mirror BeanPropertyMeta's field declaration.
- "unchecked" // Wildcard ObjectSwap captured by raw alias to allow runtime polymorphic dispatch.
- })
- private static void installSwapAwareTransforms(BeanPropertyMeta.Builder p) {
- ObjectSwap sw = (ObjectSwap) p.swap;
- ClassMeta<?> rtm = (ClassMeta<?>) p.rawTypeMeta;
- if (sw == null && (rtm == null || ! rtm.hasChildSwaps()))
- return;
- if (p.readTransform == null) {
- p.readTransform = (session, o) -> {
- try {
- if (nn(sw))
- return sw.swap(session, o);
- if (o == null)
- return null;
- if (rtm.hasChildSwaps()) {
- ObjectSwap f = rtm.getChildObjectSwapForSwap(o.getClass());
- if (nn(f))
- return f.swap(session, o);
- }
- return o;
- } catch (RuntimeException e) {
- throw e;
- } catch (Exception e) {
- throw new SerializeException(e);
- }
- };
- }
- if (p.writeTransform == null) {
- p.writeTransform = (session, o) -> {
- try {
- if (nn(sw))
- return sw.unswap(session, o, rtm);
- if (o == null)
- return null;
- if (rtm.hasChildSwaps()) {
- ObjectSwap f = rtm.getChildObjectSwapForUnswap(o.getClass());
- if (nn(f))
- return f.unswap(session, o, rtm);
- }
- return o;
- } catch (RuntimeException e) {
- throw e;
- } catch (Exception e) {
- throw new ParseException(e);
- }
- };
- }
- }
-
@Override /* Overridden from Object */
public boolean equals(Object o) {
return (o instanceof BeanMeta<?> o2) && eq(this, o2, (x, y) -> eq(x.classInfo, y.classInfo));
@@ -1042,7 +929,7 @@ public class BeanMeta<T> {
*
* @return The bean context, or <jk>null</jk> for bean-modeling-only construction.
*/
- protected MarshallingContext getMarshallingContext() { return marshallingContext; }
+ protected MarshallingContext getMarshallingContext() { return (MarshallingContext) marshallingContext; }
/**
* Returns the constructor for this bean, if one was found.
@@ -1541,19 +1428,9 @@ public class BeanMeta<T> {
*/
private BeanRegistryLookup findBeanRegistry() {
// BeanRegistry is a marshalling-side concern (polymorphic dispatch via @Marshalled(typeName)/dictionary).
- // On the commons-side construction path there is no MarshallingContext to drive it — return null and let
- // callers route through getPropertyBeanRegistry(...) (which is also empty on this path).
- if (marshallingContext == null)
- return null;
-
- // Bean dictionary on bean filter.
- var beanDictionaryClasses = opt(beanFilter).map(x -> new ArrayList<>(x.getBeanDictionary())).orElse(new ArrayList<>());
-
- // Bean dictionary from @Marshalled(typeName) annotation.
- var ba = config.getAnnotationProvider().find(Marshalled.class, classInfo);
- ba.stream().map(x -> x.inner().typeName()).filter(Utils::ne).findFirst().ifPresent(x -> beanDictionaryClasses.add(classInfo));
-
- return new BeanRegistry(marshallingContext, null, beanDictionaryClasses);
+ // Lifted out to {@link MarshalledBeanMetaInitializer} so this class no longer references
+ // {@link BeanRegistry} directly. Returns null on the commons-side path (no marshallingContext).
+ return MarshalledBeanMetaInitializer.buildBeanRegistry(marshallingContext, beanFilter, classInfo, config);
}
/*
@@ -1664,32 +1541,13 @@ public class BeanMeta<T> {
}
// Parent-class BeanRegistry lookup is a marshalling-side concern: it walks parents and interfaces to
- // see if any of THEIR ClassMeta-backed BeanRegistries declares a typeName for our raw class. On the
- // commons-side path (no marshallingContext) we skip this entirely.
- if (nn(marshallingContext)) {
- var n = classInfo
- .getParentsAndInterfaces()
- .stream()
- .skip(1)
- .map(marshallingContext::getClassMeta)
- .map(ClassMeta::getBeanRegistry)
- .filter(Objects::nonNull)
- .map(x -> x.getTypeName(rawClass))
- .filter(Objects::nonNull)
- .findFirst()
- .orElse(null);
-
- if (n != null)
- return n;
- }
+ // see if any of THEIR ClassMeta-backed BeanRegistries declares a typeName for our raw class. Lifted
+ // out to {@link MarshalledBeanMetaInitializer}. Returns null on the commons-side path.
+ var n = MarshalledBeanMetaInitializer.findTypeNameInParents(marshallingContext, classInfo, rawClass);
+ if (n != null)
+ return n;
- return config.getAnnotationProvider().find(Marshalled.class, classInfo)
- .stream()
- .map(AnnotationInfo::inner)
- .filter(x -> ! x.typeName().isEmpty())
- .map(Marshalled::typeName)
- .findFirst()
- .orElse(null);
+ return MarshalledBeanMetaInitializer.findMarshalledTypeName(config, classInfo);
}
/*
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 fe365128a6..81cda3d4ad 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
@@ -33,7 +33,6 @@ import java.util.function.*;
import java.util.stream.*;
import org.apache.juneau.annotation.*;
-import org.apache.juneau.collections.*;
import org.apache.juneau.commons.bean.*;
import org.apache.juneau.commons.collections.*;
import org.apache.juneau.commons.lang.*;
@@ -1144,7 +1143,7 @@ public class BeanPropertyMeta implements Comparable<BeanPropertyMeta> {
if (! (value1 instanceof Map)) {
if (value1 instanceof CharSequence value21)
- value1 = JsonMap.ofJson(value21).session(session);
+ value1 = session.parseToMap(value21);
else
throw bex(beanMeta.getClassMeta(), "Cannot set property ''{0}'' of type ''{1}'' to object of type ''{2}''", name, propertyClass.getName(), cn(value1));
}
@@ -1202,7 +1201,7 @@ public class BeanPropertyMeta implements Comparable<BeanPropertyMeta> {
if (! (value1 instanceof Collection)) {
if (value1 instanceof CharSequence value2)
- value1 = new JsonList(value2).setBeanSession(session);
+ value1 = session.parseToList(value2);
else
throw bex(beanMeta.getClassMeta(), "Cannot set property ''{0}'' of type ''{1}'' to object of type ''{2}''", name, propertyClass.getName(), cn(value1));
}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshalledBeanMetaInitializer.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshalledBeanMetaInitializer.java
new file mode 100644
index 0000000000..3b0c49c0a1
--- /dev/null
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshalledBeanMetaInitializer.java
@@ -0,0 +1,242 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau;
+
+import static org.apache.juneau.commons.utils.CollectionUtils.*;
+import static org.apache.juneau.commons.utils.StringUtils.*;
+import static org.apache.juneau.commons.utils.Utils.*;
+
+import java.util.*;
+
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.commons.bean.*;
+import org.apache.juneau.commons.reflect.*;
+import org.apache.juneau.commons.utils.*;
+
+/**
+ * Marshalling-side bridge for {@link BeanMeta} construction.
+ *
+ * <p>
+ * Encapsulates marshalling-aware operations that were lifted out of {@link BeanMeta} as part of TODO-5
+ * Step 8b-ii Phase C Task 3, so that {@link BeanMeta} can physically move into the
+ * {@code org.apache.juneau.commons.bean} package without directly referencing
+ * {@link BeanRegistry}, {@link MarshallingContext}, or marshalling-side annotations like
+ * {@link Marshalled @Marshalled} from inside its constructor body.
+ *
+ * <p>
+ * Each helper takes the marshalling-side {@link MarshallingContext} as an {@link Object} so the
+ * call sites inside {@link BeanMeta} do not need to import {@link MarshallingContext}. The helper
+ * casts back internally.
+ */
+final class MarshalledBeanMetaInitializer {
+
+ private MarshalledBeanMetaInitializer() {}
+
+ /**
+ * Builds the bean-level {@link BeanRegistry} for a given bean class.
+ *
+ * <p>
+ * Replaces the body of {@link BeanMeta}'s former private {@code findBeanRegistry()} method. Returns
+ * <jk>null</jk> when {@code marshallingContext} is <jk>null</jk> (commons-side construction path).
+ *
+ * @param marshallingContext The marshalling context. May be <jk>null</jk>.
+ * @param beanFilter The bean filter applied to this bean meta. May be <jk>null</jk>.
+ * @param classInfo The class info of the bean. Must not be <jk>null</jk>.
+ * @param config The bean-modeling configuration. Must not be <jk>null</jk>.
+ * @return The bean-level {@link BeanRegistryLookup}, or <jk>null</jk> on the commons-side path.
+ */
+ static BeanRegistryLookup buildBeanRegistry(Object marshallingContext, BeanFilter beanFilter, ClassInfo classInfo, BeanConfigContext config) {
+ if (marshallingContext == null)
+ return null;
+ MarshallingContext mc = (MarshallingContext) marshallingContext;
+
+ // Bean dictionary on bean filter.
+ var beanDictionaryClasses = opt(beanFilter).map(x -> new ArrayList<>(x.getBeanDictionary())).orElse(new ArrayList<>());
+
+ // Bean dictionary from @Marshalled(typeName) annotation.
+ var ba = config.getAnnotationProvider().find(Marshalled.class, classInfo);
+ ba.stream().map(x -> x.inner().typeName()).filter(Utils::ne).findFirst().ifPresent(x -> beanDictionaryClasses.add(classInfo));
+
+ return new BeanRegistry(mc, null, beanDictionaryClasses);
+ }
+
+ /**
+ * Constructs a per-property {@link BeanRegistry} chained on top of the bean-level registry.
+ *
+ * <p>
+ * Replaces the inline {@code new BeanRegistry(marshallingContext, beanRegistry, dictionaryClasses)} call that
+ * lived inside {@link BeanMeta}'s property-iteration loop.
+ *
+ * @param marshallingContext The marshalling context. Must not be <jk>null</jk>.
+ * @param parent The bean-level registry (chained behind the per-property one). May be <jk>null</jk>.
+ * @param dictionaryClasses The per-property {@link MarshalledProp#dictionary() @MarshalledProp(dictionary)} classes. Must not be <jk>null</jk>.
+ * @return The per-property {@link BeanRegistryLookup}.
+ */
+ static BeanRegistryLookup buildPropertyBeanRegistry(Object marshallingContext, BeanRegistryLookup parent, List<ClassInfo> dictionaryClasses) {
+ return new BeanRegistry((MarshallingContext) marshallingContext, (BeanRegistry) parent, dictionaryClasses);
+ }
+
+ /**
+ * Walks the parent classes/interfaces of {@code classInfo} and returns the first
+ * {@link Marshalled#typeName() @Marshalled(typeName)} mapping for {@code rawClass} found in any parent's
+ * {@link BeanRegistry}.
+ *
+ * <p>
+ * Replaces the parent-walk stream that previously used {@code marshallingContext::getClassMeta} directly
+ * inside {@link BeanMeta#findDictionaryName()}.
+ *
+ * @param marshallingContext The marshalling context. May be <jk>null</jk>.
+ * @param classInfo The bean's class info (used to derive the parents/interfaces walk).
+ * @param rawClass The raw class to look up a type name for.
+ * @return The dictionary name found in a parent's registry, or <jk>null</jk>.
+ */
+ static String findTypeNameInParents(Object marshallingContext, ClassInfo classInfo, Class<?> rawClass) {
+ if (marshallingContext == null)
+ return null;
+ MarshallingContext mc = (MarshallingContext) marshallingContext;
+ return classInfo
+ .getParentsAndInterfaces()
+ .stream()
+ .skip(1)
+ .map(mc::getClassMeta)
+ .map(ClassMeta::getBeanRegistry)
+ .filter(Objects::nonNull)
+ .map(x -> x.getTypeName(rawClass))
+ .filter(Objects::nonNull)
+ .findFirst()
+ .orElse(null);
+ }
+
+ /**
+ * Extracts the {@link BeanConfigContext} embedded in a marshalling-side {@link BeanTypeInfo} (which is always
+ * a {@link ClassMeta}).
+ *
+ * <p>
+ * Used by the protected {@code BeanMeta(BeanTypeInfo, BeanFilter, String[], ClassInfo)} bridging constructor —
+ * lifts the {@code ((ClassMeta<T>) cm).getMarshallingContext().getBeanConfigContext()} cast chain out of the
+ * commons-side {@link BeanMeta} body.
+ *
+ * @param cm The bean type info. Must be a marshalling-side {@link ClassMeta} instance.
+ * @return The bean-modeling configuration of the marshalling context.
+ */
+ static BeanConfigContext configOf(BeanTypeInfo<?> cm) {
+ return ((ClassMeta<?>) cm).getMarshallingContext().getBeanConfigContext();
+ }
+
+ /**
+ * Extracts the {@link MarshallingContext} embedded in a marshalling-side {@link BeanTypeInfo} (which is always
+ * a {@link ClassMeta}) and returns it as an {@link Object} so the caller does not need to import
+ * {@link MarshallingContext}.
+ *
+ * @param cm The bean type info. Must be a marshalling-side {@link ClassMeta} instance.
+ * @return The marshalling context, as an {@link Object}.
+ */
+ static Object contextOf(BeanTypeInfo<?> cm) {
+ return ((ClassMeta<?>) cm).getMarshallingContext();
+ }
+
+ /**
+ * Narrows a marshalling-side {@link BeanTypeInfo} (which is always a {@link ClassMeta}) to {@link ClassInfo}.
+ *
+ * <p>
+ * Since {@link ClassMeta} extends {@link ClassInfo}, the cast is direct. Provided as a helper so the commons-side
+ * caller does not have to import {@link ClassMeta}.
+ *
+ * @param cm The bean type info. Must be a marshalling-side {@link ClassMeta} instance.
+ * @return The class info for the underlying bean class.
+ */
+ static ClassInfo classInfoOf(BeanTypeInfo<?> cm) {
+ return (ClassMeta<?>) cm;
+ }
+
+ /**
+ * Looks up the {@link Marshalled#typePropertyName() @Marshalled(typePropertyName)} value for a bean class.
+ *
+ * <p>
+ * Returns the configured value, or an empty string if no {@link Marshalled @Marshalled} annotation specifies one.
+ * The caller (typically {@link BeanMeta}'s constructor) falls back to a config-supplied default when this method
+ * returns an empty string.
+ *
+ * @param config The bean-modeling configuration (used to access the annotation provider).
+ * @param classInfo The bean's class info.
+ * @return The configured type property name, or empty if not set.
+ */
+ static String findTypePropertyName(BeanConfigContext config, ClassInfo classInfo) {
+ var ba = config.getAnnotationProvider().find(Marshalled.class, classInfo);
+ return ba.stream().map(x -> x.inner().typePropertyName()).filter(Utils::ne).findFirst().orElse("");
+ }
+
+ /**
+ * Looks up the {@link Marshalled#typeName() @Marshalled(typeName)} value for a bean class.
+ *
+ * <p>
+ * Used by {@link BeanMeta#findDictionaryName()} as the last fallback when no other dictionary name is found.
+ *
+ * @param config The bean-modeling configuration (used to access the annotation provider).
+ * @param classInfo The bean's class info.
+ * @return The configured type name, or <jk>null</jk> if not set.
+ */
+ static String findMarshalledTypeName(BeanConfigContext config, ClassInfo classInfo) {
+ return config.getAnnotationProvider().find(Marshalled.class, classInfo)
+ .stream()
+ .map(AnnotationInfo::inner)
+ .filter(x -> ! x.typeName().isEmpty())
+ .map(Marshalled::typeName)
+ .findFirst()
+ .orElse(null);
+ }
+
+ /**
+ * Determines whether the class has any "registered as bean" annotation
+ * ({@link Marshalled @Marshalled} or {@link BeanType @BeanType}).
+ *
+ * <p>
+ * Used by {@link BeanMeta#findBeanConstructor()} to decide whether private constructors are permissible.
+ *
+ * @param config The bean-modeling configuration (used to access the annotation provider).
+ * @param classInfo The bean's class info.
+ * @return <jk>true</jk> if the class has either annotation.
+ */
+ static boolean hasBeanRegistrationAnnotation(BeanConfigContext config, ClassInfo classInfo) {
+ var ap = config.getAnnotationProvider();
+ return ! ap.find(Marshalled.class, classInfo).isEmpty()
+ || ! ap.find(BeanType.class, classInfo).isEmpty();
+ }
+
+ /**
+ * Resolves the bean filter for a marshalling-side {@link BeanMeta}.
+ *
+ * <p>
+ * Lifted out of {@link BeanMeta}'s former private {@code findMarshalledFilter(ClassMeta)} static helper. Returns
+ * a {@link MarshalledFilter} (the only concrete {@link BeanFilter} implementation in-tree) built from the
+ * {@link Marshalled @Marshalled} and {@link BeanType @BeanType} annotations on the class.
+ *
+ * @param cm The class meta for the bean class.
+ * @return The bean filter, or <jk>null</jk> if no relevant annotations are present.
+ */
+ static <T> MarshalledFilter findMarshalledFilter(ClassMeta<T> cm) {
+ var ap = cm.getMarshallingContext().getAnnotationProvider();
+ var l = ap.find(Marshalled.class, cm);
+ var bt = ap.find(BeanType.class, cm);
+ if (l.isEmpty() && bt.isEmpty())
+ return null;
+ return MarshalledFilter.create(cm)
+ .applyAnnotations(reverse(l.stream().map(AnnotationInfo::inner).toList()))
+ .applyBeanTypeAnnotations(reverse(bt.stream().map(AnnotationInfo::inner).toList()))
+ .build();
+ }
+}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshalledPropertyPostProcessor.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshalledPropertyPostProcessor.java
index c0f1332bc0..7f6defe51b 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshalledPropertyPostProcessor.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshalledPropertyPostProcessor.java
@@ -28,6 +28,8 @@ import java.util.*;
import org.apache.juneau.annotation.*;
import org.apache.juneau.commons.inject.*;
import org.apache.juneau.commons.reflect.*;
+import org.apache.juneau.parser.*;
+import org.apache.juneau.serializer.*;
import org.apache.juneau.swap.*;
import org.apache.juneau.swaps.*;
@@ -121,6 +123,80 @@ final class MarshalledPropertyPostProcessor {
// layer and cannot reference ObjectSwap directly).
if (nn(b.swap) && nn(b.rawTypeMeta))
b.typeMeta = bc.getClassMeta(((ObjectSwap) b.swap).getSwapClass());
+
+ // Install swap-aware read/write transforms on the builder.
+ // Previously lived as a private static helper on {@link BeanMeta}; moved here as part of TODO-5 Step 8b-ii
+ // Phase C Task 3 so {@link BeanMeta} no longer references {@link ObjectSwap}/{@link ParseException}/{@link SerializeException}.
+ installSwapAwareTransforms(b);
+ }
+
+ /**
+ * Installs swap-aware read/write transforms on a {@link BeanPropertyMeta.Builder} after validation.
+ *
+ * <p>
+ * After {@link BeanPropertyMeta.Builder#validate validate()} succeeds, the builder's {@code swap} and
+ * {@code rawTypeMeta} fields describe whether the property has a configured {@link ObjectSwap} (via
+ * {@link org.apache.juneau.annotation.MarshalledProp @MarshalledProp(format=...)} or
+ * {@link org.apache.juneau.annotation.Swap @Swap}) and whether the property's raw type has child swaps registered
+ * on it. This method packages those concerns into install-time closures so the marshalling-side swap behavior is
+ * established as data on the {@link BeanPropertyMeta} rather than executed by the bean-modeling
+ * {@link BeanPropertyMeta#get get}/{@link BeanPropertyMeta#set set} methods themselves.
+ *
+ * <p>
+ * If neither a property-level swap nor a child swap on the raw type meta is present, no transforms are installed
+ * and the property's {@code get}/{@code set} fall through to identity (raw access).
+ *
+ * @param p The builder to attach swap-aware transforms to.
+ */
+ @SuppressWarnings({
+ "rawtypes", // ObjectSwap used raw to mirror BeanPropertyMeta's field declaration.
+ "unchecked" // Wildcard ObjectSwap captured by raw alias to allow runtime polymorphic dispatch.
+ })
+ static void installSwapAwareTransforms(BeanPropertyMeta.Builder p) {
+ ObjectSwap sw = (ObjectSwap) p.swap;
+ ClassMeta<?> rtm = (ClassMeta<?>) p.rawTypeMeta;
+ if (sw == null && (rtm == null || ! rtm.hasChildSwaps()))
+ return;
+ if (p.readTransform == null) {
+ p.readTransform = (session, o) -> {
+ try {
+ if (nn(sw))
+ return sw.swap(session, o);
+ if (o == null)
+ return null;
+ if (rtm.hasChildSwaps()) {
+ ObjectSwap f = rtm.getChildObjectSwapForSwap(o.getClass());
+ if (nn(f))
+ return f.swap(session, o);
+ }
+ return o;
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new SerializeException(e);
+ }
+ };
+ }
+ if (p.writeTransform == null) {
+ p.writeTransform = (session, o) -> {
+ try {
+ if (nn(sw))
+ return sw.unswap(session, o, rtm);
+ if (o == null)
+ return null;
+ if (rtm.hasChildSwaps()) {
+ ObjectSwap f = rtm.getChildObjectSwapForUnswap(o.getClass());
+ if (nn(f))
+ return f.unswap(session, o, rtm);
+ }
+ return o;
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ParseException(e);
+ }
+ };
+ }
}
private static ObjectSwap marshalledPropSwap(AnnotationInfo<MarshalledProp> ai) {
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshallingSession.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshallingSession.java
index 66e50ee20d..f4f84bb73c 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshallingSession.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshallingSession.java
@@ -1321,4 +1321,38 @@ public class MarshallingSession extends ContextSession implements ConverterSessi
throw illegalArg("Unsupported targetType for convertToMemberType: {0}", targetType.getClass().getName());
}
+ /**
+ * Bridge implementation of {@link BeanSession#parseToMap(CharSequence)} that delegates to
+ * {@link org.apache.juneau.collections.JsonMap#ofJson(CharSequence)} paired with this session.
+ *
+ * <p>
+ * Used by {@link BeanPropertyMeta#setPropertyValue} to parse a {@link CharSequence} into a {@link Map} when
+ * the property is map-typed. Lifted out of {@link BeanPropertyMeta} so the bean-modeling layer no longer
+ * references the marshalling-side JSON parser.
+ *
+ * @param value The JSON-formatted character sequence to parse. Must not be <jk>null</jk>.
+ * @return The parsed {@link org.apache.juneau.collections.JsonMap}.
+ */
+ @Override /* BeanSession */
+ public final java.util.Map<?,?> parseToMap(CharSequence value) {
+ return org.apache.juneau.collections.JsonMap.ofJson(value).session(this);
+ }
+
+ /**
+ * Bridge implementation of {@link BeanSession#parseToList(CharSequence)} that delegates to
+ * {@link org.apache.juneau.collections.JsonList#JsonList(CharSequence)} paired with this session.
+ *
+ * <p>
+ * Used by {@link BeanPropertyMeta#setPropertyValue} to parse a {@link CharSequence} into a {@link java.util.Collection}
+ * when the property is collection-typed. Lifted out of {@link BeanPropertyMeta} so the bean-modeling layer
+ * no longer references the marshalling-side JSON parser.
+ *
+ * @param value The JSON-formatted character sequence to parse. Must not be <jk>null</jk>.
+ * @return The parsed {@link org.apache.juneau.collections.JsonList}.
+ */
+ @Override /* BeanSession */
+ public final java.util.Collection<?> parseToList(CharSequence value) {
+ return new org.apache.juneau.collections.JsonList(value).setBeanSession(this);
+ }
+
}
\ No newline at end of file
diff --git a/todo/TODO-5-bean-runtime-types-to-commons.md b/todo/TODO-5-bean-runtime-types-to-commons.md
index 1be86c3f7c..b7c19d95ed 100644
--- a/todo/TODO-5-bean-runtime-types-to-commons.md
+++ b/todo/TODO-5-bean-runtime-types-to-commons.md
@@ -4,9 +4,16 @@ This is the remaining work from **Phase 5 of the bean-layer split**. Phase 5a (t
---
-## Status (as of Phase 5h checkpoint + Step 8b-i)
+## Status (as of Phase C Tasks 1-2-3-4-4-deferred checkpoint, uncommitted)
-**Step 8b-i complete (additional SPI-seam extraction, uncommitted).** Three new SPIs landed in the working tree (no commit yet — left for user review). Build and full test suite green. See "Step 8b-i" entry in the step list below for full detail. Summary: `BeanTypeInfo<T>` (abstract class — ClassMeta extends), `BeanFilter` interface (MarshalledFilter implements), `BeanRegistryLookup` interface (BeanRegistry implements), and `Delegate<T>` physically moved to `commons.bean`. The remaining [...]
+**Phase C Tasks 1, 2, 3, 4, 4-deferred LANDED (working tree, uncommitted).** Build + full test green. See "Phase C status" block under Step 8b-ii for full detail. Summary:
+- **Task 1** — Public getter widening (`getClassMeta()` → `BeanTypeInfo<?>`, `getBeanRegistry()` → `BeanRegistryLookup`) committed locally at `48621576d7`.
+- **Task 2** — `validate(...)` body lift-out via new `BeanTypeResolver` SPI committed locally.
+- **Task 3** — `BeanMeta` constructor body lift-out via new `MarshalledBeanMetaInitializer` (this checkpoint). Includes relocation of `installSwapAwareTransforms` from `BeanMeta` into `MarshalledPropertyPostProcessor`, retype of `BeanMeta.marshallingContext` field to `Object`, and removal of `parser.*`/`serializer.*`/`swap.*` imports from `BeanMeta`.
+- **Task 4** — `setPropertyValue` Collection-branch JsonList wrap → ArrayList wrap (committed earlier).
+- **Task 4-deferred** — CharSequence-parsing sites now route through new `BeanSession.parseToMap(CharSequence)` / `BeanSession.parseToList(CharSequence)` SPI methods (this checkpoint). `BeanPropertyMeta` dropped its `import org.apache.juneau.collections.*`.
+
+**Phase C Task 5 (physical `git mv` + reference sweep) — NOT STARTED.** Survey revealed the remaining marshalling-side coupling on the 8 files is deeper than the original prep scope estimated. The bulk of remaining cleanup is lifting ~20 marshalling-side annotation reads (`@Marshalled`, `@BeanProp`, etc.) from `BeanMeta`/`BeanPropertyMeta` to `MarshalledBeanMetaInitializer`, plus retyping `BiFunction<MarshallingSession,...>` field types to use the `BeanSession` SPI seam, plus relocating [...]
**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:
@@ -110,17 +117,54 @@ Known limitations of the commons-side path (acceptable for Step 6, scoped for la
- **Sub-item 10 (`JsonList` / `JsonMap` → JDK collections)** — partial. The `propertyCache` allocations inside `BeanPropertyMeta.add(BeanMap,String,Object)` and `add(BeanMap,String,String,Object)` now use `new ArrayList<>()` / `new LinkedHashMap<>()` (test-invisible — they're internal caches for read-only beans). The `c = new JsonList(session)` / `map = new JsonMap(session)` fallback constructions in `add(...)` likewise flipped to `new ArrayList<>()` / `new LinkedHashMap<>()`. **Deferr [...]
- **Sub-item 8 (`BeanMap.load(...)` → `BeanMapLoader`)** — created `juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMapLoader.java` with `static <T> BeanMap<T> load(BeanMap<T> m, String input) throws ParseException` and `static <T> BeanMap<T> load(BeanMap<T> m, Reader r, ReaderParser p) throws ParseException, IOException`. Removed `BeanMap.load(Reader, ReaderParser)` and `BeanMap.load(String)` from `BeanMap.java`. Updated 2 call sites in `MarshallingContext.java` (`bs.n [...]
- **Phase C status (uncommitted, working tree) — IN PROGRESS. Tasks 1, 2, 4 LANDED, build + full test green. Tasks 3, 5 NOT STARTED.**
+ **Phase C status (uncommitted, working tree) — Tasks 1, 2, 3, 4, 4-deferred LANDED, build + full test green. Task 5 NOT STARTED (deeper coupling discovered).**
- Phase C-tasks-1-2-and-4 landed:
+ Phase C-tasks-1-2-3-4-and-4-deferred landed:
- **Task 1 (public getter widening) — COMPLETE.** `BeanPropertyMeta.getClassMeta()` now returns `BeanTypeInfo<?>` (was `ClassMeta<?>`). `BeanMeta.getClassMeta()` now returns `BeanTypeInfo<T>` (was `ClassMeta<T>`). `BeanMeta.getBeanRegistry()` and `BeanMeta.getPropertyBeanRegistry(BeanPropertyMeta)` now return `BeanRegistryLookup` (was `BeanRegistry`). `BeanMap.getClassMeta()` now returns `BeanTypeInfo<T>` (was `ClassMeta<T>`). The cascade across ~30 marshalling-side files needed explic [...]
- - **Task 2 (`validate(...)` body lift-out) — COMPLETE (signature seam).** Introduced new SPI `BeanTypeResolver` in `org.apache.juneau.commons.bean` (3-method interface: `resolveType(AnnotationInfo<BeanProp>, ClassInfo, TypeVariables)`, `objectType()`, `getAnnotationProvider()`). `MarshallingContext` now `implements BeanTypeResolver` and exposes two new public bridge methods (`resolveType` delegating to the existing protected `resolveClassMeta`, and `objectType` returning `cmObject`). ` [...]
- - **Task 4 (`new JsonList(valueList)` → `new ArrayList<>(valueList)`) — COMPLETE.** `BeanPropertyMeta.setPropertyValue`'s abstract-Collection branch (the path that builds a typed collection by copying then converting elements) now constructs `new ArrayList<>(valueList)` instead of `new JsonList(valueList)`. Three `BeanMap_Test` assertions (`a05_arrayProperties`, `a06_arrayProperties_usingConfig`, `a09_beanPropertyAnnotation`) updated to expect `ArrayList` for the corresponding `lb1`/`l [...]
-
- Phase C-tasks-3-and-5 NOT started:
- - **Task 3 — `BeanMeta` constructor body lift-out** — the `BeanMeta(BeanTypeInfo<T>, BeanFilter, String[], ClassInfo)` constructor still contains marshalling-layer-aware logic (cast back to `ClassMeta<T>` to read `getMarshallingContext()` / `getBeanConfigContext()`, builds swap-aware transforms, registers per-property `BeanRegistry`). Moving that body to a marshalling-side helper (`MarshalledBeanMetaInitializer`) so the commons-side constructor body is commons-clean is still pending.
- - **Task 4 (deferred CharSequence-parsing sites)** — two `CharSequence`-handling sites inside `setPropertyValue` are still in place: `value1 = JsonMap.ofJson(value21).session(session)` (line 1147, Map branch) and `value1 = new JsonList(value2).setBeanSession(session)` (line 1205, Collection branch). These invoke the marshalling-side JSON parser and should move to a marshalling-side conversion helper before `BeanPropertyMeta` physically moves (or be lifted into a pre-`set(...)` conversi [...]
- - **Task 5 — Physical `git mv` + reference sweep** — the 8 target files (`BeanMap`, `BeanMapEntry`, `BeanMeta`, `BeanMetaFiltered`, `BeanPropertyMeta`, `BeanPropertyValue`, `BeanPropertyConsumer`, `BeanProxyInvocationHandler`) are still in `juneau-core/juneau-marshall/src/main/java/org/apache/juneau/`. Before the move can succeed cleanly, the marshalling-side imports in the 8 files still need cleanup: `BeanMap` imports `annotation.*`, `internal.*`, `swap.*`; `BeanMapEntry` imports `ann [...]
+ - **Task 2 (`validate(...)` body lift-out) — COMPLETE (signature seam).** Introduced new SPI `BeanTypeResolver` in `org.apache.juneau.commons.bean` (3-method interface: `resolveType(AnnotationInfo<BeanProp>, ClassInfo, TypeVariables)`, `objectType()`, `getAnnotationProvider()`). `MarshallingContext` now `implements BeanTypeResolver` and exposes two new public bridge methods (`resolveType` delegating to the existing protected `resolveClassMeta`, and `objectType` returning `cmObject`). ` [...]
+ - **Task 3 (`BeanMeta` constructor body lift-out) — COMPLETE.** New marshalling-side helper `MarshalledBeanMetaInitializer` in `juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshalledBeanMetaInitializer.java`. It owns the marshalling-aware helpers that used to live as private code paths inside `BeanMeta`'s constructor and helpers: `buildBeanRegistry(Object marshallingContext, BeanFilter, ClassInfo, BeanConfigContext)` (replaces `findBeanRegistry()` body), `buildPropertyB [...]
+ - **Task 4 (`new JsonList(valueList)` → `new ArrayList<>(valueList)`) — COMPLETE.** `BeanPropertyMeta.setPropertyValue`'s abstract-Collection branch (the path that builds a typed collection by copying then converting elements) now constructs `new ArrayList<>(valueList)` instead of `new JsonList(valueList)`. Three `BeanMap_Test` assertions (`a05_arrayProperties`, `a06_arrayProperties_usingConfig`, `a09_beanPropertyAnnotation`) updated to expect `ArrayList` for the corresponding `lb1`/`l [...]
+ - **Task 4-deferred (CharSequence-parsing sites in `setPropertyValue`) — COMPLETE.** Added two new methods to the `BeanSession` SPI in `commons.bean`: `Map<?,?> parseToMap(CharSequence)` and `Collection<?> parseToList(CharSequence)`. `MarshallingSession` implements both as bridge methods that delegate to `JsonMap.ofJson(value).session(this)` / `new JsonList(value).setBeanSession(this)` respectively (the original inline calls). `BeanPropertyMeta.setPropertyValue`'s two `CharSequence` br [...]
+
+ **Phase C Task 5 status — NOT STARTED.** Surveying the 8 target files in light of the lifts from Tasks 1-4-deferred revealed that the remaining marshalling-side coupling is deeper than the original Task 5 prep scope. To be done in a follow-up pass; this checkpoint stops at "all SPI extractions + helper lifts done; physical move blocked on deeper coupling cleanup".
+
+ Inventory of what's still blocking the physical `git mv` (remaining marshalling-side surface area on the 8 target files):
+
+ - **`BeanPropertyMeta`** — still imports `annotation.*`, `internal.*`, `parser.*`, `serializer.*`, `swap.*`.
+ - `org.apache.juneau.annotation.*` — Javadoc references to `@MarshalledProp`, `@Marshalled`, plus `AnnotationInfo`/`AnnotationProvider` (those live in `commons.reflect` though — confirm).
+ - `org.apache.juneau.internal.*` — uses `TypeVariables`/`ClassInfo` helpers (`TypeVariables` is in `internal`, check it's not commons-reachable).
+ - `org.apache.juneau.parser.ParseException` (catch on line 1120 of `set(...)`).
+ - `org.apache.juneau.swap.ObjectSwap` (cast on line 1259 of `setPropertyValue`'s defensive double-unswap check).
+ - `MarshallingSession`-typed parameter on `setPropertyValue`, `applyChildPropertiesFilter(MarshallingSession, ClassMeta, Object)`, `swapAndFilterProperty(MarshallingSession, Object)`, plus `BiFunction<MarshallingSession,Object,Object>` field types on `readTransform`/`writeTransform` and `Builder.readTransform`/`Builder.writeTransform` setter parameters.
+ - Three remaining `((MarshallingContext) bc).X()` casts: `Builder.rawMetaType(Class<?>)` (`getClassMeta(value)`), the constructor's `ap` initialization (`getAnnotationProvider()`), and `applyChildPropertiesFilter` (`getBeanMeta(o.getClass())`).
+ - `applyChildPropertiesFilter` signature: `(MarshallingSession session, ClassMeta cm, Object o)` — both `MarshallingSession` and `ClassMeta` are marshalling-side; lifting this method to a marshalling-side post-processor (or accepting an `Object`-typed signature with marshalling-side narrowing) is the cleanest move.
+ - `newBeanMap(MarshallingSession session, Object o, BeanMetaFiltered meta)` private static helper — `BeanMetaFiltered` is one of the 8 (moves with cluster); `MarshallingSession` is the blocker.
+ - `BeanMeta#installSwapAwareTransforms` — already moved to `MarshalledPropertyPostProcessor`, but `BeanPropertyMeta.Builder` still has package-private `swap` / `rawTypeMeta` fields read from `MarshalledPropertyPostProcessor` (cross-package access becomes a problem after the move).
+ - **`BeanMeta`** — still imports `annotation.*`.
+ - `@Marshalled`, `@BeanType`, `@BeanProp`, `@BeanCtor`, `@BeanIgnore`, `@Transient`, `@Name`, `@Uri` annotation reads scattered across `create(...)`, the constructor body (line ~453: `ap.find(Marshalled.class, classInfo)`), `findBeanConstructor` (`ap.has(BeanType.class, cm)` / `ap.has(Marshalled.class, cm)`), `findFluentSetters` (uses `@Marshalled`), `findClassFieldMeta` (uses `@BeanProp`, `@Name`, `@BeanIgnore`, `@Transient`), `findGetters`/`findSetters` (uses `@BeanProp`, `@Name`, [...]
+ - `MarshalledFilter` (`beanFilter` is typed `BeanFilter` per Step 8b-i but `MarshalledFilter` cast surfaces in `getMarshalledFilter()`).
+ - `create(ClassMeta<T>, ClassInfo)` static factory — uses `ClassMeta`/`MarshallingContext.getAnnotationProvider()`; could relocate to `MarshalledBeanMetaInitializer` as `MarshalledBeanMetaInitializer.create(...)` returning `BeanMetaValue<T>`.
+ - **`BeanMap`** — still imports `annotation.*`, `internal.*`, `swap.*`.
+ - `@Marshalled` Javadoc reference and the `BeanMap.of(T)` factory which routes through `MarshallingContext.DEFAULT_SESSION.toBeanMap(bean)` — easiest cleanup is to delete `BeanMap.of(T)` and update callers to use `MarshallingContext.DEFAULT_SESSION.toBeanMap(bean)` directly (callers in `juneau-assertions` need updating; `juneau-assertions` already depends on `juneau-marshall`).
+ - `ObjectSwap` cast inside `BeanMap.getBean()` Optional handling.
+ - `org.apache.juneau.internal.*` — likely `ClassInfo` and `TypeVariables`-like helpers; check if commons-reachable.
+ - **`BeanMapEntry`** — imports `annotation.*` (Javadoc references to `@Marshalled` / `@MarshalledProp`) + `swap.*` (Javadoc `ObjectSwap`). Body references: none that survive Phase B/C. Should be a trivial cleanup: replace Javadoc fully-qualified names with `{@link org.apache.juneau.swap.ObjectSwap}` form so the imports can be dropped.
+ - **`BeanMetaFiltered`** — imports `annotation.*` (just `MarshalledFilter` and `@Marshalled` Javadoc). Single line `super(innerMeta.getClassMeta(), (MarshalledFilter) innerMeta.getMarshalledFilter(), pNames, null);` — body is marshalling-aware. After the cluster moves, this becomes a marshalling-side type that lives in `commons.bean` purely because `BeanMeta` does; the constructor stays unchanged because it threads marshalling-side data through `BeanMeta`'s protected constructor.
+ - **`BeanPropertyValue`** — clean as of Phase C Task 1 (no marshalling-side imports survive). Ready to move.
+ - **`BeanPropertyConsumer`** — clean (functional interface, no marshalling-side references). Ready to move.
+ - **`BeanProxyInvocationHandler`** — still uses `meta.getMarshallingContext().toBeanMap(arg)` inside `equals(Object)`. Route this through `BeanSession.toBeanMap(arg)` using the SPI seam; the field is already typed against an SPI that `MarshallingContext` exposes.
+
+ **Phase C Task 5 prerequisites (estimated effort):**
+ - Lift the ~20 `@Marshalled`/`@BeanProp`/`@BeanCtor`/`@BeanIgnore`/`@Transient`/`@Name`/`@Uri` annotation reads from `BeanMeta` / `BeanPropertyMeta` to `MarshalledBeanMetaInitializer` (or move `@BeanType`/`@Name`/`@Uri` to `commons.bean.annotation` since they're bean-modeling concerns). Estimated: ~150-200 lines of helper code, ~30 call-site changes in `BeanMeta`/`BeanPropertyMeta`.
+ - Replace the three `((MarshallingContext) bc).X()` casts in `BeanPropertyMeta` with SPI calls (`Builder.rawMetaType(Class<?>)`, constructor `ap`, `applyChildPropertiesFilter`). Either extend `BeanTypeResolver` with `getClassMeta(Class<?>)` and `getBeanMeta(Class<?>)` methods, or move the call sites to a marshalling-side helper.
+ - Move `applyChildPropertiesFilter(MarshallingSession, ClassMeta, Object)` to a marshalling-side helper (it's not callable from the commons-side path anyway — it throws `UnsupportedOperationException` when `bc == null`).
+ - Retype `BiFunction<MarshallingSession,Object,Object>` fields on `BeanPropertyMeta`/`BeanPropertyMeta.Builder` to `BiFunction<BeanSession,Object,Object>` (or `BiFunction<Object,Object,Object>`), updating the marshalling-side `MarshalledPropertyPostProcessor.installSwapAwareTransforms` to cast back.
+ - Route `BeanProxyInvocationHandler`'s `meta.getMarshallingContext().toBeanMap(arg)` through `BeanSession.toBeanMap(arg)` (the SPI already supports this — just need to use a `BeanSession`-typed handle).
+ - Delete `BeanMap.of(T)` (and update `juneau-assertions` callers to use `MarshallingContext.DEFAULT_SESSION.toBeanMap` directly) to remove the `MarshallingContext.DEFAULT_SESSION` reference from `BeanMap`.
+ - Clean up Javadoc references in `BeanMapEntry`, `BeanMap`, `BeanMeta`, `BeanPropertyMeta` to fully-qualify marshalling-side links so the wildcard imports can be dropped.
+ - **Then** the physical `git mv` + reference sweep across ~80-120 files + verify `juneau-commons` standalone compile + full test suite.
+
+ Total estimated effort for Task 5 completion: ~600-1000 lines of helper code lifts + careful import surgery on the 8 files + the actual physical move + reference sweep. Recommend a fresh checkpoint pass focused exclusively on Task 5 prep + Task 5.
**Phase B status (uncommitted, working tree) — COMPLETE, build + full test green (49,912 tests pass).**
@@ -138,9 +182,9 @@ Known limitations of the commons-side path (acceptable for Step 6, scoped for la
- **Phase C Task 1 — Public API widen.** — DONE. See Phase C status block above. `BeanPropertyMeta.getClassMeta()` → `BeanTypeInfo<?>`, `BeanMeta.getClassMeta()` → `BeanTypeInfo<T>`, `BeanMeta.getBeanRegistry()` / `getPropertyBeanRegistry(...)` → `BeanRegistryLookup`, `BeanMap.getClassMeta()` → `BeanTypeInfo<T>`. ~26 marshalling-side callers updated with explicit `(ClassMeta<?>)` / `(BeanRegistry)` casts at the call sites that invoke `ClassMeta`-only / `BeanRegistry`-only methods. Buil [...]
- **Phase C Task 2 — `validate(...)` body lift-out.** — DONE (signature seam). See Phase C status block above. New `BeanTypeResolver` SPI in `commons.bean`. `MarshallingContext` implements it. `Builder.validate(BeanTypeResolver bc, ...)` parameter retyped. `bc.resolveClassMeta(...)` → `bc.resolveType(...)`, `bc.object()` → `bc.objectType()`. Build + full test green. Note: three other marshalling-only seams on `BeanPropertyMeta` remain (Builder `rawMetaType(Class<?>)`, constructor `ap` [...]
- - **Phase C Task 3 — Marshalling-side `BeanMeta` factory helper.** — NOT STARTED. The `BeanMeta(BeanTypeInfo<T>, BeanFilter, String[], ClassInfo)` protected constructor still contains marshalling-layer-aware logic (cast back to `ClassMeta<T>` to read `getMarshallingContext()` / `getBeanConfigContext()`, builds swap-aware transforms, registers per-property `BeanRegistry`). Move the body of that constructor out to a marshalling-side helper that sets `classMeta` and `marshallingContext` v [...]
- - **Phase C Task 4 — `JsonList`/`JsonMap` final cleanup.** — PARTIAL. Done: `setPropertyValue`'s `new JsonList(valueList)` flipped to `new ArrayList<>(valueList)` (typed-element abstract-Collection branch). Three test assertions updated. Pass-through behavior preserved for raw-`Object`-element collections (a03/a04 tests still green). Deferred (test-invisible at the moment but still marshalling-layer leaks): two `CharSequence`-handling sites inside `setPropertyValue` (`value1 = JsonMap. [...]
- - **Phase C Task 5 — Physical `git mv` + reference sweep.** — NOT STARTED. Prerequisites: Phase C Tasks 2, 3, and the deferred Task 4 CharSequence-parsing sites. Before the move, the 8 files still need to drop their marshalling-side imports (`annotation.*`, `internal.*`, `collections.*`, `parser.*`, `serializer.*`, `swap.*`). After those are cleaned up, `git mv` the 8 files into `juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/`, update `package` declarations, r [...]
+ - **Phase C Task 3 — Marshalling-side `BeanMeta` factory helper.** — DONE. See Phase C status block above. New `MarshalledBeanMetaInitializer` in `juneau-marshall` owns the marshalling-aware helpers (`buildBeanRegistry`, `buildPropertyBeanRegistry`, `findTypeNameInParents`, `findMarshalledTypeName`, plus extractors `configOf`/`contextOf`/`classInfoOf` and the relocated `findMarshalledFilter`). `installSwapAwareTransforms` relocated from `BeanMeta` to `MarshalledPropertyPostProcessor` ( [...]
+ - **Phase C Task 4 — `JsonList`/`JsonMap` final cleanup.** — DONE. The remaining CharSequence-handling sites (`JsonMap.ofJson(value21).session(session)` and `new JsonList(value2).setBeanSession(session)`) now route through new `BeanSession` SPI methods `parseToMap(CharSequence)` / `parseToList(CharSequence)`. `MarshallingSession` implements both as bridge methods delegating to the original `JsonMap.ofJson` / `new JsonList` calls. `BeanPropertyMeta` dropped its `import org.apache.juneau [...]
+ - **Phase C Task 5 — Physical `git mv` + reference sweep.** — NOT STARTED. Prerequisites partially met (Tasks 1-4-deferred done). The remaining marshalling-side coupling in the 8 files is deeper than the original Task 5 prep scope (see "Phase C Task 5 status" block above for full inventory). Recommend a fresh checkpoint pass focused exclusively on: (a) lifting ~20 `@Marshalled`/`@BeanProp`/`@BeanCtor`/`@BeanIgnore`/`@Transient`/`@Name`/`@Uri` annotation reads from `BeanMeta`/`BeanPrope [...]
- [ ] **Step 8c** — (optional) Cleanup pass for anything that comes up during 8b-ii: 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.