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


The following commit(s) were added to refs/heads/master by this push:
     new feea646ad6 RestOpContext: drop persistent Builder reference; convert 6 
eager scalars to memoizers
feea646ad6 is described below

commit feea646ad6be53656292d6298392e61e2b819066
Author: James Bognar <[email protected]>
AuthorDate: Mon May 4 09:29:07 2026 -0400

    RestOpContext: drop persistent Builder reference; convert 6 eager scalars 
to memoizers
---
 .../java/org/apache/juneau/rest/RestOpContext.java | 86 ++++++++++++++--------
 todo/TODO.md                                       |  4 +-
 2 files changed, 56 insertions(+), 34 deletions(-)

diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOpContext.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOpContext.java
index 83624649c6..7ac687f5b2 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOpContext.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOpContext.java
@@ -311,7 +311,6 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                return 
BeanCreator.of(HttpPartSerializer.class).type(c).orElse(defaultSerializer);
        }
 
-       protected final Builder builder;
        protected final int hierarchyDepth;
        protected final Map<Class<?>,ResponseBeanMeta> responseBeanMetas = new 
ConcurrentHashMap<>();
        protected final Map<Class<?>,ResponsePartMeta> headerPartMetas = new 
ConcurrentHashMap<>();
@@ -323,13 +322,27 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
        protected final RestOpInvoker[] preCallMethods;
        protected final ResponseBeanMeta responseMeta;
 
-       /** Resolved once at construction from {@link Builder} annotation apply 
and context fallbacks (TODO-16). */
-       private final Charset resolvedDefaultCharset;
-       private final long resolvedMaxInput;
-       private final DebugEnablement resolvedDebugEnablement;
-       private final List<MediaType> resolvedSupportedAcceptTypes;
-       private final List<MediaType> resolvedSupportedContentTypes;
-       private final String resolvedHttpMethod;
+       // Sub-builder fields: annotation-applied copies captured from Builder 
before it is discarded.
+       // Non-null only when op-level annotations actually touched the 
sub-builder during construction;
+       // null means "no op annotations applied" and the memoizer falls 
through to the parent context value.
+       private final BeanContext.Builder beanContextBuilder;
+       private final EncoderSet.Builder encodersBuilder;
+       private final JsonSchemaGenerator.Builder jsonSchemaGeneratorBuilder;
+       private final ParserSet.Builder parsersBuilder;
+       private final HttpPartParser.Creator partParserCreator;
+       private final HttpPartSerializer.Creator partSerializerCreator;
+       private final SerializerSet.Builder serializersBuilder;
+
+       // The annotation work-list produced during construction (replaces 
builder.getApplied() references).
+       private final AnnotationWorkList appliedAnnotations;
+
+       // Phase 4: formerly eager scalars — now lazily resolved on first 
access.
+       private final Memoizer<Charset> defaultCharsetMemo = 
memoizer(this::findDefaultCharset);
+       private final Memoizer<Long> maxInputMemo = 
memoizer(this::findMaxInput);
+       private final Memoizer<DebugEnablement> debugEnablementMemo = 
memoizer(this::findDebugEnablement);
+       private final Memoizer<List<MediaType>> supportedAcceptTypesMemo = 
memoizer(this::findSupportedAcceptTypes);
+       private final Memoizer<List<MediaType>> supportedContentTypesMemo = 
memoizer(this::findSupportedContentTypes);
+       private final Memoizer<String> httpMethodMemo = 
memoizer(this::findHttpMethod);
 
        
//-----------------------------------------------------------------------------------------------------------------
        // Memoized allowlist fields
@@ -390,7 +403,7 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
        private final Memoizer<BeanContext> beanContextMemo = 
memoizer(this::findBeanContext);
 
        private BeanContext findBeanContext() {
-               return 
builder.getBeanContext().orElse(context.getBeanContext());
+               return beanContextBuilder != null ? beanContextBuilder.build() 
: context.getBeanContext();
        }
 
        private final Memoizer<CallLogger> callLoggerMemo = 
memoizer(this::findCallLogger);
@@ -415,9 +428,9 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
        private EncoderSet findEncoders() {
                var bs = context.getBeanStore();
                // Seed from class-level builder copy so 
EncoderSet.Builder.set(...)'s Inherit sentinel
-               // can splice in the inherited entries. (The OP-level 
EncoderSet.Builder is itself seeded
-               // from parent.encoders().copy() in createEncoders().)
-               var b = builder.encoders().copy();
+               // can splice in the inherited entries. If no op annotations 
touched encoders, fall through
+               // to a fresh copy of the parent builder (mirrors the former 
Builder.encoders() lazy-init).
+               var b = (encodersBuilder != null ? encodersBuilder : 
context.builder.encoders()).copy();
                getRestOpAnnotationsForProperty(PROPERTY_encoders).forEach(ai 
-> {
                        var c = ai.getClassArray("encoders", 
org.apache.juneau.encoders.Encoder.class).orElse(null);
                        if (nn(c) && c.length > 0)
@@ -433,7 +446,7 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
        private final Memoizer<JsonSchemaGenerator> jsonSchemaGeneratorMemo = 
memoizer(this::findJsonSchemaGenerator);
 
        private JsonSchemaGenerator findJsonSchemaGenerator() {
-               return 
builder.getJsonSchemaGenerator().orElse(context.getJsonSchemaGenerator());
+               return jsonSchemaGeneratorBuilder != null ? 
jsonSchemaGeneratorBuilder.build() : context.getJsonSchemaGenerator();
        }
 
        private final Memoizer<ParserSet> parsersMemo = 
memoizer(this::findParsers);
@@ -447,7 +460,7 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
         */
        private ParserSet findParsers() {
                var bs = context.getBeanStore();
-               var b = builder.parsers().copy();
+               var b = (parsersBuilder != null ? parsersBuilder : 
context.builder.parsers()).copy();
                getRestOpAnnotationsForProperty(PROPERTY_parsers).forEach(ai -> 
{
                        var c = ai.getClassArray("parsers", 
java.lang.Object.class).orElse(null);
                        if (nn(c) && c.length > 0)
@@ -463,13 +476,13 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
        private final Memoizer<HttpPartParser> partParserMemo = 
memoizer(this::findPartParser);
 
        private HttpPartParser findPartParser() {
-               return builder.getPartParser().orElse(context.getPartParser());
+               return partParserCreator != null ? partParserCreator.create() : 
context.getPartParser();
        }
 
        private final Memoizer<HttpPartSerializer> partSerializerMemo = 
memoizer(this::findPartSerializer);
 
        private HttpPartSerializer findPartSerializer() {
-               return 
builder.getPartSerializer().orElse(context.getPartSerializer());
+               return partSerializerCreator != null ? 
partSerializerCreator.create() : context.getPartSerializer();
        }
 
        private final Memoizer<SerializerSet> serializersMemo = 
memoizer(this::findSerializers);
@@ -483,7 +496,7 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
         */
        private SerializerSet findSerializers() {
                var bs = context.getBeanStore();
-               var b = builder.serializers().copy();
+               var b = (serializersBuilder != null ? serializersBuilder : 
context.builder.serializers()).copy();
                
getRestOpAnnotationsForProperty(PROPERTY_serializers).forEach(ai -> {
                        var c = ai.getClassArray("serializers", 
org.apache.juneau.serializer.Serializer.class).orElse(null);
                        if (nn(c) && c.length > 0)
@@ -1288,7 +1301,19 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                super(builder);
 
                try {
-                       this.builder = builder;
+                       // Capture the annotation work-list before the builder 
becomes eligible for GC.
+                       appliedAnnotations = builder.getApplied();
+
+                       // Capture sub-builder fields (raw field access — null 
means no op annotations touched that slot).
+                       // The memoizer findXxx() methods check for null and 
fall through to the parent context value.
+                       beanContextBuilder = builder.beanContext;
+                       encodersBuilder = builder.encoders;
+                       jsonSchemaGeneratorBuilder = 
builder.jsonSchemaGenerator;
+                       parsersBuilder = builder.parsers;
+                       partParserCreator = builder.partParser;
+                       partSerializerCreator = builder.partSerializer;
+                       serializersBuilder = builder.serializers;
+
                        context = builder.restContext;
                        method = builder.restMethod;
 
@@ -1298,7 +1323,7 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                        var bs = 
BasicBeanStore.of(context.getBootstrapBeanStore())
                                .addBean(RestOpContext.class, this)
                                .addBean(Method.class, method)
-                               .addBean(AnnotationWorkList.class, 
builder.getApplied());
+                               .addBean(AnnotationWorkList.class, 
appliedAnnotations);
                        // @formatter:on
                        bs.addBean(BasicBeanStore.class, bs);
 
@@ -1312,12 +1337,9 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                        bs.add(HttpPartSerializer.class, getPartSerializer());
                        bs.add(SerializerSet.class, getSerializers());
 
-                       resolvedDefaultCharset = findDefaultCharset();
-                       resolvedMaxInput = findMaxInput();
-                       resolvedDebugEnablement = findDebugEnablement();
-                       resolvedSupportedAcceptTypes = 
findSupportedAcceptTypes();
-                       resolvedSupportedContentTypes = 
findSupportedContentTypes();
-                       resolvedHttpMethod = findHttpMethod();
+                       // The 6 formerly-eager scalar fields are now memoized; 
no eagerness needed here.
+                       // Pre-warm httpMethod so it is in the memoizer cache 
for immediate use by compareTo/match.
+                       httpMethodMemo.get();
 
                        var pm = getPathMatchers();
                        bs.add(UrlPathMatcher[].class, pm);
@@ -1331,7 +1353,7 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                }
                hierarchyDepth = hierarchyDepthTemp;
 
-                       responseMeta = ResponseBeanMeta.create(mi, 
builder.getApplied());
+                       responseMeta = ResponseBeanMeta.create(mi, 
appliedAnnotations);
 
                        preCallMethods = 
context.getPreCallMethods().stream().map(x -> new RestOpInvoker(x, 
context.findRestOperationArgs(x, bs), 
context.getMethodExecStats(x))).toArray(RestOpInvoker[]::new);
                        postCallMethods = 
context.getPostCallMethods().stream().map(x -> new RestOpInvoker(x, 
context.findRestOperationArgs(x, bs), 
context.getMethodExecStats(x))).toArray(RestOpInvoker[]::new);
@@ -1441,7 +1463,7 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                "java:S112" // throws Exception intentional - 
callback/lifecycle method
        })
        public RestOpSession.Builder createSession(RestSession session) throws 
Exception {
-               return RestOpSession.create(this, 
session).logger(getCallLogger()).debug(resolvedDebugEnablement.isDebug(this, 
session.getRequest()));
+               return RestOpSession.create(this, 
session).logger(getCallLogger()).debug(debugEnablementMemo.get().isDebug(this, 
session.getRequest()));
        }
 
        @Override /* Overridden from Object */
@@ -1461,7 +1483,7 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
         *
         * @return The default charset.  Never <jk>null</jk>.
         */
-       public Charset getDefaultCharset() { return resolvedDefaultCharset; }
+       public Charset getDefaultCharset() { return defaultCharsetMemo.get(); }
 
        /**
         * Returns the default request attributes.
@@ -1510,7 +1532,7 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
         *
         * @return The HTTP method name.
         */
-       public String getHttpMethod() { return resolvedHttpMethod; }
+       public String getHttpMethod() { return httpMethodMemo.get(); }
 
        /**
         * Returns the underlying Java method that this context belongs to.
@@ -1531,7 +1553,7 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
         *
         * @return The max number of bytes to process in the input content.
         */
-       public long getMaxInput() { return resolvedMaxInput; }
+       public long getMaxInput() { return maxInputMemo.get(); }
 
        /**
         * Returns the parsers to use for this method.
@@ -1662,14 +1684,14 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
         *
         * @return An unmodifiable list.
         */
-       public List<MediaType> getSupportedAcceptTypes() { return 
resolvedSupportedAcceptTypes; }
+       public List<MediaType> getSupportedAcceptTypes() { return 
supportedAcceptTypesMemo.get(); }
 
        /**
         * Returns the list of supported content types.
         *
         * @return An unmodifiable list.
         */
-       public List<MediaType> getSupportedContentTypes() { return 
resolvedSupportedContentTypes; }
+       public List<MediaType> getSupportedContentTypes() { return 
supportedContentTypesMemo.get(); }
 
        @Override /* Overridden from Object */
        public int hashCode() {
diff --git a/todo/TODO.md b/todo/TODO.md
index d2161cb692..341a739c78 100644
--- a/todo/TODO.md
+++ b/todo/TODO.md
@@ -33,8 +33,6 @@
 
 - [TODO-18] Investigate possible useful features to add to juneau-rest-server.
 
-- [TODO-19] Remove encoders support from juneau-marshall and 
juneau-rest-server.
-
 - [TODO-20] Rethink how debugging works in RestServlet.  Can we come up with a 
simpler system?
 
 - [TODO-21] Rename and relocate bean/inject annotations: reduce confusion with 
Spring naming, align annotation vocabulary with what each actually does, and 
move resource/store contribution annotations into 
`org.apache.juneau.commons.inject`. See 
`todo/TODO-21-bean-annotations-inject-package.md`.
@@ -42,3 +40,5 @@
 - [TODO-23] New feature support in org.apache.juneau.commons.inject — roadmap 
for a simplified inject API (not a Spring replacement). See 
`todo/TODO-23-commons-inject-framework-roadmap.md`.
 
 - [TODO-24] JSR-330 alignment (no `jakarta.inject-api` dependency) + selective 
Spring-lite features for `commons.inject`. See 
`todo/TODO-24-jsr330-and-spring-lite-support.md`.
+
+- [TODO-30] Eliminate `RestOpContext.Builder` persistent reference: move 
annotation-apply results (7 sub-builders + `AnnotationWorkList`) to private 
final fields on `RestOpContext` so the `Builder` is GC-able after construction, 
and convert 6 eagerly-resolved scalar fields to memoizers. See 
`todo/TODO-30-eliminate-restopcontext-builder.md`.

Reply via email to