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 dbd5ec690f Marshall module improvements
dbd5ec690f is described below
commit dbd5ec690fdcc3e5accfd4f402abe6b961474909
Author: James Bognar <[email protected]>
AuthorDate: Thu Dec 11 14:49:51 2025 -0500
Marshall module improvements
---
.../juneau/commons/settings/FunctionalSource.java | 101 ++---
.../juneau/commons/settings/FunctionalStore.java | 174 ++++++++
.../settings/{MapSource.java => MapStore.java} | 56 +--
.../juneau/commons/settings/SettingSource.java | 73 +---
.../juneau/commons/settings/SettingStore.java | 78 ++++
.../apache/juneau/commons/settings/Settings.java | 359 +++++++++--------
.../juneau/commons/utils/AssertionUtils.java | 18 +
.../juneau/commons/utils/ThrowableUtils.java | 27 +-
.../juneau/commons/settings/Settings_Test.java | 441 ++++++++++++++++-----
9 files changed, 917 insertions(+), 410 deletions(-)
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/FunctionalSource.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/FunctionalSource.java
index 11bdc62d6b..e927a21b42 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/FunctionalSource.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/FunctionalSource.java
@@ -16,20 +16,23 @@
*/
package org.apache.juneau.commons.settings;
-import static org.apache.juneau.commons.utils.ThrowableUtils.*;
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.
+ * A functional interface for creating read-only {@link SettingSource}
instances from 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)},
+ * This functional interface allows you to create setting sources directly
from lambda expressions or method references,
+ * making it easy to wrap existing property sources (e.g., {@link
System#getProperty(String)},
* {@link System#getenv(String)}) as {@link SettingSource} instances.
*
+ * <p>
+ * Functional sources are read-only and do not implement {@link SettingStore}.
If you need a writable source,
+ * use {@link MapStore} or {@link FunctionalStore} instead.
+ *
* <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).
@@ -38,35 +41,29 @@ import java.util.function.*;
*
* <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.
+ * since the function only returns a <c>String</c>. If you need to distinguish
these cases, use {@link MapStore} instead.
*
* <h5 class='section'>Example:</h5>
* <p class='bjava'>
- * <jc>// Create a read-only source from System.getProperty</jc>
- * FunctionalSource <jv>sysProps</jv> = <jk>new</jk> FunctionalSource(x
-> System.getProperty(x));
+ * <jc>// Create a read-only source directly from a lambda (returns
Optional)</jc>
+ * Settings.<jsf>get</jsf>().addSource(name ->
opt(System.getProperty(name)));
+ *
+ * <jc>// Using the static factory method (takes Function<String,
String>)</jc>
+ *
Settings.<jsf>get</jsf>().addSource(FunctionalSource.<jsf>of</jsf>(System::getProperty));
*
* <jc>// Create a read-only source from System.getenv</jc>
- * FunctionalSource <jv>envVars</jv> = <jk>new</jk> FunctionalSource(x
-> System.getenv(x));
+ *
Settings.<jsf>get</jsf>().addSource(FunctionalSource.<jsf>of</jsf>(System::getenv));
*
- * <jc>// Add to Settings</jc>
+ * <jc>// Explicit creation for reuse</jc>
+ * FunctionalSource <jv>sysProps</jv> =
FunctionalSource.<jsf>of</jsf>(System::getProperty);
* Settings.<jsf>get</jsf>().addSource(<jv>sysProps</jv>);
* </p>
*/
-public class FunctionalSource implements SettingSource {
-
- private final Function<String,String> function;
+@FunctionalInterface
+public interface FunctionalSource extends SettingSource {
/**
- * Constructor.
- *
- * @param function The function to delegate property lookups to. Must
not be <c>null</c>.
- */
- public FunctionalSource(Function<String,String> function) {
- this.function = function;
- }
-
- /**
- * Returns a setting by delegating to the function.
+ * Returns a setting by applying the function.
*
* <p>
* If the function returns <c>null</c>, this method returns <c>null</c>
(indicating the key doesn't exist).
@@ -76,47 +73,35 @@ public class FunctionalSource implements SettingSource {
* @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);
- }
+ Optional<String> get(String name);
/**
- * Returns <c>false</c> since this source is read-only.
+ * Creates a functional source from a function that returns a string.
*
- * @return <c>false</c>
- */
- @Override
- public boolean isWriteable() {
- return false;
- }
-
- /**
- * No-op since this source is read-only.
+ * <p>
+ * This is a convenience factory method for creating functional sources
from functions that return
+ * <c>String</c> values. The function's return value is converted to an
<c>Optional</c> as follows:
+ * <ul>
+ * <li>If the function returns <c>null</c>, the source returns
<c>null</c> (key doesn't exist).
+ * <li>If the function returns a non-null value, the source
returns <c>Optional.of(value)</c>.
+ * </ul>
*
- * @param name The property name (ignored).
- * @param value The property value (ignored).
- */
- @Override
- public void set(String name, String value) {
- throw illegalArg("Attempting to set a value on a read-only
source.");
- }
-
- /**
- * No-op since this source is read-only.
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Create from a lambda</jc>
+ * FunctionalSource <jv>source1</jv> =
FunctionalSource.<jsf>of</jsf>(name -> System.getProperty(name));
*
- * @param name The property name (ignored).
- */
- @Override
- public void unset(String name) {
- throw illegalArg("Attempting to unset a value on a read-only
source.");
- }
-
- /**
- * No-op since this source is read-only.
+ * <jc>// Create from a method reference</jc>
+ * FunctionalSource <jv>source2</jv> =
FunctionalSource.<jsf>of</jsf>(System::getProperty);
+ * </p>
+ *
+ * @param function The function to delegate property lookups to. Must
not be <c>null</c>.
+ * @return A new functional source instance.
*/
- @Override
- public void clear() {
- throw illegalArg("Attempting to clear a read-only source.");
+ static FunctionalSource of(Function<String, String> function) {
+ return name -> {
+ var v = function.apply(name);
+ return v == null ? null : opt(v);
+ };
}
}
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/FunctionalStore.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/FunctionalStore.java
new file mode 100644
index 0000000000..045f82e36c
--- /dev/null
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/FunctionalStore.java
@@ -0,0 +1,174 @@
+/*
+ * 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.*;
+import java.util.function.*;
+
+import org.apache.juneau.commons.function.Snippet;
+
+/**
+ * A writable {@link SettingStore} implementation created from functional
interfaces.
+ *
+ * <p>
+ * This class allows you to create writable setting stores from lambda
expressions or method references,
+ * making it easy to wrap existing property systems (e.g., custom
configuration systems) as
+ * {@link SettingStore} instances.
+ *
+ * <h5 class='section'>Return Value Semantics:</h5>
+ * <ul class='spaced-list'>
+ * <li>If the reader function returns <c>null</c>, this store returns
<c>null</c> (key doesn't exist).
+ * <li>If the reader function returns a non-null value, this store returns
<c>Optional.of(value)</c>.
+ * </ul>
+ *
+ * <p>
+ * Note: This store cannot distinguish between a key that doesn't exist and a
key that exists with a null value,
+ * since the reader function only returns a <c>String</c>. If you need to
distinguish these cases, use {@link MapStore} instead.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Create a writable functional store</jc>
+ * FunctionalStore <jv>store</jv> = FunctionalStore.<jsf>of</jsf>(
+ * System::getProperty, <jc>// reader</jc>
+ * (k, v) -> System.setProperty(k, v), <jc>// writer</jc>
+ * k -> System.clearProperty(k), <jc>// unset</jc>
+ * () -> { <jc>// clear</jc>
+ * <jc>// Clear all properties logic</jc>
+ * }
+ * );
+ *
+ * <jc>// Use it</jc>
+ * <jv>store</jv>.set(<js>"my.property"</js>, <js>"value"</js>);
+ * Optional<String> <jv>value</jv> =
<jv>store</jv>.get(<js>"my.property"</js>);
+ * <jv>store</jv>.unset(<js>"my.property"</js>);
+ * </p>
+ */
+public class FunctionalStore implements SettingStore {
+
+ private final Function<String, String> reader;
+ private final BiConsumer<String, String> writer;
+ private final Consumer<String> unsetter;
+ private final Snippet clearer;
+
+ /**
+ * Creates a new writable functional store.
+ *
+ * @param reader The function to read property values. Must not be
<c>null</c>.
+ * @param writer The function to write property values. Must not be
<c>null</c>.
+ * @param unsetter The function to remove property values. Must not be
<c>null</c>.
+ * @param clearer The snippet to clear all property values. Must not be
<c>null</c>.
+ */
+ public FunctionalStore(
+ Function<String, String> reader,
+ BiConsumer<String, String> writer,
+ Consumer<String> unsetter,
+ Snippet clearer
+ ) {
+ assertArgNotNull("reader", reader);
+ assertArgNotNull("writer", writer);
+ assertArgNotNull("unsetter", unsetter);
+ assertArgNotNull("clearer", clearer);
+ this.reader = reader;
+ this.writer = writer;
+ this.unsetter = unsetter;
+ this.clearer = clearer;
+ }
+
+ /**
+ * Returns a setting by applying the reader function.
+ *
+ * <p>
+ * If the reader function returns <c>null</c>, this method returns
<c>null</c> (indicating the key doesn't exist).
+ * If the reader 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 reader function
returns <c>null</c>.
+ */
+ @Override
+ public Optional<String> get(String name) {
+ var v = reader.apply(name);
+ return v == null ? null : opt(v);
+ }
+
+ /**
+ * Sets a setting by applying the writer function.
+ *
+ * @param name The property name.
+ * @param value The property value, or <c>null</c> to set an empty
override.
+ */
+ @Override
+ public void set(String name, String value) {
+ writer.accept(name, value);
+ }
+
+ /**
+ * Removes a setting by applying the unsetter function.
+ *
+ * @param name The property name to remove.
+ */
+ @Override
+ public void unset(String name) {
+ unsetter.accept(name);
+ }
+
+ /**
+ * Clears all settings by invoking the clearer snippet.
+ *
+ * <p>
+ * If the clearer snippet throws an exception, it will be wrapped in a
{@link RuntimeException}.
+ */
+ @Override
+ public void clear() {
+ safe(()->clearer.run());
+ }
+
+ /**
+ * Creates a writable functional store from four functions.
+ *
+ * <p>
+ * This is a convenience factory method for creating writable
functional stores.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Create from lambdas</jc>
+ * FunctionalStore <jv>store</jv> = FunctionalStore.<jsf>of</jsf>(
+ * System::getProperty,
+ * (k, v) -> System.setProperty(k, v),
+ * k -> System.clearProperty(k),
+ * () -> { <jc>// Clear all properties</jc> }
+ * );
+ * </p>
+ *
+ * @param reader The function to read property values. Must not be
<c>null</c>.
+ * @param writer The function to write property values. Must not be
<c>null</c>.
+ * @param unsetter The function to remove property values. Must not be
<c>null</c>.
+ * @param clearer The snippet to clear all property values. Must not be
<c>null</c>.
+ * @return A new writable functional store instance.
+ */
+ public static FunctionalStore of(
+ Function<String, String> reader,
+ BiConsumer<String, String> writer,
+ Consumer<String> unsetter,
+ Snippet clearer
+ ) {
+ return new FunctionalStore(reader, writer, unsetter, clearer);
+ }
+}
+
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/MapStore.java
similarity index 67%
rename from
juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/MapSource.java
rename to
juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/MapStore.java
index 986fbf8d1f..2e826da92f 100644
---
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/MapStore.java
@@ -16,7 +16,6 @@
*/
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.*;
@@ -24,12 +23,12 @@ import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
/**
- * A writable {@link SettingSource} implementation backed by a thread-safe map.
+ * A writable {@link SettingStore} 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}.
+ * This class provides a mutable store for settings that can be modified at
runtime. It's particularly useful
+ * for creating custom property stores (e.g., Spring properties, configuration
files) that can be added to
+ * {@link Settings} as sources.
*
* <h5 class='section'>Thread Safety:</h5>
* <p>
@@ -44,29 +43,29 @@ import java.util.concurrent.atomic.*;
*
* <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>// Create a store and add properties</jc>
+ * MapStore <jv>store</jv> = <jk>new</jk> MapStore();
+ * <jv>store</jv>.set(<js>"my.property"</js>, <js>"value"</js>);
+ * <jv>store</jv>.set(<js>"another.property"</js>,
<js>"another-value"</js>);
*
- * <jc>// Add to Settings</jc>
- * Settings.<jsf>get</jsf>().addSource(<jv>source</jv>);
+ * <jc>// Add to Settings as a source (stores can be used as sources)</jc>
+ * Settings.<jsf>get</jsf>().addSource(<jv>store</jv>);
*
* <jc>// Override a system property with null</jc>
- * <jv>source</jv>.set(<js>"system.property"</js>, <jk>null</jk>);
+ * <jv>store</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>);
+ * <jv>store</jv>.unset(<js>"my.property"</js>);
* <jc>// get() will now return null for "my.property"</jc>
* </p>
*/
-public class MapSource implements SettingSource {
+public class MapStore implements SettingStore {
private final AtomicReference<Map<String,Optional<String>>> map = new
AtomicReference<>();
/**
- * Returns a setting from this source.
+ * Returns a setting from this store.
*
* <p>
* Returns <c>null</c> if the key doesn't exist in the map, or the
stored value (which may be
@@ -80,15 +79,12 @@ public class MapSource implements SettingSource {
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 null;
return m.get(key);
}
/**
- * Sets a setting in this source.
+ * Sets a setting in this store.
*
* <p>
* The internal map is lazily initialized on the first call to this
method. Setting a value to <c>null</c>
@@ -100,17 +96,16 @@ public class MapSource implements SettingSource {
*/
@Override
public void set(String key, String value) {
- assertWriteable("Attempting to set a value on a read-only map
source.");
var m = map.get();
if (m == null) {
var newMap = new
ConcurrentHashMap<String,Optional<String>>();
- m = map.compareAndSet(null, newMap) ? newMap :
map.get();
+ m = map.compareAndSet(null, newMap) ? newMap :
map.get(); // Not easily testable.
}
m.put(key, opt(value));
}
/**
- * Clears all entries from this source.
+ * Clears all entries from this store.
*
* <p>
* After calling this method, all keys will be removed from the map,
and {@link #get(String)} will return
@@ -118,37 +113,26 @@ public class MapSource implements SettingSource {
*/
@Override
public void clear() {
- assertWriteable("Attempting to update a read-only map source.");
var m = map.get();
if (m != null)
m.clear();
}
/**
- * Removes a setting from this source.
+ * Removes a setting from this store.
*
* <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>,
+ * indicating that the key doesn't exist in this store (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) {
- assertWriteable("Attempting to unset a value on a read-only map
source.");
var m = map.get();
if (m != null)
m.remove(name);
}
- @Override
- public boolean isWriteable() {
- return true;
- }
-
- private void assertWriteable(String msg, Object...args) {
- assertArg(isWriteable(), msg, args);
- }
-
}
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
index a5f4af9502..63e2fdf353 100644
---
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
@@ -22,9 +22,12 @@ 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.
+ * A setting source provides a way to retrieve property values.
* Sources are checked in reverse order (last added is checked first) when
looking up properties.
*
+ * <p>
+ * For writable sources that support modifying property values, see {@link
SettingStore}.
+ *
* <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.
@@ -35,12 +38,16 @@ import java.util.*;
*
* <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 functional source directly from a lambda</jc>
+ * FunctionalSource <jv>readOnly</jv> = name ->
opt(System.getProperty(name));
+ *
+ * <jc>// Or use the factory method</jc>
+ * FunctionalSource <jv>readOnly2</jv> =
FunctionalSource.<jsf>of</jsf>(System::getProperty);
*
- * <jc>// Create a read-only source from a function</jc>
- * FunctionalSource <jv>readOnly</jv> = <jk>new</jk> FunctionalSource(x
-> System.getProperty(x));
+ * <jc>// Stores can be used as sources (they extend SettingSource)</jc>
+ * MapStore <jv>store</jv> = <jk>new</jk> MapStore();
+ * <jv>store</jv>.set(<js>"my.property"</js>, <js>"value"</js>);
+ * Settings.<jsf>get</jsf>().addSource(<jv>store</jv>); <jc>// Stores can
be added as sources</jc>
* </p>
*/
public interface SettingSource {
@@ -62,58 +69,4 @@ public interface SettingSource {
* 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 isWriteable();
-
- /**
- * Sets a setting in this setting source.
- *
- * <p>
- * Should be a no-op if the source is not writable (i.e., {@link
#assertWriteable()} 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
#assertWriteable()} 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
#assertWriteable()} 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/SettingStore.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SettingStore.java
new file mode 100644
index 0000000000..5159b2be52
--- /dev/null
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/SettingStore.java
@@ -0,0 +1,78 @@
+/*
+ * 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;
+
+/**
+ * A writable extension of {@link SettingSource} that supports modifying
property values.
+ *
+ * <p>
+ * This interface extends {@link SettingSource} with methods for setting,
unsetting, and clearing properties.
+ * All stores that implement this interface provide read/write access and can
be modified at runtime.
+ *
+ * <p>
+ * <b>Sources vs Stores:</b>
+ * <ul>
+ * <li><b>Sources</b> ({@link SettingSource}) - Provide read-only access
to property values
+ * <li><b>Stores</b> ({@link SettingStore}) - Provide read/write access to
property values
+ * </ul>
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Create a writable store</jc>
+ * MapStore <jv>store</jv> = <jk>new</jk> MapStore();
+ * <jv>store</jv>.set(<js>"my.property"</js>, <js>"value"</js>);
+ * <jv>store</jv>.unset(<js>"my.property"</js>);
+ * <jv>store</jv>.clear();
+ * </p>
+ */
+public interface SettingStore extends SettingSource {
+
+ /**
+ * Sets a setting in this store.
+ *
+ * <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 store).
+ *
+ * @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 store.
+ *
+ * <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 store (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 store.
+ *
+ * <p>
+ * After calling this method, all keys will be removed from this store,
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
index a3e3fef582..9565cb550d 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
@@ -17,6 +17,7 @@
package org.apache.juneau.commons.settings;
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.*;
@@ -24,7 +25,10 @@ import java.net.*;
import java.nio.charset.*;
import java.nio.file.*;
import java.util.*;
-import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.*;
+import java.util.function.*;
+
+import org.apache.juneau.commons.function.*;
/**
* Encapsulates Java system properties with support for global and per-thread
overrides for unit testing.
@@ -34,30 +38,41 @@ import java.util.concurrent.CopyOnWriteArrayList;
* and per-thread level, making it useful for unit tests that need to
temporarily change system property
* values without affecting other tests or threads.
*
+ * <p>
+ * Settings instances are created using the {@link Builder} pattern. Use
{@link #create()} to create a new builder,
+ * or {@link #get()} to get the singleton instance (which is created using
default builder settings).
+ *
* <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>Per-thread store (if set via {@link #setLocal(String, String)})
+ * <li>Global store (if set via {@link #setGlobal(String, String)})
+ * <li>Sources in reverse order (last source added via {@link
Builder#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'>Sources vs Stores:</h5>
+ * <ul class='spaced-list'>
+ * <li><b>Sources</b> ({@link SettingSource}) - Provide read-only access
to property values. Examples: {@link FunctionalSource}
+ * <li><b>Stores</b> ({@link SettingStore}) - Provide read/write access to
property values. Examples: {@link MapStore}, {@link FunctionalStore}
+ * <li>Stores can be used as sources (they extend {@link SettingSource}),
so you can add stores via {@link Builder#addSource(SettingSource)}
+ * </ul>
+ *
* <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>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
* </ul>
*
* <h5 class='section'>Usage Examples:</h5>
* <p class='bjava'>
- * <jc>// Get a system property as a string</jc>
+ * <jc>// Get a system property as a string (using singleton instance)</jc>
* Optional<String> <jv>value</jv> =
Settings.<jsf>get</jsf>().get(<js>"my.property"</js>);
*
* <jc>// Get with type conversion</jc>
@@ -85,55 +100,43 @@ import java.util.concurrent.CopyOnWriteArrayList;
* <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();
+ * <jc>// Create a custom Settings instance with custom sources (e.g.,
Spring properties)</jc>
+ * MapStore <jv>springSource</jv> = <jk>new</jk> MapStore();
* <jv>springSource</jv>.set(<js>"spring.datasource.url"</js>,
<js>"jdbc:postgresql://localhost/db"</js>);
- * Settings.<jsf>get</jsf>().addSource(<jv>springSource</jv>);
+ * Settings <jv>custom</jv> = Settings.<jsf>create</jsf>()
+ * .addSource(<jv>springSource</jv>)
+ * .addSource(FunctionalSource.<jsf>of</jsf>(System::getProperty))
+ * .build();
* </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
+ * <li><c>juneau.settings.disableGlobal</c> (system property) or
<c>JUNEAU_SETTINGS_DISABLEGLOBAL</c> (system env) - 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.
* <p class='bnote'>
- * Note: This property is read once at class
initialization time and cannot be changed at runtime.
- * Changing the system property after the class has been
loaded will have no effect.
- * </p>
- * <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...)}.
- * <p class='bnote'>
- * Note: This property is read once at class
initialization time and cannot be changed at runtime.
- * Changing the system property after the class has been
loaded will have no effect.
+ * Note: This property is read once at class
initialization time when creating the singleton instance
+ * and cannot be changed at runtime. Changing the system
property after the class has been loaded will
+ * have no effect on the singleton instance. However, you
can create custom Settings instances using
+ * {@link #create()} that ignore this property.
* </p>
* </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
FunctionalSource(x -> System.getProperty(x));
+ public static final SettingSource SYSTEM_PROPERTY_SOURCE =
FunctionalSource.of(System::getProperty);
/**
* System environment variable source that delegates to {@link
System#getenv(String)}.
*/
- public static final SettingSource SYSTEM_ENV_SOURCE = new
FunctionalSource(x -> System.getenv(x));
+ public static final SettingSource SYSTEM_ENV_SOURCE =
FunctionalSource.of(System::getenv);
+
+ private static final String DISABLE_GLOBAL_PROP =
"juneau.settings.disableGlobal";
+ private static final String MSG_globalDisabled = "Global settings not
enabled";
+ private static final String MSG_localDisabled = "Local settings not
enabled";
/**
* Returns properties for this Settings object itself.
@@ -143,24 +146,139 @@ public class Settings {
var v = SYSTEM_PROPERTY_SOURCE.get(property);
if (v != null)
return v; // Not testable
- v = SYSTEM_ENV_SOURCE.get(property);
+ v = SYSTEM_ENV_SOURCE.get(uc(property.replace('.', '_')));
if (v != null)
return v; // Not testable
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);
+ /**
+ * Creates a new builder for constructing a Settings instance.
+ *
+ * <p>
+ * This method provides a convenient way to create custom Settings
instances with specific
+ * configuration. The builder allows you to configure stores, sources,
and other settings
+ * before building the final Settings instance.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Create a custom Settings instance</jc>
+ * Settings <jv>custom</jv> = Settings.<jsf>create</jsf>()
+ * .globalStore(() -> <jk>new</jk> MapStore())
+ * .localStore(() -> <jk>new</jk> MapStore())
+ *
.addSource(FunctionalSource.<jsf>of</jsf>(System::getProperty))
+ * .build();
+ * </p>
+ *
+ * @return A new Builder instance.
+ */
+ public static Builder create() {
+ return new Builder();
+ }
+
+ /**
+ * Builder for creating Settings instances.
+ */
+ public static class Builder {
+ private Supplier<SettingStore> globalStoreSupplier = () -> new
MapStore();
+ private Supplier<SettingStore> localStoreSupplier = () -> new
MapStore();
+ private final List<SettingSource> sources = new ArrayList<>();
+
+ /**
+ * Sets the supplier for the global store.
+ *
+ * @param supplier The supplier for the global store. Must not
be <c>null</c>. Can supply null to disable global store.
+ * @return This builder for method chaining.
+ */
+ public Builder globalStore(OptionalSupplier<SettingStore>
supplier) {
+ this.globalStoreSupplier = assertArgNotNull("supplier",
supplier);
+ return this;
+ }
+
+ /**
+ * Sets the supplier for the local (per-thread) store.
+ *
+ * @param supplier The supplier for the local store. Must not
be <c>null</c>.
+ * @return This builder for method chaining.
+ */
+ public Builder localStore(OptionalSupplier<SettingStore>
supplier) {
+ this.localStoreSupplier = assertArgNotNull("supplier",
supplier);
+ return this;
+ }
+
+ /**
+ * Sets the sources list, replacing any existing sources.
+ *
+ * @param sources The sources to set. Must not be <c>null</c>
or contain <c>null</c> elements.
+ * @return This builder for method chaining.
+ */
+ @SafeVarargs
+ public final Builder setSources(SettingSource...sources) {
+ assertVarargsNotNull("sources", sources);
+ this.sources.clear();
+ for (var source : sources) {
+ this.sources.add(source);
+ }
+ return this;
+ }
+
+ /**
+ * Adds a source to the sources list.
+ *
+ * @param source The source to add. Must not be <c>null</c>.
+ * @return This builder for method chaining.
+ */
+ public Builder addSource(SettingSource source) {
+ assertArgNotNull("source", source);
+ this.sources.add(source);
+ return this;
+ }
+
+ /**
+ * Adds a functional source to the sources list.
+ *
+ * @param source The functional source to add. Must not be
<c>null</c>.
+ * @return This builder for method chaining.
+ */
+ public Builder addSource(FunctionalSource source) {
+ return addSource((SettingSource)source);
+ }
+
+ /**
+ * Builds a Settings instance from this builder.
+ *
+ * @return A new Settings instance.
+ */
+ public Settings build() {
+ return new Settings(this);
+ }
+ }
+
+ private static final Settings INSTANCE = new Builder()
+
.globalStore(initProperty(DISABLE_GLOBAL_PROP).map(Boolean::valueOf).orElse(false)
? () -> null : () -> new MapStore())
+ .setSources(SYSTEM_ENV_SOURCE, SYSTEM_PROPERTY_SOURCE)
+ .build();
+
+ /**
+ * Returns the singleton instance of Settings.
+ *
+ * @return The singleton Settings instance.
+ */
+ public static Settings get() {
+ return INSTANCE;
+ }
+
+ private final ResettableSupplier<SettingStore> globalStore;
+ private final ThreadLocal<SettingStore> localStore;
+ private final List<SettingSource> sources;
/**
* 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);
+ private Settings(Builder builder) {
+ this.globalStore =
memoizeResettable(builder.globalStoreSupplier);
+ this.localStore =
ThreadLocal.withInitial(builder.localStoreSupplier);
+ this.sources = new CopyOnWriteArrayList<>(builder.sources);
}
/**
@@ -181,32 +299,24 @@ public class Settings {
*/
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
- }
+ 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
- var v = globalOverrides.get(name);
+ 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);
- 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;
+ if (result != null)
+ return result;
}
- return Optional.empty();
+ return opte();
}
/**
@@ -357,12 +467,14 @@ public class Settings {
*
* @param name The property name.
* @param value The override value, or <c>null</c> to set an empty
override.
+ * @return This object for method chaining.
* @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);
+ public Settings setGlobal(String name, String value) {
+ assertArgNotNull("name", name);
+
globalStore.orElseThrow(()->illegalState(MSG_globalDisabled)).set(name, value);
+ return this;
}
/**
@@ -377,8 +489,8 @@ public class Settings {
* @see #clearGlobal()
*/
public void unsetGlobal(String name) {
- assertArg(! DISABLE_GLOBAL, "Global settings have been disabled
via ''{0}''", DISABLE_GLOBAL_PROP);
- globalOverrides.unset(name);
+ assertArgNotNull("name", name);
+
globalStore.orElseThrow(()->illegalState(MSG_globalDisabled)).unset(name);
}
/**
@@ -395,16 +507,15 @@ public class Settings {
*
* @param name The property name.
* @param value The override value, or <c>null</c> to set an empty
override.
+ * @return This object for method chaining.
* @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);
+ public Settings setLocal(String name, String value) {
+ assertArgNotNull("name", name);
+ assertState(nn(localStore.get()), MSG_localDisabled);
+ localStore.get().set(name, value);
+ return this;
}
/**
@@ -419,9 +530,9 @@ public class Settings {
* @see #clearLocal()
*/
public void unsetLocal(String name) {
- var localSource = threadOverrides.get();
- if (localSource != null)
- localSource.unset(name);
+ assertArgNotNull("name", name);
+ assertState(nn(localStore.get()), MSG_localDisabled);
+ localStore.get().unset(name);
}
/**
@@ -435,11 +546,14 @@ public class Settings {
* 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.
*
+ * @return This object for method chaining.
* @see #setLocal(String, String)
* @see #unsetLocal(String)
*/
- public void clearLocal() {
- threadOverrides.remove();
+ public Settings clearLocal() {
+ assertState(nn(localStore.get()), MSG_localDisabled);
+ localStore.get().clear();
+ return this;
}
/**
@@ -449,96 +563,13 @@ public class Settings {
* After calling this method, all properties will fall back to resolver
values
* (or per-thread overrides if they exist).
*
+ * @return This object for method chaining.
* @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);
+ public Settings clearGlobal() {
+
globalStore.orElseThrow(()->illegalState(MSG_globalDisabled)).clear();
return this;
}
-
- public Settings addSource(FunctionalSource source) {
- return addSource((SettingSource)source);
- }
-
- /**
- * 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 > global > envSource >
springSource > system properties > 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/AssertionUtils.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/AssertionUtils.java
index b8e22005c8..30f2cf3612 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
@@ -78,6 +78,24 @@ public class AssertionUtils {
throw illegalArg(msg, args);
}
+ /**
+ * Asserts that the given expression is <c>true</c>, throwing an {@link
IllegalStateException} if it's not.
+ *
+ * <p>
+ * This method is similar to {@link #assertArg(boolean, String,
Object...)} but throws an
+ * {@link IllegalStateException} instead of an {@link
IllegalArgumentException}, making it suitable
+ * for state validation rather than argument validation.
+ *
+ * @param expression The expression to test.
+ * @param msg The error message format string.
+ * @param args The arguments for the error message format string.
+ * @throws IllegalStateException if the expression is <c>false</c>.
+ */
+ public static final void assertState(boolean expression, String msg,
Object...args) throws IllegalStateException {
+ if (! expression)
+ throw illegalState(msg, args);
+ }
+
/**
* Throws an {@link IllegalArgumentException} if the specified argument
is <jk>null</jk>.
*
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 f836e2cae7..cc9168c8e6 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
@@ -61,7 +61,7 @@ public class ThrowableUtils {
* Shortcut for creating a {@link BeanRuntimeException} with a message
and associated class.
*
* <p>
- * Same as {@link #bex(Class, String, Object...) bex(Class, String,
Object...)} but accepts a {@link ClassInfo} instead of a {@link Class}.
+ * Same as the {@code bex(Class, String, Object...)} method but accepts
a {@link ClassInfo} instead of a {@link Class}.
*
* @param c The class info associated with the exception.
* @param msg The message.
@@ -110,7 +110,7 @@ public class ThrowableUtils {
* Shortcut for creating a {@link BeanRuntimeException} with a cause,
message, and associated class.
*
* <p>
- * Same as {@link #bex(Throwable, Class, String, Object...)} but
accepts a {@link ClassInfo} instead of a {@link Class}.
+ * Same as the {@code bex(Throwable, Class, String, Object...)} method
but accepts a {@link ClassInfo} instead of a {@link Class}.
*
* @param e The cause of the exception.
* @param c The class info associated with the exception.
@@ -332,6 +332,29 @@ public class ThrowableUtils {
return new IllegalArgumentException(f(msg, args));
}
+ /**
+ * Creates an {@link IllegalStateException} with a formatted message.
+ *
+ * <p>
+ * This is a convenience method for creating state exceptions with
formatted messages.
+ * The message is formatted using {@link #f(String, Object...)}.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jk>throw</jk> <jsm>illegalState</jsm>(<js>"Invalid state:
{0}"</js>, <jv>state</jv>);
+ * </p>
+ *
+ * @param msg The message format string.
+ * @param args The arguments for the message format string.
+ * @return A new IllegalStateException with the formatted message.
+ * @see #f(String, Object...)
+ * @see #illegalArg(String, Object...)
+ */
+ public static IllegalStateException illegalState(String msg,
Object...args) {
+ return new IllegalStateException(f(msg, args));
+ }
+
+
/**
* Creates an {@link IllegalArgumentException} wrapping the given
throwable.
*
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 bae91ec0b2..2172e49477 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
@@ -16,16 +16,16 @@
*/
package org.apache.juneau.commons.settings;
+import static org.apache.juneau.commons.utils.Utils.*;
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.apache.juneau.commons.function.*;
import org.junit.jupiter.api.*;
class Settings_Test extends TestBase {
@@ -38,7 +38,6 @@ class Settings_Test extends TestBase {
// 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);
@@ -59,21 +58,21 @@ class Settings_Test extends TestBase {
@Test
void a01_get_fromSystemProperty() {
System.setProperty(TEST_PROP, "system-value");
- Optional<String> result = Settings.get().get(TEST_PROP);
+ var 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");
+ var 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);
+ var result = Settings.get().get(TEST_PROP);
assertTrue(result.isPresent());
assertEquals("global-value", result.get());
}
@@ -81,7 +80,7 @@ class Settings_Test extends TestBase {
@Test
void a04_get_fromLocalOverride() {
Settings.get().setLocal(TEST_PROP, "local-value");
- Optional<String> result = Settings.get().get(TEST_PROP);
+ var result = Settings.get().get(TEST_PROP);
assertTrue(result.isPresent());
assertEquals("local-value", result.get());
}
@@ -91,7 +90,7 @@ class Settings_Test extends TestBase {
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);
+ var result = Settings.get().get(TEST_PROP);
assertTrue(result.isPresent());
assertEquals("local-value", result.get());
}
@@ -100,7 +99,7 @@ class Settings_Test extends TestBase {
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);
+ var result = Settings.get().get(TEST_PROP);
assertTrue(result.isPresent());
assertEquals("global-value", result.get());
}
@@ -108,7 +107,7 @@ class Settings_Test extends TestBase {
@Test
void a07_get_nullValue() {
Settings.get().setLocal(TEST_PROP, null);
- Optional<String> result = Settings.get().get(TEST_PROP);
+ var result = Settings.get().get(TEST_PROP);
assertFalse(result.isPresent());
}
@@ -118,7 +117,7 @@ class Settings_Test extends TestBase {
@Test
void b01_getInteger_valid() {
System.setProperty(TEST_PROP, "123");
- Optional<Integer> result = Settings.get().getInteger(TEST_PROP);
+ var result = Settings.get().getInteger(TEST_PROP);
assertTrue(result.isPresent());
assertEquals(123, result.get());
}
@@ -126,20 +125,20 @@ class Settings_Test extends TestBase {
@Test
void b02_getInteger_invalid() {
System.setProperty(TEST_PROP, "not-a-number");
- Optional<Integer> result = Settings.get().getInteger(TEST_PROP);
+ var result = Settings.get().getInteger(TEST_PROP);
assertFalse(result.isPresent());
}
@Test
void b03_getInteger_notFound() {
- Optional<Integer> result =
Settings.get().getInteger("nonexistent.property");
+ var 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);
+ var result = Settings.get().getInteger(TEST_PROP);
assertTrue(result.isPresent());
assertEquals(456, result.get());
}
@@ -150,7 +149,7 @@ class Settings_Test extends TestBase {
@Test
void c01_getLong_valid() {
System.setProperty(TEST_PROP, "123456789");
- Optional<Long> result = Settings.get().getLong(TEST_PROP);
+ var result = Settings.get().getLong(TEST_PROP);
assertTrue(result.isPresent());
assertEquals(123456789L, result.get());
}
@@ -158,14 +157,14 @@ class Settings_Test extends TestBase {
@Test
void c02_getLong_invalid() {
System.setProperty(TEST_PROP, "not-a-number");
- Optional<Long> result = Settings.get().getLong(TEST_PROP);
+ var 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);
+ var result = Settings.get().getLong(TEST_PROP);
assertTrue(result.isPresent());
assertEquals(987654321L, result.get());
}
@@ -176,7 +175,7 @@ class Settings_Test extends TestBase {
@Test
void d01_getBoolean_true() {
System.setProperty(TEST_PROP, "true");
- Optional<Boolean> result = Settings.get().getBoolean(TEST_PROP);
+ var result = Settings.get().getBoolean(TEST_PROP);
assertTrue(result.isPresent());
assertTrue(result.get());
}
@@ -184,7 +183,7 @@ class Settings_Test extends TestBase {
@Test
void d02_getBoolean_false() {
System.setProperty(TEST_PROP, "false");
- Optional<Boolean> result = Settings.get().getBoolean(TEST_PROP);
+ var result = Settings.get().getBoolean(TEST_PROP);
assertTrue(result.isPresent());
assertFalse(result.get());
}
@@ -192,7 +191,7 @@ class Settings_Test extends TestBase {
@Test
void d03_getBoolean_caseInsensitive() {
System.setProperty(TEST_PROP, "TRUE");
- Optional<Boolean> result = Settings.get().getBoolean(TEST_PROP);
+ var result = Settings.get().getBoolean(TEST_PROP);
assertTrue(result.isPresent());
assertTrue(result.get());
}
@@ -200,14 +199,14 @@ class Settings_Test extends TestBase {
@Test
void d04_getBoolean_nonTrueValue() {
System.setProperty(TEST_PROP, "anything");
- Optional<Boolean> result = Settings.get().getBoolean(TEST_PROP);
+ var 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");
+ var result = Settings.get().getBoolean("nonexistent.property");
assertFalse(result.isPresent());
}
@@ -217,7 +216,7 @@ class Settings_Test extends TestBase {
@Test
void e01_getDouble_valid() {
System.setProperty(TEST_PROP, "123.456");
- Optional<Double> result = Settings.get().getDouble(TEST_PROP);
+ var result = Settings.get().getDouble(TEST_PROP);
assertTrue(result.isPresent());
assertEquals(123.456, result.get(), 0.0001);
}
@@ -225,7 +224,7 @@ class Settings_Test extends TestBase {
@Test
void e02_getDouble_invalid() {
System.setProperty(TEST_PROP, "not-a-number");
- Optional<Double> result = Settings.get().getDouble(TEST_PROP);
+ var result = Settings.get().getDouble(TEST_PROP);
assertFalse(result.isPresent());
}
@@ -235,7 +234,7 @@ class Settings_Test extends TestBase {
@Test
void f01_getFloat_valid() {
System.setProperty(TEST_PROP, "123.456");
- Optional<Float> result = Settings.get().getFloat(TEST_PROP);
+ var result = Settings.get().getFloat(TEST_PROP);
assertTrue(result.isPresent());
assertEquals(123.456f, result.get(), 0.0001f);
}
@@ -243,7 +242,7 @@ class Settings_Test extends TestBase {
@Test
void f02_getFloat_invalid() {
System.setProperty(TEST_PROP, "not-a-number");
- Optional<Float> result = Settings.get().getFloat(TEST_PROP);
+ var result = Settings.get().getFloat(TEST_PROP);
assertFalse(result.isPresent());
}
@@ -253,14 +252,14 @@ class Settings_Test extends TestBase {
@Test
void g01_getFile_valid() {
System.setProperty(TEST_PROP, "/tmp/test.txt");
- Optional<File> result = Settings.get().getFile(TEST_PROP);
+ var 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");
+ var result = Settings.get().getFile("nonexistent.property");
assertFalse(result.isPresent());
}
@@ -270,7 +269,7 @@ class Settings_Test extends TestBase {
@Test
void h01_getPath_valid() {
System.setProperty(TEST_PROP, "/tmp/test.txt");
- Optional<Path> result = Settings.get().getPath(TEST_PROP);
+ var result = Settings.get().getPath(TEST_PROP);
assertTrue(result.isPresent());
assertEquals(Paths.get("/tmp/test.txt"), result.get());
}
@@ -280,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");
- Optional<Path> result = Settings.get().getPath(TEST_PROP);
+ var result = Settings.get().getPath(TEST_PROP);
// May or may not be empty depending on OS, but should not throw
assertNotNull(result);
}
@@ -291,7 +290,7 @@ class Settings_Test extends TestBase {
@Test
void i01_getURI_valid() {
System.setProperty(TEST_PROP, "http://example.com/test");
- Optional<URI> result = Settings.get().getURI(TEST_PROP);
+ var result = Settings.get().getURI(TEST_PROP);
assertTrue(result.isPresent());
assertEquals(URI.create("http://example.com/test"),
result.get());
}
@@ -299,7 +298,7 @@ class Settings_Test extends TestBase {
@Test
void i02_getURI_invalid() {
System.setProperty(TEST_PROP, "not a valid uri");
- Optional<URI> result = Settings.get().getURI(TEST_PROP);
+ var result = Settings.get().getURI(TEST_PROP);
assertFalse(result.isPresent());
}
@@ -309,7 +308,7 @@ class Settings_Test extends TestBase {
@Test
void j01_getCharset_valid() {
System.setProperty(TEST_PROP, "UTF-8");
- Optional<Charset> result = Settings.get().getCharset(TEST_PROP);
+ var result = Settings.get().getCharset(TEST_PROP);
assertTrue(result.isPresent());
assertEquals(Charset.forName("UTF-8"), result.get());
}
@@ -317,7 +316,7 @@ class Settings_Test extends TestBase {
@Test
void j02_getCharset_invalid() {
System.setProperty(TEST_PROP, "INVALID-CHARSET");
- Optional<Charset> result = Settings.get().getCharset(TEST_PROP);
+ var result = Settings.get().getCharset(TEST_PROP);
assertFalse(result.isPresent());
}
@@ -327,7 +326,7 @@ class Settings_Test extends TestBase {
@Test
void k01_setGlobal() {
Settings.get().setGlobal(TEST_PROP, "global-value");
- Optional<String> result = Settings.get().get(TEST_PROP);
+ var result = Settings.get().get(TEST_PROP);
assertTrue(result.isPresent());
assertEquals("global-value", result.get());
}
@@ -336,7 +335,7 @@ class Settings_Test extends TestBase {
void k02_unsetGlobal() {
Settings.get().setGlobal(TEST_PROP, "global-value");
Settings.get().unsetGlobal(TEST_PROP);
- Optional<String> result = Settings.get().get(TEST_PROP);
+ var result = Settings.get().get(TEST_PROP);
assertFalse(result.isPresent());
}
@@ -352,7 +351,7 @@ class Settings_Test extends TestBase {
@Test
void k04_setGlobal_nullValue() {
Settings.get().setGlobal(TEST_PROP, null);
- Optional<String> result = Settings.get().get(TEST_PROP);
+ var result = Settings.get().get(TEST_PROP);
assertFalse(result.isPresent());
}
@@ -362,7 +361,7 @@ class Settings_Test extends TestBase {
@Test
void l01_setLocal() {
Settings.get().setLocal(TEST_PROP, "local-value");
- Optional<String> result = Settings.get().get(TEST_PROP);
+ var result = Settings.get().get(TEST_PROP);
assertTrue(result.isPresent());
assertEquals("local-value", result.get());
}
@@ -371,7 +370,16 @@ class Settings_Test extends TestBase {
void l02_unsetLocal() {
Settings.get().setLocal(TEST_PROP, "local-value");
Settings.get().unsetLocal(TEST_PROP);
- Optional<String> result = Settings.get().get(TEST_PROP);
+ var result = Settings.get().get(TEST_PROP);
+ assertFalse(result.isPresent());
+ }
+
+ @Test
+ void l07_unsetLocal_whenNoLocalSource() {
+ // unsetLocal should not throw when there's no local source
(threadOverrides.get() returns null)
+ Settings.get().unsetLocal(TEST_PROP);
+ // Should not throw an exception
+ var result = Settings.get().get(TEST_PROP);
assertFalse(result.isPresent());
}
@@ -387,7 +395,7 @@ class Settings_Test extends TestBase {
@Test
void l04_setLocal_nullValue() {
Settings.get().setLocal(TEST_PROP, null);
- Optional<String> result = Settings.get().get(TEST_PROP);
+ var result = Settings.get().get(TEST_PROP);
assertFalse(result.isPresent());
}
@@ -395,7 +403,7 @@ class Settings_Test extends TestBase {
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);
+ var result = Settings.get().get(TEST_PROP);
assertTrue(result.isPresent());
assertEquals("local-value", result.get());
}
@@ -404,7 +412,7 @@ class Settings_Test extends TestBase {
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);
+ var result = Settings.get().get(TEST_PROP);
assertTrue(result.isPresent());
assertEquals("local-value", result.get());
}
@@ -416,9 +424,9 @@ class Settings_Test extends TestBase {
void m01_localOverride_threadIsolation() throws InterruptedException {
Settings.get().setLocal(TEST_PROP, "thread1-value");
- Thread thread2 = new Thread(() -> {
+ var thread2 = new Thread(() -> {
Settings.get().setLocal(TEST_PROP, "thread2-value");
- Optional<String> result = Settings.get().get(TEST_PROP);
+ var result = Settings.get().get(TEST_PROP);
assertTrue(result.isPresent());
assertEquals("thread2-value", result.get());
});
@@ -427,7 +435,7 @@ class Settings_Test extends TestBase {
thread2.join();
// Original thread should still have its value
- Optional<String> result = Settings.get().get(TEST_PROP);
+ var result = Settings.get().get(TEST_PROP);
assertTrue(result.isPresent());
assertEquals("thread1-value", result.get());
}
@@ -436,8 +444,8 @@ class Settings_Test extends TestBase {
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);
+ var thread2 = new Thread(() -> {
+ var result = Settings.get().get(TEST_PROP);
assertTrue(result.isPresent());
assertEquals("global-value", result.get());
});
@@ -446,7 +454,7 @@ class Settings_Test extends TestBase {
thread2.join();
// Original thread should also see the global value
- Optional<String> result = Settings.get().get(TEST_PROP);
+ var result = Settings.get().get(TEST_PROP);
assertTrue(result.isPresent());
assertEquals("global-value", result.get());
}
@@ -456,8 +464,8 @@ class Settings_Test extends TestBase {
//====================================================================================================
@Test
void n01_get_returnsSameInstance() {
- Settings instance1 = Settings.get();
- Settings instance2 = Settings.get();
+ var instance1 = Settings.get();
+ var instance2 = Settings.get();
assertSame(instance1, instance2);
}
@@ -466,41 +474,61 @@ class Settings_Test extends TestBase {
//====================================================================================================
@Test
void o01_addSource() {
- MapSource source = new MapSource();
+ var source = new MapStore();
source.set(TEST_PROP, "source-value");
- Settings.get().addSource(source);
+ var settings = Settings.create()
+ .addSource(source)
+ .addSource(Settings.SYSTEM_PROPERTY_SOURCE)
+ .addSource(Settings.SYSTEM_ENV_SOURCE)
+ .build();
- Optional<String> result = Settings.get().get(TEST_PROP);
+ var result = settings.get(TEST_PROP);
assertTrue(result.isPresent());
assertEquals("source-value", result.get());
}
@Test
void o02_addSource_reverseOrder() {
- MapSource source1 = new MapSource();
+ var source1 = new MapStore();
source1.set(TEST_PROP, "source1-value");
- Settings.get().addSource(source1);
- MapSource source2 = new MapSource();
+ var source2 = new MapStore();
source2.set(TEST_PROP, "source2-value");
- Settings.get().addSource(source2);
+
+ var settings = Settings.create()
+ .addSource(source1)
+ .addSource(source2)
+ .addSource(Settings.SYSTEM_PROPERTY_SOURCE)
+ .addSource(Settings.SYSTEM_ENV_SOURCE)
+ .build();
// Last added source should be checked first
- Optional<String> result = Settings.get().get(TEST_PROP);
+ var result = settings.get(TEST_PROP);
assertTrue(result.isPresent());
assertEquals("source2-value", result.get());
}
@Test
void o03_addSource_afterGlobalOverride() {
- Settings.get().setGlobal(TEST_PROP, "global-value");
+ var settings = Settings.create()
+ .addSource(Settings.SYSTEM_PROPERTY_SOURCE)
+ .addSource(Settings.SYSTEM_ENV_SOURCE)
+ .build();
+
+ settings.setGlobal(TEST_PROP, "global-value");
- MapSource source = new MapSource();
+ var source = new MapStore();
source.set(TEST_PROP, "source-value");
- Settings.get().addSource(source);
+ // Note: Can't add sources after building, so we create a new
instance
+ var settingsWithSource = Settings.create()
+ .addSource(source)
+ .addSource(Settings.SYSTEM_PROPERTY_SOURCE)
+ .addSource(Settings.SYSTEM_ENV_SOURCE)
+ .build();
+ settingsWithSource.setGlobal(TEST_PROP, "global-value");
// Global override should take precedence
- Optional<String> result = Settings.get().get(TEST_PROP);
+ var result = settingsWithSource.get(TEST_PROP);
assertTrue(result.isPresent());
assertEquals("global-value", result.get());
}
@@ -509,105 +537,338 @@ class Settings_Test extends TestBase {
void o04_addSource_beforeSystemProperty() {
System.setProperty(TEST_PROP, "system-value");
- MapSource source = new MapSource();
+ var source = new MapStore();
source.set(TEST_PROP, "source-value");
- Settings.get().addSource(source);
+ // Sources are checked in reverse order, so add system sources
first, then custom source
+ var settings = Settings.create()
+ .addSource(Settings.SYSTEM_ENV_SOURCE)
+ .addSource(Settings.SYSTEM_PROPERTY_SOURCE)
+ .addSource(source)
+ .build();
- // Source should take precedence over system property
- Optional<String> result = Settings.get().get(TEST_PROP);
+ // Source should take precedence over system property (checked
first)
+ var result = settings.get(TEST_PROP);
assertTrue(result.isPresent());
assertEquals("source-value", result.get());
}
@Test
void o05_addSource_fallbackToSystemProperty() {
- MapSource source = new MapSource();
+ var source = new MapStore();
// Source doesn't have the property
System.setProperty(TEST_PROP, "system-value");
- Settings.get().addSource(source);
+ var settings = Settings.create()
+ .addSource(source)
+ .addSource(Settings.SYSTEM_PROPERTY_SOURCE)
+ .addSource(Settings.SYSTEM_ENV_SOURCE)
+ .build();
// Should fall back to system property
- Optional<String> result = Settings.get().get(TEST_PROP);
+ var result = settings.get(TEST_PROP);
assertTrue(result.isPresent());
assertEquals("system-value", result.get());
}
@Test
void o06_setSources() {
- MapSource source1 = new MapSource();
+ var source1 = new MapStore();
source1.set(TEST_PROP, "source1-value");
- MapSource source2 = new MapSource();
+ var source2 = new MapStore();
source2.set(TEST_PROP, "source2-value");
- Settings.get().setSources(source1, source2);
+ var settings = Settings.create()
+ .setSources(source1, source2,
Settings.SYSTEM_PROPERTY_SOURCE, Settings.SYSTEM_ENV_SOURCE)
+ .build();
// Last source in array should be checked first
- Optional<String> result = Settings.get().get(TEST_PROP);
+ var result = settings.get(TEST_PROP);
assertTrue(result.isPresent());
assertEquals("source2-value", result.get());
}
@Test
void o07_setSources_clearsExisting() {
- MapSource source1 = new MapSource();
+ var source1 = new MapStore();
source1.set(TEST_PROP, "source1-value");
- Settings.get().addSource(source1);
- MapSource source2 = new MapSource();
+ var source2 = new MapStore();
source2.set(TEST_PROP, "source2-value");
- Settings.get().setSources(source2);
+
+ var settings = Settings.create()
+ .addSource(source1)
+ .setSources(source2, Settings.SYSTEM_PROPERTY_SOURCE,
Settings.SYSTEM_ENV_SOURCE)
+ .build();
// Only source2 should exist now
- Optional<String> result = Settings.get().get(TEST_PROP);
+ var result = settings.get(TEST_PROP);
assertTrue(result.isPresent());
assertEquals("source2-value", result.get());
}
@Test
void o08_addSource_nullValue() {
- MapSource source = new MapSource();
+ var source = new MapStore();
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);
+
+ // Sources are checked in reverse order, so add system sources
first, then custom source
+ var settings = Settings.create()
+ .addSource(Settings.SYSTEM_ENV_SOURCE)
+ .addSource(Settings.SYSTEM_PROPERTY_SOURCE)
+ .addSource(source)
+ .build();
+
+ // Note that setting a null value on the source
(Optional.empty()) overrides the system property.
+ // When a source returns Optional.empty(), it means the
property exists but has a null value.
+ var result = settings.get(TEST_PROP);
assertFalse(result.isPresent());
}
@Test
void o09_addSource_nullSource() {
assertThrows(IllegalArgumentException.class, () -> {
- Settings.get().addSource(null);
+ Settings.create().addSource(null);
});
}
@Test
void o10_setSources_nullSource() {
- MapSource source1 = new MapSource();
+ var source1 = new MapStore();
assertThrows(IllegalArgumentException.class, () -> {
- Settings.get().setSources(source1, null);
+ Settings.create().setSources(source1, null);
});
}
@Test
void o11_source_springPropertiesExample() {
// Simulate Spring properties
- MapSource springSource = new MapSource();
+ var springSource = new MapStore();
springSource.set("spring.datasource.url",
"jdbc:postgresql://localhost/db");
springSource.set("spring.datasource.username", "admin");
- Settings.get().addSource(springSource);
+ var settings = Settings.create()
+ .addSource(springSource)
+ .addSource(Settings.SYSTEM_PROPERTY_SOURCE)
+ .addSource(Settings.SYSTEM_ENV_SOURCE)
+ .build();
- Optional<String> url =
Settings.get().get("spring.datasource.url");
+ var url = settings.get("spring.datasource.url");
assertTrue(url.isPresent());
assertEquals("jdbc:postgresql://localhost/db", url.get());
- Optional<String> username =
Settings.get().get("spring.datasource.username");
+ var username = settings.get("spring.datasource.username");
assertTrue(username.isPresent());
assertEquals("admin", username.get());
}
+
+
//====================================================================================================
+ // addSource(FunctionalSource) - Functional interface usage
+
//====================================================================================================
+ @Test
+ void p01_addSource_functionalSource() {
+ // Test the addSource(FunctionalSource) overload
+ var source = (FunctionalSource) name ->
opt(System.getProperty(name));
+ System.setProperty(TEST_PROP, "system-value");
+ var settings = Settings.create()
+ .addSource(source)
+ .addSource(Settings.SYSTEM_ENV_SOURCE)
+ .build();
+ var result = settings.get(TEST_PROP);
+ assertTrue(result.isPresent());
+ assertEquals("system-value", result.get());
+ }
+
+ @Test
+ void p02_addSource_functionalSource_factoryMethod() {
+ // Test addSource with FunctionalSource.of()
+ var source = FunctionalSource.of(System::getProperty);
+ System.setProperty(TEST_PROP, "system-value");
+ var settings = Settings.create()
+ .addSource(source)
+ .addSource(Settings.SYSTEM_ENV_SOURCE)
+ .build();
+ var result = settings.get(TEST_PROP);
+ assertTrue(result.isPresent());
+ assertEquals("system-value", result.get());
+ }
+
+
//====================================================================================================
+ // FunctionalStore
+
//====================================================================================================
+ @Test
+ void q01_writeableFunctionalSource_basic() {
+ // Create a writable functional source using system properties
+ var source = FunctionalStore.of(
+ System::getProperty,
+ (k, v) -> System.setProperty(k, v),
+ k -> System.clearProperty(k),
+ () -> { /* No-op clear for system properties */ }
+ );
+
+ // Test set and get
+ source.set(TEST_PROP, "test-value");
+ var result = source.get(TEST_PROP);
+ assertNotNull(result);
+ assertTrue(result.isPresent());
+ assertEquals("test-value", result.get());
+
+ // Test unset
+ source.unset(TEST_PROP);
+ result = source.get(TEST_PROP);
+ assertNull(result); // Should return null when property doesn't
exist
+
+ // Clean up
+ System.clearProperty(TEST_PROP);
+ }
+
+ @Test
+ void q02_writeableFunctionalSource_withSettings() {
+ // Create a writable functional source and add it to Settings
+ var source = FunctionalStore.of(
+ System::getProperty,
+ (k, v) -> System.setProperty(k, v),
+ k -> System.clearProperty(k),
+ () -> { /* No-op clear for system properties */ }
+ );
+
+ var settings = Settings.create()
+ .addSource(source)
+ .addSource(Settings.SYSTEM_PROPERTY_SOURCE)
+ .addSource(Settings.SYSTEM_ENV_SOURCE)
+ .build();
+
+ // Set a value through the source
+ source.set(TEST_PROP, "source-value");
+
+ // Get it through Settings
+ var result = settings.get(TEST_PROP);
+ assertTrue(result.isPresent());
+ assertEquals("source-value", result.get());
+
+ // Clean up
+ source.unset(TEST_PROP);
+ System.clearProperty(TEST_PROP);
+ }
+
+ @Test
+ void q03_writeableFunctionalSource_clear() {
+ // Test clear() functionality with a custom clearer
+ var map = new java.util.HashMap<String, String>();
+ var source = FunctionalStore.of(
+ map::get,
+ map::put,
+ map::remove,
+ map::clear
+ );
+
+ source.set(TEST_PROP, "test-value");
+ source.set(TEST_PROP_2, "test-value-2");
+
+ // Verify values are set
+ var result1 = source.get(TEST_PROP);
+ assertNotNull(result1);
+ assertTrue(result1.isPresent());
+ assertEquals("test-value", result1.get());
+
+ var result2 = source.get(TEST_PROP_2);
+ assertNotNull(result2);
+ assertTrue(result2.isPresent());
+ assertEquals("test-value-2", result2.get());
+
+ // Clear all values
+ source.clear();
+
+ // Verify values are cleared
+ result1 = source.get(TEST_PROP);
+ assertNull(result1);
+
+ result2 = source.get(TEST_PROP_2);
+ assertNull(result2);
+ }
+
+
//====================================================================================================
+ // Builder.localStore() - Coverage for lines 205-206
+
//====================================================================================================
+ @Test
+ void r01_localStore() {
+ // Test that localStore() method can be called on the builder
+ var settings = Settings.create()
+ .localStore(OptionalSupplier.of(() -> new MapStore()))
+ .build();
+ // Verify it works by setting a local value
+ settings.setLocal(TEST_PROP, "test-value");
+ var result = settings.get(TEST_PROP);
+ assertTrue(result.isPresent());
+ assertEquals("test-value", result.get());
+ }
+
+
//====================================================================================================
+ // Global store disabled (null supplier) - Coverage for lines 476, 493,
571
+
//====================================================================================================
+ @Test
+ void s01_setGlobal_whenGlobalStoreDisabled() {
+ // Create Settings with null global store (disabled)
+ var settings = Settings.create()
+ .globalStore(OptionalSupplier.empty())
+ .addSource(Settings.SYSTEM_PROPERTY_SOURCE)
+ .addSource(Settings.SYSTEM_ENV_SOURCE)
+ .build();
+
+ // setGlobal() should throw IllegalStateException
+ assertThrows(IllegalStateException.class, () -> {
+ settings.setGlobal(TEST_PROP, "value");
+ });
+ }
+
+ @Test
+ void s02_unsetGlobal_whenGlobalStoreDisabled() {
+ // Create Settings with null global store (disabled)
+ var settings = Settings.create()
+ .globalStore(OptionalSupplier.empty())
+ .addSource(Settings.SYSTEM_PROPERTY_SOURCE)
+ .addSource(Settings.SYSTEM_ENV_SOURCE)
+ .build();
+
+ // unsetGlobal() should throw IllegalStateException
+ assertThrows(IllegalStateException.class, () -> {
+ settings.unsetGlobal(TEST_PROP);
+ });
+ }
+
+ @Test
+ void s03_clearGlobal_whenGlobalStoreDisabled() {
+ // Create Settings with null global store (disabled)
+ var settings = Settings.create()
+ .globalStore(OptionalSupplier.empty())
+ .addSource(Settings.SYSTEM_PROPERTY_SOURCE)
+ .addSource(Settings.SYSTEM_ENV_SOURCE)
+ .build();
+
+ // clearGlobal() should throw IllegalStateException
+ assertThrows(IllegalStateException.class, () -> {
+ settings.clearGlobal();
+ });
+ }
+
+
//====================================================================================================
+ // MapStore.unset() - Coverage for line 134 (when map is null)
+
//====================================================================================================
+ @Test
+ void t01_mapStore_unset_whenMapNotInitialized() {
+ // Create a fresh MapStore that has never had set() called on it
+ // This means the internal map is still null (lazy
initialization)
+ var store = new MapStore();
+
+ // Calling unset() when map is null should not throw and should
do nothing
+ // This covers the branch where m == null in line 134
+ store.unset(TEST_PROP);
+
+ // Verify the map is still null (not initialized)
+ // get() should return null since the map doesn't exist
+ var result = store.get(TEST_PROP);
+ assertNull(result);
+ }
}