Merge pull request #16007 from akka/wip-15977-changin-all-the-small-dsl-things-ban

+str #15977 Rename and change types in the new DSL
This commit is contained in:
Björn Antonsson 2014-10-03 07:52:23 +02:00
commit a8013f20b2
63 changed files with 1858 additions and 1675 deletions

View file

@ -40,7 +40,7 @@ private object RenderSupport {
val messageBytes =
if (!skipEntity) Flow(messageStart).concat(entityBytes).toPublisher()
else {
// FIXME: This should be fixed by a CancelledSink once #15903 is done. Currently this is needed for the tests
// FIXME: This should be fixed by a CancelledDrain once #15903 is done. Currently this is needed for the tests
entityBytes.subscribe(cancelledSusbcriber)
messageStart
}

View file

@ -53,7 +53,7 @@ package object util {
})
}
// FIXME: This should be fixed by a CancelledSink once #15903 is done. Currently this is needed for the tests
// FIXME: This should be fixed by a CancelledDrain once #15903 is done. Currently this is needed for the tests
private[http] def cancelledSusbcriber[T]: Subscriber[T] = new Subscriber[T] {
override def onSubscribe(s: Subscription): Unit = s.cancel()
override def onError(t: Throwable): Unit = ()

View file

@ -62,7 +62,7 @@ class HttpModelIntegrationSpec extends WordSpec with Matchers with BeforeAndAfte
entity = HttpEntity.Default(
contentType = ContentTypes.`application/json`,
contentLength = 5,
FlowFrom(List(ByteString("hello"))).toPublisher()))
Source(List(ByteString("hello"))).toPublisher()))
// Our library uses a simple model of headers: a Seq[(String, String)].
// The body is represented as an Array[Byte]. To get the headers in
@ -141,7 +141,7 @@ class HttpModelIntegrationSpec extends WordSpec with Matchers with BeforeAndAfte
// convert the body into a Publisher[ByteString].
val byteStringBody = ByteString(byteArrayBody)
val publisherBody = FlowFrom(List(byteStringBody)).toPublisher()
val publisherBody = Source(List(byteStringBody)).toPublisher()
// Finally we can create our HttpResponse.

View file

@ -6,23 +6,21 @@ import akka.stream.scaladsl2._
import akka.stream.testkit.StreamTestKit
import org.reactivestreams.Publisher
import scala.annotation.unchecked.uncheckedVariance
class ChainSetup[In, Out](
stream: ProcessorFlow[In, In] ProcessorFlow[In, Out],
stream: Flow[In, In] Flow[In, Out],
val settings: MaterializerSettings,
materializer: FlowMaterializer,
toPublisher: (FlowWithSource[In, Out], FlowMaterializer) Publisher[Out])(implicit val system: ActorSystem) {
toPublisher: (Source[Out], FlowMaterializer) Publisher[Out])(implicit val system: ActorSystem) {
def this(stream: ProcessorFlow[In, In] ProcessorFlow[In, Out], settings: MaterializerSettings, toPublisher: (FlowWithSource[In, Out], FlowMaterializer) Publisher[Out])(implicit system: ActorSystem) =
def this(stream: Flow[In, In] Flow[In, Out], settings: MaterializerSettings, toPublisher: (Source[Out], FlowMaterializer) Publisher[Out])(implicit system: ActorSystem) =
this(stream, settings, FlowMaterializer(settings)(system), toPublisher)(system)
def this(stream: ProcessorFlow[In, In] ProcessorFlow[In, Out], settings: MaterializerSettings, materializerCreator: (MaterializerSettings, ActorRefFactory) FlowMaterializer, toPublisher: (FlowWithSource[In, Out], FlowMaterializer) Publisher[Out])(implicit system: ActorSystem) =
def this(stream: Flow[In, In] Flow[In, Out], settings: MaterializerSettings, materializerCreator: (MaterializerSettings, ActorRefFactory) FlowMaterializer, toPublisher: (Source[Out], FlowMaterializer) Publisher[Out])(implicit system: ActorSystem) =
this(stream, settings, materializerCreator(settings, system), toPublisher)(system)
val upstream = StreamTestKit.PublisherProbe[In]()
val downstream = StreamTestKit.SubscriberProbe[Out]()
private val s = stream(FlowFrom[In]).withSource(PublisherSource(upstream))
private val s = Source(upstream).connect(stream(Flow[In]))
val publisher = toPublisher(s, materializer)
val upstreamSubscription = upstream.expectSubscription()
publisher.subscribe(downstream)

View file

@ -5,7 +5,7 @@ package akka.stream.testkit2
import akka.actor.ActorSystem
import akka.stream.MaterializerSettings
import akka.stream.scaladsl2.{ FlowMaterializer, FlowWithSource, ProcessorFlow }
import akka.stream.scaladsl2.{ FlowMaterializer, Source, Flow }
import akka.stream.testkit.StreamTestKit._
import org.reactivestreams.Publisher
import org.scalatest.Matchers
@ -18,7 +18,7 @@ trait ScriptedTest extends Matchers {
class ScriptException(msg: String) extends RuntimeException(msg)
def toPublisher[In, Out]: (FlowWithSource[In, Out], FlowMaterializer) Publisher[Out] =
def toPublisher[In, Out]: (Source[Out], FlowMaterializer) Publisher[Out] =
(f, m) f.toPublisher()(m)
object Script {
@ -81,7 +81,7 @@ trait ScriptedTest extends Matchers {
}
class ScriptRunner[In, Out](
op: ProcessorFlow[In, In] ProcessorFlow[In, Out],
op: Flow[In, In] Flow[In, Out],
settings: MaterializerSettings,
script: Script[In, Out],
maximumOverrun: Int,
@ -191,7 +191,7 @@ trait ScriptedTest extends Matchers {
}
def runScript[In, Out](script: Script[In, Out], settings: MaterializerSettings, maximumOverrun: Int = 3, maximumRequest: Int = 3, maximumBuffer: Int = 3)(
op: ProcessorFlow[In, In] ProcessorFlow[In, Out])(implicit system: ActorSystem): Unit = {
op: Flow[In, In] Flow[In, Out])(implicit system: ActorSystem): Unit = {
new ScriptRunner(op, settings, script, maximumOverrun, maximumRequest, maximumBuffer).run()
}

View file

@ -30,8 +30,8 @@ abstract class TwoStreamsSetup extends AkkaSpec {
import FlowGraphImplicits._
val left = operationUnderTestLeft()
val right = operationUnderTestRight()
val x = FlowFrom(p1) ~> left ~> FlowFrom[Outputs] ~> SubscriberSink(subscriber)
FlowFrom(p2) ~> right
val x = Source(p1) ~> left ~> Flow[Outputs] ~> SubscriberDrain(subscriber)
Source(p2) ~> right
}.run()
subscriber
@ -41,7 +41,7 @@ abstract class TwoStreamsSetup extends AkkaSpec {
def completedPublisher[T]: Publisher[T] = StreamTestKit.emptyPublisher[T]
def nonemptyPublisher[T](elems: Iterator[T]): Publisher[T] = FlowFrom(elems).toPublisher()
def nonemptyPublisher[T](elems: Iterator[T]): Publisher[T] = Source(elems).toPublisher()
def soonToFailPublisher[T]: Publisher[T] = StreamTestKit.lazyErrorPublisher[T](TestException)

View file

@ -14,32 +14,28 @@ class FlowAppendSpec extends AkkaSpec with River {
val settings = MaterializerSettings(system)
implicit val materializer = FlowMaterializer(settings)
"ProcessorFlow" should {
"append ProcessorFlow" in riverOf[String] { subscriber
FlowFrom[Int]
.append(otherFlow)
.withSource(IterableSource(elements))
.publishTo(subscriber)
"Flow" should {
"append Flow" in riverOf[String] { subscriber
val flow = Flow[Int].connect(otherFlow)
Source(elements).connect(flow).publishTo(subscriber)
}
"append FlowWithSink" in riverOf[String] { subscriber
FlowFrom[Int]
.append(otherFlow.withSink(SubscriberSink(subscriber)))
.withSource(IterableSource(elements))
.run()
"append Sink" in riverOf[String] { subscriber
val sink = Flow[Int].connect(otherFlow.connect(SubscriberDrain(subscriber)))
Source(elements).connect(sink).run()
}
}
"FlowWithSource" should {
"append ProcessorFlow" in riverOf[String] { subscriber
FlowFrom(elements)
.append(otherFlow)
"Source" should {
"append Flow" in riverOf[String] { subscriber
Source(elements)
.connect(otherFlow)
.publishTo(subscriber)
}
"append FlowWithSink" in riverOf[String] { subscriber
FlowFrom(elements)
.append(otherFlow.withSink(SubscriberSink(subscriber)))
"append Sink" in riverOf[String] { subscriber
Source(elements)
.connect(otherFlow.connect(SubscriberDrain(subscriber)))
.run()
}
}
@ -49,7 +45,7 @@ class FlowAppendSpec extends AkkaSpec with River {
trait River { self: Matchers
val elements = (1 to 10)
val otherFlow = FlowFrom[Int].map(_.toString)
val otherFlow = Flow[Int].map(_.toString)
def riverOf[T](flowConstructor: Subscriber[T] Unit)(implicit system: ActorSystem) = {
val subscriber = StreamTestKit.SubscriberProbe[T]()

View file

@ -20,24 +20,24 @@ class FlowBufferSpec extends AkkaSpec {
"Buffer" must {
"pass elements through normally in backpressured mode" in {
val futureSink = FutureSink[Seq[Int]]
val mf = FlowFrom((1 to 1000).iterator).buffer(100, overflowStrategy = OverflowStrategy.backpressure).grouped(1001).
withSink(futureSink).run()
val future = futureSink.future(mf)
val futureDrain = FutureDrain[Seq[Int]]
val mf = Source((1 to 1000).iterator).buffer(100, overflowStrategy = OverflowStrategy.backpressure).grouped(1001).
connect(futureDrain).run()
val future = futureDrain.future(mf)
Await.result(future, 3.seconds) should be(1 to 1000)
}
"pass elements through normally in backpressured mode with buffer size one" in {
val futureSink = FutureSink[Seq[Int]]
val mf = FlowFrom((1 to 1000).iterator).buffer(1, overflowStrategy = OverflowStrategy.backpressure).grouped(1001).
withSink(futureSink).run()
val future = futureSink.future(mf)
val futureDrain = FutureDrain[Seq[Int]]
val mf = Source((1 to 1000).iterator).buffer(1, overflowStrategy = OverflowStrategy.backpressure).grouped(1001).
connect(futureDrain).run()
val future = futureDrain.future(mf)
Await.result(future, 3.seconds) should be(1 to 1000)
}
"pass elements through a chain of backpressured buffers of different size" in {
val futureSink = FutureSink[Seq[Int]]
val mf = FlowFrom((1 to 1000).iterator)
val futureDrain = FutureDrain[Seq[Int]]
val mf = Source((1 to 1000).iterator)
.buffer(1, overflowStrategy = OverflowStrategy.backpressure)
.buffer(10, overflowStrategy = OverflowStrategy.backpressure)
.buffer(256, overflowStrategy = OverflowStrategy.backpressure)
@ -45,8 +45,8 @@ class FlowBufferSpec extends AkkaSpec {
.buffer(5, overflowStrategy = OverflowStrategy.backpressure)
.buffer(128, overflowStrategy = OverflowStrategy.backpressure)
.grouped(1001)
.withSink(futureSink).run()
val future = futureSink.future(mf)
.connect(futureDrain).run()
val future = futureDrain.future(mf)
Await.result(future, 3.seconds) should be(1 to 1000)
}
@ -54,7 +54,7 @@ class FlowBufferSpec extends AkkaSpec {
val publisher = StreamTestKit.PublisherProbe[Int]()
val subscriber = StreamTestKit.SubscriberProbe[Int]()
FlowFrom(publisher).buffer(100, overflowStrategy = OverflowStrategy.backpressure).publishTo(subscriber)
Source(publisher).buffer(100, overflowStrategy = OverflowStrategy.backpressure).publishTo(subscriber)
val autoPublisher = new StreamTestKit.AutoPublisher(publisher)
val sub = subscriber.expectSubscription()
@ -74,7 +74,7 @@ class FlowBufferSpec extends AkkaSpec {
val publisher = StreamTestKit.PublisherProbe[Int]()
val subscriber = StreamTestKit.SubscriberProbe[Int]()
FlowFrom(publisher).buffer(100, overflowStrategy = OverflowStrategy.dropHead).publishTo(subscriber)
Source(publisher).buffer(100, overflowStrategy = OverflowStrategy.dropHead).publishTo(subscriber)
val autoPublisher = new StreamTestKit.AutoPublisher(publisher)
val sub = subscriber.expectSubscription()
@ -102,7 +102,7 @@ class FlowBufferSpec extends AkkaSpec {
val publisher = StreamTestKit.PublisherProbe[Int]()
val subscriber = StreamTestKit.SubscriberProbe[Int]()
FlowFrom(publisher).buffer(100, overflowStrategy = OverflowStrategy.dropTail).publishTo(subscriber)
Source(publisher).buffer(100, overflowStrategy = OverflowStrategy.dropTail).publishTo(subscriber)
val autoPublisher = new StreamTestKit.AutoPublisher(publisher)
val sub = subscriber.expectSubscription()
@ -133,7 +133,7 @@ class FlowBufferSpec extends AkkaSpec {
val publisher = StreamTestKit.PublisherProbe[Int]
val subscriber = StreamTestKit.SubscriberProbe[Int]()
FlowFrom(publisher).buffer(100, overflowStrategy = OverflowStrategy.dropBuffer).publishTo(subscriber)
Source(publisher).buffer(100, overflowStrategy = OverflowStrategy.dropBuffer).publishTo(subscriber)
val autoPublisher = new StreamTestKit.AutoPublisher(publisher)
val sub = subscriber.expectSubscription()
@ -164,7 +164,7 @@ class FlowBufferSpec extends AkkaSpec {
val publisher = StreamTestKit.PublisherProbe[Int]
val subscriber = StreamTestKit.SubscriberProbe[Int]()
FlowFrom(publisher).buffer(1, overflowStrategy = strategy).publishTo(subscriber)
Source(publisher).buffer(1, overflowStrategy = strategy).publishTo(subscriber)
val autoPublisher = new StreamTestKit.AutoPublisher(publisher)
val sub = subscriber.expectSubscription()

View file

@ -5,143 +5,103 @@ package akka.stream.scaladsl2
import akka.stream.MaterializerSettings
import akka.stream.testkit.AkkaSpec
import scala.collection.immutable.Seq
import scala.concurrent.Future
class FlowCompileSpec extends AkkaSpec {
val intSeq = IterableSource(Seq(1, 2, 3))
val strSeq = IterableSource(Seq("a", "b", "c"))
val intSeq = IterableTap(Seq(1, 2, 3))
val strSeq = IterableTap(Seq("a", "b", "c"))
import scala.concurrent.ExecutionContext.Implicits.global
val intFut = FutureSource(Future { 3 })
val intFut = FutureTap(Future { 3 })
implicit val materializer = FlowMaterializer(MaterializerSettings(system))
"ProcessorFlow" should {
"go through all states" in {
val f: ProcessorFlow[Int, Int] = FlowFrom[Int]
.withSource(intSeq)
.withSink(PublisherSink[Int])
.withoutSource
.withoutSink
}
"Flow" should {
"should not run" in {
val open: ProcessorFlow[Int, Int] = FlowFrom[Int]
val open: Flow[Int, Int] = Flow[Int]
"open.run()" shouldNot compile
}
"accept IterableSource" in {
val f: FlowWithSource[Int, Int] = FlowFrom[Int].withSource(intSeq)
"accept Iterable" in {
val f: Source[Int] = intSeq.connect(Flow[Int])
}
"accept FutureSource" in {
val f: FlowWithSource[Int, Int] = FlowFrom[Int].withSource(intFut)
"accept Future" in {
val f: Source[Int] = intFut.connect(Flow[Int])
}
"append ProcessorFlow" in {
val open1: ProcessorFlow[Int, String] = FlowFrom[Int].map(_.toString)
val open2: ProcessorFlow[String, Int] = FlowFrom[String].map(_.hashCode)
val open3: ProcessorFlow[Int, Int] = open1.append(open2)
"append Flow" in {
val open1: Flow[Int, String] = Flow[Int].map(_.toString)
val open2: Flow[String, Int] = Flow[String].map(_.hashCode)
val open3: Flow[Int, Int] = open1.connect(open2)
"open3.run()" shouldNot compile
val closedSource: FlowWithSource[Int, Int] = open3.withSource(intSeq)
val closedSource: Source[Int] = intSeq.connect(open3)
"closedSource.run()" shouldNot compile
val closedSink: FlowWithSink[Int, Int] = open3.withSink(PublisherSink[Int])
val closedSink: Sink[Int] = open3.connect(PublisherDrain[Int])
"closedSink.run()" shouldNot compile
closedSource.withSink(PublisherSink[Int]).run()
closedSink.withSource(intSeq).run()
closedSource.connect(PublisherDrain[Int]).run()
intSeq.connect(closedSink).run()
}
"prepend ProcessorFlow" in {
val open1: ProcessorFlow[Int, String] = FlowFrom[Int].map(_.toString)
val open2: ProcessorFlow[String, Int] = FlowFrom[String].map(_.hashCode)
val open3: ProcessorFlow[String, String] = open1.prepend(open2)
"open3.run()" shouldNot compile
val closedSource: FlowWithSource[String, String] = open3.withSource(strSeq)
"closedSource.run()" shouldNot compile
val closedSink: FlowWithSink[String, String] = open3.withSink(PublisherSink[String])
"closedSink.run()" shouldNot compile
closedSource.withSink(PublisherSink[String]).run
closedSink.withSource(strSeq).run
}
"append FlowWithSink" in {
val open: ProcessorFlow[Int, String] = FlowFrom[Int].map(_.toString)
val closedSink: FlowWithSink[String, Int] = FlowFrom[String].map(_.hashCode).withSink(PublisherSink[Int])
val appended: FlowWithSink[Int, Int] = open.append(closedSink)
"append Sink" in {
val open: Flow[Int, String] = Flow[Int].map(_.toString)
val closedDrain: Sink[String] = Flow[String].map(_.hashCode).connect(PublisherDrain[Int])
val appended: Sink[Int] = open.connect(closedDrain)
"appended.run()" shouldNot compile
"appended.toFuture" shouldNot compile
appended.withSource(intSeq).run
"appended.connect(FutureDrain[Int])" shouldNot compile
intSeq.connect(appended).run
}
"prepend FlowWithSource" in {
val open: ProcessorFlow[Int, String] = FlowFrom[Int].map(_.toString)
val closedSource: FlowWithSource[String, Int] = FlowFrom[String].map(_.hashCode).withSource(strSeq)
val prepended: FlowWithSource[String, String] = open.prepend(closedSource)
"prepended.run()" shouldNot compile
"prepended.withSource(strSeq)" shouldNot compile
prepended.withSink(PublisherSink[String]).run
"be appended to Source" in {
val open: Flow[Int, String] = Flow[Int].map(_.toString)
val closedTap: Source[Int] = strSeq.connect(Flow[String].map(_.hashCode))
val closedSource: Source[String] = closedTap.connect(open)
"closedSource.run()" shouldNot compile
"strSeq.connect(closedSource)" shouldNot compile
closedSource.connect(PublisherDrain[String]).run
}
}
"FlowWithSink" should {
val openSource: FlowWithSink[Int, String] =
FlowFrom[Int].map(_.toString).withSink(PublisherSink[String])
"Sink" should {
val openSource: Sink[Int] =
Flow[Int].map(_.toString).connect(PublisherDrain[String])
"accept Source" in {
openSource.withSource(intSeq)
}
"drop Sink" in {
openSource.withoutSink
}
"not drop Source" in {
"openSource.withoutSource" shouldNot compile
intSeq.connect(openSource)
}
"not accept Sink" in {
"openSource.ToFuture" shouldNot compile
"openSource.connect(FutureDrain[String])" shouldNot compile
}
"not run()" in {
"openSource.run()" shouldNot compile
}
}
"FlowWithSource" should {
val openSink: FlowWithSource[Int, String] =
FlowFrom(Seq(1, 2, 3)).map(_.toString)
"Source" should {
val openSource: Source[String] =
Source(Seq(1, 2, 3)).map(_.toString)
"accept Sink" in {
openSink.withSink(PublisherSink[String])
openSource.connect(PublisherDrain[String])
}
"drop Source" in {
openSink.withoutSource
}
"not drop Sink" in {
"openSink.withoutSink" shouldNot compile
}
"not accept Source" in {
"openSink.withSource(intSeq)" shouldNot compile
"not be accepted by Source" in {
"openSource.connect(intSeq)" shouldNot compile
}
"not run()" in {
"openSink.run()" shouldNot compile
"openSource.run()" shouldNot compile
}
}
"RunnableFlow" should {
val closed: RunnableFlow[Int, String] =
FlowFrom(Seq(1, 2, 3)).map(_.toString).withSink(PublisherSink[String])
FutureDrain[String]
val closed: RunnableFlow =
Source(Seq(1, 2, 3)).map(_.toString).connect(PublisherDrain[String])
"run" in {
closed.run()
}
"drop Source" in {
closed.withoutSource
}
"drop Sink" in {
closed.withoutSink
}
"not accept Source" in {
"closed.withSource(intSeq)" shouldNot compile
"not be accepted by Source" in {
"intSeq.connect(closed)" shouldNot compile
}
"not accept Sink" in {
"closed.ToFuture" shouldNot compile
"closed.connect(FutureDrain[String])" shouldNot compile
}
}
}

View file

@ -23,13 +23,13 @@ class FlowConcatAllSpec extends AkkaSpec {
val testException = new Exception("test") with NoStackTrace
"work in the happy case" in {
val s1 = FlowFrom((1 to 2).iterator)
val s2 = FlowFrom(List.empty[Int])
val s3 = FlowFrom(List(3))
val s4 = FlowFrom((4 to 6).iterator)
val s5 = FlowFrom((7 to 10).iterator)
val s1 = Source((1 to 2).iterator)
val s2 = Source(List.empty[Int])
val s3 = Source(List(3))
val s4 = Source((4 to 6).iterator)
val s5 = Source((7 to 10).iterator)
val main = FlowFrom(List(s1, s2, s3, s4, s5))
val main = Source(List(s1, s2, s3, s4, s5))
val subscriber = StreamTestKit.SubscriberProbe[Int]()
main.flatten(FlattenStrategy.concat).publishTo(subscriber)
@ -42,7 +42,7 @@ class FlowConcatAllSpec extends AkkaSpec {
"work together with SplitWhen" in {
val subscriber = StreamTestKit.SubscriberProbe[Int]()
FlowFrom((1 to 10).iterator).splitWhen(_ % 2 == 0).flatten(FlattenStrategy.concat).publishTo(subscriber)
Source((1 to 10).iterator).splitWhen(_ % 2 == 0).flatten(FlattenStrategy.concat).publishTo(subscriber)
val subscription = subscriber.expectSubscription()
subscription.request(10)
subscriber.probe.receiveN(10) should be((1 to 10).map(StreamTestKit.OnNext(_)))
@ -51,16 +51,16 @@ class FlowConcatAllSpec extends AkkaSpec {
}
"on onError on master stream cancel the current open substream and signal error" in {
val publisher = StreamTestKit.PublisherProbe[FlowWithSource[Int, Int]]()
val publisher = StreamTestKit.PublisherProbe[Source[Int]]()
val subscriber = StreamTestKit.SubscriberProbe[Int]()
FlowFrom(publisher).flatten(FlattenStrategy.concat).publishTo(subscriber)
Source(publisher).flatten(FlattenStrategy.concat).publishTo(subscriber)
val upstream = publisher.expectSubscription()
val downstream = subscriber.expectSubscription()
downstream.request(1000)
val substreamPublisher = StreamTestKit.PublisherProbe[Int]()
val substreamFlow = FlowFrom(substreamPublisher)
val substreamFlow = Source(substreamPublisher)
upstream.expectRequest()
upstream.sendNext(substreamFlow)
val subUpstream = substreamPublisher.expectSubscription()
@ -71,16 +71,16 @@ class FlowConcatAllSpec extends AkkaSpec {
}
"on onError on open substream, cancel the master stream and signal error " in {
val publisher = StreamTestKit.PublisherProbe[FlowWithSource[Int, Int]]()
val publisher = StreamTestKit.PublisherProbe[Source[Int]]()
val subscriber = StreamTestKit.SubscriberProbe[Int]()
FlowFrom(publisher).flatten(FlattenStrategy.concat).publishTo(subscriber)
Source(publisher).flatten(FlattenStrategy.concat).publishTo(subscriber)
val upstream = publisher.expectSubscription()
val downstream = subscriber.expectSubscription()
downstream.request(1000)
val substreamPublisher = StreamTestKit.PublisherProbe[Int]()
val substreamFlow = FlowFrom(substreamPublisher)
val substreamFlow = Source(substreamPublisher)
upstream.expectRequest()
upstream.sendNext(substreamFlow)
val subUpstream = substreamPublisher.expectSubscription()
@ -91,16 +91,16 @@ class FlowConcatAllSpec extends AkkaSpec {
}
"on cancellation cancel the current open substream and the master stream" in {
val publisher = StreamTestKit.PublisherProbe[FlowWithSource[Int, Int]]()
val publisher = StreamTestKit.PublisherProbe[Source[Int]]()
val subscriber = StreamTestKit.SubscriberProbe[Int]()
FlowFrom(publisher).flatten(FlattenStrategy.concat).publishTo(subscriber)
Source(publisher).flatten(FlattenStrategy.concat).publishTo(subscriber)
val upstream = publisher.expectSubscription()
val downstream = subscriber.expectSubscription()
downstream.request(1000)
val substreamPublisher = StreamTestKit.PublisherProbe[Int]()
val substreamFlow = FlowFrom(substreamPublisher)
val substreamFlow = Source(substreamPublisher)
upstream.expectRequest()
upstream.sendNext(substreamFlow)
val subUpstream = substreamPublisher.expectSubscription()

View file

@ -23,7 +23,7 @@ class FlowConflateSpec extends AkkaSpec {
val publisher = StreamTestKit.PublisherProbe[Int]()
val subscriber = StreamTestKit.SubscriberProbe[Int]()
FlowFrom(publisher).conflate[Int](seed = i i, aggregate = (sum, i) sum + i).publishTo(subscriber)
Source(publisher).conflate[Int](seed = i i, aggregate = (sum, i) sum + i).publishTo(subscriber)
val autoPublisher = new StreamTestKit.AutoPublisher(publisher)
val sub = subscriber.expectSubscription()
@ -41,7 +41,7 @@ class FlowConflateSpec extends AkkaSpec {
val publisher = StreamTestKit.PublisherProbe[Int]()
val subscriber = StreamTestKit.SubscriberProbe[Int]()
FlowFrom(publisher).conflate[Int](seed = i i, aggregate = (sum, i) sum + i).publishTo(subscriber)
Source(publisher).conflate[Int](seed = i i, aggregate = (sum, i) sum + i).publishTo(subscriber)
val autoPublisher = new StreamTestKit.AutoPublisher(publisher)
val sub = subscriber.expectSubscription()
@ -56,13 +56,13 @@ class FlowConflateSpec extends AkkaSpec {
}
"work on a variable rate chain" in {
val foldSink = FoldSink[Int, Int](0)(_ + _)
val mf = FlowFrom((1 to 1000).iterator)
val foldDrain = FoldDrain[Int, Int](0)(_ + _)
val mf = Source((1 to 1000).iterator)
.conflate[Int](seed = i i, aggregate = (sum, i) sum + i)
.map { i if (ThreadLocalRandom.current().nextBoolean()) Thread.sleep(10); i }
.withSink(foldSink)
.connect(foldDrain)
.run()
val future = foldSink.future(mf)
val future = foldDrain.future(mf)
Await.result(future, 10.seconds) should be(500500)
}
@ -70,7 +70,7 @@ class FlowConflateSpec extends AkkaSpec {
val publisher = StreamTestKit.PublisherProbe[Int]()
val subscriber = StreamTestKit.SubscriberProbe[Int]()
FlowFrom(publisher).conflate[Int](seed = i i, aggregate = (sum, i) sum + i).publishTo(subscriber)
Source(publisher).conflate[Int](seed = i i, aggregate = (sum, i) sum + i).publishTo(subscriber)
val autoPublisher = new StreamTestKit.AutoPublisher(publisher)
val sub = subscriber.expectSubscription()

View file

@ -14,7 +14,7 @@ class FlowDispatcherSpec extends AkkaSpec {
"Flow with dispatcher setting" must {
"use the specified dispatcher" in {
val probe = TestProbe()
val p = FlowFrom(List(1, 2, 3)).map(i
val p = Source(List(1, 2, 3)).map(i
{ probe.ref ! Thread.currentThread().getName(); i }).
consume()
probe.receiveN(3) foreach {

View file

@ -29,7 +29,7 @@ class FlowDropSpec extends AkkaSpec with ScriptedTest {
"not drop anything for negative n" in {
val probe = StreamTestKit.SubscriberProbe[Int]()
FlowFrom(List(1, 2, 3)).drop(-1).publishTo(probe)
Source(List(1, 2, 3)).drop(-1).publishTo(probe)
probe.expectSubscription().request(10)
probe.expectNext(1)
probe.expectNext(2)

View file

@ -18,7 +18,7 @@ class FlowDropWithinSpec extends AkkaSpec {
val input = Iterator.from(1)
val p = StreamTestKit.PublisherProbe[Int]()
val c = StreamTestKit.SubscriberProbe[Int]()
FlowFrom(p).dropWithin(1.second).publishTo(c)
Source(p).dropWithin(1.second).publishTo(c)
val pSub = p.expectSubscription
val cSub = c.expectSubscription
cSub.request(100)

View file

@ -24,7 +24,7 @@ class FlowExpandSpec extends AkkaSpec {
val subscriber = StreamTestKit.SubscriberProbe[Int]()
// Simply repeat the last element as an extrapolation step
FlowFrom(publisher).expand[Int, Int](seed = i i, extrapolate = i (i, i)).publishTo(subscriber)
Source(publisher).expand[Int, Int](seed = i i, extrapolate = i (i, i)).publishTo(subscriber)
val autoPublisher = new StreamTestKit.AutoPublisher(publisher)
val sub = subscriber.expectSubscription()
@ -44,7 +44,7 @@ class FlowExpandSpec extends AkkaSpec {
val subscriber = StreamTestKit.SubscriberProbe[Int]()
// Simply repeat the last element as an extrapolation step
FlowFrom(publisher).expand[Int, Int](seed = i i, extrapolate = i (i, i)).publishTo(subscriber)
Source(publisher).expand[Int, Int](seed = i i, extrapolate = i (i, i)).publishTo(subscriber)
val autoPublisher = new StreamTestKit.AutoPublisher(publisher)
val sub = subscriber.expectSubscription()
@ -64,13 +64,13 @@ class FlowExpandSpec extends AkkaSpec {
}
"work on a variable rate chain" in {
val foldSink = FoldSink[Set[Int], Int](Set.empty[Int])(_ + _)
val mf = FlowFrom((1 to 100).iterator)
val foldDrain = FoldDrain[Set[Int], Int](Set.empty[Int])(_ + _)
val mf = Source((1 to 100).iterator)
.map { i if (ThreadLocalRandom.current().nextBoolean()) Thread.sleep(10); i }
.expand[Int, Int](seed = i i, extrapolate = i (i, i))
.withSink(foldSink)
.connect(foldDrain)
.run()
val future = foldSink.future(mf)
val future = foldDrain.future(mf)
Await.result(future, 10.seconds) should be(Set.empty[Int] ++ (1 to 100))
}
@ -79,7 +79,7 @@ class FlowExpandSpec extends AkkaSpec {
val publisher = StreamTestKit.PublisherProbe[Int]()
val subscriber = StreamTestKit.SubscriberProbe[Int]()
FlowFrom(publisher).expand[Int, Int](seed = i i, extrapolate = i (i, i)).publishTo(subscriber)
Source(publisher).expand[Int, Int](seed = i i, extrapolate = i (i, i)).publishTo(subscriber)
val autoPublisher = new StreamTestKit.AutoPublisher(publisher)
val sub = subscriber.expectSubscription()

View file

@ -29,7 +29,7 @@ class FlowFilterSpec extends AkkaSpec with ScriptedTest {
implicit val materializer = FlowMaterializer(settings)
val probe = StreamTestKit.SubscriberProbe[Int]()
FlowFrom(Iterator.fill(1000)(0) ++ List(1)).filter(_ != 0).
Source(Iterator.fill(1000)(0) ++ List(1)).filter(_ != 0).
toPublisher().subscribe(probe)
val subscription = probe.expectSubscription()

View file

@ -15,17 +15,17 @@ class FlowFoldSpec extends AkkaSpec with DefaultTimeout {
"fold" in {
val input = 1 to 100
val foldSink = FoldSink[Int, Int](0)(_ + _)
val mf = FlowFrom(input).withSink(foldSink).run()
val future = foldSink.future(mf)
val foldDrain = FoldDrain[Int, Int](0)(_ + _)
val mf = Source(input).connect(foldDrain).run()
val future = foldDrain.future(mf)
val expected = input.fold(0)(_ + _)
Await.result(future, timeout.duration) should be(expected)
}
"propagate an error" in {
val error = new Exception with NoStackTrace
val foldSink = FoldSink[Unit, Unit](())((_, _) ())
val mf = FlowFrom[Unit](() throw error).withSink(foldSink).run()
val foldSink = FoldDrain[Unit, Unit](())((_, _) ())
val mf = Source[Unit](() throw error).connect(foldSink).run()
val future = foldSink.future(mf)
the[Exception] thrownBy Await.result(future, timeout.duration) should be(error)
}

View file

@ -16,9 +16,9 @@ class FlowForeachSpec extends AkkaSpec {
"A Foreach" must {
"call the procedure for each element" in {
val foreachSink = ForeachSink[Int](testActor ! _)
val mf = FlowFrom(1 to 3).withSink(foreachSink).run()
foreachSink.future(mf).onSuccess {
val foreachDrain = ForeachDrain[Int](testActor ! _)
val mf = Source(1 to 3).connect(foreachDrain).run()
foreachDrain.future(mf).onSuccess {
case _ testActor ! "done"
}
expectMsg(1)
@ -28,9 +28,9 @@ class FlowForeachSpec extends AkkaSpec {
}
"complete the future for an empty stream" in {
val foreachSink = ForeachSink[Int](testActor ! _)
val mf = FlowFrom(Nil).withSink(foreachSink).run()
foreachSink.future(mf).onSuccess {
val foreachDrain = ForeachDrain[Int](testActor ! _)
val mf = Source(Nil).connect(foreachDrain).run()
foreachDrain.future(mf).onSuccess {
case _ testActor ! "done"
}
expectMsg("done")
@ -38,9 +38,9 @@ class FlowForeachSpec extends AkkaSpec {
"yield the first error" in {
val p = StreamTestKit.PublisherProbe[Int]()
val foreachSink = ForeachSink[Int](testActor ! _)
val mf = FlowFrom(p).withSink(foreachSink).run()
foreachSink.future(mf).onFailure {
val foreachDrain = ForeachDrain[Int](testActor ! _)
val mf = Source(p).connect(foreachDrain).run()
foreachDrain.future(mf).onFailure {
case ex testActor ! ex
}
val proc = p.expectSubscription

View file

@ -17,7 +17,7 @@ class FlowFromFutureSpec extends AkkaSpec {
"A Flow based on a Future" must {
"produce one element from already successful Future" in {
val p = FlowFrom(Future.successful(1)).toPublisher()
val p = Source(Future.successful(1)).toPublisher()
val c = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c)
val sub = c.expectSubscription()
@ -29,7 +29,7 @@ class FlowFromFutureSpec extends AkkaSpec {
"produce error from already failed Future" in {
val ex = new RuntimeException("test") with NoStackTrace
val p = FlowFrom(Future.failed[Int](ex)).toPublisher()
val p = Source(Future.failed[Int](ex)).toPublisher()
val c = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c)
c.expectError(ex)
@ -37,7 +37,7 @@ class FlowFromFutureSpec extends AkkaSpec {
"produce one element when Future is completed" in {
val promise = Promise[Int]()
val p = FlowFrom(promise.future).toPublisher()
val p = Source(promise.future).toPublisher()
val c = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c)
val sub = c.expectSubscription()
@ -51,7 +51,7 @@ class FlowFromFutureSpec extends AkkaSpec {
"produce one element when Future is completed but not before request" in {
val promise = Promise[Int]()
val p = FlowFrom(promise.future).toPublisher()
val p = Source(promise.future).toPublisher()
val c = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c)
val sub = c.expectSubscription()
@ -64,7 +64,7 @@ class FlowFromFutureSpec extends AkkaSpec {
"produce elements with multiple subscribers" in {
val promise = Promise[Int]()
val p = FlowFrom(promise.future).toPublisher()
val p = Source(promise.future).toPublisher()
val c1 = StreamTestKit.SubscriberProbe[Int]()
val c2 = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c1)
@ -82,7 +82,7 @@ class FlowFromFutureSpec extends AkkaSpec {
"produce elements to later subscriber" in {
val promise = Promise[Int]()
val p = FlowFrom(promise.future).toPublisher()
val p = Source(promise.future).toPublisher()
val keepAlive = StreamTestKit.SubscriberProbe[Int]()
val c1 = StreamTestKit.SubscriberProbe[Int]()
val c2 = StreamTestKit.SubscriberProbe[Int]()
@ -103,7 +103,7 @@ class FlowFromFutureSpec extends AkkaSpec {
"allow cancel before receiving element" in {
val promise = Promise[Int]()
val p = FlowFrom(promise.future).toPublisher()
val p = Source(promise.future).toPublisher()
val keepAlive = StreamTestKit.SubscriberProbe[Int]()
val c = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(keepAlive)

View file

@ -25,17 +25,17 @@ class FlowGraphCompileSpec extends AkkaSpec {
}
}
val f1 = FlowFrom[String].transform("f1", op[String, String])
val f2 = FlowFrom[String].transform("f2", op[String, String])
val f3 = FlowFrom[String].transform("f3", op[String, String])
val f4 = FlowFrom[String].transform("f4", op[String, String])
val f5 = FlowFrom[String].transform("f5", op[String, String])
val f6 = FlowFrom[String].transform("f6", op[String, String])
val f1 = Flow[String].transform("f1", op[String, String])
val f2 = Flow[String].transform("f2", op[String, String])
val f3 = Flow[String].transform("f3", op[String, String])
val f4 = Flow[String].transform("f4", op[String, String])
val f5 = Flow[String].transform("f5", op[String, String])
val f6 = Flow[String].transform("f6", op[String, String])
val in1 = IterableSource(List("a", "b", "c"))
val in2 = IterableSource(List("d", "e", "f"))
val out1 = PublisherSink[String]
val out2 = FutureSink[String]
val in1 = IterableTap(List("a", "b", "c"))
val in2 = IterableTap(List("d", "e", "f"))
val out1 = PublisherDrain[String]
val out2 = FutureDrain[String]
"FlowGraph" should {
"build simple merge" in {
@ -97,7 +97,7 @@ class FlowGraphCompileSpec extends AkkaSpec {
val merge = Merge[String]
val bcast1 = Broadcast[String]
val bcast2 = Broadcast[String]
val feedbackLoopBuffer = FlowFrom[String].buffer(10, OverflowStrategy.dropBuffer)
val feedbackLoopBuffer = Flow[String].buffer(10, OverflowStrategy.dropBuffer)
b.
addEdge(in1, f1, merge).
addEdge(merge, f2, bcast1).
@ -116,7 +116,7 @@ class FlowGraphCompileSpec extends AkkaSpec {
val merge = Merge[String]
val bcast1 = Broadcast[String]
val bcast2 = Broadcast[String]
val feedbackLoopBuffer = FlowFrom[String].buffer(10, OverflowStrategy.dropBuffer)
val feedbackLoopBuffer = Flow[String].buffer(10, OverflowStrategy.dropBuffer)
import FlowGraphImplicits._
in1 ~> f1 ~> merge ~> f2 ~> bcast1 ~> f3 ~> out1
bcast1 ~> feedbackLoopBuffer ~> bcast2 ~> f5 ~> merge
@ -145,13 +145,13 @@ class FlowGraphCompileSpec extends AkkaSpec {
val m9 = Merge[String]
val m10 = Merge[String]
val m11 = Merge[String]
val in3 = IterableSource(List("b"))
val in5 = IterableSource(List("b"))
val in7 = IterableSource(List("a"))
val out2 = PublisherSink[String]
val out9 = PublisherSink[String]
val out10 = PublisherSink[String]
def f(s: String) = FlowFrom[String].transform(s, op[String, String])
val in3 = IterableTap(List("b"))
val in5 = IterableTap(List("b"))
val in7 = IterableTap(List("a"))
val out2 = PublisherDrain[String]
val out9 = PublisherDrain[String]
val out10 = PublisherDrain[String]
def f(s: String) = Flow[String].transform(s, op[String, String])
import FlowGraphImplicits._
in7 ~> f("a") ~> b7 ~> f("b") ~> m11 ~> f("c") ~> b11 ~> f("d") ~> out2
@ -164,53 +164,53 @@ class FlowGraphCompileSpec extends AkkaSpec {
}.run()
}
"attachSource and attachSink" in {
"attachTap and attachDrain" in {
val mg = FlowGraph { b
val merge = Merge[String]
val undefinedSrc1 = UndefinedSource[String]
val undefinedSrc2 = UndefinedSource[String]
val undefinedSink1 = UndefinedSink[String]
val undefinedSrc1 = UndefinedTap[String]
val undefinedSrc2 = UndefinedTap[String]
val undefinedDrain1 = UndefinedDrain[String]
b.
addEdge(undefinedSrc1, f1, merge).
addEdge(UndefinedSource[String]("src2"), f2, merge).
addEdge(merge, f3, undefinedSink1)
addEdge(UndefinedTap[String]("src2"), f2, merge).
addEdge(merge, f3, undefinedDrain1)
b.attachSource(undefinedSrc1, in1)
b.attachSource(UndefinedSource[String]("src2"), in2)
b.attachSink(undefinedSink1, out1)
b.attachTap(undefinedSrc1, in1)
b.attachTap(UndefinedTap[String]("src2"), in2)
b.attachDrain(undefinedDrain1, out1)
}.run()
out1.publisher(mg) should not be (null)
}
"build partial flow graphs" in {
val undefinedSrc1 = UndefinedSource[String]
val undefinedSrc2 = UndefinedSource[String]
val undefinedSink1 = UndefinedSink[String]
val undefinedSrc1 = UndefinedTap[String]
val undefinedSrc2 = UndefinedTap[String]
val undefinedDrain1 = UndefinedDrain[String]
val bcast = Broadcast[String]
val partial1 = PartialFlowGraph { implicit b
import FlowGraphImplicits._
val merge = Merge[String]
undefinedSrc1 ~> f1 ~> merge ~> f2 ~> bcast ~> f3 ~> undefinedSink1
undefinedSrc1 ~> f1 ~> merge ~> f2 ~> bcast ~> f3 ~> undefinedDrain1
undefinedSrc2 ~> f4 ~> merge
}
partial1.undefinedSources should be(Set(undefinedSrc1, undefinedSrc2))
partial1.undefinedSinks should be(Set(undefinedSink1))
partial1.undefinedTaps should be(Set(undefinedSrc1, undefinedSrc2))
partial1.undefinedDrains should be(Set(undefinedDrain1))
val partial2 = PartialFlowGraph(partial1) { implicit b
import FlowGraphImplicits._
b.attachSource(undefinedSrc1, in1)
b.attachSource(undefinedSrc2, in2)
bcast ~> f5 ~> UndefinedSink[String]("sink2")
b.attachTap(undefinedSrc1, in1)
b.attachTap(undefinedSrc2, in2)
bcast ~> f5 ~> UndefinedDrain[String]("drain2")
}
partial2.undefinedSources should be(Set.empty)
partial2.undefinedSinks should be(Set(undefinedSink1, UndefinedSink[String]("sink2")))
partial2.undefinedTaps should be(Set.empty)
partial2.undefinedDrains should be(Set(undefinedDrain1, UndefinedDrain[String]("drain2")))
FlowGraph(partial2) { implicit b
b.attachSink(undefinedSink1, out1)
b.attachSink(UndefinedSink[String]("sink2"), out2)
b.attachDrain(undefinedDrain1, out1)
b.attachDrain(UndefinedDrain[String]("drain2"), out2)
}.run()
}
@ -225,22 +225,13 @@ class FlowGraphCompileSpec extends AkkaSpec {
}.run()
}
"use FlowWithSource and FlowWithSink" in {
FlowGraph { implicit b
val bcast = Broadcast[String]
import FlowGraphImplicits._
f1.withSource(in1) ~> bcast ~> f2.withSink(out1)
bcast ~> f3.withSink(out2)
}.run()
}
"chain input and output ports" in {
FlowGraph { implicit b
val zip = Zip[Int, String]
val out = PublisherSink[(Int, String)]
val out = PublisherDrain[(Int, String)]
import FlowGraphImplicits._
FlowFrom(List(1, 2, 3)) ~> zip.left ~> out
FlowFrom(List("a", "b", "c")) ~> zip.right
Source(List(1, 2, 3)) ~> zip.left ~> out
Source(List("a", "b", "c")) ~> zip.right
}.run()
}
@ -248,10 +239,10 @@ class FlowGraphCompileSpec extends AkkaSpec {
FlowGraph { implicit b
val zip = Zip[Int, String]
val unzip = Unzip[Int, String]
val out = PublisherSink[(Int, String)]
val out = PublisherDrain[(Int, String)]
import FlowGraphImplicits._
FlowFrom(List(1 -> "a", 2 -> "b", 3 -> "c")) ~> unzip.in
unzip.left ~> FlowFrom[Int].map(_ * 2) ~> zip.left
Source(List(1 -> "a", 2 -> "b", 3 -> "c")) ~> unzip.in
unzip.left ~> Flow[Int].map(_ * 2) ~> zip.left
unzip.right ~> zip.right
zip.out ~> out
}.run()
@ -262,15 +253,15 @@ class FlowGraphCompileSpec extends AkkaSpec {
FlowGraph { implicit b
val zip = Zip[Int, String]
val unzip = Unzip[Int, String]
val wrongOut = PublisherSink[(Int, Int)]
val whatever = PublisherSink[Any]
val wrongOut = PublisherDrain[(Int, Int)]
val whatever = PublisherDrain[Any]
import FlowGraphImplicits._
"FlowFrom(List(1, 2, 3)) ~> zip.left ~> wrongOut" shouldNot compile
"""FlowFrom(List("a", "b", "c")) ~> zip.left""" shouldNot compile
"""FlowFrom(List("a", "b", "c")) ~> zip.out""" shouldNot compile
"Flow(List(1, 2, 3)) ~> zip.left ~> wrongOut" shouldNot compile
"""Flow(List("a", "b", "c")) ~> zip.left""" shouldNot compile
"""Flow(List("a", "b", "c")) ~> zip.out""" shouldNot compile
"zip.left ~> zip.right" shouldNot compile
"FlowFrom(List(1, 2, 3)) ~> zip.left ~> wrongOut" shouldNot compile
"""FlowFrom(List(1 -> "a", 2 -> "b", 3 -> "c")) ~> unzip.in ~> whatever""" shouldNot compile
"Flow(List(1, 2, 3)) ~> zip.left ~> wrongOut" shouldNot compile
"""Flow(List(1 -> "a", 2 -> "b", 3 -> "c")) ~> unzip.in ~> whatever""" shouldNot compile
}
}.getMessage should include("empty")
}
@ -298,50 +289,50 @@ class FlowGraphCompileSpec extends AkkaSpec {
}
"build with variance" in {
val out = SubscriberSink(SubscriberProbe[Fruit]())
val out = SubscriberDrain(SubscriberProbe[Fruit]())
FlowGraph { b
val merge = Merge[Fruit]
b.
addEdge(FlowFrom[Fruit](() Some(new Apple)), merge).
addEdge(FlowFrom[Apple](() Some(new Apple)), merge).
addEdge(merge, FlowFrom[Fruit].map(identity), out)
addEdge(Source[Fruit](() Some(new Apple)), merge).
addEdge(Source[Apple](() Some(new Apple)), merge).
addEdge(merge, Flow[Fruit].map(identity), out)
}
}
"build with implicits and variance" in {
PartialFlowGraph { implicit b
val inA = PublisherSource(PublisherProbe[Fruit]())
val inB = PublisherSource(PublisherProbe[Apple]())
val outA = SubscriberSink(SubscriberProbe[Fruit]())
val outB = SubscriberSink(SubscriberProbe[Fruit]())
val inA = PublisherTap(PublisherProbe[Fruit]())
val inB = PublisherTap(PublisherProbe[Apple]())
val outA = SubscriberDrain(SubscriberProbe[Fruit]())
val outB = SubscriberDrain(SubscriberProbe[Fruit]())
val merge = Merge[Fruit]
val unzip = Unzip[Int, String]
val whatever = PublisherSink[Any]
val whatever = PublisherDrain[Any]
import FlowGraphImplicits._
FlowFrom[Fruit](() Some(new Apple)) ~> merge
FlowFrom[Apple](() Some(new Apple)) ~> merge
Source[Fruit](() Some(new Apple)) ~> merge
Source[Apple](() Some(new Apple)) ~> merge
inA ~> merge
inB ~> merge
inA ~> FlowFrom[Fruit].map(identity) ~> merge
inB ~> FlowFrom[Apple].map(identity) ~> merge
UndefinedSource[Apple] ~> merge
UndefinedSource[Apple] ~> FlowFrom[Fruit].map(identity) ~> merge
UndefinedSource[Apple] ~> FlowFrom[Apple].map(identity) ~> merge
merge ~> FlowFrom[Fruit].map(identity) ~> outA
inA ~> Flow[Fruit].map(identity) ~> merge
inB ~> Flow[Apple].map(identity) ~> merge
UndefinedTap[Apple] ~> merge
UndefinedTap[Apple] ~> Flow[Fruit].map(identity) ~> merge
UndefinedTap[Apple] ~> Flow[Apple].map(identity) ~> merge
merge ~> Flow[Fruit].map(identity) ~> outA
FlowFrom[Apple](() Some(new Apple)) ~> Broadcast[Apple] ~> merge
FlowFrom[Apple](() Some(new Apple)) ~> Broadcast[Apple] ~> outB
FlowFrom[Apple](() Some(new Apple)) ~> Broadcast[Apple] ~> UndefinedSink[Fruit]
Source[Apple](() Some(new Apple)) ~> Broadcast[Apple] ~> merge
Source[Apple](() Some(new Apple)) ~> Broadcast[Apple] ~> outB
Source[Apple](() Some(new Apple)) ~> Broadcast[Apple] ~> UndefinedDrain[Fruit]
inB ~> Broadcast[Apple] ~> merge
FlowFrom(List(1 -> "a", 2 -> "b", 3 -> "c")) ~> unzip.in
Source(List(1 -> "a", 2 -> "b", 3 -> "c")) ~> unzip.in
unzip.right ~> whatever
unzip.left ~> UndefinedSink[Any]
unzip.left ~> UndefinedDrain[Any]
"UndefinedSource[Fruit] ~> FlowFrom[Apple].map(identity) ~> merge" shouldNot compile
"UndefinedSource[Fruit] ~> Broadcast[Apple]" shouldNot compile
"UndefinedTap[Fruit] ~> Flow[Apple].map(identity) ~> merge" shouldNot compile
"UndefinedTap[Fruit] ~> Broadcast[Apple]" shouldNot compile
"merge ~> Broadcast[Apple]" shouldNot compile
"merge ~> FlowFrom[Fruit].map(identity) ~> Broadcast[Apple]" shouldNot compile
"merge ~> Flow[Fruit].map(identity) ~> Broadcast[Apple]" shouldNot compile
"inB ~> merge ~> Broadcast[Apple]" shouldNot compile
"inA ~> Broadcast[Apple]" shouldNot compile
}

View file

@ -32,19 +32,19 @@ class FlowGroupBySpec extends AkkaSpec {
}
class SubstreamsSupport(groupCount: Int = 2, elementCount: Int = 6) {
val source = FlowFrom((1 to elementCount).iterator).toPublisher()
val groupStream = FlowFrom(source).groupBy(_ % groupCount).toPublisher()
val masterSubscriber = StreamTestKit.SubscriberProbe[(Int, FlowWithSource[Int, Int])]()
val tap = Source((1 to elementCount).iterator).toPublisher()
val groupStream = Source(tap).groupBy(_ % groupCount).toPublisher()
val masterSubscriber = StreamTestKit.SubscriberProbe[(Int, Source[Int])]()
groupStream.subscribe(masterSubscriber)
val masterSubscription = masterSubscriber.expectSubscription()
def getSubFlow(expectedKey: Int): FlowWithSource[Int, Int] = {
def getSubFlow(expectedKey: Int): Source[Int] = {
masterSubscription.request(1)
expectSubFlow(expectedKey: Int)
}
def expectSubFlow(expectedKey: Int): FlowWithSource[Int, Int] = {
def expectSubFlow(expectedKey: Int): Source[Int] = {
val (key, substream) = masterSubscriber.expectNext()
key should be(expectedKey)
substream
@ -110,8 +110,8 @@ class FlowGroupBySpec extends AkkaSpec {
"accept cancellation of master stream when not consumed anything" in {
val publisherProbeProbe = StreamTestKit.PublisherProbe[Int]()
val publisher = FlowFrom(publisherProbeProbe).groupBy(_ % 2).toPublisher()
val subscriber = StreamTestKit.SubscriberProbe[(Int, FlowWithSource[Int, Int])]()
val publisher = Source(publisherProbeProbe).groupBy(_ % 2).toPublisher()
val subscriber = StreamTestKit.SubscriberProbe[(Int, Source[Int])]()
publisher.subscribe(subscriber)
val upstreamSubscription = publisherProbeProbe.expectSubscription()
@ -141,8 +141,8 @@ class FlowGroupBySpec extends AkkaSpec {
}
"work with empty input stream" in {
val publisher = FlowFrom(List.empty[Int]).groupBy(_ % 2).toPublisher()
val subscriber = StreamTestKit.SubscriberProbe[(Int, FlowWithSource[Int, Int])]()
val publisher = Source(List.empty[Int]).groupBy(_ % 2).toPublisher()
val subscriber = StreamTestKit.SubscriberProbe[(Int, Source[Int])]()
publisher.subscribe(subscriber)
subscriber.expectCompletedOrSubscriptionFollowedByComplete()
@ -150,8 +150,8 @@ class FlowGroupBySpec extends AkkaSpec {
"abort on onError from upstream" in {
val publisherProbeProbe = StreamTestKit.PublisherProbe[Int]()
val publisher = FlowFrom(publisherProbeProbe).groupBy(_ % 2).toPublisher()
val subscriber = StreamTestKit.SubscriberProbe[(Int, FlowWithSource[Int, Int])]()
val publisher = Source(publisherProbeProbe).groupBy(_ % 2).toPublisher()
val subscriber = StreamTestKit.SubscriberProbe[(Int, Source[Int])]()
publisher.subscribe(subscriber)
val upstreamSubscription = publisherProbeProbe.expectSubscription()
@ -167,8 +167,8 @@ class FlowGroupBySpec extends AkkaSpec {
"abort on onError from upstream when substreams are running" in {
val publisherProbeProbe = StreamTestKit.PublisherProbe[Int]()
val publisher = FlowFrom(publisherProbeProbe).groupBy(_ % 2).toPublisher()
val subscriber = StreamTestKit.SubscriberProbe[(Int, FlowWithSource[Int, Int])]()
val publisher = Source(publisherProbeProbe).groupBy(_ % 2).toPublisher()
val subscriber = StreamTestKit.SubscriberProbe[(Int, Source[Int])]()
publisher.subscribe(subscriber)
val upstreamSubscription = publisherProbeProbe.expectSubscription()

View file

@ -24,7 +24,7 @@ class FlowGroupedWithinSpec extends AkkaSpec with ScriptedTest {
val input = Iterator.from(1)
val p = StreamTestKit.PublisherProbe[Int]()
val c = StreamTestKit.SubscriberProbe[immutable.Seq[Int]]()
FlowFrom(p).groupedWithin(1000, 1.second).publishTo(c)
Source(p).groupedWithin(1000, 1.second).publishTo(c)
val pSub = p.expectSubscription
val cSub = c.expectSubscription
cSub.request(100)
@ -49,7 +49,7 @@ class FlowGroupedWithinSpec extends AkkaSpec with ScriptedTest {
"deliver bufferd elements onComplete before the timeout" in {
val c = StreamTestKit.SubscriberProbe[immutable.Seq[Int]]()
FlowFrom(1 to 3).groupedWithin(1000, 10.second).publishTo(c)
Source(1 to 3).groupedWithin(1000, 10.second).publishTo(c)
val cSub = c.expectSubscription
cSub.request(100)
c.expectNext((1 to 3).toList)
@ -61,7 +61,7 @@ class FlowGroupedWithinSpec extends AkkaSpec with ScriptedTest {
val input = Iterator.from(1)
val p = StreamTestKit.PublisherProbe[Int]()
val c = StreamTestKit.SubscriberProbe[immutable.Seq[Int]]()
FlowFrom(p).groupedWithin(1000, 1.second).publishTo(c)
Source(p).groupedWithin(1000, 1.second).publishTo(c)
val pSub = p.expectSubscription
val cSub = c.expectSubscription
cSub.request(1)
@ -81,7 +81,7 @@ class FlowGroupedWithinSpec extends AkkaSpec with ScriptedTest {
"drop empty groups" in {
val p = StreamTestKit.PublisherProbe[Int]()
val c = StreamTestKit.SubscriberProbe[immutable.Seq[Int]]()
FlowFrom(p).groupedWithin(1000, 500.millis).publishTo(c)
Source(p).groupedWithin(1000, 500.millis).publishTo(c)
val pSub = p.expectSubscription
val cSub = c.expectSubscription
cSub.request(2)
@ -103,7 +103,7 @@ class FlowGroupedWithinSpec extends AkkaSpec with ScriptedTest {
val input = Iterator.from(1)
val p = StreamTestKit.PublisherProbe[Int]()
val c = StreamTestKit.SubscriberProbe[immutable.Seq[Int]]()
FlowFrom(p).groupedWithin(3, 2.second).publishTo(c)
Source(p).groupedWithin(3, 2.second).publishTo(c)
val pSub = p.expectSubscription
val cSub = c.expectSubscription
cSub.request(4)

View file

@ -18,7 +18,7 @@ class FlowIterableSpec extends AkkaSpec {
"A Flow based on an iterable" must {
"produce elements" in {
val p = FlowFrom(List(1, 2, 3)).toPublisher()
val p = Source(List(1, 2, 3)).toPublisher()
val c = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c)
val sub = c.expectSubscription()
@ -32,7 +32,7 @@ class FlowIterableSpec extends AkkaSpec {
}
"complete empty" in {
val p = FlowFrom(List.empty[Int]).toPublisher()
val p = Source(List.empty[Int]).toPublisher()
val c = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c)
c.expectComplete()
@ -44,7 +44,7 @@ class FlowIterableSpec extends AkkaSpec {
}
"produce elements with multiple subscribers" in {
val p = FlowFrom(List(1, 2, 3)).toPublisher()
val p = Source(List(1, 2, 3)).toPublisher()
val c1 = StreamTestKit.SubscriberProbe[Int]()
val c2 = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c1)
@ -68,7 +68,7 @@ class FlowIterableSpec extends AkkaSpec {
}
"produce elements to later subscriber" in {
val p = FlowFrom(List(1, 2, 3)).toPublisher()
val p = Source(List(1, 2, 3)).toPublisher()
val c1 = StreamTestKit.SubscriberProbe[Int]()
val c2 = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c1)
@ -94,7 +94,7 @@ class FlowIterableSpec extends AkkaSpec {
}
"produce elements with one transformation step" in {
val p = FlowFrom(List(1, 2, 3)).map(_ * 2).toPublisher()
val p = Source(List(1, 2, 3)).map(_ * 2).toPublisher()
val c = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c)
val sub = c.expectSubscription()
@ -106,7 +106,7 @@ class FlowIterableSpec extends AkkaSpec {
}
"produce elements with two transformation steps" in {
val p = FlowFrom(List(1, 2, 3, 4)).filter(_ % 2 == 0).map(_ * 2).toPublisher()
val p = Source(List(1, 2, 3, 4)).filter(_ % 2 == 0).map(_ * 2).toPublisher()
val c = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c)
val sub = c.expectSubscription()
@ -118,7 +118,7 @@ class FlowIterableSpec extends AkkaSpec {
"allow cancel before receiving all elements" in {
val count = 100000
val p = FlowFrom(1 to count).toPublisher()
val p = Source(1 to count).toPublisher()
val c = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c)
val sub = c.expectSubscription()
@ -134,19 +134,19 @@ class FlowIterableSpec extends AkkaSpec {
}
"have value equality of publisher" in {
val p1 = FlowFrom(List(1, 2, 3)).toPublisher()
val p2 = FlowFrom(List(1, 2, 3)).toPublisher()
val p1 = Source(List(1, 2, 3)).toPublisher()
val p2 = Source(List(1, 2, 3)).toPublisher()
p1 should be(p2)
p2 should be(p1)
val p3 = FlowFrom(List(1, 2, 3, 4)).toPublisher()
val p3 = Source(List(1, 2, 3, 4)).toPublisher()
p1 should not be (p3)
p3 should not be (p1)
val p4 = FlowFrom(Vector.empty[String]).toPublisher()
val p5 = FlowFrom(Set.empty[String]).toPublisher()
val p4 = Source(Vector.empty[String]).toPublisher()
val p5 = Source(Set.empty[String]).toPublisher()
p1 should not be (p4)
p4 should be(p5)
p5 should be(p4)
val p6 = FlowFrom(List(1, 2, 3).iterator).toPublisher()
val p6 = Source(List(1, 2, 3).iterator).toPublisher()
p1 should not be (p6)
p6 should not be (p1)
}

View file

@ -22,7 +22,7 @@ class FlowIteratorSpec extends AkkaSpec {
"A Flow based on an iterator" must {
"produce elements" in {
val p = FlowFrom(List(1, 2, 3).iterator).toPublisher()
val p = Source(List(1, 2, 3).iterator).toPublisher()
val c = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c)
val sub = c.expectSubscription()
@ -36,7 +36,7 @@ class FlowIteratorSpec extends AkkaSpec {
}
"complete empty" in {
val p = FlowFrom(List.empty[Int].iterator).toPublisher()
val p = Source(List.empty[Int].iterator).toPublisher()
val c = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c)
c.expectComplete()
@ -48,7 +48,7 @@ class FlowIteratorSpec extends AkkaSpec {
}
"produce elements with multiple subscribers" in {
val p = FlowFrom(List(1, 2, 3).iterator).toPublisher()
val p = Source(List(1, 2, 3).iterator).toPublisher()
val c1 = StreamTestKit.SubscriberProbe[Int]()
val c2 = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c1)
@ -72,7 +72,7 @@ class FlowIteratorSpec extends AkkaSpec {
}
"produce elements to later subscriber" in {
val p = FlowFrom(List(1, 2, 3).iterator).toPublisher()
val p = Source(List(1, 2, 3).iterator).toPublisher()
val c1 = StreamTestKit.SubscriberProbe[Int]()
val c2 = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c1)
@ -95,7 +95,7 @@ class FlowIteratorSpec extends AkkaSpec {
}
"produce elements with one transformation step" in {
val p = FlowFrom(List(1, 2, 3).iterator).map(_ * 2).toPublisher()
val p = Source(List(1, 2, 3).iterator).map(_ * 2).toPublisher()
val c = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c)
val sub = c.expectSubscription()
@ -107,7 +107,7 @@ class FlowIteratorSpec extends AkkaSpec {
}
"produce elements with two transformation steps" in {
val p = FlowFrom(List(1, 2, 3, 4).iterator).filter(_ % 2 == 0).map(_ * 2).toPublisher()
val p = Source(List(1, 2, 3, 4).iterator).filter(_ % 2 == 0).map(_ * 2).toPublisher()
val c = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c)
val sub = c.expectSubscription()
@ -119,7 +119,7 @@ class FlowIteratorSpec extends AkkaSpec {
"allow cancel before receiving all elements" in {
val count = 100000
val p = FlowFrom((1 to count).iterator).toPublisher()
val p = Source((1 to count).iterator).toPublisher()
val c = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c)
val sub = c.expectSubscription()

View file

@ -23,7 +23,7 @@ class FlowMapAsyncSpec extends AkkaSpec {
"produce future elements" in {
val c = StreamTestKit.SubscriberProbe[Int]()
implicit val ec = system.dispatcher
val p = FlowFrom(1 to 3).mapAsync(n Future(n)).publishTo(c)
val p = Source(1 to 3).mapAsync(n Future(n)).publishTo(c)
val sub = c.expectSubscription()
sub.request(2)
c.expectNext(1)
@ -37,7 +37,7 @@ class FlowMapAsyncSpec extends AkkaSpec {
"produce future elements in order" in {
val c = StreamTestKit.SubscriberProbe[Int]()
implicit val ec = system.dispatcher
val p = FlowFrom(1 to 50).mapAsync(n Future {
val p = Source(1 to 50).mapAsync(n Future {
Thread.sleep(ThreadLocalRandom.current().nextInt(1, 10))
n
}).publishTo(c)
@ -51,7 +51,7 @@ class FlowMapAsyncSpec extends AkkaSpec {
val probe = TestProbe()
val c = StreamTestKit.SubscriberProbe[Int]()
implicit val ec = system.dispatcher
val p = FlowFrom(1 to 20).mapAsync(n Future {
val p = Source(1 to 20).mapAsync(n Future {
probe.ref ! n
n
}).publishTo(c)
@ -76,7 +76,7 @@ class FlowMapAsyncSpec extends AkkaSpec {
val latch = TestLatch(1)
val c = StreamTestKit.SubscriberProbe[Int]()
implicit val ec = system.dispatcher
val p = FlowFrom(1 to 5).mapAsync(n Future {
val p = Source(1 to 5).mapAsync(n Future {
if (n == 3) throw new RuntimeException("err1") with NoStackTrace
else {
Await.ready(latch, 10.seconds)
@ -93,7 +93,7 @@ class FlowMapAsyncSpec extends AkkaSpec {
val latch = TestLatch(1)
val c = StreamTestKit.SubscriberProbe[Int]()
implicit val ec = system.dispatcher
val p = FlowFrom(1 to 5).mapAsync(n
val p = Source(1 to 5).mapAsync(n
if (n == 3) throw new RuntimeException("err2") with NoStackTrace
else {
Future {

View file

@ -23,7 +23,7 @@ class FlowMapAsyncUnorderedSpec extends AkkaSpec {
val c = StreamTestKit.SubscriberProbe[Int]()
implicit val ec = system.dispatcher
val latch = (1 to 4).map(_ -> TestLatch(1)).toMap
val p = FlowFrom(1 to 4).mapAsyncUnordered(n Future {
val p = Source(1 to 4).mapAsyncUnordered(n Future {
Await.ready(latch(n), 5.seconds)
n
}).publishTo(c)
@ -44,7 +44,7 @@ class FlowMapAsyncUnorderedSpec extends AkkaSpec {
val probe = TestProbe()
val c = StreamTestKit.SubscriberProbe[Int]()
implicit val ec = system.dispatcher
val p = FlowFrom(1 to 20).mapAsyncUnordered(n Future {
val p = Source(1 to 20).mapAsyncUnordered(n Future {
probe.ref ! n
n
}).publishTo(c)
@ -70,7 +70,7 @@ class FlowMapAsyncUnorderedSpec extends AkkaSpec {
val latch = TestLatch(1)
val c = StreamTestKit.SubscriberProbe[Int]()
implicit val ec = system.dispatcher
val p = FlowFrom(1 to 5).mapAsyncUnordered(n Future {
val p = Source(1 to 5).mapAsyncUnordered(n Future {
if (n == 3) throw new RuntimeException("err1") with NoStackTrace
else {
Await.ready(latch, 10.seconds)
@ -87,7 +87,7 @@ class FlowMapAsyncUnorderedSpec extends AkkaSpec {
val latch = TestLatch(1)
val c = StreamTestKit.SubscriberProbe[Int]()
implicit val ec = system.dispatcher
val p = FlowFrom(1 to 5).mapAsync(n
val p = Source(1 to 5).mapAsync(n
if (n == 3) throw new RuntimeException("err2") with NoStackTrace
else {
Future {

View file

@ -26,7 +26,7 @@ class FlowMapSpec extends AkkaSpec with ScriptedTest {
"not blow up with high request counts" in {
val probe = StreamTestKit.SubscriberProbe[Int]()
FlowFrom(List(1).iterator).
Source(List(1).iterator).
map(_ + 1).map(_ + 1).map(_ + 1).map(_ + 1).map(_ + 1).
toPublisher().subscribe(probe)

View file

@ -27,7 +27,7 @@ class FlowOnCompleteSpec extends AkkaSpec with ScriptedTest {
"invoke callback on normal completion" in {
val onCompleteProbe = TestProbe()
val p = StreamTestKit.PublisherProbe[Int]()
FlowFrom(p).withSink(OnCompleteSink(onCompleteProbe.ref ! _)).run()
Source(p).connect(OnCompleteDrain[Int](onCompleteProbe.ref ! _)).run()
val proc = p.expectSubscription
proc.expectRequest()
proc.sendNext(42)
@ -39,7 +39,7 @@ class FlowOnCompleteSpec extends AkkaSpec with ScriptedTest {
"yield the first error" in {
val onCompleteProbe = TestProbe()
val p = StreamTestKit.PublisherProbe[Int]()
FlowFrom(p).withSink(OnCompleteSink(onCompleteProbe.ref ! _)).run()
Source(p).connect(OnCompleteDrain[Int](onCompleteProbe.ref ! _)).run()
val proc = p.expectSubscription
proc.expectRequest()
val ex = new RuntimeException("ex") with NoStackTrace
@ -51,7 +51,7 @@ class FlowOnCompleteSpec extends AkkaSpec with ScriptedTest {
"invoke callback for an empty stream" in {
val onCompleteProbe = TestProbe()
val p = StreamTestKit.PublisherProbe[Int]()
FlowFrom(p).withSink(OnCompleteSink(onCompleteProbe.ref ! _)).run()
Source(p).connect(OnCompleteDrain[Int](onCompleteProbe.ref ! _)).run()
val proc = p.expectSubscription
proc.expectRequest()
proc.sendComplete()
@ -63,14 +63,14 @@ class FlowOnCompleteSpec extends AkkaSpec with ScriptedTest {
val onCompleteProbe = TestProbe()
val p = StreamTestKit.PublisherProbe[Int]()
import system.dispatcher // for the Future.onComplete
val foreachSink = ForeachSink[Int] {
val foreachDrain = ForeachDrain[Int] {
x onCompleteProbe.ref ! ("foreach-" + x)
}
val mf = FlowFrom(p).map { x
val mf = Source(p).map { x
onCompleteProbe.ref ! ("map-" + x)
x
}.withSink(foreachSink).run()
foreachSink.future(mf) onComplete { onCompleteProbe.ref ! _ }
}.connect(foreachDrain).run()
foreachDrain.future(mf) onComplete { onCompleteProbe.ref ! _ }
val proc = p.expectSubscription
proc.expectRequest()
proc.sendNext(42)

View file

@ -25,12 +25,12 @@ class FlowPrefixAndTailSpec extends AkkaSpec {
val testException = new Exception("test") with NoStackTrace
def newFutureSink = FutureSink[(immutable.Seq[Int], FlowWithSource[Int, Int])]
def newFutureDrain = FutureDrain[(immutable.Seq[Int], Source[Int])]
"work on empty input" in {
val futureSink = newFutureSink
val mf = FlowFrom(Nil).prefixAndTail(10).withSink(futureSink).run()
val fut = futureSink.future(mf)
val futureDrain = newFutureDrain
val mf = Source(Nil).prefixAndTail(10).connect(futureDrain).run()
val fut = futureDrain.future(mf)
val (prefix, tailFlow) = Await.result(fut, 3.seconds)
prefix should be(Nil)
val tailSubscriber = SubscriberProbe[Int]
@ -39,9 +39,9 @@ class FlowPrefixAndTailSpec extends AkkaSpec {
}
"work on short input" in {
val futureSink = newFutureSink
val mf = FlowFrom(List(1, 2, 3)).prefixAndTail(10).withSink(futureSink).run()
val fut = futureSink.future(mf)
val futureDrain = newFutureDrain
val mf = Source(List(1, 2, 3)).prefixAndTail(10).connect(futureDrain).run()
val fut = futureDrain.future(mf)
val (prefix, tailFlow) = Await.result(fut, 3.seconds)
prefix should be(List(1, 2, 3))
val tailSubscriber = SubscriberProbe[Int]
@ -50,48 +50,48 @@ class FlowPrefixAndTailSpec extends AkkaSpec {
}
"work on longer inputs" in {
val futureSink = newFutureSink
val mf = FlowFrom((1 to 10).iterator).prefixAndTail(5).withSink(futureSink).run()
val fut = futureSink.future(mf)
val futureDrain = newFutureDrain
val mf = Source((1 to 10).iterator).prefixAndTail(5).connect(futureDrain).run()
val fut = futureDrain.future(mf)
val (takes, tail) = Await.result(fut, 3.seconds)
takes should be(1 to 5)
val futureSink2 = FutureSink[immutable.Seq[Int]]
val mf2 = tail.grouped(6).withSink(futureSink2).run()
val fut2 = futureSink2.future(mf2)
val futureDrain2 = FutureDrain[immutable.Seq[Int]]
val mf2 = tail.grouped(6).connect(futureDrain2).run()
val fut2 = futureDrain2.future(mf2)
Await.result(fut2, 3.seconds) should be(6 to 10)
}
"handle zero take count" in {
val futureSink = newFutureSink
val mf = FlowFrom((1 to 10).iterator).prefixAndTail(0).withSink(futureSink).run()
val fut = futureSink.future(mf)
val futureDrain = newFutureDrain
val mf = Source((1 to 10).iterator).prefixAndTail(0).connect(futureDrain).run()
val fut = futureDrain.future(mf)
val (takes, tail) = Await.result(fut, 3.seconds)
takes should be(Nil)
val futureSink2 = FutureSink[immutable.Seq[Int]]
val mf2 = tail.grouped(11).withSink(futureSink2).run()
val fut2 = futureSink2.future(mf2)
val futureDrain2 = FutureDrain[immutable.Seq[Int]]
val mf2 = tail.grouped(11).connect(futureDrain2).run()
val fut2 = futureDrain2.future(mf2)
Await.result(fut2, 3.seconds) should be(1 to 10)
}
"handle negative take count" in {
val futureSink = newFutureSink
val mf = FlowFrom((1 to 10).iterator).prefixAndTail(-1).withSink(futureSink).run()
val fut = futureSink.future(mf)
val futureDrain = newFutureDrain
val mf = Source((1 to 10).iterator).prefixAndTail(-1).connect(futureDrain).run()
val fut = futureDrain.future(mf)
val (takes, tail) = Await.result(fut, 3.seconds)
takes should be(Nil)
val futureSink2 = FutureSink[immutable.Seq[Int]]
val mf2 = tail.grouped(11).withSink(futureSink2).run()
val fut2 = futureSink2.future(mf2)
val futureDrain2 = FutureDrain[immutable.Seq[Int]]
val mf2 = tail.grouped(11).connect(futureDrain2).run()
val fut2 = futureDrain2.future(mf2)
Await.result(fut2, 3.seconds) should be(1 to 10)
}
"work if size of take is equal to stream size" in {
val futureSink = newFutureSink
val mf = FlowFrom((1 to 10).iterator).prefixAndTail(10).withSink(futureSink).run()
val fut = futureSink.future(mf)
val futureDrain = newFutureDrain
val mf = Source((1 to 10).iterator).prefixAndTail(10).connect(futureDrain).run()
val fut = futureDrain.future(mf)
val (takes, tail) = Await.result(fut, 3.seconds)
takes should be(1 to 10)
@ -102,9 +102,9 @@ class FlowPrefixAndTailSpec extends AkkaSpec {
"handle onError when no substream open" in {
val publisher = StreamTestKit.PublisherProbe[Int]()
val subscriber = StreamTestKit.SubscriberProbe[(immutable.Seq[Int], FlowWithSource[Int, Int])]()
val subscriber = StreamTestKit.SubscriberProbe[(immutable.Seq[Int], Source[Int])]()
FlowFrom(publisher).prefixAndTail(3).publishTo(subscriber)
Source(publisher).prefixAndTail(3).publishTo(subscriber)
val upstream = publisher.expectSubscription()
val downstream = subscriber.expectSubscription()
@ -120,9 +120,9 @@ class FlowPrefixAndTailSpec extends AkkaSpec {
"handle onError when substream is open" in {
val publisher = StreamTestKit.PublisherProbe[Int]()
val subscriber = StreamTestKit.SubscriberProbe[(immutable.Seq[Int], FlowWithSource[Int, Int])]()
val subscriber = StreamTestKit.SubscriberProbe[(immutable.Seq[Int], Source[Int])]()
FlowFrom(publisher).prefixAndTail(1).publishTo(subscriber)
Source(publisher).prefixAndTail(1).publishTo(subscriber)
val upstream = publisher.expectSubscription()
val downstream = subscriber.expectSubscription()
@ -147,9 +147,9 @@ class FlowPrefixAndTailSpec extends AkkaSpec {
"handle master stream cancellation" in {
val publisher = StreamTestKit.PublisherProbe[Int]()
val subscriber = StreamTestKit.SubscriberProbe[(immutable.Seq[Int], FlowWithSource[Int, Int])]()
val subscriber = StreamTestKit.SubscriberProbe[(immutable.Seq[Int], Source[Int])]()
FlowFrom(publisher).prefixAndTail(3).publishTo(subscriber)
Source(publisher).prefixAndTail(3).publishTo(subscriber)
val upstream = publisher.expectSubscription()
val downstream = subscriber.expectSubscription()
@ -165,9 +165,9 @@ class FlowPrefixAndTailSpec extends AkkaSpec {
"handle substream cancellation" in {
val publisher = StreamTestKit.PublisherProbe[Int]()
val subscriber = StreamTestKit.SubscriberProbe[(immutable.Seq[Int], FlowWithSource[Int, Int])]()
val subscriber = StreamTestKit.SubscriberProbe[(immutable.Seq[Int], Source[Int])]()
FlowFrom(publisher).prefixAndTail(1).publishTo(subscriber)
Source(publisher).prefixAndTail(1).publishTo(subscriber)
val upstream = publisher.expectSubscription()
val downstream = subscriber.expectSubscription()

View file

@ -1,46 +0,0 @@
/**
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.stream.scaladsl2
import akka.stream.MaterializerSettings
import akka.stream.testkit.AkkaSpec
class FlowPrependSpec extends AkkaSpec with River {
val settings = MaterializerSettings(system)
implicit val materializer = FlowMaterializer(settings)
"ProcessorFlow" should {
"prepend ProcessorFlow" in riverOf[String] { subscriber
FlowFrom[String]
.prepend(otherFlow)
.withSource(IterableSource(elements))
.publishTo(subscriber)
}
"prepend FlowWithSource" in riverOf[String] { subscriber
FlowFrom[String]
.prepend(otherFlow.withSource(IterableSource(elements)))
.publishTo(subscriber)
}
}
"FlowWithSink" should {
"prepend ProcessorFlow" in riverOf[String] { subscriber
FlowFrom[String]
.withSink(SubscriberSink(subscriber))
.prepend(otherFlow)
.withSource(IterableSource(elements))
.run()
}
"prepend FlowWithSource" in riverOf[String] { subscriber
FlowFrom[String]
.withSink(SubscriberSink(subscriber))
.prepend(otherFlow.withSource(IterableSource(elements)))
.run()
}
}
}

View file

@ -15,11 +15,11 @@ class FlowPublishToSubscriberSpec extends AkkaSpec {
implicit val materializer = FlowMaterializer(settings)
"A Flow with SubscriberSink" must {
"A Flow with SubscriberDrain" must {
"publish elements to the subscriber" in {
val c = StreamTestKit.SubscriberProbe[Int]()
FlowFrom(List(1, 2, 3)).withSink(SubscriberSink(c)).run()
Source(List(1, 2, 3)).connect(SubscriberDrain(c)).run()
val s = c.expectSubscription()
s.request(3)
c.expectNext(1)

View file

@ -79,19 +79,19 @@ class FlowSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.debug.rece
implicit val mat = FlowMaterializer(settings)
val identity: ProcessorFlow[Any, Any] ProcessorFlow[Any, Any] = in in.map(e e)
val identity2: ProcessorFlow[Any, Any] ProcessorFlow[Any, Any] = in identity(in)
val identity: Flow[Any, Any] Flow[Any, Any] = in in.map(e e)
val identity2: Flow[Any, Any] Flow[Any, Any] = in identity(in)
val toPublisher: (FlowWithSource[Any, Any], FlowMaterializer) Publisher[Any] =
val toPublisher: (Source[Any], FlowMaterializer) Publisher[Any] =
(f, m) f.toPublisher()(m)
def toFanoutPublisher[In, Out](initialBufferSize: Int, maximumBufferSize: Int): (FlowWithSource[In, Out], FlowMaterializer) Publisher[Out] =
def toFanoutPublisher[In, Out](initialBufferSize: Int, maximumBufferSize: Int): (Source[Out], FlowMaterializer) Publisher[Out] =
(f, m) f.toFanoutPublisher(initialBufferSize, maximumBufferSize)(m)
def materializeIntoSubscriberAndPublisher[In, Out](processorFlow: ProcessorFlow[In, Out]): (Subscriber[In], Publisher[Out]) = {
val source = SubscriberSource[In]
val sink = PublisherSink[Out]
val mf = processorFlow.withSource(source).withSink(sink).run()
(source.subscriber(mf), sink.publisher(mf))
def materializeIntoSubscriberAndPublisher[In, Out](flow: Flow[In, Out]): (Subscriber[In], Publisher[Out]) = {
val tap = SubscriberTap[In]
val drain = PublisherDrain[Out]
val mf = tap.connect(flow).connect(drain).run()
(tap.subscriber(mf), drain.publisher(mf))
}
"A Flow" must {
@ -168,14 +168,14 @@ class FlowSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.debug.rece
}
"materialize into Publisher/Subscriber" in {
val processorFlow = FlowFrom[String]
val (flowIn: Subscriber[String], flowOut: Publisher[String]) = materializeIntoSubscriberAndPublisher(processorFlow)
val flow = Flow[String]
val (flowIn: Subscriber[String], flowOut: Publisher[String]) = materializeIntoSubscriberAndPublisher(flow)
val c1 = StreamTestKit.SubscriberProbe[String]()
flowOut.subscribe(c1)
val source: Publisher[String] = FlowFrom(List("1", "2", "3")).toPublisher()
source.subscribe(flowIn)
val tap: Publisher[String] = Source(List("1", "2", "3")).toPublisher()
tap.subscribe(flowIn)
val sub1 = c1.expectSubscription
sub1.request(3)
@ -186,8 +186,8 @@ class FlowSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.debug.rece
}
"materialize into Publisher/Subscriber and transformation processor" in {
val processorFlow = FlowFrom[Int].map((i: Int) i.toString)
val (flowIn: Subscriber[Int], flowOut: Publisher[String]) = materializeIntoSubscriberAndPublisher(processorFlow)
val flow = Flow[Int].map((i: Int) i.toString)
val (flowIn: Subscriber[Int], flowOut: Publisher[String]) = materializeIntoSubscriberAndPublisher(flow)
val c1 = StreamTestKit.SubscriberProbe[String]()
flowOut.subscribe(c1)
@ -195,8 +195,8 @@ class FlowSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.debug.rece
sub1.request(3)
c1.expectNoMsg(200.millis)
val source: Publisher[Int] = FlowFrom(List(1, 2, 3)).toPublisher()
source.subscribe(flowIn)
val tap: Publisher[Int] = Source(List(1, 2, 3)).toPublisher()
tap.subscribe(flowIn)
c1.expectNext("1")
c1.expectNext("2")
@ -205,8 +205,8 @@ class FlowSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.debug.rece
}
"materialize into Publisher/Subscriber and multiple transformation processors" in {
val processorFlow = FlowFrom[Int].map(_.toString).map("elem-" + _)
val (flowIn, flowOut) = materializeIntoSubscriberAndPublisher(processorFlow)
val flow = Flow[Int].map(_.toString).map("elem-" + _)
val (flowIn, flowOut) = materializeIntoSubscriberAndPublisher(flow)
val c1 = StreamTestKit.SubscriberProbe[String]()
flowOut.subscribe(c1)
@ -214,8 +214,8 @@ class FlowSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.debug.rece
sub1.request(3)
c1.expectNoMsg(200.millis)
val source: Publisher[Int] = FlowFrom(List(1, 2, 3)).toPublisher()
source.subscribe(flowIn)
val tap: Publisher[Int] = Source(List(1, 2, 3)).toPublisher()
tap.subscribe(flowIn)
c1.expectNext("elem-1")
c1.expectNext("elem-2")
@ -224,11 +224,11 @@ class FlowSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.debug.rece
}
"subscribe Subscriber" in {
val processorFlow: ProcessorFlow[String, String] = FlowFrom[String]
val flow: Flow[String, String] = Flow[String]
val c1 = StreamTestKit.SubscriberProbe[String]()
val flow: FlowWithSink[String, String] = processorFlow.withSink(SubscriberSink(c1))
val source: Publisher[String] = FlowFrom(List("1", "2", "3")).toPublisher()
flow.withSource(PublisherSource(source)).run()
val sink: Sink[String] = flow.connect(SubscriberDrain(c1))
val publisher: Publisher[String] = Source(List("1", "2", "3")).toPublisher()
Source(publisher).connect(sink).run()
val sub1 = c1.expectSubscription
sub1.request(3)
@ -239,10 +239,10 @@ class FlowSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.debug.rece
}
"perform transformation operation" in {
val processorFlow = FlowFrom[Int].map(i { testActor ! i.toString; i.toString })
val flow = Flow[Int].map(i { testActor ! i.toString; i.toString })
val source = FlowFrom(List(1, 2, 3)).toPublisher()
processorFlow.withSource(PublisherSource(source)).consume()
val publisher = Source(List(1, 2, 3)).toPublisher()
Source(publisher).connect(flow).consume()
expectMsg("1")
expectMsg("2")
@ -250,11 +250,11 @@ class FlowSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.debug.rece
}
"perform transformation operation and subscribe Subscriber" in {
val processorFlow = FlowFrom[Int].map(_.toString)
val flow = Flow[Int].map(_.toString)
val c1 = StreamTestKit.SubscriberProbe[String]()
val flow: FlowWithSink[Int, String] = processorFlow.withSink(SubscriberSink(c1))
val source: Publisher[Int] = FlowFrom(List(1, 2, 3)).toPublisher()
flow.withSource(PublisherSource(source)).run()
val sink: Sink[Int] = flow.connect(SubscriberDrain(c1))
val publisher: Publisher[Int] = Source(List(1, 2, 3)).toPublisher()
Source(publisher).connect(sink).run()
val sub1 = c1.expectSubscription
sub1.request(3)
@ -265,7 +265,7 @@ class FlowSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.debug.rece
}
"be materializable several times with fanout publisher" in {
val flow = FlowFrom(List(1, 2, 3)).map(_.toString)
val flow = Source(List(1, 2, 3)).map(_.toString)
val p1 = flow.toFanoutPublisher(2, 2)
val p2 = flow.toFanoutPublisher(2, 2)
val s1 = StreamTestKit.SubscriberProbe[String]()
@ -298,14 +298,14 @@ class FlowSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.debug.rece
}
"be covariant" in {
val f1: FlowWithSource[Fruit, Fruit] = FlowFrom[Fruit](() Some(new Apple))
val p1: Publisher[Fruit] = FlowFrom[Fruit](() Some(new Apple)).toPublisher()
val f2: FlowWithSource[Fruit, FlowWithSource[Fruit, Fruit]] = FlowFrom[Fruit](() Some(new Apple)).splitWhen(_ true)
val f3: FlowWithSource[Fruit, (Boolean, FlowWithSource[Fruit, Fruit])] = FlowFrom[Fruit](() Some(new Apple)).groupBy(_ true)
val f4: FlowWithSource[Fruit, (immutable.Seq[Fruit], FlowWithSource[Fruit, Fruit])] = FlowFrom[Fruit](() Some(new Apple)).prefixAndTail(1)
val d1: ProcessorFlow[String, FlowWithSource[Fruit, Fruit]] = FlowFrom[String].map(_ new Apple).splitWhen(_ true)
val d2: ProcessorFlow[String, (Boolean, FlowWithSource[Fruit, Fruit])] = FlowFrom[String].map(_ new Apple).groupBy(_ true)
val d3: ProcessorFlow[String, (immutable.Seq[Apple], FlowWithSource[Fruit, Fruit])] = FlowFrom[String].map(_ new Apple).prefixAndTail(1)
val f1: Source[Fruit] = Source[Fruit](() Some(new Apple))
val p1: Publisher[Fruit] = Source[Fruit](() Some(new Apple)).toPublisher()
val f2: Source[Source[Fruit]] = Source[Fruit](() Some(new Apple)).splitWhen(_ true)
val f3: Source[(Boolean, Source[Fruit])] = Source[Fruit](() Some(new Apple)).groupBy(_ true)
val f4: Source[(immutable.Seq[Fruit], Source[Fruit])] = Source[Fruit](() Some(new Apple)).prefixAndTail(1)
val d1: Flow[String, Source[Fruit]] = Flow[String].map(_ new Apple).splitWhen(_ true)
val d2: Flow[String, (Boolean, Source[Fruit])] = Flow[String].map(_ new Apple).groupBy(_ true)
val d3: Flow[String, (immutable.Seq[Apple], Source[Fruit])] = Flow[String].map(_ new Apple).prefixAndTail(1)
}
}

View file

@ -31,19 +31,19 @@ class FlowSplitWhenSpec extends AkkaSpec {
}
class SubstreamsSupport(splitWhen: Int = 3, elementCount: Int = 6) {
val source = FlowFrom((1 to elementCount).iterator)
val groupStream = source.splitWhen(_ == splitWhen).toPublisher()
val masterSubscriber = StreamTestKit.SubscriberProbe[FlowWithSource[Int, Int]]()
val tap = Source((1 to elementCount).iterator)
val groupStream = tap.splitWhen(_ == splitWhen).toPublisher()
val masterSubscriber = StreamTestKit.SubscriberProbe[Source[Int]]()
groupStream.subscribe(masterSubscriber)
val masterSubscription = masterSubscriber.expectSubscription()
def getSubFlow(): FlowWithSource[Int, Int] = {
def getSubFlow(): Source[Int] = {
masterSubscription.request(1)
expectSubPublisher()
}
def expectSubPublisher(): FlowWithSource[Int, Int] = {
def expectSubPublisher(): Source[Int] = {
val substream = masterSubscriber.expectNext()
substream
}

View file

@ -34,7 +34,7 @@ class FlowTakeSpec extends AkkaSpec with ScriptedTest {
"not take anything for negative n" in {
val probe = StreamTestKit.SubscriberProbe[Int]()
FlowFrom(List(1, 2, 3)).take(-1).publishTo(probe)
Source(List(1, 2, 3)).take(-1).publishTo(probe)
probe.expectSubscription().request(10)
probe.expectComplete()
}

View file

@ -18,7 +18,7 @@ class FlowTakeWithinSpec extends AkkaSpec {
val input = Iterator.from(1)
val p = StreamTestKit.PublisherProbe[Int]()
val c = StreamTestKit.SubscriberProbe[Int]()
FlowFrom(p).takeWithin(1.second).publishTo(c)
Source(p).takeWithin(1.second).publishTo(c)
val pSub = p.expectSubscription()
val cSub = c.expectSubscription()
cSub.request(100)
@ -38,7 +38,7 @@ class FlowTakeWithinSpec extends AkkaSpec {
"deliver bufferd elements onComplete before the timeout" in {
val c = StreamTestKit.SubscriberProbe[Int]()
FlowFrom(1 to 3).takeWithin(1.second).publishTo(c)
Source(1 to 3).takeWithin(1.second).publishTo(c)
val cSub = c.expectSubscription()
c.expectNoMsg(200.millis)
cSub.request(100)

View file

@ -18,7 +18,7 @@ class FlowThunkSpec extends AkkaSpec {
"produce elements" in {
val iter = List(1, 2, 3).iterator
val p = FlowFrom(() if (iter.hasNext) Some(iter.next()) else None).map(_ + 10).toPublisher()
val p = Source(() if (iter.hasNext) Some(iter.next()) else None).map(_ + 10).toPublisher()
val c = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c)
val sub = c.expectSubscription()
@ -32,7 +32,7 @@ class FlowThunkSpec extends AkkaSpec {
}
"complete empty" in {
val p = FlowFrom(() None).toPublisher()
val p = Source(() None).toPublisher()
val c = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c)
val sub = c.expectSubscription()
@ -44,7 +44,7 @@ class FlowThunkSpec extends AkkaSpec {
"allow cancel before receiving all elements" in {
val count = 100000
val iter = (1 to count).iterator
val p = FlowFrom(() if (iter.hasNext) Some(iter.next()) else None).toPublisher()
val p = Source(() if (iter.hasNext) Some(iter.next()) else None).toPublisher()
val c = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c)
val sub = c.expectSubscription()

View file

@ -16,7 +16,7 @@ class FlowTimerTransformerSpec extends AkkaSpec {
"A Flow with TimerTransformer operations" must {
"produce scheduled ticks as expected" in {
val p = StreamTestKit.PublisherProbe[Int]()
val p2 = FlowFrom(p).
val p2 = Source(p).
timerTransform("timer", () new TimerTransformer[Int, Int] {
schedulePeriodically("tick", 100.millis)
var tickCount = 0
@ -41,7 +41,7 @@ class FlowTimerTransformerSpec extends AkkaSpec {
"schedule ticks when last transformation step (consume)" in {
val p = StreamTestKit.PublisherProbe[Int]()
val p2 = FlowFrom(p).
val p2 = Source(p).
timerTransform("timer", () new TimerTransformer[Int, Int] {
schedulePeriodically("tick", 100.millis)
var tickCount = 0
@ -65,7 +65,7 @@ class FlowTimerTransformerSpec extends AkkaSpec {
"propagate error if onTimer throws an exception" in {
val exception = new Exception("Expected exception to the rule") with NoStackTrace
val p = StreamTestKit.PublisherProbe[Int]()
val p2 = FlowFrom(p).
val p2 = Source(p).
timerTransform("timer", () new TimerTransformer[Int, Int] {
scheduleOnce("tick", 100.millis)

View file

@ -23,8 +23,8 @@ class FlowToFutureSpec extends AkkaSpec with ScriptedTest {
"yield the first value" in {
val p = StreamTestKit.PublisherProbe[Int]()
val f = FutureSink[Int]
val m = FlowFrom(p).withSink(f).run()
val f = FutureDrain[Int]
val m = Source(p).connect(f).run()
val proc = p.expectSubscription
proc.expectRequest()
proc.sendNext(42)
@ -34,9 +34,9 @@ class FlowToFutureSpec extends AkkaSpec with ScriptedTest {
"yield the first value when actively constructing" in {
val p = StreamTestKit.PublisherProbe[Int]()
val f = FutureSink[Int]
val s = SubscriberSource[Int]
val m = FlowFrom[Int].withSource(s).withSink(f).run()
val f = FutureDrain[Int]
val s = SubscriberTap[Int]
val m = s.connect(f).run()
p.subscribe(s.subscriber(m))
val proc = p.expectSubscription
proc.expectRequest()
@ -47,8 +47,8 @@ class FlowToFutureSpec extends AkkaSpec with ScriptedTest {
"yield the first error" in {
val p = StreamTestKit.PublisherProbe[Int]()
val f = FutureSink[Int]
val m = FlowFrom(p).withSink(f).run()
val f = FutureDrain[Int]
val m = Source(p).connect(f).run()
val proc = p.expectSubscription
proc.expectRequest()
val ex = new RuntimeException("ex")
@ -60,8 +60,8 @@ class FlowToFutureSpec extends AkkaSpec with ScriptedTest {
"yield NoSuchElementExcption for empty stream" in {
val p = StreamTestKit.PublisherProbe[Int]()
val f = FutureSink[Int]
val m = FlowFrom(p).withSink(f).run()
val f = FutureDrain[Int]
val m = Source(p).connect(f).run()
val proc = p.expectSubscription
proc.expectRequest()
proc.sendComplete()

View file

@ -40,8 +40,8 @@ class FlowTransformRecoverSpec extends AkkaSpec {
"A Flow with transformRecover operations" must {
"produce one-to-one transformation as expected" in {
val p = FlowFrom(List(1, 2, 3).iterator).toPublisher()
val p2 = FlowFrom(p).
val p = Source(List(1, 2, 3).iterator).toPublisher()
val p2 = Source(p).
transform("transform", () new Transformer[Int, Int] {
var tot = 0
override def onNext(elem: Int) = {
@ -68,8 +68,8 @@ class FlowTransformRecoverSpec extends AkkaSpec {
}
"produce one-to-several transformation as expected" in {
val p = FlowFrom(List(1, 2, 3).iterator).toPublisher()
val p2 = FlowFrom(p).
val p = Source(List(1, 2, 3).iterator).toPublisher()
val p2 = Source(p).
transform("transform", () new Transformer[Int, Int] {
var tot = 0
override def onNext(elem: Int) = {
@ -99,8 +99,8 @@ class FlowTransformRecoverSpec extends AkkaSpec {
}
"produce dropping transformation as expected" in {
val p = FlowFrom(List(1, 2, 3, 4).iterator).toPublisher()
val p2 = FlowFrom(p).
val p = Source(List(1, 2, 3, 4).iterator).toPublisher()
val p2 = Source(p).
transform("transform", () new Transformer[Int, Int] {
var tot = 0
override def onNext(elem: Int) = {
@ -127,8 +127,8 @@ class FlowTransformRecoverSpec extends AkkaSpec {
}
"produce multi-step transformation as expected" in {
val p = FlowFrom(List("a", "bc", "def").iterator).toPublisher()
val p2 = FlowFrom(p).
val p = Source(List("a", "bc", "def").iterator).toPublisher()
val p2 = Source(p).
transform("transform", () new TryRecoveryTransformer[String, Int] {
var concat = ""
override def onNext(element: Try[String]) = {
@ -170,8 +170,8 @@ class FlowTransformRecoverSpec extends AkkaSpec {
}
"invoke onComplete when done" in {
val p = FlowFrom(List("a").iterator).toPublisher()
val p2 = FlowFrom(p).
val p = Source(List("a").iterator).toPublisher()
val p2 = Source(p).
transform("transform", () new TryRecoveryTransformer[String, String] {
var s = ""
override def onNext(element: Try[String]) = {
@ -191,7 +191,7 @@ class FlowTransformRecoverSpec extends AkkaSpec {
"allow cancellation using isComplete" in {
val p = StreamTestKit.PublisherProbe[Int]()
val p2 = FlowFrom(p).
val p2 = Source(p).
transform("transform", () new TryRecoveryTransformer[Int, Int] {
var s = ""
override def onNext(element: Try[Int]) = {
@ -215,7 +215,7 @@ class FlowTransformRecoverSpec extends AkkaSpec {
"call onComplete after isComplete signaled completion" in {
val p = StreamTestKit.PublisherProbe[Int]()
val p2 = FlowFrom(p).
val p2 = Source(p).
transform("transform", () new TryRecoveryTransformer[Int, Int] {
var s = ""
override def onNext(element: Try[Int]) = {
@ -240,8 +240,8 @@ class FlowTransformRecoverSpec extends AkkaSpec {
}
"report error when exception is thrown" in {
val p = FlowFrom(List(1, 2, 3).iterator).toPublisher()
val p2 = FlowFrom(p).
val p = Source(List(1, 2, 3).iterator).toPublisher()
val p2 = Source(p).
transform("transform", () new Transformer[Int, Int] {
override def onNext(elem: Int) = {
if (elem == 2) throw new IllegalArgumentException("two not allowed")
@ -266,7 +266,7 @@ class FlowTransformRecoverSpec extends AkkaSpec {
"report error after emitted elements" in {
EventFilter[IllegalArgumentException]("two not allowed") intercept {
val p2 = FlowFrom(List(1, 2, 3).iterator).
val p2 = Source(List(1, 2, 3).iterator).
mapConcat { elem
if (elem == 2) throw new IllegalArgumentException("two not allowed")
else (1 to 5).map(elem * 100 + _)
@ -315,7 +315,7 @@ class FlowTransformRecoverSpec extends AkkaSpec {
"transform errors in sequence with normal messages" in {
val p = StreamTestKit.PublisherProbe[Int]()
val p2 = FlowFrom(p).
val p2 = Source(p).
transform("transform", () new Transformer[Int, String] {
var s = ""
override def onNext(element: Int) = {
@ -348,7 +348,7 @@ class FlowTransformRecoverSpec extends AkkaSpec {
"forward errors when received and thrown" in {
val p = StreamTestKit.PublisherProbe[Int]()
val p2 = FlowFrom(p).
val p2 = Source(p).
transform("transform", () new Transformer[Int, Int] {
override def onNext(in: Int) = List(in)
override def onError(e: Throwable) = throw e
@ -366,8 +366,8 @@ class FlowTransformRecoverSpec extends AkkaSpec {
}
"support cancel as expected" in {
val p = FlowFrom(List(1, 2, 3).iterator).toPublisher()
val p2 = FlowFrom(p).
val p = Source(List(1, 2, 3).iterator).toPublisher()
val p2 = Source(p).
transform("transform", () new Transformer[Int, Int] {
override def onNext(elem: Int) = List(elem, elem)
override def onError(e: Throwable) = List(-1)

View file

@ -23,8 +23,8 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
"A Flow with transform operations" must {
"produce one-to-one transformation as expected" in {
val p = FlowFrom(List(1, 2, 3)).toPublisher()
val p2 = FlowFrom(p).
val p = Source(List(1, 2, 3)).toPublisher()
val p2 = Source(p).
transform("transform", () new Transformer[Int, Int] {
var tot = 0
override def onNext(elem: Int) = {
@ -46,8 +46,8 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
}
"produce one-to-several transformation as expected" in {
val p = FlowFrom(List(1, 2, 3)).toPublisher()
val p2 = FlowFrom(p).
val p = Source(List(1, 2, 3)).toPublisher()
val p2 = Source(p).
transform("transform", () new Transformer[Int, Int] {
var tot = 0
override def onNext(elem: Int) = {
@ -72,8 +72,8 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
}
"produce dropping transformation as expected" in {
val p = FlowFrom(List(1, 2, 3, 4)).toPublisher()
val p2 = FlowFrom(p).
val p = Source(List(1, 2, 3, 4)).toPublisher()
val p2 = Source(p).
transform("transform", () new Transformer[Int, Int] {
var tot = 0
override def onNext(elem: Int) = {
@ -99,8 +99,8 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
}
"produce multi-step transformation as expected" in {
val p = FlowFrom(List("a", "bc", "def")).toPublisher()
val p2 = FlowFrom(p).
val p = Source(List("a", "bc", "def")).toPublisher()
val p2 = Source(p).
transform("transform", () new Transformer[String, Int] {
var concat = ""
override def onNext(elem: String) = {
@ -138,8 +138,8 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
}
"invoke onComplete when done" in {
val p = FlowFrom(List("a")).toPublisher()
val p2 = FlowFrom(p).
val p = Source(List("a")).toPublisher()
val p2 = Source(p).
transform("transform", () new Transformer[String, String] {
var s = ""
override def onNext(element: String) = {
@ -159,8 +159,8 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
"invoke cleanup when done" in {
val cleanupProbe = TestProbe()
val p = FlowFrom(List("a")).toPublisher()
val p2 = FlowFrom(p).
val p = Source(List("a")).toPublisher()
val p2 = Source(p).
transform("transform", () new Transformer[String, String] {
var s = ""
override def onNext(element: String) = {
@ -182,8 +182,8 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
"invoke cleanup when done consume" in {
val cleanupProbe = TestProbe()
val p = FlowFrom(List("a")).toPublisher()
FlowFrom(p).
val p = Source(List("a")).toPublisher()
Source(p).
transform("transform", () new Transformer[String, String] {
var s = "x"
override def onNext(element: String) = {
@ -192,14 +192,14 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
}
override def cleanup() = cleanupProbe.ref ! s
}).
withSink(BlackholeSink).run()
connect(BlackholeDrain).run()
cleanupProbe.expectMsg("a")
}
"invoke cleanup when done after error" in {
val cleanupProbe = TestProbe()
val p = FlowFrom(List("a", "b", "c")).toPublisher()
val p2 = FlowFrom(p).
val p = Source(List("a", "b", "c")).toPublisher()
val p2 = Source(p).
transform("transform", () new Transformer[String, String] {
var s = ""
override def onNext(in: String) = {
@ -227,7 +227,7 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
"allow cancellation using isComplete" in {
val p = StreamTestKit.PublisherProbe[Int]()
val p2 = FlowFrom(p).
val p2 = Source(p).
transform("transform", () new Transformer[Int, Int] {
var s = ""
override def onNext(element: Int) = {
@ -252,7 +252,7 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
"call onComplete after isComplete signaled completion" in {
val cleanupProbe = TestProbe()
val p = StreamTestKit.PublisherProbe[Int]()
val p2 = FlowFrom(p).
val p2 = Source(p).
transform("transform", () new Transformer[Int, Int] {
var s = ""
override def onNext(element: Int) = {
@ -279,8 +279,8 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
}
"report error when exception is thrown" in {
val p = FlowFrom(List(1, 2, 3)).toPublisher()
val p2 = FlowFrom(p).
val p = Source(List(1, 2, 3)).toPublisher()
val p2 = Source(p).
transform("transform", () new Transformer[Int, Int] {
override def onNext(elem: Int) = {
if (elem == 2) {
@ -304,8 +304,8 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
}
"support cancel as expected" in {
val p = FlowFrom(List(1, 2, 3)).toPublisher()
val p2 = FlowFrom(p).
val p = Source(List(1, 2, 3)).toPublisher()
val p2 = Source(p).
transform("transform", () new Transformer[Int, Int] {
override def onNext(elem: Int) = List(elem, elem)
}).
@ -323,8 +323,8 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
}
"support producing elements from empty inputs" in {
val p = FlowFrom(List.empty[Int]).toPublisher()
val p2 = FlowFrom(p).
val p = Source(List.empty[Int]).toPublisher()
val p2 = Source(p).
transform("transform", () new Transformer[Int, Int] {
override def onNext(elem: Int) = Nil
override def onTermination(e: Option[Throwable]) = List(1, 2, 3)
@ -343,7 +343,7 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
"support converting onComplete into onError" in {
val subscriber = StreamTestKit.SubscriberProbe[Int]()
FlowFrom(List(5, 1, 2, 3)).transform("transform", () new Transformer[Int, Int] {
Source(List(5, 1, 2, 3)).transform("transform", () new Transformer[Int, Int] {
var expectedNumberOfElements: Option[Int] = None
var count = 0
override def onNext(elem: Int) =
@ -375,7 +375,7 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
}
"be safe to reuse" in {
val flow = FlowFrom(1 to 3).transform("transform", ()
val flow = Source(1 to 3).transform("transform", ()
new Transformer[Int, Int] {
var count = 0

View file

@ -22,9 +22,9 @@ class GraphBroadcastSpec extends AkkaSpec {
FlowGraph { implicit b
val bcast = Broadcast[Int]("broadcast")
FlowFrom(List(1, 2, 3)) ~> bcast
bcast ~> FlowFrom[Int].buffer(16, OverflowStrategy.backpressure) ~> SubscriberSink(c1)
bcast ~> FlowFrom[Int].buffer(16, OverflowStrategy.backpressure) ~> SubscriberSink(c2)
Source(List(1, 2, 3)) ~> bcast
bcast ~> Flow[Int].buffer(16, OverflowStrategy.backpressure) ~> SubscriberDrain(c1)
bcast ~> Flow[Int].buffer(16, OverflowStrategy.backpressure) ~> SubscriberDrain(c2)
}.run()
val sub1 = c1.expectSubscription()
@ -46,27 +46,27 @@ class GraphBroadcastSpec extends AkkaSpec {
}
"work with n-way broadcast" in {
val f1 = FutureSink[Seq[Int]]
val f2 = FutureSink[Seq[Int]]
val f3 = FutureSink[Seq[Int]]
val f4 = FutureSink[Seq[Int]]
val f5 = FutureSink[Seq[Int]]
val f1 = FutureDrain[Seq[Int]]
val f2 = FutureDrain[Seq[Int]]
val f3 = FutureDrain[Seq[Int]]
val f4 = FutureDrain[Seq[Int]]
val f5 = FutureDrain[Seq[Int]]
val g = FlowGraph { implicit b
val bcast = Broadcast[Int]("broadcast")
FlowFrom(List(1, 2, 3)) ~> bcast
bcast ~> FlowFrom[Int].grouped(5) ~> f1
bcast ~> FlowFrom[Int].grouped(5) ~> f2
bcast ~> FlowFrom[Int].grouped(5) ~> f3
bcast ~> FlowFrom[Int].grouped(5) ~> f4
bcast ~> FlowFrom[Int].grouped(5) ~> f5
Source(List(1, 2, 3)) ~> bcast
bcast ~> Flow[Int].grouped(5) ~> f1
bcast ~> Flow[Int].grouped(5) ~> f2
bcast ~> Flow[Int].grouped(5) ~> f3
bcast ~> Flow[Int].grouped(5) ~> f4
bcast ~> Flow[Int].grouped(5) ~> f5
}.run()
Await.result(g.getSinkFor(f1), 3.seconds) should be(List(1, 2, 3))
Await.result(g.getSinkFor(f2), 3.seconds) should be(List(1, 2, 3))
Await.result(g.getSinkFor(f3), 3.seconds) should be(List(1, 2, 3))
Await.result(g.getSinkFor(f4), 3.seconds) should be(List(1, 2, 3))
Await.result(g.getSinkFor(f5), 3.seconds) should be(List(1, 2, 3))
Await.result(g.getDrainFor(f1), 3.seconds) should be(List(1, 2, 3))
Await.result(g.getDrainFor(f2), 3.seconds) should be(List(1, 2, 3))
Await.result(g.getDrainFor(f3), 3.seconds) should be(List(1, 2, 3))
Await.result(g.getDrainFor(f4), 3.seconds) should be(List(1, 2, 3))
Await.result(g.getDrainFor(f5), 3.seconds) should be(List(1, 2, 3))
}
"produce to other even though downstream cancels" in {
@ -75,9 +75,9 @@ class GraphBroadcastSpec extends AkkaSpec {
FlowGraph { implicit b
val bcast = Broadcast[Int]("broadcast")
FlowFrom(List(1, 2, 3)) ~> bcast
bcast ~> FlowFrom[Int] ~> SubscriberSink(c1)
bcast ~> FlowFrom[Int] ~> SubscriberSink(c2)
Source(List(1, 2, 3)) ~> bcast
bcast ~> Flow[Int] ~> SubscriberDrain(c1)
bcast ~> Flow[Int] ~> SubscriberDrain(c2)
}.run()
val sub1 = c1.expectSubscription()
@ -96,9 +96,9 @@ class GraphBroadcastSpec extends AkkaSpec {
FlowGraph { implicit b
val bcast = Broadcast[Int]("broadcast")
FlowFrom(List(1, 2, 3)) ~> bcast
bcast ~> FlowFrom[Int] ~> SubscriberSink(c1)
bcast ~> FlowFrom[Int] ~> SubscriberSink(c2)
Source(List(1, 2, 3)) ~> bcast
bcast ~> Flow[Int] ~> SubscriberDrain(c1)
bcast ~> Flow[Int] ~> SubscriberDrain(c2)
}.run()
val sub1 = c1.expectSubscription()
@ -118,9 +118,9 @@ class GraphBroadcastSpec extends AkkaSpec {
FlowGraph { implicit b
val bcast = Broadcast[Int]("broadcast")
FlowFrom(p1.getPublisher) ~> bcast
bcast ~> FlowFrom[Int] ~> SubscriberSink(c1)
bcast ~> FlowFrom[Int] ~> SubscriberSink(c2)
Source(p1.getPublisher) ~> bcast
bcast ~> Flow[Int] ~> SubscriberDrain(c1)
bcast ~> Flow[Int] ~> SubscriberDrain(c2)
}.run()
val bsub = p1.expectSubscription()

View file

@ -26,13 +26,13 @@ class GraphConcatSpec extends TwoStreamsSetup {
val concat1 = Concat[Int]("concat1")
val concat2 = Concat[Int]("concat2")
FlowFrom(List.empty[Int].iterator) ~> concat1.first
FlowFrom((1 to 4).iterator) ~> concat1.second
Source(List.empty[Int].iterator) ~> concat1.first
Source((1 to 4).iterator) ~> concat1.second
concat1.out ~> concat2.first
FlowFrom((5 to 10).iterator) ~> concat2.second
Source((5 to 10).iterator) ~> concat2.second
concat2.out ~> SubscriberSink(probe)
concat2.out ~> SubscriberDrain(probe)
}.run()
val subscription = probe.expectSubscription()
@ -109,9 +109,9 @@ class GraphConcatSpec extends TwoStreamsSetup {
FlowGraph { implicit b
val concat = Concat[Int]
FlowFrom(List(1, 2, 3)) ~> concat.first
FlowFrom(promise.future) ~> concat.second
concat.out ~> SubscriberSink(subscriber)
Source(List(1, 2, 3)) ~> concat.first
Source(promise.future) ~> concat.second
concat.out ~> SubscriberDrain(subscriber)
}.run()
val subscription = subscriber.expectSubscription()

View file

@ -19,9 +19,9 @@ class GraphMergeSpec extends TwoStreamsSetup {
"work in the happy case" in {
// Different input sizes (4 and 6)
val source1 = FlowFrom((0 to 3).iterator)
val source2 = FlowFrom((4 to 9).iterator)
val source3 = FlowFrom(List.empty[Int].iterator)
val tap1 = Source((0 to 3).iterator)
val tap2 = Source((4 to 9).iterator)
val tap3 = Source(List.empty[Int].iterator)
val probe = StreamTestKit.SubscriberProbe[Int]()
FlowGraph { implicit b
@ -29,9 +29,9 @@ class GraphMergeSpec extends TwoStreamsSetup {
val m2 = Merge[Int]("m2")
val m3 = Merge[Int]("m3")
source1 ~> m1 ~> FlowFrom[Int].map(_ * 2) ~> m2 ~> FlowFrom[Int].map(_ / 2).map(_ + 1) ~> SubscriberSink(probe)
source2 ~> m1
source3 ~> m2
tap1 ~> m1 ~> Flow[Int].map(_ * 2) ~> m2 ~> Flow[Int].map(_ / 2).map(_ + 1) ~> SubscriberDrain(probe)
tap2 ~> m1
tap3 ~> m2
}.run()
@ -48,24 +48,24 @@ class GraphMergeSpec extends TwoStreamsSetup {
}
"work with n-way merge" in {
val source1 = FlowFrom(List(1))
val source2 = FlowFrom(List(2))
val source3 = FlowFrom(List(3))
val source4 = FlowFrom(List(4))
val source5 = FlowFrom(List(5))
val source6 = FlowFrom(List.empty[Int])
val tap1 = Source(List(1))
val tap2 = Source(List(2))
val tap3 = Source(List(3))
val tap4 = Source(List(4))
val tap5 = Source(List(5))
val tap6 = Source(List.empty[Int])
val probe = StreamTestKit.SubscriberProbe[Int]()
FlowGraph { implicit b
val merge = Merge[Int]("merge")
source1 ~> merge ~> FlowFrom[Int] ~> SubscriberSink(probe)
source2 ~> merge
source3 ~> merge
source4 ~> merge
source5 ~> merge
source6 ~> merge
tap1 ~> merge ~> Flow[Int] ~> SubscriberDrain(probe)
tap2 ~> merge
tap3 ~> merge
tap4 ~> merge
tap5 ~> merge
tap6 ~> merge
}.run()

View file

@ -19,26 +19,26 @@ class GraphOpsIntegrationSpec extends AkkaSpec {
"FlowGraphs" must {
"support broadcast - merge layouts" in {
val resultFuture = FutureSink[Seq[Int]]
val resultFuture = FutureDrain[Seq[Int]]
val g = FlowGraph { implicit b
val bcast = Broadcast[Int]("broadcast")
val merge = Merge[Int]("merge")
FlowFrom(List(1, 2, 3)) ~> bcast
Source(List(1, 2, 3)) ~> bcast
bcast ~> merge
bcast ~> FlowFrom[Int].map(_ + 3) ~> merge
merge ~> FlowFrom[Int].grouped(10) ~> resultFuture
bcast ~> Flow[Int].map(_ + 3) ~> merge
merge ~> Flow[Int].grouped(10) ~> resultFuture
}.run()
Await.result(g.getSinkFor(resultFuture), 3.seconds).sorted should be(List(1, 2, 3, 4, 5, 6))
Await.result(g.getDrainFor(resultFuture), 3.seconds).sorted should be(List(1, 2, 3, 4, 5, 6))
}
"support wikipedia Topological_sorting 2" in {
// see https://en.wikipedia.org/wiki/Topological_sorting#mediaviewer/File:Directed_acyclic_graph.png
val resultFuture2 = FutureSink[Seq[Int]]
val resultFuture9 = FutureSink[Seq[Int]]
val resultFuture10 = FutureSink[Seq[Int]]
val resultFuture2 = FutureDrain[Seq[Int]]
val resultFuture9 = FutureDrain[Seq[Int]]
val resultFuture10 = FutureDrain[Seq[Int]]
val g = FlowGraph { implicit b
val b3 = Broadcast[Int]("b3")
@ -48,9 +48,9 @@ class GraphOpsIntegrationSpec extends AkkaSpec {
val m9 = Merge[Int]("m9")
val m10 = Merge[Int]("m10")
val m11 = Merge[Int]("m11")
val in3 = IterableSource(List(3))
val in5 = IterableSource(List(5))
val in7 = IterableSource(List(7))
val in3 = IterableTap(List(3))
val in5 = IterableTap(List(5))
val in7 = IterableTap(List(7))
// First layer
in7 ~> b7
@ -65,24 +65,39 @@ class GraphOpsIntegrationSpec extends AkkaSpec {
// Second layer
m11 ~> b11
b11 ~> FlowFrom[Int].grouped(1000) ~> resultFuture2 // Vertex 2 is omitted since it has only one in and out
b11 ~> Flow[Int].grouped(1000) ~> resultFuture2 // Vertex 2 is omitted since it has only one in and out
b11 ~> m9
b11 ~> m10
m8 ~> m9
// Third layer
m9 ~> FlowFrom[Int].grouped(1000) ~> resultFuture9
m10 ~> FlowFrom[Int].grouped(1000) ~> resultFuture10
m9 ~> Flow[Int].grouped(1000) ~> resultFuture9
m10 ~> Flow[Int].grouped(1000) ~> resultFuture10
}.run()
Await.result(g.getSinkFor(resultFuture2), 3.seconds).sorted should be(List(5, 7))
Await.result(g.getSinkFor(resultFuture9), 3.seconds).sorted should be(List(3, 5, 7, 7))
Await.result(g.getSinkFor(resultFuture10), 3.seconds).sorted should be(List(3, 5, 7))
Await.result(g.getDrainFor(resultFuture2), 3.seconds).sorted should be(List(5, 7))
Await.result(g.getDrainFor(resultFuture9), 3.seconds).sorted should be(List(3, 5, 7, 7))
Await.result(g.getDrainFor(resultFuture10), 3.seconds).sorted should be(List(3, 5, 7))
}
"allow adding of flows to sources and sinks to flows" in {
val resultFuture = FutureDrain[Seq[Int]]
val g = FlowGraph { implicit b
val bcast = Broadcast[Int]("broadcast")
val merge = Merge[Int]("merge")
Source(List(1, 2, 3)) ~> Flow[Int].map(_ * 2) ~> bcast
bcast ~> merge
bcast ~> Flow[Int].map(_ + 3) ~> merge
merge ~> Flow[Int].grouped(10).connect(resultFuture)
}.run()
Await.result(g.getDrainFor(resultFuture), 3.seconds) should contain theSameElementsAs (Seq(2, 4, 6, 5, 7, 9))
}
}
}

View file

@ -22,9 +22,9 @@ class GraphUnzipSpec extends AkkaSpec {
FlowGraph { implicit b
val unzip = Unzip[Int, String]("unzip")
FlowFrom(List(1 -> "a", 2 -> "b", 3 -> "c")) ~> unzip.in
unzip.right ~> FlowFrom[String].buffer(16, OverflowStrategy.backpressure) ~> SubscriberSink(c2)
unzip.left ~> FlowFrom[Int].buffer(16, OverflowStrategy.backpressure).map(_ * 2) ~> SubscriberSink(c1)
Source(List(1 -> "a", 2 -> "b", 3 -> "c")) ~> unzip.in
unzip.right ~> Flow[String].buffer(16, OverflowStrategy.backpressure) ~> SubscriberDrain(c2)
unzip.left ~> Flow[Int].buffer(16, OverflowStrategy.backpressure).map(_ * 2) ~> SubscriberDrain(c1)
}.run()
val sub1 = c1.expectSubscription()
@ -51,9 +51,9 @@ class GraphUnzipSpec extends AkkaSpec {
FlowGraph { implicit b
val unzip = Unzip[Int, String]("unzip")
FlowFrom(List(1 -> "a", 2 -> "b", 3 -> "c")) ~> unzip.in
unzip.left ~> SubscriberSink(c1)
unzip.right ~> SubscriberSink(c2)
Source(List(1 -> "a", 2 -> "b", 3 -> "c")) ~> unzip.in
unzip.left ~> SubscriberDrain(c1)
unzip.right ~> SubscriberDrain(c2)
}.run()
val sub1 = c1.expectSubscription()
@ -72,9 +72,9 @@ class GraphUnzipSpec extends AkkaSpec {
FlowGraph { implicit b
val unzip = Unzip[Int, String]("unzip")
FlowFrom(List(1 -> "a", 2 -> "b", 3 -> "c")) ~> unzip.in
unzip.left ~> SubscriberSink(c1)
unzip.right ~> SubscriberSink(c2)
Source(List(1 -> "a", 2 -> "b", 3 -> "c")) ~> unzip.in
unzip.left ~> SubscriberDrain(c1)
unzip.right ~> SubscriberDrain(c2)
}.run()
val sub1 = c1.expectSubscription()
@ -94,9 +94,9 @@ class GraphUnzipSpec extends AkkaSpec {
FlowGraph { implicit b
val unzip = Unzip[Int, String]("unzip")
FlowFrom(p1.getPublisher) ~> unzip.in
unzip.left ~> SubscriberSink(c1)
unzip.right ~> SubscriberSink(c2)
Source(p1.getPublisher) ~> unzip.in
unzip.left ~> SubscriberDrain(c1)
unzip.right ~> SubscriberDrain(c2)
}.run()
val p1Sub = p1.expectSubscription()
@ -122,10 +122,10 @@ class GraphUnzipSpec extends AkkaSpec {
val zip = Zip[Int, String]
val unzip = Unzip[Int, String]
import FlowGraphImplicits._
FlowFrom(List(1 -> "a", 2 -> "b", 3 -> "c")) ~> unzip.in
Source(List(1 -> "a", 2 -> "b", 3 -> "c")) ~> unzip.in
unzip.left ~> zip.left
unzip.right ~> zip.right
zip.out ~> SubscriberSink(c1)
zip.out ~> SubscriberDrain(c1)
}.run()
val sub1 = c1.expectSubscription()

View file

@ -22,10 +22,10 @@ class GraphZipSpec extends TwoStreamsSetup {
FlowGraph { implicit b
val zip = Zip[Int, String]
FlowFrom(1 to 4) ~> zip.left
FlowFrom(List("A", "B", "C", "D", "E", "F")) ~> zip.right
Source(1 to 4) ~> zip.left
Source(List("A", "B", "C", "D", "E", "F")) ~> zip.right
zip.out ~> SubscriberSink(probe)
zip.out ~> SubscriberDrain(probe)
}.run()
val subscription = probe.expectSubscription()

View file

@ -16,15 +16,15 @@ object ImplicitFlowMaterializerSpec {
.withInputBuffer(initialSize = 2, maxSize = 16)
.withFanOutBuffer(initialSize = 1, maxSize = 16)
val flow = FlowFrom(input).map(_.toUpperCase())
val flow = Source(input).map(_.toUpperCase())
def receive = {
case "run"
// run takes an implicit FlowMaterializer parameter, which is provided by ImplicitFlowMaterializer
import context.dispatcher
val foldSink = FoldSink[String, String]("")(_ + _)
val mf = flow.withSink(foldSink).run()
foldSink.future(mf) pipeTo sender()
val foldDrain = FoldDrain[String, String]("")(_ + _)
val mf = flow.connect(foldDrain).run()
foldDrain.future(mf) pipeTo sender()
}
}
}

View file

@ -17,7 +17,7 @@ class TickPublisherSpec extends AkkaSpec {
"produce ticks" in {
val tickGen = Iterator from 1
val c = StreamTestKit.SubscriberProbe[String]()
FlowFrom(1.second, 500.millis, () "tick-" + tickGen.next()).publishTo(c)
Source(1.second, 500.millis, () "tick-" + tickGen.next()).publishTo(c)
val sub = c.expectSubscription()
sub.request(3)
c.expectNoMsg(600.millis)
@ -33,7 +33,7 @@ class TickPublisherSpec extends AkkaSpec {
"drop ticks when not requested" in {
val tickGen = Iterator from 1
val c = StreamTestKit.SubscriberProbe[String]()
FlowFrom(1.second, 1.second, () "tick-" + tickGen.next()).publishTo(c)
Source(1.second, 1.second, () "tick-" + tickGen.next()).publishTo(c)
val sub = c.expectSubscription()
sub.request(2)
c.expectNext("tick-1")
@ -50,7 +50,7 @@ class TickPublisherSpec extends AkkaSpec {
"produce ticks with multiple subscribers" in {
val tickGen = Iterator from 1
val p = FlowFrom(1.second, 1.second, () "tick-" + tickGen.next()).toPublisher()
val p = Source(1.second, 1.second, () "tick-" + tickGen.next()).toPublisher()
val c1 = StreamTestKit.SubscriberProbe[String]()
val c2 = StreamTestKit.SubscriberProbe[String]()
p.subscribe(c1)
@ -74,7 +74,7 @@ class TickPublisherSpec extends AkkaSpec {
"signal onError when tick closure throws" in {
val c = StreamTestKit.SubscriberProbe[String]()
FlowFrom(1.second, 1.second, () throw new RuntimeException("tick err") with NoStackTrace).publishTo(c)
Source[String](1.second, 1.second, () throw new RuntimeException("tick err") with NoStackTrace).publishTo(c)
val sub = c.expectSubscription()
sub.request(3)
c.expectError.getMessage should be("tick err")
@ -83,8 +83,8 @@ class TickPublisherSpec extends AkkaSpec {
// FIXME enable this test again when zip is back
"be usable with zip for a simple form of rate limiting" ignore {
// val c = StreamTestKit.SubscriberProbe[Int]()
// val rate = FlowFrom(1.second, 1.second, () "tick").toPublisher()
// FlowFrom(1 to 100).zip(rate).map { case (n, _) n }.publishTo(c)
// val rate = Source(1.second, 1.second, () "tick").toPublisher()
// Source(1 to 100).zip(rate).map { case (n, _) n }.publishTo(c)
// val sub = c.expectSubscription()
// sub.request(1000)
// c.expectNext(1)

View file

@ -132,57 +132,57 @@ case class ActorBasedFlowMaterializer(override val settings: MaterializerSetting
}
// Ops come in reverse order
override def materialize[In, Out](source: Source[In], sink: Sink[Out], ops: List[Ast.AstNode]): MaterializedFlow = {
override def materialize[In, Out](tap: Tap[In], drain: Drain[Out], ops: List[Ast.AstNode]): MaterializedPipe = {
val flowName = createFlowName()
def attachSink(pub: Publisher[Out]) = sink match {
case s: SimpleSink[Out] s.attach(pub, this, flowName)
case s: SinkWithKey[Out, _] s.attach(pub, this, flowName)
case _ throw new MaterializationException("unknown Sink type " + sink.getClass)
def attachDrain(pub: Publisher[Out]) = drain match {
case s: SimpleDrain[Out] s.attach(pub, this, flowName)
case s: DrainWithKey[Out, _] s.attach(pub, this, flowName)
case _ throw new MaterializationException("unknown Drain type " + drain.getClass)
}
def attachSource(sub: Subscriber[In]) = source match {
case s: SimpleSource[In] s.attach(sub, this, flowName)
case s: SourceWithKey[In, _] s.attach(sub, this, flowName)
case _ throw new MaterializationException("unknown Source type " + sink.getClass)
def attachTap(sub: Subscriber[In]) = tap match {
case s: SimpleTap[In] s.attach(sub, this, flowName)
case s: TapWithKey[In, _] s.attach(sub, this, flowName)
case _ throw new MaterializationException("unknown Tap type " + drain.getClass)
}
def createSink() = sink.asInstanceOf[Sink[In]] match {
case s: SimpleSink[In] s.create(this, flowName) -> (())
case s: SinkWithKey[In, _] s.create(this, flowName)
case _ throw new MaterializationException("unknown Sink type " + sink.getClass)
def createDrain() = drain.asInstanceOf[Drain[In]] match {
case s: SimpleDrain[In] s.create(this, flowName) -> (())
case s: DrainWithKey[In, _] s.create(this, flowName)
case _ throw new MaterializationException("unknown Drain type " + drain.getClass)
}
def createSource() = source.asInstanceOf[Source[Out]] match {
case s: SimpleSource[Out] s.create(this, flowName) -> (())
case s: SourceWithKey[Out, _] s.create(this, flowName)
case _ throw new MaterializationException("unknown Source type " + sink.getClass)
def createTap() = tap.asInstanceOf[Tap[Out]] match {
case s: SimpleTap[Out] s.create(this, flowName) -> (())
case s: TapWithKey[Out, _] s.create(this, flowName)
case _ throw new MaterializationException("unknown Tap type " + drain.getClass)
}
def isActive(s: AnyRef) = s match {
case source: SimpleSource[_] source.isActive
case source: SourceWithKey[_, _] source.isActive
case sink: SimpleSink[_] sink.isActive
case sink: SinkWithKey[_, _] sink.isActive
case _: Source[_] throw new MaterializationException("unknown Source type " + sink.getClass)
case _: Sink[_] throw new MaterializationException("unknown Sink type " + sink.getClass)
case tap: SimpleTap[_] tap.isActive
case tap: TapWithKey[_, _] tap.isActive
case drain: SimpleDrain[_] drain.isActive
case drain: DrainWithKey[_, _] drain.isActive
case _: Tap[_] throw new MaterializationException("unknown Tap type " + drain.getClass)
case _: Drain[_] throw new MaterializationException("unknown Drain type " + drain.getClass)
}
val (sourceValue, sinkValue) =
val (tapValue, drainValue) =
if (ops.isEmpty) {
if (isActive(sink)) {
val (sub, value) = createSink()
(attachSource(sub), value)
} else if (isActive(source)) {
val (pub, value) = createSource()
(value, attachSink(pub))
if (isActive(drain)) {
val (sub, value) = createDrain()
(attachTap(sub), value)
} else if (isActive(tap)) {
val (pub, value) = createTap()
(value, attachDrain(pub))
} else {
val id: Processor[In, Out] = processorForNode(identityTransform, flowName, 1).asInstanceOf[Processor[In, Out]]
(attachSource(id), attachSink(id))
(attachTap(id), attachDrain(id))
}
} else {
val opsSize = ops.size
val last = processorForNode(ops.head, flowName, opsSize).asInstanceOf[Processor[Any, Out]]
val first = processorChain(last, ops.tail, flowName, opsSize - 1).asInstanceOf[Processor[In, Any]]
(attachSource(first), attachSink(last))
(attachTap(first), attachDrain(last))
}
new MaterializedFlow(source, sourceValue, sink, sinkValue)
new MaterializedPipe(tap, tapValue, drain, drainValue)
}
private val identityTransform = Ast.Transform("identity", ()

View file

@ -5,7 +5,7 @@ package akka.stream.impl2
import akka.stream.impl.TransferPhase
import akka.stream.impl.MultiStreamInputProcessor
import akka.stream.scaladsl2.FlowWithSource
import akka.stream.scaladsl2.Source
import akka.stream.scaladsl2.FlowMaterializer
/**
@ -17,7 +17,7 @@ private[akka] class ConcatAllImpl(materializer: FlowMaterializer)
import MultiStreamInputProcessor._
val takeNextSubstream = TransferPhase(primaryInputs.NeedsInput && primaryOutputs.NeedsDemand) { ()
val flow = primaryInputs.dequeueInputElement().asInstanceOf[FlowWithSource[Any, Any]]
val flow = primaryInputs.dequeueInputElement().asInstanceOf[Source[Any]]
val publisher = flow.toPublisher()(materializer)
// FIXME we can pass the flow to createSubstreamInput (but avoiding copy impl now)
val inputs = createAndSubscribeSubstreamInput(publisher)

View file

@ -5,7 +5,7 @@ package akka.stream.impl2
import akka.stream.MaterializerSettings
import akka.stream.impl.TransferPhase
import akka.stream.scaladsl2.FlowFrom
import akka.stream.scaladsl2.Source
import akka.stream.impl.MultiStreamOutputProcessor
/**
@ -45,7 +45,7 @@ private[akka] class GroupByProcessorImpl(settings: MaterializerSettings, val key
nextPhase(waitNext)
} else {
val substreamOutput = createSubstreamOutput()
val substreamFlow = FlowFrom(substreamOutput) // substreamOutput is a Publisher
val substreamFlow = Source(substreamOutput) // substreamOutput is a Publisher
primaryOutputs.enqueueOutputElement((key, substreamFlow))
keyToSubstreamOutput(key) = substreamOutput
nextPhase(dispatchToSubstream(elem, substreamOutput))

View file

@ -8,7 +8,7 @@ import scala.collection.immutable
import akka.stream.impl.TransferPhase
import akka.stream.impl.EmptyPublisher
import akka.stream.impl.MultiStreamOutputProcessor
import akka.stream.scaladsl2.FlowFrom
import akka.stream.scaladsl2.Source
/**
* INTERNAL API
@ -44,13 +44,13 @@ private[akka] class PrefixAndTailImpl(_settings: MaterializerSettings, val takeM
}
def emitEmptyTail(): Unit = {
primaryOutputs.enqueueOutputElement((taken, FlowFrom(EmptyPublisher[Any])))
primaryOutputs.enqueueOutputElement((taken, Source(EmptyPublisher[Any])))
nextPhase(completedPhase)
}
def emitNonEmptyTail(): Unit = {
val substreamOutput = createSubstreamOutput()
val substreamFlow = FlowFrom(substreamOutput) // substreamOutput is a Publisher
val substreamFlow = Source(substreamOutput) // substreamOutput is a Publisher
primaryOutputs.enqueueOutputElement((taken, substreamFlow))
primaryOutputs.complete()
nextPhase(streamTailPhase(substreamOutput))

View file

@ -6,7 +6,7 @@ package akka.stream.impl2
import akka.stream.MaterializerSettings
import akka.stream.impl.TransferPhase
import akka.stream.impl.MultiStreamOutputProcessor
import akka.stream.scaladsl2.FlowFrom
import akka.stream.scaladsl2.Source
/**
* INTERNAL API
@ -24,7 +24,7 @@ private[akka] class SplitWhenProcessorImpl(_settings: MaterializerSettings, val
def openSubstream(elem: Any): TransferPhase = TransferPhase(primaryOutputs.NeedsDemand) { ()
val substreamOutput = createSubstreamOutput()
val substreamFlow = FlowFrom(substreamOutput) // substreamOutput is a Publisher
val substreamFlow = Source(substreamOutput) // substreamOutput is a Publisher
primaryOutputs.enqueueOutputElement(substreamFlow)
currentSubstream = substreamOutput
nextPhase(serveSubstreamFirst(currentSubstream, elem))

View file

@ -0,0 +1,276 @@
/**
* Copyright (C) 2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.stream.scaladsl2
import akka.actor.Props
import scala.collection.immutable
import scala.annotation.unchecked.uncheckedVariance
import scala.concurrent.{ Future, Promise }
import scala.util.{ Failure, Success, Try }
import org.reactivestreams.{ Publisher, Subscriber, Subscription }
import akka.stream.Transformer
import akka.stream.impl.{ FanoutProcessorImpl, BlackholeSubscriber }
import akka.stream.impl2.{ ActorProcessorFactory, ActorBasedFlowMaterializer }
import java.util.concurrent.atomic.AtomicReference
/**
* This trait is a marker for a pluggable stream drain. Concrete instances should
* implement [[DrainWithKey]] or [[SimpleDrain]], otherwise a custom [[FlowMaterializer]]
* will have to be used to be able to attach them.
*
* All Drains defined in this package rely upon an [[akka.stream.impl2.ActorBasedFlowMaterializer]] being
* made available to them in order to use the <code>attach</code> method. Other
* FlowMaterializers can be used but must then implement the functionality of these
* Drain nodes themselves (or construct an ActorBasedFlowMaterializer).
*/
trait Drain[-In] extends Sink[In]
/**
* A drain that does not need to create a user-accessible object during materialization.
*/
trait SimpleDrain[-In] extends Drain[In] with DrainOps[In] {
/**
* Attach this drain to the given [[org.reactivestreams.Publisher]]. Using the given
* [[FlowMaterializer]] is completely optional, especially if this drain belongs to
* a different Reactive Streams implementation. It is the responsibility of the
* caller to provide a suitable FlowMaterializer that can be used for running
* Flows if necessary.
*
* @param flowPublisher the Publisher to consume elements from
* @param materializer a FlowMaterializer that may be used for creating flows
* @param flowName the name of the current flow, which should be used in log statements or error messages
*/
def attach(flowPublisher: Publisher[In @uncheckedVariance], materializer: ActorBasedFlowMaterializer, flowName: String): Unit
/**
* This method is only used for Drains that return true from [[#isActive]], which then must
* implement it.
*/
def create(materializer: ActorBasedFlowMaterializer, flowName: String): Subscriber[In] @uncheckedVariance =
throw new UnsupportedOperationException(s"forgot to implement create() for $getClass that says isActive==true")
/**
* This method indicates whether this Drain can create a Subscriber instead of being
* attached to a Publisher. This is only used if the Flow does not contain any
* operations.
*/
def isActive: Boolean = false
}
/**
* A drain that will create an object during materialization that the user will need
* to retrieve in order to access aspects of this drain (could be a completion Future
* or a cancellation handle, etc.)
*/
trait DrainWithKey[-In, T] extends DrainOps[In] {
/**
* Attach this drain to the given [[org.reactivestreams.Publisher]]. Using the given
* [[FlowMaterializer]] is completely optional, especially if this drain belongs to
* a different Reactive Streams implementation. It is the responsibility of the
* caller to provide a suitable FlowMaterializer that can be used for running
* Flows if necessary.
*
* @param flowPublisher the Publisher to consume elements from
* @param materializer a FlowMaterializer that may be used for creating flows
* @param flowName the name of the current flow, which should be used in log statements or error messages
*/
def attach(flowPublisher: Publisher[In @uncheckedVariance], materializer: ActorBasedFlowMaterializer, flowName: String): T
/**
* This method is only used for Drains that return true from [[#isActive]], which then must
* implement it.
*/
def create(materializer: ActorBasedFlowMaterializer, flowName: String): (Subscriber[In] @uncheckedVariance, T) =
throw new UnsupportedOperationException(s"forgot to implement create() for $getClass that says isActive==true")
/**
* This method indicates whether this Drain can create a Subscriber instead of being
* attached to a Publisher. This is only used if the Flow does not contain any
* operations.
*/
def isActive: Boolean = false
// these are unique keys, case class equality would break them
final override def equals(other: Any): Boolean = super.equals(other)
final override def hashCode: Int = super.hashCode
}
/**
* Holds the downstream-most [[org.reactivestreams.Publisher]] interface of the materialized flow.
* The stream will not have any subscribers attached at this point, which means that after prefetching
* elements to fill the internal buffers it will assert back-pressure until
* a subscriber connects and creates demand for elements to be emitted.
*/
object PublisherDrain {
private val instance = new PublisherDrain[Nothing]
def apply[T]: PublisherDrain[T] = instance.asInstanceOf[PublisherDrain[T]]
def withFanout[T](initialBufferSize: Int, maximumBufferSize: Int): FanoutPublisherDrain[T] =
new FanoutPublisherDrain[T](initialBufferSize, maximumBufferSize)
}
class PublisherDrain[In] extends DrainWithKey[In, Publisher[In]] {
def attach(flowPublisher: Publisher[In], materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[In] = flowPublisher
def publisher(m: MaterializedDrain): Publisher[In] = m.getDrainFor(this)
override def toString: String = "PublisherDrain"
}
class FanoutPublisherDrain[In](initialBufferSize: Int, maximumBufferSize: Int) extends DrainWithKey[In, Publisher[In]] {
def publisher(m: MaterializedDrain): Publisher[In] = m.getDrainFor(this)
override def attach(flowPublisher: Publisher[In], materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[In] = {
val fanoutActor = materializer.actorOf(
Props(new FanoutProcessorImpl(materializer.settings, initialBufferSize, maximumBufferSize)), s"$flowName-fanoutPublisher")
val fanoutProcessor = ActorProcessorFactory[In, In](fanoutActor)
flowPublisher.subscribe(fanoutProcessor)
fanoutProcessor
}
override def toString: String = "Fanout"
}
object FutureDrain {
def apply[T]: FutureDrain[T] = new FutureDrain[T]
}
/**
* Holds a [[scala.concurrent.Future]] that will be fulfilled with the first
* thing that is signaled to this stream, which can be either an element (after
* which the upstream subscription is canceled), an error condition (putting
* the Future into the corresponding failed state) or the end-of-stream
* (failing the Future with a NoSuchElementException).
*/
class FutureDrain[In] extends DrainWithKey[In, Future[In]] {
def attach(flowPublisher: Publisher[In], materializer: ActorBasedFlowMaterializer, flowName: String): Future[In] = {
val (sub, f) = create(materializer, flowName)
flowPublisher.subscribe(sub)
f
}
override def isActive = true
override def create(materializer: ActorBasedFlowMaterializer, flowName: String): (Subscriber[In], Future[In]) = {
val p = Promise[In]()
val sub = new Subscriber[In] { // TODO #15804 verify this using the RS TCK
private val sub = new AtomicReference[Subscription]
override def onSubscribe(s: Subscription): Unit =
if (!sub.compareAndSet(null, s)) s.cancel()
else s.request(1)
override def onNext(t: In): Unit = { p.trySuccess(t); sub.get.cancel() }
override def onError(t: Throwable): Unit = p.tryFailure(t)
override def onComplete(): Unit = p.tryFailure(new NoSuchElementException("empty stream"))
}
(sub, p.future)
}
def future(m: MaterializedDrain): Future[In] = m.getDrainFor(this)
override def toString: String = "FutureDrain"
}
/**
* Attaches a subscriber to this stream which will just discard all received
* elements.
*/
final case object BlackholeDrain extends SimpleDrain[Any] {
override def attach(flowPublisher: Publisher[Any], materializer: ActorBasedFlowMaterializer, flowName: String): Unit =
flowPublisher.subscribe(create(materializer, flowName))
override def isActive: Boolean = true
override def create(materializer: ActorBasedFlowMaterializer, flowName: String): Subscriber[Any] =
new BlackholeSubscriber[Any](materializer.settings.maxInputBufferSize)
}
/**
* Attaches a subscriber to this stream.
*/
final case class SubscriberDrain[In](subscriber: Subscriber[In]) extends SimpleDrain[In] {
override def attach(flowPublisher: Publisher[In], materializer: ActorBasedFlowMaterializer, flowName: String): Unit =
flowPublisher.subscribe(subscriber)
override def isActive: Boolean = true
override def create(materializer: ActorBasedFlowMaterializer, flowName: String): Subscriber[In] = subscriber
}
object OnCompleteDrain {
private val SuccessUnit = Success[Unit](())
}
/**
* When the flow is completed, either through an error or normal
* completion, apply the provided function with [[scala.util.Success]]
* or [[scala.util.Failure]].
*/
final case class OnCompleteDrain[In](callback: Try[Unit] Unit) extends SimpleDrain[In] {
override def attach(flowPublisher: Publisher[In], materializer: ActorBasedFlowMaterializer, flowName: String): Unit =
Source(flowPublisher).transform("onCompleteDrain", () new Transformer[In, Unit] {
override def onNext(in: In) = Nil
override def onError(e: Throwable) = ()
override def onTermination(e: Option[Throwable]) = {
e match {
case None callback(OnCompleteDrain.SuccessUnit)
case Some(e) callback(Failure(e))
}
Nil
}
}).consume()(materializer.withNamePrefix(flowName))
}
/**
* Invoke the given procedure for each received element. The drain holds a [[scala.concurrent.Future]]
* that will be completed with `Success` when reaching the normal end of the stream, or completed
* with `Failure` if there is an error is signaled in the stream.
*/
final case class ForeachDrain[In](f: In Unit) extends DrainWithKey[In, Future[Unit]] {
override def attach(flowPublisher: Publisher[In], materializer: ActorBasedFlowMaterializer, flowName: String): Future[Unit] = {
val promise = Promise[Unit]()
Source(flowPublisher).transform("foreach", () new Transformer[In, Unit] {
override def onNext(in: In) = { f(in); Nil }
override def onError(cause: Throwable): Unit = ()
override def onTermination(e: Option[Throwable]) = {
e match {
case None promise.success(())
case Some(e) promise.failure(e)
}
Nil
}
}).consume()(materializer.withNamePrefix(flowName))
promise.future
}
def future(m: MaterializedDrain): Future[Unit] = m.getDrainFor(this)
}
/**
* Invoke the given function for every received element, giving it its previous
* output (or the given `zero` value) and the element as input. The drain holds a
* [[scala.concurrent.Future]] that will be completed with value of the final
* function evaluation when the input stream ends, or completed with `Failure`
* if there is an error is signaled in the stream.
*/
final case class FoldDrain[U, In](zero: U)(f: (U, In) U) extends DrainWithKey[In, Future[U]] {
override def attach(flowPublisher: Publisher[In], materializer: ActorBasedFlowMaterializer, flowName: String): Future[U] = {
val promise = Promise[U]()
Source(flowPublisher).transform("fold", () new Transformer[In, U] {
var state: U = zero
override def onNext(in: In): immutable.Seq[U] = { state = f(state, in); Nil }
override def onError(cause: Throwable) = ()
override def onTermination(e: Option[Throwable]) = {
e match {
case None promise.success(state)
case Some(e) promise.failure(e)
}
Nil
}
}).consume()(materializer.withNamePrefix(flowName))
promise.future
}
def future(m: MaterializedDrain): Future[U] = m.getDrainFor(this)
}
trait MaterializedDrain {
/**
* Do not call directly. Use accessor method in the concrete `Drain`, e.g. [[PublisherDrain#publisher]].
*/
def getDrainFor[T](drainKey: DrainWithKey[_, T]): T
}
trait DrainOps[-In] extends Drain[In] {
override def toSubscriber()(implicit materializer: FlowMaterializer): Subscriber[In @uncheckedVariance] =
Flow[In].connect(this).toSubscriber()
}

View file

@ -17,7 +17,7 @@ object FlattenStrategy {
* emitting its elements directly to the output until it completes and then taking the next stream. This has the
* consequence that if one of the input stream is infinite, no other streams after that will be consumed from.
*/
def concat[T]: FlattenStrategy[FlowWithSource[_, T], T] = Concat[T]()
def concat[T]: FlattenStrategy[Source[T], T] = Concat[T]()
private[akka] case class Concat[T]() extends FlattenStrategy[FlowWithSource[_, T], T]
}
private[akka] case class Concat[T]() extends FlattenStrategy[Source[T], T]
}

View file

@ -3,69 +3,55 @@
*/
package akka.stream.scaladsl2
import akka.stream.{ TimerTransformer, Transformer, OverflowStrategy }
import scala.collection.immutable
import scala.collection.immutable
import akka.stream.impl2.Ast._
import org.reactivestreams._
import scala.annotation.unchecked.uncheckedVariance
import scala.concurrent.Future
import scala.language.higherKinds
import akka.stream.Transformer
import scala.concurrent.duration.FiniteDuration
import scala.concurrent.duration.Duration
import akka.util.Collections.EmptyImmutableSeq
import akka.stream.TimerTransformer
import akka.stream.OverflowStrategy
import scala.concurrent.Future
import scala.language.higherKinds
/**
* This is the interface from which all concrete Flows inherit. No generic
* operations are presented because the concrete type of Flow (i.e. whether
* it has a [[Source]] or a [[Sink]]) determines what is available.
* A `Flow` is a set of stream processing steps that has one open input and one open output.
*/
sealed trait Flow
trait Flow[-In, +Out] extends FlowOps[Out] {
override type Repr[+O] <: Flow[In, O]
object FlowOps {
private case object TakeWithinTimerKey
private case object DropWithinTimerKey
private case object GroupedWithinTimerKey
/**
* Transform this flow by appending the given processing steps.
*/
def connect[T](flow: Flow[Out, T]): Flow[In, T]
private val takeCompletedTransformer: Transformer[Any, Any] = new Transformer[Any, Any] {
override def onNext(elem: Any) = Nil
override def isComplete = true
}
/**
* Connect this flow to a sink, concatenating the processing steps of both.
*/
def connect(sink: Sink[Out]): Sink[In]
}
private val identityTransformer: Transformer[Any, Any] = new Transformer[Any, Any] {
override def onNext(elem: Any) = List(elem)
}
object Flow {
/**
* Helper to create `Flow` without a [[Source]] or a [[Sink]].
* Example usage: `Flow[Int]`
*/
def apply[T]: Flow[T, T] = Pipe.empty[T]
}
/**
* Scala API: Operations offered by flows with a free output side: the DSL flows left-to-right only.
* Scala API: Operations offered by Flows and Sources with a free output side: the DSL flows left-to-right only.
*/
trait FlowOps[-In, +Out] {
import FlowOps._
type Repr[-I, +O] <: FlowOps[I, O]
// Storing ops in reverse order
protected def andThen[U](op: AstNode): Repr[In, U]
trait FlowOps[+Out] {
type Repr[+O]
/**
* Transform this stream by applying the given function to each of the elements
* as they pass through this processing step.
*/
def map[T](f: Out T): Repr[In, T] =
transform("map", () new Transformer[Out, T] {
override def onNext(in: Out) = List(f(in))
})
def map[T](f: Out T): Repr[T]
/**
* Transform each input element into a sequence of output elements that is
* then flattened into the output stream.
*/
def mapConcat[T](f: Out immutable.Seq[T]): Repr[In, T] =
transform("mapConcat", () new Transformer[Out, T] {
override def onNext(in: Out) = f(in)
})
def mapConcat[T](f: Out immutable.Seq[T]): Repr[T]
/**
* Transform this stream by applying the given function to each of the elements
@ -76,8 +62,7 @@ trait FlowOps[-In, +Out] {
*
* @see [[#mapAsyncUnordered]]
*/
def mapAsync[T](f: Out Future[T]): Repr[In, T] =
andThen(MapAsync(f.asInstanceOf[Any Future[Any]]))
def mapAsync[T](f: Out Future[T]): Repr[T]
/**
* Transform this stream by applying the given function to each of the elements
@ -89,26 +74,19 @@ trait FlowOps[-In, +Out] {
*
* @see [[#mapAsync]]
*/
def mapAsyncUnordered[T](f: Out Future[T]): Repr[In, T] =
andThen(MapAsyncUnordered(f.asInstanceOf[Any Future[Any]]))
def mapAsyncUnordered[T](f: Out Future[T]): Repr[T]
/**
* Only pass on those elements that satisfy the given predicate.
*/
def filter(p: Out Boolean): Repr[In, Out] =
transform("filter", () new Transformer[Out, Out] {
override def onNext(in: Out) = if (p(in)) List(in) else Nil
})
def filter(p: Out Boolean): Repr[Out]
/**
* Transform this stream by applying the given partial function to each of the elements
* on which the function is defined as they pass through this processing step.
* Non-matching elements are filtered out.
*/
def collect[T](pf: PartialFunction[Out, T]): Repr[In, T] =
transform("collect", () new Transformer[Out, T] {
override def onNext(in: Out) = if (pf.isDefinedAt(in)) List(pf(in)) else Nil
})
def collect[T](pf: PartialFunction[Out, T]): Repr[T]
/**
* Chunk up this stream into groups of the given size, with the last group
@ -116,22 +94,7 @@ trait FlowOps[-In, +Out] {
*
* `n` must be positive, otherwise IllegalArgumentException is thrown.
*/
def grouped(n: Int): Repr[In, immutable.Seq[Out]] = {
require(n > 0, "n must be greater than 0")
transform("grouped", () new Transformer[Out, immutable.Seq[Out]] {
var buf: Vector[Out] = Vector.empty
override def onNext(in: Out) = {
buf :+= in
if (buf.size == n) {
val group = buf
buf = Vector.empty
List(group)
} else
Nil
}
override def onTermination(e: Option[Throwable]) = if (buf.isEmpty) Nil else List(buf)
})
}
def grouped(n: Int): Repr[immutable.Seq[Out]]
/**
* Chunk up this stream into groups of elements received within a time window,
@ -143,72 +106,18 @@ trait FlowOps[-In, +Out] {
* `n` must be positive, and `d` must be greater than 0 seconds, otherwise
* IllegalArgumentException is thrown.
*/
def groupedWithin(n: Int, d: FiniteDuration): Repr[In, immutable.Seq[Out]] = {
require(n > 0, "n must be greater than 0")
require(d > Duration.Zero)
timerTransform("groupedWithin", () new TimerTransformer[Out, immutable.Seq[Out]] {
schedulePeriodically(GroupedWithinTimerKey, d)
var buf: Vector[Out] = Vector.empty
override def onNext(in: Out) = {
buf :+= in
if (buf.size == n) {
// start new time window
schedulePeriodically(GroupedWithinTimerKey, d)
emitGroup()
} else Nil
}
override def onTermination(e: Option[Throwable]) = if (buf.isEmpty) Nil else List(buf)
override def onTimer(timerKey: Any) = emitGroup()
private def emitGroup(): immutable.Seq[immutable.Seq[Out]] =
if (buf.isEmpty) EmptyImmutableSeq
else {
val group = buf
buf = Vector.empty
List(group)
}
})
}
def groupedWithin(n: Int, d: FiniteDuration): Repr[immutable.Seq[Out]]
/**
* Discard the given number of elements at the beginning of the stream.
* No elements will be dropped if `n` is zero or negative.
*/
def drop(n: Int): Repr[In, Out] =
transform("drop", () new Transformer[Out, Out] {
var delegate: Transformer[Out, Out] =
if (n <= 0) identityTransformer.asInstanceOf[Transformer[Out, Out]]
else new Transformer[Out, Out] {
var c = n
override def onNext(in: Out) = {
c -= 1
if (c == 0)
delegate = identityTransformer.asInstanceOf[Transformer[Out, Out]]
Nil
}
}
override def onNext(in: Out) = delegate.onNext(in)
})
def drop(n: Int): Repr[Out]
/**
* Discard the elements received within the given duration at beginning of the stream.
*/
def dropWithin(d: FiniteDuration): Repr[In, Out] =
timerTransform("dropWithin", () new TimerTransformer[Out, Out] {
scheduleOnce(DropWithinTimerKey, d)
var delegate: Transformer[Out, Out] =
new Transformer[Out, Out] {
override def onNext(in: Out) = Nil
}
override def onNext(in: Out) = delegate.onNext(in)
override def onTimer(timerKey: Any) = {
delegate = identityTransformer.asInstanceOf[Transformer[Out, Out]]
Nil
}
})
def dropWithin(d: FiniteDuration): Repr[Out]
/**
* Terminate processing (and cancel the upstream publisher) after the given
@ -219,23 +128,7 @@ trait FlowOps[-In, +Out] {
* The stream will be completed without producing any elements if `n` is zero
* or negative.
*/
def take(n: Int): Repr[In, Out] =
transform("take", () new Transformer[Out, Out] {
var delegate: Transformer[Out, Out] =
if (n <= 0) takeCompletedTransformer.asInstanceOf[Transformer[Out, Out]]
else new Transformer[Out, Out] {
var c = n
override def onNext(in: Out) = {
c -= 1
if (c == 0)
delegate = takeCompletedTransformer.asInstanceOf[Transformer[Out, Out]]
List(in)
}
}
override def onNext(in: Out) = delegate.onNext(in)
override def isComplete = delegate.isComplete
})
def take(n: Int): Repr[Out]
/**
* Terminate processing (and cancel the upstream publisher) after the given
@ -246,19 +139,7 @@ trait FlowOps[-In, +Out] {
* Note that this can be combined with [[#take]] to limit the number of elements
* within the duration.
*/
def takeWithin(d: FiniteDuration): Repr[In, Out] =
timerTransform("takeWithin", () new TimerTransformer[Out, Out] {
scheduleOnce(TakeWithinTimerKey, d)
var delegate: Transformer[Out, Out] = identityTransformer.asInstanceOf[Transformer[Out, Out]]
override def onNext(in: Out) = delegate.onNext(in)
override def isComplete = delegate.isComplete
override def onTimer(timerKey: Any) = {
delegate = takeCompletedTransformer.asInstanceOf[Transformer[Out, Out]]
Nil
}
})
def takeWithin(d: FiniteDuration): Repr[Out]
/**
* Allows a faster upstream to progress independently of a slower subscriber by conflating elements into a summary
@ -271,8 +152,7 @@ trait FlowOps[-In, +Out] {
* @param seed Provides the first state for a conflated value using the first unconsumed element as a start
* @param aggregate Takes the currently aggregated value and the current pending element to produce a new aggregate
*/
def conflate[S](seed: Out S, aggregate: (S, Out) S): Repr[In, S] =
andThen(Conflate(seed.asInstanceOf[Any Any], aggregate.asInstanceOf[(Any, Any) Any]))
def conflate[S](seed: Out S, aggregate: (S, Out) S): Repr[S]
/**
* Allows a faster downstream to progress independently of a slower publisher by extrapolating elements from an older
@ -287,8 +167,7 @@ trait FlowOps[-In, +Out] {
* @param extrapolate Takes the current extrapolation state to produce an output element and the next extrapolation
* state.
*/
def expand[S, U](seed: Out S, extrapolate: S (U, S)): Repr[In, U] =
andThen(Expand(seed.asInstanceOf[Any Any], extrapolate.asInstanceOf[Any (Any, Any)]))
def expand[S, U](seed: Out S, extrapolate: S (U, S)): Repr[U]
/**
* Adds a fixed size buffer in the flow that allows to store elements from a faster upstream until it becomes full.
@ -298,10 +177,7 @@ trait FlowOps[-In, +Out] {
* @param size The size of the buffer in element count
* @param overflowStrategy Strategy that is used when incoming elements cannot fit inside the buffer
*/
def buffer(size: Int, overflowStrategy: OverflowStrategy): Repr[In, Out] = {
require(size > 0, s"Buffer size must be larger than zero but was [$size]")
andThen(Buffer(size, overflowStrategy))
}
def buffer(size: Int, overflowStrategy: OverflowStrategy): Repr[Out]
/**
* Generic transformation of a stream: for each element the [[akka.stream.Transformer#onNext]]
@ -325,17 +201,14 @@ trait FlowOps[-In, +Out] {
*
* Note that you can use [[#timerTransform]] if you need support for scheduled events in the transformer.
*/
def transform[T](name: String, mkTransformer: () Transformer[Out, T]): Repr[In, T] = {
andThen(Transform(name, mkTransformer.asInstanceOf[() Transformer[Any, Any]]))
}
def transform[T](name: String, mkTransformer: () Transformer[Out, T]): Repr[T]
/**
* Takes up to `n` elements from the stream and returns a pair containing a strict sequence of the taken element
* and a stream representing the remaining elements. If ''n'' is zero or negative, then this will return a pair
* of an empty collection and a stream containing the whole upstream unchanged.
*/
def prefixAndTail[U >: Out](n: Int): Repr[In, (immutable.Seq[Out], FlowWithSource[U, U])] =
andThen(PrefixAndTail(n))
def prefixAndTail[U >: Out](n: Int): Repr[(immutable.Seq[Out], Source[U])]
/**
* This operation demultiplexes the incoming stream into separate output
@ -348,8 +221,7 @@ trait FlowOps[-In, +Out] {
* care to unblock (or cancel) all of the produced streams even if you want
* to consume only one of them.
*/
def groupBy[K, U >: Out](f: Out K): Repr[In, (K, FlowWithSource[U, U])] =
andThen(GroupBy(f.asInstanceOf[Any Any]))
def groupBy[K, U >: Out](f: Out K): Repr[(K, Source[U])]
/**
* This operation applies the given predicate to all incoming elements and
@ -364,17 +236,13 @@ trait FlowOps[-In, +Out] {
* true, false, false // elements go into third substream
* }}}
*/
def splitWhen[U >: Out](p: Out Boolean): Repr[In, FlowWithSource[U, U]] =
andThen(SplitWhen(p.asInstanceOf[Any Boolean]))
def splitWhen[U >: Out](p: Out Boolean): Repr[Source[U]]
/**
* Transforms a stream of streams into a contiguous stream of elements using the provided flattening strategy.
* This operation can be used on a stream of element type [[StreamWithSource]].
* This operation can be used on a stream of element type [[Source]].
*/
def flatten[U](strategy: FlattenStrategy[Out, U]): Repr[In, U] = strategy match {
case _: FlattenStrategy.Concat[Out] andThen(ConcatAll)
case _ throw new IllegalArgumentException(s"Unsupported flattening strategy [${strategy.getClass.getSimpleName}]")
}
def flatten[U](strategy: FlattenStrategy[Out, U]): Repr[U]
/**
* Transformation of a stream, with additional support for scheduled events.
@ -400,122 +268,19 @@ trait FlowOps[-In, +Out] {
*
* Note that you can use [[#transform]] if you just need to transform elements time plays no role in the transformation.
*/
def timerTransform[U](name: String, mkTransformer: () TimerTransformer[Out, U]): Repr[In, U] =
andThen(TimerTransform(name, mkTransformer.asInstanceOf[() TimerTransformer[Any, Any]]))
}
object ProcessorFlow {
private val emptyInstance = ProcessorFlow[Any, Any](ops = Nil)
def empty[T]: ProcessorFlow[T, T] = emptyInstance.asInstanceOf[ProcessorFlow[T, T]]
}
/**
* Flow without attached input and without attached output, can be used as a `Processor`.
*/
final case class ProcessorFlow[-In, +Out](ops: List[AstNode]) extends FlowOps[In, Out] {
override type Repr[-I, +O] = ProcessorFlow[I, O]
override protected def andThen[U](op: AstNode): Repr[In, U] = this.copy(ops = op :: ops)
def withSink(out: Sink[Out]): FlowWithSink[In, Out] = FlowWithSink(out, ops)
def withSource(in: Source[In]): FlowWithSource[In, Out] = FlowWithSource(in, ops)
def prepend[T](f: ProcessorFlow[T, In]): ProcessorFlow[T, Out] = ProcessorFlow(ops ::: f.ops)
def prepend[T](f: FlowWithSource[T, In]): FlowWithSource[T, Out] = f.append(this)
def append[T](f: ProcessorFlow[Out, T]): ProcessorFlow[In, T] = ProcessorFlow(f.ops ++: ops)
def append[T](f: FlowWithSink[Out, T]): FlowWithSink[In, T] = f.prepend(this)
}
/**
* Flow with attached output, can be used as a `Subscriber`.
*/
final case class FlowWithSink[-In, +Out](private[scaladsl2] val output: Sink[Out @uncheckedVariance], ops: List[AstNode]) {
def withSource(in: Source[In]): RunnableFlow[In, Out] = RunnableFlow(in, output, ops)
def withoutSink: ProcessorFlow[In, Out] = ProcessorFlow(ops)
def prepend[T](f: ProcessorFlow[T, In]): FlowWithSink[T, Out] = FlowWithSink(output, ops ::: f.ops)
def prepend[T](f: FlowWithSource[T, In]): RunnableFlow[T, Out] = RunnableFlow(f.input, output, ops ::: f.ops)
def toSubscriber()(implicit materializer: FlowMaterializer): Subscriber[In @uncheckedVariance] = {
val subIn = SubscriberSource[In]()
val mf = withSource(subIn).run()
subIn.subscriber(mf)
}
}
/**
* Flow with attached input, can be used as a `Publisher`.
*/
final case class FlowWithSource[-In, +Out](private[scaladsl2] val input: Source[In @uncheckedVariance], ops: List[AstNode]) extends FlowOps[In, Out] {
override type Repr[-I, +O] = FlowWithSource[I, O]
override protected def andThen[U](op: AstNode): Repr[In, U] = this.copy(ops = op :: ops)
def withSink(out: Sink[Out]): RunnableFlow[In, Out] = RunnableFlow(input, out, ops)
def withoutSource: ProcessorFlow[In, Out] = ProcessorFlow(ops)
def append[T](f: ProcessorFlow[Out, T]): FlowWithSource[In, T] = FlowWithSource(input, f.ops ++: ops)
def append[T](f: FlowWithSink[Out, T]): RunnableFlow[In, T] = RunnableFlow(input, f.output, f.ops ++: ops)
def toPublisher()(implicit materializer: FlowMaterializer): Publisher[Out @uncheckedVariance] = {
val pubOut = PublisherSink[Out]
val mf = withSink(pubOut).run()
pubOut.publisher(mf)
}
def toFanoutPublisher(initialBufferSize: Int, maximumBufferSize: Int)(implicit materializer: FlowMaterializer): Publisher[Out @uncheckedVariance] = {
val pubOut = PublisherSink.withFanout[Out](initialBufferSize, maximumBufferSize)
val mf = withSink(pubOut).run()
pubOut.publisher(mf)
}
def publishTo(subscriber: Subscriber[Out @uncheckedVariance])(implicit materializer: FlowMaterializer): Unit =
toPublisher().subscribe(subscriber)
def consume()(implicit materializer: FlowMaterializer): Unit =
withSink(BlackholeSink).run()
def timerTransform[U](name: String, mkTransformer: () TimerTransformer[Out, U]): Repr[U]
}
/**
* Flow with attached input and output, can be executed.
*/
final case class RunnableFlow[-In, +Out](private[scaladsl2] val input: Source[In @uncheckedVariance],
private[scaladsl2] val output: Sink[Out @uncheckedVariance], ops: List[AstNode]) extends Flow {
def withoutSink: FlowWithSource[In, Out] = FlowWithSource(input, ops)
def withoutSource: FlowWithSink[In, Out] = FlowWithSink(output, ops)
def run()(implicit materializer: FlowMaterializer): MaterializedFlow =
materializer.materialize(input, output, ops)
trait RunnableFlow {
def run()(implicit materializer: FlowMaterializer): MaterializedFlow
}
/**
* Returned by [[RunnableFlow#run]] and can be used as parameter to the
* accessor method to retrieve the materialized `Source` or `Sink`, e.g.
* [[SubscriberSource#subscriber]] or [[PublisherSink#publisher]].
* accessor method to retrieve the materialized `Tap` or `Drain`, e.g.
* [[SubscriberTap#subscriber]] or [[PublisherDrain#publisher]].
*/
class MaterializedFlow(sourceKey: AnyRef, matSource: Any, sinkKey: AnyRef, matSink: Any) extends MaterializedSource with MaterializedSink {
/**
* Do not call directly. Use accessor method in the concrete `Source`, e.g. [[SubscriberSource#subscriber]].
*/
override def getSourceFor[T](key: SourceWithKey[_, T]): T =
if (key == sourceKey) matSource.asInstanceOf[T]
else throw new IllegalArgumentException(s"Source key [$key] doesn't match the source [$sourceKey] of this flow")
/**
* Do not call directly. Use accessor method in the concrete `Sink`, e.g. [[PublisherSink#publisher]].
*/
def getSinkFor[T](key: SinkWithKey[_, T]): T =
if (key == sinkKey) matSink.asInstanceOf[T]
else throw new IllegalArgumentException(s"Sink key [$key] doesn't match the sink [$sinkKey] of this flow")
}
trait MaterializedSource {
def getSourceFor[T](sourceKey: SourceWithKey[_, T]): T
}
trait MaterializedSink {
def getSinkFor[T](sinkKey: SinkWithKey[_, T]): T
}
trait MaterializedFlow extends MaterializedTap with MaterializedDrain

View file

@ -4,11 +4,10 @@
package akka.stream.scaladsl2
import scala.language.existentials
import scalax.collection.edge.LkDiEdge
import scalax.collection.edge.{ LkBase, LkDiEdge }
import scalax.collection.mutable.Graph
import scalax.collection.immutable.{ Graph ImmutableGraph }
import org.reactivestreams.Subscriber
import akka.stream.impl.BlackholeSubscriber
import org.reactivestreams.Publisher
import akka.stream.impl2.Ast
@ -73,8 +72,8 @@ object Merge {
* Merge several streams, taking elements as they arrive from input streams
* (picking randomly when several have elements ready).
*
* When building the [[FlowGraph]] you must connect one or more input flows/sources
* and one output flow/sink to the `Merge` vertex.
* When building the [[FlowGraph]] you must connect one or more input pipes/taps
* and one output pipe/drain to the `Merge` vertex.
*/
final class Merge[T](override val name: Option[String]) extends FlowGraphInternal.InternalVertex with Junction[T] {
override private[akka] val vertex = this
@ -259,82 +258,83 @@ final class Concat[T](override val name: Option[String]) extends FlowGraphIntern
override private[akka] def astNode = Ast.Concat
}
object UndefinedSink {
object UndefinedDrain {
/**
* Create a new anonymous `UndefinedSink` vertex with the specified input type.
* Note that a `UndefinedSink` instance can only be used at one place (one vertex)
* Create a new anonymous `UndefinedDrain` vertex with the specified input type.
* Note that a `UndefinedDrain` instance can only be used at one place (one vertex)
* in the `FlowGraph`. This method creates a new instance every time it
* is called and those instances are not `equal`.
*/
def apply[T]: UndefinedSink[T] = new UndefinedSink[T](None)
def apply[T]: UndefinedDrain[T] = new UndefinedDrain[T](None)
/**
* Create a named `UndefinedSink` vertex with the specified input type.
* Note that a `UndefinedSink` with a specific name can only be used at one place (one vertex)
* Create a named `UndefinedDrain` vertex with the specified input type.
* Note that a `UndefinedDrain` with a specific name can only be used at one place (one vertex)
* in the `FlowGraph`. Calling this method several times with the same name
* returns instances that are `equal`.
*/
def apply[T](name: String): UndefinedSink[T] = new UndefinedSink[T](Some(name))
def apply[T](name: String): UndefinedDrain[T] = new UndefinedDrain[T](Some(name))
}
/**
* It is possible to define a [[PartialFlowGraph]] with output flows that are not connected
* yet by using this placeholder instead of the real [[Sink]]. Later the placeholder can
* be replaced with [[FlowGraphBuilder#attachSink]].
* It is possible to define a [[PartialFlowGraph]] with output pipes that are not connected
* yet by using this placeholder instead of the real [[Drain]]. Later the placeholder can
* be replaced with [[FlowGraphBuilder#attachDrain]].
*/
final class UndefinedSink[-T](override val name: Option[String]) extends FlowGraphInternal.InternalVertex {
final class UndefinedDrain[-T](override val name: Option[String]) extends FlowGraphInternal.InternalVertex {
override def minimumInputCount: Int = 1
override def maximumInputCount: Int = 1
override def minimumOutputCount: Int = 0
override def maximumOutputCount: Int = 0
override private[akka] def astNode = throw new UnsupportedOperationException("Undefined sinks cannot be materialized")
override private[akka] def astNode = throw new UnsupportedOperationException("Undefined drains cannot be materialized")
}
object UndefinedSource {
object UndefinedTap {
/**
* Create a new anonymous `UndefinedSource` vertex with the specified input type.
* Note that a `UndefinedSource` instance can only be used at one place (one vertex)
* Create a new anonymous `UndefinedTap` vertex with the specified input type.
* Note that a `UndefinedTap` instance can only be used at one place (one vertex)
* in the `FlowGraph`. This method creates a new instance every time it
* is called and those instances are not `equal`.
*/
def apply[T]: UndefinedSource[T] = new UndefinedSource[T](None)
def apply[T]: UndefinedTap[T] = new UndefinedTap[T](None)
/**
* Create a named `UndefinedSource` vertex with the specified output type.
* Note that a `UndefinedSource` with a specific name can only be used at one place (one vertex)
* Create a named `UndefinedTap` vertex with the specified output type.
* Note that a `UndefinedTap` with a specific name can only be used at one place (one vertex)
* in the `FlowGraph`. Calling this method several times with the same name
* returns instances that are `equal`.
*/
def apply[T](name: String): UndefinedSource[T] = new UndefinedSource[T](Some(name))
def apply[T](name: String): UndefinedTap[T] = new UndefinedTap[T](Some(name))
}
/**
* It is possible to define a [[PartialFlowGraph]] with input flows that are not connected
* yet by using this placeholder instead of the real [[Source]]. Later the placeholder can
* be replaced with [[FlowGraphBuilder#attachSource]].
* It is possible to define a [[PartialFlowGraph]] with input pipes that are not connected
* yet by using this placeholder instead of the real [[Tap]]. Later the placeholder can
* be replaced with [[FlowGraphBuilder#attachTap]].
*/
final class UndefinedSource[+T](override val name: Option[String]) extends FlowGraphInternal.InternalVertex {
final class UndefinedTap[+T](override val name: Option[String]) extends FlowGraphInternal.InternalVertex {
override def minimumInputCount: Int = 0
override def maximumInputCount: Int = 0
override def minimumOutputCount: Int = 1
override def maximumOutputCount: Int = 1
override private[akka] def astNode = throw new UnsupportedOperationException("Undefined sources cannot be materialized")
override private[akka] def astNode = throw new UnsupportedOperationException("Undefined taps cannot be materialized")
}
/**
* INTERNAL API
*/
private[akka] object FlowGraphInternal {
val OnlyPipesErrorMessage = "Only pipes are supported currently!"
def UnlabeledPort = -1
sealed trait Vertex
case class SourceVertex(source: Source[_]) extends Vertex {
override def toString = source.toString
case class TapVertex(tap: Tap[_]) extends Vertex {
override def toString = tap.toString
// these are unique keys, case class equality would break them
final override def equals(other: Any): Boolean = super.equals(other)
final override def hashCode: Int = super.hashCode
}
case class SinkVertex(sink: Sink[_]) extends Vertex {
override def toString = sink.toString
case class DrainVertex(drain: Drain[_]) extends Vertex {
override def toString = drain.toString
// these are unique keys, case class equality would break them
final override def equals(other: Any): Boolean = super.equals(other)
final override def hashCode: Int = super.hashCode
@ -370,107 +370,159 @@ private[akka] object FlowGraphInternal {
// flow not part of equals/hashCode
case class EdgeLabel(qualifier: Int)(
val flow: ProcessorFlow[Any, Any],
val pipe: Pipe[Any, Nothing],
val inputPort: Int,
val outputPort: Int) {
override def toString: String = flow.toString
override def toString: String = pipe.toString
}
type EdgeType[T] = LkDiEdge[T] { type L1 = EdgeLabel }
}
/**
* Builder of [[FlowGraph]] and [[PartialFlowGraph]].
* Syntactic sugar is provided by [[FlowGraphImplicits]].
*/
class FlowGraphBuilder private (graph: Graph[FlowGraphInternal.Vertex, LkDiEdge]) {
class FlowGraphBuilder private (graph: Graph[FlowGraphInternal.Vertex, FlowGraphInternal.EdgeType]) {
import FlowGraphInternal._
private[akka] def this() = this(Graph.empty[FlowGraphInternal.Vertex, LkDiEdge])
private[akka] def this() = this(Graph.empty[FlowGraphInternal.Vertex, FlowGraphInternal.EdgeType])
private[akka] def this(immutableGraph: ImmutableGraph[FlowGraphInternal.Vertex, LkDiEdge]) =
this(Graph.from(edges = immutableGraph.edges.map(e LkDiEdge(e.from.value, e.to.value)(e.label)).toIterable))
private[akka] def this(immutableGraph: ImmutableGraph[FlowGraphInternal.Vertex, FlowGraphInternal.EdgeType]) =
this({
val edges: Iterable[FlowGraphInternal.EdgeType[FlowGraphInternal.Vertex]] =
immutableGraph.edges.map(e LkDiEdge(e.from.value, e.to.value)(e.label))
Graph.from(edges = edges)
})
private implicit val edgeFactory = scalax.collection.edge.LkDiEdge
private implicit val edgeFactory = scalax.collection.edge.LkDiEdge.asInstanceOf[LkBase.LkEdgeCompanion[EdgeType]]
var edgeQualifier = graph.edges.size
private var cyclesAllowed = false
def addEdge[In, Out](source: Source[In], flow: ProcessorFlow[In, Out], sink: JunctionInPort[Out]): this.type = {
val sourceVertex = SourceVertex(source)
checkAddSourceSinkPrecondition(sourceVertex)
checkJunctionInPortPrecondition(sink)
addGraphEdge(sourceVertex, sink.vertex, flow, inputPort = sink.port, outputPort = UnlabeledPort)
private def addTapPipeEdge[In, Out](tap: Tap[In], pipe: Pipe[In, Out], drain: JunctionInPort[Out]): this.type = {
val tapVertex = TapVertex(tap)
checkAddTapDrainPrecondition(tapVertex)
checkJunctionInPortPrecondition(drain)
addGraphEdge(tapVertex, drain.vertex, pipe, inputPort = drain.port, outputPort = UnlabeledPort)
this
}
def addEdge[In, Out](source: UndefinedSource[In], flow: ProcessorFlow[In, Out], sink: JunctionInPort[Out]): this.type = {
checkAddSourceSinkPrecondition(source)
checkJunctionInPortPrecondition(sink)
addGraphEdge(source, sink.vertex, flow, inputPort = sink.port, outputPort = UnlabeledPort)
private def addPipeDrainEdge[In, Out](tap: JunctionOutPort[In], pipe: Pipe[In, Out], drain: Drain[Out]): this.type = {
val drainVertex = DrainVertex(drain)
checkAddTapDrainPrecondition(drainVertex)
checkJunctionOutPortPrecondition(tap)
addGraphEdge(tap.vertex, drainVertex, pipe, inputPort = UnlabeledPort, outputPort = tap.port)
this
}
def addEdge[In, Out](source: JunctionOutPort[In], flow: ProcessorFlow[In, Out], sink: Sink[Out]): this.type = {
val sinkVertex = SinkVertex(sink)
checkAddSourceSinkPrecondition(sinkVertex)
checkJunctionOutPortPrecondition(source)
addGraphEdge(source.vertex, sinkVertex, flow, inputPort = UnlabeledPort, outputPort = source.port)
this
}
def addEdge[In, Out](source: JunctionOutPort[In], flow: ProcessorFlow[In, Out], sink: UndefinedSink[Out]): this.type = {
checkAddSourceSinkPrecondition(sink)
checkJunctionOutPortPrecondition(source)
addGraphEdge(source.vertex, sink, flow, inputPort = UnlabeledPort, outputPort = source.port)
this
}
def addEdge[In, Out](source: JunctionOutPort[In], flow: ProcessorFlow[In, Out], sink: JunctionInPort[Out]): this.type = {
checkJunctionOutPortPrecondition(source)
checkJunctionInPortPrecondition(sink)
addGraphEdge(source.vertex, sink.vertex, flow, inputPort = sink.port, outputPort = source.port)
this
}
def addEdge[In, Out](flow: FlowWithSource[In, Out], sink: JunctionInPort[Out]): this.type = {
addEdge(flow.input, flow.withoutSource, sink)
this
}
def addEdge[In, Out](source: JunctionOutPort[In], flow: FlowWithSink[In, Out]): this.type = {
addEdge(source, flow.withoutSink, flow.output)
this
}
private def addGraphEdge[In, Out](from: Vertex, to: Vertex, flow: ProcessorFlow[In, Out], inputPort: Int, outputPort: Int): Unit = {
if (edgeQualifier == Int.MaxValue) throw new IllegalArgumentException(s"Too many edges")
val label = EdgeLabel(edgeQualifier)(flow.asInstanceOf[ProcessorFlow[Any, Any]], inputPort, outputPort)
graph.addLEdge(from, to)(label)
edgeQualifier += 1
}
def attachSink[Out](token: UndefinedSink[Out], sink: Sink[Out]): this.type = {
graph.find(token) match {
case Some(existing)
require(existing.value.isInstanceOf[UndefinedSink[_]], s"Flow already attached to a sink [${existing.value}]")
val edge = existing.incoming.head
graph.remove(existing)
graph.addLEdge(edge.from.value, SinkVertex(sink))(edge.label)
case None throw new IllegalArgumentException(s"No matching UndefinedSink [${token}]")
def addEdge[In, Out](tap: UndefinedTap[In], flow: Flow[In, Out], drain: JunctionInPort[Out]): this.type = {
checkAddTapDrainPrecondition(tap)
checkJunctionInPortPrecondition(drain)
flow match {
case pipe: Pipe[In, Out]
addGraphEdge(tap, drain.vertex, pipe, inputPort = drain.port, outputPort = UnlabeledPort)
case _
throw new IllegalArgumentException(OnlyPipesErrorMessage)
}
this
}
def attachSource[In](token: UndefinedSource[In], source: Source[In]): this.type = {
def addEdge[In, Out](tap: JunctionOutPort[In], flow: Flow[In, Out], drain: UndefinedDrain[Out]): this.type = {
checkAddTapDrainPrecondition(drain)
checkJunctionOutPortPrecondition(tap)
flow match {
case pipe: Pipe[In, Out]
addGraphEdge(tap.vertex, drain, pipe, inputPort = UnlabeledPort, outputPort = tap.port)
case _
throw new IllegalArgumentException(OnlyPipesErrorMessage)
}
this
}
def addEdge[In, Out](tap: JunctionOutPort[In], flow: Flow[In, Out], drain: JunctionInPort[Out]): this.type = {
checkJunctionOutPortPrecondition(tap)
checkJunctionInPortPrecondition(drain)
flow match {
case pipe: Pipe[In, Out]
addGraphEdge(tap.vertex, drain.vertex, pipe, inputPort = drain.port, outputPort = tap.port)
case _
throw new IllegalArgumentException(OnlyPipesErrorMessage)
}
this
}
def addEdge[In, Out](source: Source[In], flow: Flow[In, Out], drain: JunctionInPort[Out]): this.type = {
(source, flow) match {
case (tap: Tap[In], pipe: Pipe[In, Out])
addTapPipeEdge(tap, pipe, drain)
case (spipe: SourcePipe[In], pipe: Pipe[In, Out])
addEdge(spipe.connect(pipe), drain)
case _
throw new IllegalArgumentException(OnlyPipesErrorMessage)
}
this
}
def addEdge[Out](source: Source[Out], drain: JunctionInPort[Out]): this.type = {
source match {
case tap: Tap[Out]
addTapPipeEdge(tap, Pipe.empty[Out], drain)
case pipe: SourcePipe[Out]
addTapPipeEdge(pipe.input, Pipe(pipe.ops), drain)
case _
throw new IllegalArgumentException(OnlyPipesErrorMessage)
}
this
}
def addEdge[In, Out](tap: JunctionOutPort[In], sink: Sink[In]): this.type = {
sink match {
case drain: Drain[In] addPipeDrainEdge(tap, Pipe.empty[In], drain)
case pipe: SinkPipe[In] addPipeDrainEdge(tap, Pipe(pipe.ops), pipe.output)
case _ throw new IllegalArgumentException(OnlyPipesErrorMessage)
}
this
}
def addEdge[In, Out](tap: JunctionOutPort[In], flow: Flow[In, Out], sink: Sink[Out]): this.type = {
(flow, sink) match {
case (pipe: Pipe[In, Out], drain: Drain[Out])
addPipeDrainEdge(tap, pipe, drain)
case (pipe: Pipe[In, Out], spipe: SinkPipe[Out])
addEdge(tap, pipe.connect(spipe))
case _ throw new IllegalArgumentException(OnlyPipesErrorMessage)
}
this
}
private def addGraphEdge[In, Out](from: Vertex, to: Vertex, pipe: Pipe[In, Out], inputPort: Int, outputPort: Int): Unit = {
if (edgeQualifier == Int.MaxValue) throw new IllegalArgumentException(s"Too many edges")
val label = EdgeLabel(edgeQualifier)(pipe.asInstanceOf[Pipe[Any, Nothing]], inputPort, outputPort)
graph.addLEdge(from, to)(label)
edgeQualifier += 1
}
def attachDrain[Out](token: UndefinedDrain[Out], drain: Drain[Out]): this.type = {
graph.find(token) match {
case Some(existing)
val edge = existing.incoming.head
graph.remove(existing)
graph.addLEdge(edge.from.value, DrainVertex(drain))(edge.label)
case None throw new IllegalArgumentException(s"No matching UndefinedDrain [${token}]")
}
this
}
def attachTap[In](token: UndefinedTap[In], tap: Tap[In]): this.type = {
graph.find(token) match {
case Some(existing)
require(existing.value.isInstanceOf[UndefinedSource[_]], s"Flow already attached to a source [${existing.value}]")
val edge = existing.outgoing.head
graph.remove(existing)
graph.addLEdge(SourceVertex(source), edge.to.value)(edge.label)
case None throw new IllegalArgumentException(s"No matching UndefinedSource [${token}]")
graph.addLEdge(TapVertex(tap), edge.to.value)(edge.label)
case None throw new IllegalArgumentException(s"No matching UndefinedTap [${token}]")
}
this
}
@ -485,7 +537,7 @@ class FlowGraphBuilder private (graph: Graph[FlowGraphInternal.Vertex, LkDiEdge]
cyclesAllowed = true
}
private def checkAddSourceSinkPrecondition(node: Vertex): Unit =
private def checkAddTapDrainPrecondition(node: Vertex): Unit =
require(graph.find(node) == None, s"[$node] instance is already used in this flow graph")
private def checkJunctionInPortPrecondition(junction: JunctionInPort[_]): Unit = {
@ -534,8 +586,10 @@ class FlowGraphBuilder private (graph: Graph[FlowGraphInternal.Vertex, LkDiEdge]
}
//convert it to an immutable.Graph
private def immutableGraph(): ImmutableGraph[Vertex, LkDiEdge] =
ImmutableGraph.from(edges = graph.edges.map(e LkDiEdge(e.from.value, e.to.value)(e.label)).toIterable)
private def immutableGraph(): ImmutableGraph[Vertex, FlowGraphInternal.EdgeType] = {
val edges = graph.edges.map(e LkDiEdge(e.from.value, e.to.value)(e.label))
ImmutableGraph.from(edges = edges: Iterable[FlowGraphInternal.EdgeType[FlowGraphInternal.Vertex]])
}
private def checkPartialBuildPreconditions(): Unit = {
if (!cyclesAllowed) graph.findCycle match {
@ -545,18 +599,18 @@ class FlowGraphBuilder private (graph: Graph[FlowGraphInternal.Vertex, LkDiEdge]
}
private def checkBuildPreconditions(): Unit = {
val undefinedSourcesSinks = graph.nodes.filter {
val undefinedTapsDrains = graph.nodes.filter {
_.value match {
case _: UndefinedSource[_] | _: UndefinedSink[_] true
case x false
case _: UndefinedTap[_] | _: UndefinedDrain[_] true
case x false
}
}
if (undefinedSourcesSinks.nonEmpty) {
val formatted = undefinedSourcesSinks.map(n n.value match {
case u: UndefinedSource[_] s"$u -> ${n.outgoing.head.label} -> ${n.outgoing.head.to}"
case u: UndefinedSink[_] s"${n.incoming.head.from} -> ${n.incoming.head.label} -> $u"
if (undefinedTapsDrains.nonEmpty) {
val formatted = undefinedTapsDrains.map(n n.value match {
case u: UndefinedTap[_] s"$u -> ${n.outgoing.head.label} -> ${n.outgoing.head.to}"
case u: UndefinedDrain[_] s"${n.incoming.head.from} -> ${n.incoming.head.label} -> $u"
})
throw new IllegalArgumentException("Undefined sources or sinks: " + formatted.mkString(", "))
throw new IllegalArgumentException("Undefined taps or drains: " + formatted.mkString(", "))
}
graph.nodes.foreach { node
@ -580,9 +634,9 @@ class FlowGraphBuilder private (graph: Graph[FlowGraphInternal.Vertex, LkDiEdge]
require(graph.nonEmpty, "Graph must not be empty")
require(graph.exists(graph having ((node = { n n.isLeaf && n.diSuccessors.isEmpty }))),
"Graph must have at least one sink")
"Graph must have at least one drain")
require(graph.exists(graph having ((node = { n n.isLeaf && n.diPredecessors.isEmpty }))),
"Graph must have at least one source")
"Graph must have at least one tap")
require(graph.isConnected, "Graph must be connected")
}
@ -600,12 +654,12 @@ object FlowGraph {
* Build a [[FlowGraph]] from scratch.
*/
def apply(block: FlowGraphBuilder Unit): FlowGraph =
apply(ImmutableGraph.empty[FlowGraphInternal.Vertex, LkDiEdge])(block)
apply(ImmutableGraph.empty[FlowGraphInternal.Vertex, FlowGraphInternal.EdgeType])(block)
/**
* Continue building a [[FlowGraph]] from an existing `PartialFlowGraph`.
* For example you can attach undefined sources and sinks with
* [[FlowGraphBuilder#attachSource]] and [[FlowGraphBuilder#attachSink]]
* For example you can attach undefined taps and drains with
* [[FlowGraphBuilder#attachTap]] and [[FlowGraphBuilder#attachDrain]]
*/
def apply(partialFlowGraph: PartialFlowGraph)(block: FlowGraphBuilder Unit): FlowGraph =
apply(partialFlowGraph.graph)(block)
@ -617,7 +671,7 @@ object FlowGraph {
def apply(flowGraph: FlowGraph)(block: FlowGraphBuilder Unit): FlowGraph =
apply(flowGraph.graph)(block)
private def apply(graph: ImmutableGraph[FlowGraphInternal.Vertex, LkDiEdge])(block: FlowGraphBuilder Unit): FlowGraph = {
private def apply(graph: ImmutableGraph[FlowGraphInternal.Vertex, FlowGraphInternal.EdgeType])(block: FlowGraphBuilder Unit): FlowGraph = {
val builder = new FlowGraphBuilder(graph)
block(builder)
builder.build()
@ -630,23 +684,23 @@ object FlowGraph {
* Build a `FlowGraph` by starting with one of the `apply` methods in
* in [[FlowGraph$ companion object]]. Syntactic sugar is provided by [[FlowGraphImplicits]].
*/
class FlowGraph private[akka] (private[akka] val graph: ImmutableGraph[FlowGraphInternal.Vertex, LkDiEdge]) {
class FlowGraph private[akka] (private[akka] val graph: ImmutableGraph[FlowGraphInternal.Vertex, FlowGraphInternal.EdgeType]) {
import FlowGraphInternal._
/**
* Materialize the `FlowGraph` and attach all sinks and sources.
* Materialize the `FlowGraph` and attach all drains and taps.
*/
def run()(implicit materializer: FlowMaterializer): MaterializedFlowGraph = {
def run()(implicit materializer: FlowMaterializer): MaterializedPipeGraph = {
import scalax.collection.GraphTraversal._
// start with sinks
// start with drains
val startingNodes = graph.nodes.filter(n n.isLeaf && n.diSuccessors.isEmpty)
case class Memo(visited: Set[graph.EdgeT] = Set.empty,
downstreamSubscriber: Map[graph.EdgeT, Subscriber[Any]] = Map.empty,
upstreamPublishers: Map[graph.EdgeT, Publisher[Any]] = Map.empty,
sources: Map[SourceVertex, FlowWithSink[Any, Any]] = Map.empty,
materializedSinks: Map[SinkWithKey[_, _], Any] = Map.empty)
taps: Map[TapVertex, SinkPipe[Any]] = Map.empty,
materializedDrains: Map[DrainWithKey[_, _], Any] = Map.empty)
val result = startingNodes.foldLeft(Memo()) {
case (memo, start)
@ -658,38 +712,38 @@ class FlowGraph private[akka] (private[akka] val graph: ImmutableGraph[FlowGraph
if (memo.visited(edge)) {
memo
} else {
val flow = edge.label.asInstanceOf[EdgeLabel].flow
val pipe = edge.label.pipe
// returns the materialized sink, if any
def connectToDownstream(publisher: Publisher[Any]): Option[(SinkWithKey[_, _], Any)] = {
val f = flow.withSource(PublisherSource(publisher))
// returns the materialized drain, if any
def connectToDownstream(publisher: Publisher[Any]): Option[(DrainWithKey[_, _], Any)] = {
val f = pipe.withTap(PublisherTap(publisher))
edge.to.value match {
case SinkVertex(sink: SinkWithKey[_, _])
val mf = f.withSink(sink.asInstanceOf[Sink[Any]]).run()
Some(sink -> mf.getSinkFor(sink))
case SinkVertex(sink)
f.withSink(sink.asInstanceOf[Sink[Any]]).run()
case DrainVertex(drain: DrainWithKey[_, _])
val mf = f.withDrain(drain).run()
Some(drain -> mf.getDrainFor(drain))
case DrainVertex(drain)
f.withDrain(drain).run()
None
case _
f.withSink(SubscriberSink(memo.downstreamSubscriber(edge))).run()
f.withDrain(SubscriberDrain(memo.downstreamSubscriber(edge))).run()
None
}
}
edge.from.value match {
case src: SourceVertex
val f = flow.withSink(SubscriberSink(memo.downstreamSubscriber(edge)))
// connect the source with the flow later
case src: TapVertex
val f = pipe.withDrain(SubscriberDrain(memo.downstreamSubscriber(edge)))
// connect the tap with the pipe later
memo.copy(visited = memo.visited + edge,
sources = memo.sources.updated(src, f))
taps = memo.taps.updated(src, f))
case v: InternalVertex
if (memo.upstreamPublishers.contains(edge)) {
// vertex already materialized
val materializedSink = connectToDownstream(memo.upstreamPublishers(edge))
val materializedDrain = connectToDownstream(memo.upstreamPublishers(edge))
memo.copy(
visited = memo.visited + edge,
materializedSinks = memo.materializedSinks ++ materializedSink)
materializedDrains = memo.materializedDrains ++ materializedDrain)
} else {
val op = v.astNode
@ -697,16 +751,16 @@ class FlowGraph private[akka] (private[akka] val graph: ImmutableGraph[FlowGraph
materializer.materializeJunction[Any, Any](op, edge.from.inDegree, edge.from.outDegree)
// TODO: Check for gaps in port numbers
val edgeSubscribers =
edge.from.incoming.toSeq.sortBy(_.label.asInstanceOf[EdgeLabel].inputPort).zip(subscribers)
edge.from.incoming.toSeq.sortBy(_.label.inputPort).zip(subscribers)
val edgePublishers =
edge.from.outgoing.toSeq.sortBy(_.label.asInstanceOf[EdgeLabel].outputPort).zip(publishers).toMap
edge.from.outgoing.toSeq.sortBy(_.label.outputPort).zip(publishers).toMap
val publisher = edgePublishers(edge)
val materializedSink = connectToDownstream(publisher)
val materializedDrain = connectToDownstream(publisher)
memo.copy(
visited = memo.visited + edge,
downstreamSubscriber = memo.downstreamSubscriber ++ edgeSubscribers,
upstreamPublishers = memo.upstreamPublishers ++ edgePublishers,
materializedSinks = memo.materializedSinks ++ materializedSink)
materializedDrains = memo.materializedDrains ++ materializedDrain)
}
}
@ -716,17 +770,17 @@ class FlowGraph private[akka] (private[akka] val graph: ImmutableGraph[FlowGraph
}
// connect all input sources as the last thing
val materializedSources = result.sources.foldLeft(Map.empty[SourceWithKey[_, _], Any]) {
case (acc, (SourceVertex(src), flow))
val mf = flow.withSource(src).run()
// connect all input taps as the last thing
val materializedTaps = result.taps.foldLeft(Map.empty[TapWithKey[_, _], Any]) {
case (acc, (TapVertex(src), pipe))
val mf = pipe.withTap(src).run()
src match {
case srcKey: SourceWithKey[_, _] acc.updated(srcKey, mf.getSourceFor(srcKey))
case _ acc
case srcKey: TapWithKey[_, _] acc.updated(srcKey, mf.getTapFor(srcKey))
case _ acc
}
}
new MaterializedFlowGraph(materializedSources, result.materializedSinks)
new MaterializedPipeGraph(materializedTaps, result.materializedDrains)
}
}
@ -742,7 +796,7 @@ object PartialFlowGraph {
* Build a [[PartialFlowGraph]] from scratch.
*/
def apply(block: FlowGraphBuilder Unit): PartialFlowGraph =
apply(ImmutableGraph.empty[FlowGraphInternal.Vertex, LkDiEdge])(block)
apply(ImmutableGraph.empty[FlowGraphInternal.Vertex, FlowGraphInternal.EdgeType])(block)
/**
* Continue building a [[PartialFlowGraph]] from an existing `PartialFlowGraph`.
@ -756,7 +810,7 @@ object PartialFlowGraph {
def apply(flowGraph: FlowGraph)(block: FlowGraphBuilder Unit): PartialFlowGraph =
apply(flowGraph.graph)(block)
private def apply(graph: ImmutableGraph[FlowGraphInternal.Vertex, LkDiEdge])(block: FlowGraphBuilder Unit): PartialFlowGraph = {
private def apply(graph: ImmutableGraph[FlowGraphInternal.Vertex, FlowGraphInternal.EdgeType])(block: FlowGraphBuilder Unit): PartialFlowGraph = {
val builder = new FlowGraphBuilder(graph)
block(builder)
builder.partialBuild()
@ -765,53 +819,53 @@ object PartialFlowGraph {
}
/**
* `PartialFlowGraph` may have sources and sinks that are not attached, and it can therefore not
* `PartialFlowGraph` may have taps and drains that are not attached, and it can therefore not
* be `run` until those are attached.
*
* Build a `PartialFlowGraph` by starting with one of the `apply` methods in
* in [[FlowGraph$ companion object]]. Syntactic sugar is provided by [[FlowGraphImplicits]].
*/
class PartialFlowGraph private[akka] (private[akka] val graph: ImmutableGraph[FlowGraphInternal.Vertex, LkDiEdge]) {
class PartialFlowGraph private[akka] (private[akka] val graph: ImmutableGraph[FlowGraphInternal.Vertex, FlowGraphInternal.EdgeType]) {
import FlowGraphInternal._
def undefinedSources: Set[UndefinedSource[_]] =
graph.nodes.collect {
case n if n.value.isInstanceOf[UndefinedSource[_]] n.value.asInstanceOf[UndefinedSource[_]]
}(collection.breakOut)
def undefinedTaps: Set[UndefinedTap[_]] =
graph.nodes.iterator.map(_.value).collect {
case n: UndefinedTap[_] n
}.toSet
def undefinedSinks: Set[UndefinedSink[_]] =
graph.nodes.collect {
case n if n.value.isInstanceOf[UndefinedSink[_]] n.value.asInstanceOf[UndefinedSink[_]]
}(collection.breakOut)
def undefinedDrains: Set[UndefinedDrain[_]] =
graph.nodes.iterator.map(_.value).collect {
case n: UndefinedDrain[_] n
}.toSet
}
/**
* Returned by [[FlowGraph#run]] and can be used as parameter to the
* accessor method to retrieve the materialized `Source` or `Sink`, e.g.
* [[SubscriberSource#subscriber]] or [[PublisherSink#publisher]].
* accessor method to retrieve the materialized `Tap` or `Drain`, e.g.
* [[SubscriberTap#subscriber]] or [[PublisherDrain#publisher]].
*/
class MaterializedFlowGraph(materializedSources: Map[SourceWithKey[_, _], Any], materializedSinks: Map[SinkWithKey[_, _], Any])
extends MaterializedSource with MaterializedSink {
class MaterializedPipeGraph(materializedTaps: Map[TapWithKey[_, _], Any], materializedDrains: Map[DrainWithKey[_, _], Any])
extends MaterializedTap with MaterializedDrain {
/**
* Do not call directly. Use accessor method in the concrete `Source`, e.g. [[SubscriberSource#subscriber]].
* Do not call directly. Use accessor method in the concrete `Tap`, e.g. [[SubscriberTap#subscriber]].
*/
override def getSourceFor[T](key: SourceWithKey[_, T]): T =
materializedSources.get(key) match {
case Some(matSource) matSource.asInstanceOf[T]
override def getTapFor[T](key: TapWithKey[_, T]): T =
materializedTaps.get(key) match {
case Some(matTap) matTap.asInstanceOf[T]
case None
throw new IllegalArgumentException(s"Source key [$key] doesn't exist in this flow graph")
throw new IllegalArgumentException(s"Tap key [$key] doesn't exist in this flow graph")
}
/**
* Do not call directly. Use accessor method in the concrete `Sink`, e.g. [[PublisherSink#publisher]].
* Do not call directly. Use accessor method in the concrete `Drain`, e.g. [[PublisherDrain#publisher]].
*/
def getSinkFor[T](key: SinkWithKey[_, T]): T =
materializedSinks.get(key) match {
case Some(matSink) matSink.asInstanceOf[T]
def getDrainFor[T](key: DrainWithKey[_, T]): T =
materializedDrains.get(key) match {
case Some(matDrain) matDrain.asInstanceOf[T]
case None
throw new IllegalArgumentException(s"Sink key [$key] doesn't exist in this flow graph")
throw new IllegalArgumentException(s"Drain key [$key] doesn't exist in this flow graph")
}
}
@ -819,82 +873,70 @@ class MaterializedFlowGraph(materializedSources: Map[SourceWithKey[_, _], Any],
* Implicit conversions that provides syntactic sugar for building flow graphs.
*/
object FlowGraphImplicits {
implicit class SourceOps[In](val source: Source[In]) extends AnyVal {
def ~>[Out](flow: ProcessorFlow[In, Out])(implicit builder: FlowGraphBuilder): SourceNextStep[In, Out] = {
new SourceNextStep(source, flow, builder)
}
def ~>(sink: JunctionInPort[In])(implicit builder: FlowGraphBuilder): JunctionOutPort[sink.NextT] = {
builder.addEdge(source, ProcessorFlow.empty[In], sink)
sink.next
}
}
class SourceNextStep[In, Out](source: Source[In], flow: ProcessorFlow[In, Out], builder: FlowGraphBuilder) {
def ~>(sink: JunctionInPort[Out]): JunctionOutPort[sink.NextT] = {
builder.addEdge(source, flow, sink)
sink.next
class SourceNextStep[In, Out](source: Source[In], flow: Flow[In, Out], builder: FlowGraphBuilder) {
def ~>(drain: JunctionInPort[Out]): JunctionOutPort[drain.NextT] = {
builder.addEdge(source, flow, drain)
drain.next
}
}
implicit class JunctionOps[In](val junction: JunctionOutPort[In]) extends AnyVal {
def ~>[Out](flow: ProcessorFlow[In, Out])(implicit builder: FlowGraphBuilder): JunctionNextStep[In, Out] = {
def ~>[Out](flow: Flow[In, Out])(implicit builder: FlowGraphBuilder): JunctionNextStep[In, Out] =
new JunctionNextStep(junction, flow, builder)
def ~>(drain: UndefinedDrain[In])(implicit builder: FlowGraphBuilder): Unit =
builder.addEdge(junction, Pipe.empty[In], drain)
def ~>(drain: JunctionInPort[In])(implicit builder: FlowGraphBuilder): JunctionOutPort[drain.NextT] = {
builder.addEdge(junction, Pipe.empty[In], drain)
drain.next
}
def ~>(sink: Sink[In])(implicit builder: FlowGraphBuilder): Unit =
builder.addEdge(junction, ProcessorFlow.empty[In], sink)
def ~>(sink: UndefinedSink[In])(implicit builder: FlowGraphBuilder): Unit =
builder.addEdge(junction, ProcessorFlow.empty[In], sink)
def ~>(sink: JunctionInPort[In])(implicit builder: FlowGraphBuilder): JunctionOutPort[sink.NextT] = {
builder.addEdge(junction, ProcessorFlow.empty[In], sink)
sink.next
}
def ~>(flow: FlowWithSink[In, _])(implicit builder: FlowGraphBuilder): Unit =
builder.addEdge(junction, flow)
def ~>(sink: Sink[In])(implicit builder: FlowGraphBuilder): Unit = builder.addEdge(junction, sink)
}
class JunctionNextStep[In, Out](junction: JunctionOutPort[In], flow: ProcessorFlow[In, Out], builder: FlowGraphBuilder) {
def ~>(sink: JunctionInPort[Out]): JunctionOutPort[sink.NextT] = {
builder.addEdge(junction, flow, sink)
sink.next
class JunctionNextStep[In, Out](junction: JunctionOutPort[In], flow: Flow[In, Out], builder: FlowGraphBuilder) {
def ~>(drain: JunctionInPort[Out]): JunctionOutPort[drain.NextT] = {
builder.addEdge(junction, flow, drain)
drain.next
}
def ~>(sink: Sink[Out]): Unit = {
builder.addEdge(junction, flow, sink)
}
def ~>(sink: UndefinedSink[Out]): Unit = {
builder.addEdge(junction, flow, sink)
def ~>(drain: UndefinedDrain[Out]): Unit = {
builder.addEdge(junction, flow, drain)
}
}
implicit class FlowWithSourceOps[In, Out](val flow: FlowWithSource[In, Out]) extends AnyVal {
def ~>(sink: JunctionInPort[Out])(implicit builder: FlowGraphBuilder): JunctionOutPort[sink.NextT] = {
builder.addEdge(flow, sink)
sink.next
implicit class SourceOps[Out](val source: Source[Out]) extends AnyVal {
def ~>[O](flow: Flow[Out, O])(implicit builder: FlowGraphBuilder): SourceNextStep[Out, O] =
new SourceNextStep(source, flow, builder)
def ~>(drain: JunctionInPort[Out])(implicit builder: FlowGraphBuilder): JunctionOutPort[drain.NextT] = {
builder.addEdge(source, drain)
drain.next
}
}
implicit class UndefinedSourceOps[In](val source: UndefinedSource[In]) extends AnyVal {
def ~>[Out](flow: ProcessorFlow[In, Out])(implicit builder: FlowGraphBuilder): UndefinedSourceNextStep[In, Out] = {
new UndefinedSourceNextStep(source, flow, builder)
}
implicit class UndefinedTapOps[In](val tap: UndefinedTap[In]) extends AnyVal {
def ~>[Out](flow: Flow[In, Out])(implicit builder: FlowGraphBuilder): UndefinedTapNextStep[In, Out] =
new UndefinedTapNextStep(tap, flow, builder)
def ~>(sink: JunctionInPort[In])(implicit builder: FlowGraphBuilder): JunctionOutPort[sink.NextT] = {
builder.addEdge(source, ProcessorFlow.empty[In], sink)
sink.next
def ~>(drain: JunctionInPort[In])(implicit builder: FlowGraphBuilder): JunctionOutPort[drain.NextT] = {
builder.addEdge(tap, Pipe.empty[In], drain)
drain.next
}
}
class UndefinedSourceNextStep[In, Out](source: UndefinedSource[In], flow: ProcessorFlow[In, Out], builder: FlowGraphBuilder) {
def ~>(sink: JunctionInPort[Out]): JunctionOutPort[sink.NextT] = {
builder.addEdge(source, flow, sink)
sink.next
class UndefinedTapNextStep[In, Out](tap: UndefinedTap[In], flow: Flow[In, Out], builder: FlowGraphBuilder) {
def ~>(drain: JunctionInPort[Out]): JunctionOutPort[drain.NextT] = {
builder.addEdge(tap, flow, drain)
drain.next
}
}

View file

@ -138,7 +138,7 @@ abstract class FlowMaterializer(val settings: MaterializerSettings) {
* stream. The result can be highly implementation specific, ranging from
* local actor chains to remote-deployed processing networks.
*/
def materialize[In, Out](source: Source[In], sink: Sink[Out], ops: List[Ast.AstNode]): MaterializedFlow
def materialize[In, Out](tap: Tap[In], drain: Drain[Out], ops: List[Ast.AstNode]): MaterializedPipe
/**
* Create publishers and subscribers for fan-in and fan-out operations.

View file

@ -0,0 +1,330 @@
/**
* Copyright (C) 2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.stream.scaladsl2
import scala.collection.immutable
import akka.stream.impl2.Ast._
import org.reactivestreams._
import scala.concurrent.Future
import akka.stream.Transformer
import scala.concurrent.duration.FiniteDuration
import scala.concurrent.duration.Duration
import akka.util.Collections.EmptyImmutableSeq
import akka.stream.TimerTransformer
import akka.stream.OverflowStrategy
import scala.annotation.unchecked.uncheckedVariance
import scala.language.higherKinds
import scala.language.existentials
private[scaladsl2] object PipeOps {
private case object TakeWithinTimerKey
private case object DropWithinTimerKey
private case object GroupedWithinTimerKey
private val takeCompletedTransformer: Transformer[Any, Any] = new Transformer[Any, Any] {
override def onNext(elem: Any) = Nil
override def isComplete = true
}
private val identityTransformer: Transformer[Any, Any] = new Transformer[Any, Any] {
override def onNext(elem: Any) = List(elem)
}
}
/**
* Scala API: Operations offered by flows with a free output side: the DSL flows left-to-right only.
*/
private[scaladsl2] trait PipeOps[+Out] extends FlowOps[Out] {
import PipeOps._
type Repr[+O]
// Storing ops in reverse order
protected def andThen[U](op: AstNode): Repr[U]
override def map[T](f: Out T): Repr[T] =
transform("map", () new Transformer[Out, T] {
override def onNext(in: Out) = List(f(in))
})
override def mapConcat[T](f: Out immutable.Seq[T]): Repr[T] =
transform("mapConcat", () new Transformer[Out, T] {
override def onNext(in: Out) = f(in)
})
override def mapAsync[T](f: Out Future[T]): Repr[T] =
andThen(MapAsync(f.asInstanceOf[Any Future[Any]]))
override def mapAsyncUnordered[T](f: Out Future[T]): Repr[T] =
andThen(MapAsyncUnordered(f.asInstanceOf[Any Future[Any]]))
override def filter(p: Out Boolean): Repr[Out] =
transform("filter", () new Transformer[Out, Out] {
override def onNext(in: Out) = if (p(in)) List(in) else Nil
})
override def collect[T](pf: PartialFunction[Out, T]): Repr[T] =
transform("collect", () new Transformer[Out, T] {
override def onNext(in: Out) = if (pf.isDefinedAt(in)) List(pf(in)) else Nil
})
override def grouped(n: Int): Repr[immutable.Seq[Out]] = {
require(n > 0, "n must be greater than 0")
transform("grouped", () new Transformer[Out, immutable.Seq[Out]] {
var buf: Vector[Out] = Vector.empty
override def onNext(in: Out) = {
buf :+= in
if (buf.size == n) {
val group = buf
buf = Vector.empty
List(group)
} else
Nil
}
override def onTermination(e: Option[Throwable]) = if (buf.isEmpty) Nil else List(buf)
})
}
override def groupedWithin(n: Int, d: FiniteDuration): Repr[immutable.Seq[Out]] = {
require(n > 0, "n must be greater than 0")
require(d > Duration.Zero)
timerTransform("groupedWithin", () new TimerTransformer[Out, immutable.Seq[Out]] {
schedulePeriodically(GroupedWithinTimerKey, d)
var buf: Vector[Out] = Vector.empty
override def onNext(in: Out) = {
buf :+= in
if (buf.size == n) {
// start new time window
schedulePeriodically(GroupedWithinTimerKey, d)
emitGroup()
} else Nil
}
override def onTermination(e: Option[Throwable]) = if (buf.isEmpty) Nil else List(buf)
override def onTimer(timerKey: Any) = emitGroup()
private def emitGroup(): immutable.Seq[immutable.Seq[Out]] =
if (buf.isEmpty) EmptyImmutableSeq
else {
val group = buf
buf = Vector.empty
List(group)
}
})
}
override def drop(n: Int): Repr[Out] =
transform("drop", () new Transformer[Out, Out] {
var delegate: Transformer[Out, Out] =
if (n <= 0) identityTransformer.asInstanceOf[Transformer[Out, Out]]
else new Transformer[Out, Out] {
var c = n
override def onNext(in: Out) = {
c -= 1
if (c == 0)
delegate = identityTransformer.asInstanceOf[Transformer[Out, Out]]
Nil
}
}
override def onNext(in: Out) = delegate.onNext(in)
})
override def dropWithin(d: FiniteDuration): Repr[Out] =
timerTransform("dropWithin", () new TimerTransformer[Out, Out] {
scheduleOnce(DropWithinTimerKey, d)
var delegate: Transformer[Out, Out] =
new Transformer[Out, Out] {
override def onNext(in: Out) = Nil
}
override def onNext(in: Out) = delegate.onNext(in)
override def onTimer(timerKey: Any) = {
delegate = identityTransformer.asInstanceOf[Transformer[Out, Out]]
Nil
}
})
override def take(n: Int): Repr[Out] =
transform("take", () new Transformer[Out, Out] {
var delegate: Transformer[Out, Out] =
if (n <= 0) takeCompletedTransformer.asInstanceOf[Transformer[Out, Out]]
else new Transformer[Out, Out] {
var c = n
override def onNext(in: Out) = {
c -= 1
if (c == 0)
delegate = takeCompletedTransformer.asInstanceOf[Transformer[Out, Out]]
List(in)
}
}
override def onNext(in: Out) = delegate.onNext(in)
override def isComplete = delegate.isComplete
})
override def takeWithin(d: FiniteDuration): Repr[Out] =
timerTransform("takeWithin", () new TimerTransformer[Out, Out] {
scheduleOnce(TakeWithinTimerKey, d)
var delegate: Transformer[Out, Out] = identityTransformer.asInstanceOf[Transformer[Out, Out]]
override def onNext(in: Out) = delegate.onNext(in)
override def isComplete = delegate.isComplete
override def onTimer(timerKey: Any) = {
delegate = takeCompletedTransformer.asInstanceOf[Transformer[Out, Out]]
Nil
}
})
override def conflate[S](seed: Out S, aggregate: (S, Out) S): Repr[S] =
andThen(Conflate(seed.asInstanceOf[Any Any], aggregate.asInstanceOf[(Any, Any) Any]))
override def expand[S, U](seed: Out S, extrapolate: S (U, S)): Repr[U] =
andThen(Expand(seed.asInstanceOf[Any Any], extrapolate.asInstanceOf[Any (Any, Any)]))
override def buffer(size: Int, overflowStrategy: OverflowStrategy): Repr[Out] = {
require(size > 0, s"Buffer size must be larger than zero but was [$size]")
andThen(Buffer(size, overflowStrategy))
}
override def transform[T](name: String, mkTransformer: () Transformer[Out, T]): Repr[T] = {
andThen(Transform(name, mkTransformer.asInstanceOf[() Transformer[Any, Any]]))
}
override def prefixAndTail[U >: Out](n: Int): Repr[(immutable.Seq[Out], Source[U])] =
andThen(PrefixAndTail(n))
override def groupBy[K, U >: Out](f: Out K): Repr[(K, Source[U])] =
andThen(GroupBy(f.asInstanceOf[Any Any]))
override def splitWhen[U >: Out](p: Out Boolean): Repr[Source[U]] =
andThen(SplitWhen(p.asInstanceOf[Any Boolean]))
override def flatten[U](strategy: FlattenStrategy[Out, U]): Repr[U] = strategy match {
case _: FlattenStrategy.Concat[Out] andThen(ConcatAll)
case _ throw new IllegalArgumentException(s"Unsupported flattening strategy [${strategy.getClass.getSimpleName}]")
}
override def timerTransform[U](name: String, mkTransformer: () TimerTransformer[Out, U]): Repr[U] =
andThen(TimerTransform(name, mkTransformer.asInstanceOf[() TimerTransformer[Any, Any]]))
}
private[scaladsl2] object Pipe {
private val emptyInstance = Pipe[Any, Any](ops = Nil)
def empty[T]: Pipe[T, T] = emptyInstance.asInstanceOf[Pipe[T, T]]
val OnlyPipesErrorMessage = "Only pipes are supported currently!"
}
/**
* Flow with one open input and one open output..
*/
private[scaladsl2] final case class Pipe[-In, +Out](ops: List[AstNode]) extends Flow[In, Out] with PipeOps[Out] {
override type Repr[+O] = Pipe[In @uncheckedVariance, O]
override protected def andThen[U](op: AstNode): Repr[U] = this.copy(ops = op :: ops)
def withDrain(out: Drain[Out]): SinkPipe[In] = SinkPipe(out, ops)
def withTap(in: Tap[In]): SourcePipe[Out] = SourcePipe(in, ops)
override def connect[T](flow: Flow[Out, T]): Flow[In, T] = flow match {
case p: Pipe[T, In] Pipe(p.ops ++: ops)
case _ throw new IllegalArgumentException(Pipe.OnlyPipesErrorMessage)
}
override def connect(sink: Sink[Out]): Sink[In] = sink match {
case sp: SinkPipe[Out] sp.prependPipe(this)
case d: Drain[Out] this.withDrain(d)
case _ throw new IllegalArgumentException(Pipe.OnlyPipesErrorMessage)
}
}
/**
* Pipe with open input and attached output. Can be used as a `Subscriber`.
*/
private[scaladsl2] final case class SinkPipe[-In](output: Drain[_], ops: List[AstNode]) extends Sink[In] {
def withTap(in: Tap[In]): RunnablePipe = RunnablePipe(in, output, ops)
def prependPipe[T](pipe: Pipe[T, In]): SinkPipe[T] = SinkPipe(output, ops ::: pipe.ops)
override def toSubscriber()(implicit materializer: FlowMaterializer): Subscriber[In @uncheckedVariance] = {
val subIn = SubscriberTap[In]()
val mf = withTap(subIn).run()
subIn.subscriber(mf)
}
}
/**
* Pipe with open output and attached input. Can be used as a `Publisher`.
*/
private[scaladsl2] final case class SourcePipe[+Out](input: Tap[_], ops: List[AstNode]) extends Source[Out] with PipeOps[Out] {
override type Repr[+O] = SourcePipe[O]
override protected def andThen[U](op: AstNode): Repr[U] = SourcePipe(input, op :: ops)
def withDrain(out: Drain[Out]): RunnablePipe = RunnablePipe(input, out, ops)
def appendPipe[T](pipe: Pipe[Out, T]): SourcePipe[T] = SourcePipe(input, pipe.ops ++: ops)
override def connect[T](flow: Flow[Out, T]): Source[T] = flow match {
case p: Pipe[Out, T] appendPipe(p)
case _ throw new IllegalArgumentException(Pipe.OnlyPipesErrorMessage)
}
override def connect(sink: Sink[Out]): RunnableFlow = sink match {
case sp: SinkPipe[Out] RunnablePipe(input, sp.output, sp.ops ++: ops)
case d: Drain[Out] this.withDrain(d)
case _ throw new IllegalArgumentException(Pipe.OnlyPipesErrorMessage)
}
override def toPublisher()(implicit materializer: FlowMaterializer): Publisher[Out @uncheckedVariance] = {
val pubOut = PublisherDrain[Out]
val mf = withDrain(pubOut).run()
pubOut.publisher(mf)
}
override def toFanoutPublisher(initialBufferSize: Int, maximumBufferSize: Int)(implicit materializer: FlowMaterializer): Publisher[Out @uncheckedVariance] = {
val pubOut = PublisherDrain.withFanout[Out](initialBufferSize, maximumBufferSize)
val mf = withDrain(pubOut).run()
pubOut.publisher(mf)
}
override def publishTo(subscriber: Subscriber[Out @uncheckedVariance])(implicit materializer: FlowMaterializer): Unit =
toPublisher().subscribe(subscriber)
override def consume()(implicit materializer: FlowMaterializer): Unit =
withDrain(BlackholeDrain).run()
}
/**
* Pipe with attached input and output, can be executed.
*/
private[scaladsl2] final case class RunnablePipe(input: Tap[_], output: Drain[_], ops: List[AstNode]) extends RunnableFlow {
def run()(implicit materializer: FlowMaterializer): MaterializedPipe =
materializer.materialize(input, output, ops)
}
/**
* Returned by [[RunnablePipe#run]] and can be used as parameter to the
* accessor method to retrieve the materialized `Tap` or `Drain`, e.g.
* [[SubscriberTap#subscriber]] or [[PublisherDrain#publisher]].
*/
private[stream] class MaterializedPipe(tapKey: AnyRef, matTap: Any, drainKey: AnyRef, matDrain: Any) extends MaterializedFlow {
/**
* Do not call directly. Use accessor method in the concrete `Tap`, e.g. [[SubscriberTap#subscriber]].
*/
override def getTapFor[T](key: TapWithKey[_, T]): T =
if (key == tapKey) matTap.asInstanceOf[T]
else throw new IllegalArgumentException(s"Tap key [$key] doesn't match the tap [$tapKey] of this flow")
/**
* Do not call directly. Use accessor method in the concrete `Drain`, e.g. [[PublisherDrain#publisher]].
*/
def getDrainFor[T](key: DrainWithKey[_, T]): T =
if (key == drainKey) matDrain.asInstanceOf[T]
else throw new IllegalArgumentException(s"Drain key [$key] doesn't match the drain [$drainKey] of this flow")
}

View file

@ -3,263 +3,15 @@
*/
package akka.stream.scaladsl2
import akka.actor.Props
import org.reactivestreams.Subscriber
import scala.collection.immutable
import scala.language.implicitConversions
import scala.annotation.unchecked.uncheckedVariance
import scala.concurrent.{ Future, Promise }
import scala.util.{ Failure, Success, Try }
import org.reactivestreams.{ Publisher, Subscriber, Subscription }
import akka.stream.Transformer
import akka.stream.impl.{ FanoutProcessorImpl, BlackholeSubscriber }
import akka.stream.impl2.{ ActorProcessorFactory, ActorBasedFlowMaterializer }
import java.util.concurrent.atomic.AtomicReference
/**
* This trait is a marker for a pluggable stream sink. Concrete instances should
* implement [[SinkWithKey]] or [[SimpleSink]], otherwise a custom [[FlowMaterializer]]
* will have to be used to be able to attach them.
*
* All Sinks defined in this package rely upon an [[akka.stream.impl2.ActorBasedFlowMaterializer]] being
* made available to them in order to use the <code>attach</code> method. Other
* FlowMaterializers can be used but must then implement the functionality of these
* Sink nodes themselves (or construct an ActorBasedFlowMaterializer).
* A `Sink` is a set of stream processing steps that has one open input and an attached output.
* Can be used as a `Subscriber`
*/
trait Sink[-Out]
/**
* A sink that does not need to create a user-accessible object during materialization.
*/
trait SimpleSink[-Out] extends Sink[Out] {
/**
* Attach this sink to the given [[org.reactivestreams.Publisher]]. Using the given
* [[FlowMaterializer]] is completely optional, especially if this sink belongs to
* a different Reactive Streams implementation. It is the responsibility of the
* caller to provide a suitable FlowMaterializer that can be used for running
* Flows if necessary.
*
* @param flowPublisher the Publisher to consume elements from
* @param materializer a FlowMaterializer that may be used for creating flows
* @param flowName the name of the current flow, which should be used in log statements or error messages
*/
def attach(flowPublisher: Publisher[Out @uncheckedVariance], materializer: ActorBasedFlowMaterializer, flowName: String): Unit
/**
* This method is only used for Sinks that return true from [[#isActive]], which then must
* implement it.
*/
def create(materializer: ActorBasedFlowMaterializer, flowName: String): Subscriber[Out] @uncheckedVariance =
throw new UnsupportedOperationException(s"forgot to implement create() for $getClass that says isActive==true")
/**
* This method indicates whether this Sink can create a Subscriber instead of being
* attached to a Publisher. This is only used if the Flow does not contain any
* operations.
*/
def isActive: Boolean = false
trait Sink[-In] {
def toSubscriber()(implicit materializer: FlowMaterializer): Subscriber[In @uncheckedVariance]
}
/**
* A sink that will create an object during materialization that the user will need
* to retrieve in order to access aspects of this sink (could be a completion Future
* or a cancellation handle, etc.)
*/
trait SinkWithKey[-Out, T] extends Sink[Out] {
/**
* Attach this sink to the given [[org.reactivestreams.Publisher]]. Using the given
* [[FlowMaterializer]] is completely optional, especially if this sink belongs to
* a different Reactive Streams implementation. It is the responsibility of the
* caller to provide a suitable FlowMaterializer that can be used for running
* Flows if necessary.
*
* @param flowPublisher the Publisher to consume elements from
* @param materializer a FlowMaterializer that may be used for creating flows
* @param flowName the name of the current flow, which should be used in log statements or error messages
*/
def attach(flowPublisher: Publisher[Out @uncheckedVariance], materializer: ActorBasedFlowMaterializer, flowName: String): T
/**
* This method is only used for Sinks that return true from [[#isActive]], which then must
* implement it.
*/
def create(materializer: ActorBasedFlowMaterializer, flowName: String): (Subscriber[Out] @uncheckedVariance, T) =
throw new UnsupportedOperationException(s"forgot to implement create() for $getClass that says isActive==true")
/**
* This method indicates whether this Sink can create a Subscriber instead of being
* attached to a Publisher. This is only used if the Flow does not contain any
* operations.
*/
def isActive: Boolean = false
// these are unique keys, case class equality would break them
final override def equals(other: Any): Boolean = super.equals(other)
final override def hashCode: Int = super.hashCode
}
/**
* Holds the downstream-most [[org.reactivestreams.Publisher]] interface of the materialized flow.
* The stream will not have any subscribers attached at this point, which means that after prefetching
* elements to fill the internal buffers it will assert back-pressure until
* a subscriber connects and creates demand for elements to be emitted.
*/
object PublisherSink {
private val instance = new PublisherSink[Nothing]
def apply[T]: PublisherSink[T] = instance.asInstanceOf[PublisherSink[T]]
def withFanout[T](initialBufferSize: Int, maximumBufferSize: Int): FanoutPublisherSink[T] =
new FanoutPublisherSink[T](initialBufferSize, maximumBufferSize)
}
class PublisherSink[Out] extends SinkWithKey[Out, Publisher[Out]] {
def attach(flowPublisher: Publisher[Out], materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[Out] = flowPublisher
def publisher(m: MaterializedSink): Publisher[Out] = m.getSinkFor(this)
override def toString: String = "PublisherSink"
}
class FanoutPublisherSink[Out](initialBufferSize: Int, maximumBufferSize: Int) extends SinkWithKey[Out, Publisher[Out]] {
def publisher(m: MaterializedSink): Publisher[Out] = m.getSinkFor(this)
override def attach(flowPublisher: Publisher[Out], materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[Out] = {
val fanoutActor = materializer.actorOf(
Props(new FanoutProcessorImpl(materializer.settings, initialBufferSize, maximumBufferSize)), s"$flowName-fanoutPublisher")
val fanoutProcessor = ActorProcessorFactory[Out, Out](fanoutActor)
flowPublisher.subscribe(fanoutProcessor)
fanoutProcessor
}
override def toString: String = "Fanout"
}
object FutureSink {
def apply[T]: FutureSink[T] = new FutureSink[T]
}
/**
* Holds a [[scala.concurrent.Future]] that will be fulfilled with the first
* thing that is signaled to this stream, which can be either an element (after
* which the upstream subscription is canceled), an error condition (putting
* the Future into the corresponding failed state) or the end-of-stream
* (failing the Future with a NoSuchElementException).
*/
class FutureSink[Out] extends SinkWithKey[Out, Future[Out]] {
def attach(flowPublisher: Publisher[Out], materializer: ActorBasedFlowMaterializer, flowName: String): Future[Out] = {
val (sub, f) = create(materializer, flowName)
flowPublisher.subscribe(sub)
f
}
override def isActive = true
override def create(materializer: ActorBasedFlowMaterializer, flowName: String): (Subscriber[Out], Future[Out]) = {
val p = Promise[Out]()
val sub = new Subscriber[Out] { // TODO #15804 verify this using the RS TCK
private val sub = new AtomicReference[Subscription]
override def onSubscribe(s: Subscription): Unit =
if (!sub.compareAndSet(null, s)) s.cancel()
else s.request(1)
override def onNext(t: Out): Unit = { p.trySuccess(t); sub.get.cancel() }
override def onError(t: Throwable): Unit = p.tryFailure(t)
override def onComplete(): Unit = p.tryFailure(new NoSuchElementException("empty stream"))
}
(sub, p.future)
}
def future(m: MaterializedSink): Future[Out] = m.getSinkFor(this)
override def toString: String = "FutureSink"
}
/**
* Attaches a subscriber to this stream which will just discard all received
* elements.
*/
final case object BlackholeSink extends SimpleSink[Any] {
override def attach(flowPublisher: Publisher[Any], materializer: ActorBasedFlowMaterializer, flowName: String): Unit =
flowPublisher.subscribe(create(materializer, flowName))
override def isActive: Boolean = true
override def create(materializer: ActorBasedFlowMaterializer, flowName: String): Subscriber[Any] =
new BlackholeSubscriber[Any](materializer.settings.maxInputBufferSize)
}
/**
* Attaches a subscriber to this stream.
*/
final case class SubscriberSink[Out](subscriber: Subscriber[Out]) extends SimpleSink[Out] {
override def attach(flowPublisher: Publisher[Out], materializer: ActorBasedFlowMaterializer, flowName: String): Unit =
flowPublisher.subscribe(subscriber)
override def isActive: Boolean = true
override def create(materializer: ActorBasedFlowMaterializer, flowName: String): Subscriber[Out] = subscriber
}
object OnCompleteSink {
private val SuccessUnit = Success[Unit](())
}
/**
* When the flow is completed, either through an error or normal
* completion, apply the provided function with [[scala.util.Success]]
* or [[scala.util.Failure]].
*/
final case class OnCompleteSink[Out](callback: Try[Unit] Unit) extends SimpleSink[Out] {
override def attach(flowPublisher: Publisher[Out], materializer: ActorBasedFlowMaterializer, flowName: String): Unit =
FlowFrom(flowPublisher).transform("onCompleteSink", () new Transformer[Out, Unit] {
override def onNext(in: Out) = Nil
override def onError(e: Throwable) = ()
override def onTermination(e: Option[Throwable]) = {
e match {
case None callback(OnCompleteSink.SuccessUnit)
case Some(e) callback(Failure(e))
}
Nil
}
}).consume()(materializer.withNamePrefix(flowName))
}
/**
* Invoke the given procedure for each received element. The sink holds a [[scala.concurrent.Future]]
* that will be completed with `Success` when reaching the normal end of the stream, or completed
* with `Failure` if there is an error is signaled in the stream.
*/
final case class ForeachSink[Out](f: Out Unit) extends SinkWithKey[Out, Future[Unit]] {
override def attach(flowPublisher: Publisher[Out], materializer: ActorBasedFlowMaterializer, flowName: String): Future[Unit] = {
val promise = Promise[Unit]()
FlowFrom(flowPublisher).transform("foreach", () new Transformer[Out, Unit] {
override def onNext(in: Out) = { f(in); Nil }
override def onError(cause: Throwable): Unit = ()
override def onTermination(e: Option[Throwable]) = {
e match {
case None promise.success(())
case Some(e) promise.failure(e)
}
Nil
}
}).consume()(materializer.withNamePrefix(flowName))
promise.future
}
def future(m: MaterializedSink): Future[Unit] = m.getSinkFor(this)
}
/**
* Invoke the given function for every received element, giving it its previous
* output (or the given `zero` value) and the element as input. The sink holds a
* [[scala.concurrent.Future]] that will be completed with value of the final
* function evaluation when the input stream ends, or completed with `Failure`
* if there is an error is signaled in the stream.
*/
final case class FoldSink[U, Out](zero: U)(f: (U, Out) U) extends SinkWithKey[Out, Future[U]] {
override def attach(flowPublisher: Publisher[Out], materializer: ActorBasedFlowMaterializer, flowName: String): Future[U] = {
val promise = Promise[U]()
FlowFrom(flowPublisher).transform("fold", () new Transformer[Out, U] {
var state: U = zero
override def onNext(in: Out): immutable.Seq[U] = { state = f(state, in); Nil }
override def onError(cause: Throwable) = ()
override def onTermination(e: Option[Throwable]) = {
e match {
case None promise.success(state)
case Some(e) promise.failure(e)
}
Nil
}
}).consume()(materializer.withNamePrefix(flowName))
promise.future
}
def future(m: MaterializedSink): Future[U] = m.getSinkFor(this)
}

View file

@ -3,71 +3,90 @@
*/
package akka.stream.scaladsl2
import org.reactivestreams.{ Subscriber, Publisher }
import scala.annotation.unchecked.uncheckedVariance
import scala.collection.immutable
import scala.concurrent.Future
import scala.concurrent.duration.FiniteDuration
import scala.util.{ Failure, Success }
import org.reactivestreams.{ Publisher, Subscriber }
import scala.language.higherKinds
import scala.language.implicitConversions
import akka.stream.impl.{ ActorPublisher, EmptyPublisher, ErrorPublisher, FuturePublisher, IterablePublisher, IteratorPublisher, SimpleCallbackPublisher, TickPublisher, Stop }
import akka.stream.impl2.ActorBasedFlowMaterializer
/**
* A `Source` is a set of stream processing steps that has one open output and an attached input.
* Can be used as a `Publisher`
*/
trait Source[+Out] extends FlowOps[Out] {
override type Repr[+O] <: Source[O]
object FlowFrom {
/**
* Helper to create `Flow` without [[Source]].
* Example usage: `FlowFrom[Int]`
* Transform this source by appending the given processing stages.
*/
def apply[T]: ProcessorFlow[T, T] = ProcessorFlow.empty[T]
def connect[T](flow: Flow[Out, T]): Source[T]
/**
* Helper to create `Flow` with [[Source]] from `Publisher`.
* Connect this source to a sink, concatenating the processing steps of both.
*/
def connect(sink: Sink[Out]): RunnableFlow
def toPublisher()(implicit materializer: FlowMaterializer): Publisher[Out @uncheckedVariance]
def toFanoutPublisher(initialBufferSize: Int, maximumBufferSize: Int)(implicit materializer: FlowMaterializer): Publisher[Out @uncheckedVariance]
def publishTo(subscriber: Subscriber[Out @uncheckedVariance])(implicit materializer: FlowMaterializer)
def consume()(implicit materializer: FlowMaterializer): Unit
}
object Source {
/**
* Helper to create [[Source]] from `Publisher`.
*
* Construct a transformation starting with given publisher. The transformation steps
* are executed by a series of [[org.reactivestreams.Processor]] instances
* that mediate the flow of elements downstream and the propagation of
* back-pressure upstream.
*/
def apply[T](publisher: Publisher[T]): FlowWithSource[T, T] = FlowFrom[T].withSource(PublisherSource(publisher))
def apply[T](publisher: Publisher[T]): Tap[T] = PublisherTap(publisher)
/**
* Helper to create `Flow` with [[Source]] from `Iterator`.
* Example usage: `FlowFrom(Seq(1,2,3).iterator)`
* Helper to create [[Source]] from `Iterator`.
* Example usage: `Source(Seq(1,2,3).iterator)`
*
* Start a new `Flow` from the given Iterator. The produced stream of elements
* Start a new `Source` from the given Iterator. The produced stream of elements
* will continue until the iterator runs empty or fails during evaluation of
* the `next()` method. Elements are pulled out of the iterator
* in accordance with the demand coming from the downstream transformation
* steps.
*/
def apply[T](iterator: Iterator[T]): FlowWithSource[T, T] = FlowFrom[T].withSource(IteratorSource(iterator))
def apply[T](iterator: Iterator[T]): Tap[T] = IteratorTap(iterator)
/**
* Helper to create `Flow` with [[Source]] from `Iterable`.
* Example usage: `FlowFrom(Seq(1,2,3))`
* Helper to create [[Source]] from `Iterable`.
* Example usage: `Source(Seq(1,2,3))`
*
* Starts a new `Flow` from the given `Iterable`. This is like starting from an
* Starts a new `Source` from the given `Iterable`. This is like starting from an
* Iterator, but every Subscriber directly attached to the Publisher of this
* stream will see an individual flow of elements (always starting from the
* beginning) regardless of when they subscribed.
*/
def apply[T](iterable: immutable.Iterable[T]): FlowWithSource[T, T] = FlowFrom[T].withSource(IterableSource(iterable))
def apply[T](iterable: immutable.Iterable[T]): Tap[T] = IterableTap(iterable)
/**
* Define the sequence of elements to be produced by the given closure.
* The stream ends normally when evaluation of the closure returns a `None`.
* The stream ends exceptionally when an exception is thrown from the closure.
*/
def apply[T](f: () Option[T]): FlowWithSource[T, T] = FlowFrom[T].withSource(ThunkSource(f))
def apply[T](f: () Option[T]): Tap[T] = ThunkTap(f)
/**
* Start a new `Flow` from the given `Future`. The stream will consist of
* Start a new `Source` from the given `Future`. The stream will consist of
* one element when the `Future` is completed with a successful value, which
* may happen before or after materializing the `Flow`.
* The stream terminates with an error if the `Future` is completed with a failure.
*/
def apply[T](future: Future[T]): FlowWithSource[T, T] = FlowFrom[T].withSource(FutureSource(future))
def apply[T](future: Future[T]): Tap[T] = FutureTap(future)
/**
* Elements are produced from the tick closure periodically with the specified interval.
@ -76,200 +95,6 @@ object FlowFrom {
* element is produced it will not receive that tick element later. It will
* receive new tick elements as soon as it has requested more elements.
*/
def apply[T](initialDelay: FiniteDuration, interval: FiniteDuration, tick: () T): FlowWithSource[T, T] =
FlowFrom[T].withSource(TickSource(initialDelay, interval, tick))
def apply[T](initialDelay: FiniteDuration, interval: FiniteDuration, tick: () T): Tap[T] =
TickTap(initialDelay, interval, tick)
}
/**
* This trait is a marker for a pluggable stream source. Concrete instances should
* implement [[SourceWithKey]] or [[SimpleSource]], otherwise a custom [[FlowMaterializer]]
* will have to be used to be able to attach them.
*
* All Sources defined in this package rely upon an ActorBasedFlowMaterializer being
* made available to them in order to use the <code>attach</code> method. Other
* FlowMaterializers can be used but must then implement the functionality of these
* Source nodes themselves (or construct an ActorBasedFlowMaterializer).
*/
trait Source[+In]
/**
* A source that does not need to create a user-accessible object during materialization.
*/
trait SimpleSource[+In] extends Source[In] {
/**
* Attach this source to the given [[org.reactivestreams.Subscriber]]. Using the given
* [[FlowMaterializer]] is completely optional, especially if this source belongs to
* a different Reactive Streams implementation. It is the responsibility of the
* caller to provide a suitable FlowMaterializer that can be used for running
* Flows if necessary.
*
* @param flowSubscriber the Subscriber to produce elements to
* @param materializer a FlowMaterializer that may be used for creating flows
* @param flowName the name of the current flow, which should be used in log statements or error messages
*/
def attach(flowSubscriber: Subscriber[In] @uncheckedVariance, materializer: ActorBasedFlowMaterializer, flowName: String): Unit
/**
* This method is only used for Sources that return true from [[#isActive]], which then must
* implement it.
*/
def create(materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[In] @uncheckedVariance =
throw new UnsupportedOperationException(s"forgot to implement create() for $getClass that says isActive==true")
/**
* This method indicates whether this Source can create a Publisher instead of being
* attached to a Subscriber. This is only used if the Flow does not contain any
* operations.
*/
def isActive: Boolean = false
// these are unique keys, case class equality would break them
final override def equals(other: Any): Boolean = super.equals(other)
final override def hashCode: Int = super.hashCode
}
/**
* A source that will create an object during materialization that the user will need
* to retrieve in order to access aspects of this source (could be a Subscriber, a
* Future/Promise, etc.).
*/
trait SourceWithKey[+In, T] extends Source[In] {
/**
* Attach this source to the given [[org.reactivestreams.Subscriber]]. Using the given
* [[FlowMaterializer]] is completely optional, especially if this source belongs to
* a different Reactive Streams implementation. It is the responsibility of the
* caller to provide a suitable FlowMaterializer that can be used for running
* Flows if necessary.
*
* @param flowSubscriber the Subscriber to produce elements to
* @param materializer a FlowMaterializer that may be used for creating flows
* @param flowName the name of the current flow, which should be used in log statements or error messages
*/
def attach(flowSubscriber: Subscriber[In] @uncheckedVariance, materializer: ActorBasedFlowMaterializer, flowName: String): T
/**
* This method is only used for Sources that return true from [[#isActive]], which then must
* implement it.
*/
def create(materializer: ActorBasedFlowMaterializer, flowName: String): (Publisher[In] @uncheckedVariance, T) =
throw new UnsupportedOperationException(s"forgot to implement create() for $getClass that says isActive==true")
/**
* This method indicates whether this Source can create a Publisher instead of being
* attached to a Subscriber. This is only used if the Flow does not contain any
* operations.
*/
def isActive: Boolean = false
// these are unique keys, case class equality would break them
final override def equals(other: Any): Boolean = super.equals(other)
final override def hashCode: Int = super.hashCode
}
/**
* Holds a `Subscriber` representing the input side of the flow.
* The `Subscriber` can later be connected to an upstream `Publisher`.
*/
final case class SubscriberSource[In]() extends SourceWithKey[In, Subscriber[In]] {
override def attach(flowSubscriber: Subscriber[In], materializer: ActorBasedFlowMaterializer, flowName: String): Subscriber[In] =
flowSubscriber
def subscriber(m: MaterializedSource): Subscriber[In] = m.getSourceFor(this)
}
/**
* Construct a transformation starting with given publisher. The transformation steps
* are executed by a series of [[org.reactivestreams.Processor]] instances
* that mediate the flow of elements downstream and the propagation of
* back-pressure upstream.
*/
final case class PublisherSource[In](p: Publisher[In]) extends SimpleSource[In] {
override def attach(flowSubscriber: Subscriber[In], materializer: ActorBasedFlowMaterializer, flowName: String): Unit =
p.subscribe(flowSubscriber)
override def isActive: Boolean = true
override def create(materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[In] = p
}
/**
* Start a new `Flow` from the given Iterator. The produced stream of elements
* will continue until the iterator runs empty or fails during evaluation of
* the `next()` method. Elements are pulled out of the iterator
* in accordance with the demand coming from the downstream transformation
* steps.
*/
final case class IteratorSource[In](iterator: Iterator[In]) extends SimpleSource[In] {
override def attach(flowSubscriber: Subscriber[In], materializer: ActorBasedFlowMaterializer, flowName: String): Unit =
create(materializer, flowName).subscribe(flowSubscriber)
override def isActive: Boolean = true
override def create(materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[In] =
if (iterator.isEmpty) EmptyPublisher[In]
else ActorPublisher[In](materializer.actorOf(IteratorPublisher.props(iterator, materializer.settings),
name = s"$flowName-0-iterator"))
}
/**
* Starts a new `Flow` from the given `Iterable`. This is like starting from an
* Iterator, but every Subscriber directly attached to the Publisher of this
* stream will see an individual flow of elements (always starting from the
* beginning) regardless of when they subscribed.
*/
final case class IterableSource[In](iterable: immutable.Iterable[In]) extends SimpleSource[In] {
override def attach(flowSubscriber: Subscriber[In], materializer: ActorBasedFlowMaterializer, flowName: String): Unit =
create(materializer, flowName).subscribe(flowSubscriber)
override def isActive: Boolean = true
override def create(materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[In] =
if (iterable.isEmpty) EmptyPublisher[In]
else ActorPublisher[In](materializer.actorOf(IterablePublisher.props(iterable, materializer.settings),
name = s"$flowName-0-iterable"), Some(iterable))
}
/**
* Define the sequence of elements to be produced by the given closure.
* The stream ends normally when evaluation of the closure returns a `None`.
* The stream ends exceptionally when an exception is thrown from the closure.
*/
final case class ThunkSource[In](f: () Option[In]) extends SimpleSource[In] {
override def attach(flowSubscriber: Subscriber[In], materializer: ActorBasedFlowMaterializer, flowName: String): Unit =
create(materializer, flowName).subscribe(flowSubscriber)
override def isActive: Boolean = true
override def create(materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[In] =
ActorPublisher[In](materializer.actorOf(SimpleCallbackPublisher.props(materializer.settings,
() f().getOrElse(throw Stop)), name = s"$flowName-0-thunk"))
}
/**
* Start a new `Flow` from the given `Future`. The stream will consist of
* one element when the `Future` is completed with a successful value, which
* may happen before or after materializing the `Flow`.
* The stream terminates with an error if the `Future` is completed with a failure.
*/
final case class FutureSource[In](future: Future[In]) extends SimpleSource[In] {
override def attach(flowSubscriber: Subscriber[In], materializer: ActorBasedFlowMaterializer, flowName: String): Unit =
create(materializer, flowName).subscribe(flowSubscriber)
override def isActive: Boolean = true
override def create(materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[In] =
future.value match {
case Some(Success(element))
ActorPublisher[In](materializer.actorOf(IterablePublisher.props(List(element), materializer.settings),
name = s"$flowName-0-future"), Some(future))
case Some(Failure(t))
ErrorPublisher(t).asInstanceOf[Publisher[In]]
case None
ActorPublisher[In](materializer.actorOf(FuturePublisher.props(future, materializer.settings),
name = s"$flowName-0-future"), Some(future))
}
}
/**
* Elements are produced from the tick closure periodically with the specified interval.
* The tick element will be delivered to downstream consumers that has requested any elements.
* If a consumer has not requested any elements at the point in time when the tick
* element is produced it will not receive that tick element later. It will
* receive new tick elements as soon as it has requested more elements.
*/
final case class TickSource[In](initialDelay: FiniteDuration, interval: FiniteDuration, tick: () In) extends SimpleSource[In] {
override def attach(flowSubscriber: Subscriber[In], materializer: ActorBasedFlowMaterializer, flowName: String): Unit =
create(materializer, flowName).subscribe(flowSubscriber)
override def isActive: Boolean = true
override def create(materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[In] =
ActorPublisher[In](materializer.actorOf(TickPublisher.props(initialDelay, interval, tick, materializer.settings),
name = s"$flowName-0-tick"))
}

View file

@ -0,0 +1,279 @@
/**
* Copyright (C) 2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.stream.scaladsl2
import akka.stream.impl._
import akka.stream.impl2.ActorBasedFlowMaterializer
import akka.stream.{ Transformer, OverflowStrategy, TimerTransformer }
import org.reactivestreams.{ Publisher, Subscriber }
import scala.collection.immutable
import scala.concurrent.duration.FiniteDuration
import scala.concurrent.Future
import scala.util.{ Failure, Success }
import scala.annotation.unchecked.uncheckedVariance
/**
* This trait is a marker for a pluggable stream tap. Concrete instances should
* implement [[TapWithKey]] or [[SimpleTap]], otherwise a custom [[FlowMaterializer]]
* will have to be used to be able to attach them.
*
* All Taps defined in this package rely upon an ActorBasedFlowMaterializer being
* made available to them in order to use the <code>attach</code> method. Other
* FlowMaterializers can be used but must then implement the functionality of these
* Tap nodes themselves (or construct an ActorBasedFlowMaterializer).
*/
trait Tap[+Out] extends Source[Out]
/**
* A tap that does not need to create a user-accessible object during materialization.
*/
trait SimpleTap[+Out] extends Tap[Out] with TapOps[Out] {
/**
* Attach this tap to the given [[org.reactivestreams.Subscriber]]. Using the given
* [[FlowMaterializer]] is completely optional, especially if this tap belongs to
* a different Reactive Streams implementation. It is the responsibility of the
* caller to provide a suitable FlowMaterializer that can be used for running
* Flows if necessary.
*
* @param flowSubscriber the Subscriber to produce elements to
* @param materializer a FlowMaterializer that may be used for creating flows
* @param flowName the name of the current flow, which should be used in log statements or error messages
*/
def attach(flowSubscriber: Subscriber[Out] @uncheckedVariance, materializer: ActorBasedFlowMaterializer, flowName: String): Unit
/**
* This method is only used for Taps that return true from [[#isActive]], which then must
* implement it.
*/
def create(materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[Out] @uncheckedVariance =
throw new UnsupportedOperationException(s"forgot to implement create() for $getClass that says isActive==true")
/**
* This method indicates whether this Tap can create a Publisher instead of being
* attached to a Subscriber. This is only used if the Flow does not contain any
* operations.
*/
def isActive: Boolean = false
// these are unique keys, case class equality would break them
final override def equals(other: Any): Boolean = super.equals(other)
final override def hashCode: Int = super.hashCode
}
/**
* A tap that will create an object during materialization that the user will need
* to retrieve in order to access aspects of this tap (could be a Subscriber, a
* Future/Promise, etc.).
*/
trait TapWithKey[+Out, T] extends TapOps[Out] {
/**
* Attach this tap to the given [[org.reactivestreams.Subscriber]]. Using the given
* [[FlowMaterializer]] is completely optional, especially if this tap belongs to
* a different Reactive Streams implementation. It is the responsibility of the
* caller to provide a suitable FlowMaterializer that can be used for running
* Flows if necessary.
*
* @param flowSubscriber the Subscriber to produce elements to
* @param materializer a FlowMaterializer that may be used for creating flows
* @param flowName the name of the current flow, which should be used in log statements or error messages
*/
def attach(flowSubscriber: Subscriber[Out] @uncheckedVariance, materializer: ActorBasedFlowMaterializer, flowName: String): T
/**
* This method is only used for Taps that return true from [[#isActive]], which then must
* implement it.
*/
def create(materializer: ActorBasedFlowMaterializer, flowName: String): (Publisher[Out] @uncheckedVariance, T) =
throw new UnsupportedOperationException(s"forgot to implement create() for $getClass that says isActive==true")
/**
* This method indicates whether this Tap can create a Publisher instead of being
* attached to a Subscriber. This is only used if the Flow does not contain any
* operations.
*/
def isActive: Boolean = false
// these are unique keys, case class equality would break them
final override def equals(other: Any): Boolean = super.equals(other)
final override def hashCode: Int = super.hashCode
}
/**
* Holds a `Subscriber` representing the input side of the flow.
* The `Subscriber` can later be connected to an upstream `Publisher`.
*/
final case class SubscriberTap[Out]() extends TapWithKey[Out, Subscriber[Out]] {
override def attach(flowSubscriber: Subscriber[Out], materializer: ActorBasedFlowMaterializer, flowName: String): Subscriber[Out] =
flowSubscriber
def subscriber(m: MaterializedTap): Subscriber[Out] = m.getTapFor(this)
}
/**
* Construct a transformation starting with given publisher. The transformation steps
* are executed by a series of [[org.reactivestreams.Processor]] instances
* that mediate the flow of elements downstream and the propagation of
* back-pressure upstream.
*/
final case class PublisherTap[Out](p: Publisher[Out]) extends SimpleTap[Out] {
override def attach(flowSubscriber: Subscriber[Out], materializer: ActorBasedFlowMaterializer, flowName: String): Unit =
p.subscribe(flowSubscriber)
override def isActive: Boolean = true
override def create(materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[Out] = p
}
/**
* Start a new `Source` from the given Iterator. The produced stream of elements
* will continue until the iterator runs empty or fails during evaluation of
* the `next()` method. Elements are pulled out of the iterator
* in accordance with the demand coming from the downstream transformation
* steps.
*/
final case class IteratorTap[Out](iterator: Iterator[Out]) extends SimpleTap[Out] {
override def attach(flowSubscriber: Subscriber[Out], materializer: ActorBasedFlowMaterializer, flowName: String): Unit =
create(materializer, flowName).subscribe(flowSubscriber)
override def isActive: Boolean = true
override def create(materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[Out] =
if (iterator.isEmpty) EmptyPublisher[Out]
else ActorPublisher[Out](materializer.actorOf(IteratorPublisher.props(iterator, materializer.settings),
name = s"$flowName-0-iterator"))
}
/**
* Starts a new `Source` from the given `Iterable`. This is like starting from an
* Iterator, but every Subscriber directly attached to the Publisher of this
* stream will see an individual flow of elements (always starting from the
* beginning) regardless of when they subscribed.
*/
final case class IterableTap[Out](iterable: immutable.Iterable[Out]) extends SimpleTap[Out] {
override def attach(flowSubscriber: Subscriber[Out], materializer: ActorBasedFlowMaterializer, flowName: String): Unit =
create(materializer, flowName).subscribe(flowSubscriber)
override def isActive: Boolean = true
override def create(materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[Out] =
if (iterable.isEmpty) EmptyPublisher[Out]
else ActorPublisher[Out](materializer.actorOf(IterablePublisher.props(iterable, materializer.settings),
name = s"$flowName-0-iterable"), Some(iterable))
}
/**
* Define the sequence of elements to be produced by the given closure.
* The stream ends normally when evaluation of the closure returns a `None`.
* The stream ends exceptionally when an exception is thrown from the closure.
*/
final case class ThunkTap[Out](f: () Option[Out]) extends SimpleTap[Out] {
override def attach(flowSubscriber: Subscriber[Out], materializer: ActorBasedFlowMaterializer, flowName: String): Unit =
create(materializer, flowName).subscribe(flowSubscriber)
override def isActive: Boolean = true
override def create(materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[Out] =
ActorPublisher[Out](materializer.actorOf(SimpleCallbackPublisher.props(materializer.settings,
() f().getOrElse(throw Stop)), name = s"$flowName-0-thunk"))
}
/**
* Start a new `Source` from the given `Future`. The stream will consist of
* one element when the `Future` is completed with a successful value, which
* may happen before or after materializing the `Flow`.
* The stream terminates with an error if the `Future` is completed with a failure.
*/
final case class FutureTap[Out](future: Future[Out]) extends SimpleTap[Out] {
override def attach(flowSubscriber: Subscriber[Out], materializer: ActorBasedFlowMaterializer, flowName: String): Unit =
create(materializer, flowName).subscribe(flowSubscriber)
override def isActive: Boolean = true
override def create(materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[Out] =
future.value match {
case Some(Success(element))
ActorPublisher[Out](materializer.actorOf(IterablePublisher.props(List(element), materializer.settings),
name = s"$flowName-0-future"), Some(future))
case Some(Failure(t))
ErrorPublisher(t).asInstanceOf[Publisher[Out]]
case None
ActorPublisher[Out](materializer.actorOf(FuturePublisher.props(future, materializer.settings),
name = s"$flowName-0-future"), Some(future))
}
}
/**
* Elements are produced from the tick closure periodically with the specified interval.
* The tick element will be delivered to downstream consumers that has requested any elements.
* If a consumer has not requested any elements at the point in time when the tick
* element is produced it will not receive that tick element later. It will
* receive new tick elements as soon as it has requested more elements.
*/
final case class TickTap[Out](initialDelay: FiniteDuration, interval: FiniteDuration, tick: () Out) extends SimpleTap[Out] {
override def attach(flowSubscriber: Subscriber[Out], materializer: ActorBasedFlowMaterializer, flowName: String): Unit =
create(materializer, flowName).subscribe(flowSubscriber)
override def isActive: Boolean = true
override def create(materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[Out] =
ActorPublisher[Out](materializer.actorOf(TickPublisher.props(initialDelay, interval, tick, materializer.settings),
name = s"$flowName-0-tick"))
}
trait MaterializedTap {
/**
* Do not call directly. Use accessor method in the concrete `Tap`, e.g. [[SubscriberTap#subscriber]].
*/
def getTapFor[T](tapKey: TapWithKey[_, T]): T
}
trait TapOps[+Out] extends Tap[Out] {
override type Repr[+O] = SourcePipe[O]
private def sourcePipe = Pipe.empty[Out].withTap(this)
override def connect[T](flow: Flow[Out, T]): Source[T] = sourcePipe.connect(flow)
override def connect(sink: Sink[Out]): RunnableFlow = sourcePipe.connect(sink)
override def toPublisher()(implicit materializer: FlowMaterializer): Publisher[Out @uncheckedVariance] =
sourcePipe.toPublisher()(materializer)
override def toFanoutPublisher(initialBufferSize: Int, maximumBufferSize: Int)(implicit materializer: FlowMaterializer): Publisher[Out @uncheckedVariance] =
sourcePipe.toFanoutPublisher(initialBufferSize, maximumBufferSize)(materializer)
override def publishTo(subscriber: Subscriber[Out @uncheckedVariance])(implicit materializer: FlowMaterializer): Unit =
sourcePipe.publishTo(subscriber)(materializer)
override def consume()(implicit materializer: FlowMaterializer): Unit =
sourcePipe.consume()
override def map[T](f: Out T): Repr[T] = sourcePipe.map(f)
override def mapConcat[T](f: Out immutable.Seq[T]): Repr[T] = sourcePipe.mapConcat(f)
override def mapAsync[T](f: Out Future[T]): Repr[T] = sourcePipe.mapAsync(f)
override def mapAsyncUnordered[T](f: Out Future[T]): Repr[T] = sourcePipe.mapAsyncUnordered(f)
override def filter(p: Out Boolean): Repr[Out] = sourcePipe.filter(p)
override def collect[T](pf: PartialFunction[Out, T]): Repr[T] = sourcePipe.collect(pf)
override def grouped(n: Int): Repr[immutable.Seq[Out]] = sourcePipe.grouped(n)
override def groupedWithin(n: Int, d: FiniteDuration): Repr[immutable.Seq[Out]] = sourcePipe.groupedWithin(n, d)
override def drop(n: Int): Repr[Out] = sourcePipe.drop(n)
override def dropWithin(d: FiniteDuration): Repr[Out] = sourcePipe.dropWithin(d)
override def take(n: Int): Repr[Out] = sourcePipe.take(n)
override def takeWithin(d: FiniteDuration): Repr[Out] = sourcePipe.takeWithin(d)
override def conflate[S](seed: Out S, aggregate: (S, Out) S): Repr[S] = sourcePipe.conflate(seed, aggregate)
override def expand[S, U](seed: Out S, extrapolate: S (U, S)): Repr[U] = sourcePipe.expand(seed, extrapolate)
override def buffer(size: Int, overflowStrategy: OverflowStrategy): Repr[Out] = sourcePipe.buffer(size, overflowStrategy)
override def transform[T](name: String, mkTransformer: () Transformer[Out, T]): Repr[T] = sourcePipe.transform(name, mkTransformer)
override def prefixAndTail[U >: Out](n: Int): Repr[(immutable.Seq[Out], Source[U])] = sourcePipe.prefixAndTail(n)
override def groupBy[K, U >: Out](f: Out K): Repr[(K, Source[U])] = sourcePipe.groupBy(f)
override def splitWhen[U >: Out](p: Out Boolean): Repr[Source[U]] = sourcePipe.splitWhen(p)
override def flatten[U](strategy: FlattenStrategy[Out, U]): Repr[U] = sourcePipe.flatten(strategy)
override def timerTransform[U](name: String, mkTransformer: () TimerTransformer[Out, U]): Repr[U] =
sourcePipe.timerTransform(name, mkTransformer)
}

View file

@ -5,29 +5,29 @@ package akka.stream
/**
* Scala API: The flow DSL allows the formulation of stream transformations based on some
* input. The starting point is called [[Source]] and can be a collection, an iterator,
* input. The starting point is called [[Tap]] and can be a collection, an iterator,
* a block of code which is evaluated repeatedly or a [[org.reactivestreams.Publisher]].
* A flow with an attached `Source` is a [[FlowWithSource]] and is constructed
* with the `apply` methods in [[FlowFrom]].
* A flow with an attached input and open output, including `Tap` is a [[Source]], and is
* constructed with the `apply` methods in [[Source]].
*
* A flow may also be defined without an attached input `Source` and that is then
* a [[ProcessorFlow]]. The `Source` can be attached later with [[ProcessorFlow#withSource]]
* and it becomes a [[FlowWithSource]].
* A flow may also be defined without an attached input or output and that is then
* a [[Flow]]. The `Flow` can be connected to the `Source` later by using [[Source#connect]] with
* the flow as argument, and it remains a [[Source]].
*
* Transformations can appended to `FlowWithSource` and `ProcessorFlow` with the operations
* Transformations can be appended to `Source` and `Flow` with the operations
* defined in [[FlowOps]]. Each DSL element produces a new flow that can be further transformed,
* building up a description of the complete transformation pipeline.
*
* The output of the flow can be attached to a [[Sink]] with [[FlowWithSource#withSink]]
* and if it also has an attached `Source` it becomes a [[RunnableFlow]]. In order to execute
* this pipeline the flow must be materialized by calling [[RunnableFlow#run]] on it.
* The termination point of a flow is called [[Drain]] and can for example be a `Future` or
* [[org.reactivestreams.Subscriber]]. A flow with an attached output and open input,
* including `Drain`, is a [[Sink]].
*
* You may also first attach the `Sink` to a `ProcessorFlow` with [[ProcessorFlow#withSink]]
* and then it becomes a [[FlowWithSink]] and then attach the `Source` to make
* it runnable.
* If a flow has both an attached input and an attached output it becomes a [[RunnableFlow]].
* In order to execute this pipeline the flow must be materialized by calling [[RunnableFlow#run]] on it.
*
* Flows can be wired together before they are materialized by appending or prepending them, or
* connecting them into a [[FlowGraph]] with fan-in and fan-out elements.
* You can create your `Source`, `Flow` and `Sink` in any order and then wire them together before
* they are materialized by connecting them using [[Flow#connect]], or connecting them into a
* [[FlowGraph]] with fan-in and fan-out elements.
*
* See <a href="https://github.com/reactive-streams/reactive-streams/">Reactive Streams</a> for
* details on [[org.reactivestreams.Publisher]] and [[org.reactivestreams.Subscriber]].