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.utils.CollectionUtils.*;
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 MultiMap_Test extends TestBase {
28  
29  	//====================================================================================================
30  	// Basic functionality - get(Object key)
31  	//====================================================================================================
32  
33  	@Test
34  	void a01_get_fromFirstMap() {
35  		var map1 = map("key1", "value1", "key2", "value2");
36  		var map2 = map("key3", "value3");
37  		var multiMap = new MultiMap<>(map1, map2);
38  
39  		assertEquals("value1", multiMap.get("key1"));
40  		assertEquals("value2", multiMap.get("key2"));
41  		assertEquals("value3", multiMap.get("key3"));
42  	}
43  
44  	@Test
45  	void a02_get_duplicateKey_returnsFirstMatch() {
46  		var map1 = map("key1", "value1");
47  		var map2 = map("key1", "value2");
48  		var map3 = map("key1", "value3");
49  		var multiMap = new MultiMap<>(map1, map2, map3);
50  
51  		assertEquals("value1", multiMap.get("key1")); // First match wins
52  	}
53  
54  	@Test
55  	void a03_get_nonexistentKey_returnsNull() {
56  		var map1 = map("key1", "value1");
57  		var map2 = map("key2", "value2");
58  		var multiMap = new MultiMap<>(map1, map2);
59  
60  		assertNull(multiMap.get("key3"));
61  	}
62  
63  	@Test
64  	void a04_get_emptyMaps() {
65  		var map1 = map();
66  		var map2 = map();
67  		var multiMap = new MultiMap<>(map1, map2);
68  
69  		assertNull(multiMap.get("key1"));
70  		assertTrue(multiMap.isEmpty());
71  	}
72  
73  	//====================================================================================================
74  	// containsKey(Object key)
75  	//====================================================================================================
76  
77  	@Test
78  	void b01_containsKey_existsInFirstMap() {
79  		var map1 = map("key1", "value1");
80  		var map2 = map("key2", "value2");
81  		var multiMap = new MultiMap<>(map1, map2);
82  
83  		assertTrue(multiMap.containsKey("key1"));
84  		assertTrue(multiMap.containsKey("key2"));
85  		assertFalse(multiMap.containsKey("key3"));
86  	}
87  
88  	@Test
89  	void b02_containsKey_existsInSecondMap() {
90  		var map1 = map("key1", "value1");
91  		var map2 = map("key2", "value2");
92  		var multiMap = new MultiMap<>(map1, map2);
93  
94  		assertTrue(multiMap.containsKey("key2"));
95  	}
96  
97  	@Test
98  	void b03_containsKey_duplicateKey_returnsTrue() {
99  		var map1 = map("key1", "value1");
100 		var map2 = map("key1", "value2");
101 		var multiMap = new MultiMap<>(map1, map2);
102 
103 		assertTrue(multiMap.containsKey("key1"));
104 	}
105 
106 	//====================================================================================================
107 	// containsValue(Object value)
108 	//====================================================================================================
109 
110 	@Test
111 	void c01_containsValue_existsInAnyMap() {
112 		var map1 = map("key1", "value1");
113 		var map2 = map("key2", "value2");
114 		var multiMap = new MultiMap<>(map1, map2);
115 
116 		assertTrue(multiMap.containsValue("value1"));
117 		assertTrue(multiMap.containsValue("value2"));
118 		assertFalse(multiMap.containsValue("value3"));
119 	}
120 
121 	@Test
122 	void c02_containsValue_duplicateValue_returnsTrue() {
123 		var map1 = map("key1", "value1");
124 		var map2 = map("key2", "value1");
125 		var multiMap = new MultiMap<>(map1, map2);
126 
127 		assertTrue(multiMap.containsValue("value1"));
128 	}
129 
130 	//====================================================================================================
131 	// size()
132 	//====================================================================================================
133 
134 	@Test
135 	void d01_size_countsUniqueKeys() {
136 		var map1 = map("key1", "value1", "key2", "value2");
137 		var map2 = map("key3", "value3");
138 		var multiMap = new MultiMap<>(map1, map2);
139 
140 		assertEquals(3, multiMap.size());
141 	}
142 
143 	@Test
144 	void d02_size_duplicateKeys_countedOnce() {
145 		var map1 = map("key1", "value1", "key2", "value2");
146 		var map2 = map("key2", "value2b", "key3", "value3");
147 		var multiMap = new MultiMap<>(map1, map2);
148 
149 		assertEquals(3, multiMap.size()); // key1, key2, key3 (key2 counted once)
150 	}
151 
152 	@Test
153 	void d03_size_emptyMaps() {
154 		var map1 = map();
155 		var map2 = map();
156 		var multiMap = new MultiMap<>(map1, map2);
157 
158 		assertEquals(0, multiMap.size());
159 	}
160 
161 	@Test
162 	void d04_size_singleMap() {
163 		var map1 = map("key1", "value1", "key2", "value2");
164 		var multiMap = new MultiMap<>(map1);
165 
166 		assertEquals(2, multiMap.size());
167 	}
168 
169 	//====================================================================================================
170 	// isEmpty()
171 	//====================================================================================================
172 
173 	@Test
174 	void e01_isEmpty_allMapsEmpty() {
175 		var map1 = map();
176 		var map2 = map();
177 		var multiMap = new MultiMap<>(map1, map2);
178 
179 		assertTrue(multiMap.isEmpty());
180 	}
181 
182 	@Test
183 	void e02_isEmpty_someMapsHaveEntries() {
184 		Map<String, String> map1 = map();
185 		var map2 = map("key1", "value1");
186 		var multiMap = new MultiMap<>(map1, map2);
187 
188 		assertFalse(multiMap.isEmpty());
189 	}
190 
191 	//====================================================================================================
192 	// entrySet()
193 	//====================================================================================================
194 
195 	@Test
196 	void f01_entrySet_iteratesAllEntries() {
197 		var map1 = map("key1", "value1", "key2", "value2");
198 		var map2 = map("key3", "value3");
199 		var multiMap = new MultiMap<>(map1, map2);
200 
201 		var entries = new ArrayList<Map.Entry<String, String>>();
202 		multiMap.entrySet().forEach(entries::add);
203 
204 		assertEquals(3, entries.size());
205 		assertTrue(entries.stream().anyMatch(e -> e.getKey().equals("key1") && e.getValue().equals("value1")));
206 		assertTrue(entries.stream().anyMatch(e -> e.getKey().equals("key2") && e.getValue().equals("value2")));
207 		assertTrue(entries.stream().anyMatch(e -> e.getKey().equals("key3") && e.getValue().equals("value3")));
208 	}
209 
210 	@Test
211 	void f02_entrySet_duplicateKeys_onlyFirstIncluded() {
212 		var map1 = map("key1", "value1");
213 		var map2 = map("key1", "value2");
214 		var map3 = map("key2", "value3");
215 		var multiMap = new MultiMap<>(map1, map2, map3);
216 
217 		var entries = new ArrayList<Map.Entry<String, String>>();
218 		multiMap.entrySet().forEach(entries::add);
219 
220 		assertEquals(2, entries.size());
221 		// key1 should have value1 (from first map)
222 		var key1Entry = entries.stream().filter(e -> e.getKey().equals("key1")).findFirst().orElse(null);
223 		assertNotNull(key1Entry);
224 		assertEquals("value1", key1Entry.getValue());
225 	}
226 
227 	@Test
228 	void f03_entrySet_iterator_remove() {
229 		var map1 = new LinkedHashMap<>(map("key1", "value1", "key2", "value2"));
230 		var map2 = new LinkedHashMap<>(map("key3", "value3"));
231 		var multiMap = new MultiMap<>(map1, map2);
232 
233 		var iterator = multiMap.entrySet().iterator();
234 		while (iterator.hasNext()) {
235 			var entry = iterator.next();
236 			if (entry.getKey().equals("key2")) {
237 				iterator.remove();
238 			}
239 		}
240 
241 		assertFalse(map1.containsKey("key2"));
242 		assertEquals(2, multiMap.size());
243 	}
244 
245 	@Test
246 	void f04_entrySet_size() {
247 		var map1 = map("key1", "value1", "key2", "value2");
248 		var map2 = map("key2", "value2b", "key3", "value3");
249 		var multiMap = new MultiMap<>(map1, map2);
250 
251 		assertEquals(3, multiMap.entrySet().size()); // key1, key2, key3
252 	}
253 
254 	//====================================================================================================
255 	// keySet()
256 	//====================================================================================================
257 
258 	@Test
259 	void g01_keySet_containsAllKeys() {
260 		var map1 = map("key1", "value1", "key2", "value2");
261 		var map2 = map("key3", "value3");
262 		var multiMap = new MultiMap<>(map1, map2);
263 
264 		var keySet = multiMap.keySet();
265 		assertTrue(keySet.contains("key1"));
266 		assertTrue(keySet.contains("key2"));
267 		assertTrue(keySet.contains("key3"));
268 		assertFalse(keySet.contains("key4"));
269 		assertEquals(3, keySet.size());
270 	}
271 
272 	@Test
273 	void g02_keySet_duplicateKeys_countedOnce() {
274 		var map1 = map("key1", "value1", "key2", "value2");
275 		var map2 = map("key2", "value2b", "key3", "value3");
276 		var multiMap = new MultiMap<>(map1, map2);
277 
278 		var keySet = multiMap.keySet();
279 		assertEquals(3, keySet.size()); // key1, key2, key3
280 		assertTrue(keySet.contains("key1"));
281 		assertTrue(keySet.contains("key2"));
282 		assertTrue(keySet.contains("key3"));
283 	}
284 
285 	//====================================================================================================
286 	// values()
287 	//====================================================================================================
288 
289 	@Test
290 	void h01_values_containsAllValues() {
291 		var map1 = map("key1", "value1", "key2", "value2");
292 		var map2 = map("key3", "value3");
293 		var multiMap = new MultiMap<>(map1, map2);
294 
295 		var values = multiMap.values();
296 		assertTrue(values.contains("value1"));
297 		assertTrue(values.contains("value2"));
298 		assertTrue(values.contains("value3"));
299 		assertEquals(3, values.size());
300 	}
301 
302 	@Test
303 	void h02_values_duplicateKeys_usesFirstValue() {
304 		var map1 = map("key1", "value1");
305 		var map2 = map("key1", "value2");
306 		var multiMap = new MultiMap<>(map1, map2);
307 
308 		var values = multiMap.values();
309 		assertTrue(values.contains("value1"));
310 		assertFalse(values.contains("value2")); // value2 is not returned because key1 is in first map
311 		assertEquals(1, values.size());
312 	}
313 
314 	//====================================================================================================
315 	// Unsupported operations
316 	//====================================================================================================
317 
318 	@Test
319 	void i01_put_throwsUnsupportedOperationException() {
320 		var map1 = map("key1", "value1");
321 		var multiMap = new MultiMap<>(map1);
322 
323 		assertThrows(UnsupportedOperationException.class, () -> multiMap.put("key2", "value2"));
324 	}
325 
326 	@Test
327 	void i02_remove_throwsUnsupportedOperationException() {
328 		var map1 = map("key1", "value1");
329 		var multiMap = new MultiMap<>(map1);
330 
331 		assertThrows(UnsupportedOperationException.class, () -> multiMap.remove("key1"));
332 	}
333 
334 	@Test
335 	void i03_putAll_throwsUnsupportedOperationException() {
336 		var map1 = map("key1", "value1");
337 		var multiMap = new MultiMap<>(map1);
338 		var map2 = map("key2", "value2");
339 
340 		assertThrows(UnsupportedOperationException.class, () -> multiMap.putAll(map2));
341 	}
342 
343 	@Test
344 	void i04_clear_throwsUnsupportedOperationException() {
345 		var map1 = map("key1", "value1");
346 		var multiMap = new MultiMap<>(map1);
347 
348 		assertThrows(UnsupportedOperationException.class, multiMap::clear);
349 	}
350 
351 	//====================================================================================================
352 	// Edge cases
353 	//====================================================================================================
354 
355 	@Test
356 	void j01_singleMap() {
357 		var map1 = map("key1", "value1", "key2", "value2");
358 		var multiMap = new MultiMap<>(map1);
359 
360 		assertEquals(2, multiMap.size());
361 		assertEquals("value1", multiMap.get("key1"));
362 		assertEquals("value2", multiMap.get("key2"));
363 	}
364 
365 	@Test
366 	void j02_threeMaps() {
367 		var map1 = map("key1", "value1");
368 		var map2 = map("key2", "value2");
369 		var map3 = map("key3", "value3");
370 		var multiMap = new MultiMap<>(map1, map2, map3);
371 
372 		assertEquals(3, multiMap.size());
373 		assertEquals("value1", multiMap.get("key1"));
374 		assertEquals("value2", multiMap.get("key2"));
375 		assertEquals("value3", multiMap.get("key3"));
376 	}
377 
378 	@Test
379 	void j03_nullValue() {
380 		Map<String, String> map1 = map("key1", null);
381 		var map2 = map("key2", "value2");
382 		var multiMap = new MultiMap<>(map1, map2);
383 
384 		assertNull(multiMap.get("key1"));
385 		assertTrue(multiMap.containsKey("key1"));
386 		assertTrue(multiMap.containsValue(null));
387 	}
388 
389 	@Test
390 	void j04_nullKey() {
391 		var map1 = new LinkedHashMap<String, String>();
392 		map1.put(null, "value1");
393 		var map2 = map("key2", "value2");
394 		var multiMap = new MultiMap<>(map1, map2);
395 
396 		assertEquals("value1", multiMap.get(null));
397 		assertTrue(multiMap.containsKey(null));
398 	}
399 
400 	//====================================================================================================
401 	// toString()
402 	//====================================================================================================
403 
404 	@Test
405 	void k01_toString_singleMap() {
406 		var map1 = map("key1", "value1", "key2", "value2");
407 		var multiMap = new MultiMap<>(map1);
408 
409 		var expected = "[" + map1.toString() + "]";
410 		assertEquals(expected, multiMap.toString());
411 	}
412 
413 	@Test
414 	void k02_toString_multipleMaps() {
415 		var map1 = map("key1", "value1");
416 		var map2 = map("key2", "value2");
417 		var map3 = map("key3", "value3");
418 		var multiMap = new MultiMap<>(map1, map2, map3);
419 
420 		var expected = "[" + map1.toString() + ", " + map2.toString() + ", " + map3.toString() + "]";
421 		assertEquals(expected, multiMap.toString());
422 	}
423 
424 	@Test
425 	void k03_toString_emptyMaps() {
426 		Map<String, String> map1 = map();
427 		Map<String, String> map2 = map();
428 		var multiMap = new MultiMap<>(map1, map2);
429 
430 		var expected = "[" + map1.toString() + ", " + map2.toString() + "]";
431 		assertEquals(expected, multiMap.toString());
432 	}
433 
434 	@Test
435 	void k04_toString_mixedEmptyAndNonEmpty() {
436 		Map<String, String> map1 = map();
437 		var map2 = map("key1", "value1");
438 		Map<String, String> map3 = map();
439 		var multiMap = new MultiMap<>(map1, map2, map3);
440 
441 		var expected = "[" + map1.toString() + ", " + map2.toString() + ", " + map3.toString() + "]";
442 		assertEquals(expected, multiMap.toString());
443 	}
444 
445 	//====================================================================================================
446 	// equals() and hashCode()
447 	//====================================================================================================
448 
449 	@Test
450 	void l01_equals_sameContents() {
451 		var map1 = map("key1", "value1", "key2", "value2");
452 		var map2 = map("key3", "value3");
453 		var multiMap1 = new MultiMap<>(map1, map2);
454 
455 		var map3 = map("key1", "value1", "key2", "value2");
456 		var map4 = map("key3", "value3");
457 		var multiMap2 = new MultiMap<>(map3, map4);
458 
459 		assertTrue(multiMap1.equals(multiMap2));
460 		assertTrue(multiMap2.equals(multiMap1));
461 	}
462 
463 	@Test
464 	void l02_equals_differentContents() {
465 		var map1 = map("key1", "value1");
466 		var multiMap1 = new MultiMap<>(map1);
467 
468 		var map2 = map("key1", "value2");
469 		var multiMap2 = new MultiMap<>(map2);
470 
471 		assertFalse(multiMap1.equals(multiMap2));
472 		assertFalse(multiMap2.equals(multiMap1));
473 	}
474 
475 	@Test
476 	void l03_equals_regularMap() {
477 		var map1 = map("key1", "value1", "key2", "value2");
478 		var multiMap = new MultiMap<>(map1);
479 
480 		var regularMap = new LinkedHashMap<>(map("key1", "value1", "key2", "value2"));
481 
482 		assertTrue(multiMap.equals(regularMap));
483 		assertTrue(regularMap.equals(multiMap));
484 	}
485 
486 	@Test
487 	void l04_equals_notAMap() {
488 		var map1 = map("key1", "value1");
489 		var multiMap = new MultiMap<>(map1);
490 
491 		assertFalse(multiMap.equals(null));
492 	}
493 
494 	@Test
495 	void l05_hashCode_sameContents() {
496 		var map1 = map("key1", "value1", "key2", "value2");
497 		var map2 = map("key3", "value3");
498 		var multiMap1 = new MultiMap<>(map1, map2);
499 
500 		var map3 = map("key1", "value1", "key2", "value2");
501 		var map4 = map("key3", "value3");
502 		var multiMap2 = new MultiMap<>(map3, map4);
503 
504 		assertEquals(multiMap1.hashCode(), multiMap2.hashCode());
505 	}
506 
507 	@Test
508 	void l06_hashCode_regularMap() {
509 		var map1 = map("key1", "value1", "key2", "value2");
510 		var multiMap = new MultiMap<>(map1);
511 
512 		var regularMap = new LinkedHashMap<>(map("key1", "value1", "key2", "value2"));
513 
514 		assertEquals(multiMap.hashCode(), regularMap.hashCode());
515 	}
516 
517 	//====================================================================================================
518 	// Additional coverage for specific lines
519 	//====================================================================================================
520 
521 	@Test
522 	void m01_entrySet_iterator_emptyMaps() {
523 		// Line 218: if (m.length > 0) - when there are no maps
524 		Map<String, String> map1 = map();
525 		Map<String, String> map2 = map();
526 		var multiMap = new MultiMap<>(map1, map2);
527 		var iterator = multiMap.entrySet().iterator();
528 		assertFalse(iterator.hasNext());
529 	}
530 
531 	@Test
532 	void m02_entrySet_iterator_next_throwsWhenNextEntryIsNull() {
533 		// Line 253: throw NoSuchElementException when nextEntry == null
534 		Map<String, String> map1 = map();
535 		var multiMap = new MultiMap<>(map1);
536 		var iterator = multiMap.entrySet().iterator();
537 		assertThrows(NoSuchElementException.class, iterator::next);
538 	}
539 
540 	@Test
541 	void m03_entrySet_iterator_remove_throwsWhenCanRemoveIsFalse() {
542 		// Line 264: throw IllegalStateException when canRemove is false or lastIterator is null
543 		var map1 = new LinkedHashMap<>(map("key1", "value1"));
544 		var multiMap = new MultiMap<>(map1);
545 		var iterator = multiMap.entrySet().iterator();
546 		// Remove without calling next first
547 		assertThrows(IllegalStateException.class, iterator::remove);
548 	}
549 
550 	@Test
551 	void m04_entrySet_iterator_remove_throwsWhenLastIteratorIsNull() {
552 		// Line 264: throw IllegalStateException when lastIterator is null
553 		var map1 = new LinkedHashMap<>(map("key1", "value1"));
554 		var multiMap = new MultiMap<>(map1);
555 		var iterator = multiMap.entrySet().iterator();
556 		iterator.next(); // Sets canRemove = true and lastIterator
557 		iterator.remove(); // First remove works
558 		// Now try to remove again without calling next
559 		assertThrows(IllegalStateException.class, iterator::remove);
560 	}
561 
562 	@Test
563 	void m05_values_iterator_remove() {
564 		// Lines 350-351: entryIterator.remove() in values iterator
565 		// Test that remove() delegates to entryIterator.remove()
566 		var map1 = new LinkedHashMap<>(map("key1", "value1"));
567 		var multiMap = new MultiMap<>(map1);
568 		var valuesIterator = multiMap.values().iterator();
569 		
570 		// Get first value
571 		assertEquals("value1", valuesIterator.next());
572 		
573 		// Remove should work (delegates to entryIterator.remove() which calls entrySet iterator remove)
574 		// This covers lines 350-351
575 		valuesIterator.remove();
576 		
577 		// Verify the entry was removed from the underlying map
578 		assertFalse(map1.containsKey("key1"));
579 		assertTrue(map1.isEmpty());
580 	}
581 }
582