+act #3770 Added Java 8 friendly APIs for Actor and FSM

This commit is contained in:
Björn Antonsson 2013-12-11 14:53:41 +01:00
parent 301b735516
commit e5bcf8bfc9
28 changed files with 2204 additions and 15 deletions

View file

@ -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")));
}
}

View 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;
}
}

View 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);
}
}

View 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);
}
}

View file

@ -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();
}
}

View 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();
}
}

View file

@ -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();
}
}

View 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&lt;X, Y&gt; 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);
}
}

View 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;
}
}

View 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);
}
}

View 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&lt;X&gt; 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);
}
}

View 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;
}
}

View file

@ -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

View file

@ -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]

View 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])
}

View file

@ -13,3 +13,5 @@ Actors
fsm fsm
persistence persistence
testing testing
lambda-actors
lambda-fsm

View 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

View 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 lets 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
events 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
----

View 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.

View 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")

View 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>

View file

@ -0,0 +1 @@
sbt.version=0.13.1

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -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);
}};
}
}

View file

@ -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();
}};
}
}