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 <[email protected]> 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 <[email protected]> --- .../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.
