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 bc343963a5 Marshall module improvements
bc343963a5 is described below

commit bc343963a503a1e6cded28c76588e627baf581ae
Author: James Bognar <[email protected]>
AuthorDate: Thu Dec 11 12:00:14 2025 -0500

    Marshall module improvements
---
 .../apache/juneau/commons/settings/MapSource.java  | 156 ++++++
 .../juneau/commons/settings/ReadOnlySource.java    | 116 ++++
 .../juneau/commons/settings/SettingSource.java     | 119 ++++
 .../apache/juneau/commons/settings/Settings.java   | 533 ++++++++++++++++++
 .../org/apache/juneau/commons/utils/Utils.java     |  39 ++
 .../juneau/commons/settings/Settings_Test.java     | 613 +++++++++++++++++++++
 6 files changed, 1576 insertions(+)

diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/MapSource.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/MapSource.java
new file mode 100644
index 0000000000..f7ed272e44
--- /dev/null
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/MapSource.java
@@ -0,0 +1,156 @@
+/*
+ * 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.settings;
+
+import static org.apache.juneau.commons.utils.Utils.*;
+
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.*;
+
+/**
+ * A writable {@link SettingSource} implementation backed by a thread-safe map.
+ *
+ * <p>
+ * This class provides a mutable source for settings that can be modified at 
runtime. It's particularly useful
+ * for creating custom property sources (e.g., Spring properties, 
configuration files) that can be added to
+ * {@link Settings}.
+ *
+ * <h5 class='section'>Thread Safety:</h5>
+ * <p>
+ * This class is thread-safe. The internal map is lazily initialized using 
{@link AtomicReference} and
+ * {@link ConcurrentHashMap} for thread-safe operations.
+ *
+ * <h5 class='section'>Null Value Handling:</h5>
+ * <p>
+ * Setting a value to <c>null</c> stores <c>Optional.empty()</c> in the map, 
which means {@link #get(String)}
+ * will return <c>Optional.empty()</c> (not <c>null</c>). This allows you to 
explicitly override system properties
+ * with null values. Use {@link #unset(String)} if you want to remove a key 
entirely (so {@link #get(String)} returns <c>null</c>).
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ *     <jc>// Create a source and add properties</jc>
+ *     MapSource <jv>source</jv> = <jk>new</jk> MapSource();
+ *     <jv>source</jv>.set(<js>"my.property"</js>, <js>"value"</js>);
+ *     <jv>source</jv>.set(<js>"another.property"</js>, 
<js>"another-value"</js>);
+ *
+ *     <jc>// Add to Settings</jc>
+ *     Settings.<jsf>get</jsf>().addSource(<jv>source</jv>);
+ *
+ *     <jc>// Override a system property with null</jc>
+ *     <jv>source</jv>.set(<js>"system.property"</js>, <jk>null</jk>);
+ *     <jc>// get() will now return Optional.empty() for "system.property"</jc>
+ *
+ *     <jc>// Remove a property entirely</jc>
+ *     <jv>source</jv>.unset(<js>"my.property"</js>);
+ *     <jc>// get() will now return null for "my.property"</jc>
+ * </p>
+ */
+public class MapSource implements SettingSource {
+
+       private final AtomicReference<Map<String,Optional<String>>> map = new 
AtomicReference<>();
+
+       /**
+        * Returns a setting from this source.
+        *
+        * <p>
+        * Returns <c>null</c> if the key doesn't exist in the map, or the 
stored value (which may be
+        * <c>Optional.empty()</c> if the value was explicitly set to 
<c>null</c>).
+        *
+        * @param key The property name.
+        * @return The property value, <c>null</c> if the key doesn't exist, or 
<c>Optional.empty()</c> if the key
+        *      exists but has a null value.
+        */
+       @Override
+       public Optional<String> get(String key) {
+               var m = map.get();
+               if (m == null)
+                       return null; // Key not in source (map doesn't exist)
+               if (! m.containsKey(key))
+                       return null; // Key not in source (key doesn't exist in 
map)
+               // Key exists in map - return the value (which may be 
Optional.empty() if value was set to null)
+               return m.get(key);
+       }
+
+       /**
+        * Sets a setting in this source.
+        *
+        * <p>
+        * The internal map is lazily initialized on the first call to this 
method. Setting a value to <c>null</c>
+        * stores <c>Optional.empty()</c> in the map, which means {@link 
#get(String)} will return <c>Optional.empty()</c>
+        * (not <c>null</c>). This allows you to explicitly override system 
properties with null values.
+        *
+        * @param key The property name.
+        * @param value The property value, or <c>null</c> to set an empty 
override.
+        */
+       @Override
+       public void set(String key, String value) {
+               if (! canWrite())
+                       return;
+               var m = map.get();
+               if (m == null) {
+                       var newMap = new 
ConcurrentHashMap<String,Optional<String>>();
+                       m = map.compareAndSet(null, newMap) ? newMap : 
map.get();
+               }
+               m.put(key, opt(value));
+       }
+
+       /**
+        * Clears all entries from this source.
+        *
+        * <p>
+        * After calling this method, all keys will be removed from the map, 
and {@link #get(String)} will return
+        * <c>null</c> for all keys.
+        */
+       @Override
+       public void clear() {
+               if (! canWrite())
+                       return;
+               var m = map.get();
+               if (m != null)
+                       m.clear();
+       }
+
+       /**
+        * Returns <c>true</c> since this source is writable.
+        *
+        * @return <c>true</c>
+        */
+       @Override
+       public boolean canWrite() {
+               return true;
+       }
+
+       /**
+        * Removes a setting from this source.
+        *
+        * <p>
+        * After calling this method, {@link #get(String)} will return 
<c>null</c> for the specified key,
+        * indicating that the key doesn't exist in this source (as opposed to 
returning <c>Optional.empty()</c>,
+        * which would indicate the key exists but has a null value).
+        *
+        * @param name The property name to remove.
+        */
+       @Override
+       public void unset(String name) {
+               if (! canWrite())
+                       return;
+               var m = map.get();
+               if (m != null)
+                       m.remove(name);
+       }
+}
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/ReadOnlySource.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/ReadOnlySource.java
new file mode 100644
index 0000000000..25f0a576d0
--- /dev/null
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/ReadOnlySource.java
@@ -0,0 +1,116 @@
+/*
+ * 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.settings;
+
+import static org.apache.juneau.commons.utils.Utils.*;
+
+import java.util.*;
+import java.util.function.*;
+
+/**
+ * A read-only {@link SettingSource} implementation that delegates to a 
function.
+ *
+ * <p>
+ * This class provides a read-only source for settings that delegates property 
lookups to a provided function.
+ * It's particularly useful for wrapping existing property sources (e.g., 
{@link System#getProperty(String)},
+ * {@link System#getenv(String)}) as {@link SettingSource} instances.
+ *
+ * <h5 class='section'>Return Value Semantics:</h5>
+ * <ul class='spaced-list'>
+ *     <li>If the function returns <c>null</c>, this source returns 
<c>null</c> (key doesn't exist).
+ *     <li>If the function returns a non-null value, this source returns 
<c>Optional.of(value)</c>.
+ * </ul>
+ *
+ * <p>
+ * Note: This source cannot distinguish between a key that doesn't exist and a 
key that exists with a null value,
+ * since the function only returns a <c>String</c>. If you need to distinguish 
these cases, use {@link MapSource} instead.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ *     <jc>// Create a read-only source from System.getProperty</jc>
+ *     ReadOnlySource <jv>sysProps</jv> = <jk>new</jk> ReadOnlySource(x -&gt; 
System.getProperty(x));
+ *
+ *     <jc>// Create a read-only source from System.getenv</jc>
+ *     ReadOnlySource <jv>envVars</jv> = <jk>new</jk> ReadOnlySource(x -&gt; 
System.getenv(x));
+ *
+ *     <jc>// Add to Settings</jc>
+ *     Settings.<jsf>get</jsf>().addSource(<jv>sysProps</jv>);
+ * </p>
+ */
+public class ReadOnlySource implements SettingSource {
+
+       private final Function<String,String> function;
+
+       /**
+        * Constructor.
+        *
+        * @param function The function to delegate property lookups to. Must 
not be <c>null</c>.
+        */
+       public ReadOnlySource(Function<String,String> function) {
+               this.function = function;
+       }
+
+       /**
+        * Returns a setting by delegating to the function.
+        *
+        * <p>
+        * If the function returns <c>null</c>, this method returns <c>null</c> 
(indicating the key doesn't exist).
+        * If the function returns a non-null value, this method returns 
<c>Optional.of(value)</c>.
+        *
+        * @param name The property name.
+        * @return The property value, or <c>null</c> if the function returns 
<c>null</c>.
+        */
+       @Override
+       public Optional<String> get(String name) {
+               var v = function.apply(name);
+               return v == null ? null : opt(v);
+       }
+
+       /**
+        * Returns <c>false</c> since this source is read-only.
+        *
+        * @return <c>false</c>
+        */
+       @Override
+       public boolean canWrite() {
+               return false;
+       }
+
+       /**
+        * No-op since this source is read-only.
+        *
+        * @param name The property name (ignored).
+        * @param value The property value (ignored).
+        */
+       @Override
+       public void set(String name, String value) {}
+
+       /**
+        * No-op since this source is read-only.
+        *
+        * @param name The property name (ignored).
+        */
+       @Override
+       public void unset(String name) {}
+
+       /**
+        * No-op since this source is read-only.
+        */
+       @Override
+       public void clear() {}
+
+}
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SettingSource.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SettingSource.java
new file mode 100644
index 0000000000..09b88f96d6
--- /dev/null
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SettingSource.java
@@ -0,0 +1,119 @@
+/*
+ * 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.settings;
+
+import java.util.*;
+
+/**
+ * Interface for pluggable property sources used by {@link Settings}.
+ *
+ * <p>
+ * A setting source provides a way to retrieve and optionally modify property 
values.
+ * Sources are checked in reverse order (last added is checked first) when 
looking up properties.
+ *
+ * <h5 class='section'>Return Value Semantics:</h5>
+ * <ul class='spaced-list'>
+ *     <li><c>null</c> - The setting does not exist in this source. The lookup 
will continue to the next source.
+ *     <li><c>Optional.empty()</c> - The setting exists but has an explicitly 
null value. This will be returned
+ *             immediately, overriding any values from lower-priority sources.
+ *     <li><c>Optional.of(value)</c> - The setting exists and has a non-null 
value. This will be returned immediately.
+ * </ul>
+ *
+ * <h5 class='section'>Examples:</h5>
+ * <p class='bjava'>
+ *     <jc>// Create a writable source</jc>
+ *     MapSource <jv>source</jv> = <jk>new</jk> MapSource();
+ *     <jv>source</jv>.set(<js>"my.property"</js>, <js>"value"</js>);
+ *
+ *     <jc>// Create a read-only source from a function</jc>
+ *     ReadOnlySource <jv>readOnly</jv> = <jk>new</jk> ReadOnlySource(x -&gt; 
System.getProperty(x));
+ * </p>
+ */
+public interface SettingSource {
+
+       /**
+        * Returns a setting in this setting source.
+        *
+        * <p>
+        * Return value semantics:
+        * <ul>
+        *      <li><c>null</c> - The setting does not exist in this source. 
The lookup will continue to the next source.
+        *      <li><c>Optional.empty()</c> - The setting exists but has an 
explicitly null value. This will be returned
+        *              immediately, overriding any values from lower-priority 
sources.
+        *      <li><c>Optional.of(value)</c> - The setting exists and has a 
non-null value. This will be returned immediately.
+        * </ul>
+        *
+        * @param name The property name.
+        * @return The property value, <c>null</c> if the property doesn't 
exist in this source, or <c>Optional.empty()</c>
+        *      if the property exists but has a null value.
+        */
+       Optional<String> get(String name);
+
+       /**
+        * Returns whether this source is writable.
+        *
+        * <p>
+        * If <c>false</c>, all write operations ({@link #set(String, String)}, 
{@link #unset(String)}, {@link #clear()})
+        * should be no-ops.
+        *
+        * @return <c>true</c> if this source is writable, <c>false</c> 
otherwise.
+        */
+       boolean canWrite();
+
+       /**
+        * Sets a setting in this setting source.
+        *
+        * <p>
+        * Should be a no-op if the source is not writable (i.e., {@link 
#canWrite()} returns <c>false</c>).
+        *
+        * <p>
+        * Setting a value to <c>null</c> means that {@link #get(String)} will 
return <c>Optional.empty()</c> for that key,
+        * effectively overriding any values from lower-priority sources. Use 
{@link #unset(String)} if you want
+        * {@link #get(String)} to return <c>null</c> (indicating the key 
doesn't exist in this source).
+        *
+        * @param name The property name.
+        * @param value The property value, or <c>null</c> to set an empty 
override.
+        */
+       void set(String name, String value);
+
+       /**
+        * Removes a setting from this setting source.
+        *
+        * <p>
+        * Should be a no-op if the source is not writable (i.e., {@link 
#canWrite()} returns <c>false</c>).
+        *
+        * <p>
+        * After calling this method, {@link #get(String)} will return 
<c>null</c> for the specified key,
+        * indicating that the key doesn't exist in this source (as opposed to 
returning <c>Optional.empty()</c>,
+        * which would indicate the key exists but has a null value).
+        *
+        * @param name The property name to remove.
+        */
+       void unset(String name);
+
+       /**
+        * Clears all settings from this setting source.
+        *
+        * <p>
+        * Should be a no-op if the source is not writable (i.e., {@link 
#canWrite()} returns <c>false</c>).
+        *
+        * <p>
+        * After calling this method, all keys will be removed from this 
source, and {@link #get(String)} will
+        * return <c>null</c> for all keys.
+        */
+       void clear();
+}
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
new file mode 100644
index 0000000000..b57b2d3188
--- /dev/null
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/Settings.java
@@ -0,0 +1,533 @@
+/*
+ * 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.settings;
+
+import static org.apache.juneau.commons.utils.AssertionUtils.*;
+import static org.apache.juneau.commons.utils.Utils.*;
+
+import java.io.*;
+import java.net.*;
+import java.nio.charset.*;
+import java.nio.file.*;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Encapsulates Java system properties with support for global and per-thread 
overrides for unit testing.
+ *
+ * <p>
+ * This class provides a thread-safe way to access system properties that can 
be overridden at both the global level
+ * and per-thread level, making it useful for unit tests that need to 
temporarily change system property
+ * values without affecting other tests or threads.
+ *
+ * <h5 class='section'>Lookup Order:</h5>
+ * <p>
+ * When retrieving a property value, the lookup order is:
+ * <ol>
+ *     <li>Per-thread override (if set via {@link #setLocal(String, String)})
+ *     <li>Global override (if set via {@link #setGlobal(String, String)})
+ *     <li>Sources in reverse order (last source added via {@link 
#addSource(SettingSource)} is checked first)
+ *     <li>System property source (default, always second-to-last)
+ *     <li>System environment variable source (default, always last)
+ * </ol>
+ *
+ * <h5 class='section'>Features:</h5>
+ * <ul class='spaced-list'>
+ *     <li>System property access - read Java system properties with type 
conversion
+ *     <li>Global overrides - override system properties globally for all 
threads
+ *     <li>Per-thread overrides - override system properties for specific 
threads (useful for unit tests)
+ *     <li>Custom sources - add arbitrary property sources (e.g., Spring 
properties, environment variables, config files)
+ *     <li>Disable override support - system property to prevent new global 
overrides from being set
+ *     <li>Type-safe accessors - convenience methods for common types: 
Integer, Long, Boolean, Double, Float, File, Path, URI, Charset
+ * </ul>
+ *
+ * <h5 class='section'>Usage Examples:</h5>
+ * <p class='bjava'>
+ *     <jc>// Get a system property as a string</jc>
+ *     Optional&lt;String&gt; <jv>value</jv> = 
Settings.<jsf>get</jsf>().get(<js>"my.property"</js>);
+ *
+ *     <jc>// Get with type conversion</jc>
+ *     Optional&lt;Integer&gt; <jv>intValue</jv> = 
Settings.<jsf>get</jsf>().getInteger(<js>"my.int.property"</js>);
+ *     Optional&lt;Long&gt; <jv>longValue</jv> = 
Settings.<jsf>get</jsf>().getLong(<js>"my.long.property"</js>);
+ *     Optional&lt;Boolean&gt; <jv>boolValue</jv> = 
Settings.<jsf>get</jsf>().getBoolean(<js>"my.bool.property"</js>);
+ *     Optional&lt;Double&gt; <jv>doubleValue</jv> = 
Settings.<jsf>get</jsf>().getDouble(<js>"my.double.property"</js>);
+ *     Optional&lt;Float&gt; <jv>floatValue</jv> = 
Settings.<jsf>get</jsf>().getFloat(<js>"my.float.property"</js>);
+ *     Optional&lt;File&gt; <jv>fileValue</jv> = 
Settings.<jsf>get</jsf>().getFile(<js>"my.file.property"</js>);
+ *     Optional&lt;Path&gt; <jv>pathValue</jv> = 
Settings.<jsf>get</jsf>().getPath(<js>"my.path.property"</js>);
+ *     Optional&lt;URI&gt; <jv>uriValue</jv> = 
Settings.<jsf>get</jsf>().getURI(<js>"my.uri.property"</js>);
+ *     Optional&lt;Charset&gt; <jv>charsetValue</jv> = 
Settings.<jsf>get</jsf>().getCharset(<js>"my.charset.property"</js>);
+ *
+ *     <jc>// Override for current thread (useful in unit tests)</jc>
+ *     Settings.<jsf>get</jsf>().setLocal(<js>"my.property"</js>, 
<js>"test-value"</js>);
+ *     <jc>// ... test code that uses the override ...</jc>
+ *     Settings.<jsf>get</jsf>().unsetLocal(<js>"my.property"</js>);  <jc>// 
Remove specific override</jc>
+ *     <jc>// OR</jc>
+ *     Settings.<jsf>get</jsf>().clearLocal();  <jc>// Clear all thread-local 
overrides</jc>
+ *
+ *     <jc>// Set global override (applies to all threads)</jc>
+ *     Settings.<jsf>get</jsf>().setGlobal(<js>"my.property"</js>, 
<js>"global-value"</js>);
+ *     <jc>// ... code that uses the global override ...</jc>
+ *     Settings.<jsf>get</jsf>().unsetGlobal(<js>"my.property"</js>);  <jc>// 
Remove specific override</jc>
+ *     <jc>// OR</jc>
+ *     Settings.<jsf>get</jsf>().clearGlobal();  <jc>// Clear all global 
overrides</jc>
+ *
+ *     <jc>// Add a custom source (e.g., Spring properties)</jc>
+ *     MapSource <jv>springSource</jv> = <jk>new</jk> MapSource();
+ *     <jv>springSource</jv>.set(<js>"spring.datasource.url"</js>, 
<js>"jdbc:postgresql://localhost/db"</js>);
+ *     Settings.<jsf>get</jsf>().addSource(<jv>springSource</jv>);
+ * </p>
+ *
+ * <h5 class='section'>System Properties:</h5>
+ * <ul class='spaced-list'>
+ *     <li><c>juneau.settings.disableGlobal</c> - If set to <c>true</c>, 
prevents new global overrides
+ *             from being set via {@link #setGlobal(String, String)}. Existing 
global overrides will still be
+ *             returned by {@link #get(String)} until explicitly removed.
+ *     <li><c>juneau.settings.disableCustomSources</c> - If set to 
<c>true</c>, prevents custom sources
+ *             from being added via {@link #addSource(SettingSource)} or 
{@link #setSources(SettingSource...)}.
+ * </ul>
+ */
+public class Settings {
+
+       private static final Settings INSTANCE = new Settings();
+
+       /**
+        * Returns the singleton instance of Settings.
+        *
+        * @return The singleton Settings instance.
+        */
+       public static Settings get() {
+               return INSTANCE;
+       }
+
+       private final MapSource globalOverrides = new MapSource();
+       private final ThreadLocal<MapSource> threadOverrides = new 
ThreadLocal<>();
+       private final List<SettingSource> sources = new 
CopyOnWriteArrayList<>();
+
+       /**
+        * System property source that delegates to {@link 
System#getProperty(String)}.
+        */
+       public static final SettingSource SYSTEM_PROPERTY_SOURCE = new 
ReadOnlySource(x -> System.getProperty(x));
+
+       /**
+        * System environment variable source that delegates to {@link 
System#getenv(String)}.
+        */
+       public static final SettingSource SYSTEM_ENV_SOURCE = new 
ReadOnlySource(x -> System.getenv(x));
+
+       /**
+        * Returns properties for this Settings object itself.
+        * Note that these are initialized at startup and not changeable 
through System.setProperty().
+        */
+       private static final Optional<String> initProperty(String property) {
+               var v = SYSTEM_PROPERTY_SOURCE.get(property);
+               if (v != null)
+                       return v;
+               v = SYSTEM_ENV_SOURCE.get(property);
+               if (v != null)
+                       return v;
+               return opte();
+       }
+
+       private static final String DISABLE_CUSTOM_SOURCES_PROP = 
"juneau.settings.disableCustomSources";
+       private static final String DISABLE_GLOBAL_PROP = 
"juneau.settings.disableGlobal";
+       private static final boolean DISABLE_CUSTOM_SOURCES = 
initProperty(DISABLE_CUSTOM_SOURCES_PROP).map(Boolean::valueOf).orElse(false);
+       private static final boolean DISABLE_GLOBAL = 
initProperty(DISABLE_GLOBAL_PROP).map(Boolean::valueOf).orElse(false);
+
+       /**
+        * Constructor.
+        */
+       private Settings() {
+               // Initialize with system sources as defaults (env first, then 
property, so property has higher precedence)
+               sources.add(SYSTEM_ENV_SOURCE);
+               sources.add(SYSTEM_PROPERTY_SOURCE);
+       }
+
+       /**
+        * Returns the value of the specified system property.
+        *
+        * <p>
+        * The lookup order is:
+        * <ol>
+        *      <li>Per-thread override (if set via {@link #setLocal(String, 
String)})
+        *      <li>Global override (if set via {@link #setGlobal(String, 
String)})
+        *      <li>Sources in reverse order (last source added via {@link 
#addSource(SettingSource)} is checked first)
+        *      <li>System property source (default, always second-to-last)
+        *      <li>System environment variable source (default, always last)
+        * </ol>
+        *
+        * @param name The property name.
+        * @return The property value, or {@link Optional#empty()} if not found.
+        */
+       public Optional<String> get(String name) {
+               // 1. Check thread-local override
+               var localSource = threadOverrides.get();
+               if (localSource != null) {
+                       var v = localSource.get(name);
+                       if (v != null)
+                               return v; // v is Optional.empty() if key 
exists with null value, or Optional.of(value) if present
+               }
+
+               // 2. Check global override
+               var v = globalOverrides.get(name);
+               if (v != null)
+                       return v; // v is Optional.empty() if key exists with 
null value, or Optional.of(value) if present
+
+               // 3. Check sources in reverse order (last added first)
+               for (int i = sources.size() - 1; i >= 0; i--) {
+                       var source = sources.get(i);
+                       if (source == null)
+                               continue; // Skip null sources (defensive check)
+                       var result = source.get(name);
+                       if (result == null)
+                               continue; // Key not in this source, try next 
source
+                       // If result is Optional.empty(), it means key exists 
with null value, so return it
+                       // If result is present, return it
+                       return result;
+               }
+
+               return Optional.empty();
+       }
+
+       /**
+        * Returns the value of the specified system property as an Integer.
+        *
+        * <p>
+        * The property value is parsed using {@link Integer#valueOf(String)}. 
If the property is not found
+        * or cannot be parsed as an integer, returns {@link Optional#empty()}.
+        *
+        * @param name The property name.
+        * @return The property value as an Integer, or {@link 
Optional#empty()} if not found or not a valid integer.
+        */
+       public Optional<Integer> getInteger(String name) {
+               return get(name).map(v -> 
safeOrNull(()->Integer.valueOf(v))).filter(Objects::nonNull);
+       }
+
+       /**
+        * Returns the value of the specified system property as a Long.
+        *
+        * <p>
+        * The property value is parsed using {@link Long#valueOf(String)}. If 
the property is not found
+        * or cannot be parsed as a long, returns {@link Optional#empty()}.
+        *
+        * @param name The property name.
+        * @return The property value as a Long, or {@link Optional#empty()} if 
not found or not a valid long.
+        */
+       public Optional<Long> getLong(String name) {
+               return get(name).map(v -> 
safeOrNull(()->Long.valueOf(v))).filter(Objects::nonNull);
+       }
+
+       /**
+        * Returns the value of the specified system property as a Boolean.
+        *
+        * <p>
+        * The property value is parsed using {@link 
Boolean#parseBoolean(String)}, which returns <c>true</c>
+        * if the value is (case-insensitive) "true", otherwise <c>false</c>. 
Note that this method will
+        * return <c>Optional.of(false)</c> for any non-empty value that is not 
"true", and
+        * {@link Optional#empty()} only if the property is not set.
+        *
+        * @param name The property name.
+        * @return The property value as a Boolean, or {@link Optional#empty()} 
if not found.
+        */
+       public Optional<Boolean> getBoolean(String name) {
+               return get(name).map(v -> Boolean.parseBoolean(v));
+       }
+
+       /**
+        * Returns the value of the specified system property as a Double.
+        *
+        * <p>
+        * The property value is parsed using {@link Double#valueOf(String)}. 
If the property is not found
+        * or cannot be parsed as a double, returns {@link Optional#empty()}.
+        *
+        * @param name The property name.
+        * @return The property value as a Double, or {@link Optional#empty()} 
if not found or not a valid double.
+        */
+       public Optional<Double> getDouble(String name) {
+               return get(name).map(v -> 
safeOrNull(()->Double.valueOf(v))).filter(Objects::nonNull);
+       }
+
+       /**
+        * Returns the value of the specified system property as a Float.
+        *
+        * <p>
+        * The property value is parsed using {@link Float#valueOf(String)}. If 
the property is not found
+        * or cannot be parsed as a float, returns {@link Optional#empty()}.
+        *
+        * @param name The property name.
+        * @return The property value as a Float, or {@link Optional#empty()} 
if not found or not a valid float.
+        */
+       public Optional<Float> getFloat(String name) {
+               return get(name).map(v -> 
safeOrNull(()->Float.valueOf(v))).filter(Objects::nonNull);
+       }
+
+       /**
+        * Returns the value of the specified system property as a File.
+        *
+        * <p>
+        * The property value is converted to a {@link File} using the {@link 
File#File(String)} constructor.
+        * If the property is not found, returns {@link Optional#empty()}. Note 
that this method does not
+        * validate that the file path is valid or that the file exists.
+        *
+        * @param name The property name.
+        * @return The property value as a File, or {@link Optional#empty()} if 
not found.
+        */
+       public Optional<File> getFile(String name) {
+               return get(name).map(v -> new File(v));
+       }
+
+       /**
+        * Returns the value of the specified system property as a Path.
+        *
+        * <p>
+        * The property value is converted to a {@link Path} using {@link 
Paths#get(String, String...)}.
+        * If the property is not found or the path string is invalid, returns 
{@link Optional#empty()}.
+        *
+        * @param name The property name.
+        * @return The property value as a Path, or {@link Optional#empty()} if 
not found or not a valid path.
+        */
+       public Optional<Path> getPath(String name) {
+               return get(name).map(v -> 
safeOrNull(()->Paths.get(v))).filter(Objects::nonNull);
+       }
+
+       /**
+        * Returns the value of the specified system property as a URI.
+        *
+        * <p>
+        * The property value is converted to a {@link URI} using {@link 
URI#create(String)}.
+        * If the property is not found or the URI string is invalid, returns 
{@link Optional#empty()}.
+        *
+        * @param name The property name.
+        * @return The property value as a URI, or {@link Optional#empty()} if 
not found or not a valid URI.
+        */
+       public Optional<URI> getURI(String name) {
+               return get(name).map(v -> 
safeOrNull(()->URI.create(v))).filter(Objects::nonNull);
+       }
+
+       /**
+        * Returns the value of the specified system property as a Charset.
+        *
+        * <p>
+        * The property value is converted to a {@link Charset} using {@link 
Charset#forName(String)}.
+        * If the property is not found or the charset name is not supported, 
returns {@link Optional#empty()}.
+        *
+        * @param name The property name.
+        * @return The property value as a Charset, or {@link Optional#empty()} 
if not found or not a valid charset.
+        */
+       public Optional<Charset> getCharset(String name) {
+               return get(name).map(v -> 
safeOrNull(()->Charset.forName(v))).filter(Objects::nonNull);
+       }
+
+       /**
+        * Sets a global override for the specified property.
+        *
+        * <p>
+        * This override will apply to all threads and takes precedence over 
system properties.
+        * However, per-thread overrides (set via {@link #setLocal(String, 
String)}) will still take precedence
+        * over global overrides.
+        *
+        * <p>
+        * If the <c>juneau.settings.disableGlobal</c> system property is set 
to <c>true</c>,
+        * this method will throw an exception. This allows system 
administrators
+        * to prevent applications from overriding system properties globally.
+        *
+        * <p>
+        * Setting a value to <c>null</c> will store an empty optional, 
effectively overriding the system
+        * property to return empty. Use {@link #unsetGlobal(String)} to 
completely remove the override.
+        *
+        * @param name The property name.
+        * @param value The override value, or <c>null</c> to set an empty 
override.
+        * @see #unsetGlobal(String)
+        * @see #clearGlobal()
+        */
+       public void setGlobal(String name, String value) {
+               assertArg(! DISABLE_GLOBAL, "Global settings have been disabled 
via ''{0}''", DISABLE_GLOBAL_PROP);
+               globalOverrides.set(name, value);
+       }
+
+       /**
+        * Removes a global override for the specified property.
+        *
+        * <p>
+        * After calling this method, the property will fall back to the system 
property value
+        * (or per-thread override if one exists).
+        *
+        * @param name The property name.
+        * @see #setGlobal(String, String)
+        * @see #clearGlobal()
+        */
+       public void unsetGlobal(String name) {
+               assertArg(! DISABLE_GLOBAL, "Global settings have been disabled 
via ''{0}''", DISABLE_GLOBAL_PROP);
+               globalOverrides.unset(name);
+       }
+
+       /**
+        * Sets a per-thread override for the specified property.
+        *
+        * <p>
+        * This override will only apply to the current thread and takes 
precedence over global overrides
+        * and system properties. This is particularly useful in unit tests 
where you need to temporarily
+        * change a system property value without affecting other threads or 
tests.
+        *
+        * <p>
+        * Setting a value to <c>null</c> will store an empty optional, 
effectively overriding the property
+        * to return empty for this thread. Use {@link #unsetLocal(String)} to 
completely remove the override.
+        *
+        * @param name The property name.
+        * @param value The override value, or <c>null</c> to set an empty 
override.
+        * @see #unsetLocal(String)
+        * @see #clearLocal()
+        */
+       public void setLocal(String name, String value) {
+               var localSource = threadOverrides.get();
+               if (localSource == null) {
+                       localSource = new MapSource();
+                       threadOverrides.set(localSource);
+               }
+               localSource.set(name, value);
+       }
+
+       /**
+        * Removes a per-thread override for the specified property.
+        *
+        * <p>
+        * After calling this method, the property will fall back to the global 
override (if set)
+        * or the system property value for the current thread.
+        *
+        * @param name The property name.
+        * @see #setLocal(String, String)
+        * @see #clearLocal()
+        */
+       public void unsetLocal(String name) {
+               var localSource = threadOverrides.get();
+               if (localSource != null)
+                       localSource.unset(name);
+       }
+
+       /**
+        * Clears all per-thread overrides for the current thread.
+        *
+        * <p>
+        * After calling this method, all properties will fall back to global 
overrides (if set)
+        * or system property values for the current thread.
+        *
+        * <p>
+        * This is typically called in a <c>@AfterEach</c> or <c>@After</c> 
test method to clean up
+        * thread-local overrides after a test completes.
+        *
+        * @see #setLocal(String, String)
+        * @see #unsetLocal(String)
+        */
+       public void clearLocal() {
+               threadOverrides.remove();
+       }
+
+       /**
+        * Clears all global overrides.
+        *
+        * <p>
+        * After calling this method, all properties will fall back to resolver 
values
+        * (or per-thread overrides if they exist).
+        *
+        * @see #setGlobal(String, String)
+        * @see #unsetGlobal(String)
+        */
+       public void clearGlobal() {
+               globalOverrides.clear();
+       }
+
+       /**
+        * Adds a source to the source list.
+        *
+        * <p>
+        * Sources are checked in reverse order (last added is checked first), 
after global overrides
+        * but before the system property source. This allows you to add custom 
property sources such
+        * as Spring properties, environment variables, or configuration files.
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <jc>// Add a Spring properties source</jc>
+        *      MapSource <jv>springSource</jv> = <jk>new</jk> MapSource();
+        *      <jv>springSource</jv>.set(<js>"spring.datasource.url"</js>, 
<js>"jdbc:postgresql://localhost/db"</js>);
+        *      Settings.<jsf>get</jsf>().addSource(<jv>springSource</jv>);
+        * </p>
+        *
+        * @param source The source. Must not be <c>null</c>.
+        * @return This object for method chaining.
+        * @see #setSources(SettingSource...)
+        */
+       public Settings addSource(SettingSource source) {
+               assertArg(! DISABLE_CUSTOM_SOURCES, "Global custom sources have 
been disabled via ''{0}''", DISABLE_CUSTOM_SOURCES_PROP);
+               assertArgNotNull("source", source);
+               // Add at the end - since we iterate in reverse order, this 
means it's checked first (before system sources)
+               sources.add(source);
+               return this;
+       }
+
+       /**
+        * Sets the source list, replacing all existing sources.
+        *
+        * <p>
+        * This method clears all existing sources (except the system property 
and environment variable sources which are always
+        * present) and adds the specified sources in order. The sources will 
be checked in reverse
+        * order (last source in the array is checked first).
+        *
+        * <p>
+        * Note that using this method resets existing sources, so if you want 
to maintain resolving environment variables
+        * or system properties, you'll need to add {@link #SYSTEM_ENV_SOURCE} 
and {@link #SYSTEM_PROPERTIES_SOURCE} to the
+        * list of sources added to this method.
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <jc>// Set multiple sources</jc>
+        *      MapSource <jv>springSource</jv> = <jk>new</jk> MapSource();
+        *      MapSource <jv>envSource</jv> = <jk>new</jk> MapSource();
+        *      Settings.<jsf>get</jsf>().setSources(<jv>springSource</jv>, 
<jv>envSource</jv>);
+        *      <jc>// Lookup order: local &gt; global &gt; envSource &gt; 
springSource &gt; system properties &gt; system env vars</jc>
+        * </p>
+        *
+        * <p>
+        * Note that the order of precedence of the sources is in reverse 
order.  So sources at the end of the list are
+        * checked before sources at the beginning of the list.
+        *
+        * @param sources The sources to add. Must not be <c>null</c> or 
contain <c>null</c> elements.
+        * @return This object for method chaining.
+        * @see #addSource(SettingSource)
+        */
+       @SafeVarargs
+       public final Settings setSources(SettingSource...sources) {
+               assertArg(! DISABLE_CUSTOM_SOURCES, "Global custom sources have 
been disabled via ''{0}''", DISABLE_CUSTOM_SOURCES_PROP);
+               assertVarargsNotNull("sources", sources);
+               // Remove all sources
+               this.sources.clear();
+               // Add new sources
+               for (var source : sources) {
+                       this.sources.add(source);
+               }
+               return this;
+       }
+
+       /**
+        * Resets the sources list to default (only system sources).
+        * Package-private for testing purposes.
+        */
+       void resetSources() {
+               this.sources.clear();
+               this.sources.add(SYSTEM_ENV_SOURCE);
+               this.sources.add(SYSTEM_PROPERTY_SOURCE);
+       }
+}
+
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 3a67bf6090..510fe76bdc 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
@@ -1519,6 +1519,45 @@ public class Utils {
                }
        }
 
+       /**
+        * Executes a supplier that may throw an exception and returns the 
result or <c>null</c>.
+        *
+        * <p>
+        * If the supplier executes successfully, returns the result.
+        * If the supplier throws any exception, returns <c>null</c>.
+        *
+        * <p>
+        * This is useful for operations that may fail but you want to handle 
the failure
+        * gracefully by returning <c>null</c> instead of throwing an 
exception. This is particularly
+        * useful in fluent method chains where you want to filter out failed 
conversions.
+        *
+        * <p>
+        * This method is similar to {@link #safeOpt(ThrowingSupplier)} but 
returns <c>null</c> instead
+        * of <c>Optional.empty()</c> when an exception occurs. Use this method 
when you prefer <c>null</c>
+        * over <c>Optional</c> for error handling.
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <jc>// Parse an integer, returning null if parsing fails</jc>
+        *      Integer <jv>value</jv> = <jsm>safeOrNull</jsm>(() -&gt; 
Integer.valueOf(<js>"123"</js>));  <jc>// 123</jc>
+        *      Integer <jv>invalid</jv> = <jsm>safeOrNull</jsm>(() -&gt; 
Integer.valueOf(<js>"abc"</js>));  <jc>// null</jc>
+        *
+        *      <jc>// Use in a fluent chain to filter out failed 
conversions</jc>
+        *      Optional&lt;Integer&gt; <jv>parsed</jv> = 
get(<js>"my.property"</js>)
+        *              .map(<jv>v</jv> -&gt; <jsm>safeOrNull</jsm>(() -&gt; 
Integer.valueOf(<jv>v</jv>)))
+        *              .filter(Objects::nonNull);
+        * </p>
+        *
+        * @param <T> The return type.
+        * @param s The supplier that may throw an exception.
+        * @return The result of the supplier if successful, or <c>null</c> if 
an exception was thrown.
+        * @see #safeOpt(ThrowingSupplier)
+        * @see #safe(ThrowingSupplier)
+        */
+       public static <T> T safeOrNull(ThrowingSupplier<T> s) {
+               return safeOpt(s).orElse(null);
+       }
+
        /**
         * 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/settings/Settings_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/commons/settings/Settings_Test.java
new file mode 100644
index 0000000000..bae91ec0b2
--- /dev/null
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/commons/settings/Settings_Test.java
@@ -0,0 +1,613 @@
+/*
+ * 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.settings;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.io.File;
+import java.net.URI;
+import java.nio.charset.Charset;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Optional;
+
+import org.apache.juneau.*;
+import org.junit.jupiter.api.*;
+
+class Settings_Test extends TestBase {
+
+       private static final String TEST_PROP = "juneau.test.property";
+       private static final String TEST_PROP_2 = "juneau.test.property2";
+
+       @BeforeEach
+       void setUp() {
+               // Clean up before each test
+               Settings.get().clearLocal();
+               Settings.get().clearGlobal();
+               Settings.get().resetSources();
+               // Remove test system properties if they exist
+               System.clearProperty(TEST_PROP);
+               System.clearProperty(TEST_PROP_2);
+       }
+
+       @AfterEach
+       void tearDown() {
+               // Clean up after each test
+               Settings.get().clearLocal();
+               Settings.get().clearGlobal();
+               System.clearProperty(TEST_PROP);
+               System.clearProperty(TEST_PROP_2);
+       }
+
+       
//====================================================================================================
+       // get() - Basic functionality
+       
//====================================================================================================
+       @Test
+       void a01_get_fromSystemProperty() {
+               System.setProperty(TEST_PROP, "system-value");
+               Optional<String> result = Settings.get().get(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertEquals("system-value", result.get());
+       }
+
+       @Test
+       void a02_get_notFound() {
+               Optional<String> result = 
Settings.get().get("nonexistent.property");
+               assertFalse(result.isPresent());
+       }
+
+       @Test
+       void a03_get_fromGlobalOverride() {
+               Settings.get().setGlobal(TEST_PROP, "global-value");
+               Optional<String> result = Settings.get().get(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertEquals("global-value", result.get());
+       }
+
+       @Test
+       void a04_get_fromLocalOverride() {
+               Settings.get().setLocal(TEST_PROP, "local-value");
+               Optional<String> result = Settings.get().get(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertEquals("local-value", result.get());
+       }
+
+       @Test
+       void a05_get_lookupOrder_localOverridesGlobal() {
+               System.setProperty(TEST_PROP, "system-value");
+               Settings.get().setGlobal(TEST_PROP, "global-value");
+               Settings.get().setLocal(TEST_PROP, "local-value");
+               Optional<String> result = Settings.get().get(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertEquals("local-value", result.get());
+       }
+
+       @Test
+       void a06_get_lookupOrder_globalOverridesSystem() {
+               System.setProperty(TEST_PROP, "system-value");
+               Settings.get().setGlobal(TEST_PROP, "global-value");
+               Optional<String> result = Settings.get().get(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertEquals("global-value", result.get());
+       }
+
+       @Test
+       void a07_get_nullValue() {
+               Settings.get().setLocal(TEST_PROP, null);
+               Optional<String> result = Settings.get().get(TEST_PROP);
+               assertFalse(result.isPresent());
+       }
+
+       
//====================================================================================================
+       // getInteger()
+       
//====================================================================================================
+       @Test
+       void b01_getInteger_valid() {
+               System.setProperty(TEST_PROP, "123");
+               Optional<Integer> result = Settings.get().getInteger(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertEquals(123, result.get());
+       }
+
+       @Test
+       void b02_getInteger_invalid() {
+               System.setProperty(TEST_PROP, "not-a-number");
+               Optional<Integer> result = Settings.get().getInteger(TEST_PROP);
+               assertFalse(result.isPresent());
+       }
+
+       @Test
+       void b03_getInteger_notFound() {
+               Optional<Integer> result = 
Settings.get().getInteger("nonexistent.property");
+               assertFalse(result.isPresent());
+       }
+
+       @Test
+       void b04_getInteger_fromOverride() {
+               Settings.get().setLocal(TEST_PROP, "456");
+               Optional<Integer> result = Settings.get().getInteger(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertEquals(456, result.get());
+       }
+
+       
//====================================================================================================
+       // getLong()
+       
//====================================================================================================
+       @Test
+       void c01_getLong_valid() {
+               System.setProperty(TEST_PROP, "123456789");
+               Optional<Long> result = Settings.get().getLong(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertEquals(123456789L, result.get());
+       }
+
+       @Test
+       void c02_getLong_invalid() {
+               System.setProperty(TEST_PROP, "not-a-number");
+               Optional<Long> result = Settings.get().getLong(TEST_PROP);
+               assertFalse(result.isPresent());
+       }
+
+       @Test
+       void c03_getLong_fromOverride() {
+               Settings.get().setLocal(TEST_PROP, "987654321");
+               Optional<Long> result = Settings.get().getLong(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertEquals(987654321L, result.get());
+       }
+
+       
//====================================================================================================
+       // getBoolean()
+       
//====================================================================================================
+       @Test
+       void d01_getBoolean_true() {
+               System.setProperty(TEST_PROP, "true");
+               Optional<Boolean> result = Settings.get().getBoolean(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertTrue(result.get());
+       }
+
+       @Test
+       void d02_getBoolean_false() {
+               System.setProperty(TEST_PROP, "false");
+               Optional<Boolean> result = Settings.get().getBoolean(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertFalse(result.get());
+       }
+
+       @Test
+       void d03_getBoolean_caseInsensitive() {
+               System.setProperty(TEST_PROP, "TRUE");
+               Optional<Boolean> result = Settings.get().getBoolean(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertTrue(result.get());
+       }
+
+       @Test
+       void d04_getBoolean_nonTrueValue() {
+               System.setProperty(TEST_PROP, "anything");
+               Optional<Boolean> result = Settings.get().getBoolean(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertFalse(result.get());
+       }
+
+       @Test
+       void d05_getBoolean_notFound() {
+               Optional<Boolean> result = 
Settings.get().getBoolean("nonexistent.property");
+               assertFalse(result.isPresent());
+       }
+
+       
//====================================================================================================
+       // getDouble()
+       
//====================================================================================================
+       @Test
+       void e01_getDouble_valid() {
+               System.setProperty(TEST_PROP, "123.456");
+               Optional<Double> result = Settings.get().getDouble(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertEquals(123.456, result.get(), 0.0001);
+       }
+
+       @Test
+       void e02_getDouble_invalid() {
+               System.setProperty(TEST_PROP, "not-a-number");
+               Optional<Double> result = Settings.get().getDouble(TEST_PROP);
+               assertFalse(result.isPresent());
+       }
+
+       
//====================================================================================================
+       // getFloat()
+       
//====================================================================================================
+       @Test
+       void f01_getFloat_valid() {
+               System.setProperty(TEST_PROP, "123.456");
+               Optional<Float> result = Settings.get().getFloat(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertEquals(123.456f, result.get(), 0.0001f);
+       }
+
+       @Test
+       void f02_getFloat_invalid() {
+               System.setProperty(TEST_PROP, "not-a-number");
+               Optional<Float> result = Settings.get().getFloat(TEST_PROP);
+               assertFalse(result.isPresent());
+       }
+
+       
//====================================================================================================
+       // getFile()
+       
//====================================================================================================
+       @Test
+       void g01_getFile_valid() {
+               System.setProperty(TEST_PROP, "/tmp/test.txt");
+               Optional<File> result = Settings.get().getFile(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertEquals(new File("/tmp/test.txt"), result.get());
+       }
+
+       @Test
+       void g02_getFile_notFound() {
+               Optional<File> result = 
Settings.get().getFile("nonexistent.property");
+               assertFalse(result.isPresent());
+       }
+
+       
//====================================================================================================
+       // getPath()
+       
//====================================================================================================
+       @Test
+       void h01_getPath_valid() {
+               System.setProperty(TEST_PROP, "/tmp/test.txt");
+               Optional<Path> result = Settings.get().getPath(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertEquals(Paths.get("/tmp/test.txt"), result.get());
+       }
+
+       @Test
+       void h02_getPath_invalid() {
+               // Paths.get() can throw exceptions for invalid paths on some 
systems
+               // This test verifies that invalid paths return empty
+               System.setProperty(TEST_PROP, "\0invalid");
+               Optional<Path> result = Settings.get().getPath(TEST_PROP);
+               // May or may not be empty depending on OS, but should not throw
+               assertNotNull(result);
+       }
+
+       
//====================================================================================================
+       // getURI()
+       
//====================================================================================================
+       @Test
+       void i01_getURI_valid() {
+               System.setProperty(TEST_PROP, "http://example.com/test";);
+               Optional<URI> result = Settings.get().getURI(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertEquals(URI.create("http://example.com/test";), 
result.get());
+       }
+
+       @Test
+       void i02_getURI_invalid() {
+               System.setProperty(TEST_PROP, "not a valid uri");
+               Optional<URI> result = Settings.get().getURI(TEST_PROP);
+               assertFalse(result.isPresent());
+       }
+
+       
//====================================================================================================
+       // getCharset()
+       
//====================================================================================================
+       @Test
+       void j01_getCharset_valid() {
+               System.setProperty(TEST_PROP, "UTF-8");
+               Optional<Charset> result = Settings.get().getCharset(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertEquals(Charset.forName("UTF-8"), result.get());
+       }
+
+       @Test
+       void j02_getCharset_invalid() {
+               System.setProperty(TEST_PROP, "INVALID-CHARSET");
+               Optional<Charset> result = Settings.get().getCharset(TEST_PROP);
+               assertFalse(result.isPresent());
+       }
+
+       
//====================================================================================================
+       // setGlobal() / unsetGlobal() / clearGlobal()
+       
//====================================================================================================
+       @Test
+       void k01_setGlobal() {
+               Settings.get().setGlobal(TEST_PROP, "global-value");
+               Optional<String> result = Settings.get().get(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertEquals("global-value", result.get());
+       }
+
+       @Test
+       void k02_unsetGlobal() {
+               Settings.get().setGlobal(TEST_PROP, "global-value");
+               Settings.get().unsetGlobal(TEST_PROP);
+               Optional<String> result = Settings.get().get(TEST_PROP);
+               assertFalse(result.isPresent());
+       }
+
+       @Test
+       void k03_clearGlobal() {
+               Settings.get().setGlobal(TEST_PROP, "value1");
+               Settings.get().setGlobal(TEST_PROP_2, "value2");
+               Settings.get().clearGlobal();
+               assertFalse(Settings.get().get(TEST_PROP).isPresent());
+               assertFalse(Settings.get().get(TEST_PROP_2).isPresent());
+       }
+
+       @Test
+       void k04_setGlobal_nullValue() {
+               Settings.get().setGlobal(TEST_PROP, null);
+               Optional<String> result = Settings.get().get(TEST_PROP);
+               assertFalse(result.isPresent());
+       }
+
+       
//====================================================================================================
+       // setLocal() / unsetLocal() / clearLocal()
+       
//====================================================================================================
+       @Test
+       void l01_setLocal() {
+               Settings.get().setLocal(TEST_PROP, "local-value");
+               Optional<String> result = Settings.get().get(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertEquals("local-value", result.get());
+       }
+
+       @Test
+       void l02_unsetLocal() {
+               Settings.get().setLocal(TEST_PROP, "local-value");
+               Settings.get().unsetLocal(TEST_PROP);
+               Optional<String> result = Settings.get().get(TEST_PROP);
+               assertFalse(result.isPresent());
+       }
+
+       @Test
+       void l03_clearLocal() {
+               Settings.get().setLocal(TEST_PROP, "value1");
+               Settings.get().setLocal(TEST_PROP_2, "value2");
+               Settings.get().clearLocal();
+               assertFalse(Settings.get().get(TEST_PROP).isPresent());
+               assertFalse(Settings.get().get(TEST_PROP_2).isPresent());
+       }
+
+       @Test
+       void l04_setLocal_nullValue() {
+               Settings.get().setLocal(TEST_PROP, null);
+               Optional<String> result = Settings.get().get(TEST_PROP);
+               assertFalse(result.isPresent());
+       }
+
+       @Test
+       void l05_setLocal_overridesGlobal() {
+               Settings.get().setGlobal(TEST_PROP, "global-value");
+               Settings.get().setLocal(TEST_PROP, "local-value");
+               Optional<String> result = Settings.get().get(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertEquals("local-value", result.get());
+       }
+
+       @Test
+       void l06_setLocal_overridesSystemProperty() {
+               System.setProperty(TEST_PROP, "system-value");
+               Settings.get().setLocal(TEST_PROP, "local-value");
+               Optional<String> result = Settings.get().get(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertEquals("local-value", result.get());
+       }
+
+       
//====================================================================================================
+       // Thread isolation
+       
//====================================================================================================
+       @Test
+       void m01_localOverride_threadIsolation() throws InterruptedException {
+               Settings.get().setLocal(TEST_PROP, "thread1-value");
+
+               Thread thread2 = new Thread(() -> {
+                       Settings.get().setLocal(TEST_PROP, "thread2-value");
+                       Optional<String> result = Settings.get().get(TEST_PROP);
+                       assertTrue(result.isPresent());
+                       assertEquals("thread2-value", result.get());
+               });
+
+               thread2.start();
+               thread2.join();
+
+               // Original thread should still have its value
+               Optional<String> result = Settings.get().get(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertEquals("thread1-value", result.get());
+       }
+
+       @Test
+       void m02_globalOverride_sharedAcrossThreads() throws 
InterruptedException {
+               Settings.get().setGlobal(TEST_PROP, "global-value");
+
+               Thread thread2 = new Thread(() -> {
+                       Optional<String> result = Settings.get().get(TEST_PROP);
+                       assertTrue(result.isPresent());
+                       assertEquals("global-value", result.get());
+               });
+
+               thread2.start();
+               thread2.join();
+
+               // Original thread should also see the global value
+               Optional<String> result = Settings.get().get(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertEquals("global-value", result.get());
+       }
+
+       
//====================================================================================================
+       // Singleton pattern
+       
//====================================================================================================
+       @Test
+       void n01_get_returnsSameInstance() {
+               Settings instance1 = Settings.get();
+               Settings instance2 = Settings.get();
+               assertSame(instance1, instance2);
+       }
+
+       
//====================================================================================================
+       // Sources
+       
//====================================================================================================
+       @Test
+       void o01_addSource() {
+               MapSource source = new MapSource();
+               source.set(TEST_PROP, "source-value");
+               Settings.get().addSource(source);
+
+               Optional<String> result = Settings.get().get(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertEquals("source-value", result.get());
+       }
+
+       @Test
+       void o02_addSource_reverseOrder() {
+               MapSource source1 = new MapSource();
+               source1.set(TEST_PROP, "source1-value");
+               Settings.get().addSource(source1);
+
+               MapSource source2 = new MapSource();
+               source2.set(TEST_PROP, "source2-value");
+               Settings.get().addSource(source2);
+
+               // Last added source should be checked first
+               Optional<String> result = Settings.get().get(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertEquals("source2-value", result.get());
+       }
+
+       @Test
+       void o03_addSource_afterGlobalOverride() {
+               Settings.get().setGlobal(TEST_PROP, "global-value");
+
+               MapSource source = new MapSource();
+               source.set(TEST_PROP, "source-value");
+               Settings.get().addSource(source);
+
+               // Global override should take precedence
+               Optional<String> result = Settings.get().get(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertEquals("global-value", result.get());
+       }
+
+       @Test
+       void o04_addSource_beforeSystemProperty() {
+               System.setProperty(TEST_PROP, "system-value");
+
+               MapSource source = new MapSource();
+               source.set(TEST_PROP, "source-value");
+               Settings.get().addSource(source);
+
+               // Source should take precedence over system property
+               Optional<String> result = Settings.get().get(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertEquals("source-value", result.get());
+       }
+
+       @Test
+       void o05_addSource_fallbackToSystemProperty() {
+               MapSource source = new MapSource();
+               // Source doesn't have the property
+
+               System.setProperty(TEST_PROP, "system-value");
+               Settings.get().addSource(source);
+
+               // Should fall back to system property
+               Optional<String> result = Settings.get().get(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertEquals("system-value", result.get());
+       }
+
+       @Test
+       void o06_setSources() {
+               MapSource source1 = new MapSource();
+               source1.set(TEST_PROP, "source1-value");
+
+               MapSource source2 = new MapSource();
+               source2.set(TEST_PROP, "source2-value");
+
+               Settings.get().setSources(source1, source2);
+
+               // Last source in array should be checked first
+               Optional<String> result = Settings.get().get(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertEquals("source2-value", result.get());
+       }
+
+       @Test
+       void o07_setSources_clearsExisting() {
+               MapSource source1 = new MapSource();
+               source1.set(TEST_PROP, "source1-value");
+               Settings.get().addSource(source1);
+
+               MapSource source2 = new MapSource();
+               source2.set(TEST_PROP, "source2-value");
+               Settings.get().setSources(source2);
+
+               // Only source2 should exist now
+               Optional<String> result = Settings.get().get(TEST_PROP);
+               assertTrue(result.isPresent());
+               assertEquals("source2-value", result.get());
+       }
+
+       @Test
+       void o08_addSource_nullValue() {
+               MapSource source = new MapSource();
+               source.set(TEST_PROP, null);
+               Settings.get().addSource(source);
+
+               // Note that setting a null value on the source overrides the 
system property.
+               System.setProperty(TEST_PROP, "system-value");
+               Optional<String> result = Settings.get().get(TEST_PROP);
+               assertFalse(result.isPresent());
+       }
+
+       @Test
+       void o09_addSource_nullSource() {
+               assertThrows(IllegalArgumentException.class, () -> {
+                       Settings.get().addSource(null);
+               });
+       }
+
+       @Test
+       void o10_setSources_nullSource() {
+               MapSource source1 = new MapSource();
+               assertThrows(IllegalArgumentException.class, () -> {
+                       Settings.get().setSources(source1, null);
+               });
+       }
+
+       @Test
+       void o11_source_springPropertiesExample() {
+               // Simulate Spring properties
+               MapSource springSource = new MapSource();
+               springSource.set("spring.datasource.url", 
"jdbc:postgresql://localhost/db");
+               springSource.set("spring.datasource.username", "admin");
+
+               Settings.get().addSource(springSource);
+
+               Optional<String> url = 
Settings.get().get("spring.datasource.url");
+               assertTrue(url.isPresent());
+               assertEquals("jdbc:postgresql://localhost/db", url.get());
+
+               Optional<String> username = 
Settings.get().get("spring.datasource.username");
+               assertTrue(username.isPresent());
+               assertEquals("admin", username.get());
+       }
+}
+

Reply via email to