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.config;
18  
19  import static org.apache.juneau.TestUtils.*;
20  import static org.junit.jupiter.api.Assertions.*;
21  
22  import java.io.*;
23  import java.net.*;
24  import java.util.*;
25  import java.util.concurrent.*;
26  
27  import org.apache.juneau.*;
28  import org.apache.juneau.collections.*;
29  import org.apache.juneau.config.event.*;
30  import org.apache.juneau.config.mod.*;
31  import org.apache.juneau.config.store.*;
32  import org.apache.juneau.parser.*;
33  import org.apache.juneau.svl.*;
34  import org.apache.juneau.uon.*;
35  import org.junit.jupiter.api.*;
36  
37  class Config_Test extends TestBase {
38  
39  	private Config.Builder cb = Config.create().store(MemoryStore.DEFAULT).name("Test.cfg");
40  
41  	private Config init(String...lines) {
42  		MemoryStore.DEFAULT.update("Test.cfg", lines);
43  		return cb.build().rollback();
44  	}
45  
46  	//====================================================================================================
47  	//	public String get(String key)
48  	//====================================================================================================
49  	@Test void get() {
50  		var c = init("a=1", "[S]", "b=2");
51  
52  		assertEquals("1", c.get("a").get());
53  		assertEquals("1", c.get("a").get());
54  		assertEquals("2", c.get("S/b").get());
55  		assertNull(c.get("b").orElse(null));
56  		assertNull(c.get("S/c").orElse(null));
57  		assertNull(c.get("T/d").orElse(null));
58  		assertThrowsWithMessage(IllegalArgumentException.class, "Argument 'key' cannot be null.", ()->c.get(null));
59  		c.close();
60  	}
61  
62  	//====================================================================================================
63  	//	public Config set(String key, String value)
64  	//====================================================================================================
65  	@Test void set1() throws Exception {
66  		var c = init("a1=1", "[S]", "b1=1");
67  
68  		c.set("a1", "2");
69  		c.set("a2", "3");
70  		c.set("a3", "4");
71  		c.set("S/b1", "5");
72  		c.set("S/b2", "6");
73  		c.set("T/c1", "7");
74  
75  		assertEquals("2", c.get("a1").get());
76  		assertEquals("3", c.get("a2").get());
77  		assertEquals("4", c.get("a3").get());
78  		assertEquals("5", c.get("S/b1").get());
79  		assertEquals("6", c.get("S/b2").get());
80  		assertEquals("7", c.get("T/c1").get());
81  
82  		c.commit();
83  
84  		assertEquals("2", c.get("a1").get());
85  		assertEquals("3", c.get("a2").get());
86  		assertEquals("4", c.get("a3").get());
87  		assertEquals("5", c.get("S/b1").get());
88  		assertEquals("6", c.get("S/b2").get());
89  		assertEquals("7", c.get("T/c1").get());
90  
91  		c = cb.build();
92  
93  		assertEquals("2", c.get("a1").get());
94  		assertEquals("3", c.get("a2").get());
95  		assertEquals("4", c.get("a3").get());
96  		assertEquals("5", c.get("S/b1").get());
97  		assertEquals("6", c.get("S/b2").get());
98  		assertEquals("7", c.get("T/c1").get());
99  
100 		assertEquals("a1 = 2|a2 = 3|a3 = 4|[S]|b1 = 5|b2 = 6|[T]|c1 = 7|", pipedLines(c));
101 	}
102 
103 	//====================================================================================================
104 	//	public Config set(String key, Object value)
105 	//====================================================================================================
106 	@Test void set2() throws Exception {
107 		var c = init("a1=1", "[S]", "b1=1");
108 
109 		c.set("a1", 2);
110 		c.set("a2", 3);
111 		c.set("a3", 4);
112 		c.set("S/b1", 5);
113 		c.set("S/b2", 6);
114 		c.set("T/c1", 7);
115 
116 		assertEquals("2", c.get("a1").get());
117 		assertEquals("3", c.get("a2").get());
118 		assertEquals("4", c.get("a3").get());
119 		assertEquals("5", c.get("S/b1").get());
120 		assertEquals("6", c.get("S/b2").get());
121 		assertEquals("7", c.get("T/c1").get());
122 
123 		c.commit();
124 
125 		assertEquals("2", c.get("a1").get());
126 		assertEquals("3", c.get("a2").get());
127 		assertEquals("4", c.get("a3").get());
128 		assertEquals("5", c.get("S/b1").get());
129 		assertEquals("6", c.get("S/b2").get());
130 		assertEquals("7", c.get("T/c1").get());
131 
132 		c = cb.build();
133 
134 		assertEquals("2", c.get("a1").get());
135 		assertEquals("3", c.get("a2").get());
136 		assertEquals("4", c.get("a3").get());
137 		assertEquals("5", c.get("S/b1").get());
138 		assertEquals("6", c.get("S/b2").get());
139 		assertEquals("7", c.get("T/c1").get());
140 
141 		assertEquals("a1 = 2|a2 = 3|a3 = 4|[S]|b1 = 5|b2 = 6|[T]|c1 = 7|", pipedLines(c));
142 	}
143 
144 	//====================================================================================================
145 	//	public Config set(String key, Object value, Serializer serializer)
146 	//====================================================================================================
147 	@Test void set3() {
148 		var c = init("a1=1", "[S]", "b1=1");
149 
150 		var b = new ABean().init();
151 		c.set("a1", b, UonSerializer.DEFAULT);
152 		c.set("a2", b, UonSerializer.DEFAULT);
153 		c.set("a3", b, UonSerializer.DEFAULT);
154 		c.set("S/b1", b, UonSerializer.DEFAULT);
155 		c.set("S/b2", b, UonSerializer.DEFAULT);
156 		c.set("T/c1", b, UonSerializer.DEFAULT);
157 
158 		assertEquals("(foo=bar)", c.get("a1").get());
159 		assertEquals("(foo=bar)", c.get("a2").get());
160 		assertEquals("(foo=bar)", c.get("a3").get());
161 		assertEquals("(foo=bar)", c.get("S/b1").get());
162 		assertEquals("(foo=bar)", c.get("S/b2").get());
163 		assertEquals("(foo=bar)", c.get("T/c1").get());
164 	}
165 
166 	//====================================================================================================
167 	//	public Config set(String key, Object value, Serializer serializer, ConfigMod[] modifiers, String comment, List<String> preLines)
168 	//====================================================================================================
169 	@Test void set4() throws Exception {
170 		var c = init("a1=1", "[S]", "b1=1");
171 
172 		var b = new ABean().init();
173 		c.set("a1", b, UonSerializer.DEFAULT, "*", "comment", Arrays.asList("#c1","#c2"));
174 		c.set("a2", b, UonSerializer.DEFAULT, "*", "comment", Arrays.asList("#c1","#c2"));
175 		c.set("a3", b, UonSerializer.DEFAULT, "*", "comment", Arrays.asList("#c1","#c2"));
176 		c.set("S/b1", b, UonSerializer.DEFAULT, "*", "comment", Arrays.asList("#c1","#c2"));
177 		c.set("S/b2", b, UonSerializer.DEFAULT, "*", "comment", Arrays.asList("#c1","#c2"));
178 		c.set("T/c1", b, UonSerializer.DEFAULT, "*", "comment", Arrays.asList("#c1","#c2"));
179 
180 		assertEquals("#c1|#c2|a1<*> = {RhMWWFIFVksf} # comment|#c1|#c2|a2<*> = {RhMWWFIFVksf} # comment|#c1|#c2|a3<*> = {RhMWWFIFVksf} # comment|[S]|#c1|#c2|b1<*> = {RhMWWFIFVksf} # comment|#c1|#c2|b2<*> = {RhMWWFIFVksf} # comment|[T]|#c1|#c2|c1<*> = {RhMWWFIFVksf} # comment|", pipedLines(c));
181 		c.commit();
182 		assertEquals("#c1|#c2|a1<*> = {RhMWWFIFVksf} # comment|#c1|#c2|a2<*> = {RhMWWFIFVksf} # comment|#c1|#c2|a3<*> = {RhMWWFIFVksf} # comment|[S]|#c1|#c2|b1<*> = {RhMWWFIFVksf} # comment|#c1|#c2|b2<*> = {RhMWWFIFVksf} # comment|[T]|#c1|#c2|c1<*> = {RhMWWFIFVksf} # comment|", pipedLines(c));
183 		c = cb.build();
184 		assertEquals("#c1|#c2|a1<*> = {RhMWWFIFVksf} # comment|#c1|#c2|a2<*> = {RhMWWFIFVksf} # comment|#c1|#c2|a3<*> = {RhMWWFIFVksf} # comment|[S]|#c1|#c2|b1<*> = {RhMWWFIFVksf} # comment|#c1|#c2|b2<*> = {RhMWWFIFVksf} # comment|[T]|#c1|#c2|c1<*> = {RhMWWFIFVksf} # comment|", pipedLines(c));
185 
186 		assertEquals("(foo=bar)", c.get("a1").get());
187 		assertEquals("(foo=bar)", c.get("a2").get());
188 		assertEquals("(foo=bar)", c.get("a3").get());
189 		assertEquals("(foo=bar)", c.get("S/b1").get());
190 		assertEquals("(foo=bar)", c.get("S/b2").get());
191 		assertEquals("(foo=bar)", c.get("T/c1").get());
192 	}
193 
194 	//====================================================================================================
195 	//	public Config remove(String key)
196 	//====================================================================================================
197 	@Test void remove() throws Exception {
198 		var c = init("a1=1", "a2=2", "[S]", "b1=1");
199 
200 		c.remove("a1");
201 		c.remove("a2");
202 		c.remove("a3");
203 		c.remove("S/b1");
204 		c.remove("T/c1");
205 
206 		assertEquals("[S]|", pipedLines(c));
207 		c.commit();
208 		assertEquals("[S]|", pipedLines(c));
209 		c = cb.build();
210 		assertEquals("[S]|", pipedLines(c));
211 	}
212 
213 	//====================================================================================================
214 	//	public String getString1(String key)
215 	//====================================================================================================
216 	@Test void xgetString1() {
217 		var c = init("a1=1", "a2=2", "[S]", "b1=1", "b2=");
218 
219 		assertEquals("1", c.get("a1").as(String.class).orElse(null));
220 		assertEquals("2", c.get("a2").as(String.class).orElse(null));
221 		assertEquals(null, c.get("a3").as(String.class).orElse(null));
222 		assertEquals("1", c.get("S/b1").as(String.class).orElse(null));
223 		assertEquals("", c.get("S/b2").as(String.class).orElse(null));
224 		assertEquals(null, c.get("S/b3").as(String.class).orElse(null));
225 		assertEquals(null, c.get("T/c1").as(String.class).orElse(null));
226 	}
227 
228 	//====================================================================================================
229 	//	public String getString(String key, String def)
230 	//====================================================================================================
231 	@Test void getString2() {
232 		var c = init("a1=1", "a2=2", "[S]", "b1=1", "b2=");
233 		assertEquals("1", c.get("a1").orElse("foo"));
234 		assertEquals("2", c.get("a2").orElse("foo"));
235 		assertEquals("foo", c.get("a3").orElse("foo"));
236 		assertEquals("1", c.get("S/b1").orElse("foo"));
237 		assertEquals("", c.get("S/b2").orElse("foo"));
238 		assertEquals("foo", c.get("S/b3").orElse("foo"));
239 		assertEquals("foo", c.get("T/c1").orElse("foo"));
240 	}
241 
242 	//====================================================================================================
243 	//	public String[] getStringArray(String key)
244 	//====================================================================================================
245 	@Test void getStringArray1() {
246 		var c = init("a1=1,2", "a2= 2 , 3 ", "[S]", "b1=1", "b2=");
247 		assertList(c.get("a1").as(String[].class).orElse(null), "1", "2");
248 		assertList(c.get("a2").as(String[].class).orElse(null), "2", "3");
249 		assertNull(c.get("a3").as(String[].class).orElse(null));
250 		assertList(c.get("S/b1").as(String[].class).orElse(null), "1");
251 		assertEmpty(c.get("S/b2").as(String[].class).orElse(null));
252 		assertNull(c.get("S/b3").as(String[].class).orElse(null));
253 		assertNull(c.get("T/c1").as(String[].class).orElse(null));
254 	}
255 
256 	//====================================================================================================
257 	//	public String[] getStringArray(String key, String[] def)
258 	//====================================================================================================
259 	@Test void getStringArray2() {
260 		var c = init("a1=1,2", "a2= 2 , 3 ", "[S]", "b1=1", "b2=");
261 		assertList(c.get("a1").asStringArray().orElse(new String[] {"foo"}), "1", "2");
262 		assertList(c.get("a2").asStringArray().orElse(new String[] {"foo"}), "2", "3");
263 		assertList(c.get("a3").asStringArray().orElse(new String[] {"foo"}), "foo");
264 		assertList(c.get("S/b1").asStringArray().orElse(new String[] {"foo"}), "1");
265 		assertEmpty(c.get("S/b2").asStringArray().orElse(new String[] {"foo"}));
266 		assertList(c.get("S/b3").asStringArray().orElse(new String[] {"foo"}), "foo");
267 		assertList(c.get("T/c1").asStringArray().orElse(new String[] {"foo"}), "foo");
268 	}
269 
270 	//====================================================================================================
271 	//	public int getInt(String key)
272 	//====================================================================================================
273 	@Test void getInt1() {
274 		var c = init("a1=1", "a2=2", "[S]", "b1=1", "b2=");
275 		assertEquals(1, c.get("a1").asInteger().orElse(0));
276 		assertEquals(2, c.get("a2").asInteger().orElse(0));
277 		assertEquals(0, c.get("a3").asInteger().orElse(0));
278 		assertEquals(1, c.get("S/b1").asInteger().orElse(0));
279 		assertEquals(0, c.get("S/b2").asInteger().orElse(0));
280 		assertEquals(0, c.get("S/b3").asInteger().orElse(0));
281 		assertEquals(0, c.get("T/c1").asInteger().orElse(0));
282 	}
283 
284 	@Test void getInt1BadValues() {
285 		var c = init("a1=foo", "a2=2.3", "a3=[1]", "a4=false");
286 		assertThrows(Exception.class, ()->c.get("a1").asInteger().orElse(0));
287 		assertThrows(Exception.class, ()->c.get("a2").asInteger().orElse(0));
288 		assertThrows(Exception.class, ()->c.get("a3").asInteger().orElse(0));
289 		assertThrows(Exception.class, ()->c.get("a4").asInteger().orElse(0));
290 	}
291 
292 	//====================================================================================================
293 	//	public int getInt2(String key, int def)
294 	//====================================================================================================
295 	@Test void getInt2() {
296 		var c = init("a1=1", "a2=2", "[S]", "b1=1", "b2=");
297 		assertEquals(1, c.get("a1").asInteger().orElse(-1));
298 		assertEquals(2, c.get("a2").asInteger().orElse(-1));
299 		assertEquals(-1, c.get("a3").asInteger().orElse(-1));
300 		assertEquals(1, c.get("S/b1").asInteger().orElse(-1));
301 		assertEquals(-1, c.get("S/b2").asInteger().orElse(-1));
302 		assertEquals(-1, c.get("S/b3").asInteger().orElse(-1));
303 		assertEquals(-1, c.get("T/c1").asInteger().orElse(-1));
304 	}
305 
306 	@Test void getInt2BadValues() {
307 		var c = init("a1=foo", "a2=2.3", "a3=[1]", "a4=false");
308 		assertThrows(Exception.class, ()->c.get("a1").asInteger().orElse(-1));
309 		assertThrows(Exception.class, ()->c.get("a2").asInteger().orElse(-1));
310 		assertThrows(Exception.class, ()->c.get("a3").asInteger().orElse(-1));
311 		assertThrows(Exception.class, ()->c.get("a4").asInteger().orElse(-1));
312 	}
313 
314 	//====================================================================================================
315 	//	public boolean getBoolean(String key)
316 	//====================================================================================================
317 	@Test void getBoolean1() {
318 		var c = init("a1=true", "a2=false", "[S]", "b1=TRUE", "b2=");
319 		assertEquals(true, c.get("a1").asBoolean().orElse(false));
320 		assertEquals(false, c.get("a2").asBoolean().orElse(false));
321 		assertEquals(false, c.get("a3").asBoolean().orElse(false));
322 		assertEquals(true, c.get("S/b1").asBoolean().orElse(false));
323 		assertEquals(false, c.get("S/b2").asBoolean().orElse(false));
324 		assertEquals(false, c.get("S/b3").asBoolean().orElse(false));
325 		assertEquals(false, c.get("T/c1").asBoolean().orElse(false));
326 	}
327 
328 	@Test void getBoolean1BadValues() {
329 		var c = init("a1=foo", "a2=2.3", "a3=[1]", "a4=T");
330 		assertEquals(false, c.get("a1").asBoolean().orElse(false));
331 		assertEquals(false, c.get("a2").asBoolean().orElse(false));
332 		assertEquals(false, c.get("a3").asBoolean().orElse(false));
333 		assertEquals(false, c.get("a4").asBoolean().orElse(false));
334 	}
335 
336 	//====================================================================================================
337 	//	public boolean getBoolean(String key, boolean def)
338 	//====================================================================================================
339 	@Test void getBoolean2() {
340 		var c = init("a1=true", "a2=false", "[S]", "b1=TRUE", "b2=");
341 		assertEquals(true, c.get("a1").asBoolean().orElse(true));
342 		assertEquals(false, c.get("a2").asBoolean().orElse(true));
343 		assertEquals(true, c.get("a3").asBoolean().orElse(true));
344 		assertEquals(true, c.get("S/b1").asBoolean().orElse(true));
345 		assertEquals(true, c.get("S/b2").asBoolean().orElse(true));
346 		assertEquals(true, c.get("S/b3").asBoolean().orElse(true));
347 		assertEquals(true, c.get("T/c1").asBoolean().orElse(true));
348 	}
349 
350 	@Test void getBoolean2BadValues() {
351 		var c = init("a1=foo", "a2=2.3", "a3=[1]", "a4=T");
352 		assertEquals(false, c.get("a1").asBoolean().orElse(true));
353 		assertEquals(false, c.get("a2").asBoolean().orElse(true));
354 		assertEquals(false, c.get("a3").asBoolean().orElse(true));
355 		assertEquals(false, c.get("a4").asBoolean().orElse(true));
356 	}
357 
358 	//====================================================================================================
359 	//	public long getLong(String key)
360 	//====================================================================================================
361 	@Test void getLong1() {
362 		var c = init("a1=1", "a2=2", "[S]", "b1=1", "b2=");
363 		assertEquals(1L, c.get("a1").asLong().orElse(0L));
364 		assertEquals(2L, c.get("a2").asLong().orElse(0L));
365 		assertEquals(0L, c.get("a3").asLong().orElse(0L));
366 		assertEquals(1L, c.get("S/b1").asLong().orElse(0L));
367 		assertEquals(0L, c.get("S/b2").asLong().orElse(0L));
368 		assertEquals(0L, c.get("S/b3").asLong().orElse(0L));
369 		assertEquals(0L, c.get("T/c1").asLong().orElse(0L));
370 	}
371 
372 	@Test void getLong1BadValues() {
373 		var c = init("a1=foo", "a2=2.3", "a3=[1]", "a4=false");
374 		assertThrows(Exception.class, ()->c.get("a1").as(long.class));
375 		assertThrows(Exception.class, ()->c.get("a2").as(long.class));
376 		assertThrows(Exception.class, ()->c.get("a3").as(long.class));
377 		assertThrows(Exception.class, ()->c.get("a4").as(long.class));
378 	}
379 
380 	//====================================================================================================
381 	//	public long getLong(String key, long def)
382 	//====================================================================================================
383 	@Test void getLong2() {
384 		var c = init("a1=1", "a2=2", "[S]", "b1=1", "b2=");
385 		assertEquals(1L, c.get("a1").asLong().orElse(Long.MAX_VALUE));
386 		assertEquals(2L, c.get("a2").asLong().orElse(Long.MAX_VALUE));
387 		assertEquals(Long.MAX_VALUE, c.get("a3").asLong().orElse(Long.MAX_VALUE));
388 		assertEquals(1L, c.get("S/b1").asLong().orElse(Long.MAX_VALUE));
389 		assertEquals(Long.MAX_VALUE, c.get("S/b2").asLong().orElse(Long.MAX_VALUE));
390 		assertEquals(Long.MAX_VALUE, c.get("S/b3").asLong().orElse(Long.MAX_VALUE));
391 		assertEquals(Long.MAX_VALUE, c.get("T/c1").asLong().orElse(Long.MAX_VALUE));
392 	}
393 
394 	@Test void getLong2BadValues() {
395 		var c = init("a1=foo", "a2=2.3", "a3=[1]", "a4=false");
396 
397 		assertThrows(NumberFormatException.class, ()->c.get("a1").asLong().orElse(-1L));
398 		assertThrows(NumberFormatException.class, ()->c.get("a2").asLong().orElse(-1L));
399 		assertThrows(NumberFormatException.class, ()->c.get("a3").asLong().orElse(-1L));
400 		assertThrows(NumberFormatException.class, ()->c.get("a4").asLong().orElse(-1L));
401 	}
402 
403 	//====================================================================================================
404 	//	public boolean getBytes(String key)
405 	//====================================================================================================
406 	@Test void getBytes1() {
407 		var c = init("a1=Zm9v", "a2=Zm", "\t9v", "a3=");
408 
409 		assertList(c.get("a1").as(byte[].class).get(), (byte)102, (byte)111, (byte)111);
410 		assertList(c.get("a2").as(byte[].class).get(), (byte)102, (byte)111, (byte)111);
411 		assertEmpty(c.get("a3").as(byte[].class).get());
412 		assertFalse(c.get("a4").as(byte[].class).isPresent());
413 	}
414 
415 	//====================================================================================================
416 	//	public boolean getBytes(String key, byte[] def)
417 	//====================================================================================================
418 	@Test void getBytes2() {
419 		var c = init("a1=Zm9v", "a2=Zm", "\t9v", "a3=");
420 
421 		assertList(c.get("a1").asBytes().orElse(new byte[] {1}), (byte)102, (byte)111, (byte)111);
422 		assertList(c.get("a2").asBytes().orElse(new byte[] {1}), (byte)102, (byte)111, (byte)111);
423 		assertEmpty(c.get("a3").asBytes().orElse(new byte[] {1}));
424 		assertList(c.get("a4").asBytes().orElse(new byte[] {1}), (byte)1);
425 	}
426 
427 	//====================================================================================================
428 	//	public <T> T getObject(String key, Type type, Type...args) throws ParseException
429 	//====================================================================================================
430 	@SuppressWarnings("unchecked")
431 	@Test void getObject1() {
432 		var c = init(
433 			"a1={foo:123}",
434 			"a2=[{foo:123}]",
435 			"a3=",
436 			"a4=\t{",
437 			"\t foo : 123 /* comment */",
438 			"\t}"
439 			);
440 
441 		var a1 = (Map<String,Integer>) c.get("a1").as(Map.class, String.class, Integer.class).get();
442 		assertJson("{foo:123}", a1);
443 		assertInstanceOf(String.class, a1.keySet().iterator().next());
444 		assertInstanceOf(Integer.class, a1.values().iterator().next());
445 
446 		List<Map<String,Integer>> a2a = (List<Map<String,Integer>>) c.get("a2").as(List.class, Map.class, String.class, Integer.class).get();
447 		assertJson("[{foo:123}]", a2a);
448 		assertInstanceOf(String.class, a2a.get(0).keySet().iterator().next());
449 		assertInstanceOf(Integer.class, a2a.get(0).values().iterator().next());
450 
451 		var a2b = (List<ABean>) c.get("a2").as(List.class, ABean.class).get();
452 		assertJson("[{foo:'123'}]", a2b);
453 		assertInstanceOf(ABean.class, a2b.get(0));
454 
455 		var a3 = (Map<String,Integer>) c.get("a3").as(Map.class, String.class, Integer.class).orElse(null);
456 		assertNull(a3);
457 
458 		var a4a = (Map<String,Integer>) c.get("a4").as(Map.class, String.class, Integer.class).get();
459 		assertJson("{foo:123}", a4a);
460 		assertInstanceOf(String.class, a4a.keySet().iterator().next());
461 		assertInstanceOf(Integer.class, a4a.values().iterator().next());
462 
463 		var a4b = c.get("a4").as(ABean.class).get();
464 		assertJson("{foo:'123'}", a4b);
465 		assertInstanceOf(ABean.class, a4b);
466 	}
467 
468 	//====================================================================================================
469 	//	public <T> T getObject(String key, Parser parser, Type type, Type...args) throws ParseException
470 	//====================================================================================================
471 	@SuppressWarnings("unchecked")
472 	@Test void getObject2() {
473 		var c = init(
474 			"a1=(foo=123)",
475 			"a2=@((foo=123))",
476 			"a3=",
477 			"a4=\t(",
478 			"\t foo = 123",
479 			"\t)"
480 			);
481 
482 		var a1 = (Map<String,Integer>) c.get("a1").as(UonParser.DEFAULT, Map.class, String.class, Integer.class).get();
483 		assertJson("{foo:123}", a1);
484 		assertInstanceOf(String.class, a1.keySet().iterator().next());
485 		assertInstanceOf(Integer.class, a1.values().iterator().next());
486 
487 		var a2a = (List<Map<String,Integer>>) c.get("a2").as(UonParser.DEFAULT, List.class, Map.class, String.class, Integer.class).get();
488 		assertJson("[{foo:123}]", a2a);
489 		assertInstanceOf(String.class, a2a.get(0).keySet().iterator().next());
490 		assertInstanceOf(Integer.class, a2a.get(0).values().iterator().next());
491 
492 		var a2b = (List<ABean>) c.get("a2").as(UonParser.DEFAULT, List.class, ABean.class).get();
493 		assertJson("[{foo:'123'}]", a2b);
494 		assertInstanceOf(ABean.class, a2b.get(0));
495 
496 		var a3 = (Map<String,Integer>) c.get("a3").as(UonParser.DEFAULT, Map.class, String.class, Integer.class).orElse(null);
497 		assertNull(a3);
498 
499 		var a4a = (Map<String,Integer>) c.get("a4").as(UonParser.DEFAULT, Map.class, String.class, Integer.class).get();
500 		assertJson("{foo:123}", a4a);
501 		assertInstanceOf(String.class, a4a.keySet().iterator().next());
502 		assertInstanceOf(Integer.class, a4a.values().iterator().next());
503 
504 		var a4b = c.get("a4").as(UonParser.DEFAULT, ABean.class).get();
505 		assertJson("{foo:'123'}", a4b);
506 		assertInstanceOf(ABean.class, a4b);
507 	}
508 
509 	//====================================================================================================
510 	//	public <T> T getObject(String key, Class<T> type) throws ParseException
511 	//====================================================================================================
512 	@SuppressWarnings("rawtypes")
513 	@Test void getObject3() {
514 		var c = init(
515 			"a1={foo:123}",
516 			"a2=[{foo:123}]",
517 			"a3=",
518 			"a4=\t{",
519 			"\t foo : 123 /* comment */",
520 			"\t}"
521 			);
522 
523 		var a1 = c.get("a1").as(Map.class).get();
524 		assertJson("{foo:123}", a1);
525 		assertInstanceOf(String.class, a1.keySet().iterator().next());
526 		assertInstanceOf(Integer.class, a1.values().iterator().next());
527 
528 		var a2a = c.get("a2").as(List.class).get();
529 		assertJson("[{foo:123}]", a2a);
530 		assertInstanceOf(String.class, ((Map)a2a.get(0)).keySet().iterator().next());
531 		assertInstanceOf(Integer.class, ((Map)a2a.get(0)).values().iterator().next());
532 
533 		var a3 = c.get("a3").as(Map.class).orElse(null);
534 		assertNull(a3);
535 
536 		var a4a = c.get("a4").as(Map.class).get();
537 		assertJson("{foo:123}", a4a);
538 		assertInstanceOf(String.class, a4a.keySet().iterator().next());
539 		assertInstanceOf(Integer.class, a4a.values().iterator().next());
540 
541 		var a4b = c.get("a4").as(ABean.class).orElse(null);
542 		assertJson("{foo:'123'}", a4b);
543 		assertInstanceOf(ABean.class, a4b);
544 	}
545 
546 	//====================================================================================================
547 	//	public <T> T getObject(String key, Parser parser, Class<T> type) throws ParseException
548 	//====================================================================================================
549 	@SuppressWarnings("rawtypes")
550 	@Test void getObject4() {
551 		var c = init(
552 			"a1=(foo=123)",
553 			"a2=@((foo=123))",
554 			"a3=",
555 			"a4=\t(",
556 			"\t foo = 123",
557 			"\t)"
558 		);
559 
560 		var a1 = c.get("a1").as(UonParser.DEFAULT, Map.class).get();
561 		assertJson("{foo:123}", a1);
562 		assertInstanceOf(String.class, a1.keySet().iterator().next());
563 		assertInstanceOf(Integer.class, a1.values().iterator().next());
564 
565 		var a2a = c.get("a2").as(UonParser.DEFAULT, List.class).get();
566 		assertJson("[{foo:123}]", a2a);
567 		assertInstanceOf(String.class, ((Map)a2a.get(0)).keySet().iterator().next());
568 		assertInstanceOf(Integer.class, ((Map)a2a.get(0)).values().iterator().next());
569 
570 		var a3 = c.get("a3").as(UonParser.DEFAULT, Map.class).orElse(null);
571 		assertNull(a3);
572 
573 		var a4a = c.get("a4").as(UonParser.DEFAULT, Map.class).get();
574 		assertJson("{foo:123}", a4a);
575 		assertInstanceOf(String.class, a4a.keySet().iterator().next());
576 		assertInstanceOf(Integer.class, a4a.values().iterator().next());
577 
578 		var a4b = c.get("a4").as(UonParser.DEFAULT, ABean.class).get();
579 		assertJson("{foo:'123'}", a4b);
580 		assertInstanceOf(ABean.class, a4b);
581 	}
582 
583 	//====================================================================================================
584 	//	public <T> T getObjectWithDefault(String key, T def, Class<T> type) throws ParseException
585 	//====================================================================================================
586 	@SuppressWarnings("rawtypes")
587 	@Test void getObjectWithDefault1() {
588 		var c = init(
589 			"a1={foo:123}",
590 			"a2=[{foo:123}]",
591 			"a3=",
592 			"a4=\t{",
593 			"\t foo : 123 /* comment */",
594 			"\t}"
595 		);
596 
597 		var a1 = c.get("a1").as(Map.class).orElseGet(JsonMap::new);
598 		assertJson("{foo:123}", a1);
599 		assertInstanceOf(String.class, a1.keySet().iterator().next());
600 		assertInstanceOf(Integer.class, a1.values().iterator().next());
601 
602 		var a1b = c.get("a1b").as(Map.class).orElseGet(JsonMap::new);
603 		assertJson("{}", a1b);
604 
605 		var a2a = c.get("a2").as(List.class).orElseGet(JsonList::new);
606 		assertJson("[{foo:123}]", a2a);
607 		assertInstanceOf(String.class, ((Map)a2a.get(0)).keySet().iterator().next());
608 		assertInstanceOf(Integer.class, ((Map)a2a.get(0)).values().iterator().next());
609 
610 		var a2b = c.get("a2b").as(List.class).orElseGet(JsonList::new);
611 		assertJson("[]", a2b);
612 
613 		var a3 = c.get("a3").as(Map.class).orElseGet(JsonMap::new);
614 		assertJson("{}", a3);
615 
616 		var a4a = c.get("a4").as(Map.class).orElseGet(JsonMap::new);
617 		assertJson("{foo:123}", a4a);
618 		assertInstanceOf(String.class, a4a.keySet().iterator().next());
619 		assertInstanceOf(Integer.class, a4a.values().iterator().next());
620 
621 		var a4b = c.get("a4b").as(Map.class).orElseGet(JsonMap::new);
622 		assertJson("{}", a4b);
623 
624 		var a4c = c.get("a4c").as(ABean.class).orElse(new ABean().init());
625 		assertJson("{foo:'bar'}", a4c);
626 		assertInstanceOf(ABean.class, a4c);
627 	}
628 
629 	//====================================================================================================
630 	//	public <T> T getObjectWithDefault(String key, Parser parser, T def, Class<T> type) throws ParseException
631 	//====================================================================================================
632 	@SuppressWarnings("rawtypes")
633 	@Test void getObjectWithDefault2() {
634 		var c = init(
635 			"a1=(foo=123)",
636 			"a2=@((foo=123))",
637 			"a3=",
638 			"a4=\t(",
639 			"\t foo = 123",
640 			"\t)"
641 		);
642 
643 		var a1 = c.get("a1").as(UonParser.DEFAULT, Map.class).orElseGet(JsonMap::new);
644 		assertJson("{foo:123}", a1);
645 		assertInstanceOf(String.class, a1.keySet().iterator().next());
646 		assertInstanceOf(Integer.class, a1.values().iterator().next());
647 
648 		var a1b = c.get("a1b").as(UonParser.DEFAULT, Map.class).orElseGet(JsonMap::new);
649 		assertJson("{}", a1b);
650 
651 		var a2a = c.get("a2").as(UonParser.DEFAULT, List.class).orElseGet(JsonList::new);
652 		assertJson("[{foo:123}]", a2a);
653 		assertInstanceOf(String.class, ((Map)a2a.get(0)).keySet().iterator().next());
654 		assertInstanceOf(Integer.class, ((Map)a2a.get(0)).values().iterator().next());
655 
656 		var a2b = c.get("a2b").as(UonParser.DEFAULT, List.class).orElseGet(JsonList::new);
657 		assertJson("[]", a2b);
658 
659 		var a3 = c.get("a3").as(UonParser.DEFAULT, Map.class).orElseGet(JsonMap::new);
660 		assertJson("{}", a3);
661 
662 		var a4a = c.get("a4").as(UonParser.DEFAULT, Map.class).orElseGet(JsonMap::new);
663 		assertJson("{foo:123}", a4a);
664 		assertInstanceOf(String.class, a4a.keySet().iterator().next());
665 		assertInstanceOf(Integer.class, a4a.values().iterator().next());
666 
667 		var a4b = c.get("a4b").as(UonParser.DEFAULT, Map.class).orElseGet(JsonMap::new);
668 		assertJson("{}", a4b);
669 
670 		var a4c = c.get("a4c").as(UonParser.DEFAULT, ABean.class).orElse(new ABean().init());
671 		assertJson("{foo:'bar'}", a4c);
672 		assertInstanceOf(ABean.class, a4c);
673 	}
674 
675 	//====================================================================================================
676 	//	public <T> T getObjectWithDefault(String key, T def, Type type, Type...args) throws ParseException
677 	//====================================================================================================
678 	@SuppressWarnings("unchecked")
679 	@Test void getObjectWithDefault3() {
680 		var c = init(
681 			"a1={foo:123}",
682 			"a2=[{foo:123}]",
683 			"a3=",
684 			"a4=\t{",
685 			"\t foo : 123 /* comment */",
686 			"\t}"
687 		);
688 
689 		var a1 = (Map<String,Integer>) c.get("a1").as(Map.class, String.class, Integer.class).orElse(new HashMap<>());
690 		assertJson("{foo:123}", a1);
691 		assertInstanceOf(String.class, a1.keySet().iterator().next());
692 		assertInstanceOf(Integer.class, a1.values().iterator().next());
693 
694 		var a1b = (Map<String,Integer>) c.get("a1b").as(Map.class, String.class, Integer.class).orElse(new HashMap<>());
695 		assertJson("{}", a1b);
696 
697 		var a2a = (List<Map<String,Integer>>) c.get("a2").as(List.class, Map.class, String.class, Integer.class).orElse(new ArrayList<>());
698 		assertJson("[{foo:123}]", a2a);
699 		assertInstanceOf(String.class, a2a.get(0).keySet().iterator().next());
700 		assertInstanceOf(Integer.class, a2a.get(0).values().iterator().next());
701 
702 		var a2b = (List<ABean>) c.get("a2b").as(List.class, ABean.class).orElse(new ArrayList<>());
703 		assertJson("[]", a2b);
704 
705 		var a3 = (Map<String,Object>) c.get("a3").as(Map.class, String.class, Object.class).orElse(new JsonMap());
706 		assertJson("{}", a3);
707 
708 		var a4a = (Map<String,Integer>) c.get("a4").as(Map.class, String.class, Integer.class).orElse(new HashMap<>());
709 		assertJson("{foo:123}", a4a);
710 		assertInstanceOf(String.class, a4a.keySet().iterator().next());
711 		assertInstanceOf(Integer.class, a4a.values().iterator().next());
712 
713 		var a4b = (Map<String,Integer>) c.get("a4b").as(Map.class, String.class, Integer.class).orElse(new HashMap<>());
714 		assertJson("{}", a4b);
715 
716 		var a4c = c.get("a4c").as(ABean.class).orElse(new ABean().init());
717 		assertJson("{foo:'bar'}", a4c);
718 		assertInstanceOf(ABean.class, a4c);
719 	}
720 
721 	//====================================================================================================
722 	//	public <T> T getObjectWithDefault(String key, Parser parser, T def, Type type, Type...args) throws ParseException
723 	//====================================================================================================
724 	@SuppressWarnings("unchecked")
725 	@Test void getObjectWithDefault4() {
726 		var c = init(
727 			"a1=(foo=123)",
728 			"a2=@((foo=123))",
729 			"a3=",
730 			"a4=\t(",
731 			"\t foo = 123",
732 			"\t)"
733 		);
734 
735 		var a1 = (Map<String,Integer>) c.get("a1").as(UonParser.DEFAULT, Map.class, String.class, Integer.class).orElse(new HashMap<>());
736 		assertJson("{foo:123}", a1);
737 		assertInstanceOf(String.class, a1.keySet().iterator().next());
738 		assertInstanceOf(Integer.class, a1.values().iterator().next());
739 
740 		var a1b = (Map<String,Integer>) c.get("a1b").as(UonParser.DEFAULT, Map.class, String.class, Integer.class).orElse(new HashMap<>());
741 		assertJson("{}", a1b);
742 
743 		var a2a = (List<Map<String,Integer>>) c.get("a2").as(UonParser.DEFAULT, List.class, Map.class, String.class, Integer.class).orElse(new ArrayList<>());
744 		assertJson("[{foo:123}]", a2a);
745 		assertInstanceOf(String.class, a2a.get(0).keySet().iterator().next());
746 		assertInstanceOf(Integer.class, a2a.get(0).values().iterator().next());
747 
748 		var a2b = (List<ABean>) c.get("a2b").as(UonParser.DEFAULT, List.class, ABean.class).orElse(new ArrayList<>());
749 		assertJson("[]", a2b);
750 
751 		var a3 = (Map<String,Object>) c.get("a3").as(UonParser.DEFAULT,Map.class, String.class, Object.class).orElse( new JsonMap());
752 		assertJson("{}", a3);
753 
754 		var a4a = (Map<String,Integer>) c.get("a4").as(UonParser.DEFAULT, Map.class, String.class, Integer.class).orElse(new HashMap<>());
755 		assertJson("{foo:123}", a4a);
756 		assertInstanceOf(String.class, a4a.keySet().iterator().next());
757 		assertInstanceOf(Integer.class, a4a.values().iterator().next());
758 
759 		var a4b = (Map<String,Integer>) c.get("a4b").as(UonParser.DEFAULT, Map.class, String.class, Integer.class).orElse(new HashMap<>());
760 		assertJson("{}", a4b);
761 
762 		var a4c = c.get("a4c").as(UonParser.DEFAULT, ABean.class).orElse(new ABean().init());
763 		assertJson("{foo:'bar'}", a4c);
764 		assertInstanceOf(ABean.class, a4c);
765 	}
766 
767 	//====================================================================================================
768 	//	public Set<String> getKeys(String section)
769 	//====================================================================================================
770 	@Test void getKeys() {
771 		var c = init("a1=1", "a2=2", "[S]", "b1=1", "b2=");
772 
773 		assertJson("['a1','a2']", c.getKeys(""));
774 		assertJson("['a1','a2']", c.getKeys(""));
775 		assertJson("['b1','b2']", c.getKeys("S"));
776 		assertTrue(c.getKeys("T").isEmpty());
777 
778 		assertThrowsWithMessage(IllegalArgumentException.class, "Argument 'section' cannot be null.", ()->c.getKeys(null));
779 	}
780 
781 	//====================================================================================================
782 	//	public Config writeProperties(String section, Object bean, boolean ignoreUnknownProperties)
783 	//====================================================================================================
784 	@Test void writeProperties() {
785 		var a = new ABean().init();
786 		var b = new BBean().init();
787 
788 		var c = init("foo=qux", "[S]", "foo=baz", "bar=baz");
789 		c.getSection("S").writeToBean(a, true);
790 		assertJson("{foo:'baz'}", a);
791 		c.getSection("S").writeToBean(b, true);
792 		assertJson("{foo:'baz'}", b);
793 		assertThrowsWithMessage(ParseException.class, "Unknown property 'bar' encountered in configuration section 'S'.", ()->c.getSection("S").writeToBean(a, false));
794 		assertThrowsWithMessage(ParseException.class, "Unknown property 'bar' encountered in configuration section 'S'.", ()->c.getSection("S").writeToBean(b, false));
795 		c.getSection("").writeToBean(b, true);
796 		assertJson("{foo:'qux'}", b);
797 		c.getSection("").writeToBean(a, true);
798 		assertJson("{foo:'qux'}", a);
799 		c.getSection(null).writeToBean(a, true);
800 		assertJson("{foo:'qux'}", a);
801 	}
802 
803 	//====================================================================================================
804 	//	public <T> T getSectionAsBean(String sectionName, Class<T>c)
805 	//====================================================================================================
806 	@Test void getSectionAsBean1() {
807 		var c = init("foo=qux", "[S]", "foo=baz", "[T]", "foo=qux", "bar=qux");
808 
809 		var a = c.getSection("").asBean(ABean.class).get();
810 		assertJson("{foo:'qux'}", a);
811 		a = c.getSection("").asBean(ABean.class).get();
812 		assertJson("{foo:'qux'}", a);
813 		a = c.getSection(null).asBean(ABean.class).get();
814 		assertJson("{foo:'qux'}", a);
815 		a = c.getSection("S").asBean(ABean.class).get();
816 		assertJson("{foo:'baz'}", a);
817 
818 		var b = c.getSection("").asBean(BBean.class).get();
819 		assertJson("{foo:'qux'}", b);
820 		b = c.getSection("").asBean(BBean.class).get();
821 		assertJson("{foo:'qux'}", b);
822 		b = c.getSection("S").asBean(BBean.class).get();
823 		assertJson("{foo:'baz'}", b);
824 
825 		assertThrowsWithMessage(ParseException.class, "Unknown property 'bar' encountered in configuration section 'T'.", ()->c.getSection("T").asBean(ABean.class).get());
826 		assertThrowsWithMessage(ParseException.class, "Unknown property 'bar' encountered in configuration section 'T'.", ()->c.getSection("T").asBean(BBean.class).get());
827 	}
828 
829 	//====================================================================================================
830 	//	public <T> T getSectionAsBean(String section, Class<T> c, boolean ignoreUnknownProperties)
831 	//====================================================================================================
832 	@Test void getSectionAsBean2() {
833 		var c = init("foo=qux", "[S]", "foo=baz", "[T]", "foo=qux", "bar=qux");
834 
835 		var a = c.getSection("T").asBean(ABean.class, true).get();
836 		assertJson("{foo:'qux'}", a);
837 		var b = c.getSection("T").asBean(BBean.class, true).get();
838 		assertJson("{foo:'qux'}", b);
839 
840 		assertThrowsWithMessage(ParseException.class, "Unknown property 'bar' encountered in configuration section 'T'.", ()->c.getSection("T").asBean(ABean.class, false).get());
841 		assertThrowsWithMessage(ParseException.class, "Unknown property 'bar' encountered in configuration section 'T'.", ()->c.getSection("T").asBean(BBean.class, false).get());
842 	}
843 
844 	//====================================================================================================
845 	//	public JsonMap getSectionAsMap(String section)
846 	//====================================================================================================
847 	@Test void getSectionAsMap() {
848 		var c = init("a=1", "[S]", "b=2", "[T]");
849 
850 		assertJson("{a:'1'}", c.getSection("").asMap().get());
851 		assertJson("{a:'1'}", c.getSection("").asMap().get());
852 		assertJson("{a:'1'}", c.getSection(null).asMap().get());
853 		assertJson("{b:'2'}", c.getSection("S").asMap().get());
854 		assertJson("{}", c.getSection("T").asMap().get());
855 		assertFalse(c.getSection("U").asMap().isPresent());
856 	}
857 
858 	//====================================================================================================
859 	//	public <T> T getSectionAsInterface(final String sectionName, final Class<T> c)
860 	//====================================================================================================
861 	@Test void getSectionAsInterface() {
862 		var c = init("foo=qux", "[S]", "foo=baz", "[T]", "foo=qux", "bar=qux");
863 
864 		var a = c.getSection("").asInterface(AInterface.class).get();
865 		assertEquals("qux", a.getFoo());
866 
867 		a = c.getSection("").asInterface(AInterface.class).get();
868 		assertEquals("qux", a.getFoo());
869 
870 		a = c.getSection(null).asInterface(AInterface.class).get();
871 		assertEquals("qux", a.getFoo());
872 
873 		a = c.getSection("S").asInterface(AInterface.class).get();
874 		assertEquals("baz", a.getFoo());
875 
876 		a = c.getSection("T").asInterface(AInterface.class).get();
877 		assertEquals("qux", a.getFoo());
878 
879 		assertThrowsWithMessage(IllegalArgumentException.class, "Class 'org.apache.juneau.config.Config_Test$ABean' passed to toInterface() is not an interface.", ()->c.getSection("T").asInterface(ABean.class).get());
880 	}
881 
882 	public interface AInterface {
883 		String getFoo();
884 	}
885 
886 	//====================================================================================================
887 	//	public boolean exists(String key)
888 	//====================================================================================================
889 	@Test void exists() {
890 		var c = init("a=1", "[S]", "b=2", "c=", "[T]");
891 
892 		assertTrue(c.exists("a"));
893 		assertFalse(c.exists("b"));
894 		assertTrue(c.exists("S/b"));
895 		assertFalse(c.exists("S/c"));
896 		assertFalse(c.exists("T/d"));
897 		assertFalse(c.exists("U/e"));
898 	}
899 
900 	//====================================================================================================
901 	//	public Config setSection(String name, List<String> preLines)
902 	//====================================================================================================
903 	@Test void setSection1() {
904 		var c = init();
905 
906 		c.setSection("", Arrays.asList("#C1", "#C2"));
907 		assertEquals("#C1|#C2||", pipedLines(c));
908 
909 		c.setSection("", Arrays.asList("#C3", "#C4"));
910 		assertEquals("#C3|#C4||", pipedLines(c));
911 
912 		c.setSection("S1", Arrays.asList("", "#C5", "#C6"));
913 		assertEquals("#C3|#C4|||#C5|#C6|[S1]|", pipedLines(c));
914 
915 		c.setSection("S1", null);
916 		assertEquals("#C3|#C4|||#C5|#C6|[S1]|", pipedLines(c));
917 
918 		c.setSection("S1", Collections.<String>emptyList());
919 		assertEquals("#C3|#C4||[S1]|", pipedLines(c));
920 
921 		assertThrowsWithMessage(IllegalArgumentException.class, "Argument 'section' cannot be null.", ()->c.setSection(null, Arrays.asList("", "#C5", "#C6")));
922 	}
923 
924 	//====================================================================================================
925 	//	public Config setSection(String name, List<String> preLines, Map<String,Object> contents)
926 	//====================================================================================================
927 	@Test void setSection2() {
928 		var c = init();
929 		var m = JsonMap.of("a", "b");
930 
931 		c.setSection("", Arrays.asList("#C1", "#C2"), m);
932 		assertEquals("#C1|#C2||a = b|", pipedLines(c));
933 
934 		c.setSection("", Arrays.asList("#C3", "#C4"), m);
935 		assertEquals("#C3|#C4||a = b|", pipedLines(c));
936 
937 		c.setSection("S1", Arrays.asList("", "#C5", "#C6"), m);
938 		assertEquals("#C3|#C4||a = b||#C5|#C6|[S1]|a = b|", pipedLines(c));
939 
940 		c.setSection("S1", null, m);
941 		assertEquals("#C3|#C4||a = b||#C5|#C6|[S1]|a = b|", pipedLines(c));
942 
943 		c.setSection("S1", Collections.<String>emptyList(), m);
944 		assertEquals("#C3|#C4||a = b|[S1]|a = b|", pipedLines(c));
945 
946 		assertThrowsWithMessage(IllegalArgumentException.class, "Argument 'section' cannot be null.", ()->c.setSection(null, Arrays.asList("", "#C5", "#C6"), m));
947 	}
948 
949 	//====================================================================================================
950 	//	public Config removeSection(String name)
951 	//====================================================================================================
952 	@Test void removeSection() {
953 		var c = init("a=1", "[S]", "b=2", "c=", "[T]");
954 
955 		c.removeSection("S");
956 		c.removeSection("T");
957 
958 		assertEquals("a=1|", pipedLines(c));
959 
960 		c.removeSection("");
961 		assertEquals("", pipedLines(c));
962 	}
963 
964 	//====================================================================================================
965 	//	public Writer writeTo(Writer w)
966 	//====================================================================================================
967 	@Test void writeTo() throws Exception {
968 		var c = init("a=1", "[S]", "b=2", "c=", "[T]");
969 
970 		assertEquals("a=1|[S]|b=2|c=|[T]|", pipedLines(c.writeTo(new StringWriter())));
971 	}
972 
973 	//====================================================================================================
974 	// testExampleInConfig - Example in Config
975 	//====================================================================================================
976 	@Test void a01_exampleInConfig() throws Exception {
977 
978 		var cf = init(
979 			"# Default section", "key1 = 1", "key2 = true", "key3 = [1,2,3]", "key4 = http://foo", "",
980 			"# section1", "# Section 1",
981 			"[section1]", "key1 = 2", "key2 = false", "key3 = [4,5,6]", "key4 = http://bar"
982 		);
983 
984 		assertEquals(1, cf.get("key1").asInteger().get());
985 		assertTrue(cf.get("key2").asBoolean().get());
986 		assertEquals(3, cf.get("key3").as(int[].class).get()[2]);
987 		assertEquals(6, cf.get("xkey3").as(int[].class).orElse(new int[]{4,5,6})[2]);
988 		assertEquals(6, cf.get("X/key3").as(int[].class).orElse(new int[]{4,5,6})[2]);
989 		assertEquals(url("http://foo").toString(), cf.get("key4").as(URL.class).get().toString());
990 
991 		assertEquals(2, cf.get("section1/key1").asInteger().get());
992 		assertFalse(cf.get("section1/key2").asBoolean().get());
993 		assertEquals(6, cf.get("section1/key3").as(int[].class).get()[2]);
994 		assertEquals(url("http://bar").toString(), cf.get("section1/key4").as(URL.class).get().toString());
995 
996 		cf = init(
997 			"# Default section",
998 			"[section1]", "# Section 1"
999 		);
1000 
1001 		cf.set("key1", 1);
1002 		cf.set("key2", true);
1003 		cf.set("key3", new int[]{1,2,3});
1004 		cf.set("key4", url("http://foo"));
1005 		cf.set("section1/key1", 2);
1006 		cf.set("section1/key2", false);
1007 		cf.set("section1/key3", new int[]{4,5,6});
1008 		cf.set("section1/key4", url("http://bar"));
1009 
1010 		cf.commit();
1011 
1012 		assertEquals(1, cf.get("key1").asInteger().get());
1013 		assertTrue(cf.get("key2").asBoolean().get());
1014 		assertEquals(3, cf.get("key3").as(int[].class).get()[2]);
1015 		assertEquals(url("http://foo").toString(), cf.get("key4").as(URL.class).get().toString());
1016 
1017 		assertEquals(2, cf.get("section1/key1").asInteger().get());
1018 		assertFalse(cf.get("section1/key2").asBoolean().get());
1019 		assertEquals(6, cf.get("section1/key3").as(int[].class).get()[2]);
1020 		assertEquals(url("http://bar").toString(), cf.get("section1/key4").as(URL.class).get().toString());
1021 	}
1022 
1023 	//====================================================================================================
1024 	// testEnum
1025 	//====================================================================================================
1026 	@Test void a02_enum() throws Exception {
1027 		var cf = init(
1028 			"key1 = MINUTES"
1029 		);
1030 		assertEquals(TimeUnit.MINUTES, cf.get("key1").as(TimeUnit.class).get());
1031 
1032 		cf.commit();
1033 
1034 		assertEquals(TimeUnit.MINUTES, cf.get("key1").as(TimeUnit.class).get());
1035 	}
1036 
1037 	//====================================================================================================
1038 	// testEncodedValues
1039 	//====================================================================================================
1040 	@Test void a03_encodedValues() throws Exception {
1041 		var cf = init(
1042 			"[s1]", "", "foo<*> = "
1043 		);
1044 
1045 		cf.set("s1/foo", "mypassword");
1046 
1047 		assertEquals("mypassword", cf.get("s1/foo").get());
1048 
1049 		cf.commit();
1050 
1051 		assertEquals("[s1]||foo<*> = {AwwJVhwUQFZEMg==}|", pipedLines(cf));
1052 
1053 		assertEquals("mypassword", cf.get("s1/foo").get());
1054 
1055 		cf.load(reader("[s1]\nfoo<*> = mypassword2\n"), true);
1056 
1057 		assertEquals("mypassword2", cf.get("s1/foo").get());
1058 
1059 		cf.set("s1/foo", "mypassword");
1060 
1061 		// INI output should be encoded
1062 		var sw = new StringWriter();
1063 		cf.writeTo(new PrintWriter(sw));
1064 		assertEquals("[s1]|foo<*> = {AwwJVhwUQFZEMg==}|", pipedLines(sw));
1065 	}
1066 
1067 	//====================================================================================================
1068 	// public Config encodeEntries()
1069 	//====================================================================================================
1070 	@Test void a04_encodeEntries() throws Exception {
1071 		var cf = init(
1072 			"[s1]", "", "foo<*> = mypassword"
1073 		);
1074 
1075 		cf.applyMods();
1076 		cf.commit();
1077 		assertEquals("[s1]||foo<*> = {AwwJVhwUQFZEMg==}|", pipedLines(MemoryStore.DEFAULT.read("Test.cfg")));
1078 	}
1079 
1080 	//====================================================================================================
1081 	// testVariables
1082 	//====================================================================================================
1083 	@Test void a05_variables() {
1084 
1085 		var cf = init(
1086 			"[s1]",
1087 			"f1 = $S{foo}",
1088 			"f2 = $S{foo,bar}",
1089 			"f3 = $S{$S{baz,bing},bar}"
1090 		);
1091 
1092 		System.getProperties().remove("foo");
1093 		System.getProperties().remove("bar");
1094 		System.getProperties().remove("baz");
1095 		System.getProperties().remove("bing");
1096 
1097 		assertEquals("", cf.get("s1/f1").get());
1098 		assertEquals("bar", cf.get("s1/f2").get());
1099 		assertEquals("bar", cf.get("s1/f3").get());
1100 
1101 		System.setProperty("foo", "123");
1102 		assertEquals("123", cf.get("s1/f1").get());
1103 		assertEquals("123", cf.get("s1/f2").get());
1104 		assertEquals("bar", cf.get("s1/f3").get());
1105 
1106 		System.setProperty("foo", "$S{bar}");
1107 		System.setProperty("bar", "baz");
1108 		assertEquals("baz", cf.get("s1/f1").get());
1109 		assertEquals("baz", cf.get("s1/f2").get());
1110 		assertEquals("bar", cf.get("s1/f3").get());
1111 
1112 		System.setProperty("bing", "$S{foo}");
1113 		assertEquals("baz", cf.get("s1/f3").get());
1114 
1115 		System.setProperty("baz", "foo");
1116 		System.setProperty("foo", "123");
1117 		assertEquals("123", cf.get("s1/f3").get());
1118 	}
1119 
1120 	//====================================================================================================
1121 	// testXorEncoder
1122 	//====================================================================================================
1123 	@Test void a06_xorEncoder() {
1124 		testXor("foo");
1125 		testXor("");
1126 		testXor("123");
1127 		testXor("€");  // 3-byte UTF-8 character
1128 		testXor("𤭢"); // 4-byte UTF-8 character
1129 	}
1130 
1131 	private void testXor(String in) {
1132 		var e = new XorEncodeMod();
1133 		var s = e.apply(in);
1134 		var s2 = e.remove(s);
1135 		assertEquals(in, s2);
1136 	}
1137 
1138 	//====================================================================================================
1139 	// testHex
1140 	//====================================================================================================
1141 	@Test void a07_hex() throws Exception {
1142 		var cf = init().copy().binaryFormat(BinaryFormat.HEX).build();
1143 
1144 		cf.set("foo", "bar".getBytes("UTF-8"));
1145 		assertEquals("626172", cf.get("foo").getValue());
1146 		assertJson("[98,97,114]", cf.get("foo").asBytes().get());
1147 	}
1148 
1149 	//====================================================================================================
1150 	// testSpacedHex
1151 	//====================================================================================================
1152 	@Test void a08_spacedHex() throws Exception {
1153 		var cf = init().copy().binaryFormat(BinaryFormat.SPACED_HEX).build();
1154 
1155 		cf.set("foo", "bar".getBytes("UTF-8"));
1156 		assertEquals("62 61 72", cf.get("foo").getValue());
1157 		assertJson("[98,97,114]", cf.get("foo").asBytes().get());
1158 	}
1159 
1160 	//====================================================================================================
1161 	// testMultiLines
1162 	//====================================================================================================
1163 	@Test void a09_multiLines() throws Exception {
1164 		var cf = init(
1165 			"[s1]",
1166 			"f1 = x \n\ty \n\tz"
1167 		);
1168 
1169 		assertEquals("x \ny \nz", cf.get("s1/f1").get());
1170 
1171 		var sw = new StringWriter();
1172 		cf.writeTo(sw);
1173 		assertEquals("[s1]|f1 = x |\ty |\tz|", pipedLines(sw));
1174 	}
1175 
1176 	//====================================================================================================
1177 	// testNumberShortcuts
1178 	//====================================================================================================
1179 	@Test void a10_numberShortcuts() {
1180 		var cf = init(
1181 			"[s1]",
1182 			"f1 = 1M",
1183 			"f2 = 1K",
1184 			"f3 = 1 M",
1185 			"f4 = 1 K"
1186 		);
1187 		assertEquals(1048576, cf.get("s1/f1").asInteger().get());
1188 		assertEquals(1024, cf.get("s1/f2").asInteger().get());
1189 		assertEquals(1048576, cf.get("s1/f3").asInteger().get());
1190 		assertEquals(1024, cf.get("s1/f4").asInteger().get());
1191 	}
1192 
1193 	//====================================================================================================
1194 	// testListeners
1195 	//====================================================================================================
1196 	@Test void a11_listeners() throws Exception {
1197 		var cf = init();
1198 
1199 		final var changes = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
1200 
1201 		cf.addListener(events -> {
1202 			for (var ce : events) {
1203 				var key = (ce.getSection().isEmpty() ? "" : (ce.getSection() + '/')) + ce.getKey();
1204 				if (ce.getType() == ConfigEventType.REMOVE_ENTRY) {
1205 					changes.add("REMOVE_ENTRY(" + key + ")");
1206 				} else if (ce.getType() == ConfigEventType.REMOVE_SECTION) {
1207 					changes.add("REMOVE_SECTION(" + ce.getSection() + ")");
1208 				} else if (ce.getType() == ConfigEventType.SET_SECTION) {
1209 					changes.add("SET_SECTION(" + ce.getSection() + ")");
1210 				} else {
1211 					changes.add(key + '=' + ce.getValue());
1212 				}
1213 			}
1214 		});
1215 
1216 		// No changes until save.
1217 		changes.clear();
1218 		cf.set("a1", 3);
1219 		cf.set("a3", 3);
1220 		cf.set("B/b1", 3);
1221 		cf.set("B/b3", 3);
1222 		assertJson("[]", changes);
1223 		cf.commit();
1224 		assertJson("['a1=3','a3=3','B/b1=3','B/b3=3']", changes);
1225 
1226 		// Rollback.
1227 		changes.clear();
1228 		cf.set("a1", 3);
1229 		cf.set("a3", 3);
1230 		cf.set("B/b1", 3);
1231 		cf.set("B/b3", 3);
1232 		assertJson("[]", changes);
1233 		cf.rollback();
1234 		cf.commit();
1235 		assertJson("[]", changes);
1236 
1237 		// Overwrite
1238 		changes.clear();
1239 		cf.set("a1", "2");
1240 		cf.set("B/b1", "2");
1241 		cf.set("a2", "2");
1242 		cf.set("B/b2", "2");
1243 		cf.set("C/c1", "2");
1244 		cf.set("C/c2", "2");
1245 		cf.commit();
1246 		assertJson("['a1=2','a2=2','B/b1=2','B/b2=2','C/c1=2','C/c2=2']", changes);
1247 
1248 		// Encoded
1249 		changes.clear();
1250 		cf.set("a4", "4", null, "*", null, null);
1251 		cf.set("B/b4", "4", null, "*", null, null);
1252 		cf.commit();
1253 		assertJson("['a4={Wg==}','B/b4={Wg==}']", changes);
1254 
1255 		// Encoded overwrite
1256 		changes.clear();
1257 		cf.set("a4", "5");
1258 		cf.set("B/b4", "5");
1259 		cf.commit();
1260 		assertJson("['a4={Ww==}','B/b4={Ww==}']", changes);
1261 
1262 		// Remove entries
1263 		changes.clear();
1264 		cf.remove("a4");
1265 		cf.remove("ax");
1266 		cf.remove("B/b4");
1267 		cf.remove("B/bx");
1268 		cf.remove("X/bx");
1269 		cf.commit();
1270 		assertJson("['REMOVE_ENTRY(a4)','REMOVE_ENTRY(B/b4)']", changes);
1271 
1272 		// Add section
1273 		// Shouldn't trigger listener.
1274 		changes.clear();
1275 		cf.setSection("D", Arrays.asList("#comment"));
1276 		cf.commit();
1277 		assertJson("[]", changes);
1278 
1279 		// Add section with contents
1280 		changes.clear();
1281 		cf.setSection("E", null, map("e1","1","e2","2"));
1282 		cf.commit();
1283 		assertJson("['E/e1=1','E/e2=2']", changes);
1284 
1285 		// Remove section
1286 		changes.clear();
1287 		cf.removeSection("E");
1288 		cf.commit();
1289 		assertJson("['REMOVE_ENTRY(E/e1)','REMOVE_ENTRY(E/e2)']", changes);
1290 
1291 		// Remove non-existent section
1292 		changes.clear();
1293 		cf.removeSection("E");
1294 		cf.commit();
1295 		assertJson("[]", changes);
1296 	}
1297 
1298 	//====================================================================================================
1299 	// getObjectArray(Class c, String key)
1300 	// getObjectArray(Class c, String key, T[] def)
1301 	//====================================================================================================
1302 	@Test void a12_getObjectArray() {
1303 		var cf = init("[A]", "a1=[1,2,3]");
1304 		assertJson("[1,2,3]", cf.get("A/a1").as(Integer[].class).get());
1305 		assertJson("[4,5,6]", cf.get("A/a2").as(Integer[].class).orElse(new Integer[]{4,5,6}));
1306 		assertJson("[7,8,9]", cf.get("B/a1").as(Integer[].class).orElse(new Integer[]{7,8,9}));
1307 		assertFalse(cf.get("B/a1").as(Integer[].class).isPresent());
1308 
1309 		cf = init("[A]", "a1 = [1 ,\n\t2 ,\n\t3] ");
1310 		assertJson("[1,2,3]", cf.get("A/a1").as(Integer[].class).get());
1311 
1312 		// We cannot cast primitive arrays to Object[], so the following throws exceptions.
1313 		assertJson("[1,2,3]", cf.get("A/a1").as(int[].class).get());
1314 		assertEquals("int", cf.get("A/a1").as(int[].class).get().getClass().getComponentType().getSimpleName());
1315 		assertFalse(cf.get("B/a1").as(int[].class).isPresent());
1316 		assertEquals("int", cf.get("B/a1").as(int[].class).orElse(new int[0]).getClass().getComponentType().getSimpleName());
1317 		assertFalse(cf.get("A/a2").as(int[].class).isPresent());
1318 		assertEquals("int", cf.get("A/a2").as(int[].class).orElse(new int[0]).getClass().getComponentType().getSimpleName());
1319 
1320 		assertJson("[1,2,3]", cf.get("A/a1").as(int[].class).orElse(new int[]{4}));
1321 		assertEquals("int", cf.get("A/a1").as(int[].class).orElse(new int[]{4}).getClass().getComponentType().getSimpleName());
1322 		assertJson("[4]", cf.get("B/a1").as(int[].class).orElse(new int[]{4}));
1323 		assertEquals("int", cf.get("B/a1").as(int[].class).orElse(new int[]{4}).getClass().getComponentType().getSimpleName());
1324 		assertJson("[4]", cf.get("A/a2").as(int[].class).orElse(new int[]{4}));
1325 		assertEquals("int", cf.get("A/a2").as(int[].class).orElse(new int[]{4}).getClass().getComponentType().getSimpleName());
1326 
1327 		System.setProperty("X", "[4,5,6]");
1328 		cf = init("x1=$C{A/a1}", "x2=$S{X}", "x3=$S{Y}", "[A]", "a1=[1,2,3]");
1329 		assertJson("[1,2,3]", cf.get("x1").as(int[].class).orElse(new int[]{9}));
1330 		assertJson("[4,5,6]", cf.get("x2").as(int[].class).orElse(new int[]{9}));
1331 		assertJson("[]", cf.get("x3").as(int[].class).orElse(new int[]{9}));
1332 		System.clearProperty("X");
1333 	}
1334 
1335 	//====================================================================================================
1336 	// getStringArray(String key)
1337 	// getStringArray(String key, String[] def)
1338 	//====================================================================================================
1339 	@Test void a13_getStringArray() {
1340 		var cf = init("[A]", "a1=1,2,3");
1341 		assertJson("['1','2','3']", cf.get("A/a1").asStringArray().get());
1342 		assertJson("['4','5','6']", cf.get("A/a2").asStringArray().orElse(new String[]{"4","5","6"}));
1343 		assertJson("['7','8','9']", cf.get("B/a1").asStringArray().orElse(new String[]{"7","8","9"}));
1344 		assertNull(cf.get("B/a1").asStringArray().orElse(null));
1345 
1346 		cf = init("[A]", "a1 = 1 ,\n\t2 ,\n\t3 ");
1347 		assertJson("['1','2','3']", cf.get("A/a1").asStringArray().get());
1348 
1349 		System.setProperty("X", "4,5,6");
1350 		cf = init(null, "x1=$C{A/a1}", "x2=$S{X}", "x3=$S{Y}", "x4=$S{Y,$S{X}}", "[A]", "a1=1,2,3");
1351 		assertJson("['1','2','3']", cf.get("x1").asStringArray().orElse(new String[]{"9"}));
1352 		assertJson("['4','5','6']", cf.get("x2").asStringArray().orElse(new String[]{"9"}));
1353 		assertJson("['9']", cf.get("x9").asStringArray().orElse(new String[]{"9"}));
1354 
1355 		System.clearProperty("X");
1356 	}
1357 
1358 	//====================================================================================================
1359 	// getSectionMap(String name)
1360 	//====================================================================================================
1361 	@Test void a14_getSectionMap() {
1362 		var cf = init("[A]", "a1=1", "", "[D]", "d1=$C{A/a1}","d2=$S{X}");
1363 
1364 		assertJson("{a1:'1'}", cf.getSection("A").asMap().get());
1365 		assertFalse(cf.getSection("B").asMap().isPresent());
1366 		assertJson("null", cf.getSection("C").asMap().orElse(null));
1367 
1368 		var m = cf.getSection("A").asMap().get();
1369 		assertJson("{a1:'1'}", m);
1370 
1371 		System.setProperty("X", "x");
1372 		m = cf.getSection("D").asMap().get();
1373 		assertJson("{d1:'1',d2:'x'}", m);
1374 		System.clearProperty("X");
1375 	}
1376 
1377 	//====================================================================================================
1378 	// toWritable()
1379 	//====================================================================================================
1380 	@Test void a15_toWritable() throws Exception {
1381 		var cf = init("a = b");
1382 
1383 		var sw = new StringWriter();
1384 		cf.writeTo(sw);
1385 		assertEquals("a = b|", pipedLines(sw));
1386 	}
1387 
1388 
1389 	//====================================================================================================
1390 	// Test resolving with override
1391 	//====================================================================================================
1392 	@Test void a16_resolvingWithOverride() {
1393 		var cf = init();
1394 		cf.set("a", "$A{X}");
1395 		cf.set("b", "$B{X}");
1396 		cf.set("c", "$A{$B{X}}");
1397 		cf.set("d", "$B{$A{X}}");
1398 		cf.set("e", "$D{X}");
1399 
1400 		var vr = VarResolver.create().defaultVars().vars(ALVar.class, BLVar.class).build();
1401 
1402 		cf = cf.resolving(vr.createSession());
1403 
1404 		assertEquals("aXa", cf.get("a").get());
1405 		assertEquals("bXb", cf.get("b").get());
1406 		assertEquals("abXba", cf.get("c").get());
1407 		assertEquals("baXab", cf.get("d").get());
1408 		assertEquals("$D{X}", cf.get("e").get());
1409 
1410 		// Create new resolver that addx $C and overrides $A
1411 		var vr2 = vr.copy().vars(AUVar.class, DUVar.class).build();
1412 
1413 		// true == augment by adding existing as parent to the new resolver
1414 		cf = cf.resolving(vr2.createSession());
1415 		assertEquals("AXA", cf.get("a").get());
1416 		assertEquals("bXb", cf.get("b").get());
1417 		assertEquals("AbXbA", cf.get("c").get());
1418 		assertEquals("bAXAb", cf.get("d").get());
1419 		assertEquals("DXD", cf.get("e").get());
1420 	}
1421 
1422 	public static class ALVar extends SimpleVar {
1423 		public ALVar() {
1424 			super("A");
1425 		}
1426 		@Override
1427 		public String resolve(VarResolverSession session, String key) {
1428 			return 'a' + key + 'a';
1429 		}
1430 	}
1431 
1432 	public static class AUVar extends SimpleVar {
1433 		public AUVar() {
1434 			super("A");
1435 		}
1436 		@Override
1437 		public String resolve(VarResolverSession session, String key) {
1438 			return 'A' + key + 'A';
1439 		}
1440 	}
1441 
1442 	public static class BLVar extends SimpleVar {
1443 		public BLVar() {
1444 			super("B");
1445 		}
1446 		@Override
1447 		public String resolve(VarResolverSession session, String key) {
1448 			return 'b' + key + 'b';
1449 		}
1450 	}
1451 
1452 	public static class DUVar extends SimpleVar {
1453 		public DUVar() {
1454 			super("D");
1455 		}
1456 		@Override
1457 		public String resolve(VarResolverSession session, String key) {
1458 			return 'D' + key + 'D';
1459 		}
1460 	}
1461 
1462 
1463 	//====================================================================================================
1464 	// Ensure pound signs in values are encoded.
1465 	//====================================================================================================
1466 	@Test void a17_poundSignEscape() throws Exception {
1467 		var cf = init();
1468 		cf.set("a", "a,#b,=c");
1469 		cf.set("A/a", "a,#b,=c");
1470 
1471 		assertEquals("a = a,\\u0023b,=c|[A]|a = a,\\u0023b,=c|", pipedLines(cf));
1472 		cf.commit();
1473 		assertEquals("a = a,\\u0023b,=c|[A]|a = a,\\u0023b,=c|", pipedLines(cf));
1474 
1475 		assertEquals("a,#b,=c", cf.get("a").get());
1476 		assertEquals("a,#b,=c", cf.get("A/a").get());
1477 
1478 		cf.set("a", "a,#b,=c", null, null, "comment#comment", null);
1479 		assertEquals("a = a,\\u0023b,=c # comment#comment|[A]|a = a,\\u0023b,=c|", pipedLines(cf));
1480 		assertEquals("a,#b,=c", cf.get("a").get());
1481 	}
1482 
1483 	public static class ABean {
1484 		public String foo;
1485 
1486 		public ABean init() {
1487 			foo = "bar";
1488 			return this;
1489 		}
1490 	}
1491 
1492 	public static class BBean {
1493 		private String foo;
1494 
1495 		public String getFoo() { return foo; }
1496 		public BBean setFoo(String v) {this.foo = v; return this;}
1497 		public BBean init() {
1498 			foo = "bar";
1499 			return this;
1500 		}
1501 	}
1502 
1503 	@Test void a18_getCandidateSystemDefaultConfigNames() {
1504 
1505 		System.setProperty("juneau.configFile", "foo.txt");
1506 		assertJson("['foo.txt']", Config.getCandidateSystemDefaultConfigNames());
1507 
1508 		System.clearProperty("juneau.configFile");
1509 		assertJson("['test.cfg','juneau.cfg','default.cfg','application.cfg','app.cfg','settings.cfg','application.properties']", Config.getCandidateSystemDefaultConfigNames());
1510 	}
1511 
1512 	//====================================================================================================
1513 	//	setSystemProperties
1514 	//====================================================================================================
1515 
1516 	@Test void setSystemProperties() {
1517 		var c = init("a=1", "[S]", "b=2");
1518 		c.setSystemProperties();
1519 		assertEquals("1", System.getProperty("a"));
1520 		assertEquals("2", System.getProperty("S/b"));
1521 	}
1522 }