!act,sam #3889 Adding Activator template FSM/become for Java with Lambda support

* Dining Hakkers Activator template for FSM and become
* Cleaup of FSM event matchers to be more usable and consistent
This commit is contained in:
Björn Antonsson 2014-02-24 12:11:02 +01:00
parent 6af33381b3
commit 07e361c684
30 changed files with 1026 additions and 103 deletions

View file

@ -12,6 +12,8 @@ import scala.PartialFunction;
*
* @param <I> the input type, that this PartialFunction will be applied to
* @param <R> the return type, that the results of the application will have
*
* This is an EXPERIMENTAL feature and is subject to change until it has received more real world testing.
*/
class AbstractMatch<I, R> {

View file

@ -11,6 +11,8 @@ import scala.PartialFunction;
*
* @param <F> the input type, that this PartialFunction will be applied to
* @param <T> the return type, that the results of the application will have
*
* This is an EXPERIMENTAL feature and is subject to change until it has received more real world testing.
*/
abstract class AbstractPFBuilder<F, T> {

View file

@ -7,6 +7,8 @@ package akka.japi.pf;
/**
* Class that encapsulates all the Functional Interfaces
* used for creating partial functions.
*
* This is an EXPERIMENTAL feature and is subject to change until it has received more real world testing.
*/
public final class FI {
private FI() {
@ -61,6 +63,23 @@ public final class FI {
public boolean defined(T t);
}
/**
* Functional interface for a predicate.
*
* @param <T> the type that the predicate will operate on.
* @param <U> the type that the predicate will operate on.
*/
public static interface TypedPredicate2<T, U> {
/**
* The predicate to evaluate.
*
* @param t an instance that the predicate is evaluated on.
* @param u an instance that the predicate is evaluated on.
* @return the result of the predicate
*/
public boolean defined(T t, U u);
}
/**
* Functional interface for an application.
*

View file

@ -6,8 +6,6 @@ package akka.japi.pf;
import akka.actor.FSM;
import scala.PartialFunction;
import java.util.Arrays;
import java.util.List;
/**
@ -15,6 +13,8 @@ import java.util.List;
*
* @param <S> the state type
* @param <D> the data type
*
* This is an EXPERIMENTAL feature and is subject to change until it has received more real world testing.
*/
@SuppressWarnings("rawtypes")
public class FSMStateFunctionBuilder<S, D> {
@ -22,6 +22,89 @@ public class FSMStateFunctionBuilder<S, D> {
private PFBuilder<FSM.Event<D>, FSM.State<S, D>> builder =
new PFBuilder<FSM.Event<D>, FSM.State<S, D>>();
/**
* An erased processing of the event matcher. The compile time checks are enforced
* by the public typed versions.
*
* It works like this.
*
* If eventOrType or dataOrType is a Class, then we do a isInstance check,
* otherwise we do an equals check. The null value compares true for anything.
* If the predicate is null, it is skipped otherwise the predicate has to match
* as well.
*
* @param eventOrType an event or a type to match against
* @param dataOrType a data instance or a type to match against
* @param predicate a predicate to match against
* @param apply an action to apply to the event and state data if there is a match
* @return the builder with the case statement added
*/
private FSMStateFunctionBuilder<S, D> erasedEvent(final Object eventOrType,
final Object dataOrType,
final FI.TypedPredicate2 predicate,
final FI.Apply2 apply) {
builder.match(FSM.Event.class,
new FI.TypedPredicate<FSM.Event>() {
@Override
public boolean defined(FSM.Event e) {
boolean res = true;
if (eventOrType != null) {
if (eventOrType instanceof Class) {
Class eventType = (Class) eventOrType;
res = eventType.isInstance(e.event());
}
else {
res = eventOrType.equals(e.event());
}
}
if (res && dataOrType != null) {
if (dataOrType instanceof Class) {
Class dataType = (Class) dataOrType;
res = dataType.isInstance(e.stateData());
}
else {
res = dataOrType.equals(e.stateData());
}
}
if (res && predicate != null) {
@SuppressWarnings("unchecked")
boolean ures = predicate.defined(e.event(), e.stateData());
res = ures;
}
return res;
}
},
new FI.Apply<FSM.Event, FSM.State<S, D>>() {
public FSM.State<S, D> apply(FSM.Event e) throws Exception {
@SuppressWarnings("unchecked")
FSM.State<S, D> res = (FSM.State<S, D>) apply.apply(e.event(), e.stateData());
return res;
}
}
);
return this;
}
/**
* Add a case statement that matches on an event and data type and a predicate.
*
* @param eventType the event type to match on
* @param dataType the data type to match on
* @param predicate a predicate to evaluate on the matched types
* @param apply an action to apply to the event and state data if there is a match
* @param <P> the event type to match on
* @param <Q> the data type to match on
* @return the builder with the case statement added
*/
public final <P, Q> FSMStateFunctionBuilder<S, D> event(final Class<P> eventType,
final Class<Q> dataType,
final FI.TypedPredicate2<P, Q> predicate,
final FI.Apply2<P, Q, FSM.State<S, D>> apply) {
erasedEvent(eventType, dataType, predicate, apply);
return this;
}
/**
* Add a case statement that matches on an event and data type.
*
@ -35,25 +118,45 @@ public class FSMStateFunctionBuilder<S, D> {
public <P, Q> FSMStateFunctionBuilder<S, D> event(final Class<P> eventType,
final Class<Q> dataType,
final FI.Apply2<P, Q, FSM.State<S, D>> apply) {
builder.match(FSM.Event.class,
new FI.TypedPredicate<FSM.Event>() {
@Override
public boolean defined(FSM.Event e) {
return eventType.isInstance(e.event()) && dataType.isInstance(e.stateData());
}
},
new FI.Apply<FSM.Event, FSM.State<S, D>>() {
public FSM.State<S, D> apply(FSM.Event e) throws Exception {
@SuppressWarnings("unchecked")
P p = (P) e.event();
@SuppressWarnings("unchecked")
Q q = (Q) e.stateData();
return apply.apply(p, q);
}
}
);
return erasedEvent(eventType, dataType, null, apply);
}
return this;
/**
* Add a case statement that matches if the event type and predicate matches.
*
* @param eventType the event type to match on
* @param predicate a predicate that will be evaluated on the data and the event
* @param apply an action to apply to the event and state data if there is a match
* @return the builder with the case statement added
*/
public <P> FSMStateFunctionBuilder<S, D> event(final Class<P> eventType,
final FI.TypedPredicate2<P, D> predicate,
final FI.Apply2<P, D, FSM.State<S, D>> apply) {
return erasedEvent(eventType, null, predicate, apply);
}
/**
* Add a case statement that matches if the event type and predicate matches.
*
* @param eventType the event type to match on
* @param apply an action to apply to the event and state data if there is a match
* @return the builder with the case statement added
*/
public <P> FSMStateFunctionBuilder<S, D> event(final Class<P> eventType,
final FI.Apply2<P, D, FSM.State<S, D>> apply) {
return erasedEvent(eventType, null, null, apply);
}
/**
* Add a case statement that matches if the predicate matches.
*
* @param predicate a predicate that will be evaluated on the data and the event
* @param apply an action to apply to the event and state data if there is a match
* @return the builder with the case statement added
*/
public FSMStateFunctionBuilder<S, D> event(final FI.TypedPredicate2<Object, D> predicate,
final FI.Apply2<Object, D, FSM.State<S, D>> apply) {
return erasedEvent(null, null, predicate, apply);
}
/**
@ -68,12 +171,12 @@ public class FSMStateFunctionBuilder<S, D> {
*/
public <Q> FSMStateFunctionBuilder<S, D> event(final List<Object> eventMatches,
final Class<Q> dataType,
final FI.Apply<Q, FSM.State<S, D>> apply) {
final FI.Apply2<Object, Q, FSM.State<S, D>> apply) {
builder.match(FSM.Event.class,
new FI.TypedPredicate<FSM.Event>() {
@Override
public boolean defined(FSM.Event e) {
if (!dataType.isInstance(e.stateData()))
if (dataType != null && !dataType.isInstance(e.stateData()))
return false;
boolean emMatch = false;
@ -95,7 +198,7 @@ public class FSMStateFunctionBuilder<S, D> {
public FSM.State<S, D> apply(FSM.Event e) throws Exception {
@SuppressWarnings("unchecked")
Q q = (Q) e.stateData();
return apply.apply(q);
return apply.apply(e.event(), q);
}
}
);
@ -103,21 +206,6 @@ public class FSMStateFunctionBuilder<S, D> {
return this;
}
/**
* Add a case statement that matches on the data type and if the event compares equal.
*
* @param event an event to compare equal against
* @param dataType the data type to match on
* @param apply an action to apply to the event and state data if there is a match
* @param <Q> the data type to match on
* @return the builder with the case statement added
*/
public <Q> FSMStateFunctionBuilder<S, D> eventEquals(final Object event,
final Class<Q> dataType,
final FI.Apply<Q, FSM.State<S, D>> apply) {
return event(Arrays.asList(event), dataType, apply);
}
/**
* Add a case statement that matches if any of the event types in the list match or
* any of the event instances in the list compares equal.
@ -127,36 +215,23 @@ public class FSMStateFunctionBuilder<S, D> {
* @return the builder with the case statement added
*/
public FSMStateFunctionBuilder<S, D> event(final List<Object> eventMatches,
final FI.Apply<D, FSM.State<S, D>> apply) {
builder.match(FSM.Event.class,
new FI.TypedPredicate<FSM.Event>() {
@Override
public boolean defined(FSM.Event e) {
boolean emMatch = false;
Object event = e.event();
for (Object em : eventMatches) {
if (em instanceof Class) {
Class emc = (Class) em;
emMatch = emc.isInstance(event);
} else {
emMatch = event.equals(em);
}
if (emMatch)
break;
}
return emMatch;
}
},
new FI.Apply<FSM.Event, FSM.State<S, D>>() {
public FSM.State<S, D> apply(FSM.Event e) throws Exception {
@SuppressWarnings("unchecked")
D d = (D) e.stateData();
return apply.apply(d);
}
}
);
final FI.Apply2<Object, D, FSM.State<S, D>> apply) {
return event(eventMatches, null, apply);
}
return this;
/**
* Add a case statement that matches on the data type and if the event compares equal.
*
* @param event an event to compare equal against
* @param dataType the data type to match on
* @param apply an action to apply to the event and state data if there is a match
* @param <Q> the data type to match on
* @return the builder with the case statement added
*/
public <P, Q> FSMStateFunctionBuilder<S, D> eventEquals(final P event,
final Class<Q> dataType,
final FI.Apply2<P, Q, FSM.State<S, D>> apply) {
return erasedEvent(event, dataType, null, apply);
}
/**
@ -166,9 +241,9 @@ public class FSMStateFunctionBuilder<S, D> {
* @param apply an action to apply to the event and state data if there is a match
* @return the builder with the case statement added
*/
public FSMStateFunctionBuilder<S, D> eventEquals(final Object event,
final FI.Apply<D, FSM.State<S, D>> apply) {
return event(Arrays.asList(event), apply);
public <P> FSMStateFunctionBuilder<S, D> eventEquals(final P event,
final FI.Apply2<P, D, FSM.State<S, D>> apply) {
return erasedEvent(event, null, null, apply);
}
/**
@ -178,16 +253,7 @@ public class FSMStateFunctionBuilder<S, D> {
* @return the builder with the case statement added
*/
public FSMStateFunctionBuilder<S, D> anyEvent(final FI.Apply2<Object, D, FSM.State<S, D>> apply) {
builder.match(FSM.Event.class,
new FI.Apply<FSM.Event, FSM.State<S, D>>() {
public FSM.State<S, D> apply(FSM.Event e) throws Exception {
@SuppressWarnings("unchecked")
D d = (D) e.stateData();
return apply.apply(e.event(), d);
}
});
return this;
return erasedEvent(null, null, null, apply);
}
/**

View file

@ -13,6 +13,8 @@ import scala.runtime.BoxedUnit;
*
* @param <S> the state type
* @param <D> the data type
*
* This is an EXPERIMENTAL feature and is subject to change until it has received more real world testing.
*/
public class FSMStopBuilder<S, D> {

View file

@ -12,6 +12,8 @@ import scala.Tuple2;
* Builder used to create a partial function for {@link akka.actor.FSM#onTransition}.
*
* @param <S> the state type
*
* This is an EXPERIMENTAL feature and is subject to change until it has received more real world testing.
*/
public class FSMTransitionHandlerBuilder<S> {

View file

@ -13,6 +13,8 @@ import scala.PartialFunction;
*
* @param <I> the input type, that this PartialFunction will be applied to
* @param <R> the return type, that the results of the application will have
*
* This is an EXPERIMENTAL feature and is subject to change until it has received more real world testing.
*/
public class Match<I, R> extends AbstractMatch<I, R> {

View file

@ -9,6 +9,8 @@ package akka.japi.pf;
*
* @param <I> the input type, that this PartialFunction will be applied to
* @param <R> the return type, that the results of the application will have
*
* This is an EXPERIMENTAL feature and is subject to change until it has received more real world testing.
*/
public final class PFBuilder<I, R> extends AbstractPFBuilder<I, R> {

View file

@ -28,6 +28,7 @@ package akka.japi.pf;
* }
* </pre>
*
* This is an EXPERIMENTAL feature and is subject to change until it has received more real world testing.
*/
public class ReceiveBuilder {
private ReceiveBuilder() {

View file

@ -15,6 +15,8 @@ import scala.runtime.BoxedUnit;
* void methods to {@link scala.runtime.BoxedUnit}.
*
* @param <I> the input type, that this PartialFunction will be applied to
*
* This is an EXPERIMENTAL feature and is subject to change until it has received more real world testing.
*/
public class UnitMatch<I> extends AbstractMatch<I, BoxedUnit> {

View file

@ -12,6 +12,8 @@ import scala.runtime.BoxedUnit;
* void methods to {@link scala.runtime.BoxedUnit}.
*
* @param <I> the input type, that this PartialFunction to be applied to
*
* This is an EXPERIMENTAL feature and is subject to change until it has received more real world testing.
*/
public final class UnitPFBuilder<I> extends AbstractPFBuilder<I, BoxedUnit> {

View file

@ -4,6 +4,11 @@
package akka.actor
/**
* Java API: compatible with lambda expressions
*
* This is an EXPERIMENTAL feature and is subject to change until it has received more real world testing.
*/
object AbstractActor {
/**
* emptyBehavior is a Receive-expression that matches no messages at all, ever.
@ -12,6 +17,8 @@ object AbstractActor {
}
/**
* Java API: compatible with lambda expressions
*
* Actor base class that should be extended to create Java actors that use lambdas.
* <p/>
* Example:
@ -33,6 +40,8 @@ object AbstractActor {
* }
* }
* </pre>
*
* This is an EXPERIMENTAL feature and is subject to change until it has received more real world testing.
*/
abstract class AbstractActor extends Actor {
/**
@ -44,6 +53,8 @@ abstract class AbstractActor extends Actor {
}
/**
* Java API: compatible with lambda expressions
*
* Actor base class that should be extended to create an actor with a stash.
*
* The stash enables an actor to temporarily stash away messages that can not or
@ -83,18 +94,28 @@ abstract class AbstractActor extends Actor {
* For a `Stash` based actor that enforces unbounded deques see [[akka.actor.AbstractActorWithUnboundedStash]].
* There is also an unrestricted version [[akka.actor.AbstractActorWithUnrestrictedStash]] that does not
* enforce the mailbox type.
*
* This is an EXPERIMENTAL feature and is subject to change until it has received more real world testing.
*/
abstract class AbstractActorWithStash extends AbstractActor with Stash
/**
* Java API: compatible with lambda expressions
*
* Actor base class with `Stash` that enforces an unbounded deque for the actor. The proper mailbox has to be configured
* manually, and the mailbox should extend the [[akka.dispatch.DequeBasedMessageQueueSemantics]] marker trait.
* See [[akka.actor.AbstractActorWithStash]] for details on how `Stash` works.
*
* This is an EXPERIMENTAL feature and is subject to change until it has received more real world testing.
*/
abstract class AbstractActorWithUnboundedStash extends AbstractActor with UnboundedStash
/**
* Java API: compatible with lambda expressions
*
* Actor base class with `Stash` that does not enforce any mailbox type. The mailbox of the actor has to be configured
* manually. See [[akka.actor.AbstractActorWithStash]] for details on how `Stash` works.
*
* This is an EXPERIMENTAL feature and is subject to change until it has received more real world testing.
*/
abstract class AbstractActorWithUnrestrictedStash extends AbstractActor with UnrestrictedStash

View file

@ -755,7 +755,9 @@ trait LoggingFSM[S, D] extends FSM[S, D] { this: Actor ⇒
}
/**
* Java API
* Java API: compatible with lambda expressions
*
* This is an EXPERIMENTAL feature and is subject to change until it has received more real world testing.
*/
object AbstractFSM {
/**
@ -770,9 +772,11 @@ object AbstractFSM {
}
/**
* Java API
* Java API: compatible with lambda expressions
*
* Finite State Machine actor abstract base class.
*
* This is an EXPERIMENTAL feature and is subject to change until it has received more real world testing.
*/
abstract class AbstractFSM[S, D] extends FSM[S, D] {
import akka.japi.pf._
@ -874,6 +878,20 @@ abstract class AbstractFSM[S, D] extends FSM[S, D] {
final def onTermination(stopBuilder: FSMStopBuilder[S, D]): Unit =
onTermination(stopBuilder.build().asInstanceOf[PartialFunction[StopEvent, Unit]])
/**
* Create an [[akka.japi.pf.FSMStateFunctionBuilder]] with the first case statement set.
*
* A case statement that matches on an event and data type and a predicate.
*
* @param eventType the event type to match on
* @param dataType the data type to match on
* @param predicate a predicate to evaluate on the matched types
* @param apply an action to apply to the event and state data if there is a match
* @return the builder with the case statement added
*/
final def matchEvent[ET, DT <: D](eventType: Class[ET], dataType: Class[DT], predicate: TypedPredicate2[ET, DT], apply: Apply2[ET, DT, State]): FSMStateFunctionBuilder[S, D] =
new FSMStateFunctionBuilder[S, D]().event(eventType, dataType, apply)
/**
* Create an [[akka.japi.pf.FSMStateFunctionBuilder]] with the first case statement set.
*
@ -887,6 +905,43 @@ abstract class AbstractFSM[S, D] extends FSM[S, D] {
final def matchEvent[ET, DT <: D](eventType: Class[ET], dataType: Class[DT], apply: Apply2[ET, DT, State]): FSMStateFunctionBuilder[S, D] =
new FSMStateFunctionBuilder[S, D]().event(eventType, dataType, apply)
/**
* Create an [[akka.japi.pf.FSMStateFunctionBuilder]] with the first case statement set.
*
* A case statement that matches if the event type and predicate matches.
*
* @param eventType the event type to match on
* @param predicate a predicate that will be evaluated on the data and the event
* @param apply an action to apply to the event and state data if there is a match
* @return the builder with the case statement added
*/
final def matchEvent[ET](eventType: Class[ET], predicate: TypedPredicate2[ET, D], apply: Apply2[ET, D, State]): FSMStateFunctionBuilder[S, D] =
new FSMStateFunctionBuilder[S, D]().event(eventType, predicate, apply);
/**
* Create an [[akka.japi.pf.FSMStateFunctionBuilder]] with the first case statement set.
*
* A case statement that matches if the event type matches.
*
* @param eventType the event type to match on
* @param apply an action to apply to the event and state data if there is a match
* @return the builder with the case statement added
*/
final def matchEvent[ET](eventType: Class[ET], apply: Apply2[ET, D, State]): FSMStateFunctionBuilder[S, D] =
new FSMStateFunctionBuilder[S, D]().event(eventType, apply);
/**
* Create an [[akka.japi.pf.FSMStateFunctionBuilder]] with the first case statement set.
*
* A case statement that matches if the predicate matches.
*
* @param predicate a predicate that will be evaluated on the data and the event
* @param apply an action to apply to the event and state data if there is a match
* @return the builder with the case statement added
*/
final def matchEvent(predicate: TypedPredicate2[AnyRef, D], apply: Apply2[AnyRef, D, State]): FSMStateFunctionBuilder[S, D] =
new FSMStateFunctionBuilder[S, D]().event(predicate, apply);
/**
* Create an [[akka.japi.pf.FSMStateFunctionBuilder]] with the first case statement set.
*
@ -898,22 +953,9 @@ abstract class AbstractFSM[S, D] extends FSM[S, D] {
* @param apply an action to apply to the event and state data if there is a match
* @return the builder with the case statement added
*/
final def matchEvent[DT <: D](eventMatches: JList[AnyRef], dataType: Class[DT], apply: Apply[DT, State]): FSMStateFunctionBuilder[S, D] =
final def matchEvent[DT <: D](eventMatches: JList[AnyRef], dataType: Class[DT], apply: Apply2[AnyRef, DT, State]): FSMStateFunctionBuilder[S, D] =
new FSMStateFunctionBuilder[S, D]().event(eventMatches, dataType, apply);
/**
* Create an [[akka.japi.pf.FSMStateFunctionBuilder]] with the first case statement set.
*
* A case statement that matches on the data type and if the event compares equal.
*
* @param event an event to compare equal against
* @param dataType the data type to match on
* @param apply an action to apply to the event and state data if there is a match
* @return the builder with the case statement added
*/
final def matchEventEquals[DT <: D](event: AnyRef, dataType: Class[DT], apply: Apply[DT, State]): FSMStateFunctionBuilder[S, D] =
new FSMStateFunctionBuilder[S, D]().eventEquals(event, dataType, apply);
/**
* Create an [[akka.japi.pf.FSMStateFunctionBuilder]] with the first case statement set.
*
@ -924,9 +966,22 @@ abstract class AbstractFSM[S, D] extends FSM[S, D] {
* @param apply an action to apply to the event and state data if there is a match
* @return the builder with the case statement added
*/
final def matchEvent(eventMatches: JList[AnyRef], apply: Apply[D, State]): FSMStateFunctionBuilder[S, D] =
final def matchEvent(eventMatches: JList[AnyRef], apply: Apply2[AnyRef, D, State]): FSMStateFunctionBuilder[S, D] =
new FSMStateFunctionBuilder[S, D]().event(eventMatches, apply);
/**
* Create an [[akka.japi.pf.FSMStateFunctionBuilder]] with the first case statement set.
*
* A case statement that matches on the data type and if the event compares equal.
*
* @param event an event to compare equal against
* @param dataType the data type to match on
* @param apply an action to apply to the event and state data if there is a match
* @return the builder with the case statement added
*/
final def matchEventEquals[E, DT <: D](event: E, dataType: Class[DT], apply: Apply2[E, DT, State]): FSMStateFunctionBuilder[S, D] =
new FSMStateFunctionBuilder[S, D]().eventEquals(event, dataType, apply);
/**
* Create an [[akka.japi.pf.FSMStateFunctionBuilder]] with the first case statement set.
*
@ -936,7 +991,7 @@ abstract class AbstractFSM[S, D] extends FSM[S, D] {
* @param apply an action to apply to the event and state data if there is a match
* @return the builder with the case statement added
*/
final def matchEventEquals(event: AnyRef, apply: Apply[D, State]): FSMStateFunctionBuilder[S, D] =
final def matchEventEquals[E](event: E, apply: Apply2[E, D, State]): FSMStateFunctionBuilder[S, D] =
new FSMStateFunctionBuilder[S, D]().eventEquals(event, apply);
/**
@ -1068,8 +1123,10 @@ abstract class AbstractFSM[S, D] extends FSM[S, D] {
}
/**
* Java API
* Java API: compatible with lambda expressions
*
* Finite State Machine actor abstract base class.
*
* This is an EXPERIMENTAL feature and is subject to change until it has received more real world testing.
*/
abstract class AbstractLoggingFSM[S, D] extends AbstractFSM[S, D] with LoggingFSM[S, D]

View file

@ -21,6 +21,8 @@ prior deprecation.
../scala/persistence
../dev/multi-node-testing
../java/lambda-actors
../java/lambda-fsm
Another reason for marking a module as experimental is that it's too early
to tell if the module has a maintainer that can take the responsibility

View file

@ -17,6 +17,13 @@ its syntax from Erlang.
.. _Actor Model: http://en.wikipedia.org/wiki/Actor_model
.. warning::
The Java with lambda support part of Akka is marked as **“experimental”** as of its introduction in
Akka 2.3.0. We will continue to improve this API based on our users feedback, which implies that
while we try to keep incompatible changes to a minimum, but the binary compatibility guarantee for
maintenance releases does not apply to the :class:`akka.actor.AbstractActor`, related classes and
the :class:`akka.japi.pf` package.
Creating Actors
===============

View file

@ -21,6 +21,14 @@ These relations are interpreted as meaning:
*If we are in state S and the event E occurs, we should perform the actions A
and make a transition to the state S'.*
.. warning::
The Java with lambda support part of Akka is marked as **“experimental”** as of its introduction in
Akka 2.3.0. We will continue to improve this API based on our users feedback, which implies that
while we try to keep incompatible changes to a minimum, but the binary compatibility guarantee for
maintenance releases does not apply to the :class:`akka.actor.AbstractFSM`, related classes and the
:class:`akka.japi.pf` package.
A Simple Example
================

View file

@ -47,7 +47,7 @@ public class Buncher extends AbstractFSM<State, Data> {
when(Active, Duration.create(1, "second"),
matchEvent(Arrays.asList(Flush.class, StateTimeout()), Todo.class,
todo -> goTo(Idle).using(todo.copy(new LinkedList<>()))));
(event, todo) -> goTo(Idle).using(todo.copy(new LinkedList<>()))));
//#unhandled-elided
whenUnhandled(

View file

@ -85,7 +85,7 @@ public class FSMDocTest {
//#alt-transition-syntax
//#stop-syntax
when(Error, matchEventEquals("stop", (data) -> {
when(Error, matchEventEquals("stop", (event, data) -> {
// do cleanup ...
return stop();
}));
@ -103,7 +103,7 @@ public class FSMDocTest {
//#unhandled-syntax
whenUnhandled(
matchEvent(X.class, null, (x, data) -> {
matchEvent(X.class, (x, data) -> {
log().info("Received unhandled event: " + x);
return stay();
}).
@ -146,7 +146,7 @@ public class FSMDocTest {
target.tell("going active", self());
return goTo(Active);
}));
when(Active, matchEventEquals("stop", (data) -> {
when(Active, matchEventEquals("stop", (event, data) -> {
target.tell("stopping", self());
return stop(new Failure("This is not the error you're looking for"));
}));

View file

@ -0,0 +1,121 @@
Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.

View file

@ -0,0 +1,10 @@
Activator Template by Typesafe
Licensed under Public Domain (CC0)
To the extent possible under law, the person who associated CC0 with
this Activator Tempate has waived all copyright and related or neighboring
rights to this Activator Template.
You should have received a copy of the CC0 legalcode along with this
work. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.

View file

@ -0,0 +1,7 @@
name=akka-sample-fsm-java-lambda
title=Akka FSM in Java with Lambdas
description=Illustrating how to implement a finite state machine in actors.
tags=akka,java,java8,sample
authorName=Akka Team
authorLink=http://akka.io/
sourceLink=https://github.com/akka/akka

View file

@ -0,0 +1,15 @@
name := "akka-docs-java-lambda"
version := "1.0"
scalaVersion := "2.10.3"
javacOptions ++= Seq("-source", "1.8", "-target", "1.8", "-Xlint")
testOptions += Tests.Argument(TestFrameworks.JUnit, "-v", "-a")
libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-actor" % "2.3-SNAPSHOT",
"com.typesafe.akka" %% "akka-testkit" % "2.3-SNAPSHOT" % "test",
"junit" % "junit" % "4.11" % "test",
"com.novocode" % "junit-interface" % "0.10" % "test")

View file

@ -0,0 +1,53 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<groupId>sample</groupId>
<artifactId>akka-sample-fsm-java-lambda</artifactId>
<packaging>jar</packaging>
<version>1.0</version>
<dependencies>
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-actor_2.10</artifactId>
<version>2.3-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-testkit_2.10</artifactId>
<version>2.3-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<fork>true</fork>
<compilerArgs>
<arg>-Xlint</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
</project>

View file

@ -0,0 +1 @@
sbt.version=0.13.1

View file

@ -0,0 +1,162 @@
package sample.become;
import akka.actor.*;
import akka.japi.pf.ReceiveBuilder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import scala.concurrent.duration.Duration;
import scala.concurrent.duration.FiniteDuration;
import scala.PartialFunction;
import scala.runtime.BoxedUnit;
import static sample.become.Messages.*;
import static java.util.concurrent.TimeUnit.*;
// Akka adaptation of
// http://www.dalnefre.com/wp/2010/08/dining-philosophers-in-humus/
public class DiningHakkersOnBecome {
/*
* A Chopstick is an actor, it can be taken, and put back
*/
public static class Chopstick extends AbstractActor {
//When a Chopstick is taken by a hakker
//It will refuse to be taken by other hakkers
//But the owning hakker can put it back
PartialFunction<Object, BoxedUnit> takenBy(ActorRef hakker) {
return ReceiveBuilder.
match(Take.class,
t -> t.hakker.tell(new Busy(self()), self())).
match(Put.class, p -> p.hakker == hakker,
p -> context().become(available)).
build();
}
//When a Chopstick is available, it can be taken by a hakker
PartialFunction<Object, BoxedUnit> available = ReceiveBuilder.
match(Take.class, t -> {
context().become(takenBy(t.hakker));
t.hakker.tell(new Taken(self()), self());
}).build();
//A Chopstick begins its existence as available
public PartialFunction<Object, BoxedUnit> receive() {
return available;
}
}
/*
* A hakker is an awesome dude or dudette who either thinks about hacking or has to eat ;-)
*/
public static class Hakker extends AbstractActor {
private String name;
private ActorRef left;
private ActorRef right;
public Hakker(String name, ActorRef left, ActorRef right) {
this.name = name;
this.left = left;
this.right = right;
}
//When a hakker is eating, he can decide to start to think,
//then he puts down his chopsticks and starts to think
PartialFunction<Object, BoxedUnit> eating = ReceiveBuilder.
matchEquals(Think, m -> {
left.tell(new Put(self()), self());
right.tell(new Put(self()), self());
System.out.println(String.format("%s puts down his chopsticks and starts to think", name));
startThinking(Duration.create(5, SECONDS));
}).build();
//When a hakker is waiting for the last chopstick it can either obtain it
//and start eating, or the other chopstick was busy, and the hakker goes
//back to think about how he should obtain his chopsticks :-)
PartialFunction<Object, BoxedUnit> waitingFor(ActorRef chopstickToWaitFor, ActorRef otherChopstick) {
return ReceiveBuilder.
match(Taken.class, t -> t.chopstick == chopstickToWaitFor, t -> {
System.out.println(String.format("%s has picked up %s and %s and starts to eat",
name, left.path().name(), right.path().name()));
context().become(eating);
context().system().scheduler().scheduleOnce(Duration.create(5, SECONDS), self(), Think, context().system().dispatcher(), self());
}).
match(Busy.class, b -> {
otherChopstick.tell(new Put(self()), self());
startThinking(Duration.create(10, MILLISECONDS));
}).
build();
}
//When the results of the other grab comes back,
//he needs to put it back if he got the other one.
//Then go back and think and try to grab the chopsticks again
PartialFunction<Object, BoxedUnit> deniedAChopstick = ReceiveBuilder.
match(Taken.class, t -> {
t.chopstick.tell(new Put(self()), self());
startThinking(Duration.create(10, MILLISECONDS));
}).
match(Busy.class, b ->
startThinking(Duration.create(10, MILLISECONDS))).
build();
//When a hakker is hungry it tries to pick up its chopsticks and eat
//When it picks one up, it goes into wait for the other
//If the hakkers first attempt at grabbing a chopstick fails,
//it starts to wait for the response of the other grab
PartialFunction<Object, BoxedUnit> hungry = ReceiveBuilder.
match(Taken.class, t -> t.chopstick == left,
t -> context().become(waitingFor(right, left))).
match(Taken.class, t -> t.chopstick == right,
t -> context().become(waitingFor(left, right))).
match(Busy.class,
b -> context().become(deniedAChopstick)).
build();
//When a hakker is thinking it can become hungry
//and try to pick up its chopsticks and eat
PartialFunction<Object, BoxedUnit> thinking = ReceiveBuilder.
matchEquals(Eat, m -> {
context().become(hungry);
left.tell(new Take(self()), self());
right.tell(new Take(self()), self());
}).build();
//All hakkers start in a non-eating state
public PartialFunction<Object, BoxedUnit> receive() {
return ReceiveBuilder.matchEquals(Think, m -> {
System.out.println(String.format("%s starts to think", name));
startThinking(Duration.create(5, SECONDS));
}).build();
}
private void startThinking(FiniteDuration duration) {
context().become(thinking);
context().system().scheduler().scheduleOnce(duration, self(), Eat, context().system().dispatcher(), self());
}
}
/*
* Alright, here's our test-harness
*/
public static void main(String[] args) {
ActorSystem system = ActorSystem.create();
//Create 5 chopsticks
ActorRef[] chopsticks = new ActorRef[5];
for (int i = 0; i < 5; i++)
chopsticks[i] = system.actorOf(Props.create(Chopstick.class), "Chopstick" + i);
//Create 5 awesome hakkers and assign them their left and right chopstick
List<String> names = Arrays.asList("Ghosh", "Boner", "Klang", "Krasser", "Manie");
List<ActorRef> hakkers = new ArrayList<>();
int i = 0;
for (String name: names) {
hakkers.add(system.actorOf(Props.create(Hakker.class, name, chopsticks[i], chopsticks[(i + 1) % 5])));
i++;
}
//Signal all hakkers that they should start thinking, and watch the show
hakkers.stream().forEach(hakker -> hakker.tell(Think, ActorRef.noSender()));
}
}

View file

@ -0,0 +1,39 @@
package sample.become;
import akka.actor.ActorRef;
public class Messages {
public static final class Busy {
public final ActorRef chopstick;
public Busy(ActorRef chopstick){
this.chopstick = chopstick;
}
}
public static final class Put {
public final ActorRef hakker;
public Put(ActorRef hakker){
this.hakker = hakker;
}
}
public static final class Take {
public final ActorRef hakker;
public Take(ActorRef hakker){
this.hakker = hakker;
}
}
public static final class Taken {
public final ActorRef chopstick;
public Taken(ActorRef chopstick){
this.chopstick = chopstick;
}
}
private static interface EatMessage {};
public static final Object Eat = new EatMessage() {};
private static interface ThinkMessage {};
public static final Object Think = new ThinkMessage() {};
}

View file

@ -0,0 +1,205 @@
package sample.fsm;
import akka.actor.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import scala.concurrent.duration.Duration;
import scala.concurrent.duration.FiniteDuration;
import static java.util.concurrent.TimeUnit.*;
import static sample.fsm.Messages.*;
// Akka adaptation of
// http://www.dalnefre.com/wp/2010/08/dining-philosophers-in-humus/
public class DiningHakkersOnFsm {
/**
* Some states the chopstick can be in
*/
public static enum CS {
Available,
Taken
}
/**
* Some state container for the chopstick
*/
public static final class TakenBy {
public final ActorRef hakker;
public TakenBy(ActorRef hakker){
this.hakker = hakker;
}
}
/*
* A chopstick is an actor, it can be taken, and put back
*/
public static class Chopstick extends AbstractLoggingFSM<CS, TakenBy> {
{
// A chopstick begins its existence as available and taken by no one
startWith(CS.Available, new TakenBy(context().system().deadLetters()));
// When a chopstick is available, it can be taken by a some hakker
when(CS.Available,
matchEventEquals(Take, (take, data) ->
goTo(CS.Taken).using(new TakenBy(sender())).replying(new Taken(self()))));
// When a chopstick is taken by a hakker
// It will refuse to be taken by other hakkers
// But the owning hakker can put it back
when(CS.Taken,
matchEventEquals(Take, (take, data) ->
stay().replying(new Busy(self()))).
event((event, data) -> (event == Put) && (data.hakker == sender()), (event, data) ->
goTo(CS.Available).using(new TakenBy(context().system().deadLetters()))));
// Initialze the chopstick
initialize();
}
}
/**
* Some fsm hakker states
*/
public static enum HS {
Waiting,
Thinking,
Hungry,
WaitForOtherChopstick,
FirstChopstickDenied,
Eating
}
/**
* Some state container to keep track of which chopsticks we have
*/
public static final class TakenChopsticks {
public final ActorRef left;
public final ActorRef right;
public TakenChopsticks(ActorRef left, ActorRef right) {
this.left = left;
this.right = right;
}
}
/*
* A fsm hakker is an awesome dude or dudette who either thinks about hacking or has to eat ;-)
*/
public static class Hakker extends AbstractLoggingFSM<HS, TakenChopsticks> {
private String name;
private ActorRef left;
private ActorRef right;
public Hakker(String name, ActorRef left, ActorRef right) {
this.name = name;
this.left = left;
this.right = right;
}
{
//All hakkers start waiting
startWith(HS.Waiting, new TakenChopsticks(null, null));
when(HS.Waiting,
matchEventEquals(Think, (think, data) -> {
System.out.println(String.format("%s starts to think", name));
return startThinking(Duration.create(5, SECONDS));
}));
//When a hakker is thinking it can become hungry
//and try to pick up its chopsticks and eat
when(HS.Thinking,
matchEventEquals(StateTimeout(), (event, data) -> {
left.tell(Take, self());
right.tell(Take, self());
return goTo(HS.Hungry);
}));
// When a hakker is hungry it tries to pick up its chopsticks and eat
// When it picks one up, it goes into wait for the other
// If the hakkers first attempt at grabbing a chopstick fails,
// it starts to wait for the response of the other grab
when(HS.Hungry,
matchEvent(Taken.class, (taken, data) -> taken.chopstick == left,
(taken, data) -> goTo(HS.WaitForOtherChopstick).using(new TakenChopsticks(left, null))).
event(Taken.class, (taken, data) -> taken.chopstick == right,
(taken, data) -> goTo(HS.WaitForOtherChopstick).using(new TakenChopsticks(null, right))).
event(Busy.class,
(busy, data) -> goTo(HS.FirstChopstickDenied)));
// When a hakker is waiting for the last chopstick it can either obtain it
// and start eating, or the other chopstick was busy, and the hakker goes
// back to think about how he should obtain his chopsticks :-)
when(HS.WaitForOtherChopstick,
matchEvent(Taken.class,
(taken, data) -> (taken.chopstick == left && data.left == null && data.right != null),
(taken, data) -> startEating(left, right)).
event(Taken.class,
(taken, data) -> (taken.chopstick == right && data.left != null && data.right == null),
(taken, data) -> startEating(left, right)).
event(Busy.class, (busy, data) -> {
if (data.left != null) left.tell(Put, self());
if (data.right != null) right.tell(Put, self());
return startThinking(Duration.create(10, MILLISECONDS));
}));
// When the results of the other grab comes back,
// he needs to put it back if he got the other one.
// Then go back and think and try to grab the chopsticks again
when(HS.FirstChopstickDenied,
matchEvent(Taken.class, (taken, data) -> {
taken.chopstick.tell(Put, self());
return startThinking(Duration.create(10, MILLISECONDS));
}).
event(Busy.class, (busy, data) ->
startThinking(Duration.create(10, MILLISECONDS))));
// When a hakker is eating, he can decide to start to think,
// then he puts down his chopsticks and starts to think
when(HS.Eating,
matchEventEquals(StateTimeout(), (event, data) -> {
left.tell(Put, self());
right.tell(Put, self());
System.out.println(String.format("%s puts down his chopsticks and starts to think", name));
return startThinking(Duration.create(5, SECONDS));
}));
// Initialize the hakker
initialize();
}
private FSM.State<HS, TakenChopsticks> startEating(ActorRef left, ActorRef right) {
System.out.println(String.format("%s has picked up %s and %s and starts to eat",
name, left.path().name(), right.path().name()));
return goTo(HS.Eating).using(new TakenChopsticks(left, right)).forMax(Duration.create(5, SECONDS));
}
private FSM.State<HS, TakenChopsticks> startThinking(FiniteDuration duration) {
return goTo(HS.Thinking).using(new TakenChopsticks(null, null)).forMax(duration);
}
}
/*
* Alright, here's our test-harness
*/
public static void main(String[] args) {
ActorSystem system = ActorSystem.create();
//Create 5 chopsticks
ActorRef[] chopsticks = new ActorRef[5];
for (int i = 0; i < 5; i++)
chopsticks[i] = system.actorOf(Props.create(Chopstick.class), "Chopstick" + i);
//Create 5 awesome hakkers and assign them their left and right chopstick
List<String> names = Arrays.asList("Ghosh", "Boner", "Klang", "Krasser", "Manie");
List<ActorRef> hakkers = new ArrayList<>();
int i = 0;
for (String name: names) {
hakkers.add(system.actorOf(Props.create(Hakker.class, name, chopsticks[i], chopsticks[(i + 1) % 5]), name));
i++;
}
//Signal all hakkers that they should start thinking, and watch the show
hakkers.stream().forEach(hakker -> hakker.tell(Think, ActorRef.noSender()));
}
}

View file

@ -0,0 +1,35 @@
package sample.fsm;
import akka.actor.ActorRef;
public class Messages {
public static final class Busy {
public final ActorRef chopstick;
public Busy(ActorRef chopstick){
this.chopstick = chopstick;
}
}
private static interface PutMessage {};
public static final Object Put = new PutMessage() {
@Override
public String toString() { return "Put"; }
};
private static interface TakeMessage {};
public static final Object Take = new TakeMessage() {
@Override
public String toString() { return "Take"; }
};
public static final class Taken {
public final ActorRef chopstick;
public Taken(ActorRef chopstick){
this.chopstick = chopstick;
}
}
private static interface ThinkMessage {};
public static final Object Think = new ThinkMessage() {};
}

View file

@ -0,0 +1,73 @@
<!-- <html> -->
<head>
<title>Akka FSM in Java with Lambdas</title>
</head>
<body>
<div>
<h2>Finite State Machine in Actors</h2>
<p>
This sample is an adaptation of
<a href="http://www.dalnefre.com/wp/2010/08/dining-philosophers-in-humus/" target="_blank">Dining Hakkers</a>.
It illustrates how state and behavior can be managed within
an Actor with two different approaches; using <code>become</code> and using
the <code>AbstractFSM</code> class.
</p>
</div>
<div>
<h2>Dining Hakkers with Become</h2>
<p>
Open <a href="#code/src/main/java/sample/become/DiningHakkersOnBecome.java" class="shortcut">DiningHakkersOnBecome.java</a>.
</p>
<p>
It illustrates how current behavior can be replaced with <code>context.become</code>.
Note that no <code>var</code> members are used, instead the state is encoded in the current
behavior and its parameters.
</p>
<p>
Go to the <a href="#run" class="shortcut">Run</a> tab, and start the application main class
<code>sample.become.DiningHakkersOnBecome</code>.
In the log output you can see the actions of the <code>Hakker</code> actors.
</p>
<p>
Read more about <code>become</code> in
<a href="http://doc.akka.io/docs/akka/2.3-SNAPSHOT/java/lambda-actors.html#Become_Unbecome" target="_blank">the documentation</a>.
</p>
</div>
<div>
<h2>Dining Hakkers with FSM</h2>
<p>
Open <a href="#code/src/main/java/sample/fsm/DiningHakkersOnFsm.java" class="shortcut">DiningHakkersOnFsm.java</a>.
</p>
<p>
It illustrates how the states and transitions can be defined with the <code>akka.actor.AbstractFSM</code> class.
</p>
<p>
Go to the <a href="#run" class="shortcut">Run</a> tab, and start the application main class
<code>sample.fsm.DiningHakkersOnFsm</code>.
In the log output you can see the actions of the <code>Hakker</code> actors.
</p>
<p>
Read more about <code>akka.actor.FSM</code> in
<a href="http://doc.akka.io/docs/akka/2.3-SNAPSHOT/java/lambda-fsm.html" target="_blank">the documentation</a>.
</p>
</div>
</body>
</html>

View file

@ -92,3 +92,8 @@ tmp="$script_dir/../../akka-samples/akka-docs-java-lambda"
try cd "$tmp" "can't step into project directory: $tmp"
export JAVA_HOME="$java8_home"
try mvn clean test "mvn execution in akka-docs-java-lambda failed"
tmp="$script_dir/../../akka-samples/akka-sample-fsm-java-lambda"
try cd "$tmp" "can't step into project directory: $tmp"
export JAVA_HOME="$java8_home"
try mvn clean test "mvn execution in akka-sample-fsm-java-lambda failed"