mirror of
https://github.com/davidalves04/Trabalho-Pratico-SD.git
synced 2025-12-08 12:33:31 +00:00
bullshit fixes
This commit is contained in:
@@ -22,41 +22,43 @@ 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 {
|
||||||
|
|
||||||
private final String intersectionId;
|
private final String intersectionId;
|
||||||
|
|
||||||
private final SimulationConfig config;
|
private final SimulationConfig config;
|
||||||
|
|
||||||
private final Intersection intersection;
|
private final Intersection intersection;
|
||||||
|
|
||||||
private ServerSocket serverSocket;
|
private ServerSocket serverSocket;
|
||||||
|
|
||||||
private final Map<String, SocketConnection> outgoingConnections;
|
private final Map<String, SocketConnection> outgoingConnections;
|
||||||
|
|
||||||
private final ExecutorService connectionHandlerPool;
|
private final ExecutorService connectionHandlerPool;
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tracks which direction currently has the green light.
|
* Tracks which direction currently has the green light.
|
||||||
* null means no direction is currently green (all are red).
|
* null means no direction is currently green (all are red).
|
||||||
*/
|
*/
|
||||||
private volatile String currentGreenDirection;
|
private volatile String currentGreenDirection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new IntersectionProcess.
|
* Constructs a new IntersectionProcess.
|
||||||
*
|
*
|
||||||
@@ -74,113 +76,113 @@ public class IntersectionProcess {
|
|||||||
this.running = false;
|
this.running = false;
|
||||||
this.trafficCoordinationLock = new ReentrantLock();
|
this.trafficCoordinationLock = new ReentrantLock();
|
||||||
this.currentGreenDirection = null;
|
this.currentGreenDirection = null;
|
||||||
|
|
||||||
System.out.println("=".repeat(60));
|
System.out.println("=".repeat(60));
|
||||||
System.out.println("INTERSECTION PROCESS: " + intersectionId);
|
System.out.println("INTERSECTION PROCESS: " + intersectionId);
|
||||||
System.out.println("=".repeat(60));
|
System.out.println("=".repeat(60));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
System.out.println("\n[" + intersectionId + "] Initializing intersection...");
|
System.out.println("\n[" + intersectionId + "] Initializing intersection...");
|
||||||
|
|
||||||
createTrafficLights();
|
createTrafficLights();
|
||||||
|
|
||||||
configureRouting();
|
configureRouting();
|
||||||
|
|
||||||
System.out.println("[" + intersectionId + "] Initialization complete.");
|
System.out.println("[" + intersectionId + "] Initialization complete.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
*/
|
*/
|
||||||
private void createTrafficLights() {
|
private void createTrafficLights() {
|
||||||
System.out.println("\n[" + intersectionId + "] Creating traffic lights...");
|
System.out.println("\n[" + intersectionId + "] Creating traffic lights...");
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (String direction : directions) {
|
for (String direction : directions) {
|
||||||
double greenTime = config.getTrafficLightGreenTime(intersectionId, direction);
|
double greenTime = config.getTrafficLightGreenTime(intersectionId, direction);
|
||||||
double redTime = config.getTrafficLightRedTime(intersectionId, direction);
|
double redTime = config.getTrafficLightRedTime(intersectionId, direction);
|
||||||
|
|
||||||
TrafficLight light = new TrafficLight(
|
TrafficLight light = new TrafficLight(
|
||||||
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 +
|
||||||
" (Green: " + greenTime + "s, Red: " + redTime + "s)");
|
" (Green: " + greenTime + "s, Red: " + redTime + "s)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void configureRouting() {
|
private void configureRouting() {
|
||||||
System.out.println("\n[" + intersectionId + "] Configuring routing...");
|
System.out.println("\n[" + intersectionId + "] Configuring routing...");
|
||||||
|
|
||||||
switch (intersectionId) {
|
switch (intersectionId) {
|
||||||
case "Cr1":
|
case "Cr1":
|
||||||
intersection.configureRoute("Cr2", "East");
|
intersection.configureRoute("Cr2", "East");
|
||||||
intersection.configureRoute("Cr4", "South");
|
intersection.configureRoute("Cr4", "South");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "Cr2":
|
case "Cr2":
|
||||||
intersection.configureRoute("Cr1", "West");
|
intersection.configureRoute("Cr1", "West");
|
||||||
intersection.configureRoute("Cr3", "East");
|
intersection.configureRoute("Cr3", "East");
|
||||||
intersection.configureRoute("Cr5", "South");
|
intersection.configureRoute("Cr5", "South");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "Cr3":
|
case "Cr3":
|
||||||
intersection.configureRoute("Cr2", "West");
|
intersection.configureRoute("Cr2", "West");
|
||||||
intersection.configureRoute("S", "South");
|
intersection.configureRoute("S", "South");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "Cr4":
|
case "Cr4":
|
||||||
intersection.configureRoute("Cr5", "East");
|
intersection.configureRoute("Cr5", "East");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "Cr5":
|
case "Cr5":
|
||||||
intersection.configureRoute("S", "East");
|
intersection.configureRoute("S", "East");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
System.err.println(" Error: unknown intersection ID: " + intersectionId);
|
System.err.println(" Error: unknown intersection ID: " + intersectionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
System.out.println(" Routing configured.");
|
System.out.println(" Routing configured.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts all traffic light threads.
|
* Starts all traffic light threads.
|
||||||
*/
|
*/
|
||||||
private void startTrafficLights() {
|
private void startTrafficLights() {
|
||||||
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));
|
trafficLightPool.submit(() -> runTrafficLightCycle(light));
|
||||||
System.out.println(" Started thread for: " + light.getDirection());
|
System.out.println(" Started thread for: " + light.getDirection());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The main loop for a traffic light thread.
|
* The main loop for a traffic light thread.
|
||||||
* Continuously cycles between green and red states.
|
* Continuously cycles between green and red states.
|
||||||
@@ -191,7 +193,7 @@ public class IntersectionProcess {
|
|||||||
*/
|
*/
|
||||||
private void runTrafficLightCycle(TrafficLight light) {
|
private void runTrafficLightCycle(TrafficLight light) {
|
||||||
System.out.println("[" + light.getId() + "] Traffic light thread started.");
|
System.out.println("[" + light.getId() + "] Traffic light thread started.");
|
||||||
|
|
||||||
while (running) {
|
while (running) {
|
||||||
try {
|
try {
|
||||||
// Acquire coordination lock to become green
|
// Acquire coordination lock to become green
|
||||||
@@ -203,26 +205,26 @@ public class IntersectionProcess {
|
|||||||
Thread.sleep(100); // Brief wait before retrying
|
Thread.sleep(100); // Brief wait before retrying
|
||||||
trafficCoordinationLock.lock();
|
trafficCoordinationLock.lock();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!running) {
|
if (!running) {
|
||||||
break; // Exit if shutting down
|
break; // Exit if shutting down
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark this direction as the current green light
|
// Mark this direction as the current green light
|
||||||
currentGreenDirection = light.getDirection();
|
currentGreenDirection = light.getDirection();
|
||||||
light.changeState(TrafficLightState.GREEN);
|
light.changeState(TrafficLightState.GREEN);
|
||||||
System.out.println("[" + light.getId() + "] State: GREEN");
|
System.out.println("[" + light.getId() + "] State: GREEN");
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
trafficCoordinationLock.unlock();
|
trafficCoordinationLock.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process vehicles while green
|
// Process vehicles while green
|
||||||
processGreenLight(light);
|
processGreenLight(light);
|
||||||
|
|
||||||
// Wait for green duration
|
// Wait for green duration
|
||||||
Thread.sleep((long) (light.getGreenTime() * 1000));
|
Thread.sleep((long) (light.getGreenTime() * 1000));
|
||||||
|
|
||||||
// Release coordination lock (turn red)
|
// Release coordination lock (turn red)
|
||||||
trafficCoordinationLock.lock();
|
trafficCoordinationLock.lock();
|
||||||
try {
|
try {
|
||||||
@@ -232,19 +234,19 @@ public class IntersectionProcess {
|
|||||||
} finally {
|
} finally {
|
||||||
trafficCoordinationLock.unlock();
|
trafficCoordinationLock.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for red duration
|
// Wait for red duration
|
||||||
Thread.sleep((long) (light.getRedTime() * 1000));
|
Thread.sleep((long) (light.getRedTime() * 1000));
|
||||||
|
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
System.out.println("[" + light.getId() + "] Traffic light thread interrupted.");
|
System.out.println("[" + light.getId() + "] Traffic light thread interrupted.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
System.out.println("[" + light.getId() + "] Traffic light thread stopped.");
|
System.out.println("[" + light.getId() + "] Traffic light thread stopped.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes vehicles when a traffic light is GREEN.
|
* Processes vehicles when a traffic light is GREEN.
|
||||||
* Dequeues vehicles and sends them to their next destination.
|
* Dequeues vehicles and sends them to their next destination.
|
||||||
@@ -254,11 +256,11 @@ public class IntersectionProcess {
|
|||||||
private void processGreenLight(TrafficLight light) {
|
private void processGreenLight(TrafficLight light) {
|
||||||
while (light.getState() == TrafficLightState.GREEN && light.getQueueSize() > 0) {
|
while (light.getState() == TrafficLightState.GREEN && light.getQueueSize() > 0) {
|
||||||
Vehicle vehicle = light.removeVehicle();
|
Vehicle vehicle = light.removeVehicle();
|
||||||
|
|
||||||
if (vehicle != null) {
|
if (vehicle != null) {
|
||||||
// Get crossing time based on vehicle type
|
// Get crossing time based on vehicle type
|
||||||
double crossingTime = getCrossingTimeForVehicle(vehicle);
|
double crossingTime = getCrossingTimeForVehicle(vehicle);
|
||||||
|
|
||||||
// Simulate crossing time
|
// Simulate crossing time
|
||||||
try {
|
try {
|
||||||
Thread.sleep((long) (crossingTime * 1000));
|
Thread.sleep((long) (crossingTime * 1000));
|
||||||
@@ -266,19 +268,19 @@ public class IntersectionProcess {
|
|||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update vehicle statistics
|
// Update vehicle statistics
|
||||||
vehicle.addCrossingTime(crossingTime);
|
vehicle.addCrossingTime(crossingTime);
|
||||||
|
|
||||||
// Update intersection statistics
|
// Update intersection statistics
|
||||||
intersection.incrementVehiclesSent();
|
intersection.incrementVehiclesSent();
|
||||||
|
|
||||||
// Send vehicle to next destination
|
// Send vehicle to next destination
|
||||||
sendVehicleToNextDestination(vehicle);
|
sendVehicleToNextDestination(vehicle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the crossing time for a vehicle based on its type.
|
* Gets the crossing time for a vehicle based on its type.
|
||||||
*
|
*
|
||||||
@@ -297,7 +299,7 @@ public class IntersectionProcess {
|
|||||||
return config.getLightVehicleCrossingTime();
|
return config.getLightVehicleCrossingTime();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a vehicle to its next destination via socket connection.
|
* Sends a vehicle to its next destination via socket connection.
|
||||||
*
|
*
|
||||||
@@ -305,56 +307,55 @@ public class IntersectionProcess {
|
|||||||
*/
|
*/
|
||||||
public void sendVehicleToNextDestination(Vehicle vehicle) {
|
public void sendVehicleToNextDestination(Vehicle vehicle) {
|
||||||
String nextDestination = vehicle.getCurrentDestination();
|
String nextDestination = vehicle.getCurrentDestination();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get or create connection to next destination
|
// Get or create connection to next destination
|
||||||
SocketConnection connection = getOrCreateConnection(nextDestination);
|
SocketConnection connection = getOrCreateConnection(nextDestination);
|
||||||
|
|
||||||
// Create and send message
|
// Create and send message
|
||||||
MessageProtocol message = new VehicleTransferMessage(
|
MessageProtocol message = new VehicleTransferMessage(
|
||||||
intersectionId,
|
intersectionId,
|
||||||
nextDestination,
|
nextDestination,
|
||||||
vehicle
|
vehicle);
|
||||||
);
|
|
||||||
|
|
||||||
connection.sendMessage(message);
|
connection.sendMessage(message);
|
||||||
|
|
||||||
System.out.println("[" + intersectionId + "] Sent vehicle " + vehicle.getId() +
|
System.out.println("[" + intersectionId + "] Sent vehicle " + vehicle.getId() +
|
||||||
" to " + nextDestination);
|
" to " + nextDestination);
|
||||||
|
|
||||||
// Note: vehicle route is advanced when it arrives at the next intersection
|
// Note: vehicle route is advanced when it arrives at the next intersection
|
||||||
|
|
||||||
} catch (IOException | InterruptedException e) {
|
} catch (IOException | InterruptedException e) {
|
||||||
System.err.println("[" + intersectionId + "] Failed to send vehicle " +
|
System.err.println("[" + intersectionId + "] Failed to send vehicle " +
|
||||||
vehicle.getId() + " to " + nextDestination + ": " + e.getMessage());
|
vehicle.getId() + " to " + nextDestination + ": " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets an existing connection to a destination or creates a new one.
|
* Gets an existing connection to a destination or creates a new one.
|
||||||
*
|
*
|
||||||
* @param destinationId The ID of the destination node.
|
* @param destinationId The ID of the destination node.
|
||||||
* @return The SocketConnection to that destination.
|
* @return The SocketConnection to that destination.
|
||||||
* @throws IOException If connection cannot be established.
|
* @throws IOException If connection cannot be established.
|
||||||
* @throws InterruptedException If connection attempt is interrupted.
|
* @throws InterruptedException If connection attempt is interrupted.
|
||||||
*/
|
*/
|
||||||
private synchronized SocketConnection getOrCreateConnection(String destinationId)
|
private synchronized SocketConnection getOrCreateConnection(String destinationId)
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
|
|
||||||
if (!outgoingConnections.containsKey(destinationId)) {
|
if (!outgoingConnections.containsKey(destinationId)) {
|
||||||
String host = getHostForDestination(destinationId);
|
String host = getHostForDestination(destinationId);
|
||||||
int port = getPortForDestination(destinationId);
|
int port = getPortForDestination(destinationId);
|
||||||
|
|
||||||
System.out.println("[" + intersectionId + "] Creating connection to " +
|
System.out.println("[" + intersectionId + "] Creating connection to " +
|
||||||
destinationId + " at " + host + ":" + port);
|
destinationId + " at " + host + ":" + port);
|
||||||
|
|
||||||
SocketConnection connection = new SocketConnection(host, port);
|
SocketConnection connection = new SocketConnection(host, port);
|
||||||
outgoingConnections.put(destinationId, connection);
|
outgoingConnections.put(destinationId, connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
return outgoingConnections.get(destinationId);
|
return outgoingConnections.get(destinationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the host address for a destination node from configuration.
|
* Gets the host address for a destination node from configuration.
|
||||||
*
|
*
|
||||||
@@ -368,7 +369,7 @@ public class IntersectionProcess {
|
|||||||
return config.getIntersectionHost(destinationId);
|
return config.getIntersectionHost(destinationId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the port number for a destination node from configuration.
|
* Gets the port number for a destination node from configuration.
|
||||||
*
|
*
|
||||||
@@ -382,7 +383,7 @@ public class IntersectionProcess {
|
|||||||
return config.getIntersectionPort(destinationId);
|
return config.getIntersectionPort(destinationId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts the server socket and begins accepting incoming connections.
|
* Starts the server socket and begins accepting incoming connections.
|
||||||
* This is the main listening loop of the process.
|
* This is the main listening loop of the process.
|
||||||
@@ -393,31 +394,43 @@ public class IntersectionProcess {
|
|||||||
int port = config.getIntersectionPort(intersectionId);
|
int port = config.getIntersectionPort(intersectionId);
|
||||||
serverSocket = new ServerSocket(port);
|
serverSocket = new ServerSocket(port);
|
||||||
running = true;
|
running = true;
|
||||||
|
|
||||||
System.out.println("\n[" + intersectionId + "] Server started on port " + port);
|
System.out.println("\n[" + intersectionId + "] Server started on port " + port);
|
||||||
|
|
||||||
// Start traffic light threads when running is true
|
// Start traffic light threads when running is true
|
||||||
startTrafficLights();
|
startTrafficLights();
|
||||||
|
|
||||||
System.out.println("[" + intersectionId + "] Waiting for incoming connections...\n");
|
System.out.println("[" + intersectionId + "] Waiting for incoming connections...\n");
|
||||||
|
|
||||||
// Main accept loop
|
// Main accept loop
|
||||||
while (running) {
|
while (running) {
|
||||||
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 - prevents accepting during shutdown
|
||||||
|
if (!running) {
|
||||||
|
clientSocket.close();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
System.err.println("[" + intersectionId + "] Error accepting connection: " +
|
if (!running) {
|
||||||
e.getMessage());
|
break; // Normal shutdown
|
||||||
}
|
}
|
||||||
|
// Unexpected error during normal operation
|
||||||
|
System.err.println("[" + intersectionId + "] Error accepting connection: " +
|
||||||
|
e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles an incoming connection from another process.
|
* Handles an incoming connection from another process.
|
||||||
* Continuously listens for vehicle transfer messages.
|
* Continuously listens for vehicle transfer messages.
|
||||||
@@ -426,38 +439,49 @@ public class IntersectionProcess {
|
|||||||
*/
|
*/
|
||||||
private void handleIncomingConnection(Socket clientSocket) {
|
private void handleIncomingConnection(Socket clientSocket) {
|
||||||
try (SocketConnection connection = new SocketConnection(clientSocket)) {
|
try (SocketConnection connection = new SocketConnection(clientSocket)) {
|
||||||
|
|
||||||
|
// Set socket timeout so receiveMessage() won't block forever
|
||||||
|
clientSocket.setSoTimeout(1000); // 1 second timeout
|
||||||
|
|
||||||
System.out.println("[" + intersectionId + "] New connection accepted from " +
|
System.out.println("[" + intersectionId + "] New connection accepted from " +
|
||||||
clientSocket.getInetAddress().getHostAddress());
|
clientSocket.getInetAddress().getHostAddress());
|
||||||
|
|
||||||
// Continuously receive messages while connection is active
|
// Continuously receive messages while connection is active
|
||||||
while (running && connection.isConnected()) {
|
while (running && connection.isConnected()) {
|
||||||
try {
|
try {
|
||||||
MessageProtocol message = connection.receiveMessage();
|
MessageProtocol message = connection.receiveMessage();
|
||||||
|
|
||||||
if (message.getType() == MessageType.VEHICLE_TRANSFER) {
|
if (message.getType() == MessageType.VEHICLE_TRANSFER) {
|
||||||
Vehicle vehicle = (Vehicle) message.getPayload();
|
Vehicle vehicle = (Vehicle) message.getPayload();
|
||||||
|
|
||||||
System.out.println("[" + intersectionId + "] Received vehicle: " +
|
System.out.println("[" + intersectionId + "] Received vehicle: " +
|
||||||
vehicle.getId() + " from " + message.getSourceNode());
|
vehicle.getId() + " from " + message.getSourceNode());
|
||||||
|
|
||||||
// Add vehicle to appropriate queue
|
// Add vehicle to appropriate queue
|
||||||
intersection.receiveVehicle(vehicle);
|
intersection.receiveVehicle(vehicle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} catch (java.net.SocketTimeoutException e) {
|
||||||
|
// Timeout is expected - just 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
if (running) {
|
if (running) {
|
||||||
System.err.println("[" + intersectionId + "] Connection error: " + e.getMessage());
|
System.err.println("[" + intersectionId + "] Connection error: " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
// Expected during shutdown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops the intersection process gracefully.
|
* Stops the intersection process gracefully.
|
||||||
* Shuts down all threads and closes all connections.
|
* Shuts down all threads and closes all connections.
|
||||||
@@ -465,47 +489,46 @@ public class IntersectionProcess {
|
|||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
System.out.println("\n[" + intersectionId + "] Shutting down...");
|
System.out.println("\n[" + intersectionId + "] Shutting down...");
|
||||||
running = false;
|
running = false;
|
||||||
|
|
||||||
// Close server socket
|
if (serverSocket != null && !serverSocket.isClosed()) {
|
||||||
try {
|
try {
|
||||||
if (serverSocket != null && !serverSocket.isClosed()) {
|
|
||||||
serverSocket.close();
|
serverSocket.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println("[" + intersectionId + "] Error closing server socket: " +
|
||||||
|
e.getMessage());
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
|
||||||
System.err.println("[" + intersectionId + "] Error closing server socket: " +
|
|
||||||
e.getMessage());
|
|
||||||
}
|
}
|
||||||
|
synchronized (outgoingConnections) {
|
||||||
// Shutdown thread pools
|
for (SocketConnection conn : outgoingConnections.values()) {
|
||||||
|
try {
|
||||||
|
conn.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Ignore errors during shutdown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outgoingConnections.clear();
|
||||||
|
}
|
||||||
|
|
||||||
trafficLightPool.shutdown();
|
trafficLightPool.shutdown();
|
||||||
connectionHandlerPool.shutdown();
|
connectionHandlerPool.shutdownNow(); // Use shutdownNow() to interrupt running tasks
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!trafficLightPool.awaitTermination(5, TimeUnit.SECONDS)) {
|
if (!trafficLightPool.awaitTermination(2, TimeUnit.SECONDS)) {
|
||||||
trafficLightPool.shutdownNow();
|
trafficLightPool.shutdownNow();
|
||||||
}
|
}
|
||||||
if (!connectionHandlerPool.awaitTermination(5, TimeUnit.SECONDS)) {
|
if (!connectionHandlerPool.awaitTermination(2, TimeUnit.SECONDS)) {
|
||||||
connectionHandlerPool.shutdownNow();
|
connectionHandlerPool.shutdownNow();
|
||||||
}
|
}
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
trafficLightPool.shutdownNow();
|
trafficLightPool.shutdownNow();
|
||||||
connectionHandlerPool.shutdownNow();
|
connectionHandlerPool.shutdownNow();
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close all outgoing connections
|
|
||||||
for (Map.Entry<String, SocketConnection> entry : outgoingConnections.entrySet()) {
|
|
||||||
try {
|
|
||||||
entry.getValue().close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
System.err.println("[" + intersectionId + "] Error closing connection to " +
|
|
||||||
entry.getKey() + ": " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
System.out.println("[" + intersectionId + "] Shutdown complete.");
|
System.out.println("[" + intersectionId + "] Shutdown complete.");
|
||||||
System.out.println("=".repeat(60));
|
System.out.println("=".repeat(60) + "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the Intersection object managed by this process.
|
* Gets the Intersection object managed by this process.
|
||||||
* Useful for testing and monitoring.
|
* Useful for testing and monitoring.
|
||||||
@@ -515,40 +538,40 @@ public class IntersectionProcess {
|
|||||||
public Intersection getIntersection() {
|
public Intersection getIntersection() {
|
||||||
return intersection;
|
return intersection;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Inner class for Vehicle Transfer Messages ---
|
// --- Inner class for Vehicle Transfer Messages ---
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of MessageProtocol for vehicle transfers between processes.
|
* Implementation of MessageProtocol for vehicle transfers between processes.
|
||||||
*/
|
*/
|
||||||
private static class VehicleTransferMessage implements MessageProtocol {
|
private static class VehicleTransferMessage implements MessageProtocol {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
private final String sourceNode;
|
private final String sourceNode;
|
||||||
private final String destinationNode;
|
private final String destinationNode;
|
||||||
private final Vehicle payload;
|
private final Vehicle payload;
|
||||||
|
|
||||||
public VehicleTransferMessage(String sourceNode, String destinationNode, Vehicle vehicle) {
|
public VehicleTransferMessage(String sourceNode, String destinationNode, Vehicle vehicle) {
|
||||||
this.sourceNode = sourceNode;
|
this.sourceNode = sourceNode;
|
||||||
this.destinationNode = destinationNode;
|
this.destinationNode = destinationNode;
|
||||||
this.payload = vehicle;
|
this.payload = vehicle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MessageType getType() {
|
public MessageType getType() {
|
||||||
return MessageType.VEHICLE_TRANSFER;
|
return MessageType.VEHICLE_TRANSFER;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getPayload() {
|
public Object getPayload() {
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getSourceNode() {
|
public String getSourceNode() {
|
||||||
return sourceNode;
|
return sourceNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDestinationNode() {
|
public String getDestinationNode() {
|
||||||
return destinationNode;
|
return destinationNode;
|
||||||
|
|||||||
@@ -9,150 +9,100 @@ 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
|
||||||
|
private Thread currentThread;
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor for the Traffic Light Thread.
|
|
||||||
*
|
|
||||||
* @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() {
|
||||||
|
// Capture the current thread reference
|
||||||
|
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) {
|
|
||||||
|
|
||||||
// --- 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
|
// Wait for red duration
|
||||||
Thread.sleep((long) (light.getRedTime() * 1000)); //
|
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
|
// Restore interrupt status
|
||||||
|
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,9 +19,10 @@ 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,
|
||||||
* vehicle transfer and network stuff
|
* vehicle transfer and network stuff
|
||||||
*/
|
*/
|
||||||
public class IntersectionProcessTest {
|
public class IntersectionProcessTest {
|
||||||
@@ -37,10 +38,10 @@ public class IntersectionProcessTest {
|
|||||||
public void setUp() throws IOException {
|
public void setUp() throws IOException {
|
||||||
// create temp config file
|
// create temp config file
|
||||||
configFile = tempDir.resolve("test-simulation.properties");
|
configFile = tempDir.resolve("test-simulation.properties");
|
||||||
|
|
||||||
String configContent = """
|
String configContent = """
|
||||||
# Test Simulation Configuration
|
# Test Simulation Configuration
|
||||||
|
|
||||||
# Intersection Network Configuration
|
# Intersection Network Configuration
|
||||||
intersection.Cr1.host=localhost
|
intersection.Cr1.host=localhost
|
||||||
intersection.Cr1.port=18001
|
intersection.Cr1.port=18001
|
||||||
@@ -52,15 +53,15 @@ public class IntersectionProcessTest {
|
|||||||
intersection.Cr4.port=18004
|
intersection.Cr4.port=18004
|
||||||
intersection.Cr5.host=localhost
|
intersection.Cr5.host=localhost
|
||||||
intersection.Cr5.port=18005
|
intersection.Cr5.port=18005
|
||||||
|
|
||||||
# Exit Configuration
|
# Exit Configuration
|
||||||
exit.host=localhost
|
exit.host=localhost
|
||||||
exit.port=18099
|
exit.port=18099
|
||||||
|
|
||||||
# Dashboard Configuration
|
# Dashboard Configuration
|
||||||
dashboard.host=localhost
|
dashboard.host=localhost
|
||||||
dashboard.port=18100
|
dashboard.port=18100
|
||||||
|
|
||||||
# Traffic Light Timing (seconds)
|
# Traffic Light Timing (seconds)
|
||||||
trafficLight.Cr1.East.greenTime=5.0
|
trafficLight.Cr1.East.greenTime=5.0
|
||||||
trafficLight.Cr1.East.redTime=5.0
|
trafficLight.Cr1.East.redTime=5.0
|
||||||
@@ -68,31 +69,31 @@ public class IntersectionProcessTest {
|
|||||||
trafficLight.Cr1.South.redTime=5.0
|
trafficLight.Cr1.South.redTime=5.0
|
||||||
trafficLight.Cr1.West.greenTime=5.0
|
trafficLight.Cr1.West.greenTime=5.0
|
||||||
trafficLight.Cr1.West.redTime=5.0
|
trafficLight.Cr1.West.redTime=5.0
|
||||||
|
|
||||||
trafficLight.Cr2.West.greenTime=4.0
|
trafficLight.Cr2.West.greenTime=4.0
|
||||||
trafficLight.Cr2.West.redTime=6.0
|
trafficLight.Cr2.West.redTime=6.0
|
||||||
trafficLight.Cr2.East.greenTime=4.0
|
trafficLight.Cr2.East.greenTime=4.0
|
||||||
trafficLight.Cr2.East.redTime=6.0
|
trafficLight.Cr2.East.redTime=6.0
|
||||||
trafficLight.Cr2.South.greenTime=4.0
|
trafficLight.Cr2.South.greenTime=4.0
|
||||||
trafficLight.Cr2.South.redTime=6.0
|
trafficLight.Cr2.South.redTime=6.0
|
||||||
|
|
||||||
trafficLight.Cr3.West.greenTime=3.0
|
trafficLight.Cr3.West.greenTime=3.0
|
||||||
trafficLight.Cr3.West.redTime=7.0
|
trafficLight.Cr3.West.redTime=7.0
|
||||||
trafficLight.Cr3.East.greenTime=3.0
|
trafficLight.Cr3.East.greenTime=3.0
|
||||||
trafficLight.Cr3.East.redTime=7.0
|
trafficLight.Cr3.East.redTime=7.0
|
||||||
|
|
||||||
trafficLight.Cr4.East.greenTime=6.0
|
trafficLight.Cr4.East.greenTime=6.0
|
||||||
trafficLight.Cr4.East.redTime=4.0
|
trafficLight.Cr4.East.redTime=4.0
|
||||||
|
|
||||||
trafficLight.Cr5.East.greenTime=5.0
|
trafficLight.Cr5.East.greenTime=5.0
|
||||||
trafficLight.Cr5.East.redTime=5.0
|
trafficLight.Cr5.East.redTime=5.0
|
||||||
|
|
||||||
# Vehicle Crossing Times (seconds)
|
# Vehicle Crossing Times (seconds)
|
||||||
vehicle.bike.crossingTime=2.0
|
vehicle.bike.crossingTime=2.0
|
||||||
vehicle.light.crossingTime=3.0
|
vehicle.light.crossingTime=3.0
|
||||||
vehicle.heavy.crossingTime=5.0
|
vehicle.heavy.crossingTime=5.0
|
||||||
""";
|
""";
|
||||||
|
|
||||||
Files.writeString(configFile, configContent);
|
Files.writeString(configFile, configContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,7 +157,7 @@ public class IntersectionProcessTest {
|
|||||||
public void testTrafficLightCreation_Cr1_HasCorrectDirections() throws IOException {
|
public void testTrafficLightCreation_Cr1_HasCorrectDirections() throws IOException {
|
||||||
intersectionProcess = new IntersectionProcess("Cr1", configFile.toString());
|
intersectionProcess = new IntersectionProcess("Cr1", configFile.toString());
|
||||||
intersectionProcess.initialize();
|
intersectionProcess.initialize();
|
||||||
|
|
||||||
// cant access private fields but initialization succeds
|
// cant access private fields but initialization succeds
|
||||||
assertNotNull(intersectionProcess);
|
assertNotNull(intersectionProcess);
|
||||||
}
|
}
|
||||||
@@ -165,7 +166,7 @@ public class IntersectionProcessTest {
|
|||||||
public void testTrafficLightCreation_Cr3_HasCorrectDirections() throws IOException {
|
public void testTrafficLightCreation_Cr3_HasCorrectDirections() throws IOException {
|
||||||
intersectionProcess = new IntersectionProcess("Cr3", configFile.toString());
|
intersectionProcess = new IntersectionProcess("Cr3", configFile.toString());
|
||||||
intersectionProcess.initialize();
|
intersectionProcess.initialize();
|
||||||
|
|
||||||
// Cr3 has west and south only
|
// Cr3 has west and south only
|
||||||
assertNotNull(intersectionProcess);
|
assertNotNull(intersectionProcess);
|
||||||
}
|
}
|
||||||
@@ -174,7 +175,7 @@ public class IntersectionProcessTest {
|
|||||||
public void testTrafficLightCreation_Cr4_HasSingleDirection() throws IOException {
|
public void testTrafficLightCreation_Cr4_HasSingleDirection() throws IOException {
|
||||||
intersectionProcess = new IntersectionProcess("Cr4", configFile.toString());
|
intersectionProcess = new IntersectionProcess("Cr4", configFile.toString());
|
||||||
intersectionProcess.initialize();
|
intersectionProcess.initialize();
|
||||||
|
|
||||||
// Cr4 only has east direction
|
// Cr4 only has east direction
|
||||||
assertNotNull(intersectionProcess);
|
assertNotNull(intersectionProcess);
|
||||||
}
|
}
|
||||||
@@ -186,8 +187,8 @@ public class IntersectionProcessTest {
|
|||||||
public void testServerStart_BindsToCorrectPort() throws IOException, InterruptedException {
|
public void testServerStart_BindsToCorrectPort() throws IOException, InterruptedException {
|
||||||
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();
|
||||||
@@ -196,14 +197,23 @@ 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);
|
||||||
}
|
}
|
||||||
@@ -214,30 +224,36 @@ public class IntersectionProcessTest {
|
|||||||
// test 2 intersections on diferent ports
|
// test 2 intersections on diferent ports
|
||||||
IntersectionProcess cr1 = new IntersectionProcess("Cr1", configFile.toString());
|
IntersectionProcess cr1 = new IntersectionProcess("Cr1", configFile.toString());
|
||||||
IntersectionProcess cr2 = new IntersectionProcess("Cr2", configFile.toString());
|
IntersectionProcess cr2 = new IntersectionProcess("Cr2", configFile.toString());
|
||||||
|
|
||||||
cr1.initialize();
|
cr1.initialize();
|
||||||
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();
|
||||||
thread2.start();
|
thread2.start();
|
||||||
|
|
||||||
Thread.sleep(500);
|
Thread.sleep(500);
|
||||||
|
|
||||||
// check both are running
|
// check both are running
|
||||||
try (Socket socket1 = new Socket("localhost", 18001);
|
try (Socket socket1 = new Socket("localhost", 18001);
|
||||||
Socket socket2 = new Socket("localhost", 18002)) {
|
Socket socket2 = new Socket("localhost", 18002)) {
|
||||||
assertTrue(socket1.isConnected());
|
assertTrue(socket1.isConnected());
|
||||||
assertTrue(socket2.isConnected());
|
assertTrue(socket2.isConnected());
|
||||||
}
|
}
|
||||||
|
|
||||||
cr1.shutdown();
|
cr1.shutdown();
|
||||||
cr2.shutdown();
|
cr2.shutdown();
|
||||||
thread1.join(2000);
|
thread1.join(2000);
|
||||||
@@ -252,33 +268,35 @@ public class IntersectionProcessTest {
|
|||||||
// setup reciever intersection
|
// setup reciever intersection
|
||||||
intersectionProcess = new IntersectionProcess("Cr2", configFile.toString());
|
intersectionProcess = new IntersectionProcess("Cr2", configFile.toString());
|
||||||
intersectionProcess.initialize();
|
intersectionProcess.initialize();
|
||||||
|
|
||||||
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 {
|
||||||
java.util.List<String> route = Arrays.asList("Cr2", "Cr3", "S");
|
// create test vehicle - FIXED: use 4-parameter constructor
|
||||||
Vehicle vehicle = new Vehicle("V001", VehicleType.LIGHT, 0.0, route);
|
java.util.List<String> route = Arrays.asList("Cr2", "Cr3", "S");
|
||||||
|
Vehicle vehicle = new Vehicle("V001", VehicleType.LIGHT, 0.0, route);
|
||||||
// send vehicle from Cr1 to Cr2
|
|
||||||
try (Socket socket = new Socket("localhost", 18002)) {
|
// send vehicle from Cr1 to Cr2 - FIXED: use SocketConnection
|
||||||
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
|
try (Socket socket = new Socket("localhost", 18002);
|
||||||
|
SocketConnection conn = new SocketConnection(socket)) {
|
||||||
TestVehicleMessage message = new TestVehicleMessage("Cr1", "Cr2", vehicle);
|
|
||||||
out.writeObject(message);
|
TestVehicleMessage message = new TestVehicleMessage("Cr1", "Cr2", vehicle);
|
||||||
out.flush();
|
conn.sendMessage(message);
|
||||||
|
|
||||||
Thread.sleep(1000); // wait for procesing
|
Thread.sleep(1000); // wait for processing
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
intersectionProcess.shutdown();
|
||||||
|
serverThread.join(2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
intersectionProcess.shutdown();
|
|
||||||
serverThread.join(2000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// routing config tests
|
// routing config tests
|
||||||
@@ -287,7 +305,7 @@ public class IntersectionProcessTest {
|
|||||||
public void testRoutingConfiguration_Cr1() throws IOException {
|
public void testRoutingConfiguration_Cr1() throws IOException {
|
||||||
intersectionProcess = new IntersectionProcess("Cr1", configFile.toString());
|
intersectionProcess = new IntersectionProcess("Cr1", configFile.toString());
|
||||||
intersectionProcess.initialize();
|
intersectionProcess.initialize();
|
||||||
|
|
||||||
// indirect test - if init works routing should be ok
|
// indirect test - if init works routing should be ok
|
||||||
assertNotNull(intersectionProcess);
|
assertNotNull(intersectionProcess);
|
||||||
}
|
}
|
||||||
@@ -296,7 +314,7 @@ public class IntersectionProcessTest {
|
|||||||
public void testRoutingConfiguration_Cr5() throws IOException {
|
public void testRoutingConfiguration_Cr5() throws IOException {
|
||||||
intersectionProcess = new IntersectionProcess("Cr5", configFile.toString());
|
intersectionProcess = new IntersectionProcess("Cr5", configFile.toString());
|
||||||
intersectionProcess.initialize();
|
intersectionProcess.initialize();
|
||||||
|
|
||||||
// Cr5 routes to exit
|
// Cr5 routes to exit
|
||||||
assertNotNull(intersectionProcess);
|
assertNotNull(intersectionProcess);
|
||||||
}
|
}
|
||||||
@@ -308,19 +326,20 @@ public class IntersectionProcessTest {
|
|||||||
public void testShutdown_GracefulTermination() throws IOException, InterruptedException {
|
public void testShutdown_GracefulTermination() throws IOException, InterruptedException {
|
||||||
intersectionProcess = new IntersectionProcess("Cr1", configFile.toString());
|
intersectionProcess = new IntersectionProcess("Cr1", configFile.toString());
|
||||||
intersectionProcess.initialize();
|
intersectionProcess.initialize();
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
// shutdown should be fast
|
// shutdown should be fast
|
||||||
assertDoesNotThrow(() -> intersectionProcess.shutdown());
|
assertDoesNotThrow(() -> intersectionProcess.shutdown());
|
||||||
|
|
||||||
serverThread.join(2000);
|
serverThread.join(2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,31 +348,36 @@ public class IntersectionProcessTest {
|
|||||||
public void testShutdown_ClosesServerSocket() throws IOException, InterruptedException {
|
public void testShutdown_ClosesServerSocket() throws IOException, InterruptedException {
|
||||||
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
|
||||||
@@ -361,23 +385,24 @@ public class IntersectionProcessTest {
|
|||||||
public void testShutdown_StopsTrafficLightThreads() throws IOException, InterruptedException {
|
public void testShutdown_StopsTrafficLightThreads() throws IOException, InterruptedException {
|
||||||
intersectionProcess = new IntersectionProcess("Cr1", configFile.toString());
|
intersectionProcess = new IntersectionProcess("Cr1", configFile.toString());
|
||||||
intersectionProcess.initialize();
|
intersectionProcess.initialize();
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
int threadCountBefore = Thread.activeCount();
|
int threadCountBefore = Thread.activeCount();
|
||||||
|
|
||||||
intersectionProcess.shutdown();
|
intersectionProcess.shutdown();
|
||||||
serverThread.join(2000);
|
serverThread.join(2000);
|
||||||
|
|
||||||
Thread.sleep(500); // wait for threads to die
|
Thread.sleep(500); // wait for threads to die
|
||||||
|
|
||||||
// thread count should decrese (traffic light threads stop)
|
// thread count should decrese (traffic light threads stop)
|
||||||
int threadCountAfter = Thread.activeCount();
|
int threadCountAfter = Thread.activeCount();
|
||||||
assertTrue(threadCountAfter <= threadCountBefore);
|
assertTrue(threadCountAfter <= threadCountBefore);
|
||||||
@@ -388,45 +413,68 @@ public class IntersectionProcessTest {
|
|||||||
@Test
|
@Test
|
||||||
@Timeout(15)
|
@Timeout(15)
|
||||||
public void testIntegration_TwoIntersectionsVehicleTransfer() throws IOException, InterruptedException {
|
public void testIntegration_TwoIntersectionsVehicleTransfer() throws IOException, InterruptedException {
|
||||||
// setup 2 intersections
|
IntersectionProcess cr1 = null;
|
||||||
IntersectionProcess cr1 = new IntersectionProcess("Cr1", configFile.toString());
|
IntersectionProcess cr2 = null;
|
||||||
IntersectionProcess cr2 = new IntersectionProcess("Cr2", configFile.toString());
|
Thread thread1 = null;
|
||||||
|
Thread thread2 = null;
|
||||||
cr1.initialize();
|
|
||||||
cr2.initialize();
|
try {
|
||||||
|
// setup 2 intersections
|
||||||
// start both
|
cr1 = new IntersectionProcess("Cr1", configFile.toString());
|
||||||
Thread thread1 = new Thread(() -> {
|
cr2 = new IntersectionProcess("Cr2", configFile.toString());
|
||||||
try { cr1.start(); } catch (IOException e) { }
|
|
||||||
});
|
cr1.initialize();
|
||||||
|
cr2.initialize();
|
||||||
Thread thread2 = new Thread(() -> {
|
|
||||||
try { cr2.start(); } catch (IOException e) { }
|
// start both
|
||||||
});
|
final IntersectionProcess cr1Final = cr1;
|
||||||
|
thread1 = new Thread(() -> {
|
||||||
thread1.start();
|
try {
|
||||||
thread2.start();
|
cr1Final.start();
|
||||||
|
} catch (IOException e) {
|
||||||
Thread.sleep(1000); // wait for servers
|
}
|
||||||
|
});
|
||||||
// send vehicle to Cr1 that goes to Cr2
|
|
||||||
java.util.List<String> route = Arrays.asList("Cr1", "Cr2", "S");
|
final IntersectionProcess cr2Final = cr2;
|
||||||
Vehicle vehicle = new Vehicle("V001", VehicleType.LIGHT, 0.0, route);
|
thread2 = new Thread(() -> {
|
||||||
|
try {
|
||||||
try (Socket socket = new Socket("localhost", 18001)) {
|
cr2Final.start();
|
||||||
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
TestVehicleMessage message = new TestVehicleMessage("Entry", "Cr1", vehicle);
|
});
|
||||||
out.writeObject(message);
|
|
||||||
out.flush();
|
thread1.start();
|
||||||
|
thread2.start();
|
||||||
Thread.sleep(2000); // time for processing
|
|
||||||
|
Thread.sleep(1000); // wait for servers
|
||||||
|
|
||||||
|
// send vehicle to Cr1 that goes to Cr2 - FIXED: use 4-parameter constructor
|
||||||
|
java.util.List<String> route = Arrays.asList("Cr1", "Cr2", "S");
|
||||||
|
Vehicle vehicle = new Vehicle("V001", VehicleType.LIGHT, 0.0, route);
|
||||||
|
|
||||||
|
// FIXED: use SocketConnection
|
||||||
|
try (Socket socket = new Socket("localhost", 18001);
|
||||||
|
SocketConnection conn = new SocketConnection(socket)) {
|
||||||
|
|
||||||
|
TestVehicleMessage message = new TestVehicleMessage("Entry", "Cr1", vehicle);
|
||||||
|
conn.sendMessage(message);
|
||||||
|
|
||||||
|
Thread.sleep(2000); // time for processing
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (cr1 != null) {
|
||||||
|
cr1.shutdown();
|
||||||
|
}
|
||||||
|
if (cr2 != null) {
|
||||||
|
cr2.shutdown();
|
||||||
|
}
|
||||||
|
if (thread1 != null) {
|
||||||
|
thread1.join(2000);
|
||||||
|
}
|
||||||
|
if (thread2 != null) {
|
||||||
|
thread2.join(2000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cr1.shutdown();
|
|
||||||
cr2.shutdown();
|
|
||||||
thread1.join(2000);
|
|
||||||
thread2.join(2000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -439,32 +487,32 @@ public class IntersectionProcessTest {
|
|||||||
// helper class for testing vehicle messages
|
// helper class for testing vehicle messages
|
||||||
private static class TestVehicleMessage implements sd.protocol.MessageProtocol {
|
private static class TestVehicleMessage implements sd.protocol.MessageProtocol {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
private final String sourceNode;
|
private final String sourceNode;
|
||||||
private final String destinationNode;
|
private final String destinationNode;
|
||||||
private final Vehicle payload;
|
private final Vehicle payload;
|
||||||
|
|
||||||
public TestVehicleMessage(String sourceNode, String destinationNode, Vehicle vehicle) {
|
public TestVehicleMessage(String sourceNode, String destinationNode, Vehicle vehicle) {
|
||||||
this.sourceNode = sourceNode;
|
this.sourceNode = sourceNode;
|
||||||
this.destinationNode = destinationNode;
|
this.destinationNode = destinationNode;
|
||||||
this.payload = vehicle;
|
this.payload = vehicle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MessageType getType() {
|
public MessageType getType() {
|
||||||
return MessageType.VEHICLE_TRANSFER;
|
return MessageType.VEHICLE_TRANSFER;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getPayload() {
|
public Object getPayload() {
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getSourceNode() {
|
public String getSourceNode() {
|
||||||
return sourceNode;
|
return sourceNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDestinationNode() {
|
public String getDestinationNode() {
|
||||||
return destinationNode;
|
return destinationNode;
|
||||||
|
|||||||
Reference in New Issue
Block a user