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 6cb92ee715 Utility class modernization
6cb92ee715 is described below

commit 6cb92ee7159c111d74ea6433aac1519e043abcba
Author: James Bognar <[email protected]>
AuthorDate: Tue Nov 4 18:31:01 2025 -0500

    Utility class modernization
---
 .../juneau/common/function/ResettableSupplier.java | 109 ++++++++++++
 .../juneau/common/reflect/AnnotationInfo.java      |  12 ++
 .../juneau/common/reflect/ExecutableInfo.java      |   9 +-
 .../juneau/common/reflect/ParameterInfo.java       | 190 +++++++++++----------
 .../java/org/apache/juneau/common/utils/Utils.java |  44 +++++
 .../java/org/apache/juneau/annotation/Name.java    |  39 ++++-
 .../java/org/apache/juneau/annotation/Named.java   |  76 +++++++++
 .../main/java/org/apache/juneau/cp/BeanStore.java  |  10 +-
 .../org/apache/juneau/rest/arg/DefaultArg.java     |   6 +-
 .../java/org/apache/juneau/rest/arg/PathArg.java   |   2 +-
 .../juneau/common/reflect/ExecutableInfo_Test.java |  18 +-
 .../juneau/common/reflect/ParamInfoTest.java       |  35 +++-
 .../java/org/apache/juneau/cp/BeanStore_Test.java  |  19 ++-
 .../juneau/rest/RestContext_Builder_Test.java      |   6 +-
 14 files changed, 443 insertions(+), 132 deletions(-)

diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/function/ResettableSupplier.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/function/ResettableSupplier.java
new file mode 100644
index 0000000000..d50a356b8d
--- /dev/null
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/function/ResettableSupplier.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.common.function;
+
+import java.util.*;
+import java.util.concurrent.atomic.*;
+import java.util.function.*;
+
+/**
+ * A thread-safe supplier that caches the result of the first call and 
supports resetting the cache.
+ *
+ * <p>
+ * This class provides both standard {@link Supplier#get()} functionality and 
a {@link #reset()} method
+ * to clear the cache, forcing recomputation on the next call to {@link 
#get()}.
+ *
+ * <h5 class='section'>Usage:</h5>
+ * <p class='bjava'>
+ *     <jc>// Create a resettable supplier</jc>
+ *     ResettableSupplier&lt;String&gt; <jv>supplier</jv> = <jk>new</jk> 
ResettableSupplier&lt;&gt;(() -&gt; expensiveComputation());
+ *
+ *     <jc>// First call computes and caches</jc>
+ *     String <jv>result1</jv> = <jv>supplier</jv>.get();
+ *
+ *     <jc>// Subsequent calls return cached value</jc>
+ *     String <jv>result2</jv> = <jv>supplier</jv>.get();
+ *
+ *     <jc>// Reset forces recomputation on next get()</jc>
+ *     <jv>supplier</jv>.reset();
+ *     String <jv>result3</jv> = <jv>supplier</jv>.get();  <jc>// 
Recomputes</jc>
+ * </p>
+ *
+ * <h5 class='section'>Thread Safety:</h5>
+ * <p>
+ * This class is thread-safe for both {@link #get()} and {@link #reset()} 
operations.
+ * If multiple threads call get() simultaneously after a reset, the supplier 
may be invoked
+ * multiple times, but only one result will be cached.
+ *
+ * <h5 class='section'>Notes:</h5>
+ * <ul>
+ *     <li>The supplier may be called multiple times if threads race, but only 
one result is cached.
+ *     <li>The cached value can be <jk>null</jk> if the supplier returns 
<jk>null</jk>.
+ *     <li>After reset, the next get() will recompute the value.
+ *     <li>This is particularly useful for testing when configuration changes 
and cached values need to be recalculated.
+ * </ul>
+ *
+ * <h5 class='section'>See Also:</h5>
+ * <ul>
+ *     <li class='jm'>{@link 
org.apache.juneau.common.utils.Utils#memoizeResettable(Supplier)}
+ * </ul>
+ *
+ * @param <T> The type of value supplied.
+ */
+public class ResettableSupplier<T> implements Supplier<T> {
+       private final Supplier<T> supplier;
+       private final AtomicReference<Optional<T>> cache = new 
AtomicReference<>();
+
+       /**
+        * Constructor.
+        *
+        * @param supplier The underlying supplier to call when computing 
values.
+        */
+       public ResettableSupplier(Supplier<T> supplier) {
+               this.supplier = supplier;
+       }
+
+       /**
+        * Returns the cached value if present, otherwise computes it using the 
underlying supplier.
+        *
+        * @return The cached or newly computed value.
+        */
+       @Override
+       public T get() {
+               Optional<T> h = cache.get();
+               if (h == null) {
+                       h = Optional.ofNullable(supplier.get());
+                       if (!cache.compareAndSet(null, h)) {
+                               // Another thread beat us, use their value
+                               h = cache.get();
+                       }
+               }
+               return h.orElse(null);
+       }
+
+       /**
+        * Clears the cached value, forcing the next call to {@link #get()} to 
recompute the value.
+        *
+        * <p>
+        * This method is thread-safe and can be called from multiple threads. 
After reset,
+        * the next get() call will invoke the underlying supplier to compute a 
fresh value.
+        */
+       public void reset() {
+               cache.set(null);
+       }
+}
+
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationInfo.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationInfo.java
index ee3099c133..0b2e046bea 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationInfo.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationInfo.java
@@ -493,4 +493,16 @@ public class AnnotationInfo<T extends Annotation> {
                if (nn(mi))
                        mi.getDeclaredAnnotationInfos().forEach(ai -> 
ai.accept(filter, action));
        }
+
+       public boolean hasSimpleName(String value) {
+               return eq(value, a.annotationType().getSimpleName());
+       }
+
+       public String getValue() {
+               return getString("value");
+       }
+
+       public String getString(String methodName) {
+               return methods.get().stream().filter(x -> eq(methodName, 
x.getSimpleName()) && x.hasReturnType(String.class)).map(x -> 
s(x.invoke(a))).findFirst().orElse(null);
+       }
 }
\ No newline at end of file
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ExecutableInfo.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ExecutableInfo.java
index 6495f2451c..0892e03a0c 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ExecutableInfo.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ExecutableInfo.java
@@ -21,7 +21,6 @@ import static 
org.apache.juneau.common.reflect.ClassNameFormat.*;
 import static org.apache.juneau.common.utils.CollectionUtils.*;
 import static org.apache.juneau.common.utils.PredicateUtils.*;
 import static org.apache.juneau.common.utils.StringUtils.*;
-import static org.apache.juneau.common.utils.ThrowableUtils.*;
 import static org.apache.juneau.common.utils.Utils.*;
 import static java.util.stream.Collectors.*;
 
@@ -87,9 +86,9 @@ public abstract class ExecutableInfo extends AccessibleInfo {
         */
        @Deprecated
        public final <A extends Annotation> ExecutableInfo 
forEachParameterAnnotation(int index, Class<A> type, Predicate<A> predicate, 
Consumer<A> consumer) {
-               for (var a : getParameter(index).getAnnotations())
-                       if (type.isInstance(a))
-                               consumeIf(predicate, consumer, type.cast(a));
+               for (var ai : getParameter(index).getAnnotationInfos())
+                       if (type.isInstance(ai.inner()))
+                               consumeIf(predicate, consumer, 
type.cast(ai.inner()));
                return this;
        }
 
@@ -420,7 +419,7 @@ public abstract class ExecutableInfo extends AccessibleInfo 
{
        public final boolean hasMatchingParameters(List<ParameterInfo> params) {
                var myParams = getParameters();
                return myParams.size() == params.size()
-                       && IntStream.range(0, params.size()).allMatch(i -> 
+                       && IntStream.range(0, params.size()).allMatch(i ->
                                
myParams.get(i).getParameterType().is(params.get(i).getParameterType()));
        }
 
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ParameterInfo.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ParameterInfo.java
index 6f120b0a35..c34e0610ff 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ParameterInfo.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ParameterInfo.java
@@ -28,6 +28,7 @@ import java.util.function.*;
 import java.util.stream.*;
 
 import org.apache.juneau.common.collections.*;
+import org.apache.juneau.common.function.ResettableSupplier;
 
 /**
  * Lightweight utility class for introspecting information about a method 
parameter.
@@ -37,6 +38,21 @@ import org.apache.juneau.common.collections.*;
  */
 public class ParameterInfo extends ElementInfo implements Annotatable {
 
+       /**
+        * Resettable supplier for the system property to disable bytecode 
parameter name detection.
+        *
+        * <p>
+        * When the value is <jk>true</jk>, parameter names will only come from 
{@link org.apache.juneau.annotation.Name @Name}
+        * annotations and not from bytecode parameter names (even if compiled 
with <c>-parameters</c> flag).
+        *
+        * <p>
+        * This can be set via system property: 
<c>juneau.disableParamNameDetection=true</c>
+        *
+        * <p>
+        * The supplier can be reset for testing purposes using {@link 
#resetDisableParamNameDetection()}.
+        */
+       static final ResettableSupplier<Boolean> DISABLE_PARAM_NAME_DETECTION = 
memoizeResettable(() -> Boolean.getBoolean("juneau.disableParamNameDetection"));
+
        private final ExecutableInfo executable;
        private final Parameter parameter;
        private final int index;
@@ -47,7 +63,8 @@ public class ParameterInfo extends ElementInfo implements 
Annotatable {
 
        private final Supplier<List<AnnotationInfo<Annotation>>> annotations = 
memoize(this::_findAnnotations);
        private final Supplier<List<ParameterInfo>> matchingParameters = 
memoize(this::_findMatchingParameters);
-       private final Supplier<String> foundName = 
memoize(this::findNameInternal);
+       private final ResettableSupplier<String> foundName = 
memoizeResettable(this::findNameInternal);
+       private final ResettableSupplier<String> foundQualifier = 
memoizeResettable(this::findQualifierInternal);
 
        /**
         * Constructor.
@@ -79,6 +96,18 @@ public class ParameterInfo extends ElementInfo implements 
Annotatable {
                return stream(parameter.getAnnotations()).map(a -> 
AnnotationInfo.of(this, a)).toList();
        }
 
+       /**
+        * Returns all annotations declared on this parameter.
+        *
+        * <p>
+        * Returns annotations directly declared on this parameter, wrapped as 
{@link AnnotationInfo} objects.
+        *
+        * @return An unmodifiable list of annotations on this parameter, never 
<jk>null</jk>.
+        */
+       public List<AnnotationInfo<Annotation>> getAnnotationInfos() {
+               return annotations.get();
+       }
+
        /**
         * Returns <jk>true</jk> if this parameter can accept the specified 
value.
         *
@@ -222,9 +251,9 @@ public class ParameterInfo extends ElementInfo implements 
Annotatable {
         */
        public <A extends Annotation> A getDeclaredAnnotation(Class<A> type) {
                if (nn(type))
-                       for (var a : getAnnotations())
-                               if (type.isInstance(a))
-                                       return type.cast(a);
+                       for (var ai : getAnnotationInfos())
+                               if (type.isInstance(ai.inner()))
+                                       return type.cast(ai.inner());
                return null;
        }
 
@@ -243,63 +272,88 @@ public class ParameterInfo extends ElementInfo implements 
Annotatable {
        public MethodInfo getMethod() { return executable.isConstructor() ? 
null : (MethodInfo)executable; }
 
        /**
-        * Helper method to extract the name from any annotation with the 
simple name "Name".
+        * Finds the name of this parameter for bean property mapping.
+        *
+        * <p>
+        * Searches for the parameter name in the following order:
+        * <ol>
+        *      <li>{@link org.apache.juneau.annotation.Name @Name} annotation 
value
+        *      <li>Bytecode parameter name (if available and not disabled via 
system property)
+        *      <li>Matching parameters in parent classes/interfaces
+        * </ol>
+        *
+        * <p>
+        * This method is used for mapping constructor parameters to bean 
properties.
         *
         * <p>
-        * This method uses reflection to find any annotation with the simple 
name "Name"
-        * and dynamically invokes its <c>value()</c> method to retrieve the 
parameter name.
-        * This allows it to work with any <c>@Name</c> annotation from any 
package without
-        * creating a compile-time dependency.
+        * <b>Note:</b> This is different from {@link #findQualifier()} which 
looks for {@link org.apache.juneau.annotation.Named @Named}
+        * annotations for bean injection purposes.
         *
-        * @return The name from the annotation, or <jk>null</jk> if no 
compatible annotation is found.
+        * @return The parameter name if found, or <jk>null</jk> if not 
available.
+        * @see #findQualifier()
+        * @see #getName()
         */
-       private String findNameFromAnnotation() {
-               for (var annotation : parameter.getAnnotations()) {
-                       var annotationType = annotation.annotationType();
-                       if ("Name".equals(annotationType.getSimpleName())) {
-                               try {
-                                       var valueMethod = 
annotationType.getMethod("value");
-                                       if (valueMethod.getReturnType() == 
String.class) {
-                                               var value = 
valueMethod.invoke(annotation);
-                                               if (value instanceof String)
-                                                       return (String)value;
-                                       }
-                               } catch (Exception e) {
-                                       // Ignore - annotation doesn't have a 
compatible value() method
+       public String findName() {
+               return foundName.get();
+       }
+
+       static void reset() {
+               DISABLE_PARAM_NAME_DETECTION.reset();
+       }
+
+       private String findNameInternal() {
+               // Search through matching parameters in hierarchy for @Name 
annotations only.
+               // Note: We intentionally prioritize @Name annotations over 
bytecode parameter names
+               // because bytecode names are unreliable - users may or may not 
compile with -parameters flag.
+               for (var mp : getMatchingParameters()) {
+                       for (var ai : mp.getAnnotationInfos()) {
+                               if (ai.hasSimpleName("Name")) {
+                                       String value = ai.getValue();
+                                       if (value != null)
+                                               return value;
                                }
                        }
                }
+
+               // Fall back to bytecode parameter name if available and not 
disabled
+               if (!DISABLE_PARAM_NAME_DETECTION.get() && 
parameter.isNamePresent()) {
+                       return parameter.getName();
+               }
+
                return null;
        }
 
        /**
-        * Finds the name of this parameter by searching the hierarchy.
+        * Finds the bean injection qualifier for this parameter.
         *
         * <p>
-        * Searches for the parameter name in the following order:
-        * <ol>
-        *      <li>@Name annotation value (takes precedence over bytecode 
parameter names)
-        *      <li>Bytecode parameter name (if compiled with -parameters flag)
-        *      <li>Matching parameters in parent classes/interfaces (for 
methods)
-        * </ol>
+        * Searches for the {@link org.apache.juneau.annotation.Named @Named} 
annotation value to determine
+        * which named bean should be injected.
         *
-        * @return The parameter name if found, or <jk>null</jk> if not 
available.
+        * <p>
+        * This method is used by the {@link org.apache.juneau.cp.BeanStore} 
for bean injection.
+        *
+        * <p>
+        * <b>Note:</b> This is different from {@link #findName()} which looks 
for {@link org.apache.juneau.annotation.Name @Name}
+        * annotations for bean property mapping.
+        *
+        * @return The bean qualifier name if {@code @Named} annotation is 
found, or <jk>null</jk> if not annotated.
+        * @see #findName()
         */
-       public String findName() {
-               return foundName.get();
+       public String findQualifier() {
+               return foundQualifier.get();
        }
 
-       private String findNameInternal() {
-               // Search through matching parameters in hierarchy
+       private String findQualifierInternal() {
+               // Search through matching parameters in hierarchy for @Named 
or javax.inject.Qualifier annotations
                for (var mp : getMatchingParameters()) {
-                       // Check for @Name annotation first (overrides bytecode 
names)
-                       String annotationName = mp.findNameFromAnnotation();
-                       if (annotationName != null)
-                               return annotationName;
-                       
-                       // Then check if bytecode name is present
-                       if (mp.parameter.isNamePresent())
-                               return mp.parameter.getName();
+                       for (var ai : mp.getAnnotationInfos()) {
+                               if (ai.hasSimpleName("Named") || 
ai.hasSimpleName("Qualifier")) {
+                                       String value = ai.getValue();
+                                       if (value != null)
+                                               return value;
+                               }
+                       }
                }
                return null;
        }
@@ -327,18 +381,6 @@ public class ParameterInfo extends ElementInfo implements 
Annotatable {
                return name != null ? name : parameter.getName();
        }
 
-       /**
-        * Returns all annotations declared on this parameter.
-        *
-        * <p>
-        * Returns annotations directly declared on this parameter, wrapped as 
{@link AnnotationInfo} objects.
-        *
-        * @return An unmodifiable list of annotations on this parameter, never 
<jk>null</jk>.
-        */
-       public List<AnnotationInfo<Annotation>> getAnnotationInfos() {
-               return annotations.get();
-       }
-
        /**
         * Returns the class type of this parameter.
         *
@@ -621,30 +663,6 @@ public class ParameterInfo extends ElementInfo implements 
Annotatable {
                return parameter.getAnnotatedType();
        }
 
-       /**
-        * Returns annotations that are <em>present</em> on this parameter.
-        *
-        * <p>
-        * Same as calling {@link Parameter#getAnnotations()}.
-        *
-        * <p>
-        * <b>Note:</b> This returns the simple array of annotations directly 
present on the parameter.
-        * For Juneau's enhanced annotation searching (through class 
hierarchies), use {@link #findAnnotationInfo(Class)} instead.
-        *
-        * <h5 class='section'>Example:</h5>
-        * <p class='bjava'>
-        *      <jc>// Get all annotations on parameter</jc>
-        *      ParameterInfo <jv>pi</jv> = ...;
-        *      Annotation[] <jv>annotations</jv> = 
<jv>pi</jv>.getAnnotations();
-        * </p>
-        *
-        * @return Annotations present on this parameter, or an empty array if 
there are none.
-        * @see Parameter#getAnnotations()
-        */
-       public Annotation[] getAnnotations() {
-               return parameter.getAnnotations();
-       }
-
        /**
         * Returns annotations that are <em>directly present</em> on this 
parameter.
         *
@@ -653,7 +671,7 @@ public class ParameterInfo extends ElementInfo implements 
Annotatable {
         *
         * <p>
         * <b>Note:</b> This returns the simple array of declared annotations.
-        * For Juneau's enhanced annotation searching, use {@link 
#getDeclaredAnnotation(Class)} instead.
+        * For Juneau's enhanced annotation searching, use {@link 
#findAnnotationInfo(Class)} instead.
         *
         * <h5 class='section'>Example:</h5>
         * <p class='bjava'>
@@ -669,10 +687,6 @@ public class ParameterInfo extends ElementInfo implements 
Annotatable {
                return parameter.getDeclaredAnnotations();
        }
 
-       
//-----------------------------------------------------------------------------------------------------------------
-       // Medium Priority Methods (repeatable annotations)
-       
//-----------------------------------------------------------------------------------------------------------------
-
        /**
         * Returns this element's annotations of the specified type (including 
repeated annotations).
         *
@@ -754,11 +768,11 @@ public class ParameterInfo extends ElementInfo implements 
Annotatable {
        private <A extends Annotation> ParameterInfo 
forEachAnnotation(AnnotationProvider ap, Class<A> a, Predicate<A> filter, 
Consumer<A> action) {
                if (executable.isConstructor) {
                        var ci = 
executable.getParameter(index).getParameterType().unwrap(Value.class, 
Optional.class);
-                       var annotations = getAnnotations();
+                       var annotationInfos = getAnnotationInfos();
                        ci.forEachAnnotation(ap, a, filter, action);
-                       for (var a2 : annotations)
-                               if (a.isInstance(a2))
-                                       consumeIf(filter, action, a.cast(a2));
+                       for (var ai : annotationInfos)
+                               if (a.isInstance(ai.inner()))
+                                       consumeIf(filter, action, 
a.cast(ai.inner()));
                } else {
                        var mi = (MethodInfo)executable;
                        var ci = 
executable.getParameter(index).getParameterType().unwrap(Value.class, 
Optional.class);
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/Utils.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/Utils.java
index 5eab131bd3..112793f574 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/Utils.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/Utils.java
@@ -31,6 +31,7 @@ import java.util.stream.*;
 
 import org.apache.juneau.common.collections.*;
 import org.apache.juneau.common.function.*;
+import org.apache.juneau.common.function.ResettableSupplier;
 
 /**
  * Common utility methods.
@@ -1174,6 +1175,49 @@ public class Utils {
                };
        }
 
+       /**
+        * Creates a resettable memoizing supplier that caches the result of 
the first call and optionally allows resetting.
+        *
+        * <p>
+        * This is similar to {@link #memoize(Supplier)}, but returns a {@link 
ResettableSupplier} that supports
+        * clearing the cached value, forcing recomputation on the next call.
+        *
+        * <h5 class='section'>Usage:</h5>
+        * <p class='bjava'>
+        *      ResettableSupplier&lt;String&gt; <jv>supplier</jv> = 
Utils.<jsm>memoizeResettable</jsm>(() -&gt; expensiveComputation());
+        *
+        *      <jc>// First call computes and caches</jc>
+        *      String <jv>result1</jv> = <jv>supplier</jv>.get();
+        *
+        *      <jc>// Subsequent calls return cached value</jc>
+        *      String <jv>result2</jv> = <jv>supplier</jv>.get();
+        *
+        *      <jc>// Reset forces recomputation on next get()</jc>
+        *      <jv>supplier</jv>.reset();
+        *      String <jv>result3</jv> = <jv>supplier</jv>.get();  <jc>// 
Recomputes</jc>
+        * </p>
+        *
+        * <h5 class='section'>Thread Safety:</h5>
+        * <p>
+        * The returned supplier is thread-safe for both {@link 
ResettableSupplier#get()} and
+        * {@link ResettableSupplier#reset()} operations. If multiple threads 
call get() simultaneously
+        * after a reset, the supplier may be invoked multiple times, but only 
one result will be cached.
+        *
+        * <h5 class='section'>See Also:</h5>
+        * <ul>
+        *      <li class='jc'>{@link ResettableSupplier}
+        * </ul>
+        *
+        * @param <T> The type of value supplied.
+        * @param supplier The supplier to memoize. Must not be <jk>null</jk>.
+        * @return A thread-safe resettable memoizing wrapper around the 
supplier.
+        * @throws NullPointerException if supplier is <jk>null</jk>.
+        */
+       public static <T> ResettableSupplier<T> memoizeResettable(Supplier<T> 
supplier) {
+               assertArgNotNull("supplier", supplier);
+               return new ResettableSupplier<>(supplier);
+       }
+
        /**
         * Converts the specified object into an identifiable string of the 
form "Class[identityHashCode]"
         * @param o The object to convert to a string.
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/Name.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/Name.java
index 3fb25cf051..2e85bd71ed 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/Name.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/Name.java
@@ -22,7 +22,12 @@ import static java.lang.annotation.RetentionPolicy.*;
 import java.lang.annotation.*;
 
 /**
- * Annotation that can be used on method parameters to identify their name.
+ * Specifies the parameter name for bean property mapping.
+ *
+ * <p>
+ * This annotation is used to explicitly specify a parameter name when 
bytecode parameter names
+ * are not available (i.e., when code is not compiled with the {@code 
-parameters} flag).
+ * It allows constructors to map parameters to bean properties by name.
  *
  * <p>
  * Can be used in the following locations:
@@ -32,16 +37,38 @@ import java.lang.annotation.*;
  *
  * <h5 class='figure'>Examples:</h5>
  * <p class='bjava'>
- *     <jc>// Identifying bean property names.
- *     // The field name can be anything.</jc>
- *     <jk>public class</jk> MyBean {
+ *     <jc>// Without -parameters flag, parameter names would be arg0, arg1, 
etc.</jc>
+ *     <jk>public class</jk> Person {
+ *             <jk>public</jk> Person(<ja>@Name</ja>(<js>"firstName"</js>) 
String <jv>arg0</jv>, <ja>@Name</ja>(<js>"lastName"</js>) String <jv>arg1</jv>) 
{
+ *                     <jc>// Parameters can be mapped to bean properties 
"firstName" and "lastName"</jc>
+ *             }
+ *     }
+ * </p>
+ *
+ * <h5 class='section'>Comparison with @Named:</h5>
+ * <p>
+ * Do not confuse this annotation with {@link Named @Named}, which serves a 
different purpose:
+ * <ul>
+ *     <li><b>{@link Name @Name}</b> - Specifies the parameter name for bean 
property mapping
+ *     <li><b>{@link Named @Named}</b> - Specifies which named bean to inject 
(bean qualifier)
+ * </ul>
+ *
+ * <h5 class='section'>Example showing the difference:</h5>
+ * <p class='bjava'>
+ *     <jc>// @Name - for parameter naming</jc>
+ *     <jk>public</jk> Person(<ja>@Name</ja>(<js>"firstName"</js>) String 
<jv>arg0</jv>) {
+ *             <jc>// Maps parameter to bean property "firstName"</jc>
+ *     }
  *
- *             <jk>public</jk> MyBean(<ja>@Name</ja>(<js>"bar"</js>) 
<jk>int</jk> <jv>foo</jv>) {}
+ *     <jc>// @Named - for bean injection</jc>
+ *     <jk>public</jk> MyService(<ja>@Named</ja>(<js>"primaryDb"</js>) 
Database <jv>db</jv>) {
+ *             <jc>// Injects the bean named "primaryDb" from BeanStore</jc>
  *     }
  * </p>
  *
  * <h5 class='section'>See Also:</h5><ul>
-
+ *     <li class='ja'>{@link Named}
+ *     <li class='jc'>{@link org.apache.juneau.cp.BeanStore}
  * </ul>
  */
 @Documented
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/Named.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/Named.java
new file mode 100644
index 0000000000..9d1a75da1c
--- /dev/null
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/Named.java
@@ -0,0 +1,76 @@
+// 
***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright 
ownership.  The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not 
use this file except in compliance            *
+// * with the License.  You may obtain a copy of the License at                
                                              *
+// *                                                                           
                                              *
+// *  http://www.apache.org/licenses/LICENSE-2.0                               
                                              *
+// *                                                                           
                                              *
+// * Unless required by applicable law or agreed to in writing, software 
distributed under the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
express or implied.  See the License for the        *
+// * specific language governing permissions and limitations under the 
License.                                              *
+// 
***************************************************************************************************************************
+package org.apache.juneau.annotation;
+
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.*;
+
+import java.lang.annotation.*;
+
+/**
+ * Identifies a bean injection qualifier for constructor/method parameters.
+ *
+ * <p>
+ * This annotation is used to specify which named bean should be injected into 
a constructor
+ * or method parameter. It serves the same purpose as {@link 
javax.inject.Named} but without
+ * requiring a dependency on the javax.inject module.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ *     <jk>public</jk> MyClass(<ja>@Named</ja>(<js>"myBean"</js>) MyBean 
<jv>bean</jv>) {
+ *             <jc>// Constructor will receive the bean named "myBean" from 
the BeanStore</jc>
+ *     }
+ * </p>
+ *
+ * <h5 class='section'>Comparison with @Name:</h5>
+ * <p>
+ * Do not confuse this annotation with {@link Name @Name}, which serves a 
different purpose:
+ * <ul>
+ *     <li><b>{@link Named @Named}</b> - Specifies which named bean to inject 
(bean qualifier)
+ *     <li><b>{@link Name @Name}</b> - Specifies the parameter name for bean 
property mapping when
+ *             bytecode parameter names are not available
+ * </ul>
+ *
+ * <h5 class='section'>Example showing the difference:</h5>
+ * <p class='bjava'>
+ *     <jc>// @Named - for bean injection</jc>
+ *     <jk>public</jk> MyService(<ja>@Named</ja>(<js>"primaryDb"</js>) 
Database <jv>db</jv>) {
+ *             <jc>// Injects the bean named "primaryDb" from BeanStore</jc>
+ *     }
+ *
+ *     <jc>// @Name - for parameter naming (when bytecode names 
unavailable)</jc>
+ *     <jk>public</jk> Person(<ja>@Name</ja>(<js>"firstName"</js>) String 
<jv>arg0</jv>, <ja>@Name</ja>(<js>"lastName"</js>) String <jv>arg1</jv>) {
+ *             <jc>// Maps constructor parameters to bean properties 
"firstName" and "lastName"</jc>
+ *             <jc>// Useful when class is not compiled with -parameters 
flag</jc>
+ *     }
+ * </p>
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ *     <li class='ja'>{@link Name}
+ *     <li class='jc'>{@link org.apache.juneau.cp.BeanStore}
+ * </ul>
+ */
+@Documented
+@Target({PARAMETER})
+@Retention(RUNTIME)
+@Inherited
+public @interface Named {
+
+       /**
+        * The bean name to use for injection.
+        *
+        * @return The bean name.
+        */
+       String value();
+}
+
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/cp/BeanStore.java 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/cp/BeanStore.java
index 63de07f0c3..984c6f524a 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/cp/BeanStore.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/cp/BeanStore.java
@@ -511,7 +511,7 @@ public class BeanStore {
                                continue loop;
                        if (pt.is(Optional.class) || pt.is(BeanStore.class))
                                continue loop;
-                       String beanName = pi.findName();
+                       String beanName = pi.findQualifier();  // Use @Named 
for bean injection
                        Class<?> ptc = pt.inner();
                        if (beanName == null && ! hasBean(ptc))
                                l.add(pt.getNameSimple());
@@ -537,9 +537,9 @@ public class BeanStore {
                        } else if (pt.is(BeanStore.class)) {
                                o[i] = this;
                        } else {
-                               String beanName = pi.findName();
+                               String beanQualifier = pi.findQualifier();
                                Class<?> ptc = 
pt.unwrap(Optional.class).inner();
-                               Optional<?> o2 = beanName == null ? 
getBean(ptc) : getBean(ptc, beanName);
+                               Optional<?> o2 = beanQualifier == null ? 
getBean(ptc) : getBean(ptc, beanQualifier);
                                o[i] = pt.is(Optional.class) ? o2 : 
o2.orElse(null);
                        }
                }
@@ -560,9 +560,9 @@ public class BeanStore {
                                continue loop;
                        if (pt.is(Optional.class) || pt.is(BeanStore.class))
                                continue loop;
-                       String beanName = pi.findName();
+                       String beanQualifier = pi.findQualifier();
                        Class<?> ptc = pt.inner();
-                       if ((beanName == null && ! hasBean(ptc)) || 
(nn(beanName) && ! hasBean(ptc, beanName)))
+                       if ((beanQualifier == null && ! hasBean(ptc)) || 
(nn(beanQualifier) && ! hasBean(ptc, beanQualifier)))
                                return false;
                }
                return true;
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/arg/DefaultArg.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/arg/DefaultArg.java
index c3f7eae8a3..c3449a8cb9 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/arg/DefaultArg.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/arg/DefaultArg.java
@@ -54,7 +54,7 @@ public class DefaultArg implements RestOpArg {
        }
 
        private final Class<?> type;
-       private final String name;
+       private final String qualifier;
 
        private final ParameterInfo paramInfo;
 
@@ -66,11 +66,11 @@ public class DefaultArg implements RestOpArg {
        protected DefaultArg(ParameterInfo paramInfo) {
                this.type = paramInfo.getParameterType().inner();
                this.paramInfo = paramInfo;
-               this.name = paramInfo.findName();
+               this.qualifier = paramInfo.findQualifier();
        }
 
        @Override /* Overridden from RestOpArg */
        public Object resolve(RestOpSession opSession) throws Exception {
-               return opSession.getBeanStore().getBean(type, 
name).orElseThrow(() -> new ArgException(paramInfo, "Could not resolve bean 
type {0}", cn(type)));
+               return opSession.getBeanStore().getBean(type, 
qualifier).orElseThrow(() -> new ArgException(paramInfo, "Could not resolve 
bean type {0}", cn(type)));
        }
 }
\ No newline at end of file
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/arg/PathArg.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/arg/PathArg.java
index 7089c77e8a..67071ab620 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/arg/PathArg.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/arg/PathArg.java
@@ -206,7 +206,7 @@ public class PathArg implements RestOpArg {
 
                        String[] vars = pathMatcher.getVars();
                        if (vars.length <= idx)
-                               throw new ArgException(pi, "Number of attribute 
parameters exceeds the number of URL pattern variables");
+                               throw new ArgException(pi, "Number of attribute 
parameters exceeds the number of URL pattern variables.  vars.length={0}, 
idx={1}", vars.length, idx);
 
                        // Check for {#} variables.
                        var idxs = String.valueOf(idx);
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/common/reflect/ExecutableInfo_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/common/reflect/ExecutableInfo_Test.java
index b0bc7c9f10..88b46d5e6e 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/common/reflect/ExecutableInfo_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/common/reflect/ExecutableInfo_Test.java
@@ -47,6 +47,8 @@ class ExecutableInfo_Test extends TestBase {
                                return 
((List<?>)t).stream().map(this).collect(Collectors.joining(","));
                        if (isArray(t))
                                return StreamSupport.stream(toList(t, 
Object.class).spliterator(), false).map(this).collect(Collectors.joining(","));
+                       if (t instanceof AnnotationInfo)
+                               return "@" + 
((AnnotationInfo<?>)t).inner().annotationType().getSimpleName() + "()";
                        if (t instanceof Annotation)
                                return "@" + 
((Annotation)t).annotationType().getSimpleName() + "()";
                        if (t instanceof Class)
@@ -257,17 +259,17 @@ class ExecutableInfo_Test extends TestBase {
        ;
 
        @Test void getParameterAnnotations() {
-               check("", c_c1.getParameters().stream().map(p -> 
p.getAnnotations()).toArray());
-               check("@CA()", c_c2.getParameters().stream().map(p -> 
p.getAnnotations()).toArray());
-               check("", c_c3.getParameters().stream().map(p -> 
p.getAnnotations()).toArray());
-               check("", c_m1.getParameters().stream().map(p -> 
p.getAnnotations()).toArray());
-               check("@CA()", c_m2.getParameters().stream().map(p -> 
p.getAnnotations()).toArray());
-               check("", c_m3.getParameters().stream().map(p -> 
p.getAnnotations()).toArray());
+               check("", c_c1.getParameters().stream().map(p -> 
p.getAnnotationInfos()).toArray());
+               check("@CA()", c_c2.getParameters().stream().map(p -> 
p.getAnnotationInfos()).toArray());
+               check("", c_c3.getParameters().stream().map(p -> 
p.getAnnotationInfos()).toArray());
+               check("", c_m1.getParameters().stream().map(p -> 
p.getAnnotationInfos()).toArray());
+               check("@CA()", c_m2.getParameters().stream().map(p -> 
p.getAnnotationInfos()).toArray());
+               check("", c_m3.getParameters().stream().map(p -> 
p.getAnnotationInfos()).toArray());
        }
 
        @Test void getParameterAnnotations_atIndex() {
-               check("@CA()", c_c2.getParameter(0).getAnnotations());
-               check("@CA()", c_m2.getParameter(0).getAnnotations());
+               check("@CA()", c_c2.getParameter(0).getAnnotationInfos());
+               check("@CA()", c_m2.getParameter(0).getAnnotationInfos());
        }
 
        @Test void hasAnnotation() {
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/common/reflect/ParamInfoTest.java
 
b/juneau-utest/src/test/java/org/apache/juneau/common/reflect/ParamInfoTest.java
index d7350cae6e..6a17a956cc 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/common/reflect/ParamInfoTest.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/common/reflect/ParamInfoTest.java
@@ -38,6 +38,28 @@ import org.apache.juneau.annotation.Name;
  */
 class ParamInfoTest extends TestBase {
 
+       private static String originalDisableParamNameDetection;
+
+       @BeforeAll
+       public static void beforeAll() {
+               // Save original system property value
+               originalDisableParamNameDetection = 
System.getProperty("juneau.disableParamNameDetection");
+               
+               // Set to true to ensure consistent behavior regardless of JVM 
compiler settings
+               System.setProperty("juneau.disableParamNameDetection", "true");
+               ParameterInfo.reset();
+       }
+
+       @AfterAll
+       public static void afterAll() {
+               // Restore original system property value
+               if (originalDisableParamNameDetection == null)
+                       
System.clearProperty("juneau.disableParamNameDetection");
+               else
+                       System.setProperty("juneau.disableParamNameDetection", 
originalDisableParamNameDetection);
+               ParameterInfo.reset();
+       }
+
        @Documented
        @Target(METHOD)
        @Retention(RUNTIME)
@@ -340,13 +362,18 @@ class ParamInfoTest extends TestBase {
                e_a1_b = e.getMethod(x -> x.hasName("a1")).getParameter(1);  // 
NOSONAR
 
        @Test void hasName() {
-               e_a1_a.hasName();  // This might be true or false based on the 
JVM compiler used.
-               assertTrue(e_a1_b.hasName());
+               // With DISABLE_PARAM_NAME_DETECTION=true, only parameters with 
@Name annotation have names
+               assertFalse(e_a1_a.hasName());  // No @Name annotation
+               assertTrue(e_a1_b.hasName());   // Has @Name("b")
        }
 
        @Test void getName() {
-               e_a1_a.getName();  // This might be null or a value based on 
the JVM compiler used.
-               assertEquals("b", e_a1_b.getName());
+               // With DISABLE_PARAM_NAME_DETECTION=true:
+               // - Parameters with @Name use the annotation value
+               // - Parameters without @Name fall back to parameter.getName() 
which may return
+               //   bytecode names (if compiled with -parameters) or synthetic 
names (arg0, arg1, etc.)
+               assertNotNull(e_a1_a.getName());  // No @Name, falls back to 
parameter.getName()
+               assertEquals("b", e_a1_b.getName());  // Has @Name("b")
        }
 
        @Test void toString2() {
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/cp/BeanStore_Test.java 
b/juneau-utest/src/test/java/org/apache/juneau/cp/BeanStore_Test.java
index cf4a560af8..98c32e3e23 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/cp/BeanStore_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/cp/BeanStore_Test.java
@@ -28,6 +28,7 @@ import java.util.function.*;
 
 import org.apache.juneau.*;
 import org.apache.juneau.annotation.*;
+import org.apache.juneau.annotation.Named;
 import org.apache.juneau.common.reflect.*;
 import org.apache.juneau.common.utils.*;
 import org.junit.jupiter.api.*;
@@ -240,7 +241,7 @@ class BeanStore_Test extends TestBase {
                        this.a3 = a3;
                }
 
-               public B1(@Name("foo") A1 a1, @Name("bar") Optional<A2> a2) {
+               public B1(@Named("foo") A1 a1, @Named("bar") Optional<A2> a2) {
                        this.a1 = a1;
                        this.a2 = a2;
                }
@@ -251,7 +252,7 @@ class BeanStore_Test extends TestBase {
                        this.a3 = a3;
                }
 
-               public void m2(@Name("foo") A1 a1, @Name("bar") Optional<A2> 
a2) {
+               public void m2(@Named("foo") A1 a1, @Named("bar") Optional<A2> 
a2) {
                        this.a1 = a1;
                        this.a2 = a2;
                        this.a3 = null;
@@ -391,7 +392,7 @@ class BeanStore_Test extends TestBase {
                        this.a3 = a3;
                }
 
-               public B2(@Name("foo") A1 a1, @Name("bar") Optional<A2> a2) {
+               public B2(@Named("foo") A1 a1, @Named("bar") Optional<A2> a2) {
                        this.a1 = a1;
                        this.a2 = a2;
                }
@@ -581,8 +582,8 @@ class BeanStore_Test extends TestBase {
                public C createC1(A1 a) { return new C(); }
                public static C createC2(A1 a) { return new C(); }
                public static C createC3(Optional<A1> a) { C e = new C(); e.a = 
a.orElse(null); return e; }
-               public static C createC4(@Name("Foo") A1 a) { return new C(); }
-               public static C createC5(@Name("Foo") Optional<A1> a) { C e = 
new C(); e.a = a.orElse(null); return e; }
+               public static C createC4(@Named("Foo") A1 a) { return new C(); }
+               public static C createC5(@Named("Foo") Optional<A1> a) { C e = 
new C(); e.a = a.orElse(null); return e; }
                public static C createC6(BeanStore bs) { assertNotNull(bs); 
return new C(); }
        }
 
@@ -969,8 +970,8 @@ class BeanStore_Test extends TestBase {
 
        public static class D14 {
                public String a;
-               public D14(@Name("foo") String o) { a = o; }
-               public D14(@Name("foo") String o, Integer i) { a = o + "," + i; 
}
+               public D14(@Named("foo") String o) { a = o; }
+               public D14(@Named("foo") String o, Integer i) { a = o + "," + 
i; }
        }
 
        @Test void d14_createBean_constructors_namedBean() {
@@ -982,8 +983,8 @@ class BeanStore_Test extends TestBase {
 
        public class D15 {
                public String a;
-               public D15(@Name("foo") String o) { a = o; }
-               public D15(@Name("foo") String o, Integer i) { a = o + "," + i; 
}
+               public D15(@Named("foo") String o) { a = o; }
+               public D15(@Named("foo") String o, Integer i) { a = o + "," + 
i; }
        }
 
        @Test void d15_createBean_constructors_namedBean_withOuter() {
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/rest/RestContext_Builder_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/rest/RestContext_Builder_Test.java
index ff8d31a492..b1be8e391b 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/rest/RestContext_Builder_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/rest/RestContext_Builder_Test.java
@@ -19,7 +19,7 @@ package org.apache.juneau.rest;
 import static org.junit.jupiter.api.Assertions.*;
 
 import org.apache.juneau.*;
-import org.apache.juneau.annotation.Name;
+import org.apache.juneau.annotation.Named;
 import org.apache.juneau.cp.*;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.client.*;
@@ -110,7 +110,7 @@ class RestContext_Builder_Test extends TestBase {
                @RestInject(name="b2") B b4;
 
                @RestGet("/a1") public B a1(B b) { return b; }
-               @RestGet("/a2") public B a2(@Name("b2") B b) { return b; }
+               @RestGet("/a2") public B a2(@Named("b2") B b) { return b; }
                @RestGet("/a3") public B a3() { return b3; }
                @RestGet("/a4") public B a4() { return b4; }
        }
@@ -118,7 +118,7 @@ class RestContext_Builder_Test extends TestBase {
        @Rest
        public static class B1b extends B1a {
                @RestGet("/a5") public B a5(B b) { return b; }
-               @RestGet("/a6") public B a6(@Name("b2") B b) { return b; }
+               @RestGet("/a6") public B a6(@Named("b2") B b) { return b; }
                @RestGet("/a7") public B a7() { return b3; }
                @RestGet("/a8") public B a8() { return b4; }
        }


Reply via email to