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 5c564a347d Settings improvements
5c564a347d is described below

commit 5c564a347dcf5c399de5b0ab371dccecc4a3c0b8
Author: James Bognar <[email protected]>
AuthorDate: Tue Dec 16 09:46:11 2025 -0500

    Settings improvements
---
 .../commons/function/ResettableSupplier.java       |  39 ++++
 .../apache/juneau/commons/settings/Setting.java    | 123 +++++++++++
 .../apache/juneau/commons/settings/Settings.java   | 230 +++++----------------
 .../juneau/commons/settings/StringSetting.java     | 223 ++++++++++++++++++++
 .../juneau/commons/utils/AssertionUtils.java       |  12 +-
 .../juneau/commons/utils/ThrowableUtils.java       |   5 +-
 .../juneau/commons/settings/Settings_Test.java     |  48 ++---
 7 files changed, 478 insertions(+), 202 deletions(-)

diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/function/ResettableSupplier.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/function/ResettableSupplier.java
index 60f7e5a8ac..baffb64971 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/function/ResettableSupplier.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/function/ResettableSupplier.java
@@ -179,4 +179,43 @@ public class ResettableSupplier<T> implements 
OptionalSupplier<T> {
                        return new ResettableSupplier<>(supplier);
                return new ResettableSupplier<>(()->o.get());
        }
+
+       /**
+        * If a value is present, applies the provided mapping function to it 
and returns a ResettableSupplier describing the result.
+        *
+        * <p>
+        * The returned ResettableSupplier maintains its own cache, independent 
of this supplier.
+        * Resetting the mapped supplier does not affect this supplier, and 
vice versa.
+        *
+        * @param <U> The type of the result of the mapping function.
+        * @param mapper A mapping function to apply to the value, if present. 
Must not be <jk>null</jk>.
+        * @return A ResettableSupplier describing the result of applying a 
mapping function to the value of this ResettableSupplier, if a value is 
present, otherwise an empty ResettableSupplier.
+        */
+       @Override
+       public <U> ResettableSupplier<U> map(Function<? super T, ? extends U> 
mapper) {
+               assertArgNotNull("mapper", mapper);
+               return new ResettableSupplier<>(() -> {
+                       T value = get();
+                       return nn(value) ? mapper.apply(value) : null;
+               });
+       }
+
+       /**
+        * If a value is present, and the value matches the given predicate, 
returns a ResettableSupplier describing the value, otherwise returns an empty 
ResettableSupplier.
+        *
+        * <p>
+        * The returned ResettableSupplier maintains its own cache, independent 
of this supplier.
+        * Resetting the filtered supplier does not affect this supplier, and 
vice versa.
+        *
+        * @param predicate A predicate to apply to the value, if present. Must 
not be <jk>null</jk>.
+        * @return A ResettableSupplier describing the value of this 
ResettableSupplier if a value is present and the value matches the given 
predicate, otherwise an empty ResettableSupplier.
+        */
+       @Override
+       public ResettableSupplier<T> filter(Predicate<? super T> predicate) {
+               assertArgNotNull("predicate", predicate);
+               return new ResettableSupplier<>(() -> {
+                       T value = get();
+                       return (nn(value) && predicate.test(value)) ? value : 
null;
+               });
+       }
 }
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/Setting.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/Setting.java
new file mode 100644
index 0000000000..c4ec98d0c9
--- /dev/null
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/Setting.java
@@ -0,0 +1,123 @@
+/*
+ * 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.util.Optional;
+import java.util.function.*;
+
+import org.apache.juneau.commons.function.ResettableSupplier;
+
+/**
+ * A resettable supplier that provides convenience methods for type conversion.
+ *
+ * <p>
+ * This class extends {@link ResettableSupplier} to provide methods to convert 
the string value
+ * to various types, similar to the {@link Settings#getInteger(String)}, 
{@link Settings#getBoolean(String)}, etc. methods.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ *     StringSetting <jv>setting</jv> = 
Settings.<jsf>get</jsf>().setting(<js>"my.property"</js>);
+ *     Setting&lt;Integer&gt; <jv>intValue</jv> = <jv>setting</jv>.asInteger();
+ *     Setting&lt;Boolean&gt; <jv>boolValue</jv> = 
<jv>setting</jv>.asBoolean();
+ *     Setting&lt;Charset&gt; <jv>charset</jv> = <jv>setting</jv>.asCharset();
+ *
+ *     <jc>// Reset the cache to force recomputation</jc>
+ *     <jv>setting</jv>.reset();
+ * </p>
+ *
+ * @param <T> The type of value supplied.
+ */
+public class Setting<T> extends ResettableSupplier<T> {
+       private final Settings settings;
+
+       /**
+        * Creates a new Setting from a Settings instance and a Supplier.
+        *
+        * @param settings The Settings instance that created this setting. 
Must not be <jk>null</jk>.
+        * @param supplier The supplier that provides the value. Must not be 
<jk>null</jk>.
+        */
+       public Setting(Settings settings, Supplier<T> supplier) {
+               super(assertArgNotNull("supplier", supplier));
+               this.settings = assertArgNotNull("settings", settings);
+       }
+
+       /**
+        * Returns the Settings instance that created this setting.
+        *
+        * @return The Settings instance.
+        */
+       public Settings getSettings() {
+               return settings;
+       }
+
+       /**
+        * Returns the underlying Optional&lt;T&gt;.
+        *
+        * <p>
+        * <b>Note:</b> The returned {@link Optional} is a snapshot-in-time of 
the current value.
+        * Resetting this {@link Setting} will not affect the returned {@link 
Optional} instance.
+        * To get an updated value after resetting, call this method again.
+        *
+        * @return The optional value.
+        */
+       public Optional<T> asOptional() {
+               return opt(get());
+       }
+
+       /**
+        * If a value is present, applies the provided mapping function to it 
and returns a Setting describing the result.
+        *
+        * <p>
+        * The returned Setting maintains its own cache, independent of this 
supplier.
+        * Resetting the mapped supplier does not affect this supplier, and 
vice versa.
+        *
+        * @param <U> The type of the result of the mapping function.
+        * @param mapper A mapping function to apply to the value, if present. 
Must not be <jk>null</jk>.
+        * @return A Setting describing the result of applying a mapping 
function to the value of this Setting, if a value is present, otherwise an 
empty Setting.
+        */
+       @Override
+       public <U> Setting<U> map(Function<? super T, ? extends U> mapper) {
+               assertArgNotNull("mapper", mapper);
+               return new Setting<>(settings, () -> {
+                       T value = get();
+                       return nn(value) ? mapper.apply(value) : null;
+               });
+       }
+
+       /**
+        * If a value is present, and the value matches the given predicate, 
returns a Setting describing the value, otherwise returns an empty Setting.
+        *
+        * <p>
+        * The returned Setting maintains its own cache, independent of this 
supplier.
+        * Resetting the filtered supplier does not affect this supplier, and 
vice versa.
+        *
+        * @param predicate A predicate to apply to the value, if present. Must 
not be <jk>null</jk>.
+        * @return A Setting describing the value of this Setting if a value is 
present and the value matches the given predicate, otherwise an empty Setting.
+        */
+       @Override
+       public Setting<T> filter(Predicate<? super T> predicate) {
+               assertArgNotNull("predicate", predicate);
+               return new Setting<>(settings, () -> {
+                       T value = get();
+                       return (nn(value) && predicate.test(value)) ? value : 
null;
+               });
+       }
+}
+
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 5fb3891c0c..909d974e9e 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
@@ -20,10 +20,7 @@ import static 
org.apache.juneau.commons.utils.AssertionUtils.*;
 import static org.apache.juneau.commons.utils.ThrowableUtils.*;
 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.*;
 import java.util.function.*;
@@ -62,29 +59,37 @@ import org.apache.juneau.commons.function.*;
  *
  * <h5 class='section'>Features:</h5>
  * <ul class='spaced-list'>
- *     <li>System property access - read Java system properties with type 
conversion
+ *     <li>System property access - read Java system properties with type 
conversion via {@link StringSetting}
  *     <li>Global overrides - override system properties globally for all 
threads (stored in a {@link SettingStore})
  *     <li>Per-thread overrides - override system properties for specific 
threads (stored in a per-thread {@link SettingStore})
  *     <li>Custom sources - add arbitrary property sources (e.g., Spring 
properties, environment variables, config files) via the {@link Builder}
  *     <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
+ *     <li>Type-safe accessors - type conversion methods on {@link 
StringSetting} for common types: Integer, Long, Boolean, Double, Float, File, 
Path, URI, Charset
+ *     <li>Resettable suppliers - settings are returned as {@link 
StringSetting} instances that can be reset to force recomputation
  * </ul>
  *
  * <h5 class='section'>Usage Examples:</h5>
  * <p class='bjava'>
- *     <jc>// Get a system property as a string (using singleton instance)</jc>
- *     Optional&lt;String&gt; <jv>value</jv> = 
Settings.<jsf>get</jsf>().get(<js>"my.property"</js>);
+ *     <jc>// Get a system property as a StringSetting (using singleton 
instance)</jc>
+ *     StringSetting <jv>setting</jv> = 
Settings.<jsf>get</jsf>().get(<js>"my.property"</js>);
+ *     String <jv>value</jv> = <jv>setting</jv>.get();  <jc>// Get the string 
value</jc>
  *
- *     <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>// Get with type conversion using StringSetting methods</jc>
+ *     Setting&lt;Integer&gt; <jv>intSetting</jv> = 
Settings.<jsf>get</jsf>().get(<js>"my.int.property"</js>).asInteger();
+ *     Setting&lt;Long&gt; <jv>longSetting</jv> = 
Settings.<jsf>get</jsf>().get(<js>"my.long.property"</js>).asLong();
+ *     Setting&lt;Boolean&gt; <jv>boolSetting</jv> = 
Settings.<jsf>get</jsf>().get(<js>"my.bool.property"</js>).asBoolean();
+ *     Setting&lt;Double&gt; <jv>doubleSetting</jv> = 
Settings.<jsf>get</jsf>().get(<js>"my.double.property"</js>).asDouble();
+ *     Setting&lt;Float&gt; <jv>floatSetting</jv> = 
Settings.<jsf>get</jsf>().get(<js>"my.float.property"</js>).asFloat();
+ *     Setting&lt;File&gt; <jv>fileSetting</jv> = 
Settings.<jsf>get</jsf>().get(<js>"my.file.property"</js>).asFile();
+ *     Setting&lt;Path&gt; <jv>pathSetting</jv> = 
Settings.<jsf>get</jsf>().get(<js>"my.path.property"</js>).asPath();
+ *     Setting&lt;URI&gt; <jv>uriSetting</jv> = 
Settings.<jsf>get</jsf>().get(<js>"my.uri.property"</js>).asURI();
+ *     Setting&lt;Charset&gt; <jv>charsetSetting</jv> = 
Settings.<jsf>get</jsf>().get(<js>"my.charset.property"</js>).asCharset();
+ *
+ *     <jc>// Use custom type conversion</jc>
+ *     Setting&lt;MyCustomType&gt; <jv>customSetting</jv> = 
Settings.<jsf>get</jsf>().get(<js>"my.custom.property"</js>).asType(MyCustomType.<jk>class</jk>);
+ *
+ *     <jc>// Reset a setting to force recomputation</jc>
+ *     <jv>setting</jv>.reset();
  *
  *     <jc>// Override for current thread (useful in unit tests)</jc>
  *     Settings.<jsf>get</jsf>().setLocal(<js>"my.property"</js>, 
<js>"test-value"</js>);
@@ -325,7 +330,12 @@ public class Settings {
        }
 
        /**
-        * Returns the value of the specified system property.
+        * Returns a {@link StringSetting} for the specified system property.
+        *
+        * <p>
+        * The returned {@link StringSetting} is a resettable supplier that 
caches the lookup result.
+        * Use the {@link StringSetting#asInteger()}, {@link 
StringSetting#asBoolean()}, etc. methods
+        * to convert to different types.
         *
         * <p>
         * The lookup order is:
@@ -337,31 +347,32 @@ public class Settings {
         *      <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.
+        * @param name The property name. Must not be <jk>null</jk>.
+        * @return A {@link StringSetting} that provides the property value, or 
<jk>null</jk> if not found.
         */
-       public Optional<String> get(String name) {
+       public StringSetting get(String name) {
                assertArgNotNull("name", name);
+               return new StringSetting(this, () -> {
+                       // 1. Check thread-local override
+                       var v = localStore.get().get(name);
+                       if (v != null)
+                               return v.orElse(null); // v is Optional.empty() 
if key exists with null value, or Optional.of(value) if present
+
+                       // 2. Check global override
+                       v = globalStore.get().get(name);
+                       if (v != null)
+                               return v.orElse(null); // 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);
+                               var result = source.get(name);
+                               if (result != null)
+                                       return result.orElse(null);
+                       }
 
-               // 1. Check thread-local override
-               var v = localStore.get().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
-               v = globalStore.get().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);
-                       var result = source.get(name);
-                       if (result != null)
-                               return result;
-               }
-
-               return opte();
+                       return null;
+               });
        }
 
        /**
@@ -391,138 +402,10 @@ public class Settings {
         * @see #get(String)
         * @see #toType(String, Object)
         */
+       @SuppressWarnings("unchecked")
        public <T> T get(String name, T def) {
                assertArgNotNull("def", def);
-               return get(name).map(x -> toType(x, def)).orElse(def);
-       }
-
-       /**
-        * 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);
+               return get(name).asType((Class<T>)def.getClass()).orElse(def);
        }
 
        /**
@@ -654,13 +537,14 @@ public class Settings {
         *
         * @param <T> The target type.
         * @param s The string to convert. Must not be <jk>null</jk>.
-        * @param def The default value (used to determine the target type). 
Must not be <jk>null</jk>.
+        * @param c The target class. Must not be <jk>null</jk>.
         * @return The converted value.
         * @throws RuntimeException If the type is not supported for conversion.
         */
        @SuppressWarnings({ "unchecked", "rawtypes" })
-       private <T> T toType(String s, T def) {
-               var c = (Class<T>)def.getClass();
+       protected <T> T toType(String s, Class<T> c) {
+               assertArgNotNull("s", s);
+               assertArgNotNull("c", c);
                var f = (Function<String,T>)customTypeFunctions.get(c);
                if (f == null) {
                        if (c == String.class)
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/StringSetting.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/StringSetting.java
new file mode 100644
index 0000000000..0f553e927d
--- /dev/null
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/StringSetting.java
@@ -0,0 +1,223 @@
+/*
+ * 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.function.*;
+
+/**
+ * A specialized {@link Setting} for string values that provides convenience 
methods for type conversion.
+ *
+ * <p>
+ * This class extends {@link Setting} with type-specific conversion methods 
such as {@link #asInteger()},
+ * {@link #asBoolean()}, {@link #asCharset()}, etc.
+ */
+public class StringSetting extends Setting<String> {
+
+       /**
+        * Creates a new StringSetting from a Settings instance and a Supplier.
+        *
+        * @param settings The Settings instance that created this setting. 
Must not be <jk>null</jk>.
+        * @param supplier The supplier that provides the string value. Must 
not be <jk>null</jk>.
+        */
+       public StringSetting(Settings settings, Supplier<String> supplier) {
+               super(settings, supplier);
+       }
+
+       /**
+        * If a value is present, applies the provided mapping function to it 
and returns a StringSetting describing the result.
+        *
+        * <p>
+        * This method is specifically for String-to-String mappings. For 
mappings to other types, use the inherited {@link #map(Function)} method.
+        *
+        * <p>
+        * The returned StringSetting maintains its own cache, independent of 
this supplier.
+        * Resetting the mapped supplier does not affect this supplier, and 
vice versa.
+        *
+        * @param mapper A mapping function to apply to the value, if present. 
Must not be <jk>null</jk>.
+        * @return A StringSetting describing the result of applying a mapping 
function to the value of this StringSetting, if a value is present, otherwise 
an empty StringSetting.
+        */
+       public StringSetting mapString(Function<? super String, ? extends 
String> mapper) {
+               assertArgNotNull("mapper", mapper);
+               return new StringSetting(getSettings(), () -> {
+                       String value = get();
+                       return nn(value) ? mapper.apply(value) : null;
+               });
+       }
+
+       /**
+        * If a value is present, and the value matches the given predicate, 
returns a StringSetting describing the value, otherwise returns an empty 
StringSetting.
+        *
+        * <p>
+        * The returned StringSetting maintains its own cache, independent of 
this supplier.
+        * Resetting the filtered supplier does not affect this supplier, and 
vice versa.
+        *
+        * @param predicate A predicate to apply to the value, if present. Must 
not be <jk>null</jk>.
+        * @return A StringSetting describing the value of this StringSetting 
if a value is present and the value matches the given predicate, otherwise an 
empty StringSetting.
+        */
+       @Override
+       public StringSetting filter(Predicate<? super String> predicate) {
+               assertArgNotNull("predicate", predicate);
+               return new StringSetting(getSettings(), () -> {
+                       String value = get();
+                       return (nn(value) && predicate.test(value)) ? value : 
null;
+               });
+       }
+
+       /**
+        * Converts the string value to 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()}.
+        *
+        * @return The property value as an Integer, or {@link 
Optional#empty()} if not found or not a valid integer.
+        */
+       public Setting<Integer> asInteger() {
+               return map(v -> safeOrNull(() -> 
Integer.valueOf(v))).filter(Objects::nonNull);
+       }
+
+       /**
+        * Converts the string value to 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()}.
+        *
+        * @return The property value as a Long, or {@link Optional#empty()} if 
not found or not a valid long.
+        */
+       public Setting<Long> asLong() {
+               return map(v -> safeOrNull(() -> 
Long.valueOf(v))).filter(Objects::nonNull);
+       }
+
+       /**
+        * Converts the string value to 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.
+        *
+        * @return The property value as a Boolean, or {@link Optional#empty()} 
if not found.
+        */
+       public Setting<Boolean> asBoolean() {
+               return map(v -> Boolean.parseBoolean(v));
+       }
+
+       /**
+        * Converts the string value to 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()}.
+        *
+        * @return The property value as a Double, or {@link Optional#empty()} 
if not found or not a valid double.
+        */
+       public Setting<Double> asDouble() {
+               return map(v -> safeOrNull(() -> 
Double.valueOf(v))).filter(Objects::nonNull);
+       }
+
+       /**
+        * Converts the string value to 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()}.
+        *
+        * @return The property value as a Float, or {@link Optional#empty()} 
if not found or not a valid float.
+        */
+       public Setting<Float> asFloat() {
+               return map(v -> safeOrNull(() -> 
Float.valueOf(v))).filter(Objects::nonNull);
+       }
+
+       /**
+        * Converts the string value to 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.
+        *
+        * @return The property value as a File, or {@link Optional#empty()} if 
not found.
+        */
+       public Setting<File> asFile() {
+               return map(v -> new File(v));
+       }
+
+       /**
+        * Converts the string value to 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()}.
+        *
+        * @return The property value as a Path, or {@link Optional#empty()} if 
not found or not a valid path.
+        */
+       public Setting<Path> asPath() {
+               return map(v -> safeOrNull(() -> 
Paths.get(v))).filter(Objects::nonNull);
+       }
+
+       /**
+        * Converts the string value to 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()}.
+        *
+        * @return The property value as a URI, or {@link Optional#empty()} if 
not found or not a valid URI.
+        */
+       public Setting<URI> asURI() {
+               return map(v -> safeOrNull(() -> 
URI.create(v))).filter(Objects::nonNull);
+       }
+
+       /**
+        * Converts the string value to 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()}.
+        *
+        * @return The property value as a Charset, or {@link Optional#empty()} 
if not found or not a valid charset.
+        */
+       public Setting<Charset> asCharset() {
+               return map(v -> safeOrNull(() -> 
Charset.forName(v))).filter(Objects::nonNull);
+       }
+
+       /**
+        * Converts the string value to the specified type using the Settings 
type conversion functions.
+        *
+        * <p>
+        * The property value is converted using {@link Settings#toType(String, 
Class)}. If the property is not found
+        * or cannot be converted to the specified type, returns {@link 
Optional#empty()}.
+        *
+        * @param <T> The target type.
+        * @param c The target class. Must not be <jk>null</jk>.
+        * @return The property value as the specified type, or {@link 
Optional#empty()} if not found or not a valid conversion.
+        */
+       public <T> Setting<T> asType(Class<T> c) {
+               assertArgNotNull("c", c);
+               return map(v -> getSettings().toType(v, 
c)).filter(Objects::nonNull);
+       }
+}
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/AssertionUtils.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/AssertionUtils.java
index 7f9b31ce2a..514accf17f 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/AssertionUtils.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/AssertionUtils.java
@@ -19,8 +19,6 @@ package org.apache.juneau.commons.utils;
 import static org.apache.juneau.commons.utils.ThrowableUtils.*;
 import static org.apache.juneau.commons.utils.Utils.*;
 
-import java.util.function.*;
-
 /**
  * Utility methods for argument validation and assertion.
  *
@@ -122,6 +120,16 @@ public class AssertionUtils {
                return o;
        }
 
+       /**
+        * Asserts that the specified object is not <jk>null</jk>, throwing an 
{@link IllegalStateException} if it is.
+        *
+        * @param <T> The type of the object.
+        * @param o The object to check. Can be <jk>null</jk>.
+        * @param msg The error message format string. Must not be 
<jk>null</jk>.
+        * @param args The arguments for the message format string.
+        * @return The non-null object.
+        * @throws IllegalStateException If the object is <jk>null</jk>.
+        */
        public static final <T> T assertNotNull(T o, String msg, Object...args) 
throws IllegalStateException {
                if (o == null)
                        throw illegalState(msg, args);
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/ThrowableUtils.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/ThrowableUtils.java
index 7621517243..a8dabce9a0 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/ThrowableUtils.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/ThrowableUtils.java
@@ -20,7 +20,6 @@ import static org.apache.juneau.commons.utils.Utils.*;
 
 import java.io.*;
 import java.util.*;
-import java.util.concurrent.atomic.*;
 
 import org.apache.juneau.commons.reflect.*;
 import org.apache.juneau.commons.settings.*;
@@ -30,7 +29,7 @@ import org.apache.juneau.commons.settings.*;
  */
 public class ThrowableUtils {
 
-       static AtomicBoolean VERBOSE = new 
AtomicBoolean(Settings.get().getBoolean("juneau.enableVerboseExceptions").orElse(false));
+       static Setting<Boolean> VERBOSE = 
Settings.get().get("juneau.enableVerboseExceptions").asBoolean();
 
        /**
         * Interface used with {@link 
Utils#safeSupplier(SupplierWithThrowable)}.
@@ -337,7 +336,7 @@ public class ThrowableUtils {
        }
 
        private static <T extends Throwable> T log(T exception) {
-               if (VERBOSE.get()) exception.printStackTrace();
+               if (VERBOSE.orElse(false)) exception.printStackTrace();
                return exception;
        }
 
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 b471c88e4f..e0bce38301 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
@@ -117,7 +117,7 @@ class Settings_Test extends TestBase {
        @Test
        void b01_getInteger_valid() {
                System.setProperty(TEST_PROP, "123");
-               var result = Settings.get().getInteger(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP).asInteger();
                assertTrue(result.isPresent());
                assertEquals(123, result.get());
        }
@@ -125,20 +125,20 @@ class Settings_Test extends TestBase {
        @Test
        void b02_getInteger_invalid() {
                System.setProperty(TEST_PROP, "not-a-number");
-               var result = Settings.get().getInteger(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP).asInteger();
                assertFalse(result.isPresent());
        }
 
        @Test
        void b03_getInteger_notFound() {
-               var result = Settings.get().getInteger("nonexistent.property");
+               var result = 
Settings.get().get("nonexistent.property").asInteger();
                assertFalse(result.isPresent());
        }
 
        @Test
        void b04_getInteger_fromOverride() {
                Settings.get().setLocal(TEST_PROP, "456");
-               var result = Settings.get().getInteger(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP).asInteger();
                assertTrue(result.isPresent());
                assertEquals(456, result.get());
        }
@@ -149,7 +149,7 @@ class Settings_Test extends TestBase {
        @Test
        void c01_getLong_valid() {
                System.setProperty(TEST_PROP, "123456789");
-               var result = Settings.get().getLong(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP).asLong();
                assertTrue(result.isPresent());
                assertEquals(123456789L, result.get());
        }
@@ -157,14 +157,14 @@ class Settings_Test extends TestBase {
        @Test
        void c02_getLong_invalid() {
                System.setProperty(TEST_PROP, "not-a-number");
-               var result = Settings.get().getLong(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP).asLong();
                assertFalse(result.isPresent());
        }
 
        @Test
        void c03_getLong_fromOverride() {
                Settings.get().setLocal(TEST_PROP, "987654321");
-               var result = Settings.get().getLong(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP).asLong();
                assertTrue(result.isPresent());
                assertEquals(987654321L, result.get());
        }
@@ -175,7 +175,7 @@ class Settings_Test extends TestBase {
        @Test
        void d01_getBoolean_true() {
                System.setProperty(TEST_PROP, "true");
-               var result = Settings.get().getBoolean(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP).asBoolean();
                assertTrue(result.isPresent());
                assertTrue(result.get());
        }
@@ -183,7 +183,7 @@ class Settings_Test extends TestBase {
        @Test
        void d02_getBoolean_false() {
                System.setProperty(TEST_PROP, "false");
-               var result = Settings.get().getBoolean(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP).asBoolean();
                assertTrue(result.isPresent());
                assertFalse(result.get());
        }
@@ -191,7 +191,7 @@ class Settings_Test extends TestBase {
        @Test
        void d03_getBoolean_caseInsensitive() {
                System.setProperty(TEST_PROP, "TRUE");
-               var result = Settings.get().getBoolean(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP).asBoolean();
                assertTrue(result.isPresent());
                assertTrue(result.get());
        }
@@ -199,14 +199,14 @@ class Settings_Test extends TestBase {
        @Test
        void d04_getBoolean_nonTrueValue() {
                System.setProperty(TEST_PROP, "anything");
-               var result = Settings.get().getBoolean(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP).asBoolean();
                assertTrue(result.isPresent());
                assertFalse(result.get());
        }
 
        @Test
        void d05_getBoolean_notFound() {
-               var result = Settings.get().getBoolean("nonexistent.property");
+               var result = 
Settings.get().get("nonexistent.property").asBoolean();
                assertFalse(result.isPresent());
        }
 
@@ -216,7 +216,7 @@ class Settings_Test extends TestBase {
        @Test
        void e01_getDouble_valid() {
                System.setProperty(TEST_PROP, "123.456");
-               var result = Settings.get().getDouble(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP).asDouble();
                assertTrue(result.isPresent());
                assertEquals(123.456, result.get(), 0.0001);
        }
@@ -224,7 +224,7 @@ class Settings_Test extends TestBase {
        @Test
        void e02_getDouble_invalid() {
                System.setProperty(TEST_PROP, "not-a-number");
-               var result = Settings.get().getDouble(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP).asDouble();
                assertFalse(result.isPresent());
        }
 
@@ -234,7 +234,7 @@ class Settings_Test extends TestBase {
        @Test
        void f01_getFloat_valid() {
                System.setProperty(TEST_PROP, "123.456");
-               var result = Settings.get().getFloat(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP).asFloat();
                assertTrue(result.isPresent());
                assertEquals(123.456f, result.get(), 0.0001f);
        }
@@ -242,7 +242,7 @@ class Settings_Test extends TestBase {
        @Test
        void f02_getFloat_invalid() {
                System.setProperty(TEST_PROP, "not-a-number");
-               var result = Settings.get().getFloat(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP).asFloat();
                assertFalse(result.isPresent());
        }
 
@@ -252,14 +252,14 @@ class Settings_Test extends TestBase {
        @Test
        void g01_getFile_valid() {
                System.setProperty(TEST_PROP, "/tmp/test.txt");
-               var result = Settings.get().getFile(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP).asFile();
                assertTrue(result.isPresent());
                assertEquals(new File("/tmp/test.txt"), result.get());
        }
 
        @Test
        void g02_getFile_notFound() {
-               var result = Settings.get().getFile("nonexistent.property");
+               var result = 
Settings.get().get("nonexistent.property").asFile();
                assertFalse(result.isPresent());
        }
 
@@ -269,7 +269,7 @@ class Settings_Test extends TestBase {
        @Test
        void h01_getPath_valid() {
                System.setProperty(TEST_PROP, "/tmp/test.txt");
-               var result = Settings.get().getPath(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP).asPath();
                assertTrue(result.isPresent());
                assertEquals(Paths.get("/tmp/test.txt"), result.get());
        }
@@ -279,7 +279,7 @@ class Settings_Test extends TestBase {
                // Paths.get() can throw exceptions for invalid paths on some 
systems
                // This test verifies that invalid paths return empty
                System.setProperty(TEST_PROP, "\0invalid");
-               var result = Settings.get().getPath(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP).asPath();
                // May or may not be empty depending on OS, but should not throw
                assertNotNull(result);
        }
@@ -290,7 +290,7 @@ class Settings_Test extends TestBase {
        @Test
        void i01_getURI_valid() {
                System.setProperty(TEST_PROP, "http://example.com/test";);
-               var result = Settings.get().getURI(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP).asURI();
                assertTrue(result.isPresent());
                assertEquals(URI.create("http://example.com/test";), 
result.get());
        }
@@ -298,7 +298,7 @@ class Settings_Test extends TestBase {
        @Test
        void i02_getURI_invalid() {
                System.setProperty(TEST_PROP, "not a valid uri");
-               var result = Settings.get().getURI(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP).asURI();
                assertFalse(result.isPresent());
        }
 
@@ -308,7 +308,7 @@ class Settings_Test extends TestBase {
        @Test
        void j01_getCharset_valid() {
                System.setProperty(TEST_PROP, "UTF-8");
-               var result = Settings.get().getCharset(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP).asCharset();
                assertTrue(result.isPresent());
                assertEquals(Charset.forName("UTF-8"), result.get());
        }
@@ -316,7 +316,7 @@ class Settings_Test extends TestBase {
        @Test
        void j02_getCharset_invalid() {
                System.setProperty(TEST_PROP, "INVALID-CHARSET");
-               var result = Settings.get().getCharset(TEST_PROP);
+               var result = Settings.get().get(TEST_PROP).asCharset();
                assertFalse(result.isPresent());
        }
 


Reply via email to