!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:
parent
6af33381b3
commit
07e361c684
30 changed files with 1026 additions and 103 deletions
|
|
@ -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> {
|
||||
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
===============
|
||||
|
|
|
|||
|
|
@ -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
|
||||
================
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
}));
|
||||
|
|
|
|||
121
akka-samples/akka-sample-fsm-java-lambda/COPYING
Normal file
121
akka-samples/akka-sample-fsm-java-lambda/COPYING
Normal 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.
|
||||
10
akka-samples/akka-sample-fsm-java-lambda/LICENSE
Normal file
10
akka-samples/akka-sample-fsm-java-lambda/LICENSE
Normal 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/>.
|
||||
|
|
@ -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
|
||||
15
akka-samples/akka-sample-fsm-java-lambda/build.sbt
Normal file
15
akka-samples/akka-sample-fsm-java-lambda/build.sbt
Normal 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")
|
||||
53
akka-samples/akka-sample-fsm-java-lambda/pom.xml
Normal file
53
akka-samples/akka-sample-fsm-java-lambda/pom.xml
Normal 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>
|
||||
|
|
@ -0,0 +1 @@
|
|||
sbt.version=0.13.1
|
||||
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
|
@ -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() {};
|
||||
}
|
||||
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
|
@ -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() {};
|
||||
}
|
||||
73
akka-samples/akka-sample-fsm-java-lambda/tutorial/index.html
Normal file
73
akka-samples/akka-sample-fsm-java-lambda/tutorial/index.html
Normal 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>
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue