diff --git a/akka-actor/src/main/scala/akka/actor/FSM.scala b/akka-actor/src/main/scala/akka/actor/FSM.scala index bf20b43274..ee9d38efab 100644 --- a/akka-actor/src/main/scala/akka/actor/FSM.scala +++ b/akka-actor/src/main/scala/akka/actor/FSM.scala @@ -49,21 +49,6 @@ object FSM { } } - /* - * This extractor is just convenience for matching a (S, S) pair, including a - * reminder what the new state is. - */ - object -> { - def unapply[S](in: (S, S)) = Some(in) - } - - /* - * With these implicits in scope, you can write "5 seconds" anywhere a - * Duration or Option[Duration] is expected. This is conveniently true - * for derived classes. - */ - implicit def d2od(d: Duration): Option[Duration] = Some(d) - case class LogEntry[S, D](stateName: S, stateData: D, event: Any) case class State[S, D](stateName: S, stateData: D, timeout: Option[Duration] = None, stopReason: Option[Reason] = None, replies: List[Any] = Nil) { @@ -208,7 +193,12 @@ trait FSM[S, D] extends Listeners { * @param stateTimeout default state timeout for this state * @param stateFunction partial function describing response to input */ - protected final def when(stateName: S, stateTimeout: Timeout = None)(stateFunction: StateFunction) = { + protected final def when(stateName: S, stateTimeout: Duration = null)(stateFunction: StateFunction) = { + register(stateName, stateFunction, Option(stateTimeout)) + } + + @deprecated("use the more import-friendly variant taking a Duration", "2.0") + protected final def when(stateName: S, stateTimeout: Timeout)(stateFunction: StateFunction) = { register(stateName, stateFunction, stateTimeout) } @@ -312,6 +302,14 @@ trait FSM[S, D] extends Listeners { stateTimeouts(state) = timeout } + /** + * This extractor is just convenience for matching a (S, S) pair, including a + * reminder what the new state is. + */ + object -> { + def unapply[S](in: (S, S)) = Some(in) + } + /** * Set handler which is called upon each state transition, i.e. not when * staying in the same state. This may use the pair extractor defined in the diff --git a/akka-docs/scala/code/akka/docs/actor/FSMDocSpec.scala b/akka-docs/scala/code/akka/docs/actor/FSMDocSpec.scala new file mode 100644 index 0000000000..5668d88868 --- /dev/null +++ b/akka-docs/scala/code/akka/docs/actor/FSMDocSpec.scala @@ -0,0 +1,93 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ +package akka.docs.actor + +import akka.testkit.AkkaSpec + +class FSMDocSpec extends AkkaSpec { + + "simple finite state machine" must { + //#simple-imports + import akka.actor.{ Actor, ActorRef, FSM, Props } + import akka.util.duration._ + //#simple-imports + //#simple-events + // received events + case class SetTarget(ref: ActorRef) + case class Queue(obj: Any) + case object Flush + + // sent events + case class Batch(obj: Seq[Any]) + //#simple-events + //#simple-state + // states + trait State + case object Idle extends State + case object Active extends State + + trait Data + case object Uninitialized extends Data + case class Todo(target: ActorRef, queue: Seq[Any]) extends Data + //#simple-state + //#simple-fsm + class Buncher extends Actor with FSM[State, Data] { + + startWith(Idle, Uninitialized) + + when(Idle) { + case Event(SetTarget(ref), Uninitialized) ⇒ stay using Todo(ref, Vector.empty) + } + + //#simple-transition + onTransition { + case Active -> Idle ⇒ + stateData match { + case Todo(ref, queue) ⇒ ref ! Batch(queue) + } + } + //#simple-transition + + when(Active, stateTimeout = 1 second) { + case Event(Flush | FSM.StateTimeout, t: Todo) ⇒ goto(Idle) using t.copy(queue = Vector.empty) + } + + //#simple-unhandled + whenUnhandled { + // common code for both states + case Event(Queue(obj), t @ Todo(_, v)) ⇒ + goto(Active) using t.copy(queue = v :+ obj) + + case Event(e, s) ⇒ + log.warning("received unhandled request {} in state {}/{}", e, stateName, s) + stay + } + //#simple-unhandled + + initialize + } + //#simple-fsm + + "batch correctly" in { + val buncher = system.actorOf(Props(new Buncher)) + buncher ! SetTarget(testActor) + buncher ! Queue(42) + buncher ! Queue(43) + expectMsg(Batch(Seq(42, 43))) + buncher ! Queue(44) + buncher ! Flush + buncher ! Queue(45) + expectMsg(Batch(Seq(44))) + expectMsg(Batch(Seq(45))) + } + + "batch not if uninitialized" in { + val buncher = system.actorOf(Props(new Buncher)) + buncher ! Queue(42) + expectNoMsg + } + + } + +} \ No newline at end of file diff --git a/akka-testkit/src/main/scala/akka/testkit/TestFSMRef.scala b/akka-testkit/src/main/scala/akka/testkit/TestFSMRef.scala index f486e3a5bb..fd3567f19c 100644 --- a/akka-testkit/src/main/scala/akka/testkit/TestFSMRef.scala +++ b/akka-testkit/src/main/scala/akka/testkit/TestFSMRef.scala @@ -62,8 +62,8 @@ class TestFSMRef[S, D, T <: Actor]( * corresponding transition initiated from within the FSM, including timeout * and stop handling. */ - def setState(stateName: S = fsm.stateName, stateData: D = fsm.stateData, timeout: Option[Duration] = None, stopReason: Option[FSM.Reason] = None) { - fsm.applyState(FSM.State(stateName, stateData, timeout, stopReason)) + def setState(stateName: S = fsm.stateName, stateData: D = fsm.stateData, timeout: Duration = null, stopReason: Option[FSM.Reason] = None) { + fsm.applyState(FSM.State(stateName, stateData, Option(timeout), stopReason)) } /**