View Javadoc
1   /*
2    * Copyright (C) 2026 B3Partners B.V.
3    *
4    * SPDX-License-Identifier: MIT
5    */
6   package org.tailormap.api.geotools.collection;
7   
8   import java.util.concurrent.atomic.AtomicInteger;
9   import java.util.function.IntConsumer;
10  import org.geotools.api.feature.simple.SimpleFeature;
11  import org.geotools.data.simple.SimpleFeatureIterator;
12  import org.geotools.feature.collection.DecoratingSimpleFeatureIterator;
13  import org.jspecify.annotations.Nullable;
14  
15  /** A decorating feature iterator that will call a callback after a specified number of features are handled. */
16  public class ProgressReportingFeatureIterator extends DecoratingSimpleFeatureIterator {
17  
18    private final AtomicInteger count = new AtomicInteger(0);
19    private final int progressInterval;
20    private final IntConsumer progressCallback;
21    private SimpleFeatureIterator iterator;
22  
23    /**
24     * Creates an iterator that reports progress after every configured number of processed features.
25     *
26     * @param iterator the wrapped feature iterator, must not be {@code null}
27     * @param progressInterval the number of processed features between progress updates, must be greater than {@code 0}
28     * @param progressCallback the callback that receives the current processed feature count; may be {@code null}
29     * @throws IllegalArgumentException if {@code progressInterval <= 0}
30     */
31    public ProgressReportingFeatureIterator(
32        SimpleFeatureIterator iterator, int progressInterval, @Nullable IntConsumer progressCallback) {
33      super(iterator);
34      if (progressInterval <= 0) {
35        throw new IllegalArgumentException("progressInterval must be greater than 0");
36      }
37      this.iterator = iterator;
38      this.progressInterval = progressInterval;
39      this.progressCallback = progressCallback;
40    }
41  
42    @Override
43    public SimpleFeature next() {
44      if (count.incrementAndGet() % progressInterval == 0) {
45        if (progressCallback != null) {
46          progressCallback.accept(count.get());
47        }
48      }
49      return iterator.next();
50    }
51  
52    @Override
53    public boolean hasNext() {
54      return iterator.hasNext();
55    }
56  
57    @Override
58    public void close() {
59      iterator.close();
60      iterator = null;
61      count.set(0);
62    }
63  }