/** * Copyright (C) 2015 Typesafe Inc. */ package akka.stream.javadsl; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; import org.junit.ClassRule; import org.junit.Test; import scala.concurrent.Await; import scala.concurrent.Future; import scala.concurrent.duration.Duration; import scala.concurrent.duration.FiniteDuration; import scala.runtime.BoxedUnit; import akka.japi.Pair; import akka.stream.*; import akka.stream.testkit.AkkaSpec; import akka.stream.javadsl.GraphDSL.Builder; import akka.japi.function.*; import akka.util.ByteString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertArrayEquals; public class BidiFlowTest extends StreamTest { public BidiFlowTest() { super(actorSystemResource); } @ClassRule public static AkkaJUnitActorSystemResource actorSystemResource = new AkkaJUnitActorSystemResource( "FlowTest", AkkaSpec.testConf()); private final BidiFlow bidi = BidiFlow .fromGraph(GraphDSL.create( new Function, BidiShape>() { @Override public BidiShape apply(Builder b) throws Exception { final FlowShape top = b.add(Flow .of(Integer.class).map(new Function() { @Override public Long apply(Integer arg) { return (long) ((int) arg) + 2; } })); final FlowShape bottom = b.add(Flow .of(ByteString.class).map(new Function() { @Override public String apply(ByteString arg) { return arg.decodeString("UTF-8"); } })); return new BidiShape(top .in(), top.out(), bottom.in(), bottom.out()); } })); private final BidiFlow inverse = BidiFlow .fromGraph( GraphDSL.create( new Function, BidiShape>() { @Override public BidiShape apply(Builder b) throws Exception { final FlowShape top = b.add(Flow.of(Long.class) .map(new Function() { @Override public Integer apply(Long arg) { return (int) ((long) arg) + 2; } })); final FlowShape bottom = b.add(Flow .of(String.class).map(new Function() { @Override public ByteString apply(String arg) { return ByteString.fromString(arg); } })); return new BidiShape(top .in(), top.out(), bottom.in(), bottom.out()); } })); private final BidiFlow> bidiMat = BidiFlow.fromGraph( GraphDSL.create( Sink.head(), new Function2>, SinkShape, BidiShape>() { @Override public BidiShape apply(Builder> b, SinkShape sink) throws Exception { b.from(b.add(Source.single(42))).to(sink); final FlowShape top = b.add(Flow .of(Integer.class).map(new Function() { @Override public Long apply(Integer arg) { return (long) ((int) arg) + 2; } })); final FlowShape bottom = b.add(Flow .of(ByteString.class).map(new Function() { @Override public String apply(ByteString arg) { return arg.decodeString("UTF-8"); } })); return new BidiShape(top .in(), top.out(), bottom.in(), bottom.out()); } })); private final String str = "Hello World"; private final ByteString bytes = ByteString.fromString(str); private final List list = new ArrayList(); { list.add(1); list.add(2); list.add(3); } private final FiniteDuration oneSec = Duration.create(1, TimeUnit.SECONDS); @Test public void mustWorkInIsolation() throws Exception { final Pair, Future> p = RunnableGraph.fromGraph(GraphDSL .create(Sink. head(), Sink. head(), Keep., Future> both(), new Function3, Future>>, SinkShape, SinkShape, ClosedShape>() { @Override public ClosedShape apply(Builder, Future>> b, SinkShape st, SinkShape sb) throws Exception { final BidiShape s = b.add(bidi); b.from(b.add(Source.single(1))).toInlet(s.in1()); b.from(s.out1()).to(st); b.from(b.add(Source.single(bytes))).toInlet(s.in2()); b.from(s.out2()).to(sb); return ClosedShape.getInstance(); } })).run(materializer); final Long rt = Await.result(p.first(), oneSec); final String rb = Await.result(p.second(), oneSec); assertEquals((Long) 3L, rt); assertEquals(str, rb); } @Test public void mustWorkAsAFlowThatIsOpenOnTheLeft() throws Exception { final Flow f = bidi.join(Flow.of(Long.class).map( new Function() { @Override public ByteString apply(Long arg) { return ByteString.fromString("Hello " + arg); } })); final Future> result = Source.from(list).via(f).grouped(10).runWith(Sink.> head(), materializer); assertEquals(Arrays.asList("Hello 3", "Hello 4", "Hello 5"), Await.result(result, oneSec)); } @Test public void mustWorkAsAFlowThatIsOpenOnTheRight() throws Exception { final Flow f = Flow.of(String.class).map( new Function() { @Override public Integer apply(String arg) { return Integer.valueOf(arg); } }).join(bidi); final List inputs = Arrays.asList(ByteString.fromString("1"), ByteString.fromString("2")); final Future> result = Source.from(inputs).via(f).grouped(10).runWith(Sink.> head(), materializer); assertEquals(Arrays.asList(3L, 4L), Await.result(result, oneSec)); } @Test public void mustWorkWhenAtopItsInverse() throws Exception { final Flow f = bidi.atop(inverse).join(Flow.of(Integer.class).map( new Function() { @Override public String apply(Integer arg) { return arg.toString(); } })); final Future> result = Source.from(list).via(f).grouped(10).runWith(Sink.> head(), materializer); assertEquals(Arrays.asList("5", "6", "7"), Await.result(result, oneSec)); } @Test public void mustWorkWhenReversed() throws Exception { final Flow f = Flow.of(Integer.class).map( new Function() { @Override public String apply(Integer arg) { return arg.toString(); } }).join(inverse.reversed()).join(bidi.reversed()); final Future> result = Source.from(list).via(f).grouped(10).runWith(Sink.> head(), materializer); assertEquals(Arrays.asList("5", "6", "7"), Await.result(result, oneSec)); } @Test public void mustMaterializeToItsValue() throws Exception { final Future f = RunnableGraph.fromGraph( GraphDSL.create(bidiMat, new Function2 >, BidiShape, ClosedShape>() { @Override public ClosedShape apply(Builder> b, BidiShape shape) throws Exception { final FlowShape left = b.add(Flow.of(String.class).map( new Function() { @Override public Integer apply(String arg) { return Integer.valueOf(arg); } })); final FlowShape right = b.add(Flow.of(Long.class).map( new Function() { @Override public ByteString apply(Long arg) { return ByteString.fromString("Hello " + arg); } })); b.from(shape.out2()).via(left).toInlet(shape.in1()) .from(shape.out1()).via(right).toInlet(shape.in2()); return ClosedShape.getInstance(); } })).run(materializer); assertEquals((Integer) 42, Await.result(f, oneSec)); } @Test public void mustCombineMaterializationValues() throws Exception { final Flow> left = Flow.fromGraph(GraphDSL.create( Sink.head(), new Function2>, SinkShape, FlowShape>() { @Override public FlowShape apply(Builder> b, SinkShape sink) throws Exception { final UniformFanOutShape bcast = b.add(Broadcast.create(2)); final UniformFanInShape merge = b.add(Merge.create(2)); final FlowShape flow = b.add(Flow.of(String.class).map( new Function() { @Override public Integer apply(String arg) { return Integer.valueOf(arg); } })); b.from(bcast).to(sink) .from(b.add(Source.single(1))).viaFanOut(bcast).toFanIn(merge) .from(flow).toFanIn(merge); return new FlowShape(flow.in(), merge.out()); } })); final Flow>> right = Flow.fromGraph(GraphDSL.create( Sink.>head(), new Function2>>, SinkShape>, FlowShape>() { @Override public FlowShape apply(Builder>> b, SinkShape> sink) throws Exception { final FlowShape> flow = b.add(Flow.of(Long.class).grouped(10)); b.from(flow).to(sink); return new FlowShape(flow.in(), b.add(Source.single(ByteString.fromString("10"))).out()); } })); final Pair, Future>, Future>> result = left.joinMat(bidiMat, Keep., Future> both()).joinMat(right, Keep., Future>, Future>> both()).run(materializer); final Future l = result.first().first(); final Future m = result.first().second(); final Future> r = result.second(); assertEquals((Integer) 1, Await.result(l, oneSec)); assertEquals((Integer) 42, Await.result(m, oneSec)); final Long[] rr = Await.result(r, oneSec).toArray(new Long[2]); Arrays.sort(rr); assertArrayEquals(new Long[] { 3L, 12L }, rr); } public void mustSuitablyOverrideAttributeHandlingMethods() { @SuppressWarnings("unused") final BidiFlow b = bidi.withAttributes(Attributes.name("")).addAttributes(Attributes.asyncBoundary()).named(""); } }