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 1d08c90aee RestOpContext: inline all findXxx() bodies into memoizer 
lambdas, rename Memo fields, add Javadoc
1d08c90aee is described below

commit 1d08c90aee994b025f6a09034c81c46b61cb5de3
Author: James Bognar <[email protected]>
AuthorDate: Mon May 4 10:32:45 2026 -0400

    RestOpContext: inline all findXxx() bodies into memoizer lambdas, rename 
Memo fields, add Javadoc
---
 .../java/org/apache/juneau/rest/RestOpContext.java | 686 ++++++++++-----------
 todo/TODO.md                                       |   2 -
 2 files changed, 311 insertions(+), 377 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 7ac687f5b2..d7f806147c 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
@@ -82,8 +82,7 @@ import jakarta.servlet.http.*;
  * </ul>
  */
 @SuppressWarnings({
-       "java:S115",  // Constants use UPPER_snakeCase convention (e.g., 
PROP_defaultRequestFormData)
-       "java:S6539"  // Legacy central operation context; decomposition is 
tracked separately from current TODO scope.
+       "java:S115"  // Constants use UPPER_snakeCase convention (e.g., 
PROP_defaultRequestFormData)
 })
 public class RestOpContext extends Context implements 
Comparable<RestOpContext> {
 
@@ -111,13 +110,6 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
 
        /**
         * Internal construction-time state holder.
-        *
-        * <p>
-        * Package-private since 9.5 (TODO-16 Phase D-3). Was previously {@code 
public static class Builder}; the external
-        * Builder surface is now deleted &mdash; {@code RestOpContext} is 
instantiated via the positional
-        * {@link #RestOpContext(java.lang.reflect.Method, RestContext) 2-arg 
ctor} or the protected 3-arg ctor used by
-        * subclasses needing a custom bean store. This class is an internal 
implementation detail that may be inlined or
-        * removed entirely in future phases without notice.
         */
        static class Builder extends Context.Builder {
 
@@ -323,7 +315,7 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
        protected final ResponseBeanMeta responseMeta;
 
        // 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;
+       // 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;
@@ -336,250 +328,317 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
        // 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);
+       /** The effective default {@link Charset} for this operation, resolved 
from op annotations, context, or env. */
+       private final Memoizer<Charset> defaultCharset = memoizer(() -> {
+               var v = findOpString(PROPERTY_defaultCharset);
+               if (v.isPresent())
+                       return Charset.forName(v.get());
+               if (isInherited(PROPERTY_defaultCharset)) {
+                       var rv = 
restContext().mergeReplacedStringAttribute(PROPERTY_defaultCharset, null);
+                       if (rv != null && !rv.isEmpty())
+                               return Charset.forName(rv);
+               }
+               return envDefaultRestCharset();
+       });
+
+       /** The effective max-input byte limit for this operation. */
+       private final Memoizer<Long> maxInput = memoizer(() -> {
+               var v = findOpString(PROPERTY_maxInput);
+               if (v.isPresent())
+                       return parseLongWithSuffix(v.get());
+               if (isInherited(PROPERTY_maxInput)) {
+                       var rv = 
restContext().mergeReplacedStringAttribute(PROPERTY_maxInput, null);
+                       if (rv != null && !rv.isEmpty())
+                               return parseLongWithSuffix(rv);
+               }
+               return envDefaultRestMaxInput();
+       });
+
+       /** The effective {@link DebugEnablement} for this operation. */
+       private final Memoizer<DebugEnablement> debugEnablement = memoizer(() 
-> {
+               var v = findOpString(PROPERTY_debug);
+               if (v.isPresent())
+                       return 
DebugEnablement.create(restContext().getBeanStore()).enable(Enablement.fromString(v.get()),
 "*").build();
+               if (isInherited(PROPERTY_debug))
+                       return restContext().getDebugEnablement();
+               return 
DebugEnablement.create(restContext().getBeanStore()).build();
+       });
+
+       /**
+        * The supported response accept types for this operation.
+        *
+        * <p>
+        * Walks op-level {@code @RestOp}-group annotations for {@code 
produces} (and, when
+        * {@code noInherit} does not block it, the class-level {@code 
@Rest(produces)} hierarchy).
+        * Falls back to the supported media types of the operation's {@link 
SerializerSet}.
+        */
+       private final Memoizer<List<MediaType>> supportedAcceptTypes = 
memoizer(() -> {
+               var result = collectAnnotationMediaTypes(PROPERTY_produces);
+               if (result.isEmpty())
+                       return u(getSerializers().getSupportedMediaTypes());
+               return u(result);
+       });
+
+       /**
+        * The supported request content types for this operation.
+        *
+        * <p>
+        * Walks op-level {@code @RestOp}-group annotations for {@code 
consumes} (and, when
+        * {@code noInherit} does not block it, the class-level {@code 
@Rest(consumes)} hierarchy).
+        * Falls back to the supported content types of the operation's {@link 
ParserSet}.
+        */
+       private final Memoizer<List<MediaType>> supportedContentTypes = 
memoizer(() -> {
+               var result = collectAnnotationMediaTypes(PROPERTY_consumes);
+               if (result.isEmpty())
+                       return u(getParsers().getSupportedMediaTypes());
+               return u(result);
+       });
+
+       /**
+        * The HTTP method string for this operation (e.g. {@code "GET"}, 
{@code "*"}).
+        *
+        * <p>
+        * Walks {@code @RestOp}-group annotations child-to-parent; 
verb-specific annotations
+        * ({@link RestGet}, etc.) imply their fixed verb. Falls back to
+        * {@link HttpUtils#detectHttpMethod} when no annotation declares a 
verb.
+        */
+       @SuppressWarnings("java:S3776")
+       private final Memoizer<String> httpMethod = memoizer(() -> {
+               var vr = restContext().getVarResolver();
+               for (var ai : getRestOpAnnotations()) {
+                       var v = httpMethodFromAnnotation(ai.inner(), vr);
+                       if (v != null && !v.isEmpty())
+                               return normalizeHttpMethod(v);
+               }
+               return normalizeHttpMethod(HttpUtils.detectHttpMethod(method(), 
true, "GET"));
+       });
+
+       private RestContext restContext() { return context; }
+       private Method method() { return method; }
+       private BeanContext.Builder beanContextBuilder() { return 
beanContextBuilder; }
+       private EncoderSet.Builder encodersBuilder() { return encodersBuilder; }
+       private JsonSchemaGenerator.Builder jsonSchemaGeneratorBuilder() { 
return jsonSchemaGeneratorBuilder; }
+       private ParserSet.Builder parsersBuilder() { return parsersBuilder; }
+       private HttpPartParser.Creator partParserCreator() { return 
partParserCreator; }
+       private HttpPartSerializer.Creator partSerializerCreator() { return 
partSerializerCreator; }
+       private SerializerSet.Builder serializersBuilder() { return 
serializersBuilder; }
 
        
//-----------------------------------------------------------------------------------------------------------------
-       // Memoized allowlist fields
+       // Memoized fields
        
//-----------------------------------------------------------------------------------------------------------------
 
-       /** Memoized all {@link RestOp}-group annotations on this method, 
child-to-parent order. */
-       private final Memoizer<List<AnnotationInfo<?>>> restOpAnnotations = 
memoizer(this::findRestOpAnnotations);
-
-       private List<AnnotationInfo<?>> findRestOpAnnotations() {
-               // Use the same MethodInfo as {@link Builder}'s annotation 
traversal (resource class + method), not
-               // {@link MethodInfo#of(Method)} alone, so method-level {@code 
@RestGet}/@RestOp metadata (e.g. noInherit)
-               // resolves consistently when the implementation class differs 
from the method's declaring class.
-               var methodInfo = MethodInfo.of(context.getResourceClass(), 
method).accessible();
-               return context.getAnnotationProvider().find(methodInfo, SELF, 
MATCHING_METHODS).stream()
+       /**
+        * All {@link RestOp}-group annotations on this method, in 
child-to-parent order.
+        *
+        * <p>
+        * Uses the same {@link MethodInfo} as the annotation traversal in the 
{@link Builder}, so
+        * method-level metadata (e.g. {@code noInherit}) resolves consistently 
when the implementation
+        * class differs from the method's declaring class.
+        */
+       private final Memoizer<List<AnnotationInfo<?>>> restOpAnnotations = 
memoizer(() -> {
+               var methodInfo = 
MethodInfo.of(restContext().getResourceClass(), method()).accessible();
+               return restContext().getAnnotationProvider().find(methodInfo, 
SELF, MATCHING_METHODS).stream()
                        .filter(ai -> ai.isInGroup(RestOp.class))
                        .toList();
-       }
-
-       /** Memoized aggregated {@code noInherit} keys from all RestOp-group 
annotations on this operation. */
-       private final Memoizer<SortedSet<String>> noInheritOp = 
memoizer(this::findNoInheritOp);
+       });
 
-       private SortedSet<String> findNoInheritOp() {
+       /** Aggregated {@code noInherit} keys from all RestOp-group annotations 
on this operation. */
+       private final Memoizer<SortedSet<String>> noInheritOp = memoizer(() -> {
                var l = getRestOpAnnotations().stream()
                        .map(ai -> 
ai.getStringArray("noInherit").orElse(StringUtils.EMPTY_STRING_ARRAY))
                        .flatMap(this::resolveCdl)
                        .toList();
                return 
Collections.unmodifiableSortedSet(treeSet(String.CASE_INSENSITIVE_ORDER, l));
-       }
+       });
 
-       /** Memoized effective allowed parser option keys for this operation. */
-       private final Memoizer<SortedSet<String>> allowedParserOptions = 
memoizer(this::findAllowedParserOptions);
-
-       private SortedSet<String> findAllowedParserOptions() {
+       /** Effective allowed parser session-option keys for this operation. */
+       private final Memoizer<SortedSet<String>> allowedParserOptions = 
memoizer(() -> {
                var l = new ArrayList<String>();
                var p = PROPERTY_allowedParserOptions;
                if (isInherited(p))
-                       l.addAll(context.getAllowedParserOptions());
+                       l.addAll(restContext().getAllowedParserOptions());
                getRestOpAnnotations().stream()
                        .flatMap(ai -> 
resolveCdl(ai.getStringArray(p).orElse(new String[0])))
                        .forEach(l::add);
                return 
Collections.unmodifiableSortedSet(treeSet(String.CASE_INSENSITIVE_ORDER, 
removeNegations(l)));
-       }
-
-       /** Memoized effective allowed serializer option keys for this 
operation. */
-       private final Memoizer<SortedSet<String>> allowedSerializerOptions = 
memoizer(this::findAllowedSerializerOptions);
+       });
 
-       private SortedSet<String> findAllowedSerializerOptions() {
+       /** Effective allowed serializer session-option keys for this 
operation. */
+       private final Memoizer<SortedSet<String>> allowedSerializerOptions = 
memoizer(() -> {
                var l = new ArrayList<String>();
                var p = PROPERTY_allowedSerializerOptions;
                if (isInherited(p))
-                       l.addAll(context.getAllowedSerializerOptions());
+                       l.addAll(restContext().getAllowedSerializerOptions());
                getRestOpAnnotations().stream()
                        .flatMap(ai -> 
resolveCdl(ai.getStringArray(p).orElse(new String[0])))
                        .forEach(l::add);
                return 
Collections.unmodifiableSortedSet(treeSet(String.CASE_INSENSITIVE_ORDER, 
removeNegations(l)));
-       }
-
-       private final Memoizer<BeanContext> beanContextMemo = 
memoizer(this::findBeanContext);
+       });
 
-       private BeanContext findBeanContext() {
-               return beanContextBuilder != null ? beanContextBuilder.build() 
: context.getBeanContext();
-       }
+       /** The {@link BeanContext} for this operation (op-level annotations 
applied on top of the parent context). */
+       private final Memoizer<BeanContext> beanContext = memoizer(() ->
+               beanContextBuilder() != null ? beanContextBuilder().build() : 
restContext().getBeanContext()
+       );
 
-       private final Memoizer<CallLogger> callLoggerMemo = 
memoizer(this::findCallLogger);
-
-       private CallLogger findCallLogger() {
-               return context.getCallLogger();
-       }
-
-       private final Memoizer<EncoderSet> encodersMemo = 
memoizer(this::findEncoders);
+       /** The call logger for this operation (delegated to the parent {@link 
RestContext}). */
+       private final Memoizer<CallLogger> callLogger = memoizer(() -> 
restContext().getCallLogger());
 
        /**
-        * Resolves the encoder group for this operation. Walks the {@code 
@RestOp(encoders)} chain
-        * (parent-to-child); each non-empty {@code encoders()} array REPLACES 
the inherited set
-        * (with {@link Inherit} as a sentinel that re-injects the prior set's 
entries at the
-        * specified position — matches the legacy {@code 
EncoderSet.Builder.set(...)} semantics).
+        * The encoder group for this operation.
         *
         * <p>
-        * If no op annotation declares encoders, falls through to the 
class-level
-        * {@link RestContext#getEncoders()}. An {@code @RestInject EncoderSet} 
bean (matching this
-        * operation's method scope) REPLACES the result entirely.
+        * Walks the {@code @RestOp(encoders)} chain (parent-to-child); each 
non-empty {@code encoders()}
+        * array REPLACES the inherited set (with {@link Inherit} as a sentinel 
that re-injects the prior
+        * set's entries at the specified position — matches the legacy {@code 
EncoderSet.Builder.set(...)}
+        * semantics). Falls through to the class-level {@link 
RestContext#getEncoders()} when no op
+        * annotation declares encoders. An {@code @RestInject EncoderSet} bean 
(matching this operation's
+        * method scope) REPLACES the result entirely.
         */
-       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. 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();
+       private final Memoizer<EncoderSet> encoders = memoizer(() -> {
+               var bs = restContext().getBeanStore();
+               var b = (encodersBuilder() != null ? encodersBuilder() : 
restContext().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)
                                b.set(c);
                });
                var v = Value.of(b.build());
-               new BeanCreateMethodFinder<>(EncoderSet.class, 
context.getResource(), bs)
+               new BeanCreateMethodFinder<>(EncoderSet.class, 
restContext().getResource(), bs)
                        .find(this::matchesInjectScope)
                        .run(v::set);
                return v.get();
-       }
-
-       private final Memoizer<JsonSchemaGenerator> jsonSchemaGeneratorMemo = 
memoizer(this::findJsonSchemaGenerator);
+       });
 
-       private JsonSchemaGenerator findJsonSchemaGenerator() {
-               return jsonSchemaGeneratorBuilder != null ? 
jsonSchemaGeneratorBuilder.build() : context.getJsonSchemaGenerator();
-       }
-
-       private final Memoizer<ParserSet> parsersMemo = 
memoizer(this::findParsers);
+       /** The JSON-Schema generator for this operation (op-level annotations 
applied on top of the parent). */
+       private final Memoizer<JsonSchemaGenerator> jsonSchemaGenerator = 
memoizer(() ->
+               jsonSchemaGeneratorBuilder() != null ? 
jsonSchemaGeneratorBuilder().build() : restContext().getJsonSchemaGenerator()
+       );
 
        /**
-        * Resolves the parser group for this operation. Walks the {@code 
@RestOp(parsers)} chain
-        * (parent-to-child); the most-derived non-empty {@code parsers()} 
array REPLACES the entire
-        * inherited set. If no op annotation declares parsers, falls through 
to the class-level
-        * {@link RestContext#getParsers()}. An {@code @RestInject ParserSet} 
bean (matching this
-        * operation's method scope) REPLACES the result.
+        * The parser group for this operation.
+        *
+        * <p>
+        * Walks the {@code @RestOp(parsers)} chain (parent-to-child); the 
most-derived non-empty
+        * {@code parsers()} array REPLACES the entire inherited set. Falls 
through to the class-level
+        * {@link RestContext#getParsers()} when no op annotation declares 
parsers. An
+        * {@code @RestInject ParserSet} bean (matching this operation's method 
scope) REPLACES the result.
         */
-       private ParserSet findParsers() {
-               var bs = context.getBeanStore();
-               var b = (parsersBuilder != null ? parsersBuilder : 
context.builder.parsers()).copy();
+       private final Memoizer<ParserSet> parsers = memoizer(() -> {
+               var bs = restContext().getBeanStore();
+               var b = (parsersBuilder() != null ? parsersBuilder() : 
restContext().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)
                                b.set(c);
                });
                var result = Value.of(b.build());
-               new BeanCreateMethodFinder<>(ParserSet.class, 
context.getResource(), bs)
+               new BeanCreateMethodFinder<>(ParserSet.class, 
restContext().getResource(), bs)
                        .find(this::matchesInjectScope)
                        .run(result::set);
                return result.get();
-       }
-
-       private final Memoizer<HttpPartParser> partParserMemo = 
memoizer(this::findPartParser);
-
-       private HttpPartParser findPartParser() {
-               return partParserCreator != null ? partParserCreator.create() : 
context.getPartParser();
-       }
+       });
 
-       private final Memoizer<HttpPartSerializer> partSerializerMemo = 
memoizer(this::findPartSerializer);
+       /** The HTTP part parser for this operation (op-level creator applied 
on top of the parent). */
+       private final Memoizer<HttpPartParser> partParser = memoizer(() ->
+               partParserCreator() != null ? partParserCreator().create() : 
restContext().getPartParser()
+       );
 
-       private HttpPartSerializer findPartSerializer() {
-               return partSerializerCreator != null ? 
partSerializerCreator.create() : context.getPartSerializer();
-       }
-
-       private final Memoizer<SerializerSet> serializersMemo = 
memoizer(this::findSerializers);
+       /** The HTTP part serializer for this operation (op-level creator 
applied on top of the parent). */
+       private final Memoizer<HttpPartSerializer> partSerializer = memoizer(() 
->
+               partSerializerCreator() != null ? 
partSerializerCreator().create() : restContext().getPartSerializer()
+       );
 
        /**
-        * Resolves the serializer group for this operation. Walks the {@code 
@RestOp(serializers)} chain
-        * (parent-to-child); the most-derived non-empty {@code serializers()} 
array REPLACES the entire
-        * inherited set. If no op annotation declares serializers, falls 
through to the class-level
-        * {@link RestContext#getSerializers()}. An {@code @RestInject 
SerializerSet} bean (matching this
-        * operation's method scope) REPLACES the result.
+        * The serializer group for this operation.
+        *
+        * <p>
+        * Walks the {@code @RestOp(serializers)} chain (parent-to-child); the 
most-derived non-empty
+        * {@code serializers()} array REPLACES the entire inherited set. Falls 
through to the class-level
+        * {@link RestContext#getSerializers()} when no op annotation declares 
serializers. An
+        * {@code @RestInject SerializerSet} bean (matching this operation's 
method scope) REPLACES the result.
         */
-       private SerializerSet findSerializers() {
-               var bs = context.getBeanStore();
-               var b = (serializersBuilder != null ? serializersBuilder : 
context.builder.serializers()).copy();
+       private final Memoizer<SerializerSet> serializers = memoizer(() -> {
+               var bs = restContext().getBeanStore();
+               var b = (serializersBuilder() != null ? serializersBuilder() : 
restContext().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)
                                b.set(c);
                });
                var result = Value.of(b.build());
-               new BeanCreateMethodFinder<>(SerializerSet.class, 
context.getResource(), bs)
+               new BeanCreateMethodFinder<>(SerializerSet.class, 
restContext().getResource(), bs)
                        .find(this::matchesInjectScope)
                        .run(result::set);
                return result.get();
-       }
-
-       private final Memoizer<NamedAttributeMap> defaultRequestAttributesMemo 
= memoizer(this::findDefaultRequestAttributes);
+       });
 
        /**
-        * Resolves the default request attribute map for this operation. 
Starts with the class-level
-        * value (which already incorporates {@code 
@Rest(defaultRequestAttributes)}), then walks the
-        * {@code @RestOp}/verb chain (parent-to-child) and adds each entry — 
{@link NamedAttributeMap#add}
-        * uses put-semantics so child entries override parent entries by name.
+        * The default request attribute map for this operation.
         *
         * <p>
-        * An {@code @RestInject(name="defaultRequestAttributes") 
NamedAttributeMap} bean (matching this
+        * Starts with the class-level value (which already incorporates
+        * {@code @Rest(defaultRequestAttributes)}), then walks the {@code 
@RestOp}/verb chain
+        * (parent-to-child) and adds each entry — {@link 
NamedAttributeMap#add} uses put-semantics so
+        * child entries override parent entries by name. An
+        * {@code @RestInject(name="defaultRequestAttributes") 
NamedAttributeMap} bean (matching this
         * operation's method scope) REPLACES the entire result.
         */
-       private NamedAttributeMap findDefaultRequestAttributes() {
-               var v = Value.of(context.getDefaultRequestAttributes().copy());
+       private final Memoizer<NamedAttributeMap> defaultRequestAttributes = 
memoizer(() -> {
+               var v = 
Value.of(restContext().getDefaultRequestAttributes().copy());
                
getRestOpAnnotationsForProperty(PROPERTY_defaultRequestAttributes).forEach(ai 
-> {
                        for (var s : 
ai.getStringArray(PROPERTY_defaultRequestAttributes).orElse(EMPTY_STRING_ARRAY))
                                v.get().add(BasicNamedAttribute.ofPair(s));
                });
-               new BeanCreateMethodFinder<>(NamedAttributeMap.class, 
context.getResource(), context.getBeanStore())
+               new BeanCreateMethodFinder<>(NamedAttributeMap.class, 
restContext().getResource(), restContext().getBeanStore())
                        .find(x -> matchesInjectScope(x, 
PROPERTY_defaultRequestAttributes))
                        .run(v::set);
                return v.get();
-       }
-
-       private final Memoizer<PartList> defaultRequestFormDataMemo = 
memoizer(this::findDefaultRequestFormData);
+       });
 
        /**
-        * Resolves the default request form-data parts for this operation. 
Walks the {@code @RestOp}/verb
-        * chain (parent-to-child); each {@code defaultRequestFormData} entry 
is applied with
-        * {@link PartList#setDefault} (first-in-chain wins per name). 
Method-parameter
-        * {@link FormData @FormData} annotations with a {@link 
Schema#default_()}/{@link Schema#df()}
-        * default are folded in last (also {@code setDefault} = first wins).
+        * The default request form-data parts for this operation.
         *
         * <p>
-        * An {@code @RestInject(name="defaultRequestFormData") PartList} bean 
(matching this operation's
-        * method scope) REPLACES the entire result.
+        * Walks the {@code @RestOp}/verb chain (parent-to-child); each {@code 
defaultRequestFormData}
+        * entry is applied with {@link PartList#setDefault} (first-in-chain 
wins per name).
+        * Method-parameter {@link FormData @FormData} annotations with a
+        * {@link Schema#default_()}/{@link Schema#df()} default are folded in 
last (also
+        * {@code setDefault} = first wins). An {@code 
@RestInject(name="defaultRequestFormData") PartList}
+        * bean (matching this operation's method scope) REPLACES the entire 
result.
         */
-       private PartList findDefaultRequestFormData() {
+       private final Memoizer<PartList> defaultRequestFormData = memoizer(() 
-> {
                var v = Value.of(PartList.create());
                
getRestOpAnnotationsForProperty(PROPERTY_defaultRequestFormData).forEach(ai -> {
                        for (var s : 
ai.getStringArray(PROPERTY_defaultRequestFormData).orElse(EMPTY_STRING_ARRAY))
                                v.get().setDefault(basicPart(s));
                });
                applyParameterFormData(v.get());
-               new BeanCreateMethodFinder<>(PartList.class, 
context.getResource(), context.getBeanStore())
+               new BeanCreateMethodFinder<>(PartList.class, 
restContext().getResource(), restContext().getBeanStore())
                        .find(x -> matchesInjectScope(x, 
PROPERTY_defaultRequestFormData))
                        .run(v::set);
                return v.get();
-       }
-
-       private final Memoizer<HeaderList> defaultRequestHeadersMemo = 
memoizer(this::findDefaultRequestHeaders);
+       });
 
        /**
-        * Resolves the default request headers for this operation. Starts with 
the class-level value
-        * (which already incorporates {@code 
@Rest(defaultRequestHeaders|defaultAccept|defaultContentType)}),
-        * then walks the {@code @RestOp}/verb chain (parent-to-child); each 
annotation's
-        * {@code defaultRequestHeaders} entries plus its {@code defaultAccept} 
/ {@code defaultContentType}
-        * (folded into {@code Accept} / {@code Content-Type} headers) are 
applied with
-        * {@link HeaderList#setDefault} (first-in-chain wins per name — 
class-level beats op-level beats
-        * later op-level entries).
+        * The default request headers for this operation.
         *
         * <p>
-        * Method-parameter {@link Header @Header} annotations with a {@link 
Schema#default_()}/{@link Schema#df()}
-        * default are folded in last via {@link HeaderList#set} (overrides any 
prior entry with the same name).
+        * Starts with the class-level value (which already incorporates
+        * {@code 
@Rest(defaultRequestHeaders|defaultAccept|defaultContentType)}), then walks the
+        * {@code @RestOp}/verb chain (parent-to-child); each annotation's 
{@code defaultRequestHeaders}
+        * entries plus its {@code defaultAccept} / {@code defaultContentType} 
(folded into
+        * {@code Accept} / {@code Content-Type} headers) are applied with 
{@link HeaderList#setDefault}
+        * (first-in-chain wins per name — class-level beats op-level beats 
later op-level entries).
         *
         * <p>
-        * An {@code @RestInject(name="defaultRequestHeaders") HeaderList} bean 
(matching this operation's
+        * Method-parameter {@link Header @Header} annotations with a
+        * {@link Schema#default_()}/{@link Schema#df()} default are folded in 
last via
+        * {@link HeaderList#set} (overrides any prior entry with the same 
name). An
+        * {@code @RestInject(name="defaultRequestHeaders") HeaderList} bean 
(matching this operation's
         * method scope) REPLACES the entire result.
         */
-       private HeaderList findDefaultRequestHeaders() {
-               var v = Value.of(context.getDefaultRequestHeaders().copy());
+       private final Memoizer<HeaderList> defaultRequestHeaders = memoizer(() 
-> {
+               var v = 
Value.of(restContext().getDefaultRequestHeaders().copy());
                
getRestOpAnnotationsForProperty(PROPERTY_defaultRequestHeaders).forEach(ai -> {
                        for (var s : 
ai.getStringArray(PROPERTY_defaultRequestHeaders).orElse(EMPTY_STRING_ARRAY))
                                v.get().setDefault(stringHeader(s));
@@ -587,66 +646,63 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                        ai.getString(PROPERTY_defaultContentType).filter(s -> 
!s.isEmpty()).ifPresent(s -> v.get().setDefault(contentType(s)));
                });
                applyParameterHeaders(v.get());
-               new BeanCreateMethodFinder<>(HeaderList.class, 
context.getResource(), context.getBeanStore())
+               new BeanCreateMethodFinder<>(HeaderList.class, 
restContext().getResource(), restContext().getBeanStore())
                        .find(x -> matchesInjectScope(x, 
PROPERTY_defaultRequestHeaders))
                        .run(v::set);
                return v.get();
-       }
-
-       private final Memoizer<PartList> defaultRequestQueryDataMemo = 
memoizer(this::findDefaultRequestQueryData);
+       });
 
        /**
-        * Resolves the default request query-data parts for this operation. 
Walks the {@code @RestOp}/verb
-        * chain (parent-to-child); each {@code defaultRequestQueryData} entry 
is applied with
-        * {@link PartList#setDefault} (first-in-chain wins per name). 
Method-parameter
-        * {@link Query @Query} annotations with a {@link 
Schema#default_()}/{@link Schema#df()} default
-        * are folded in last (also {@code setDefault} = first wins).
+        * The default request query-data parts for this operation.
         *
         * <p>
-        * An {@code @RestInject(name="defaultRequestQueryData") PartList} bean 
(matching this operation's
-        * method scope) REPLACES the entire result.
+        * Walks the {@code @RestOp}/verb chain (parent-to-child); each {@code 
defaultRequestQueryData}
+        * entry is applied with {@link PartList#setDefault} (first-in-chain 
wins per name).
+        * Method-parameter {@link Query @Query} annotations with a
+        * {@link Schema#default_()}/{@link Schema#df()} default are folded in 
last (also
+        * {@code setDefault} = first wins). An {@code 
@RestInject(name="defaultRequestQueryData") PartList}
+        * bean (matching this operation's method scope) REPLACES the entire 
result.
         */
-       private PartList findDefaultRequestQueryData() {
+       private final Memoizer<PartList> defaultRequestQueryData = memoizer(() 
-> {
                var v = Value.of(PartList.create());
                
getRestOpAnnotationsForProperty(PROPERTY_defaultRequestQueryData).forEach(ai -> 
{
                        for (var s : 
ai.getStringArray(PROPERTY_defaultRequestQueryData).orElse(EMPTY_STRING_ARRAY))
                                v.get().setDefault(basicPart(s));
                });
                applyParameterQueryData(v.get());
-               new BeanCreateMethodFinder<>(PartList.class, 
context.getResource(), context.getBeanStore())
+               new BeanCreateMethodFinder<>(PartList.class, 
restContext().getResource(), restContext().getBeanStore())
                        .find(x -> matchesInjectScope(x, 
PROPERTY_defaultRequestQueryData))
                        .run(v::set);
                return v.get();
-       }
-
-       private final Memoizer<HeaderList> defaultResponseHeadersMemo = 
memoizer(this::findDefaultResponseHeaders);
+       });
 
        /**
-        * Resolves the default response headers for this operation. Starts 
with the class-level value
-        * (which already incorporates {@code @Rest(defaultResponseHeaders)}), 
then walks the
-        * {@code @RestOp}/verb chain (parent-to-child); each annotation's 
{@code defaultResponseHeaders}
-        * entries are applied with {@link HeaderList#setDefault} 
(first-in-chain wins per name).
+        * The default response headers for this operation.
         *
         * <p>
-        * An {@code @RestInject(name="defaultResponseHeaders") HeaderList} 
bean (matching this operation's
+        * Starts with the class-level value (which already incorporates
+        * {@code @Rest(defaultResponseHeaders)}), then walks the {@code 
@RestOp}/verb chain
+        * (parent-to-child); each annotation's {@code defaultResponseHeaders} 
entries are applied with
+        * {@link HeaderList#setDefault} (first-in-chain wins per name). An
+        * {@code @RestInject(name="defaultResponseHeaders") HeaderList} bean 
(matching this operation's
         * method scope) REPLACES the entire result.
         */
-       private HeaderList findDefaultResponseHeaders() {
-               var v = Value.of(context.getDefaultResponseHeaders().copy());
+       private final Memoizer<HeaderList> defaultResponseHeaders = memoizer(() 
-> {
+               var v = 
Value.of(restContext().getDefaultResponseHeaders().copy());
                
getRestOpAnnotationsForProperty(PROPERTY_defaultResponseHeaders).forEach(ai -> {
                        for (var s : 
ai.getStringArray(PROPERTY_defaultResponseHeaders).orElse(EMPTY_STRING_ARRAY))
                                v.get().setDefault(stringHeader(s));
                });
-               new BeanCreateMethodFinder<>(HeaderList.class, 
context.getResource(), context.getBeanStore())
+               new BeanCreateMethodFinder<>(HeaderList.class, 
restContext().getResource(), restContext().getBeanStore())
                        .find(x -> matchesInjectScope(x, 
PROPERTY_defaultResponseHeaders))
                        .run(v::set);
                return v.get();
-       }
+       });
 
        /**
         * Folds method-parameter {@link Header @Header} annotations (with a 
{@link Schema#default_()} /
         * {@link Schema#df()} default) into the supplied {@link HeaderList} 
using {@link HeaderList#set}
-        * (overrides). Used by {@link #findDefaultRequestHeaders()}.
+        * (overrides). Used by the {@link #defaultRequestHeaders} memoizer.
         */
        private void applyParameterHeaders(HeaderList list) {
                processParameterDefaults((paramAnn, def) -> {
@@ -663,7 +719,7 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
        /**
         * Folds method-parameter {@link Query @Query} annotations (with a 
{@link Schema#default_()} /
         * {@link Schema#df()} default) into the supplied {@link PartList} 
using {@link PartList#setDefault}
-        * (first wins). Used by {@link #findDefaultRequestQueryData()}.
+        * (first wins). Used by the {@link #defaultRequestQueryData} memoizer.
         */
        private void applyParameterQueryData(PartList list) {
                processParameterDefaults((paramAnn, def) -> {
@@ -680,7 +736,7 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
        /**
         * Folds method-parameter {@link FormData @FormData} annotations (with 
a {@link Schema#default_()} /
         * {@link Schema#df()} default) into the supplied {@link PartList} 
using {@link PartList#setDefault}
-        * (first wins). Used by {@link #findDefaultRequestFormData()}.
+        * (first wins). Used by the {@link #defaultRequestFormData} memoizer.
         */
        private void applyParameterFormData(PartList list) {
                processParameterDefaults((paramAnn, def) -> {
@@ -726,28 +782,22 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                return (Class<? extends T>[])new Class<?>[] { value };
        }
 
-       private final Memoizer<RestConverter[]> convertersMemo = 
memoizer(this::findConverters);
-
        /**
-        * Resolves the response-converter list for this operation by walking 
the {@code @Rest(converters)}
-        * class chain (parent-to-child) followed by the {@code 
@RestOp(converters)} method chain
-        * (parent-to-child).
-        *
-        * <p>
-        * Op-level {@code noInherit={"converters"}} cuts off the class-chain 
contribution; an op-level
-        * {@code noInherit} on a parent annotation in the op chain cuts off 
the rest of the op chain too
-        * (mirrors the existing {@code allowed*Options} semantics).
+        * The response-converter array for this operation.
         *
         * <p>
-        * An {@code @RestInject RestConverterList} bean (either as a 
name-anonymous bean in the bean store or
-        * as a {@code @RestInject} method whose {@code methodScope} matches 
this operation's method name)
-        * REPLACES the entire annotation-derived list — see TODO-16 Decision 
#1 (Phase D-1 lock-in).
+        * Walks the {@code @Rest(converters)} class chain (parent-to-child) 
followed by the
+        * {@code @RestOp(converters)} method chain (parent-to-child). Op-level
+        * {@code noInherit={"converters"}} cuts off the class-chain 
contribution. An
+        * {@code @RestInject RestConverterList} bean (either as a 
name-anonymous bean in the bean store
+        * or as a {@code @RestInject} method whose {@code methodScope} matches 
this operation's method
+        * name) REPLACES the entire annotation-derived list — see TODO-16 
Decision #1 (Phase D-1 lock-in).
         */
-       private RestConverter[] findConverters() {
-               var bs = context.getBeanStore();
+       private final Memoizer<RestConverter[]> converters = memoizer(() -> {
+               var bs = restContext().getBeanStore();
                var v = Value.of(RestConverterList.create(bs));
                if (isInherited(PROPERTY_converters))
-                       
context.getRestAnnotationsForProperty(PROPERTY_converters)
+                       
restContext().getRestAnnotationsForProperty(PROPERTY_converters)
                                .forEach(ai -> 
v.get().append(ai.inner().converters()));
                getRestOpAnnotationsForProperty(PROPERTY_converters)
                        .forEach(ai -> ai.getClassArray("converters", 
RestConverter.class).ifPresent(classes -> {
@@ -755,11 +805,11 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                                        v.get().append(classArray(c));
                        }));
                bs.getBean(RestConverterList.class).ifPresent(x -> 
v.get().impl(x));
-               new BeanCreateMethodFinder<>(RestConverterList.class, 
context.getResource(), bs)
+               new BeanCreateMethodFinder<>(RestConverterList.class, 
restContext().getResource(), bs)
                        .find(this::matchesInjectScope)
                        .run(x -> v.get().impl(x));
                return v.get().build().asArray();
-       }
+       });
 
        /**
         * Returns {@code true} if the given method has a {@code @RestInject} 
annotation whose
@@ -804,12 +854,10 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                return false;
        }
 
-       private final Memoizer<RestGuard[]> guardsMemo = 
memoizer(this::findGuards);
-
        /**
-        * Resolves the request-guard list for this operation. <b>Cross-bucket 
memoizer</b> — folds three
-        * annotation attributes ({@code guards}, {@code roleGuard}, {@code 
rolesDeclared}) into a single
-        * effective {@link RestGuardList}.
+        * The request-guard array for this operation. <b>Cross-bucket 
memoizer</b> — folds three
+        * annotation attributes ({@code guards}, {@code roleGuard}, {@code 
rolesDeclared}) into a
+        * single effective {@link RestGuardList}.
         *
         * <p>
         * Walk order: class {@code @Rest} chain (parent-to-child, gated by 
op-level
@@ -826,8 +874,8 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
         * An {@code @RestInject RestGuardList} bean (via the bean store or an 
{@code @RestInject} method
         * with matching {@code methodScope}) REPLACES the entire 
annotation-derived list (Decision #1).
         */
-       private RestGuard[] findGuards() {
-               var bs = context.getBeanStore();
+       private final Memoizer<RestGuard[]> guards = memoizer(() -> {
+               var bs = restContext().getBeanStore();
                var v = Value.of(RestGuardList.create(bs));
                var rolesDeclaredSet = new java.util.LinkedHashSet<String>();
                var roleGuardStrs = new ArrayList<String>();
@@ -844,7 +892,7 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                };
 
                if (isInherited(PROPERTY_guards))
-                       
context.getRestAnnotationsForProperty(PROPERTY_guards).forEach(walk::accept);
+                       
restContext().getRestAnnotationsForProperty(PROPERTY_guards).forEach(walk::accept);
                getRestOpAnnotationsForProperty(PROPERTY_guards).forEach(walk);
 
                // When no @Rest/@RestOp(rolesDeclared) is set, pass null so 
RoleBasedRestGuard
@@ -860,31 +908,27 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                }
 
                bs.getBean(RestGuardList.class).ifPresent(x -> v.get().impl(x));
-               new BeanCreateMethodFinder<>(RestGuardList.class, 
context.getResource(), bs)
+               new BeanCreateMethodFinder<>(RestGuardList.class, 
restContext().getResource(), bs)
                        .find(this::matchesInjectScope)
                        .run(x -> v.get().impl(x));
                return v.get().build().asArray();
-       }
-
-       private final Memoizer<RestMatcherList> matchersListMemo = 
memoizer(this::findMatchersList);
+       });
 
        /**
-        * Resolves the request-matcher list for this operation. 
<b>Cross-bucket memoizer</b> — folds
-        * {@code matchers} and {@code clientVersion} (both op-level only — 
neither attribute exists on
-        * {@code @Rest}) into a single effective {@link RestMatcherList}.
+        * The request-matcher list for this operation. <b>Cross-bucket 
memoizer</b> — folds
+        * {@code matchers} and {@code clientVersion} (both op-level only) into 
a single effective
+        * {@link RestMatcherList}.
         *
         * <p>
-        * Walks the {@code @RestOp} / verb annotation chain (parent-to-child, 
gated by op-level
+        * Walks the {@code @RestOp}/verb annotation chain (parent-to-child, 
gated by op-level
         * {@code noInherit={"matchers"}}). Each annotation contributes its 
{@code matchers()} classes
         * (appended in chain order). The final non-blank {@code 
clientVersion()} (most-derived wins)
         * appends a single {@link ClientVersionMatcher} keyed off the 
resource's client-version header.
-        *
-        * <p>
         * An {@code @RestInject RestMatcherList} bean (via the bean store or 
an {@code @RestInject}
         * method with matching {@code methodScope}) REPLACES the entire 
annotation-derived list.
         */
-       private RestMatcherList findMatchersList() {
-               var bs = context.getBeanStore();
+       private final Memoizer<RestMatcherList> matchersList = memoizer(() -> {
+               var bs = restContext().getBeanStore();
                var v = Value.of(RestMatcherList.create(bs));
                var clientVersion = new String[]{null};
 
@@ -897,49 +941,38 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                });
 
                if (nn(clientVersion[0]))
-                       v.get().append(new 
ClientVersionMatcher(context.getClientVersionHeader(), MethodInfo.of(method)));
+                       v.get().append(new 
ClientVersionMatcher(restContext().getClientVersionHeader(), 
MethodInfo.of(method())));
 
                bs.getBean(RestMatcherList.class).ifPresent(x -> 
v.get().impl(x));
-               new BeanCreateMethodFinder<>(RestMatcherList.class, 
context.getResource(), bs)
+               new BeanCreateMethodFinder<>(RestMatcherList.class, 
restContext().getResource(), bs)
                        .find(this::matchesInjectScope)
                        .run(x -> v.get().impl(x));
                return v.get().build();
-       }
-
-       private final Memoizer<RestMatcher[]> optionalMatchersMemo = 
memoizer(this::findOptionalMatchers);
+       });
 
-       private RestMatcher[] findOptionalMatchers() {
-               return matchersListMemo.get().getOptionalEntries();
-       }
+       /** The optional (non-required) matchers extracted from {@link 
#matchersList}. */
+       private final Memoizer<RestMatcher[]> optionalMatchers = memoizer(() -> 
matchersList.get().getOptionalEntries());
 
-       private final Memoizer<RestMatcher[]> requiredMatchersMemo = 
memoizer(this::findRequiredMatchers);
-
-       private RestMatcher[] findRequiredMatchers() {
-               return matchersListMemo.get().getRequiredEntries();
-       }
-
-       private final Memoizer<UrlPathMatcher[]> pathMatchersMemo = 
memoizer(this::findPathMatchers);
+       /** The required matchers extracted from {@link #matchersList}. */
+       private final Memoizer<RestMatcher[]> requiredMatchers = memoizer(() -> 
matchersList.get().getRequiredEntries());
 
        /**
-        * Resolves the URL path matchers for this operation by walking the 
{@code @RestOp}/verb annotation
-        * chain (parent-to-child) and collecting each annotation's {@code 
path[]} array plus its
-        * {@code value()} (the latter being the conventional shortcut form on
-        * {@link RestGet @RestGet}/{@link RestPost @RestPost}/etc., or the 
{@code "METHOD path"} pair on
-        * {@link RestOp @RestOp}). If no explicit paths are declared, the 
operation method name (with the
-        * verb prefix stripped where applicable) is auto-detected via {@link 
HttpUtils#detectHttpPath}.
+        * The URL path matchers for this operation.
         *
         * <p>
-        * Special case: for an RRPC operation with no explicit path, a 
trailing {@code "/*"} is appended so
-        * the matcher matches anything below the method's URL — see TODO-16 
Decision #17 (the legacy
-        * {@code Builder.dotAll()} flag was removed in favor of bake-in here).
-        *
-        * <p>
-        * Op-level {@code noInherit={"path"}} cuts off any further 
parent-chain contribution. A
-        * {@code @RestInject UrlPathMatcherList} bean (matching this 
operation's method scope) REPLACES the
-        * entire result.
+        * Walks the {@code @RestOp}/verb annotation chain (parent-to-child) 
and collects each
+        * annotation's {@code path[]} array plus its {@code value()} (the 
conventional shortcut form on
+        * {@link RestGet @RestGet}/{@link RestPost @RestPost}/etc., or the 
{@code "METHOD path"} pair on
+        * {@link RestOp @RestOp}). When no explicit paths are declared, the 
operation method name (with
+        * the verb prefix stripped where applicable) is auto-detected via 
{@link HttpUtils#detectHttpPath}.
+        * For RRPC operations with no explicit path, a trailing {@code "/*"} 
is appended so the matcher
+        * matches anything below the method's URL — see TODO-16 Decision #17. 
Op-level
+        * {@code noInherit={"path"}} cuts off any further parent-chain 
contribution. A
+        * {@code @RestInject UrlPathMatcherList} bean (matching this 
operation's method scope) REPLACES
+        * the entire result.
         */
        @SuppressWarnings("java:S3776")
-       private UrlPathMatcher[] findPathMatchers() {
+       private final Memoizer<UrlPathMatcher[]> pathMatchers = memoizer(() -> {
                var v = Value.of(UrlPathMatcherList.create());
                getRestOpAnnotationsForProperty(PROPERTY_path).forEach(ai -> {
                        for (var p : 
ai.getStringArray(PROPERTY_path).orElse(StringUtils.EMPTY_STRING_ARRAY))
@@ -961,7 +994,7 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                });
 
                if (v.get().isEmpty()) {
-                       var methodInfo2 = MethodInfo.of(method);
+                       var methodInfo2 = MethodInfo.of(method());
                        String httpMethod2 = null;
                        if (methodInfo2.hasAnnotation(RestGet.class))
                                httpMethod2 = "get";
@@ -982,7 +1015,7 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                                // @formatter:on
                        }
 
-                       var p = HttpUtils.detectHttpPath(method, httpMethod2);
+                       var p = HttpUtils.detectHttpPath(method(), httpMethod2);
 
                        // RRPC operations match anything below the method's 
URL when no explicit path is supplied
                        // (TODO-16 Decision #17 — replaces the legacy 
`Builder.dotAll()` flag).
@@ -992,13 +1025,13 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                        v.get().add(UrlPathMatcher.of(p));
                }
 
-               new BeanCreateMethodFinder<>(UrlPathMatcherList.class, 
context.getResource(), context.getBeanStore())
+               new BeanCreateMethodFinder<>(UrlPathMatcherList.class, 
restContext().getResource(), restContext().getBeanStore())
                        .addBean(UrlPathMatcherList.class, v.get())
                        .find(this::matchesInjectScope)
                        .run(v::set);
 
                return v.get().asArray();
-       }
+       });
 
        private Stream<String> resolveCdl(String...values) {
                if (values == null || values.length == 0)
@@ -1027,8 +1060,8 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
         *
         * <p>
         * Mirrors {@link RestContext#getRestAnnotationsForProperty(String)} 
but for the op-level chain. Used by
-        * the Phase D-2 op-level memoizers ({@code findConverters}, {@code 
findGuards}, {@code findMatchersList},
-        * {@code findEncoders}, etc.) when accumulating values from each 
{@code @RestOp} / {@code @RestGet} /
+        * op-level memoizers ({@link #converters}, {@link #guards}, {@link 
#matchersList},
+        * {@link #encoders}, etc.) when accumulating values from each {@code 
@RestOp} / {@code @RestGet} /
         * {@code @RestPut} / {@code @RestPost} / {@code @RestDelete} / {@code 
@RestPatch} / {@code @RestOptions}
         * annotation in the method-override chain.
         *
@@ -1085,7 +1118,7 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
         * @return The resolved string, or empty if no annotation defines it.
         */
        private Optional<String> findOpString(String attr) {
-               var vr = context.getVarResolver();
+               var vr = restContext().getVarResolver();
                for (var ai : getRestOpAnnotations()) {
                        var s = ai.getString(attr).orElse("");
                        if (!s.isEmpty()) {
@@ -1097,81 +1130,12 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                return Optional.empty();
        }
 
-       private Charset findDefaultCharset() {
-               var v = findOpString(PROPERTY_defaultCharset);
-               if (v.isPresent())
-                       return Charset.forName(v.get());
-               if (isInherited(PROPERTY_defaultCharset)) {
-                       var rv = 
context.mergeReplacedStringAttribute(PROPERTY_defaultCharset, null);
-                       if (rv != null && !rv.isEmpty())
-                               return Charset.forName(rv);
-               }
-               return envDefaultRestCharset();
-       }
-
-       private long findMaxInput() {
-               var v = findOpString(PROPERTY_maxInput);
-               if (v.isPresent())
-                       return parseLongWithSuffix(v.get());
-               if (isInherited(PROPERTY_maxInput)) {
-                       var rv = 
context.mergeReplacedStringAttribute(PROPERTY_maxInput, null);
-                       if (rv != null && !rv.isEmpty())
-                               return parseLongWithSuffix(rv);
-               }
-               return envDefaultRestMaxInput();
-       }
-
-       private DebugEnablement findDebugEnablement() {
-               var v = findOpString(PROPERTY_debug);
-               if (v.isPresent())
-                       return 
DebugEnablement.create(context.getBeanStore()).enable(Enablement.fromString(v.get()),
 "*").build();
-               if (isInherited(PROPERTY_debug))
-                       return context.getDebugEnablement();
-               return DebugEnablement.create(context.getBeanStore()).build();
-       }
-
-       /**
-        * Resolves the supported request content types for this operation.
-        *
-        * <p>
-        * Walks the op-level {@code @RestOp}-group annotations for {@code 
consumes} attributes and (when
-        * {@code noInherit} does not block it) the class-level {@code 
@Rest(consumes)} hierarchy. Each
-        * value is SVL-resolved. If no explicit values are declared, falls 
back to the supported content
-        * types of the operation's {@link ParserSet}.
-        *
-        * @return An unmodifiable list of media types, never {@code null}.
-        */
-       private List<MediaType> findSupportedContentTypes() {
-               var result = collectAnnotationMediaTypes(PROPERTY_consumes);
-               if (result.isEmpty())
-                       return u(getParsers().getSupportedMediaTypes());
-               return u(result);
-       }
-
-       /**
-        * Resolves the supported response accept types for this operation.
-        *
-        * <p>
-        * Walks the op-level {@code @RestOp}-group annotations for {@code 
produces} attributes and (when
-        * {@code noInherit} does not block it) the class-level {@code 
@Rest(produces)} hierarchy. Each
-        * value is SVL-resolved. If no explicit values are declared, falls 
back to the supported media
-        * types of the operation's {@link SerializerSet}.
-        *
-        * @return An unmodifiable list of media types, never {@code null}.
-        */
-       private List<MediaType> findSupportedAcceptTypes() {
-               var result = collectAnnotationMediaTypes(PROPERTY_produces);
-               if (result.isEmpty())
-                       return u(getSerializers().getSupportedMediaTypes());
-               return u(result);
-       }
-
        private List<MediaType> collectAnnotationMediaTypes(String attr) {
                var result = new ArrayList<MediaType>();
-               var vr = context.getVarResolver();
+               var vr = restContext().getVarResolver();
                // Class-level @Rest(consumes|produces) first (when inheritance 
is allowed), then op-level overrides append.
                if (isInherited(attr)) {
-                       for (var ai : context.getRestAnnotations())
+                       for (var ai : restContext().getRestAnnotations())
                                appendResolvedMediaTypes(ai, attr, vr, result);
                }
                for (var ai : getRestOpAnnotations())
@@ -1192,34 +1156,6 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                }
        }
 
-       /**
-        * Resolves the HTTP method for this operation from {@code 
@RestOp}-group annotations.
-        *
-        * <p>
-        * Walks the op-level annotations in child-to-parent order:
-        * <ul>
-        *   <li>{@code @RestGet}/{@code @RestPut}/{@code @RestPost}/{@code 
@RestDelete}/{@code @RestPatch}/{@code @RestOptions}
-        *       imply their fixed verb.
-        *   <li>{@code @RestOp(method)} is SVL-resolved; if blank, {@code 
@RestOp(value)} is parsed for a leading verb token.
-        * </ul>
-        * <p>
-        * If no annotation supplies a value, the verb is inferred from the 
Java method name via
-        * {@link HttpUtils#detectHttpMethod(Method, boolean, String)}. The 
literal {@code "METHOD"} is
-        * normalized to the wildcard {@code "*"}, and the result is 
upper-cased.
-        *
-        * @return The resolved HTTP method, never {@code null}.
-        */
-       @SuppressWarnings("java:S3776")
-       private String findHttpMethod() {
-               var vr = context.getVarResolver();
-               for (var ai : getRestOpAnnotations()) {
-                       var v = httpMethodFromAnnotation(ai.inner(), vr);
-                       if (v != null && !v.isEmpty())
-                               return normalizeHttpMethod(v);
-               }
-               return normalizeHttpMethod(HttpUtils.detectHttpMethod(method, 
true, "GET"));
-       }
-
        @SuppressWarnings("java:S3776")
        private static String httpMethodFromAnnotation(Annotation a, 
VarResolver vr) {
                if (a instanceof RestGet)
@@ -1339,7 +1275,7 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
 
                        // 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();
+                       httpMethod.get();
 
                        var pm = getPathMatchers();
                        bs.add(UrlPathMatcher[].class, pm);
@@ -1463,7 +1399,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(debugEnablementMemo.get().isDebug(this, 
session.getRequest()));
+               return RestOpSession.create(this, 
session).logger(getCallLogger()).debug(debugEnablement.get().isDebug(this, 
session.getRequest()));
        }
 
        @Override /* Overridden from Object */
@@ -1476,63 +1412,63 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
         *
         * @return The bean context associated with this context.
         */
-       public BeanContext getBeanContext() { return beanContextMemo.get(); }
+       public BeanContext getBeanContext() { return beanContext.get(); }
 
        /**
         * Returns the default charset.
         *
         * @return The default charset.  Never <jk>null</jk>.
         */
-       public Charset getDefaultCharset() { return defaultCharsetMemo.get(); }
+       public Charset getDefaultCharset() { return defaultCharset.get(); }
 
        /**
         * Returns the default request attributes.
         *
         * @return The default request attributes.  Never <jk>null</jk>.
         */
-       public NamedAttributeMap getDefaultRequestAttributes() { return 
defaultRequestAttributesMemo.get(); }
+       public NamedAttributeMap getDefaultRequestAttributes() { return 
defaultRequestAttributes.get(); }
 
        /**
         * Returns the default form data parameters.
         *
         * @return The default form data parameters.  Never <jk>null</jk>.
         */
-       public PartList getDefaultRequestFormData() { return 
defaultRequestFormDataMemo.get(); }
+       public PartList getDefaultRequestFormData() { return 
defaultRequestFormData.get(); }
 
        /**
         * Returns the default request headers.
         *
         * @return The default request headers.  Never <jk>null</jk>.
         */
-       public HeaderList getDefaultRequestHeaders() { return 
defaultRequestHeadersMemo.get(); }
+       public HeaderList getDefaultRequestHeaders() { return 
defaultRequestHeaders.get(); }
 
        /**
         * Returns the default request query parameters.
         *
         * @return The default request query parameters.  Never <jk>null</jk>.
         */
-       public PartList getDefaultRequestQueryData() { return 
defaultRequestQueryDataMemo.get(); }
+       public PartList getDefaultRequestQueryData() { return 
defaultRequestQueryData.get(); }
 
        /**
         * Returns the default response headers.
         *
         * @return The default response headers.  Never <jk>null</jk>.
         */
-       public HeaderList getDefaultResponseHeaders() { return 
defaultResponseHeadersMemo.get(); }
+       public HeaderList getDefaultResponseHeaders() { return 
defaultResponseHeaders.get(); }
 
        /**
         * Returns the compression encoders to use for this method.
         *
         * @return The compression encoders to use for this method.
         */
-       public EncoderSet getEncoders() { return encodersMemo.get(); }
+       public EncoderSet getEncoders() { return encoders.get(); }
 
        /**
         * Returns the HTTP method name (e.g. <js>"GET"</js>).
         *
         * @return The HTTP method name.
         */
-       public String getHttpMethod() { return httpMethodMemo.get(); }
+       public String getHttpMethod() { return httpMethod.get(); }
 
        /**
         * Returns the underlying Java method that this context belongs to.
@@ -1546,35 +1482,35 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
         *
         * @return The JSON-Schema generator applicable to this Java method.
         */
-       public JsonSchemaGenerator getJsonSchemaGenerator() { return 
jsonSchemaGeneratorMemo.get(); }
+       public JsonSchemaGenerator getJsonSchemaGenerator() { return 
jsonSchemaGenerator.get(); }
 
        /**
         * Returns the max number of bytes to process in the input content.
         *
         * @return The max number of bytes to process in the input content.
         */
-       public long getMaxInput() { return maxInputMemo.get(); }
+       public long getMaxInput() { return maxInput.get(); }
 
        /**
         * Returns the parsers to use for this method.
         *
         * @return The parsers to use for this method.
         */
-       public ParserSet getParsers() { return parsersMemo.get(); }
+       public ParserSet getParsers() { return parsers.get(); }
 
        /**
         * Bean property getter:  <property>partParser</property>.
         *
         * @return The value of the <property>partParser</property> property on 
this bean, or <jk>null</jk> if it is not set.
         */
-       public HttpPartParser getPartParser() { return partParserMemo.get(); }
+       public HttpPartParser getPartParser() { return partParser.get(); }
 
        /**
         * Bean property getter:  <property>partSerializer</property>.
         *
         * @return The value of the <property>partSerializer</property> 
property on this bean, or <jk>null</jk> if it is not set.
         */
-       public HttpPartSerializer getPartSerializer() { return 
partSerializerMemo.get(); }
+       public HttpPartSerializer getPartSerializer() { return 
partSerializer.get(); }
 
        /**
         * Returns the path pattern for this method.
@@ -1589,7 +1525,7 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
         * @return The URL path matchers for this operation.
         *      <br>Never <jk>null</jk>.
         */
-       public UrlPathMatcher[] getPathMatchers() { return 
pathMatchersMemo.get(); }
+       public UrlPathMatcher[] getPathMatchers() { return pathMatchers.get(); }
 
        /**
         * Returns the optional matchers for this operation.
@@ -1597,7 +1533,7 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
         * @return The optional matchers for this operation.
         *      <br>Never <jk>null</jk>.
         */
-       public RestMatcher[] getOptionalMatchers() { return 
optionalMatchersMemo.get(); }
+       public RestMatcher[] getOptionalMatchers() { return 
optionalMatchers.get(); }
 
        /**
         * Returns the required matchers for this operation.
@@ -1605,7 +1541,7 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
         * @return The required matchers for this operation.
         *      <br>Never <jk>null</jk>.
         */
-       public RestMatcher[] getRequiredMatchers() { return 
requiredMatchersMemo.get(); }
+       public RestMatcher[] getRequiredMatchers() { return 
requiredMatchers.get(); }
 
        /**
         * Returns the call logger for this operation.
@@ -1613,7 +1549,7 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
         * @return The call logger for this operation.
         *      <br>Never <jk>null</jk>.
         */
-       public CallLogger getCallLogger() { return callLoggerMemo.get(); }
+       public CallLogger getCallLogger() { return callLogger.get(); }
 
        /**
         * Returns metadata about the specified response object if it's 
annotated with {@link Response @Response}.
@@ -1677,21 +1613,21 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
         *
         * @return The serializers to use for this method.
         */
-       public SerializerSet getSerializers() { return serializersMemo.get(); }
+       public SerializerSet getSerializers() { return serializers.get(); }
 
        /**
         * Returns a list of supported accept types.
         *
         * @return An unmodifiable list.
         */
-       public List<MediaType> getSupportedAcceptTypes() { return 
supportedAcceptTypesMemo.get(); }
+       public List<MediaType> getSupportedAcceptTypes() { return 
supportedAcceptTypes.get(); }
 
        /**
         * Returns the list of supported content types.
         *
         * @return An unmodifiable list.
         */
-       public List<MediaType> getSupportedContentTypes() { return 
supportedContentTypesMemo.get(); }
+       public List<MediaType> getSupportedContentTypes() { return 
supportedContentTypes.get(); }
 
        @Override /* Overridden from Object */
        public int hashCode() {
@@ -1770,9 +1706,9 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                        .a(PROP_httpMethod, getHttpMethod());
        }
 
-       RestConverter[] getConverters() { return convertersMemo.get(); }
+       RestConverter[] getConverters() { return converters.get(); }
 
-       RestGuard[] getGuards() { return guardsMemo.get(); }
+       RestGuard[] getGuards() { return guards.get(); }
 
        RestOpInvoker getMethodInvoker() { return methodInvoker; }
 
diff --git a/todo/TODO.md b/todo/TODO.md
index 341a739c78..019315deac 100644
--- a/todo/TODO.md
+++ b/todo/TODO.md
@@ -40,5 +40,3 @@
 - [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