diff --git a/finite-state-machine/etc/finite-state-machine.png b/finite-state-machine/etc/finite-state-machine.png new file mode 100644 index 000000000000..59b3e3035e86 Binary files /dev/null and b/finite-state-machine/etc/finite-state-machine.png differ diff --git a/finite-state-machine/pom.xml b/finite-state-machine/pom.xml new file mode 100644 index 000000000000..ada7c8c4007b --- /dev/null +++ b/finite-state-machine/pom.xml @@ -0,0 +1,15 @@ + + + + java-design-patterns + com.iluwatar + 1.5.0 + + 4.0.0 + + finite-state-machine + + + \ No newline at end of file diff --git a/finite-state-machine/src/main/java/com/iluwatar/fsm/FiniteStateMachine.java b/finite-state-machine/src/main/java/com/iluwatar/fsm/FiniteStateMachine.java new file mode 100644 index 000000000000..aa6bd4be0660 --- /dev/null +++ b/finite-state-machine/src/main/java/com/iluwatar/fsm/FiniteStateMachine.java @@ -0,0 +1,181 @@ +package com.iluwatar.fsm; + +import java.util.HashMap; +import java.util.Map; + +/** + * Represents a simple finite state machine implementation with a custom context and where events and states are defined as enumerations. + * + * Created by Stephen Lazarionok. + */ +public final class FiniteStateMachine { + + private Enum state; + + private C context; + + private Map transitions; + + public Enum getState() { + return state; + } + + public void raiseEvent(Enum event) { + + System.out.println("Event '" + event + "' has been raised..."); + + // Tries to find the transition mapping by the start state and the event raised. + final TransitionMappingValue transitionMappingValue = transitions.get(new TransitionMappingKey(event, state)); + if (transitionMappingValue != null) { + + // Gets the target state + final Enum targetState = transitionMappingValue.getState(); + + // Invokes the transition handler if exists + if (transitionMappingValue.getTransitionHandler() != null) { + transitionMappingValue.getTransitionHandler().handle(state, targetState, context); + } + + final Enum oldState = this.state; + this.state = targetState; + System.out.println("Transition from '" + oldState + "' to '" + state + "' triggered by '"+ event + "' event has been completed."); + } + else { + System.out.println("No available transitions found by event '" + event + "' from state '" + state + "'."); + } + } + + private static final class TransitionMappingKey { + + private Enum event; + + private Enum state; + + private TransitionMappingKey(Enum event, Enum state) { + this.event = event; + this.state = state; + } + + public Enum getEvent() { + return event; + } + + public Enum getState() { + return state; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TransitionMappingKey that = (TransitionMappingKey) o; + + if (event != null ? !event.equals(that.event) : that.event != null) return false; + if (state != null ? !state.equals(that.state) : that.state != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = event != null ? event.hashCode() : 0; + result = 31 * result + (state != null ? state.hashCode() : 0); + return result; + } + } + + private static final class TransitionMappingValue { + + private TransitionHandler transitionHandler; + + private Enum state; + + public TransitionMappingValue(TransitionHandler transitionHandler, Enum state) { + this.transitionHandler = transitionHandler; + this.state = state; + } + + public TransitionHandler getTransitionHandler() { + return transitionHandler; + } + + public Enum getState() { + return state; + } + } + + private FiniteStateMachine() { + } + + /** + * A builder for custom finite state machines. + * + */ + public static final class Builder { + + + private Enum state; + + private C context; + + private Map transitions = new HashMap(); + + private Builder() { + } + + /** + * Creates a new builder. + * @return a new builder instance. + */ + public static Builder create() { + return new Builder(); + } + + /** + * Defines an initial state. + * @param state + * @return + */ + public Builder withDefaultState(final Enum state) { + this.state = state; + return this; + } + + /** + * Add a transition. + * @param from - 'from' state + * @param event - an event that triggers the transition + * @param to - 'to' state + * @param transitionHandler - a transition handler. + * @return + */ + public Builder addTransition(final Enum from, final Enum event, final Enum to, final TransitionHandler transitionHandler) { + transitions.put(new TransitionMappingKey(event, from), new TransitionMappingValue(transitionHandler, to)); + return this; + } + + /** + * Defines the context of the FSM. Might be used to pass external services to handle transitions within the FSM. + * @param context + * @return + */ + public Builder withContext(final C context) { + this.context = context; + return this; + } + + /** + * Builds a FSM based on the rules defined. + * @return + */ + public FiniteStateMachine build() { + final FiniteStateMachine result = new FiniteStateMachine(); + result.context = context; + result.transitions = transitions; + result.state = state; + return result; + } + } + +} diff --git a/finite-state-machine/src/main/java/com/iluwatar/fsm/TransitionHandler.java b/finite-state-machine/src/main/java/com/iluwatar/fsm/TransitionHandler.java new file mode 100644 index 000000000000..9689d3168aaf --- /dev/null +++ b/finite-state-machine/src/main/java/com/iluwatar/fsm/TransitionHandler.java @@ -0,0 +1,18 @@ +package com.iluwatar.fsm; + +/** + * Represents a hook that handles transitions from one to another state. + * + * Created by Stephen Lazarionok. + */ +public interface TransitionHandler { + + /** + * Handles a transition from one to another state. + * + * @param from - an initial state + * @param to - a target state + * @param context - a custom context passed. + */ + void handle(Enum from, Enum to, C context); +} diff --git a/finite-state-machine/src/main/java/com/iluwatar/fsm/sample/Car.java b/finite-state-machine/src/main/java/com/iluwatar/fsm/sample/Car.java new file mode 100644 index 000000000000..3effd6911a1c --- /dev/null +++ b/finite-state-machine/src/main/java/com/iluwatar/fsm/sample/Car.java @@ -0,0 +1,15 @@ +package com.iluwatar.fsm.sample; + +/** + * Created by Stephen Lazarionok. + */ +public class Car { + + public void drive() { + System.out.println("***************************"); + System.out.println("***************************"); + System.out.println("Driving..."); + System.out.println("***************************"); + System.out.println("***************************"); + } +} diff --git a/finite-state-machine/src/main/java/com/iluwatar/fsm/sample/Sample.java b/finite-state-machine/src/main/java/com/iluwatar/fsm/sample/Sample.java new file mode 100644 index 000000000000..0a1b6bc2abc4 --- /dev/null +++ b/finite-state-machine/src/main/java/com/iluwatar/fsm/sample/Sample.java @@ -0,0 +1,56 @@ +package com.iluwatar.fsm.sample; + +import com.iluwatar.fsm.FiniteStateMachine; +import com.iluwatar.fsm.TransitionHandler; + +import java.util.HashMap; +import java.util.Map; + + +/** + * A demo application that shows how to work with traffic lights. + *

+ * Created by Stephen Lazarionok. + */ +public class Sample { + + public static void main(final String[] args) { + + // Build a simple context based on map + final Map context = new HashMap(); + context.put("mycar", new Car()); + + // A handler that just logs a transition + final TransitionHandler> logTransitionHandler = new TransitionHandler>() { + + @Override + public void handle(Enum from, Enum to, Map context) { + System.out.println("Performing transition from '" + from + "' to '" + to + "'..."); + } + }; + + // A handler that starts driving my car + final TransitionHandler> startDrivingHandler = new TransitionHandler>() { + + @Override + public void handle(Enum from, Enum to, Map context) { + + Car.class.cast(context.get("mycar")).drive(); + } + }; + + final FiniteStateMachine> trafficLightFsm = FiniteStateMachine.Builder.create() + .withDefaultState(TrafficLightState.RED) + .addTransition(TrafficLightState.GREEN, TrafficLightEvent.SWITCH, TrafficLightState.YELLOW, logTransitionHandler) + .addTransition(TrafficLightState.YELLOW, TrafficLightEvent.SWITCH, TrafficLightState.RED, logTransitionHandler) + .addTransition(TrafficLightState.RED, TrafficLightEvent.SWITCH, TrafficLightState.YELLOW, logTransitionHandler) + .addTransition(TrafficLightState.YELLOW, TrafficLightEvent.SWITCH, TrafficLightState.GREEN, startDrivingHandler) + .withContext(context) + .build(); + + trafficLightFsm.raiseEvent(TrafficLightEvent.SWITCH); + trafficLightFsm.raiseEvent(TrafficLightEvent.SWITCH); + + } + +} diff --git a/finite-state-machine/src/main/java/com/iluwatar/fsm/sample/TrafficLightEvent.java b/finite-state-machine/src/main/java/com/iluwatar/fsm/sample/TrafficLightEvent.java new file mode 100644 index 000000000000..e8ca2ea93bf9 --- /dev/null +++ b/finite-state-machine/src/main/java/com/iluwatar/fsm/sample/TrafficLightEvent.java @@ -0,0 +1,10 @@ +package com.iluwatar.fsm.sample; + +/** + * Contains all the events that may happen with traffic light. + * + * Created by Stephen Lazarionok. + */ +public enum TrafficLightEvent { + SWITCH +} diff --git a/finite-state-machine/src/main/java/com/iluwatar/fsm/sample/TrafficLightState.java b/finite-state-machine/src/main/java/com/iluwatar/fsm/sample/TrafficLightState.java new file mode 100644 index 000000000000..f7c03ac34854 --- /dev/null +++ b/finite-state-machine/src/main/java/com/iluwatar/fsm/sample/TrafficLightState.java @@ -0,0 +1,10 @@ +package com.iluwatar.fsm.sample; + +/** + * Contains all the states of traffic light. + * + * Created by Stephen Lazarionok. + */ +public enum TrafficLightState { + RED, YELLOW, GREEN +}