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.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.junit.jupiter.api.*;
27  
28  class MultiList_Test extends TestBase {
29  
30  	@Test
31  	void a01_basicIteration() {
32  		List<String> l1, l2;
33  		MultiList<String> ml;
34  
35  		l1 = l(a("1", "2"));
36  		l2 = l(a("3", "4"));
37  		ml = new MultiList<>(l1, l2);
38  		var i1 = ml.iterator();
39  		assertTrue(i1.hasNext());
40  		assertEquals("1", i1.next());
41  		assertTrue(i1.hasNext());
42  		assertEquals("2", i1.next());
43  		assertTrue(i1.hasNext());
44  		assertEquals("3", i1.next());
45  		assertTrue(i1.hasNext());
46  		assertEquals("4", i1.next());
47  		assertFalse(i1.hasNext());
48  		assertThrows(NoSuchElementException.class, i1::next);
49  	}
50  
51  	@Test
52  	void a02_emptySecondList() {
53  		List<String> l1 = l(a("1", "2"));
54  		List<String> l2 = l(a());
55  		MultiList<String> ml = new MultiList<>(l1, l2);
56  		var i2 = ml.iterator();
57  		assertTrue(i2.hasNext());
58  		assertEquals("1", i2.next());
59  		assertTrue(i2.hasNext());
60  		assertEquals("2", i2.next());
61  		assertFalse(i2.hasNext());
62  		assertThrows(NoSuchElementException.class, i2::next);
63  	}
64  
65  	@Test
66  	void a03_emptyFirstList() {
67  		List<String> l1 = l(a());
68  		List<String> l2 = l(a("3", "4"));
69  		MultiList<String> ml = new MultiList<>(l1, l2);
70  		var i3 = ml.iterator();
71  		assertTrue(i3.hasNext());
72  		assertEquals("3", i3.next());
73  		assertTrue(i3.hasNext());
74  		assertEquals("4", i3.next());
75  		assertFalse(i3.hasNext());
76  		assertThrows(NoSuchElementException.class, i3::next);
77  	}
78  
79  	@Test
80  	void a04_bothEmptyLists() {
81  		List<String> l1 = l(a());
82  		List<String> l2 = l(a());
83  		MultiList<String> ml = new MultiList<>(l1, l2);
84  		var i4 = ml.iterator();
85  		assertFalse(i4.hasNext());
86  		assertThrows(NoSuchElementException.class, i4::next);
87  	}
88  
89  	@Test
90  	void a05_singleList() {
91  		List<String> l1 = l(a("1", "2"));
92  		MultiList<String> ml = new MultiList<>(l1);
93  		var i5 = ml.iterator();
94  		assertTrue(i5.hasNext());
95  		assertEquals("1", i5.next());
96  		assertTrue(i5.hasNext());
97  		assertEquals("2", i5.next());
98  		assertFalse(i5.hasNext());
99  		assertThrows(NoSuchElementException.class, i5::next);
100 	}
101 
102 	@Test
103 	void a06_assertListAndEnumerator() {
104 		List<String> l1 = new LinkedList<>(l(a("1", "2")));
105 		List<String> l2 = new LinkedList<>(l(a("3", "4")));
106 		MultiList<String> ml = new MultiList<>(l1, l2);
107 		assertList(ml, "1", "2", "3", "4");
108 		assertList(ml.enumerator(), "1", "2", "3", "4");
109 		assertSize(4, ml);
110 	}
111 
112 	@Test
113 	void a07_iteratorRemove() {
114 		List<String> l1 = new LinkedList<>(l(a("1", "2")));
115 		List<String> l2 = new LinkedList<>(l(a("3", "4")));
116 		MultiList<String> ml = new MultiList<>(l1, l2);
117 
118 		var t = ml.iterator();
119 		t.next();
120 		t.remove();
121 		assertList(ml.enumerator(), "2", "3", "4");
122 
123 		t = ml.iterator();
124 		t.next();
125 		t.remove();
126 		assertList(ml.enumerator(), "3", "4");
127 
128 		t = ml.iterator();
129 		t.next();
130 		t.remove();
131 		assertList(ml.enumerator(), "4");
132 
133 		t = ml.iterator();
134 		t.next();
135 		t.remove();
136 		assertEmpty(ml.enumerator());
137 		assertEmpty(ml);
138 	}
139 
140 	@Test
141 	void a08_emptyMultiList() {
142 		MultiList<String> ml = new MultiList<>();
143 		assertEmpty(ml);
144 		assertThrows(NoSuchElementException.class, () -> new MultiList<String>().iterator().next());
145 		assertThrows(IllegalStateException.class, () -> new MultiList<String>().iterator().remove());
146 	}
147 
148 	@Test
149 	void a09_nullListThrowsException() {
150 		assertThrows(IllegalArgumentException.class, () -> new MultiList<>((List<String>)null));
151 	}
152 
153 	@Test
154 	void a10_hasNext_whenCurrentIteratorExhausted_butMoreListsHaveElements() {
155 		// Test the hasNext() logic when current iterator is exhausted but remaining lists have elements
156 		var l1 = l(a("1", "2"));
157 		var l2 = l(a("3", "4"));
158 		var l3 = l(a("5", "6"));
159 		var ml = new MultiList<>(l1, l2, l3);
160 		var it = ml.iterator();
161 
162 		// Exhaust the first list's iterator
163 		assertTrue(it.hasNext());
164 		assertEquals("1", it.next());
165 		assertTrue(it.hasNext());
166 		assertEquals("2", it.next());
167 
168 		// Now i2.hasNext() should be false, but hasNext() should return true
169 		// because there are more lists with elements
170 		assertTrue(it.hasNext()); // Should check remaining lists
171 		assertEquals("3", it.next());
172 
173 		// Continue to exhaust second list
174 		assertTrue(it.hasNext());
175 		assertEquals("4", it.next());
176 
177 		// Now should check third list
178 		assertTrue(it.hasNext());
179 		assertEquals("5", it.next());
180 		assertTrue(it.hasNext());
181 		assertEquals("6", it.next());
182 		assertFalse(it.hasNext());
183 	}
184 
185 	@Test
186 	void a11_hasNext_withEmptyListsInBetween() {
187 		// Test hasNext() when there are empty lists between non-empty ones
188 		var l1 = l(a("1"));
189 		var l2 = l(new String[0]);
190 		var l3 = l(a("2"));
191 		var l4 = l(new String[0]);
192 		var l5 = l(a("3"));
193 		var ml = new MultiList<>(l1, l2, l3, l4, l5);
194 		var it = ml.iterator();
195 
196 		// Exhaust first list
197 		assertTrue(it.hasNext());
198 		assertEquals("1", it.next());
199 
200 		// Now hasNext() should skip empty lists and find l3
201 		assertTrue(it.hasNext()); // Should skip l2 (empty) and find l3
202 		assertEquals("2", it.next());
203 
204 		// Should skip l4 (empty) and find l5
205 		assertTrue(it.hasNext());
206 		assertEquals("3", it.next());
207 		assertFalse(it.hasNext());
208 	}
209 
210 	//-----------------------------------------------------------------------------------------------------------------
211 	// get(int index) tests
212 	//-----------------------------------------------------------------------------------------------------------------
213 
214 	@Test
215 	void b01_getByIndex() {
216 		var l1 = l(a("1", "2"));
217 		var l2 = l(a("3", "4", "5"));
218 		var ml = new MultiList<>(l1, l2);
219 
220 		assertEquals("1", ml.get(0));
221 		assertEquals("2", ml.get(1));
222 		assertEquals("3", ml.get(2));
223 		assertEquals("4", ml.get(3));
224 		assertEquals("5", ml.get(4));
225 	}
226 
227 	@Test
228 	void b02_getByIndex_withEmptyLists() {
229 		var l1 = l(a("1"));
230 		var l2 = l(new String[0]);
231 		var l3 = l(a("2", "3"));
232 		var ml = new MultiList<>(l1, l2, l3);
233 
234 		assertEquals("1", ml.get(0));
235 		assertEquals("2", ml.get(1));
236 		assertEquals("3", ml.get(2));
237 	}
238 
239 	@Test
240 	void b03_getByIndex_outOfBounds() {
241 		var l1 = l(a("1", "2"));
242 		var ml = new MultiList<>(l1);
243 
244 		assertThrows(IndexOutOfBoundsException.class, () -> ml.get(-1));
245 		assertThrows(IndexOutOfBoundsException.class, () -> ml.get(2));
246 	}
247 
248 	@Test
249 	void b04_getByIndex_singleElementLists() {
250 		var l1 = l(a("1"));
251 		var l2 = l(a("2"));
252 		var l3 = l(a("3"));
253 		var ml = new MultiList<>(l1, l2, l3);
254 
255 		assertEquals("1", ml.get(0));
256 		assertEquals("2", ml.get(1));
257 		assertEquals("3", ml.get(2));
258 	}
259 
260 	//-----------------------------------------------------------------------------------------------------------------
261 	// listIterator() tests
262 	//-----------------------------------------------------------------------------------------------------------------
263 
264 	@Test
265 	void c01_listIterator_forward() {
266 		var l1 = l(a("1", "2"));
267 		var l2 = l(a("3", "4"));
268 		var ml = new MultiList<>(l1, l2);
269 		var li = ml.listIterator();
270 
271 		assertTrue(li.hasNext());
272 		assertEquals(0, li.nextIndex());
273 		assertEquals("1", li.next());
274 		assertEquals(1, li.nextIndex());
275 		assertEquals("2", li.next());
276 		assertEquals(2, li.nextIndex());
277 		assertEquals("3", li.next());
278 		assertEquals(3, li.nextIndex());
279 		assertEquals("4", li.next());
280 		assertEquals(4, li.nextIndex());
281 		assertFalse(li.hasNext());
282 	}
283 
284 	@Test
285 	void c02_listIterator_backward() {
286 		var l1 = l(a("1", "2"));
287 		var l2 = l(a("3", "4"));
288 		var ml = new MultiList<>(l1, l2);
289 		var li = ml.listIterator(ml.size());
290 
291 		assertTrue(li.hasPrevious());
292 		assertEquals(3, li.previousIndex());
293 		assertEquals("4", li.previous());
294 		assertEquals(2, li.previousIndex());
295 		assertEquals("3", li.previous());
296 		assertEquals(1, li.previousIndex());
297 		assertEquals("2", li.previous());
298 		assertEquals(0, li.previousIndex());
299 		assertEquals("1", li.previous());
300 		assertEquals(-1, li.previousIndex());
301 		assertFalse(li.hasPrevious());
302 	}
303 
304 	@Test
305 	void c03_listIterator_bidirectional() {
306 		var l1 = l(a("1", "2"));
307 		var l2 = l(a("3", "4"));
308 		var ml = new MultiList<>(l1, l2);
309 		var li = ml.listIterator();
310 
311 		// Forward
312 		assertEquals("1", li.next());
313 		assertEquals("2", li.next());
314 
315 		// Backward
316 		assertEquals("2", li.previous());
317 		assertEquals("1", li.previous());
318 
319 		// Forward again
320 		assertEquals("1", li.next());
321 		assertEquals("2", li.next());
322 		assertEquals("3", li.next());
323 	}
324 
325 	@Test
326 	void c04_listIterator_remove() {
327 		var l1 = new LinkedList<>(l(a("1", "2")));
328 		var l2 = new LinkedList<>(l(a("3", "4")));
329 		var ml = new MultiList<>(l1, l2);
330 		var li = ml.listIterator();
331 
332 		li.next(); // "1"
333 		li.next(); // "2"
334 		li.remove(); // Remove "2"
335 		assertList(ml, "1", "3", "4");
336 
337 		li.next(); // "3"
338 		li.remove(); // Remove "3"
339 		assertList(ml, "1", "4");
340 	}
341 
342 	@Test
343 	void c05_listIterator_set() {
344 		var l1 = new ArrayList<>(l(a("1", "2")));
345 		var l2 = new ArrayList<>(l(a("3", "4")));
346 		var ml = new MultiList<>(l1, l2);
347 		var li = ml.listIterator();
348 
349 		li.next(); // "1"
350 		li.set("10");
351 		assertEquals("10", ml.get(0));
352 
353 		li.next(); // "2"
354 		li.next(); // "3"
355 		li.set("30");
356 		assertEquals("30", ml.get(2));
357 	}
358 
359 	@Test
360 	void c06_listIterator_addThrowsException() {
361 		var l1 = l(a("1", "2"));
362 		var ml = new MultiList<>(l1);
363 		var li = ml.listIterator();
364 
365 		li.next();
366 		assertThrows(UnsupportedOperationException.class, () -> li.add("x"));
367 	}
368 
369 	@Test
370 	void c07_listIterator_startAtIndex() {
371 		var l1 = l(a("1", "2"));
372 		var l2 = l(a("3", "4"));
373 		var ml = new MultiList<>(l1, l2);
374 		var li = ml.listIterator(2);
375 
376 		assertTrue(li.hasNext());
377 		assertEquals(2, li.nextIndex());
378 		assertEquals("3", li.next());
379 		assertEquals("4", li.next());
380 		assertFalse(li.hasNext());
381 	}
382 
383 	@Test
384 	void c08_listIterator_startAtEnd() {
385 		var l1 = l(a("1", "2"));
386 		var l2 = l(a("3", "4"));
387 		var ml = new MultiList<>(l1, l2);
388 		var li = ml.listIterator(ml.size());
389 
390 		assertFalse(li.hasNext());
391 		assertTrue(li.hasPrevious());
392 		assertEquals("4", li.previous());
393 	}
394 
395 	@Test
396 	void c09_listIterator_outOfBounds() {
397 		var l1 = l(a("1", "2"));
398 		var ml = new MultiList<>(l1);
399 
400 		assertThrows(IndexOutOfBoundsException.class, () -> ml.listIterator(-1));
401 		assertThrows(IndexOutOfBoundsException.class, () -> ml.listIterator(3));
402 	}
403 
404 	//-----------------------------------------------------------------------------------------------------------------
405 	// size() tests
406 	//-----------------------------------------------------------------------------------------------------------------
407 
408 	@Test
409 	void d01_size() {
410 		var l1 = l(a("1", "2"));
411 		var l2 = l(a("3", "4", "5"));
412 		var ml = new MultiList<>(l1, l2);
413 
414 		assertEquals(5, ml.size());
415 	}
416 
417 	@Test
418 	void d02_size_withEmptyLists() {
419 		var l1 = l(new String[0]);
420 		var l2 = l(a("1"));
421 		var l3 = l(new String[0]);
422 		var ml = new MultiList<>(l1, l2, l3);
423 
424 		assertEquals(1, ml.size());
425 	}
426 
427 	@Test
428 	void d03_size_emptyMultiList() {
429 		var ml = new MultiList<String>();
430 		assertEquals(0, ml.size());
431 	}
432 
433 	//-----------------------------------------------------------------------------------------------------------------
434 	// Integration tests
435 	//-----------------------------------------------------------------------------------------------------------------
436 
437 	@Test
438 	void e01_forEach() {
439 		var l1 = l(a("1", "2"));
440 		var l2 = l(a("3", "4"));
441 		var ml = new MultiList<>(l1, l2);
442 		var result = new ArrayList<String>();
443 
444 		ml.forEach(result::add);
445 		assertList(result, "1", "2", "3", "4");
446 	}
447 
448 	@Test
449 	void e02_stream() {
450 		var l1 = l(a("1", "2"));
451 		var l2 = l(a("3", "4"));
452 		var ml = new MultiList<>(l1, l2);
453 
454 		var result = ml.stream().toList();
455 		assertList(result, "1", "2", "3", "4");
456 	}
457 
458 	@Test
459 	void e03_indexOf() {
460 		var l1 = l(a("1", "2"));
461 		var l2 = l(a("3", "2", "4"));
462 		var ml = new MultiList<>(l1, l2);
463 
464 		assertEquals(1, ml.indexOf("2")); // First occurrence at index 1
465 		assertEquals(3, ml.lastIndexOf("2")); // Last occurrence at index 3 (not 4, which is "4")
466 	}
467 
468 	@Test
469 	void e04_contains() {
470 		var l1 = l(a("1", "2"));
471 		var l2 = l(a("3", "4"));
472 		var ml = new MultiList<>(l1, l2);
473 
474 		assertTrue(ml.contains("2"));
475 		assertTrue(ml.contains("3"));
476 		assertFalse(ml.contains("5"));
477 	}
478 
479 	@Test
480 	void e05_toArray() {
481 		var l1 = l(a("1", "2"));
482 		var l2 = l(a("3", "4"));
483 		var ml = new MultiList<>(l1, l2);
484 
485 		var array = ml.toArray();
486 		assertEquals(4, array.length);
487 		assertEquals("1", array[0]);
488 		assertEquals("2", array[1]);
489 		assertEquals("3", array[2]);
490 		assertEquals("4", array[3]);
491 	}
492 
493 	//====================================================================================================
494 	// toString()
495 	//====================================================================================================
496 
497 	@Test
498 	void f01_toString_singleList() {
499 		var l1 = l(a("1", "2"));
500 		var ml = new MultiList<>(l1);
501 
502 		var expected = "[" + l1.toString() + "]";
503 		assertEquals(expected, ml.toString());
504 	}
505 
506 	@Test
507 	void f02_toString_multipleLists() {
508 		var l1 = l(a("1", "2"));
509 		var l2 = l(a("3", "4"));
510 		var l3 = l(a("5", "6"));
511 		var ml = new MultiList<>(l1, l2, l3);
512 
513 		var expected = "[" + l1.toString() + ", " + l2.toString() + ", " + l3.toString() + "]";
514 		assertEquals(expected, ml.toString());
515 	}
516 
517 	@Test
518 	void f03_toString_emptyLists() {
519 		var l1 = l(a());
520 		var l2 = l(a());
521 		var ml = new MultiList<>(l1, l2);
522 
523 		var expected = "[" + l1.toString() + ", " + l2.toString() + "]";
524 		assertEquals(expected, ml.toString());
525 	}
526 
527 	@Test
528 	void f04_toString_mixedEmptyAndNonEmpty() {
529 		List<String> l1 = l(a());
530 		var l2 = l(a("1", "2"));
531 		List<String> l3 = l(a());
532 		var ml = new MultiList<>(l1, l2, l3);
533 
534 		var expected = "[" + l1.toString() + ", " + l2.toString() + ", " + l3.toString() + "]";
535 		assertEquals(expected, ml.toString());
536 	}
537 
538 	//====================================================================================================
539 	// equals() and hashCode()
540 	//====================================================================================================
541 
542 	@Test
543 	void g01_equals_sameContents() {
544 		var l1 = l(a("1", "2"));
545 		var l2 = l(a("3", "4"));
546 		var multiList1 = new MultiList<>(l1, l2);
547 
548 		var l3 = l(a("1", "2"));
549 		var l4 = l(a("3", "4"));
550 		var multiList2 = new MultiList<>(l3, l4);
551 
552 		assertTrue(multiList1.equals(multiList2));
553 		assertTrue(multiList2.equals(multiList1));
554 	}
555 
556 	@Test
557 	void g02_equals_differentContents() {
558 		var l1 = l(a("1", "2"));
559 		var multiList1 = new MultiList<>(l1);
560 
561 		var l2 = l(a("1", "3"));
562 		var multiList2 = new MultiList<>(l2);
563 
564 		assertFalse(multiList1.equals(multiList2));
565 		assertFalse(multiList2.equals(multiList1));
566 	}
567 
568 	@Test
569 	void g03_equals_differentOrder() {
570 		var l1 = l(a("1", "2"));
571 		var l2 = l(a("3", "4"));
572 		var multiList1 = new MultiList<>(l1, l2);
573 
574 		var l3 = l(a("3", "4"));
575 		var l4 = l(a("1", "2"));
576 		var multiList2 = new MultiList<>(l3, l4);
577 
578 		assertFalse(multiList1.equals(multiList2)); // Order matters for lists
579 	}
580 
581 	@Test
582 	void g04_equals_regularList() {
583 		var l1 = l(a("1", "2", "3"));
584 		var multiList = new MultiList<>(l1);
585 
586 		var regularList = new ArrayList<>(l(a("1", "2", "3")));
587 
588 		assertTrue(multiList.equals(regularList));
589 		assertTrue(regularList.equals(multiList));
590 	}
591 
592 	@Test
593 	void g05_equals_notAList() {
594 		var l1 = l(a("1", "2"));
595 		var multiList = new MultiList<>(l1);
596 		assertFalse(multiList.equals(null));
597 	}
598 
599 	@Test
600 	void g06_hashCode_sameContents() {
601 		var l1 = l(a("1", "2", "3"));
602 		var multiList1 = new MultiList<>(l1);
603 
604 		var l2 = l(a("1", "2", "3"));
605 		var multiList2 = new MultiList<>(l2);
606 
607 		assertEquals(multiList1.hashCode(), multiList2.hashCode());
608 	}
609 
610 	@Test
611 	void g07_hashCode_regularList() {
612 		var l1 = l(a("1", "2", "3"));
613 		var multiList = new MultiList<>(l1);
614 
615 		var regularList = new ArrayList<>(l(a("1", "2", "3")));
616 
617 		assertEquals(multiList.hashCode(), regularList.hashCode());
618 	}
619 
620 	//====================================================================================================
621 	// Additional coverage for specific lines
622 	//====================================================================================================
623 
624 	@Test
625 	void h01_iterator_hasNext_whenI2IsNull() {
626 		// Line 258: return false when i2 == null
627 		// This happens when MultiList is created with no lists
628 		var ml = new MultiList<String>();
629 		var it = ml.iterator();
630 		assertFalse(it.hasNext()); // i2 is null, should return false
631 	}
632 
633 	@Test
634 	void h02_listIterator_hasNext_whenCurrentIteratorIsNull() {
635 		// Line 356: return false when currentIterator == null
636 		// This can happen in edge cases with empty lists
637 		List<String> l1 = l(a());
638 		List<String> l2 = l(a());
639 		var ml = new MultiList<>(l1, l2);
640 		var li = ml.listIterator(0); // Start at beginning of empty lists
641 		// After positioning, if all lists are empty, currentIterator might be null
642 		assertFalse(li.hasNext());
643 	}
644 
645 	@Test
646 	void h03_listIterator_hasNext_findsNextNonEmptyList() {
647 		// Lines 359-360: Loop checking for next non-empty list
648 		var l1 = l(a("1", "2"));
649 		List<String> l2 = l(a()); // Empty list
650 		var l3 = l(a("3", "4"));
651 		var ml = new MultiList<>(l1, l2, l3);
652 		var li = ml.listIterator();
653 
654 		// Exhaust first list
655 		assertEquals("1", li.next());
656 		assertEquals("2", li.next());
657 		// Now currentIterator.hasNext() is false, but hasNext() should find l3
658 		assertTrue(li.hasNext()); // Should check remaining lists and find l3
659 		assertEquals("3", li.next());
660 	}
661 
662 	@Test
663 	void h04_listIterator_next_throwsWhenCurrentIteratorIsNull() {
664 		// Line 368: throw NoSuchElementException when currentIterator == null
665 		// This is tricky to trigger, but can happen with edge cases
666 		List<String> l1 = l(a());
667 		var ml = new MultiList<>(l1);
668 		var li = ml.listIterator(0);
669 		// If we somehow get into a state where currentIterator is null
670 		assertThrows(NoSuchElementException.class, li::next);
671 	}
672 
673 	@Test
674 	void h05_listIterator_next_throwsWhenExhausted() {
675 		// Line 371: throw NoSuchElementException when no more elements
676 		var l1 = l(a("1", "2"));
677 		var l2 = l(a("3"));
678 		var ml = new MultiList<>(l1, l2);
679 		var li = ml.listIterator();
680 
681 		// Exhaust all elements
682 		assertEquals("1", li.next());
683 		assertEquals("2", li.next());
684 		assertEquals("3", li.next());
685 		// Now listIndex + 1 >= l.length, should throw
686 		assertThrows(NoSuchElementException.class, li::next);
687 	}
688 
689 	@Test
690 	void h06_listIterator_hasPrevious_whenCurrentIteratorIsNull() {
691 		// Line 382: return false when currentIterator == null
692 		var l1 = l(a());
693 		var ml = new MultiList<>(l1);
694 		var li = ml.listIterator(0);
695 		assertFalse(li.hasPrevious()); // currentIterator is null
696 	}
697 
698 	@Test
699 	void h07_listIterator_hasPrevious_findsPreviousList() {
700 		// Line 387: return true when previous list has elements
701 		var l1 = l(a("1", "2"));
702 		List<String> l2 = l(a()); // Empty list
703 		var l3 = l(a("3", "4"));
704 		var ml = new MultiList<>(l1, l2, l3);
705 		var li = ml.listIterator(ml.size()); // Start at end
706 
707 		// Move back through l3
708 		assertEquals("4", li.previous());
709 		assertEquals("3", li.previous());
710 		// Now currentIterator.hasPrevious() is false, but hasPrevious() should find l1
711 		assertTrue(li.hasPrevious()); // Should check previous lists and find l1
712 		assertEquals("2", li.previous());
713 	}
714 
715 	@Test
716 	void h08_listIterator_remove_throwsWhenCurrentIteratorIsNull() {
717 		// Line 418: throw IllegalStateException when currentIterator == null
718 		var l1 = l(a("1"));
719 		var ml = new MultiList<>(l1);
720 		var li = ml.listIterator();
721 		// Remove without calling next/previous first
722 		assertThrows(IllegalStateException.class, li::remove);
723 	}
724 
725 	@Test
726 	void h09_listIterator_set_throwsWhenCurrentIteratorIsNull() {
727 		// Line 426: throw IllegalStateException when currentIterator == null
728 		var l1 = l(a("1"));
729 		var ml = new MultiList<>(l1);
730 		var li = ml.listIterator();
731 		// Set without calling next/previous first
732 		assertThrows(IllegalStateException.class, () -> li.set("x"));
733 	}
734 
735 	@Test
736 	void h10_listIterator_constructor_atEndWithNonEmptyLists() {
737 		// Line 346: if (currentIterator == null && l.length > 0)
738 		// This happens when index is at the end
739 		var l1 = l(a("1", "2"));
740 		var l2 = l(a("3", "4"));
741 		var ml = new MultiList<>(l1, l2);
742 		var li = ml.listIterator(ml.size()); // Index at end
743 
744 		// Should position at last list
745 		assertTrue(li.hasPrevious());
746 		assertEquals("4", li.previous());
747 	}
748 
749 	@Test
750 	void h11_equals_differentLengths() {
751 		// Line 509: while (e1.hasNext() && e2.hasNext())
752 		// Line 515: return !(e1.hasNext() || e2.hasNext());
753 		// Test when lists have different lengths
754 		var l1 = l(a("1", "2"));
755 		var ml1 = new MultiList<>(l1);
756 
757 		var l2 = l(a("1", "2", "3"));
758 		var ml2 = new MultiList<>(l2);
759 
760 		assertFalse(ml1.equals(ml2)); // Different lengths
761 		assertFalse(ml2.equals(ml1));
762 	}
763 
764 	@Test
765 	void h12_equals_oneExhausted() {
766 		// Test equals when one iterator is exhausted before the other
767 		var l1 = l(a("1", "2"));
768 		var ml1 = new MultiList<>(l1);
769 
770 		var l2 = l(a("1", "2", "3"));
771 		var ml2 = new MultiList<>(l2);
772 
773 		// ml1 is shorter, so e1.hasNext() becomes false first
774 		// Then we check e2.hasNext() which is true, so return false
775 		assertFalse(ml1.equals(ml2));
776 	}
777 
778 	@Test
779 	void h13_hashCode_iteratesThroughAllElements() {
780 		// Line 541: for (E e : this)
781 		// Test that hashCode iterates through all elements
782 		var l1 = l(a("1", "2"));
783 		var l2 = l(a("3", "4"));
784 		var ml = new MultiList<>(l1, l2);
785 
786 		// Calculate expected hashCode manually
787 		int expectedHashCode = 1;
788 		for (String e : ml) {
789 			expectedHashCode = 31 * expectedHashCode + (e == null ? 0 : e.hashCode());
790 		}
791 
792 		assertEquals(expectedHashCode, ml.hashCode());
793 	}
794 
795 	@Test
796 	void h14_hashCode_withNullElements() {
797 		// Test hashCode with null elements
798 		var l1 = l(a("1", null));
799 		var l2 = l(a("2"));
800 		var ml = new MultiList<>(l1, l2);
801 
802 		// Calculate expected hashCode manually (null contributes 0)
803 		int expectedHashCode = 1;
804 		expectedHashCode = 31 * expectedHashCode + "1".hashCode();
805 		expectedHashCode = 31 * expectedHashCode + 0; // null
806 		expectedHashCode = 31 * expectedHashCode + "2".hashCode();
807 
808 		assertEquals(expectedHashCode, ml.hashCode());
809 	}
810 }
811