001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.commons.settings;
018
019import static org.apache.juneau.commons.function.Suppliers.*;
020import static org.apache.juneau.commons.reflect.ReflectionUtils.*;
021import static org.apache.juneau.commons.utils.Shorts.*;
022
023import java.nio.charset.*;
024import java.util.*;
025import java.util.concurrent.*;
026import java.util.function.*;
027
028import org.apache.juneau.commons.function.*;
029import org.apache.juneau.commons.reflect.*;
030
031/**
032 * Encapsulates Java system properties with support for global and per-thread overrides for unit testing.
033 *
034 * <p>
035 * This class provides a thread-safe way to access system properties that can be overridden at both the global level
036 * and per-thread level, making it useful for unit tests that need to temporarily change system property
037 * values without affecting other tests or threads.
038 *
039 * <p>
040 * Settings instances are created using the {@link Builder} pattern. Use {@link #create()} to create a new builder,
041 * or {@link #get()} to get the singleton instance (which is created using default builder settings).
042 *
043 * <h5 class='section'>Lookup Order:</h5>
044 * <p>
045 * When retrieving a property value, the lookup order is:
046 * <ol>
047 *    <li>Per-thread store (if set via {@link #setLocal(String, String)})
048 *    <li>Global store (if set via {@link #setGlobal(String, String)})
049 *    <li>Sources added at runtime through {@link #addSource(PropertySource)}, newest first
050 *    <li>The system-default <c>Config</c>, when juneau-config is on the classpath (SPI order 100)
051 *    <li>Command-line args (SPI order 50)
052 *    <li>Java system properties (SPI order 40)
053 *    <li>Environment variables, verbatim (SPI order 30)
054 *    <li>The <c>.env</c> file (SPI order 20)
055 *    <li>The jar manifest (SPI order 10)
056 *    <li>Environment variables, relaxed spelling (registered first, so checked last)
057 * </ol>
058 *
059 * <p>
060 * If SPI registered no system-property source (a shaded jar that does not merge <c>META-INF/services</c>, for example),
061 * a fallback system-property source sits just above the relaxed environment source.
062 *
063 * <h5 class='section'>Sources vs Stores:</h5>
064 * <ul class='spaced-list'>
065 *    <li><b>Sources</b> ({@link PropertySource}) - Provide read-only access to property values. Examples: {@link FunctionalPropertySource}
066 *    <li><b>Stores</b> ({@link PropertyStore}) - Provide read/write access to property values. Examples: {@link MapStore}, {@link FunctionalPropertyStore}
067 *    <li>Stores can be used as sources (they extend {@link PropertySource}), so you can add stores via {@link Builder#addSource(PropertySource)}
068 * </ul>
069 *
070 * <h5 class='section'>Features:</h5>
071 * <ul class='spaced-list'>
072 *    <li>System property access - read Java system properties with type conversion via {@link StringSetting}
073 *    <li>Global overrides - override system properties globally for all threads (stored in a {@link PropertyStore})
074 *    <li>Per-thread overrides - override system properties for specific threads (stored in a per-thread {@link PropertyStore})
075 *    <li>Custom sources - add arbitrary property sources (e.g., Spring properties, environment variables, config files) via the {@link Builder}
076 *    <li>Disable override support - system property to prevent new global overrides from being set
077 *    <li>Type-safe accessors - type conversion methods on {@link StringSetting} for common types: Integer, Long, Boolean, Double, Float, File, Path, URI, Charset
078 *    <li>Resettable suppliers - settings are returned as {@link StringSetting} instances that can be reset to force recomputation
079 * </ul>
080 *
081 * <h5 class='section'>Usage Examples:</h5>
082 * <p class='bjava'>
083 *    <jc>// Get a system property as a StringSetting (using singleton instance)</jc>
084 *    StringSetting <jv>setting</jv> = Settings.<jsf>get</jsf>().get(<js>"my.property"</js>);
085 *    String <jv>value</jv> = <jv>setting</jv>.get();  <jc>// Get the string value</jc>
086 *
087 *    <jc>// Get with type conversion using StringSetting methods</jc>
088 *    Setting&lt;Integer&gt; <jv>intSetting</jv> = Settings.<jsf>get</jsf>().get(<js>"my.int.property"</js>).asInteger();
089 *    Setting&lt;Long&gt; <jv>longSetting</jv> = Settings.<jsf>get</jsf>().get(<js>"my.long.property"</js>).asLong();
090 *    Setting&lt;Boolean&gt; <jv>boolSetting</jv> = Settings.<jsf>get</jsf>().get(<js>"my.bool.property"</js>).asBoolean();
091 *    Setting&lt;Double&gt; <jv>doubleSetting</jv> = Settings.<jsf>get</jsf>().get(<js>"my.double.property"</js>).asDouble();
092 *    Setting&lt;Float&gt; <jv>floatSetting</jv> = Settings.<jsf>get</jsf>().get(<js>"my.float.property"</js>).asFloat();
093 *    Setting&lt;File&gt; <jv>fileSetting</jv> = Settings.<jsf>get</jsf>().get(<js>"my.file.property"</js>).asFile();
094 *    Setting&lt;Path&gt; <jv>pathSetting</jv> = Settings.<jsf>get</jsf>().get(<js>"my.path.property"</js>).asPath();
095 *    Setting&lt;URI&gt; <jv>uriSetting</jv> = Settings.<jsf>get</jsf>().get(<js>"my.uri.property"</js>).asURI();
096 *    Setting&lt;Charset&gt; <jv>charsetSetting</jv> = Settings.<jsf>get</jsf>().get(<js>"my.charset.property"</js>).asCharset();
097 *
098 *    <jc>// Use custom type conversion</jc>
099 *    Setting&lt;MyCustomType&gt; <jv>customSetting</jv> = Settings.<jsf>get</jsf>().get(<js>"my.custom.property"</js>).asType(MyCustomType.<jk>class</jk>);
100 *
101 *    <jc>// Reset a setting to force recomputation</jc>
102 *    <jv>setting</jv>.reset();
103 *
104 *    <jc>// Override for current thread (useful in unit tests)</jc>
105 *    Settings.<jsf>get</jsf>().setLocal(<js>"my.property"</js>, <js>"test-value"</js>);
106 *    <jc>// ... test code that uses the override ...</jc>
107 *    Settings.<jsf>get</jsf>().unsetLocal(<js>"my.property"</js>);  <jc>// Remove specific override</jc>
108 *    <jc>// OR</jc>
109 *    Settings.<jsf>get</jsf>().clearLocal();  <jc>// Clear all thread-local overrides</jc>
110 *
111 *    <jc>// Set global override (applies to all threads)</jc>
112 *    Settings.<jsf>get</jsf>().setGlobal(<js>"my.property"</js>, <js>"global-value"</js>);
113 *    <jc>// ... code that uses the global override ...</jc>
114 *    Settings.<jsf>get</jsf>().unsetGlobal(<js>"my.property"</js>);  <jc>// Remove specific override</jc>
115 *    <jc>// OR</jc>
116 *    Settings.<jsf>get</jsf>().clearGlobal();  <jc>// Clear all global overrides</jc>
117 *
118 *    <jc>// Create a custom Settings instance with custom sources (e.g., Spring properties)</jc>
119 *    MapStore <jv>springSource</jv> = <jk>new</jk> MapStore();
120 *    <jv>springSource</jv>.set(<js>"spring.datasource.url"</js>, <js>"jdbc:postgresql://localhost/db"</js>);
121 *    Settings <jv>custom</jv> = Settings.<jsf>create</jsf>()
122 *       .addSource(<jv>springSource</jv>)
123 *       .addSource(FunctionalPropertySource.<jsf>of</jsf>(System::getProperty))
124 *       .build();
125 * </p>
126 *
127 * <h5 class='section'>System Properties:</h5>
128 * <ul class='spaced-list'>
129 *    <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
130 *       from being set via {@link #setGlobal(String, String)}. Existing global overrides will still be
131 *       returned by {@link #get(String)} until explicitly removed.
132 *       <p class='bnote'>
133 *          Note: This property is read once at class initialization time when creating the singleton instance
134 *          and cannot be changed at runtime. Changing the system property after the class has been loaded will
135 *          have no effect on the singleton instance. However, you can create custom Settings instances using
136 *          {@link #create()} that ignore this property.
137 *       </p>
138 * </ul>
139 */
140@SuppressWarnings({
141   "java:S115", // Constants use UPPER_snakeCase convention (e.g., MSG_globalDisabled)
142   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
143   "unchecked" // Type erasure requires unchecked cast
144})
145public class Settings {
146
147   /**
148    * System property source that delegates to {@link System#getProperty(String)}.
149    */
150   public static final PropertySource SYSTEM_PROPERTY_SOURCE = FunctionalPropertySource.of(System::getProperty);
151
152   private static final Set<String> FROM_STRING_METHOD_NAMES = new LinkedHashSet<>(Arrays.asList("fromString", "parse", "forName", "valueOf"));
153
154   /**
155    * System environment variable source that delegates to {@link System#getenv(String)}.
156    */
157   public static final PropertySource SYSTEM_ENV_SOURCE = FunctionalPropertySource.of(System::getenv);
158
159   /**
160    * {@link #SYSTEM_ENV_SOURCE} wrapped with {@link RelaxedPropertySource relaxed binding}, so a single logical key
161    * resolves from any of the common environment-variable spellings (e.g. {@code my.prop} / {@code myProp} /
162    * {@code MySection/myKey} all satisfied by {@code MY_PROP} / {@code MY_SECTION_MY_KEY}).
163    *
164    * <p>
165    * This is the env source used by the default {@link #get() singleton}.  It is verbatim-first, so exact-match
166    * behavior is unchanged &mdash; a relaxed variant is only probed when the exact name misses.
167    *
168    * <p>
169    * Its runtime class is a {@link CachingPropertySource} wrapping the relaxed source.  The environment is immutable
170    * inside the JVM, so caching is safe, and every chain that uses this constant shares one cache.
171    *
172    * @since 10.0.0
173    */
174   public static final PropertySource RELAXED_SYSTEM_ENV_SOURCE = CachingPropertySource.of(new RelaxedPropertySource(SYSTEM_ENV_SOURCE));
175
176   private static final String DISABLE_GLOBAL_PROP = "juneau.settings.disableGlobal";
177   private static final String MSG_globalDisabled = "Global settings not enabled";
178   private static final String MSG_localDisabled = "Local settings not enabled";
179
180   /**
181    * Returns properties for this Settings object itself.
182    * Note that these are initialized at startup and not changeable through System.setProperty().
183    */
184   private static final Optional<String> initProperty(String property) {
185      var v = SYSTEM_PROPERTY_SOURCE.get(property);
186      if (v.isPresent())
187         return v.value();  // Not testable
188      v = SYSTEM_ENV_SOURCE.get(ucr(property.replace('.', '_')));
189      if (v.isPresent())
190         return v.value();  // Not testable
191      return oe();
192   }
193
194   /**
195    * Creates a new builder for constructing a Settings instance.
196    *
197    * <p>
198    * This method provides a convenient way to create custom Settings instances with specific
199    * configuration. The builder allows you to configure stores, sources, and other settings
200    * before building the final Settings instance.
201    *
202    * <h5 class='section'>Example:</h5>
203    * <p class='bjava'>
204    *    <jc>// Create a custom Settings instance</jc>
205    *    Settings <jv>custom</jv> = Settings.<jsf>create</jsf>()
206    *       .globalStore(() -&gt; <jk>new</jk> MapStore())
207    *       .localStore(() -&gt; <jk>new</jk> MapStore())
208    *       .addSource(FunctionalPropertySource.<jsf>of</jsf>(System::getProperty))
209    *       .build();
210    * </p>
211    *
212    * @return A new Builder instance.
213    */
214   public static Builder create() {
215      return new Builder();
216   }
217
218   /**
219    * Builder for creating Settings instances.
220    */
221   public static class Builder {
222      private Supplier<PropertyStore> globalStoreSupplier = MapStore::new;
223      private Supplier<PropertyStore> localStoreSupplier = MapStore::new;
224      private final List<PropertySource> sources = new ArrayList<>();
225      private final Map<Class<?>,Function<String,?>> customTypeFunctions = new IdentityHashMap<>();
226
227      /**
228       * Sets the supplier for the global store.
229       *
230       * @param supplier The supplier for the global store.  Must not be <c>null</c>.  Can supply null to disable global store.
231       * @return This builder for method chaining.
232       */
233      public Builder globalStore(NullableSupplier<PropertyStore> supplier) {
234         this.globalStoreSupplier = reqnn("supplier", supplier);
235         return this;
236      }
237
238      /**
239       * Sets the supplier for the local (per-thread) store.
240       *
241       * @param supplier The supplier for the local store. Must not be <c>null</c>.
242       * @return This builder for method chaining.
243       */
244      public Builder localStore(NullableSupplier<PropertyStore> supplier) {
245         this.localStoreSupplier = reqnn("supplier", supplier);
246         return this;
247      }
248
249      /**
250       * Sets the sources list, replacing any existing sources.
251       *
252       * @param sources The sources to set. Must not be <c>null</c> or contain <c>null</c> elements.
253       * @return This builder for method chaining.
254       */
255      @SafeVarargs
256      public final Builder setSources(PropertySource...sources) {
257         reqnns("sources", sources);
258         this.sources.clear();
259         for (var source : sources) {
260            this.sources.add(source);
261         }
262         return this;
263      }
264
265      /**
266       * Adds a source to the sources list.
267       *
268       * @param source The source to add. Must not be <c>null</c>.
269       * @return This builder for method chaining.
270       */
271      public Builder addSource(PropertySource source) {
272         reqnn("source", source);
273         this.sources.add(source);
274         return this;
275      }
276
277      /**
278       * Adds a functional source to the sources list.
279       *
280       * @param source The functional source to add. Must not be <c>null</c>.
281       * @return This builder for method chaining.
282       */
283      public Builder addSource(FunctionalPropertySource source) {
284         return addSource((PropertySource)source);
285      }
286
287      /**
288       * Discovers and registers {@link PropertySourceProvider} instances via {@link ServiceLoader}.
289       *
290       * <p>
291       * Providers are sorted by {@link PropertySourceProvider#order()} ascending.
292       *
293       * @return This builder for method chaining.
294       */
295      public Builder useServiceLoader() {
296         return addProviders(ServiceLoader.load(PropertySourceProvider.class).stream().map(ServiceLoader.Provider::get).toList());
297      }
298
299      /** Sorts the providers by {@link PropertySourceProvider#order()} ascending, creates their sources and appends the non-null ones. */
300      Builder addProviders(Collection<? extends PropertySourceProvider> providers) {
301         providers.stream()
302            .sorted(Comparator.comparingInt(PropertySourceProvider::order))
303            .map(PropertySourceProvider::create)
304            .filter(Objects::nonNull)
305            .forEach(this::addSource);
306         return this;
307      }
308
309      /** Inserts a source at the given index. */
310      Builder insertSource(int index, PropertySource source) {
311         reqnn("source", source);
312         this.sources.add(index, source);
313         return this;
314      }
315
316      /** An unmodifiable view of the sources registered so far. */
317      List<PropertySource> sourceList() {
318         return Collections.unmodifiableList(sources);
319      }
320
321      /**
322       * Registers a custom type conversion function for the specified type.
323       *
324       * <p>
325       * This allows you to add support for converting string values to custom types when using
326       * {@link Settings#get(String, Object)}. The function will be used to convert string values
327       * to the specified type.
328       *
329       * <h5 class='section'>Example:</h5>
330       * <p class='bjava'>
331       *    <jc>// Register a custom converter for a custom type</jc>
332       *    Settings <jv>custom</jv> = Settings.<jsf>create</jsf>()
333       *       .addTypeFunction(Integer.<jk>class</jk>, Integer::valueOf)
334       *       .addTypeFunction(MyCustomType.<jk>class</jk>, MyCustomType::fromString)
335       *       .build();
336       *
337       *    <jc>// Now you can use get() with these types</jc>
338       *    Integer <jv>intValue</jv> = <jv>custom</jv>.get(<js>"my.int.property"</js>, 0);
339       *    MyCustomType <jv>customValue</jv> = <jv>custom</jv>.get(<js>"my.custom.property"</js>, MyCustomType.DEFAULT);
340       * </p>
341       *
342       * @param <T> The type to register a converter for.
343       * @param type The class type to register a converter for. Must not be <c>null</c>.
344       * @param function The function that converts a string to the specified type. Must not be <c>null</c>.
345       * @return This builder for method chaining.
346       */
347      public <T> Builder addTypeFunction(Class<T> type, Function<String,T> function) {
348         reqnn("type", type);
349         reqnn("function", function);
350         customTypeFunctions.put(type, function);
351         return this;
352      }
353
354      /**
355       * Builds a Settings instance from this builder.
356       *
357       * @return A new Settings instance.
358       */
359      public Settings build() {
360         return new Settings(this);
361      }
362   }
363
364   /**
365    * Builds the default chain: the relaxed env source, then the SPI providers sorted by order, then (only when no SPI
366    * provider registered a system-property source) {@link #SYSTEM_PROPERTY_SOURCE} at index 1.
367    *
368    * <p>
369    * Detection may fail safe (keep a duplicate) but must never fail unsafe (drop the only system-property source).
370    */
371   static Builder defaultBuilder(Collection<PropertySourceProvider> providers) {
372      var b = new Builder().setSources(RELAXED_SYSTEM_ENV_SOURCE).addProviders(providers);
373      if (b.sourceList().stream().noneMatch(Settings::isSpiSystemPropertySource))
374         b.insertSource(1, SYSTEM_PROPERTY_SOURCE);  // no SPI sysprop source: keep today's fallback at today's index
375      return b;
376   }
377
378   private static boolean isSpiSystemPropertySource(PropertySource s) {
379      return s.getClass().getName().equals(SystemPropertyPropertySource.class.getName());  // exact class name, any loader
380   }
381
382   private static final Settings INSTANCE = defaultBuilder(ServiceLoader.load(PropertySourceProvider.class).stream().map(ServiceLoader.Provider::get).toList())
383      .globalStore(initProperty(DISABLE_GLOBAL_PROP).map(Boolean::valueOf).orElse(false) ? () -> null : MapStore::new)
384      .build();
385
386   /**
387    * Returns the singleton instance of Settings.
388    *
389    * @return The singleton Settings instance.
390    */
391   public static Settings get() {
392      return INSTANCE;
393   }
394
395   private final Memoizer<PropertyStore> globalStore;
396   @SuppressWarnings({
397      "java:S5164" // Cleanup method provided: cleanup()
398   })
399   private final ThreadLocal<PropertyStore> localStore;
400   private final List<PropertySource> sources;
401   private final Map<Class<?>,Function<String,?>> toTypeFunctions;
402
403   /**
404    * Constructor.
405    */
406   private Settings(Builder builder) {
407      this.globalStore = memoizer(builder.globalStoreSupplier);
408      this.localStore = ThreadLocal.withInitial(builder.localStoreSupplier);
409      this.sources = new CopyOnWriteArrayList<>(builder.sources);
410      this.toTypeFunctions = new ConcurrentHashMap<>(builder.customTypeFunctions);
411   }
412
413   /**
414    * Adds a property source after this settings instance has been built.
415    *
416    * @param source The property source to add. Must not be <jk>null</jk>.
417    * @return This object for method chaining.
418    */
419   public Settings addSource(PropertySource source) {
420      reqnn("source", source);
421      sources.add(source);
422      return this;
423   }
424
425   /**
426    * Returns the number of {@link PropertySource}s currently registered with this
427    * {@code Settings} instance.
428    *
429    * <p>
430    * Intended primarily for tests that need to verify nothing has leaked extra sources into the
431    * process-wide {@link #get() Settings.get()} singleton &mdash; the historical failure mode
432    * documented where a per-{@code RestContext}
433    * config bridge added a {@code ConfigPropertySource} per cached {@code RestContext} via
434    * {@link #addSource(PropertySource)} and never removed it.
435    *
436    * @return The current source count. Always {@code &gt;= 0}.
437    */
438   public int sourceCount() {
439      return sources.size();
440   }
441
442   /**
443    * Returns a live, read-only view of the registered sources, in registration order.  Package-private, for tests.
444    *
445    * @return An unmodifiable view; later {@link #addSource} / {@link #removeSource} calls show through.
446    */
447   List<PropertySource> sources() {
448      return Collections.unmodifiableList(sources);
449   }
450
451   /**
452    * Removes a previously-added property source.
453    *
454    * <p>
455    * The match is by identity ({@code ==}), not equality.  Callers that mutate the singleton
456    * {@link #get() Settings.get()} from a test should capture the exact reference returned to/passed
457    * into {@link #addSource(PropertySource)} and pass it back here in their teardown to avoid
458    * cross-test bleed.
459    *
460    * @param source The property source to remove. Must not be <jk>null</jk>.
461    * @return <jk>true</jk> if the source was present and removed, <jk>false</jk> otherwise.
462    */
463   public boolean removeSource(PropertySource source) {
464      reqnn("source", source);
465      // Identity-based removal so that PropertySource impls without a sensible equals() still
466      // round-trip cleanly through add/remove pairs.
467      // Removals are serialized so the index found in the snapshot is still valid when removed; addSource only appends.
468      synchronized (sources) {
469         var snapshot = sources.toArray(new PropertySource[0]);
470         for (var i = 0; i < snapshot.length; i++) {
471            if (snapshot[i] == source) {
472               sources.remove(i);
473               return true;
474            }
475         }
476      }
477      return false;
478   }
479
480   /**
481    * Returns a {@link StringSetting} for the specified system property.
482    *
483    * <p>
484    * The returned {@link StringSetting} is a resettable supplier that caches the lookup result.
485    * Use the {@link StringSetting#asInteger()}, {@link StringSetting#asBoolean()}, etc. methods
486    * to convert to different types.
487    *
488    * <p>
489    * The lookup order is:
490    * <ol>
491    *    <li>Per-thread override (if set via {@link #setLocal(String, String)})
492    *    <li>Global override (if set via {@link #setGlobal(String, String)})
493    *    <li>Sources added at runtime through {@link #addSource(PropertySource)}, newest first
494    *    <li>The system-default <c>Config</c>, when juneau-config is on the classpath (SPI order 100)
495    *    <li>Command-line args (SPI order 50)
496    *    <li>Java system properties (SPI order 40)
497    *    <li>Environment variables, verbatim (SPI order 30)
498    *    <li>The <c>.env</c> file (SPI order 20)
499    *    <li>The jar manifest (SPI order 10)
500    *    <li>Environment variables, relaxed spelling (registered first, so checked last)
501    * </ol>
502    *
503    * <p>
504    * If SPI registered no system-property source (a shaded jar that does not merge <c>META-INF/services</c>, for example),
505    * a fallback system-property source sits just above the relaxed environment source.
506    *
507    * @param name The property name. Must not be <jk>null</jk>.
508    * @return A {@link StringSetting} that provides the resolved property value.
509    */
510   public StringSetting get(String name) {
511      reqnn("name", name);
512      return new StringSetting(this, () -> resolve(name));
513   }
514
515   /**
516    * Walks the thread-local store, the global store, then every source newest first. The one copy of the lookup order.
517    *
518    * @return The resolved value, or <jk>null</jk> when no store or source defines the name or the winner is an explicit null.
519    */
520   private String resolve(String name) {
521      // 1. Check thread-local override
522      var v = localStore.get().get(name);
523      if (v.isPresent())
524         return v.value().orElse(null); // Present result: Optional.empty() means explicit null override.
525
526      // 2. Check global override
527      v = globalLookup(name);
528      if (v.isPresent())
529         return v.value().orElse(null); // Present result: Optional.empty() means explicit null override.
530
531      // 3. Check sources in reverse order (last added first)
532      // Iterate one snapshot so a concurrent removeSource() can't shrink the list between size() and get(i).
533      var snapshot = sources.toArray(new PropertySource[0]);
534      for (int i = snapshot.length - 1; i >= 0; i--) {
535         var source = snapshot[i];
536         var result = source.get(name);
537         if (result.isPresent())
538            return result.value().orElse(null);
539      }
540
541      return null;
542   }
543
544   /**
545    * Returns the override value for the specified property, consulting only the per-thread
546    * (local) and global stores &mdash; <i>not</i> the sources list.
547    *
548    * <p>
549    * Intended for callers (notably the {@code @Value} resolution path) that interleave a
550    * caller-scoped property source between the Settings override stores and the sources list.
551    * Using this method, the caller can honor the "{@link #setLocal(String, String) local}
552    * / {@link #setGlobal(String, String) global} override wins" contract before consulting
553    * its own scoped sources, and still fall through to {@link #get(String)} (which checks
554    * the sources list) afterwards without the override stores accidentally being consulted twice.
555    *
556    * <p>
557    * Lookup order honored by this method:
558    * <ol>
559    *    <li>Per-thread (local) store
560    *    <li>Global store
561    * </ol>
562    *
563    * <p>
564    * If a store is present with an empty value (i.e. an explicit null override), the returned
565    * {@link Optional} is {@code Optional.empty()} but {@link #isOverridden(String)} would
566    * return {@code true}. Callers needing to distinguish "absent" from "explicit null" should
567    * use {@link #isOverridden(String)} alongside this method.
568    *
569    * @param name The property name. Must not be <jk>null</jk>.
570    * @return The override value, or {@link Optional#empty()} if neither store carries an override.
571    * @see #isOverridden(String)
572    * @see #get(String)
573    */
574   public Optional<String> getOverride(String name) {
575      reqnn("name", name);
576      var v = localStore.get().get(name);
577      if (v.isPresent())
578         return v.value();
579      v = globalLookup(name);
580      if (v.isPresent())
581         return v.value();
582      return oe();
583   }
584
585   /**
586    * Returns <jk>true</jk> if the specified property has an override in either the per-thread
587    * (local) or global store.
588    *
589    * <p>
590    * Distinguishes the "explicit null override" case &mdash; where {@link #getOverride(String)}
591    * returns {@link Optional#empty()} because the override value itself was {@code null} &mdash;
592    * from the "no override" case.
593    *
594    * @param name The property name. Must not be <jk>null</jk>.
595    * @return <jk>true</jk> if either store carries an override (even with a {@code null} value).
596    */
597   public boolean isOverridden(String name) {
598      reqnn("name", name);
599      return localStore.get().get(name).isPresent() || globalLookup(name).isPresent();
600   }
601
602   /** Looks up an override in the global store, which is absent (null) when global overrides are disabled. */
603   private PropertyLookupResult globalLookup(String name) {
604      var store = globalStore.get();
605      return n(store) ? PropertyLookupResult.missing() : store.get(name);
606   }
607
608   /**
609    * Looks up a system property, returning a default value if not found.
610    *
611    * <p>
612    * This method searches for a value using the same lookup order as {@link #get(String)}.
613    * If a value is found, it is converted to the type of the default value using {@link #toType(String, Class)}.
614    * Supported types include any type that has a static method with signature <c>public static &lt;T&gt; T anyName(String arg)</c>
615    * or a public constructor with signature <c>public T(String arg)</c>, such as {@link Boolean}, {@link Integer}, {@link Charset}, {@link java.io.File}, etc.
616    *
617    * <h5 class='section'>Example:</h5>
618    * <p class='bjava'>
619    *    <jc>// System property: -Dmy.property=true</jc>
620    *    Boolean <jv>flag</jv> = get(<js>"my.property"</js>, <jk>false</jk>);  <jc>// true</jc>
621    *
622    *    <jc>// Environment variable: MY_PROPERTY=UTF-8</jc>
623    *    Charset <jv>charset</jv> = get(<js>"my.property"</js>, Charset.defaultCharset());  <jc>// UTF-8</jc>
624    *
625    *    <jc>// Not found, returns default</jc>
626    *    String <jv>value</jv> = get(<js>"nonexistent"</js>, <js>"default"</js>);  <jc>// "default"</jc>
627    * </p>
628    *
629    * @param <T> The type to convert the value to.
630    * @param name The property name. Must not be <jk>null</jk>.
631    * @param def The default value to return if not found. Must not be <jk>null</jk>.
632    * @return The found value (converted to type T), or the default value if not found.
633    * @see #get(String)
634    * @see #toType(String, Class)
635    */
636   public <T> T get(String name, T def) {
637      reqnn("def", def);
638      reqnn("name", name);
639      var v = resolve(name);
640      if (v == null)
641         return def;
642      var t = toType(v, (Class<T>)def.getClass());
643      return t == null ? def : t;  // matches the filter(Objects::nonNull) in StringSetting.asType
644   }
645
646   /**
647    * Sets a global override for the specified property.
648    *
649    * <p>
650    * This override will apply to all threads and takes precedence over system properties.
651    * However, per-thread overrides (set via {@link #setLocal(String, String)}) will still take precedence
652    * over global overrides.
653    *
654    * <p>
655    * If the <c>juneau.settings.disableGlobal</c> system property is set to <c>true</c>,
656    * this method will throw an exception. This allows system administrators
657    * to prevent applications from overriding system properties globally.
658    *
659    * <p>
660    * Setting a value to <c>null</c> will store an empty optional, effectively overriding the system
661    * property to return empty. Use {@link #unsetGlobal(String)} to completely remove the override.
662    *
663    * @param name The property name. Must not be <jk>null</jk>.
664    * @param value The override value, or <c>null</c> to set an empty override.
665    * @return This object for method chaining.
666    * @see #unsetGlobal(String)
667    * @see #clearGlobal()
668    */
669   public Settings setGlobal(String name, String value) {
670      reqnn("name", name);
671      globalStore.orElseThrow(()->isex(MSG_globalDisabled)).set(name, value);
672      return this;
673   }
674
675   /**
676    * Removes a global override for the specified property.
677    *
678    * <p>
679    * After calling this method, the property will fall back to the system property value
680    * (or per-thread override if one exists).
681    *
682    * @param name The property name. Must not be <jk>null</jk>.
683    * @see #setGlobal(String, String)
684    * @see #clearGlobal()
685    */
686   public void unsetGlobal(String name) {
687      reqnn("name", name);
688      globalStore.orElseThrow(()->isex(MSG_globalDisabled)).unset(name);
689   }
690
691   /**
692    * Sets a per-thread override for the specified property.
693    *
694    * <p>
695    * This override will only apply to the current thread and takes precedence over global overrides
696    * and system properties. This is particularly useful in unit tests where you need to temporarily
697    * change a system property value without affecting other threads or tests.
698    *
699    * <p>
700    * Setting a value to <c>null</c> will store an empty optional, effectively overriding the property
701    * to return empty for this thread. Use {@link #unsetLocal(String)} to completely remove the override.
702    *
703    * @param name The property name. Must not be <jk>null</jk>.
704    * @param value The override value, or <c>null</c> to set an empty override.
705    * @return This object for method chaining.
706    * @see #unsetLocal(String)
707    * @see #clearLocal()
708    */
709   public Settings setLocal(String name, String value) {
710      reqnn("name", name);
711      chk(nn(localStore.get()), MSG_localDisabled);
712      localStore.get().set(name, value);
713      return this;
714   }
715
716   /**
717    * Removes a per-thread override for the specified property.
718    *
719    * <p>
720    * After calling this method, the property will fall back to the global override (if set)
721    * or the system property value for the current thread.
722    *
723    * @param name The property name. Must not be <jk>null</jk>.
724    * @see #setLocal(String, String)
725    * @see #clearLocal()
726    */
727   public void unsetLocal(String name) {
728      reqnn("name", name);
729      chk(nn(localStore.get()), MSG_localDisabled);
730      localStore.get().unset(name);
731   }
732
733   /**
734    * Clears all per-thread overrides for the current thread.
735    *
736    * <p>
737    * After calling this method, all properties will fall back to global overrides (if set)
738    * or system property values for the current thread.
739    *
740    * <p>
741    * This is typically called in a <c>@AfterEach</c> or <c>@After</c> test method to clean up
742    * thread-local overrides after a test completes.
743    *
744    * @return This object for method chaining.
745    * @see #setLocal(String, String)
746    * @see #unsetLocal(String)
747    */
748   public Settings clearLocal() {
749      chk(nn(localStore.get()), MSG_localDisabled);
750      localStore.get().clear();
751      return this;
752   }
753
754   /**
755    * Cleans up thread-local storage for the current thread.
756    *
757    * <p>
758    * This method should be called when a thread is finished using this Settings instance to prevent memory leaks
759    * in thread pool environments where threads are reused.
760    */
761   public void cleanup() {
762      if (localStore != null) {
763         localStore.remove();
764      }
765   }
766
767   /**
768    * Clears all global overrides.
769    *
770    * <p>
771    * After calling this method, all properties will fall back to resolver values
772    * (or per-thread overrides if they exist).
773    *
774    * @return This object for method chaining.
775    * @see #setGlobal(String, String)
776    * @see #unsetGlobal(String)
777    */
778   public Settings clearGlobal() {
779      globalStore.orElseThrow(()->isex(MSG_globalDisabled)).clear();
780      return this;
781   }
782
783   /**
784    * Converts a string to the specified type using reflection to find conversion methods or constructors.
785    *
786    * <p>
787    * This method attempts to convert a string to the specified type using the following lookup order:
788    * <ol>
789    *    <li>Custom type functions registered via {@link Builder#addTypeFunction(Class, Function)}</li>
790    *    <li>Special handling for {@link String} (returns the string as-is)</li>
791    *    <li>Special handling for {@link Enum} types (uses {@link Enum#valueOf(Class, String)})</li>
792    *    <li>Reflection lookup for static methods with signature <c>public static &lt;T&gt; T anyName(String arg)</c>
793    *       on the target class</li>
794    *    <li>Reflection lookup for static methods on the superclass (for abstract classes like {@link Charset}
795    *       where concrete implementations need to use the abstract class's static method)</li>
796    *    <li>Reflection lookup for public constructors with signature <c>public T(String arg)</c></li>
797    * </ol>
798    *
799    * <p>
800    * When a conversion method or constructor is found via reflection, it is cached in the
801    * <c>toTypeFunctions</c> map for future use.
802    *
803    * <h5 class='section'>Examples:</h5>
804    * <ul class='spaced-list'>
805    *    <li><c>Boolean</c> - Uses <c>Boolean.valueOf(String)</c> static method
806    *    <li><c>Integer</c> - Uses <c>Integer.valueOf(String)</c> static method
807    *    <li><c>Charset</c> - Uses <c>Charset.forName(String)</c> static method (even for concrete implementations)
808    *    <li><c>File</c> - Uses <c>File(String)</c> constructor
809    * </ul>
810    *
811    * @param <T> The target type.
812    * @param s The string to convert. Must not be <jk>null</jk>.
813    * @param c The target class. Must not be <jk>null</jk>.
814    * @return The converted value.
815    * @throws RuntimeException If the type is not supported for conversion (no static method or constructor found).
816    */
817   @SuppressWarnings({
818      "rawtypes" // Raw types necessary for generic type handling
819   })
820   public <T> T toType(String s, Class<T> c) {
821      reqnn("s", s);
822      reqnn("c", c);
823      var f = (Function<String,T>)toTypeFunctions.get(c);
824      if (f == null) {
825         if (c == String.class)
826            return (T)s;
827         if (c.isEnum())
828            return (T)Enum.valueOf((Class<? extends Enum>)c, s);
829         ClassInfoTyped<T> ci = info(c);
830         f = ci.getDeclaredMethod(x -> x.isStatic() && x.hasParameterTypes(String.class) && x.hasReturnType(c) && FROM_STRING_METHOD_NAMES.contains(x.getName())).map(x -> (Function<String,T>)s2 -> x.invoke(null, s2)).orElse(null);
831         // Also accept `static T parse(CharSequence)` etc. — common for java.time types (Instant,
832         // LocalDate, Duration) whose static factories declare the wider CharSequence parameter.
833         if (f == null)
834            f = ci.getDeclaredMethod(x -> x.isStatic() && x.hasParameterTypes(CharSequence.class) && x.hasReturnType(c) && FROM_STRING_METHOD_NAMES.contains(x.getName())).map(x -> (Function<String,T>)s2 -> x.invoke(null, s2)).orElse(null);
835         if (f == null)
836            f = ci.getPublicConstructor(x -> x.hasParameterTypes(String.class)).map(x -> (Function<String,T>)x::newInstance).orElse(null);
837         if (f != null)
838            toTypeFunctions.putIfAbsent(c, f);
839      }
840      if (f == null)
841         throw rex("Invalid env type: %s", c);
842      return f.apply(s);
843   }
844}
845