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.collections;
18  
19  import static org.apache.juneau.commons.lang.TriState.*;
20  import static org.apache.juneau.junit.bct.BctAssertions.*;
21  import static org.junit.jupiter.api.Assertions.*;
22  
23  import java.util.*;
24  
25  import org.apache.juneau.*;
26  import org.apache.juneau.junit.bct.annotations.*;
27  import org.junit.jupiter.api.*;
28  
29  class Maps_Test extends TestBase {
30  
31  	//-----------------------------------------------------------------------------------------------------------------
32  	// Basic tests
33  	//-----------------------------------------------------------------------------------------------------------------
34  
35  	@Test
36  	void a01_create() {
37  		var b = Maps.create(String.class, Integer.class);
38  		assertNotNull(b);
39  	}
40  
41  	@Test
42  	void a02_addSingle() {
43  		var map = Maps.create(String.class, Integer.class)
44  			.add("a", 1)
45  			.build();
46  
47  		assertMap(map, "a=1");
48  	}
49  
50  	@Test
51  	void a03_addMultiple() {
52  		var map = Maps.create(String.class, Integer.class)
53  			.add("a", 1)
54  			.add("b", 2)
55  			.add("c", 3)
56  			.build();
57  
58  		assertMap(map, "a=1", "b=2", "c=3");
59  	}
60  
61  	@Test
62  	void a04_addAll() {
63  		var existing = new LinkedHashMap<String,Integer>();
64  		existing.put("x", 10);
65  		existing.put("y", 20);
66  
67  		var map = Maps.create(String.class, Integer.class)
68  			.add("a", 1)
69  			.addAll(existing)
70  			.add("b", 2)
71  			.build();
72  
73  		// Without ordered(), order is not guaranteed (HashMap)
74  		assertSize(4, map);
75  		assertEquals(1, map.get("a"));
76  		assertEquals(10, map.get("x"));
77  		assertEquals(20, map.get("y"));
78  		assertEquals(2, map.get("b"));
79  	}
80  
81  	@Test
82  	void a05_addAllNull() {
83  		var map = Maps.create(String.class, Integer.class)
84  			.add("a", 1)
85  			.addAll(null)
86  			.add("b", 2)
87  			.build();
88  
89  		assertMap(map, "a=1", "b=2");
90  	}
91  
92  	//-----------------------------------------------------------------------------------------------------------------
93  	// Add pairs
94  	//-----------------------------------------------------------------------------------------------------------------
95  
96  	@Test
97  	@BctConfig(sortMaps = TRUE)
98  	void b01_addPairs() {
99  		var map = Maps.create(String.class, String.class)
100 			.addPairs("host", "localhost", "port", "8080", "protocol", "https")
101 			.build();
102 
103 		assertMap(map, "host=localhost", "port=8080", "protocol=https");
104 	}
105 
106 	@Test
107 	void b02_addPairs_oddNumber() {
108 		var b = Maps.create(String.class, String.class);
109 		assertThrows(IllegalArgumentException.class, () -> b.addPairs("a", "b", "c"));
110 	}
111 
112 	//-----------------------------------------------------------------------------------------------------------------
113 	// Ordered
114 	//-----------------------------------------------------------------------------------------------------------------
115 
116 	@Test
117 	void c00_ordered_preservesInsertionOrder() {
118 		var map = Maps.create(String.class, Integer.class)
119 			.ordered()
120 			.add("c", 3)
121 			.add("a", 1)
122 			.add("b", 2)
123 			.build();
124 
125 		assertSize(3, map);
126 		assertTrue(map instanceof LinkedHashMap);
127 		// With ordered(), insertion order is preserved
128 		var keys = new ArrayList<>(map.keySet());
129 		assertEquals("c", keys.get(0));
130 		assertEquals("a", keys.get(1));
131 		assertEquals("b", keys.get(2));
132 	}
133 
134 	@Test
135 	void c00b_ordered_defaultIsHashMap() {
136 		var map = Maps.create(String.class, Integer.class)
137 			.add("a", 1)
138 			.add("b", 2)
139 			.build();
140 
141 		assertSize(2, map);
142 		// Without ordered(), should be HashMap (unordered)
143 		assertTrue(map instanceof HashMap);
144 		assertFalse(map instanceof LinkedHashMap);
145 		// Verify it's not ordered by checking key order is not preserved
146 		// (HashMap doesn't guarantee order, so we can't rely on it)
147 		assertEquals(1, map.get("a"));
148 		assertEquals(2, map.get("b"));
149 	}
150 
151 	@Test
152 	void c00c_ordered_boolean() {
153 		var map1 = Maps.create(String.class, Integer.class)
154 			.ordered(true)
155 			.add("a", 1)
156 			.build();
157 		assertTrue(map1 instanceof LinkedHashMap);
158 
159 		var map2 = Maps.create(String.class, Integer.class)
160 			.ordered(false)
161 			.add("a", 1)
162 			.build();
163 		assertTrue(map2 instanceof HashMap);
164 	}
165 
166 	@Test
167 	void c00d_ordered_lastOneWins() {
168 		// If ordered() is called first, then sorted(), sorted() wins
169 		var map1 = Maps.create(String.class, Integer.class)
170 			.ordered()
171 			.add("c", 3)
172 			.add("a", 1)
173 			.add("b", 2)
174 			.sorted()
175 			.build();
176 		assertTrue(map1 instanceof TreeMap);
177 		assertList(map1.keySet(), "a", "b", "c");
178 
179 		// If sorted() is called first, then ordered(), ordered() wins
180 		var map2 = Maps.create(String.class, Integer.class)
181 			.sorted()
182 			.add("c", 3)
183 			.add("a", 1)
184 			.add("b", 2)
185 			.ordered()
186 			.build();
187 		assertTrue(map2 instanceof LinkedHashMap);
188 		var keys2 = new ArrayList<>(map2.keySet());
189 		assertEquals("c", keys2.get(0));
190 		assertEquals("a", keys2.get(1));
191 		assertEquals("b", keys2.get(2));
192 	}
193 
194 	//-----------------------------------------------------------------------------------------------------------------
195 	// Sorting
196 	//-----------------------------------------------------------------------------------------------------------------
197 
198 	@Test
199 	void c01_sorted_naturalOrder() {
200 		var map = Maps.create(String.class, Integer.class)
201 			.add("c", 3)
202 			.add("a", 1)
203 			.add("b", 2)
204 			.sorted()
205 			.build();
206 
207 		assertSize(3, map);
208 		assertList(map.keySet(), "a", "b", "c");
209 		assertTrue(map instanceof TreeMap);
210 	}
211 
212 	@Test
213 	void c02_sorted_customComparator() {
214 		var map = Maps.create(String.class, Integer.class)
215 			.add("a", 1)
216 			.add("bb", 2)
217 			.add("ccc", 3)
218 			.sorted(Comparator.comparing(String::length))
219 			.build();
220 
221 		assertList(map.keySet(), "a", "bb", "ccc");
222 	}
223 
224 	//-----------------------------------------------------------------------------------------------------------------
225 	// Sparse mode
226 	//-----------------------------------------------------------------------------------------------------------------
227 
228 	@Test
229 	void d01_sparse_empty() {
230 		var map = Maps.create(String.class, Integer.class)
231 			.sparse()
232 			.build();
233 
234 		assertNull(map);
235 	}
236 
237 	@Test
238 	void d02_sparse_notEmpty() {
239 		var map = Maps.create(String.class, Integer.class)
240 			.add("a", 1)
241 			.sparse()
242 			.build();
243 
244 		assertNotNull(map);
245 		assertSize(1, map);
246 	}
247 
248 	@Test
249 	void d03_notSparse_empty() {
250 		var map = Maps.create(String.class, Integer.class)
251 			.build();
252 
253 		assertNotNull(map);
254 		assertEmpty(map);
255 	}
256 
257 	//-----------------------------------------------------------------------------------------------------------------
258 	// Unmodifiable
259 	//-----------------------------------------------------------------------------------------------------------------
260 
261 	@Test
262 	void e01_unmodifiable() {
263 		var map = Maps.create(String.class, Integer.class)
264 			.add("a", 1)
265 			.add("b", 2)
266 			.unmodifiable()
267 			.build();
268 
269 		assertSize(2, map);
270 		assertThrows(UnsupportedOperationException.class, () -> map.put("c", 3));
271 	}
272 
273 	@Test
274 	void e02_modifiable() {
275 		var map = Maps.create(String.class, Integer.class)
276 			.add("a", 1)
277 			.build();
278 
279 		map.put("b", 2);
280 		assertSize(2, map);
281 	}
282 
283 	//-----------------------------------------------------------------------------------------------------------------
284 	// Copy mode
285 	//-----------------------------------------------------------------------------------------------------------------
286 
287 	//-----------------------------------------------------------------------------------------------------------------
288 	// Complex scenarios
289 	//-----------------------------------------------------------------------------------------------------------------
290 
291 	@Test
292 	void g01_multipleOperations() {
293 		var existing = new LinkedHashMap<String,Integer>();
294 		existing.put("x", 10);
295 		existing.put("y", 20);
296 
297 		var map = Maps.create(String.class, Integer.class)
298 			.add("a", 1)
299 			.addAll(existing)
300 			.add("b", 2)
301 			.sorted()
302 			.build();
303 
304 		assertSize(4, map);
305 		// Should be sorted by key
306 		assertList(map.keySet(), "a", "b", "x", "y");
307 	}
308 
309 	@Test
310 	void g01b_multipleOperations_withOrdered() {
311 		var existing = new LinkedHashMap<String,Integer>();
312 		existing.put("x", 10);
313 		existing.put("y", 20);
314 
315 		var map = Maps.create(String.class, Integer.class)
316 			.ordered()
317 			.add("a", 1)
318 			.addAll(existing)
319 			.add("b", 2)
320 			.build();
321 
322 		assertSize(4, map);
323 		assertTrue(map instanceof LinkedHashMap);
324 		// With ordered(), insertion order is preserved
325 		var keys = new ArrayList<>(map.keySet());
326 		assertEquals("a", keys.get(0));
327 		assertEquals("x", keys.get(1));
328 		assertEquals("y", keys.get(2));
329 		assertEquals("b", keys.get(3));
330 	}
331 
332 	@Test
333 	void g02_sortedAndUnmodifiable() {
334 		var map = Maps.create(String.class, Integer.class)
335 			.add("c", 3)
336 			.add("a", 1)
337 			.add("b", 2)
338 			.sorted()
339 			.unmodifiable()
340 			.build();
341 
342 		assertSize(3, map);
343 		assertList(map.keySet(), "a", "b", "c");
344 		assertThrows(UnsupportedOperationException.class, () -> map.put("d", 4));
345 	}
346 
347 	@Test
348 	void g03_sparseAndSorted() {
349 		var map1 = Maps.create(String.class, Integer.class)
350 			.add("c", 3)
351 			.add("a", 1)
352 			.add("b", 2)
353 			.sorted()
354 			.sparse()
355 			.build();
356 
357 		assertNotNull(map1);
358 		assertSize(3, map1);
359 
360 		var map2 = Maps.create(String.class, Integer.class)
361 			.sorted()
362 			.sparse()
363 			.build();
364 
365 		assertNull(map2);
366 	}
367 
368 	//-----------------------------------------------------------------------------------------------------------------
369 	// Edge cases
370 	//-----------------------------------------------------------------------------------------------------------------
371 
372 	@Test
373 	void h01_buildEmptyMap() {
374 		var map = Maps.create(String.class, Integer.class)
375 			.build();
376 
377 		assertNotNull(map);
378 		assertEmpty(map);
379 	}
380 
381 	@Test
382 	void h02_addNullKey() {
383 		var map = Maps.create(String.class, Integer.class)
384 			.add(null, 1)
385 			.add("a", 2)
386 			.build();
387 
388 		assertMap(map, "<null>=1", "a=2");
389 	}
390 
391 	@Test
392 	void h03_addNullValue() {
393 		var map = Maps.create(String.class, Integer.class)
394 			.add("a", 1)
395 			.add("b", null)
396 			.add("c", 3)
397 			.build();
398 
399 		assertMap(map, "a=1", "b=<null>", "c=3");
400 	}
401 
402 	@Test
403 	void h04_duplicateKeys() {
404 		var map = Maps.create(String.class, Integer.class)
405 			.add("a", 1)
406 			.add("a", 2)  // Overwrites first value
407 			.add("a", 3)  // Overwrites again
408 			.build();
409 
410 		assertSize(1, map);  // Only one entry for key "a"
411 		assertMap(map, "a=3");  // Last value wins
412 	}
413 
414 	//-----------------------------------------------------------------------------------------------------------------
415 	// Filtering
416 	//-----------------------------------------------------------------------------------------------------------------
417 
418 	@Test
419 	void i01_filtered_customPredicate() {
420 		var map = Maps.create(String.class, String.class)
421 			.filtered((k, v) -> v != null && !v.equals(""))
422 			.add("a", "foo")
423 			.add("b", null)     // Not added
424 			.add("c", "")       // Not added
425 			.add("d", "bar")
426 			.build();
427 
428 		assertMap(map, "a=foo", "d=bar");
429 	}
430 
431 	@Test
432 	void i02_filtered_defaultFilter() {
433 		var map = Maps.create(String.class, Object.class)
434 			.filtered()
435 			.add("name", "John")
436 			.add("age", -1)              // Not added
437 			.add("enabled", false)       // Not added
438 			.add("tags", new String[0])  // Not added
439 			.add("emptyMap", new LinkedHashMap<>())  // Not added
440 			.add("emptyList", new ArrayList<>())     // Not added
441 			.add("value", 42)
442 			.build();
443 
444 		assertMap(map, "name=John", "value=42");
445 	}
446 
447 	@Test
448 	void i03_filtered_rejectsValue() {
449 		var map = Maps.create(String.class, Integer.class)
450 			.filtered((k, v) -> v != null && v > 0)
451 			.add("a", 1)
452 			.add("b", -1)  // Not added
453 			.add("c", 0)   // Not added
454 			.add("d", 2)
455 			.build();
456 
457 		assertMap(map, "a=1", "d=2");
458 	}
459 
460 	@Test
461 	void i04_add_withFilter() {
462 		var map = Maps.create(String.class, String.class)
463 			.filtered((k, v) -> v != null && v.length() > 2)
464 			.add("a", "foo")   // Added
465 			.add("b", "ab")    // Not added (length <= 2)
466 			.add("c", "bar")   // Added
467 			.build();
468 
469 		assertMap(map, "a=foo", "c=bar");
470 	}
471 
472 	@Test
473 	void i05_addAll_withFilter() {
474 		var existing = new LinkedHashMap<String,String>();
475 		existing.put("x", "longvalue");
476 		existing.put("y", "ab");  // Will be filtered out
477 		existing.put("z", "another");
478 
479 		var map = Maps.create(String.class, String.class)
480 			.filtered((k, v) -> v != null && v.length() > 2)
481 			.addAll(existing)
482 			.build();
483 
484 		assertMap(map, "x=longvalue", "z=another");
485 	}
486 
487 	//-----------------------------------------------------------------------------------------------------------------
488 	// Key/Value Functions
489 	//-----------------------------------------------------------------------------------------------------------------
490 
491 	@Test
492 	void j01_keyValueFunctions_notSet() {
493 		var map = Maps.create(String.class, Integer.class)
494 			.add("a", 1)
495 			.build();
496 
497 		assertMap(map, "a=1");
498 	}
499 
500 	@Test
501 	void j02_keyValueFunctions_withValueFunction() {
502 		var inputMap = new LinkedHashMap<String,String>();
503 		inputMap.put("a", "1");
504 		inputMap.put("b", "2");
505 
506 		var map = Maps.create(String.class, Integer.class)
507 			.valueFunction(o -> o instanceof String ? Integer.parseInt((String)o) : (Integer)o)
508 			.addAny(inputMap)
509 			.build();
510 
511 		assertMap(map, "a=1", "b=2");
512 	}
513 
514 	@Test
515 	void j03_keyValueFunctions_withBothFunctions() {
516 		var inputMap = new LinkedHashMap<Integer,String>();
517 		inputMap.put(1, "10");
518 		inputMap.put(2, "20");
519 
520 		var map = Maps.create(String.class, Integer.class)
521 			.keyFunction(o -> String.valueOf(o))
522 			.valueFunction(o -> o instanceof String ? Integer.parseInt((String)o) : (Integer)o)
523 			.addAny(inputMap)
524 			.build();
525 
526 		assertMap(map, "1=10", "2=20");
527 	}
528 
529 	@Test
530 	void j04_keyValueFunctions_functionsConvenienceMethod() {
531 		var inputMap = new LinkedHashMap<Integer,String>();
532 		inputMap.put(1, "10");
533 		inputMap.put(2, "20");
534 
535 		var map = Maps.create(String.class, Integer.class)
536 			.functions(
537 				o -> String.valueOf(o),  // key function
538 				o -> o instanceof String ? Integer.parseInt((String)o) : (Integer)o  // value function
539 			)
540 			.addAny(inputMap)
541 			.build();
542 
543 		assertMap(map, "1=10", "2=20");
544 	}
545 
546 	//-----------------------------------------------------------------------------------------------------------------
547 	// AddAny
548 	//-----------------------------------------------------------------------------------------------------------------
549 
550 	@Test
551 	void k01_addAny_withMap() {
552 		var inputMap = new LinkedHashMap<String,Integer>();
553 		inputMap.put("a", 1);
554 		inputMap.put("b", 2);
555 
556 		var map = Maps.create(String.class, Integer.class)
557 			.addAny(inputMap)
558 			.build();
559 
560 		assertMap(map, "a=1", "b=2");
561 	}
562 
563 	@Test
564 	void k02_addAny_withMultipleMaps() {
565 		var map1 = new LinkedHashMap<String,Integer>();
566 		map1.put("a", 1);
567 		map1.put("b", 2);
568 
569 		var map2 = new LinkedHashMap<String,Integer>();
570 		map2.put("c", 3);
571 		map2.put("d", 4);
572 
573 		var map = Maps.create(String.class, Integer.class)
574 			.addAny(map1, map2)
575 			.build();
576 
577 		assertMap(map, "a=1", "b=2", "c=3", "d=4");
578 	}
579 
580 	@Test
581 	void k03_addAny_withNullMap() {
582 		var inputMap = new LinkedHashMap<String,Integer>();
583 		inputMap.put("a", 1);
584 		inputMap.put("b", 2);
585 
586 		var map = Maps.create(String.class, Integer.class)
587 			.addAny(inputMap, null)  // null map should be ignored
588 			.build();
589 
590 		assertMap(map, "a=1", "b=2");
591 	}
592 
593 	@Test
594 	void k03b_addAny_withNullValueInMap() {
595 		// addAny uses toType which doesn't handle null values
596 		var inputMap = new LinkedHashMap<String,Integer>();
597 		inputMap.put("a", 1);
598 		inputMap.put("b", null);
599 
600 		assertThrows(RuntimeException.class, () -> {
601 			Maps.create(String.class, Integer.class)
602 				.addAny(inputMap)
603 				.build();
604 		});
605 	}
606 
607 	@Test
608 	void k04_addAny_withTypeConversion() {
609 		var inputMap = new LinkedHashMap<Integer,String>();
610 		inputMap.put(1, "10");
611 		inputMap.put(2, "20");
612 
613 		var map = Maps.create(String.class, Integer.class)
614 			.keyFunction(o -> String.valueOf(o))
615 			.valueFunction(o -> o instanceof String ? Integer.parseInt((String)o) : (Integer)o)
616 			.addAny(inputMap)
617 			.build();
618 
619 		assertMap(map, "1=10", "2=20");
620 	}
621 
622 	@Test
623 	void k05_addAny_withStringToMapConversion() {
624 		// This test is no longer applicable since addAny only accepts Map instances
625 		// If we want to parse strings, we'd need to do it before calling addAny
626 		var parsedMap = new LinkedHashMap<String,String>();
627 		for (var pair : "a=1,b=2".split(",")) {
628 			var kv = pair.split("=");
629 			if (kv.length == 2) {
630 				parsedMap.put(kv[0], kv[1]);
631 			}
632 		}
633 
634 		var map = Maps.create(String.class, String.class)
635 			.addAny(parsedMap)
636 			.build();
637 
638 		assertMap(map, "a=1", "b=2");
639 	}
640 
641 	@Test
642 	void k06_addAny_withMultipleMaps_parsedFromStrings() {
643 		var map1 = new LinkedHashMap<String,String>();
644 		map1.put("x", "foo");
645 		var map2 = new LinkedHashMap<String,String>();
646 		map2.put("y", "bar");
647 
648 		var map = Maps.create(String.class, String.class)
649 			.addAny(map1, map2)
650 			.build();
651 
652 		assertMap(map, "x=foo", "y=bar");
653 	}
654 
655 	@Test
656 	void k07_addAny_noKeyOrValueType() {
657 		assertThrows(IllegalArgumentException.class, () -> new Maps<String,Integer>(null, null));
658 		assertThrows(IllegalArgumentException.class, () -> new Maps<String,Integer>(String.class, null));
659 		assertThrows(IllegalArgumentException.class, () -> new Maps<String,Integer>(null, Integer.class));
660 	}
661 
662 	@Test
663 	void k08_addAny_nonMapObject() {
664 		// addAny only accepts Map instances, non-Map objects throw exception
665 		assertThrows(RuntimeException.class, () -> {
666 			Maps.create(String.class, Integer.class)
667 				.addAny("not-a-map")
668 				.build();
669 		});
670 	}
671 
672 	@Test
673 	void k09_addAny_valueFunctionReturnsNull() {
674 		// Value function that can't convert should throw exception
675 		var inputMap = new LinkedHashMap<String,String>();
676 		inputMap.put("a", "not-an-integer");
677 
678 		assertThrows(RuntimeException.class, () -> {
679 			Maps.create(String.class, Integer.class)
680 				.valueFunction(o -> {
681 					if (o instanceof String) {
682 						try {
683 							return Integer.parseInt((String)o);
684 						} catch (NumberFormatException e) {
685 							throw new RuntimeException("Cannot convert", e);
686 						}
687 					}
688 					return (Integer)o;
689 				})
690 				.addAny(inputMap)
691 				.build();
692 		});
693 	}
694 
695 	@Test
696 	void k10_addAny_keyFunctionConversionFailure() {
697 		var inputMap = new LinkedHashMap<Object,String>();
698 		inputMap.put(new Object(), "value");  // Object can't be converted to String
699 
700 		assertThrows(RuntimeException.class, () -> {
701 			Maps.create(String.class, String.class)
702 				.keyFunction(o -> {
703 					if (o instanceof String)
704 						return (String)o;
705 					throw new RuntimeException("Cannot convert key");
706 				})
707 				.addAny(inputMap)
708 				.build();
709 		});
710 	}
711 
712 	@Test
713 	void k11_addAny_noKeyFunction_conversionFailure() {
714 		// Test line 726: convertKey throws exception when keyFunction is null and key can't be converted
715 		var inputMap = new LinkedHashMap<Object,String>();
716 		inputMap.put(new Object(), "value");  // Object can't be converted to String, and no keyFunction
717 
718 		assertThrows(RuntimeException.class, () -> {
719 			Maps.create(String.class, String.class)
720 				// No keyFunction set
721 				.addAny(inputMap)
722 				.build();
723 		});
724 	}
725 
726 	//-----------------------------------------------------------------------------------------------------------------
727 	// Concurrent
728 	//-----------------------------------------------------------------------------------------------------------------
729 
730 	@Test
731 	void o01_concurrent_defaultHashMap() {
732 		var map = Maps.create(String.class, Integer.class)
733 			.concurrent()
734 			.add("a", 1)
735 			.add("b", 2)
736 			.build();
737 
738 		assertSize(2, map);
739 		assertTrue(map instanceof java.util.concurrent.ConcurrentHashMap);
740 	}
741 
742 	@Test
743 	void o02_concurrent_ordered() {
744 		var map = Maps.create(String.class, Integer.class)
745 			.ordered()
746 			.concurrent()
747 			.add("a", 1)
748 			.add("b", 2)
749 			.build();
750 
751 		assertSize(2, map);
752 		// Should be a synchronized LinkedHashMap - verify by checking order is preserved
753 		var keys = new ArrayList<>(map.keySet());
754 		assertEquals("a", keys.get(0));
755 		assertEquals("b", keys.get(1));
756 		// Verify it's synchronized by checking it's not a ConcurrentHashMap
757 		assertFalse(map instanceof java.util.concurrent.ConcurrentHashMap);
758 	}
759 
760 	@Test
761 	void o03_concurrent_sorted() {
762 		var map = Maps.create(String.class, Integer.class)
763 			.sorted()
764 			.concurrent()
765 			.add("c", 3)
766 			.add("a", 1)
767 			.add("b", 2)
768 			.build();
769 
770 		assertSize(3, map);
771 		assertTrue(map instanceof java.util.concurrent.ConcurrentSkipListMap);
772 		assertList(map.keySet(), "a", "b", "c");
773 	}
774 
775 	@Test
776 	void o04_concurrent_boolean() {
777 		var map1 = Maps.create(String.class, Integer.class)
778 			.concurrent(true)
779 			.add("a", 1)
780 			.build();
781 		assertTrue(map1 instanceof java.util.concurrent.ConcurrentHashMap);
782 
783 		var map2 = Maps.create(String.class, Integer.class)
784 			.concurrent(false)
785 			.add("a", 1)
786 			.build();
787 		assertTrue(map2 instanceof HashMap);
788 	}
789 
790 	@Test
791 	void o05_concurrent_unmodifiable() {
792 		var map = Maps.create(String.class, Integer.class)
793 			.concurrent()
794 			.unmodifiable()
795 			.add("a", 1)
796 			.build();
797 
798 		assertSize(1, map);
799 		assertThrows(UnsupportedOperationException.class, () -> map.put("b", 2));
800 	}
801 
802 	//-----------------------------------------------------------------------------------------------------------------
803 	// Build edge cases
804 	//-----------------------------------------------------------------------------------------------------------------
805 
806 	@Test
807 	void l01_build_sparseWithNullMap() {
808 		var map = Maps.create(String.class, Integer.class)
809 			.sparse()
810 			.build();
811 
812 		assertNull(map);
813 	}
814 
815 	@Test
816 	void l03_build_notSparseWithNullMap() {
817 		var map = Maps.create(String.class, Integer.class)
818 			.build();
819 
820 		assertNotNull(map);
821 		assertEmpty(map);
822 	}
823 
824 	@Test
825 	void l04_build_sortedWithNullMap() {
826 		var map = Maps.create(String.class, Integer.class)
827 			.sorted()
828 			.build();
829 
830 		assertNotNull(map);
831 		assertTrue(map instanceof TreeMap);
832 		assertEmpty(map);
833 	}
834 
835 	@Test
836 	void l06_build_orderedWithNullMap() {
837 		var map = Maps.create(String.class, Integer.class)
838 			.ordered()
839 			.build();
840 
841 		assertNotNull(map);
842 		assertTrue(map instanceof LinkedHashMap);
843 		assertEmpty(map);
844 	}
845 
846 	@Test
847 	void l07_build_defaultHashMap() {
848 		var map = Maps.create(String.class, Integer.class)
849 			.build();
850 
851 		assertNotNull(map);
852 		// Without ordered(), should be HashMap (unordered)
853 		assertTrue(map instanceof HashMap);
854 		assertFalse(map instanceof LinkedHashMap);
855 		assertEmpty(map);
856 	}
857 
858 	@Test
859 	void l05_build_unmodifiableWithNullMap() {
860 		var map = Maps.create(String.class, Integer.class)
861 			.unmodifiable()
862 			.build();
863 
864 		assertNotNull(map);
865 		assertThrows(UnsupportedOperationException.class, () -> map.put("a", 1));
866 	}
867 
868 	//-----------------------------------------------------------------------------------------------------------------
869 	// BuildFluent
870 	//-----------------------------------------------------------------------------------------------------------------
871 
872 	@Test
873 	void m01_buildFluent_returnsFluentMap() {
874 		var map = Maps.create(String.class, Integer.class)
875 			.add("a", 1)
876 			.add("b", 2)
877 			.buildFluent();
878 
879 		assertNotNull(map);
880 		assertSize(2, map);
881 		assertMap(map, "a=1", "b=2");
882 	}
883 
884 	@Test
885 	void m02_buildFluent_sparseEmpty() {
886 		var map = Maps.create(String.class, Integer.class)
887 			.sparse()
888 			.buildFluent();
889 
890 		assertNull(map);
891 	}
892 
893 	@Test
894 	void m03_buildFluent_withFiltering() {
895 		var map = Maps.create(String.class, Integer.class)
896 			.filtered((k, v) -> v != null && v > 0)
897 			.add("a", 1)
898 			.add("b", -1)  // Filtered out
899 			.buildFluent();
900 
901 		assertNotNull(map);
902 		assertSize(1, map);
903 		assertMap(map, "a=1");
904 	}
905 
906 	//-----------------------------------------------------------------------------------------------------------------
907 	// BuildFiltered
908 	//-----------------------------------------------------------------------------------------------------------------
909 
910 	@Test
911 	void n01_buildFiltered_withFilter() {
912 		var map = Maps.create(String.class, Integer.class)
913 			.filtered((k, v) -> v != null && v > 0)
914 			.add("a", 1)
915 			.add("b", -1)  // Filtered out
916 			.add("c", 2)
917 			.buildFiltered();
918 
919 		assertNotNull(map);
920 		assertSize(2, map);
921 		assertMap(map, "a=1", "c=2");
922 	}
923 
924 	@Test
925 	void n02_buildFiltered_withoutFilter() {
926 		// buildFiltered() should work even without explicit filter (uses default filter)
927 		var map = Maps.create(String.class, Integer.class)
928 			.add("a", 1)
929 			.add("b", 2)
930 			.buildFiltered();
931 
932 		assertNotNull(map);
933 		assertSize(2, map);
934 	}
935 
936 	@Test
937 	void n03_buildFiltered_sparseEmpty() {
938 		var map = Maps.create(String.class, Integer.class)
939 			.filtered((k, v) -> v != null && v > 0)
940 			.sparse()
941 			.buildFiltered();
942 
943 		assertNull(map);
944 	}
945 
946 	@Test
947 	void n04_buildFiltered_withSorted() {
948 		var map = Maps.create(String.class, Integer.class)
949 			.filtered((k, v) -> v != null && v > 0)
950 			.add("c", 3)
951 			.add("a", 1)
952 			.add("b", 2)
953 			.sorted()
954 			.buildFiltered();
955 
956 		assertNotNull(map);
957 		assertList(map.keySet(), "a", "b", "c");
958 	}
959 
960 	@Test
961 	void n05_buildFiltered_multipleFilters() {
962 		var map = Maps.create(String.class, Integer.class)
963 			.filtered((k, v) -> v != null)
964 			.filtered((k, v) -> v > 0)
965 			.filtered((k, v) -> !k.startsWith("_"))
966 			.add("a", 1)
967 			.add("_b", 2)  // Filtered out (starts with _)
968 			.add("c", -1)  // Filtered out (not > 0)
969 			.add("d", 3)
970 			.buildFiltered();
971 
972 		assertNotNull(map);
973 		assertMap(map, "a=1", "d=3");
974 	}
975 }