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 95a6c24cef SonarQube bug fixes
95a6c24cef is described below
commit 95a6c24ceff9b7edc19e7d96d91c31f553a64c57
Author: James Bognar <[email protected]>
AuthorDate: Thu Feb 5 10:40:20 2026 -0500
SonarQube bug fixes
---
.../apache/juneau/commons/collections/Cache.java | 21 +++++++++
.../apache/juneau/commons/collections/Cache2.java | 15 ++++++
.../apache/juneau/commons/collections/Cache3.java | 15 ++++++
.../apache/juneau/commons/collections/Cache4.java | 15 ++++++
.../apache/juneau/commons/collections/Cache5.java | 15 ++++++
.../juneau/commons/io/CharSequenceReader.java | 2 +-
.../apache/juneau/commons/settings/Settings.java | 14 ++++++
.../org/apache/juneau/commons/utils/IoUtils.java | 18 +++++++
.../examples/core/config/store/SqlStore.java | 2 +-
.../juneau/commons/collections/Cache2_Test.java | 44 +++++++++++++++++
.../juneau/commons/collections/Cache3_Test.java | 44 +++++++++++++++++
.../juneau/commons/collections/Cache4_Test.java | 44 +++++++++++++++++
.../juneau/commons/collections/Cache5_Test.java | 44 +++++++++++++++++
.../juneau/commons/collections/Cache_Test.java | 55 ++++++++++++++++++++++
.../juneau/commons/settings/Settings_Test.java | 49 +++++++++++++++++++
.../apache/juneau/commons/utils/IoUtils_Test.java | 16 +++++++
16 files changed, 411 insertions(+), 2 deletions(-)
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/Cache.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/Cache.java
index c307bddd29..b761c590a9 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/Cache.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/Cache.java
@@ -454,6 +454,7 @@ public class Cache<K,V> {
// Internal map with Tuple1 keys for content-based equality (especially
for arrays)
// If threadLocal is true, this is null and threadLocalMap is used
instead
private final Map<Tuple1<K>,V> map;
+ @SuppressWarnings("java:S5164") // Cleanup method provided: cleanup()
private final ThreadLocal<Map<Tuple1<K>,V>> threadLocalMap;
private final boolean isThreadLocal;
@@ -468,6 +469,7 @@ public class Cache<K,V> {
*/
private final Map<K,Tuple1<K>> wrapperCache;
+ @SuppressWarnings("java:S5164") // Cleanup method provided: cleanup()
private final ThreadLocal<Map<K,Tuple1<K>>> threadLocalWrapperCache;
private final int maxSize;
@@ -525,6 +527,25 @@ public class Cache<K,V> {
getWrapperCache().clear(); // Clean up wrapper cache
}
+ /**
+ * Cleans up thread-local storage for the current thread.
+ *
+ * <p>
+ * This method should be called when a thread is finished using this
cache to prevent memory leaks
+ * in thread pool environments where threads are reused.
+ * Only has an effect if this cache was created with thread-local mode
enabled.
+ */
+ public void cleanup() {
+ if (isThreadLocal) {
+ if (threadLocalMap != null) {
+ threadLocalMap.remove();
+ }
+ if (threadLocalWrapperCache != null) {
+ threadLocalWrapperCache.remove();
+ }
+ }
+ }
+
/**
* Returns <jk>true</jk> if the cache contains a mapping for the
specified key.
*
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/Cache2.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/Cache2.java
index 273f5f5246..39b017f9ba 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/Cache2.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/Cache2.java
@@ -391,6 +391,7 @@ public class Cache2<K1,K2,V> {
// If threadLocal is true, this is null and threadLocalMap is used
instead
private final java.util.Map<Tuple2<K1,K2>,V> map;
+ @SuppressWarnings("java:S5164") // Cleanup method provided: cleanup()
private final ThreadLocal<Map<Tuple2<K1,K2>,V>> threadLocalMap;
private final boolean isThreadLocal;
@@ -440,6 +441,20 @@ public class Cache2<K1,K2,V> {
getMap().clear();
}
+ /**
+ * Cleans up thread-local storage for the current thread.
+ *
+ * <p>
+ * This method should be called when a thread is finished using this
cache to prevent memory leaks
+ * in thread pool environments where threads are reused.
+ * Only has an effect if this cache was created with thread-local mode
enabled.
+ */
+ public void cleanup() {
+ if (isThreadLocal && threadLocalMap != null) {
+ threadLocalMap.remove();
+ }
+ }
+
/**
* Returns <jk>true</jk> if the cache contains a mapping for the
specified key pair.
*
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/Cache3.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/Cache3.java
index 1183dc9c42..b829ed87ae 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/Cache3.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/Cache3.java
@@ -259,6 +259,7 @@ public class Cache3<K1,K2,K3,V> {
// If threadLocal is true, this is null and threadLocalMap is used
instead
private final java.util.Map<Tuple3<K1,K2,K3>,V> map;
+ @SuppressWarnings("java:S5164") // Cleanup method provided: cleanup()
private final ThreadLocal<Map<Tuple3<K1,K2,K3>,V>> threadLocalMap;
private final boolean isThreadLocal;
@@ -308,6 +309,20 @@ public class Cache3<K1,K2,K3,V> {
getMap().clear();
}
+ /**
+ * Cleans up thread-local storage for the current thread.
+ *
+ * <p>
+ * This method should be called when a thread is finished using this
cache to prevent memory leaks
+ * in thread pool environments where threads are reused.
+ * Only has an effect if this cache was created with thread-local mode
enabled.
+ */
+ public void cleanup() {
+ if (isThreadLocal && threadLocalMap != null) {
+ threadLocalMap.remove();
+ }
+ }
+
/**
* Returns <jk>true</jk> if the cache contains a mapping for the
specified key triplet.
*
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/Cache4.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/Cache4.java
index 1ee8436bb2..f5ab6ed5bd 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/Cache4.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/Cache4.java
@@ -249,6 +249,7 @@ public class Cache4<K1,K2,K3,K4,V> {
// If threadLocal is true, this is null and threadLocalMap is used
instead
private final java.util.Map<Tuple4<K1,K2,K3,K4>,V> map;
+ @SuppressWarnings("java:S5164") // Cleanup method provided: cleanup()
private final ThreadLocal<java.util.Map<Tuple4<K1,K2,K3,K4>,V>>
threadLocalMap;
private final boolean isThreadLocal;
@@ -298,6 +299,20 @@ public class Cache4<K1,K2,K3,K4,V> {
getMap().clear();
}
+ /**
+ * Cleans up thread-local storage for the current thread.
+ *
+ * <p>
+ * This method should be called when a thread is finished using this
cache to prevent memory leaks
+ * in thread pool environments where threads are reused.
+ * Only has an effect if this cache was created with thread-local mode
enabled.
+ */
+ public void cleanup() {
+ if (isThreadLocal && threadLocalMap != null) {
+ threadLocalMap.remove();
+ }
+ }
+
/**
* Returns <jk>true</jk> if the cache contains a mapping for the
specified four-part key.
*
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/Cache5.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/Cache5.java
index 516999a2e7..54eaaac350 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/Cache5.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/Cache5.java
@@ -255,6 +255,7 @@ public class Cache5<K1,K2,K3,K4,K5,V> {
// If threadLocal is true, this is null and threadLocalMap is used
instead
private final java.util.Map<Tuple5<K1,K2,K3,K4,K5>,V> map;
+ @SuppressWarnings("java:S5164") // Cleanup method provided: cleanup()
private final ThreadLocal<java.util.Map<Tuple5<K1,K2,K3,K4,K5>,V>>
threadLocalMap;
private final boolean isThreadLocal;
@@ -304,6 +305,20 @@ public class Cache5<K1,K2,K3,K4,K5,V> {
getMap().clear();
}
+ /**
+ * Cleans up thread-local storage for the current thread.
+ *
+ * <p>
+ * This method should be called when a thread is finished using this
cache to prevent memory leaks
+ * in thread pool environments where threads are reused.
+ * Only has an effect if this cache was created with thread-local mode
enabled.
+ */
+ public void cleanup() {
+ if (isThreadLocal && threadLocalMap != null) {
+ threadLocalMap.remove();
+ }
+ }
+
/**
* Returns <jk>true</jk> if the cache contains a mapping for the
specified five-part key.
*
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/io/CharSequenceReader.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/io/CharSequenceReader.java
index 4477755835..cddaafeea9 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/io/CharSequenceReader.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/io/CharSequenceReader.java
@@ -175,7 +175,7 @@ public class CharSequenceReader extends BufferedReader {
public long skip(long ns) {
if (next >= length)
return 0;
- long n = Math.min(length - next, ns);
+ long n = Math.min((long) length - next, ns);
n = Math.max(-next, n);
next += n;
return n;
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/Settings.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/Settings.java
index e2bf5e6761..8a6f19c54f 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/Settings.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/Settings.java
@@ -323,6 +323,7 @@ public class Settings {
}
private final ResettableSupplier<SettingStore> globalStore;
+ @SuppressWarnings("java:S5164") // Cleanup method provided: cleanup()
private final ThreadLocal<SettingStore> localStore;
private final List<SettingSource> sources;
private final Map<Class<?>,Function<String,?>> toTypeFunctions;
@@ -525,6 +526,19 @@ public class Settings {
return this;
}
+ /**
+ * Cleans up thread-local storage for the current thread.
+ *
+ * <p>
+ * This method should be called when a thread is finished using this
Settings instance to prevent memory leaks
+ * in thread pool environments where threads are reused.
+ */
+ public void cleanup() {
+ if (localStore != null) {
+ localStore.remove();
+ }
+ }
+
/**
* Clears all global overrides.
*
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/IoUtils.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/IoUtils.java
index 8447e56f8a..b720ec3a96 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/IoUtils.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/IoUtils.java
@@ -47,7 +47,9 @@ public class IoUtils {
private static final int BUFF_SIZE = 1024;
+ @SuppressWarnings("java:S5164") // Cleanup method provided:
cleanupThreadLocals()
private static final ThreadLocal<byte[]> BYTE_BUFFER_CACHE =
(Boolean.getBoolean("juneau.disableIoBufferReuse") ? null : new
ThreadLocal<>());
+ @SuppressWarnings("java:S5164") // Cleanup method provided:
cleanupThreadLocals()
private static final ThreadLocal<char[]> CHAR_BUFFER_CACHE =
(Boolean.getBoolean("juneau.disableIoBufferReuse") ? null : new
ThreadLocal<>());
static final AtomicInteger BYTE_BUFFER_CACHE_HITS = new AtomicInteger();
@@ -59,6 +61,22 @@ public class IoUtils {
shutdownMessage(() -> "Char buffer cache: hits=" +
CHAR_BUFFER_CACHE_HITS.get() + ", misses=" + CHAR_BUFFER_CACHE_MISSES);
}
+ /**
+ * Cleans up thread-local buffer caches for the current thread.
+ *
+ * <p>
+ * This method should be called when a thread is finished using the
buffer caches to prevent memory leaks
+ * in thread pool environments where threads are reused.
+ */
+ public static void cleanupThreadLocals() {
+ if (BYTE_BUFFER_CACHE != null) {
+ BYTE_BUFFER_CACHE.remove();
+ }
+ if (CHAR_BUFFER_CACHE != null) {
+ CHAR_BUFFER_CACHE.remove();
+ }
+ }
+
/** Reusable empty reader. */
public static final Reader EMPTY_READER = new Reader() {
@Override
diff --git
a/juneau-examples/juneau-examples-core/src/main/java/org/apache/juneau/examples/core/config/store/SqlStore.java
b/juneau-examples/juneau-examples-core/src/main/java/org/apache/juneau/examples/core/config/store/SqlStore.java
index c5a51a822d..fab473ccc5 100644
---
a/juneau-examples/juneau-examples-core/src/main/java/org/apache/juneau/examples/core/config/store/SqlStore.java
+++
b/juneau-examples/juneau-examples-core/src/main/java/org/apache/juneau/examples/core/config/store/SqlStore.java
@@ -167,7 +167,7 @@ public class SqlStore extends ConfigStore {
};
this.watcher = new Timer("MyTimer");
- watcher.scheduleAtFixedRate(timerTask, 0, pollInterval * 1000);
+ watcher.scheduleAtFixedRate(timerTask, 0, (long) pollInterval *
1000);
}
@Override /* Closeable */
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 b34c199f52..558b960c96 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
@@ -906,5 +906,49 @@ class Cache2_Test extends TestBase {
x.get("k4", 4);
assertSize(1, x);
}
+
+
//====================================================================================================
+ // cleanup() - Thread-local cleanup
+
//====================================================================================================
+
+ @Test
+ void q01_cleanup_threadLocal() {
+ var x = Cache2.of(String.class, Integer.class, String.class)
+ .threadLocal()
+ .supplier((k1, k2) -> k1 + ":" + k2)
+ .build();
+
+ // Use the cache to populate ThreadLocal
+ x.get("key1", 1);
+ x.get("key2", 2);
+ assertSize(2, x);
+
+ // Cleanup should remove ThreadLocal values
+ x.cleanup();
+
+ // After cleanup, cache should be empty (ThreadLocal was
cleared)
+ assertEmpty(x);
+
+ // Call cleanup again to ensure it's safe to call multiple times
+ x.cleanup();
+ // Should not throw
+ }
+
+ @Test
+ void q02_cleanup_nonThreadLocal_noEffect() {
+ var x = Cache2.of(String.class, Integer.class, String.class)
+ .supplier((k1, k2) -> k1 + ":" + k2)
+ .build(); // Not thread-local
+
+ x.get("key1", 1);
+ x.get("key2", 2);
+ assertSize(2, x);
+
+ // Cleanup on non-thread-local cache should not affect the cache
+ x.cleanup();
+
+ // Cache should still have values (cleanup only affects
thread-local caches)
+ assertSize(2, 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 560092ac8d..99a7373c24 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
@@ -603,5 +603,49 @@ class Cache3_Test extends TestBase {
x.get("es", "ES", 4);
assertSize(1, x);
}
+
+
//====================================================================================================
+ // cleanup() - Thread-local cleanup
+
//====================================================================================================
+
+ @Test
+ void q01_cleanup_threadLocal() {
+ var x = Cache3.of(String.class, String.class, Integer.class,
String.class)
+ .threadLocal()
+ .supplier((k1, k2, k3) -> k1 + ":" + k2 + ":" + k3)
+ .build();
+
+ // Use the cache to populate ThreadLocal
+ x.get("key1", "key2", 1);
+ x.get("key3", "key4", 2);
+ assertSize(2, x);
+
+ // Cleanup should remove ThreadLocal values
+ x.cleanup();
+
+ // After cleanup, cache should be empty (ThreadLocal was
cleared)
+ assertEmpty(x);
+
+ // Call cleanup again to ensure it's safe to call multiple times
+ x.cleanup();
+ // Should not throw
+ }
+
+ @Test
+ void q02_cleanup_nonThreadLocal_noEffect() {
+ var x = Cache3.of(String.class, String.class, Integer.class,
String.class)
+ .supplier((k1, k2, k3) -> k1 + ":" + k2 + ":" + k3)
+ .build(); // Not thread-local
+
+ x.get("key1", "key2", 1);
+ x.get("key3", "key4", 2);
+ assertSize(2, x);
+
+ // Cleanup on non-thread-local cache should not affect the cache
+ x.cleanup();
+
+ // Cache should still have values (cleanup only affects
thread-local caches)
+ assertSize(2, 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 b1b2e55259..52538b2793 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
@@ -603,5 +603,49 @@ class Cache4_Test extends TestBase {
x.get("es", "ES", "informal", 4);
assertSize(1, x);
}
+
+
//====================================================================================================
+ // cleanup() - Thread-local cleanup
+
//====================================================================================================
+
+ @Test
+ void q01_cleanup_threadLocal() {
+ 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();
+
+ // Use the cache to populate ThreadLocal
+ x.get("key1", "key2", "key3", 1);
+ x.get("key4", "key5", "key6", 2);
+ assertSize(2, x);
+
+ // Cleanup should remove ThreadLocal values
+ x.cleanup();
+
+ // After cleanup, cache should be empty (ThreadLocal was
cleared)
+ assertEmpty(x);
+
+ // Call cleanup again to ensure it's safe to call multiple times
+ x.cleanup();
+ // Should not throw
+ }
+
+ @Test
+ void q02_cleanup_nonThreadLocal_noEffect() {
+ var x = Cache4.of(String.class, String.class, String.class,
Integer.class, String.class)
+ .supplier((k1, k2, k3, k4) -> k1 + ":" + k2 + ":" + k3
+ ":" + k4)
+ .build(); // Not thread-local
+
+ x.get("key1", "key2", "key3", 1);
+ x.get("key4", "key5", "key6", 2);
+ assertSize(2, x);
+
+ // Cleanup on non-thread-local cache should not affect the cache
+ x.cleanup();
+
+ // Cache should still have values (cleanup only affects
thread-local caches)
+ assertSize(2, 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 b0a1bf3eea..f81959210d 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
@@ -604,5 +604,49 @@ class Cache5_Test extends TestBase {
x.get("es", "ES", "south", "informal", 4);
assertSize(1, x);
}
+
+
//====================================================================================================
+ // cleanup() - Thread-local cleanup
+
//====================================================================================================
+
+ @Test
+ void q01_cleanup_threadLocal() {
+ 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();
+
+ // Use the cache to populate ThreadLocal
+ x.get("key1", "key2", "key3", "key4", 1);
+ x.get("key5", "key6", "key7", "key8", 2);
+ assertSize(2, x);
+
+ // Cleanup should remove ThreadLocal values
+ x.cleanup();
+
+ // After cleanup, cache should be empty (ThreadLocal was
cleared)
+ assertEmpty(x);
+
+ // Call cleanup again to ensure it's safe to call multiple times
+ x.cleanup();
+ // Should not throw
+ }
+
+ @Test
+ void q02_cleanup_nonThreadLocal_noEffect() {
+ var x = Cache5.of(String.class, String.class, String.class,
String.class, Integer.class, String.class)
+ .supplier((k1, k2, k3, k4, k5) -> k1 + ":" + k2 + ":" +
k3 + ":" + k4 + ":" + k5)
+ .build(); // Not thread-local
+
+ x.get("key1", "key2", "key3", "key4", 1);
+ x.get("key5", "key6", "key7", "key8", 2);
+ assertSize(2, x);
+
+ // Cleanup on non-thread-local cache should not affect the cache
+ x.cleanup();
+
+ // Cache should still have values (cleanup only affects
thread-local caches)
+ assertSize(2, 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 c7ee9138e0..9afab8441c 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
@@ -1216,5 +1216,60 @@ class Cache_Test extends TestBase {
cache.get("five", () -> 5);
assertSize(1, cache);
}
+
+
//====================================================================================================
+ // cleanup() - Thread-local cleanup
+
//====================================================================================================
+
+ @Test void a64_cleanup_threadLocal() {
+ var cache = Cache.of(String.class, String.class)
+ .threadLocal()
+ .build();
+
+ // Use the cache to populate ThreadLocal
+ cache.get("key1", () -> "value1");
+ cache.get("key2", () -> "value2");
+ assertSize(2, cache);
+
+ // Cleanup should remove ThreadLocal values
+ cache.cleanup();
+
+ // After cleanup, cache should be empty (ThreadLocal was
cleared)
+ assertEmpty(cache);
+
+ // Call cleanup again to ensure it's safe to call multiple times
+ cache.cleanup();
+ // Should not throw
+ }
+
+ @Test void a65_cleanup_nonThreadLocal_noEffect() {
+ var cache = Cache.of(String.class, String.class)
+ .build(); // Not thread-local
+
+ cache.get("key1", () -> "value1");
+ cache.get("key2", () -> "value2");
+ assertSize(2, cache);
+
+ // Cleanup on non-thread-local cache should not affect the cache
+ cache.cleanup();
+
+ // Cache should still have values (cleanup only affects
thread-local caches)
+ assertSize(2, cache);
+ }
+
+ @Test void a66_cleanup_threadLocal_weakMode() {
+ var cache = Cache.of(String.class, String.class)
+ .threadLocal()
+ .cacheMode(WEAK)
+ .build();
+
+ cache.get("key1", () -> "value1");
+ cache.get("key2", () -> "value2");
+ assertSize(2, cache);
+
+ // Cleanup should work with weak mode too
+ cache.cleanup();
+ assertEmpty(cache);
+ }
}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/settings/Settings_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/settings/Settings_Test.java
index c7e27176a3..a9d974e885 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/commons/settings/Settings_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/settings/Settings_Test.java
@@ -1267,5 +1267,54 @@ class Settings_Test extends TestBase {
assertTrue(optional2.isPresent());
assertEquals("value1", optional2.get()); // Still value1 since
system property hasn't changed
}
+
+
//====================================================================================================
+ // cleanup()
+
//====================================================================================================
+ @Test
+ void u01_cleanup() {
+ // Set a local value to ensure ThreadLocal has a value
+ Settings.get().setLocal(TEST_PROP, "local-value");
+ var result = Settings.get().get(TEST_PROP);
+ assertTrue(result.isPresent());
+ assertEquals("local-value", result.get());
+
+ // Cleanup should remove the ThreadLocal value
+ Settings.get().cleanup();
+
+ // After cleanup, the local value should be gone (falls back to
system property if set)
+ // If system property is not set, should return empty
+ var resultAfterCleanup = Settings.get().get(TEST_PROP);
+ // The value may still be present if there's a system property,
but the local override should be gone
+ // We can verify cleanup was called by ensuring it doesn't throw
+ assertNotNull(resultAfterCleanup);
+
+ // Call cleanup again to ensure it's safe to call multiple times
+ Settings.get().cleanup();
+ // Should not throw
+ }
+
+ @Test
+ void u02_cleanup_removesLocalOverride() {
+ // Set both system property and local override
+ System.setProperty(TEST_PROP, "system-value");
+ Settings.get().setLocal(TEST_PROP, "local-value");
+
+ // Verify local override takes precedence
+ var result = Settings.get().get(TEST_PROP);
+ assertTrue(result.isPresent());
+ assertEquals("local-value", result.get());
+
+ // Cleanup should remove local override
+ Settings.get().cleanup();
+
+ // After cleanup, should fall back to system property
+ var resultAfterCleanup = Settings.get().get(TEST_PROP);
+ assertTrue(resultAfterCleanup.isPresent());
+ assertEquals("system-value", resultAfterCleanup.get());
+
+ // Clean up
+ System.clearProperty(TEST_PROP);
+ }
}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/utils/IoUtils_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/utils/IoUtils_Test.java
index 64ca73ea36..0f1a3af5d5 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/commons/utils/IoUtils_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/utils/IoUtils_Test.java
@@ -961,6 +961,22 @@ class IoUtils_Test extends TestBase {
assertEquals(-1, EMPTY_READER.read(buf, 0, 10));
}
+
//====================================================================================================
+ // cleanupThreadLocals()
+
//====================================================================================================
+ @Test
+ void a046_cleanupThreadLocals() {
+ // Test that cleanupThreadLocals() can be called without
throwing
+ // Note: We can't easily verify the ThreadLocal was cleared
without accessing private fields,
+ // but we can verify the method executes successfully
+ cleanupThreadLocals();
+ // Should not throw
+
+ // Call again to ensure it's safe to call multiple times
+ cleanupThreadLocals();
+ // Should not throw
+ }
+
//====================================================================================================
// Test helper classes
//====================================================================================================