Make the AbstractBehavior builder mutable (#26309)

* Make the AbstractBehavior builder mutable #26260

* Use mutable builder style in first sample, mention that fluent is an option

* A bit of rework of the Java builders:

 * onAnyMessage added
 * use the japi SAMs throughout in the APIs
 * avoid wrapping the japi functions in Scala functions for the most common cases
 * more Java test coverage

* Not just any exception

* Works on 2.11 as well as 2.12
This commit is contained in:
Johan Andrén 2019-02-12 15:38:35 +01:00 committed by Patrik Nordwall
parent ddada9a8e1
commit 8fabb73f2b
22 changed files with 391 additions and 268 deletions

View file

@ -4,6 +4,9 @@
package akka.actor.typed.javadsl; package akka.actor.typed.javadsl;
import akka.actor.testkit.typed.javadsl.TestKitJunitResource;
import akka.actor.testkit.typed.javadsl.TestProbe;
import org.junit.ClassRule;
import org.junit.Test; import org.junit.Test;
import org.scalatest.junit.JUnitSuite; import org.scalatest.junit.JUnitSuite;
@ -15,9 +18,13 @@ import java.util.ArrayList;
import static akka.actor.typed.javadsl.Behaviors.same; import static akka.actor.typed.javadsl.Behaviors.same;
import static akka.actor.typed.javadsl.Behaviors.stopped; import static akka.actor.typed.javadsl.Behaviors.stopped;
import static org.junit.Assert.assertEquals;
/** Test creating [[Behavior]]s using [[BehaviorBuilder]] */ /** Test creating [[Behavior]]s using [[BehaviorBuilder]] */
public class BehaviorBuilderTest extends JUnitSuite { public class BehaviorBuilderTest extends JUnitSuite {
@ClassRule public static final TestKitJunitResource testKit = new TestKitJunitResource();
interface Message {} interface Message {}
static final class One implements Message { static final class One implements Message {
@ -28,7 +35,6 @@ public class BehaviorBuilderTest extends JUnitSuite {
static final class MyList<T> extends ArrayList<T> implements Message {}; static final class MyList<T> extends ArrayList<T> implements Message {};
@Test
public void shouldCompile() { public void shouldCompile() {
Behavior<Message> b = Behavior<Message> b =
Behaviors.receive(Message.class) Behaviors.receive(Message.class)
@ -54,6 +60,86 @@ public class BehaviorBuilderTest extends JUnitSuite {
.build(); .build();
} }
@Test
public void caseSelectedInOrderAdded() {
final TestProbe<Object> probe = testKit.createTestProbe();
Behavior<Object> behavior =
BehaviorBuilder.create()
.onMessage(
String.class,
(context, msg) -> {
probe.ref().tell("handler 1: " + msg);
return Behaviors.same();
})
.onMessage(
String.class,
(context, msg) -> {
probe.ref().tell("handler 2: " + msg);
return Behaviors.same();
})
.build();
ActorRef<Object> ref = testKit.spawn(behavior);
ref.tell("message");
probe.expectMessage("handler 1: message");
}
@Test
public void handleMessageBasedOnEquality() {
final TestProbe<Object> probe = testKit.createTestProbe();
Behavior<Object> behavior =
BehaviorBuilder.create()
.onMessageEquals(
"message",
(context) -> {
probe.ref().tell("got it");
return Behaviors.same();
})
.build();
ActorRef<Object> ref = testKit.spawn(behavior);
ref.tell("message");
probe.expectMessage("got it");
}
@Test
public void applyPredicate() {
final TestProbe<Object> probe = testKit.createTestProbe();
Behavior<Object> behavior =
BehaviorBuilder.create()
.onMessage(
String.class,
(msg) -> "other".equals(msg),
(context, msg) -> {
probe.ref().tell("handler 1: " + msg);
return Behaviors.same();
})
.onMessage(
String.class,
(context, msg) -> {
probe.ref().tell("handler 2: " + msg);
return Behaviors.same();
})
.build();
ActorRef<Object> ref = testKit.spawn(behavior);
ref.tell("message");
probe.expectMessage("handler 2: message");
}
@Test
public void catchAny() {
final TestProbe<Object> probe = testKit.createTestProbe();
Behavior<Object> behavior =
BehaviorBuilder.create()
.onAnyMessage(
(context, msg) -> {
probe.ref().tell(msg);
return same();
})
.build();
ActorRef<Object> ref = testKit.spawn(behavior);
ref.tell("message");
probe.expectMessage("message");
}
interface CounterMessage {}; interface CounterMessage {};
static final class Increase implements CounterMessage {}; static final class Increase implements CounterMessage {};
@ -92,6 +178,12 @@ public class BehaviorBuilderTest extends JUnitSuite {
@Test @Test
public void testImmutableCounter() { public void testImmutableCounter() {
Behavior<CounterMessage> immutable = immutableCounter(0); ActorRef<CounterMessage> ref = testKit.spawn(immutableCounter(0));
TestProbe<Got> probe = testKit.createTestProbe();
ref.tell(new Get(probe.getRef()));
assertEquals(0, probe.expectMessageClass(Got.class).n);
ref.tell(new Increase());
ref.tell(new Get(probe.getRef()));
assertEquals(1, probe.expectMessageClass(Got.class).n);
} }
} }

View file

@ -4,16 +4,23 @@
package akka.actor.typed.javadsl; package akka.actor.typed.javadsl;
import akka.actor.testkit.typed.javadsl.TestKitJunitResource;
import akka.actor.testkit.typed.javadsl.TestProbe;
import akka.actor.typed.ActorRef;
import org.junit.ClassRule;
import org.junit.Test; import org.junit.Test;
import org.scalatest.junit.JUnitSuite; import org.scalatest.junit.JUnitSuite;
import akka.actor.typed.Behavior; import akka.actor.typed.Behavior;
import static akka.actor.typed.javadsl.Behaviors.same;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
/** Test creating [[MutableActor]]s using [[ReceiveBuilder]] */ /** Test creating [[MutableActor]]s using [[ReceiveBuilder]] */
public class ReceiveBuilderTest extends JUnitSuite { public class ReceiveBuilderTest extends JUnitSuite {
@ClassRule public static final TestKitJunitResource testKit = new TestKitJunitResource();
@Test @Test
public void testMutableCounter() { public void testMutableCounter() {
Behavior<BehaviorBuilderTest.CounterMessage> mutable = Behavior<BehaviorBuilderTest.CounterMessage> mutable =
@ -36,7 +43,7 @@ public class ReceiveBuilderTest extends JUnitSuite {
@Override @Override
public Receive<BehaviorBuilderTest.CounterMessage> createReceive() { public Receive<BehaviorBuilderTest.CounterMessage> createReceive() {
return receiveBuilder() return newReceiveBuilder()
.onMessage(BehaviorBuilderTest.Increase.class, this::receiveIncrease) .onMessage(BehaviorBuilderTest.Increase.class, this::receiveIncrease)
.onMessage(BehaviorBuilderTest.Get.class, this::receiveGet) .onMessage(BehaviorBuilderTest.Get.class, this::receiveGet)
.build(); .build();
@ -56,7 +63,7 @@ public class ReceiveBuilderTest extends JUnitSuite {
@Override @Override
public Receive<BehaviorBuilderTest.CounterMessage> createReceive() { public Receive<BehaviorBuilderTest.CounterMessage> createReceive() {
assertEquals(42, value); assertEquals(42, value);
return receiveBuilder().build(); return newReceiveBuilder().build();
} }
} }
@ -65,4 +72,67 @@ public class ReceiveBuilderTest extends JUnitSuite {
MyAbstractBehavior mutable = new MyAbstractBehavior(42); MyAbstractBehavior mutable = new MyAbstractBehavior(42);
assertEquals(Behaviors.unhandled(), mutable.receive(null, new BehaviorBuilderTest.Increase())); assertEquals(Behaviors.unhandled(), mutable.receive(null, new BehaviorBuilderTest.Increase()));
} }
@Test
public void caseSelectedInOrderAdded() {
final TestProbe<Object> probe = testKit.createTestProbe();
Behavior<Object> behavior =
ReceiveBuilder.<Object>create()
.onMessage(
String.class,
msg -> {
probe.ref().tell("handler 1: " + msg);
return Behaviors.same();
})
.onMessage(
String.class,
msg -> {
probe.ref().tell("handler 2: " + msg);
return Behaviors.same();
})
.build();
ActorRef<Object> ref = testKit.spawn(behavior);
ref.tell("message");
probe.expectMessage("handler 1: message");
}
@Test
public void applyPredicate() {
final TestProbe<Object> probe = testKit.createTestProbe();
Behavior<Object> behavior =
ReceiveBuilder.create()
.onMessage(
String.class,
msg -> "other".equals(msg),
msg -> {
probe.ref().tell("handler 1: " + msg);
return Behaviors.same();
})
.onMessage(
String.class,
msg -> {
probe.ref().tell("handler 2: " + msg);
return Behaviors.same();
})
.build();
ActorRef<Object> ref = testKit.spawn(behavior);
ref.tell("message");
probe.expectMessage("handler 2: message");
}
@Test
public void catchAny() {
final TestProbe<Object> probe = testKit.createTestProbe();
Behavior<Object> behavior =
ReceiveBuilder.create()
.onAnyMessage(
msg -> {
probe.ref().tell(msg);
return same();
})
.build();
ActorRef<Object> ref = testKit.spawn(behavior);
ref.tell("message");
probe.expectMessage("message");
}
} }

View file

@ -207,7 +207,7 @@ public class InteractionPatternsTest extends JUnitSuite {
@Override @Override
public Receive<Command> createReceive() { public Receive<Command> createReceive() {
return receiveBuilder() return newReceiveBuilder()
.onMessage( .onMessage(
Translate.class, Translate.class,
cmd -> { cmd -> {
@ -603,7 +603,7 @@ public class InteractionPatternsTest extends JUnitSuite {
@Override @Override
public Receive<Object> createReceive() { public Receive<Object> createReceive() {
return receiveBuilder() return newReceiveBuilder()
.onMessage( .onMessage(
Wallet.class, Wallet.class,
(wallet) -> { (wallet) -> {

View file

@ -5,15 +5,11 @@
package jdocs.akka.typed; package jdocs.akka.typed;
// #imports // #imports
import akka.NotUsed;
import akka.actor.typed.ActorRef; import akka.actor.typed.ActorRef;
import akka.actor.typed.ActorSystem; import akka.actor.typed.ActorSystem;
import akka.actor.typed.Behavior; import akka.actor.typed.Behavior;
import akka.actor.typed.Terminated; import akka.actor.typed.Terminated;
import akka.actor.typed.javadsl.AbstractBehavior; import akka.actor.typed.javadsl.*;
import akka.actor.typed.javadsl.ActorContext;
import akka.actor.typed.javadsl.Behaviors;
import akka.actor.typed.javadsl.Receive;
// #imports // #imports
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -112,8 +108,9 @@ public class OOIntroTest {
@Override @Override
public Receive<RoomCommand> createReceive() { public Receive<RoomCommand> createReceive() {
return receiveBuilder() ReceiveBuilder<RoomCommand> builder = newReceiveBuilder();
.onMessage(
builder.onMessage(
GetSession.class, GetSession.class,
getSession -> { getSession -> {
ActorRef<SessionEvent> client = getSession.replyTo; ActorRef<SessionEvent> client = getSession.replyTo;
@ -125,16 +122,18 @@ public class OOIntroTest {
client.tell(new SessionGranted(ses.narrow())); client.tell(new SessionGranted(ses.narrow()));
sessions.add(ses); sessions.add(ses);
return this; return this;
}) });
.onMessage(
builder.onMessage(
PublishSessionMessage.class, PublishSessionMessage.class,
pub -> { pub -> {
NotifyClient notification = NotifyClient notification =
new NotifyClient((new MessagePosted(pub.screenName, pub.message))); new NotifyClient((new MessagePosted(pub.screenName, pub.message)));
sessions.forEach(s -> s.tell(notification)); sessions.forEach(s -> s.tell(notification));
return this; return this;
}) });
.build();
return builder.build();
} }
} }

View file

@ -42,13 +42,13 @@ abstract class AbstractBehavior[T] extends ExtensibleBehavior[T] {
/** /**
* Implement this to define how messages and signals are processed. Use the * Implement this to define how messages and signals are processed. Use the
* [[AbstractBehavior.receiveBuilder]] to define the message dispatch. * [[AbstractBehavior.newReceiveBuilder]] to define the message dispatch.
*/ */
def createReceive: Receive[T] def createReceive: Receive[T]
/** /**
* Create a [[ReceiveBuilder]] to define the message dispatch of the `Behavior`. * Create a new [[ReceiveBuilder]] to define the message dispatch of the `Behavior`.
* Typically used from [[AbstractBehavior.createReceive]]. * Typically used from [[AbstractBehavior.createReceive]].
*/ */
def receiveBuilder: ReceiveBuilder[T] = ReceiveBuilder.create def newReceiveBuilder: ReceiveBuilder[T] = ReceiveBuilder.create
} }

View file

@ -5,29 +5,34 @@
package akka.actor.typed.javadsl package akka.actor.typed.javadsl
import scala.annotation.tailrec import scala.annotation.tailrec
import akka.japi.function.{ Function JFunction }
import akka.japi.function.{ Function, Function2, Predicate } import akka.japi.function.{ Function2 JFunction2 }
import akka.japi.function.{ Predicate JPredicate }
import akka.annotation.InternalApi import akka.annotation.InternalApi
import akka.actor.typed import akka.actor.typed.Behavior
import akka.actor.typed.{ Behavior, ExtensibleBehavior, Signal } import akka.actor.typed.ExtensibleBehavior
import akka.actor.typed.Signal
import akka.actor.typed.TypedActorContext
import akka.actor.typed.Behavior.unhandled import akka.actor.typed.Behavior.unhandled
import BehaviorBuilder._ import BehaviorBuilder._
import akka.util.OptionVal
/** /**
* Used for creating a [[Behavior]] by 'chaining' message and signal handlers. * Immutable builder used for creating a [[Behavior]] by 'chaining' message and signal handlers.
* *
* When handling a message or signal, this [[Behavior]] will consider all handlers in the order they were added, * When handling a message or signal, this [[Behavior]] will consider all handlers in the order they were added,
* looking for the first handler for which both the type and the (optional) predicate match. * looking for the first handler for which both the type and the (optional) predicate match.
* *
* @tparam T the common superclass of all supported messages. * @tparam T the common superclass of all supported messages.
*/ */
class BehaviorBuilder[T] private ( final class BehaviorBuilder[T] private (
private val messageHandlers: List[Case[T, T]], messageHandlers: List[Case[T, T]],
private val signalHandlers: List[Case[T, Signal]] signalHandlers: List[Case[T, Signal]]
) { ) {
/**
* Build a Behavior from the current state of the builder
*/
def build(): Behavior[T] = new BuiltBehavior(messageHandlers.reverse, signalHandlers.reverse) def build(): Behavior[T] = new BuiltBehavior(messageHandlers.reverse, signalHandlers.reverse)
/** /**
@ -36,10 +41,10 @@ class BehaviorBuilder[T] private (
* @param type type of message to match * @param type type of message to match
* @param handler action to apply if the type matches * @param handler action to apply if the type matches
* @tparam M type of message to match * @tparam M type of message to match
* @return a new behavior with the specified handling appended * @return a new behavior builder with the specified handling appended
*/ */
def onMessage[M <: T](`type`: Class[M], handler: Function2[ActorContext[T], M, Behavior[T]]): BehaviorBuilder[T] = def onMessage[M <: T](`type`: Class[M], handler: JFunction2[ActorContext[T], M, Behavior[T]]): BehaviorBuilder[T] =
withMessage(`type`, None, (i1: ActorContext[T], msg: T) handler.apply(i1, msg.asInstanceOf[M])) withMessage(OptionVal.Some(`type`), OptionVal.None, handler)
/** /**
* Add a new predicated case to the message handling. * Add a new predicated case to the message handling.
@ -48,14 +53,13 @@ class BehaviorBuilder[T] private (
* @param test a predicate that will be evaluated on the argument if the type matches * @param test a predicate that will be evaluated on the argument if the type matches
* @param handler action to apply if the type matches and the predicate returns true * @param handler action to apply if the type matches and the predicate returns true
* @tparam M type of message to match * @tparam M type of message to match
* @return a new behavior with the specified handling appended * @return a new behavior builder with the specified handling appended
*/ */
def onMessage[M <: T](`type`: Class[M], test: Predicate[M], handler: Function2[ActorContext[T], M, Behavior[T]]): BehaviorBuilder[T] = def onMessage[M <: T](`type`: Class[M], test: JPredicate[M], handler: JFunction2[ActorContext[T], M, Behavior[T]]): BehaviorBuilder[T] =
withMessage( withMessage(
`type`, OptionVal.Some(`type`),
Some((t: T) test.test(t.asInstanceOf[M])), OptionVal.Some((t: T) test.test(t.asInstanceOf[M])),
(i1: ActorContext[T], msg: T) handler.apply(i1, msg.asInstanceOf[M]) handler)
)
/** /**
* Add a new case to the message handling without compile time type check. * Add a new case to the message handling without compile time type check.
@ -65,20 +69,36 @@ class BehaviorBuilder[T] private (
* *
* @param type type of message to match * @param type type of message to match
* @param handler action to apply when the type matches * @param handler action to apply when the type matches
* @return a new behavior with the specified handling appended * @return a new behavior builder with the specified handling appended
*/ */
def onMessageUnchecked[M <: T](`type`: Class[_ <: T], handler: Function2[ActorContext[T], M, Behavior[T]]): BehaviorBuilder[T] = def onMessageUnchecked[M <: T](`type`: Class[_ <: T], handler: JFunction2[ActorContext[T], M, Behavior[T]]): BehaviorBuilder[T] =
withMessage(`type`, None, (i1: ActorContext[T], msg: T) handler.apply(i1, msg.asInstanceOf[M])) withMessage[M](
OptionVal.Some(`type`.asInstanceOf[Class[M]]), OptionVal.None, handler)
/** /**
* Add a new case to the message handling matching equal messages. * Add a new case to the message handling matching equal messages.
* *
* @param msg the message to compare to * @param msg the message to compare to
* @param handler action to apply when the message matches * @param handler action to apply when the message matches
* @return a new behavior with the specified handling appended * @return a new behavior builder with the specified handling appended
*/ */
def onMessageEquals(msg: T, handler: Function[ActorContext[T], Behavior[T]]): BehaviorBuilder[T] = def onMessageEquals(msg: T, handler: JFunction[ActorContext[T], Behavior[T]]): BehaviorBuilder[T] =
withMessage(msg.getClass, Some(_.equals(msg)), (ctx: ActorContext[T], _: T) handler.apply(ctx)) withMessage[T](
OptionVal.Some(msg.getClass.asInstanceOf[Class[T]]),
OptionVal.Some(_.equals(msg)),
new JFunction2[ActorContext[T], T, Behavior[T]] {
override def apply(ctx: ActorContext[T], msg: T): Behavior[T] = handler.apply(ctx)
})
/**
* Add a new case to the message handling matching any message. Subsequent `onMessage` clauses will
* never see any messages.
*
* @param handler action to apply for any message
* @return a new behavior builder with the specified handling appended
*/
def onAnyMessage(handler: JFunction2[ActorContext[T], T, Behavior[T]]): BehaviorBuilder[T] =
withMessage(OptionVal.None, OptionVal.None, handler)
/** /**
* Add a new case to the signal handling. * Add a new case to the signal handling.
@ -86,10 +106,13 @@ class BehaviorBuilder[T] private (
* @param type type of signal to match * @param type type of signal to match
* @param handler action to apply if the type matches * @param handler action to apply if the type matches
* @tparam M type of signal to match * @tparam M type of signal to match
* @return a new behavior with the specified handling appended * @return a new behavior builder with the specified handling appended
*/ */
def onSignal[M <: Signal](`type`: Class[M], handler: Function2[ActorContext[T], M, Behavior[T]]): BehaviorBuilder[T] = def onSignal[M <: Signal](`type`: Class[M], handler: JFunction2[ActorContext[T], M, Behavior[T]]): BehaviorBuilder[T] =
withSignal(`type`, None, (ctx: ActorContext[T], signal: Signal) handler.apply(ctx, signal.asInstanceOf[M])) withSignal(
`type`,
OptionVal.None,
handler.asInstanceOf[JFunction2[ActorContext[T], Signal, Behavior[T]]])
/** /**
* Add a new predicated case to the signal handling. * Add a new predicated case to the signal handling.
@ -98,168 +121,87 @@ class BehaviorBuilder[T] private (
* @param test a predicate that will be evaluated on the argument if the type matches * @param test a predicate that will be evaluated on the argument if the type matches
* @param handler action to apply if the type matches and the predicate returns true * @param handler action to apply if the type matches and the predicate returns true
* @tparam M type of signal to match * @tparam M type of signal to match
* @return a new behavior with the specified handling appended * @return a new behavior builder with the specified handling appended
*/ */
def onSignal[M <: Signal](`type`: Class[M], test: Predicate[M], handler: Function2[ActorContext[T], M, Behavior[T]]): BehaviorBuilder[T] = def onSignal[M <: Signal](`type`: Class[M], test: JPredicate[M], handler: JFunction2[ActorContext[T], M, Behavior[T]]): BehaviorBuilder[T] =
withSignal( withSignal(
`type`, `type`,
Some((t: Signal) test.test(t.asInstanceOf[M])), OptionVal.Some((t: Signal) test.test(t.asInstanceOf[M])),
(ctx: ActorContext[T], signal: Signal) handler.apply(ctx, signal.asInstanceOf[M]) handler.asInstanceOf[JFunction2[ActorContext[T], Signal, Behavior[T]]]
) )
/**
* Add a new case to the signal handling without compile time type check.
*
* Should normally not be used, but when matching on class with generic type
* argument it can be useful, e.g. <code>GenMsg.class</code> and <code>(ActorContext<Message> ctx, GenMsg&lt;String&gt; list) -> {...}</code>
*
* @param type type of signal to match
* @param handler action to apply when the type matches
* @return a new behavior with the specified handling appended
*/
def onSignalUnchecked[M <: Signal](`type`: Class[_ <: Signal], handler: Function2[ActorContext[T], M, Behavior[T]]): BehaviorBuilder[T] =
withSignal(`type`, None, (ctx: ActorContext[T], signal: Signal) handler.apply(ctx, signal.asInstanceOf[M]))
/** /**
* Add a new case to the signal handling matching equal signals. * Add a new case to the signal handling matching equal signals.
* *
* @param signal the signal to compare to * @param signal the signal to compare to
* @param handler action to apply when the message matches * @param handler action to apply when the message matches
* @return a new behavior with the specified handling appended * @return a new behavior builder with the specified handling appended
*/ */
def onSignalEquals(signal: Signal, handler: Function[ActorContext[T], Behavior[T]]): BehaviorBuilder[T] = def onSignalEquals(signal: Signal, handler: Function[ActorContext[T], Behavior[T]]): BehaviorBuilder[T] =
withSignal(signal.getClass, Some(_.equals(signal)), (ctx: ActorContext[T], _: Signal) handler.apply(ctx)) withSignal(
signal.getClass,
OptionVal.Some(_.equals(signal)),
new JFunction2[ActorContext[T], Signal, Behavior[T]] {
override def apply(ctx: ActorContext[T], signal: Signal): Behavior[T] = {
handler.apply(ctx)
}
})
private def withMessage(`type`: Class[_ <: T], test: Option[T Boolean], handler: (ActorContext[T], T) Behavior[T]): BehaviorBuilder[T] = private def withMessage[M <: T](clazz: OptionVal[Class[M]], test: OptionVal[M Boolean], handler: JFunction2[ActorContext[T], M, Behavior[T]]): BehaviorBuilder[T] = {
new BehaviorBuilder[T](Case[T, T](`type`, test, handler) +: messageHandlers, signalHandlers) val newCase = Case(
clazz,
test,
handler
)
new BehaviorBuilder[T](newCase.asInstanceOf[Case[T, T]] +: messageHandlers, signalHandlers)
}
private def withSignal[M <: Signal](`type`: Class[M], test: Option[Signal Boolean], handler: (ActorContext[T], Signal) Behavior[T]): BehaviorBuilder[T] = private def withSignal[M <: Signal](`type`: Class[M], test: OptionVal[Signal Boolean], handler: JFunction2[ActorContext[T], Signal, Behavior[T]]): BehaviorBuilder[T] = {
new BehaviorBuilder[T](messageHandlers, Case[T, Signal](`type`, test, handler) +: signalHandlers) new BehaviorBuilder[T](
messageHandlers,
Case(OptionVal.Some(`type`), test, handler).asInstanceOf[Case[T, Signal]] +: signalHandlers
)
}
} }
object BehaviorBuilder { object BehaviorBuilder {
def create[T]: BehaviorBuilder[T] = new BehaviorBuilder[T](Nil, Nil)
private val _empty = new BehaviorBuilder[Nothing](Nil, Nil)
// used for both matching signals and messages so we throw away types after they are enforced by the builder API above
/** INTERNAL API */ /** INTERNAL API */
@InternalApi @InternalApi
private[javadsl] final case class Case[BT, MT](`type`: Class[_ <: MT], test: Option[MT Boolean], handler: (ActorContext[BT], MT) Behavior[BT]) private[javadsl] final case class Case[BT, MT](`type`: OptionVal[Class[_ <: MT]], test: OptionVal[MT Boolean], handler: JFunction2[ActorContext[BT], MT, Behavior[BT]])
/** /**
* Start a new behavior chain starting with this case. * @return new empty immutable behavior builder.
*
* @param type type of message to match
* @param handler action to apply if the type matches
* @tparam T type of behavior to create
* @tparam M type of message to match
* @return a new behavior with the specified handling appended
*/ */
def message[T, M <: T](`type`: Class[M], handler: Function2[ActorContext[T], M, Behavior[T]]): BehaviorBuilder[T] = def create[T]: BehaviorBuilder[T] = _empty.asInstanceOf[BehaviorBuilder[T]]
BehaviorBuilder.create[T].onMessage(`type`, handler)
/**
* Start a new behavior chain starting with this predicated case.
*
* @param type type of message to match
* @param test a predicate that will be evaluated on the argument if the type matches
* @param handler action to apply if the type matches and the predicate returns true
* @tparam T type of behavior to create
* @tparam M type of message to match
* @return a new behavior with the specified handling appended
*/
def message[T, M <: T](`type`: Class[M], test: Predicate[M], handler: Function2[ActorContext[T], M, Behavior[T]]): BehaviorBuilder[T] =
BehaviorBuilder.create[T].onMessage(`type`, test, handler)
/**
* Start a new behavior chain starting with a handler without compile time type check.
*
* Should normally not be used, but when matching on class with generic type
* argument it can be useful, e.g. <code>List.class</code> and <code>(List&lt;String&gt; list) -> {...}</code>
*
* @param type type of message to match
* @param handler action to apply when the type matches
* @return a new behavior with the specified handling appended
*/
def messageUnchecked[T, M <: T](`type`: Class[_ <: T], handler: Function2[ActorContext[T], M, Behavior[T]]): BehaviorBuilder[T] =
BehaviorBuilder.create[T].onMessageUnchecked(`type`, handler)
/**
* Start a new behavior chain starting with a handler for equal messages.
*
* @param msg the message to compare to
* @param handler action to apply when the message matches
* @tparam T type of behavior to create
* @return a new behavior with the specified handling appended
*/
def messageEquals[T](msg: T, handler: Function[ActorContext[T], Behavior[T]]): BehaviorBuilder[T] =
BehaviorBuilder.create[T].onMessageEquals(msg, handler)
/**
* Start a new behavior chain starting with this signal case.
*
* @param type type of signal to match
* @param handler action to apply if the type matches
* @tparam T type of behavior to create
* @tparam M type of signal to match
* @return a new behavior with the specified handling appended
*/
def signal[T, M <: Signal](`type`: Class[M], handler: Function2[ActorContext[T], M, Behavior[T]]): BehaviorBuilder[T] =
BehaviorBuilder.create[T].onSignal(`type`, handler)
/**
* Start a new behavior chain starting with this predicated signal case.
*
* @param type type of signals to match
* @param test a predicate that will be evaluated on the argument if the type matches
* @param handler action to apply if the type matches and the predicate returns true
* @tparam T type of behavior to create
* @tparam M type of signal to match
* @return a new behavior with the specified handling appended
*/
def signal[T, M <: Signal](`type`: Class[M], test: Predicate[M], handler: Function2[ActorContext[T], M, Behavior[T]]): BehaviorBuilder[T] =
BehaviorBuilder.create[T].onSignal(`type`, test, handler)
/**
* Start a new behavior chain starting with this unchecked signal case.
*
* Should normally not be used, but when matching on class with generic type
* argument it can be useful, e.g. <code>GenMsg.class</code> and <code>(ActorContext<Message> ctx, GenMsg&lt;String&gt; list) -> {...}</code>
*
* @param type type of signal to match
* @param handler action to apply when the type matches
* @return a new behavior with the specified handling appended
*/
def signalUnchecked[T, M <: Signal](`type`: Class[_ <: Signal], handler: Function2[ActorContext[T], M, Behavior[T]]): BehaviorBuilder[T] =
BehaviorBuilder.create[T].onSignalUnchecked(`type`, handler)
/**
* Start a new behavior chain starting with a handler for this specific signal.
*
* @param signal the signal to compare to
* @param handler action to apply when the message matches
* @tparam T type of behavior to create
* @return a new behavior with the specified handling appended
*/
def signalEquals[T](signal: Signal, handler: Function[ActorContext[T], Behavior[T]]): BehaviorBuilder[T] =
BehaviorBuilder.create[T].onSignalEquals(signal, handler)
} }
private class BuiltBehavior[T]( /**
private val messageHandlers: List[Case[T, T]], * The concrete behavior
private val signalHandlers: List[Case[T, Signal]] *
* INTERNAL API
*/
@InternalApi
private final class BuiltBehavior[T](
messageHandlers: List[Case[T, T]],
signalHandlers: List[Case[T, Signal]]
) extends ExtensibleBehavior[T] { ) extends ExtensibleBehavior[T] {
override def receive(ctx: typed.TypedActorContext[T], msg: T): Behavior[T] = receive[T](ctx.asJava, msg, messageHandlers) override def receive(ctx: TypedActorContext[T], msg: T): Behavior[T] = receive(ctx.asJava, msg, messageHandlers)
override def receiveSignal(ctx: typed.TypedActorContext[T], msg: Signal): Behavior[T] = receive[Signal](ctx.asJava, msg, signalHandlers) override def receiveSignal(ctx: TypedActorContext[T], msg: Signal): Behavior[T] = receive(ctx.asJava, msg, signalHandlers)
@tailrec @tailrec
private def receive[M](ctx: ActorContext[T], msg: M, handlers: List[Case[T, M]]): Behavior[T] = private def receive[M](ctx: ActorContext[T], msg: M, handlers: List[Case[T, M]]): Behavior[T] =
handlers match { handlers match {
case Case(cls, predicate, handler) :: tail case Case(cls, predicate, handler) :: tail
if (cls.isAssignableFrom(msg.getClass) && (predicate.isEmpty || predicate.get.apply(msg))) handler(ctx, msg) if ((cls.isEmpty || cls.get.isAssignableFrom(msg.getClass)) && (predicate.isEmpty || predicate.get.apply(msg)))
else receive[M](ctx, msg, tail) handler(ctx, msg)
case _ else receive(ctx, msg, tail)
case Nil
unhandled[T] unhandled[T]
} }

View file

@ -5,26 +5,29 @@
package akka.actor.typed.javadsl package akka.actor.typed.javadsl
import scala.annotation.tailrec import scala.annotation.tailrec
import akka.japi.function.{ Creator, Function, Predicate } import akka.japi.function.Creator
import akka.japi.function.{ Function JFunction }
import akka.japi.function.{ Predicate JPredicate }
import akka.actor.typed.{ Behavior, Signal } import akka.actor.typed.{ Behavior, Signal }
import akka.annotation.InternalApi import akka.annotation.InternalApi
import akka.util.OptionVal
/** /**
* Used when implementing [[AbstractBehavior]]. * Mutable builder used when implementing [[AbstractBehavior]].
* *
* When handling a message or signal, this [[Behavior]] will consider all handlers in the order they were added, * When handling a message or signal, this [[Behavior]] will consider all handlers in the order they were added,
* looking for the first handler for which both the type and the (optional) predicate match. * looking for the first handler for which both the type and the (optional) predicate match.
* *
* @tparam T the common superclass of all supported messages. * @tparam T the common superclass of all supported messages.
*/ */
class ReceiveBuilder[T] private ( final class ReceiveBuilder[T] private (
private val messageHandlers: List[ReceiveBuilder.Case[T, T]], private var messageHandlers: List[ReceiveBuilder.Case[T, T]],
private val signalHandlers: List[ReceiveBuilder.Case[T, Signal]] private var signalHandlers: List[ReceiveBuilder.Case[T, Signal]]
) { ) {
import ReceiveBuilder.Case import ReceiveBuilder.Case
def build(): Receive[T] = new BuiltReceive(messageHandlers.reverse, signalHandlers.reverse) def build(): Receive[T] = new BuiltReceive[T](messageHandlers.reverse, signalHandlers.reverse)
/** /**
* Add a new case to the message handling. * Add a new case to the message handling.
@ -32,10 +35,10 @@ class ReceiveBuilder[T] private (
* @param type type of message to match * @param type type of message to match
* @param handler action to apply if the type matches * @param handler action to apply if the type matches
* @tparam M type of message to match * @tparam M type of message to match
* @return a new behavior with the specified handling appended * @return this behavior builder
*/ */
def onMessage[M <: T](`type`: Class[M], handler: Function[M, Behavior[T]]): ReceiveBuilder[T] = def onMessage[M <: T](`type`: Class[M], handler: JFunction[M, Behavior[T]]): ReceiveBuilder[T] =
withMessage(`type`, None, msg handler.apply(msg.asInstanceOf[M])) withMessage(OptionVal.Some(`type`), OptionVal.None, handler)
/** /**
* Add a new predicated case to the message handling. * Add a new predicated case to the message handling.
@ -44,14 +47,10 @@ class ReceiveBuilder[T] private (
* @param test a predicate that will be evaluated on the argument if the type matches * @param test a predicate that will be evaluated on the argument if the type matches
* @param handler action to apply if the type matches and the predicate returns true * @param handler action to apply if the type matches and the predicate returns true
* @tparam M type of message to match * @tparam M type of message to match
* @return a new behavior with the specified handling appended * @return this behavior builder
*/ */
def onMessage[M <: T](`type`: Class[M], test: Predicate[M], handler: Function[M, Behavior[T]]): ReceiveBuilder[T] = def onMessage[M <: T](`type`: Class[M], test: JPredicate[M], handler: JFunction[M, Behavior[T]]): ReceiveBuilder[T] =
withMessage( withMessage(OptionVal.Some(`type`), OptionVal.Some(test), handler)
`type`,
Some((t: T) test.test(t.asInstanceOf[M])),
msg handler.apply(msg.asInstanceOf[M])
)
/** /**
* Add a new case to the message handling without compile time type check. * Add a new case to the message handling without compile time type check.
@ -61,20 +60,35 @@ class ReceiveBuilder[T] private (
* *
* @param type type of message to match * @param type type of message to match
* @param handler action to apply when the type matches * @param handler action to apply when the type matches
* @return a new behavior with the specified handling appended * @return this behavior builder
*/ */
def onMessageUnchecked[M <: T](`type`: Class[_ <: T], handler: Function[M, Behavior[T]]): ReceiveBuilder[T] = def onMessageUnchecked[M <: T](`type`: Class[_ <: T], handler: JFunction[M, Behavior[T]]): ReceiveBuilder[T] =
withMessage(`type`, None, msg handler.apply(msg.asInstanceOf[M])) withMessage[M](OptionVal.Some(`type`.asInstanceOf[Class[M]]), OptionVal.None, handler)
/** /**
* Add a new case to the message handling matching equal messages. * Add a new case to the message handling matching equal messages.
* *
* @param msg the message to compare to * @param msg the message to compare to
* @param handler action to apply when the message matches * @param handler action to apply when the message matches
* @return a new behavior with the specified handling appended * @return this behavior builder
*/ */
def onMessageEquals(msg: T, handler: Creator[Behavior[T]]): ReceiveBuilder[T] = def onMessageEquals(msg: T, handler: Creator[Behavior[T]]): ReceiveBuilder[T] =
withMessage(msg.getClass, Some(_.equals(msg)), _ handler.create()) withMessage(OptionVal.Some(msg.getClass), OptionVal.Some(new JPredicate[T] {
override def test(param: T): Boolean = param == (msg)
}), new JFunction[T, Behavior[T]] {
// invoke creator without the message
override def apply(param: T): Behavior[T] = handler.create()
})
/**
* Add a new case to the message handling matching any message. Subsequent `onMessage` clauses will
* never see any messages.
*
* @param handler action to apply for any message
* @return this behavior builder
*/
def onAnyMessage(handler: JFunction[T, Behavior[T]]): ReceiveBuilder[T] =
withMessage(OptionVal.None, OptionVal.None, handler)
/** /**
* Add a new case to the signal handling. * Add a new case to the signal handling.
@ -82,10 +96,10 @@ class ReceiveBuilder[T] private (
* @param type type of signal to match * @param type type of signal to match
* @param handler action to apply if the type matches * @param handler action to apply if the type matches
* @tparam M type of signal to match * @tparam M type of signal to match
* @return a new behavior with the specified handling appended * @return this behavior builder
*/ */
def onSignal[M <: Signal](`type`: Class[M], handler: Function[M, Behavior[T]]): ReceiveBuilder[T] = def onSignal[M <: Signal](`type`: Class[M], handler: JFunction[M, Behavior[T]]): ReceiveBuilder[T] =
withSignal(`type`, None, signal handler.apply(signal.asInstanceOf[M])) withSignal(`type`, OptionVal.None, handler)
/** /**
* Add a new predicated case to the signal handling. * Add a new predicated case to the signal handling.
@ -94,60 +108,55 @@ class ReceiveBuilder[T] private (
* @param test a predicate that will be evaluated on the argument if the type matches * @param test a predicate that will be evaluated on the argument if the type matches
* @param handler action to apply if the type matches and the predicate returns true * @param handler action to apply if the type matches and the predicate returns true
* @tparam M type of signal to match * @tparam M type of signal to match
* @return a new behavior with the specified handling appended * @return this behavior builder
*/ */
def onSignal[M <: Signal](`type`: Class[M], test: Predicate[M], handler: Function[M, Behavior[T]]): ReceiveBuilder[T] = def onSignal[M <: Signal](`type`: Class[M], test: JPredicate[M], handler: JFunction[M, Behavior[T]]): ReceiveBuilder[T] =
withSignal( withSignal(`type`, OptionVal.Some(test), handler)
`type`,
Some((t: Signal) test.test(t.asInstanceOf[M])),
signal handler.apply(signal.asInstanceOf[M])
)
/**
* Add a new case to the signal handling without compile time type check.
*
* Should normally not be used, but when matching on class with generic type
* argument it can be useful, e.g. <code>GenMsg.class</code> and <code>(ActorContext<Message> ctx, GenMsg&lt;String&gt; list) -> {...}</code>
*
* @param type type of signal to match
* @param handler action to apply when the type matches
* @return a new behavior with the specified handling appended
*/
def onSignalUnchecked[M <: Signal](`type`: Class[_ <: Signal], handler: Function[M, Behavior[T]]): ReceiveBuilder[T] =
withSignal(`type`, None, signal handler.apply(signal.asInstanceOf[M]))
/** /**
* Add a new case to the signal handling matching equal signals. * Add a new case to the signal handling matching equal signals.
* *
* @param signal the signal to compare to * @param signal the signal to compare to
* @param handler action to apply when the message matches * @param handler action to apply when the message matches
* @return a new behavior with the specified handling appended * @return this behavior builder
*/ */
def onSignalEquals(signal: Signal, handler: Creator[Behavior[T]]): ReceiveBuilder[T] = def onSignalEquals(signal: Signal, handler: Creator[Behavior[T]]): ReceiveBuilder[T] =
withSignal(signal.getClass, Some(_.equals(signal)), _ handler.create()) withSignal(signal.getClass, OptionVal.Some(new JPredicate[Signal] {
override def test(param: Signal): Boolean = param == signal
}), new JFunction[Signal, Behavior[T]] {
override def apply(param: Signal): Behavior[T] = handler.create()
})
private def withMessage(`type`: Class[_ <: T], test: Option[T Boolean], handler: T Behavior[T]): ReceiveBuilder[T] = private def withMessage[M <: T](`type`: OptionVal[Class[M]], test: OptionVal[JPredicate[M]], handler: JFunction[M, Behavior[T]]): ReceiveBuilder[T] = {
new ReceiveBuilder[T](Case[T, T](`type`, test, handler) +: messageHandlers, signalHandlers) messageHandlers = Case[T, M](`type`, test, handler).asInstanceOf[Case[T, T]] +: messageHandlers
this
}
private def withSignal[M <: Signal](`type`: Class[M], test: Option[Signal Boolean], handler: Signal Behavior[T]): ReceiveBuilder[T] = private def withSignal[M <: Signal](`type`: Class[M], test: OptionVal[JPredicate[M]], handler: JFunction[M, Behavior[T]]): ReceiveBuilder[T] = {
new ReceiveBuilder[T](messageHandlers, Case[T, Signal](`type`, test, handler) +: signalHandlers) signalHandlers = Case[T, M](OptionVal.Some(`type`), test, handler).asInstanceOf[Case[T, Signal]] +: signalHandlers
this
}
} }
object ReceiveBuilder { object ReceiveBuilder {
/** Create a new mutable receive builder */
def create[T]: ReceiveBuilder[T] = new ReceiveBuilder[T](Nil, Nil) def create[T]: ReceiveBuilder[T] = new ReceiveBuilder[T](Nil, Nil)
/** INTERNAL API */ /** INTERNAL API */
@InternalApi @InternalApi
private[javadsl] final case class Case[BT, MT](`type`: Class[_ <: MT], test: Option[MT Boolean], handler: MT Behavior[BT]) private[javadsl] final case class Case[BT, MT](`type`: OptionVal[Class[_ <: MT]], test: OptionVal[JPredicate[MT]], handler: JFunction[MT, Behavior[BT]])
} }
/** /**
* Receive type for [[AbstractBehavior]] * Receive type for [[AbstractBehavior]]
*
* INTERNAL API
*/ */
@InternalApi
private final class BuiltReceive[T]( private final class BuiltReceive[T](
private val messageHandlers: List[ReceiveBuilder.Case[T, T]], messageHandlers: List[ReceiveBuilder.Case[T, T]],
private val signalHandlers: List[ReceiveBuilder.Case[T, Signal]] signalHandlers: List[ReceiveBuilder.Case[T, Signal]]
) extends Receive[T] { ) extends Receive[T] {
import ReceiveBuilder.Case import ReceiveBuilder.Case
@ -159,7 +168,7 @@ private final class BuiltReceive[T](
private def receive[M](msg: M, handlers: List[Case[T, M]]): Behavior[T] = private def receive[M](msg: M, handlers: List[Case[T, M]]): Behavior[T] =
handlers match { handlers match {
case Case(cls, predicate, handler) :: tail case Case(cls, predicate, handler) :: tail
if (cls.isAssignableFrom(msg.getClass) && (predicate.isEmpty || predicate.get.apply(msg))) handler(msg) if ((cls.isEmpty || cls.get.isAssignableFrom(msg.getClass)) && (predicate.isEmpty || predicate.get.test(msg))) handler(msg)
else receive[M](msg, tail) else receive[M](msg, tail)
case _ case _
Behaviors.unhandled Behaviors.unhandled

View file

@ -7,6 +7,7 @@ package akka.japi.function
/** /**
* A Function interface. Used to create first-class-functions is Java. * A Function interface. Used to create first-class-functions is Java.
* `Serializable` is needed to be able to grab line number for Java 8 lambdas. * `Serializable` is needed to be able to grab line number for Java 8 lambdas.
* Supports throwing `Exception` in the apply, which the `java.util.function.Function` counterpart does not.
*/ */
@SerialVersionUID(1L) @SerialVersionUID(1L)
trait Function[-T, +R] extends java.io.Serializable { trait Function[-T, +R] extends java.io.Serializable {
@ -17,6 +18,7 @@ trait Function[-T, +R] extends java.io.Serializable {
/** /**
* A Function interface. Used to create 2-arg first-class-functions is Java. * A Function interface. Used to create 2-arg first-class-functions is Java.
* `Serializable` is needed to be able to grab line number for Java 8 lambdas. * `Serializable` is needed to be able to grab line number for Java 8 lambdas.
* Supports throwing `Exception` in the apply, which the `java.util.function.BiFunction` counterpart does not.
*/ */
@SerialVersionUID(1L) @SerialVersionUID(1L)
trait Function2[-T1, -T2, +R] extends java.io.Serializable { trait Function2[-T1, -T2, +R] extends java.io.Serializable {
@ -27,6 +29,7 @@ trait Function2[-T1, -T2, +R] extends java.io.Serializable {
/** /**
* A Procedure is like a Function, but it doesn't produce a return value. * A Procedure is like a Function, but it doesn't produce a return value.
* `Serializable` is needed to be able to grab line number for Java 8 lambdas. * `Serializable` is needed to be able to grab line number for Java 8 lambdas.
* Supports throwing `Exception` in the apply, which the `java.util.function.Consumer` counterpart does not.
*/ */
@SerialVersionUID(1L) @SerialVersionUID(1L)
trait Procedure[-T] extends java.io.Serializable { trait Procedure[-T] extends java.io.Serializable {
@ -37,9 +40,11 @@ trait Procedure[-T] extends java.io.Serializable {
/** /**
* An executable piece of code that takes no parameters and doesn't return any value. * An executable piece of code that takes no parameters and doesn't return any value.
* `Serializable` is needed to be able to grab line number for Java 8 lambdas. * `Serializable` is needed to be able to grab line number for Java 8 lambdas.
* Supports throwing `Exception` in the apply, which the `java.util.function.Effect` counterpart does not.
*/ */
@SerialVersionUID(1L) @SerialVersionUID(1L)
trait Effect extends java.io.Serializable { trait Effect extends java.io.Serializable {
@throws(classOf[Exception]) @throws(classOf[Exception])
def apply(): Unit def apply(): Unit
} }
@ -47,6 +52,7 @@ trait Effect extends java.io.Serializable {
/** /**
* Java API: Defines a criteria and determines whether the parameter meets this criteria. * Java API: Defines a criteria and determines whether the parameter meets this criteria.
* `Serializable` is needed to be able to grab line number for Java 8 lambdas. * `Serializable` is needed to be able to grab line number for Java 8 lambdas.
* Supports throwing `Exception` in the apply, which the `java.util.function.Predicate` counterpart does not.
*/ */
@SerialVersionUID(1L) @SerialVersionUID(1L)
trait Predicate[-T] extends java.io.Serializable { trait Predicate[-T] extends java.io.Serializable {
@ -55,6 +61,7 @@ trait Predicate[-T] extends java.io.Serializable {
/** /**
* A constructor/factory, takes no parameters but creates a new value of type T every call. * A constructor/factory, takes no parameters but creates a new value of type T every call.
* Supports throwing `Exception` in the apply, which the `java.util.function.Creator` counterpart does not.
*/ */
@SerialVersionUID(1L) @SerialVersionUID(1L)
trait Creator[+T] extends Serializable { trait Creator[+T] extends Serializable {

View file

@ -134,7 +134,7 @@ public class ReplicatorTest extends JUnitSuite {
@Override @Override
public Receive<ClientCommand> createReceive() { public Receive<ClientCommand> createReceive() {
return receiveBuilder() return newReceiveBuilder()
.onMessage(Increment.class, this::onIncrement) .onMessage(Increment.class, this::onIncrement)
.onMessage(InternalUpdateResponse.class, msg -> Behaviors.same()) .onMessage(InternalUpdateResponse.class, msg -> Behaviors.same())
.onMessage(GetValue.class, this::onGetValue) .onMessage(GetValue.class, this::onGetValue)

View file

@ -50,7 +50,7 @@ public class ReceptionistExampleTest extends JUnitSuite {
@Override @Override
public Receive<Object> createReceive() { public Receive<Object> createReceive() {
return receiveBuilder() return newReceiveBuilder()
.onMessage( .onMessage(
Receptionist.Listing.class, Receptionist.Listing.class,
listing -> listing.isForKey(serviceKey), listing -> listing.isForKey(serviceKey),
@ -131,7 +131,7 @@ public class ReceptionistExampleTest extends JUnitSuite {
@Override @Override
public Receive<Object> createReceive() { public Receive<Object> createReceive() {
return receiveBuilder() return newReceiveBuilder()
.onMessage( .onMessage(
Receptionist.Listing.class, Receptionist.Listing.class,
listing -> listing.isForKey(serviceKey), listing -> listing.isForKey(serviceKey),

View file

@ -141,7 +141,7 @@ The console output may look like this:
The next example is more realistic and demonstrates some important patterns: The next example is more realistic and demonstrates some important patterns:
* Using a sealed trait and case class/objects to represent multiple messages an actor can receive * Using @scala[a sealed trait and case class/objects]@java[an interface and classes implementing that interface] to represent multiple messages an actor can receive
* Handle sessions by using child actors * Handle sessions by using child actors
* Handling state by changing behavior * Handling state by changing behavior
* Using multiple typed actors to represent different parts of a protocol in a type safe way * Using multiple typed actors to represent different parts of a protocol in a type safe way
@ -357,6 +357,10 @@ As the state is mutable, we never return a different behavior from the message l
the `AbstractBehavior` instance itself (`this`) as a behavior to use for processing the next message coming in. the `AbstractBehavior` instance itself (`this`) as a behavior to use for processing the next message coming in.
We could also return `Behavior.same` to achieve the same. We could also return `Behavior.same` to achieve the same.
@java[In this sample we make separate statements for creating the behavior builder, but it also returns the builder
itself from each step so a more fluent behavior definition style is also possible. What you should prefer depends on
how big the set of messages the actor accepts is.]
It is also possible to return a new different `AbstractBehavior`, for example to represent a different state in a It is also possible to return a new different `AbstractBehavior`, for example to represent a different state in a
finite state machine (FSM), or use one of the functional behavior factories to combine the object oriented finite state machine (FSM), or use one of the functional behavior factories to combine the object oriented
with the functional style for different parts of the lifecycle of the same Actor behavior. with the functional style for different parts of the lifecycle of the same Actor behavior.

View file

@ -36,7 +36,7 @@ class PrintMyActorRefActor extends AbstractBehavior<String> {
@Override @Override
public Receive<String> createReceive() { public Receive<String> createReceive() {
return receiveBuilder().onMessageEquals("printit", this::printIt).build(); return newReceiveBuilder().onMessageEquals("printit", this::printIt).build();
} }
private Behavior<String> printIt() { private Behavior<String> printIt() {
@ -60,7 +60,7 @@ class StartStopActor1 extends AbstractBehavior<String> {
@Override @Override
public Receive<String> createReceive() { public Receive<String> createReceive() {
return receiveBuilder() return newReceiveBuilder()
.onMessageEquals("stop", Behaviors::stopped) .onMessageEquals("stop", Behaviors::stopped)
.onSignal(PostStop.class, signal -> postStop()) .onSignal(PostStop.class, signal -> postStop())
.build(); .build();
@ -84,7 +84,7 @@ class StartStopActor2 extends AbstractBehavior<String> {
@Override @Override
public Receive<String> createReceive() { public Receive<String> createReceive() {
return receiveBuilder().onSignal(PostStop.class, signal -> postStop()).build(); return newReceiveBuilder().onSignal(PostStop.class, signal -> postStop()).build();
} }
private Behavior<String> postStop() { private Behavior<String> postStop() {
@ -113,7 +113,7 @@ class SupervisingActor extends AbstractBehavior<String> {
@Override @Override
public Receive<String> createReceive() { public Receive<String> createReceive() {
return receiveBuilder().onMessageEquals("failChild", this::failChild).build(); return newReceiveBuilder().onMessageEquals("failChild", this::failChild).build();
} }
private Behavior<String> failChild() { private Behavior<String> failChild() {
@ -134,7 +134,7 @@ class SupervisedActor extends AbstractBehavior<String> {
@Override @Override
public Receive<String> createReceive() { public Receive<String> createReceive() {
return receiveBuilder() return newReceiveBuilder()
.onMessageEquals("fail", this::fail) .onMessageEquals("fail", this::fail)
.onSignal(PreRestart.class, signal -> preRestart()) .onSignal(PreRestart.class, signal -> preRestart())
.onSignal(PostStop.class, signal -> postStop()) .onSignal(PostStop.class, signal -> postStop())
@ -174,7 +174,7 @@ class Main extends AbstractBehavior<String> {
@Override @Override
public Receive<String> createReceive() { public Receive<String> createReceive() {
return receiveBuilder().onMessageEquals("start", this::start).build(); return newReceiveBuilder().onMessageEquals("start", this::start).build();
} }
private Behavior<String> start() { private Behavior<String> start() {

View file

@ -28,7 +28,7 @@ public class IotSupervisor extends AbstractBehavior<Void> {
// No need to handle any messages // No need to handle any messages
@Override @Override
public Receive<Void> createReceive() { public Receive<Void> createReceive() {
return receiveBuilder().onSignal(PostStop.class, signal -> postStop()).build(); return newReceiveBuilder().onSignal(PostStop.class, signal -> postStop()).build();
} }
private IotSupervisor postStop() { private IotSupervisor postStop() {

View file

@ -46,7 +46,7 @@ public class Device extends AbstractBehavior<DeviceMessage> {
@Override @Override
public Receive<DeviceMessage> createReceive() { public Receive<DeviceMessage> createReceive() {
return receiveBuilder() return newReceiveBuilder()
.onMessage(RecordTemperature.class, this::recordTemperature) .onMessage(RecordTemperature.class, this::recordTemperature)
.onMessage(ReadTemperature.class, this::readTemperature) .onMessage(ReadTemperature.class, this::readTemperature)
.onSignal(PostStop.class, signal -> postStop()) .onSignal(PostStop.class, signal -> postStop())

View file

@ -46,7 +46,7 @@ public class Device extends AbstractBehavior<DeviceMessage> {
@Override @Override
public Receive<DeviceMessage> createReceive() { public Receive<DeviceMessage> createReceive() {
return receiveBuilder() return newReceiveBuilder()
.onMessage(ReadTemperature.class, this::readTemperature) .onMessage(ReadTemperature.class, this::readTemperature)
.onSignal(PostStop.class, signal -> postStop()) .onSignal(PostStop.class, signal -> postStop())
.build(); .build();

View file

@ -46,7 +46,7 @@ public class Device extends AbstractBehavior<DeviceMessage> {
@Override @Override
public Receive<DeviceMessage> createReceive() { public Receive<DeviceMessage> createReceive() {
return receiveBuilder() return newReceiveBuilder()
.onMessage(RecordTemperature.class, this::recordTemperature) .onMessage(RecordTemperature.class, this::recordTemperature)
.onMessage(ReadTemperature.class, this::readTemperature) .onMessage(ReadTemperature.class, this::readTemperature)
.onMessage(Passivate.class, m -> Behaviors.stopped()) .onMessage(Passivate.class, m -> Behaviors.stopped())

View file

@ -98,7 +98,7 @@ public class DeviceGroup extends AbstractBehavior<DeviceGroupMessage> {
@Override @Override
public Receive<DeviceGroupMessage> createReceive() { public Receive<DeviceGroupMessage> createReceive() {
return receiveBuilder() return newReceiveBuilder()
.onMessage(RequestTrackDevice.class, this::onTrackDevice) .onMessage(RequestTrackDevice.class, this::onTrackDevice)
// #device-group-register // #device-group-register
// #device-group-remove // #device-group-remove

View file

@ -74,7 +74,7 @@ public class DeviceManager extends AbstractBehavior<DeviceManagerMessage> {
} }
public Receive<DeviceManagerMessage> createReceive() { public Receive<DeviceManagerMessage> createReceive() {
return receiveBuilder() return newReceiveBuilder()
.onMessage(RequestTrackDevice.class, this::onTrackDevice) .onMessage(RequestTrackDevice.class, this::onTrackDevice)
.onMessage(RequestDeviceList.class, this::onRequestDeviceList) .onMessage(RequestDeviceList.class, this::onRequestDeviceList)
.onMessage(DeviceGroupTerminated.class, this::onTerminated) .onMessage(DeviceGroupTerminated.class, this::onTerminated)

View file

@ -37,7 +37,7 @@ public class Device extends AbstractBehavior<DeviceMessage> {
@Override @Override
public Receive<DeviceMessage> createReceive() { public Receive<DeviceMessage> createReceive() {
return receiveBuilder() return newReceiveBuilder()
.onMessage(RecordTemperature.class, this::recordTemperature) .onMessage(RecordTemperature.class, this::recordTemperature)
.onMessage(ReadTemperature.class, this::readTemperature) .onMessage(ReadTemperature.class, this::readTemperature)
.onMessage(Passivate.class, m -> Behaviors.stopped()) .onMessage(Passivate.class, m -> Behaviors.stopped())

View file

@ -113,7 +113,7 @@ public class DeviceGroup extends AbstractBehavior<DeviceGroupMessage> {
@Override @Override
public Receive<DeviceGroupMessage> createReceive() { public Receive<DeviceGroupMessage> createReceive() {
return receiveBuilder() return newReceiveBuilder()
// #query-added // #query-added
.onMessage(RequestTrackDevice.class, this::onTrackDevice) .onMessage(RequestTrackDevice.class, this::onTrackDevice)
.onMessage(RequestDeviceList.class, r -> r.groupId.equals(groupId), this::onDeviceList) .onMessage(RequestDeviceList.class, r -> r.groupId.equals(groupId), this::onDeviceList)

View file

@ -95,7 +95,7 @@ public class DeviceGroupQuery extends AbstractBehavior<DeviceGroupQueryMessage>
// #query-state // #query-state
@Override @Override
public Receive<DeviceGroupQueryMessage> createReceive() { public Receive<DeviceGroupQueryMessage> createReceive() {
return receiveBuilder() return newReceiveBuilder()
.onMessage(WrappedRespondTemperature.class, this::onRespondTemperature) .onMessage(WrappedRespondTemperature.class, this::onRespondTemperature)
.onMessage(DeviceTerminated.class, this::onDeviceTerminated) .onMessage(DeviceTerminated.class, this::onDeviceTerminated)
.onMessage(CollectionTimeout.class, this::onCollectionTimeout) .onMessage(CollectionTimeout.class, this::onCollectionTimeout)

View file

@ -83,7 +83,7 @@ public class DeviceManager extends AbstractBehavior<DeviceManagerMessage> {
} }
public Receive<DeviceManagerMessage> createReceive() { public Receive<DeviceManagerMessage> createReceive() {
return receiveBuilder() return newReceiveBuilder()
.onMessage(RequestTrackDevice.class, this::onTrackDevice) .onMessage(RequestTrackDevice.class, this::onTrackDevice)
.onMessage(RequestDeviceList.class, this::onRequestDeviceList) .onMessage(RequestDeviceList.class, this::onRequestDeviceList)
.onMessage(RequestAllTemperatures.class, this::onRequestAllTemperatures) .onMessage(RequestAllTemperatures.class, this::onRequestAllTemperatures)