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<String> <jv>supplier</jv> = <jk>new</jk>
ResettableSupplier<>(() -> 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<String> <jv>supplier</jv> =
Utils.<jsm>memoizeResettable</jsm>(() -> 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; }
}