+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,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;
}
}