This is an automated email from the ASF dual-hosted git repository.

He-Pin pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/pekko.git


The following commit(s) were added to refs/heads/main by this push:
     new a626d6212a feat(stream): add withContext filtering and truncating 
operators (#3199)
a626d6212a is described below

commit a626d6212a369ba051e0797f6d44ec3e462e0b6b
Author: He-Pin(kerr) <[email protected]>
AuthorDate: Sat Jun 27 23:23:18 2026 +0800

    feat(stream): add withContext filtering and truncating operators (#3199)
    
    Motivation:
    SourceWithContext and FlowWithContext were missing several safe
    filtering and truncating operators that ordinary Source and Flow
    already provide.  Additionally, PR #3164 accidentally regressed
    ConfigSSLEngineProvider from eager val back to lazy val, breaking
    the fail-fast behavior added by #3165.
    
    Modification:
    Add context-preserving mapOption, collectFirst, collectWhile,
    collectType, drop, dropWithin, dropRepeated, dropWhile, take,
    takeWhile, takeUntil, takeWithin, limit, and limitWeighted operators
    to the Scala and Java withContext DSLs.  Add Scala withFilter for
    for-comprehension filtering.  Add @since 2.0.0 to all 49 new API
    methods.  Restore eager SSLContext init in classic remoting's
    ConfigSSLEngineProvider.
    
    Result:
    Users can keep automatic context propagation while using these
    operators directly on SourceWithContext and FlowWithContext.
    ConfigSSLEngineProvider once again surfaces keystore errors at
    construction time.
    
    Tests:
    - scalafmt --mode diff-ref=origin/main
    - sbt headerCreateAll
    - sbt +headerCheckAll
    - sbt "+stream / mimaReportBinaryIssues"
    - sbt "stream-tests / Test / testOnly 
org.apache.pekko.stream.scaladsl.SourceWithContextSpec 
org.apache.pekko.stream.scaladsl.FlowWithContextSpec 
org.apache.pekko.stream.javadsl.FlowWithContextTest 
org.apache.pekko.stream.javadsl.SourceWithContextTest"
    - sbt "docs / Test / testOnly docs.stream.operators.WithContextSpec 
jdocs.stream.operators.WithContextTest"
    - sbt docs/paradox
    - git diff --check
    - sbt validatePullRequest
    
    References:
    Fixes #3177
---
 docs/src/main/paradox/stream/stream-context.md     |  11 ++
 .../jdocs/stream/operators/WithContextTest.java    |  27 ++++
 .../docs/stream/operators/WithContextSpec.scala    |  25 +++
 .../pekko/stream/javadsl/FlowWithContextTest.java  | 134 +++++++++++++++++
 .../stream/javadsl/SourceWithContextTest.java      | 158 +++++++++++++++++++
 .../stream/scaladsl/FlowWithContextSpec.scala      |  92 ++++++++++++
 .../stream/scaladsl/SourceWithContextSpec.scala    |  93 ++++++++++++
 .../pekko/stream/javadsl/FlowWithContext.scala     | 157 +++++++++++++++++++
 .../pekko/stream/javadsl/SourceWithContext.scala   | 153 +++++++++++++++++++
 .../pekko/stream/scaladsl/FlowWithContextOps.scala | 167 +++++++++++++++++++++
 10 files changed, 1017 insertions(+)

diff --git a/docs/src/main/paradox/stream/stream-context.md 
b/docs/src/main/paradox/stream/stream-context.md
index b91c06864c..bb90adecf2 100644
--- a/docs/src/main/paradox/stream/stream-context.md
+++ b/docs/src/main/paradox/stream/stream-context.md
@@ -37,6 +37,17 @@ while grouping operations will keep all contexts from the 
elements in
 the group. Streaming one-to-many operations such as `mapConcat`
 associate the original context with each of the produced elements.
 
+The context-preserving filtering and truncating operators include
+`filter`, `filterNot`, `collect`, `collectFirst`, `collectWhile`, 
`collectType`,
+`mapOption`, `drop`, `dropWhile`, `dropWithin`, `dropRepeated`, `take`, 
`takeWhile`,
+`takeUntil`, `takeWithin`, `limit`, and `limitWeighted`.
+
+Scala
+:  @@snip 
[snip](/docs/src/test/scala/docs/stream/operators/WithContextSpec.scala) { 
#withContextOperators }
+
+Java
+:  @@snip 
[snip](/docs/src/test/java/jdocs/stream/operators/WithContextTest.java) { 
#withContextOperators }
+
 As an escape hatch, there is a `via` operator that allows you to
 insert an arbitrary @apidoc[Flow] that can process the
 @scala[tuples]@java[pairs] of elements and context in any way
diff --git a/docs/src/test/java/jdocs/stream/operators/WithContextTest.java 
b/docs/src/test/java/jdocs/stream/operators/WithContextTest.java
index aaa5235102..47326cbb96 100644
--- a/docs/src/test/java/jdocs/stream/operators/WithContextTest.java
+++ b/docs/src/test/java/jdocs/stream/operators/WithContextTest.java
@@ -15,9 +15,11 @@ package jdocs.stream.operators;
 
 import static org.hamcrest.CoreMatchers.hasItems;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 
 import java.util.Collection;
 import java.util.List;
+import java.util.Optional;
 import java.util.concurrent.CompletionStage;
 import java.util.concurrent.TimeUnit;
 import jdocs.AbstractJavaTest;
@@ -119,4 +121,29 @@ public class WithContextTest extends AbstractJavaTest {
         list, hasItems(Pair.create("yins", 1), Pair.create("zwyi", 2), 
Pair.create("dryi", 3)));
     // #asFlowWithContext
   }
+
+  @Test
+  public void documentWithContextOperators() throws Exception {
+    // #withContextOperators
+    Collection<Pair<String, Integer>> values =
+        List.of(
+            Pair.create("eins", 1),
+            Pair.create("eins", 2),
+            Pair.create("zwei", 3),
+            Pair.create("drei", 4));
+
+    SourceWithContext<String, Integer, NotUsed> filtered =
+        Source.from(values)
+            .asSourceWithContext(Pair::second)
+            .map(Pair::first)
+            .dropRepeated()
+            .mapOption(
+                word -> word.startsWith("z") ? Optional.empty() : 
Optional.of(word.toUpperCase()))
+            .take(2);
+
+    CompletionStage<List<Pair<String, Integer>>> result = 
filtered.runWith(Sink.seq(), system);
+    List<Pair<String, Integer>> list = result.toCompletableFuture().get(1, 
TimeUnit.SECONDS);
+    assertEquals(List.of(Pair.create("EINS", 1), Pair.create("DREI", 4)), 
list);
+    // #withContextOperators
+  }
 }
diff --git a/docs/src/test/scala/docs/stream/operators/WithContextSpec.scala 
b/docs/src/test/scala/docs/stream/operators/WithContextSpec.scala
index 7ff27297a8..7fe9d22499 100644
--- a/docs/src/test/scala/docs/stream/operators/WithContextSpec.scala
+++ b/docs/src/test/scala/docs/stream/operators/WithContextSpec.scala
@@ -89,4 +89,29 @@ class WithContextSpec extends PekkoSpec {
     result.futureValue should contain theSameElementsInOrderAs 
immutable.Seq("snie" -> 1, "iewz" -> 2, "ierd" -> 3)
     // #asFlowWithContext
   }
+
+  "use filtering and truncating operators with context" in {
+    // #withContextOperators
+    import org.apache.pekko
+    import pekko.NotUsed
+    import pekko.stream.scaladsl.Sink
+    import pekko.stream.scaladsl.Source
+    import pekko.stream.scaladsl.SourceWithContext
+    import scala.collection.immutable
+
+    val values: immutable.Seq[(String, Int)] =
+      immutable.Seq("eins" -> 1, "eins" -> 2, "zwei" -> 3, "drei" -> 4)
+
+    val filtered: SourceWithContext[String, Int, NotUsed] =
+      Source(values)
+        .asSourceWithContext(_._2)
+        .map(_._1)
+        .dropRepeated()
+        .mapOption(word => if (word.startsWith("z")) None else 
Some(word.toUpperCase))
+        .take(2)
+
+    val result = filtered.runWith(Sink.seq)
+    result.futureValue should contain theSameElementsInOrderAs 
immutable.Seq("EINS" -> 1, "DREI" -> 4)
+    // #withContextOperators
+  }
 }
diff --git 
a/stream-tests/src/test/java/org/apache/pekko/stream/javadsl/FlowWithContextTest.java
 
b/stream-tests/src/test/java/org/apache/pekko/stream/javadsl/FlowWithContextTest.java
index 76a4012f8b..8a887a6d1b 100644
--- 
a/stream-tests/src/test/java/org/apache/pekko/stream/javadsl/FlowWithContextTest.java
+++ 
b/stream-tests/src/test/java/org/apache/pekko/stream/javadsl/FlowWithContextTest.java
@@ -16,11 +16,14 @@ package org.apache.pekko.stream.javadsl;
 import static org.apache.pekko.NotUsed.notUsed;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
+import java.time.Duration;
 import java.util.List;
+import java.util.Optional;
 import java.util.concurrent.CompletionStage;
 import java.util.concurrent.TimeUnit;
 import org.apache.pekko.NotUsed;
 import org.apache.pekko.japi.Pair;
+import org.apache.pekko.japi.pf.PFBuilder;
 import org.apache.pekko.stream.StreamTestJupiter;
 import org.apache.pekko.testkit.PekkoJUnitJupiterActorSystemResource;
 import org.apache.pekko.testkit.PekkoSpec;
@@ -70,4 +73,135 @@ public class FlowWithContextTest extends StreamTestJupiter {
     assertEquals("1", pairs.get(0).first());
     assertEquals(notUsed(), pairs.get(0).second());
   }
+
+  @Test
+  public void mustPassContextThroughAdditionalFilteringOperators() throws 
Exception {
+    final FlowWithContext<Object, String, Integer, String, NotUsed> 
collectFlow =
+        FlowWithContext.<Object, String>create()
+            .collectType(Integer.class)
+            .collectWhile(
+                PFBuilder.<Integer, Integer>create()
+                    .match(Integer.class, value -> value < 3, value -> value * 
10)
+                    .build());
+
+    final CompletionStage<List<Pair<Integer, String>>> collectResult =
+        Source.from(
+                List.of(
+                    new Pair<Object, String>(1, "one"),
+                    new Pair<Object, String>(2, "two"),
+                    new Pair<Object, String>("three", "three-string"),
+                    new Pair<Object, String>(3, "three")))
+            .via(collectFlow.asFlow())
+            .runWith(Sink.seq(), system);
+
+    assertEquals(
+        List.of(new Pair<>(10, "one"), new Pair<>(20, "two")),
+        collectResult.toCompletableFuture().get(3, TimeUnit.SECONDS));
+
+    final FlowWithContext<Object, String, Integer, String, NotUsed> 
collectFirstFlow =
+        FlowWithContext.<Object, String>create()
+            .collectFirst(
+                PFBuilder.<Object, Integer>create()
+                    .match(Integer.class, value -> value > 1, value -> value * 
10)
+                    .build());
+
+    final CompletionStage<List<Pair<Integer, String>>> collectFirstResult =
+        Source.from(
+                List.of(
+                    new Pair<Object, String>(1, "one"),
+                    new Pair<Object, String>(2, "two"),
+                    new Pair<Object, String>(3, "three")))
+            .via(collectFirstFlow.asFlow())
+            .runWith(Sink.seq(), system);
+
+    assertEquals(
+        List.of(new Pair<>(20, "two")),
+        collectFirstResult.toCompletableFuture().get(3, TimeUnit.SECONDS));
+
+    final FlowWithContext<Integer, String, Integer, String, NotUsed> 
mapOptionFlow =
+        FlowWithContext.<Integer, String>create()
+            .mapOption(value -> value == 0 ? Optional.empty() : 
Optional.of(value * 10));
+
+    final CompletionStage<List<Pair<Integer, String>>> mapOptionResult =
+        Source.from(
+                List.of(
+                    new Pair<>(0, "zero"),
+                    new Pair<>(1, "one"),
+                    new Pair<>(2, "two"),
+                    new Pair<>(3, "three")))
+            .via(mapOptionFlow.asFlow())
+            .runWith(Sink.seq(), system);
+
+    assertEquals(
+        List.of(new Pair<>(10, "one"), new Pair<>(20, "two"), new Pair<>(30, 
"three")),
+        mapOptionResult.toCompletableFuture().get(3, TimeUnit.SECONDS));
+  }
+
+  @Test
+  public void mustPassContextThroughTruncatingOperators() throws Exception {
+    final FlowWithContext<Integer, String, Integer, String, NotUsed> flow =
+        FlowWithContext.<Integer, String>create()
+            .drop(1)
+            .dropRepeated()
+            .dropWhile(value -> value < 2)
+            .takeUntil(value -> value == 3)
+            .takeWithin(Duration.ofDays(1))
+            .take(2)
+            .limit(2)
+            .limitWeighted(2, value -> 1L);
+
+    final CompletionStage<List<Pair<Integer, String>>> result =
+        Source.from(
+                List.of(
+                    new Pair<>(0, "zero"),
+                    new Pair<>(1, "one"),
+                    new Pair<>(1, "one-duplicate"),
+                    new Pair<>(2, "two"),
+                    new Pair<>(3, "three"),
+                    new Pair<>(4, "four")))
+            .via(flow.asFlow())
+            .runWith(Sink.seq(), system);
+
+    assertEquals(
+        List.of(new Pair<>(2, "two"), new Pair<>(3, "three")),
+        result.toCompletableFuture().get(3, TimeUnit.SECONDS));
+
+    final CompletionStage<List<Pair<Integer, String>>> inclusiveResult =
+        Source.from(List.of(new Pair<>(1, "one"), new Pair<>(2, "two"), new 
Pair<>(3, "three")))
+            .via(
+                FlowWithContext.<Integer, String>create()
+                    .takeWhile(value -> value < 2, true)
+                    .asFlow())
+            .runWith(Sink.seq(), system);
+
+    assertEquals(
+        List.of(new Pair<>(1, "one"), new Pair<>(2, "two")),
+        inclusiveResult.toCompletableFuture().get(3, TimeUnit.SECONDS));
+
+    final CompletionStage<List<Pair<Integer, String>>> customPredicateResult =
+        Source.from(List.of(new Pair<>(1, "one"), new Pair<>(3, "three"), new 
Pair<>(4, "four")))
+            .via(
+                FlowWithContext.<Integer, String>create()
+                    .dropRepeated((left, right) -> left % 2 == right % 2)
+                    .takeWhile(value -> value < 3)
+                    .asFlow())
+            .runWith(Sink.seq(), system);
+
+    assertEquals(
+        List.of(new Pair<>(1, "one")),
+        customPredicateResult.toCompletableFuture().get(3, TimeUnit.SECONDS));
+
+    final CompletionStage<List<Pair<Integer, String>>> timedResult =
+        Source.single(new Pair<>(1, "one"))
+            .initialDelay(Duration.ofMillis(50))
+            .via(
+                FlowWithContext.<Integer, String>create()
+                    .dropWithin(Duration.ofMillis(10))
+                    .takeWithin(Duration.ofDays(1))
+                    .asFlow())
+            .runWith(Sink.seq(), system);
+
+    assertEquals(
+        List.of(new Pair<>(1, "one")), 
timedResult.toCompletableFuture().get(3, TimeUnit.SECONDS));
+  }
 }
diff --git 
a/stream-tests/src/test/java/org/apache/pekko/stream/javadsl/SourceWithContextTest.java
 
b/stream-tests/src/test/java/org/apache/pekko/stream/javadsl/SourceWithContextTest.java
new file mode 100644
index 0000000000..ea47272e6f
--- /dev/null
+++ 
b/stream-tests/src/test/java/org/apache/pekko/stream/javadsl/SourceWithContextTest.java
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pekko.stream.javadsl;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+import org.apache.pekko.japi.Pair;
+import org.apache.pekko.japi.pf.PFBuilder;
+import org.apache.pekko.stream.StreamTestJupiter;
+import org.apache.pekko.testkit.PekkoJUnitJupiterActorSystemResource;
+import org.apache.pekko.testkit.PekkoSpec;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public class SourceWithContextTest extends StreamTestJupiter {
+
+  public SourceWithContextTest() {
+    super(actorSystemResource);
+  }
+
+  @RegisterExtension
+  static PekkoJUnitJupiterActorSystemResource actorSystemResource =
+      new PekkoJUnitJupiterActorSystemResource("SourceWithContextTest", 
PekkoSpec.testConf());
+
+  @Test
+  public void mustPassContextThroughAdditionalFilteringOperators() throws 
Exception {
+    List<Pair<Object, String>> collectInput =
+        List.of(
+            new Pair<Object, String>(1, "one"),
+            new Pair<Object, String>(2, "two"),
+            new Pair<Object, String>("three", "three-string"),
+            new Pair<Object, String>(3, "three"));
+
+    List<Pair<Integer, String>> collectResult =
+        SourceWithContext.fromPairs(Source.from(collectInput))
+            .collectType(Integer.class)
+            .collectWhile(
+                PFBuilder.<Integer, Integer>create()
+                    .match(Integer.class, value -> value < 3, value -> value * 
10)
+                    .build())
+            .runWith(Sink.seq(), system)
+            .toCompletableFuture()
+            .get(3, TimeUnit.SECONDS);
+
+    assertEquals(List.of(new Pair<>(10, "one"), new Pair<>(20, "two")), 
collectResult);
+
+    List<Pair<Integer, String>> collectFirstResult =
+        SourceWithContext.fromPairs(Source.from(collectInput))
+            .collectFirst(
+                PFBuilder.<Object, Integer>create()
+                    .match(Integer.class, value -> value > 1, value -> value * 
10)
+                    .build())
+            .runWith(Sink.seq(), system)
+            .toCompletableFuture()
+            .get(3, TimeUnit.SECONDS);
+
+    assertEquals(List.of(new Pair<>(20, "two")), collectFirstResult);
+
+    List<Pair<Integer, String>> mapOptionInput =
+        List.of(
+            new Pair<>(0, "zero"),
+            new Pair<>(1, "one"),
+            new Pair<>(2, "two"),
+            new Pair<>(3, "three"));
+
+    List<Pair<Integer, String>> mapOptionResult =
+        SourceWithContext.fromPairs(Source.from(mapOptionInput))
+            .mapOption(value -> value == 0 ? Optional.empty() : 
Optional.of(value * 10))
+            .runWith(Sink.seq(), system)
+            .toCompletableFuture()
+            .get(3, TimeUnit.SECONDS);
+
+    assertEquals(
+        List.of(new Pair<>(10, "one"), new Pair<>(20, "two"), new Pair<>(30, 
"three")),
+        mapOptionResult);
+  }
+
+  @Test
+  public void mustPassContextThroughTruncatingOperators() throws Exception {
+    List<Pair<Integer, String>> input =
+        List.of(
+            new Pair<>(0, "zero"),
+            new Pair<>(1, "one"),
+            new Pair<>(1, "one-duplicate"),
+            new Pair<>(2, "two"),
+            new Pair<>(3, "three"),
+            new Pair<>(4, "four"));
+
+    List<Pair<Integer, String>> result =
+        SourceWithContext.fromPairs(Source.from(input))
+            .drop(1)
+            .dropRepeated()
+            .dropWhile(value -> value < 2)
+            .takeUntil(value -> value == 3)
+            .takeWithin(Duration.ofDays(1))
+            .take(2)
+            .limit(2)
+            .limitWeighted(2, value -> 1L)
+            .runWith(Sink.seq(), system)
+            .toCompletableFuture()
+            .get(3, TimeUnit.SECONDS);
+
+    assertEquals(List.of(new Pair<>(2, "two"), new Pair<>(3, "three")), 
result);
+
+    List<Pair<Integer, String>> inclusiveResult =
+        SourceWithContext.fromPairs(
+                Source.from(
+                    List.of(new Pair<>(1, "one"), new Pair<>(2, "two"), new 
Pair<>(3, "three"))))
+            .takeWhile(value -> value < 2, true)
+            .runWith(Sink.seq(), system)
+            .toCompletableFuture()
+            .get(3, TimeUnit.SECONDS);
+
+    assertEquals(List.of(new Pair<>(1, "one"), new Pair<>(2, "two")), 
inclusiveResult);
+
+    List<Pair<Integer, String>> customPredicateResult =
+        SourceWithContext.fromPairs(
+                Source.from(
+                    List.of(new Pair<>(1, "one"), new Pair<>(3, "three"), new 
Pair<>(4, "four"))))
+            .dropRepeated((left, right) -> left % 2 == right % 2)
+            .takeWhile(value -> value < 3)
+            .runWith(Sink.seq(), system)
+            .toCompletableFuture()
+            .get(3, TimeUnit.SECONDS);
+
+    assertEquals(List.of(new Pair<>(1, "one")), customPredicateResult);
+
+    List<Pair<Integer, String>> timedResult =
+        SourceWithContext.fromPairs(
+                Source.single(new Pair<>(1, 
"one")).initialDelay(Duration.ofMillis(50)))
+            .dropWithin(Duration.ofMillis(10))
+            .takeWithin(Duration.ofDays(1))
+            .runWith(Sink.seq(), system)
+            .toCompletableFuture()
+            .get(3, TimeUnit.SECONDS);
+
+    assertEquals(List.of(new Pair<>(1, "one")), timedResult);
+  }
+}
diff --git 
a/stream-tests/src/test/scala/org/apache/pekko/stream/scaladsl/FlowWithContextSpec.scala
 
b/stream-tests/src/test/scala/org/apache/pekko/stream/scaladsl/FlowWithContextSpec.scala
index fd27c27238..0325cc1650 100644
--- 
a/stream-tests/src/test/scala/org/apache/pekko/stream/scaladsl/FlowWithContextSpec.scala
+++ 
b/stream-tests/src/test/scala/org/apache/pekko/stream/scaladsl/FlowWithContextSpec.scala
@@ -81,6 +81,98 @@ class FlowWithContextSpec extends StreamSpec {
         .expectError(boom)
     }
 
+    "pass through contexts using additional filtering operators" in {
+      val flow = FlowWithContext[Any, String]
+        .collectType[Int]
+        .collectWhile { case value if value < 3 => value * 10 }
+
+      SourceWithContext
+        .fromTuples(Source(Vector((1: Any, "one"), (2: Any, "two"), ("three": 
Any, "three"), (3: Any, "three-int"))))
+        .via(flow)
+        .runWith(TestSink[(Int, String)]())
+        .request(3)
+        .expectNext((10, "one"), (20, "two"))
+        .expectComplete()
+
+      SourceWithContext
+        .fromTuples(Source(Vector((0, "zero"), (1, "one"), (2, "two"), (3, 
"three"))))
+        .via(FlowWithContext[Int, String].mapOption(value => if (value == 0) 
None else Some(value * 10)))
+        .runWith(TestSink[(Int, String)]())
+        .request(3)
+        .expectNext((10, "one"), (20, "two"), (30, "three"))
+        .expectComplete()
+
+      SourceWithContext
+        .fromTuples(Source(Vector((1: Any, "one"), (2: Any, "two"), (3: Any, 
"three"))))
+        .via(FlowWithContext[Any, String].collectFirst { case value: Int if 
value > 1 => value * 10 })
+        .runWith(TestSink[(Int, String)]())
+        .request(1)
+        .expectNext((20, "two"))
+        .expectComplete()
+
+      val forComprehensionFlow =
+        for {
+          value <- FlowWithContext[Int, String]
+          if value % 2 == 1
+        } yield value * 10
+
+      SourceWithContext
+        .fromTuples(Source(Vector((1, "one"), (2, "two"), (3, "three"))))
+        .via(forComprehensionFlow)
+        .runWith(TestSink[(Int, String)]())
+        .request(2)
+        .expectNext((10, "one"), (30, "three"))
+        .expectComplete()
+    }
+
+    "pass through contexts using truncating operators" in {
+      val flow = FlowWithContext[Int, String]
+        .drop(1)
+        .dropRepeated()
+        .dropWhile(_ < 2)
+        .takeUntil(_ == 3)
+        .takeWithin(1.day)
+        .take(2)
+        .limit(2)
+        .limitWeighted(2)(_ => 1)
+
+      SourceWithContext
+        .fromTuples(Source(Vector((0, "zero"), (1, "one"), (1, 
"one-duplicate"), (2, "two"), (3, "three"),
+          (4, "four"))))
+        .via(flow)
+        .runWith(TestSink[(Int, String)]())
+        .request(2)
+        .expectNext((2, "two"), (3, "three"))
+        .expectComplete()
+
+      SourceWithContext
+        .fromTuples(Source(Vector((1, "one"), (2, "two"), (3, "three"))))
+        .via(FlowWithContext[Int, String].takeWhile(_ < 2, inclusive = true))
+        .runWith(TestSink[(Int, String)]())
+        .request(2)
+        .expectNext((1, "one"), (2, "two"))
+        .expectComplete()
+
+      SourceWithContext
+        .fromTuples(Source(Vector((1, "one"), (3, "three"), (4, "four"))))
+        .via(
+          FlowWithContext[Int, String]
+            .dropRepeated((left, right) => left % 2 == right % 2)
+            .takeWhile(_ < 3))
+        .runWith(TestSink[(Int, String)]())
+        .request(2)
+        .expectNext((1, "one"))
+        .expectComplete()
+
+      SourceWithContext
+        .fromTuples(Source.single((1, "one")).initialDelay(50.millis))
+        .via(FlowWithContext[Int, 
String].dropWithin(10.millis).takeWithin(1.day))
+        .runWith(TestSink[(Int, String)]())
+        .request(1)
+        .expectNext((1, "one"))
+        .expectComplete()
+    }
+
     "pass through all data when using alsoTo" in {
       // alsoTo feeds an asynchronous side Sink, which may still be draining 
when the
       // main stream completes. Poll until it has observed every element 
instead of
diff --git 
a/stream-tests/src/test/scala/org/apache/pekko/stream/scaladsl/SourceWithContextSpec.scala
 
b/stream-tests/src/test/scala/org/apache/pekko/stream/scaladsl/SourceWithContextSpec.scala
index 02b9c9e535..363a0133cc 100644
--- 
a/stream-tests/src/test/scala/org/apache/pekko/stream/scaladsl/SourceWithContextSpec.scala
+++ 
b/stream-tests/src/test/scala/org/apache/pekko/stream/scaladsl/SourceWithContextSpec.scala
@@ -78,6 +78,99 @@ class SourceWithContextSpec extends StreamSpec {
         .expectComplete()
     }
 
+    "pass through contexts using additional filtering operators" in {
+      SourceWithContext
+        .fromTuples(Source(Vector((0, "zero"), (1, "one"), (2, "two"), (3, 
"three"))))
+        .mapOption {
+          case 0     => None
+          case value => Some(value * 10)
+        }
+        .runWith(TestSink[(Int, String)]())
+        .request(3)
+        .expectNext((10, "one"), (20, "two"), (30, "three"))
+        .expectComplete()
+
+      SourceWithContext
+        .fromTuples(Source(Vector((1, "one"), (2, "two"), (3, "three"))))
+        .collectFirst { case value if value > 1 => value * 10 }
+        .runWith(TestSink[(Int, String)]())
+        .request(1)
+        .expectNext((20, "two"))
+        .expectComplete()
+
+      SourceWithContext
+        .fromTuples(Source(Vector((1, "one"), (2, "two"), (3, "three"))))
+        .collectWhile { case value if value < 3 => value * 10 }
+        .runWith(TestSink[(Int, String)]())
+        .request(3)
+        .expectNext((10, "one"), (20, "two"))
+        .expectComplete()
+
+      SourceWithContext
+        .fromTuples(Source(Vector((1: Any, "one"), ("two": Any, "two"), (3: 
Any, "three"))))
+        .collectType[Int]
+        .runWith(TestSink[(Int, String)]())
+        .request(2)
+        .expectNext((1, "one"), (3, "three"))
+        .expectComplete()
+
+      val forComprehensionResult =
+        for {
+          value <- SourceWithContext.fromTuples(Source(Vector((1, "one"), (2, 
"two"), (3, "three"))))
+          if value % 2 == 1
+        } yield value * 10
+
+      forComprehensionResult
+        .runWith(TestSink[(Int, String)]())
+        .request(2)
+        .expectNext((10, "one"), (30, "three"))
+        .expectComplete()
+    }
+
+    "pass through contexts using truncating operators" in {
+      SourceWithContext
+        .fromTuples(Source(Vector((0, "zero"), (1, "one"), (1, 
"one-duplicate"), (2, "two"), (3, "three"),
+          (4, "four"))))
+        .drop(1)
+        .dropRepeated()
+        .dropWhile(_ < 2)
+        .takeUntil(_ == 3)
+        .takeWithin(1.day)
+        .take(2)
+        .limit(2)
+        .limitWeighted(2)(_ => 1)
+        .runWith(TestSink[(Int, String)]())
+        .request(2)
+        .expectNext((2, "two"), (3, "three"))
+        .expectComplete()
+
+      SourceWithContext
+        .fromTuples(Source(Vector((1, "one"), (2, "two"), (3, "three"))))
+        .takeWhile(_ < 2, inclusive = true)
+        .runWith(TestSink[(Int, String)]())
+        .request(2)
+        .expectNext((1, "one"), (2, "two"))
+        .expectComplete()
+
+      SourceWithContext
+        .fromTuples(Source(Vector((1, "one"), (3, "three"), (4, "four"))))
+        .dropRepeated((left, right) => left % 2 == right % 2)
+        .takeWhile(_ < 3)
+        .runWith(TestSink[(Int, String)]())
+        .request(2)
+        .expectNext((1, "one"))
+        .expectComplete()
+
+      SourceWithContext
+        .fromTuples(Source.single((1, "one")).initialDelay(50.millis))
+        .dropWithin(10.millis)
+        .takeWithin(1.day)
+        .runWith(TestSink[(Int, String)]())
+        .request(1)
+        .expectNext((1, "one"))
+        .expectComplete()
+    }
+
     "pass through all data when using alsoTo" in {
       // alsoTo feeds an asynchronous side Sink, which may still be draining 
when the
       // main stream completes. Poll until it has observed every element 
instead of
diff --git 
a/stream/src/main/scala/org/apache/pekko/stream/javadsl/FlowWithContext.scala 
b/stream/src/main/scala/org/apache/pekko/stream/javadsl/FlowWithContext.scala
index 00675baa84..68c6699fe3 100644
--- 
a/stream/src/main/scala/org/apache/pekko/stream/javadsl/FlowWithContext.scala
+++ 
b/stream/src/main/scala/org/apache/pekko/stream/javadsl/FlowWithContext.scala
@@ -21,6 +21,7 @@ import scala.jdk.CollectionConverters._
 import scala.jdk.DurationConverters._
 import scala.jdk.FutureConverters._
 import scala.jdk.OptionConverters._
+import scala.reflect.ClassTag
 
 import org.apache.pekko
 import pekko.annotation.ApiMayChange
@@ -168,6 +169,39 @@ final class FlowWithContext[In, CtxIn, Out, CtxOut, +Mat](
   def collect[Out2](pf: PartialFunction[Out, Out2]): FlowWithContext[In, 
CtxIn, Out2, CtxOut, Mat] =
     viaScala(_.collect(pf))
 
+  /**
+   * Context-preserving variant of [[pekko.stream.javadsl.Flow.collectFirst]].
+   *
+   * Note, that the context of elements that are filtered out is skipped as 
well.
+   *
+   * @see [[pekko.stream.javadsl.Flow.collectFirst]]
+   * @since 2.0.0
+   */
+  def collectFirst[Out2](pf: PartialFunction[Out, Out2]): FlowWithContext[In, 
CtxIn, Out2, CtxOut, Mat] =
+    viaScala(_.collectFirst(pf))
+
+  /**
+   * Context-preserving variant of [[pekko.stream.javadsl.Flow.collectWhile]].
+   *
+   * Note, that the context of elements that are filtered out is skipped as 
well.
+   *
+   * @see [[pekko.stream.javadsl.Flow.collectWhile]]
+   * @since 2.0.0
+   */
+  def collectWhile[Out2](pf: PartialFunction[Out, Out2]): FlowWithContext[In, 
CtxIn, Out2, CtxOut, Mat] =
+    viaScala(_.collectWhile(pf))
+
+  /**
+   * Context-preserving variant of [[pekko.stream.javadsl.Flow.collectType]].
+   *
+   * Note, that the context of elements that are filtered out is skipped as 
well.
+   *
+   * @see [[pekko.stream.javadsl.Flow.collectType]]
+   * @since 2.0.0
+   */
+  def collectType[Out2](clazz: Class[Out2]): FlowWithContext[In, CtxIn, Out2, 
CtxOut, Mat] =
+    viaScala(_.collectType[Out2](ClassTag[Out2](clazz)))
+
   /**
    * Context-preserving variant of [[pekko.stream.javadsl.Flow.filter]].
    *
@@ -188,6 +222,51 @@ final class FlowWithContext[In, CtxIn, Out, CtxOut, +Mat](
   def filterNot(p: function.Predicate[Out]): FlowWithContext[In, CtxIn, Out, 
CtxOut, Mat] =
     viaScala(_.filterNot(p.test))
 
+  /**
+   * Context-preserving variant of [[pekko.stream.javadsl.Flow.dropRepeated]].
+   *
+   * @see [[pekko.stream.javadsl.Flow.dropRepeated]]
+   * @since 2.0.0
+   */
+  def dropRepeated(): FlowWithContext[In, CtxIn, Out, CtxOut, Mat] =
+    viaScala(_.dropRepeated())
+
+  /**
+   * Context-preserving variant of [[pekko.stream.javadsl.Flow.dropRepeated]].
+   *
+   * @see [[pekko.stream.javadsl.Flow.dropRepeated]]
+   * @since 2.0.0
+   */
+  def dropRepeated(p: function.Function2[Out, Out, Boolean]): 
FlowWithContext[In, CtxIn, Out, CtxOut, Mat] =
+    viaScala(_.dropRepeated((left, right) => p.apply(left, right)))
+
+  /**
+   * Context-preserving variant of [[pekko.stream.javadsl.Flow.dropWhile]].
+   *
+   * @see [[pekko.stream.javadsl.Flow.dropWhile]]
+   * @since 2.0.0
+   */
+  def dropWhile(p: function.Predicate[Out]): FlowWithContext[In, CtxIn, Out, 
CtxOut, Mat] =
+    viaScala(_.dropWhile(p.test))
+
+  /**
+   * Context-preserving variant of [[pekko.stream.javadsl.Flow.drop]].
+   *
+   * @see [[pekko.stream.javadsl.Flow.drop]]
+   * @since 2.0.0
+   */
+  def drop(n: Long): FlowWithContext[In, CtxIn, Out, CtxOut, Mat] =
+    viaScala(_.drop(n))
+
+  /**
+   * Context-preserving variant of [[pekko.stream.javadsl.Flow.dropWithin]].
+   *
+   * @see [[pekko.stream.javadsl.Flow.dropWithin]]
+   * @since 2.0.0
+   */
+  def dropWithin(duration: java.time.Duration): FlowWithContext[In, CtxIn, 
Out, CtxOut, Mat] =
+    viaScala(_.dropWithin(duration.toScala))
+
   /**
    * Context-preserving variant of [[pekko.stream.javadsl.Flow.grouped]].
    *
@@ -209,6 +288,18 @@ final class FlowWithContext[In, CtxIn, Out, CtxOut, +Mat](
   def map[Out2](f: function.Function[Out, Out2]): FlowWithContext[In, CtxIn, 
Out2, CtxOut, Mat] =
     viaScala(_.map(f.apply))
 
+  /**
+   * Context-preserving variant of [[pekko.stream.javadsl.Flow.mapOption]].
+   *
+   * Note, that the context of elements that are filtered out is skipped as 
well.
+   *
+   * @see [[pekko.stream.javadsl.Flow.mapOption]]
+   * @since 2.0.0
+   */
+  def mapOption[Out2](
+      f: function.Function[Out, Optional[Out2]]): FlowWithContext[In, CtxIn, 
Out2, CtxOut, Mat] =
+    viaScala(_.mapOption(out => f.apply(out).toScala))
+
   /**
    * Context-preserving variant of [[pekko.stream.javadsl.Flow.mapAsync]].
    *
@@ -285,6 +376,25 @@ final class FlowWithContext[In, CtxIn, Out, CtxOut, +Mat](
     viaScala(_.mapContext(extractContext.apply))
   }
 
+  /**
+   * Context-preserving variant of [[pekko.stream.javadsl.Flow.limit]].
+   *
+   * @see [[pekko.stream.javadsl.Flow.limit]]
+   * @since 2.0.0
+   */
+  def limit(max: Long): FlowWithContext[In, CtxIn, Out, CtxOut, Mat] =
+    viaScala(_.limit(max))
+
+  /**
+   * Context-preserving variant of [[pekko.stream.javadsl.Flow.limitWeighted]].
+   *
+   * @see [[pekko.stream.javadsl.Flow.limitWeighted]]
+   * @since 2.0.0
+   */
+  def limitWeighted(max: Long, costFn: function.Function[Out, java.lang.Long])
+      : FlowWithContext[In, CtxIn, Out, CtxOut, Mat] =
+    viaScala(_.limitWeighted(max)(costFn.apply))
+
   /**
    * Context-preserving variant of [[pekko.stream.javadsl.Flow.sliding]].
    *
@@ -377,6 +487,53 @@ final class FlowWithContext[In, CtxIn, Out, CtxOut, +Mat](
       marker: function.Function2[Out, CtxOut, LogMarker]): FlowWithContext[In, 
CtxIn, Out, CtxOut, Mat] =
     this.logWithMarker(name, marker, ConstantFun.javaIdentityFunction[Out], 
null)
 
+  /**
+   * Context-preserving variant of [[pekko.stream.javadsl.Flow.takeWhile]].
+   *
+   * @see [[pekko.stream.javadsl.Flow.takeWhile]]
+   * @since 2.0.0
+   */
+  def takeWhile(
+      p: function.Predicate[Out],
+      inclusive: Boolean): FlowWithContext[In, CtxIn, Out, CtxOut, Mat] =
+    viaScala(_.takeWhile(p.test, inclusive))
+
+  /**
+   * Context-preserving variant of [[pekko.stream.javadsl.Flow.takeWhile]].
+   *
+   * @see [[pekko.stream.javadsl.Flow.takeWhile]]
+   * @since 2.0.0
+   */
+  def takeWhile(p: function.Predicate[Out]): FlowWithContext[In, CtxIn, Out, 
CtxOut, Mat] =
+    takeWhile(p, inclusive = false)
+
+  /**
+   * Context-preserving variant of [[pekko.stream.javadsl.Flow.takeUntil]].
+   *
+   * @see [[pekko.stream.javadsl.Flow.takeUntil]]
+   * @since 2.0.0
+   */
+  def takeUntil(p: function.Predicate[Out]): FlowWithContext[In, CtxIn, Out, 
CtxOut, Mat] =
+    viaScala(_.takeUntil(p.test))
+
+  /**
+   * Context-preserving variant of [[pekko.stream.javadsl.Flow.take]].
+   *
+   * @see [[pekko.stream.javadsl.Flow.take]]
+   * @since 2.0.0
+   */
+  def take(n: Long): FlowWithContext[In, CtxIn, Out, CtxOut, Mat] =
+    viaScala(_.take(n))
+
+  /**
+   * Context-preserving variant of [[pekko.stream.javadsl.Flow.takeWithin]].
+   *
+   * @see [[pekko.stream.javadsl.Flow.takeWithin]]
+   * @since 2.0.0
+   */
+  def takeWithin(duration: java.time.Duration): FlowWithContext[In, CtxIn, 
Out, CtxOut, Mat] =
+    viaScala(_.takeWithin(duration.toScala))
+
   /**
    * Context-preserving variant of [[pekko.stream.javadsl.Flow.throttle]].
    *
diff --git 
a/stream/src/main/scala/org/apache/pekko/stream/javadsl/SourceWithContext.scala 
b/stream/src/main/scala/org/apache/pekko/stream/javadsl/SourceWithContext.scala
index f0593e4420..e46bff7ae0 100644
--- 
a/stream/src/main/scala/org/apache/pekko/stream/javadsl/SourceWithContext.scala
+++ 
b/stream/src/main/scala/org/apache/pekko/stream/javadsl/SourceWithContext.scala
@@ -21,6 +21,7 @@ import scala.jdk.CollectionConverters._
 import scala.jdk.DurationConverters._
 import scala.jdk.FutureConverters._
 import scala.jdk.OptionConverters._
+import scala.reflect.ClassTag
 
 import org.apache.pekko
 import pekko.actor.ClassicActorSystemProvider
@@ -165,6 +166,39 @@ final class SourceWithContext[+Out, +Ctx, +Mat](delegate: 
scaladsl.SourceWithCon
   def collect[Out2](pf: PartialFunction[Out, Out2]): SourceWithContext[Out2, 
Ctx, Mat] =
     viaScala(_.collect(pf))
 
+  /**
+   * Context-preserving variant of 
[[pekko.stream.javadsl.Source.collectFirst]].
+   *
+   * Note, that the context of elements that are filtered out is skipped as 
well.
+   *
+   * @see [[pekko.stream.javadsl.Source.collectFirst]]
+   * @since 2.0.0
+   */
+  def collectFirst[Out2](pf: PartialFunction[Out, Out2]): 
SourceWithContext[Out2, Ctx, Mat] =
+    viaScala(_.collectFirst(pf))
+
+  /**
+   * Context-preserving variant of 
[[pekko.stream.javadsl.Source.collectWhile]].
+   *
+   * Note, that the context of elements that are filtered out is skipped as 
well.
+   *
+   * @see [[pekko.stream.javadsl.Source.collectWhile]]
+   * @since 2.0.0
+   */
+  def collectWhile[Out2](pf: PartialFunction[Out, Out2]): 
SourceWithContext[Out2, Ctx, Mat] =
+    viaScala(_.collectWhile(pf))
+
+  /**
+   * Context-preserving variant of [[pekko.stream.javadsl.Source.collectType]].
+   *
+   * Note, that the context of elements that are filtered out is skipped as 
well.
+   *
+   * @see [[pekko.stream.javadsl.Source.collectType]]
+   * @since 2.0.0
+   */
+  def collectType[Out2](clazz: Class[Out2]): SourceWithContext[Out2, Ctx, Mat] 
=
+    viaScala(_.collectType[Out2](ClassTag[Out2](clazz)))
+
   /**
    * Context-preserving variant of [[pekko.stream.javadsl.Source.filter]].
    *
@@ -185,6 +219,51 @@ final class SourceWithContext[+Out, +Ctx, +Mat](delegate: 
scaladsl.SourceWithCon
   def filterNot(p: function.Predicate[Out]): SourceWithContext[Out, Ctx, Mat] =
     viaScala(_.filterNot(p.test))
 
+  /**
+   * Context-preserving variant of 
[[pekko.stream.javadsl.Source.dropRepeated]].
+   *
+   * @see [[pekko.stream.javadsl.Source.dropRepeated]]
+   * @since 2.0.0
+   */
+  def dropRepeated(): SourceWithContext[Out, Ctx, Mat] =
+    viaScala(_.dropRepeated())
+
+  /**
+   * Context-preserving variant of 
[[pekko.stream.javadsl.Source.dropRepeated]].
+   *
+   * @see [[pekko.stream.javadsl.Source.dropRepeated]]
+   * @since 2.0.0
+   */
+  def dropRepeated(p: function.Function2[Out, Out, Boolean]): 
SourceWithContext[Out, Ctx, Mat] =
+    viaScala(_.dropRepeated((left, right) => p.apply(left, right)))
+
+  /**
+   * Context-preserving variant of [[pekko.stream.javadsl.Source.dropWhile]].
+   *
+   * @see [[pekko.stream.javadsl.Source.dropWhile]]
+   * @since 2.0.0
+   */
+  def dropWhile(p: function.Predicate[Out]): SourceWithContext[Out, Ctx, Mat] =
+    viaScala(_.dropWhile(p.test))
+
+  /**
+   * Context-preserving variant of [[pekko.stream.javadsl.Source.drop]].
+   *
+   * @see [[pekko.stream.javadsl.Source.drop]]
+   * @since 2.0.0
+   */
+  def drop(n: Long): SourceWithContext[Out, Ctx, Mat] =
+    viaScala(_.drop(n))
+
+  /**
+   * Context-preserving variant of [[pekko.stream.javadsl.Source.dropWithin]].
+   *
+   * @see [[pekko.stream.javadsl.Source.dropWithin]]
+   * @since 2.0.0
+   */
+  def dropWithin(duration: java.time.Duration): SourceWithContext[Out, Ctx, 
Mat] =
+    viaScala(_.dropWithin(duration.toScala))
+
   /**
    * Context-preserving variant of [[pekko.stream.javadsl.Source.grouped]].
    *
@@ -204,6 +283,17 @@ final class SourceWithContext[+Out, +Ctx, +Mat](delegate: 
scaladsl.SourceWithCon
   def map[Out2](f: function.Function[Out, Out2]): SourceWithContext[Out2, Ctx, 
Mat] =
     viaScala(_.map(f.apply))
 
+  /**
+   * Context-preserving variant of [[pekko.stream.javadsl.Source.mapOption]].
+   *
+   * Note, that the context of elements that are filtered out is skipped as 
well.
+   *
+   * @see [[pekko.stream.javadsl.Source.mapOption]]
+   * @since 2.0.0
+   */
+  def mapOption[Out2](f: function.Function[Out, Optional[Out2]]): 
SourceWithContext[Out2, Ctx, Mat] =
+    viaScala(_.mapOption(out => f.apply(out).toScala))
+
   /**
    * Context-preserving variant of [[pekko.stream.javadsl.Source.mapAsync]].
    *
@@ -276,6 +366,24 @@ final class SourceWithContext[+Out, +Ctx, +Mat](delegate: 
scaladsl.SourceWithCon
   def mapContext[Ctx2](extractContext: function.Function[Ctx, Ctx2]): 
SourceWithContext[Out, Ctx2, Mat] =
     viaScala(_.mapContext(extractContext.apply))
 
+  /**
+   * Context-preserving variant of [[pekko.stream.javadsl.Source.limit]].
+   *
+   * @see [[pekko.stream.javadsl.Source.limit]]
+   * @since 2.0.0
+   */
+  def limit(max: Int): SourceWithContext[Out, Ctx, Mat] =
+    viaScala(_.limit(max))
+
+  /**
+   * Context-preserving variant of 
[[pekko.stream.javadsl.Source.limitWeighted]].
+   *
+   * @see [[pekko.stream.javadsl.Source.limitWeighted]]
+   * @since 2.0.0
+   */
+  def limitWeighted(max: Long, costFn: function.Function[Out, 
java.lang.Long]): SourceWithContext[Out, Ctx, Mat] =
+    viaScala(_.limitWeighted(max)(costFn.apply))
+
   /**
    * Context-preserving variant of [[pekko.stream.javadsl.Source.sliding]].
    *
@@ -362,6 +470,51 @@ final class SourceWithContext[+Out, +Ctx, +Mat](delegate: 
scaladsl.SourceWithCon
   def logWithMarker(name: String, marker: function.Function2[Out, Ctx, 
LogMarker]): SourceWithContext[Out, Ctx, Mat] =
     this.logWithMarker(name, marker, ConstantFun.javaIdentityFunction[Out], 
null)
 
+  /**
+   * Context-preserving variant of [[pekko.stream.javadsl.Source.takeWhile]].
+   *
+   * @see [[pekko.stream.javadsl.Source.takeWhile]]
+   * @since 2.0.0
+   */
+  def takeWhile(p: function.Predicate[Out], inclusive: Boolean): 
SourceWithContext[Out, Ctx, Mat] =
+    viaScala(_.takeWhile(p.test, inclusive))
+
+  /**
+   * Context-preserving variant of [[pekko.stream.javadsl.Source.takeWhile]].
+   *
+   * @see [[pekko.stream.javadsl.Source.takeWhile]]
+   * @since 2.0.0
+   */
+  def takeWhile(p: function.Predicate[Out]): SourceWithContext[Out, Ctx, Mat] =
+    takeWhile(p, inclusive = false)
+
+  /**
+   * Context-preserving variant of [[pekko.stream.javadsl.Source.takeUntil]].
+   *
+   * @see [[pekko.stream.javadsl.Source.takeUntil]]
+   * @since 2.0.0
+   */
+  def takeUntil(p: function.Predicate[Out]): SourceWithContext[Out, Ctx, Mat] =
+    viaScala(_.takeUntil(p.test))
+
+  /**
+   * Context-preserving variant of [[pekko.stream.javadsl.Source.take]].
+   *
+   * @see [[pekko.stream.javadsl.Source.take]]
+   * @since 2.0.0
+   */
+  def take(n: Long): SourceWithContext[Out, Ctx, Mat] =
+    viaScala(_.take(n))
+
+  /**
+   * Context-preserving variant of [[pekko.stream.javadsl.Source.takeWithin]].
+   *
+   * @see [[pekko.stream.javadsl.Source.takeWithin]]
+   * @since 2.0.0
+   */
+  def takeWithin(duration: java.time.Duration): SourceWithContext[Out, Ctx, 
Mat] =
+    viaScala(_.takeWithin(duration.toScala))
+
   /**
    * Context-preserving variant of [[pekko.stream.javadsl.Source.throttle]].
    *
diff --git 
a/stream/src/main/scala/org/apache/pekko/stream/scaladsl/FlowWithContextOps.scala
 
b/stream/src/main/scala/org/apache/pekko/stream/scaladsl/FlowWithContextOps.scala
index 9fafa15e64..3a1193cf86 100644
--- 
a/stream/src/main/scala/org/apache/pekko/stream/scaladsl/FlowWithContextOps.scala
+++ 
b/stream/src/main/scala/org/apache/pekko/stream/scaladsl/FlowWithContextOps.scala
@@ -18,6 +18,7 @@ import scala.collection.immutable
 import scala.concurrent.ExecutionContext
 import scala.concurrent.Future
 import scala.concurrent.duration.FiniteDuration
+import scala.reflect.ClassTag
 
 import org.apache.pekko
 import pekko.NotUsed
@@ -126,6 +127,17 @@ trait FlowWithContextOps[+Out, +Ctx, +Mat] {
   def map[Out2](f: Out => Out2): Repr[Out2, Ctx] =
     via(flow.map { case (e, ctx) => (f(e), ctx) })
 
+  /**
+   * Context-preserving variant of [[pekko.stream.scaladsl.FlowOps.mapOption]].
+   *
+   * Note, that the context of elements that are filtered out is skipped as 
well.
+   *
+   * @see [[pekko.stream.scaladsl.FlowOps.mapOption]]
+   * @since 2.0.0
+   */
+  def mapOption[Out2](f: Out => Option[Out2]): Repr[Out2, Ctx] =
+    via(flow.mapOption { case (e, ctx) => f(e).map(_ -> ctx) })
+
   /**
    * Context-preserving variant of [[pekko.stream.scaladsl.FlowOps.mapError]].
    *
@@ -206,6 +218,161 @@ trait FlowWithContextOps[+Out, +Ctx, +Mat] {
   def filterNot(pred: Out => Boolean): Repr[Out, Ctx] =
     collect { case e if !pred(e) => e }
 
+  /**
+   * Alias for [[filter]], added to enable filtering in for comprehensions.
+   *
+   * @see [[pekko.stream.scaladsl.FlowOps.withFilter]]
+   * @since 2.0.0
+   */
+  @ApiMayChange
+  def withFilter(pred: Out => Boolean): Repr[Out, Ctx] =
+    filter(pred)
+
+  /**
+   * Context-preserving variant of 
[[pekko.stream.scaladsl.FlowOps.dropRepeated]].
+   *
+   * @see [[pekko.stream.scaladsl.FlowOps.dropRepeated]]
+   * @since 2.0.0
+   */
+  def dropRepeated(): Repr[Out, Ctx] =
+    dropRepeated(ConstantFun.scalaAnyTwoEquals)
+
+  /**
+   * Context-preserving variant of 
[[pekko.stream.scaladsl.FlowOps.dropRepeated]].
+   *
+   * @see [[pekko.stream.scaladsl.FlowOps.dropRepeated]]
+   * @since 2.0.0
+   */
+  def dropRepeated(pred: (Out, Out) => Boolean): Repr[Out, Ctx] =
+    via(flow.dropRepeated((left, right) => pred(left._1, right._1)))
+
+  /**
+   * Context-preserving variant of [[pekko.stream.scaladsl.FlowOps.takeWhile]].
+   *
+   * @see [[pekko.stream.scaladsl.FlowOps.takeWhile]]
+   * @since 2.0.0
+   */
+  def takeWhile(pred: Out => Boolean): Repr[Out, Ctx] =
+    takeWhile(pred, inclusive = false)
+
+  /**
+   * Context-preserving variant of [[pekko.stream.scaladsl.FlowOps.takeUntil]].
+   *
+   * @see [[pekko.stream.scaladsl.FlowOps.takeUntil]]
+   * @since 2.0.0
+   */
+  def takeUntil(pred: Out => Boolean): Repr[Out, Ctx] =
+    takeWhile(!pred(_), inclusive = true)
+
+  /**
+   * Context-preserving variant of [[pekko.stream.scaladsl.FlowOps.takeWhile]].
+   *
+   * @see [[pekko.stream.scaladsl.FlowOps.takeWhile]]
+   * @since 2.0.0
+   */
+  def takeWhile(pred: Out => Boolean, inclusive: Boolean): Repr[Out, Ctx] =
+    via(flow.takeWhile({ case (e, _) => pred(e) }, inclusive))
+
+  /**
+   * Context-preserving variant of [[pekko.stream.scaladsl.FlowOps.dropWhile]].
+   *
+   * @see [[pekko.stream.scaladsl.FlowOps.dropWhile]]
+   * @since 2.0.0
+   */
+  def dropWhile(pred: Out => Boolean): Repr[Out, Ctx] =
+    via(flow.dropWhile { case (e, _) => pred(e) })
+
+  /**
+   * Context-preserving variant of 
[[pekko.stream.scaladsl.FlowOps.collectFirst]].
+   *
+   * Note, that the context of elements that are filtered out is skipped as 
well.
+   *
+   * @see [[pekko.stream.scaladsl.FlowOps.collectFirst]]
+   * @since 2.0.0
+   */
+  def collectFirst[Out2](f: PartialFunction[Out, Out2]): Repr[Out2, Ctx] =
+    via(flow.collectFirst {
+      case (e, ctx) if f.isDefinedAt(e) => (f(e), ctx)
+    })
+
+  /**
+   * Context-preserving variant of 
[[pekko.stream.scaladsl.FlowOps.collectWhile]].
+   *
+   * Note, that the context of elements that are filtered out is skipped as 
well.
+   *
+   * @see [[pekko.stream.scaladsl.FlowOps.collectWhile]]
+   * @since 2.0.0
+   */
+  def collectWhile[Out2](f: PartialFunction[Out, Out2]): Repr[Out2, Ctx] =
+    via(flow.collectWhile {
+      case (e, ctx) if f.isDefinedAt(e) => (f(e), ctx)
+    })
+
+  /**
+   * Context-preserving variant of 
[[pekko.stream.scaladsl.FlowOps.collectType]].
+   *
+   * Note, that the context of elements that are filtered out is skipped as 
well.
+   *
+   * @see [[pekko.stream.scaladsl.FlowOps.collectType]]
+   * @since 2.0.0
+   */
+  def collectType[Out2](implicit tag: ClassTag[Out2]): Repr[Out2, Ctx] =
+    collect { case tag(e) => e }
+
+  /**
+   * Context-preserving variant of [[pekko.stream.scaladsl.FlowOps.drop]].
+   *
+   * @see [[pekko.stream.scaladsl.FlowOps.drop]]
+   * @since 2.0.0
+   */
+  def drop(n: Long): Repr[Out, Ctx] =
+    via(flow.drop(n))
+
+  /**
+   * Context-preserving variant of 
[[pekko.stream.scaladsl.FlowOps.dropWithin]].
+   *
+   * @see [[pekko.stream.scaladsl.FlowOps.dropWithin]]
+   * @since 2.0.0
+   */
+  def dropWithin(d: FiniteDuration): Repr[Out, Ctx] =
+    via(flow.dropWithin(d))
+
+  /**
+   * Context-preserving variant of [[pekko.stream.scaladsl.FlowOps.take]].
+   *
+   * @see [[pekko.stream.scaladsl.FlowOps.take]]
+   * @since 2.0.0
+   */
+  def take(n: Long): Repr[Out, Ctx] =
+    via(flow.take(n))
+
+  /**
+   * Context-preserving variant of 
[[pekko.stream.scaladsl.FlowOps.takeWithin]].
+   *
+   * @see [[pekko.stream.scaladsl.FlowOps.takeWithin]]
+   * @since 2.0.0
+   */
+  def takeWithin(d: FiniteDuration): Repr[Out, Ctx] =
+    via(flow.takeWithin(d))
+
+  /**
+   * Context-preserving variant of [[pekko.stream.scaladsl.FlowOps.limit]].
+   *
+   * @see [[pekko.stream.scaladsl.FlowOps.limit]]
+   * @since 2.0.0
+   */
+  def limit(max: Long): Repr[Out, Ctx] =
+    limitWeighted(max)(_ => 1)
+
+  /**
+   * Context-preserving variant of 
[[pekko.stream.scaladsl.FlowOps.limitWeighted]].
+   *
+   * @see [[pekko.stream.scaladsl.FlowOps.limitWeighted]]
+   * @since 2.0.0
+   */
+  def limitWeighted(max: Long)(costFn: Out => Long): Repr[Out, Ctx] =
+    via(flow.limitWeighted(max) { case (e, _) => costFn(e) })
+
   /**
    * Context-preserving variant of [[pekko.stream.scaladsl.FlowOps.grouped]].
    *


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to