From 684fb408efb14251d3f9c5a9ab8dd0ea9b13a682 Mon Sep 17 00:00:00 2001 From: David Alves Date: Mon, 27 Oct 2025 22:53:37 +0000 Subject: [PATCH] Create IntersectionProcess main class --- .../src/main/java/sd/IntersectionProcess.java | 565 ++++++++++++++++++ 1 file changed, 565 insertions(+) create mode 100644 main/src/main/java/sd/IntersectionProcess.java diff --git a/main/src/main/java/sd/IntersectionProcess.java b/main/src/main/java/sd/IntersectionProcess.java new file mode 100644 index 0000000..cbe7cef --- /dev/null +++ b/main/src/main/java/sd/IntersectionProcess.java @@ -0,0 +1,565 @@ +package sd; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import sd.config.SimulationConfig; +import sd.model.Intersection; +import sd.model.MessageType; +import sd.model.TrafficLight; +import sd.model.TrafficLightState; +import sd.model.Vehicle; +import sd.protocol.MessageProtocol; +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) + * 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. + + /** + * Constructs a new IntersectionProcess. + * + * @param intersectionId The ID of this intersection (e.g., "Cr1"). + * @param configFilePath Path to the simulation.properties file. + * @throws IOException If configuration cannot be loaded. + */ + public IntersectionProcess(String intersectionId, String configFilePath) throws IOException { + this.intersectionId = intersectionId; + this.config = new SimulationConfig(configFilePath); + this.intersection = new Intersection(intersectionId); + this.outgoingConnections = new HashMap<>(); + this.connectionHandlerPool = Executors.newCachedThreadPool(); + this.trafficLightPool = Executors.newFixedThreadPool(4); // Max 4 directions + this.running = false; + + 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(); + + startTrafficLights(); + + System.out.println("[" + intersectionId + "] Initialization complete."); + } + + /** + * 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..."); + + // Define directions based on the actual network topology + String[] directions; + switch (intersectionId) { + case "Cr1": + // Cr1: East (to Cr2), South (to Cr4), West (from Cr2) + directions = new String[]{"East", "South", "West"}; + break; + case "Cr2": + // Cr2: West (to Cr1), East (to Cr3), South (to Cr5) + // Plus receiving from Cr1 and Cr3 + directions = new String[]{"West", "East", "South"}; + break; + case "Cr3": + // Cr3: West (to Cr2), East (to S) + directions = new String[]{"West", "East"}; + break; + case "Cr4": + // Cr4: East (to Cr5), plus pedestrian crossing + directions = new String[]{"East"}; + break; + case "Cr5": + // Cr5: East (to S), receives from Cr2 and Cr4 + directions = new String[]{"East"}; + break; + default: + // Fallback to all directions + directions = new String[]{"North", "South", "East", "West"}; + } + + 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 + ); + + intersection.addTrafficLight(light); + 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": + // Cr1 connections: → Cr2 (East), → Cr4 (South), ← Cr2 (West) + intersection.configureRoute("Cr2", "East"); // Go to Cr2 + intersection.configureRoute("Cr4", "South"); // Go to Cr4 + // Routes through other intersections to reach S + intersection.configureRoute("S", "East"); // S via Cr2 + break; + + case "Cr2": + // Cr2 connections: ↔ Cr1 (West/East), ↔ Cr3 (East/West), → Cr5 (South) + intersection.configureRoute("Cr1", "West"); // Go to Cr1 + intersection.configureRoute("Cr3", "East"); // Go to Cr3 + intersection.configureRoute("Cr5", "South"); // Go to Cr5 + intersection.configureRoute("S", "South"); // S via Cr5 or direct + break; + + case "Cr3": + // Cr3 connections: ← Cr2 (West), → S (South/East) + intersection.configureRoute("Cr2", "West"); // Go back to Cr2 + intersection.configureRoute("S", "East"); // Go to exit S + break; + + case "Cr4": + // Cr4 connections: → Cr5 (East) + intersection.configureRoute("Cr5", "East"); // Go to Cr5 + intersection.configureRoute("S", "East"); // S via Cr5 + break; + + case "Cr5": + // Cr5 connections: → S (East/South) + intersection.configureRoute("S", "East"); // Go to exit S + // Cr5 might also receive from Cr2 and Cr4 but doesn't route back + break; + + default: + System.err.println(" Warning: 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. + * + * @param light The traffic light to control. + */ + private void runTrafficLightCycle(TrafficLight light) { + System.out.println("[" + light.getId() + "] Traffic light thread started."); + + while (running) { + try { + // GREEN phase + light.changeState(TrafficLightState.GREEN); + System.out.println("[" + light.getId() + "] State: GREEN"); + + // Process vehicles while green + processGreenLight(light); + + // Wait for green duration + Thread.sleep((long) (light.getGreenTime() * 1000)); + + // RED phase + light.changeState(TrafficLightState.RED); + System.out.println("[" + light.getId() + "] State: RED"); + + // Wait for red duration + Thread.sleep((long) (light.getRedTime() * 1000)); + + } catch (InterruptedException e) { + System.out.println("[" + light.getId() + "] Traffic light thread interrupted."); + break; + } + } + + System.out.println("[" + light.getId() + "] Traffic light thread stopped."); + } + + /** + * Processes vehicles when a traffic light is GREEN. + * Dequeues vehicles and sends them to their next destination. + * + * @param light The traffic light that is currently green. + */ + private void processGreenLight(TrafficLight light) { + while (light.getState() == TrafficLightState.GREEN && light.getQueueSize() > 0) { + Vehicle vehicle = light.removeVehicle(); + + if (vehicle != null) { + // Get crossing time based on vehicle type + double crossingTime = getCrossingTimeForVehicle(vehicle); + + // Simulate crossing time + try { + Thread.sleep((long) (crossingTime * 1000)); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + + // Update vehicle statistics + vehicle.addCrossingTime(crossingTime); + + // Update intersection statistics + intersection.incrementVehiclesSent(); + + // Send vehicle to next destination + sendVehicleToNextDestination(vehicle); + } + } + } + + /** + * Gets the crossing time for a vehicle based on its type. + * + * @param vehicle The vehicle. + * @return The crossing time in seconds. + */ + private double getCrossingTimeForVehicle(Vehicle vehicle) { + switch (vehicle.getType()) { + case BIKE: + return config.getBikeVehicleCrossingTime(); + case LIGHT: + return config.getLightVehicleCrossingTime(); + case HEAVY: + return config.getHeavyVehicleCrossingTime(); + default: + return config.getLightVehicleCrossingTime(); + } + } + + /** + * Sends a vehicle to its next destination via socket connection. + * + * @param vehicle The vehicle that has crossed this intersection. + */ + private 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 + ); + + connection.sendMessage(message); + + System.out.println("[" + intersectionId + "] Sent vehicle " + vehicle.getId() + + " to " + nextDestination); + + // Update vehicle's path - advance to next destination in route + vehicle.advanceRoute(); + + } catch (IOException | InterruptedException e) { + 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 InterruptedException If connection attempt is interrupted. + */ + 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); + + SocketConnection connection = new SocketConnection(host, port); + outgoingConnections.put(destinationId, connection); + } + + return outgoingConnections.get(destinationId); + } + + /** + * Gets the host address for a destination node from configuration. + * + * @param destinationId The destination node ID. + * @return The host address. + */ + private String getHostForDestination(String destinationId) { + if (destinationId.equals("S")) { + return config.getExitHost(); + } else if (destinationId.startsWith("Cr")) { + return config.getIntersectionHost(destinationId); + } else { + return config.getDashboardHost(); + } + } + + /** + * Gets the port number for a destination node from configuration. + * + * @param destinationId The destination node ID. + * @return The port number. + */ + private int getPortForDestination(String destinationId) { + if (destinationId.equals("S")) { + return config.getExitPort(); + } else if (destinationId.startsWith("Cr")) { + return config.getIntersectionPort(destinationId); + } else { + return config.getDashboardPort(); + } + } + + /** + * Starts the server socket and begins accepting incoming connections. + * This is the main listening loop of the process. + * + * @throws IOException If the server socket cannot be created. + */ + public void start() throws IOException { + int port = config.getIntersectionPort(intersectionId); + serverSocket = new ServerSocket(port); + running = true; + + System.out.println("\n[" + intersectionId + "] Server started on port " + port); + System.out.println("[" + intersectionId + "] Waiting for incoming connections...\n"); + + // Main accept loop + while (running) { + try { + Socket clientSocket = serverSocket.accept(); + + // 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()); + } + } + } + } + + /** + * Handles an incoming connection from another process. + * Continuously listens for vehicle transfer messages. + * + * @param clientSocket The accepted socket connection. + */ + private void handleIncomingConnection(Socket clientSocket) { + try (SocketConnection connection = new SocketConnection(clientSocket)) { + + System.out.println("[" + intersectionId + "] New connection accepted from " + + 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()); + + // Add vehicle to appropriate queue + intersection.receiveVehicle(vehicle); + } + + } catch (ClassNotFoundException e) { + System.err.println("[" + intersectionId + "] Unknown message type received: " + + e.getMessage()); + } + } + + } catch (IOException e) { + if (running) { + System.err.println("[" + intersectionId + "] Connection error: " + e.getMessage()); + } + } + } + + /** + * Stops the intersection process gracefully. + * Shuts down all threads and closes all connections. + */ + public void shutdown() { + System.out.println("\n[" + intersectionId + "] Shutting down..."); + running = false; + + // Close server socket + try { + if (serverSocket != null && !serverSocket.isClosed()) { + serverSocket.close(); + } + } catch (IOException e) { + System.err.println("[" + intersectionId + "] Error closing server socket: " + + e.getMessage()); + } + + // Shutdown thread pools + trafficLightPool.shutdown(); + connectionHandlerPool.shutdown(); + + try { + if (!trafficLightPool.awaitTermination(5, TimeUnit.SECONDS)) { + trafficLightPool.shutdownNow(); + } + if (!connectionHandlerPool.awaitTermination(5, TimeUnit.SECONDS)) { + connectionHandlerPool.shutdownNow(); + } + } catch (InterruptedException e) { + trafficLightPool.shutdownNow(); + connectionHandlerPool.shutdownNow(); + } + + // 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)); + } + + /** + * Main method to start an intersection process. + * + * @param args Command-line arguments: + * args[0] - Intersection ID (required, e.g., "Cr1") + * args[1] - Config file path (optional, defaults to "simulation.properties") + */ + public static void main(String[] args) { + if (args.length < 1) { + System.err.println("Usage: java IntersectionProcess [configFile]"); + System.err.println("Example: java IntersectionProcess Cr1"); + System.exit(1); + } + + String intersectionId = args[0]; + String configFile = args.length > 1 ? args[1] : "simulation.properties"; + + IntersectionProcess process = null; + + try { + process = new IntersectionProcess(intersectionId, configFile); + process.initialize(); + + // Add shutdown hook for graceful termination + final IntersectionProcess finalProcess = process; + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + finalProcess.shutdown(); + })); + + // Start the process + process.start(); + + } catch (IOException e) { + System.err.println("Error starting intersection process: " + e.getMessage()); + e.printStackTrace(); + System.exit(1); + } + } + + // --- 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; + } + } +}