2012-01-24 10:45:18 +01:00
|
|
|
/**
|
|
|
|
|
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
|
|
|
|
*/
|
2012-05-22 11:37:09 +02:00
|
|
|
package docs.actor;
|
2012-01-24 10:45:18 +01:00
|
|
|
|
|
|
|
|
//#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;
|
2012-01-30 14:51:25 +01:00
|
|
|
import akka.testkit.AkkaSpec;
|
2012-01-24 10:45:18 +01:00
|
|
|
|
|
|
|
|
public class FSMDocTestBase {
|
|
|
|
|
|
2012-09-26 10:56:25 +02:00
|
|
|
static
|
2012-01-24 10:45:18 +01:00
|
|
|
//#data
|
2012-09-26 10:56:25 +02:00
|
|
|
public final class SetTarget {
|
2012-01-24 10:45:18 +01:00
|
|
|
final ActorRef ref;
|
2012-01-30 14:51:25 +01:00
|
|
|
|
2012-01-24 10:45:18 +01:00
|
|
|
public SetTarget(ActorRef ref) {
|
|
|
|
|
this.ref = ref;
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-01-30 14:51:25 +01:00
|
|
|
|
2012-09-26 10:56:25 +02:00
|
|
|
//#data
|
|
|
|
|
static
|
|
|
|
|
//#data
|
|
|
|
|
public final class Queue {
|
2012-01-24 10:45:18 +01:00
|
|
|
final Object o;
|
2012-01-30 14:51:25 +01:00
|
|
|
|
2012-01-24 10:45:18 +01:00
|
|
|
public Queue(Object o) {
|
|
|
|
|
this.o = o;
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-01-30 14:51:25 +01:00
|
|
|
|
2012-09-26 10:56:25 +02:00
|
|
|
//#data
|
|
|
|
|
static
|
|
|
|
|
//#data
|
|
|
|
|
public final Object flush = new Object();
|
2012-01-30 14:51:25 +01:00
|
|
|
|
2012-09-26 10:56:25 +02:00
|
|
|
//#data
|
|
|
|
|
static
|
|
|
|
|
//#data
|
|
|
|
|
public final class Batch {
|
2012-01-24 10:45:18 +01:00
|
|
|
final List<Object> objects;
|
2012-01-30 14:51:25 +01:00
|
|
|
|
2012-01-24 10:45:18 +01:00
|
|
|
public Batch(List<Object> objects) {
|
|
|
|
|
this.objects = objects;
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-01-30 14:51:25 +01:00
|
|
|
|
2012-01-24 10:45:18 +01:00
|
|
|
//#data
|
|
|
|
|
|
2012-09-26 10:56:25 +02:00
|
|
|
static
|
2012-01-24 10:45:18 +01:00
|
|
|
//#base
|
2012-09-26 10:56:25 +02:00
|
|
|
public abstract class MyFSMBase extends UntypedActor {
|
2012-01-30 14:51:25 +01:00
|
|
|
|
2012-01-24 10:45:18 +01:00
|
|
|
/*
|
|
|
|
|
* This is the mutable state of this state machine.
|
|
|
|
|
*/
|
2012-01-30 14:51:25 +01:00
|
|
|
protected enum State {
|
|
|
|
|
IDLE, ACTIVE;
|
|
|
|
|
}
|
|
|
|
|
|
2012-01-24 10:45:18 +01:00
|
|
|
private State state = State.IDLE;
|
|
|
|
|
private ActorRef target;
|
|
|
|
|
private List<Object> queue;
|
2012-01-30 14:51:25 +01:00
|
|
|
|
2012-01-24 10:45:18 +01:00
|
|
|
/*
|
|
|
|
|
* Then come all the mutator methods:
|
|
|
|
|
*/
|
|
|
|
|
protected void init(ActorRef target) {
|
|
|
|
|
this.target = target;
|
|
|
|
|
queue = new ArrayList<Object>();
|
|
|
|
|
}
|
2012-01-30 14:51:25 +01:00
|
|
|
|
2012-01-24 10:45:18 +01:00
|
|
|
protected void setState(State s) {
|
|
|
|
|
if (state != s) {
|
|
|
|
|
transition(state, s);
|
|
|
|
|
state = s;
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-01-30 14:51:25 +01:00
|
|
|
|
2012-01-24 10:45:18 +01:00
|
|
|
protected void enqueue(Object o) {
|
2012-01-30 14:51:25 +01:00
|
|
|
if (queue != null)
|
|
|
|
|
queue.add(o);
|
2012-01-24 10:45:18 +01:00
|
|
|
}
|
2012-01-30 14:51:25 +01:00
|
|
|
|
2012-01-24 10:45:18 +01:00
|
|
|
protected List<Object> drainQueue() {
|
|
|
|
|
final List<Object> q = queue;
|
2012-01-30 14:51:25 +01:00
|
|
|
if (q == null)
|
|
|
|
|
throw new IllegalStateException("drainQueue(): not yet initialized");
|
2012-01-24 10:45:18 +01:00
|
|
|
queue = new ArrayList<Object>();
|
|
|
|
|
return q;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Here are the interrogation methods:
|
|
|
|
|
*/
|
|
|
|
|
protected boolean isInitialized() {
|
|
|
|
|
return target != null;
|
|
|
|
|
}
|
2012-01-30 14:51:25 +01:00
|
|
|
|
2012-01-24 10:45:18 +01:00
|
|
|
protected State getState() {
|
|
|
|
|
return state;
|
|
|
|
|
}
|
2012-01-30 14:51:25 +01:00
|
|
|
|
2012-01-24 10:45:18 +01:00
|
|
|
protected ActorRef getTarget() {
|
2012-01-30 14:51:25 +01:00
|
|
|
if (target == null)
|
|
|
|
|
throw new IllegalStateException("getTarget(): not yet initialized");
|
2012-01-24 10:45:18 +01:00
|
|
|
return target;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* And finally the callbacks (only one in this example: react to state change)
|
|
|
|
|
*/
|
|
|
|
|
abstract protected void transition(State old, State next);
|
|
|
|
|
}
|
2012-01-30 14:51:25 +01:00
|
|
|
|
2012-01-24 10:45:18 +01:00
|
|
|
//#base
|
|
|
|
|
|
2012-09-26 10:56:25 +02:00
|
|
|
static
|
2012-01-24 10:45:18 +01:00
|
|
|
//#actor
|
2012-09-26 10:56:25 +02:00
|
|
|
public class MyFSM extends MyFSMBase {
|
2012-01-30 14:51:25 +01:00
|
|
|
|
2012-09-26 10:56:25 +02:00
|
|
|
private final LoggingAdapter log =
|
|
|
|
|
Logging.getLogger(getContext().system(), this);
|
2012-01-30 14:51:25 +01:00
|
|
|
|
2012-01-24 10:45:18 +01:00
|
|
|
@Override
|
|
|
|
|
public void onReceive(Object o) {
|
2012-01-30 14:51:25 +01:00
|
|
|
|
2012-01-24 10:45:18 +01:00
|
|
|
if (getState() == State.IDLE) {
|
2012-01-30 14:51:25 +01:00
|
|
|
|
|
|
|
|
if (o instanceof SetTarget)
|
2012-01-24 10:45:18 +01:00
|
|
|
init(((SetTarget) o).ref);
|
2012-01-30 14:51:25 +01:00
|
|
|
|
|
|
|
|
else
|
|
|
|
|
whenUnhandled(o);
|
2012-01-24 10:45:18 +01:00
|
|
|
|
|
|
|
|
} else if (getState() == State.ACTIVE) {
|
|
|
|
|
|
2012-01-30 14:51:25 +01:00
|
|
|
if (o == flush)
|
2012-01-24 10:45:18 +01:00
|
|
|
setState(State.IDLE);
|
|
|
|
|
|
2012-01-30 14:51:25 +01:00
|
|
|
else
|
|
|
|
|
whenUnhandled(o);
|
2012-01-24 10:45:18 +01:00
|
|
|
}
|
|
|
|
|
}
|
2012-01-30 14:51:25 +01:00
|
|
|
|
2012-01-24 10:45:18 +01:00
|
|
|
@Override
|
|
|
|
|
public void transition(State old, State next) {
|
|
|
|
|
if (old == State.ACTIVE) {
|
2012-09-19 23:55:53 +02:00
|
|
|
getTarget().tell(new Batch(drainQueue()), getSelf());
|
2012-01-24 10:45:18 +01:00
|
|
|
}
|
|
|
|
|
}
|
2012-01-30 14:51:25 +01:00
|
|
|
|
2012-01-24 10:45:18 +01:00
|
|
|
private void whenUnhandled(Object o) {
|
|
|
|
|
if (o instanceof Queue && isInitialized()) {
|
|
|
|
|
enqueue(((Queue) o).o);
|
|
|
|
|
setState(State.ACTIVE);
|
2012-01-30 14:51:25 +01:00
|
|
|
|
2012-01-24 10:45:18 +01:00
|
|
|
} else {
|
|
|
|
|
log.warning("received unknown message {} in state {}", o, getState());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-01-30 14:51:25 +01:00
|
|
|
|
2012-01-24 10:45:18 +01:00
|
|
|
//#actor
|
2012-01-30 14:51:25 +01:00
|
|
|
|
|
|
|
|
ActorSystem system;
|
|
|
|
|
|
|
|
|
|
@org.junit.Before
|
|
|
|
|
public void setUp() {
|
|
|
|
|
system = ActorSystem.create("FSMSystem", AkkaSpec.testConf());
|
|
|
|
|
}
|
|
|
|
|
|
2012-01-24 10:45:18 +01:00
|
|
|
@org.junit.Test
|
|
|
|
|
public void mustBunch() {
|
|
|
|
|
final ActorRef buncher = system.actorOf(new Props(MyFSM.class));
|
|
|
|
|
final TestProbe probe = new TestProbe(system);
|
2012-09-19 23:55:53 +02:00
|
|
|
buncher.tell(new SetTarget(probe.ref()), null);
|
|
|
|
|
buncher.tell(new Queue(1), null);
|
|
|
|
|
buncher.tell(new Queue(2), null);
|
|
|
|
|
buncher.tell(flush, null);
|
|
|
|
|
buncher.tell(new Queue(3), null);
|
2012-01-24 10:45:18 +01:00
|
|
|
final Batch b = probe.expectMsgClass(Batch.class);
|
|
|
|
|
assert b.objects.size() == 2;
|
|
|
|
|
assert b.objects.contains(1);
|
|
|
|
|
assert b.objects.contains(2);
|
|
|
|
|
}
|
2012-01-30 14:51:25 +01:00
|
|
|
|
2012-01-24 10:45:18 +01:00
|
|
|
@org.junit.After
|
|
|
|
|
public void cleanup() {
|
|
|
|
|
system.shutdown();
|
|
|
|
|
}
|
2012-01-30 14:51:25 +01:00
|
|
|
|
2012-01-24 10:45:18 +01:00
|
|
|
}
|