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 0ae599d2b2caff74ac7c5eee1e2429c8b7122e74
Author: James Bognar <[email protected]>
AuthorDate: Tue May 12 11:20:37 2026 -0400

    refactor: introduce BeanConfigContext foundation for bean-runtime move 
(Phase 5b of bean-layer split)
    
    Adds BeanConfigContext, the runtime sibling of @BeanConfig, in
    org.apache.juneau.commons.bean. Carries the bean-modeling subset of
    runtime configuration (visibility thresholds, beans*Require* toggles,
    fluent-setter detection, property naming, bean-type property name,
    not-a-bean exclusion sets, BeanStore, AnnotationProvider, optional
    isNotABean predicate override) so the bean-modeling runtime can be
    consumed without referencing any marshalling-aware types.
    
    MarshallingContext now exposes a memoized getBeanConfigContext()
    returning a snapshot view, so future steps can migrate BeanMeta /
    BeanPropertyMeta off direct MarshallingContext access without the
    disruption of moving the eight runtime types in a single commit.
    
    This is Step 1 of TODO-5; the eight runtime types (BeanMap, BeanMeta,
    BeanPropertyMeta, BeanMapEntry, BeanMetaFiltered, BeanPropertyValue,
    BeanPropertyConsumer, BeanProxyInvocationHandler) still live in
    juneau-marshall and still couple to ClassMeta / ObjectSwap /
    BeanRegistry. TODO-5-bean-runtime-types-to-commons.md is updated to
    mark Step 1 done and describe the remaining steps.
    
    Tests: 100% instruction / 97% branch coverage on BeanConfigContext via
    
juneau-utest/src/test/java/org/apache/juneau/commons/bean/BeanConfigContext_Test.java.
    Full juneau test suite passes; juneau-commons compiles standalone.
    
    Co-authored-by: Cursor <[email protected]>
---
 .../juneau/commons/bean/BeanConfigContext.java     | 646 +++++++++++++++++++++
 .../java/org/apache/juneau/MarshallingContext.java |  51 ++
 .../commons/bean/BeanConfigContext_Test.java       | 422 ++++++++++++++
 todo/TODO-5-bean-runtime-types-to-commons.md       |  19 +
 4 files changed, 1138 insertions(+)

diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanConfigContext.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanConfigContext.java
new file mode 100644
index 0000000000..e9309fa9ee
--- /dev/null
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanConfigContext.java
@@ -0,0 +1,646 @@
+/*
+ * 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.commons.bean;
+
+import static org.apache.juneau.commons.utils.AssertionUtils.*;
+import static org.apache.juneau.commons.utils.CollectionUtils.*;
+import static org.apache.juneau.commons.utils.Utils.*;
+
+import java.util.*;
+import java.util.function.*;
+
+import org.apache.juneau.commons.inject.*;
+import org.apache.juneau.commons.reflect.*;
+
+/**
+ * Immutable runtime configuration for the bean-modeling layer.
+ *
+ * <p>
+ * {@code BeanConfigContext} is the runtime sibling of the {@link BeanConfig 
@BeanConfig} annotation:
+ * it carries the resolved values that drive how Java types are introspected 
as beans, independent
+ * of any marshalling concern.  Settings include visibility thresholds, 
required-property toggles,
+ * fluent-setter detection, property naming, not-bean exclusions, the active 
{@link BeanStore} and
+ * {@link AnnotationProvider}, and a few related hooks.
+ *
+ * <p>
+ * This type is the bean-modeling counterpart of the marshalling-layer context 
that lives in
+ * {@code juneau-marshall}.  It exists so the bean-modeling runtime (in {@code 
commons.bean}) can
+ * be used independently of the marshalling stack, and so the marshalling 
layer can compose a
+ * {@code BeanConfigContext} snapshot to feed bean-modeling code without 
exposing marshalling-only
+ * APIs.
+ *
+ * <h5 class='section'>Construction:</h5>
+ * <p class='bjava'>
+ *     <jc>// All defaults.</jc>
+ *     BeanConfigContext <jv>defaults</jv> = 
BeanConfigContext.<jsf>DEFAULT</jsf>;
+ *
+ *     <jc>// Customized.</jc>
+ *     BeanConfigContext <jv>ctx</jv> = BeanConfigContext.<jsm>create</jsm>()
+ *             .beanClassVisibility(Visibility.<jsf>PROTECTED</jsf>)
+ *             .findFluentSetters(<jk>true</jk>)
+ *             .propertyNamer(<jk>new</jk> PropertyNamerDLC())
+ *             .build();
+ *
+ *     <jc>// Copy and tweak.</jc>
+ *     BeanConfigContext <jv>ctx2</jv> = <jv>ctx</jv>.copy()
+ *             .unsortedProperties(<jk>true</jk>)
+ *             .build();
+ * </p>
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ *     <li class='ja'>{@link BeanConfig @BeanConfig} — the annotation form.
+ * </ul>
+ */
+@SuppressWarnings({
+       "java:S107" // Builder.build() invokes a multi-arg constructor; high 
cardinality is inherent to a configuration POJO.
+})
+public final class BeanConfigContext {
+
+       /**
+        * Default {@link BeanConfigContext} instance with all settings at 
their defaults.
+        *
+        * <p>
+        * Equivalent to {@code BeanConfigContext.create().build()}.
+        */
+       public static final BeanConfigContext DEFAULT = create().build();
+
+       /**
+        * Creates a new builder for {@link BeanConfigContext}.
+        *
+        * @return A new builder.
+        */
+       public static Builder create() {
+               return new Builder();
+       }
+
+       private final Visibility beanClassVisibility;
+       private final Visibility beanConstructorVisibility;
+       private final Visibility beanFieldVisibility;
+       private final Visibility beanMethodVisibility;
+
+       private final boolean beansRequireDefaultConstructor;
+       private final boolean beansRequireSerializable;
+       private final boolean beansRequireSettersForGetters;
+       private final boolean beansRequireSomeProperties;
+       private final boolean findFluentSetters;
+       private final boolean ignoreMissingSetters;
+       private final boolean ignoreTransientFields;
+       private final boolean ignoreUnknownBeanProperties;
+       private final boolean unsortedProperties;
+       private final boolean useInterfaceProxies;
+       private final boolean useJavaBeanIntrospector;
+
+       private final PropertyNamer propertyNamer;
+       private final String beanTypePropertyName;
+
+       private final Set<String> notBeanPackageNames;
+       private final Set<String> notBeanPackagePrefixes;
+       private final Set<Class<?>> notBeanClasses;
+
+       private final BeanStore beanStore;
+       private final AnnotationProvider annotationProvider;
+       private final Predicate<ClassInfo> notABeanPredicate;
+
+       private BeanConfigContext(Builder b) {
+               beanClassVisibility = b.beanClassVisibility;
+               beanConstructorVisibility = b.beanConstructorVisibility;
+               beanFieldVisibility = b.beanFieldVisibility;
+               beanMethodVisibility = b.beanMethodVisibility;
+               beansRequireDefaultConstructor = 
b.beansRequireDefaultConstructor;
+               beansRequireSerializable = b.beansRequireSerializable;
+               beansRequireSettersForGetters = b.beansRequireSettersForGetters;
+               beansRequireSomeProperties = b.beansRequireSomeProperties;
+               findFluentSetters = b.findFluentSetters;
+               ignoreMissingSetters = b.ignoreMissingSetters;
+               ignoreTransientFields = b.ignoreTransientFields;
+               ignoreUnknownBeanProperties = b.ignoreUnknownBeanProperties;
+               unsortedProperties = b.unsortedProperties;
+               useInterfaceProxies = b.useInterfaceProxies;
+               useJavaBeanIntrospector = b.useJavaBeanIntrospector;
+               propertyNamer = b.propertyNamer;
+               beanTypePropertyName = b.beanTypePropertyName;
+               notBeanPackageNames = u(b.notBeanPackageNames);
+               notBeanPackagePrefixes = u(b.notBeanPackagePrefixes);
+               notBeanClasses = u(b.notBeanClasses);
+               beanStore = b.beanStore;
+               annotationProvider = b.annotationProvider;
+               notABeanPredicate = b.notABeanPredicate;
+       }
+
+       /**
+        * Returns a builder pre-populated with the values from this context.
+        *
+        * @return A new builder.
+        */
+       public Builder copy() {
+               return new Builder(this);
+       }
+
+       /**
+        * Minimum bean class visibility.
+        *
+        * <p>
+        * Classes are not considered beans unless they meet this minimum 
visibility requirement.
+        *
+        * @return The minimum bean class visibility.  Never <jk>null</jk>.
+        */
+       public Visibility getBeanClassVisibility() { return 
beanClassVisibility; }
+
+       /**
+        * Minimum bean constructor visibility.
+        *
+        * @return The minimum bean constructor visibility.  Never 
<jk>null</jk>.
+        */
+       public Visibility getBeanConstructorVisibility() { return 
beanConstructorVisibility; }
+
+       /**
+        * Minimum bean field visibility.
+        *
+        * @return The minimum bean field visibility.  Never <jk>null</jk>.
+        */
+       public Visibility getBeanFieldVisibility() { return 
beanFieldVisibility; }
+
+       /**
+        * Minimum bean method visibility.
+        *
+        * @return The minimum bean method visibility.  Never <jk>null</jk>.
+        */
+       public Visibility getBeanMethodVisibility() { return 
beanMethodVisibility; }
+
+       /**
+        * Returns whether classes must have a no-arg constructor to be 
considered beans.
+        *
+        * @return <jk>true</jk> if a no-arg constructor is required.
+        */
+       public boolean isBeansRequireDefaultConstructor() { return 
beansRequireDefaultConstructor; }
+
+       /**
+        * Returns whether classes must implement {@link java.io.Serializable} 
to be considered beans.
+        *
+        * @return <jk>true</jk> if {@code Serializable} is required.
+        */
+       public boolean isBeansRequireSerializable() { return 
beansRequireSerializable; }
+
+       /**
+        * Returns whether bean properties must have a setter to be considered 
writable from the getter.
+        *
+        * @return <jk>true</jk> if setters are required for getters.
+        */
+       public boolean isBeansRequireSettersForGetters() { return 
beansRequireSettersForGetters; }
+
+       /**
+        * Returns whether classes must have at least one property to be 
considered beans.
+        *
+        * @return <jk>true</jk> if at least one property is required.
+        */
+       public boolean isBeansRequireSomeProperties() { return 
beansRequireSomeProperties; }
+
+       /**
+        * Returns whether fluent-style setters (returning <c>this</c>) should 
be detected.
+        *
+        * @return <jk>true</jk> if fluent setters are detected.
+        */
+       public boolean isFindFluentSetters() { return findFluentSetters; }
+
+       /**
+        * Returns whether bean properties without setters should be silently 
ignored during deserialization.
+        *
+        * @return <jk>true</jk> if missing setters are ignored.
+        */
+       public boolean isIgnoreMissingSetters() { return ignoreMissingSetters; }
+
+       /**
+        * Returns whether {@code transient} fields should be excluded from 
bean property detection.
+        *
+        * @return <jk>true</jk> if transient fields are ignored.
+        */
+       public boolean isIgnoreTransientFields() { return 
ignoreTransientFields; }
+
+       /**
+        * Returns whether unknown properties on incoming bean payloads should 
be ignored.
+        *
+        * @return <jk>true</jk> if unknown properties are ignored.
+        */
+       public boolean isIgnoreUnknownBeanProperties() { return 
ignoreUnknownBeanProperties; }
+
+       /**
+        * Returns whether properties should preserve their JVM-discovered 
(non-alphabetical) order.
+        *
+        * @return <jk>true</jk> if properties remain unsorted.
+        */
+       public boolean isUnsortedProperties() { return unsortedProperties; }
+
+       /**
+        * Returns whether interface proxies should be created for bean 
interfaces.
+        *
+        * @return <jk>true</jk> if interface proxies are enabled.
+        */
+       public boolean isUseInterfaceProxies() { return useInterfaceProxies; }
+
+       /**
+        * Returns whether {@link java.beans.Introspector} should be used to 
discover bean properties.
+        *
+        * @return <jk>true</jk> if the JavaBeans introspector is used.
+        */
+       public boolean isUseJavaBeanIntrospector() { return 
useJavaBeanIntrospector; }
+
+       /**
+        * Returns the {@link PropertyNamer} used to derive property names from 
getter/setter/field names.
+        *
+        * @return The active property namer.  Never <jk>null</jk>.
+        */
+       public PropertyNamer getPropertyNamer() { return propertyNamer; }
+
+       /**
+        * Returns the property name used to embed the bean dictionary type 
name (default: <js>"_type"</js>).
+        *
+        * @return The bean type property name.  Never <jk>null</jk>.
+        */
+       public String getBeanTypePropertyName() { return beanTypePropertyName; }
+
+       /**
+        * Returns the set of fully qualified package names whose classes are 
excluded from bean detection.
+        *
+        * @return The not-a-bean package name set.  Never <jk>null</jk>.  
Unmodifiable.
+        */
+       public Set<String> getNotBeanPackageNames() { return 
notBeanPackageNames; }
+
+       /**
+        * Returns the set of fully qualified package prefixes whose classes 
are excluded from bean detection.
+        *
+        * @return The not-a-bean package prefix set.  Never <jk>null</jk>.  
Unmodifiable.
+        */
+       public Set<String> getNotBeanPackagePrefixes() { return 
notBeanPackagePrefixes; }
+
+       /**
+        * Returns the set of classes (and supertypes) that are explicitly 
excluded from bean detection.
+        *
+        * @return The not-a-bean class set.  Never <jk>null</jk>.  
Unmodifiable.
+        */
+       public Set<Class<?>> getNotBeanClasses() { return notBeanClasses; }
+
+       /**
+        * Returns the active {@link BeanStore} used for factory-based bean 
instantiation, or <jk>null</jk> if none configured.
+        *
+        * @return The bean store, or <jk>null</jk>.
+        */
+       public BeanStore getBeanStore() { return beanStore; }
+
+       /**
+        * Returns the {@link AnnotationProvider} used to discover annotations 
on classes/methods/fields/parameters.
+        *
+        * @return The annotation provider.  Never <jk>null</jk>.
+        */
+       public AnnotationProvider getAnnotationProvider() { return 
annotationProvider; }
+
+       /**
+        * Returns <jk>true</jk> if the specified class is excluded from bean 
detection.
+        *
+        * <p>
+        * The default implementation checks {@link #getNotBeanClasses()}, 
{@link #getNotBeanPackageNames()}, and
+        * {@link #getNotBeanPackagePrefixes()}, and rejects 
arrays/primitives/enums/annotations.  A custom
+        * predicate (set via {@link Builder#notABeanPredicate(Predicate)}) 
overrides this behavior entirely.
+        *
+        * @param ci The class info being tested.  Must not be <jk>null</jk>.
+        * @return <jk>true</jk> if the class is excluded from bean detection.
+        */
+       public boolean isNotABean(ClassInfo ci) {
+               assertArgNotNull("ci", ci);
+               if (notABeanPredicate != null)
+                       return notABeanPredicate.test(ci);
+               if (ci.isArray() || ci.isPrimitive() || ci.isEnum() || 
ci.isAnnotation())
+                       return true;
+               var p = ci.getPackage();
+               if (nn(p)) {
+                       var pn = p.getName();
+                       for (var p2 : notBeanPackageNames)
+                               if (pn.equals(p2))
+                                       return true;
+                       for (var p2 : notBeanPackagePrefixes)
+                               if (pn.startsWith(p2))
+                                       return true;
+               }
+               for (var exclude : notBeanClasses)
+                       if (ci.isAssignableTo(exclude))
+                               return true;
+               return false;
+       }
+
+       /**
+        * Builder for {@link BeanConfigContext}.
+        */
+       public static final class Builder {
+
+               private Visibility beanClassVisibility = Visibility.PUBLIC;
+               private Visibility beanConstructorVisibility = 
Visibility.PUBLIC;
+               private Visibility beanFieldVisibility = Visibility.PUBLIC;
+               private Visibility beanMethodVisibility = Visibility.PUBLIC;
+
+               private boolean beansRequireDefaultConstructor;
+               private boolean beansRequireSerializable;
+               private boolean beansRequireSettersForGetters;
+               private boolean beansRequireSomeProperties = true;
+               private boolean findFluentSetters;
+               private boolean ignoreMissingSetters = true;
+               private boolean ignoreTransientFields = true;
+               private boolean ignoreUnknownBeanProperties;
+               private boolean unsortedProperties;
+               private boolean useInterfaceProxies = true;
+               private boolean useJavaBeanIntrospector;
+
+               private PropertyNamer propertyNamer = new BasicPropertyNamer();
+               private String beanTypePropertyName = "_type";
+
+               private Set<String> notBeanPackageNames = new LinkedHashSet<>();
+               private Set<String> notBeanPackagePrefixes = new 
LinkedHashSet<>();
+               private Set<Class<?>> notBeanClasses = new LinkedHashSet<>();
+
+               private BeanStore beanStore;
+               private AnnotationProvider annotationProvider = 
AnnotationProvider.INSTANCE;
+               private Predicate<ClassInfo> notABeanPredicate;
+
+               private Builder() {}
+
+               private Builder(BeanConfigContext src) {
+                       beanClassVisibility = src.beanClassVisibility;
+                       beanConstructorVisibility = 
src.beanConstructorVisibility;
+                       beanFieldVisibility = src.beanFieldVisibility;
+                       beanMethodVisibility = src.beanMethodVisibility;
+                       beansRequireDefaultConstructor = 
src.beansRequireDefaultConstructor;
+                       beansRequireSerializable = src.beansRequireSerializable;
+                       beansRequireSettersForGetters = 
src.beansRequireSettersForGetters;
+                       beansRequireSomeProperties = 
src.beansRequireSomeProperties;
+                       findFluentSetters = src.findFluentSetters;
+                       ignoreMissingSetters = src.ignoreMissingSetters;
+                       ignoreTransientFields = src.ignoreTransientFields;
+                       ignoreUnknownBeanProperties = 
src.ignoreUnknownBeanProperties;
+                       unsortedProperties = src.unsortedProperties;
+                       useInterfaceProxies = src.useInterfaceProxies;
+                       useJavaBeanIntrospector = src.useJavaBeanIntrospector;
+                       propertyNamer = src.propertyNamer;
+                       beanTypePropertyName = src.beanTypePropertyName;
+                       notBeanPackageNames = new 
LinkedHashSet<>(src.notBeanPackageNames);
+                       notBeanPackagePrefixes = new 
LinkedHashSet<>(src.notBeanPackagePrefixes);
+                       notBeanClasses = new 
LinkedHashSet<>(src.notBeanClasses);
+                       beanStore = src.beanStore;
+                       annotationProvider = src.annotationProvider;
+                       notABeanPredicate = src.notABeanPredicate;
+               }
+
+               /**
+                * Builds a new {@link BeanConfigContext} from this builder's 
state.
+                *
+                * @return A new immutable {@link BeanConfigContext}.
+                */
+               public BeanConfigContext build() {
+                       return new BeanConfigContext(this);
+               }
+
+               /**
+                * Sets the minimum bean class visibility.
+                *
+                * @param value The visibility threshold.  Must not be 
<jk>null</jk>.
+                * @return This object.
+                */
+               public Builder beanClassVisibility(Visibility value) { 
beanClassVisibility = assertArgNotNull("value", value); return this; }
+
+               /**
+                * Sets the minimum bean constructor visibility.
+                *
+                * @param value The visibility threshold.  Must not be 
<jk>null</jk>.
+                * @return This object.
+                */
+               public Builder beanConstructorVisibility(Visibility value) { 
beanConstructorVisibility = assertArgNotNull("value", value); return this; }
+
+               /**
+                * Sets the minimum bean field visibility.
+                *
+                * @param value The visibility threshold.  Must not be 
<jk>null</jk>.
+                * @return This object.
+                */
+               public Builder beanFieldVisibility(Visibility value) { 
beanFieldVisibility = assertArgNotNull("value", value); return this; }
+
+               /**
+                * Sets the minimum bean method visibility.
+                *
+                * @param value The visibility threshold.  Must not be 
<jk>null</jk>.
+                * @return This object.
+                */
+               public Builder beanMethodVisibility(Visibility value) { 
beanMethodVisibility = assertArgNotNull("value", value); return this; }
+
+               /**
+                * Toggles the requirement that beans have a no-arg default 
constructor.
+                *
+                * @param value The new value.
+                * @return This object.
+                */
+               public Builder beansRequireDefaultConstructor(boolean value) { 
beansRequireDefaultConstructor = value; return this; }
+
+               /**
+                * Toggles the requirement that beans implement {@link 
java.io.Serializable}.
+                *
+                * @param value The new value.
+                * @return This object.
+                */
+               public Builder beansRequireSerializable(boolean value) { 
beansRequireSerializable = value; return this; }
+
+               /**
+                * Toggles the requirement that bean getters have matching 
setters.
+                *
+                * @param value The new value.
+                * @return This object.
+                */
+               public Builder beansRequireSettersForGetters(boolean value) { 
beansRequireSettersForGetters = value; return this; }
+
+               /**
+                * Toggles the requirement that beans expose at least one 
property.
+                *
+                * @param value The new value.
+                * @return This object.
+                */
+               public Builder beansRequireSomeProperties(boolean value) { 
beansRequireSomeProperties = value; return this; }
+
+               /**
+                * Toggles fluent-setter detection (setters that return 
<c>this</c>).
+                *
+                * @param value The new value.
+                * @return This object.
+                */
+               public Builder findFluentSetters(boolean value) { 
findFluentSetters = value; return this; }
+
+               /**
+                * Toggles silent ignoring of properties without setters during 
deserialization.
+                *
+                * @param value The new value.
+                * @return This object.
+                */
+               public Builder ignoreMissingSetters(boolean value) { 
ignoreMissingSetters = value; return this; }
+
+               /**
+                * Toggles exclusion of {@code transient} fields from bean 
property detection.
+                *
+                * @param value The new value.
+                * @return This object.
+                */
+               public Builder ignoreTransientFields(boolean value) { 
ignoreTransientFields = value; return this; }
+
+               /**
+                * Toggles silent ignoring of unknown properties on incoming 
bean payloads.
+                *
+                * @param value The new value.
+                * @return This object.
+                */
+               public Builder ignoreUnknownBeanProperties(boolean value) { 
ignoreUnknownBeanProperties = value; return this; }
+
+               /**
+                * Toggles whether properties remain in JVM-discovered 
(non-alphabetical) order.
+                *
+                * @param value The new value.
+                * @return This object.
+                */
+               public Builder unsortedProperties(boolean value) { 
unsortedProperties = value; return this; }
+
+               /**
+                * Toggles automatic creation of interface proxies for bean 
interfaces.
+                *
+                * @param value The new value.
+                * @return This object.
+                */
+               public Builder useInterfaceProxies(boolean value) { 
useInterfaceProxies = value; return this; }
+
+               /**
+                * Toggles use of {@link java.beans.Introspector} for property 
discovery.
+                *
+                * @param value The new value.
+                * @return This object.
+                */
+               public Builder useJavaBeanIntrospector(boolean value) { 
useJavaBeanIntrospector = value; return this; }
+
+               /**
+                * Sets the {@link PropertyNamer} used to derive property names.
+                *
+                * @param value The property namer.  Must not be <jk>null</jk>.
+                * @return This object.
+                */
+               public Builder propertyNamer(PropertyNamer value) { 
propertyNamer = assertArgNotNull("value", value); return this; }
+
+               /**
+                * Sets the property name used to embed the bean dictionary 
type (default: <js>"_type"</js>).
+                *
+                * @param value The property name.  Must not be <jk>null</jk>.
+                * @return This object.
+                */
+               public Builder beanTypePropertyName(String value) { 
beanTypePropertyName = assertArgNotNull("value", value); return this; }
+
+               /**
+                * Adds package names whose classes should be excluded from 
bean detection.
+                *
+                * @param values The package names.  Must not be <jk>null</jk>.
+                * @return This object.
+                */
+               public Builder notBeanPackageNames(String...values) {
+                       assertArgNotNull("values", values);
+                       Collections.addAll(notBeanPackageNames, values);
+                       return this;
+               }
+
+               /**
+                * Adds package prefixes whose classes should be excluded from 
bean detection.
+                *
+                * @param values The package prefixes.  Must not be 
<jk>null</jk>.
+                * @return This object.
+                */
+               public Builder notBeanPackagePrefixes(String...values) {
+                       assertArgNotNull("values", values);
+                       Collections.addAll(notBeanPackagePrefixes, values);
+                       return this;
+               }
+
+               /**
+                * Adds classes (and supertypes) that should be excluded from 
bean detection.
+                *
+                * @param values The classes.  Must not be <jk>null</jk>.
+                * @return This object.
+                */
+               public Builder notBeanClasses(Class<?>...values) {
+                       assertArgNotNull("values", values);
+                       Collections.addAll(notBeanClasses, values);
+                       return this;
+               }
+
+               /**
+                * Replaces the not-a-bean package name set.
+                *
+                * @param values The package names.  May be <jk>null</jk> for 
an empty set.
+                * @return This object.
+                */
+               public Builder notBeanPackageNames(Collection<String> values) {
+                       notBeanPackageNames = new LinkedHashSet<>(values == 
null ? sete() : values);
+                       return this;
+               }
+
+               /**
+                * Replaces the not-a-bean package prefix set.
+                *
+                * @param values The package prefixes.  May be <jk>null</jk> 
for an empty set.
+                * @return This object.
+                */
+               public Builder notBeanPackagePrefixes(Collection<String> 
values) {
+                       notBeanPackagePrefixes = new LinkedHashSet<>(values == 
null ? sete() : values);
+                       return this;
+               }
+
+               /**
+                * Replaces the not-a-bean class set.
+                *
+                * @param values The classes.  May be <jk>null</jk> for an 
empty set.
+                * @return This object.
+                */
+               public Builder notBeanClasses(Collection<? extends Class<?>> 
values) {
+                       notBeanClasses = new LinkedHashSet<>(values == null ? 
sete() : values);
+                       return this;
+               }
+
+               /**
+                * Sets the active {@link BeanStore} used for factory-based 
bean instantiation.
+                *
+                * @param value The bean store.  May be <jk>null</jk>.
+                * @return This object.
+                */
+               public Builder beanStore(BeanStore value) { beanStore = value; 
return this; }
+
+               /**
+                * Sets the active {@link AnnotationProvider}.
+                *
+                * @param value The annotation provider.  Must not be 
<jk>null</jk>.
+                * @return This object.
+                */
+               public Builder annotationProvider(AnnotationProvider value) { 
annotationProvider = assertArgNotNull("value", value); return this; }
+
+               /**
+                * Installs a custom predicate that fully overrides {@link 
BeanConfigContext#isNotABean(ClassInfo)}.
+                *
+                * <p>
+                * When set, the default not-bean computation (package/class 
exclusions, array/primitive/enum/annotation
+                * checks) is bypassed entirely.  Pass <jk>null</jk> to revert 
to the built-in behavior.
+                *
+                * @param value The predicate.  May be <jk>null</jk>.
+                * @return This object.
+                */
+               public Builder notABeanPredicate(Predicate<ClassInfo> value) { 
notABeanPredicate = value; return this; }
+       }
+}
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshallingContext.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshallingContext.java
index bfac213a84..cbf369aae2 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshallingContext.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshallingContext.java
@@ -3640,6 +3640,7 @@ public class MarshallingContext extends Context 
implements ConversionFinder {
        }
 
        private final NullableSupplier<WriterSerializer> beanToStringSerializer;
+       private final NullableSupplier<BeanConfigContext> beanConfigContext;
        private final BeanRegistry beanRegistry;
        private final MarshallingSession defaultSession;
        private final ConfigurableConverter converter;
@@ -3757,6 +3758,40 @@ public class MarshallingContext extends Context 
implements ConversionFinder {
                beanRegistry = new BeanRegistry(this, null, list());
                defaultSession = createSession().unmodifiable().build();
                beanToStringSerializer = memoize(() -> 
Json5Serializer.create().marshallingContext(this).build());
+               beanConfigContext = memoize(this::buildBeanConfigContext);
+       }
+
+       /*
+        * Builds the {@link BeanConfigContext} snapshot exposed by {@link 
#getBeanConfigContext()}.
+        *
+        * Lifts the bean-modeling subset of this context's settings into a 
portable POJO that the bean-modeling
+        * runtime in {@code commons.bean} can consume without referencing any 
marshalling-aware types.
+        */
+       private BeanConfigContext buildBeanConfigContext() {
+               return BeanConfigContext.create()
+                       .beanClassVisibility(beanClassVisibility)
+                       .beanConstructorVisibility(beanConstructorVisibility)
+                       .beanFieldVisibility(beanFieldVisibility)
+                       .beanMethodVisibility(beanMethodVisibility)
+                       
.beansRequireDefaultConstructor(beansRequireDefaultConstructor)
+                       .beansRequireSerializable(beansRequireSerializable)
+                       
.beansRequireSettersForGetters(beansRequireSettersForGetters)
+                       .beansRequireSomeProperties(beansRequireSomeProperties)
+                       .findFluentSetters(findFluentSetters)
+                       .ignoreMissingSetters(ignoreMissingSetters)
+                       .ignoreTransientFields(ignoreTransientFields)
+                       
.ignoreUnknownBeanProperties(ignoreUnknownBeanProperties)
+                       .unsortedProperties(unsortedProperties)
+                       .useInterfaceProxies(useInterfaceProxies)
+                       .useJavaBeanIntrospector(useJavaBeanIntrospector)
+                       .propertyNamer(propertyNamerBean)
+                       .beanTypePropertyName(typePropertyName)
+                       .notBeanPackageNames(notBeanPackageNames)
+                       .notBeanPackagePrefixes(notBeanPackagePrefixes)
+                       
.notBeanClasses(notBeanClasses.stream().map(ClassInfo::inner).toList())
+                       .beanStore(beanStore)
+                       .annotationProvider(getAnnotationProvider())
+                       .build();
        }
 
        /**
@@ -3823,6 +3858,22 @@ public class MarshallingContext extends Context 
implements ConversionFinder {
         */
        public final List<ClassInfo> getBeanDictionary() { return 
beanDictionary; }
 
+       /**
+        * Returns the {@link BeanConfigContext} snapshot for this marshalling 
context.
+        *
+        * <p>
+        * The snapshot lifts the bean-modeling subset of this context's 
settings into a portable POJO that the
+        * bean-modeling runtime in {@code commons.bean} can consume without 
referencing any marshalling-aware types.
+        * It is computed lazily on first access and cached for the lifetime of 
this context.
+        *
+        * <p>
+        * Mutating the returned snapshot's settings (via {@link 
BeanConfigContext#copy()} and a fresh build) does not
+        * affect this marshalling context.
+        *
+        * @return The bean-modeling configuration snapshot.  Never 
<jk>null</jk>.
+        */
+       public final BeanConfigContext getBeanConfigContext() { return 
beanConfigContext.get(); }
+
        /**
         * Minimum bean field visibility.
         *
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/commons/bean/BeanConfigContext_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/commons/bean/BeanConfigContext_Test.java
new file mode 100644
index 0000000000..77e1e3a8bb
--- /dev/null
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/commons/bean/BeanConfigContext_Test.java
@@ -0,0 +1,422 @@
+/*
+ * 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.commons.bean;
+
+import static org.apache.juneau.commons.reflect.ReflectionUtils.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.commons.inject.*;
+import org.apache.juneau.commons.reflect.*;
+import org.junit.jupiter.api.*;
+
+class BeanConfigContext_Test extends TestBase {
+
+       
//====================================================================================================
+       // Defaults / DEFAULT singleton
+       
//====================================================================================================
+
+       @Test
+       void a01_default_visibilityIsPublic() {
+               var ctx = BeanConfigContext.DEFAULT;
+               assertEquals(Visibility.PUBLIC, ctx.getBeanClassVisibility());
+               assertEquals(Visibility.PUBLIC, 
ctx.getBeanConstructorVisibility());
+               assertEquals(Visibility.PUBLIC, ctx.getBeanFieldVisibility());
+               assertEquals(Visibility.PUBLIC, ctx.getBeanMethodVisibility());
+       }
+
+       @Test
+       void a02_default_booleanToggles() {
+               var ctx = BeanConfigContext.DEFAULT;
+               assertFalse(ctx.isBeansRequireDefaultConstructor());
+               assertFalse(ctx.isBeansRequireSerializable());
+               assertFalse(ctx.isBeansRequireSettersForGetters());
+               assertTrue(ctx.isBeansRequireSomeProperties());
+               assertFalse(ctx.isFindFluentSetters());
+               assertTrue(ctx.isIgnoreMissingSetters());
+               assertTrue(ctx.isIgnoreTransientFields());
+               assertFalse(ctx.isIgnoreUnknownBeanProperties());
+               assertFalse(ctx.isUnsortedProperties());
+               assertTrue(ctx.isUseInterfaceProxies());
+               assertFalse(ctx.isUseJavaBeanIntrospector());
+       }
+
+       @Test
+       void a03_default_namingAndCollections() {
+               var ctx = BeanConfigContext.DEFAULT;
+               assertNotNull(ctx.getPropertyNamer());
+               assertInstanceOf(BasicPropertyNamer.class, 
ctx.getPropertyNamer());
+               assertEquals("_type", ctx.getBeanTypePropertyName());
+               assertTrue(ctx.getNotBeanPackageNames().isEmpty());
+               assertTrue(ctx.getNotBeanPackagePrefixes().isEmpty());
+               assertTrue(ctx.getNotBeanClasses().isEmpty());
+       }
+
+       @Test
+       void a04_default_storeAndAnnotationProvider() {
+               var ctx = BeanConfigContext.DEFAULT;
+               assertNull(ctx.getBeanStore());
+               assertNotNull(ctx.getAnnotationProvider());
+               assertSame(AnnotationProvider.INSTANCE, 
ctx.getAnnotationProvider());
+       }
+
+       
//====================================================================================================
+       // Builder setters
+       
//====================================================================================================
+
+       @Test
+       void b01_builder_visibilitySetters() {
+               var ctx = BeanConfigContext.create()
+                       .beanClassVisibility(Visibility.PROTECTED)
+                       .beanConstructorVisibility(Visibility.PRIVATE)
+                       .beanFieldVisibility(Visibility.DEFAULT)
+                       .beanMethodVisibility(Visibility.PROTECTED)
+                       .build();
+               assertEquals(Visibility.PROTECTED, 
ctx.getBeanClassVisibility());
+               assertEquals(Visibility.PRIVATE, 
ctx.getBeanConstructorVisibility());
+               assertEquals(Visibility.DEFAULT, ctx.getBeanFieldVisibility());
+               assertEquals(Visibility.PROTECTED, 
ctx.getBeanMethodVisibility());
+       }
+
+       @Test
+       void b02_builder_booleanToggles() {
+               var ctx = BeanConfigContext.create()
+                       .beansRequireDefaultConstructor(true)
+                       .beansRequireSerializable(true)
+                       .beansRequireSettersForGetters(true)
+                       .beansRequireSomeProperties(false)
+                       .findFluentSetters(true)
+                       .ignoreMissingSetters(false)
+                       .ignoreTransientFields(false)
+                       .ignoreUnknownBeanProperties(true)
+                       .unsortedProperties(true)
+                       .useInterfaceProxies(false)
+                       .useJavaBeanIntrospector(true)
+                       .build();
+               assertTrue(ctx.isBeansRequireDefaultConstructor());
+               assertTrue(ctx.isBeansRequireSerializable());
+               assertTrue(ctx.isBeansRequireSettersForGetters());
+               assertFalse(ctx.isBeansRequireSomeProperties());
+               assertTrue(ctx.isFindFluentSetters());
+               assertFalse(ctx.isIgnoreMissingSetters());
+               assertFalse(ctx.isIgnoreTransientFields());
+               assertTrue(ctx.isIgnoreUnknownBeanProperties());
+               assertTrue(ctx.isUnsortedProperties());
+               assertFalse(ctx.isUseInterfaceProxies());
+               assertTrue(ctx.isUseJavaBeanIntrospector());
+       }
+
+       @Test
+       void b03_builder_propertyNamerAndTypeName() {
+               var dlc = new PropertyNamerDLC();
+               var ctx = BeanConfigContext.create()
+                       .propertyNamer(dlc)
+                       .beanTypePropertyName("kind")
+                       .build();
+               assertSame(dlc, ctx.getPropertyNamer());
+               assertEquals("kind", ctx.getBeanTypePropertyName());
+       }
+
+       @Test
+       void b04_builder_notBeanVarargs() {
+               var ctx = BeanConfigContext.create()
+                       .notBeanPackageNames("pkg.a", "pkg.b")
+                       .notBeanPackagePrefixes("pkg.c.", "pkg.d.")
+                       .notBeanClasses(String.class, Integer.class)
+                       .build();
+               assertEquals(Set.of("pkg.a", "pkg.b"), 
ctx.getNotBeanPackageNames());
+               assertEquals(Set.of("pkg.c.", "pkg.d."), 
ctx.getNotBeanPackagePrefixes());
+               assertEquals(Set.of(String.class, Integer.class), 
ctx.getNotBeanClasses());
+       }
+
+       @Test
+       void b05_builder_notBeanCollectionReplacers() {
+               var ctx = BeanConfigContext.create()
+                       .notBeanPackageNames("seed.a")
+                       .notBeanPackageNames(List.of("only.a", "only.b"))
+                       .notBeanPackagePrefixes(List.of("pre."))
+                       .notBeanClasses(List.of(String.class, Long.class))
+                       .build();
+               assertEquals(Set.of("only.a", "only.b"), 
ctx.getNotBeanPackageNames());
+               assertEquals(Set.of("pre."), ctx.getNotBeanPackagePrefixes());
+               assertEquals(Set.of(String.class, Long.class), 
ctx.getNotBeanClasses());
+       }
+
+       @Test
+       void b06_builder_notBeanCollectionReplacers_nullClearsAll() {
+               var ctx = BeanConfigContext.create()
+                       .notBeanPackageNames("seed.a")
+                       .notBeanPackagePrefixes("seed.b.")
+                       .notBeanClasses(String.class)
+                       .notBeanPackageNames((Collection<String>)null)
+                       .notBeanPackagePrefixes((Collection<String>)null)
+                       .notBeanClasses((Collection<? extends Class<?>>)null)
+                       .build();
+               assertTrue(ctx.getNotBeanPackageNames().isEmpty());
+               assertTrue(ctx.getNotBeanPackagePrefixes().isEmpty());
+               assertTrue(ctx.getNotBeanClasses().isEmpty());
+       }
+
+       @Test
+       void b07_builder_storeAndAnnotationProvider() {
+               var store = new BasicBeanStore(null);
+               var ap = AnnotationProvider.create().build();
+               var ctx = BeanConfigContext.create()
+                       .beanStore(store)
+                       .annotationProvider(ap)
+                       .build();
+               assertSame(store, ctx.getBeanStore());
+               assertSame(ap, ctx.getAnnotationProvider());
+       }
+
+       @Test
+       void b08_builder_beanStore_acceptsNull() {
+               var ctx = BeanConfigContext.create().beanStore(null).build();
+               assertNull(ctx.getBeanStore());
+       }
+
+       
//====================================================================================================
+       // Setter null-arg validation
+       
//====================================================================================================
+
+       @Test
+       void c01_setters_rejectNullVisibility() {
+               var b = BeanConfigContext.create();
+               assertThrows(IllegalArgumentException.class, () -> 
b.beanClassVisibility(null));
+               assertThrows(IllegalArgumentException.class, () -> 
b.beanConstructorVisibility(null));
+               assertThrows(IllegalArgumentException.class, () -> 
b.beanFieldVisibility(null));
+               assertThrows(IllegalArgumentException.class, () -> 
b.beanMethodVisibility(null));
+       }
+
+       @Test
+       void c02_setters_rejectNullPropertyNamer() {
+               var b = BeanConfigContext.create();
+               assertThrows(IllegalArgumentException.class, () -> 
b.propertyNamer(null));
+       }
+
+       @Test
+       void c03_setters_rejectNullTypeName() {
+               var b = BeanConfigContext.create();
+               assertThrows(IllegalArgumentException.class, () -> 
b.beanTypePropertyName(null));
+       }
+
+       @Test
+       void c04_setters_rejectNullVarargArrays() {
+               var b = BeanConfigContext.create();
+               assertThrows(IllegalArgumentException.class, () -> 
b.notBeanPackageNames((String[])null));
+               assertThrows(IllegalArgumentException.class, () -> 
b.notBeanPackagePrefixes((String[])null));
+               assertThrows(IllegalArgumentException.class, () -> 
b.notBeanClasses((Class<?>[])null));
+       }
+
+       @Test
+       void c05_setters_rejectNullAnnotationProvider() {
+               var b = BeanConfigContext.create();
+               assertThrows(IllegalArgumentException.class, () -> 
b.annotationProvider(null));
+       }
+
+       
//====================================================================================================
+       // copy()
+       
//====================================================================================================
+
+       @Test
+       void d01_copy_preservesAllValues() {
+               var store = new BasicBeanStore(null);
+               var ap = AnnotationProvider.create().build();
+               var dlc = new PropertyNamerDLC();
+               var src = BeanConfigContext.create()
+                       .beanClassVisibility(Visibility.PROTECTED)
+                       .beanConstructorVisibility(Visibility.PRIVATE)
+                       .beanFieldVisibility(Visibility.DEFAULT)
+                       .beanMethodVisibility(Visibility.PROTECTED)
+                       .beansRequireDefaultConstructor(true)
+                       .beansRequireSerializable(true)
+                       .beansRequireSettersForGetters(true)
+                       .beansRequireSomeProperties(false)
+                       .findFluentSetters(true)
+                       .ignoreMissingSetters(false)
+                       .ignoreTransientFields(false)
+                       .ignoreUnknownBeanProperties(true)
+                       .unsortedProperties(true)
+                       .useInterfaceProxies(false)
+                       .useJavaBeanIntrospector(true)
+                       .propertyNamer(dlc)
+                       .beanTypePropertyName("kind")
+                       .notBeanPackageNames("pkg.a")
+                       .notBeanPackagePrefixes("pkg.b.")
+                       .notBeanClasses(String.class)
+                       .beanStore(store)
+                       .annotationProvider(ap)
+                       .notABeanPredicate(ci -> false)
+                       .build();
+
+               var copy = src.copy().build();
+
+               assertEquals(src.getBeanClassVisibility(), 
copy.getBeanClassVisibility());
+               assertEquals(src.getBeanConstructorVisibility(), 
copy.getBeanConstructorVisibility());
+               assertEquals(src.getBeanFieldVisibility(), 
copy.getBeanFieldVisibility());
+               assertEquals(src.getBeanMethodVisibility(), 
copy.getBeanMethodVisibility());
+               assertEquals(src.isBeansRequireDefaultConstructor(), 
copy.isBeansRequireDefaultConstructor());
+               assertEquals(src.isBeansRequireSerializable(), 
copy.isBeansRequireSerializable());
+               assertEquals(src.isBeansRequireSettersForGetters(), 
copy.isBeansRequireSettersForGetters());
+               assertEquals(src.isBeansRequireSomeProperties(), 
copy.isBeansRequireSomeProperties());
+               assertEquals(src.isFindFluentSetters(), 
copy.isFindFluentSetters());
+               assertEquals(src.isIgnoreMissingSetters(), 
copy.isIgnoreMissingSetters());
+               assertEquals(src.isIgnoreTransientFields(), 
copy.isIgnoreTransientFields());
+               assertEquals(src.isIgnoreUnknownBeanProperties(), 
copy.isIgnoreUnknownBeanProperties());
+               assertEquals(src.isUnsortedProperties(), 
copy.isUnsortedProperties());
+               assertEquals(src.isUseInterfaceProxies(), 
copy.isUseInterfaceProxies());
+               assertEquals(src.isUseJavaBeanIntrospector(), 
copy.isUseJavaBeanIntrospector());
+               assertSame(src.getPropertyNamer(), copy.getPropertyNamer());
+               assertEquals(src.getBeanTypePropertyName(), 
copy.getBeanTypePropertyName());
+               assertEquals(src.getNotBeanPackageNames(), 
copy.getNotBeanPackageNames());
+               assertEquals(src.getNotBeanPackagePrefixes(), 
copy.getNotBeanPackagePrefixes());
+               assertEquals(src.getNotBeanClasses(), copy.getNotBeanClasses());
+               assertSame(src.getBeanStore(), copy.getBeanStore());
+               assertSame(src.getAnnotationProvider(), 
copy.getAnnotationProvider());
+               // custom predicate path delegates to user predicate => false 
for any input
+               assertFalse(copy.isNotABean(info(String.class)));
+       }
+
+       @Test
+       void d02_copy_isIndependent() {
+               var src = 
BeanConfigContext.create().findFluentSetters(true).build();
+               var copy = src.copy().findFluentSetters(false).build();
+               assertTrue(src.isFindFluentSetters());
+               assertFalse(copy.isFindFluentSetters());
+       }
+
+       @Test
+       void d03_copy_collectionsDoNotShareInstance() {
+               var src = 
BeanConfigContext.create().notBeanPackageNames("pkg.a").build();
+               var copy = src.copy().notBeanPackageNames("pkg.b").build();
+               assertEquals(Set.of("pkg.a"), src.getNotBeanPackageNames());
+               assertEquals(Set.of("pkg.a", "pkg.b"), 
copy.getNotBeanPackageNames());
+       }
+
+       
//====================================================================================================
+       // Returned collections are unmodifiable
+       
//====================================================================================================
+
+       @Test
+       void e01_collectionsAreUnmodifiable() {
+               var ctx = BeanConfigContext.create()
+                       .notBeanPackageNames("pkg.a")
+                       .notBeanPackagePrefixes("pkg.b.")
+                       .notBeanClasses(String.class)
+                       .build();
+               assertThrows(UnsupportedOperationException.class, () -> 
ctx.getNotBeanPackageNames().add("x"));
+               assertThrows(UnsupportedOperationException.class, () -> 
ctx.getNotBeanPackagePrefixes().add("y"));
+               assertThrows(UnsupportedOperationException.class, () -> 
ctx.getNotBeanClasses().add(Object.class));
+       }
+
+       
//====================================================================================================
+       // isNotABean default behavior
+       
//====================================================================================================
+
+       @Test
+       void f01_isNotABean_rejectsNonClassKinds() {
+               var ctx = BeanConfigContext.DEFAULT;
+               assertTrue(ctx.isNotABean(info(int.class)));
+               assertTrue(ctx.isNotABean(info(int[].class)));
+               
assertTrue(ctx.isNotABean(info(java.lang.annotation.Retention.class)));
+               assertTrue(ctx.isNotABean(info(java.time.DayOfWeek.class)));
+       }
+
+       @Test
+       void f02_isNotABean_acceptsRegularClasses() {
+               var ctx = BeanConfigContext.DEFAULT;
+               assertFalse(ctx.isNotABean(info(BasicBeanStore.class)));
+       }
+
+       @Test
+       void f03_isNotABean_packageName_excludes() {
+               var ctx = BeanConfigContext.create()
+                       
.notBeanPackageNames(BasicBeanStore.class.getPackage().getName())
+                       .build();
+               assertTrue(ctx.isNotABean(info(BasicBeanStore.class)));
+       }
+
+       @Test
+       void f04_isNotABean_packagePrefix_excludes() {
+               var ctx = BeanConfigContext.create()
+                       .notBeanPackagePrefixes("org.apache.juneau.commons.")
+                       .build();
+               assertTrue(ctx.isNotABean(info(BasicBeanStore.class)));
+       }
+
+       @Test
+       void f05_isNotABean_classExclude() {
+               var ctx = 
BeanConfigContext.create().notBeanClasses(BasicBeanStore.class).build();
+               assertTrue(ctx.isNotABean(info(BasicBeanStore.class)));
+       }
+
+       @Test
+       void f06_isNotABean_classExclude_includesSubtypes() {
+               var ctx = 
BeanConfigContext.create().notBeanClasses(CharSequence.class).build();
+               assertTrue(ctx.isNotABean(info(String.class)));
+       }
+
+       @Test
+       void f07_isNotABean_customPredicateOverridesDefault() {
+               var ctx = BeanConfigContext.create()
+                       .notABeanPredicate(ci -> ci.is(String.class))
+                       .build();
+               assertTrue(ctx.isNotABean(info(String.class)));
+               assertFalse(ctx.isNotABean(info(int.class))); // default would 
say true; predicate overrides
+       }
+
+       @Test
+       void f08_isNotABean_customPredicateClearedToNullRevertsToDefault() {
+               var ctx = BeanConfigContext.create()
+                       .notABeanPredicate(ci -> false)
+                       .notABeanPredicate(null)
+                       .build();
+               assertTrue(ctx.isNotABean(info(int.class)));
+       }
+
+       @Test
+       void f09_isNotABean_rejectsNullArg() {
+               var ctx = BeanConfigContext.DEFAULT;
+               assertThrows(IllegalArgumentException.class, () -> 
ctx.isNotABean(null));
+       }
+
+       @Test
+       void f10_isNotABean_packageName_nonMatchingEntryIsSkipped() {
+               var ctx = BeanConfigContext.create()
+                       .notBeanPackageNames("never.matches.this")
+                       .build();
+               assertFalse(ctx.isNotABean(info(BasicBeanStore.class)));
+       }
+
+       @Test
+       void f11_isNotABean_packagePrefix_nonMatchingEntryIsSkipped() {
+               var ctx = BeanConfigContext.create()
+                       .notBeanPackagePrefixes("never.matches.")
+                       .build();
+               assertFalse(ctx.isNotABean(info(BasicBeanStore.class)));
+       }
+
+       @Test
+       void f12_isNotABean_classExclude_nonMatchingEntryIsSkipped() {
+               var ctx = BeanConfigContext.create()
+                       .notBeanClasses(java.util.UUID.class)
+                       .build();
+               assertFalse(ctx.isNotABean(info(BasicBeanStore.class)));
+       }
+}
diff --git a/todo/TODO-5-bean-runtime-types-to-commons.md 
b/todo/TODO-5-bean-runtime-types-to-commons.md
index d6f418a8e7..b086acfdbe 100644
--- a/todo/TODO-5-bean-runtime-types-to-commons.md
+++ b/todo/TODO-5-bean-runtime-types-to-commons.md
@@ -4,6 +4,25 @@ This is the remaining work from **Phase 5 of the bean-layer 
split**. Phase 5a (t
 
 ---
 
+## Status (as of Phase 5b checkpoint)
+
+**Step 1 complete.** A `BeanConfigContext` POJO + builder now lives in 
`commons.bean`; `MarshallingContext.getBeanConfigContext()` returns a memoized 
snapshot view. The eight runtime types still live in `juneau-marshall` and 
still use `MarshallingContext` directly — Step 1 is purely additive 
infrastructure that future steps can lean on.
+
+- [x] **Step 1** — `BeanConfigContext` POJO + builder in `commons.bean`. 
Carries: visibility settings, all `beans*Require*` toggles, 
`findFluentSetters`, `unsortedProperties`, `useInterfaceProxies`, 
`useJavaBeanIntrospector`, `ignoreMissingSetters`, `ignoreTransientFields`, 
`ignoreUnknownBeanProperties`, `propertyNamer`, `beanTypePropertyName`, 
`notBeanPackageNames` / `notBeanPackagePrefixes` / `notBeanClasses`, 
`BeanStore`, `AnnotationProvider`, optional `Predicate<ClassInfo>` override  
[...]
+- [ ] **Step 2** — Replace `ClassMeta` with `ClassInfo` in `BeanMeta` / 
`BeanPropertyMeta`. Most `cm.*` calls (`isAnonymousClass`, `isMemberClass`, 
`isAssignableTo`, `getModifiers`, `getRecordComponents`, `inner`, …) are pure 
reflection that already exists on `ClassInfo`. Two outliers — 
`cm.getProxyInvocationHandler()` (replace with a `BeanConfigContext` hook or 
move proxy creation into `BeanMeta`) and `cm.getMarshallingContext().string()` 
(use a plain `Class<String>`/`ClassInfo`).
+- [ ] **Step 3** — Remove swap-aware `get/set` from `BeanPropertyMeta`. Add 
identity-default `BiFunction<Object,Object,Object>` callbacks (or a small 
`BeanPropertyTransform` SPI) so the marshalling layer installs swap-aware 
behavior at session construction.
+- [ ] **Step 4** — Remove `MarshallingSession` back-pointer from `BeanMap`. 
After Step 3, `BeanMap.get/put` are raw property reads/writes; 
`MarshallingSession.toBeanMap` wraps a `BeanMap` for serialization and applies 
swaps externally.
+- [ ] **Step 5** — Remove `BeanRegistry` field from `BeanPropertyMeta`. Lift 
dictionary metadata into a marshalling-side companion (`MarshalledPropertyMeta` 
or a side-map keyed by `BeanPropertyMeta`).
+- [ ] **Step 6** — `BeanMeta` becomes constructible by both `ClassMeta` and 
direct `commons.bean` callers via `BeanMeta.of(MyClass.class, 
BeanConfigContext.DEFAULT)`. `ClassMeta` becomes a *consumer* of `BeanMeta` 
rather than its creator.
+- [ ] **Step 7** — Re-check whether `ExtendedBeanMeta` and per-format 
extensions (`XmlBeanMeta`, `RdfBeanMeta`, `HtmlBeanMeta`) need to follow 
`BeanMeta` to `commons.bean`. Default expectation: they stay in 
`juneau-marshall`.
+- [ ] **Step 8** — `git mv` the eight runtime types into 
`juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/`. 
Verify `juneau-commons` still compiles standalone (`cd 
juneau-core/juneau-commons && mvn clean compile`).
+- [ ] **Step 9** — Reference sweep: 80–120 unique files (mostly inside 
`juneau-marshall`). Update imports, Javadoc `{@link …}` references, 
package-info docs.
+- [ ] **Step 10** — Update `juneau-docs` release notes / migration guide 
(`docs/pages/release-notes/9.5.0.md`, `## Package Moves` section) with the 
bean-runtime relocations.
+
+The "incomplete-but-documented over broken-build" rule from Phase 5a still 
applies. When picking up the next slice of this work, Step 2 is the recommended 
next checkpoint — it removes one of the two big blockers (`ClassMeta` coupling) 
without yet attempting the swap/registry/session decoupling that requires 
reworking serializer/parser code paths.
+
+---
+
 ## Goal
 
 Move these eight `BeanXxx` runtime types out of `org.apache.juneau` (in 
`juneau-marshall`) into `org.apache.juneau.commons.bean` (in `juneau-commons`) 
so the **bean-modeling runtime** is independently usable without dragging in 
the full marshalling stack:

Reply via email to