From 84cba39597414fbb8eb7583f02b22ec1725db09e Mon Sep 17 00:00:00 2001 From: Leandro Afonso Date: Thu, 6 Nov 2025 20:31:59 +0000 Subject: [PATCH] bullshit fixes --- .../src/main/java/sd/IntersectionProcess.java | 321 ++++++++++-------- .../java/sd/engine/TrafficLightThread.java | 130 +++---- .../test/java/IntersectionProcessTest.java | 308 ++++++++++------- 3 files changed, 390 insertions(+), 369 deletions(-) diff --git a/main/src/main/java/sd/IntersectionProcess.java b/main/src/main/java/sd/IntersectionProcess.java index 3942f0d..a2bef3a 100644 --- a/main/src/main/java/sd/IntersectionProcess.java +++ b/main/src/main/java/sd/IntersectionProcess.java @@ -22,41 +22,43 @@ import sd.protocol.SocketConnection; /** * 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. */ public class IntersectionProcess { private final String intersectionId; - + private final SimulationConfig config; - + private final Intersection intersection; - + private ServerSocket serverSocket; - + private final Map outgoingConnections; - + private final ExecutorService connectionHandlerPool; - + private final ExecutorService trafficLightPool; - - private volatile boolean running; //Quando uma thread escreve um valor volatile, todas as outras - //threads veem a mudança imediatamente. - + + private volatile boolean running; // Quando uma thread escreve um valor volatile, todas as outras + // threads veem a mudança imediatamente. + // Traffic Light Coordination /** * 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; - + /** * Tracks which direction currently has the green light. * null means no direction is currently green (all are red). */ private volatile String currentGreenDirection; - + /** * Constructs a new IntersectionProcess. * @@ -74,113 +76,113 @@ public class IntersectionProcess { this.running = false; this.trafficCoordinationLock = new ReentrantLock(); this.currentGreenDirection = null; - + System.out.println("=".repeat(60)); System.out.println("INTERSECTION PROCESS: " + intersectionId); System.out.println("=".repeat(60)); } - + public void initialize() { System.out.println("\n[" + intersectionId + "] Initializing intersection..."); - + createTrafficLights(); - + configureRouting(); - + 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 * according to the network topology. */ private void createTrafficLights() { System.out.println("\n[" + intersectionId + "] Creating traffic lights..."); - + String[] directions = new String[0]; switch (intersectionId) { case "Cr1": - directions = new String[]{"East", "South"}; + directions = new String[] { "East", "South" }; break; case "Cr2": - directions = new String[]{"West", "East", "South"}; + directions = new String[] { "West", "East", "South" }; break; case "Cr3": - directions = new String[]{"West", "South"}; + directions = new String[] { "West", "South" }; break; case "Cr4": - directions = new String[]{"East"}; + directions = new String[] { "East" }; break; case "Cr5": - directions = new String[]{"East"}; + directions = new String[] { "East" }; break; } - + for (String direction : directions) { double greenTime = config.getTrafficLightGreenTime(intersectionId, direction); double redTime = config.getTrafficLightRedTime(intersectionId, direction); - + TrafficLight light = new TrafficLight( - intersectionId + "-" + direction, - direction, - greenTime, - redTime - ); - + intersectionId + "-" + direction, + direction, + greenTime, + redTime); + intersection.addTrafficLight(light); - System.out.println(" Created traffic light: " + direction + - " (Green: " + greenTime + "s, Red: " + redTime + "s)"); + System.out.println(" Created traffic light: " + direction + + " (Green: " + greenTime + "s, Red: " + redTime + "s)"); } } - + private void configureRouting() { System.out.println("\n[" + intersectionId + "] Configuring routing..."); - + switch (intersectionId) { case "Cr1": intersection.configureRoute("Cr2", "East"); - intersection.configureRoute("Cr4", "South"); + intersection.configureRoute("Cr4", "South"); break; - + case "Cr2": intersection.configureRoute("Cr1", "West"); intersection.configureRoute("Cr3", "East"); intersection.configureRoute("Cr5", "South"); break; - + case "Cr3": intersection.configureRoute("Cr2", "West"); intersection.configureRoute("S", "South"); break; - + case "Cr4": intersection.configureRoute("Cr5", "East"); break; - + case "Cr5": intersection.configureRoute("S", "East"); break; - + default: System.err.println(" Error: unknown intersection ID: " + intersectionId); } - + System.out.println(" Routing configured."); } - + /** * Starts all traffic light threads. */ private void startTrafficLights() { System.out.println("\n[" + intersectionId + "] Starting traffic light threads..."); - + for (TrafficLight light : intersection.getTrafficLights()) { trafficLightPool.submit(() -> runTrafficLightCycle(light)); System.out.println(" Started thread for: " + light.getDirection()); } } - + /** * The main loop for a traffic light thread. * Continuously cycles between green and red states. @@ -191,7 +193,7 @@ public class IntersectionProcess { */ private void runTrafficLightCycle(TrafficLight light) { System.out.println("[" + light.getId() + "] Traffic light thread started."); - + while (running) { try { // Acquire coordination lock to become green @@ -203,26 +205,26 @@ public class IntersectionProcess { 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 { @@ -232,19 +234,19 @@ public class IntersectionProcess { } 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. @@ -254,11 +256,11 @@ public class IntersectionProcess { 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)); @@ -266,19 +268,19 @@ public class IntersectionProcess { 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. * @@ -297,7 +299,7 @@ public class IntersectionProcess { return config.getLightVehicleCrossingTime(); } } - + /** * Sends a vehicle to its next destination via socket connection. * @@ -305,56 +307,55 @@ public class IntersectionProcess { */ public void sendVehicleToNextDestination(Vehicle vehicle) { String nextDestination = vehicle.getCurrentDestination(); - + try { // Get or create connection to next destination SocketConnection connection = getOrCreateConnection(nextDestination); - + // Create and send message MessageProtocol message = new VehicleTransferMessage( - intersectionId, - nextDestination, - vehicle - ); - + intersectionId, + nextDestination, + vehicle); + connection.sendMessage(message); - - System.out.println("[" + intersectionId + "] Sent vehicle " + vehicle.getId() + - " to " + nextDestination); - + + System.out.println("[" + intersectionId + "] Sent vehicle " + vehicle.getId() + + " to " + nextDestination); + // Note: vehicle route is advanced when it arrives at the next intersection - + } catch (IOException | InterruptedException e) { - System.err.println("[" + intersectionId + "] Failed to send vehicle " + - vehicle.getId() + " to " + nextDestination + ": " + e.getMessage()); + System.err.println("[" + intersectionId + "] Failed to send vehicle " + + vehicle.getId() + " to " + nextDestination + ": " + e.getMessage()); } } - + /** * Gets an existing connection to a destination or creates a new one. * * @param destinationId The ID of the destination node. * @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. */ - private synchronized SocketConnection getOrCreateConnection(String destinationId) + private synchronized SocketConnection getOrCreateConnection(String destinationId) throws IOException, InterruptedException { - + if (!outgoingConnections.containsKey(destinationId)) { String host = getHostForDestination(destinationId); int port = getPortForDestination(destinationId); - - System.out.println("[" + intersectionId + "] Creating connection to " + - destinationId + " at " + host + ":" + port); - + + System.out.println("[" + intersectionId + "] Creating connection to " + + destinationId + " at " + host + ":" + port); + SocketConnection connection = new SocketConnection(host, port); outgoingConnections.put(destinationId, connection); } - + return outgoingConnections.get(destinationId); } - + /** * Gets the host address for a destination node from configuration. * @@ -368,7 +369,7 @@ public class IntersectionProcess { return config.getIntersectionHost(destinationId); } } - + /** * Gets the port number for a destination node from configuration. * @@ -382,7 +383,7 @@ public class IntersectionProcess { return config.getIntersectionPort(destinationId); } } - + /** * Starts the server socket and begins accepting incoming connections. * This is the main listening loop of the process. @@ -393,31 +394,43 @@ public class IntersectionProcess { int port = config.getIntersectionPort(intersectionId); serverSocket = new ServerSocket(port); running = true; - + System.out.println("\n[" + intersectionId + "] Server started on port " + port); - + // Start traffic light threads when running is true startTrafficLights(); - + System.out.println("[" + intersectionId + "] Waiting for incoming connections...\n"); - + // Main accept loop while (running) { try { 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 connectionHandlerPool.submit(() -> handleIncomingConnection(clientSocket)); - + } catch (IOException e) { - if (running) { - System.err.println("[" + intersectionId + "] Error accepting connection: " + - e.getMessage()); + // Expected when serverSocket.close() is called during shutdown + if (!running) { + break; // Normal shutdown } + // Unexpected error during normal operation + System.err.println("[" + intersectionId + "] Error accepting connection: " + + e.getMessage()); } } } - + /** * Handles an incoming connection from another process. * Continuously listens for vehicle transfer messages. @@ -426,38 +439,49 @@ public class IntersectionProcess { */ private void handleIncomingConnection(Socket 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 " + - clientSocket.getInetAddress().getHostAddress()); - + clientSocket.getInetAddress().getHostAddress()); + // Continuously receive messages while connection is active while (running && connection.isConnected()) { try { MessageProtocol message = connection.receiveMessage(); - + if (message.getType() == MessageType.VEHICLE_TRANSFER) { Vehicle vehicle = (Vehicle) message.getPayload(); - - System.out.println("[" + intersectionId + "] Received vehicle: " + - vehicle.getId() + " from " + message.getSourceNode()); - + + System.out.println("[" + intersectionId + "] Received vehicle: " + + vehicle.getId() + " from " + message.getSourceNode()); + // Add vehicle to appropriate queue 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) { - System.err.println("[" + intersectionId + "] Unknown message type received: " + - e.getMessage()); + System.err.println("[" + intersectionId + "] Unknown message type received: " + + e.getMessage()); + break; // Invalid message, close connection } } - + } catch (IOException e) { if (running) { System.err.println("[" + intersectionId + "] Connection error: " + e.getMessage()); } + // Expected during shutdown } } - + /** * Stops the intersection process gracefully. * Shuts down all threads and closes all connections. @@ -465,47 +489,46 @@ public class IntersectionProcess { public void shutdown() { System.out.println("\n[" + intersectionId + "] Shutting down..."); running = false; - - // Close server socket - try { - if (serverSocket != null && !serverSocket.isClosed()) { + + if (serverSocket != null && !serverSocket.isClosed()) { + try { 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()); } - - // Shutdown thread pools + synchronized (outgoingConnections) { + for (SocketConnection conn : outgoingConnections.values()) { + try { + conn.close(); + } catch (Exception e) { + // Ignore errors during shutdown + } + } + outgoingConnections.clear(); + } + trafficLightPool.shutdown(); - connectionHandlerPool.shutdown(); - + connectionHandlerPool.shutdownNow(); // Use shutdownNow() to interrupt running tasks + try { - if (!trafficLightPool.awaitTermination(5, TimeUnit.SECONDS)) { + if (!trafficLightPool.awaitTermination(2, TimeUnit.SECONDS)) { trafficLightPool.shutdownNow(); } - if (!connectionHandlerPool.awaitTermination(5, TimeUnit.SECONDS)) { + if (!connectionHandlerPool.awaitTermination(2, TimeUnit.SECONDS)) { connectionHandlerPool.shutdownNow(); } } catch (InterruptedException e) { trafficLightPool.shutdownNow(); connectionHandlerPool.shutdownNow(); + Thread.currentThread().interrupt(); } - - // Close all outgoing connections - for (Map.Entry 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("=".repeat(60)); + System.out.println("=".repeat(60) + "\n"); } - + /** * Gets the Intersection object managed by this process. * Useful for testing and monitoring. @@ -515,40 +538,40 @@ public class IntersectionProcess { public Intersection getIntersection() { return intersection; } - + // --- Inner class for Vehicle Transfer Messages --- - + /** * Implementation of MessageProtocol for vehicle transfers between processes. */ private static class VehicleTransferMessage implements MessageProtocol { private static final long serialVersionUID = 1L; - + private final String sourceNode; private final String destinationNode; private final Vehicle payload; - + public VehicleTransferMessage(String sourceNode, String destinationNode, Vehicle vehicle) { this.sourceNode = sourceNode; this.destinationNode = destinationNode; this.payload = vehicle; } - + @Override public MessageType getType() { return MessageType.VEHICLE_TRANSFER; } - + @Override public Object getPayload() { return payload; } - + @Override public String getSourceNode() { return sourceNode; } - + @Override public String getDestinationNode() { return destinationNode; diff --git a/main/src/main/java/sd/engine/TrafficLightThread.java b/main/src/main/java/sd/engine/TrafficLightThread.java index 6d40fdf..f3951df 100644 --- a/main/src/main/java/sd/engine/TrafficLightThread.java +++ b/main/src/main/java/sd/engine/TrafficLightThread.java @@ -9,150 +9,100 @@ import sd.model.Vehicle; /** * Implements the control logic for a single TrafficLight * as a Runnable task that runs in its own Thread. - * */ public class TrafficLightThread implements Runnable { - /** - * The TrafficLight object (the *model*) that this thread controls. - * Contains the queue and the state. - */ 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; - - /** - * The simulation configuration, used to get timings (e.g., crossing time). - */ private final SimulationConfig config; - - /** - * Volatile flag to control the graceful shutdown mechanism. - * When set to 'false', the 'run()' loop terminates. - */ 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) { this.light = light; this.process = process; 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 public void run() { + // Capture the current thread reference + this.currentThread = Thread.currentThread(); this.running = true; System.out.println("[" + light.getId() + "] Traffic light thread started."); try { - // Main thread loop, continues while 'running' is true - // This 'running' flag is controlled by the parent IntersectionProcess - while (running) { + while (running && !Thread.currentThread().isInterrupted()) { // --- GREEN Phase --- - light.changeState(TrafficLightState.GREEN); // + light.changeState(TrafficLightState.GREEN); System.out.println("[" + light.getId() + "] State: GREEN"); - // Process vehicles in the queue processGreenLightQueue(); - // Wait for green duration - Thread.sleep((long) (light.getGreenTime() * 1000)); // + if (!running || Thread.currentThread().isInterrupted()) break; - 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 --- - light.changeState(TrafficLightState.RED); // + light.changeState(TrafficLightState.RED); System.out.println("[" + light.getId() + "] State: RED"); // Wait for red duration - Thread.sleep((long) (light.getRedTime() * 1000)); // - + Thread.sleep((long) (light.getRedTime() * 1000)); } } catch (InterruptedException e) { - // Apanha a InterruptedException (outra forma de parar a thread) 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 { - // - while (running && light.getState() == TrafficLightState.GREEN && light.getQueueSize() > 0) { + while (running && !Thread.currentThread().isInterrupted() + && light.getState() == TrafficLightState.GREEN + && light.getQueueSize() > 0) { - Vehicle vehicle = light.removeVehicle(); // + Vehicle vehicle = light.removeVehicle(); 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); // - - // 4. Update intersection statistics - - process.getIntersection().incrementVehiclesSent(); // - - // 5. Call the parent Process to send the vehicle - - process.sendVehicleToNextDestination(vehicle); // + vehicle.addCrossingTime(crossingTime); + process.getIntersection().incrementVehiclesSent(); + 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) { - switch (vehicle.getType()) { // - case BIKE: - return config.getBikeVehicleCrossingTime(); // - case LIGHT: - return config.getLightVehicleCrossingTime(); // - case HEAVY: - return config.getHeavyVehicleCrossingTime(); // - default: - return config.getLightVehicleCrossingTime(); // - } + return switch (vehicle.getType()) { + case BIKE -> config.getBikeVehicleCrossingTime(); + case LIGHT -> config.getLightVehicleCrossingTime(); + case HEAVY -> config.getHeavyVehicleCrossingTime(); + default -> config.getLightVehicleCrossingTime(); + }; } /** - * Requests the thread to stop gracefully (graceful shutdown). - * Sets the 'running' flag to false. The thread will finish - * its current sleep cycle and exit the 'run()' loop. + * Requests the thread to stop gracefully. + * Sets the running flag and interrupts the thread to unblock any sleep() calls. */ public void shutdown() { this.running = false; + if (currentThread != null && currentThread.isAlive()) { + currentThread.interrupt(); + } } } \ No newline at end of file diff --git a/main/src/test/java/IntersectionProcessTest.java b/main/src/test/java/IntersectionProcessTest.java index 90de4f1..2b5ee08 100644 --- a/main/src/test/java/IntersectionProcessTest.java +++ b/main/src/test/java/IntersectionProcessTest.java @@ -1,5 +1,5 @@ import java.io.IOException; -import java.io.ObjectOutputStream; +import java.net.InetSocketAddress; import java.net.Socket; import java.nio.file.Files; import java.nio.file.Path; @@ -19,9 +19,10 @@ import sd.IntersectionProcess; import sd.model.MessageType; import sd.model.Vehicle; 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 */ public class IntersectionProcessTest { @@ -37,10 +38,10 @@ public class IntersectionProcessTest { public void setUp() throws IOException { // create temp config file configFile = tempDir.resolve("test-simulation.properties"); - + String configContent = """ # Test Simulation Configuration - + # Intersection Network Configuration intersection.Cr1.host=localhost intersection.Cr1.port=18001 @@ -52,15 +53,15 @@ public class IntersectionProcessTest { intersection.Cr4.port=18004 intersection.Cr5.host=localhost intersection.Cr5.port=18005 - + # Exit Configuration exit.host=localhost exit.port=18099 - + # Dashboard Configuration dashboard.host=localhost dashboard.port=18100 - + # Traffic Light Timing (seconds) trafficLight.Cr1.East.greenTime=5.0 trafficLight.Cr1.East.redTime=5.0 @@ -68,31 +69,31 @@ public class IntersectionProcessTest { trafficLight.Cr1.South.redTime=5.0 trafficLight.Cr1.West.greenTime=5.0 trafficLight.Cr1.West.redTime=5.0 - + trafficLight.Cr2.West.greenTime=4.0 trafficLight.Cr2.West.redTime=6.0 trafficLight.Cr2.East.greenTime=4.0 trafficLight.Cr2.East.redTime=6.0 trafficLight.Cr2.South.greenTime=4.0 trafficLight.Cr2.South.redTime=6.0 - + trafficLight.Cr3.West.greenTime=3.0 trafficLight.Cr3.West.redTime=7.0 trafficLight.Cr3.East.greenTime=3.0 trafficLight.Cr3.East.redTime=7.0 - + trafficLight.Cr4.East.greenTime=6.0 trafficLight.Cr4.East.redTime=4.0 - + trafficLight.Cr5.East.greenTime=5.0 trafficLight.Cr5.East.redTime=5.0 - + # Vehicle Crossing Times (seconds) vehicle.bike.crossingTime=2.0 vehicle.light.crossingTime=3.0 vehicle.heavy.crossingTime=5.0 """; - + Files.writeString(configFile, configContent); } @@ -156,7 +157,7 @@ public class IntersectionProcessTest { public void testTrafficLightCreation_Cr1_HasCorrectDirections() throws IOException { intersectionProcess = new IntersectionProcess("Cr1", configFile.toString()); intersectionProcess.initialize(); - + // cant access private fields but initialization succeds assertNotNull(intersectionProcess); } @@ -165,7 +166,7 @@ public class IntersectionProcessTest { public void testTrafficLightCreation_Cr3_HasCorrectDirections() throws IOException { intersectionProcess = new IntersectionProcess("Cr3", configFile.toString()); intersectionProcess.initialize(); - + // Cr3 has west and south only assertNotNull(intersectionProcess); } @@ -174,7 +175,7 @@ public class IntersectionProcessTest { public void testTrafficLightCreation_Cr4_HasSingleDirection() throws IOException { intersectionProcess = new IntersectionProcess("Cr4", configFile.toString()); intersectionProcess.initialize(); - + // Cr4 only has east direction assertNotNull(intersectionProcess); } @@ -186,8 +187,8 @@ public class IntersectionProcessTest { public void testServerStart_BindsToCorrectPort() throws IOException, InterruptedException { intersectionProcess = new IntersectionProcess("Cr1", configFile.toString()); intersectionProcess.initialize(); - - // start server in seperate thread + + // start server in separate thread Thread serverThread = new Thread(() -> { try { intersectionProcess.start(); @@ -196,14 +197,23 @@ public class IntersectionProcessTest { } }); serverThread.start(); - - Thread.sleep(500); // wait for server to start - - // try connecting to check if its running - try (Socket clientSocket = new Socket("localhost", 18001)) { - assertTrue(clientSocket.isConnected()); + + // Wait for server to actually start with retries + boolean serverReady = false; + for (int i = 0; i < 20; i++) { + Thread.sleep(100); + 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(); serverThread.join(2000); } @@ -214,30 +224,36 @@ public class IntersectionProcessTest { // test 2 intersections on diferent ports IntersectionProcess cr1 = new IntersectionProcess("Cr1", configFile.toString()); IntersectionProcess cr2 = new IntersectionProcess("Cr2", configFile.toString()); - + cr1.initialize(); cr2.initialize(); - + Thread thread1 = new Thread(() -> { - try { cr1.start(); } catch (IOException e) { } + try { + cr1.start(); + } catch (IOException e) { + } }); - + Thread thread2 = new Thread(() -> { - try { cr2.start(); } catch (IOException e) { } + try { + cr2.start(); + } catch (IOException e) { + } }); - + thread1.start(); thread2.start(); - + Thread.sleep(500); - + // check both are running try (Socket socket1 = new Socket("localhost", 18001); - Socket socket2 = new Socket("localhost", 18002)) { + Socket socket2 = new Socket("localhost", 18002)) { assertTrue(socket1.isConnected()); assertTrue(socket2.isConnected()); } - + cr1.shutdown(); cr2.shutdown(); thread1.join(2000); @@ -252,33 +268,35 @@ public class IntersectionProcessTest { // setup reciever intersection intersectionProcess = new IntersectionProcess("Cr2", configFile.toString()); intersectionProcess.initialize(); - + Thread serverThread = new Thread(() -> { try { intersectionProcess.start(); - } catch (IOException e) { } + } catch (IOException e) { + } }); serverThread.start(); - + Thread.sleep(500); - - // create test vehicle - java.util.List 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)) { - ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream()); - - TestVehicleMessage message = new TestVehicleMessage("Cr1", "Cr2", vehicle); - out.writeObject(message); - out.flush(); - - Thread.sleep(1000); // wait for procesing + + try { + // create test vehicle - FIXED: use 4-parameter constructor + java.util.List route = Arrays.asList("Cr2", "Cr3", "S"); + Vehicle vehicle = new Vehicle("V001", VehicleType.LIGHT, 0.0, route); + + // send vehicle from Cr1 to Cr2 - FIXED: use SocketConnection + try (Socket socket = new Socket("localhost", 18002); + SocketConnection conn = new SocketConnection(socket)) { + + TestVehicleMessage message = new TestVehicleMessage("Cr1", "Cr2", vehicle); + conn.sendMessage(message); + + Thread.sleep(1000); // wait for processing + } + } finally { + intersectionProcess.shutdown(); + serverThread.join(2000); } - - intersectionProcess.shutdown(); - serverThread.join(2000); } // routing config tests @@ -287,7 +305,7 @@ public class IntersectionProcessTest { public void testRoutingConfiguration_Cr1() throws IOException { intersectionProcess = new IntersectionProcess("Cr1", configFile.toString()); intersectionProcess.initialize(); - + // indirect test - if init works routing should be ok assertNotNull(intersectionProcess); } @@ -296,7 +314,7 @@ public class IntersectionProcessTest { public void testRoutingConfiguration_Cr5() throws IOException { intersectionProcess = new IntersectionProcess("Cr5", configFile.toString()); intersectionProcess.initialize(); - + // Cr5 routes to exit assertNotNull(intersectionProcess); } @@ -308,19 +326,20 @@ public class IntersectionProcessTest { public void testShutdown_GracefulTermination() throws IOException, InterruptedException { intersectionProcess = new IntersectionProcess("Cr1", configFile.toString()); intersectionProcess.initialize(); - + Thread serverThread = new Thread(() -> { try { intersectionProcess.start(); - } catch (IOException e) { } + } catch (IOException e) { + } }); serverThread.start(); - + Thread.sleep(500); - + // shutdown should be fast assertDoesNotThrow(() -> intersectionProcess.shutdown()); - + serverThread.join(2000); } @@ -329,31 +348,36 @@ public class IntersectionProcessTest { public void testShutdown_ClosesServerSocket() throws IOException, InterruptedException { intersectionProcess = new IntersectionProcess("Cr1", configFile.toString()); intersectionProcess.initialize(); - + + // Start server in separate thread Thread serverThread = new Thread(() -> { try { intersectionProcess.start(); - } catch (IOException e) { } + } catch (IOException e) { + // Expected on shutdown + } }); serverThread.start(); - + + // Wait for server to start Thread.sleep(500); - - // verify server running - try (Socket socket = new Socket("localhost", 18001)) { - assertTrue(socket.isConnected()); - } - + + // Shutdown intersectionProcess.shutdown(); serverThread.join(2000); - - // after shutdown conection should fail - Thread.sleep(500); - Exception exception = assertThrows(IOException.class, () -> { - Socket socket = new Socket("localhost", 18001); - socket.close(); - }); - assertNotNull(exception); + + // Give shutdown time to complete + Thread.sleep(200); + + // Verify we cannot connect (server socket is closed) + boolean connectionFailed = false; + try (Socket testSocket = new Socket()) { + 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 @@ -361,23 +385,24 @@ public class IntersectionProcessTest { public void testShutdown_StopsTrafficLightThreads() throws IOException, InterruptedException { intersectionProcess = new IntersectionProcess("Cr1", configFile.toString()); intersectionProcess.initialize(); - + Thread serverThread = new Thread(() -> { try { intersectionProcess.start(); - } catch (IOException e) { } + } catch (IOException e) { + } }); serverThread.start(); - + Thread.sleep(500); - + int threadCountBefore = Thread.activeCount(); - + intersectionProcess.shutdown(); serverThread.join(2000); - + Thread.sleep(500); // wait for threads to die - + // thread count should decrese (traffic light threads stop) int threadCountAfter = Thread.activeCount(); assertTrue(threadCountAfter <= threadCountBefore); @@ -388,45 +413,68 @@ public class IntersectionProcessTest { @Test @Timeout(15) public void testIntegration_TwoIntersectionsVehicleTransfer() throws IOException, InterruptedException { - // setup 2 intersections - IntersectionProcess cr1 = new IntersectionProcess("Cr1", configFile.toString()); - IntersectionProcess cr2 = new IntersectionProcess("Cr2", configFile.toString()); - - cr1.initialize(); - cr2.initialize(); - - // start both - Thread thread1 = new Thread(() -> { - try { cr1.start(); } catch (IOException e) { } - }); - - Thread thread2 = new Thread(() -> { - try { cr2.start(); } catch (IOException e) { } - }); - - thread1.start(); - thread2.start(); - - Thread.sleep(1000); // wait for servers - - // send vehicle to Cr1 that goes to Cr2 - java.util.List route = Arrays.asList("Cr1", "Cr2", "S"); - Vehicle vehicle = new Vehicle("V001", VehicleType.LIGHT, 0.0, route); - - try (Socket socket = new Socket("localhost", 18001)) { - ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream()); - - TestVehicleMessage message = new TestVehicleMessage("Entry", "Cr1", vehicle); - out.writeObject(message); - out.flush(); - - Thread.sleep(2000); // time for processing + IntersectionProcess cr1 = null; + IntersectionProcess cr2 = null; + Thread thread1 = null; + Thread thread2 = null; + + try { + // setup 2 intersections + cr1 = new IntersectionProcess("Cr1", configFile.toString()); + cr2 = new IntersectionProcess("Cr2", configFile.toString()); + + cr1.initialize(); + cr2.initialize(); + + // start both + final IntersectionProcess cr1Final = cr1; + thread1 = new Thread(() -> { + try { + cr1Final.start(); + } catch (IOException e) { + } + }); + + final IntersectionProcess cr2Final = cr2; + thread2 = new Thread(() -> { + try { + cr2Final.start(); + } catch (IOException e) { + } + }); + + thread1.start(); + thread2.start(); + + Thread.sleep(1000); // wait for servers + + // send vehicle to Cr1 that goes to Cr2 - FIXED: use 4-parameter constructor + java.util.List 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 @@ -439,32 +487,32 @@ public class IntersectionProcessTest { // helper class for testing vehicle messages private static class TestVehicleMessage implements sd.protocol.MessageProtocol { private static final long serialVersionUID = 1L; - + private final String sourceNode; private final String destinationNode; private final Vehicle payload; - + public TestVehicleMessage(String sourceNode, String destinationNode, Vehicle vehicle) { this.sourceNode = sourceNode; this.destinationNode = destinationNode; this.payload = vehicle; } - + @Override public MessageType getType() { return MessageType.VEHICLE_TRANSFER; } - + @Override public Object getPayload() { return payload; } - + @Override public String getSourceNode() { return sourceNode; } - + @Override public String getDestinationNode() { return destinationNode;