3939import org .apache .beam .sdk .metrics .SourceMetrics ;
4040import org .apache .beam .sdk .options .PipelineOptions ;
4141import org .apache .beam .sdk .transforms .SerializableFunction ;
42+ import org .apache .beam .sdk .transforms .windowing .BoundedWindow ;
4243import org .apache .beam .sdk .values .PCollection ;
4344import org .apache .beam .vendor .guava .v32_1_2_jre .com .google .common .collect .ImmutableList ;
4445import org .checkerframework .checker .nullness .qual .Nullable ;
@@ -95,7 +96,8 @@ static BoundedSource<Long> createSourceForSubrange(long startIndex, long endInde
9596 /** Create a new {@link UnboundedCountingSource}. */
9697 // package-private to return a typed UnboundedCountingSource rather than the UnboundedSource type.
9798 static UnboundedCountingSource createUnboundedFrom (long start ) {
98- return new UnboundedCountingSource (start , 1 , 1L , Duration .ZERO , new NowTimestampFn ());
99+ return new UnboundedCountingSource (
100+ start , 1 , Long .MAX_VALUE , 1L , Duration .ZERO , new NowTimestampFn ());
99101 }
100102
101103 /**
@@ -130,7 +132,7 @@ public static UnboundedSource<Long, CounterMark> unbounded() {
130132 @ Deprecated
131133 public static UnboundedSource <Long , CounterMark > unboundedWithTimestampFn (
132134 SerializableFunction <Long , Instant > timestampFn ) {
133- return new UnboundedCountingSource (0 , 1 , 1L , Duration .ZERO , timestampFn );
135+ return new UnboundedCountingSource (0 , 1 , Long . MAX_VALUE , 1L , Duration .ZERO , timestampFn );
134136 }
135137
136138 /////////////////////////////////////////////////////////////////////////////////////////////
@@ -267,11 +269,13 @@ public void close() throws IOException {}
267269 }
268270
269271 /** An implementation of {@link CountingSource} that produces an unbounded {@link PCollection}. */
270- static class UnboundedCountingSource extends UnboundedSource <Long , CounterMark > {
272+ public static class UnboundedCountingSource extends UnboundedSource <Long , CounterMark > {
271273 /** The first number (>= 0) generated by this {@link UnboundedCountingSource}. */
272274 private final long start ;
273275 /** The interval between numbers generated by this {@link UnboundedCountingSource}. */
274276 private final long stride ;
277+ /** The exclusive limit for the sequence. */
278+ private final long end ;
275279 /** The number of elements to produce each period. */
276280 private final long elementsPerPeriod ;
277281 /** The time between producing numbers from this {@link UnboundedCountingSource}. */
@@ -291,11 +295,13 @@ static class UnboundedCountingSource extends UnboundedSource<Long, CounterMark>
291295 private UnboundedCountingSource (
292296 long start ,
293297 long stride ,
298+ long end ,
294299 long elementsPerPeriod ,
295300 Duration period ,
296301 SerializableFunction <Long , Instant > timestampFn ) {
297302 this .start = start ;
298303 this .stride = stride ;
304+ this .end = end ;
299305 checkArgument (
300306 elementsPerPeriod > 0L ,
301307 "Must produce at least one element per period, got %s" ,
@@ -312,7 +318,8 @@ private UnboundedCountingSource(
312318 * will be produced with an interval between them equal to the period.
313319 */
314320 public UnboundedCountingSource withRate (long elementsPerPeriod , Duration period ) {
315- return new UnboundedCountingSource (start , stride , elementsPerPeriod , period , timestampFn );
321+ return new UnboundedCountingSource (
322+ start , stride , end , elementsPerPeriod , period , timestampFn );
316323 }
317324
318325 /**
@@ -324,7 +331,18 @@ public UnboundedCountingSource withRate(long elementsPerPeriod, Duration period)
324331 public UnboundedCountingSource withTimestampFn (
325332 SerializableFunction <Long , Instant > timestampFn ) {
326333 checkNotNull (timestampFn );
327- return new UnboundedCountingSource (start , stride , elementsPerPeriod , period , timestampFn );
334+ return new UnboundedCountingSource (
335+ start , stride , end , elementsPerPeriod , period , timestampFn );
336+ }
337+
338+ /**
339+ * Returns an {@link UnboundedCountingSource} like this one but with the specified exclusive
340+ * limit.
341+ */
342+ public UnboundedCountingSource to (long end ) {
343+ checkArgument (end >= start , "end (%s) must be >= start (%s)" , end , start );
344+ return new UnboundedCountingSource (
345+ start , stride , end , elementsPerPeriod , period , timestampFn );
328346 }
329347
330348 /**
@@ -348,7 +366,7 @@ public List<? extends UnboundedSource<Long, CountingSource.CounterMark>> split(
348366 // 0, 2, and 4.
349367 splits .add (
350368 new UnboundedCountingSource (
351- start + i * stride , newStride , elementsPerPeriod , period , timestampFn ));
369+ start + i * stride , newStride , end , elementsPerPeriod , period , timestampFn ));
352370 }
353371 return splits .build ();
354372 }
@@ -376,14 +394,15 @@ public boolean equals(@Nullable Object other) {
376394 UnboundedCountingSource that = (UnboundedCountingSource ) other ;
377395 return this .start == that .start
378396 && this .stride == that .stride
397+ && this .end == that .end
379398 && this .elementsPerPeriod == that .elementsPerPeriod
380399 && Objects .equals (this .period , that .period )
381400 && Objects .equals (this .timestampFn , that .timestampFn );
382401 }
383402
384403 @ Override
385404 public int hashCode () {
386- return Objects .hash (start , stride , elementsPerPeriod , period , timestampFn );
405+ return Objects .hash (start , stride , end , elementsPerPeriod , period , timestampFn );
387406 }
388407 }
389408
@@ -395,6 +414,7 @@ public int hashCode() {
395414 private static class UnboundedCountingReader extends UnboundedReader <Long > {
396415 private UnboundedCountingSource source ;
397416 private long current ;
417+ private boolean done = false ;
398418
399419 // Initialized on first advance()
400420 private @ Nullable Instant currentTimestamp ;
@@ -431,6 +451,10 @@ public boolean advance() throws IOException {
431451 return false ;
432452 }
433453 long nextValue = current + source .stride ;
454+ if (nextValue >= source .end ) {
455+ done = true ;
456+ return false ;
457+ }
434458 if (expectedValue () < nextValue ) {
435459 return false ;
436460 }
@@ -453,7 +477,7 @@ private long expectedValue() {
453477
454478 @ Override
455479 public Instant getWatermark () {
456- return source .timestampFn .apply (current );
480+ return done ? BoundedWindow . TIMESTAMP_MAX_VALUE : source .timestampFn .apply (current );
457481 }
458482
459483 @ Override
0 commit comments