+act #3770 Added Java 8 friendly APIs for Actor and FSM
This commit is contained in:
parent
301b735516
commit
e5bcf8bfc9
28 changed files with 2204 additions and 15 deletions
|
|
@ -0,0 +1,41 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package akka.japi;
|
||||||
|
|
||||||
|
import akka.japi.pf.FI;
|
||||||
|
import akka.japi.pf.Match;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
import org.junit.Test;
|
||||||
|
import scala.MatchError;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class MatchBuilderTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ExpectedException exception = ExpectedException.none();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldPassBasicMatchTest() {
|
||||||
|
Match<Object, Double> pf = Match.create(Match.match(Integer.class, new FI.Apply<Integer, Double>() {
|
||||||
|
@Override
|
||||||
|
public Double apply(Integer integer) {
|
||||||
|
return integer * 10.0;
|
||||||
|
}
|
||||||
|
}).match(Number.class, new FI.Apply<Number, Double>() {
|
||||||
|
@Override
|
||||||
|
public Double apply(Number number) {
|
||||||
|
return number.doubleValue() * (-10.0);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
assertTrue("An integer should be multiplied by 10", new Double(47110).equals(pf.match(new Integer(4711))));
|
||||||
|
assertTrue("A double should be multiplied by -10", new Double(-47110).equals(pf.match(new Double(4711))));
|
||||||
|
|
||||||
|
exception.expect(MatchError.class);
|
||||||
|
assertFalse("A string should throw a MatchError", new Double(4711).equals(pf.match("4711")));
|
||||||
|
}
|
||||||
|
}
|
||||||
36
akka-actor/src/main/java/akka/japi/pf/AbstractMatch.java
Normal file
36
akka-actor/src/main/java/akka/japi/pf/AbstractMatch.java
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package akka.japi.pf;
|
||||||
|
|
||||||
|
import scala.PartialFunction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version of {@link scala.PartialFunction} that can be built during
|
||||||
|
* runtime from Java.
|
||||||
|
*
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
class AbstractMatch<I, R> {
|
||||||
|
|
||||||
|
protected final PartialFunction<I, R> statements;
|
||||||
|
|
||||||
|
AbstractMatch(PartialFunction<I, R> statements) {
|
||||||
|
PartialFunction<I, R> empty = CaseStatement.empty();
|
||||||
|
if (statements == null)
|
||||||
|
this.statements = empty;
|
||||||
|
else
|
||||||
|
this.statements = statements.orElse(empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turn this {@link Match} into a {@link scala.PartialFunction}.
|
||||||
|
*
|
||||||
|
* @return a partial function representation ot his {@link Match}
|
||||||
|
*/
|
||||||
|
public PartialFunction<I, R> asPF() {
|
||||||
|
return statements;
|
||||||
|
}
|
||||||
|
}
|
||||||
42
akka-actor/src/main/java/akka/japi/pf/AbstractPFBuilder.java
Normal file
42
akka-actor/src/main/java/akka/japi/pf/AbstractPFBuilder.java
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package akka.japi.pf;
|
||||||
|
|
||||||
|
import scala.PartialFunction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A builder for {@link 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
|
||||||
|
*/
|
||||||
|
abstract class AbstractPFBuilder<F, T> {
|
||||||
|
|
||||||
|
protected PartialFunction<F, T> statements = null;
|
||||||
|
|
||||||
|
protected void addStatement(PartialFunction<F, T> statement) {
|
||||||
|
if (statements == null)
|
||||||
|
statements = statement;
|
||||||
|
else
|
||||||
|
statements = statements.orElse(statement);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a {@link scala.PartialFunction} from this builder.
|
||||||
|
* After this call the builder will be reset.
|
||||||
|
*
|
||||||
|
* @return a PartialFunction for this builder.
|
||||||
|
*/
|
||||||
|
public PartialFunction<F, T> build() {
|
||||||
|
PartialFunction<F, T> empty = CaseStatement.empty();
|
||||||
|
PartialFunction<F, T> statements = this.statements;
|
||||||
|
|
||||||
|
this.statements = null;
|
||||||
|
if (statements == null)
|
||||||
|
return empty;
|
||||||
|
else
|
||||||
|
return statements.orElse(empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
136
akka-actor/src/main/java/akka/japi/pf/FI.java
Normal file
136
akka-actor/src/main/java/akka/japi/pf/FI.java
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package akka.japi.pf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class that encapsulates all the Functional Interfaces
|
||||||
|
* used for creating partial functions.
|
||||||
|
*/
|
||||||
|
public final class FI {
|
||||||
|
private FI() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Functional interface for an application.
|
||||||
|
*
|
||||||
|
* @param <I> the input type, that this Apply will be applied to
|
||||||
|
* @param <R> the return type, that the results of the application will have
|
||||||
|
*/
|
||||||
|
public static interface Apply<I, R> {
|
||||||
|
/**
|
||||||
|
* The application to perform.
|
||||||
|
*
|
||||||
|
* @param i an instance that the application is performed on
|
||||||
|
* @return the result of the application
|
||||||
|
*/
|
||||||
|
public abstract R apply(I i);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Functional interface for an application.
|
||||||
|
*
|
||||||
|
* @param <I1> the first input type, that this Apply will be applied to
|
||||||
|
* @param <I2> the second input type, that this Apply will be applied to
|
||||||
|
* @param <R> the return type, that the results of the application will have
|
||||||
|
*/
|
||||||
|
public static interface Apply2<I1, I2, R> {
|
||||||
|
/**
|
||||||
|
* The application to perform.
|
||||||
|
*
|
||||||
|
* @param i1 an instance that the application is performed on
|
||||||
|
* @param i2 an instance that the application is performed on
|
||||||
|
* @return the result of the application
|
||||||
|
*/
|
||||||
|
public abstract R apply(I1 i1, I2 i2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Functional interface for a predicate.
|
||||||
|
*
|
||||||
|
* @param <T> the type that the predicate will operate on.
|
||||||
|
*/
|
||||||
|
public static interface TypedPredicate<T> {
|
||||||
|
/**
|
||||||
|
* The predicate to evaluate.
|
||||||
|
*
|
||||||
|
* @param t an instance that the predicate is evaluated on.
|
||||||
|
* @return the result of the predicate
|
||||||
|
*/
|
||||||
|
public abstract boolean defined(T t);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Functional interface for an application.
|
||||||
|
*
|
||||||
|
* @param <I> the input type, that this Apply will be applied to
|
||||||
|
*/
|
||||||
|
public static interface UnitApply<I> {
|
||||||
|
/**
|
||||||
|
* The application to perform.
|
||||||
|
*
|
||||||
|
* @param i an instance that the application is performed on
|
||||||
|
*/
|
||||||
|
public abstract void apply(I i);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Functional interface for an application.
|
||||||
|
*
|
||||||
|
* @param <I1> the first input type, that this Apply will be applied to
|
||||||
|
* @param <I2> the second input type, that this Apply will be applied to
|
||||||
|
*/
|
||||||
|
public static interface UnitApply2<I1, I2> {
|
||||||
|
/**
|
||||||
|
* The application to perform.
|
||||||
|
*
|
||||||
|
* @param i1 an instance that the application is performed on
|
||||||
|
* @param i2 an instance that the application is performed on
|
||||||
|
*/
|
||||||
|
public abstract void apply(I1 i1, I2 i2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Functional interface for an application.
|
||||||
|
*
|
||||||
|
* @param <I1> the first input type, that this Apply will be applied to
|
||||||
|
* @param <I2> the second input type, that this Apply will be applied to
|
||||||
|
* @param <I3> the third input type, that this Apply will be applied to
|
||||||
|
*/
|
||||||
|
public static interface UnitApply3<I1, I2, I3> {
|
||||||
|
/**
|
||||||
|
* The application to perform.
|
||||||
|
*
|
||||||
|
* @param i1 an instance that the application is performed on
|
||||||
|
* @param i2 an instance that the application is performed on
|
||||||
|
* @param i3 an instance that the application is performed on
|
||||||
|
*/
|
||||||
|
public abstract void apply(I1 i1, I2 i2, I3 i3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Functional interface for an application.
|
||||||
|
*/
|
||||||
|
public static interface UnitApplyVoid {
|
||||||
|
/**
|
||||||
|
* The application to perform.
|
||||||
|
*/
|
||||||
|
public abstract void apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Package scoped functional interface for a predicate. Used internally to match against arbitrary types.
|
||||||
|
*/
|
||||||
|
static interface Predicate {
|
||||||
|
/**
|
||||||
|
* The predicate to evaluate.
|
||||||
|
*
|
||||||
|
* @param o an instance that the predicate is evaluated on.
|
||||||
|
* @return the result of the predicate
|
||||||
|
*/
|
||||||
|
public abstract boolean defined(Object o);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,131 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package akka.japi.pf;
|
||||||
|
|
||||||
|
import akka.actor.FSM;
|
||||||
|
import scala.PartialFunction;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder used to create a partial function for {@link akka.actor.FSM#whenUnhandled}.
|
||||||
|
*
|
||||||
|
* @param <S> the state type
|
||||||
|
* @param <D> the data type
|
||||||
|
*/
|
||||||
|
public class FSMStateFunctionBuilder<S, D> {
|
||||||
|
|
||||||
|
private PFBuilder<FSM.Event<D>, FSM.State<S, D>> builder =
|
||||||
|
new PFBuilder<FSM.Event<D>, FSM.State<S, D>>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a case statement that matches on an event and data type.
|
||||||
|
*
|
||||||
|
* @param eventType the event type to match on
|
||||||
|
* @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 <P> the event type to match on
|
||||||
|
* @param <Q> the data type to match on
|
||||||
|
* @return the builder with the case statement added
|
||||||
|
*/
|
||||||
|
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) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
P p = (P) e.event();
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Q q = (Q) e.stateData();
|
||||||
|
return apply.apply(p, q);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a case statement that matches on the data type and if any of the event types
|
||||||
|
* in the list match or any of the event instances in the list compares equal.
|
||||||
|
*
|
||||||
|
* @param eventMatches a list of types or instances to match 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> event(final List<Object> eventMatches,
|
||||||
|
final Class<Q> dataType,
|
||||||
|
final FI.Apply<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()))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
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) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Q q = (Q) e.stateData();
|
||||||
|
return apply.apply(q);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a case statement that matches on any type of event.
|
||||||
|
*
|
||||||
|
* @param apply an action to apply to the event and state data
|
||||||
|
* @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) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
D d = (D) e.stateData();
|
||||||
|
return apply.apply(e.event(), d);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a {@link scala.PartialFunction} from this builder.
|
||||||
|
* After this call the builder will be reset.
|
||||||
|
*
|
||||||
|
* @return a PartialFunction for this builder.
|
||||||
|
*/
|
||||||
|
public PartialFunction<FSM.Event<D>, FSM.State<S, D>> build() {
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
121
akka-actor/src/main/java/akka/japi/pf/FSMStopBuilder.java
Normal file
121
akka-actor/src/main/java/akka/japi/pf/FSMStopBuilder.java
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package akka.japi.pf;
|
||||||
|
|
||||||
|
import akka.actor.FSM;
|
||||||
|
import scala.PartialFunction;
|
||||||
|
import scala.runtime.BoxedUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder used to create a partial function for {@link akka.actor.FSM#onTermination}.
|
||||||
|
*
|
||||||
|
* @param <S> the state type
|
||||||
|
* @param <D> the data type
|
||||||
|
*/
|
||||||
|
public class FSMStopBuilder<S, D> {
|
||||||
|
|
||||||
|
private UnitPFBuilder<FSM.StopEvent<S, D>> builder =
|
||||||
|
new UnitPFBuilder<FSM.StopEvent<S, D>>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a case statement that matches on an {@link FSM.Reason}.
|
||||||
|
*
|
||||||
|
* @param reason the reason for the termination
|
||||||
|
* @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 FSMStopBuilder<S, D> stop(final FSM.Reason reason,
|
||||||
|
final FI.UnitApply2<S, D> apply) {
|
||||||
|
builder.match(FSM.StopEvent.class,
|
||||||
|
new FI.TypedPredicate<FSM.StopEvent>() {
|
||||||
|
@Override
|
||||||
|
public boolean defined(FSM.StopEvent e) {
|
||||||
|
return reason.equals(e.reason());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new FI.UnitApply<FSM.StopEvent>() {
|
||||||
|
public void apply(FSM.StopEvent e) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
S s = (S) e.currentState();
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
D d = (D) e.stateData();
|
||||||
|
apply.apply(s, d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a case statement that matches on a reason type.
|
||||||
|
*
|
||||||
|
* @param reasonType the reason type to match on
|
||||||
|
* @param apply an action to apply to the reason, event and state data if there is a match
|
||||||
|
* @param <P> the reason type to match on
|
||||||
|
* @return the builder with the case statement added
|
||||||
|
*/
|
||||||
|
public <P extends FSM.Reason> FSMStopBuilder<S, D> stop(final Class<P> reasonType,
|
||||||
|
final FI.UnitApply3<P, S, D> apply) {
|
||||||
|
return this.stop(reasonType,
|
||||||
|
new FI.TypedPredicate<P>() {
|
||||||
|
@Override
|
||||||
|
public boolean defined(P p) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}, apply);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a case statement that matches on a reason type and a predicate.
|
||||||
|
*
|
||||||
|
* @param reasonType the reason type to match on
|
||||||
|
* @param apply an action to apply to the reason, event and state data if there is a match
|
||||||
|
* @param predicate a predicate that will be evaluated on the reason if the type matches
|
||||||
|
* @param <P> the reason type to match on
|
||||||
|
* @return the builder with the case statement added
|
||||||
|
*/
|
||||||
|
public <P extends FSM.Reason> FSMStopBuilder<S, D> stop(final Class<P> reasonType,
|
||||||
|
final FI.TypedPredicate<P> predicate,
|
||||||
|
final FI.UnitApply3<P, S, D> apply) {
|
||||||
|
builder.match(FSM.StopEvent.class,
|
||||||
|
new FI.TypedPredicate<FSM.StopEvent>() {
|
||||||
|
@Override
|
||||||
|
public boolean defined(FSM.StopEvent e) {
|
||||||
|
if (reasonType.isInstance(e.reason())) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
P p = (P) e.reason();
|
||||||
|
return predicate.defined(p);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new FI.UnitApply<FSM.StopEvent>() {
|
||||||
|
public void apply(FSM.StopEvent e) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
P p = (P) e.reason();
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
S s = (S) e.currentState();
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
D d = (D) e.stateData();
|
||||||
|
apply.apply(p, s, d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a {@link scala.PartialFunction} from this builder.
|
||||||
|
* After this call the builder will be reset.
|
||||||
|
*
|
||||||
|
* @return a PartialFunction for this builder.
|
||||||
|
*/
|
||||||
|
public PartialFunction<FSM.StopEvent<S, D>, BoxedUnit> build() {
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package akka.japi.pf;
|
||||||
|
|
||||||
|
import scala.PartialFunction;
|
||||||
|
import scala.runtime.BoxedUnit;
|
||||||
|
import scala.Tuple2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder used to create a partial function for {@link akka.actor.FSM#onTransition}.
|
||||||
|
*
|
||||||
|
* @param <S> the state type
|
||||||
|
*/
|
||||||
|
public class FSMTransitionHandlerBuilder<S> {
|
||||||
|
|
||||||
|
private UnitPFBuilder<Tuple2<S, S>> builder =
|
||||||
|
new UnitPFBuilder<Tuple2<S, S>>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a case statement that matches on a from state and a to state.
|
||||||
|
*
|
||||||
|
* @param fromState the from state to match on
|
||||||
|
* @param toState the to state to match on
|
||||||
|
* @param apply an action to apply when the states match
|
||||||
|
* @return the builder with the case statement added
|
||||||
|
*/
|
||||||
|
public FSMTransitionHandlerBuilder<S> state(final S fromState,
|
||||||
|
final S toState,
|
||||||
|
final FI.UnitApplyVoid apply) {
|
||||||
|
builder.match(Tuple2.class,
|
||||||
|
new FI.TypedPredicate<Tuple2>() {
|
||||||
|
@Override
|
||||||
|
public boolean defined(Tuple2 t) {
|
||||||
|
return fromState.equals(t._1()) && toState.equals(t._2());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new FI.UnitApply<Tuple2>() {
|
||||||
|
@Override
|
||||||
|
public void apply(Tuple2 t) {
|
||||||
|
apply.apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a {@link scala.PartialFunction} from this builder.
|
||||||
|
* After this call the builder will be reset.
|
||||||
|
*
|
||||||
|
* @return a PartialFunction for this builder.
|
||||||
|
*/
|
||||||
|
public PartialFunction<Tuple2<S, S>, BoxedUnit> build() {
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
79
akka-actor/src/main/java/akka/japi/pf/Match.java
Normal file
79
akka-actor/src/main/java/akka/japi/pf/Match.java
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package akka.japi.pf;
|
||||||
|
|
||||||
|
import scala.MatchError;
|
||||||
|
import scala.PartialFunction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version of {@link scala.PartialFunction} that can be built during
|
||||||
|
* runtime from Java.
|
||||||
|
*
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
public class Match<I, R> extends AbstractMatch<I, R> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience function to create a {@link PFBuilder} with the first
|
||||||
|
* case statement added.
|
||||||
|
*
|
||||||
|
* @param type a type to match the argument against
|
||||||
|
* @param apply an action to apply to the argument if the type matches
|
||||||
|
* @return a builder with the case statement added
|
||||||
|
* @see PFBuilder#match(Class, FI.Apply)
|
||||||
|
*/
|
||||||
|
public static final <F, T, P> PFBuilder<F, T> match(final Class<P> type,
|
||||||
|
final FI.Apply<P, T> apply) {
|
||||||
|
return new PFBuilder<F, T>().match(type, apply);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience function to create a {@link PFBuilder} with the first
|
||||||
|
* case statement added.
|
||||||
|
*
|
||||||
|
* @param type a type to match the argument against
|
||||||
|
* @param predicate a predicate that will be evaluated on the argument if the type matches
|
||||||
|
* @param apply an action to apply to the argument if the type matches
|
||||||
|
* @return a builder with the case statement added
|
||||||
|
* @see PFBuilder#match(Class, FI.TypedPredicate, FI.Apply)
|
||||||
|
*/
|
||||||
|
public static <F, T, P> PFBuilder<F, T> match(final Class<P> type,
|
||||||
|
final FI.TypedPredicate<P> predicate,
|
||||||
|
final FI.Apply<P, T> apply) {
|
||||||
|
return new PFBuilder<F, T>().match(type, predicate, apply);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@link Match} from the builder.
|
||||||
|
*
|
||||||
|
* @param builder a builder representing the partial function
|
||||||
|
* @return a {@link Match} that can be reused
|
||||||
|
*/
|
||||||
|
public static final <F, T> Match<F, T> create(PFBuilder<F, T> builder) {
|
||||||
|
return new Match<F, T>(builder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
Match(PartialFunction<I, R> statements) {
|
||||||
|
super(statements);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience function to make the Java code more readable.
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* Matcher<X, Y> matcher = Matcher.create(...);
|
||||||
|
*
|
||||||
|
* Y someY = matcher.match(obj);
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param i the argument to apply the match to
|
||||||
|
* @return the result of the application
|
||||||
|
* @throws MatchError if there is no match
|
||||||
|
*/
|
||||||
|
public R match(I i) throws MatchError {
|
||||||
|
return statements.apply(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
81
akka-actor/src/main/java/akka/japi/pf/PFBuilder.java
Normal file
81
akka-actor/src/main/java/akka/japi/pf/PFBuilder.java
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package akka.japi.pf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A builder for {@link 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
|
||||||
|
*/
|
||||||
|
public final class PFBuilder<I, R> extends AbstractPFBuilder<I, R> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a PFBuilder.
|
||||||
|
*/
|
||||||
|
public PFBuilder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new case statement to this builder.
|
||||||
|
*
|
||||||
|
* @param type a type to match the argument against
|
||||||
|
* @param apply an action to apply to the argument if the type matches
|
||||||
|
* @return a builder with the case statement added
|
||||||
|
*/
|
||||||
|
public <P> PFBuilder<I, R> match(final Class<P> type, FI.Apply<P, R> apply) {
|
||||||
|
addStatement(new CaseStatement<I, P, R>(
|
||||||
|
new FI.Predicate() {
|
||||||
|
@Override
|
||||||
|
public boolean defined(Object o) {
|
||||||
|
return type.isInstance(o);
|
||||||
|
}
|
||||||
|
}, apply));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new case statement to this builder.
|
||||||
|
*
|
||||||
|
* @param type a type to match the argument against
|
||||||
|
* @param predicate a predicate that will be evaluated on the argument if the type matches
|
||||||
|
* @param apply an action to apply to the argument if the type matches and the predicate returns true
|
||||||
|
* @return a builder with the case statement added
|
||||||
|
*/
|
||||||
|
public <P> PFBuilder<I, R> match(final Class<P> type,
|
||||||
|
final FI.TypedPredicate<P> predicate,
|
||||||
|
final FI.Apply<P, R> apply) {
|
||||||
|
addStatement(new CaseStatement<I, P, R>(
|
||||||
|
new FI.Predicate() {
|
||||||
|
@Override
|
||||||
|
public boolean defined(Object o) {
|
||||||
|
if (!type.isInstance(o))
|
||||||
|
return false;
|
||||||
|
else {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
P p = (P) o;
|
||||||
|
return predicate.defined(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, apply));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new case statement to this builder, that matches any argument.
|
||||||
|
* @param apply an action to apply to the argument
|
||||||
|
* @return a builder with the case statement added
|
||||||
|
*/
|
||||||
|
public PFBuilder<I, R> matchAny(final FI.Apply<Object, R> apply) {
|
||||||
|
addStatement(new CaseStatement<I, Object, R>(
|
||||||
|
new FI.Predicate() {
|
||||||
|
@Override
|
||||||
|
public boolean defined(Object o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}, apply));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
58
akka-actor/src/main/java/akka/japi/pf/ReceiveBuilder.java
Normal file
58
akka-actor/src/main/java/akka/japi/pf/ReceiveBuilder.java
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package akka.japi.pf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for building a partial function for {@link akka.actor.Actor#receive() Actor.receive()}.
|
||||||
|
*
|
||||||
|
* There is both a match on type only, and a match on type and predicate.
|
||||||
|
*
|
||||||
|
* Inside an actor you can use it like this with Java 8 to define your receive method:
|
||||||
|
* <pre>
|
||||||
|
* @Override
|
||||||
|
* public PartialFunction<Object, BoxedUnit> receive() {
|
||||||
|
* return ReceiveBuilder.
|
||||||
|
* match(Double.class, d -> {
|
||||||
|
* sender().tell(d.isNaN() ? 0 : d, self());
|
||||||
|
* }).
|
||||||
|
* match(Integer.class, i -> {
|
||||||
|
* sender().tell(i * 10, self());
|
||||||
|
* }).
|
||||||
|
* match(String.class, s -> s.startsWith("foo"), s -> {
|
||||||
|
* sender().tell(s.toUpperCase(), self());
|
||||||
|
* }).build();
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class ReceiveBuilder {
|
||||||
|
private ReceiveBuilder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a new {@link UnitPFBuilder} with a case statement added.
|
||||||
|
*
|
||||||
|
* @param type a type to match the argument against
|
||||||
|
* @param apply an action to apply to the argument if the type matches
|
||||||
|
* @return a builder with the case statement added
|
||||||
|
*/
|
||||||
|
public static <P> UnitPFBuilder<Object> match(final Class<P> type, FI.UnitApply<P> apply) {
|
||||||
|
return UnitMatch.match(type, apply);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a new {@link UnitPFBuilder} with a case statement added.
|
||||||
|
*
|
||||||
|
* @param type a type to match the argument against
|
||||||
|
* @param predicate a predicate that will be evaluated on the argument if the type matches
|
||||||
|
* @param apply an action to apply to the argument if the type matches and the predicate returns true
|
||||||
|
* @return a builder with the case statement added
|
||||||
|
*/
|
||||||
|
public static <P> UnitPFBuilder<Object> match(final Class<P> type,
|
||||||
|
FI.TypedPredicate<P> predicate,
|
||||||
|
FI.UnitApply<P> apply) {
|
||||||
|
return UnitMatch.match(type, predicate, apply);
|
||||||
|
}
|
||||||
|
}
|
||||||
79
akka-actor/src/main/java/akka/japi/pf/UnitMatch.java
Normal file
79
akka-actor/src/main/java/akka/japi/pf/UnitMatch.java
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package akka.japi.pf;
|
||||||
|
|
||||||
|
import scala.MatchError;
|
||||||
|
import scala.PartialFunction;
|
||||||
|
import scala.runtime.BoxedUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version of {@link scala.PartialFunction} that can be built during
|
||||||
|
* runtime from Java.
|
||||||
|
* This is a specialized version of {@link UnitMatch} to map java
|
||||||
|
* void methods to {@link scala.runtime.BoxedUnit}.
|
||||||
|
*
|
||||||
|
* @param <I> the input type, that this PartialFunction will be applied to
|
||||||
|
*/
|
||||||
|
public class UnitMatch<I> extends AbstractMatch<I, BoxedUnit> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience function to create a {@link UnitPFBuilder} with the first
|
||||||
|
* case statement added.
|
||||||
|
*
|
||||||
|
* @param type a type to match the argument against
|
||||||
|
* @param apply an action to apply to the argument if the type matches
|
||||||
|
* @return a builder with the case statement added
|
||||||
|
* @see UnitPFBuilder#match(Class, FI.UnitApply)
|
||||||
|
*/
|
||||||
|
public static final <F, P> UnitPFBuilder<F> match(final Class<P> type, FI.UnitApply<P> apply) {
|
||||||
|
return new UnitPFBuilder<F>().match(type, apply);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience function to create a {@link UnitPFBuilder} with the first
|
||||||
|
* case statement added.
|
||||||
|
*
|
||||||
|
* @param type a type to match the argument against
|
||||||
|
* @param predicate a predicate that will be evaluated on the argument if the type matches
|
||||||
|
* @param apply an action to apply to the argument if the type and predicate matches
|
||||||
|
* @return a builder with the case statement added
|
||||||
|
* @see UnitPFBuilder#match(Class, FI.TypedPredicate, FI.UnitApply)
|
||||||
|
*/
|
||||||
|
public static <F, P> UnitPFBuilder<F> match(final Class<P> type,
|
||||||
|
final FI.TypedPredicate<P> predicate,
|
||||||
|
final FI.UnitApply<P> apply) {
|
||||||
|
return new UnitPFBuilder<F>().match(type, predicate, apply);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@link UnitMatch} from the builder.
|
||||||
|
*
|
||||||
|
* @param builder a builder representing the partial function
|
||||||
|
* @return a {@link UnitMatch} that can be reused
|
||||||
|
*/
|
||||||
|
public static <F> UnitMatch<F> create(UnitPFBuilder<F> builder) {
|
||||||
|
return new UnitMatch<F>(builder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
private UnitMatch(PartialFunction<I, BoxedUnit> statements) {
|
||||||
|
super(statements);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience function to make the Java code more readable.
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* UnitMatcher<X> matcher = UnitMatcher.create(...);
|
||||||
|
*
|
||||||
|
* matcher.match(obj);
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param i the argument to apply the match to
|
||||||
|
* @throws scala.MatchError if there is no match
|
||||||
|
*/
|
||||||
|
public void match(I i) throws MatchError {
|
||||||
|
statements.apply(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
85
akka-actor/src/main/java/akka/japi/pf/UnitPFBuilder.java
Normal file
85
akka-actor/src/main/java/akka/japi/pf/UnitPFBuilder.java
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package akka.japi.pf;
|
||||||
|
|
||||||
|
import scala.runtime.BoxedUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A builder for {@link scala.PartialFunction}.
|
||||||
|
* This is a specialized version of {@link PFBuilder} to map java
|
||||||
|
* void methods to {@link scala.runtime.BoxedUnit}.
|
||||||
|
*
|
||||||
|
* @param <I> the input type, that this PartialFunction to be applied to
|
||||||
|
*/
|
||||||
|
public final class UnitPFBuilder<I> extends AbstractPFBuilder<I, BoxedUnit> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a UnitPFBuilder.
|
||||||
|
*/
|
||||||
|
public UnitPFBuilder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new case statement to this builder.
|
||||||
|
*
|
||||||
|
* @param type a type to match the argument against
|
||||||
|
* @param apply an action to apply to the argument if the type matches
|
||||||
|
* @return a builder with the case statement added
|
||||||
|
*/
|
||||||
|
public <P> UnitPFBuilder<I> match(final Class<P> type,
|
||||||
|
final FI.UnitApply<P> apply) {
|
||||||
|
addStatement(new UnitCaseStatement<I, P>(
|
||||||
|
new FI.Predicate() {
|
||||||
|
@Override
|
||||||
|
public boolean defined(Object o) {
|
||||||
|
return type.isInstance(o);
|
||||||
|
}
|
||||||
|
}, apply));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new case statement to this builder.
|
||||||
|
*
|
||||||
|
* @param type a type to match the argument against
|
||||||
|
* @param predicate a predicate that will be evaluated on the argument if the type matches
|
||||||
|
* @param apply an action to apply to the argument if the type matches and the predicate returns true
|
||||||
|
* @return a builder with the case statement added
|
||||||
|
*/
|
||||||
|
public <P> UnitPFBuilder<I> match(final Class<P> type,
|
||||||
|
final FI.TypedPredicate<P> predicate,
|
||||||
|
final FI.UnitApply<P> apply) {
|
||||||
|
addStatement(new UnitCaseStatement<I, P>(
|
||||||
|
new FI.Predicate() {
|
||||||
|
@Override
|
||||||
|
public boolean defined(Object o) {
|
||||||
|
if (!type.isInstance(o))
|
||||||
|
return false;
|
||||||
|
else {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
P p = (P) o;
|
||||||
|
return predicate.defined(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, apply));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new case statement to this builder, that matches any argument.
|
||||||
|
* @param apply an action to apply to the argument
|
||||||
|
* @return a builder with the case statement added
|
||||||
|
*/
|
||||||
|
public UnitPFBuilder<I> matchAny(final FI.UnitApply<Object> apply) {
|
||||||
|
addStatement(new UnitCaseStatement<I, Object>(
|
||||||
|
new FI.Predicate() {
|
||||||
|
@Override
|
||||||
|
public boolean defined(Object o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}, apply));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,11 +5,9 @@
|
||||||
package akka.actor
|
package akka.actor
|
||||||
|
|
||||||
import akka.AkkaException
|
import akka.AkkaException
|
||||||
import scala.collection.immutable
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
import scala.reflect.BeanProperty
|
import scala.reflect.BeanProperty
|
||||||
import scala.util.control.NoStackTrace
|
import scala.util.control.NoStackTrace
|
||||||
import java.util.regex.Pattern
|
|
||||||
import akka.event.LoggingAdapter
|
import akka.event.LoggingAdapter
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -566,3 +564,10 @@ trait Actor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Java API
|
||||||
|
*
|
||||||
|
* Abstract base class for Java Actors.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
abstract class AbstractActor extends akka.actor.Actor
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
package akka.actor
|
package akka.actor
|
||||||
|
|
||||||
import language.implicitConversions
|
import language.implicitConversions
|
||||||
import akka.util._
|
|
||||||
import scala.concurrent.duration.Duration
|
import scala.concurrent.duration.Duration
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
import akka.routing.{ Deafen, Listen, Listeners }
|
import akka.routing.{ Deafen, Listen, Listeners }
|
||||||
|
|
@ -162,6 +161,17 @@ object FSM {
|
||||||
copy(stopReason = Some(reason))
|
copy(stopReason = Some(reason))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* All messages sent to the [[akka.actor.FSM]] will be wrapped inside an
|
||||||
|
* `Event`, which allows pattern matching to extract both state and data.
|
||||||
|
*/
|
||||||
|
case class Event[D](event: Any, stateData: D) extends NoSerializationVerificationNeeded
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Case class representing the state of the [[akka.actor.FSM]] whithin the
|
||||||
|
* `onTermination` block.
|
||||||
|
*/
|
||||||
|
case class StopEvent[S, D](reason: Reason, currentState: S, stateData: D) extends NoSerializationVerificationNeeded
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -250,10 +260,15 @@ trait FSM[S, D] extends Actor with Listeners with ActorLogging {
|
||||||
import FSM._
|
import FSM._
|
||||||
|
|
||||||
type State = FSM.State[S, D]
|
type State = FSM.State[S, D]
|
||||||
|
type Event = FSM.Event[D]
|
||||||
|
type StopEvent = FSM.StopEvent[S, D]
|
||||||
type StateFunction = scala.PartialFunction[Event, State]
|
type StateFunction = scala.PartialFunction[Event, State]
|
||||||
type Timeout = Option[FiniteDuration]
|
type Timeout = Option[FiniteDuration]
|
||||||
type TransitionHandler = PartialFunction[(S, S), Unit]
|
type TransitionHandler = PartialFunction[(S, S), Unit]
|
||||||
|
|
||||||
|
val Event: FSM.Event.type = FSM.Event
|
||||||
|
val StopEvent: FSM.StopEvent.type = FSM.StopEvent
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* “import” so that these are visible without an import
|
* “import” so that these are visible without an import
|
||||||
*/
|
*/
|
||||||
|
|
@ -666,18 +681,6 @@ trait FSM[S, D] extends Actor with Listeners with ActorLogging {
|
||||||
case Failure(msg: AnyRef) ⇒ log.error(msg.toString)
|
case Failure(msg: AnyRef) ⇒ log.error(msg.toString)
|
||||||
case _ ⇒
|
case _ ⇒
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* All messages sent to the [[akka.actor.FSM]] will be wrapped inside an
|
|
||||||
* `Event`, which allows pattern matching to extract both state and data.
|
|
||||||
*/
|
|
||||||
case class Event(event: Any, stateData: D) extends NoSerializationVerificationNeeded
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Case class representing the state of the [[akka.actor.FSM]] whithin the
|
|
||||||
* `onTermination` block.
|
|
||||||
*/
|
|
||||||
case class StopEvent(reason: Reason, currentState: S, stateData: D) extends NoSerializationVerificationNeeded
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -750,3 +753,251 @@ trait LoggingFSM[S, D] extends FSM[S, D] { this: Actor ⇒
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Java API
|
||||||
|
*
|
||||||
|
* Finite State Machine actor abstract base class.
|
||||||
|
*/
|
||||||
|
abstract class AbstractFSM[S, D] extends FSM[S, D] {
|
||||||
|
import akka.japi.pf._
|
||||||
|
import akka.japi.pf.FI._
|
||||||
|
import java.util.{ List ⇒ JList }
|
||||||
|
import FSM._
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert a new StateFunction at the end of the processing chain for the
|
||||||
|
* given state.
|
||||||
|
*
|
||||||
|
* @param stateName designator for the state
|
||||||
|
* @param stateFunction partial function describing response to input
|
||||||
|
*/
|
||||||
|
final def when(stateName: S)(stateFunction: StateFunction): Unit =
|
||||||
|
when(stateName, null: FiniteDuration)(stateFunction)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert a new StateFunction at the end of the processing chain for the
|
||||||
|
* given state.
|
||||||
|
*
|
||||||
|
* @param stateName designator for the state
|
||||||
|
* @param stateFunctionBuilder partial function builder describing response to input
|
||||||
|
*/
|
||||||
|
final def when(stateName: S, stateFunctionBuilder: FSMStateFunctionBuilder[S, D]): Unit =
|
||||||
|
when(stateName, null, stateFunctionBuilder)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert a new StateFunction at the end of the processing chain for the
|
||||||
|
* given state. If the stateTimeout parameter is set, entering this state
|
||||||
|
* without a differing explicit timeout setting will trigger a StateTimeout
|
||||||
|
* event; the same is true when using #stay.
|
||||||
|
*
|
||||||
|
* @param stateName designator for the state
|
||||||
|
* @param stateTimeout default state timeout for this state
|
||||||
|
* @param stateFunctionBuilder partial function builder describing response to input
|
||||||
|
*/
|
||||||
|
final def when(stateName: S,
|
||||||
|
stateTimeout: FiniteDuration,
|
||||||
|
stateFunctionBuilder: FSMStateFunctionBuilder[S, D]): Unit =
|
||||||
|
when(stateName, stateTimeout)(stateFunctionBuilder.build())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set initial state. Call this method from the constructor before the [[#initialize]] method.
|
||||||
|
* If different state is needed after a restart this method, followed by [[#initialize]], can
|
||||||
|
* be used in the actor life cycle hooks [[akka.actor.Actor#preStart]] and [[akka.actor.Actor#postRestart]].
|
||||||
|
*
|
||||||
|
* @param stateName initial state designator
|
||||||
|
* @param stateData initial state data
|
||||||
|
*/
|
||||||
|
final def startWith(stateName: S, stateData: D): Unit =
|
||||||
|
startWith(stateName, stateData, null: FiniteDuration)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set initial state. Call this method from the constructor before the [[#initialize]] method.
|
||||||
|
* If different state is needed after a restart this method, followed by [[#initialize]], can
|
||||||
|
* be used in the actor life cycle hooks [[akka.actor.Actor#preStart]] and [[akka.actor.Actor#postRestart]].
|
||||||
|
*
|
||||||
|
* @param stateName initial state designator
|
||||||
|
* @param stateData initial state data
|
||||||
|
* @param timeout state timeout for the initial state, overriding the default timeout for that state
|
||||||
|
*/
|
||||||
|
final def startWith(stateName: S, stateData: D, timeout: FiniteDuration): Unit =
|
||||||
|
startWith(stateName, stateData, Option(timeout))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a handler which is called upon each state transition, i.e. not when
|
||||||
|
* staying in the same state.
|
||||||
|
*
|
||||||
|
* <b>Multiple handlers may be installed, and every one of them will be
|
||||||
|
* called, not only the first one matching.</b>
|
||||||
|
*/
|
||||||
|
final def onTransition(transitionHandlerBuilder: FSMTransitionHandlerBuilder[S]): Unit =
|
||||||
|
onTransition(transitionHandlerBuilder.build().asInstanceOf[TransitionHandler])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set handler which is called upon reception of unhandled messages. Calling
|
||||||
|
* this method again will overwrite the previous contents.
|
||||||
|
*
|
||||||
|
* The current state may be queried using ``stateName``.
|
||||||
|
*/
|
||||||
|
final def whenUnhandled(stateFunctionBuilder: FSMStateFunctionBuilder[S, D]): Unit =
|
||||||
|
whenUnhandled(stateFunctionBuilder.build())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set handler which is called upon termination of this FSM actor. Calling
|
||||||
|
* this method again will overwrite the previous contents.
|
||||||
|
*/
|
||||||
|
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.
|
||||||
|
*
|
||||||
|
* @param eventType the event type to match on
|
||||||
|
* @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 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 on the data type and if any of the event types
|
||||||
|
* in the list match or any of the event instances in the list compares equal.
|
||||||
|
*
|
||||||
|
* @param eventMatches a list of types or instances to match 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 matchEvent[DT <: D](eventMatches: JList[AnyRef], dataType: Class[DT], apply: Apply[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 any type of 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 matchAnyEvent(apply: Apply2[AnyRef, D, State]): FSMStateFunctionBuilder[S, D] =
|
||||||
|
new FSMStateFunctionBuilder[S, D]().anyEvent(apply)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an [[akka.japi.pf.FSMTransitionHandlerBuilder]] with the first case statement set.
|
||||||
|
*
|
||||||
|
* A case statement that matches on a from state and a to state.
|
||||||
|
*
|
||||||
|
* @param fromState the from state to match on
|
||||||
|
* @param toState the to state to match on
|
||||||
|
* @param apply an action to apply when the states match
|
||||||
|
* @return the builder with the case statement added
|
||||||
|
*/
|
||||||
|
final def matchState(fromState: S, toState: S, apply: UnitApplyVoid): FSMTransitionHandlerBuilder[S] =
|
||||||
|
new FSMTransitionHandlerBuilder[S]().state(fromState, toState, apply)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an [[akka.japi.pf.FSMStopBuilder]] with the first case statement set.
|
||||||
|
*
|
||||||
|
* A case statement that matches on an [[FSM.Reason]].
|
||||||
|
*
|
||||||
|
* @param reason the reason for the termination
|
||||||
|
* @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 matchStop(reason: Reason, apply: UnitApply2[S, D]): FSMStopBuilder[S, D] =
|
||||||
|
new FSMStopBuilder[S, D]().stop(reason, apply)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an [[akka.japi.pf.FSMStopBuilder]] with the first case statement set.
|
||||||
|
*
|
||||||
|
* A case statement that matches on a reason type.
|
||||||
|
*
|
||||||
|
* @param reasonType the reason type to match on
|
||||||
|
* @param apply an action to apply to the reason, event and state data if there is a match
|
||||||
|
* @return the builder with the case statement added
|
||||||
|
*/
|
||||||
|
final def matchStop[RT <: Reason](reasonType: Class[RT], apply: UnitApply3[RT, S, D]): FSMStopBuilder[S, D] =
|
||||||
|
new FSMStopBuilder[S, D]().stop(reasonType, apply)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an [[akka.japi.pf.FSMStopBuilder]] with the first case statement set.
|
||||||
|
*
|
||||||
|
* A case statement that matches on a reason type and a predicate.
|
||||||
|
*
|
||||||
|
* @param reasonType the reason type to match on
|
||||||
|
* @param apply an action to apply to the reason, event and state data if there is a match
|
||||||
|
* @param predicate a predicate that will be evaluated on the reason if the type matches
|
||||||
|
* @return the builder with the case statement added
|
||||||
|
*/
|
||||||
|
final def matchStop[RT <: Reason](reasonType: Class[RT], predicate: TypedPredicate[RT], apply: UnitApply3[RT, S, D]): FSMStopBuilder[S, D] =
|
||||||
|
new FSMStopBuilder[S, D]().stop(reasonType, predicate, apply)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a [[akka.japi.pf.UnitPFBuilder]] with the first case statement set.
|
||||||
|
*
|
||||||
|
* @param dataType a type to match the argument against
|
||||||
|
* @param apply an action to apply to the argument if the type matches
|
||||||
|
* @return a builder with the case statement added
|
||||||
|
*/
|
||||||
|
final def matchData[DT <: D](dataType: Class[DT], apply: UnitApply[DT]): UnitPFBuilder[D] =
|
||||||
|
UnitMatch.`match`(dataType, apply)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a [[akka.japi.pf.UnitPFBuilder]] with the first case statement set.
|
||||||
|
*
|
||||||
|
* @param dataType a type to match the argument against
|
||||||
|
* @param predicate a predicate that will be evaluated on the argument if the type matches
|
||||||
|
* @param apply an action to apply to the argument if the type and predicate matches
|
||||||
|
* @return a builder with the case statement added
|
||||||
|
*/
|
||||||
|
final def matchData[DT <: D](dataType: Class[DT], predicate: TypedPredicate[DT], apply: UnitApply[DT]): UnitPFBuilder[D] =
|
||||||
|
UnitMatch.`match`(dataType, predicate, apply)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produce transition to other state. Return this from a state function in
|
||||||
|
* order to effect the transition.
|
||||||
|
*
|
||||||
|
* @param nextStateName state designator for the next state
|
||||||
|
* @return state transition descriptor
|
||||||
|
*/
|
||||||
|
final def goTo(nextStateName: S): State = goto(nextStateName)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default reason if calling `stop()`.
|
||||||
|
*/
|
||||||
|
val Normal: FSM.Reason = FSM.Normal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reason given when someone was calling `system.stop(fsm)` from outside;
|
||||||
|
* also applies to `Stop` supervision directive.
|
||||||
|
*/
|
||||||
|
val Shutdown: FSM.Reason = FSM.Shutdown
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signifies that the [[akka.actor.FSM]] is shutting itself down because of
|
||||||
|
* an error, e.g. if the state to transition into does not exist. You can use
|
||||||
|
* this to match on a Failure in the [[akka.japi.pf.FSMStopBuilder]].
|
||||||
|
*/
|
||||||
|
def Failure: Class[_ <: FSM.Reason] = classOf[FSM.Failure]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A partial function value which does not match anything and can be used to
|
||||||
|
* “reset” `whenUnhandled` and `onTermination` handlers.
|
||||||
|
*
|
||||||
|
* {{{
|
||||||
|
* onTermination(FSM.NullFunction)
|
||||||
|
* }}}
|
||||||
|
*/
|
||||||
|
val NullFunction: PartialFunction[Any, Nothing] = FSM.NullFunction
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Java API
|
||||||
|
*
|
||||||
|
* Finite State Machine actor abstract base class.
|
||||||
|
*/
|
||||||
|
abstract class AbstractLoggingFSM[S, D] extends AbstractFSM[S, D] with LoggingFSM[S, D]
|
||||||
|
|
|
||||||
27
akka-actor/src/main/scala/akka/japi/pf/CaseStatements.scala
Normal file
27
akka-actor/src/main/scala/akka/japi/pf/CaseStatements.scala
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
package akka.japi.pf
|
||||||
|
|
||||||
|
import FI.{ UnitApply, Apply, Predicate }
|
||||||
|
|
||||||
|
private[pf] object CaseStatement {
|
||||||
|
def empty[F, T](): PartialFunction[F, T] = PartialFunction.empty
|
||||||
|
}
|
||||||
|
|
||||||
|
private[pf] class CaseStatement[F, P, T](predicate: Predicate, apply: Apply[P, T])
|
||||||
|
extends PartialFunction[F, T] {
|
||||||
|
|
||||||
|
override def isDefinedAt(o: F) = predicate.defined(o)
|
||||||
|
|
||||||
|
override def apply(o: F) = apply.apply(o.asInstanceOf[P])
|
||||||
|
}
|
||||||
|
|
||||||
|
private[pf] class UnitCaseStatement[F, P](predicate: Predicate, apply: UnitApply[P])
|
||||||
|
extends PartialFunction[F, Unit] {
|
||||||
|
|
||||||
|
override def isDefinedAt(o: F) = predicate.defined(o)
|
||||||
|
|
||||||
|
override def apply(o: F) = apply.apply(o.asInstanceOf[P])
|
||||||
|
}
|
||||||
|
|
@ -13,3 +13,5 @@ Actors
|
||||||
fsm
|
fsm
|
||||||
persistence
|
persistence
|
||||||
testing
|
testing
|
||||||
|
lambda-actors
|
||||||
|
lambda-fsm
|
||||||
|
|
|
||||||
46
akka-docs/rst/java/lambda-actors.rst
Normal file
46
akka-docs/rst/java/lambda-actors.rst
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
.. _lambda-actors-java:
|
||||||
|
|
||||||
|
###################################
|
||||||
|
Actors (Java8 with Lambda Support)
|
||||||
|
###################################
|
||||||
|
|
||||||
|
|
||||||
|
Defining an Actor class
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
Actor classes are implemented by extending the :class:AbstractActor class and implementing
|
||||||
|
the :meth:`receive` method. The :meth:`receive` method should define a series of match
|
||||||
|
statements (which has the type ``PartialFunction<Object, BoxedUnit>``) that defines
|
||||||
|
which messages your Actor can handle, along with the implementation of how the
|
||||||
|
messages should be processed.
|
||||||
|
|
||||||
|
Don't let the type signature scare you. To allow you to easily build up a partial
|
||||||
|
function there is a builder named ``ReceiveBuilder`` that you can use.
|
||||||
|
|
||||||
|
Here is an example:
|
||||||
|
|
||||||
|
.. includecode:: ../../../akka-samples/akka-sample-java8/src/main/java/sample/java8/MyActor.java
|
||||||
|
:include: imports,my-actor
|
||||||
|
|
||||||
|
Please note that the Akka Actor ``receive`` message loop is exhaustive, which
|
||||||
|
is different compared to Erlang and the late Scala Actors. This means that you
|
||||||
|
need to provide a pattern match for all messages that it can accept and if you
|
||||||
|
want to be able to handle unknown messages then you need to have a default case
|
||||||
|
as in the example above. Otherwise an ``akka.actor.UnhandledMessage(message,
|
||||||
|
sender, recipient)`` will be published to the ``ActorSystem``'s ``EventStream``.
|
||||||
|
|
||||||
|
Note further that the return type of the behavior defined above is ``Unit``; if
|
||||||
|
the actor shall reply to the received message then this must be done explicitly
|
||||||
|
as explained below.
|
||||||
|
|
||||||
|
The result of the :meth:`receive` method is a partial function object, which is
|
||||||
|
stored within the actor as its “initial behavior”.
|
||||||
|
|
||||||
|
Here is s slightly bigger example:
|
||||||
|
|
||||||
|
.. includecode:: ../../../akka-samples/akka-sample-java8/src/main/java/sample/java8/SampleActor.java
|
||||||
|
|
||||||
|
TODO:
|
||||||
|
-----
|
||||||
|
|
||||||
|
Lots of doc missing here
|
||||||
399
akka-docs/rst/java/lambda-fsm.rst
Normal file
399
akka-docs/rst/java/lambda-fsm.rst
Normal file
|
|
@ -0,0 +1,399 @@
|
||||||
|
.. _lambda-fsm-java:
|
||||||
|
|
||||||
|
################################
|
||||||
|
FSM (Java 8 with Lambda Support)
|
||||||
|
################################
|
||||||
|
|
||||||
|
|
||||||
|
Overview
|
||||||
|
========
|
||||||
|
|
||||||
|
The FSM (Finite State Machine) is available as an abstract base class that implements
|
||||||
|
an akka Actor and is best described in the `Erlang design principles
|
||||||
|
<http://www.erlang.org/documentation/doc-4.8.2/doc/design_principles/fsm.html>`_
|
||||||
|
|
||||||
|
A FSM can be described 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'.*
|
||||||
|
|
||||||
|
A Simple Example
|
||||||
|
================
|
||||||
|
|
||||||
|
To demonstrate most of the features of the :class:`AbstractFSM` class, consider an
|
||||||
|
actor which shall receive and queue messages while they arrive in a burst and
|
||||||
|
send them on after the burst ended or a flush request is received.
|
||||||
|
|
||||||
|
First, consider all of the below to use these import statements:
|
||||||
|
|
||||||
|
.. includecode:: ../../../akka-samples/akka-sample-java8/src/main/java/sample/java8/buncher/Buncher.java#simple-imports
|
||||||
|
|
||||||
|
The contract of our “Buncher” actor is that it accepts or produces the following messages:
|
||||||
|
|
||||||
|
.. includecode:: ../../../akka-samples/akka-sample-java8/src/main/java/sample/java8/buncher/Events.java
|
||||||
|
:include: simple-events
|
||||||
|
:exclude: boilerplate
|
||||||
|
|
||||||
|
``SetTarget`` is needed for starting it up, setting the destination for the
|
||||||
|
``Batches`` to be passed on; ``Queue`` will add to the internal queue while
|
||||||
|
``Flush`` will mark the end of a burst.
|
||||||
|
|
||||||
|
|
||||||
|
The actor can be in two states: no message queued (aka ``Idle``) or some
|
||||||
|
message queued (aka ``Active``). The states and the state data is defined like this:
|
||||||
|
|
||||||
|
.. includecode:: ../../../akka-samples/akka-sample-java8/src/main/java/sample/java8/buncher/Buncher.java
|
||||||
|
:include: simple-state
|
||||||
|
:exclude: boilerplate
|
||||||
|
|
||||||
|
The actor starts out in the idle state. Once a message arrives it will go to the
|
||||||
|
active state and stay there as long as messages keep arriving and no flush is
|
||||||
|
requested. The internal state data of the actor is made up of the target actor
|
||||||
|
reference to send the batches to and the actual queue of messages.
|
||||||
|
|
||||||
|
Now let’s take a look at the skeleton for our FSM actor:
|
||||||
|
|
||||||
|
.. includecode:: ../../../akka-samples/akka-sample-java8/src/main/java/sample/java8/buncher/Buncher.java
|
||||||
|
:include: simple-fsm
|
||||||
|
:exclude: transition-elided,unhandled-elided,termination-elided
|
||||||
|
|
||||||
|
The basic strategy is to declare the actor, by inheriting the :class:`AbstractFSM` class
|
||||||
|
and specifying the possible states and data values as type parameters. Within
|
||||||
|
the body of the actor a DSL is used for declaring the state machine:
|
||||||
|
|
||||||
|
* :meth:`startWith` defines the initial state and initial data
|
||||||
|
* then there is one :meth:`when(<state>) { ... }` declaration per state to be
|
||||||
|
handled (could potentially be multiple ones, the passed
|
||||||
|
:class:`PartialFunction` will be concatenated using :meth:`orElse`)
|
||||||
|
* finally starting it up using :meth:`initialize`, which performs the
|
||||||
|
transition into the initial state and sets up timers (if required).
|
||||||
|
|
||||||
|
In this case, we start out in the ``Idle`` and ``Uninitialized`` state, where
|
||||||
|
only the ``SetTarget()`` message is handled; ``stay`` prepares to end this
|
||||||
|
event’s processing for not leaving the current state, while the ``using``
|
||||||
|
modifier makes the FSM replace the internal state (which is ``Uninitialized``
|
||||||
|
at this point) with a fresh ``Todo()`` object containing the target actor
|
||||||
|
reference. The ``Active`` state has a state timeout declared, which means that
|
||||||
|
if no message is received for 1 second, a ``FSM.StateTimeout`` message will be
|
||||||
|
generated. This has the same effect as receiving the ``Flush`` command in this
|
||||||
|
case, namely to transition back into the ``Idle`` state and resetting the
|
||||||
|
internal queue to the empty vector. But how do messages get queued? Since this
|
||||||
|
shall work identically in both states, we make use of the fact that any event
|
||||||
|
which is not handled by the ``when()`` block is passed to the
|
||||||
|
``whenUnhandled()`` block:
|
||||||
|
|
||||||
|
.. includecode:: ../../../akka-samples/akka-sample-java8/src/main/java/sample/java8/buncher/Buncher.java#unhandled-elided
|
||||||
|
|
||||||
|
The first case handled here is adding ``Queue()`` requests to the internal
|
||||||
|
queue and going to the ``Active`` state (this does the obvious thing of staying
|
||||||
|
in the ``Active`` state if already there), but only if the FSM data are not
|
||||||
|
``Uninitialized`` when the ``Queue()`` event is received. Otherwise—and in all
|
||||||
|
other non-handled cases—the second case just logs a warning and does not change
|
||||||
|
the internal state.
|
||||||
|
|
||||||
|
The only missing piece is where the ``Batches`` are actually sent to the
|
||||||
|
target, for which we use the ``onTransition`` mechanism: you can declare
|
||||||
|
multiple such blocks and all of them will be tried for matching behavior in
|
||||||
|
case a state transition occurs (i.e. only when the state actually changes).
|
||||||
|
|
||||||
|
.. includecode:: ../../../akka-samples/akka-sample-java8/src/main/java/sample/java8/buncher/Buncher.java#transition-elided
|
||||||
|
|
||||||
|
The transition callback is a partial function which takes as input a pair of
|
||||||
|
states—the current and the next state. During the state change, the old state
|
||||||
|
data is available via ``stateData`` as shown, and the new state data would be
|
||||||
|
available as ``nextStateData``.
|
||||||
|
|
||||||
|
TODO
|
||||||
|
----
|
||||||
|
|
||||||
|
Add the test here
|
||||||
|
|
||||||
|
Reference
|
||||||
|
=========
|
||||||
|
|
||||||
|
The AbstractFSM Class
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
The :class:`AbstractFSM` abstract class is the base class used to implement an FSM. It implements
|
||||||
|
Actor since an Actor is created to drive the FSM.
|
||||||
|
|
||||||
|
.. includecode:: ../../../akka-samples/akka-sample-java8/src/main/java/sample/java8/buncher/Buncher.java
|
||||||
|
:include: simple-fsm
|
||||||
|
:exclude: fsm-body
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The AbstractFSM class defines a ``receive`` method which handles internal messages
|
||||||
|
and passes everything else through to the FSM logic (according to the
|
||||||
|
current state). When overriding the ``receive`` method, keep in mind that
|
||||||
|
e.g. state timeout handling depends on actually passing the messages through
|
||||||
|
the FSM logic.
|
||||||
|
|
||||||
|
The :class:`AbstractFSM` class takes two type parameters:
|
||||||
|
|
||||||
|
#. the supertype of all state names, usually an enum,
|
||||||
|
#. the type of the state data which are tracked by the :class:`AbstractFSM` module
|
||||||
|
itself.
|
||||||
|
|
||||||
|
.. _fsm-philosophy:
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The state data together with the state name describe the internal state of
|
||||||
|
the state machine; if you stick to this scheme and do not add mutable fields
|
||||||
|
to the FSM class you have the advantage of making all changes of the
|
||||||
|
internal state explicit in a few well-known places.
|
||||||
|
|
||||||
|
Defining States
|
||||||
|
---------------
|
||||||
|
|
||||||
|
A state is defined by one or more invocations of the method
|
||||||
|
|
||||||
|
:func:`when(<name>[, stateTimeout = <timeout>])(stateFunction)`.
|
||||||
|
|
||||||
|
The given name must be an object which is type-compatible with the first type
|
||||||
|
parameter given to the :class:`AbstractFSM` class. This object is used as a hash key,
|
||||||
|
so you must ensure that it properly implements :meth:`equals` and
|
||||||
|
:meth:`hashCode`; in particular it must not be mutable. The easiest fit for
|
||||||
|
these requirements are case objects.
|
||||||
|
|
||||||
|
If the :meth:`stateTimeout` parameter is given, then all transitions into this
|
||||||
|
state, including staying, receive this timeout by default. Initiating the
|
||||||
|
transition with an explicit timeout may be used to override this default, see
|
||||||
|
`Initiating Transitions`_ for more information. The state timeout of any state
|
||||||
|
may be changed during action processing with
|
||||||
|
:func:`setStateTimeout(state, duration)`. This enables runtime configuration
|
||||||
|
e.g. via external message.
|
||||||
|
|
||||||
|
The :meth:`stateFunction` argument is a :class:`PartialFunction[Event, State]`,
|
||||||
|
which is conveniently given using state function builder syntax as demonstrated below:
|
||||||
|
|
||||||
|
.. includecode:: ../../../akka-samples/akka-sample-java8/src/main/java/sample/java8/buncher/Buncher.java
|
||||||
|
:include: when-syntax
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
It is required that you define handlers for each of the possible FSM states,
|
||||||
|
otherwise there will be failures when trying to switch to undeclared states.
|
||||||
|
|
||||||
|
It is recommended practice to declare the states as an enum and then verify that there is a
|
||||||
|
``when`` clause for each of the states. If you want to leave the handling of a state
|
||||||
|
“unhandled” (more below), it still needs to be declared like this:
|
||||||
|
|
||||||
|
.. includecode:: ../../../akka-samples/akka-sample-java8/src/main/java/sample/java8/buncher/Buncher.java#NullFunction
|
||||||
|
|
||||||
|
Defining the Initial State
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
Each FSM needs a starting point, which is declared using
|
||||||
|
|
||||||
|
:func:`startWith(state, data[, timeout]);`
|
||||||
|
|
||||||
|
The optionally given timeout argument overrides any specification given for the
|
||||||
|
desired initial state. If you want to cancel a default timeout, use
|
||||||
|
:obj:`Duration.Inf`.
|
||||||
|
|
||||||
|
Unhandled Events
|
||||||
|
----------------
|
||||||
|
|
||||||
|
If a state doesn't handle a received event a warning is logged. If you want to
|
||||||
|
do something else in this case you can specify that with
|
||||||
|
:func:`whenUnhandled(stateFunction)`:
|
||||||
|
|
||||||
|
.. includecode:: ../../../akka-samples/akka-sample-java8/src/main/java/sample/java8/buncher/Buncher.java
|
||||||
|
:include: unhandled-syntax
|
||||||
|
|
||||||
|
Within this handler the state of the FSM may be queried using the
|
||||||
|
:meth:`stateName` method.
|
||||||
|
|
||||||
|
**IMPORTANT**: This handler is not stacked, meaning that each invocation of
|
||||||
|
:func:`whenUnhandled` replaces the previously installed handler.
|
||||||
|
|
||||||
|
Initiating Transitions
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
The result of any :obj:`stateFunction` must be a definition of the next state
|
||||||
|
unless terminating the FSM, which is described in `Termination from Inside`_.
|
||||||
|
The state definition can either be the current state, as described by the
|
||||||
|
:func:`stay` directive, or it is a different state as given by
|
||||||
|
:func:`goto(state)`. The resulting object allows further qualification by way
|
||||||
|
of the modifiers described in the following:
|
||||||
|
|
||||||
|
* :meth:`forMax(duration)`
|
||||||
|
|
||||||
|
This modifier sets a state timeout on the next state. This means that a timer
|
||||||
|
is started which upon expiry sends a :obj:`StateTimeout` message to the FSM.
|
||||||
|
This timer is canceled upon reception of any other message in the meantime;
|
||||||
|
you can rely on the fact that the :obj:`StateTimeout` message will not be
|
||||||
|
processed after an intervening message.
|
||||||
|
|
||||||
|
This modifier can also be used to override any default timeout which is
|
||||||
|
specified for the target state. If you want to cancel the default timeout,
|
||||||
|
use :obj:`Duration.Inf`.
|
||||||
|
|
||||||
|
* :meth:`using(data)`
|
||||||
|
|
||||||
|
This modifier replaces the old state data with the new data given. If you
|
||||||
|
follow the advice :ref:`above <fsm-philosophy>`, this is the only place where
|
||||||
|
internal state data are ever modified.
|
||||||
|
|
||||||
|
* :meth:`replying(msg)`
|
||||||
|
|
||||||
|
This modifier sends a reply to the currently processed message and otherwise
|
||||||
|
does not modify the state transition.
|
||||||
|
|
||||||
|
All modifier can be chained to achieve a nice and concise description:
|
||||||
|
|
||||||
|
.. includecode:: ../../../akka-samples/akka-sample-java8/src/main/java/sample/java8/buncher/Buncher.java
|
||||||
|
:include: modifier-syntax
|
||||||
|
|
||||||
|
The parentheses are not actually needed in all cases, but they visually
|
||||||
|
distinguish between modifiers and their arguments and therefore make the code
|
||||||
|
even more pleasant to read for foreigners.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Please note that the ``return`` statement may not be used in :meth:`when`
|
||||||
|
blocks or similar; this is a Scala restriction. Either refactor your code
|
||||||
|
using ``if () ... else ...`` or move it into a method definition.
|
||||||
|
|
||||||
|
Monitoring Transitions
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
Transitions occur "between states" conceptually, which means after any actions
|
||||||
|
you have put into the event handling block; this is obvious since the next
|
||||||
|
state is only defined by the value returned by the event handling logic. You do
|
||||||
|
not need to worry about the exact order with respect to setting the internal
|
||||||
|
state variable, as everything within the FSM actor is running single-threaded
|
||||||
|
anyway.
|
||||||
|
|
||||||
|
Internal Monitoring
|
||||||
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Up to this point, the FSM DSL has been centered on states and events. The dual
|
||||||
|
view is to describe it as a series of transitions. This is enabled by the
|
||||||
|
method
|
||||||
|
|
||||||
|
:func:`onTransition(handler)`
|
||||||
|
|
||||||
|
which associates actions with a transition instead of with a state and event.
|
||||||
|
The handler is a partial function which takes a pair of states as input; no
|
||||||
|
resulting state is needed as it is not possible to modify the transition in
|
||||||
|
progress.
|
||||||
|
|
||||||
|
.. includecode:: ../../../akka-samples/akka-sample-java8/src/main/java/sample/java8/buncher/Buncher.java
|
||||||
|
:include: transition-syntax
|
||||||
|
|
||||||
|
The convenience extractor :obj:`->` enables decomposition of the pair of states
|
||||||
|
with a clear visual reminder of the transition's direction. As usual in pattern
|
||||||
|
matches, an underscore may be used for irrelevant parts; alternatively you
|
||||||
|
could bind the unconstrained state to a variable, e.g. for logging as shown in
|
||||||
|
the last case.
|
||||||
|
|
||||||
|
The handlers registered with this method are stacked, so you can intersperse
|
||||||
|
:func:`onTransition` blocks with :func:`when` blocks as suits your design. It
|
||||||
|
should be noted, however, that *all handlers will be invoked for each
|
||||||
|
transition*, not only the first matching one. This is designed specifically so
|
||||||
|
you can put all transition handling for a certain aspect into one place without
|
||||||
|
having to worry about earlier declarations shadowing later ones; the actions
|
||||||
|
are still executed in declaration order, though.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This kind of internal monitoring may be used to structure your FSM according
|
||||||
|
to transitions, so that for example the cancellation of a timer upon leaving
|
||||||
|
a certain state cannot be forgot when adding new target states.
|
||||||
|
|
||||||
|
External Monitoring
|
||||||
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
External actors may be registered to be notified of state transitions by
|
||||||
|
sending a message :class:`SubscribeTransitionCallBack(actorRef)`. The named
|
||||||
|
actor will be sent a :class:`CurrentState(self, stateName)` message immediately
|
||||||
|
and will receive :class:`Transition(actorRef, oldState, newState)` messages
|
||||||
|
whenever a new state is reached. External monitors may be unregistered by
|
||||||
|
sending :class:`UnsubscribeTransitionCallBack(actorRef)` to the FSM actor.
|
||||||
|
|
||||||
|
Stopping a listener without unregistering will not remove the listener from the
|
||||||
|
subscription list; use :class:`UnsubscribeTransitionCallback` before stopping
|
||||||
|
the listener.
|
||||||
|
|
||||||
|
Timers
|
||||||
|
------
|
||||||
|
|
||||||
|
Besides state timeouts, FSM manages timers identified by :class:`String` names.
|
||||||
|
You may set a timer using
|
||||||
|
|
||||||
|
:func:`setTimer(name, msg, interval, repeat)`
|
||||||
|
|
||||||
|
where :obj:`msg` is the message object which will be sent after the duration
|
||||||
|
:obj:`interval` has elapsed. If :obj:`repeat` is :obj:`true`, then the timer is
|
||||||
|
scheduled at fixed rate given by the :obj:`interval` parameter. Timers may be
|
||||||
|
canceled using
|
||||||
|
|
||||||
|
:func:`cancelTimer(name)`
|
||||||
|
|
||||||
|
which is guaranteed to work immediately, meaning that the scheduled message
|
||||||
|
will not be processed after this call even if the timer already fired and
|
||||||
|
queued it. The status of any timer may be inquired with
|
||||||
|
|
||||||
|
:func:`isTimerActive(name)`
|
||||||
|
|
||||||
|
These named timers complement state timeouts because they are not affected by
|
||||||
|
intervening reception of other messages.
|
||||||
|
|
||||||
|
Termination from Inside
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
The FSM is stopped by specifying the result state as
|
||||||
|
|
||||||
|
:func:`stop([reason[, data]])`
|
||||||
|
|
||||||
|
The reason must be one of :obj:`Normal` (which is the default), :obj:`Shutdown`
|
||||||
|
or :obj:`Failure(reason)`, and the second argument may be given to change the
|
||||||
|
state data which is available during termination handling.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
It should be noted that :func:`stop` does not abort the actions and stop the
|
||||||
|
FSM immediately. The stop action must be returned from the event handler in
|
||||||
|
the same way as a state transition (but note that the ``return`` statement
|
||||||
|
may not be used within a :meth:`when` block).
|
||||||
|
|
||||||
|
.. includecode:: ../../../akka-samples/akka-sample-java8/src/main/java/sample/java8/buncher/Buncher.java
|
||||||
|
:include: stop-syntax
|
||||||
|
|
||||||
|
You can use :func:`onTermination(handler)` to specify custom code that is
|
||||||
|
executed when the FSM is stopped. The handler is a partial function which takes
|
||||||
|
a :class:`StopEvent(reason, stateName, stateData)` as argument:
|
||||||
|
|
||||||
|
.. includecode:: ../../../akka-samples/akka-sample-java8/src/main/java/sample/java8/buncher/Buncher.java
|
||||||
|
:include: termination-syntax
|
||||||
|
|
||||||
|
As for the :func:`whenUnhandled` case, this handler is not stacked, so each
|
||||||
|
invocation of :func:`onTermination` replaces the previously installed handler.
|
||||||
|
|
||||||
|
Termination from Outside
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
When an :class:`ActorRef` associated to a FSM is stopped using the
|
||||||
|
:meth:`stop()` method, its :meth:`postStop` hook will be executed. The default
|
||||||
|
implementation by the :class:`AbstractFSM` class is to execute the
|
||||||
|
:meth:`onTermination` handler if that is prepared to handle a
|
||||||
|
:obj:`StopEvent(Shutdown, ...)`.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
In case you override :meth:`postStop` and want to have your
|
||||||
|
:meth:`onTermination` handler called, do not forget to call
|
||||||
|
``super.postStop``.
|
||||||
|
|
||||||
|
Testing and Debugging Finite State Machines
|
||||||
|
===========================================
|
||||||
|
|
||||||
|
TODO
|
||||||
|
----
|
||||||
13
akka-samples/akka-sample-java8/LICENSE
Normal file
13
akka-samples/akka-sample-java8/LICENSE
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
Copyright 2014 Typesafe, Inc.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
17
akka-samples/akka-sample-java8/build.sbt
Normal file
17
akka-samples/akka-sample-java8/build.sbt
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
name := "akka-sample-java8"
|
||||||
|
|
||||||
|
version := "0.0.1-SNAPSHOT"
|
||||||
|
|
||||||
|
scalaVersion := "2.10.3"
|
||||||
|
|
||||||
|
compileOrder := CompileOrder.ScalaThenJava
|
||||||
|
|
||||||
|
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")
|
||||||
55
akka-samples/akka-sample-java8/pom.xml
Normal file
55
akka-samples/akka-sample-java8/pom.xml
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
|
||||||
|
<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-java8</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<version>0.0.1-SNAPSHOT</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>
|
||||||
|
|
||||||
1
akka-samples/akka-sample-java8/project/build.properties
Normal file
1
akka-samples/akka-sample-java8/project/build.properties
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
sbt.version=0.13.1
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package sample.java8;
|
||||||
|
|
||||||
|
//#imports
|
||||||
|
import akka.actor.AbstractActor;
|
||||||
|
import akka.event.Logging;
|
||||||
|
import akka.event.LoggingAdapter;
|
||||||
|
import akka.japi.pf.ReceiveBuilder;
|
||||||
|
import scala.PartialFunction;
|
||||||
|
import scala.runtime.BoxedUnit;
|
||||||
|
|
||||||
|
//#imports
|
||||||
|
|
||||||
|
//#my-actor
|
||||||
|
public class MyActor extends AbstractActor {
|
||||||
|
private final LoggingAdapter log = Logging.getLogger(context().system(), this);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PartialFunction<Object, BoxedUnit> receive() {
|
||||||
|
return ReceiveBuilder.
|
||||||
|
match(String.class, s -> s.equals("test"), s -> log.info("received test")).
|
||||||
|
matchAny(o -> log.info("received unknown message")).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#my-actor
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
package sample.java8;
|
||||||
|
|
||||||
|
//#sample-actor
|
||||||
|
import akka.actor.AbstractActor;
|
||||||
|
import akka.japi.pf.ReceiveBuilder;
|
||||||
|
import scala.PartialFunction;
|
||||||
|
import scala.runtime.BoxedUnit;
|
||||||
|
|
||||||
|
public class SampleActor extends AbstractActor {
|
||||||
|
|
||||||
|
private PartialFunction<Object, BoxedUnit> guarded = ReceiveBuilder.
|
||||||
|
match(String.class, s -> s.contains("guard"), s -> {
|
||||||
|
sender().tell("contains(guard): " + s, self());
|
||||||
|
context().unbecome();
|
||||||
|
}).build();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PartialFunction<Object, BoxedUnit> receive() {
|
||||||
|
return ReceiveBuilder.
|
||||||
|
match(Double.class, d -> {
|
||||||
|
sender().tell(d.isNaN() ? 0 : d, self());
|
||||||
|
}).
|
||||||
|
match(Integer.class, i -> {
|
||||||
|
sender().tell(i * 10, self());
|
||||||
|
}).
|
||||||
|
match(String.class, s -> s.startsWith("guard"), s -> {
|
||||||
|
sender().tell("startsWith(guard): " + s.toUpperCase(), self());
|
||||||
|
context().become(guarded, false);
|
||||||
|
}).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#sample-actor
|
||||||
|
|
@ -0,0 +1,155 @@
|
||||||
|
package sample.java8.buncher;
|
||||||
|
|
||||||
|
//#simple-imports
|
||||||
|
import akka.actor.AbstractFSM;
|
||||||
|
import akka.actor.ActorRef;
|
||||||
|
import akka.japi.pf.UnitMatch;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import scala.concurrent.duration.Duration;
|
||||||
|
//#simple-imports
|
||||||
|
|
||||||
|
import static sample.java8.buncher.Buncher.State;
|
||||||
|
import static sample.java8.buncher.Buncher.Data;
|
||||||
|
import static sample.java8.buncher.Buncher.State.*;
|
||||||
|
import static sample.java8.buncher.Buncher.Uninitialized.*;
|
||||||
|
import static sample.java8.buncher.Events.*;
|
||||||
|
|
||||||
|
//#simple-fsm
|
||||||
|
public class Buncher extends AbstractFSM<State, Data> {
|
||||||
|
|
||||||
|
{
|
||||||
|
//#fsm-body
|
||||||
|
startWith(Idle, Uninitialized);
|
||||||
|
|
||||||
|
//#when-syntax
|
||||||
|
when(Idle,
|
||||||
|
matchEvent(SetTarget.class, Uninitialized.class,
|
||||||
|
(setTarget, uninitialized) -> stay().using(new Todo(setTarget.getRef(), new LinkedList<>()))));
|
||||||
|
//#when-syntax
|
||||||
|
|
||||||
|
//#transition-elided
|
||||||
|
onTransition(
|
||||||
|
matchState(Active, Idle, () -> {
|
||||||
|
// reuse this matcher
|
||||||
|
final UnitMatch<Data> m = UnitMatch.create(
|
||||||
|
matchData(Todo.class,
|
||||||
|
todo -> todo.getTarget().tell(new Batch(todo.getQueue()), self())));
|
||||||
|
m.match(stateData());
|
||||||
|
}).
|
||||||
|
state(Idle, Active, () -> {/* Do something here */}));
|
||||||
|
//#transition-elided
|
||||||
|
|
||||||
|
when(Active, Duration.create(1, "second"),
|
||||||
|
matchEvent(Arrays.asList(Flush.class, StateTimeout()), Todo.class,
|
||||||
|
todo -> goTo(Idle).using(todo.copy(new LinkedList<>()))));
|
||||||
|
|
||||||
|
//#unhandled-elided
|
||||||
|
whenUnhandled(
|
||||||
|
matchEvent(Queue.class, Todo.class,
|
||||||
|
(queue, todo) -> goTo(Active).using(todo.addElement(queue.getObj()))).
|
||||||
|
anyEvent((event, state) -> {
|
||||||
|
log().warning("received unhandled request {} in state {}/{}", event, stateName(), state);
|
||||||
|
return stay();
|
||||||
|
}));
|
||||||
|
//#unhandled-elided
|
||||||
|
|
||||||
|
//#termination-elided
|
||||||
|
//#termination-syntax
|
||||||
|
onTermination(
|
||||||
|
matchStop(Normal(),
|
||||||
|
(state, data) -> {/* Do something here */}).
|
||||||
|
stop(Shutdown(),
|
||||||
|
(state, data) -> {/* Do something here */}).
|
||||||
|
stop(Failure(),
|
||||||
|
(reason, state, data) -> {/* Do something here */}));
|
||||||
|
//#termination-syntax
|
||||||
|
//#termination-elided
|
||||||
|
//#fsm-body
|
||||||
|
|
||||||
|
initialize();
|
||||||
|
}
|
||||||
|
//#simple-fsm
|
||||||
|
|
||||||
|
static
|
||||||
|
//#simple-state
|
||||||
|
// states
|
||||||
|
enum State {
|
||||||
|
Idle, Active
|
||||||
|
}
|
||||||
|
|
||||||
|
//#simple-state
|
||||||
|
static
|
||||||
|
//#simple-state
|
||||||
|
// state data
|
||||||
|
interface Data {
|
||||||
|
}
|
||||||
|
|
||||||
|
//#simple-state
|
||||||
|
static
|
||||||
|
//#simple-state
|
||||||
|
enum Uninitialized implements Data {
|
||||||
|
Uninitialized
|
||||||
|
}
|
||||||
|
|
||||||
|
//#simple-state
|
||||||
|
static
|
||||||
|
//#simple-state
|
||||||
|
final class Todo implements Data {
|
||||||
|
private final ActorRef target;
|
||||||
|
private final List<Object> queue;
|
||||||
|
|
||||||
|
public Todo(ActorRef target, List<Object> queue) {
|
||||||
|
this.target = target;
|
||||||
|
this.queue = queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActorRef getTarget() {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Object> getQueue() {
|
||||||
|
return queue;
|
||||||
|
}
|
||||||
|
//#boilerplate
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Todo{" +
|
||||||
|
"target=" + target +
|
||||||
|
", queue=" + queue +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
|
||||||
|
public Todo addElement(Object element) {
|
||||||
|
List<Object> nQueue = new LinkedList<>(queue);
|
||||||
|
nQueue.add(element);
|
||||||
|
return new Todo(this.target, nQueue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Todo copy(List<Object> queue) {
|
||||||
|
return new Todo(this.target, queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Todo copy(ActorRef target) {
|
||||||
|
return new Todo(target, this.queue);
|
||||||
|
}
|
||||||
|
//#boilerplate
|
||||||
|
}
|
||||||
|
//#simple-state
|
||||||
|
//#simple-fsm
|
||||||
|
}
|
||||||
|
//#simple-fsm
|
||||||
|
|
||||||
|
//#NullFunction
|
||||||
|
//#unhandled-syntax
|
||||||
|
//#modifier-syntax
|
||||||
|
//#transition-syntax
|
||||||
|
//#stop-syntax
|
||||||
|
// TODO add code example here
|
||||||
|
//#stop-syntax
|
||||||
|
//#transition-syntax
|
||||||
|
//#modifier-syntax
|
||||||
|
//#unhandled-syntax
|
||||||
|
//#NullFunction
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
package sample.java8.buncher;
|
||||||
|
|
||||||
|
import akka.actor.ActorRef;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Events {
|
||||||
|
|
||||||
|
static
|
||||||
|
//#simple-events
|
||||||
|
public final class SetTarget {
|
||||||
|
private final ActorRef ref;
|
||||||
|
|
||||||
|
public SetTarget(ActorRef ref) {
|
||||||
|
this.ref = ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActorRef getRef() {
|
||||||
|
return ref;
|
||||||
|
}
|
||||||
|
//#boilerplate
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "SetTarget{" +
|
||||||
|
"ref=" + ref +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
//#boilerplate
|
||||||
|
}
|
||||||
|
|
||||||
|
//#simple-events
|
||||||
|
static
|
||||||
|
//#simple-events
|
||||||
|
public final class Queue {
|
||||||
|
private final Object obj;
|
||||||
|
|
||||||
|
public Queue(Object obj) {
|
||||||
|
this.obj = obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getObj() {
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
//#boilerplate
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Queue{" +
|
||||||
|
"obj=" + obj +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
//#boilerplate
|
||||||
|
}
|
||||||
|
|
||||||
|
//#simple-events
|
||||||
|
static
|
||||||
|
//#simple-events
|
||||||
|
public final class Batch {
|
||||||
|
private final List<Object> list;
|
||||||
|
|
||||||
|
public Batch(List<Object> list) {
|
||||||
|
this.list = list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Object> getList() {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
//#boilerplate
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
Batch batch = (Batch) o;
|
||||||
|
|
||||||
|
return list.equals(batch.list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return list.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
final StringBuilder builder = new StringBuilder();
|
||||||
|
builder.append( "Batch{list=");
|
||||||
|
list.stream().forEachOrdered(e -> { builder.append(e); builder.append(","); });
|
||||||
|
int len = builder.length();
|
||||||
|
builder.replace(len, len, "}");
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
//#boilerplate
|
||||||
|
}
|
||||||
|
|
||||||
|
//#simple-events
|
||||||
|
static
|
||||||
|
//#simple-events
|
||||||
|
public enum Flush {
|
||||||
|
Flush
|
||||||
|
}
|
||||||
|
//#simple-events
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
package sample;
|
||||||
|
|
||||||
|
import akka.actor.ActorRef;
|
||||||
|
import akka.actor.ActorSystem;
|
||||||
|
import akka.actor.Props;
|
||||||
|
import akka.testkit.JavaTestKit;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
|
||||||
|
import sample.java8.buncher.*;
|
||||||
|
import static sample.java8.buncher.Events.Batch;
|
||||||
|
import static sample.java8.buncher.Events.Queue;
|
||||||
|
import static sample.java8.buncher.Events.SetTarget;
|
||||||
|
import static sample.java8.buncher.Events.Flush.Flush;
|
||||||
|
|
||||||
|
public class BuncherTest {
|
||||||
|
|
||||||
|
static ActorSystem system;
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void setup() {
|
||||||
|
system = ActorSystem.create("BuncherTest");
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void tearDown() {
|
||||||
|
JavaTestKit.shutdownActorSystem(system);
|
||||||
|
system = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBuncherActor()
|
||||||
|
{
|
||||||
|
new JavaTestKit(system) {{
|
||||||
|
final ActorRef buncher = system.actorOf(Props.create(Buncher.class), "buncher-actor");
|
||||||
|
final ActorRef probe = getRef();
|
||||||
|
|
||||||
|
buncher.tell(new SetTarget(probe), probe);
|
||||||
|
buncher.tell(new Queue(42), probe);
|
||||||
|
buncher.tell(new Queue(43), probe);
|
||||||
|
LinkedList<Object> list1 = new LinkedList<>();
|
||||||
|
list1.add(42);
|
||||||
|
list1.add(43);
|
||||||
|
expectMsgEquals(new Batch(list1));
|
||||||
|
buncher.tell(new Queue(44), probe);
|
||||||
|
buncher.tell(Flush, probe);
|
||||||
|
buncher.tell(new Queue(45), probe);
|
||||||
|
LinkedList<Object> list2 = new LinkedList<>();
|
||||||
|
list2.add(44);
|
||||||
|
expectMsgEquals(new Batch(list2));
|
||||||
|
LinkedList<Object> list3 = new LinkedList<>();
|
||||||
|
list3.add(45);
|
||||||
|
expectMsgEquals(new Batch(list3));
|
||||||
|
system.stop(buncher);
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
package sample;
|
||||||
|
|
||||||
|
import akka.actor.ActorRef;
|
||||||
|
import akka.actor.ActorSystem;
|
||||||
|
import akka.actor.Props;
|
||||||
|
import akka.testkit.JavaTestKit;
|
||||||
|
import sample.java8.SampleActor;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class SampleActorTest {
|
||||||
|
|
||||||
|
static ActorSystem system;
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void setup() {
|
||||||
|
system = ActorSystem.create("SampleActorTest");
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void tearDown() {
|
||||||
|
JavaTestKit.shutdownActorSystem(system);
|
||||||
|
system = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSampleActor()
|
||||||
|
{
|
||||||
|
new JavaTestKit(system) {{
|
||||||
|
final ActorRef subject = system.actorOf(Props.create(SampleActor.class), "sample-actor");
|
||||||
|
final ActorRef probeRef = getRef();
|
||||||
|
|
||||||
|
subject.tell(47.11, probeRef);
|
||||||
|
subject.tell("and no guard in the beginning", probeRef);
|
||||||
|
subject.tell("guard is a good thing", probeRef);
|
||||||
|
subject.tell(47.11, probeRef);
|
||||||
|
subject.tell(4711, probeRef);
|
||||||
|
subject.tell("and no guard in the beginning", probeRef);
|
||||||
|
subject.tell(4711, probeRef);
|
||||||
|
subject.tell("and an unmatched message", probeRef);
|
||||||
|
|
||||||
|
expectMsgEquals(47.11);
|
||||||
|
assertTrue(expectMsgClass(String.class).startsWith("startsWith(guard):"));
|
||||||
|
assertTrue(expectMsgClass(String.class).startsWith("contains(guard):"));
|
||||||
|
expectMsgEquals(47110);
|
||||||
|
expectNoMsg();
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue