From 6afed30d430e29f99b7f34040e6e6c8c0cccdcc6 Mon Sep 17 00:00:00 2001 From: Roland Date: Tue, 24 Jan 2012 10:45:18 +0100 Subject: [PATCH] add Java FSM example and reST, see #1428 --- akka-docs/general/actors.rst | 14 +- .../code/akka/docs/actor/FSMDocTest.scala | 8 + .../code/akka/docs/actor/FSMDocTestBase.java | 174 ++++++++++++++++++ akka-docs/java/fsm.rst | 79 ++++++++ akka-docs/java/index.rst | 1 + akka-docs/scala/fsm.rst | 5 +- 6 files changed, 272 insertions(+), 9 deletions(-) create mode 100644 akka-docs/java/code/akka/docs/actor/FSMDocTest.scala create mode 100644 akka-docs/java/code/akka/docs/actor/FSMDocTestBase.java create mode 100644 akka-docs/java/fsm.rst diff --git a/akka-docs/general/actors.rst b/akka-docs/general/actors.rst index 952b1b08e4..4d8f2c8096 100644 --- a/akka-docs/general/actors.rst +++ b/akka-docs/general/actors.rst @@ -32,13 +32,13 @@ State Actor objects will typically contain some variables which reflect possible states the actor may be in. This can be an explicit state machine (e.g. using -the :ref:`fsm` module), or it could be a counter, set of listeners, pending -requests, etc. These data are what make an actor valuable, and they must be -protected from corruption by other actors. The good news is that Akka actors -conceptually each have their own light-weight thread, which is completely -shielded from the rest of the system. This means that instead of having to -synchronize access using locks you can just write your actor code without -worrying about concurrency at all. +the :ref:`fsm-scala` module), or it could be a counter, set of listeners, +pending requests, etc. These data are what make an actor valuable, and they +must be protected from corruption by other actors. The good news is that Akka +actors conceptually each have their own light-weight thread, which is +completely shielded from the rest of the system. This means that instead of +having to synchronize access using locks you can just write your actor code +without worrying about concurrency at all. Behind the scenes Akka will run sets of actors on sets of real threads, where typically many actors share one thread, and subsequent invocations of one actor diff --git a/akka-docs/java/code/akka/docs/actor/FSMDocTest.scala b/akka-docs/java/code/akka/docs/actor/FSMDocTest.scala new file mode 100644 index 0000000000..11bb542808 --- /dev/null +++ b/akka-docs/java/code/akka/docs/actor/FSMDocTest.scala @@ -0,0 +1,8 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ +package akka.docs.actor + +import org.scalatest.junit.JUnitSuite + +class FSMDocTest extends FSMDocTestBase with JUnitSuite \ No newline at end of file diff --git a/akka-docs/java/code/akka/docs/actor/FSMDocTestBase.java b/akka-docs/java/code/akka/docs/actor/FSMDocTestBase.java new file mode 100644 index 0000000000..981cac15b1 --- /dev/null +++ b/akka-docs/java/code/akka/docs/actor/FSMDocTestBase.java @@ -0,0 +1,174 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ +package akka.docs.actor; + +//#imports-data +import java.util.ArrayList; +import java.util.List; +import akka.actor.ActorRef; +//#imports-data + +//#imports-actor +import akka.event.LoggingAdapter; +import akka.event.Logging; +import akka.actor.UntypedActor; +//#imports-actor + +import akka.actor.ActorSystem; +import akka.actor.Props; +import akka.testkit.TestProbe; + +public class FSMDocTestBase { + + //#data + public static final class SetTarget { + final ActorRef ref; + public SetTarget(ActorRef ref) { + this.ref = ref; + } + } + + public static final class Queue { + final Object o; + public Queue(Object o) { + this.o = o; + } + } + + public static final Object flush = new Object(); + + public static final class Batch { + final List objects; + public Batch(List objects) { + this.objects = objects; + } + } + //#data + + //#base + static abstract class MyFSMBase extends UntypedActor { + + /* + * This is the mutable state of this state machine. + */ + protected enum State { IDLE, ACTIVE; } + private State state = State.IDLE; + private ActorRef target; + private List queue; + + /* + * Then come all the mutator methods: + */ + protected void init(ActorRef target) { + this.target = target; + queue = new ArrayList(); + } + + protected void setState(State s) { + if (state != s) { + transition(state, s); + state = s; + } + } + + protected void enqueue(Object o) { + if (queue != null) queue.add(o); + } + + protected List drainQueue() { + final List q = queue; + if (q == null) throw new IllegalStateException("drainQueue(): not yet initialized"); + queue = new ArrayList(); + return q; + } + + /* + * Here are the interrogation methods: + */ + protected boolean isInitialized() { + return target != null; + } + + protected State getState() { + return state; + } + + protected ActorRef getTarget() { + if (target == null) throw new IllegalStateException("getTarget(): not yet initialized"); + return target; + } + + /* + * And finally the callbacks (only one in this example: react to state change) + */ + abstract protected void transition(State old, State next); + } + //#base + + //#actor + static public class MyFSM extends MyFSMBase { + + private final LoggingAdapter log = Logging.getLogger(getContext().system(), this); + + @Override + public void onReceive(Object o) { + + if (getState() == State.IDLE) { + + if (o instanceof SetTarget) + init(((SetTarget) o).ref); + + else whenUnhandled(o); + + } else if (getState() == State.ACTIVE) { + + if (o == flush) + setState(State.IDLE); + + else whenUnhandled(o); + } + } + + @Override + public void transition(State old, State next) { + if (old == State.ACTIVE) { + getTarget().tell(new Batch(drainQueue())); + } + } + + private void whenUnhandled(Object o) { + if (o instanceof Queue && isInitialized()) { + enqueue(((Queue) o).o); + setState(State.ACTIVE); + + } else { + log.warning("received unknown message {} in state {}", o, getState()); + } + } + } + //#actor + + ActorSystem system = ActorSystem.create(); + + @org.junit.Test + public void mustBunch() { + final ActorRef buncher = system.actorOf(new Props(MyFSM.class)); + final TestProbe probe = new TestProbe(system); + buncher.tell(new SetTarget(probe.ref())); + buncher.tell(new Queue(1)); + buncher.tell(new Queue(2)); + buncher.tell(flush); + buncher.tell(new Queue(3)); + final Batch b = probe.expectMsgClass(Batch.class); + assert b.objects.size() == 2; + assert b.objects.contains(1); + assert b.objects.contains(2); + } + + @org.junit.After + public void cleanup() { + system.shutdown(); + } + +} diff --git a/akka-docs/java/fsm.rst b/akka-docs/java/fsm.rst new file mode 100644 index 0000000000..d66627d416 --- /dev/null +++ b/akka-docs/java/fsm.rst @@ -0,0 +1,79 @@ +.. _fsm-java: + +########################################### +Building Finite State Machine Actors (Java) +########################################### + +.. sidebar:: Contents + + .. contents:: :local: + +Overview +======== + +The FSM (Finite State Machine) pattern is best described in the `Erlang design +principles +`_. +In short, it can be seen as a set of relations of the form: + + **State(S) x Event(E) -> Actions (A), State(S')** + +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'.* + +While the Scala programming language enables the formulation of a nice internal +DSL (domain specific language) for formulating finite state machines (see +:ref:`fsm-scala`), Java’s verbosity does not lend itself well to the same +approach. This chapter describes ways to effectively achieve the same +separation of concerns through self-discipline. + +How State should be Handled +=========================== + +All mutable fields (or transitively mutable data structures) referenced by the +FSM actor’s implementation should be collected in one place and only mutated +using a small well-defined set of methods. One way to achieve this is to +assemble all mutable state in a superclass which keeps it private and offers +protected methods for mutating it. + +.. includecode:: code/akka/docs/actor/FSMDocTestBase.java#imports-data + +.. includecode:: code/akka/docs/actor/FSMDocTestBase.java#base + +The benefit of this approach is that state changes can be acted upon in one +central place, which makes it impossible to forget inserting code for reacting +to state transitions when adding to the FSM’s machinery. + +Message Buncher Example +======================= + +The base class shown above is designed to support a similar example as for the +Scala FSM documentation: an actor which receives and queues messages, to be +delivered in batches to a configurable target actor. The messages involved are: + +.. includecode:: code/akka/docs/actor/FSMDocTestBase.java#data + +This actor has only the two states ``IDLE`` and ``ACTIVE``, making their +handling quite straight-forward in the concrete actor derived from the base +class: + +.. includecode:: code/akka/docs/actor/FSMDocTestBase.java#imports-actor + +.. includecode:: code/akka/docs/actor/FSMDocTestBase.java#actor + +The trick here is to factor out common functionality like :meth:`whenUnhandled` +and :meth:`transition` in order to obtain a few well-defined points for +reacting to change or insert logging. + +State-Centric vs. Event-Centric +=============================== + +In the example above, the subjective complexity of state and events was roughly +equal, making it a matter of taste whether to choose primary dispatch on +either; in the example a state-based dispatch was chosen. Depending on how +evenly the matrix of possible states and events is populated, it may be more +practical to handle different events first and distinguish the states in the +second tier. An example would be a state machine which has a multitude of +internal states but handles only very few distinct events. diff --git a/akka-docs/java/index.rst b/akka-docs/java/index.rst index 4b0226fc35..319dbab302 100644 --- a/akka-docs/java/index.rst +++ b/akka-docs/java/index.rst @@ -21,4 +21,5 @@ Java API stm agents transactors + fsm extending-akka diff --git a/akka-docs/scala/fsm.rst b/akka-docs/scala/fsm.rst index 7b3d136ae4..618381901c 100644 --- a/akka-docs/scala/fsm.rst +++ b/akka-docs/scala/fsm.rst @@ -1,4 +1,4 @@ -.. _fsm: +.. _fsm-scala: ### FSM @@ -21,7 +21,8 @@ A FSM can be described as a set of relations of the form: 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'.* + *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'.* A Simple Example ================