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.junit.bct.BctAssertions.*;
20  import static org.junit.jupiter.api.Assertions.*;
21  
22  import java.util.*;
23  
24  import org.apache.juneau.*;
25  import org.junit.jupiter.api.*;
26  
27  class BidiMap_Test extends TestBase {
28  
29  	//====================================================================================================
30  	// Basic forward and reverse lookups
31  	//====================================================================================================
32  
33  	@Test void a01_basicForwardLookup() {
34  		var map = BidiMap.<String,Integer>create()
35  			.add("one", 1)
36  			.add("two", 2)
37  			.add("three", 3)
38  			.build();
39  
40  		assertEquals(1, map.get("one"));
41  		assertEquals(2, map.get("two"));
42  		assertEquals(3, map.get("three"));
43  		assertNull(map.get("four"));
44  	}
45  
46  	@Test void a02_basicReverseLookup() {
47  		var map = BidiMap.<String,Integer>create()
48  			.add("one", 1)
49  			.add("two", 2)
50  			.add("three", 3)
51  			.build();
52  
53  		assertEquals("one", map.getKey(1));
54  		assertEquals("two", map.getKey(2));
55  		assertEquals("three", map.getKey(3));
56  		assertNull(map.getKey(4));
57  	}
58  
59  	//====================================================================================================
60  	// Null handling
61  	//====================================================================================================
62  
63  	@Test void a03_nullKeysAndValuesFilteredOut() {
64  		var map = BidiMap.<String,Integer>create()
65  			.add("one", 1)
66  			.add(null, 2)
67  			.add("three", null)
68  			.add(null, null)
69  			.build();
70  
71  		assertSize(1, map);
72  		assertEquals(1, map.get("one"));
73  		assertNull(map.get(null));
74  		assertNull(map.getKey(null));
75  	}
76  
77  	@Test void a04_nullLookups() {
78  		var map = BidiMap.<String,Integer>create()
79  			.add("one", 1)
80  			.build();
81  
82  		assertNull(map.get(null));
83  		assertNull(map.getKey(null));
84  	}
85  
86  	//====================================================================================================
87  	// Builder operations
88  	//====================================================================================================
89  
90  	@Test void a05_emptyMap() {
91  		var map = BidiMap.<String,Integer>create().build();
92  
93  		assertEmpty(map);
94  		assertEmpty(map);
95  		assertNull(map.get("anything"));
96  		assertNull(map.getKey(123));
97  	}
98  
99  	@Test void a06_builderChaining() {
100 		var map = BidiMap.<String,Integer>create()
101 			.add("a", 1)
102 			.add("b", 2)
103 			.add("c", 3)
104 			.build();
105 
106 		assertSize(3, map);
107 		assertEquals(1, map.get("a"));
108 		assertEquals("b", map.getKey(2));
109 	}
110 
111 	//====================================================================================================
112 	// Map interface - containsKey, containsValue
113 	//====================================================================================================
114 
115 	@Test void a07_containsKey() {
116 		var map = BidiMap.<String,Integer>create()
117 			.add("one", 1)
118 			.add("two", 2)
119 			.build();
120 
121 		assertTrue(map.containsKey("one"));
122 		assertTrue(map.containsKey("two"));
123 		assertFalse(map.containsKey("three"));
124 		assertFalse(map.containsKey(null));
125 	}
126 
127 	@Test void a08_containsValue() {
128 		var map = BidiMap.<String,Integer>create()
129 			.add("one", 1)
130 			.add("two", 2)
131 			.build();
132 
133 		assertTrue(map.containsValue(1));
134 		assertTrue(map.containsValue(2));
135 		assertFalse(map.containsValue(3));
136 		assertFalse(map.containsValue(null));
137 	}
138 
139 	//====================================================================================================
140 	// Map interface - put, putAll, remove, clear
141 	//====================================================================================================
142 
143 	@Test void a09_put() {
144 		var map = BidiMap.<String,Integer>create().build();
145 
146 		assertNull(map.put("one", 1));
147 		assertEquals(1, map.get("one"));
148 		assertEquals("one", map.getKey(1));
149 
150 		assertEquals(1, map.put("one", 10));
151 		assertEquals(10, map.get("one"));
152 		assertEquals("one", map.getKey(10));
153 	}
154 
155 	@Test void a10_putAll() {
156 		var map = BidiMap.<String,Integer>create()
157 			.add("one", 1)
158 			.build();
159 
160 		var toAdd = new HashMap<String,Integer>();
161 		toAdd.put("two", 2);
162 		toAdd.put("three", 3);
163 
164 		map.putAll(toAdd);
165 
166 		assertSize(3, map);
167 		assertEquals(2, map.get("two"));
168 		assertEquals("three", map.getKey(3));
169 	}
170 
171 	@Test void a11_remove() {
172 		var map = BidiMap.<String,Integer>create()
173 			.add("one", 1)
174 			.add("two", 2)
175 			.build();
176 
177 		assertEquals(1, map.remove("one"));
178 		assertFalse(map.containsKey("one"));
179 		assertFalse(map.containsValue(1));
180 		assertNull(map.getKey(1));
181 
182 		assertNull(map.remove("three"));
183 	}
184 
185 	@Test void a12_clear() {
186 		var map = BidiMap.<String,Integer>create()
187 			.add("one", 1)
188 			.add("two", 2)
189 			.build();
190 
191 		map.clear();
192 
193 		assertEmpty(map);
194 		assertEmpty(map);
195 		assertNull(map.get("one"));
196 		assertNull(map.getKey(1));
197 	}
198 
199 	//====================================================================================================
200 	// Map interface - keySet, values, entrySet
201 	//====================================================================================================
202 
203 	@Test void a13_keySet() {
204 		var map = BidiMap.<String,Integer>create()
205 			.add("one", 1)
206 			.add("two", 2)
207 			.add("three", 3)
208 			.build();
209 
210 		var keys = map.keySet();
211 
212 		assertSize(3, keys);
213 		assertTrue(keys.contains("one"));
214 		assertTrue(keys.contains("two"));
215 		assertTrue(keys.contains("three"));
216 	}
217 
218 	@Test void a14_values() {
219 		var map = BidiMap.<String,Integer>create()
220 			.add("one", 1)
221 			.add("two", 2)
222 			.add("three", 3)
223 			.build();
224 
225 		var values = map.values();
226 
227 		assertSize(3, values);
228 		assertTrue(values.contains(1));
229 		assertTrue(values.contains(2));
230 		assertTrue(values.contains(3));
231 	}
232 
233 	@Test void a15_entrySet() {
234 		var map = BidiMap.<String,Integer>create()
235 			.add("one", 1)
236 			.add("two", 2)
237 			.build();
238 
239 		var entries = map.entrySet();
240 
241 		assertSize(2, entries);
242 
243 		boolean foundOne = false;
244 		boolean foundTwo = false;
245 
246 		for (var entry : entries) {
247 			if ("one".equals(entry.getKey()) && Integer.valueOf(1).equals(entry.getValue())) {
248 				foundOne = true;
249 			}
250 			if ("two".equals(entry.getKey()) && Integer.valueOf(2).equals(entry.getValue())) {
251 				foundTwo = true;
252 			}
253 		}
254 
255 		assertTrue(foundOne);
256 		assertTrue(foundTwo);
257 	}
258 
259 	//====================================================================================================
260 	// Unmodifiable maps
261 	//====================================================================================================
262 
263 	@Test void a16_unmodifiable_put() {
264 		var map = BidiMap.<String,Integer>create()
265 			.add("one", 1)
266 			.unmodifiable()
267 			.build();
268 
269 		assertThrows(UnsupportedOperationException.class, () -> map.put("two", 2));
270 	}
271 
272 	@Test void a17_unmodifiable_putAll() {
273 		var map = BidiMap.<String,Integer>create()
274 			.add("one", 1)
275 			.unmodifiable()
276 			.build();
277 
278 		var toAdd = new HashMap<String,Integer>();
279 		toAdd.put("two", 2);
280 
281 		assertThrows(UnsupportedOperationException.class, () -> map.putAll(toAdd));
282 	}
283 
284 	@Test void a18_unmodifiable_remove() {
285 		var map = BidiMap.<String,Integer>create()
286 			.add("one", 1)
287 			.unmodifiable()
288 			.build();
289 
290 		assertThrows(UnsupportedOperationException.class, () -> map.remove("one"));
291 	}
292 
293 	@Test void a19_unmodifiable_clear() {
294 		var map = BidiMap.<String,Integer>create()
295 			.add("one", 1)
296 			.unmodifiable()
297 			.build();
298 
299 		assertThrows(UnsupportedOperationException.class, () -> map.clear());
300 	}
301 
302 	@Test void a20_unmodifiable_readOperationsWork() {
303 		var map = BidiMap.<String,Integer>create()
304 			.add("one", 1)
305 			.add("two", 2)
306 			.unmodifiable()
307 			.build();
308 
309 		assertEquals(1, map.get("one"));
310 		assertEquals("two", map.getKey(2));
311 		assertSize(2, map);
312 		assertTrue(map.containsKey("one"));
313 		assertTrue(map.containsValue(2));
314 	}
315 
316 	//====================================================================================================
317 	// Insertion order preservation
318 	//====================================================================================================
319 
320 	@Test void a21_insertionOrderPreserved() {
321 		var map = BidiMap.<String,Integer>create()
322 			.add("c", 3)
323 			.add("a", 1)
324 			.add("b", 2)
325 			.build();
326 
327 		var keys = new ArrayList<>(map.keySet());
328 		assertEquals("c", keys.get(0));
329 		assertEquals("a", keys.get(1));
330 		assertEquals("b", keys.get(2));
331 	}
332 
333 	//====================================================================================================
334 	// Edge cases
335 	//====================================================================================================
336 
337 	@Test void a22_overwriteInBuilder() {
338 		var map = BidiMap.<String,Integer>create()
339 			.add("one", 1)
340 			.add("one", 10)
341 			.build();
342 
343 		assertSize(1, map);
344 		assertEquals(10, map.get("one"));
345 		assertEquals("one", map.getKey(10));
346 		assertNull(map.getKey(1));
347 	}
348 
349 	@Test void a22b_overwriteInBuilder_differentValue() {
350 		// Test line 123: overwriting a key with a different value
351 		// This should remove the old value from tracking
352 		var map = BidiMap.<String,Integer>create()
353 			.add("key1", 100)
354 			.add("key2", 200)
355 			.add("key1", 300)  // Overwrite key1 with different value (line 123)
356 			.build();
357 
358 		assertSize(2, map);
359 		assertEquals(300, map.get("key1"));
360 		assertEquals(200, map.get("key2"));
361 		assertEquals("key1", map.getKey(300));
362 		assertNull(map.getKey(100)); // Old value should be removed
363 	}
364 
365 	@Test void a23_duplicateValuesInBuilder() {
366 		// Duplicate values are not allowed
367 		assertThrows(IllegalArgumentException.class, () ->
368 			BidiMap.<String,Integer>create()
369 				.add("one", 1)
370 				.add("uno", 1)
371 				.build()
372 		);
373 	}
374 
375 	@Test void a23b_duplicateValuesInBuilder_overwritingKey() {
376 		// Test line 127: trying to add a value that's already mapped to a different key
377 		// This should throw IllegalArgumentException
378 		// The condition is: values.contains(value) && ! value.equals(existingValue)
379 		// where existingValue is the value currently mapped to the key being added
380 		assertThrows(IllegalArgumentException.class, () ->
381 			BidiMap.<String,Integer>create()
382 				.add("key1", 100)
383 				.add("key2", 200)
384 				.add("key1", 200)  // Try to map key1 to 200, but 200 is already mapped to key2
385 				.build()
386 		);
387 	}
388 
389 	@Test void a23c_overwriteKeyWithSameValue() {
390 		// Test line 127: overwriting a key with the same value should not throw
391 		// The condition is: values.contains(value) && ! value.equals(existingValue)
392 		// When value.equals(existingValue), the condition is false, so assertArg passes
393 		var map = BidiMap.<String,Integer>create()
394 			.add("key1", 100)
395 			.add("key2", 200)
396 			.add("key1", 100)  // Overwrite key1 with the same value (should be allowed)
397 			.build();
398 
399 		assertSize(2, map);
400 		assertEquals(100, map.get("key1"));
401 		assertEquals(200, map.get("key2"));
402 	}
403 
404 	@Test void a23d_overwriteKeyWithNewValue_notInValues() {
405 		// Test line 127: overwriting a key with a new value not in the values set
406 		// The condition is: values.contains(value) && ! value.equals(existingValue)
407 		// When !values.contains(value), the condition is false, so assertArg passes
408 		var map = BidiMap.<String,Integer>create()
409 			.add("key1", 100)
410 			.add("key2", 200)
411 			.add("key1", 300)  // Overwrite key1 with a new value not in values set
412 			.build();
413 
414 		assertSize(2, map);
415 		assertEquals(300, map.get("key1"));
416 		assertEquals(200, map.get("key2"));
417 	}
418 
419 	//====================================================================================================
420 	// Constructor coverage - toMap merge function (lines 185-186)
421 	//====================================================================================================
422 
423 	@Test void a24_constructorMergeFunction_duplicateKeysInBuilder() {
424 		// Test lines 185-186: The toMap merge function (a, b) -> b
425 		// Note: Since the builder uses LinkedHashMap, duplicate keys are overwritten before
426 		// reaching the constructor, so the merge function may not be called in practice.
427 		// However, this test verifies that the constructor correctly processes entries
428 		// and that the merge function is present as a safety measure.
429 		var map = BidiMap.<String,Integer>create()
430 			.add("key1", 100)
431 			.add("key1", 200)  // Overwrite - LinkedHashMap handles this
432 			.add("key1", 300)  // Overwrite again
433 			.build();
434 
435 		// The last value should win (merge function behavior: (a, b) -> b)
436 		assertSize(1, map);
437 		assertEquals(300, map.get("key1"));
438 		assertEquals("key1", map.getKey(300));
439 	}
440 
441 	@Test void a25_constructorMergeFunction_duplicateValuesInBuilder() {
442 		// Test line 186: The reverse map's toMap merge function (a, b) -> b
443 		// Note: The builder prevents duplicate values, so the merge function may not be called.
444 		// However, this test verifies that the constructor correctly processes entries
445 		// and handles value overwrites correctly.
446 		var map = BidiMap.<String,Integer>create()
447 			.add("key1", 100)
448 			.add("key2", 200)
449 			.add("key1", 300)  // Overwrite key1, removing 100 from reverse map
450 			.build();
451 
452 		// Verify the reverse map correctly reflects the overwrite
453 		assertSize(2, map);
454 		assertEquals(300, map.get("key1"));
455 		assertEquals(200, map.get("key2"));
456 		assertEquals("key1", map.getKey(300));
457 		assertEquals("key2", map.getKey(200));
458 		assertNull(map.getKey(100)); // Old value should be removed
459 	}
460 
461 	@Test void a26_constructorWithNullEntries_filteredCorrectly() {
462 		// Test lines 185-186: Verify that null keys and values are filtered correctly
463 		// in both forward and reverse maps during construction
464 		var map = BidiMap.<String,Integer>create()
465 			.add("key1", 1)
466 			.add(null, 2)      // Null key - should be filtered
467 			.add("key3", null) // Null value - should be filtered
468 			.add(null, null)   // Both null - should be filtered
469 			.add("key5", 5)
470 			.build();
471 
472 		// Only non-null entries should be present
473 		assertSize(2, map);
474 		assertEquals(1, map.get("key1"));
475 		assertEquals(5, map.get("key5"));
476 		assertEquals("key1", map.getKey(1));
477 		assertEquals("key5", map.getKey(5));
478 		assertNull(map.get(null));
479 		assertNull(map.getKey(null));
480 	}
481 
482 	@Test void a24_sizeAfterOperations() {
483 		var map = BidiMap.<String,Integer>create().build();
484 
485 		assertEmpty(map);
486 
487 		map.put("one", 1);
488 		assertSize(1, map);
489 
490 		map.put("two", 2);
491 		assertSize(2, map);
492 
493 		map.remove("one");
494 		assertSize(1, map);
495 
496 		map.clear();
497 		assertEmpty(map);
498 	}
499 
500 	@Test void a25_differentKeyValueTypes() {
501 		var map = BidiMap.<Integer,String>create()
502 			.add(1, "one")
503 			.add(2, "two")
504 			.build();
505 
506 		assertEquals("one", map.get(1));
507 		assertEquals(Integer.valueOf(2), map.getKey("two"));
508 	}
509 
510 	@Test void a26_put_duplicateValueThrowsException() {
511 		var map = BidiMap.<String,Integer>create()
512 			.add("one", 1)
513 			.build();
514 
515 		// Trying to add a different key with the same value should throw
516 		assertThrows(IllegalArgumentException.class, () -> map.put("uno", 1));
517 	}
518 
519 	@Test void a27_put_sameKeyDifferentValueAllowed() {
520 		var map = BidiMap.<String,Integer>create()
521 			.add("one", 1)
522 			.build();
523 
524 		// Updating the same key with a different value is allowed
525 		assertEquals(1, map.put("one", 10));
526 		assertEquals(10, map.get("one"));
527 		assertEquals("one", map.getKey(10));
528 		assertNull(map.getKey(1));
529 	}
530 
531 	@Test void a28_putAll_duplicateValueThrowsException() {
532 		var map = BidiMap.<String,Integer>create()
533 			.add("one", 1)
534 			.build();
535 
536 		var toAdd = new HashMap<String,Integer>();
537 		toAdd.put("uno", 1);
538 
539 		// Trying to add entries with duplicate values should throw
540 		assertThrows(IllegalArgumentException.class, () -> map.putAll(toAdd));
541 	}
542 
543 	//====================================================================================================
544 	// isEmpty
545 	//====================================================================================================
546 
547 	@Test void a29_isEmpty_emptyMap() {
548 		var map = BidiMap.<String,Integer>create().build();
549 
550 		assertTrue(map.isEmpty());
551 		assertSize(0, map);
552 	}
553 
554 	@Test void a30_isEmpty_nonEmptyMap() {
555 		var map = BidiMap.<String,Integer>create()
556 			.add("one", 1)
557 			.build();
558 
559 		assertFalse(map.isEmpty());
560 		assertSize(1, map);
561 	}
562 
563 	@Test void a31_isEmpty_afterClear() {
564 		var map = BidiMap.<String,Integer>create()
565 			.add("one", 1)
566 			.add("two", 2)
567 			.build();
568 
569 		assertFalse(map.isEmpty());
570 		map.clear();
571 		assertTrue(map.isEmpty());
572 	}
573 
574 	@Test void a32_isEmpty_afterRemove() {
575 		var map = BidiMap.<String,Integer>create()
576 			.add("one", 1)
577 			.build();
578 
579 		assertFalse(map.isEmpty());
580 		map.remove("one");
581 		assertTrue(map.isEmpty());
582 	}
583 
584 	//====================================================================================================
585 	// toString(), equals(), hashCode()
586 	//====================================================================================================
587 
588 	@Test
589 	void w01_toString_delegatesToForwardMap() {
590 		var map = BidiMap.<String,Integer>create()
591 			.add("one", 1)
592 			.add("two", 2)
593 			.build();
594 
595 		var forwardMap = new LinkedHashMap<String, Integer>();
596 		forwardMap.put("one", 1);
597 		forwardMap.put("two", 2);
598 
599 		assertEquals(forwardMap.toString(), map.toString());
600 	}
601 
602 	@Test
603 	void w02_equals_sameContents() {
604 		var map1 = BidiMap.<String,Integer>create()
605 			.add("one", 1)
606 			.add("two", 2)
607 			.build();
608 
609 		var map2 = BidiMap.<String,Integer>create()
610 			.add("one", 1)
611 			.add("two", 2)
612 			.build();
613 
614 		assertTrue(map1.equals(map2));
615 		assertTrue(map2.equals(map1));
616 	}
617 
618 	@Test
619 	void w03_equals_differentContents() {
620 		var map1 = BidiMap.<String,Integer>create()
621 			.add("one", 1)
622 			.build();
623 
624 		var map2 = BidiMap.<String,Integer>create()
625 			.add("one", 2)
626 			.build();
627 
628 		assertFalse(map1.equals(map2));
629 		assertFalse(map2.equals(map1));
630 	}
631 
632 	@Test
633 	void w04_equals_regularMap() {
634 		var map = BidiMap.<String,Integer>create()
635 			.add("one", 1)
636 			.add("two", 2)
637 			.build();
638 
639 		var regularMap = new LinkedHashMap<String, Integer>();
640 		regularMap.put("one", 1);
641 		regularMap.put("two", 2);
642 
643 		assertTrue(map.equals(regularMap));
644 		assertTrue(regularMap.equals(map));
645 	}
646 
647 	@Test
648 	void w05_equals_notAMap() {
649 		var map = BidiMap.<String,Integer>create()
650 			.add("one", 1)
651 			.build();
652 
653 		assertFalse(map.equals(null));
654 	}
655 
656 	@Test
657 	void w06_hashCode_sameContents() {
658 		var map1 = BidiMap.<String,Integer>create()
659 			.add("one", 1)
660 			.add("two", 2)
661 			.build();
662 
663 		var map2 = BidiMap.<String,Integer>create()
664 			.add("one", 1)
665 			.add("two", 2)
666 			.build();
667 
668 		assertEquals(map1.hashCode(), map2.hashCode());
669 	}
670 
671 	@Test
672 	void w07_hashCode_regularMap() {
673 		var map = BidiMap.<String,Integer>create()
674 			.add("one", 1)
675 			.add("two", 2)
676 			.build();
677 
678 		var regularMap = new LinkedHashMap<String, Integer>();
679 		regularMap.put("one", 1);
680 		regularMap.put("two", 2);
681 
682 		assertEquals(map.hashCode(), regularMap.hashCode());
683 	}
684 }