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 a61bede3b5c988f7de5c260284d9ac8d9fe6a9cb Author: James Bognar <[email protected]> AuthorDate: Tue May 12 15:37:27 2026 -0400 refactor: extract BeanTypeInfo/BeanFilter/BeanRegistryLookup SPI seams in commons.bean (TODO-5 Step 8b-i) Co-authored-by: Cursor <[email protected]> --- .../org/apache/juneau/commons/bean/BeanFilter.java | 131 ++++++++++++++++++ .../juneau/commons/bean/BeanRegistryLookup.java | 51 +++++++ .../apache/juneau/commons/bean/BeanTypeInfo.java | 147 +++++++++++++++++++++ .../org/apache/juneau/commons/bean}/Delegate.java | 14 +- .../apache/juneau/jena/RdfSerializerSession.java | 3 +- .../juneau/jena/RdfStreamSerializerSession.java | 3 +- .../src/main/java/org/apache/juneau/BeanMeta.java | 10 +- .../main/java/org/apache/juneau/BeanRegistry.java | 3 +- .../src/main/java/org/apache/juneau/ClassMeta.java | 4 +- .../java/org/apache/juneau/MarshalledFilter.java | 2 +- .../apache/juneau/html/HtmlSerializerSession.java | 5 +- .../org/apache/juneau/internal/DelegateList.java | 1 + .../org/apache/juneau/internal/DelegateMap.java | 1 + .../org/apache/juneau/internal/FilteredKeyMap.java | 1 + .../apache/juneau/xml/XmlSerializerSession.java | 5 +- todo/TODO-5-bean-runtime-types-to-commons.md | 30 ++++- 16 files changed, 389 insertions(+), 22 deletions(-) diff --git a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanFilter.java b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanFilter.java new file mode 100644 index 0000000000..740e506ad8 --- /dev/null +++ b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanFilter.java @@ -0,0 +1,131 @@ +/* + * 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 java.util.List; +import java.util.Set; + +import org.apache.juneau.commons.reflect.ClassInfo; +import org.apache.juneau.commons.reflect.ClassInfoTyped; + +/** + * Bean-modeling SPI seam that exposes the per-class filter surface the bean-runtime types + * ({@link BeanMeta}-equivalent) need without coupling the bean-modeling layer to the marshalling-side + * {@code MarshalledFilter}. + * + * <p> + * Marshalling-side {@code MarshalledFilter} implements this interface; bean-modeling-side code only + * sees {@link BeanFilter}. This interface is restricted to commons-compatible return types + * (collections, {@link ClassInfo}, {@link PropertyNamer}, plain values). + */ +public interface BeanFilter { + + /** + * @return The bean class that this filter applies to, or <jk>null</jk> if this is a non-bean filter. + */ + ClassInfoTyped<?> getBeanClass(); + + /** + * @return The class that this filter applies to. + */ + Class<?> getMarshalledClass(); + + /** + * @return The dictionary name associated with this bean, or <jk>null</jk> if no name is defined. + */ + String getTypeName(); + + /** + * @return The example associated with this class, or <jk>null</jk>. + */ + String getExample(); + + /** + * @return The implementation class associated with this class, or <jk>null</jk>. + */ + ClassInfo getImplClass(); + + /** + * @return The interface class associated with this class, or <jk>null</jk>. + */ + ClassInfo getInterfaceClass(); + + /** + * @return The stop class associated with this class, or <jk>null</jk>. + */ + ClassInfo getStopClass(); + + /** + * @return The names of the properties associated with the bean class (ordered), or an empty set if all properties. + */ + Set<String> getProperties(); + + /** + * @return The names of the properties to ignore on a bean, or an empty set. + */ + Set<String> getExcludeProperties(); + + /** + * @return The names of the read-only properties on a bean, or an empty set. + */ + Set<String> getReadOnlyProperties(); + + /** + * @return The names of the write-only properties on a bean, or an empty set. + */ + Set<String> getWriteOnlyProperties(); + + /** + * @return The property namer for this filter, or <jk>null</jk>. + */ + PropertyNamer getPropertyNamer(); + + /** + * @return The list of bean dictionary classes, or an empty list. + */ + List<ClassInfo> getBeanDictionary(); + + /** + * @return <jk>true</jk> if fluent setters should be found. + */ + boolean isFluentSetters(); + + /** + * @return <jk>true</jk> if this bean opts out of alphabetical property sorting. + */ + boolean isUnsortedProperties(); + + /** + * Calls the {@link BeanInterceptor#readProperty(Object, String, Object)} on the registered interceptor. + * + * @param bean The bean from which the property was read. + * @param name The property name. + * @param value The value just extracted from calling the bean getter. + * @return The value to serialize. Default is just to return the existing value. + */ + Object readProperty(Object bean, String name, Object value); + + /** + * Calls the {@link BeanInterceptor#writeProperty(Object, String, Object)} on the registered interceptor. + * + * @param bean The bean to which the property is being written. + * @param name The property name. + * @param value The value just parsed. + * @return The value to assign. Default is just to return the existing value. + */ + Object writeProperty(Object bean, String name, Object value); +} diff --git a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanRegistryLookup.java b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanRegistryLookup.java new file mode 100644 index 0000000000..af2963c58d --- /dev/null +++ b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanRegistryLookup.java @@ -0,0 +1,51 @@ +/* + * 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; + +/** + * Bean-modeling SPI seam that exposes the dictionary-lookup surface the bean-runtime types need without + * coupling the bean-modeling layer to the marshalling-side {@code BeanRegistry}. + * + * <p> + * Marshalling-side {@code BeanRegistry} implements this interface. The bean-modeling-side + * only sees {@link BeanRegistryLookup} when it needs to translate between a raw {@link Class} + * and a polymorphic type name. All operations involving {@code ClassMeta} (e.g. + * {@code getClassMeta(String)}) remain on the marshalling-side {@code BeanRegistry} itself + * and are reached only via narrowing casts from marshalling-side call sites. + * + * <p> + * The bean-modeling layer never instantiates a registry directly — instances are always supplied + * by the marshalling layer via marshalling-side hooks. + */ +public interface BeanRegistryLookup { + + /** + * Given the specified raw class, return the dictionary name for it, or <jk>null</jk> if not found. + * + * @param c The class to lookup in this registry. + * @return The dictionary name for the specified class, or <jk>null</jk> if not found. + */ + String getTypeName(Class<?> c); + + /** + * Returns <jk>true</jk> if this dictionary has an entry for the specified type name. + * + * @param typeName The bean type name. + * @return <jk>true</jk> if this dictionary has an entry for the specified type name. + */ + boolean hasName(String typeName); +} diff --git a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanTypeInfo.java b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanTypeInfo.java new file mode 100644 index 0000000000..ac96183c0c --- /dev/null +++ b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanTypeInfo.java @@ -0,0 +1,147 @@ +/* + * 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 java.lang.reflect.Type; + +import org.apache.juneau.commons.reflect.ClassInfoTyped; +import org.apache.juneau.commons.reflect.ExecutableException; + +/** + * Bean-modeling SPI seam that exposes the type-classification surface the bean-runtime types + * ({@link BeanMeta}-equivalents, {@link BeanPropertyMeta}-equivalents, etc.) need without coupling + * the bean-modeling layer to the marshalling-side {@code ClassMeta}. + * + * <p> + * Marshalling-side {@code ClassMeta<T>} extends this class; bean-modeling-side code only sees + * {@link BeanTypeInfo}. The bean-modeling-side never instantiates this class directly — it always + * gets instances handed in by the marshalling layer (e.g. via builder calls like + * {@code BeanPropertyMeta.Builder#rawMetaType(ClassMeta)}). + * + * <p> + * All extra abstract methods declared here mirror methods that {@code ClassMeta} already implements, + * so {@code ClassMeta} satisfies this contract by virtue of its existing implementation. + * + * @param <T> The raw class type this instance represents. + */ +public abstract class BeanTypeInfo<T> extends ClassInfoTyped<T> { + + /** + * Constructor. + * + * @param inner The class type. + */ + protected BeanTypeInfo(Class<T> inner) { + super(inner); + } + + /** + * Constructor. + * + * @param inner The class type. + * @param innerType The generic type (if parameterized type). + */ + protected BeanTypeInfo(Class<T> inner, Type innerType) { + super(inner, innerType); + } + + /** + * Returns <jk>true</jk> if this class is a URI/URL or annotated with a URI marker. + * + * @return <jk>true</jk> if this class is a URI. + */ + public abstract boolean isUri(); + + /** + * Returns <jk>true</jk> if this class is a {@link java.util.Optional}. + * + * @return <jk>true</jk> if this class is an Optional. + */ + public abstract boolean isOptional(); + + /** + * Returns <jk>true</jk> if this class is classified as a bean. + * + * @return <jk>true</jk> if this class is a bean. + */ + public abstract boolean isBean(); + + /** + * Returns <jk>true</jk> if this class is {@link Object}. + * + * @return <jk>true</jk> if this class is {@code Object}. + */ + public abstract boolean isObject(); + + /** + * For array and {@code Collection} types, returns the type info of the element type, or <jk>null</jk> + * if this is not an array/collection. + * + * @return The element type info, or <jk>null</jk>. + */ + public abstract BeanTypeInfo<?> getElementType(); + + /** + * For {@code Map} types, returns the type info of the key type, or <jk>null</jk> if this is not a map. + * + * @return The key type info, or <jk>null</jk>. + */ + public abstract BeanTypeInfo<?> getKeyType(); + + /** + * For {@code Map} types, returns the type info of the value type, or <jk>null</jk> if this is not a map. + * + * @return The value type info, or <jk>null</jk>. + */ + public abstract BeanTypeInfo<?> getValueType(); + + /** + * Returns <jk>true</jk> if this class can be instantiated using a no-arg constructor. + * + * @return <jk>true</jk> if a new instance can be created. + */ + public abstract boolean canCreateNewInstance(); + + /** + * Returns <jk>true</jk> if this class can be instantiated, optionally with the specified outer object + * for non-static inner classes. + * + * @param outer The outer object instance, or <jk>null</jk> if not applicable. + * @return <jk>true</jk> if a new instance can be created. + */ + public abstract boolean canCreateNewInstance(Object outer); + + /** + * Creates a new instance of this class. + * + * @return A new instance of the class. + * @throws ExecutableException If the class could not be instantiated. + */ + public abstract T newInstance() throws ExecutableException; + + /** + * For {@link java.util.Optional} types, returns an empty optional default value, or <jk>null</jk> + * if this isn't an Optional. + * + * <p> + * Returns {@link Object} rather than {@code T} because the marshalling-side {@code ClassMeta} returns + * a wildcard {@code Optional<?>} which is not assignable to {@code T} under Java's type system. + * + * @return The default empty Optional value, or <jk>null</jk>. + */ + public abstract Object getOptionalDefault(); +} diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/Delegate.java b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/Delegate.java similarity index 69% rename from juneau-core/juneau-marshall/src/main/java/org/apache/juneau/Delegate.java rename to juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/Delegate.java index 9f3b58ec6a..605003c830 100644 --- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/Delegate.java +++ b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/Delegate.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.juneau; +package org.apache.juneau.commons.bean; /** * An object that represents another object, often wrapping that object. @@ -23,17 +23,21 @@ package org.apache.juneau; * <b>*** Internal Interface - Not intended for external use ***</b> * * <p> - * For example, {@link BeanMap} is a map representation of a bean. + * For example, {@code BeanMap} is a map representation of a bean. * + * <p> + * The returned type info is a {@link BeanTypeInfo} so the bean-modeling layer does not depend + * on the marshalling-side {@code ClassMeta}. Marshalling-side implementations narrow the return + * type via Java covariant returns (e.g. {@code ClassMeta<T> getClassMeta()}). * * @param <T> The represented class type. */ public interface Delegate<T> { /** - * The {@link ClassMeta} of the class of the represented object. + * The {@link BeanTypeInfo} of the class of the represented object. * * @return The class type of the represented object. */ - ClassMeta<T> getClassMeta(); -} \ No newline at end of file + BeanTypeInfo<T> getClassMeta(); +} diff --git a/juneau-core/juneau-marshall-rdf/src/main/java/org/apache/juneau/jena/RdfSerializerSession.java b/juneau-core/juneau-marshall-rdf/src/main/java/org/apache/juneau/jena/RdfSerializerSession.java index 84f964b21d..b0e5a6ae93 100644 --- a/juneau-core/juneau-marshall-rdf/src/main/java/org/apache/juneau/jena/RdfSerializerSession.java +++ b/juneau-core/juneau-marshall-rdf/src/main/java/org/apache/juneau/jena/RdfSerializerSession.java @@ -32,6 +32,7 @@ import java.util.*; import java.util.function.*; import org.apache.juneau.*; +import org.apache.juneau.commons.bean.*; import org.apache.juneau.commons.utils.*; import org.apache.juneau.httppart.*; import org.apache.juneau.jena.annotation.*; @@ -340,7 +341,7 @@ public class RdfSerializerSession extends WriterSerializerSession { if (aType.isDelegate()) { wType = aType; - aType = ((Delegate)o).getClassMeta(); + aType = (ClassMeta)((Delegate)o).getClassMeta(); } sType = aType; diff --git a/juneau-core/juneau-marshall-rdf/src/main/java/org/apache/juneau/jena/RdfStreamSerializerSession.java b/juneau-core/juneau-marshall-rdf/src/main/java/org/apache/juneau/jena/RdfStreamSerializerSession.java index f8461aa88b..e6ad4d09a1 100644 --- a/juneau-core/juneau-marshall-rdf/src/main/java/org/apache/juneau/jena/RdfStreamSerializerSession.java +++ b/juneau-core/juneau-marshall-rdf/src/main/java/org/apache/juneau/jena/RdfStreamSerializerSession.java @@ -29,6 +29,7 @@ import java.util.*; import java.util.function.*; import org.apache.juneau.*; +import org.apache.juneau.commons.bean.*; import org.apache.juneau.commons.utils.*; import org.apache.juneau.httppart.*; import org.apache.juneau.serializer.*; @@ -284,7 +285,7 @@ public class RdfStreamSerializerSession extends OutputStreamSerializerSession { if (nn(o)) { if (aType.isDelegate()) { wType = aType; - aType = ((Delegate)o).getClassMeta(); + aType = (ClassMeta)((Delegate)o).getClassMeta(); } sType = aType; var swap = aType.getSwap(this); 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 eb0cc118cd..8795a98aba 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 @@ -361,7 +361,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 BeanConfigContext config; // Bean-modeling settings facade — always non-null. Sources: marshallingContext.getBeanConfigContext() (marshalling-side) or the explicit BeanConfigContext (commons-side). - private final MarshalledFilter beanFilter; // Optional bean filter associated with the target class. + 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). private final Supplier<BeanRegistry> beanRegistry; // The bean registry for this bean. private final Supplier<List<ClassInfo>> classHierarchy; // List of all classes traversed in the class hierarchy. @@ -444,7 +444,7 @@ public class BeanMeta<T> { * @param pNames Explicit list of property names and order. If <jk>null</jk>, properties are determined automatically. * @param implClass Optional implementation class constructor to use if one cannot be found. Can be <jk>null</jk>. */ - protected BeanMeta(ClassMeta<T> cm, MarshalledFilter bf, String[] pNames, ClassInfo implClass) { + protected BeanMeta(ClassMeta<T> cm, BeanFilter bf, String[] pNames, ClassInfo implClass) { this(cm, cm, cm.getMarshallingContext().getBeanConfigContext(), cm.getMarshallingContext(), bf, pNames, implClass); } @@ -468,7 +468,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, MarshalledFilter bf, String[] pNames, ClassInfo implClass) { + private BeanMeta(ClassMeta<T> cm, ClassInfo ci0, BeanConfigContext config, MarshallingContext mc, BeanFilter bf, String[] pNames, ClassInfo implClass) { classMeta = cm; classInfo = ci0; this.config = config; @@ -809,7 +809,9 @@ public class BeanMeta<T> { * @see Bean */ public MarshalledFilter getMarshalledFilter() { - return beanFilter; + // The field is typed as the bean-modeling-side BeanFilter SPI but the only concrete + // implementation in-tree is MarshalledFilter, so the narrowing cast is safe. + return (MarshalledFilter) beanFilter; } /** diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanRegistry.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanRegistry.java index a1197c11bd..831d2f5d54 100644 --- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanRegistry.java +++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanRegistry.java @@ -27,6 +27,7 @@ import java.util.*; import java.util.concurrent.*; import org.apache.juneau.annotation.*; +import org.apache.juneau.commons.bean.*; import org.apache.juneau.commons.collections.*; import org.apache.juneau.commons.inject.*; import org.apache.juneau.commons.reflect.*; @@ -55,7 +56,7 @@ import org.apache.juneau.commons.utils.*; "java:S115", // Constants use UPPER_snakeCase naming convention "java:S1452" // Wildcard required - Class<?> for bean dictionary types }) -public class BeanRegistry { +public class BeanRegistry implements BeanRegistryLookup { // Argument name constants for assertArgNotNull private static final String ARG_bc = "bc"; diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java index cf99d27494..f05140c218 100644 --- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java +++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java @@ -36,7 +36,7 @@ import java.util.function.*; import java.util.stream.*; import org.apache.juneau.annotation.*; -import org.apache.juneau.commons.bean.BeanType; +import org.apache.juneau.commons.bean.*; import org.apache.juneau.commons.collections.*; import org.apache.juneau.commons.conversion.*; import org.apache.juneau.commons.function.*; @@ -73,7 +73,7 @@ import org.apache.juneau.swap.*; "java:S1452", // Wildcard required - ClassMeta<?>, ObjectSwap<T,?>, etc. for element/component types "java:S6539" // Monster Class: ClassMeta is a focused reflection-metadata cache; splitting would increase coupling }) -public class ClassMeta<T> extends ClassInfoTyped<T> { +public class ClassMeta<T> extends BeanTypeInfo<T> { private static class Categories { int bits; diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshalledFilter.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshalledFilter.java index 8df0ec7415..bf7dc0a076 100644 --- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshalledFilter.java +++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshalledFilter.java @@ -82,7 +82,7 @@ import org.apache.juneau.swap.*; "rawtypes", // Raw types necessary for generic type handling "java:S1452" // Wildcard required - ClassInfoTyped<?>, ClassMeta<?> for filter metadata }) -public class MarshalledFilter { +public class MarshalledFilter implements BeanFilter { /** * Builder class. diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java index 336ff0294e..36eabf0cac 100644 --- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java +++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java @@ -33,6 +33,7 @@ import java.util.function.*; import java.util.regex.*; import org.apache.juneau.*; +import org.apache.juneau.commons.bean.*; import org.apache.juneau.commons.lang.*; import org.apache.juneau.html.annotation.*; import org.apache.juneau.httppart.*; @@ -981,7 +982,7 @@ public class HtmlSerializerSession extends XmlSerializerSession { if (aType.isDelegate()) { wType = aType; - aType = ((Delegate)o).getClassMeta(); + aType = (ClassMeta)((Delegate)o).getClassMeta(); } sType = aType; @@ -1144,7 +1145,7 @@ public class HtmlSerializerSession extends XmlSerializerSession { if (type == null) type = object(); else if (type.isDelegate()) - type = ((Delegate)o).getClassMeta(); + type = (ClassMeta)((Delegate)o).getClassMeta(); ObjectSwap swap = type.getSwap(this); if (nn(swap)) { o = swap(swap, o); diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/DelegateList.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/DelegateList.java index f574ba96ba..5cc51b917a 100644 --- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/DelegateList.java +++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/DelegateList.java @@ -20,6 +20,7 @@ import java.util.*; import org.apache.juneau.*; import org.apache.juneau.collections.*; +import org.apache.juneau.commons.bean.*; /** * Represents a wrapped {@link Collection} where entries in the list can be removed or reordered without affecting the diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/DelegateMap.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/DelegateMap.java index 5035a39337..d179fa1b4c 100644 --- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/DelegateMap.java +++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/DelegateMap.java @@ -22,6 +22,7 @@ import java.util.*; import org.apache.juneau.*; import org.apache.juneau.collections.*; +import org.apache.juneau.commons.bean.*; /** * Represents a wrapped {@link Map} where entries in the map can be removed without affecting the underlying map. diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/FilteredKeyMap.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/FilteredKeyMap.java index dc72676195..8a6b02bc20 100644 --- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/FilteredKeyMap.java +++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/FilteredKeyMap.java @@ -22,6 +22,7 @@ import static org.apache.juneau.commons.utils.Utils.*; import java.util.*; import org.apache.juneau.*; +import org.apache.juneau.commons.bean.*; /** * Wrapper around a map where the key names are overridden. diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/xml/XmlSerializerSession.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/xml/XmlSerializerSession.java index 82481eb423..13b46ad88c 100644 --- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/xml/XmlSerializerSession.java +++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/xml/XmlSerializerSession.java @@ -32,6 +32,7 @@ import java.util.*; import java.util.function.*; import org.apache.juneau.*; +import org.apache.juneau.commons.bean.*; import org.apache.juneau.commons.lang.*; import org.apache.juneau.httppart.*; import org.apache.juneau.serializer.*; @@ -761,7 +762,7 @@ public class XmlSerializerSession extends WriterSerializerSession { } else if (aType.isBean()) { bm = toBeanMap(o); } else if (aType.isDelegate()) { - var innerType = ((Delegate<?>)o).getClassMeta(); + var innerType = (ClassMeta<?>)((Delegate<?>)o).getClassMeta(); var ns = Value.of(getXmlClassMeta(innerType).getNamespace()); if (ns.isPresent()) { if (nn(ns.get().uri)) @@ -934,7 +935,7 @@ public class XmlSerializerSession extends WriterSerializerSession { if (aType.isDelegate()) { wType = aType; - eType = aType = ((Delegate<?>)o).getClassMeta(); + eType = aType = (ClassMeta<?>)((Delegate<?>)o).getClassMeta(); } sType = aType; diff --git a/todo/TODO-5-bean-runtime-types-to-commons.md b/todo/TODO-5-bean-runtime-types-to-commons.md index 56d740ecbd..fd6b4ee8e1 100644 --- a/todo/TODO-5-bean-runtime-types-to-commons.md +++ b/todo/TODO-5-bean-runtime-types-to-commons.md @@ -4,7 +4,9 @@ This is the remaining work from **Phase 5 of the bean-layer split**. Phase 5a (t --- -## Status (as of Phase 5h checkpoint) +## Status (as of Phase 5h checkpoint + Step 8b-i) + +**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 [...] **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: @@ -90,8 +92,30 @@ Known limitations of the commons-side path (acceptable for Step 6, scoped for la - [x] **Step 6** — `BeanMeta.of(Class<T>, BeanConfigContext)` factory + `protected BeanMeta(Class<T>, BeanConfigContext)` constructor wired up. `BeanMeta` now carries a non-null `BeanConfigContext config` facade for all settings reads; the `marshallingContext` and `classMeta` fields are documented-nullable and stay null on the commons-side path. `BeanPropertyMeta.Builder.bc` and `BeanPropertyMeta.bc` similarly nullable; new mirrored `config` field on both. `Builder.validate(...)` accepts [...] - [x] **Step 7** — Per-format extension survey + pre-Step-8 hardening. (a) Survey result: `ExtendedBeanMeta` (composes `BeanMeta<?>`), `XmlBeanMeta` (extends `ExtendedBeanMeta`), `RdfBeanMeta` (extends `ExtendedBeanMeta`); no `HtmlBeanMeta` exists. All marshalling-side, all stay in `juneau-marshall`. (b) Hardened `BeanPropertyMeta.add(BeanMap,String,Object)` / `add(BeanMap,String,String,Object)` / `setArray` / `applyChildPropertiesFilter` (all throw `UnsupportedOperationException` with a [...] - [x] **Step 8a** — SPI seams in `commons.bean` (commit `3a74fcd50a`). `BeanSession` interface created, `MarshallingSession` implements it, four marshalling-only-historically settings migrated to `BeanConfigContext` so `BeanPropertyMeta`/`BeanMap` can stop reading them through `MarshallingContext`. See "Status (as of Phase 5h checkpoint)" above for full detail. -- [ ] **Step 8b** — Round out the remaining SPI seams (`BeanTypeInfo` for `ClassMeta`, `BeanRegistryLookup` for `BeanRegistry`, optional `BeanPropertySwap` for `ObjectSwap`), retype the 8 types' fields to use the seams (or `Object`) on the bean-modeling side, then `git mv` all 8 files into `juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/`. Verify `juneau-commons` still compiles standalone (`cd juneau-core/juneau-commons && mvn clean compile`). -- [ ] **Step 8c** — (optional) Cleanup pass for anything that comes up during 8b: deprecated bridges, stale imports, package-info docs, etc. +- [x] **Step 8b-i** — Foundational SPI extraction (uncommitted, working tree). Three new SPI seams and one type relocation, all build- and test-green: + - **`BeanTypeInfo<T>`** in `commons.bean` (abstract class extending `ClassInfoTyped<T>`). Declares the bean-modeling-side type-classification surface needed by the 8 target types: `isUri()`, `isOptional()`, `isBean()`, `isObject()`, `getElementType()`, `getKeyType()`, `getValueType()`, `canCreateNewInstance()`, `canCreateNewInstance(Object)`, `newInstance()` (throws `ExecutableException` from commons), `getOptionalDefault()` (returns `Object` because marshalling-side `ClassMeta.getOpti [...] + - **`BeanFilter`** interface in `commons.bean`. Declares the per-class filter surface (property includes/excludes, read/write-only sets, propertyNamer, beanDictionary, interfaceClass/stopClass/implClass, typeName, example, fluentSetters flag, unsortedProperties flag, plus `readProperty`/`writeProperty` BeanInterceptor wrappers). `MarshalledFilter` now `implements BeanFilter` — no source changes inside `MarshalledFilter` were needed (every method on `BeanFilter` already existed on `Mars [...] + - **`BeanRegistryLookup`** interface in `commons.bean` (minimal surface: `String getTypeName(Class<?>)` and `boolean hasName(String)`). `BeanRegistry` implements it. The `Map<BeanPropertyMeta,BeanRegistry>` side-map on `BeanMeta` is unchanged for now (retyping it to `BeanRegistryLookup` would force casts on the 3 marshalling-side callers in `ParserSession`/`SerializerSession`/`XmlParserSession` that call `BeanRegistry`-only methods like `getClassMeta(typeName)` on the result — deferred [...] + - **`Delegate<T>` moved** from `org.apache.juneau` to `org.apache.juneau.commons.bean` via `git mv`. The interface's `getClassMeta()` method now returns `BeanTypeInfo<T>` instead of `ClassMeta<T>`. All five in-tree `Delegate` implementations (`BeanMap`, `DelegateBeanMap`, `DelegateList`, `DelegateMap`, `FilteredKeyMap`) keep their `ClassMeta<T>` return types via Java covariant returns (no source changes needed in those classes). Five marshalling-side files that used the raw-typed `(Del [...] + - **Build/test verification.** `python3 scripts/test.py --full` passed clean (juneau + juneau-rest + juneau-microservice + examples + utests, ~70k tests). `cd juneau-core/juneau-commons && mvn clean compile` passes standalone — `juneau-commons` still compiles without depending on `juneau-marshall`. `ReadLints` clean on all modified files. +- [ ] **Step 8b-ii** — Continuation of the bean-runtime move. Remaining work to physically relocate the 8 target files (`BeanMap`, `BeanMapEntry`, `BeanMeta`, `BeanMetaFiltered`, `BeanPropertyMeta`, `BeanPropertyValue`, `BeanPropertyConsumer`, `BeanProxyInvocationHandler`) into `commons.bean`. The SPI seams from 8b-i are in place; this step is the field-retype + cross-module-leakage cleanup pass: + - **`BeanPropertyMeta.rawTypeMeta` / `typeMeta`** — retype from `ClassMeta<?>` to `BeanTypeInfo<?>`. `BeanPropertyMeta.getClassMeta()` return type widens to `BeanTypeInfo<?>`. The ~70 marshalling-side call sites that call `pMeta.getClassMeta().<method>` mostly work unchanged (inherited `ClassInfo`/`ClassInfoTyped` methods + new `BeanTypeInfo` abstract methods are sufficient). The few sites that call `ClassMeta`-only methods (e.g. `getSerializedClassMeta(this)`, `getMarshallingContext() [...] + - **`BeanPropertyMeta.swap`** — retype from `ObjectSwap` to `Object`. Internal references (`setPropertyValue`'s defensive double-unswap check, `properties()` debug method, Javadocs) cast or remove the check (it's belt-and-braces — the install-time `writeTransform` already normalizes the value before this point). + - **`BeanPropertyMeta.Builder.bc`** — retype from `MarshallingContext` to `Object`. `Builder.bc.resolveClassMeta(...)`, `Builder.bc.getClassMeta(...)`, `Builder.bc.object()` calls inside `validate(...)` lift out to a marshalling-side post-processor (or route through a `BeanSession`-style narrow SPI). The bean-modeling-side path already short-circuits when `bc == null`, so the lift-out is mostly about giving marshalling-side callers a place to install type metadata after the commons-sid [...] + - **`@MarshalledProp` annotation reads** inside `BeanPropertyMeta.Builder.validate(...)` — lift out to a marshalling-side post-processor. After `Builder.build()` returns, the marshalling layer reads `@MarshalledProp` annotations off the property's getter/setter/field and post-processes the just-built `BeanPropertyMeta` to install the swap transforms / dictionary classes / property override list. + - **`BeanMeta.beanRegistry`** and **`propertyBeanRegistries`** — retype from `BeanRegistry` to `BeanRegistryLookup`. `BeanMeta.getBeanRegistry()` and `getPropertyBeanRegistry(...)` return types narrow to `BeanRegistry` via casts (only concrete impl in-tree), OR the public getter return types widen to `BeanRegistryLookup` and the 3 callers (ParserSession/SerializerSession/XmlParserSession) cast at their call sites. The `findBeanRegistry()` helper either: (a) stays in `BeanMeta` with `Ob [...] + - **`BeanMeta` constructor signatures** — the `protected BeanMeta(ClassMeta<T>, BeanFilter, String[], ClassInfo)` constructor takes a `ClassMeta` that the bean-modeling layer should not know about. Three options: (a) keep this constructor on `BeanMeta` typed as `Object`/`BeanTypeInfo`, marshalling-side callers cast; (b) move it to a marshalling-side factory helper (e.g. `ClassMeta.findBeanMeta()` was the only original caller) and delete it from `BeanMeta`; (c) introduce a marshalling-s [...] + - **`BeanMetaFiltered.super(...)`** call — uses the old `(ClassMeta, MarshalledFilter, String[], ClassInfo)` BeanMeta constructor. After Step 8b-ii's constructor refactor, this call site needs to either inline a private helper-build path or route through the new commons-side `BeanMeta.of(Class, BeanConfigContext)` factory. + - **`BeanMap.load(Reader, ReaderParser)`** and **`BeanMap.load(String)`** — these use `JsonMap.ofText(...)` / `JsonMap.ofJson(...)` which are marshalling-side. Move both methods to a marshalling-side helper class (e.g. `BeanMapLoader`); the static methods take a `BeanMap<T>` + `Reader`/`String` and call `putAll(JsonMap.ofText(...))`. Updates the JsonMap-aware Javadoc references on `BeanMap`. + - **`BeanMap.getBean()`** uses `Json5Serializer.DEFAULT.toString(...)` for an error message — replace with `Arrays.toString(...)` or simple class-name iteration. + - **`BeanProxyInvocationHandler.toString()`** uses `Json5Serializer.DEFAULT.toString(...)` — replace with `Objects.toString(...)` or a hand-rolled "{prop1: val1, prop2: val2}" formatter. + - **`JsonMap` / `JsonList` constructions** inside `BeanPropertyMeta.add(...)` and `BeanPropertyMeta.setPropertyValue(...)` — replace with `LinkedHashMap` / `ArrayList` (the only reason `JsonMap`/`JsonList` were used is for the `MarshallingSession`-aware member-type conversion; after Step 3 those conversions go through `BeanSession.convertToMemberType(...)` directly, so plain JDK collections work). + - **`BeanInstantiator.of(Collection.class)/of(Map.class)` calls** inside `setPropertyValue` — `BeanInstantiator` is already in `commons.inject`, but the `type(rawTypeMeta)` overload may need updating to take `BeanTypeInfo<?>` rather than `ClassMeta<?>`. + - **`ParseException`** imports — `BeanMap.load(...)` and `BeanPropertyMeta.set` throw `ParseException` which lives in `org.apache.juneau.parser`. After the load() methods move to a marshalling-side helper, the only remaining `ParseException` site is `BeanPropertyMeta.set` where it's caught from `session.convertToMemberType(...)` and re-thrown as a `BeanRuntimeException` (which is the desired bean-modeling-side exception). The catch block already wraps it; just delete the `throws ParseE [...] + - **`Surrogate.class`** reference inside `BeanPropertyMeta.Builder.swapSwap(...)` — already gated by an `unsupportedOp("TODO - Surrogate swaps not yet supported on bean properties.")` throw. Either move `Surrogate` to commons (it's a marker class), or skip the check entirely on the commons-side path (rely on the marshalling-side post-processor to do `@Swap`/`@Surrogate` validation). + - **`StringFormatSwap`** instantiation inside `BeanPropertyMeta.Builder.marshalledPropSwap(...)` — marshalling-side type. Move into the marshalling-side post-processor along with the `@MarshalledProp` annotation read (one location). + - **Final cleanup** — `git mv` the 8 files into `juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/`, update `package` declarations, repo-wide reference sweep (`import org.apache.juneau.BeanMap` → `import org.apache.juneau.commons.bean.BeanMap`, etc., plus `{@link …}` references in Javadoc), verify `cd juneau-core/juneau-commons && mvn clean compile` standalone passes, run `python3 scripts/test.py --full`. +- [ ] **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.
