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 844fe59b57 Unit tests
844fe59b57 is described below
commit 844fe59b5706ba2e03a6b49b7f69faf51dc25761
Author: James Bognar <[email protected]>
AuthorDate: Tue Dec 2 06:55:42 2025 -0800
Unit tests
---
.../juneau/commons/reflect/AccessibleInfo.java | 17 +-
.../juneau/commons/reflect/ExecutableInfo.java | 11 +-
.../org/apache/juneau/commons/utils/Utils.java | 33 +
.../juneau/commons/collections/Cache2_Test.java | 289 +++++++++
.../juneau/commons/collections/Cache3_Test.java | 264 ++++++++
.../juneau/commons/collections/Cache4_Test.java | 264 ++++++++
.../juneau/commons/collections/Cache5_Test.java | 264 ++++++++
.../juneau/commons/collections/Cache_Test.java | 290 +++++++++
.../juneau/commons/reflect/AnnotationInfoTest.java | 57 --
.../commons/reflect/AnnotationInfo_Test.java | 706 +++++++++++++++++++++
.../reflect/AnnotationInfo_ValueMethods_Test.java | 188 ------
...ctorInfoTest.java => ConstructorInfo_Test.java} | 27 +-
.../commons/reflect/ExecutableInfo_Test.java | 33 +
.../reflect/FieldInfo_AnnotationInfos_Test.java | 117 ----
.../commons/reflect/FieldInfo_FullName_Test.java | 74 ---
.../juneau/commons/reflect/FieldInfo_Test.java | 157 +++++
...ameterInfoTest.java => ParameterInfo_Test.java} | 2 +-
17 files changed, 2336 insertions(+), 457 deletions(-)
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/AccessibleInfo.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/AccessibleInfo.java
index f5f41a6deb..5a52351c21 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/AccessibleInfo.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/AccessibleInfo.java
@@ -17,6 +17,7 @@
package org.apache.juneau.commons.reflect;
import static org.apache.juneau.commons.utils.Utils.*;
+import static org.apache.juneau.commons.utils.AssertionUtils.*;
import java.lang.reflect.*;
@@ -83,7 +84,7 @@ public abstract class AccessibleInfo extends ElementInfo {
*/
protected AccessibleInfo(AccessibleObject inner, int modifiers) {
super(modifiers);
- this.inner = inner;
+ this.inner = assertArgNotNull("inner", inner);
}
//-----------------------------------------------------------------------------------------------------------------
@@ -107,11 +108,7 @@ public abstract class AccessibleInfo extends ElementInfo {
* @return <jk>true</jk> if this object is accessible, <jk>false</jk>
otherwise or if not supported.
*/
public boolean isAccessible() {
- try {
- return
(boolean)AccessibleObject.class.getMethod("isAccessible").invoke(inner);
- } catch (@SuppressWarnings("unused") Exception ex) {
- return false;
- }
+ return safeOpt(() ->
(boolean)AccessibleObject.class.getMethod("isAccessible").invoke(inner)).orElse(false);
}
/**
@@ -120,12 +117,6 @@ public abstract class AccessibleInfo extends ElementInfo {
* @return <jk>true</jk> if call was successful.
*/
public boolean setAccessible() {
- try {
- if (nn(inner))
- inner.setAccessible(true);
- return true;
- } catch (@SuppressWarnings("unused") SecurityException e) {
- return false;
- }
+ return safeOpt(() -> { inner.setAccessible(true); return
true;}).orElse(false);
}
}
\ No newline at end of file
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/ExecutableInfo.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/ExecutableInfo.java
index cc99b5f9a9..dc32bd735b 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/ExecutableInfo.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/ExecutableInfo.java
@@ -711,13 +711,12 @@ public abstract class ExecutableInfo extends
AccessibleInfo {
*/
@Override
public final boolean setAccessible() {
- try {
- if (nn(inner))
- inner.setAccessible(true);
- return true;
- } catch (@SuppressWarnings("unused") SecurityException e) {
+ if (!nn(inner))
return false;
- }
+ return safeOpt(() -> {
+ inner.setAccessible(true);
+ return true;
+ }).orElse(false);
}
/**
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/Utils.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/Utils.java
index 1e4c6c9d88..b43b7ced47 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/Utils.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/Utils.java
@@ -1395,6 +1395,39 @@ public class Utils {
}
}
+ /**
+ * Executes a supplier that may throw an exception and returns an
Optional.
+ *
+ * <p>
+ * If the supplier executes successfully, returns {@link
Optional#of(Object)} with the result.
+ * If the supplier throws any exception, returns {@link
Optional#empty()}.
+ *
+ * <p>
+ * This is useful for operations that may fail but you want to handle
the failure
+ * gracefully by returning an empty Optional instead of throwing an
exception.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Check if AccessibleObject.isAccessible() method exists
(Java 9+)</jc>
+ * <jk>boolean</jk> <jv>isAccessible</jv> = <jsm>safeOpt</jsm>(()
->
+ *
(<jk>boolean</jk>)AccessibleObject.<jk>class</jk>.getMethod(<js>"isAccessible"</js>).invoke(<jv>obj</jv>)
+ * ).orElse(<jk>false</jk>);
+ * </p>
+ *
+ * @param <T> The return type.
+ * @param s The supplier that may throw an exception.
+ * @return An Optional containing the result if successful, or empty if
an exception was thrown.
+ * @see #safe(ThrowingSupplier)
+ * @see #opt(Object)
+ */
+ public static <T> Optional<T> safeOpt(ThrowingSupplier<T> s) {
+ try {
+ return Optional.of(s.get());
+ } catch (@SuppressWarnings("unused") Exception e) {
+ return Optional.empty();
+ }
+ }
+
/**
* Allows you to wrap a supplier that throws an exception so that it
can be used in a fluent interface.
*
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Cache2_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Cache2_Test.java
index 392a9927df..ee9cd12df3 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Cache2_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Cache2_Test.java
@@ -20,6 +20,7 @@ import static org.junit.jupiter.api.Assertions.*;
import static org.apache.juneau.commons.collections.CacheMode.*;
import static org.apache.juneau.junit.bct.BctAssertions.*;
+import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import org.apache.juneau.*;
@@ -301,6 +302,46 @@ class Cache2_Test extends TestBase {
assertSize(1, x);
}
+ @Test
+ void d06_weakMethod_basicCaching() {
+ // Test the weak() convenience method
+ var callCount = new AtomicInteger();
+ var x = Cache2.of(String.class, Integer.class, String.class)
+ .weak()
+ .supplier((k1, k2) -> {
+ callCount.incrementAndGet();
+ return k1 + ":" + k2;
+ })
+ .build();
+
+ // First call - cache miss
+ var result1 = x.get("user", 123);
+
+ // Second call - cache hit
+ var result2 = x.get("user", 123);
+
+ assertEquals("user:123", result1);
+ assertEquals("user:123", result2);
+ assertSame(result1, result2);
+ assertEquals(1, callCount.get()); // Supplier only called once
+ assertSize(1, x);
+ assertEquals(1, x.getCacheHits());
+ }
+
+ @Test
+ void d07_weakMethod_chaining() {
+ // Test that weak() can be chained with other builder methods
+ var x = Cache2.of(String.class, Integer.class, String.class)
+ .weak()
+ .maxSize(100)
+ .supplier((k1, k2) -> k1 + ":" + k2)
+ .build();
+
+ var result = x.get("user", 123);
+ assertEquals("user:123", result);
+ assertSize(1, x);
+ }
+
//====================================================================================================
// e - Max size and eviction
//====================================================================================================
@@ -523,6 +564,17 @@ class Cache2_Test extends TestBase {
assertFalse(x.containsValue("value1"));
}
+ @Test
+ void m04_containsValue_nullValue() {
+ var x = Cache2.of(String.class, Integer.class,
String.class).build();
+ // Null values can't be cached, so containsValue(null) should
return false
+ x.get("user", 123, () -> null);
+ assertFalse(x.containsValue(null));
+ // Also test with empty cache
+ var x2 = Cache2.of(String.class, Integer.class,
String.class).build();
+ assertFalse(x2.containsValue(null));
+ }
+
//====================================================================================================
// n - logOnExit() builder methods
//====================================================================================================
@@ -616,5 +668,242 @@ class Cache2_Test extends TestBase {
assertEquals(2, callCount.get());
assertTrue(x.isEmpty());
}
+
+
//====================================================================================================
+ // o - Thread-local cache mode
+
//====================================================================================================
+
+ @Test
+ void o01_threadLocal_basicCaching() {
+ var callCount = new AtomicInteger();
+ var x = Cache2.of(String.class, Integer.class, String.class)
+ .threadLocal()
+ .supplier((k1, k2) -> {
+ callCount.incrementAndGet();
+ return k1 + ":" + k2;
+ })
+ .build();
+
+ // First call - cache miss
+ var result1 = x.get("user", 123);
+
+ // Second call - cache hit
+ var result2 = x.get("user", 123);
+
+ assertEquals("user:123", result1);
+ assertEquals("user:123", result2);
+ assertSame(result1, result2);
+ assertEquals(1, callCount.get()); // Supplier only called once
+ assertSize(1, x);
+ assertEquals(1, x.getCacheHits());
+ }
+
+ @Test
+ void o02_threadLocal_eachThreadHasOwnCache() throws Exception {
+ var x = Cache2.of(String.class, Integer.class, String.class)
+ .threadLocal()
+ .build();
+ var executor =
java.util.concurrent.Executors.newFixedThreadPool(2);
+ var threadValues = new ConcurrentHashMap<Thread, String>();
+
+ // Each thread caches ("user", 123) with its own value
+ var future1 =
java.util.concurrent.CompletableFuture.runAsync(() -> {
+ var value = x.get("user", 123, () -> "thread1-value");
+ threadValues.put(Thread.currentThread(), value);
+ }, executor);
+
+ var future2 =
java.util.concurrent.CompletableFuture.runAsync(() -> {
+ var value = x.get("user", 123, () -> "thread2-value");
+ threadValues.put(Thread.currentThread(), value);
+ }, executor);
+
+ java.util.concurrent.CompletableFuture.allOf(future1,
future2).get(5, java.util.concurrent.TimeUnit.SECONDS);
+
+ // Verify both threads cached their own values
+ assertEquals(2, threadValues.size());
+ assertTrue(threadValues.containsValue("thread1-value"));
+ assertTrue(threadValues.containsValue("thread2-value"));
+
+ // Verify each thread's cache is independent - same thread
should get same cached value
+ var threadValues2 = new ConcurrentHashMap<Thread, String>();
+ var threads = new
java.util.ArrayList<Thread>(threadValues.keySet());
+
+ future1 = java.util.concurrent.CompletableFuture.runAsync(() ->
{
+ var value = x.get("user", 123, () ->
"should-not-be-called");
+ threadValues2.put(Thread.currentThread(), value);
+ }, executor);
+
+ future2 = java.util.concurrent.CompletableFuture.runAsync(() ->
{
+ var value = x.get("user", 123, () ->
"should-not-be-called");
+ threadValues2.put(Thread.currentThread(), value);
+ }, executor);
+
+ java.util.concurrent.CompletableFuture.allOf(future1,
future2).get(5, java.util.concurrent.TimeUnit.SECONDS);
+
+ // Each thread should get its own cached value (same as what it
cached before)
+ for (var thread : threads) {
+ if (threadValues2.containsKey(thread)) {
+ assertEquals(threadValues.get(thread),
threadValues2.get(thread),
+ "Thread " + thread + " should get its
own cached value");
+ }
+ }
+
+ executor.shutdown();
+ }
+
+ @Test
+ void o03_threadLocal_multipleKeys() {
+ var x = Cache2.of(String.class, Integer.class, String.class)
+ .threadLocal()
+ .supplier((k1, k2) -> k1 + ":" + k2)
+ .build();
+
+ x.get("user", 123);
+ x.get("admin", 456);
+ x.get("guest", 789);
+
+ assertSize(3, x);
+ assertEquals(0, x.getCacheHits());
+
+ // Verify all cached
+ assertEquals("user:123", x.get("user", 123));
+ assertEquals("admin:456", x.get("admin", 456));
+ assertEquals("guest:789", x.get("guest", 789));
+ assertEquals(3, x.getCacheHits());
+ }
+
+ @Test
+ void o04_threadLocal_clear() {
+ var x = Cache2.of(String.class, Integer.class, String.class)
+ .threadLocal()
+ .supplier((k1, k2) -> k1 + ":" + k2)
+ .build();
+
+ x.get("user", 123);
+ x.get("admin", 456);
+ assertSize(2, x);
+
+ x.clear();
+ assertEmpty(x);
+ }
+
+ @Test
+ void o05_threadLocal_maxSize() {
+ var x = Cache2.of(String.class, Integer.class, String.class)
+ .threadLocal()
+ .maxSize(2)
+ .supplier((k1, k2) -> k1 + ":" + k2)
+ .build();
+
+ x.get("k1", 1);
+ x.get("k2", 2);
+ assertSize(2, x);
+
+ // 3rd item doesn't trigger eviction yet
+ x.get("k3", 3);
+ assertSize(3, x);
+
+ // 4th item triggers eviction
+ x.get("k4", 4);
+ assertSize(1, x);
+ }
+
+
//====================================================================================================
+ // p - Thread-local + weak mode combination
+
//====================================================================================================
+
+ @Test
+ void p01_threadLocal_weakMode_basicCaching() {
+ var callCount = new AtomicInteger();
+ var x = Cache2.of(String.class, Integer.class, String.class)
+ .threadLocal()
+ .cacheMode(WEAK)
+ .supplier((k1, k2) -> {
+ callCount.incrementAndGet();
+ return k1 + ":" + k2;
+ })
+ .build();
+
+ // First call - cache miss
+ var result1 = x.get("user", 123);
+
+ // Second call - cache hit
+ var result2 = x.get("user", 123);
+
+ assertEquals("user:123", result1);
+ assertEquals("user:123", result2);
+ assertSame(result1, result2);
+ assertEquals(1, callCount.get()); // Supplier only called once
+ assertSize(1, x);
+ assertEquals(1, x.getCacheHits());
+ }
+
+ @Test
+ void p02_threadLocal_weakMode_eachThreadHasOwnCache() throws Exception {
+ var x = Cache2.of(String.class, Integer.class, String.class)
+ .threadLocal()
+ .cacheMode(WEAK)
+ .build();
+ var executor =
java.util.concurrent.Executors.newFixedThreadPool(2);
+ var threadValues = new ConcurrentHashMap<Thread, String>();
+
+ // Each thread caches ("user", 123) with its own value
+ var future1 =
java.util.concurrent.CompletableFuture.runAsync(() -> {
+ var value = x.get("user", 123, () -> "thread1-value");
+ threadValues.put(Thread.currentThread(), value);
+ }, executor);
+
+ var future2 =
java.util.concurrent.CompletableFuture.runAsync(() -> {
+ var value = x.get("user", 123, () -> "thread2-value");
+ threadValues.put(Thread.currentThread(), value);
+ }, executor);
+
+ java.util.concurrent.CompletableFuture.allOf(future1,
future2).get(5, java.util.concurrent.TimeUnit.SECONDS);
+
+ // Verify both threads cached their own values
+ assertEquals(2, threadValues.size());
+ assertTrue(threadValues.containsValue("thread1-value"));
+ assertTrue(threadValues.containsValue("thread2-value"));
+
+ executor.shutdown();
+ }
+
+ @Test
+ void p03_threadLocal_weakMode_clear() {
+ var x = Cache2.of(String.class, Integer.class, String.class)
+ .threadLocal()
+ .cacheMode(WEAK)
+ .supplier((k1, k2) -> k1 + ":" + k2)
+ .build();
+
+ x.get("user", 123);
+ x.get("admin", 456);
+ assertSize(2, x);
+
+ x.clear();
+ assertEmpty(x);
+ }
+
+ @Test
+ void p04_threadLocal_weakMode_maxSize() {
+ var x = Cache2.of(String.class, Integer.class, String.class)
+ .threadLocal()
+ .cacheMode(WEAK)
+ .maxSize(2)
+ .supplier((k1, k2) -> k1 + ":" + k2)
+ .build();
+
+ x.get("k1", 1);
+ x.get("k2", 2);
+ assertSize(2, x);
+
+ // 3rd item doesn't trigger eviction yet
+ x.get("k3", 3);
+ assertSize(3, x);
+
+ // 4th item triggers eviction
+ x.get("k4", 4);
+ assertSize(1, x);
+ }
}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Cache3_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Cache3_Test.java
index a89408d392..560092ac8d 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Cache3_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Cache3_Test.java
@@ -178,6 +178,46 @@ class Cache3_Test extends TestBase {
assertSize(1, x);
}
+ @Test
+ void a04f_weakMethod_basicCaching() {
+ // Test the weak() convenience method
+ var callCount = new AtomicInteger();
+ var x = Cache3.of(String.class, String.class, Integer.class,
String.class)
+ .weak()
+ .supplier((k1, k2, k3) -> {
+ callCount.incrementAndGet();
+ return k1 + ":" + k2 + ":" + k3;
+ })
+ .build();
+
+ // First call - cache miss
+ var result1 = x.get("en", "US", 1);
+
+ // Second call - cache hit
+ var result2 = x.get("en", "US", 1);
+
+ assertEquals("en:US:1", result1);
+ assertEquals("en:US:1", result2);
+ assertSame(result1, result2);
+ assertEquals(1, callCount.get()); // Supplier only called once
+ assertSize(1, x);
+ assertEquals(1, x.getCacheHits());
+ }
+
+ @Test
+ void a04g_weakMethod_chaining() {
+ // Test that weak() can be chained with other builder methods
+ var x = Cache3.of(String.class, String.class, Integer.class,
String.class)
+ .weak()
+ .maxSize(100)
+ .supplier((k1, k2, k3) -> k1 + ":" + k2 + ":" + k3)
+ .build();
+
+ var result = x.get("en", "US", 1);
+ assertEquals("en:US:1", result);
+ assertSize(1, x);
+ }
+
@Test
void a05_maxSize() {
var x = Cache3.of(String.class, String.class, Integer.class,
String.class)
@@ -316,6 +356,17 @@ class Cache3_Test extends TestBase {
assertFalse(x.containsValue("value2"));
}
+ @Test
+ void d03_containsValue_nullValue() {
+ var x = Cache3.of(String.class, String.class, Integer.class,
String.class).build();
+ // Null values can't be cached, so containsValue(null) should
return false
+ x.get("en", "US", 1, () -> null);
+ assertFalse(x.containsValue(null));
+ // Also test with empty cache
+ var x2 = Cache3.of(String.class, String.class, Integer.class,
String.class).build();
+ assertFalse(x2.containsValue(null));
+ }
+
//====================================================================================================
// e - logOnExit() builder methods
//====================================================================================================
@@ -339,5 +390,218 @@ class Cache3_Test extends TestBase {
x.get("en", "US", 1);
assertSize(1, x);
}
+
+
//====================================================================================================
+ // f - Thread-local cache mode
+
//====================================================================================================
+
+ @Test
+ void f01_threadLocal_basicCaching() {
+ var callCount = new AtomicInteger();
+ var x = Cache3.of(String.class, String.class, Integer.class,
String.class)
+ .threadLocal()
+ .supplier((k1, k2, k3) -> {
+ callCount.incrementAndGet();
+ return k1 + ":" + k2 + ":" + k3;
+ })
+ .build();
+
+ // First call - cache miss
+ var result1 = x.get("en", "US", 1);
+
+ // Second call - cache hit
+ var result2 = x.get("en", "US", 1);
+
+ assertEquals("en:US:1", result1);
+ assertEquals("en:US:1", result2);
+ assertSame(result1, result2);
+ assertEquals(1, callCount.get()); // Supplier only called once
+ assertSize(1, x);
+ assertEquals(1, x.getCacheHits());
+ }
+
+ @Test
+ void f02_threadLocal_eachThreadHasOwnCache() throws Exception {
+ var x = Cache3.of(String.class, String.class, Integer.class,
String.class)
+ .threadLocal()
+ .build();
+ var executor =
java.util.concurrent.Executors.newFixedThreadPool(2);
+ var threadValues = new
java.util.concurrent.ConcurrentHashMap<Thread, String>();
+
+ // Each thread caches ("en", "US", 1) with its own value
+ var future1 =
java.util.concurrent.CompletableFuture.runAsync(() -> {
+ var value = x.get("en", "US", 1, () -> "thread1-value");
+ threadValues.put(Thread.currentThread(), value);
+ }, executor);
+
+ var future2 =
java.util.concurrent.CompletableFuture.runAsync(() -> {
+ var value = x.get("en", "US", 1, () -> "thread2-value");
+ threadValues.put(Thread.currentThread(), value);
+ }, executor);
+
+ java.util.concurrent.CompletableFuture.allOf(future1,
future2).get(5, java.util.concurrent.TimeUnit.SECONDS);
+
+ // Verify both threads cached their own values
+ assertEquals(2, threadValues.size());
+ assertTrue(threadValues.containsValue("thread1-value"));
+ assertTrue(threadValues.containsValue("thread2-value"));
+
+ executor.shutdown();
+ }
+
+ @Test
+ void f03_threadLocal_multipleKeys() {
+ var x = Cache3.of(String.class, String.class, Integer.class,
String.class)
+ .threadLocal()
+ .supplier((k1, k2, k3) -> k1 + ":" + k2 + ":" + k3)
+ .build();
+
+ x.get("en", "US", 1);
+ x.get("fr", "FR", 2);
+ x.get("de", "DE", 3);
+
+ assertSize(3, x);
+ assertEquals(0, x.getCacheHits());
+
+ // Verify all cached
+ assertEquals("en:US:1", x.get("en", "US", 1));
+ assertEquals("fr:FR:2", x.get("fr", "FR", 2));
+ assertEquals("de:DE:3", x.get("de", "DE", 3));
+ assertEquals(3, x.getCacheHits());
+ }
+
+ @Test
+ void f04_threadLocal_clear() {
+ var x = Cache3.of(String.class, String.class, Integer.class,
String.class)
+ .threadLocal()
+ .supplier((k1, k2, k3) -> "value")
+ .build();
+
+ x.get("en", "US", 1);
+ x.get("fr", "FR", 2);
+ assertSize(2, x);
+
+ x.clear();
+ assertEmpty(x);
+ }
+
+ @Test
+ void f05_threadLocal_maxSize() {
+ var x = Cache3.of(String.class, String.class, Integer.class,
String.class)
+ .threadLocal()
+ .maxSize(2)
+ .supplier((k1, k2, k3) -> "value")
+ .build();
+
+ x.get("en", "US", 1);
+ x.get("fr", "FR", 2);
+ assertSize(2, x);
+
+ // 3rd item doesn't trigger eviction yet
+ x.get("de", "DE", 3);
+ assertSize(3, x);
+
+ // 4th item triggers eviction
+ x.get("es", "ES", 4);
+ assertSize(1, x);
+ }
+
+
//====================================================================================================
+ // g - Thread-local + weak mode combination
+
//====================================================================================================
+
+ @Test
+ void g01_threadLocal_weakMode_basicCaching() {
+ var callCount = new AtomicInteger();
+ var x = Cache3.of(String.class, String.class, Integer.class,
String.class)
+ .threadLocal()
+ .cacheMode(WEAK)
+ .supplier((k1, k2, k3) -> {
+ callCount.incrementAndGet();
+ return k1 + ":" + k2 + ":" + k3;
+ })
+ .build();
+
+ // First call - cache miss
+ var result1 = x.get("en", "US", 1);
+
+ // Second call - cache hit
+ var result2 = x.get("en", "US", 1);
+
+ assertEquals("en:US:1", result1);
+ assertEquals("en:US:1", result2);
+ assertSame(result1, result2);
+ assertEquals(1, callCount.get()); // Supplier only called once
+ assertSize(1, x);
+ assertEquals(1, x.getCacheHits());
+ }
+
+ @Test
+ void g02_threadLocal_weakMode_eachThreadHasOwnCache() throws Exception {
+ var x = Cache3.of(String.class, String.class, Integer.class,
String.class)
+ .threadLocal()
+ .cacheMode(WEAK)
+ .build();
+ var executor =
java.util.concurrent.Executors.newFixedThreadPool(2);
+ var threadValues = new
java.util.concurrent.ConcurrentHashMap<Thread, String>();
+
+ // Each thread caches ("en", "US", 1) with its own value
+ var future1 =
java.util.concurrent.CompletableFuture.runAsync(() -> {
+ var value = x.get("en", "US", 1, () -> "thread1-value");
+ threadValues.put(Thread.currentThread(), value);
+ }, executor);
+
+ var future2 =
java.util.concurrent.CompletableFuture.runAsync(() -> {
+ var value = x.get("en", "US", 1, () -> "thread2-value");
+ threadValues.put(Thread.currentThread(), value);
+ }, executor);
+
+ java.util.concurrent.CompletableFuture.allOf(future1,
future2).get(5, java.util.concurrent.TimeUnit.SECONDS);
+
+ // Verify both threads cached their own values
+ assertEquals(2, threadValues.size());
+ assertTrue(threadValues.containsValue("thread1-value"));
+ assertTrue(threadValues.containsValue("thread2-value"));
+
+ executor.shutdown();
+ }
+
+ @Test
+ void g03_threadLocal_weakMode_clear() {
+ var x = Cache3.of(String.class, String.class, Integer.class,
String.class)
+ .threadLocal()
+ .cacheMode(WEAK)
+ .supplier((k1, k2, k3) -> "value")
+ .build();
+
+ x.get("en", "US", 1);
+ x.get("fr", "FR", 2);
+ assertSize(2, x);
+
+ x.clear();
+ assertEmpty(x);
+ }
+
+ @Test
+ void g04_threadLocal_weakMode_maxSize() {
+ var x = Cache3.of(String.class, String.class, Integer.class,
String.class)
+ .threadLocal()
+ .cacheMode(WEAK)
+ .maxSize(2)
+ .supplier((k1, k2, k3) -> "value")
+ .build();
+
+ x.get("en", "US", 1);
+ x.get("fr", "FR", 2);
+ assertSize(2, x);
+
+ // 3rd item doesn't trigger eviction yet
+ x.get("de", "DE", 3);
+ assertSize(3, x);
+
+ // 4th item triggers eviction
+ x.get("es", "ES", 4);
+ assertSize(1, x);
+ }
}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Cache4_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Cache4_Test.java
index 76821a4ec6..b1b2e55259 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Cache4_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Cache4_Test.java
@@ -179,6 +179,46 @@ class Cache4_Test extends TestBase {
assertSize(1, x);
}
+ @Test
+ void a04f_weakMethod_basicCaching() {
+ // Test the weak() convenience method
+ var callCount = new AtomicInteger();
+ var x = Cache4.of(String.class, String.class, String.class,
Integer.class, String.class)
+ .weak()
+ .supplier((k1, k2, k3, k4) -> {
+ callCount.incrementAndGet();
+ return k1 + ":" + k2 + ":" + k3 + ":" + k4;
+ })
+ .build();
+
+ // First call - cache miss
+ var result1 = x.get("en", "US", "formal", 1);
+
+ // Second call - cache hit
+ var result2 = x.get("en", "US", "formal", 1);
+
+ assertEquals("en:US:formal:1", result1);
+ assertEquals("en:US:formal:1", result2);
+ assertSame(result1, result2);
+ assertEquals(1, callCount.get()); // Supplier only called once
+ assertSize(1, x);
+ assertEquals(1, x.getCacheHits());
+ }
+
+ @Test
+ void a04g_weakMethod_chaining() {
+ // Test that weak() can be chained with other builder methods
+ var x = Cache4.of(String.class, String.class, String.class,
Integer.class, String.class)
+ .weak()
+ .maxSize(100)
+ .supplier((k1, k2, k3, k4) -> k1 + ":" + k2 + ":" + k3
+ ":" + k4)
+ .build();
+
+ var result = x.get("en", "US", "formal", 1);
+ assertEquals("en:US:formal:1", result);
+ assertSize(1, x);
+ }
+
@Test
void a05_maxSize() {
var x = Cache4.of(String.class, String.class, String.class,
Integer.class, String.class)
@@ -316,6 +356,17 @@ class Cache4_Test extends TestBase {
assertFalse(x.containsValue("value2"));
}
+ @Test
+ void d03_containsValue_nullValue() {
+ var x = Cache4.of(String.class, String.class, String.class,
Integer.class, String.class).build();
+ // Null values can't be cached, so containsValue(null) should
return false
+ x.get("en", "US", "formal", 1, () -> null);
+ assertFalse(x.containsValue(null));
+ // Also test with empty cache
+ var x2 = Cache4.of(String.class, String.class, String.class,
Integer.class, String.class).build();
+ assertFalse(x2.containsValue(null));
+ }
+
//====================================================================================================
// e - logOnExit() builder methods
//====================================================================================================
@@ -339,5 +390,218 @@ class Cache4_Test extends TestBase {
x.get("en", "US", "formal", 1);
assertSize(1, x);
}
+
+
//====================================================================================================
+ // f - Thread-local cache mode
+
//====================================================================================================
+
+ @Test
+ void f01_threadLocal_basicCaching() {
+ var callCount = new AtomicInteger();
+ var x = Cache4.of(String.class, String.class, String.class,
Integer.class, String.class)
+ .threadLocal()
+ .supplier((k1, k2, k3, k4) -> {
+ callCount.incrementAndGet();
+ return k1 + ":" + k2 + ":" + k3 + ":" + k4;
+ })
+ .build();
+
+ // First call - cache miss
+ var result1 = x.get("en", "US", "formal", 1);
+
+ // Second call - cache hit
+ var result2 = x.get("en", "US", "formal", 1);
+
+ assertEquals("en:US:formal:1", result1);
+ assertEquals("en:US:formal:1", result2);
+ assertSame(result1, result2);
+ assertEquals(1, callCount.get()); // Supplier only called once
+ assertSize(1, x);
+ assertEquals(1, x.getCacheHits());
+ }
+
+ @Test
+ void f02_threadLocal_eachThreadHasOwnCache() throws Exception {
+ var x = Cache4.of(String.class, String.class, String.class,
Integer.class, String.class)
+ .threadLocal()
+ .build();
+ var executor =
java.util.concurrent.Executors.newFixedThreadPool(2);
+ var threadValues = new
java.util.concurrent.ConcurrentHashMap<Thread, String>();
+
+ // Each thread caches ("en", "US", "formal", 1) with its own
value
+ var future1 =
java.util.concurrent.CompletableFuture.runAsync(() -> {
+ var value = x.get("en", "US", "formal", 1, () ->
"thread1-value");
+ threadValues.put(Thread.currentThread(), value);
+ }, executor);
+
+ var future2 =
java.util.concurrent.CompletableFuture.runAsync(() -> {
+ var value = x.get("en", "US", "formal", 1, () ->
"thread2-value");
+ threadValues.put(Thread.currentThread(), value);
+ }, executor);
+
+ java.util.concurrent.CompletableFuture.allOf(future1,
future2).get(5, java.util.concurrent.TimeUnit.SECONDS);
+
+ // Verify both threads cached their own values
+ assertEquals(2, threadValues.size());
+ assertTrue(threadValues.containsValue("thread1-value"));
+ assertTrue(threadValues.containsValue("thread2-value"));
+
+ executor.shutdown();
+ }
+
+ @Test
+ void f03_threadLocal_multipleKeys() {
+ var x = Cache4.of(String.class, String.class, String.class,
Integer.class, String.class)
+ .threadLocal()
+ .supplier((k1, k2, k3, k4) -> k1 + ":" + k2 + ":" + k3
+ ":" + k4)
+ .build();
+
+ x.get("en", "US", "formal", 1);
+ x.get("fr", "FR", "informal", 2);
+ x.get("de", "DE", "formal", 3);
+
+ assertSize(3, x);
+ assertEquals(0, x.getCacheHits());
+
+ // Verify all cached
+ assertEquals("en:US:formal:1", x.get("en", "US", "formal", 1));
+ assertEquals("fr:FR:informal:2", x.get("fr", "FR", "informal",
2));
+ assertEquals("de:DE:formal:3", x.get("de", "DE", "formal", 3));
+ assertEquals(3, x.getCacheHits());
+ }
+
+ @Test
+ void f04_threadLocal_clear() {
+ var x = Cache4.of(String.class, String.class, String.class,
Integer.class, String.class)
+ .threadLocal()
+ .supplier((k1, k2, k3, k4) -> "value")
+ .build();
+
+ x.get("en", "US", "formal", 1);
+ x.get("fr", "FR", "informal", 2);
+ assertSize(2, x);
+
+ x.clear();
+ assertEmpty(x);
+ }
+
+ @Test
+ void f05_threadLocal_maxSize() {
+ var x = Cache4.of(String.class, String.class, String.class,
Integer.class, String.class)
+ .threadLocal()
+ .maxSize(2)
+ .supplier((k1, k2, k3, k4) -> "value")
+ .build();
+
+ x.get("en", "US", "formal", 1);
+ x.get("fr", "FR", "informal", 2);
+ assertSize(2, x);
+
+ // 3rd item doesn't trigger eviction yet
+ x.get("de", "DE", "formal", 3);
+ assertSize(3, x);
+
+ // 4th item triggers eviction
+ x.get("es", "ES", "informal", 4);
+ assertSize(1, x);
+ }
+
+
//====================================================================================================
+ // g - Thread-local + weak mode combination
+
//====================================================================================================
+
+ @Test
+ void g01_threadLocal_weakMode_basicCaching() {
+ var callCount = new AtomicInteger();
+ var x = Cache4.of(String.class, String.class, String.class,
Integer.class, String.class)
+ .threadLocal()
+ .cacheMode(WEAK)
+ .supplier((k1, k2, k3, k4) -> {
+ callCount.incrementAndGet();
+ return k1 + ":" + k2 + ":" + k3 + ":" + k4;
+ })
+ .build();
+
+ // First call - cache miss
+ var result1 = x.get("en", "US", "formal", 1);
+
+ // Second call - cache hit
+ var result2 = x.get("en", "US", "formal", 1);
+
+ assertEquals("en:US:formal:1", result1);
+ assertEquals("en:US:formal:1", result2);
+ assertSame(result1, result2);
+ assertEquals(1, callCount.get()); // Supplier only called once
+ assertSize(1, x);
+ assertEquals(1, x.getCacheHits());
+ }
+
+ @Test
+ void g02_threadLocal_weakMode_eachThreadHasOwnCache() throws Exception {
+ var x = Cache4.of(String.class, String.class, String.class,
Integer.class, String.class)
+ .threadLocal()
+ .cacheMode(WEAK)
+ .build();
+ var executor =
java.util.concurrent.Executors.newFixedThreadPool(2);
+ var threadValues = new
java.util.concurrent.ConcurrentHashMap<Thread, String>();
+
+ // Each thread caches ("en", "US", "formal", 1) with its own
value
+ var future1 =
java.util.concurrent.CompletableFuture.runAsync(() -> {
+ var value = x.get("en", "US", "formal", 1, () ->
"thread1-value");
+ threadValues.put(Thread.currentThread(), value);
+ }, executor);
+
+ var future2 =
java.util.concurrent.CompletableFuture.runAsync(() -> {
+ var value = x.get("en", "US", "formal", 1, () ->
"thread2-value");
+ threadValues.put(Thread.currentThread(), value);
+ }, executor);
+
+ java.util.concurrent.CompletableFuture.allOf(future1,
future2).get(5, java.util.concurrent.TimeUnit.SECONDS);
+
+ // Verify both threads cached their own values
+ assertEquals(2, threadValues.size());
+ assertTrue(threadValues.containsValue("thread1-value"));
+ assertTrue(threadValues.containsValue("thread2-value"));
+
+ executor.shutdown();
+ }
+
+ @Test
+ void g03_threadLocal_weakMode_clear() {
+ var x = Cache4.of(String.class, String.class, String.class,
Integer.class, String.class)
+ .threadLocal()
+ .cacheMode(WEAK)
+ .supplier((k1, k2, k3, k4) -> "value")
+ .build();
+
+ x.get("en", "US", "formal", 1);
+ x.get("fr", "FR", "informal", 2);
+ assertSize(2, x);
+
+ x.clear();
+ assertEmpty(x);
+ }
+
+ @Test
+ void g04_threadLocal_weakMode_maxSize() {
+ var x = Cache4.of(String.class, String.class, String.class,
Integer.class, String.class)
+ .threadLocal()
+ .cacheMode(WEAK)
+ .maxSize(2)
+ .supplier((k1, k2, k3, k4) -> "value")
+ .build();
+
+ x.get("en", "US", "formal", 1);
+ x.get("fr", "FR", "informal", 2);
+ assertSize(2, x);
+
+ // 3rd item doesn't trigger eviction yet
+ x.get("de", "DE", "formal", 3);
+ assertSize(3, x);
+
+ // 4th item triggers eviction
+ x.get("es", "ES", "informal", 4);
+ assertSize(1, x);
+ }
}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Cache5_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Cache5_Test.java
index e74ad86bfb..b0a1bf3eea 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Cache5_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Cache5_Test.java
@@ -180,6 +180,46 @@ class Cache5_Test extends TestBase {
assertSize(1, x);
}
+ @Test
+ void a04f_weakMethod_basicCaching() {
+ // Test the weak() convenience method
+ var callCount = new AtomicInteger();
+ var x = Cache5.of(String.class, String.class, String.class,
String.class, Integer.class, String.class)
+ .weak()
+ .supplier((k1, k2, k3, k4, k5) -> {
+ callCount.incrementAndGet();
+ return k1 + ":" + k2 + ":" + k3 + ":" + k4 +
":" + k5;
+ })
+ .build();
+
+ // First call - cache miss
+ var result1 = x.get("en", "US", "west", "formal", 1);
+
+ // Second call - cache hit
+ var result2 = x.get("en", "US", "west", "formal", 1);
+
+ assertEquals("en:US:west:formal:1", result1);
+ assertEquals("en:US:west:formal:1", result2);
+ assertSame(result1, result2);
+ assertEquals(1, callCount.get()); // Supplier only called once
+ assertSize(1, x);
+ assertEquals(1, x.getCacheHits());
+ }
+
+ @Test
+ void a04g_weakMethod_chaining() {
+ // Test that weak() can be chained with other builder methods
+ var x = Cache5.of(String.class, String.class, String.class,
String.class, Integer.class, String.class)
+ .weak()
+ .maxSize(100)
+ .supplier((k1, k2, k3, k4, k5) -> k1 + ":" + k2 + ":" +
k3 + ":" + k4 + ":" + k5)
+ .build();
+
+ var result = x.get("en", "US", "west", "formal", 1);
+ assertEquals("en:US:west:formal:1", result);
+ assertSize(1, x);
+ }
+
@Test
void a05_maxSize() {
var x = Cache5.of(String.class, String.class, String.class,
String.class, Integer.class, String.class)
@@ -317,6 +357,17 @@ class Cache5_Test extends TestBase {
assertFalse(x.containsValue("value2"));
}
+ @Test
+ void d03_containsValue_nullValue() {
+ var x = Cache5.of(String.class, String.class, String.class,
String.class, Integer.class, String.class).build();
+ // Null values can't be cached, so containsValue(null) should
return false
+ x.get("en", "US", "west", "formal", 1, () -> null);
+ assertFalse(x.containsValue(null));
+ // Also test with empty cache
+ var x2 = Cache5.of(String.class, String.class, String.class,
String.class, Integer.class, String.class).build();
+ assertFalse(x2.containsValue(null));
+ }
+
//====================================================================================================
// e - logOnExit() builder methods
//====================================================================================================
@@ -340,5 +391,218 @@ class Cache5_Test extends TestBase {
x.get("en", "US", "west", "formal", 1);
assertSize(1, x);
}
+
+
//====================================================================================================
+ // f - Thread-local cache mode
+
//====================================================================================================
+
+ @Test
+ void f01_threadLocal_basicCaching() {
+ var callCount = new AtomicInteger();
+ var x = Cache5.of(String.class, String.class, String.class,
String.class, Integer.class, String.class)
+ .threadLocal()
+ .supplier((k1, k2, k3, k4, k5) -> {
+ callCount.incrementAndGet();
+ return k1 + ":" + k2 + ":" + k3 + ":" + k4 +
":" + k5;
+ })
+ .build();
+
+ // First call - cache miss
+ var result1 = x.get("en", "US", "west", "formal", 1);
+
+ // Second call - cache hit
+ var result2 = x.get("en", "US", "west", "formal", 1);
+
+ assertEquals("en:US:west:formal:1", result1);
+ assertEquals("en:US:west:formal:1", result2);
+ assertSame(result1, result2);
+ assertEquals(1, callCount.get()); // Supplier only called once
+ assertSize(1, x);
+ assertEquals(1, x.getCacheHits());
+ }
+
+ @Test
+ void f02_threadLocal_eachThreadHasOwnCache() throws Exception {
+ var x = Cache5.of(String.class, String.class, String.class,
String.class, Integer.class, String.class)
+ .threadLocal()
+ .build();
+ var executor =
java.util.concurrent.Executors.newFixedThreadPool(2);
+ var threadValues = new
java.util.concurrent.ConcurrentHashMap<Thread, String>();
+
+ // Each thread caches ("en", "US", "west", "formal", 1) with
its own value
+ var future1 =
java.util.concurrent.CompletableFuture.runAsync(() -> {
+ var value = x.get("en", "US", "west", "formal", 1, ()
-> "thread1-value");
+ threadValues.put(Thread.currentThread(), value);
+ }, executor);
+
+ var future2 =
java.util.concurrent.CompletableFuture.runAsync(() -> {
+ var value = x.get("en", "US", "west", "formal", 1, ()
-> "thread2-value");
+ threadValues.put(Thread.currentThread(), value);
+ }, executor);
+
+ java.util.concurrent.CompletableFuture.allOf(future1,
future2).get(5, java.util.concurrent.TimeUnit.SECONDS);
+
+ // Verify both threads cached their own values
+ assertEquals(2, threadValues.size());
+ assertTrue(threadValues.containsValue("thread1-value"));
+ assertTrue(threadValues.containsValue("thread2-value"));
+
+ executor.shutdown();
+ }
+
+ @Test
+ void f03_threadLocal_multipleKeys() {
+ var x = Cache5.of(String.class, String.class, String.class,
String.class, Integer.class, String.class)
+ .threadLocal()
+ .supplier((k1, k2, k3, k4, k5) -> k1 + ":" + k2 + ":" +
k3 + ":" + k4 + ":" + k5)
+ .build();
+
+ x.get("en", "US", "west", "formal", 1);
+ x.get("fr", "FR", "east", "informal", 2);
+ x.get("de", "DE", "north", "formal", 3);
+
+ assertSize(3, x);
+ assertEquals(0, x.getCacheHits());
+
+ // Verify all cached
+ assertEquals("en:US:west:formal:1", x.get("en", "US", "west",
"formal", 1));
+ assertEquals("fr:FR:east:informal:2", x.get("fr", "FR", "east",
"informal", 2));
+ assertEquals("de:DE:north:formal:3", x.get("de", "DE", "north",
"formal", 3));
+ assertEquals(3, x.getCacheHits());
+ }
+
+ @Test
+ void f04_threadLocal_clear() {
+ var x = Cache5.of(String.class, String.class, String.class,
String.class, Integer.class, String.class)
+ .threadLocal()
+ .supplier((k1, k2, k3, k4, k5) -> "value")
+ .build();
+
+ x.get("en", "US", "west", "formal", 1);
+ x.get("fr", "FR", "east", "informal", 2);
+ assertSize(2, x);
+
+ x.clear();
+ assertEmpty(x);
+ }
+
+ @Test
+ void f05_threadLocal_maxSize() {
+ var x = Cache5.of(String.class, String.class, String.class,
String.class, Integer.class, String.class)
+ .threadLocal()
+ .maxSize(2)
+ .supplier((k1, k2, k3, k4, k5) -> "value")
+ .build();
+
+ x.get("en", "US", "west", "formal", 1);
+ x.get("fr", "FR", "east", "informal", 2);
+ assertSize(2, x);
+
+ // 3rd item doesn't trigger eviction yet
+ x.get("de", "DE", "north", "formal", 3);
+ assertSize(3, x);
+
+ // 4th item triggers eviction
+ x.get("es", "ES", "south", "informal", 4);
+ assertSize(1, x);
+ }
+
+
//====================================================================================================
+ // g - Thread-local + weak mode combination
+
//====================================================================================================
+
+ @Test
+ void g01_threadLocal_weakMode_basicCaching() {
+ var callCount = new AtomicInteger();
+ var x = Cache5.of(String.class, String.class, String.class,
String.class, Integer.class, String.class)
+ .threadLocal()
+ .cacheMode(WEAK)
+ .supplier((k1, k2, k3, k4, k5) -> {
+ callCount.incrementAndGet();
+ return k1 + ":" + k2 + ":" + k3 + ":" + k4 +
":" + k5;
+ })
+ .build();
+
+ // First call - cache miss
+ var result1 = x.get("en", "US", "west", "formal", 1);
+
+ // Second call - cache hit
+ var result2 = x.get("en", "US", "west", "formal", 1);
+
+ assertEquals("en:US:west:formal:1", result1);
+ assertEquals("en:US:west:formal:1", result2);
+ assertSame(result1, result2);
+ assertEquals(1, callCount.get()); // Supplier only called once
+ assertSize(1, x);
+ assertEquals(1, x.getCacheHits());
+ }
+
+ @Test
+ void g02_threadLocal_weakMode_eachThreadHasOwnCache() throws Exception {
+ var x = Cache5.of(String.class, String.class, String.class,
String.class, Integer.class, String.class)
+ .threadLocal()
+ .cacheMode(WEAK)
+ .build();
+ var executor =
java.util.concurrent.Executors.newFixedThreadPool(2);
+ var threadValues = new
java.util.concurrent.ConcurrentHashMap<Thread, String>();
+
+ // Each thread caches ("en", "US", "west", "formal", 1) with
its own value
+ var future1 =
java.util.concurrent.CompletableFuture.runAsync(() -> {
+ var value = x.get("en", "US", "west", "formal", 1, ()
-> "thread1-value");
+ threadValues.put(Thread.currentThread(), value);
+ }, executor);
+
+ var future2 =
java.util.concurrent.CompletableFuture.runAsync(() -> {
+ var value = x.get("en", "US", "west", "formal", 1, ()
-> "thread2-value");
+ threadValues.put(Thread.currentThread(), value);
+ }, executor);
+
+ java.util.concurrent.CompletableFuture.allOf(future1,
future2).get(5, java.util.concurrent.TimeUnit.SECONDS);
+
+ // Verify both threads cached their own values
+ assertEquals(2, threadValues.size());
+ assertTrue(threadValues.containsValue("thread1-value"));
+ assertTrue(threadValues.containsValue("thread2-value"));
+
+ executor.shutdown();
+ }
+
+ @Test
+ void g03_threadLocal_weakMode_clear() {
+ var x = Cache5.of(String.class, String.class, String.class,
String.class, Integer.class, String.class)
+ .threadLocal()
+ .cacheMode(WEAK)
+ .supplier((k1, k2, k3, k4, k5) -> "value")
+ .build();
+
+ x.get("en", "US", "west", "formal", 1);
+ x.get("fr", "FR", "east", "informal", 2);
+ assertSize(2, x);
+
+ x.clear();
+ assertEmpty(x);
+ }
+
+ @Test
+ void g04_threadLocal_weakMode_maxSize() {
+ var x = Cache5.of(String.class, String.class, String.class,
String.class, Integer.class, String.class)
+ .threadLocal()
+ .cacheMode(WEAK)
+ .maxSize(2)
+ .supplier((k1, k2, k3, k4, k5) -> "value")
+ .build();
+
+ x.get("en", "US", "west", "formal", 1);
+ x.get("fr", "FR", "east", "informal", 2);
+ assertSize(2, x);
+
+ // 3rd item doesn't trigger eviction yet
+ x.get("de", "DE", "north", "formal", 3);
+ assertSize(3, x);
+
+ // 4th item triggers eviction
+ x.get("es", "ES", "south", "informal", 4);
+ assertSize(1, x);
+ }
}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Cache_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Cache_Test.java
index d6c237b050..04d7a155b3 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Cache_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Cache_Test.java
@@ -332,6 +332,46 @@ class Cache_Test extends TestBase {
assertSize(1, cache);
}
+ @Test void a16b_weakMethod_basicCaching() {
+ // Test the weak() convenience method
+ var cache = Cache.of(String.class, String.class)
+ .weak()
+ .build();
+ var callCount = new AtomicInteger();
+
+ // First call - cache miss
+ var result1 = cache.get("key1", () -> {
+ callCount.incrementAndGet();
+ return "value1";
+ });
+
+ // Second call - cache hit
+ var result2 = cache.get("key1", () -> {
+ callCount.incrementAndGet();
+ return "should not be called";
+ });
+
+ assertEquals("value1", result1);
+ assertEquals("value1", result2);
+ assertSame(result1, result2);
+ assertEquals(1, callCount.get()); // Supplier only called once
+ assertSize(1, cache);
+ assertEquals(1, cache.getCacheHits());
+ }
+
+ @Test void a16c_weakMethod_chaining() {
+ // Test that weak() can be chained with other builder methods
+ var cache = Cache.of(String.class, Integer.class)
+ .weak()
+ .maxSize(100)
+ .supplier(k -> k.length())
+ .build();
+
+ var result = cache.get("hello");
+ assertEquals(5, result);
+ assertSize(1, cache);
+ }
+
//====================================================================================================
// Builder configuration
//====================================================================================================
@@ -925,5 +965,255 @@ class Cache_Test extends TestBase {
assertTrue(cache.isEmpty());
assertEquals(0, cache.getCacheHits());
}
+
+
//====================================================================================================
+ // Thread-local cache mode
+
//====================================================================================================
+
+ @Test void a54_threadLocal_basicCaching() throws Exception {
+ var cache = Cache.of(String.class, String.class)
+ .threadLocal()
+ .build();
+ var callCount = new AtomicInteger();
+
+ // First call - cache miss
+ var result1 = cache.get("key1", () -> {
+ callCount.incrementAndGet();
+ return "value1";
+ });
+
+ // Second call - cache hit
+ var result2 = cache.get("key1", () -> {
+ callCount.incrementAndGet();
+ return "should not be called";
+ });
+
+ assertEquals("value1", result1);
+ assertEquals("value1", result2);
+ assertSame(result1, result2);
+ assertEquals(1, callCount.get()); // Supplier only called once
+ assertSize(1, cache);
+ assertEquals(1, cache.getCacheHits());
+ }
+
+ @Test void a55_threadLocal_eachThreadHasOwnCache() throws Exception {
+ var cache = Cache.of(String.class, String.class)
+ .threadLocal()
+ .build();
+ var executor = Executors.newFixedThreadPool(2);
+ var threadValues = new ConcurrentHashMap<Thread, String>();
+
+ // Each thread caches "key1" with its own value
+ var future1 = CompletableFuture.runAsync(() -> {
+ var value = cache.get("key1", () -> "thread1-value");
+ threadValues.put(Thread.currentThread(), value);
+ }, executor);
+
+ var future2 = CompletableFuture.runAsync(() -> {
+ var value = cache.get("key1", () -> "thread2-value");
+ threadValues.put(Thread.currentThread(), value);
+ }, executor);
+
+ CompletableFuture.allOf(future1, future2).get(5,
TimeUnit.SECONDS);
+
+ // Verify both threads cached their own values
+ assertEquals(2, threadValues.size());
+ assertTrue(threadValues.containsValue("thread1-value"));
+ assertTrue(threadValues.containsValue("thread2-value"));
+
+ // Verify each thread's cache is independent - same thread
should get same cached value
+ var threadValues2 = new ConcurrentHashMap<Thread, String>();
+ var threads = new
java.util.ArrayList<Thread>(threadValues.keySet());
+
+ future1 = CompletableFuture.runAsync(() -> {
+ var value = cache.get("key1", () ->
"should-not-be-called");
+ threadValues2.put(Thread.currentThread(), value);
+ }, executor);
+
+ future2 = CompletableFuture.runAsync(() -> {
+ var value = cache.get("key1", () ->
"should-not-be-called");
+ threadValues2.put(Thread.currentThread(), value);
+ }, executor);
+
+ CompletableFuture.allOf(future1, future2).get(5,
TimeUnit.SECONDS);
+
+ // Each thread should get its own cached value (same as what it
cached before)
+ for (var thread : threads) {
+ if (threadValues2.containsKey(thread)) {
+ assertEquals(threadValues.get(thread),
threadValues2.get(thread),
+ "Thread " + thread + " should get its
own cached value");
+ }
+ }
+
+ executor.shutdown();
+ }
+
+ @Test void a56_threadLocal_multipleKeys() {
+ var cache = Cache.of(String.class, Integer.class)
+ .threadLocal()
+ .build();
+
+ cache.get("one", () -> 1);
+ cache.get("two", () -> 2);
+ cache.get("three", () -> 3);
+
+ assertSize(3, cache);
+ assertEquals(0, cache.getCacheHits());
+
+ // Verify all cached
+ assertEquals(1, cache.get("one", () -> 999));
+ assertEquals(2, cache.get("two", () -> 999));
+ assertEquals(3, cache.get("three", () -> 999));
+ assertEquals(3, cache.getCacheHits());
+ }
+
+ @Test void a57_threadLocal_clear() {
+ var cache = Cache.of(String.class, Integer.class)
+ .threadLocal()
+ .build();
+
+ cache.get("one", () -> 1);
+ cache.get("two", () -> 2);
+ assertSize(2, cache);
+
+ cache.clear();
+ assertEmpty(cache);
+ }
+
+ @Test void a58_threadLocal_maxSize() {
+ var cache = Cache.of(String.class, Integer.class)
+ .threadLocal()
+ .maxSize(3)
+ .build();
+
+ cache.get("one", () -> 1);
+ cache.get("two", () -> 2);
+ cache.get("three", () -> 3);
+ assertSize(3, cache);
+
+ // 4th item doesn't trigger eviction yet
+ cache.get("four", () -> 4);
+ assertSize(4, cache);
+
+ // 5th item triggers eviction
+ cache.get("five", () -> 5);
+ assertSize(1, cache);
+ }
+
+ @Test void a59_threadLocal_cacheHits() {
+ var cache = Cache.of(String.class, Integer.class)
+ .threadLocal()
+ .build();
+
+ assertEquals(0, cache.getCacheHits());
+
+ cache.get("one", () -> 1); // Miss
+ assertEquals(0, cache.getCacheHits());
+
+ cache.get("one", () -> 999); // Hit
+ assertEquals(1, cache.getCacheHits());
+
+ cache.get("two", () -> 2); // Miss
+ assertEquals(1, cache.getCacheHits());
+
+ cache.get("one", () -> 999); // Hit
+ cache.get("two", () -> 999); // Hit
+ assertEquals(3, cache.getCacheHits());
+ }
+
+
//====================================================================================================
+ // Thread-local + weak mode combination
+
//====================================================================================================
+
+ @Test void a60_threadLocal_weakMode_basicCaching() {
+ var cache = Cache.of(String.class, String.class)
+ .threadLocal()
+ .cacheMode(WEAK)
+ .build();
+ var callCount = new AtomicInteger();
+
+ // First call - cache miss
+ var result1 = cache.get("key1", () -> {
+ callCount.incrementAndGet();
+ return "value1";
+ });
+
+ // Second call - cache hit
+ var result2 = cache.get("key1", () -> {
+ callCount.incrementAndGet();
+ return "should not be called";
+ });
+
+ assertEquals("value1", result1);
+ assertEquals("value1", result2);
+ assertSame(result1, result2);
+ assertEquals(1, callCount.get()); // Supplier only called once
+ assertSize(1, cache);
+ assertEquals(1, cache.getCacheHits());
+ }
+
+ @Test void a61_threadLocal_weakMode_eachThreadHasOwnCache() throws
Exception {
+ var cache = Cache.of(String.class, String.class)
+ .threadLocal()
+ .cacheMode(WEAK)
+ .build();
+ var executor = Executors.newFixedThreadPool(2);
+ var threadValues = new ConcurrentHashMap<Thread, String>();
+
+ // Each thread caches "key1" with its own value
+ var future1 = CompletableFuture.runAsync(() -> {
+ var value = cache.get("key1", () -> "thread1-value");
+ threadValues.put(Thread.currentThread(), value);
+ }, executor);
+
+ var future2 = CompletableFuture.runAsync(() -> {
+ var value = cache.get("key1", () -> "thread2-value");
+ threadValues.put(Thread.currentThread(), value);
+ }, executor);
+
+ CompletableFuture.allOf(future1, future2).get(5,
TimeUnit.SECONDS);
+
+ // Verify both threads cached their own values
+ assertEquals(2, threadValues.size());
+ assertTrue(threadValues.containsValue("thread1-value"));
+ assertTrue(threadValues.containsValue("thread2-value"));
+
+ executor.shutdown();
+ }
+
+ @Test void a62_threadLocal_weakMode_clear() {
+ var cache = Cache.of(String.class, Integer.class)
+ .threadLocal()
+ .cacheMode(WEAK)
+ .build();
+
+ cache.get("one", () -> 1);
+ cache.get("two", () -> 2);
+ assertSize(2, cache);
+
+ cache.clear();
+ assertEmpty(cache);
+ }
+
+ @Test void a63_threadLocal_weakMode_maxSize() {
+ var cache = Cache.of(String.class, Integer.class)
+ .threadLocal()
+ .cacheMode(WEAK)
+ .maxSize(3)
+ .build();
+
+ cache.get("one", () -> 1);
+ cache.get("two", () -> 2);
+ cache.get("three", () -> 3);
+ assertSize(3, cache);
+
+ // 4th item doesn't trigger eviction yet
+ cache.get("four", () -> 4);
+ assertSize(4, cache);
+
+ // 5th item triggers eviction
+ cache.get("five", () -> 5);
+ assertSize(1, cache);
+ }
}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/AnnotationInfoTest.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/AnnotationInfoTest.java
deleted file mode 100644
index a61c82fe0f..0000000000
---
a/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/AnnotationInfoTest.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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.commons.reflect;
-
-import static java.lang.annotation.ElementType.*;
-import static java.lang.annotation.RetentionPolicy.*;
-import static org.apache.juneau.commons.utils.CollectionUtils.*;
-import static org.apache.juneau.junit.bct.BctAssertions.*;
-
-import java.lang.annotation.*;
-import org.apache.juneau.*;
-import org.apache.juneau.commons.annotation.*;
-import org.junit.jupiter.api.*;
-
-class AnnotationInfoTest extends TestBase {
-
-
//-----------------------------------------------------------------------------------------------------------------
- // Is in group.
-
//-----------------------------------------------------------------------------------------------------------------
-
- @Target(TYPE)
- @Retention(RUNTIME)
- @AnnotationGroup(D1.class)
- public static @interface D1 {}
-
- @Target(TYPE)
- @Retention(RUNTIME)
- @AnnotationGroup(D1.class)
- public static @interface D2 {}
-
- @Target(TYPE)
- @Retention(RUNTIME)
- public static @interface D3 {}
-
- @D1 @D2 @D3
- public static class D {}
-
- @Test void d01_isInGroup() {
- var d = ClassInfo.of(D.class);
- var l = rstream(d.getAnnotations()).filter(x ->
x.isInGroup(D1.class));
- assertSize(2, l);
- }
-}
\ No newline at end of file
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/AnnotationInfo_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/AnnotationInfo_Test.java
new file mode 100644
index 0000000000..fe27bf4730
--- /dev/null
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/AnnotationInfo_Test.java
@@ -0,0 +1,706 @@
+/*
+ * 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.commons.reflect;
+
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.*;
+import static org.apache.juneau.commons.utils.CollectionUtils.*;
+import static org.apache.juneau.junit.bct.BctAssertions.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.lang.annotation.*;
+import org.apache.juneau.*;
+import org.apache.juneau.commons.annotation.*;
+import org.junit.jupiter.api.*;
+
+class AnnotationInfo_Test extends TestBase {
+
+
//====================================================================================================
+ // Test annotations and classes
+
//====================================================================================================
+
+ @Target(TYPE)
+ @Retention(RUNTIME)
+ public static @interface TestAnnotation {
+ String value() default "default";
+ }
+
+ @Target(TYPE)
+ @Retention(RUNTIME)
+ public static @interface MultiTypeAnnotation {
+ String stringValue() default "default";
+
+ int intValue() default 0;
+
+ boolean boolValue() default true;
+
+ long longValue() default 100L;
+
+ double doubleValue() default 3.14;
+
+ float floatValue() default 2.5f;
+
+ Class<?> classValue() default String.class;
+
+ String[] stringArray() default { "a", "b" };
+
+ Class<?>[] classArray() default { String.class, Integer.class };
+ }
+
+ @Target(TYPE)
+ @Retention(RUNTIME)
+ public static @interface RankedAnnotation {
+ int rank() default 0;
+ }
+
+ @Target(TYPE)
+ @Retention(RUNTIME)
+ public static @interface UnrankedAnnotation {
+ String value() default "";
+ }
+
+ @Target(TYPE)
+ @Retention(RUNTIME)
+ public static @interface ClassArrayAnnotation {
+ Class<?>[] classes() default {};
+ }
+
+ @Target(TYPE)
+ @Retention(RUNTIME)
+ public static @interface ClassValueAnnotation {
+ Class<?> value() default String.class;
+ }
+
+ @Target(TYPE)
+ @Retention(RUNTIME)
+ @Documented
+ public static @interface DocumentedAnnotation {}
+
+ @Target(TYPE)
+ @Retention(RUNTIME)
+ @AnnotationGroup(GroupAnnotation.class)
+ public static @interface GroupAnnotation {}
+
+ @Target(TYPE)
+ @Retention(RUNTIME)
+ @AnnotationGroup(GroupAnnotation.class)
+ public static @interface GroupMember1 {}
+
+ @Target(TYPE)
+ @Retention(RUNTIME)
+ @AnnotationGroup(GroupAnnotation.class)
+ public static @interface GroupMember2 {}
+
+ @Target(TYPE)
+ @Retention(RUNTIME)
+ public static @interface NotInGroup {}
+
+ @TestAnnotation("test")
+ public static class TestClass {}
+
+ @MultiTypeAnnotation(stringValue = "test", intValue = 123, boolValue =
false, longValue = 999L, doubleValue = 1.23, floatValue = 4.56f, classValue =
Integer.class, stringArray = { "x", "y",
+ "z" }, classArray = { Long.class, Double.class })
+ public static class MultiTypeClass {}
+
+ @RankedAnnotation(rank = 5)
+ public static class RankedClass {}
+
+ @UnrankedAnnotation
+ public static class UnrankedClass {}
+
+ @ClassArrayAnnotation(classes = { String.class, Integer.class })
+ public static class ClassArrayClass {}
+
+ @ClassValueAnnotation(Integer.class)
+ public static class ClassValueClass {}
+
+ @DocumentedAnnotation
+ public static class DocumentedClass {}
+
+ @GroupMember1
+ @GroupMember2
+ @NotInGroup
+ public static class GroupTestClass {}
+
+
//====================================================================================================
+ // of() - Static factory method
+
//====================================================================================================
+
+ @Test
+ void a01_of_createsAnnotationInfo() {
+ var ci = ClassInfo.of(TestClass.class);
+ var annotation = ci.inner().getAnnotation(TestAnnotation.class);
+ var ai = AnnotationInfo.of(ci, annotation);
+
+ assertNotNull(ai);
+ assertEquals(TestAnnotation.class, ai.annotationType());
+ assertEquals("test", ai.getValue().orElse(null));
+ }
+
+ @Test
+ void a02_of_withNullAnnotation_throwsException() {
+ var ci = ClassInfo.of(TestClass.class);
+ assertThrows(IllegalArgumentException.class, () ->
AnnotationInfo.of(ci, null));
+ }
+
+
//====================================================================================================
+ // annotationType()
+
//====================================================================================================
+
+ @Test
+ void b01_annotationType_returnsCorrectType() {
+ var ci = ClassInfo.of(TestClass.class);
+ var ai =
ci.getAnnotations(TestAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+ assertEquals(TestAnnotation.class, ai.annotationType());
+ }
+
+
//====================================================================================================
+ // cast()
+
//====================================================================================================
+
+ @Test
+ void c01_cast_sameType_returnsThis() {
+ var ci = ClassInfo.of(TestClass.class);
+ var ai =
ci.getAnnotations(TestAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+
+ var casted = ai.cast(TestAnnotation.class);
+ assertNotNull(casted);
+ assertSame(ai, casted);
+ }
+
+ @Test
+ void c02_cast_differentType_returnsNull() {
+ var ci = ClassInfo.of(TestClass.class);
+ var ai =
ci.getAnnotations(TestAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+
+ var casted = ai.cast(Deprecated.class);
+ assertNull(casted);
+ }
+
+
//====================================================================================================
+ // equals() and hashCode()
+
//====================================================================================================
+
+ @Test
+ void d01_equals_sameAnnotation_returnsTrue() {
+ var ci = ClassInfo.of(TestClass.class);
+ var ai1 =
ci.getAnnotations(TestAnnotation.class).findFirst().orElse(null);
+ var ai2 =
ci.getAnnotations(TestAnnotation.class).findFirst().orElse(null);
+
+ assertNotNull(ai1);
+ assertNotNull(ai2);
+ assertEquals(ai1, ai2);
+ assertEquals(ai1.hashCode(), ai2.hashCode());
+ }
+
+ @Test
+ void d02_equals_differentAnnotation_returnsFalse() {
+ var ci = ClassInfo.of(TestClass.class);
+ var ai1 =
ci.getAnnotations(TestAnnotation.class).findFirst().orElse(null);
+
+ @Deprecated
+ class DeprecatedClass {}
+ var ci2 = ClassInfo.of(DeprecatedClass.class);
+ var ai2 =
ci2.getAnnotations(Deprecated.class).findFirst().orElse(null);
+
+ assertNotNull(ai1);
+ assertNotNull(ai2);
+ assertNotEquals(ai1, ai2);
+ }
+
+ @Test
+ void d03_equals_withAnnotationInfo_comparesAnnotations() {
+ var ci = ClassInfo.of(TestClass.class);
+ var ai1 =
ci.getAnnotations(TestAnnotation.class).findFirst().orElse(null);
+ var ai2 = AnnotationInfo.of(ci,
ci.inner().getAnnotation(TestAnnotation.class));
+
+ assertNotNull(ai1);
+ assertEquals(ai1, ai2);
+ }
+
+ @Test
+ void d04_equals_withAnnotation_comparesAnnotations() {
+ var ci = ClassInfo.of(TestClass.class);
+ var ai =
ci.getAnnotations(TestAnnotation.class).findFirst().orElse(null);
+ var annotation = ci.inner().getAnnotation(TestAnnotation.class);
+
+ assertNotNull(ai);
+ assertEquals(ai, annotation);
+ }
+
+
//====================================================================================================
+ // getName()
+
//====================================================================================================
+
+ @Test
+ void e01_getName_returnsSimpleName() {
+ var ci = ClassInfo.of(TestClass.class);
+ var ai =
ci.getAnnotations(TestAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+ assertEquals("TestAnnotation", ai.getName());
+ }
+
+
//====================================================================================================
+ // getRank()
+
//====================================================================================================
+
+ @Test
+ void f01_getRank_withRankMethod_returnsRank() {
+ var ci = ClassInfo.of(RankedClass.class);
+ var ai =
ci.getAnnotations(RankedAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+ assertEquals(5, ai.getRank());
+ }
+
+ @Test
+ void f02_getRank_withoutRankMethod_returnsZero() {
+ var ci = ClassInfo.of(UnrankedClass.class);
+ var ai =
ci.getAnnotations(UnrankedAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+ assertEquals(0, ai.getRank());
+ }
+
+
//====================================================================================================
+ // getMethod()
+
//====================================================================================================
+
+ @Test
+ void g01_getMethod_existingMethod_returnsMethodInfo() {
+ var ci = ClassInfo.of(TestClass.class);
+ var ai =
ci.getAnnotations(TestAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+
+ var method = ai.getMethod("value");
+ assertTrue(method.isPresent());
+ assertEquals("value", method.get().getSimpleName());
+ }
+
+ @Test
+ void g02_getMethod_nonexistentMethod_returnsEmpty() {
+ var ci = ClassInfo.of(TestClass.class);
+ var ai =
ci.getAnnotations(TestAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+
+ var method = ai.getMethod("nonexistent");
+ assertFalse(method.isPresent());
+ }
+
+
//====================================================================================================
+ // getReturnType()
+
//====================================================================================================
+
+ @Test
+ void h01_getReturnType_returnsCorrectType() {
+ var ci = ClassInfo.of(MultiTypeClass.class);
+ var ai =
ci.getAnnotations(MultiTypeAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+
+ assertTrue(ai.getReturnType("stringValue").isPresent());
+ assertEquals(String.class,
ai.getReturnType("stringValue").get().inner());
+ assertEquals(int.class,
ai.getReturnType("intValue").get().inner());
+ assertEquals(boolean.class,
ai.getReturnType("boolValue").get().inner());
+ assertEquals(long.class,
ai.getReturnType("longValue").get().inner());
+ assertEquals(double.class,
ai.getReturnType("doubleValue").get().inner());
+ assertEquals(float.class,
ai.getReturnType("floatValue").get().inner());
+ assertEquals(Class.class,
ai.getReturnType("classValue").get().inner());
+ assertEquals(String[].class,
ai.getReturnType("stringArray").get().inner());
+ assertEquals(Class[].class,
ai.getReturnType("classArray").get().inner());
+ }
+
+ @Test
+ void h02_getReturnType_nonexistentMethod_returnsEmpty() {
+ var ci = ClassInfo.of(MultiTypeClass.class);
+ var ai =
ci.getAnnotations(MultiTypeAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+
+ assertFalse(ai.getReturnType("nonexistent").isPresent());
+ }
+
+
//====================================================================================================
+ // Value retrieval methods - getString(), getInt(), getBoolean(), etc.
+
//====================================================================================================
+
+ @Test
+ void i01_getString_returnsStringValue() {
+ var ci = ClassInfo.of(MultiTypeClass.class);
+ var ai =
ci.getAnnotations(MultiTypeAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+
+ assertTrue(ai.getString("stringValue").isPresent());
+ assertEquals("test", ai.getString("stringValue").get());
+ assertFalse(ai.getString("nonexistent").isPresent());
+ }
+
+ @Test
+ void i02_getInt_returnsIntValue() {
+ var ci = ClassInfo.of(MultiTypeClass.class);
+ var ai =
ci.getAnnotations(MultiTypeAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+
+ assertTrue(ai.getInt("intValue").isPresent());
+ assertEquals(123, ai.getInt("intValue").get());
+ assertFalse(ai.getInt("nonexistent").isPresent());
+ }
+
+ @Test
+ void i03_getBoolean_returnsBooleanValue() {
+ var ci = ClassInfo.of(MultiTypeClass.class);
+ var ai =
ci.getAnnotations(MultiTypeAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+
+ assertTrue(ai.getBoolean("boolValue").isPresent());
+ assertEquals(false, ai.getBoolean("boolValue").get());
+ assertFalse(ai.getBoolean("nonexistent").isPresent());
+ }
+
+ @Test
+ void i04_getLong_returnsLongValue() {
+ var ci = ClassInfo.of(MultiTypeClass.class);
+ var ai =
ci.getAnnotations(MultiTypeAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+
+ assertTrue(ai.getLong("longValue").isPresent());
+ assertEquals(999L, ai.getLong("longValue").get());
+ assertFalse(ai.getLong("nonexistent").isPresent());
+ }
+
+ @Test
+ void i05_getDouble_returnsDoubleValue() {
+ var ci = ClassInfo.of(MultiTypeClass.class);
+ var ai =
ci.getAnnotations(MultiTypeAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+
+ assertTrue(ai.getDouble("doubleValue").isPresent());
+ assertEquals(1.23, ai.getDouble("doubleValue").get(), 0.001);
+ assertFalse(ai.getDouble("nonexistent").isPresent());
+ }
+
+ @Test
+ void i06_getFloat_returnsFloatValue() {
+ var ci = ClassInfo.of(MultiTypeClass.class);
+ var ai =
ci.getAnnotations(MultiTypeAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+
+ assertTrue(ai.getFloat("floatValue").isPresent());
+ assertEquals(4.56f, ai.getFloat("floatValue").get(), 0.001);
+ assertFalse(ai.getFloat("nonexistent").isPresent());
+ }
+
+ @Test
+ void i07_getClassValue_returnsClassValue() {
+ var ci = ClassInfo.of(MultiTypeClass.class);
+ var ai =
ci.getAnnotations(MultiTypeAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+
+ assertTrue(ai.getClassValue("classValue").isPresent());
+ assertEquals(Integer.class,
ai.getClassValue("classValue").get());
+ assertFalse(ai.getClassValue("nonexistent").isPresent());
+ }
+
+ @Test
+ void i08_getStringArray_returnsStringArray() {
+ var ci = ClassInfo.of(MultiTypeClass.class);
+ var ai =
ci.getAnnotations(MultiTypeAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+
+ assertTrue(ai.getStringArray("stringArray").isPresent());
+ var array = ai.getStringArray("stringArray").get();
+ assertNotNull(array);
+ assertEquals(3, array.length);
+ assertEquals("x", array[0]);
+ assertEquals("y", array[1]);
+ assertEquals("z", array[2]);
+ assertFalse(ai.getStringArray("nonexistent").isPresent());
+ }
+
+ @Test
+ void i09_getClassArray_returnsClassArray() {
+ var ci = ClassInfo.of(MultiTypeClass.class);
+ var ai =
ci.getAnnotations(MultiTypeAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+
+ assertTrue(ai.getClassArray("classArray").isPresent());
+ var array = ai.getClassArray("classArray").get();
+ assertNotNull(array);
+ assertEquals(2, array.length);
+ assertEquals(Long.class, array[0]);
+ assertEquals(Double.class, array[1]);
+ assertFalse(ai.getClassArray("nonexistent").isPresent());
+ }
+
+
//====================================================================================================
+ // getValue() - Convenience method
+
//====================================================================================================
+
+ @Test
+ void j01_getValue_returnsValueMethod() {
+ var ci = ClassInfo.of(TestClass.class);
+ var ai =
ci.getAnnotations(TestAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+
+ var value = ai.getValue();
+ assertTrue(value.isPresent());
+ assertEquals("test", value.get());
+ }
+
+
//====================================================================================================
+ // getValue(Class<V> type, String name)
+
//====================================================================================================
+
+ @Test
+ void k01_getValue_withType_returnsTypedValue() {
+ var ci = ClassInfo.of(MultiTypeClass.class);
+ var ai =
ci.getAnnotations(MultiTypeAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+
+ var stringValue = ai.getValue(String.class, "stringValue");
+ assertTrue(stringValue.isPresent());
+ assertEquals("test", stringValue.get());
+
+ // intValue returns int.class, not Integer.class
+ var intValue = ai.getValue(int.class, "intValue");
+ assertTrue(intValue.isPresent());
+ assertEquals(123, intValue.get());
+ }
+
+ @Test
+ void k02_getValue_wrongType_returnsEmpty() {
+ var ci = ClassInfo.of(MultiTypeClass.class);
+ var ai =
ci.getAnnotations(MultiTypeAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+
+ // Try to get stringValue as Integer
+ var intValue = ai.getValue(Integer.class, "stringValue");
+ assertFalse(intValue.isPresent());
+ }
+
+
//====================================================================================================
+ // getClassArray(String, Class<T>)
+
//====================================================================================================
+
+ @Test
+ void l01_getClassArray_typed_returnsTypedArray() {
+ var ci = ClassInfo.of(ClassArrayClass.class);
+ var ai =
ci.getAnnotations(ClassArrayAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+
+ // Both String and Integer are assignable to Object
+ var classes = ai.getClassArray("classes", Object.class);
+ assertTrue(classes.isPresent());
+ var array = classes.get();
+ assertEquals(2, array.length);
+ assertEquals(String.class, array[0]);
+ assertEquals(Integer.class, array[1]);
+ }
+
+ @Test
+ void l02_getClassArray_typed_notAssignable_returnsEmpty() {
+ var ci = ClassInfo.of(ClassArrayClass.class);
+ var ai =
ci.getAnnotations(ClassArrayAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+
+ // String and Integer are not assignable to Exception
+ var classes = ai.getClassArray("classes", Exception.class);
+ assertFalse(classes.isPresent());
+ }
+
+
//====================================================================================================
+ // getClassValue(String, Class<T>)
+
//====================================================================================================
+
+ @Test
+ void m01_getClassValue_typed_returnsTypedClass() {
+ var ci = ClassInfo.of(ClassValueClass.class);
+ var ai =
ci.getAnnotations(ClassValueAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+
+ var numberClass = ai.getClassValue("value", Number.class);
+ assertTrue(numberClass.isPresent());
+ assertEquals(Integer.class, numberClass.get());
+ }
+
+ @Test
+ void m02_getClassValue_typed_notAssignable_returnsEmpty() {
+ var ci = ClassInfo.of(ClassValueClass.class);
+ var ai =
ci.getAnnotations(ClassValueAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+
+ // Integer is not assignable to Exception
+ var exceptionClass = ai.getClassValue("value", Exception.class);
+ assertFalse(exceptionClass.isPresent());
+ }
+
+
//====================================================================================================
+ // hasName() and hasSimpleName()
+
//====================================================================================================
+
+ @Test
+ void n01_hasName_returnsTrueForFullyQualifiedName() {
+ var ci = ClassInfo.of(TestClass.class);
+ var ai =
ci.getAnnotations(TestAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+
+ var fullyQualifiedName = TestAnnotation.class.getName();
+ assertTrue(ai.hasName(fullyQualifiedName));
+ assertFalse(ai.hasName("TestAnnotation"));
+ }
+
+ @Test
+ void n02_hasSimpleName_returnsTrueForSimpleName() {
+ var ci = ClassInfo.of(TestClass.class);
+ var ai =
ci.getAnnotations(TestAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+
+ assertTrue(ai.hasSimpleName("TestAnnotation"));
+ assertFalse(ai.hasSimpleName(TestAnnotation.class.getName()));
+ }
+
+
//====================================================================================================
+ // hasAnnotation()
+
//====================================================================================================
+
+ @Test
+ void o01_hasAnnotation_withMetaAnnotation_returnsTrue() {
+ var ci = ClassInfo.of(DocumentedClass.class);
+ var ai =
ci.getAnnotations(DocumentedAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+
+ assertTrue(ai.hasAnnotation(Documented.class));
+ }
+
+ @Test
+ void o02_hasAnnotation_withoutMetaAnnotation_returnsFalse() {
+ var ci = ClassInfo.of(TestClass.class);
+ var ai =
ci.getAnnotations(TestAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+
+ assertFalse(ai.hasAnnotation(Documented.class));
+ }
+
+
//====================================================================================================
+ // inner()
+
//====================================================================================================
+
+ @Test
+ void p01_inner_returnsWrappedAnnotation() {
+ var ci = ClassInfo.of(TestClass.class);
+ var ai =
ci.getAnnotations(TestAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+
+ var annotation = ai.inner();
+ assertNotNull(annotation);
+ assertEquals(TestAnnotation.class, annotation.annotationType());
+ assertEquals("test", annotation.value());
+ }
+
+
//====================================================================================================
+ // isInGroup()
+
//====================================================================================================
+
+ @Test
+ void q01_isInGroup_returnsTrueForGroupMembers() {
+ var ci = ClassInfo.of(GroupTestClass.class);
+ var groupMember1 =
ci.getAnnotations(GroupMember1.class).findFirst().orElse(null);
+ var groupMember2 =
ci.getAnnotations(GroupMember2.class).findFirst().orElse(null);
+ var notInGroup =
ci.getAnnotations(NotInGroup.class).findFirst().orElse(null);
+
+ assertNotNull(groupMember1);
+ assertNotNull(groupMember2);
+ assertNotNull(notInGroup);
+
+ assertTrue(groupMember1.isInGroup(GroupAnnotation.class));
+ assertTrue(groupMember2.isInGroup(GroupAnnotation.class));
+ assertFalse(notInGroup.isInGroup(GroupAnnotation.class));
+ }
+
+
//====================================================================================================
+ // isType()
+
//====================================================================================================
+
+ @Test
+ void r01_isType_sameType_returnsTrue() {
+ var ci = ClassInfo.of(TestClass.class);
+ var ai =
ci.getAnnotations(TestAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+
+ assertTrue(ai.isType(TestAnnotation.class));
+ }
+
+ @Test
+ void r02_isType_differentType_returnsFalse() {
+ var ci = ClassInfo.of(TestClass.class);
+ var ai =
ci.getAnnotations(TestAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+
+ assertFalse(ai.isType(Deprecated.class));
+ }
+
+
//====================================================================================================
+ // toMap()
+
//====================================================================================================
+
+ @Test
+ void s01_toMap_returnsMapRepresentation() {
+ var ci = ClassInfo.of(TestClass.class);
+ var ai =
ci.getAnnotations(TestAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+
+ var map = ai.toMap();
+ assertNotNull(map);
+ assertTrue(map.containsKey("CLASS_TYPE"));
+ assertTrue(map.containsKey("@TestAnnotation"));
+
+ var annotationMap =
(java.util.Map<String,Object>)map.get("@TestAnnotation");
+ assertNotNull(annotationMap);
+ assertEquals("test", annotationMap.get("value"));
+ }
+
+
//====================================================================================================
+ // toSimpleString()
+
//====================================================================================================
+
+ @Test
+ void t01_toSimpleString_returnsFormattedString() {
+ var ci = ClassInfo.of(TestClass.class);
+ var ai =
ci.getAnnotations(TestAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+
+ var str = ai.toSimpleString();
+ assertNotNull(str);
+ assertTrue(str.contains("@TestAnnotation"));
+ assertTrue(str.contains("on="));
+ }
+
+
//====================================================================================================
+ // toString()
+
//====================================================================================================
+
+ @Test
+ void u01_toString_returnsStringRepresentation() {
+ var ci = ClassInfo.of(TestClass.class);
+ var ai =
ci.getAnnotations(TestAnnotation.class).findFirst().orElse(null);
+ assertNotNull(ai);
+
+ var str = ai.toString();
+ assertNotNull(str);
+ // toString() returns the map representation
+ assertTrue(str.contains("CLASS_TYPE") ||
str.contains("@TestAnnotation"));
+ }
+}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/AnnotationInfo_ValueMethods_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/AnnotationInfo_ValueMethods_Test.java
deleted file mode 100644
index ad62cab056..0000000000
---
a/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/AnnotationInfo_ValueMethods_Test.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * 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.commons.reflect;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-import java.lang.annotation.*;
-
-import org.junit.jupiter.api.*;
-
-/**
- * Tests for AnnotationInfo value retrieval methods.
- */
-public class AnnotationInfo_ValueMethods_Test {
-
- @Target(ElementType.TYPE)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface TestAnnotation {
- String stringValue() default "default";
- int intValue() default 42;
- boolean boolValue() default true;
- long longValue() default 100L;
- double doubleValue() default 3.14;
- float floatValue() default 2.5f;
- Class<?> classValue() default String.class;
- String[] stringArray() default {"a", "b"};
- Class<?>[] classArray() default {String.class, Integer.class};
- }
-
- @TestAnnotation(
- stringValue = "test",
- intValue = 123,
- boolValue = false,
- longValue = 999L,
- doubleValue = 1.23,
- floatValue = 4.56f,
- classValue = Integer.class,
- stringArray = {"x", "y", "z"},
- classArray = {Long.class, Double.class}
- )
- public static class AnnotatedClass {}
-
- private static AnnotationInfo<TestAnnotation> getTestAnnotationInfo() {
- var ci = ClassInfo.of(AnnotatedClass.class);
- return
ci.getAnnotations(TestAnnotation.class).findFirst().orElse(null);
- }
-
- @Test
- public void testHasName() {
- var ai = getTestAnnotationInfo();
-
-
assertTrue(ai.hasName("org.apache.juneau.commons.reflect.AnnotationInfo_ValueMethods_Test$TestAnnotation"));
- assertFalse(ai.hasName("TestAnnotation"));
- }
-
- @Test
- public void testHasSimpleName() {
- var ai = getTestAnnotationInfo();
-
- assertTrue(ai.hasSimpleName("TestAnnotation"));
-
assertFalse(ai.hasSimpleName("org.apache.juneau.commons.reflect.AnnotationInfo_ValueMethods_Test$TestAnnotation"));
- }
-
- @Test
- public void testGetString() {
- var ai = getTestAnnotationInfo();
-
- assertTrue(ai.getString("stringValue").isPresent());
- assertEquals("test", ai.getString("stringValue").get());
- assertTrue(ai.getString("nonexistent").isEmpty());
- }
-
- @Test
- public void testGetInt() {
- var ai = getTestAnnotationInfo();
-
- assertTrue(ai.getInt("intValue").isPresent());
- assertEquals(123, ai.getInt("intValue").get());
- assertTrue(ai.getInt("nonexistent").isEmpty());
- }
-
- @Test
- public void testGetBoolean() {
- var ai = getTestAnnotationInfo();
-
- assertTrue(ai.getBoolean("boolValue").isPresent());
- assertEquals(false, ai.getBoolean("boolValue").get());
- assertTrue(ai.getBoolean("nonexistent").isEmpty());
- }
-
- @Test
- public void testGetLong() {
- var ai = getTestAnnotationInfo();
-
- assertTrue(ai.getLong("longValue").isPresent());
- assertEquals(999L, ai.getLong("longValue").get());
- assertTrue(ai.getLong("nonexistent").isEmpty());
- }
-
- @Test
- public void testGetDouble() {
- var ai = getTestAnnotationInfo();
-
- assertTrue(ai.getDouble("doubleValue").isPresent());
- assertEquals(1.23, ai.getDouble("doubleValue").get(), 0.001);
- assertTrue(ai.getDouble("nonexistent").isEmpty());
- }
-
- @Test
- public void testGetFloat() {
- var ai = getTestAnnotationInfo();
-
- assertTrue(ai.getFloat("floatValue").isPresent());
- assertEquals(4.56f, ai.getFloat("floatValue").get(), 0.001);
- assertTrue(ai.getFloat("nonexistent").isEmpty());
- }
-
- @Test
- public void testGetClassValue() {
- var ai = getTestAnnotationInfo();
-
- assertTrue(ai.getClassValue("classValue").isPresent());
- assertEquals(Integer.class,
ai.getClassValue("classValue").get());
- assertTrue(ai.getClassValue("nonexistent").isEmpty());
- }
-
- @Test
- public void testGetStringArray() {
- var ai = getTestAnnotationInfo();
-
- assertTrue(ai.getStringArray("stringArray").isPresent());
- String[] array = ai.getStringArray("stringArray").get();
- assertNotNull(array);
- assertEquals(3, array.length);
- assertEquals("x", array[0]);
- assertEquals("y", array[1]);
- assertEquals("z", array[2]);
- assertTrue(ai.getStringArray("nonexistent").isEmpty());
- }
-
- @Test
- public void testGetClassArray() {
- var ai = getTestAnnotationInfo();
-
- assertTrue(ai.getClassArray("classArray").isPresent());
- Class<?>[] array = ai.getClassArray("classArray").get();
- assertNotNull(array);
- assertEquals(2, array.length);
- assertEquals(Long.class, array[0]);
- assertEquals(Double.class, array[1]);
- assertTrue(ai.getClassArray("nonexistent").isEmpty());
- }
-
- @Test
- public void testGetReturnType() {
- var ai = getTestAnnotationInfo();
-
- // Test various return types
- assertTrue(ai.getReturnType("stringValue").isPresent());
- assertEquals(String.class,
ai.getReturnType("stringValue").get().inner());
- assertEquals(int.class,
ai.getReturnType("intValue").get().inner());
- assertEquals(boolean.class,
ai.getReturnType("boolValue").get().inner());
- assertEquals(long.class,
ai.getReturnType("longValue").get().inner());
- assertEquals(double.class,
ai.getReturnType("doubleValue").get().inner());
- assertEquals(float.class,
ai.getReturnType("floatValue").get().inner());
- assertEquals(Class.class,
ai.getReturnType("classValue").get().inner());
- assertEquals(String[].class,
ai.getReturnType("stringArray").get().inner());
- assertEquals(Class[].class,
ai.getReturnType("classArray").get().inner());
-
- // Nonexistent method
- assertTrue(ai.getReturnType("nonexistent").isEmpty());
- }
-}
-
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/ConstructorInfoTest.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/ConstructorInfo_Test.java
similarity index 79%
rename from
juneau-utest/src/test/java/org/apache/juneau/commons/reflect/ConstructorInfoTest.java
rename to
juneau-utest/src/test/java/org/apache/juneau/commons/reflect/ConstructorInfo_Test.java
index a75b1916c9..2b6ad852d6 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/ConstructorInfoTest.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/ConstructorInfo_Test.java
@@ -28,7 +28,7 @@ import java.util.stream.*;
import org.apache.juneau.*;
import org.junit.jupiter.api.*;
-class ConstructorInfoTest extends TestBase {
+class ConstructorInfo_Test extends TestBase {
private static void check(String expected, Object o) {
assertEquals(expected, TO_STRING.apply(o));
@@ -122,6 +122,31 @@ class ConstructorInfoTest extends TestBase {
assertEquals(null, b_c3.newInstanceLenient(123).toString());
}
+ @Test void isAccessible() {
+ // Test isAccessible() before and after setAccessible()
+ // Note: isAccessible() was added in Java 9, so behavior may
vary
+
+ // Before setAccessible(), private/protected constructors
should not be accessible
+ // (unless they're already accessible due to module system)
+ var privateBefore = b_c3.isAccessible();
+
+ // Make it accessible
+ b_c3.setAccessible();
+
+ // After setAccessible(), it should be accessible (if Java 9+)
+ // If Java 8 or earlier, isAccessible() will return false
+ var privateAfter = b_c3.isAccessible();
+
+ // Verify the method doesn't throw and returns a boolean
+ // The actual value depends on Java version, but it should be
consistent
+ assertTrue(privateAfter || !privateBefore, "After
setAccessible(), isAccessible() should return true (Java 9+) or false (Java
8)");
+
+ // Public constructors might already be accessible
+ var publicAccessible = b_c1.isAccessible();
+ // Should return a boolean (either true or false depending on
Java version)
+ assertNotNull(Boolean.valueOf(publicAccessible));
+ }
+
@Test void compareTo() {
var s = new TreeSet<>(l(b_c1, b_c2, b_c3, b_c4, a));
check("A(),B(),B(int),B(String),B(String,String)", s);
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/ExecutableInfo_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/ExecutableInfo_Test.java
index fdb3e3e79f..630af13e08 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/ExecutableInfo_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/ExecutableInfo_Test.java
@@ -462,6 +462,39 @@ class ExecutableInfo_Test extends TestBase {
assertDoesNotThrow(()->f_isDefault.accessible());
}
+ @Test void isAccessible() {
+ // Test isAccessible() before and after setAccessible()
+ // Note: isAccessible() was added in Java 9, so behavior may
vary
+
+ // Before setAccessible(), private/protected/default methods
should not be accessible
+ // (unless they're already accessible due to module system)
+ var privateBefore = f_isPrivate.isAccessible();
+ var protectedBefore = f_isProtected.isAccessible();
+ var defaultBefore = f_isDefault.isAccessible();
+
+ // Make them accessible
+ f_isPrivate.setAccessible();
+ f_isProtected.setAccessible();
+ f_isDefault.setAccessible();
+
+ // After setAccessible(), they should be accessible (if Java 9+)
+ // If Java 8 or earlier, isAccessible() will return false
+ var privateAfter = f_isPrivate.isAccessible();
+ var protectedAfter = f_isProtected.isAccessible();
+ var defaultAfter = f_isDefault.isAccessible();
+
+ // Verify the method doesn't throw and returns a boolean
+ // The actual value depends on Java version, but it should be
consistent
+ assertTrue(privateAfter || !privateBefore, "After
setAccessible(), isAccessible() should return true (Java 9+) or false (Java
8)");
+ assertTrue(protectedAfter || !protectedBefore, "After
setAccessible(), isAccessible() should return true (Java 9+) or false (Java
8)");
+ assertTrue(defaultAfter || !defaultBefore, "After
setAccessible(), isAccessible() should return true (Java 9+) or false (Java
8)");
+
+ // Public methods might already be accessible
+ var publicAccessible = f_isPublic.isAccessible();
+ // Should return a boolean (either true or false depending on
Java version)
+ assertNotNull(Boolean.valueOf(publicAccessible));
+ }
+
@Test void isVisible() {
assertTrue(f_isPublic.isVisible(Visibility.PUBLIC));
assertTrue(f_isPublic.isVisible(Visibility.PROTECTED));
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/FieldInfo_AnnotationInfos_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/FieldInfo_AnnotationInfos_Test.java
deleted file mode 100644
index 8bfc2a48ba..0000000000
---
a/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/FieldInfo_AnnotationInfos_Test.java
+++ /dev/null
@@ -1,117 +0,0 @@
-//
***************************************************************************************************************************
-// * 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.commons.reflect;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-import java.lang.annotation.*;
-
-import org.junit.jupiter.api.*;
-
-/**
- * Tests for {@link FieldInfo#getAnnotations()} methods.
- */
-public class FieldInfo_AnnotationInfos_Test {
-
- @Retention(RetentionPolicy.RUNTIME)
- @Target(ElementType.FIELD)
- public @interface TestAnnotation1 {
- String value() default "";
- }
-
- @Retention(RetentionPolicy.RUNTIME)
- @Target(ElementType.FIELD)
- public @interface TestAnnotation2 {
- int value() default 0;
- }
-
- @Retention(RetentionPolicy.RUNTIME)
- @Target(ElementType.FIELD)
- public @interface TestAnnotation3 {
- String value() default "";
- }
-
- public static class TestClass {
- @TestAnnotation1("test1")
- @TestAnnotation2(42)
- public String field1;
-
- @TestAnnotation1("test2")
- public String field2;
-
- public String field3;
- }
-
- @Test
- public void testGetAnnotationInfos() {
- var ci = ClassInfo.of(TestClass.class);
- var field1 = ci.getPublicField(x ->
x.getName().equals("field1")).get();
- var field2 = ci.getPublicField(x ->
x.getName().equals("field2")).get();
- var field3 = ci.getPublicField(x ->
x.getName().equals("field3")).get();
-
- // field1 has 2 annotations
- var annotations1 = field1.getAnnotations();
- assertEquals(2, annotations1.size());
- assertTrue(annotations1.stream().anyMatch(a ->
a.hasSimpleName("TestAnnotation1")));
- assertTrue(annotations1.stream().anyMatch(a ->
a.hasSimpleName("TestAnnotation2")));
-
- // field2 has 1 annotation
- var annotations2 = field2.getAnnotations();
- assertEquals(1, annotations2.size());
- assertTrue(annotations2.stream().anyMatch(a ->
a.hasSimpleName("TestAnnotation1")));
-
- // field3 has no annotations
- var annotations3 = field3.getAnnotations();
- assertEquals(0, annotations3.size());
- }
-
- @Test
- public void testGetAnnotationInfosTyped() {
- var ci = ClassInfo.of(TestClass.class);
- var field1 = ci.getPublicField(x ->
x.getName().equals("field1")).get();
- var field2 = ci.getPublicField(x ->
x.getName().equals("field2")).get();
-
- // Test filtering by type for field1
- var ann1_type1 =
field1.getAnnotations(TestAnnotation1.class).toList();
- assertEquals(1, ann1_type1.size());
- assertEquals("test1", ann1_type1.get(0).getValue().get());
-
- var ann1_type2 =
field1.getAnnotations(TestAnnotation2.class).toList();
- assertEquals(1, ann1_type2.size());
- assertEquals(42, ann1_type2.get(0).getInt("value").get());
-
- // Test filtering by type that doesn't exist
- var ann1_type3 =
field1.getAnnotations(TestAnnotation3.class).toList();
- assertEquals(0, ann1_type3.size());
-
- // Test filtering for field2
- var ann2_type1 =
field2.getAnnotations(TestAnnotation1.class).toList();
- assertEquals(1, ann2_type1.size());
- assertEquals("test2", ann2_type1.get(0).getValue().get());
-
- var ann2_type2 =
field2.getAnnotations(TestAnnotation2.class).toList();
- assertEquals(0, ann2_type2.size());
- }
-
- @Test
- public void testGetAnnotationInfosMemoization() {
- var ci = ClassInfo.of(TestClass.class);
- var field1 = ci.getPublicField(x ->
x.getName().equals("field1")).get();
-
- // Calling getDeclaredAnnotationInfos() multiple times should
return the same list instance
- var annotations1 = field1.getAnnotations();
- var annotations2 = field1.getAnnotations();
- assertSame(annotations1, annotations2);
- }
-}
-
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/FieldInfo_FullName_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/FieldInfo_FullName_Test.java
deleted file mode 100644
index bcda6c1154..0000000000
---
a/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/FieldInfo_FullName_Test.java
+++ /dev/null
@@ -1,74 +0,0 @@
-//
***************************************************************************************************************************
-// * 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.commons.reflect;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-import org.junit.jupiter.api.*;
-
-/**
- * Tests for {@link FieldInfo#getFullName()} method.
- */
-public class FieldInfo_FullName_Test {
-
- public static class TestClass {
- public String field1;
- public int field2;
- }
-
- @Test
- public void testGetFullName() {
- var ci = ClassInfo.of(TestClass.class);
- var field1 = ci.getPublicField(x ->
x.getName().equals("field1")).get();
- var field2 = ci.getPublicField(x ->
x.getName().equals("field2")).get();
-
- // Verify full names are correct
- String fullName1 = field1.getFullName();
- String fullName2 = field2.getFullName();
-
-
assertTrue(fullName1.endsWith("FieldInfo_FullName_Test$TestClass.field1"));
-
assertTrue(fullName2.endsWith("FieldInfo_FullName_Test$TestClass.field2"));
-
- // Verify package is included
-
assertTrue(fullName1.startsWith("org.apache.juneau.commons.reflect."));
-
assertTrue(fullName2.startsWith("org.apache.juneau.commons.reflect."));
- }
-
- @Test
- public void testGetFullNameMemoization() {
- var ci = ClassInfo.of(TestClass.class);
- var field1 = ci.getPublicField(x ->
x.getName().equals("field1")).get();
-
- // Calling getFullName() multiple times should return the same
String instance (memoized)
- String name1 = field1.getFullName();
- String name2 = field1.getFullName();
- assertSame(name1, name2);
- }
-
- public static class InnerClass {
- public String innerField;
- }
-
- @Test
- public void testGetFullNameWithInnerClass() {
- var ci = ClassInfo.of(InnerClass.class);
- var field = ci.getPublicField(x ->
x.getName().equals("innerField")).get();
-
- String fullName = field.getFullName();
-
- // Verify $ separator is used for inner class
-
assertTrue(fullName.contains("FieldInfo_FullName_Test$InnerClass"));
- assertTrue(fullName.endsWith(".innerField"));
- }
-}
-
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/FieldInfo_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/FieldInfo_Test.java
index 7810833eab..4d8a359223 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/FieldInfo_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/FieldInfo_Test.java
@@ -47,6 +47,24 @@ class FieldInfo_Test extends TestBase {
String value();
}
+ @Target(FIELD)
+ @Retention(RUNTIME)
+ public static @interface TestAnnotation1 {
+ String value() default "";
+ }
+
+ @Target(FIELD)
+ @Retention(RUNTIME)
+ public static @interface TestAnnotation2 {
+ int value() default 0;
+ }
+
+ @Target(FIELD)
+ @Retention(RUNTIME)
+ public static @interface TestAnnotation3 {
+ String value() default "";
+ }
+
private static void check(String expected, Object o) {
assertEquals(expected, TO_STRING.apply(o));
}
@@ -252,6 +270,39 @@ class FieldInfo_Test extends TestBase {
assertDoesNotThrow(()->d_isDefault.setAccessible());
}
+ @Test void isAccessible() {
+ // Test isAccessible() before and after setAccessible()
+ // Note: isAccessible() was added in Java 9, so behavior may
vary
+
+ // Before setAccessible(), private/protected/default fields
should not be accessible
+ // (unless they're already accessible due to module system)
+ var privateBefore = d_isPrivate.isAccessible();
+ var protectedBefore = d_isProtected.isAccessible();
+ var defaultBefore = d_isDefault.isAccessible();
+
+ // Make them accessible
+ d_isPrivate.setAccessible();
+ d_isProtected.setAccessible();
+ d_isDefault.setAccessible();
+
+ // After setAccessible(), they should be accessible (if Java 9+)
+ // If Java 8 or earlier, isAccessible() will return false
+ var privateAfter = d_isPrivate.isAccessible();
+ var protectedAfter = d_isProtected.isAccessible();
+ var defaultAfter = d_isDefault.isAccessible();
+
+ // Verify the method doesn't throw and returns a boolean
+ // The actual value depends on Java version, but it should be
consistent
+ assertTrue(privateAfter || !privateBefore, "After
setAccessible(), isAccessible() should return true (Java 9+) or false (Java
8)");
+ assertTrue(protectedAfter || !protectedBefore, "After
setAccessible(), isAccessible() should return true (Java 9+) or false (Java
8)");
+ assertTrue(defaultAfter || !defaultBefore, "After
setAccessible(), isAccessible() should return true (Java 9+) or false (Java
8)");
+
+ // Public fields might already be accessible
+ var publicAccessible = d_isPublic.isAccessible();
+ // Should return a boolean (either true or false depending on
Java version)
+ assertNotNull(Boolean.valueOf(publicAccessible));
+ }
+
@Test void isVisible() {
assertTrue(d_isPublic.isVisible(Visibility.PUBLIC));
assertTrue(d_isPublic.isVisible(Visibility.PROTECTED));
@@ -301,4 +352,110 @@ class FieldInfo_Test extends TestBase {
@Test void toString2() {
assertEquals("org.apache.juneau.commons.reflect.FieldInfo_Test$E.a1",
e_a1.toString());
}
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // getAnnotations()
+
//-----------------------------------------------------------------------------------------------------------------
+
+ public static class F {
+ @TestAnnotation1("test1")
+ @TestAnnotation2(42)
+ public String field1;
+
+ @TestAnnotation1("test2")
+ public String field2;
+
+ public String field3;
+ }
+
+ static ClassInfo f = ClassInfo.of(F.class);
+ static FieldInfo
+ f_field1 = f.getPublicField(x -> x.hasName("field1")).get(),
+ f_field2 = f.getPublicField(x -> x.hasName("field2")).get(),
+ f_field3 = f.getPublicField(x -> x.hasName("field3")).get();
+
+ @Test void getAnnotations_returnsAllAnnotations() {
+ var annotations1 = f_field1.getAnnotations();
+ assertEquals(2, annotations1.size());
+ assertTrue(annotations1.stream().anyMatch(a ->
a.hasSimpleName("TestAnnotation1")));
+ assertTrue(annotations1.stream().anyMatch(a ->
a.hasSimpleName("TestAnnotation2")));
+
+ var annotations2 = f_field2.getAnnotations();
+ assertEquals(1, annotations2.size());
+ assertTrue(annotations2.stream().anyMatch(a ->
a.hasSimpleName("TestAnnotation1")));
+
+ var annotations3 = f_field3.getAnnotations();
+ assertEquals(0, annotations3.size());
+ }
+
+ @Test void getAnnotations_typed_filtersByType() {
+ var ann1_type1 =
f_field1.getAnnotations(TestAnnotation1.class).toList();
+ assertEquals(1, ann1_type1.size());
+ assertEquals("test1", ann1_type1.get(0).getValue().get());
+
+ var ann1_type2 =
f_field1.getAnnotations(TestAnnotation2.class).toList();
+ assertEquals(1, ann1_type2.size());
+ assertEquals(42, ann1_type2.get(0).getInt("value").get());
+
+ var ann1_type3 =
f_field1.getAnnotations(TestAnnotation3.class).toList();
+ assertEquals(0, ann1_type3.size());
+
+ var ann2_type1 =
f_field2.getAnnotations(TestAnnotation1.class).toList();
+ assertEquals(1, ann2_type1.size());
+ assertEquals("test2", ann2_type1.get(0).getValue().get());
+
+ var ann2_type2 =
f_field2.getAnnotations(TestAnnotation2.class).toList();
+ assertEquals(0, ann2_type2.size());
+ }
+
+ @Test void getAnnotations_memoization_returnsSameInstance() {
+ var annotations1 = f_field1.getAnnotations();
+ var annotations2 = f_field1.getAnnotations();
+ assertSame(annotations1, annotations2);
+ }
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // getFullName()
+
//-----------------------------------------------------------------------------------------------------------------
+
+ public static class G {
+ public String field1;
+ public int field2;
+ }
+
+ static ClassInfo g = ClassInfo.of(G.class);
+ static FieldInfo
+ g_field1 = g.getPublicField(x -> x.hasName("field1")).get(),
+ g_field2 = g.getPublicField(x -> x.hasName("field2")).get();
+
+ @Test void getFullName_returnsFullyQualifiedName() {
+ String fullName1 = g_field1.getFullName();
+ String fullName2 = g_field2.getFullName();
+
+ assertTrue(fullName1.endsWith("FieldInfo_Test$G.field1"));
+ assertTrue(fullName2.endsWith("FieldInfo_Test$G.field2"));
+
+
assertTrue(fullName1.startsWith("org.apache.juneau.commons.reflect."));
+
assertTrue(fullName2.startsWith("org.apache.juneau.commons.reflect."));
+ }
+
+ @Test void getFullName_memoization_returnsSameInstance() {
+ String name1 = g_field1.getFullName();
+ String name2 = g_field1.getFullName();
+ assertSame(name1, name2);
+ }
+
+ public static class InnerClass {
+ public String innerField;
+ }
+
+ static ClassInfo inner = ClassInfo.of(InnerClass.class);
+ static FieldInfo inner_field = inner.getPublicField(x ->
x.hasName("innerField")).get();
+
+ @Test void getFullName_withInnerClass_usesDollarSeparator() {
+ String fullName = inner_field.getFullName();
+
+ assertTrue(fullName.contains("FieldInfo_Test$InnerClass"));
+ assertTrue(fullName.endsWith(".innerField"));
+ }
}
\ No newline at end of file
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/ParameterInfoTest.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/ParameterInfo_Test.java
similarity index 99%
rename from
juneau-utest/src/test/java/org/apache/juneau/commons/reflect/ParameterInfoTest.java
rename to
juneau-utest/src/test/java/org/apache/juneau/commons/reflect/ParameterInfo_Test.java
index 8268a3d14a..9583354b06 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/ParameterInfoTest.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/reflect/ParameterInfo_Test.java
@@ -35,7 +35,7 @@ import org.apache.juneau.annotation.Name;
/**
* ParameterInfo tests.
*/
-class ParameterInfoTest extends TestBase {
+class ParameterInfo_Test extends TestBase {
private static String originalDisableParamNameDetection;