@@ -238,77 +238,6 @@ void forEachCompactsWithTailGaps() {
238238 }
239239 }
240240
241- @ Nested
242- @ DisplayName ("Compaction tests" )
243- class CompactionTests {
244-
245- @ Test
246- @ DisplayName ("Access compacts the list when gaps exist" )
247- void accessCompactsWithGaps () {
248- var list = new ElementAwareArrayList <String >();
249- list .addEntry ("first" );
250- var entry2 = list .addEntry ("second" );
251- list .addEntry ("third" );
252-
253- entry2 .remove ();
254-
255- assertThat (list ).hasSize (2 );
256- assertThat (list .get (0 )).isEqualTo ("first" );
257- assertThat (list .get (1 )).isEqualTo ("third" );
258- }
259-
260- @ Test
261- @ DisplayName ("Access returns empty when all elements removed" )
262- void accessWhenAllRemoved () {
263- var list = new ElementAwareArrayList <String >();
264- var entry1 = list .addEntry ("first" );
265-
266- entry1 .remove ();
267-
268- assertThat (list ).isEmpty ();
269- }
270-
271- @ Test
272- @ DisplayName ("Access compacts with tail gaps" )
273- void accessCompactsWithTailGaps () {
274- var list = new ElementAwareArrayList <String >();
275- list .add ("first" );
276- list .add ("second" );
277- var entry3 = list .addEntry ("third" );
278- var entry4 = list .addEntry ("fourth" );
279- var entry5 = list .addEntry ("fifth" );
280-
281- entry3 .remove ();
282- entry4 .remove ();
283- entry5 .remove ();
284-
285- assertThat (list ).hasSize (2 );
286- assertThat (entry3 .isRemoved ()).isTrue ();
287- assertThat (entry4 .isRemoved ()).isTrue ();
288- assertThat (entry5 .isRemoved ()).isTrue ();
289- }
290-
291- @ Test
292- @ DisplayName ("addAt with trailing gaps only rotates entry into first suffix gap" )
293- void addAtAfterTrailingGapOnly () {
294- var list = new ElementAwareArrayList <String >();
295- var e1 = list .addEntry ("a" );
296- var e2 = list .addEntry ("b" );
297- var e3 = list .addEntry ("c" );
298- var e4 = list .addEntry ("d" );
299-
300- e3 .remove ();
301- e4 .remove ();
302- // Physical: [a, b, null], gapCount=1, size=2 (d trim reduced lastElementPosition)
303-
304- list .add (1 , "x" ); // partialCompact(0): no prefix gap; slot 1 non-null → rotate b into gap at slot 2
305- assertThat (list ).hasSize (3 );
306- assertThat (copyUsingForEach (list )).containsExactly ("a" , "x" , "b" );
307- assertThat (e1 .toString ()).contains ("@0" );
308- assertThat (e2 .toString ()).contains ("@2" );
309- }
310- }
311-
312241 @ Nested
313242 @ DisplayName ("Partial compaction tests" )
314243 class PartialCompactionTests {
@@ -675,14 +604,35 @@ void removeNullEntry() {
675604 }
676605
677606 @ Test
678- @ DisplayName ("iterator visits null elements" )
607+ @ DisplayName ("listIterator visits null elements in forward and backward order " )
679608 void iterator () {
680609 var list = new ElementAwareArrayList <@ Nullable String >();
681610 list .addEntry (null );
682611 list .addEntry ("a" );
683612 list .addEntry (null );
684- var result = copyUsingForEach (list );
685- assertThat (result ).containsExactly (null , "a" , null );
613+
614+ // Forward traversal returns null elements.
615+ var it = list .listIterator ();
616+ assertThat (it .next ()).isNull ();
617+ assertThat (it .next ()).isEqualTo ("a" );
618+ assertThat (it .next ()).isNull ();
619+ assertThat (it .hasNext ()).isFalse ();
620+
621+ // Backward traversal returns null elements.
622+ assertThat (it .previous ()).isNull ();
623+ assertThat (it .previous ()).isEqualTo ("a" );
624+ assertThat (it .previous ()).isNull ();
625+ assertThat (it .hasPrevious ()).isFalse ();
626+
627+ // remove() creates a gap (Entry == null) adjacent to a live null element (Entry != null, element == null);
628+ // next() must skip the former without skipping the latter.
629+ it = list .listIterator ();
630+ assertThat (it .next ()).isNull (); // null element at logical 0
631+ it .remove (); // slot 0 becomes a removed gap; live null element remains at slot 2
632+ assertThat (it .next ()).isEqualTo ("a" );
633+ assertThat (it .next ()).isNull (); // live null element still reachable
634+ assertThat (it .hasNext ()).isFalse ();
635+ assertThat (list ).containsExactly ("a" , null );
686636 }
687637
688638 @ Test
@@ -1091,25 +1041,6 @@ void backwardIteration() {
10911041 assertThat (it .hasPrevious ()).isFalse ();
10921042 }
10931043
1094- @ Test
1095- @ DisplayName ("forward iteration skips null slots" )
1096- void forwardWithGaps () {
1097- var list = new ElementAwareArrayList <String >();
1098- list .add ("a" );
1099- var entryB = list .addEntry ("b" );
1100- list .add ("c" );
1101- var entryD = list .addEntry ("d" );
1102- list .add ("e" );
1103- entryB .remove ();
1104- entryD .remove ();
1105-
1106- var it = list .listIterator ();
1107- assertThat (it .next ()).isEqualTo ("a" );
1108- assertThat (it .next ()).isEqualTo ("c" );
1109- assertThat (it .next ()).isEqualTo ("e" );
1110- assertThat (it .hasNext ()).isFalse ();
1111- }
1112-
11131044 @ Test
11141045 @ DisplayName ("remove after next() removes correct element, adjusts cursor" )
11151046 void removeFwd () {
@@ -1195,6 +1126,30 @@ void addWithGaps() {
11951126 assertThat (list ).containsExactly ("a" , "x" , "c" );
11961127 }
11971128
1129+ @ Test
1130+ @ DisplayName ("add() appending to list made gappy mid-iteration positions cursor for previous()" )
1131+ void addAtEndAfterMidIterationRemoval () {
1132+ var list = new ElementAwareArrayList <String >();
1133+ list .add ("a" );
1134+ list .add ("b" );
1135+ list .add ("c" );
1136+ list .add ("d" );
1137+
1138+ var it = list .listIterator ();
1139+ it .next (); // a
1140+ it .next (); // b
1141+ it .remove (); // gap at physical 1; logical [a, c, d]
1142+ it .next (); // c
1143+ it .next (); // d -> logical end, interior gap still present
1144+ it .add ("e" ); // append via addEntry while gap exists
1145+
1146+ assertThat (it .hasNext ()).isFalse ();
1147+ assertThat (it .previous ()).isEqualTo ("e" );
1148+ assertThat (it .previous ()).isEqualTo ("d" );
1149+ assertThat (it .previous ()).isEqualTo ("c" );
1150+ assertThat (list ).containsExactly ("a" , "c" , "d" , "e" );
1151+ }
1152+
11981153 @ Test
11991154 @ DisplayName ("next() past end throws NoSuchElementException" )
12001155 void noSuchElement () {
@@ -1234,67 +1189,6 @@ void startAtIndex() {
12341189 assertThat (it .previous ()).isEqualTo ("b" );
12351190 }
12361191
1237- @ Test
1238- @ DisplayName ("listIterator(index) on gappy list compacts before starting" )
1239- void startAtIndexWithGaps () {
1240- var list = new ElementAwareArrayList <String >();
1241- list .add ("a" );
1242- var entry = list .addEntry ("b" );
1243- list .add ("c" );
1244- list .add ("d" );
1245- entry .remove ();
1246-
1247- var it = list .listIterator (1 );
1248- assertThat (it .next ()).isEqualTo ("c" );
1249- assertThat (it .previous ()).isEqualTo ("c" );
1250- assertThat (it .previous ()).isEqualTo ("a" );
1251- }
1252-
1253- @ Test
1254- @ DisplayName ("forward iteration through gaps then full backward traversal" )
1255- void forwardWithGapsThenBackward () {
1256- var list = new ElementAwareArrayList <String >();
1257- list .add ("a" );
1258- var entryB = list .addEntry ("b" );
1259- list .add ("c" );
1260- var entryD = list .addEntry ("d" );
1261- list .add ("e" );
1262- entryB .remove ();
1263- entryD .remove ();
1264- // Physical: [a, null, c, null, e]; gaps never compacted during forward scan.
1265-
1266- var it = list .listIterator ();
1267- assertThat (it .next ()).isEqualTo ("a" );
1268- assertThat (it .next ()).isEqualTo ("c" );
1269- assertThat (it .next ()).isEqualTo ("e" );
1270- assertThat (it .hasNext ()).isFalse ();
1271-
1272- // previous() must traverse back through the uncompacted gaps.
1273- assertThat (it .previous ()).isEqualTo ("e" );
1274- assertThat (it .previous ()).isEqualTo ("c" );
1275- assertThat (it .previous ()).isEqualTo ("a" );
1276- assertThat (it .hasPrevious ()).isFalse ();
1277- }
1278-
1279- @ Test
1280- @ DisplayName ("backward iteration from end of gappy list (gaps at head and middle)" )
1281- void backwardFromEndWithGaps () {
1282- var list = new ElementAwareArrayList <String >();
1283- var entryA = list .addEntry ("a" );
1284- list .add ("b" );
1285- var entryC = list .addEntry ("c" );
1286- list .add ("d" );
1287- entryA .remove ();
1288- entryC .remove ();
1289- // Physical: [null, b, null, d], logical: [b, d].
1290-
1291- var it = list .listIterator (list .size ());
1292- assertThat (it .hasPrevious ()).isTrue ();
1293- assertThat (it .previous ()).isEqualTo ("d" );
1294- assertThat (it .previous ()).isEqualTo ("b" );
1295- assertThat (it .hasPrevious ()).isFalse ();
1296- }
1297-
12981192 @ Test
12991193 @ DisplayName ("iterator remove on list that already has gaps" )
13001194 void removeViaIteratorOnGappyList () {
@@ -1304,7 +1198,7 @@ void removeViaIteratorOnGappyList() {
13041198 list .add ("c" );
13051199 list .add ("d" );
13061200 entryB .remove ();
1307- // Physical: [a, null, c, d], logical: [a, c, d] .
1201+ // listIterator() compacts the b-gap away first; the gap under test is the one it.remove() creates mid-iteration .
13081202
13091203 var it = list .listIterator ();
13101204 assertThat (it .next ()).isEqualTo ("a" );
@@ -1328,7 +1222,7 @@ void nextRemoveNextPreviousCycle() {
13281222 list .add ("b" );
13291223 list .add ("c" );
13301224 entryX .remove ();
1331- // Physical: [a, null, b, c], logical: [a, b, c] .
1225+ // listIterator() compacts the x-gap away first; the gap under test is the one it.remove() creates mid-iteration .
13321226
13331227 var it = list .listIterator ();
13341228 assertThat (it .next ()).isEqualTo ("a" );
0 commit comments