/*
* Copyright 2015 the original author or authors.
*
* 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.
*/
package org.springframework.statemachine.state;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.messaging.Message;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.action.ActionListener;
import org.springframework.statemachine.action.CompositeActionListener;
import org.springframework.statemachine.region.Region;
import org.springframework.statemachine.support.LifecycleObjectSupport;
import org.springframework.statemachine.trigger.Trigger;
/**
* Base implementation of a {@link State}.
*
* @author Janne Valkealahti
*
* @param <S> the type of state
* @param <E> the type of event
*/
public abstract class AbstractState<S, E> extends LifecycleObjectSupport implements State<S, E> {
private static final Log log = LogFactory.getLog(AbstractState.class);
private final S id;
private final PseudoState<S, E> pseudoState;
private final Collection<E> deferred;
private final Collection<? extends Action<S, E>> entryActions;
private final Collection<? extends Action<S, E>> exitActions;
private final Collection<? extends Action<S, E>> stateActions;
private final Collection<Region<S, E>> regions = new ArrayList<Region<S, E>>();
private final StateMachine<S, E> submachine;
private List<Trigger<S, E>> triggers = new ArrayList<Trigger<S, E>>();
private final CompositeStateListener<S, E> stateListener = new CompositeStateListener<S, E>();
private final List<ScheduledFuture<?>> cancellableActions = new ArrayList<>();
private CompositeActionListener<S, E> actionListener;
/**
* Instantiates a new abstract state.
*
* @param id the state identifier
* @param pseudoState the pseudo state
*/
public AbstractState(S id, PseudoState<S, E> pseudoState) {
this(id, null, null, null, pseudoState);
}
/**
* Instantiates a new abstract state.
*
* @param id the state identifier
* @param deferred the deferred
*/
public AbstractState(S id, Collection<E> deferred) {
this(id, deferred, null, null);
}
/**
* Instantiates a new abstract state.
*
* @param id the state identifier
* @param deferred the deferred
* @param entryActions the entry actions
* @param exitActions the exit actions
*/
public AbstractState(S id, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions,
Collection<? extends Action<S, E>> exitActions) {
this(id, deferred, entryActions, exitActions, null);
}
/**
* Instantiates a new abstract state.
*
* @param id the state identifier
* @param deferred the deferred
* @param entryActions the entry actions
* @param exitActions the exit actions
* @param pseudoState the pseudo state
*/
public AbstractState(S id, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions,
Collection<? extends Action<S, E>> exitActions, PseudoState<S, E> pseudoState) {
this(id, deferred, entryActions, exitActions, pseudoState, null, null);
}
/**
* Instantiates a new abstract state.
*
* @param id the state identifier
* @param deferred the deferred
* @param entryActions the entry actions
* @param exitActions the exit actions
* @param pseudoState the pseudo state
* @param submachine the submachine
*/
public AbstractState(S id, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions,
Collection<? extends Action<S, E>> exitActions, PseudoState<S, E> pseudoState, StateMachine<S, E> submachine) {
this(id, deferred, entryActions, exitActions, pseudoState, null, submachine);
}
/**
* Instantiates a new abstract state.
*
* @param id the state identifier
* @param deferred the deferred
* @param entryActions the entry actions
* @param exitActions the exit actions
* @param pseudoState the pseudo state
* @param regions the regions
*/
public AbstractState(S id, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions,
Collection<? extends Action<S, E>> exitActions, PseudoState<S, E> pseudoState, Collection<Region<S, E>> regions) {
this(id, deferred, entryActions, exitActions, pseudoState, regions, null);
}
/**
* Instantiates a new abstract state.
*
* @param id the state identifier
* @param deferred the deferred
* @param entryActions the entry actions
* @param exitActions the exit actions
* @param pseudoState the pseudo state
* @param regions the regions
* @param submachine the submachine
*/
public AbstractState(S id, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions,
Collection<? extends Action<S, E>> exitActions, PseudoState<S, E> pseudoState, Collection<Region<S, E>> regions,
StateMachine<S, E> submachine) {
this(id, deferred, entryActions, exitActions, null, pseudoState, regions, submachine);
}
/**
* Instantiates a new abstract state.
*
* @param id the state identifier
* @param deferred the deferred
* @param entryActions the entry actions
* @param exitActions the exit actions
* @param stateActions the state actions
* @param pseudoState the pseudo state
* @param regions the regions
* @param submachine the submachine
*/
public AbstractState(S id, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions,
Collection<? extends Action<S, E>> exitActions, Collection<? extends Action<S, E>> stateActions,
PseudoState<S, E> pseudoState, Collection<Region<S, E>> regions, StateMachine<S, E> submachine) {
this.id = id;
this.deferred = deferred != null ? deferred : Collections.<E>emptySet();
this.entryActions = entryActions != null ? entryActions : Collections.<Action<S, E>>emptySet();
this.exitActions = exitActions != null ? exitActions : Collections.<Action<S, E>>emptySet();
this.stateActions = stateActions != null ? stateActions : Collections.<Action<S, E>>emptySet();
this.pseudoState = pseudoState;
// use of private ctor should prevent user to
// add regions and a submachine which is not allowed.
if (regions != null) {
this.regions.addAll(regions);
}
this.submachine = submachine;
}
@Override
public boolean sendEvent(Message<E> event) {
return false;
}
@Override
public boolean shouldDefer(Message<E> event) {
return deferred.contains(event.getPayload());
}
@Override
public void exit(StateContext<S, E> context) {
cancelStateActions();
stateListener.onExit(context);
for (Trigger<S, E> trigger : triggers) {
trigger.disarm();
}
}
@Override
public void entry(StateContext<S, E> context) {
stateListener.onEntry(context);
for (Trigger<S, E> trigger : triggers) {
trigger.arm();
}
scheduleStateActions(context);
}
@Override
public S getId() {
return id;
}
@Override
public abstract Collection<S> getIds();
@Override
public abstract Collection<State<S, E>> getStates();
@Override
public PseudoState<S, E> getPseudoState() {
return pseudoState;
}
@Override
public Collection<E> getDeferredEvents() {
return deferred;
}
@Override
public Collection<? extends Action<S, E>> getEntryActions() {
return entryActions;
}
@Override
public Collection<? extends Action<S, E>> getExitActions() {
return exitActions;
}
@Override
public boolean isComposite() {
return !regions.isEmpty();
}
@Override
public boolean isOrthogonal() {
return regions.size() > 1;
}
@Override
public boolean isSimple() {
return !isSubmachineState() && !isComposite();
}
@Override
public boolean isSubmachineState() {
return submachine != null;
}
@Override
public void addStateListener(StateListener<S, E> listener) {
stateListener.register(listener);
}
@Override
public void removeStateListener(StateListener<S, E> listener) {
stateListener.unregister(listener);
}
@Override
public void addActionListener(ActionListener<S, E> listener) {
synchronized (this) {
if (this.actionListener == null) {
this.actionListener = new CompositeActionListener<>();
}
this.actionListener.register(listener);
}
}
@Override
public void removeActionListener(ActionListener<S, E> listener) {
synchronized (this) {
if (this.actionListener != null) {
this.actionListener.unregister(listener);
}
}
}
/**
* Gets the submachine.
*
* @return the submachine or null if not set
*/
public StateMachine<S, E> getSubmachine() {
return submachine;
}
/**
* Gets the regions.
*
* @return the regions or empty collection if no regions
*/
public Collection<Region<S, E>> getRegions() {
return regions;
}
/**
* Sets the triggers.
*
* @param triggers the triggers
*/
public void setTriggers(List<Trigger<S, E>> triggers) {
if (triggers != null) {
this.triggers = triggers;
} else {
this.triggers.clear();
}
}
/**
* Gets the triggers.
*
* @return the triggers
*/
public List<Trigger<S, E>> getTriggers() {
return triggers;
}
/**
* Cancel existing state actions and clear list.
*/
protected void cancelStateActions() {
for (ScheduledFuture<?> future : cancellableActions) {
future.cancel(true);
}
cancellableActions.clear();
}
/**
* Schedule state actions and store futures into list to
* be cancelled.
*
* @param context the context
*/
protected void scheduleStateActions(StateContext<S, E> context) {
for (Action<S, E> action : stateActions) {
ScheduledFuture<?> future = scheduleAction(action, context);
if (future != null) {
cancellableActions.add(future);
}
}
}
/**
* Execute action and notify action listener if set.
*
* @param action the action
* @param context the context
*/
protected void executeAction(Action<S, E> action, StateContext<S, E> context) {
long now = System.currentTimeMillis();
action.execute(context);
if (this.actionListener != null) {
try {
this.actionListener.onExecute(context.getStateMachine(), action, System.currentTimeMillis() - now);
} catch (Exception e) {
log.warn("Error with actionListener", e);
}
}
}
/**
* Schedule action and return future which can be used to cancel it.
*
* @param action the action
* @param context the context
* @return the scheduled future
*/
protected ScheduledFuture<?> scheduleAction(final Action<S, E> action, final StateContext<S, E> context) {
TaskScheduler taskScheduler = getTaskScheduler();
if (taskScheduler == null) {
log.error("Unable to schedule action as taskSchedule is not set, action=[" + action + "]");
return null;
}
ScheduledFuture<?> future = taskScheduler.schedule(new Runnable() {
@Override
public void run() {
executeAction(action, context);
}
}, new Date());
return future;
}
@Override
public String toString() {
return "AbstractState [id=" + id + ", pseudoState=" + pseudoState + ", deferred=" + deferred + ", entryActions="
+ entryActions + ", exitActions=" + exitActions + ", stateActions=" + stateActions + ", regions="
+ regions + ", submachine=" + submachine + "]";
}
}