11package ai .timefold .solver .core .impl .bavet .common .index ;
22
3+ import ai .timefold .solver .core .impl .util .ElementAwareList ;
4+ import org .jspecify .annotations .NullMarked ;
5+ import org .jspecify .annotations .Nullable ;
6+
37import java .util .ArrayList ;
48import java .util .Collections ;
59import java .util .List ;
610import java .util .Objects ;
711import java .util .function .Consumer ;
812import java .util .function .Predicate ;
913
10- import ai .timefold .solver .core .impl .util .ElementAwareList ;
11-
12- import org .jspecify .annotations .NullMarked ;
13- import org .jspecify .annotations .Nullable ;
14-
1514/**
1615 * {@link ArrayList}-backed set which allows to {@link #remove(Object)} an element
1716 * without knowing its position and without an expensive lookup.
@@ -132,9 +131,7 @@ public int size() {
132131 * The order of iteration may change as elements are added and removed.
133132 *
134133 * @param elementConsumer the action to be performed for each element;
135- * may include removing elements from the collection,
136- * but additions or swaps aren't allowed;
137- * undefined behavior will occur if that is attempted.
134+ * mustn't modify the collection
138135 */
139136 public void forEach (Consumer <T > elementConsumer ) {
140137 if (isEmpty ()) {
@@ -227,7 +224,7 @@ private void forEachCompacting(Consumer<T> elementConsumer) {
227224 * As defined by {@link #forEach(Consumer)},
228225 * but stops when the predicate returns true for an element.
229226 *
230- * @param elementPredicate the predicate to be tested for each element
227+ * @param elementPredicate the predicate to be tested for each element; mustn't modify the collection
231228 * @return the first element for which the predicate returned true, or null if none
232229 */
233230 public @ Nullable T findFirst (Predicate <T > elementPredicate ) {
@@ -275,6 +272,39 @@ private void forEachCompacting(Consumer<T> elementConsumer) {
275272 return null ;
276273 }
277274
275+ /**
276+ * Empties the collection.
277+ * For each element being removed, the given consumer is called.
278+ * This is a more efficient implementation than removing elements individually.
279+ *
280+ * @param elementConsumer the consumer to be called for each element being removed; mustn't modify the collection
281+ */
282+ public void clear (Consumer <T > elementConsumer ) {
283+ var nonGapCount = size ();
284+ if (nonGapCount == 0 ) {
285+ forceClear ();
286+ return ;
287+ }
288+ var oldLastElementPosition = lastElementPosition ;
289+ for (var i = 0 ; i <= oldLastElementPosition ; i ++) {
290+ var element = elementList .get (i );
291+ if (element == null ) {
292+ continue ;
293+ }
294+ elementConsumer .accept (element );
295+ if (lastElementPosition != oldLastElementPosition ) {
296+ throw new IllegalStateException ("Impossible state: the IndexedSet was modified while being cleared." );
297+ }
298+ elementPositionTracker .clearPosition (element );
299+ // We can stop early once all non-gap elements have been processed.
300+ nonGapCount --;
301+ if (nonGapCount == 0 ) {
302+ break ;
303+ }
304+ }
305+ forceClear ();
306+ }
307+
278308 /**
279309 * Returns a standard {@link List} view of this collection.
280310 * Users mustn't modify the returned list, as that'd also change the underlying data structure.
@@ -285,7 +315,7 @@ public List<T> asList() {
285315 if (elementList == null ) {
286316 return Collections .emptyList ();
287317 }
288- if (gapCount > 0 ) { // The list must not return any nulls.
318+ if (gapCount > 0 ) { // The list mustn't return any nulls.
289319 forceCompaction ();
290320 }
291321 return elementList .isEmpty () ? Collections .emptyList () : elementList ;
@@ -306,30 +336,4 @@ private void forceCompaction() {
306336 }
307337 }
308338
309- public void clear (Consumer <T > elementConsumer ) {
310- var nonGapCount = size ();
311- if (nonGapCount == 0 ) {
312- forceClear ();
313- return ;
314- }
315- var oldLastElementPosition = lastElementPosition ;
316- for (var i = 0 ; i <= oldLastElementPosition ; i ++) {
317- var element = elementList .get (i );
318- if (element == null ) {
319- continue ;
320- }
321- elementConsumer .accept (element );
322- if (lastElementPosition != oldLastElementPosition ) {
323- throw new IllegalStateException ("Impossible state: the IndexedSet was modified while being cleared." );
324- }
325- elementPositionTracker .clearPosition (element );
326- // We can stop early once all non-gap elements have been processed.
327- nonGapCount --;
328- if (nonGapCount == 0 ) {
329- break ;
330- }
331- }
332- forceClear ();
333- }
334-
335339}
0 commit comments