/*
* Quasar: lightweight threads and actors for the JVM.
* Copyright (c) 2013-2015, Parallel Universe Software Co. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v1.0 as published by
* the Eclipse Foundation
*
* or (per the licensee's choosing)
*
* under the terms of the GNU Lesser General Public License version 3.0
* as published by the Free Software Foundation.
*/
package co.paralleluniverse.actors.behaviors;
import co.paralleluniverse.actors.Actor;
import co.paralleluniverse.actors.MailboxConfig;
import co.paralleluniverse.actors.MessageProcessor;
import co.paralleluniverse.actors.SelectiveReceiveHelper;
import co.paralleluniverse.fibers.SuspendExecution;
import co.paralleluniverse.strands.Strand;
import co.paralleluniverse.strands.SuspendableCallable;
import co.paralleluniverse.strands.Timeout;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A {@link BehaviorActor behavior} implementing a <i>finite-state-machine<i/>.
* The {@code FiniteStateMachineActor}'s behavior is implemented by overriding the {@link #initialState()} method to return an initial *state*
* which then runs as the actor's body until it returns a next state, and so on until a state returns {@link #TERMINATE TERMINATE},
* in which case the actor terminates.
*
* Initialization and termination behavior can be implemented by either 1) subclassing this class and overriding some or all of:
* {@link #init() init}, {@link #terminate(java.lang.Throwable) terminate},
* or 2) providing an instance of {@link Initializer} which implements these methods to the constructor.
*
* @author pron
*/
public class FiniteStateMachineActor extends BehaviorActor {
protected static Object NULL_RETURN_VALUE = new Object();
private static final Logger LOG = LoggerFactory.getLogger(FiniteStateMachineActor.class);
/**
* The termination state for the FSM
*/
public static final SuspendableCallable<SuspendableCallable> TERMINATE = new SuspendableCallable<SuspendableCallable>() {
@Override
public SuspendableCallable run() throws SuspendExecution, InterruptedException {
throw new AssertionError();
}
};
private SuspendableCallable<SuspendableCallable> state;
private final SelectiveReceiveHelper<Object> helper = new SelectiveReceiveHelper<>(this);
/**
* Creates a new FSM actor
*
* @param name the actor name (may be {@code null}).
* @param initializer an optional delegate object that will be run upon actor initialization and termination. May be {@code null}.
* @param strand this actor's strand.
* @param mailboxConfig this actor's mailbox settings.
*/
@SuppressWarnings("OverridableMethodCallInConstructor")
public FiniteStateMachineActor(String name, Initializer initializer, Strand strand, MailboxConfig mailboxConfig) {
super(name, initializer, strand, mailboxConfig);
state = initialState();
if (state == null)
throw new NullPointerException();
}
/**
* Creates a new FSM actor
*
* @param name the actor name (may be {@code null}).
* @param initializer an optional delegate object that will be run upon actor initialization and termination. May be {@code null}.
* @param strand this actor's strand.
* @param mailboxConfig this actor's mailbox settings.
* @param initialState the initial state; will be used instead of calling {@link #initialState() initialState()}.
*/
public FiniteStateMachineActor(String name, Initializer initializer, Strand strand, MailboxConfig mailboxConfig, SuspendableCallable<SuspendableCallable> initialState) {
super(name, initializer, strand, mailboxConfig);
state = initialState;
if (state == null)
throw new NullPointerException();
}
//<editor-fold defaultstate="collapsed" desc="Behavior boilerplate">
/////////// Behavior boilerplate ///////////////////////////////////
public static FiniteStateMachineActor currentFiniteStateMachineActor() {
return (FiniteStateMachineActor) Actor.<Object, Void>currentActor();
}
@Override
public Logger log() {
return LOG;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="Constructors">
/////////// Constructors ///////////////////////////////////
/**
* Creates a new FSM actor
*
* @param name the actor name (may be {@code null}).
* @param initializer an optional delegate object that will be run upon actor initialization and termination. May be {@code null}.
* @param mailboxConfig this actor's mailbox settings.
*/
public FiniteStateMachineActor(String name, Initializer initializer, MailboxConfig mailboxConfig) {
this(name, initializer, null, mailboxConfig);
}
/**
* Creates a new FSM actor
*
* @param name the actor name (may be {@code null}).
* @param initializer an optional delegate object that will be run upon actor initialization and termination. May be {@code null}.
*/
public FiniteStateMachineActor(String name, Initializer initializer) {
this(name, initializer, null, null);
}
/**
* Creates a new FSM actor
*
* @param initializer an optional delegate object that will be run upon actor initialization and termination. May be {@code null}.
* @param mailboxConfig this actor's mailbox settings.
*/
public FiniteStateMachineActor(Initializer initializer, MailboxConfig mailboxConfig) {
this(null, initializer, null, mailboxConfig);
}
/**
* Creates a new FSM actor
*
* @param initializer an optional delegate object that will be run upon actor initialization and termination. May be {@code null}.
*/
public FiniteStateMachineActor(Initializer initializer) {
this(null, initializer, null, null);
}
/**
* Creates a new FSM actor
*
* @param name the actor name (may be {@code null}).
* @param mailboxConfig this actor's mailbox settings.
*/
public FiniteStateMachineActor(String name, MailboxConfig mailboxConfig) {
this(name, null, null, mailboxConfig);
}
/**
* Creates a new FSM actor
*
* @param name the actor name (may be {@code null}).
*/
public FiniteStateMachineActor(String name) {
this(name, null, null, null);
}
/**
* Creates a new FSM actor
*
* @param mailboxConfig this actor's mailbox settings.
*/
public FiniteStateMachineActor(MailboxConfig mailboxConfig) {
this(null, null, null, mailboxConfig);
}
/**
* Creates a new FSM actor
*/
public FiniteStateMachineActor() {
this(null, null, null, null);
}
//</editor-fold>
/**
* Returns this finite-state-machine actor's initial state; the default implementation returns {@link #TERMINATE TERMINATE}.
* @return this finite-state-machine actor's initial state
*/
protected SuspendableCallable<SuspendableCallable> initialState() {
return TERMINATE;
}
@Override
protected final void behavior() throws InterruptedException, SuspendExecution {
while (isRunning()) {
if (state == null)
throw new NullPointerException();
if (state == TERMINATE)
break;
checkCodeSwap();
state = state.run();
}
}
/**
* Performs a selective receive. This method blocks until a message that is {@link MessageProcessor#process(java.lang.Object) selected} by
* the given {@link MessageProcessor} is available in the mailbox, and returns the value returned by {@link MessageProcessor#process(java.lang.Object) MessageProcessor.process}.
* <p/>
* Messages that are not selected, are temporarily skipped. They will remain in the mailbox until another call to receive (selective or
* non-selective) retrieves them.
*
* @param proc performs the selection.
* @return The non-null value returned by {@link MessageProcessor#process(java.lang.Object) MessageProcessor.process}
* @throws InterruptedException
*/
public final SuspendableCallable<SuspendableCallable> receive(MessageProcessor<Object, SuspendableCallable<SuspendableCallable>> proc) throws SuspendExecution, InterruptedException {
return helper.receive(proc);
}
/**
* Performs a selective receive. This method blocks (but for no longer than the given timeout) until a message that is
* {@link MessageProcessor#process(java.lang.Object) selected} by the given {@link MessageProcessor} is available in the mailbox,
* and returns the value returned by {@link MessageProcessor#process(java.lang.Object) MessageProcessor.process}.
* If the given timeout expires, this method returns {@code null}.
* <p/>
* Messages that are not selected, are temporarily skipped. They will remain in the mailbox until another call to receive (selective or
* non-selective) retrieves them.
*
* @param timeout the duration to wait for a matching message to arrive.
* @param unit timeout's time unit.
* @param proc performs the selection.
* @return The non-null value returned by {@link MessageProcessor#process(java.lang.Object) MessageProcessor.process}, or {@code null} if the timeout expired.
* @throws InterruptedException
*/
public final SuspendableCallable<SuspendableCallable> receive(long timeout, TimeUnit unit, MessageProcessor<Object, SuspendableCallable<SuspendableCallable>> proc) throws TimeoutException, SuspendExecution, InterruptedException {
return helper.receive(timeout, unit, proc);
}
/**
* Performs a selective receive. This method blocks (but for no longer than the given timeout) until a message that is
* {@link MessageProcessor#process(java.lang.Object) selected} by the given {@link MessageProcessor} is available in the mailbox,
* and returns the value returned by {@link MessageProcessor#process(java.lang.Object) MessageProcessor.process}.
* If the given timeout expires, this method returns {@code null}.
* <p/>
* Messages that are not selected, are temporarily skipped. They will remain in the mailbox until another call to receive (selective or
* non-selective) retrieves them.
*
* @param <T> The type of the returned value
* @param timeout the method will not block for longer than the amount remaining in the {@link Timeout}
* @param proc performs the selection.
* @return The non-null value returned by {@link MessageProcessor#process(java.lang.Object) MessageProcessor.process}, or {@code null} if the timeout expired.
* @throws InterruptedException
*/
public final SuspendableCallable<SuspendableCallable> receive(Timeout timeout, MessageProcessor<Object, SuspendableCallable<SuspendableCallable>> proc) throws TimeoutException, SuspendExecution, InterruptedException {
return helper.receive(timeout, proc);
}
/**
* Tries to perform a selective receive. If a message {@link MessageProcessor#process(java.lang.Object) selected} by
* the given {@link MessageProcessor} is immediately available in the mailbox, returns the value returned by {@link MessageProcessor#process(java.lang.Object) MessageProcessor.process}.
* This method never blocks.
* <p/>
* Messages that are not selected, are temporarily skipped. They will remain in the mailbox until another call to receive (selective or
* non-selective) retrieves them.
*
* @param proc performs the selection.
* @return The non-null value returned by {@link MessageProcessor#process(java.lang.Object) MessageProcessor.process}, or {@code null} if no message was slected.
*/
public final SuspendableCallable<SuspendableCallable> tryReceive(MessageProcessor<Object, SuspendableCallable<SuspendableCallable>> proc) {
return helper.tryReceive(proc);
}
/**
* Performs a selective receive based on type. This method blocks until a message of the given type is available in the mailbox,
* and returns it.
* <p/>
* Messages that are not selected, are temporarily skipped. They will remain in the mailbox until another call to receive (selective or
* non-selective) retrieves them.
*
* @param type the type of the messages to select
* @return The next message of the wanted type.
* @throws InterruptedException
*/
public final <M> M receive(final Class<M> type) throws SuspendExecution, InterruptedException {
return helper.receive(SelectiveReceiveHelper.ofType(type));
}
/**
* Performs a selective receive based on type. This method blocks (but for no longer than the given timeout) until a message of the given type
* is available in the mailbox, and returns it. If the given timeout expires, this method returns {@code null}.
* <p/>
* Messages that are not selected, are temporarily skipped. They will remain in the mailbox until another call to receive (selective or
* non-selective) retrieves them.
*
* @param timeout the duration to wait for a matching message to arrive.
* @param unit timeout's time unit.
* @param type the type of the messages to select
* @return The next message of the wanted type, or {@code null} if the timeout expires.
* @throws SuspendExecution
* @throws InterruptedException
*/
public final <M> M receive(long timeout, TimeUnit unit, final Class<M> type) throws SuspendExecution, InterruptedException, TimeoutException {
return helper.receive(timeout, unit, SelectiveReceiveHelper.ofType(type));
}
/**
* Performs a selective receive based on type. This method blocks (but for no longer than the given timeout) until a message of the given type
* is available in the mailbox, and returns it. If the given timeout expires, this method returns {@code null}.
* <p/>
* Messages that are not selected, are temporarily skipped. They will remain in the mailbox until another call to receive (selective or
* non-selective) retrieves them.
*
* @param timeout the method will not block for longer than the amount remaining in the {@link Timeout}
* @param type the type of the messages to select
* @return The next message of the wanted type, or {@code null} if the timeout expires.
* @throws SuspendExecution
* @throws InterruptedException
*/
public final <M> M receive(Timeout timeout, final Class<M> type) throws SuspendExecution, InterruptedException, TimeoutException {
return helper.receive(timeout, SelectiveReceiveHelper.ofType(type));
}
/**
* Tries to performs a selective receive based on type. If a message of the given type is immediately found in the mailbox, it is returned.
* Otherwise this method returns {@code null}.
* This method never blocks.
* <p/>
* Messages that are not selected, are temporarily skipped. They will remain in the mailbox until another call to receive (selective or
* non-selective) retrieves them.
*
* @param type the type of the messages to select
* @return The next message of the wanted type if immediately found; {@code null} otherwise.
*/
public final <M> M tryReceive(final Class<M> type) {
return helper.tryReceive(SelectiveReceiveHelper.ofType(type));
}
@Override
protected Object readResolve() throws java.io.ObjectStreamException {
Object x = super.readResolve();
assert x == this;
helper.setActor(this);
return this;
}
}