/*
* 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.test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.messaging.Message;
import org.springframework.statemachine.StateMachine;
/**
* A builder for {@link StateMachineTestPlan}.
*
* @author Janne Valkealahti
*
* @param <S> the type of state
* @param <E> the type of event
*/
public class StateMachineTestPlanBuilder<S, E> {
private Map<Object, StateMachine<S, E>> stateMachines = new HashMap<Object, StateMachine<S, E>>();
private final List<StateMachineTestPlanStep<S, E>> steps = new ArrayList<StateMachineTestPlanStep<S, E>>();
private Integer defaultAwaitTime;
/**
* Gets a new instance of this builder.
*
* @param <S> the type of state
* @param <E> the type of event
* @return the state machine test plan builder
*/
public static <S, E> StateMachineTestPlanBuilder<S, E> builder() {
return new StateMachineTestPlanBuilder<S, E>();
}
/**
* Associate a state machine with this builder.
*
* @param stateMachine the state machine
* @return the state machine test plan builder
*/
public StateMachineTestPlanBuilder<S, E> stateMachine(StateMachine<S, E> stateMachine) {
return stateMachine(stateMachine, stateMachine);
}
/**
* Associate a state machine with this builder.
*
* @param stateMachine the state machine
* @param machineId the machine id to use for sending
* @return the state machine test plan builder
*/
public StateMachineTestPlanBuilder<S, E> stateMachine(StateMachine<S, E> stateMachine, Object machineId) {
this.stateMachines.put(machineId, stateMachine);
return this;
}
/**
* Sets default await time. This is in seconds how long a latch
* will be waited for listening various callbacks.
*
* @param seconds the default await time in seconds
* @return the state machine test plan builder
*/
public StateMachineTestPlanBuilder<S, E> defaultAwaitTime(int seconds) {
if (seconds < 0) {
throw new IllegalArgumentException("Default await time cannot be negative, was " + seconds);
}
this.defaultAwaitTime = seconds;
return this;
}
/**
* Gets a new step builder.
*
* @return the state machine test plan step builder
*/
public StateMachineTestPlanStepBuilder step() {
return new StateMachineTestPlanStepBuilder();
}
/**
* Builds the state machine test plan.
*
* @return the state machine test plan
*/
public StateMachineTestPlan<S, E> build() {
return new StateMachineTestPlan<S, E>(stateMachines, steps, defaultAwaitTime);
}
/**
* Builder for individual plan steps.
*/
public class StateMachineTestPlanStepBuilder {
final List<E> sendEvent = new ArrayList<E>();
final List<Message<E>> sendMessage = new ArrayList<Message<E>>();
Object sendEventMachineId;
boolean sendEventToAll = false;
boolean sendEventParallel = false;
final Collection<S> expectStates = new ArrayList<S>();
final Collection<S> expectStatesEntrered = new ArrayList<S>();
final Collection<S> expectStatesExited = new ArrayList<S>();
Integer expectStateChanged;
Integer expectStateEntered;
Integer expectStateExited;
Integer expectEventNotAccepted;
Integer expectTransition;
Integer expectTransitionStarted;
Integer expectTransitionEnded;
Integer expectStateMachineStarted;
Integer expectStateMachineStopped;
Integer expectExtendedStateChanged;
final Collection<Object> expectVariableKeys = new ArrayList<Object>();
final Map<Object, Object> expectVariables = new HashMap<Object, Object>();
/**
* Expect a state {@code S}.
*
* @param state the state
* @return the state machine test plan step builder
*/
public StateMachineTestPlanStepBuilder expectState(S state) {
this.expectStates.add(state);
return this;
}
/**
* Expect a states {@code S}.
*
* @param states the states
* @return the state machine test plan step builder
*/
@SuppressWarnings("unchecked")
public StateMachineTestPlanStepBuilder expectStates(S... states) {
this.expectStates.addAll(Arrays.asList(states));
return this;
}
/**
* Send an event {@code E}. In case multiple state machines
* exists, a random one will be chosen to send this event.
* Multiple events can be defined which are then send in
* defined order.
*
* @param event the event
* @return the state machine test plan step builder
*/
public StateMachineTestPlanStepBuilder sendEvent(E event) {
return sendEvent(event, false);
}
/**
* Send an event {@code E}. If {@code sendToAll} is set to {@code TRUE} event
* will be send to all existing machines. Multiple events can be defined
* which are then send in defined order.
*
* @param event the event
* @param sendToAll send to all machines
* @return the state machine test plan step builder
*/
public StateMachineTestPlanStepBuilder sendEvent(E event, boolean sendToAll) {
sendEvent(event, sendToAll, false);
return this;
}
/**
* Send an event {@code E}. If {@code sendToAll} is set to {@code TRUE} event
* will be send to all existing machines. If {@code sendPalallel} is set to
* {@code TRUE} event to all machines will be send by parallel threads.
* Multiple events can be defined which are then send in defined order.
*
* @param event the event
* @param sendToAll send to all machines
* @param sendParallel send event parallel
* @return the state machine test plan step builder
*/
public StateMachineTestPlanStepBuilder sendEvent(E event, boolean sendToAll, boolean sendParallel) {
this.sendEvent.add(event);
this.sendEventMachineId = null;
this.sendEventToAll = sendToAll;
this.sendEventParallel = sendParallel;
return this;
}
/**
* Send an event {@code E} into a state machine identified
* by {@code machineId}. Multiple events can be defined
* which are then send in defined order.
*
* @param event the event
* @param machineId the machine identifier for sending event
* @return the state machine test plan step builder
*/
public StateMachineTestPlanStepBuilder sendEvent(E event, Object machineId) {
this.sendEvent.add(event);
this.sendEventMachineId = machineId;
return this;
}
/**
* Send a message {@code Message<E>}. In case multiple state machines
* exists, a random one will be chosen to send this event. Multiple
* events can be defined which are then send in defined order.
*
* @param event the event
* @return the state machine test plan step builder
*/
public StateMachineTestPlanStepBuilder sendEvent(Message<E> event) {
return sendEvent(event, false);
}
/**
* Send a message {@code Message<E>}. If {@code sendToAll} is set to {@code TRUE} event
* will be send to all existing machines.Multiple events can be defined which are
* then send in defined order.
*
* @param event the event
* @param sendToAll send to all machines
* @return the state machine test plan step builder
*/
public StateMachineTestPlanStepBuilder sendEvent(Message<E> event, boolean sendToAll) {
this.sendMessage.add(event);
this.sendEventMachineId = null;
this.sendEventToAll = sendToAll;
return this;
}
/**
* Send a message {@code Message<E>} into a state machine identified
* by {@code machineId}. Multiple events can be defined which are then
* send in defined order.
*
* @param event the event
* @param machineId the machine identifier for sending event
* @return the state machine test plan step builder
*/
public StateMachineTestPlanStepBuilder sendEvent(Message<E> event, Object machineId) {
this.sendMessage.add(event);
this.sendEventMachineId = machineId;
return this;
}
/**
* Expect variable to exist in extended state variables.
*
* @param key the key
* @return the state machine test plan step builder
*/
public StateMachineTestPlanStepBuilder expectVariable(Object key) {
this.expectVariableKeys.add(key);
return this;
}
/**
* Expect variable to exist in extended state variables and match
* with the value.
*
* @param key the key
* @param value the value
* @return the state machine test plan step builder
*/
public StateMachineTestPlanStepBuilder expectVariable(Object key, Object value) {
this.expectVariables.put(key, value);
return this;
}
/**
* Expect state changed happening {@code count} times.
*
* @param count the count
* @return the state machine test plan step builder
*/
public StateMachineTestPlanStepBuilder expectStateChanged(int count) {
if (count < 0) {
throw new IllegalArgumentException("Expected count cannot be negative, was " + count);
}
this.expectStateChanged = count;
return this;
}
/**
* Expect states entered in order given.
*
* @param states the states entered
* @return the state machine test plan step builder
*/
@SuppressWarnings("unchecked")
public StateMachineTestPlanStepBuilder expectStateEntered(S... states) {
this.expectStatesEntrered.addAll(Arrays.asList(states));
return this;
}
/**
* Expect states exited in order given.
*
* @param states the states exited
* @return the state machine test plan step builder
*/
@SuppressWarnings("unchecked")
public StateMachineTestPlanStepBuilder expectStateExited(S... states) {
this.expectStatesExited.addAll(Arrays.asList(states));
return this;
}
/**
* Expect state enter happening {@code count} times.
*
* @param count the count
* @return the state machine test plan step builder
*/
public StateMachineTestPlanStepBuilder expectStateEntered(int count) {
if (count < 0) {
throw new IllegalArgumentException("Expected count cannot be negative, was " + count);
}
this.expectStateEntered = count;
return this;
}
/**
* Expect state exit happening {@code count} times.
*
* @param count the count
* @return the state machine test plan step builder
*/
public StateMachineTestPlanStepBuilder expectStateExited(int count) {
if (count < 0) {
throw new IllegalArgumentException("Expected count cannot be negative, was " + count);
}
this.expectStateExited = count;
return this;
}
/**
* Expect event not accepter happening {@code count} times.
*
* @param count the count
* @return the state machine test plan step builder
*/
public StateMachineTestPlanStepBuilder expectEventNotAccepted(int count) {
if (count < 0) {
throw new IllegalArgumentException("Expected count cannot be negative, was " + count);
}
this.expectEventNotAccepted = count;
return this;
}
/**
* Expect transition happening {@code count} times.
*
* @param count the count
* @return the state machine test plan step builder
*/
public StateMachineTestPlanStepBuilder expectTransition(int count) {
if (count < 0) {
throw new IllegalArgumentException("Expected count cannot be negative, was " + count);
}
this.expectTransition = count;
return this;
}
/**
* Expect transition start happening {@code count} times.
*
* @param count the count
* @return the state machine test plan step builder
*/
public StateMachineTestPlanStepBuilder expectTransitionStarted(int count) {
if (count < 0) {
throw new IllegalArgumentException("Expected count cannot be negative, was " + count);
}
this.expectTransitionStarted = count;
return this;
}
/**
* Expect transition end happening {@code count} times.
*
* @param count the count
* @return the state machine test plan step builder
*/
public StateMachineTestPlanStepBuilder expectTransitionEnded(int count) {
if (count < 0) {
throw new IllegalArgumentException("Expected count cannot be negative, was " + count);
}
this.expectTransitionEnded = count;
return this;
}
/**
* Expect state machine start happening {@code count} times.
*
* @param count the count
* @return the state machine test plan step builder
*/
public StateMachineTestPlanStepBuilder expectStateMachineStarted(int count) {
if (count < 0) {
throw new IllegalArgumentException("Expected count cannot be negative, was " + count);
}
this.expectStateMachineStarted = count;
return this;
}
/**
* Expect state machine stop happening {@code count} times.
*
* @param count the count
* @return the state machine test plan step builder
*/
public StateMachineTestPlanStepBuilder expectStateMachineStopped(int count) {
if (count < 0) {
throw new IllegalArgumentException("Expected count cannot be negative, was " + count);
}
this.expectStateMachineStopped = count;
return this;
}
/**
* Expect state machine extended state variables changing {@code count} times.
*
* @param count the count
* @return the state machine test plan step builder
*/
public StateMachineTestPlanStepBuilder expectExtendedStateChanged(int count) {
if (count < 0) {
throw new IllegalArgumentException("Expected count cannot be negative, was " + count);
}
this.expectExtendedStateChanged = count;
return this;
}
/**
* Add a new step and return {@link StateMachineTestPlanBuilder}
* for chaining.
*
* @return the state machine test plan builder for chaining
*/
public StateMachineTestPlanBuilder<S, E> and() {
steps.add(new StateMachineTestPlanStep<S, E>(sendEvent, sendMessage, sendEventMachineId, sendEventToAll,
sendEventParallel, expectStates, expectStateChanged, expectStateEntered, expectStateExited,
expectEventNotAccepted, expectTransition, expectTransitionStarted, expectTransitionEnded,
expectStateMachineStarted, expectStateMachineStopped, expectVariableKeys, expectVariables,
expectExtendedStateChanged, expectStatesEntrered, expectStatesExited));
return StateMachineTestPlanBuilder.this;
}
}
static class StateMachineTestPlanStep<S, E> {
final List<E> sendEvent;
final List<Message<E>> sendMessage;
Object sendEventMachineId;
boolean sendEventToAll = false;
boolean sendEventParallel = false;
final Collection<S> expectStates;
final Collection<S> expectStatesEntrered;
final Collection<S> expectStatesExited;
Integer expectStateChanged;
Integer expectStateEntered;
Integer expectStateExited;
Integer expectEventNotAccepted;
Integer expectTransition;
Integer expectTransitionStarted;
Integer expectTransitionEnded;
Integer expectStateMachineStarted;
Integer expectStateMachineStopped;
Integer expectExtendedStateChanged;
final Collection<Object> expectVariableKeys;
final Map<Object, Object> expectVariables;
public StateMachineTestPlanStep(List<E> sendEvent, List<Message<E>> sendMessage, Object sendEventMachineId,
boolean sendEventToAll, boolean sendEventParallel, Collection<S> expectStates,
Integer expectStateChanged, Integer expectStateEntered, Integer expectStateExited,
Integer expectEventNotAccepted, Integer expectTransition, Integer expectTransitionStarted,
Integer expectTransitionEnded, Integer expectStateMachineStarted, Integer expectStateMachineStopped,
Collection<Object> expectVariableKeys, Map<Object, Object> expectVariables,
Integer expectExtendedStateChanged, Collection<S> expectStatesEntrered, Collection<S> expectStatesExited) {
this.sendEvent = sendEvent;
this.sendMessage = sendMessage;
this.sendEventMachineId = sendEventMachineId;
this.sendEventToAll = sendEventToAll;
this.sendEventParallel = sendEventParallel;
this.expectStates = expectStates;
this.expectStateChanged = expectStateChanged;
this.expectStateEntered = expectStateEntered;
this.expectStateExited = expectStateExited;
this.expectEventNotAccepted = expectEventNotAccepted;
this.expectTransition = expectTransition;
this.expectTransitionStarted = expectTransitionStarted;
this.expectTransitionEnded = expectTransitionEnded;
this.expectStateMachineStarted = expectStateMachineStarted;
this.expectStateMachineStopped = expectStateMachineStopped;
this.expectVariableKeys = expectVariableKeys;
this.expectVariables = expectVariables;
this.expectExtendedStateChanged = expectExtendedStateChanged;
this.expectStatesEntrered = expectStatesEntrered;
this.expectStatesExited = expectStatesExited;
}
}
}