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 8bb6452065 refactor: add treeSetCi/u(SortedSet) utilities and inline 
applyParameter* helpers in RestOpContext
8bb6452065 is described below

commit 8bb6452065603d670cb59e6cc1be271442f73a02
Author: James Bognar <[email protected]>
AuthorDate: Mon May 4 11:13:43 2026 -0400

    refactor: add treeSetCi/u(SortedSet) utilities and inline applyParameter* 
helpers in RestOpContext
---
 .../juneau/commons/utils/CollectionUtils.java      |  27 ++++
 .../java/org/apache/juneau/rest/RestContext.java   |   9 +-
 .../java/org/apache/juneau/rest/RestOpContext.java | 144 +++++++++------------
 3 files changed, 92 insertions(+), 88 deletions(-)

diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/CollectionUtils.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/CollectionUtils.java
index f68bc75d02..1c3a72c37c 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/CollectionUtils.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/CollectionUtils.java
@@ -2095,6 +2095,20 @@ public class CollectionUtils {
                return s;
        }
 
+       /**
+        * Creates a case-insensitive {@link TreeSet} from a collection of 
strings.
+        *
+        * <p>
+        * Equivalent to <c>treeSet(String.CASE_INSENSITIVE_ORDER, 
elements)</c>.
+        * Null elements in the collection are silently skipped.
+        *
+        * @param elements The initial elements. Can be <jk>null</jk> (treated 
as empty).
+        * @return A new case-insensitive {@link TreeSet} containing all 
non-null elements from the collection.
+        */
+       public static SortedSet<String> treeSetCi(Collection<String> elements) {
+               return treeSet(String.CASE_INSENSITIVE_ORDER, elements);
+       }
+
        /**
         * Shortcut for creating a modifiable set out of an array of values.
         *
@@ -2693,5 +2707,18 @@ public class CollectionUtils {
                return value == null ? null : 
Collections.unmodifiableSet(value);
        }
 
+       /**
+        * Creates an unmodifiable view of the specified sorted set.
+        *
+        * <p>This is a null-safe wrapper around {@link 
Collections#unmodifiableSortedSet(SortedSet)}.</p>
+        *
+        * @param <T> The element type.
+        * @param value The sorted set to make unmodifiable. Can be null.
+        * @return An unmodifiable view of the sorted set, or null if the input 
was null.
+        */
+       public static <T> SortedSet<T> u(SortedSet<T> value) {
+               return value == null ? null : 
Collections.unmodifiableSortedSet(value);
+       }
+
        private CollectionUtils() {}
 }
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
index 1ce6eaab9e..db98470871 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
@@ -1888,8 +1888,7 @@ public class RestContext extends Context {
                                                // 
`RestOpContext.create(method, 
context).beanStore(beanStore).type(RestOpContext.class).build()` →
                                                // `new RestOpContext(method, 
context)`. The `.beanStore(beanStore)` override is equivalent to
                                                // the ctor default 
`BasicBeanStore.of(context.getBeanStore())` since `beanStore` here IS the
-                                               // resource-context's bean 
store; `.type(RestOpContext.class)` was the default. See TODO-16
-                                               // Phase C-3 Route B for the 
full migration record.
+                                               // resource-context's bean 
store; `.type(RestOpContext.class)` was the default. 
                                                var roc = new 
RestOpContext(mi.inner(), restContext);
 
                                                String httpMethod = 
roc.getHttpMethod();
@@ -1902,7 +1901,7 @@ public class RestContext extends Context {
                                                        // 
`RestOpContext.create(method, 
context).beanStore(restContext.getBootstrapBeanStore()).type(RrpcRestOpContext.class).build()`
 →
                                                        // `new 
RrpcRestOpContext(method, context)`. The bean-store override (root, not the
                                                        // resource-layered 
store) is preserved verbatim inside the new 2-arg ctor on
-                                                       // `RrpcRestOpContext`. 
The `.dotAll()` flag was removed per TODO-16 Decision #17 —
+                                                       // `RrpcRestOpContext`.
                                                        // RRPC operations 
auto-append `/*` inside `Builder.getPathMatchers()`.
                                                        RestOpContext roc2 = 
new RrpcRestOpContext(mi.inner(), restContext);
                                                        v.get()
@@ -2612,7 +2611,7 @@ public class RestContext extends Context {
                if (isInherited(p) && parentContext != null)
                        l.addAll(parentContext.getAllowedParserOptions());
                getRestAnnotationsForProperty(p).forEach(x -> 
resolveCdl(x.getStringArray(p)).forEach(l::add));
-               return 
Collections.unmodifiableSortedSet(treeSet(String.CASE_INSENSITIVE_ORDER, 
removeNegations(l)));
+               return u(treeSetCi(removeNegations(l)));
        }
 
        /**
@@ -2630,7 +2629,7 @@ public class RestContext extends Context {
                if (isInherited(p) && parentContext != null)
                        l.addAll(parentContext.getAllowedSerializerOptions());
                getRestAnnotationsForProperty(p).forEach(x -> 
resolveCdl(x.getStringArray(p)).forEach(l::add));
-               return 
Collections.unmodifiableSortedSet(treeSet(String.CASE_INSENSITIVE_ORDER, 
removeNegations(l)));
+               return u(treeSetCi(removeNegations(l)));
        }
 
        private Stream<AnnotationInfo<Rest>> 
restAnnotationsForPropertySortedByRank(String propertyName) {
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 d7f806147c..6f955c3307 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
@@ -328,6 +328,20 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
        // The annotation work-list produced during construction (replaces 
builder.getApplied() references).
        private final AnnotationWorkList appliedAnnotations;
 
+       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 fields
+       
//-----------------------------------------------------------------------------------------------------------------
+
        /** 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);
@@ -413,20 +427,6 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                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 fields
-       
//-----------------------------------------------------------------------------------------------------------------
-
        /**
         * All {@link RestOp}-group annotations on this method, in 
child-to-parent order.
         *
@@ -448,7 +448,7 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                        .map(ai -> 
ai.getStringArray("noInherit").orElse(StringUtils.EMPTY_STRING_ARRAY))
                        .flatMap(this::resolveCdl)
                        .toList();
-               return 
Collections.unmodifiableSortedSet(treeSet(String.CASE_INSENSITIVE_ORDER, l));
+               return u(treeSetCi(l));
        });
 
        /** Effective allowed parser session-option keys for this operation. */
@@ -460,7 +460,7 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                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)));
+               return u(treeSetCi(removeNegations(l)));
        });
 
        /** Effective allowed serializer session-option keys for this 
operation. */
@@ -472,7 +472,7 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                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)));
+               return u(treeSetCi(removeNegations(l)));
        });
 
        /** The {@link BeanContext} for this operation (op-level annotations 
applied on top of the parent context). */
@@ -612,7 +612,15 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                        for (var s : 
ai.getStringArray(PROPERTY_defaultRequestFormData).orElse(EMPTY_STRING_ARRAY))
                                v.get().setDefault(basicPart(s));
                });
-               applyParameterFormData(v.get());
+               processParameterDefaults((paramAnn, def) -> {
+                       if (paramAnn instanceof FormData f) {
+                               try {
+                                       
v.get().setDefault(basicPart(firstNonEmpty(f.name(), f.value()), 
parseIfJson(def)));
+                               } catch (ParseException e) {
+                                       throw new ConfigException(e, "Malformed 
@FormData annotation");
+                               }
+                       }
+               });
                new BeanCreateMethodFinder<>(PartList.class, 
restContext().getResource(), restContext().getBeanStore())
                        .find(x -> matchesInjectScope(x, 
PROPERTY_defaultRequestFormData))
                        .run(v::set);
@@ -645,7 +653,15 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                        ai.getString(PROPERTY_defaultAccept).filter(s -> 
!s.isEmpty()).ifPresent(s -> v.get().setDefault(accept(s)));
                        ai.getString(PROPERTY_defaultContentType).filter(s -> 
!s.isEmpty()).ifPresent(s -> v.get().setDefault(contentType(s)));
                });
-               applyParameterHeaders(v.get());
+               processParameterDefaults((paramAnn, def) -> {
+                       if (paramAnn instanceof Header h) {
+                               try {
+                                       
v.get().set(basicHeader(firstNonEmpty(h.name(), h.value()), parseIfJson(def)));
+                               } catch (ParseException e) {
+                                       throw new ConfigException(e, "Malformed 
@Header annotation");
+                               }
+                       }
+               });
                new BeanCreateMethodFinder<>(HeaderList.class, 
restContext().getResource(), restContext().getBeanStore())
                        .find(x -> matchesInjectScope(x, 
PROPERTY_defaultRequestHeaders))
                        .run(v::set);
@@ -669,7 +685,15 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                        for (var s : 
ai.getStringArray(PROPERTY_defaultRequestQueryData).orElse(EMPTY_STRING_ARRAY))
                                v.get().setDefault(basicPart(s));
                });
-               applyParameterQueryData(v.get());
+               processParameterDefaults((paramAnn, def) -> {
+                       if (paramAnn instanceof Query q) {
+                               try {
+                                       
v.get().setDefault(basicPart(firstNonEmpty(q.name(), q.value()), 
parseIfJson(def)));
+                               } catch (ParseException e) {
+                                       throw new ConfigException(e, "Malformed 
@Query annotation");
+                               }
+                       }
+               });
                new BeanCreateMethodFinder<>(PartList.class, 
restContext().getResource(), restContext().getBeanStore())
                        .find(x -> matchesInjectScope(x, 
PROPERTY_defaultRequestQueryData))
                        .run(v::set);
@@ -699,62 +723,15 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                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 the {@link #defaultRequestHeaders} memoizer.
-        */
-       private void applyParameterHeaders(HeaderList list) {
-               processParameterDefaults((paramAnn, def) -> {
-                       if (paramAnn instanceof Header h) {
-                               try {
-                                       
list.set(basicHeader(firstNonEmpty(h.name(), h.value()), parseIfJson(def)));
-                               } catch (ParseException e) {
-                                       throw new ConfigException(e, "Malformed 
@Header annotation");
-                               }
-                       }
-               });
-       }
-
        /**
         * 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 the {@link #defaultRequestQueryData} memoizer.
         */
-       private void applyParameterQueryData(PartList list) {
-               processParameterDefaults((paramAnn, def) -> {
-                       if (paramAnn instanceof Query q) {
-                               try {
-                                       
list.setDefault(basicPart(firstNonEmpty(q.name(), q.value()), 
parseIfJson(def)));
-                               } catch (ParseException e) {
-                                       throw new ConfigException(e, "Malformed 
@Query annotation");
-                               }
-                       }
-               });
-       }
-
-       /**
-        * 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 the {@link #defaultRequestFormData} memoizer.
-        */
-       private void applyParameterFormData(PartList list) {
-               processParameterDefaults((paramAnn, def) -> {
-                       if (paramAnn instanceof FormData f) {
-                               try {
-                                       
list.setDefault(basicPart(firstNonEmpty(f.name(), f.value()), 
parseIfJson(def)));
-                               } catch (ParseException e) {
-                                       throw new ConfigException(e, "Malformed 
@FormData annotation");
-                               }
-                       }
-               });
-       }
-
        /**
         * Iterates over each parameter annotation on the operation method, 
computing the parameter's
         * {@link Schema#default_()}/{@link Schema#df()} string 
(joined-non-blank-first) and dispatching
-        * each annotation+default pair to the supplied callback. Used by the 
three
-        * {@code applyParameter*} helpers above.
+        * each annotation+default pair to the supplied callback.
         */
        private void 
processParameterDefaults(java.util.function.BiConsumer<Annotation,String> 
callback) {
                for (var aa : method.getParameterAnnotations()) {
@@ -770,18 +747,6 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                }
        }
 
-       private static String joinnlFirstNonEmptyArray(String[]...s) {
-               for (var ss : s)
-                       if (ss.length > 0)
-                               return joinnl(ss);
-               return null;
-       }
-
-       @SuppressWarnings("unchecked")
-       private static <T> Class<? extends T>[] classArray(Class<? extends T> 
value) {
-               return (Class<? extends T>[])new Class<?>[] { value };
-       }
-
        /**
         * The response-converter array for this operation.
         *
@@ -791,7 +756,7 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
         * {@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).
+        * name) REPLACES the entire annotation-derived list.
         */
        private final Memoizer<RestConverter[]> converters = memoizer(() -> {
                var bs = restContext().getBeanStore();
@@ -966,7 +931,7 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
         * {@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
+        * matches anything below the method's URL.
         * {@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.
@@ -1018,7 +983,6 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                        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).
                        if ("RRPC".equalsIgnoreCase(httpMethod2) && ! 
p.endsWith("/*"))
                                p += "/*";
 
@@ -1715,4 +1679,18 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
        RestOpInvoker[] getPostCallMethods() { return postCallMethods; }
 
        RestOpInvoker[] getPreCallMethods() { return preCallMethods; }
+       
+       private static String joinnlFirstNonEmptyArray(String[]...s) {
+               for (var ss : s)
+                       if (ss.length > 0)
+                               return joinnl(ss);
+               return null;
+       }
+
+       @SuppressWarnings("unchecked")
+       private static <T> Class<? extends T>[] classArray(Class<? extends T> 
value) {
+               return (Class<? extends T>[])new Class<?>[] { value };
+       }
+
+
 }
\ No newline at end of file

Reply via email to