View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.juneau.commons.settings;
18  
19  import static org.apache.juneau.commons.utils.Utils.*;
20  import static org.junit.jupiter.api.Assertions.*;
21  
22  import java.io.File;
23  import java.net.URI;
24  import java.nio.charset.Charset;
25  import java.nio.file.Paths;
26  import java.util.concurrent.atomic.AtomicInteger;
27  
28  import org.apache.juneau.*;
29  import org.apache.juneau.commons.function.*;
30  import org.junit.jupiter.api.*;
31  
32  class Settings_Test extends TestBase {
33  
34  	private static final String TEST_PROP = "juneau.test.property";
35  	private static final String TEST_PROP_2 = "juneau.test.property2";
36  
37  	@BeforeEach
38  	void setUp() {
39  		// Clean up before each test
40  		Settings.get().clearLocal();
41  		Settings.get().clearGlobal();
42  		// Remove test system properties if they exist
43  		System.clearProperty(TEST_PROP);
44  		System.clearProperty(TEST_PROP_2);
45  	}
46  
47  	@AfterEach
48  	void tearDown() {
49  		// Clean up after each test
50  		Settings.get().clearLocal();
51  		Settings.get().clearGlobal();
52  		System.clearProperty(TEST_PROP);
53  		System.clearProperty(TEST_PROP_2);
54  	}
55  
56  	//====================================================================================================
57  	// get() - Basic functionality
58  	//====================================================================================================
59  	@Test
60  	void a01_get_fromSystemProperty() {
61  		System.setProperty(TEST_PROP, "system-value");
62  		var result = Settings.get().get(TEST_PROP);
63  		assertTrue(result.isPresent());
64  		assertEquals("system-value", result.get());
65  	}
66  
67  	@Test
68  	void a02_get_notFound() {
69  		var result = Settings.get().get("nonexistent.property");
70  		assertFalse(result.isPresent());
71  	}
72  
73  	@Test
74  	void a03_get_fromGlobalOverride() {
75  		Settings.get().setGlobal(TEST_PROP, "global-value");
76  		var result = Settings.get().get(TEST_PROP);
77  		assertTrue(result.isPresent());
78  		assertEquals("global-value", result.get());
79  	}
80  
81  	@Test
82  	void a04_get_fromLocalOverride() {
83  		Settings.get().setLocal(TEST_PROP, "local-value");
84  		var result = Settings.get().get(TEST_PROP);
85  		assertTrue(result.isPresent());
86  		assertEquals("local-value", result.get());
87  	}
88  
89  	@Test
90  	void a05_get_lookupOrder_localOverridesGlobal() {
91  		System.setProperty(TEST_PROP, "system-value");
92  		Settings.get().setGlobal(TEST_PROP, "global-value");
93  		Settings.get().setLocal(TEST_PROP, "local-value");
94  		var result = Settings.get().get(TEST_PROP);
95  		assertTrue(result.isPresent());
96  		assertEquals("local-value", result.get());
97  	}
98  
99  	@Test
100 	void a06_get_lookupOrder_globalOverridesSystem() {
101 		System.setProperty(TEST_PROP, "system-value");
102 		Settings.get().setGlobal(TEST_PROP, "global-value");
103 		var result = Settings.get().get(TEST_PROP);
104 		assertTrue(result.isPresent());
105 		assertEquals("global-value", result.get());
106 	}
107 
108 	@Test
109 	void a07_get_nullValue() {
110 		Settings.get().setLocal(TEST_PROP, null);
111 		var result = Settings.get().get(TEST_PROP);
112 		assertFalse(result.isPresent());
113 	}
114 
115 	//====================================================================================================
116 	// getInteger()
117 	//====================================================================================================
118 	@Test
119 	void b01_getInteger_valid() {
120 		System.setProperty(TEST_PROP, "123");
121 		var result = Settings.get().get(TEST_PROP).asInteger();
122 		assertTrue(result.isPresent());
123 		assertEquals(123, result.get());
124 	}
125 
126 	@Test
127 	void b02_getInteger_invalid() {
128 		System.setProperty(TEST_PROP, "not-a-number");
129 		var result = Settings.get().get(TEST_PROP).asInteger();
130 		assertFalse(result.isPresent());
131 	}
132 
133 	@Test
134 	void b03_getInteger_notFound() {
135 		var result = Settings.get().get("nonexistent.property").asInteger();
136 		assertFalse(result.isPresent());
137 	}
138 
139 	@Test
140 	void b04_getInteger_fromOverride() {
141 		Settings.get().setLocal(TEST_PROP, "456");
142 		var result = Settings.get().get(TEST_PROP).asInteger();
143 		assertTrue(result.isPresent());
144 		assertEquals(456, result.get());
145 	}
146 
147 	//====================================================================================================
148 	// getLong()
149 	//====================================================================================================
150 	@Test
151 	void c01_getLong_valid() {
152 		System.setProperty(TEST_PROP, "123456789");
153 		var result = Settings.get().get(TEST_PROP).asLong();
154 		assertTrue(result.isPresent());
155 		assertEquals(123456789L, result.get());
156 	}
157 
158 	@Test
159 	void c02_getLong_invalid() {
160 		System.setProperty(TEST_PROP, "not-a-number");
161 		var result = Settings.get().get(TEST_PROP).asLong();
162 		assertFalse(result.isPresent());
163 	}
164 
165 	@Test
166 	void c03_getLong_fromOverride() {
167 		Settings.get().setLocal(TEST_PROP, "987654321");
168 		var result = Settings.get().get(TEST_PROP).asLong();
169 		assertTrue(result.isPresent());
170 		assertEquals(987654321L, result.get());
171 	}
172 
173 	//====================================================================================================
174 	// getBoolean()
175 	//====================================================================================================
176 	@Test
177 	void d01_getBoolean_true() {
178 		System.setProperty(TEST_PROP, "true");
179 		var result = Settings.get().get(TEST_PROP).asBoolean();
180 		assertTrue(result.isPresent());
181 		assertTrue(result.get());
182 	}
183 
184 	@Test
185 	void d02_getBoolean_false() {
186 		System.setProperty(TEST_PROP, "false");
187 		var result = Settings.get().get(TEST_PROP).asBoolean();
188 		assertTrue(result.isPresent());
189 		assertFalse(result.get());
190 	}
191 
192 	@Test
193 	void d03_getBoolean_caseInsensitive() {
194 		System.setProperty(TEST_PROP, "TRUE");
195 		var result = Settings.get().get(TEST_PROP).asBoolean();
196 		assertTrue(result.isPresent());
197 		assertTrue(result.get());
198 	}
199 
200 	@Test
201 	void d04_getBoolean_nonTrueValue() {
202 		System.setProperty(TEST_PROP, "anything");
203 		var result = Settings.get().get(TEST_PROP).asBoolean();
204 		assertTrue(result.isPresent());
205 		assertFalse(result.get());
206 	}
207 
208 	@Test
209 	void d05_getBoolean_notFound() {
210 		var result = Settings.get().get("nonexistent.property").asBoolean();
211 		assertFalse(result.isPresent());
212 	}
213 
214 	//====================================================================================================
215 	// getDouble()
216 	//====================================================================================================
217 	@Test
218 	void e01_getDouble_valid() {
219 		System.setProperty(TEST_PROP, "123.456");
220 		var result = Settings.get().get(TEST_PROP).asDouble();
221 		assertTrue(result.isPresent());
222 		assertEquals(123.456, result.get(), 0.0001);
223 	}
224 
225 	@Test
226 	void e02_getDouble_invalid() {
227 		System.setProperty(TEST_PROP, "not-a-number");
228 		var result = Settings.get().get(TEST_PROP).asDouble();
229 		assertFalse(result.isPresent());
230 	}
231 
232 	//====================================================================================================
233 	// getFloat()
234 	//====================================================================================================
235 	@Test
236 	void f01_getFloat_valid() {
237 		System.setProperty(TEST_PROP, "123.456");
238 		var result = Settings.get().get(TEST_PROP).asFloat();
239 		assertTrue(result.isPresent());
240 		assertEquals(123.456f, result.get(), 0.0001f);
241 	}
242 
243 	@Test
244 	void f02_getFloat_invalid() {
245 		System.setProperty(TEST_PROP, "not-a-number");
246 		var result = Settings.get().get(TEST_PROP).asFloat();
247 		assertFalse(result.isPresent());
248 	}
249 
250 	//====================================================================================================
251 	// getFile()
252 	//====================================================================================================
253 	@Test
254 	void g01_getFile_valid() {
255 		System.setProperty(TEST_PROP, "/tmp/test.txt");
256 		var result = Settings.get().get(TEST_PROP).asFile();
257 		assertTrue(result.isPresent());
258 		assertEquals(new File("/tmp/test.txt"), result.get());
259 	}
260 
261 	@Test
262 	void g02_getFile_notFound() {
263 		var result = Settings.get().get("nonexistent.property").asFile();
264 		assertFalse(result.isPresent());
265 	}
266 
267 	//====================================================================================================
268 	// getPath()
269 	//====================================================================================================
270 	@Test
271 	void h01_getPath_valid() {
272 		System.setProperty(TEST_PROP, "/tmp/test.txt");
273 		var result = Settings.get().get(TEST_PROP).asPath();
274 		assertTrue(result.isPresent());
275 		assertEquals(Paths.get("/tmp/test.txt"), result.get());
276 	}
277 
278 	@Test
279 	void h02_getPath_invalid() {
280 		// Paths.get() can throw exceptions for invalid paths on some systems
281 		// This test verifies that invalid paths return empty
282 		System.setProperty(TEST_PROP, "\0invalid");
283 		var result = Settings.get().get(TEST_PROP).asPath();
284 		// May or may not be empty depending on OS, but should not throw
285 		assertNotNull(result);
286 	}
287 
288 	//====================================================================================================
289 	// getURI()
290 	//====================================================================================================
291 	@Test
292 	void i01_getURI_valid() {
293 		System.setProperty(TEST_PROP, "http://example.com/test");
294 		var result = Settings.get().get(TEST_PROP).asURI();
295 		assertTrue(result.isPresent());
296 		assertEquals(URI.create("http://example.com/test"), result.get());
297 	}
298 
299 	@Test
300 	void i02_getURI_invalid() {
301 		System.setProperty(TEST_PROP, "not a valid uri");
302 		var result = Settings.get().get(TEST_PROP).asURI();
303 		assertFalse(result.isPresent());
304 	}
305 
306 	//====================================================================================================
307 	// getCharset()
308 	//====================================================================================================
309 	@Test
310 	void j01_getCharset_valid() {
311 		System.setProperty(TEST_PROP, "UTF-8");
312 		var result = Settings.get().get(TEST_PROP).asCharset();
313 		assertTrue(result.isPresent());
314 		assertEquals(Charset.forName("UTF-8"), result.get());
315 	}
316 
317 	@Test
318 	void j02_getCharset_invalid() {
319 		System.setProperty(TEST_PROP, "INVALID-CHARSET");
320 		var result = Settings.get().get(TEST_PROP).asCharset();
321 		assertFalse(result.isPresent());
322 	}
323 
324 	//====================================================================================================
325 	// setGlobal() / unsetGlobal() / clearGlobal()
326 	//====================================================================================================
327 	@Test
328 	void k01_setGlobal() {
329 		Settings.get().setGlobal(TEST_PROP, "global-value");
330 		var result = Settings.get().get(TEST_PROP);
331 		assertTrue(result.isPresent());
332 		assertEquals("global-value", result.get());
333 	}
334 
335 	@Test
336 	void k02_unsetGlobal() {
337 		Settings.get().setGlobal(TEST_PROP, "global-value");
338 		Settings.get().unsetGlobal(TEST_PROP);
339 		var result = Settings.get().get(TEST_PROP);
340 		assertFalse(result.isPresent());
341 	}
342 
343 	@Test
344 	void k03_clearGlobal() {
345 		Settings.get().setGlobal(TEST_PROP, "value1");
346 		Settings.get().setGlobal(TEST_PROP_2, "value2");
347 		Settings.get().clearGlobal();
348 		assertFalse(Settings.get().get(TEST_PROP).isPresent());
349 		assertFalse(Settings.get().get(TEST_PROP_2).isPresent());
350 	}
351 
352 	@Test
353 	void k04_setGlobal_nullValue() {
354 		Settings.get().setGlobal(TEST_PROP, null);
355 		var result = Settings.get().get(TEST_PROP);
356 		assertFalse(result.isPresent());
357 	}
358 
359 	//====================================================================================================
360 	// setLocal() / unsetLocal() / clearLocal()
361 	//====================================================================================================
362 	@Test
363 	void l01_setLocal() {
364 		Settings.get().setLocal(TEST_PROP, "local-value");
365 		var result = Settings.get().get(TEST_PROP);
366 		assertTrue(result.isPresent());
367 		assertEquals("local-value", result.get());
368 	}
369 
370 	@Test
371 	void l02_unsetLocal() {
372 		Settings.get().setLocal(TEST_PROP, "local-value");
373 		Settings.get().unsetLocal(TEST_PROP);
374 		var result = Settings.get().get(TEST_PROP);
375 		assertFalse(result.isPresent());
376 	}
377 
378 	@Test
379 	void l07_unsetLocal_whenNoLocalSource() {
380 		// unsetLocal should not throw when there's no local source (threadOverrides.get() returns null)
381 		Settings.get().unsetLocal(TEST_PROP);
382 		// Should not throw an exception
383 		var result = Settings.get().get(TEST_PROP);
384 		assertFalse(result.isPresent());
385 	}
386 
387 	@Test
388 	void l03_clearLocal() {
389 		Settings.get().setLocal(TEST_PROP, "value1");
390 		Settings.get().setLocal(TEST_PROP_2, "value2");
391 		Settings.get().clearLocal();
392 		assertFalse(Settings.get().get(TEST_PROP).isPresent());
393 		assertFalse(Settings.get().get(TEST_PROP_2).isPresent());
394 	}
395 
396 	@Test
397 	void l04_setLocal_nullValue() {
398 		Settings.get().setLocal(TEST_PROP, null);
399 		var result = Settings.get().get(TEST_PROP);
400 		assertFalse(result.isPresent());
401 	}
402 
403 	@Test
404 	void l05_setLocal_overridesGlobal() {
405 		Settings.get().setGlobal(TEST_PROP, "global-value");
406 		Settings.get().setLocal(TEST_PROP, "local-value");
407 		var result = Settings.get().get(TEST_PROP);
408 		assertTrue(result.isPresent());
409 		assertEquals("local-value", result.get());
410 	}
411 
412 	@Test
413 	void l06_setLocal_overridesSystemProperty() {
414 		System.setProperty(TEST_PROP, "system-value");
415 		Settings.get().setLocal(TEST_PROP, "local-value");
416 		var result = Settings.get().get(TEST_PROP);
417 		assertTrue(result.isPresent());
418 		assertEquals("local-value", result.get());
419 	}
420 
421 	//====================================================================================================
422 	// Thread isolation
423 	//====================================================================================================
424 	@Test
425 	void m01_localOverride_threadIsolation() throws InterruptedException {
426 		Settings.get().setLocal(TEST_PROP, "thread1-value");
427 
428 		var thread2 = new Thread(() -> {
429 			Settings.get().setLocal(TEST_PROP, "thread2-value");
430 			var result = Settings.get().get(TEST_PROP);
431 			assertTrue(result.isPresent());
432 			assertEquals("thread2-value", result.get());
433 		});
434 
435 		thread2.start();
436 		thread2.join();
437 
438 		// Original thread should still have its value
439 		var result = Settings.get().get(TEST_PROP);
440 		assertTrue(result.isPresent());
441 		assertEquals("thread1-value", result.get());
442 	}
443 
444 	@Test
445 	void m02_globalOverride_sharedAcrossThreads() throws InterruptedException {
446 		Settings.get().setGlobal(TEST_PROP, "global-value");
447 
448 		var thread2 = new Thread(() -> {
449 			var result = Settings.get().get(TEST_PROP);
450 			assertTrue(result.isPresent());
451 			assertEquals("global-value", result.get());
452 		});
453 
454 		thread2.start();
455 		thread2.join();
456 
457 		// Original thread should also see the global value
458 		var result = Settings.get().get(TEST_PROP);
459 		assertTrue(result.isPresent());
460 		assertEquals("global-value", result.get());
461 	}
462 
463 	//====================================================================================================
464 	// Singleton pattern
465 	//====================================================================================================
466 	@Test
467 	void n01_get_returnsSameInstance() {
468 		var instance1 = Settings.get();
469 		var instance2 = Settings.get();
470 		assertSame(instance1, instance2);
471 	}
472 
473 	//====================================================================================================
474 	// Sources
475 	//====================================================================================================
476 	@Test
477 	void o01_addSource() {
478 		var source = new MapStore();
479 		source.set(TEST_PROP, "source-value");
480 		var settings = Settings.create()
481 			.addSource(source)
482 			.addSource(Settings.SYSTEM_PROPERTY_SOURCE)
483 			.addSource(Settings.SYSTEM_ENV_SOURCE)
484 			.build();
485 
486 		var result = settings.get(TEST_PROP);
487 		assertTrue(result.isPresent());
488 		assertEquals("source-value", result.get());
489 	}
490 
491 	@Test
492 	void o02_addSource_reverseOrder() {
493 		var source1 = new MapStore();
494 		source1.set(TEST_PROP, "source1-value");
495 
496 		var source2 = new MapStore();
497 		source2.set(TEST_PROP, "source2-value");
498 
499 		var settings = Settings.create()
500 			.addSource(source1)
501 			.addSource(source2)
502 			.addSource(Settings.SYSTEM_PROPERTY_SOURCE)
503 			.addSource(Settings.SYSTEM_ENV_SOURCE)
504 			.build();
505 
506 		// Last added source should be checked first
507 		var result = settings.get(TEST_PROP);
508 		assertTrue(result.isPresent());
509 		assertEquals("source2-value", result.get());
510 	}
511 
512 	@Test
513 	void o03_addSource_afterGlobalOverride() {
514 		var settings = Settings.create()
515 			.addSource(Settings.SYSTEM_PROPERTY_SOURCE)
516 			.addSource(Settings.SYSTEM_ENV_SOURCE)
517 			.build();
518 
519 		settings.setGlobal(TEST_PROP, "global-value");
520 
521 		var source = new MapStore();
522 		source.set(TEST_PROP, "source-value");
523 		// Note: Can't add sources after building, so we create a new instance
524 		var settingsWithSource = Settings.create()
525 			.addSource(source)
526 			.addSource(Settings.SYSTEM_PROPERTY_SOURCE)
527 			.addSource(Settings.SYSTEM_ENV_SOURCE)
528 			.build();
529 		settingsWithSource.setGlobal(TEST_PROP, "global-value");
530 
531 		// Global override should take precedence
532 		var result = settingsWithSource.get(TEST_PROP);
533 		assertTrue(result.isPresent());
534 		assertEquals("global-value", result.get());
535 	}
536 
537 	@Test
538 	void o04_addSource_beforeSystemProperty() {
539 		System.setProperty(TEST_PROP, "system-value");
540 
541 		var source = new MapStore();
542 		source.set(TEST_PROP, "source-value");
543 		// Sources are checked in reverse order, so add system sources first, then custom source
544 		var settings = Settings.create()
545 			.addSource(Settings.SYSTEM_ENV_SOURCE)
546 			.addSource(Settings.SYSTEM_PROPERTY_SOURCE)
547 			.addSource(source)
548 			.build();
549 
550 		// Source should take precedence over system property (checked first)
551 		var result = settings.get(TEST_PROP);
552 		assertTrue(result.isPresent());
553 		assertEquals("source-value", result.get());
554 	}
555 
556 	@Test
557 	void o05_addSource_fallbackToSystemProperty() {
558 		var source = new MapStore();
559 		// Source doesn't have the property
560 
561 		System.setProperty(TEST_PROP, "system-value");
562 		var settings = Settings.create()
563 			.addSource(source)
564 			.addSource(Settings.SYSTEM_PROPERTY_SOURCE)
565 			.addSource(Settings.SYSTEM_ENV_SOURCE)
566 			.build();
567 
568 		// Should fall back to system property
569 		var result = settings.get(TEST_PROP);
570 		assertTrue(result.isPresent());
571 		assertEquals("system-value", result.get());
572 	}
573 
574 	@Test
575 	void o06_setSources() {
576 		var source1 = new MapStore();
577 		source1.set(TEST_PROP, "source1-value");
578 
579 		var source2 = new MapStore();
580 		source2.set(TEST_PROP, "source2-value");
581 
582 		var settings = Settings.create()
583 			.setSources(source1, source2, Settings.SYSTEM_PROPERTY_SOURCE, Settings.SYSTEM_ENV_SOURCE)
584 			.build();
585 
586 		// Last source in array should be checked first
587 		var result = settings.get(TEST_PROP);
588 		assertTrue(result.isPresent());
589 		assertEquals("source2-value", result.get());
590 	}
591 
592 	@Test
593 	void o07_setSources_clearsExisting() {
594 		var source1 = new MapStore();
595 		source1.set(TEST_PROP, "source1-value");
596 
597 		var source2 = new MapStore();
598 		source2.set(TEST_PROP, "source2-value");
599 
600 		var settings = Settings.create()
601 			.addSource(source1)
602 			.setSources(source2, Settings.SYSTEM_PROPERTY_SOURCE, Settings.SYSTEM_ENV_SOURCE)
603 			.build();
604 
605 		// Only source2 should exist now
606 		var result = settings.get(TEST_PROP);
607 		assertTrue(result.isPresent());
608 		assertEquals("source2-value", result.get());
609 	}
610 
611 	@Test
612 	void o08_addSource_nullValue() {
613 		var source = new MapStore();
614 		source.set(TEST_PROP, null);
615 		System.setProperty(TEST_PROP, "system-value");
616 
617 		// Sources are checked in reverse order, so add system sources first, then custom source
618 		var settings = Settings.create()
619 			.addSource(Settings.SYSTEM_ENV_SOURCE)
620 			.addSource(Settings.SYSTEM_PROPERTY_SOURCE)
621 			.addSource(source)
622 			.build();
623 
624 		// Note that setting a null value on the source (Optional.empty()) overrides the system property.
625 		// When a source returns Optional.empty(), it means the property exists but has a null value.
626 		var result = settings.get(TEST_PROP);
627 		assertFalse(result.isPresent());
628 	}
629 
630 	@Test
631 	void o09_addSource_nullSource() {
632 		assertThrows(IllegalArgumentException.class, () -> {
633 			Settings.create().addSource(null);
634 		});
635 	}
636 
637 	@Test
638 	void o10_setSources_nullSource() {
639 		var source1 = new MapStore();
640 		assertThrows(IllegalArgumentException.class, () -> {
641 			Settings.create().setSources(source1, null);
642 		});
643 	}
644 
645 	@Test
646 	void o11_source_springPropertiesExample() {
647 		// Simulate Spring properties
648 		var springSource = new MapStore();
649 		springSource.set("spring.datasource.url", "jdbc:postgresql://localhost/db");
650 		springSource.set("spring.datasource.username", "admin");
651 
652 		var settings = Settings.create()
653 			.addSource(springSource)
654 			.addSource(Settings.SYSTEM_PROPERTY_SOURCE)
655 			.addSource(Settings.SYSTEM_ENV_SOURCE)
656 			.build();
657 
658 		var url = settings.get("spring.datasource.url");
659 		assertTrue(url.isPresent());
660 		assertEquals("jdbc:postgresql://localhost/db", url.get());
661 
662 		var username = settings.get("spring.datasource.username");
663 		assertTrue(username.isPresent());
664 		assertEquals("admin", username.get());
665 	}
666 
667 	//====================================================================================================
668 	// addSource(FunctionalSource) - Functional interface usage
669 	//====================================================================================================
670 	@Test
671 	void p01_addSource_functionalSource() {
672 		// Test the addSource(FunctionalSource) overload
673 		var source = (FunctionalSource) name -> opt(System.getProperty(name));
674 		System.setProperty(TEST_PROP, "system-value");
675 		var settings = Settings.create()
676 			.addSource(source)
677 			.addSource(Settings.SYSTEM_ENV_SOURCE)
678 			.build();
679 		var result = settings.get(TEST_PROP);
680 		assertTrue(result.isPresent());
681 		assertEquals("system-value", result.get());
682 	}
683 
684 	@Test
685 	void p02_addSource_functionalSource_factoryMethod() {
686 		// Test addSource with FunctionalSource.of()
687 		var source = FunctionalSource.of(System::getProperty);
688 		System.setProperty(TEST_PROP, "system-value");
689 		var settings = Settings.create()
690 			.addSource(source)
691 			.addSource(Settings.SYSTEM_ENV_SOURCE)
692 			.build();
693 		var result = settings.get(TEST_PROP);
694 		assertTrue(result.isPresent());
695 		assertEquals("system-value", result.get());
696 	}
697 
698 	//====================================================================================================
699 	// FunctionalStore
700 	//====================================================================================================
701 	@Test
702 	void q01_writeableFunctionalSource_basic() {
703 		// Create a writable functional source using system properties
704 		var source = FunctionalStore.of(
705 			System::getProperty,
706 			(k, v) -> System.setProperty(k, v),
707 			k -> System.clearProperty(k),
708 			() -> { /* No-op clear for system properties */ }
709 		);
710 
711 		// Test set and get
712 		source.set(TEST_PROP, "test-value");
713 		var result = source.get(TEST_PROP);
714 		assertNotNull(result);
715 		assertTrue(result.isPresent());
716 		assertEquals("test-value", result.get());
717 
718 		// Test unset
719 		source.unset(TEST_PROP);
720 		result = source.get(TEST_PROP);
721 		assertNull(result); // Should return null when property doesn't exist
722 
723 		// Clean up
724 		System.clearProperty(TEST_PROP);
725 	}
726 
727 	@Test
728 	void q02_writeableFunctionalSource_withSettings() {
729 		// Create a writable functional source and add it to Settings
730 		var source = FunctionalStore.of(
731 			System::getProperty,
732 			(k, v) -> System.setProperty(k, v),
733 			k -> System.clearProperty(k),
734 			() -> { /* No-op clear for system properties */ }
735 		);
736 
737 		var settings = Settings.create()
738 			.addSource(source)
739 			.addSource(Settings.SYSTEM_PROPERTY_SOURCE)
740 			.addSource(Settings.SYSTEM_ENV_SOURCE)
741 			.build();
742 
743 		// Set a value through the source
744 		source.set(TEST_PROP, "source-value");
745 
746 		// Get it through Settings
747 		var result = settings.get(TEST_PROP);
748 		assertTrue(result.isPresent());
749 		assertEquals("source-value", result.get());
750 
751 		// Clean up
752 		source.unset(TEST_PROP);
753 		System.clearProperty(TEST_PROP);
754 	}
755 
756 	@Test
757 	void q03_writeableFunctionalSource_clear() {
758 		// Test clear() functionality with a custom clearer
759 		var map = new java.util.HashMap<String, String>();
760 		var source = FunctionalStore.of(
761 			map::get,
762 			map::put,
763 			map::remove,
764 			map::clear
765 		);
766 
767 		source.set(TEST_PROP, "test-value");
768 		source.set(TEST_PROP_2, "test-value-2");
769 
770 		// Verify values are set
771 		var result1 = source.get(TEST_PROP);
772 		assertNotNull(result1);
773 		assertTrue(result1.isPresent());
774 		assertEquals("test-value", result1.get());
775 
776 		var result2 = source.get(TEST_PROP_2);
777 		assertNotNull(result2);
778 		assertTrue(result2.isPresent());
779 		assertEquals("test-value-2", result2.get());
780 
781 		// Clear all values
782 		source.clear();
783 
784 		// Verify values are cleared
785 		result1 = source.get(TEST_PROP);
786 		assertNull(result1);
787 
788 		result2 = source.get(TEST_PROP_2);
789 		assertNull(result2);
790 	}
791 
792 	//====================================================================================================
793 	// Builder.localStore() - Coverage for lines 205-206
794 	//====================================================================================================
795 	@Test
796 	void r01_localStore() {
797 		// Test that localStore() method can be called on the builder
798 		var settings = Settings.create()
799 			.localStore(OptionalSupplier.of(() -> new MapStore()))
800 			.build();
801 		// Verify it works by setting a local value
802 		settings.setLocal(TEST_PROP, "test-value");
803 		var result = settings.get(TEST_PROP);
804 		assertTrue(result.isPresent());
805 		assertEquals("test-value", result.get());
806 	}
807 
808 	//====================================================================================================
809 	// Global store disabled (null supplier) - Coverage for lines 476, 493, 571
810 	//====================================================================================================
811 	@Test
812 	void s01_setGlobal_whenGlobalStoreDisabled() {
813 		// Create Settings with null global store (disabled)
814 		var settings = Settings.create()
815 			.globalStore(OptionalSupplier.empty())
816 			.addSource(Settings.SYSTEM_PROPERTY_SOURCE)
817 			.addSource(Settings.SYSTEM_ENV_SOURCE)
818 			.build();
819 
820 		// setGlobal() should throw IllegalStateException
821 		assertThrows(IllegalStateException.class, () -> {
822 			settings.setGlobal(TEST_PROP, "value");
823 		});
824 	}
825 
826 	@Test
827 	void s02_unsetGlobal_whenGlobalStoreDisabled() {
828 		// Create Settings with null global store (disabled)
829 		var settings = Settings.create()
830 			.globalStore(OptionalSupplier.empty())
831 			.addSource(Settings.SYSTEM_PROPERTY_SOURCE)
832 			.addSource(Settings.SYSTEM_ENV_SOURCE)
833 			.build();
834 
835 		// unsetGlobal() should throw IllegalStateException
836 		assertThrows(IllegalStateException.class, () -> {
837 			settings.unsetGlobal(TEST_PROP);
838 		});
839 	}
840 
841 	@Test
842 	void s03_clearGlobal_whenGlobalStoreDisabled() {
843 		// Create Settings with null global store (disabled)
844 		var settings = Settings.create()
845 			.globalStore(OptionalSupplier.empty())
846 			.addSource(Settings.SYSTEM_PROPERTY_SOURCE)
847 			.addSource(Settings.SYSTEM_ENV_SOURCE)
848 			.build();
849 
850 		// clearGlobal() should throw IllegalStateException
851 		assertThrows(IllegalStateException.class, () -> {
852 			settings.clearGlobal();
853 		});
854 	}
855 
856 	//====================================================================================================
857 	// MapStore.unset() - Coverage for line 134 (when map is null)
858 	//====================================================================================================
859 	@Test
860 	void t01_mapStore_unset_whenMapNotInitialized() {
861 		// Create a fresh MapStore that has never had set() called on it
862 		// This means the internal map is still null (lazy initialization)
863 		var store = new MapStore();
864 
865 		// Calling unset() when map is null should not throw and should do nothing
866 		// This covers the branch where m == null in line 134
867 		store.unset(TEST_PROP);
868 
869 		// Verify the map is still null (not initialized)
870 		// get() should return null since the map doesn't exist
871 		var result = store.get(TEST_PROP);
872 		assertNull(result);
873 	}
874 
875 	//====================================================================================================
876 	// get(String, T) - Type conversion with default value
877 	//====================================================================================================
878 	@Test
879 	void u01_get_withDefaultString_found() {
880 		System.setProperty(TEST_PROP, "found-value");
881 		var result = Settings.get().get(TEST_PROP, "default-value");
882 		assertEquals("found-value", result);
883 	}
884 
885 	@Test
886 	void u02_get_withDefaultString_notFound() {
887 		var result = Settings.get().get("nonexistent.property", "default-value");
888 		assertEquals("default-value", result);
889 	}
890 
891 	@Test
892 	void u03_get_withDefaultBoolean_found() {
893 		System.setProperty(TEST_PROP, "true");
894 		var result = Settings.get().get(TEST_PROP, false);
895 		assertTrue(result);
896 	}
897 
898 	@Test
899 	void u04_get_withDefaultBoolean_notFound() {
900 		var result = Settings.get().get("nonexistent.property", true);
901 		assertTrue(result);
902 	}
903 
904 	@Test
905 	void u05_get_withDefaultBoolean_falseValue() {
906 		System.setProperty(TEST_PROP, "false");
907 		var result = Settings.get().get(TEST_PROP, true);
908 		assertFalse(result);
909 	}
910 
911 	@Test
912 	void u07_get_withDefaultCharset_notFound() {
913 		// Use Charset.forName to get a Charset instance (not a concrete implementation)
914 		var defaultCharset = Charset.forName("UTF-8");
915 		var result = Settings.get().get("nonexistent.property", defaultCharset);
916 		assertEquals(defaultCharset, result);
917 	}
918 
919 	@Test
920 	void u08_get_withDefaultEnum_found() {
921 		enum TestEnum { VALUE1, VALUE2, VALUE3 }
922 		System.setProperty(TEST_PROP, "VALUE2");
923 		var result = Settings.get().get(TEST_PROP, TestEnum.VALUE1);
924 		assertEquals(TestEnum.VALUE2, result);
925 	}
926 
927 	@Test
928 	void u09_get_withDefaultEnum_notFound() {
929 		enum TestEnum { VALUE1, VALUE2, VALUE3 }
930 		var result = Settings.get().get("nonexistent.property", TestEnum.VALUE1);
931 		assertEquals(TestEnum.VALUE1, result);
932 	}
933 
934 	@Test
935 	void u10_get_withDefaultString_nullDefault() {
936 		// Null defaults are not allowed
937 		System.setProperty(TEST_PROP, "found-value");
938 		assertThrows(IllegalArgumentException.class, () -> {
939 			Settings.get().get(TEST_PROP, (String)null);
940 		});
941 	}
942 
943 	@Test
944 	void u11_get_withDefaultString_nullProperty() {
945 		Settings.get().setLocal(TEST_PROP, null);
946 		var result = Settings.get().get(TEST_PROP, "default-value");
947 		// When property is set to null, get() returns Optional.empty(), so get(String, T) returns default
948 		assertEquals("default-value", result);
949 	}
950 
951 	//====================================================================================================
952 	// addTypeFunction() - Custom type conversion
953 	//====================================================================================================
954 	@Test
955 	void v01_addTypeFunction_customType() {
956 		// Register a custom type converter for Integer
957 		var settings = Settings.create()
958 			.addSource(Settings.SYSTEM_PROPERTY_SOURCE)
959 			.addSource(Settings.SYSTEM_ENV_SOURCE)
960 			.addTypeFunction(Integer.class, Integer::valueOf)
961 			.build();
962 
963 		System.setProperty(TEST_PROP, "123");
964 		var result = settings.get(TEST_PROP, 0);
965 		assertEquals(123, result.intValue());
966 	}
967 
968 	@Test
969 	void v02_addTypeFunction_customType_notFound() {
970 		// Register a custom type converter for Integer
971 		var settings = Settings.create()
972 			.addSource(Settings.SYSTEM_PROPERTY_SOURCE)
973 			.addSource(Settings.SYSTEM_ENV_SOURCE)
974 			.addTypeFunction(Integer.class, Integer::valueOf)
975 			.build();
976 
977 		var result = settings.get("nonexistent.property", 999);
978 		assertEquals(999, result.intValue());
979 	}
980 
981 	@Test
982 	void v03_addTypeFunction_overridesDefault() {
983 		// Register a custom converter for Boolean that inverts the value
984 		var settings = Settings.create()
985 			.addSource(Settings.SYSTEM_PROPERTY_SOURCE)
986 			.addSource(Settings.SYSTEM_ENV_SOURCE)
987 			.addTypeFunction(Boolean.class, s -> !Boolean.valueOf(s))
988 			.build();
989 
990 		System.setProperty(TEST_PROP, "true");
991 		var result = settings.get(TEST_PROP, false);
992 		// Should be inverted (true -> false)
993 		assertFalse(result);
994 	}
995 
996 	@Test
997 	void v04_addTypeFunction_multipleTypes() {
998 		// Register multiple custom type converters
999 		var settings = Settings.create()
1000 			.addSource(Settings.SYSTEM_PROPERTY_SOURCE)
1001 			.addSource(Settings.SYSTEM_ENV_SOURCE)
1002 			.addTypeFunction(Integer.class, Integer::valueOf)
1003 			.addTypeFunction(Long.class, Long::valueOf)
1004 			.build();
1005 
1006 		System.setProperty(TEST_PROP, "123");
1007 		var intResult = settings.get(TEST_PROP, 0);
1008 		assertEquals(123, intResult.intValue());
1009 
1010 		System.setProperty(TEST_PROP_2, "456");
1011 		var longResult = settings.get(TEST_PROP_2, 0L);
1012 		assertEquals(456L, longResult.longValue());
1013 	}
1014 
1015 	@Test
1016 	void v05_addTypeFunction_customClass() {
1017 		// Create a custom class with a fromString method
1018 		// Using a static nested class to avoid local class issues
1019 		var settings = Settings.create()
1020 			.addSource(Settings.SYSTEM_PROPERTY_SOURCE)
1021 			.addSource(Settings.SYSTEM_ENV_SOURCE)
1022 			.addTypeFunction(String.class, s -> "custom-" + s)
1023 			.build();
1024 
1025 		System.setProperty(TEST_PROP, "value");
1026 		// String is already supported, but we override it with a custom function
1027 		var result = settings.get(TEST_PROP, "default");
1028 		assertEquals("custom-value", result);
1029 	}
1030 
1031 	@Test
1032 	void v06_addTypeFunction_nullType() {
1033 		assertThrows(IllegalArgumentException.class, () -> {
1034 			Settings.create().addTypeFunction(null, Integer::valueOf);
1035 		});
1036 	}
1037 
1038 	@Test
1039 	void v07_addTypeFunction_nullFunction() {
1040 		assertThrows(IllegalArgumentException.class, () -> {
1041 			Settings.create().addTypeFunction(Integer.class, null);
1042 		});
1043 	}
1044 
1045 	@Test
1046 	void v08_addTypeFunction_unsupportedType() {
1047 		// Try to use a type that doesn't have a static method or constructor
1048 		// Custom class without static method or String constructor
1049 		class UnsupportedType {
1050 			@SuppressWarnings("unused")
1051 			private final int value;
1052 			UnsupportedType(int value) { this.value = value; }
1053 		}
1054 
1055 		var settings = Settings.create()
1056 			.addSource(Settings.SYSTEM_PROPERTY_SOURCE)
1057 			.addSource(Settings.SYSTEM_ENV_SOURCE)
1058 			.build();
1059 
1060 		System.setProperty(TEST_PROP, "123");
1061 		assertThrows(RuntimeException.class, () -> {
1062 			settings.get(TEST_PROP, new UnsupportedType(0)); // No static method or String constructor
1063 		});
1064 	}
1065 
1066 	@Test
1067 	void v09_addTypeFunction_usesReflectionWhenCustomNotRegistered() {
1068 		// Types with static methods or String constructors work via reflection
1069 		var settings = Settings.create()
1070 			.addSource(Settings.SYSTEM_PROPERTY_SOURCE)
1071 			.addSource(Settings.SYSTEM_ENV_SOURCE)
1072 			.build();
1073 
1074 		// Boolean has Boolean.valueOf(String) static method, so it should work
1075 		System.setProperty(TEST_PROP, "true");
1076 		var result = settings.get(TEST_PROP, false);
1077 		assertTrue(result);
1078 
1079 		// Integer has Integer.valueOf(String) static method, so it should work
1080 		System.setProperty(TEST_PROP_2, "123");
1081 		var intResult = settings.get(TEST_PROP_2, 0);
1082 		assertEquals(123, intResult.intValue());
1083 	}
1084 
1085 	//====================================================================================================
1086 	// StringSetting.filter()
1087 	//====================================================================================================
1088 	@Test
1089 	void r01_stringSetting_filter_returnsStringSetting() {
1090 		System.setProperty(TEST_PROP, "hello");
1091 		var setting = Settings.get().get(TEST_PROP);
1092 		var filtered = setting.filter(s -> s.length() > 3);
1093 		assertEquals("hello", filtered.get());
1094 	}
1095 
1096 	@Test
1097 	void r02_stringSetting_filter_noMatch() {
1098 		System.setProperty(TEST_PROP, "hi");
1099 		var setting = Settings.get().get(TEST_PROP);
1100 		var filtered = setting.filter(s -> s.length() > 3);
1101 		assertNull(filtered.get());
1102 	}
1103 
1104 	@Test
1105 	void r03_stringSetting_filter_empty() {
1106 		var setting = Settings.get().get("nonexistent.property");
1107 		var filtered = setting.filter(s -> s.length() > 3);
1108 		assertNull(filtered.get());
1109 	}
1110 
1111 	@Test
1112 	void r04_stringSetting_filter_cached() {
1113 		var callCount = new AtomicInteger();
1114 		var settings = Settings.create()
1115 			.addSource(Settings.SYSTEM_PROPERTY_SOURCE)
1116 			.addSource(Settings.SYSTEM_ENV_SOURCE)
1117 			.build();
1118 		System.setProperty(TEST_PROP, "hello");
1119 		var setting = settings.get(TEST_PROP);
1120 		var filtered = setting.filter(s -> {
1121 			callCount.incrementAndGet();
1122 			return s.length() > 3;
1123 		});
1124 
1125 		// First call
1126 		assertEquals("hello", filtered.get());
1127 		assertEquals(1, callCount.get());
1128 
1129 		// Second call - should use cached value
1130 		assertEquals("hello", filtered.get());
1131 		assertEquals(1, callCount.get());
1132 	}
1133 
1134 	@Test
1135 	void r05_stringSetting_filter_independent() {
1136 		System.setProperty(TEST_PROP, "hello");
1137 		var setting = Settings.get().get(TEST_PROP);
1138 		var filtered = setting.filter(s -> s.length() > 3);
1139 
1140 		// Reset original
1141 		setting.reset();
1142 		// Filtered should still have cached value
1143 		assertEquals("hello", filtered.get());
1144 	}
1145 
1146 	//====================================================================================================
1147 	// StringSetting.mapString()
1148 	//====================================================================================================
1149 	@Test
1150 	void s01_stringSetting_mapString_returnsStringSetting() {
1151 		System.setProperty(TEST_PROP, "hello");
1152 		var setting = Settings.get().get(TEST_PROP);
1153 		var mapped = setting.mapString(String::toUpperCase);
1154 		assertEquals("HELLO", mapped.get());
1155 	}
1156 
1157 	@Test
1158 	void s02_stringSetting_mapString_empty() {
1159 		var setting = Settings.get().get("nonexistent.property");
1160 		var mapped = setting.mapString(String::toUpperCase);
1161 		assertNull(mapped.get());
1162 	}
1163 
1164 	@Test
1165 	void s03_stringSetting_mapString_cached() {
1166 		var callCount = new AtomicInteger();
1167 		var settings = Settings.create()
1168 			.addSource(Settings.SYSTEM_PROPERTY_SOURCE)
1169 			.addSource(Settings.SYSTEM_ENV_SOURCE)
1170 			.build();
1171 		System.setProperty(TEST_PROP, "hello");
1172 		var setting = settings.get(TEST_PROP);
1173 		var mapped = setting.mapString(s -> {
1174 			callCount.incrementAndGet();
1175 			return s.toUpperCase();
1176 		});
1177 
1178 		// First call
1179 		assertEquals("HELLO", mapped.get());
1180 		assertEquals(1, callCount.get());
1181 
1182 		// Second call - should use cached value
1183 		assertEquals("HELLO", mapped.get());
1184 		assertEquals(1, callCount.get());
1185 	}
1186 
1187 	@Test
1188 	void s04_stringSetting_mapString_independent() {
1189 		System.setProperty(TEST_PROP, "hello");
1190 		var setting = Settings.get().get(TEST_PROP);
1191 		var mapped = setting.mapString(String::toUpperCase);
1192 
1193 		// Reset original
1194 		setting.reset();
1195 		// Mapped should still have cached value
1196 		assertEquals("HELLO", mapped.get());
1197 	}
1198 
1199 	@Test
1200 	void s05_stringSetting_mapString_returnsNull() {
1201 		System.setProperty(TEST_PROP, "hello");
1202 		var setting = Settings.get().get(TEST_PROP);
1203 		var mapped = setting.mapString(s -> null);
1204 		assertNull(mapped.get());
1205 	}
1206 
1207 	//====================================================================================================
1208 	// Setting.asOptional()
1209 	//====================================================================================================
1210 	@Test
1211 	void t01_setting_asOptional_present() {
1212 		System.setProperty(TEST_PROP, "value");
1213 		var setting = Settings.get().get(TEST_PROP);
1214 		var optional = setting.asOptional();
1215 		assertTrue(optional.isPresent());
1216 		assertEquals("value", optional.get());
1217 	}
1218 
1219 	@Test
1220 	void t02_setting_asOptional_empty() {
1221 		var setting = Settings.get().get("nonexistent.property");
1222 		var optional = setting.asOptional();
1223 		assertFalse(optional.isPresent());
1224 	}
1225 
1226 	@Test
1227 	void t03_setting_asOptional_snapshot() {
1228 		var settings = Settings.create()
1229 			.addSource(Settings.SYSTEM_PROPERTY_SOURCE)
1230 			.addSource(Settings.SYSTEM_ENV_SOURCE)
1231 			.build();
1232 		System.setProperty(TEST_PROP, "value1");
1233 		var setting = settings.get(TEST_PROP);
1234 		var optional1 = setting.asOptional();
1235 		assertTrue(optional1.isPresent());
1236 		assertEquals("value1", optional1.get());
1237 
1238 		// Change the value
1239 		System.setProperty(TEST_PROP, "value2");
1240 		setting.reset(); // Force recomputation
1241 
1242 		// Original Optional should still have old value (snapshot)
1243 		assertTrue(optional1.isPresent());
1244 		assertEquals("value1", optional1.get());
1245 
1246 		// New Optional should have new value
1247 		var optional2 = setting.asOptional();
1248 		assertTrue(optional2.isPresent());
1249 		assertEquals("value2", optional2.get());
1250 	}
1251 
1252 	@Test
1253 	void t04_setting_asOptional_resetDoesNotAffect() {
1254 		var settings = Settings.create()
1255 			.addSource(Settings.SYSTEM_PROPERTY_SOURCE)
1256 			.addSource(Settings.SYSTEM_ENV_SOURCE)
1257 			.build();
1258 		System.setProperty(TEST_PROP, "value1");
1259 		var setting = settings.get(TEST_PROP);
1260 		var optional = setting.asOptional();
1261 		assertTrue(optional.isPresent());
1262 		assertEquals("value1", optional.get());
1263 
1264 		// Reset the setting
1265 		setting.reset();
1266 
1267 		// Optional should still have the original value (snapshot)
1268 		assertTrue(optional.isPresent());
1269 		assertEquals("value1", optional.get());
1270 
1271 		// Getting a new Optional after reset should get the current value
1272 		var optional2 = setting.asOptional();
1273 		assertTrue(optional2.isPresent());
1274 		assertEquals("value1", optional2.get()); // Still value1 since system property hasn't changed
1275 	}
1276 }
1277