mirror of
https://github.com/davidalves04/Trabalho-Pratico-SD.git
synced 2025-12-08 20:43:32 +00:00
Compare commits
4 Commits
16-integra
...
17-create-
| Author | SHA1 | Date | |
|---|---|---|---|
| 06f079ce5b | |||
| 72893f87ae | |||
| 6b94d727e2 | |||
| 84cba39597 |
@@ -12,17 +12,18 @@ import java.util.concurrent.locks.Lock;
|
|||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
import sd.config.SimulationConfig;
|
import sd.config.SimulationConfig;
|
||||||
|
import sd.engine.TrafficLightThread;
|
||||||
import sd.model.Intersection;
|
import sd.model.Intersection;
|
||||||
import sd.model.MessageType;
|
import sd.model.MessageType;
|
||||||
import sd.model.TrafficLight;
|
import sd.model.TrafficLight;
|
||||||
import sd.model.TrafficLightState;
|
|
||||||
import sd.model.Vehicle;
|
import sd.model.Vehicle;
|
||||||
import sd.protocol.MessageProtocol;
|
import sd.protocol.MessageProtocol;
|
||||||
import sd.protocol.SocketConnection;
|
import sd.protocol.SocketConnection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main class for an Intersection Process in the distributed traffic simulation.
|
* Main class for an Intersection Process in the distributed traffic simulation.
|
||||||
* * Each IntersectionProcess runs as an independent Java application (JVM instance)
|
* * Each IntersectionProcess runs as an independent Java application (JVM
|
||||||
|
* instance)
|
||||||
* representing one of the five intersections (Cr1-Cr5) in the network.
|
* representing one of the five intersections (Cr1-Cr5) in the network.
|
||||||
*/
|
*/
|
||||||
public class IntersectionProcess {
|
public class IntersectionProcess {
|
||||||
@@ -41,13 +42,14 @@ public class IntersectionProcess {
|
|||||||
|
|
||||||
private final ExecutorService trafficLightPool;
|
private final ExecutorService trafficLightPool;
|
||||||
|
|
||||||
private volatile boolean running; //Quando uma thread escreve um valor volatile, todas as outras
|
private volatile boolean running; // Quando uma thread escreve um valor volatile, todas as outras
|
||||||
//threads veem a mudança imediatamente.
|
// threads veem a mudança imediatamente.
|
||||||
|
|
||||||
// Traffic Light Coordination
|
// Traffic Light Coordination
|
||||||
/**
|
/**
|
||||||
* Lock to ensure mutual exclusion between traffic lights.
|
* Lock to ensure mutual exclusion between traffic lights.
|
||||||
* Only one traffic light can be green at any given time within this intersection.
|
* Only one traffic light can be green at any given time within this
|
||||||
|
* intersection.
|
||||||
*/
|
*/
|
||||||
private final Lock trafficCoordinationLock;
|
private final Lock trafficCoordinationLock;
|
||||||
|
|
||||||
@@ -91,7 +93,8 @@ public class IntersectionProcess {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates traffic lights for this intersection based on its physical connections.
|
* Creates traffic lights for this intersection based on its physical
|
||||||
|
* connections.
|
||||||
* Each intersection has different number and directions of traffic lights
|
* Each intersection has different number and directions of traffic lights
|
||||||
* according to the network topology.
|
* according to the network topology.
|
||||||
*/
|
*/
|
||||||
@@ -101,19 +104,19 @@ public class IntersectionProcess {
|
|||||||
String[] directions = new String[0];
|
String[] directions = new String[0];
|
||||||
switch (intersectionId) {
|
switch (intersectionId) {
|
||||||
case "Cr1":
|
case "Cr1":
|
||||||
directions = new String[]{"East", "South"};
|
directions = new String[] { "East", "South" };
|
||||||
break;
|
break;
|
||||||
case "Cr2":
|
case "Cr2":
|
||||||
directions = new String[]{"West", "East", "South"};
|
directions = new String[] { "West", "East", "South" };
|
||||||
break;
|
break;
|
||||||
case "Cr3":
|
case "Cr3":
|
||||||
directions = new String[]{"West", "South"};
|
directions = new String[] { "West", "South" };
|
||||||
break;
|
break;
|
||||||
case "Cr4":
|
case "Cr4":
|
||||||
directions = new String[]{"East"};
|
directions = new String[] { "East" };
|
||||||
break;
|
break;
|
||||||
case "Cr5":
|
case "Cr5":
|
||||||
directions = new String[]{"East"};
|
directions = new String[] { "East" };
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,8 +128,7 @@ public class IntersectionProcess {
|
|||||||
intersectionId + "-" + direction,
|
intersectionId + "-" + direction,
|
||||||
direction,
|
direction,
|
||||||
greenTime,
|
greenTime,
|
||||||
redTime
|
redTime);
|
||||||
);
|
|
||||||
|
|
||||||
intersection.addTrafficLight(light);
|
intersection.addTrafficLight(light);
|
||||||
System.out.println(" Created traffic light: " + direction +
|
System.out.println(" Created traffic light: " + direction +
|
||||||
@@ -169,6 +171,29 @@ public class IntersectionProcess {
|
|||||||
System.out.println(" Routing configured.");
|
System.out.println(" Routing configured.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests permission for a traffic light to turn green.
|
||||||
|
* Blocks until permission is granted (no other light is green).
|
||||||
|
*
|
||||||
|
* @param direction The direction requesting green light
|
||||||
|
*/
|
||||||
|
public void requestGreenLight(String direction) {
|
||||||
|
trafficCoordinationLock.lock();
|
||||||
|
currentGreenDirection = direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Releases the green light permission, allowing another light to turn green.
|
||||||
|
*
|
||||||
|
* @param direction The direction releasing green light
|
||||||
|
*/
|
||||||
|
public void releaseGreenLight(String direction) {
|
||||||
|
if (direction.equals(currentGreenDirection)) {
|
||||||
|
currentGreenDirection = null;
|
||||||
|
trafficCoordinationLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts all traffic light threads.
|
* Starts all traffic light threads.
|
||||||
*/
|
*/
|
||||||
@@ -176,128 +201,15 @@ public class IntersectionProcess {
|
|||||||
System.out.println("\n[" + intersectionId + "] Starting traffic light threads...");
|
System.out.println("\n[" + intersectionId + "] Starting traffic light threads...");
|
||||||
|
|
||||||
for (TrafficLight light : intersection.getTrafficLights()) {
|
for (TrafficLight light : intersection.getTrafficLights()) {
|
||||||
trafficLightPool.submit(() -> runTrafficLightCycle(light));
|
|
||||||
|
TrafficLightThread lightTask = new TrafficLightThread(light, this, config);
|
||||||
|
|
||||||
|
trafficLightPool.submit(lightTask);
|
||||||
|
|
||||||
System.out.println(" Started thread for: " + light.getDirection());
|
System.out.println(" Started thread for: " + light.getDirection());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The main loop for a traffic light thread.
|
|
||||||
* Continuously cycles between green and red states.
|
|
||||||
*
|
|
||||||
* only one traffic light can be green at any given time in this intersection.
|
|
||||||
*
|
|
||||||
* @param light The traffic light to control.
|
|
||||||
*/
|
|
||||||
private void runTrafficLightCycle(TrafficLight light) {
|
|
||||||
System.out.println("[" + light.getId() + "] Traffic light thread started.");
|
|
||||||
|
|
||||||
while (running) {
|
|
||||||
try {
|
|
||||||
// Acquire coordination lock to become green
|
|
||||||
trafficCoordinationLock.lock();
|
|
||||||
try {
|
|
||||||
// Wait until no other direction is green
|
|
||||||
while (currentGreenDirection != null && running) {
|
|
||||||
trafficCoordinationLock.unlock();
|
|
||||||
Thread.sleep(100); // Brief wait before retrying
|
|
||||||
trafficCoordinationLock.lock();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!running) {
|
|
||||||
break; // Exit if shutting down
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark this direction as the current green light
|
|
||||||
currentGreenDirection = light.getDirection();
|
|
||||||
light.changeState(TrafficLightState.GREEN);
|
|
||||||
System.out.println("[" + light.getId() + "] State: GREEN");
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
trafficCoordinationLock.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process vehicles while green
|
|
||||||
processGreenLight(light);
|
|
||||||
|
|
||||||
// Wait for green duration
|
|
||||||
Thread.sleep((long) (light.getGreenTime() * 1000));
|
|
||||||
|
|
||||||
// Release coordination lock (turn red)
|
|
||||||
trafficCoordinationLock.lock();
|
|
||||||
try {
|
|
||||||
light.changeState(TrafficLightState.RED);
|
|
||||||
currentGreenDirection = null; // Release exclusive access
|
|
||||||
System.out.println("[" + light.getId() + "] State: RED (RELEASED ACCESS)");
|
|
||||||
} finally {
|
|
||||||
trafficCoordinationLock.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for red duration
|
|
||||||
Thread.sleep((long) (light.getRedTime() * 1000));
|
|
||||||
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
System.out.println("[" + light.getId() + "] Traffic light thread interrupted.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
System.out.println("[" + light.getId() + "] Traffic light thread stopped.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Processes vehicles when a traffic light is GREEN.
|
|
||||||
* Dequeues vehicles and sends them to their next destination.
|
|
||||||
*
|
|
||||||
* @param light The traffic light that is currently green.
|
|
||||||
*/
|
|
||||||
private void processGreenLight(TrafficLight light) {
|
|
||||||
while (light.getState() == TrafficLightState.GREEN && light.getQueueSize() > 0) {
|
|
||||||
Vehicle vehicle = light.removeVehicle();
|
|
||||||
|
|
||||||
if (vehicle != null) {
|
|
||||||
// Get crossing time based on vehicle type
|
|
||||||
double crossingTime = getCrossingTimeForVehicle(vehicle);
|
|
||||||
|
|
||||||
// Simulate crossing time
|
|
||||||
try {
|
|
||||||
Thread.sleep((long) (crossingTime * 1000));
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update vehicle statistics
|
|
||||||
vehicle.addCrossingTime(crossingTime);
|
|
||||||
|
|
||||||
// Update intersection statistics
|
|
||||||
intersection.incrementVehiclesSent();
|
|
||||||
|
|
||||||
// Send vehicle to next destination
|
|
||||||
sendVehicleToNextDestination(vehicle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the crossing time for a vehicle based on its type.
|
|
||||||
*
|
|
||||||
* @param vehicle The vehicle.
|
|
||||||
* @return The crossing time in seconds.
|
|
||||||
*/
|
|
||||||
private double getCrossingTimeForVehicle(Vehicle vehicle) {
|
|
||||||
switch (vehicle.getType()) {
|
|
||||||
case BIKE:
|
|
||||||
return config.getBikeVehicleCrossingTime();
|
|
||||||
case LIGHT:
|
|
||||||
return config.getLightVehicleCrossingTime();
|
|
||||||
case HEAVY:
|
|
||||||
return config.getHeavyVehicleCrossingTime();
|
|
||||||
default:
|
|
||||||
return config.getLightVehicleCrossingTime();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a vehicle to its next destination via socket connection.
|
* Sends a vehicle to its next destination via socket connection.
|
||||||
*
|
*
|
||||||
@@ -314,8 +226,7 @@ public class IntersectionProcess {
|
|||||||
MessageProtocol message = new VehicleTransferMessage(
|
MessageProtocol message = new VehicleTransferMessage(
|
||||||
intersectionId,
|
intersectionId,
|
||||||
nextDestination,
|
nextDestination,
|
||||||
vehicle
|
vehicle);
|
||||||
);
|
|
||||||
|
|
||||||
connection.sendMessage(message);
|
connection.sendMessage(message);
|
||||||
|
|
||||||
@@ -406,17 +317,37 @@ public class IntersectionProcess {
|
|||||||
try {
|
try {
|
||||||
Socket clientSocket = serverSocket.accept();
|
Socket clientSocket = serverSocket.accept();
|
||||||
|
|
||||||
|
System.out.println("[" + intersectionId + "] New connection accepted from " +
|
||||||
|
clientSocket.getInetAddress().getHostAddress());
|
||||||
|
|
||||||
|
// Check running flag again before handling
|
||||||
|
if (!running) {
|
||||||
|
clientSocket.close();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// **Set timeout before submitting to handler**
|
||||||
|
try {
|
||||||
|
clientSocket.setSoTimeout(1000);
|
||||||
|
} catch (java.net.SocketException e) {
|
||||||
|
System.err.println("[" + intersectionId + "] Failed to set timeout: " + e.getMessage());
|
||||||
|
clientSocket.close();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle each connection in a separate thread
|
// Handle each connection in a separate thread
|
||||||
connectionHandlerPool.submit(() -> handleIncomingConnection(clientSocket));
|
connectionHandlerPool.submit(() -> handleIncomingConnection(clientSocket));
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
if (running) {
|
// Expected when serverSocket.close() is called during shutdown
|
||||||
|
if (!running) {
|
||||||
|
break; // Normal shutdown
|
||||||
|
}
|
||||||
System.err.println("[" + intersectionId + "] Error accepting connection: " +
|
System.err.println("[" + intersectionId + "] Error accepting connection: " +
|
||||||
e.getMessage());
|
e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles an incoming connection from another process.
|
* Handles an incoming connection from another process.
|
||||||
@@ -425,6 +356,14 @@ public class IntersectionProcess {
|
|||||||
* @param clientSocket The accepted socket connection.
|
* @param clientSocket The accepted socket connection.
|
||||||
*/
|
*/
|
||||||
private void handleIncomingConnection(Socket clientSocket) {
|
private void handleIncomingConnection(Socket clientSocket) {
|
||||||
|
try {
|
||||||
|
clientSocket.setSoTimeout(1000); // 1 second timeout
|
||||||
|
|
||||||
|
} catch (java.net.SocketException e) {
|
||||||
|
System.err.println("[" + intersectionId + "] Failed to set socket timeout: " + e.getMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try (SocketConnection connection = new SocketConnection(clientSocket)) {
|
try (SocketConnection connection = new SocketConnection(clientSocket)) {
|
||||||
|
|
||||||
System.out.println("[" + intersectionId + "] New connection accepted from " +
|
System.out.println("[" + intersectionId + "] New connection accepted from " +
|
||||||
@@ -445,9 +384,16 @@ public class IntersectionProcess {
|
|||||||
intersection.receiveVehicle(vehicle);
|
intersection.receiveVehicle(vehicle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} catch (java.net.SocketTimeoutException e) {
|
||||||
|
// Timeout - check running flag and continue
|
||||||
|
if (!running) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Continue waiting for next message
|
||||||
} catch (ClassNotFoundException e) {
|
} catch (ClassNotFoundException e) {
|
||||||
System.err.println("[" + intersectionId + "] Unknown message type received: " +
|
System.err.println("[" + intersectionId + "] Unknown message type received: " +
|
||||||
e.getMessage());
|
e.getMessage());
|
||||||
|
break; // Invalid message, close connection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,6 +401,7 @@ public class IntersectionProcess {
|
|||||||
if (running) {
|
if (running) {
|
||||||
System.err.println("[" + intersectionId + "] Connection error: " + e.getMessage());
|
System.err.println("[" + intersectionId + "] Connection error: " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
// Expected during shutdown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -463,47 +410,57 @@ public class IntersectionProcess {
|
|||||||
* Shuts down all threads and closes all connections.
|
* Shuts down all threads and closes all connections.
|
||||||
*/
|
*/
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
|
// Check if already shutdown
|
||||||
|
if (!running) {
|
||||||
|
return; // Already shutdown, do nothing
|
||||||
|
}
|
||||||
|
|
||||||
System.out.println("\n[" + intersectionId + "] Shutting down...");
|
System.out.println("\n[" + intersectionId + "] Shutting down...");
|
||||||
running = false;
|
running = false;
|
||||||
|
|
||||||
// Close server socket
|
// 1. Close ServerSocket first
|
||||||
try {
|
|
||||||
if (serverSocket != null && !serverSocket.isClosed()) {
|
if (serverSocket != null && !serverSocket.isClosed()) {
|
||||||
serverSocket.close();
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
System.err.println("[" + intersectionId + "] Error closing server socket: " +
|
|
||||||
e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shutdown thread pools
|
|
||||||
trafficLightPool.shutdown();
|
|
||||||
connectionHandlerPool.shutdown();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!trafficLightPool.awaitTermination(5, TimeUnit.SECONDS)) {
|
serverSocket.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Expected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Shutdown thread pools with force
|
||||||
|
if (trafficLightPool != null && !trafficLightPool.isShutdown()) {
|
||||||
trafficLightPool.shutdownNow();
|
trafficLightPool.shutdownNow();
|
||||||
}
|
}
|
||||||
if (!connectionHandlerPool.awaitTermination(5, TimeUnit.SECONDS)) {
|
if (connectionHandlerPool != null && !connectionHandlerPool.isShutdown()) {
|
||||||
connectionHandlerPool.shutdownNow();
|
connectionHandlerPool.shutdownNow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. Wait briefly for termination (don't block forever)
|
||||||
|
try {
|
||||||
|
if (trafficLightPool != null) {
|
||||||
|
trafficLightPool.awaitTermination(1, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
if (connectionHandlerPool != null) {
|
||||||
|
connectionHandlerPool.awaitTermination(1, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
trafficLightPool.shutdownNow();
|
Thread.currentThread().interrupt();
|
||||||
connectionHandlerPool.shutdownNow();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close all outgoing connections
|
// 4. Close outgoing connections
|
||||||
for (Map.Entry<String, SocketConnection> entry : outgoingConnections.entrySet()) {
|
synchronized (outgoingConnections) {
|
||||||
|
for (SocketConnection conn : outgoingConnections.values()) {
|
||||||
try {
|
try {
|
||||||
entry.getValue().close();
|
conn.close();
|
||||||
} catch (IOException e) {
|
} catch (Exception e) {
|
||||||
System.err.println("[" + intersectionId + "] Error closing connection to " +
|
// Ignore
|
||||||
entry.getKey() + ": " + e.getMessage());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
outgoingConnections.clear();
|
||||||
|
}
|
||||||
|
|
||||||
System.out.println("[" + intersectionId + "] Shutdown complete.");
|
System.out.println("[" + intersectionId + "] Shutdown complete.");
|
||||||
System.out.println("=".repeat(60));
|
System.out.println("============================================================\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
110
main/src/main/java/sd/dashboard/DashboardClientHandler.java
Normal file
110
main/src/main/java/sd/dashboard/DashboardClientHandler.java
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
package sd.dashboard;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
|
import sd.model.MessageType;
|
||||||
|
import sd.protocol.MessageProtocol;
|
||||||
|
import sd.protocol.SocketConnection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes statistics messages from a single client connection.
|
||||||
|
* Runs in a separate thread per client.
|
||||||
|
*/
|
||||||
|
public class DashboardClientHandler implements Runnable {
|
||||||
|
|
||||||
|
private final Socket clientSocket;
|
||||||
|
private final DashboardStatistics statistics;
|
||||||
|
|
||||||
|
public DashboardClientHandler(Socket clientSocket, DashboardStatistics statistics) {
|
||||||
|
this.clientSocket = clientSocket;
|
||||||
|
this.statistics = statistics;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
String clientInfo = clientSocket.getInetAddress().getHostAddress() + ":" + clientSocket.getPort();
|
||||||
|
|
||||||
|
try (SocketConnection connection = new SocketConnection(clientSocket)) {
|
||||||
|
System.out.println("[Handler] Started handling client: " + clientInfo);
|
||||||
|
|
||||||
|
while (!Thread.currentThread().isInterrupted()) {
|
||||||
|
try {
|
||||||
|
MessageProtocol message = connection.receiveMessage();
|
||||||
|
|
||||||
|
if (message == null) {
|
||||||
|
System.out.println("[Handler] Client disconnected: " + clientInfo);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
processMessage(message);
|
||||||
|
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
System.err.println("[Handler] Unknown message class from " + clientInfo + ": " + e.getMessage());
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.out.println("[Handler] Connection error with " + clientInfo + ": " + e.getMessage());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println("[Handler] Error initializing connection with " + clientInfo + ": " + e.getMessage());
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (!clientSocket.isClosed()) {
|
||||||
|
clientSocket.close();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println("[Handler] Error closing socket for " + clientInfo + ": " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processMessage(MessageProtocol message) {
|
||||||
|
if (message.getType() != MessageType.STATS_UPDATE) {
|
||||||
|
System.out.println("[Handler] Ignoring non-statistics message type: " + message.getType());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String senderId = message.getSourceNode();
|
||||||
|
Object payload = message.getPayload();
|
||||||
|
|
||||||
|
System.out.println("[Handler] Received STATS_UPDATE from: " + senderId);
|
||||||
|
|
||||||
|
if (payload instanceof StatsUpdatePayload stats) {
|
||||||
|
updateStatistics(senderId, stats);
|
||||||
|
} else {
|
||||||
|
System.err.println("[Handler] Unknown payload type: " +
|
||||||
|
(payload != null ? payload.getClass().getName() : "null"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateStatistics(String senderId, StatsUpdatePayload stats) {
|
||||||
|
if (stats.getTotalVehiclesGenerated() >= 0) {
|
||||||
|
statistics.updateVehiclesGenerated(stats.getTotalVehiclesGenerated());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stats.getTotalVehiclesCompleted() >= 0) {
|
||||||
|
statistics.updateVehiclesCompleted(stats.getTotalVehiclesCompleted());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stats.getTotalSystemTime() >= 0) {
|
||||||
|
statistics.addSystemTime(stats.getTotalSystemTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stats.getTotalWaitingTime() >= 0) {
|
||||||
|
statistics.addWaitingTime(stats.getTotalWaitingTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (senderId.startsWith("Cr") || senderId.startsWith("E")) {
|
||||||
|
statistics.updateIntersectionStats(
|
||||||
|
senderId,
|
||||||
|
stats.getIntersectionArrivals(),
|
||||||
|
stats.getIntersectionDepartures(),
|
||||||
|
stats.getIntersectionQueueSize()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("[Handler] Successfully updated statistics from: " + senderId);
|
||||||
|
}
|
||||||
|
}
|
||||||
148
main/src/main/java/sd/dashboard/DashboardServer.java
Normal file
148
main/src/main/java/sd/dashboard/DashboardServer.java
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
package sd.dashboard;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
import sd.config.SimulationConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aggregates and displays real-time statistics from all simulation processes.
|
||||||
|
* Uses a thread pool to handle concurrent client connections.
|
||||||
|
*/
|
||||||
|
public class DashboardServer {
|
||||||
|
|
||||||
|
private final int port;
|
||||||
|
private final DashboardStatistics statistics;
|
||||||
|
private final ExecutorService clientHandlerPool;
|
||||||
|
private final AtomicBoolean running;
|
||||||
|
private ServerSocket serverSocket;
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.out.println("=".repeat(60));
|
||||||
|
System.out.println("DASHBOARD SERVER - DISTRIBUTED TRAFFIC SIMULATION");
|
||||||
|
System.out.println("=".repeat(60));
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Load configuration
|
||||||
|
String configFile = args.length > 0 ? args[0] : "src/main/resources/simulation.properties";
|
||||||
|
System.out.println("Loading configuration from: " + configFile);
|
||||||
|
|
||||||
|
SimulationConfig config = new SimulationConfig(configFile);
|
||||||
|
DashboardServer server = new DashboardServer(config);
|
||||||
|
|
||||||
|
// Start the server
|
||||||
|
System.out.println("\n" + "=".repeat(60));
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
// Keep running until interrupted
|
||||||
|
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||||
|
System.out.println("\n\nShutdown signal received...");
|
||||||
|
server.stop();
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Display statistics periodically
|
||||||
|
server.displayLoop();
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println("Failed to start Dashboard Server: " + e.getMessage());
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DashboardServer(SimulationConfig config) {
|
||||||
|
this.port = config.getDashboardPort();
|
||||||
|
this.statistics = new DashboardStatistics();
|
||||||
|
this.clientHandlerPool = Executors.newFixedThreadPool(10);
|
||||||
|
this.running = new AtomicBoolean(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() throws IOException {
|
||||||
|
if (running.get()) {
|
||||||
|
System.out.println("Dashboard Server is already running.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
serverSocket = new ServerSocket(port);
|
||||||
|
running.set(true);
|
||||||
|
|
||||||
|
System.out.println("Dashboard Server started on port " + port);
|
||||||
|
System.out.println("Waiting for statistics updates from simulation processes...");
|
||||||
|
System.out.println("=".repeat(60));
|
||||||
|
|
||||||
|
Thread acceptThread = new Thread(this::acceptConnections, "DashboardServer-Accept");
|
||||||
|
acceptThread.setDaemon(false);
|
||||||
|
acceptThread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void acceptConnections() {
|
||||||
|
while (running.get()) {
|
||||||
|
try {
|
||||||
|
Socket clientSocket = serverSocket.accept();
|
||||||
|
System.out.println("[Connection] New client connected: " +
|
||||||
|
clientSocket.getInetAddress().getHostAddress() + ":" + clientSocket.getPort());
|
||||||
|
|
||||||
|
clientHandlerPool.execute(new DashboardClientHandler(clientSocket, statistics));
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (running.get()) {
|
||||||
|
System.err.println("[Error] Failed to accept client connection: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("BusyWait")
|
||||||
|
private void displayLoop() {
|
||||||
|
final long DISPLAY_INTERVAL_MS = 5000;
|
||||||
|
|
||||||
|
while (running.get()) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(DISPLAY_INTERVAL_MS);
|
||||||
|
displayStatistics();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void displayStatistics() {
|
||||||
|
System.out.println("\n" + "=".repeat(60));
|
||||||
|
System.out.println("REAL-TIME SIMULATION STATISTICS");
|
||||||
|
System.out.println("=".repeat(60));
|
||||||
|
statistics.display();
|
||||||
|
System.out.println("=".repeat(60));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
if (!running.get()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("\nStopping Dashboard Server...");
|
||||||
|
running.set(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (serverSocket != null && !serverSocket.isClosed()) {
|
||||||
|
serverSocket.close();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println("Error closing server socket: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
clientHandlerPool.shutdownNow();
|
||||||
|
System.out.println("Dashboard Server stopped.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public DashboardStatistics getStatistics() {
|
||||||
|
return statistics;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRunning() {
|
||||||
|
return running.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
214
main/src/main/java/sd/dashboard/DashboardStatistics.java
Normal file
214
main/src/main/java/sd/dashboard/DashboardStatistics.java
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
package sd.dashboard;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
import sd.model.VehicleType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread-safe storage for aggregated simulation statistics.
|
||||||
|
* Uses atomic types and concurrent collections for lock-free updates.
|
||||||
|
*/
|
||||||
|
public class DashboardStatistics {
|
||||||
|
|
||||||
|
private final AtomicInteger totalVehiclesGenerated;
|
||||||
|
private final AtomicInteger totalVehiclesCompleted;
|
||||||
|
private final AtomicLong totalSystemTime;
|
||||||
|
private final AtomicLong totalWaitingTime;
|
||||||
|
|
||||||
|
private final Map<String, IntersectionStats> intersectionStats;
|
||||||
|
private final Map<VehicleType, AtomicInteger> vehicleTypeCount;
|
||||||
|
private final Map<VehicleType, AtomicLong> vehicleTypeWaitTime;
|
||||||
|
|
||||||
|
private volatile long lastUpdateTime;
|
||||||
|
|
||||||
|
public DashboardStatistics() {
|
||||||
|
this.totalVehiclesGenerated = new AtomicInteger(0);
|
||||||
|
this.totalVehiclesCompleted = new AtomicInteger(0);
|
||||||
|
this.totalSystemTime = new AtomicLong(0);
|
||||||
|
this.totalWaitingTime = new AtomicLong(0);
|
||||||
|
|
||||||
|
this.intersectionStats = new ConcurrentHashMap<>();
|
||||||
|
this.vehicleTypeCount = new ConcurrentHashMap<>();
|
||||||
|
this.vehicleTypeWaitTime = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
for (VehicleType type : VehicleType.values()) {
|
||||||
|
vehicleTypeCount.put(type, new AtomicInteger(0));
|
||||||
|
vehicleTypeWaitTime.put(type, new AtomicLong(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastUpdateTime = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateVehiclesGenerated(int count) {
|
||||||
|
totalVehiclesGenerated.set(count);
|
||||||
|
updateTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void incrementVehiclesGenerated() {
|
||||||
|
totalVehiclesGenerated.incrementAndGet();
|
||||||
|
updateTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateVehiclesCompleted(int count) {
|
||||||
|
totalVehiclesCompleted.set(count);
|
||||||
|
updateTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void incrementVehiclesCompleted() {
|
||||||
|
totalVehiclesCompleted.incrementAndGet();
|
||||||
|
updateTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addSystemTime(long timeMs) {
|
||||||
|
totalSystemTime.addAndGet(timeMs);
|
||||||
|
updateTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addWaitingTime(long timeMs) {
|
||||||
|
totalWaitingTime.addAndGet(timeMs);
|
||||||
|
updateTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateVehicleTypeStats(VehicleType type, int count, long waitTimeMs) {
|
||||||
|
vehicleTypeCount.get(type).set(count);
|
||||||
|
vehicleTypeWaitTime.get(type).set(waitTimeMs);
|
||||||
|
updateTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void incrementVehicleType(VehicleType type) {
|
||||||
|
vehicleTypeCount.get(type).incrementAndGet();
|
||||||
|
updateTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateIntersectionStats(String intersectionId, int arrivals,
|
||||||
|
int departures, int currentQueueSize) {
|
||||||
|
intersectionStats.compute(intersectionId, (id, stats) -> {
|
||||||
|
if (stats == null) {
|
||||||
|
stats = new IntersectionStats(intersectionId);
|
||||||
|
}
|
||||||
|
stats.updateStats(arrivals, departures, currentQueueSize);
|
||||||
|
return stats;
|
||||||
|
});
|
||||||
|
updateTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTimestamp() {
|
||||||
|
lastUpdateTime = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalVehiclesGenerated() {
|
||||||
|
return totalVehiclesGenerated.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalVehiclesCompleted() {
|
||||||
|
return totalVehiclesCompleted.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getAverageSystemTime() {
|
||||||
|
int completed = totalVehiclesCompleted.get();
|
||||||
|
if (completed == 0) return 0.0;
|
||||||
|
return (double) totalSystemTime.get() / completed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getAverageWaitingTime() {
|
||||||
|
int completed = totalVehiclesCompleted.get();
|
||||||
|
if (completed == 0) return 0.0;
|
||||||
|
return (double) totalWaitingTime.get() / completed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getVehicleTypeCount(VehicleType type) {
|
||||||
|
return vehicleTypeCount.get(type).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getAverageWaitingTimeByType(VehicleType type) {
|
||||||
|
int count = vehicleTypeCount.get(type).get();
|
||||||
|
if (count == 0) return 0.0;
|
||||||
|
return (double) vehicleTypeWaitTime.get(type).get() / count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IntersectionStats getIntersectionStats(String intersectionId) {
|
||||||
|
return intersectionStats.get(intersectionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, IntersectionStats> getAllIntersectionStats() {
|
||||||
|
return new HashMap<>(intersectionStats);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLastUpdateTime() {
|
||||||
|
return lastUpdateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void display() {
|
||||||
|
System.out.println("\n--- GLOBAL STATISTICS ---");
|
||||||
|
System.out.printf("Total Vehicles Generated: %d%n", getTotalVehiclesGenerated());
|
||||||
|
System.out.printf("Total Vehicles Completed: %d%n", getTotalVehiclesCompleted());
|
||||||
|
System.out.printf("Vehicles In Transit: %d%n",
|
||||||
|
getTotalVehiclesGenerated() - getTotalVehiclesCompleted());
|
||||||
|
System.out.printf("Average System Time: %.2f ms%n", getAverageSystemTime());
|
||||||
|
System.out.printf("Average Waiting Time: %.2f ms%n", getAverageWaitingTime());
|
||||||
|
|
||||||
|
System.out.println("\n--- VEHICLE TYPE STATISTICS ---");
|
||||||
|
for (VehicleType type : VehicleType.values()) {
|
||||||
|
int count = getVehicleTypeCount(type);
|
||||||
|
double avgWait = getAverageWaitingTimeByType(type);
|
||||||
|
System.out.printf("%s: %d vehicles, avg wait: %.2f ms%n",
|
||||||
|
type, count, avgWait);
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("\n--- INTERSECTION STATISTICS ---");
|
||||||
|
if (intersectionStats.isEmpty()) {
|
||||||
|
System.out.println("(No data received yet)");
|
||||||
|
} else {
|
||||||
|
for (IntersectionStats stats : intersectionStats.values()) {
|
||||||
|
stats.display();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.printf("%nLast Update: %tT%n", lastUpdateTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class IntersectionStats {
|
||||||
|
private final String intersectionId;
|
||||||
|
private final AtomicInteger totalArrivals;
|
||||||
|
private final AtomicInteger totalDepartures;
|
||||||
|
private final AtomicInteger currentQueueSize;
|
||||||
|
|
||||||
|
public IntersectionStats(String intersectionId) {
|
||||||
|
this.intersectionId = intersectionId;
|
||||||
|
this.totalArrivals = new AtomicInteger(0);
|
||||||
|
this.totalDepartures = new AtomicInteger(0);
|
||||||
|
this.currentQueueSize = new AtomicInteger(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateStats(int arrivals, int departures, int queueSize) {
|
||||||
|
this.totalArrivals.set(arrivals);
|
||||||
|
this.totalDepartures.set(departures);
|
||||||
|
this.currentQueueSize.set(queueSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIntersectionId() {
|
||||||
|
return intersectionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalArrivals() {
|
||||||
|
return totalArrivals.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalDepartures() {
|
||||||
|
return totalDepartures.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCurrentQueueSize() {
|
||||||
|
return currentQueueSize.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void display() {
|
||||||
|
System.out.printf("%s: Arrivals=%d, Departures=%d, Queue=%d%n",
|
||||||
|
intersectionId, getTotalArrivals(), getTotalDepartures(), getCurrentQueueSize());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
48
main/src/main/java/sd/dashboard/StatsMessage.java
Normal file
48
main/src/main/java/sd/dashboard/StatsMessage.java
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package sd.dashboard;
|
||||||
|
|
||||||
|
import sd.model.MessageType;
|
||||||
|
import sd.protocol.MessageProtocol;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message wrapper for sending statistics to the dashboard.
|
||||||
|
*/
|
||||||
|
public class StatsMessage implements MessageProtocol {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private final String sourceNode;
|
||||||
|
private final String destinationNode;
|
||||||
|
private final StatsUpdatePayload payload;
|
||||||
|
|
||||||
|
public StatsMessage(String sourceNode, StatsUpdatePayload payload) {
|
||||||
|
this.sourceNode = sourceNode;
|
||||||
|
this.destinationNode = "DashboardServer";
|
||||||
|
this.payload = payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageType getType() {
|
||||||
|
return MessageType.STATS_UPDATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getPayload() {
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSourceNode() {
|
||||||
|
return sourceNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDestinationNode() {
|
||||||
|
return destinationNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("StatsMessage[from=%s, to=%s, payload=%s]",
|
||||||
|
sourceNode, destinationNode, payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
121
main/src/main/java/sd/dashboard/StatsUpdatePayload.java
Normal file
121
main/src/main/java/sd/dashboard/StatsUpdatePayload.java
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
package sd.dashboard;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import sd.model.VehicleType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data transfer object for statistics updates to the dashboard.
|
||||||
|
* Use -1 for fields not being updated in this message.
|
||||||
|
*/
|
||||||
|
public class StatsUpdatePayload implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private int totalVehiclesGenerated = -1;
|
||||||
|
private int totalVehiclesCompleted = -1;
|
||||||
|
private long totalSystemTime = -1;
|
||||||
|
private long totalWaitingTime = -1;
|
||||||
|
|
||||||
|
private int intersectionArrivals = 0;
|
||||||
|
private int intersectionDepartures = 0;
|
||||||
|
private int intersectionQueueSize = 0;
|
||||||
|
|
||||||
|
private Map<VehicleType, Integer> vehicleTypeCounts;
|
||||||
|
private Map<VehicleType, Long> vehicleTypeWaitTimes;
|
||||||
|
|
||||||
|
public StatsUpdatePayload() {
|
||||||
|
this.vehicleTypeCounts = new HashMap<>();
|
||||||
|
this.vehicleTypeWaitTimes = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalVehiclesGenerated() {
|
||||||
|
return totalVehiclesGenerated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalVehiclesCompleted() {
|
||||||
|
return totalVehiclesCompleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTotalSystemTime() {
|
||||||
|
return totalSystemTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTotalWaitingTime() {
|
||||||
|
return totalWaitingTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIntersectionArrivals() {
|
||||||
|
return intersectionArrivals;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIntersectionDepartures() {
|
||||||
|
return intersectionDepartures;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIntersectionQueueSize() {
|
||||||
|
return intersectionQueueSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<VehicleType, Integer> getVehicleTypeCounts() {
|
||||||
|
return vehicleTypeCounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<VehicleType, Long> getVehicleTypeWaitTimes() {
|
||||||
|
return vehicleTypeWaitTimes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StatsUpdatePayload setTotalVehiclesGenerated(int totalVehiclesGenerated) {
|
||||||
|
this.totalVehiclesGenerated = totalVehiclesGenerated;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StatsUpdatePayload setTotalVehiclesCompleted(int totalVehiclesCompleted) {
|
||||||
|
this.totalVehiclesCompleted = totalVehiclesCompleted;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StatsUpdatePayload setTotalSystemTime(long totalSystemTime) {
|
||||||
|
this.totalSystemTime = totalSystemTime;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StatsUpdatePayload setTotalWaitingTime(long totalWaitingTime) {
|
||||||
|
this.totalWaitingTime = totalWaitingTime;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StatsUpdatePayload setIntersectionArrivals(int intersectionArrivals) {
|
||||||
|
this.intersectionArrivals = intersectionArrivals;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StatsUpdatePayload setIntersectionDepartures(int intersectionDepartures) {
|
||||||
|
this.intersectionDepartures = intersectionDepartures;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StatsUpdatePayload setIntersectionQueueSize(int intersectionQueueSize) {
|
||||||
|
this.intersectionQueueSize = intersectionQueueSize;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StatsUpdatePayload setVehicleTypeCounts(Map<VehicleType, Integer> vehicleTypeCounts) {
|
||||||
|
this.vehicleTypeCounts = vehicleTypeCounts;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StatsUpdatePayload setVehicleTypeWaitTimes(Map<VehicleType, Long> vehicleTypeWaitTimes) {
|
||||||
|
this.vehicleTypeWaitTimes = vehicleTypeWaitTimes;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("StatsUpdatePayload[generated=%d, completed=%d, arrivals=%d, departures=%d, queueSize=%d]",
|
||||||
|
totalVehiclesGenerated, totalVehiclesCompleted, intersectionArrivals,
|
||||||
|
intersectionDepartures, intersectionQueueSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,150 +9,107 @@ import sd.model.Vehicle;
|
|||||||
/**
|
/**
|
||||||
* Implements the control logic for a single TrafficLight
|
* Implements the control logic for a single TrafficLight
|
||||||
* as a Runnable task that runs in its own Thread.
|
* as a Runnable task that runs in its own Thread.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public class TrafficLightThread implements Runnable {
|
public class TrafficLightThread implements Runnable {
|
||||||
|
|
||||||
/**
|
|
||||||
* The TrafficLight object (the *model*) that this thread controls.
|
|
||||||
* Contains the queue and the state.
|
|
||||||
*/
|
|
||||||
private final TrafficLight light;
|
private final TrafficLight light;
|
||||||
|
|
||||||
/**
|
|
||||||
* The IntersectionProcess (the Process) that "owns" this thread.
|
|
||||||
* Used to call methods on the process, such as sendVehicleToNextDestination().
|
|
||||||
*/
|
|
||||||
private final IntersectionProcess process;
|
private final IntersectionProcess process;
|
||||||
|
|
||||||
/**
|
|
||||||
* The simulation configuration, used to get timings (e.g., crossing time).
|
|
||||||
*/
|
|
||||||
private final SimulationConfig config;
|
private final SimulationConfig config;
|
||||||
|
|
||||||
/**
|
|
||||||
* Volatile flag to control the graceful shutdown mechanism.
|
|
||||||
* When set to 'false', the 'run()' loop terminates.
|
|
||||||
*/
|
|
||||||
private volatile boolean running;
|
private volatile boolean running;
|
||||||
|
|
||||||
/**
|
// Store the thread reference for proper interruption
|
||||||
* Constructor for the Traffic Light Thread.
|
private Thread currentThread;
|
||||||
*
|
|
||||||
* @param light The TrafficLight object (model) to be controlled.
|
|
||||||
* @param process The parent IntersectionProcess (for callbacks).
|
|
||||||
* @param config The simulation configuration (to get timings).
|
|
||||||
*/
|
|
||||||
public TrafficLightThread(TrafficLight light, IntersectionProcess process, SimulationConfig config) {
|
public TrafficLightThread(TrafficLight light, IntersectionProcess process, SimulationConfig config) {
|
||||||
this.light = light;
|
this.light = light;
|
||||||
this.process = process;
|
this.process = process;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.running = false; // Starts as 'stopped'
|
this.running = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The main entry point for the thread.
|
|
||||||
* Implements the GREEN/RED cycle logic extracted from IntersectionProcess.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
this.currentThread = Thread.currentThread();
|
||||||
this.running = true;
|
this.running = true;
|
||||||
System.out.println("[" + light.getId() + "] Traffic light thread started.");
|
System.out.println("[" + light.getId() + "] Traffic light thread started.");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Main thread loop, continues while 'running' is true
|
while (running && !Thread.currentThread().isInterrupted()) {
|
||||||
// This 'running' flag is controlled by the parent IntersectionProcess
|
|
||||||
while (running) {
|
|
||||||
|
|
||||||
|
// Request permission to turn green (blocks until granted)
|
||||||
|
process.requestGreenLight(light.getDirection());
|
||||||
|
|
||||||
|
try {
|
||||||
// --- GREEN Phase ---
|
// --- GREEN Phase ---
|
||||||
light.changeState(TrafficLightState.GREEN); //
|
light.changeState(TrafficLightState.GREEN);
|
||||||
System.out.println("[" + light.getId() + "] State: GREEN");
|
System.out.println("[" + light.getId() + "] State: GREEN");
|
||||||
|
|
||||||
// Process vehicles in the queue
|
|
||||||
processGreenLightQueue();
|
processGreenLightQueue();
|
||||||
|
|
||||||
// Wait for green duration
|
if (!running || Thread.currentThread().isInterrupted()) break;
|
||||||
Thread.sleep((long) (light.getGreenTime() * 1000)); //
|
|
||||||
|
|
||||||
if (!running) break; // Check flag after sleep
|
// Wait for green duration
|
||||||
|
Thread.sleep((long) (light.getGreenTime() * 1000));
|
||||||
|
|
||||||
|
if (!running || Thread.currentThread().isInterrupted()) break;
|
||||||
|
|
||||||
// --- RED Phase ---
|
// --- RED Phase ---
|
||||||
light.changeState(TrafficLightState.RED); //
|
light.changeState(TrafficLightState.RED);
|
||||||
System.out.println("[" + light.getId() + "] State: RED");
|
System.out.println("[" + light.getId() + "] State: RED");
|
||||||
|
|
||||||
// Wait for red duration
|
} finally {
|
||||||
Thread.sleep((long) (light.getRedTime() * 1000)); //
|
// Always release the green light permission
|
||||||
|
process.releaseGreenLight(light.getDirection());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for red duration
|
||||||
|
Thread.sleep((long) (light.getRedTime() * 1000));
|
||||||
}
|
}
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
// Apanha a InterruptedException (outra forma de parar a thread)
|
|
||||||
System.out.println("[" + light.getId() + "] Traffic light thread interrupted.");
|
System.out.println("[" + light.getId() + "] Traffic light thread interrupted.");
|
||||||
this.running = false; // Garante que o loop termina
|
Thread.currentThread().interrupt();
|
||||||
}
|
} finally {
|
||||||
|
this.running = false;
|
||||||
System.out.println("[" + light.getId() + "] Traffic light thread stopped.");
|
System.out.println("[" + light.getId() + "] Traffic light thread stopped.");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Processes vehicles in the queue while the traffic light is GREEN.
|
|
||||||
* Logic extracted from IntersectionProcess.processGreenLight()
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
private void processGreenLightQueue() throws InterruptedException {
|
private void processGreenLightQueue() throws InterruptedException {
|
||||||
//
|
while (running && !Thread.currentThread().isInterrupted()
|
||||||
while (running && light.getState() == TrafficLightState.GREEN && light.getQueueSize() > 0) {
|
&& light.getState() == TrafficLightState.GREEN
|
||||||
|
&& light.getQueueSize() > 0) {
|
||||||
|
|
||||||
Vehicle vehicle = light.removeVehicle(); //
|
Vehicle vehicle = light.removeVehicle();
|
||||||
|
|
||||||
if (vehicle != null) {
|
if (vehicle != null) {
|
||||||
// 1. Get the crossing time (t_sem)
|
double crossingTime = getCrossingTimeForVehicle(vehicle);
|
||||||
double crossingTime = getCrossingTimeForVehicle(vehicle); //
|
|
||||||
|
|
||||||
// 2. Simulate the time the vehicle takes to cross
|
Thread.sleep((long) (crossingTime * 1000));
|
||||||
Thread.sleep((long) (crossingTime * 1000)); //
|
|
||||||
|
|
||||||
// 3. Update vehicle statistics
|
vehicle.addCrossingTime(crossingTime);
|
||||||
vehicle.addCrossingTime(crossingTime); //
|
process.getIntersection().incrementVehiclesSent();
|
||||||
|
process.sendVehicleToNextDestination(vehicle);
|
||||||
// 4. Update intersection statistics
|
|
||||||
|
|
||||||
process.getIntersection().incrementVehiclesSent(); //
|
|
||||||
|
|
||||||
// 5. Call the parent Process to send the vehicle
|
|
||||||
|
|
||||||
process.sendVehicleToNextDestination(vehicle); //
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the crossing time for a vehicle based on its type.
|
|
||||||
* Logic extracted from IntersectionProcess.getCrossingTimeForVehicle()
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param vehicle The vehicle.
|
|
||||||
* @return The crossing time in seconds.
|
|
||||||
*/
|
|
||||||
private double getCrossingTimeForVehicle(Vehicle vehicle) {
|
private double getCrossingTimeForVehicle(Vehicle vehicle) {
|
||||||
switch (vehicle.getType()) { //
|
return switch (vehicle.getType()) {
|
||||||
case BIKE:
|
case BIKE -> config.getBikeVehicleCrossingTime();
|
||||||
return config.getBikeVehicleCrossingTime(); //
|
case LIGHT -> config.getLightVehicleCrossingTime();
|
||||||
case LIGHT:
|
case HEAVY -> config.getHeavyVehicleCrossingTime();
|
||||||
return config.getLightVehicleCrossingTime(); //
|
default -> config.getLightVehicleCrossingTime();
|
||||||
case HEAVY:
|
};
|
||||||
return config.getHeavyVehicleCrossingTime(); //
|
|
||||||
default:
|
|
||||||
return config.getLightVehicleCrossingTime(); //
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests the thread to stop gracefully (graceful shutdown).
|
* Requests the thread to stop gracefully.
|
||||||
* Sets the 'running' flag to false. The thread will finish
|
* Sets the running flag and interrupts the thread to unblock any sleep() calls.
|
||||||
* its current sleep cycle and exit the 'run()' loop.
|
|
||||||
*/
|
*/
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
this.running = false;
|
this.running = false;
|
||||||
|
if (currentThread != null && currentThread.isAlive()) {
|
||||||
|
currentThread.interrupt();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.ObjectOutputStream;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@@ -19,6 +19,7 @@ import sd.IntersectionProcess;
|
|||||||
import sd.model.MessageType;
|
import sd.model.MessageType;
|
||||||
import sd.model.Vehicle;
|
import sd.model.Vehicle;
|
||||||
import sd.model.VehicleType;
|
import sd.model.VehicleType;
|
||||||
|
import sd.protocol.SocketConnection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for IntersectionProcess - covers initialization, traffic lights,
|
* Tests for IntersectionProcess - covers initialization, traffic lights,
|
||||||
@@ -96,11 +97,17 @@ public class IntersectionProcessTest {
|
|||||||
Files.writeString(configFile, configContent);
|
Files.writeString(configFile, configContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// cleanup after tests
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
if (intersectionProcess != null) {
|
if (intersectionProcess != null) {
|
||||||
|
try {
|
||||||
|
// Only shutdown if still running
|
||||||
intersectionProcess.shutdown();
|
intersectionProcess.shutdown();
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("Error in tearDown: " + e.getMessage());
|
||||||
|
} finally {
|
||||||
|
intersectionProcess = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +194,7 @@ public class IntersectionProcessTest {
|
|||||||
intersectionProcess = new IntersectionProcess("Cr1", configFile.toString());
|
intersectionProcess = new IntersectionProcess("Cr1", configFile.toString());
|
||||||
intersectionProcess.initialize();
|
intersectionProcess.initialize();
|
||||||
|
|
||||||
// start server in seperate thread
|
// start server in separate thread
|
||||||
Thread serverThread = new Thread(() -> {
|
Thread serverThread = new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
intersectionProcess.start();
|
intersectionProcess.start();
|
||||||
@@ -197,13 +204,22 @@ public class IntersectionProcessTest {
|
|||||||
});
|
});
|
||||||
serverThread.start();
|
serverThread.start();
|
||||||
|
|
||||||
Thread.sleep(500); // wait for server to start
|
// Wait for server to actually start with retries
|
||||||
|
boolean serverReady = false;
|
||||||
// try connecting to check if its running
|
for (int i = 0; i < 20; i++) {
|
||||||
try (Socket clientSocket = new Socket("localhost", 18001)) {
|
Thread.sleep(100);
|
||||||
assertTrue(clientSocket.isConnected());
|
try (Socket testSocket = new Socket()) {
|
||||||
|
testSocket.connect(new java.net.InetSocketAddress("localhost", 18001), 500);
|
||||||
|
serverReady = true;
|
||||||
|
break;
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Server not ready yet, continue waiting
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assertTrue(serverReady, "Server should start and bind to port 18001");
|
||||||
|
|
||||||
|
// Shutdown immediately after confirming server is running
|
||||||
intersectionProcess.shutdown();
|
intersectionProcess.shutdown();
|
||||||
serverThread.join(2000);
|
serverThread.join(2000);
|
||||||
}
|
}
|
||||||
@@ -219,11 +235,17 @@ public class IntersectionProcessTest {
|
|||||||
cr2.initialize();
|
cr2.initialize();
|
||||||
|
|
||||||
Thread thread1 = new Thread(() -> {
|
Thread thread1 = new Thread(() -> {
|
||||||
try { cr1.start(); } catch (IOException e) { }
|
try {
|
||||||
|
cr1.start();
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Thread thread2 = new Thread(() -> {
|
Thread thread2 = new Thread(() -> {
|
||||||
try { cr2.start(); } catch (IOException e) { }
|
try {
|
||||||
|
cr2.start();
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
thread1.start();
|
thread1.start();
|
||||||
@@ -256,30 +278,32 @@ public class IntersectionProcessTest {
|
|||||||
Thread serverThread = new Thread(() -> {
|
Thread serverThread = new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
intersectionProcess.start();
|
intersectionProcess.start();
|
||||||
} catch (IOException e) { }
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
});
|
});
|
||||||
serverThread.start();
|
serverThread.start();
|
||||||
|
|
||||||
Thread.sleep(500);
|
Thread.sleep(500);
|
||||||
|
|
||||||
// create test vehicle
|
try {
|
||||||
|
// create test vehicle - FIXED: use 4-parameter constructor
|
||||||
java.util.List<String> route = Arrays.asList("Cr2", "Cr3", "S");
|
java.util.List<String> route = Arrays.asList("Cr2", "Cr3", "S");
|
||||||
Vehicle vehicle = new Vehicle("V001", VehicleType.LIGHT, 0.0, route);
|
Vehicle vehicle = new Vehicle("V001", VehicleType.LIGHT, 0.0, route);
|
||||||
|
|
||||||
// send vehicle from Cr1 to Cr2
|
// send vehicle from Cr1 to Cr2 - FIXED: use SocketConnection
|
||||||
try (Socket socket = new Socket("localhost", 18002)) {
|
try (Socket socket = new Socket("localhost", 18002);
|
||||||
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
|
SocketConnection conn = new SocketConnection(socket)) {
|
||||||
|
|
||||||
TestVehicleMessage message = new TestVehicleMessage("Cr1", "Cr2", vehicle);
|
TestVehicleMessage message = new TestVehicleMessage("Cr1", "Cr2", vehicle);
|
||||||
out.writeObject(message);
|
conn.sendMessage(message);
|
||||||
out.flush();
|
|
||||||
|
|
||||||
Thread.sleep(1000); // wait for procesing
|
Thread.sleep(1000); // wait for processing
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
intersectionProcess.shutdown();
|
intersectionProcess.shutdown();
|
||||||
serverThread.join(2000);
|
serverThread.join(2000);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// routing config tests
|
// routing config tests
|
||||||
|
|
||||||
@@ -312,7 +336,8 @@ public class IntersectionProcessTest {
|
|||||||
Thread serverThread = new Thread(() -> {
|
Thread serverThread = new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
intersectionProcess.start();
|
intersectionProcess.start();
|
||||||
} catch (IOException e) { }
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
});
|
});
|
||||||
serverThread.start();
|
serverThread.start();
|
||||||
|
|
||||||
@@ -330,30 +355,35 @@ public class IntersectionProcessTest {
|
|||||||
intersectionProcess = new IntersectionProcess("Cr1", configFile.toString());
|
intersectionProcess = new IntersectionProcess("Cr1", configFile.toString());
|
||||||
intersectionProcess.initialize();
|
intersectionProcess.initialize();
|
||||||
|
|
||||||
|
// Start server in separate thread
|
||||||
Thread serverThread = new Thread(() -> {
|
Thread serverThread = new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
intersectionProcess.start();
|
intersectionProcess.start();
|
||||||
} catch (IOException e) { }
|
} catch (IOException e) {
|
||||||
|
// Expected on shutdown
|
||||||
|
}
|
||||||
});
|
});
|
||||||
serverThread.start();
|
serverThread.start();
|
||||||
|
|
||||||
|
// Wait for server to start
|
||||||
Thread.sleep(500);
|
Thread.sleep(500);
|
||||||
|
|
||||||
// verify server running
|
// Shutdown
|
||||||
try (Socket socket = new Socket("localhost", 18001)) {
|
|
||||||
assertTrue(socket.isConnected());
|
|
||||||
}
|
|
||||||
|
|
||||||
intersectionProcess.shutdown();
|
intersectionProcess.shutdown();
|
||||||
serverThread.join(2000);
|
serverThread.join(2000);
|
||||||
|
|
||||||
// after shutdown conection should fail
|
// Give shutdown time to complete
|
||||||
Thread.sleep(500);
|
Thread.sleep(200);
|
||||||
Exception exception = assertThrows(IOException.class, () -> {
|
|
||||||
Socket socket = new Socket("localhost", 18001);
|
// Verify we cannot connect (server socket is closed)
|
||||||
socket.close();
|
boolean connectionFailed = false;
|
||||||
});
|
try (Socket testSocket = new Socket()) {
|
||||||
assertNotNull(exception);
|
testSocket.connect(new InetSocketAddress("localhost", 18001), 500);
|
||||||
|
} catch (IOException e) {
|
||||||
|
connectionFailed = true; // Expected - server should be closed
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue(connectionFailed, "Server socket should be closed after shutdown");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -365,7 +395,8 @@ public class IntersectionProcessTest {
|
|||||||
Thread serverThread = new Thread(() -> {
|
Thread serverThread = new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
intersectionProcess.start();
|
intersectionProcess.start();
|
||||||
} catch (IOException e) { }
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
});
|
});
|
||||||
serverThread.start();
|
serverThread.start();
|
||||||
|
|
||||||
@@ -388,20 +419,34 @@ public class IntersectionProcessTest {
|
|||||||
@Test
|
@Test
|
||||||
@Timeout(15)
|
@Timeout(15)
|
||||||
public void testIntegration_TwoIntersectionsVehicleTransfer() throws IOException, InterruptedException {
|
public void testIntegration_TwoIntersectionsVehicleTransfer() throws IOException, InterruptedException {
|
||||||
|
IntersectionProcess cr1 = null;
|
||||||
|
IntersectionProcess cr2 = null;
|
||||||
|
Thread thread1 = null;
|
||||||
|
Thread thread2 = null;
|
||||||
|
|
||||||
|
try {
|
||||||
// setup 2 intersections
|
// setup 2 intersections
|
||||||
IntersectionProcess cr1 = new IntersectionProcess("Cr1", configFile.toString());
|
cr1 = new IntersectionProcess("Cr1", configFile.toString());
|
||||||
IntersectionProcess cr2 = new IntersectionProcess("Cr2", configFile.toString());
|
cr2 = new IntersectionProcess("Cr2", configFile.toString());
|
||||||
|
|
||||||
cr1.initialize();
|
cr1.initialize();
|
||||||
cr2.initialize();
|
cr2.initialize();
|
||||||
|
|
||||||
// start both
|
// start both
|
||||||
Thread thread1 = new Thread(() -> {
|
final IntersectionProcess cr1Final = cr1;
|
||||||
try { cr1.start(); } catch (IOException e) { }
|
thread1 = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
cr1Final.start();
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Thread thread2 = new Thread(() -> {
|
final IntersectionProcess cr2Final = cr2;
|
||||||
try { cr2.start(); } catch (IOException e) { }
|
thread2 = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
cr2Final.start();
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
thread1.start();
|
thread1.start();
|
||||||
@@ -409,25 +454,34 @@ public class IntersectionProcessTest {
|
|||||||
|
|
||||||
Thread.sleep(1000); // wait for servers
|
Thread.sleep(1000); // wait for servers
|
||||||
|
|
||||||
// send vehicle to Cr1 that goes to Cr2
|
// send vehicle to Cr1 that goes to Cr2 - FIXED: use 4-parameter constructor
|
||||||
java.util.List<String> route = Arrays.asList("Cr1", "Cr2", "S");
|
java.util.List<String> route = Arrays.asList("Cr1", "Cr2", "S");
|
||||||
Vehicle vehicle = new Vehicle("V001", VehicleType.LIGHT, 0.0, route);
|
Vehicle vehicle = new Vehicle("V001", VehicleType.LIGHT, 0.0, route);
|
||||||
|
|
||||||
try (Socket socket = new Socket("localhost", 18001)) {
|
// FIXED: use SocketConnection
|
||||||
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
|
try (Socket socket = new Socket("localhost", 18001);
|
||||||
|
SocketConnection conn = new SocketConnection(socket)) {
|
||||||
|
|
||||||
TestVehicleMessage message = new TestVehicleMessage("Entry", "Cr1", vehicle);
|
TestVehicleMessage message = new TestVehicleMessage("Entry", "Cr1", vehicle);
|
||||||
out.writeObject(message);
|
conn.sendMessage(message);
|
||||||
out.flush();
|
|
||||||
|
|
||||||
Thread.sleep(2000); // time for processing
|
Thread.sleep(2000); // time for processing
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
if (cr1 != null) {
|
||||||
cr1.shutdown();
|
cr1.shutdown();
|
||||||
|
}
|
||||||
|
if (cr2 != null) {
|
||||||
cr2.shutdown();
|
cr2.shutdown();
|
||||||
|
}
|
||||||
|
if (thread1 != null) {
|
||||||
thread1.join(2000);
|
thread1.join(2000);
|
||||||
|
}
|
||||||
|
if (thread2 != null) {
|
||||||
thread2.join(2000);
|
thread2.join(2000);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMain_MissingArguments() {
|
public void testMain_MissingArguments() {
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
package sd;
|
package sd;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.AfterEach;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
|
|
||||||
import sd.model.TrafficLight;
|
|
||||||
import sd.model.TrafficLightState;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import sd.model.TrafficLight;
|
||||||
|
import sd.model.TrafficLightState;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test class to verify traffic light coordination within an intersection.
|
* Test class to verify traffic light coordination within an intersection.
|
||||||
@@ -108,7 +108,7 @@ public class TrafficLightCoordinationTest {
|
|||||||
assertTrue(maxGreenSimultaneously.get() <= 1,
|
assertTrue(maxGreenSimultaneously.get() <= 1,
|
||||||
"At most ONE light should be GREEN at any time. Found: " + maxGreenSimultaneously.get());
|
"At most ONE light should be GREEN at any time. Found: " + maxGreenSimultaneously.get());
|
||||||
|
|
||||||
System.out.println("\n✅ Traffic light coordination working correctly!");
|
System.out.println("\nTraffic light coordination working correctly!");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
164
main/src/test/java/sd/dashboard/DashboardTest.java
Normal file
164
main/src/test/java/sd/dashboard/DashboardTest.java
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
package sd.dashboard;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import sd.config.SimulationConfig;
|
||||||
|
import sd.model.VehicleType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for Dashboard Server components.
|
||||||
|
*/
|
||||||
|
class DashboardTest {
|
||||||
|
|
||||||
|
private DashboardStatistics statistics;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
statistics = new DashboardStatistics();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void tearDown() {
|
||||||
|
statistics = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testInitialStatistics() {
|
||||||
|
assertEquals(0, statistics.getTotalVehiclesGenerated(),
|
||||||
|
"Initial vehicles generated should be 0");
|
||||||
|
assertEquals(0, statistics.getTotalVehiclesCompleted(),
|
||||||
|
"Initial vehicles completed should be 0");
|
||||||
|
assertEquals(0.0, statistics.getAverageSystemTime(),
|
||||||
|
"Initial average system time should be 0.0");
|
||||||
|
assertEquals(0.0, statistics.getAverageWaitingTime(),
|
||||||
|
"Initial average waiting time should be 0.0");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testVehicleCounters() {
|
||||||
|
statistics.incrementVehiclesGenerated();
|
||||||
|
assertEquals(1, statistics.getTotalVehiclesGenerated());
|
||||||
|
|
||||||
|
statistics.updateVehiclesGenerated(10);
|
||||||
|
assertEquals(10, statistics.getTotalVehiclesGenerated());
|
||||||
|
|
||||||
|
statistics.incrementVehiclesCompleted();
|
||||||
|
assertEquals(1, statistics.getTotalVehiclesCompleted());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAverageCalculations() {
|
||||||
|
// Add 3 completed vehicles with known times
|
||||||
|
statistics.updateVehiclesCompleted(3);
|
||||||
|
statistics.addSystemTime(3000); // 3000ms total
|
||||||
|
statistics.addWaitingTime(1500); // 1500ms total
|
||||||
|
|
||||||
|
assertEquals(1000.0, statistics.getAverageSystemTime(), 0.01,
|
||||||
|
"Average system time should be 1000ms");
|
||||||
|
assertEquals(500.0, statistics.getAverageWaitingTime(), 0.01,
|
||||||
|
"Average waiting time should be 500ms");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testVehicleTypeStatistics() {
|
||||||
|
statistics.incrementVehicleType(VehicleType.LIGHT);
|
||||||
|
statistics.incrementVehicleType(VehicleType.LIGHT);
|
||||||
|
statistics.incrementVehicleType(VehicleType.HEAVY);
|
||||||
|
|
||||||
|
assertEquals(2, statistics.getVehicleTypeCount(VehicleType.LIGHT));
|
||||||
|
assertEquals(1, statistics.getVehicleTypeCount(VehicleType.HEAVY));
|
||||||
|
assertEquals(0, statistics.getVehicleTypeCount(VehicleType.BIKE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testIntersectionStatistics() {
|
||||||
|
statistics.updateIntersectionStats("Cr1", 10, 8, 2);
|
||||||
|
|
||||||
|
DashboardStatistics.IntersectionStats stats =
|
||||||
|
statistics.getIntersectionStats("Cr1");
|
||||||
|
|
||||||
|
assertNotNull(stats, "Intersection stats should not be null");
|
||||||
|
assertEquals("Cr1", stats.getIntersectionId());
|
||||||
|
assertEquals(10, stats.getTotalArrivals());
|
||||||
|
assertEquals(8, stats.getTotalDepartures());
|
||||||
|
assertEquals(2, stats.getCurrentQueueSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMultipleIntersections() {
|
||||||
|
statistics.updateIntersectionStats("Cr1", 10, 8, 2);
|
||||||
|
statistics.updateIntersectionStats("Cr2", 15, 12, 3);
|
||||||
|
statistics.updateIntersectionStats("Cr3", 5, 5, 0);
|
||||||
|
|
||||||
|
assertEquals(3, statistics.getAllIntersectionStats().size(),
|
||||||
|
"Should have 3 intersections");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testStatsUpdatePayload() {
|
||||||
|
StatsUpdatePayload payload = new StatsUpdatePayload()
|
||||||
|
.setTotalVehiclesGenerated(50)
|
||||||
|
.setTotalVehiclesCompleted(20)
|
||||||
|
.setIntersectionArrivals(30)
|
||||||
|
.setIntersectionDepartures(25)
|
||||||
|
.setIntersectionQueueSize(5);
|
||||||
|
|
||||||
|
assertEquals(50, payload.getTotalVehiclesGenerated());
|
||||||
|
assertEquals(20, payload.getTotalVehiclesCompleted());
|
||||||
|
assertEquals(30, payload.getIntersectionArrivals());
|
||||||
|
assertEquals(25, payload.getIntersectionDepartures());
|
||||||
|
assertEquals(5, payload.getIntersectionQueueSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testStatsMessage() {
|
||||||
|
StatsUpdatePayload payload = new StatsUpdatePayload()
|
||||||
|
.setIntersectionArrivals(10);
|
||||||
|
|
||||||
|
StatsMessage message = new StatsMessage("Cr1", payload);
|
||||||
|
|
||||||
|
assertEquals("Cr1", message.getSourceNode());
|
||||||
|
assertEquals("DashboardServer", message.getDestinationNode());
|
||||||
|
assertEquals(sd.model.MessageType.STATS_UPDATE, message.getType());
|
||||||
|
assertNotNull(message.getPayload());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testThreadSafety() throws InterruptedException {
|
||||||
|
// Test concurrent updates
|
||||||
|
Thread t1 = new Thread(() -> {
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
statistics.incrementVehiclesGenerated();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Thread t2 = new Thread(() -> {
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
statistics.incrementVehiclesGenerated();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
t1.start();
|
||||||
|
t2.start();
|
||||||
|
t1.join();
|
||||||
|
t2.join();
|
||||||
|
|
||||||
|
assertEquals(200, statistics.getTotalVehiclesGenerated(),
|
||||||
|
"Concurrent increments should total 200");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDashboardServerCreation() throws Exception {
|
||||||
|
SimulationConfig config = new SimulationConfig("simulation.properties");
|
||||||
|
DashboardServer server = new DashboardServer(config);
|
||||||
|
|
||||||
|
assertNotNull(server, "Server should be created successfully");
|
||||||
|
assertNotNull(server.getStatistics(), "Statistics should be initialized");
|
||||||
|
assertFalse(server.isRunning(), "Server should not be running initially");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user