Skip to content
Closed
34 changes: 34 additions & 0 deletions FiniteStateMachine/etc/FiniteStateMachine.urm.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
@startuml
package com.iluwatar.trafficlight {
class GreenLightState {
+ GreenLightState()
+ handleEvent(context : TrafficLightContext)
}
class RedLightState {
+ RedLightState()
+ handleEvent(context : TrafficLightContext)
}
class TrafficLightContext {
- currentState : TrafficLightState
+ TrafficLightContext(initialState : TrafficLightState)
+ getCurrentState() : TrafficLightState
+ handleEvent()
+ setState(newState : TrafficLightState)
}
class TrafficLightFsm {
+ TrafficLightFsm()
+ main(args : String[]) {static}
}
interface TrafficLightState {
+ handleEvent(TrafficLightContext) {abstract}
}
class YellowLightState {
+ YellowLightState()
+ handleEvent(context : TrafficLightContext)
}
}
TrafficLightContext --> "-currentState" TrafficLightState
GreenLightState ..|> TrafficLightState
RedLightState ..|> TrafficLightState
YellowLightState ..|> TrafficLightState
@enduml
31 changes: 31 additions & 0 deletions FiniteStateMachine/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
</parent>

<artifactId>FiniteStateMachine</artifactId>

<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
Comment on lines +13 to +17
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comes from parent pom.xml and can be left out

<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.iluwatar.trafficlight;

/**
* Concrete state representing the Green Light.
*/
public class GreenLightState implements TrafficLightState {

/**
* Handles the event for the Green Light.
* This method transitions the traffic light to the Yellow state after the Green state.
*
* @param context The traffic light context to manage the state transitions.
*/
@Override
public void handleEvent(TrafficLightContext context) {
System.out.println("Green Light: Go!");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a proper logger

// Transition to the Yellow light state
context.setState(new YellowLightState());
}
}



Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.iluwatar.trafficlight;

/**
* Concrete state representing the Red Light.
*/
public class RedLightState implements TrafficLightState {
@Override
public void handleEvent(TrafficLightContext context) {
System.out.println("Red Light: Stop!");
context.setState(new GreenLightState());
}
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.iluwatar.trafficlight;

/**
* Context class for managing the current state and transitions.
*/
public class TrafficLightContext {
private TrafficLightState currentState;

/**
* Initializes the context with the given initial state.
*
* @param initialState the initial state of the traffic light
*/
public TrafficLightContext(TrafficLightState initialState) {
this.currentState = initialState;
}

/**
* Updates the current state of the traffic light.
*
* @param newState the new state to transition to
*/
public void setState(TrafficLightState newState) {
this.currentState = newState;
}

/**
* Handles the current state's event and transitions to the next state.
*/
public void handleEvent() {
currentState.handleEvent(this);
}

/**
* Gets the current state of the traffic light.
* This can be useful for testing purposes.
*/
public TrafficLightState getCurrentState() {
return currentState;
}
Comment on lines +38 to +40
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lombok can be used to get rid of boilerplate such as getters and setters

}


Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.iluwatar.trafficlight;

/**
* Simulates a traffic light system using a Finite State Machine (FSM).
*/
public class TrafficLightFsm {

/**
* Runs the traffic light simulation.
*
* @param args command-line arguments (not used here)
*/
public static void main(String[] args) {
// Start with the Red light
TrafficLightContext trafficLight = new TrafficLightContext(new RedLightState());

// Cycle through the traffic light states
for (int i = 0; i < 6; i++) {
trafficLight.handleEvent();
}
}
}





Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.iluwatar.trafficlight;

/**
* State interface for traffic light states.
*/
public interface TrafficLightState {

/**
* Handles the transition to the next state based on the current state.
*
* @param context the context object that manages the traffic light's state
*/
void handleEvent(TrafficLightContext context);
}





Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.iluwatar.trafficlight;

/**
* Concrete state representing the Yellow Light.
*/
public class YellowLightState implements TrafficLightState {
@Override
public void handleEvent(TrafficLightContext context) {
System.out.println("Yellow Light: Caution!");
context.setState(new RedLightState());
}
}

176 changes: 176 additions & 0 deletions FiniteStateMachine/src/test/java/com/iluwater/TrafficLightTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package com.iluwater;

import static org.junit.jupiter.api.Assertions.assertTrue;

import com.iluwatar.trafficlight.GreenLightState;
import com.iluwatar.trafficlight.RedLightState;
import com.iluwatar.trafficlight.TrafficLightContext;
import com.iluwatar.trafficlight.TrafficLightFsm;
import com.iluwatar.trafficlight.YellowLightState;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

/**
* Test class for the Traffic Light FSM.
*/
public class TrafficLightTest {

private TrafficLightContext context;

@BeforeEach
void setUp() {
// Start with the Red Light state
context = new TrafficLightContext(new RedLightState());
}

@Test
void testInitialState() {
assertTrue(context.getCurrentState() instanceof RedLightState, "Initial state should be RedLightState.");
}

@Test
void testRedToGreenTransition() {
context.handleEvent();
assertTrue(context.getCurrentState() instanceof GreenLightState, "Red Light should transition to Green Light.");
}

@Test
void testGreenToYellowTransition() {
context.setState(new GreenLightState());
context.handleEvent();
assertTrue(context.getCurrentState() instanceof YellowLightState, "Green Light should transition to Yellow Light.");
}

@Test
void testYellowToRedTransition() {
context.setState(new YellowLightState());
context.handleEvent();
assertTrue(context.getCurrentState() instanceof RedLightState, "Yellow Light should transition to Red Light.");
}

@Test
void testFullCycle() {
context.handleEvent(); // Red -> Green
assertTrue(context.getCurrentState() instanceof GreenLightState);

context.handleEvent(); // Green -> Yellow
assertTrue(context.getCurrentState() instanceof YellowLightState);

context.handleEvent(); // Yellow -> Red
assertTrue(context.getCurrentState() instanceof RedLightState);
}

// Test invalid state transition
@Test
void testInvalidStateTransition() {
context.setState(new RedLightState());
try {
// This should fail, as it doesn't make sense for Red to handle an event again.
context.handleEvent();
} catch (IllegalStateException e) {
assertTrue(true, "Handled invalid state transition.");
}
}

// Test state reset
@Test
void testStateReset() {
context.setState(new YellowLightState());
context.handleEvent(); // Yellow -> Red
assertTrue(context.getCurrentState() instanceof RedLightState);

context.setState(new GreenLightState());
context.handleEvent(); // Green -> Yellow
assertTrue(context.getCurrentState() instanceof YellowLightState);

context.setState(new RedLightState());
context.handleEvent(); // Red -> Green
assertTrue(context.getCurrentState() instanceof GreenLightState);
}

// Test manually setting the state
@Test
void testManualStateSet() {
context.setState(new GreenLightState());
assertTrue(context.getCurrentState() instanceof GreenLightState);

context.setState(new YellowLightState());
assertTrue(context.getCurrentState() instanceof YellowLightState);

context.setState(new RedLightState());
assertTrue(context.getCurrentState() instanceof RedLightState);
}

// Additional tests for edge cases

// Test if state is correctly set in the middle of a cycle
@Test
void testMidCycleStateSet() {
context.handleEvent(); // Red -> Green
assertTrue(context.getCurrentState() instanceof GreenLightState);

// Set state manually in the middle of the cycle
context.setState(new YellowLightState());
assertTrue(context.getCurrentState() instanceof YellowLightState);

context.handleEvent(); // Yellow -> Red
assertTrue(context.getCurrentState() instanceof RedLightState);
}

// Test if context properly resets after complete cycle
@Test
void testContextResetAfterFullCycle() {
context.handleEvent(); // Red -> Green
context.handleEvent(); // Green -> Yellow
context.handleEvent(); // Yellow -> Red

// Reset and verify again
context.setState(new GreenLightState());
assertTrue(context.getCurrentState() instanceof GreenLightState);

context.handleEvent(); // Green -> Yellow
assertTrue(context.getCurrentState() instanceof YellowLightState);

context.handleEvent(); // Yellow -> Red
assertTrue(context.getCurrentState() instanceof RedLightState);
}

// Test if the initial state remains unchanged after multiple cycles
@Test
void testMultipleCycles() {
context.handleEvent(); // Red -> Green
context.handleEvent(); // Green -> Yellow
context.handleEvent(); // Yellow -> Red

// Perform another full cycle and ensure the state remains correct
context.handleEvent(); // Red -> Green
assertTrue(context.getCurrentState() instanceof GreenLightState);

context.handleEvent(); // Green -> Yellow
assertTrue(context.getCurrentState() instanceof YellowLightState);

context.handleEvent(); // Yellow -> Red
assertTrue(context.getCurrentState() instanceof RedLightState);
}

// Test if state changes properly when reset in the middle of cycles
@Test
void testResetMidCycle() {
context.handleEvent(); // Red -> Green
context.setState(new YellowLightState()); // Manually set Yellow
assertTrue(context.getCurrentState() instanceof YellowLightState);
context.handleEvent(); // Yellow -> Red
assertTrue(context.getCurrentState() instanceof RedLightState);
}

// Test for traffic light FSM running through multiple cycles
@Test
void testTrafficLightFsmCycles() {
// Start the FSM and cycle through multiple events
TrafficLightFsm.main(new String[0]); // This will run the logic in the main function of TrafficLightFsm
}
}




Loading