From 83c3d65e38ed822430befd64db594a76a4955022 Mon Sep 17 00:00:00 2001 From: Leandro Afonso Date: Sun, 7 Dec 2025 19:57:40 +0000 Subject: [PATCH 1/2] more javadoc - dashboard, des, logging --- .../main/java/sd/dashboard/DashboardUI.java | 96 +++++++++++-------- .../dashboard/SimulationProcessManager.java | 61 ++++++++---- .../main/java/sd/dashboard/StatsMessage.java | 35 ++++++- .../java/sd/dashboard/StatsUpdatePayload.java | 43 ++++++++- .../src/main/java/sd/des/SimulationEvent.java | 70 ++++++++++---- .../main/java/sd/des/TrafficLightEvent.java | 25 ++++- .../src/main/java/sd/logging/EventLogger.java | 82 +++++++++++----- main/src/main/java/sd/logging/EventType.java | 18 +++- .../main/java/sd/logging/VehicleTracer.java | 79 +++++++++------ 9 files changed, 370 insertions(+), 139 deletions(-) diff --git a/main/src/main/java/sd/dashboard/DashboardUI.java b/main/src/main/java/sd/dashboard/DashboardUI.java index 88378d3..8757784 100644 --- a/main/src/main/java/sd/dashboard/DashboardUI.java +++ b/main/src/main/java/sd/dashboard/DashboardUI.java @@ -30,8 +30,22 @@ import sd.config.SimulationConfig; import sd.model.VehicleType; /** - * JavaFX-based Dashboard UI for displaying real-time simulation statistics. - * Provides a graphical interface with auto-updating statistics panels. + * Interface Gráfica (GUI) baseada em JavaFX para visualização de telemetria em tempo real. + *

+ * Esta classe atua como a camada de apresentação (View) do sistema. Implementa o padrão + * Observer (via polling) para refletir o estado do modelo {@link DashboardStatistics} + * nos componentes visuais. + *

+ * Aspetos Técnicos Relevantes: + *

*/ public class DashboardUI extends Application { @@ -52,7 +66,7 @@ public class DashboardUI extends Application { // Intersection Table private TableView intersectionTable; - // Update scheduler + // Update scheduler (Background Thread) private ScheduledExecutorService updateScheduler; // Configuration controls @@ -60,6 +74,10 @@ public class DashboardUI extends Application { private String selectedConfigFile = "simulation.properties"; private Label configInfoLabel; + /** + * Ponto de entrada da aplicação JavaFX. + * Configura o Stage primário, inicializa o servidor de backend e constrói a árvore de cena (Scene Graph). + */ @Override public void start(Stage primaryStage) { try { @@ -72,29 +90,27 @@ public class DashboardUI extends Application { server = new DashboardServer(config); statistics = server.getStatistics(); - // Start the dashboard server + // Start the dashboard server (Backend listening port) server.start(); - // Build UI + // Build UI Layout BorderPane root = new BorderPane(); root.getStyleClass().add("root"); - // Header + // Header (Top) VBox header = createHeader(); root.setTop(header); - // Main content + // Main content (Center) VBox mainContent = createMainContent(); root.setCenter(mainContent); - // Footer + // Footer (Bottom) HBox footer = createFooter(); root.setBottom(footer); - // Create scene + // Create scene & apply CSS Scene scene = new Scene(root, 1200, 850); - - // Load CSS String cssUrl = getClass().getResource("/dashboard.css").toExternalForm(); scene.getStylesheets().add(cssUrl); @@ -102,10 +118,10 @@ public class DashboardUI extends Application { primaryStage.setScene(scene); primaryStage.show(); - // Start periodic updates + // Start periodic updates loop startPeriodicUpdates(); - // Handle window close + // Handle window close (Graceful shutdown) primaryStage.setOnCloseRequest(event -> { shutdown(); }); @@ -149,6 +165,8 @@ public class DashboardUI extends Application { // Passar o ficheiro de configuração selecionado processManager.setConfigFile(selectedConfigFile); processManager.startSimulation(); + + // Toggle UI state btnStart.setDisable(true); btnStop.setDisable(false); configFileSelector.setDisable(true); // Bloquear mudanças durante simulação @@ -159,6 +177,8 @@ public class DashboardUI extends Application { btnStop.setOnAction(e -> { processManager.stopSimulation(); + + // Toggle UI state btnStart.setDisable(false); btnStop.setDisable(true); configFileSelector.setDisable(false); // Desbloquear para nova simulação @@ -435,13 +455,23 @@ public class DashboardUI extends Application { grid.add(container, colGroup, row); } + /** + * Inicia o ciclo de polling em background. + * Atualiza a UI a cada 100ms. + */ private void startPeriodicUpdates() { updateScheduler = Executors.newSingleThreadScheduledExecutor(); updateScheduler.scheduleAtFixedRate(() -> { + // Crucial: Encapsular atualização de UI em Platform.runLater + // para garantir execução na JavaFX Application Thread Platform.runLater(this::updateUI); }, 0, 100, TimeUnit.MILLISECONDS); } + /** + * Sincroniza o estado atual do objeto Statistics com os controlos JavaFX. + * Chamado periodicamente pela thread de UI. + */ private void updateUI() { // Update global statistics lblVehiclesGenerated.setText(String.valueOf(statistics.getTotalVehiclesGenerated())); @@ -548,7 +578,9 @@ public class DashboardUI extends Application { launch(args); } - // Inner classes for TableView data models + // --- DTOs para Data Binding nas Tabelas --- + + /** DTO para linhas da tabela de Tipos de Veículo. */ public static class VehicleTypeRow { private final String vehicleType; private final int count; @@ -560,19 +592,12 @@ public class DashboardUI extends Application { this.avgWaitTime = avgWaitTime; } - public String getVehicleType() { - return vehicleType; - } - - public int getCount() { - return count; - } - - public String getAvgWaitTime() { - return avgWaitTime; - } + public String getVehicleType() { return vehicleType; } + public int getCount() { return count; } + public String getAvgWaitTime() { return avgWaitTime; } } + /** DTO para linhas da tabela de Interseções. */ public static class IntersectionRow { private final String intersectionId; private final int arrivals; @@ -586,20 +611,9 @@ public class DashboardUI extends Application { this.queueSize = queueSize; } - public String getIntersectionId() { - return intersectionId; - } - - public int getArrivals() { - return arrivals; - } - - public int getDepartures() { - return departures; - } - - public int getQueueSize() { - return queueSize; - } + public String getIntersectionId() { return intersectionId; } + public int getArrivals() { return arrivals; } + public int getDepartures() { return departures; } + public int getQueueSize() { return queueSize; } } -} +} \ No newline at end of file diff --git a/main/src/main/java/sd/dashboard/SimulationProcessManager.java b/main/src/main/java/sd/dashboard/SimulationProcessManager.java index 56d1c33..063a355 100644 --- a/main/src/main/java/sd/dashboard/SimulationProcessManager.java +++ b/main/src/main/java/sd/dashboard/SimulationProcessManager.java @@ -6,9 +6,17 @@ import java.util.ArrayList; import java.util.List; /** - * Gere o ciclo de vida dos processos de simulação (Intersections, Exit Node, - * Coordinator). - * Permite iniciar e parar a simulação distribuída dentro da aplicação Java. + * Orquestrador de processos para o ambiente de simulação distribuída. + *

+ * Esta classe atua como um supervisor (Process Manager), responsável pelo bootstrapping + * e teardown das múltiplas Java Virtual Machines (JVMs) que compõem o sistema. + *

+ * Funcionalidades principais: + *

*/ public class SimulationProcessManager { @@ -16,6 +24,10 @@ public class SimulationProcessManager { private final String classpath; private String configFile; + /** + * Inicializa o gestor capturando o classpath da JVM atual. + * Isto garante que os processos filhos herdam as mesmas dependências e configurações de ambiente. + */ public SimulationProcessManager() { this.runningProcesses = new ArrayList<>(); this.classpath = System.getProperty("java.class.path"); @@ -23,9 +35,9 @@ public class SimulationProcessManager { } /** - * Define o ficheiro de configuração a usar. - * - * @param configFile nome do ficheiro (ex: "simulation-low.properties") + * Define o perfil de configuração a ser injetado nos processos filhos. + * Útil para alternar entre cenários (Low/Medium/High Load) dinamicamente. + * * @param configFile Nome do ficheiro de propriedades (ex: "simulation-low.properties"). */ public void setConfigFile(String configFile) { this.configFile = "src/main/resources/" + configFile; @@ -33,9 +45,16 @@ public class SimulationProcessManager { } /** - * Inicia a simulação completa: 5 Intersections, 1 Exit Node, e 1 Coordinator. - * - * @throws IOException se um processo falhar ao iniciar + * Executa o procedimento de arranque (Bootstrap) da simulação distribuída. + *

+ * A ordem de inicialização é crítica para evitar Race Conditions na conexão TCP: + *

    + *
  1. Workers (Interseções): Iniciam os ServerSockets.
  2. + *
  3. Sink (Exit Node): Prepara-se para receber métricas finais.
  4. + *
  5. Delay de Estabilização: Pausa de 1s para garantir que os sockets estão em LISTENING.
  6. + *
  7. Source (Coordinator): Inicia a geração de carga e conecta-se aos nós.
  8. + *
+ * * @throws IOException Se falhar o fork de algum processo. */ public void startSimulation() throws IOException { if (!runningProcesses.isEmpty()) { @@ -65,8 +84,11 @@ public class SimulationProcessManager { } /** - * Checks if the coordinator process (last process started) is still running. - * When the coordinator finishes, the simulation is complete. + * Verifica o estado de "liveness" da simulação monitorizando o processo Coordenador. + *

+ * Como o Coordenador gere o relógio DES e a geração de eventos, a sua terminação + * (após o drain time) sinaliza o fim efetivo da simulação. + * * @return true se o Coordenador ainda estiver ativo (alive). */ public boolean isSimulationRunning() { if (runningProcesses.isEmpty()) { @@ -78,8 +100,10 @@ public class SimulationProcessManager { } /** - * Waits for the simulation to complete naturally. - * Returns true if completed, false if timeout. + * Bloqueia a thread atual até que a simulação termine naturalmente ou ocorra timeout. + * * @param timeoutSeconds Tempo máximo de espera. + * @return true se terminou, false se o timeout expirou. + * @throws InterruptedException Se a espera for interrompida. */ public boolean waitForCompletion(long timeoutSeconds) throws InterruptedException { if (runningProcesses.isEmpty()) { @@ -91,7 +115,11 @@ public class SimulationProcessManager { } /** - * Stops all running simulation processes. + * Executa o procedimento de encerramento (Teardown) de todos os processos. + *

+ * Tenta primeiro uma paragem graciosa (`SIGTERM`), aguarda meio segundo, e + * força a paragem (`SIGKILL`) para processos persistentes, garantindo que não + * ficam processos órfãos no SO. */ public void stopSimulation() { System.out.println("Stopping simulation processes..."); @@ -120,7 +148,8 @@ public class SimulationProcessManager { } /** - * Helper para iniciar um único processo Java. + * Helper de baixo nível para construção e lançamento de processos Java. + * Configura o redirecionamento de I/O para ficheiros de log na diretoria temporária do SO. */ private void startProcess(String className, String arg) throws IOException { String javaBin = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java"; @@ -152,4 +181,4 @@ public class SimulationProcessManager { // print where the logs are actually going System.out.println("Logs redirected to: " + logFile.getAbsolutePath()); } -} +} \ No newline at end of file diff --git a/main/src/main/java/sd/dashboard/StatsMessage.java b/main/src/main/java/sd/dashboard/StatsMessage.java index 7209130..abc4730 100644 --- a/main/src/main/java/sd/dashboard/StatsMessage.java +++ b/main/src/main/java/sd/dashboard/StatsMessage.java @@ -4,7 +4,14 @@ import sd.model.MessageType; import sd.protocol.MessageProtocol; /** - * Message wrapper for sending statistics to the dashboard. + * Implementação concreta do protocolo de mensagens destinada ao transporte de telemetria. + *

+ * Esta classe atua como um envelope especializado para o envio de dados estatísticos + * (encapsulados em {@link StatsUpdatePayload}) dos nós operacionais (Interseções, Coordenador) + * para o servidor de Dashboard centralizado. + *

+ * Diferencia-se das mensagens de controlo genéricas por ter o destino fixado no + * "DashboardServer" e um tipo de mensagem imutável ({@code STATS_UPDATE}). */ public class StatsMessage implements MessageProtocol { @@ -14,27 +21,49 @@ public class StatsMessage implements MessageProtocol { private final String destinationNode; private final StatsUpdatePayload payload; + /** + * Cria uma nova mensagem de estatística. + * + * @param sourceNode O ID do nó que gerou as estatísticas (ex: "Cr1", "ExitNode"). + * @param payload O objeto DTO contendo os dados estatísticos brutos ou agregados. + */ public StatsMessage(String sourceNode, StatsUpdatePayload payload) { this.sourceNode = sourceNode; - this.destinationNode = "DashboardServer"; + this.destinationNode = "DashboardServer"; // Destino implícito e fixo this.payload = payload; } + /** + * Retorna o tipo da mensagem, que identifica semanticamente o conteúdo para o recetor. + * @return Sempre {@link MessageType#STATS_UPDATE}. + */ @Override public MessageType getType() { return MessageType.STATS_UPDATE; } + /** + * Obtém a carga útil da mensagem. + * @return O objeto {@link StatsUpdatePayload} associado. + */ @Override public Object getPayload() { return payload; } + /** + * Identifica a origem da mensagem. + * @return O ID do nó remetente. + */ @Override public String getSourceNode() { return sourceNode; } + /** + * Identifica o destino da mensagem. + * @return Sempre "DashboardServer". + */ @Override public String getDestinationNode() { return destinationNode; @@ -45,4 +74,4 @@ public class StatsMessage implements MessageProtocol { return String.format("StatsMessage[from=%s, to=%s, payload=%s]", sourceNode, destinationNode, payload); } -} +} \ No newline at end of file diff --git a/main/src/main/java/sd/dashboard/StatsUpdatePayload.java b/main/src/main/java/sd/dashboard/StatsUpdatePayload.java index e62e866..6f1915e 100644 --- a/main/src/main/java/sd/dashboard/StatsUpdatePayload.java +++ b/main/src/main/java/sd/dashboard/StatsUpdatePayload.java @@ -7,25 +7,60 @@ import java.util.Map; import sd.model.VehicleType; /** - * DTO para atualizações de estatísticas ao dashboard. - * Campos com valor -1 não são atualizados nesta mensagem. + * Objeto de Transferência de Dados (DTO) otimizado para transporte de telemetria. + *

+ * Esta classe encapsula as métricas de desempenho enviadas pelos nós da simulação (Coordenador, + * Interseções, ExitNode) para o Dashboard. Foi desenhada para suportar atualizações parciais + * (Sparse Updates): + *

+ * Implementa {@link Serializable} para transmissão direta via Java Sockets. + * + +[Image of data transfer object pattern] + */ public class StatsUpdatePayload implements Serializable { private static final long serialVersionUID = 1L; + // Global Metrics (Coordinator/ExitNode) + /** Total gerado. Valor -1 indica que este campo deve ser ignorado na atualização. */ private int totalVehiclesGenerated = -1; + + /** Total completado. Valor -1 indica que este campo deve ser ignorado. */ private int totalVehiclesCompleted = -1; + + /** Tempo total de sistema acumulado (ms). Valor -1 indica que deve ser ignorado. */ private long totalSystemTime = -1; + + /** Tempo total de espera acumulado (ms). Valor -1 indica que deve ser ignorado. */ private long totalWaitingTime = -1; + // Intersection Metrics (Worker Nodes) + /** Número de veículos que entraram na interseção desde o último reporte. */ private int intersectionArrivals = 0; + + /** Número de veículos que saíram da interseção desde o último reporte. */ private int intersectionDepartures = 0; + + /** Snapshot do tamanho atual da fila na interseção. */ private int intersectionQueueSize = 0; + // Detailed Breakdowns + /** Contagem acumulada por tipo de veículo. */ private Map vehicleTypeCounts; + + /** Tempos de espera acumulados por tipo de veículo. */ private Map vehicleTypeWaitTimes; + /** + * Inicializa o payload com os mapas vazios e contadores globais a -1 (estado neutro). + */ public StatsUpdatePayload() { this.vehicleTypeCounts = new HashMap<>(); this.vehicleTypeWaitTimes = new HashMap<>(); @@ -67,6 +102,8 @@ public class StatsUpdatePayload implements Serializable { return vehicleTypeWaitTimes; } + // Setters implementam Fluent Interface para construção encadeada + public StatsUpdatePayload setTotalVehiclesGenerated(int totalVehiclesGenerated) { this.totalVehiclesGenerated = totalVehiclesGenerated; return this; @@ -118,4 +155,4 @@ public class StatsUpdatePayload implements Serializable { totalVehiclesGenerated, totalVehiclesCompleted, intersectionArrivals, intersectionDepartures, intersectionQueueSize); } -} +} \ No newline at end of file diff --git a/main/src/main/java/sd/des/SimulationEvent.java b/main/src/main/java/sd/des/SimulationEvent.java index 7d486d9..589bc5e 100644 --- a/main/src/main/java/sd/des/SimulationEvent.java +++ b/main/src/main/java/sd/des/SimulationEvent.java @@ -3,31 +3,46 @@ package sd.des; import java.io.Serializable; /** - * Evento discreto da simulação. - * - *

Unidade fundamental de execução num sistema DES: + * Representa um evento atómico e imutável no contexto da Simulação de Eventos Discretos (DES). + *

+ * Esta classe é a unidade fundamental de processamento. Numa arquitetura DES, o estado do sistema + * não muda continuamente, mas sim em instantes discretos definidos por estes eventos. + *

+ * Características principais: *

    - *
  • timestamp - quando ocorre - *
  • type - o que acontece - *
  • payload - dados associados - *
  • location - qual processo o trata + *
  • Ordenação Temporal: Implementa {@link Comparable} para ser armazenado numa Fila de + * Eventos Futuros (FEL - Future Event List), garantindo execução cronológica.
  • + *
  • Distribuído: Implementa {@link Serializable} para permitir que eventos gerados num nó + * (ex: Coordenador) sejam transmitidos e executados noutro (ex: Interseção).
  • + *
  • Polimórfico: Transporta um {@code payload} genérico, permitindo associar qualquer + * entidade (Veículo, Sinal, etc.) ao evento.
  • *
*/ public class SimulationEvent implements Comparable, Serializable { private static final long serialVersionUID = 1L; + /** O instante virtual exato em que o evento deve ser processado. */ private final double timestamp; + + /** A categoria do evento (ex: VEHICLE_ARRIVAL, LIGHT_CHANGE). */ private final DESEventType type; + + /** Dados contextuais associados (ex: o objeto Vehicle que chegou). */ private final Object payload; - private final String location; // Process ID (e.g., "Cr1", "Coordinator", "Exit") /** - * Cria um novo evento de simulação. - * - * @param timestamp instante do evento (tempo de simulação em segundos) - * @param type tipo de evento - * @param payload dados associados (ex: objeto Vehicle) - * @param location processo que trata o evento + * O identificador do nó de destino onde o evento deve ser executado. + * (ex: "Cr1", "Coordinator", "ExitNode"). Se null, é um evento local. + */ + private final String location; + + /** + * Instancia um novo evento de simulação completo. + * + * @param timestamp Instante de execução (segundos virtuais). + * @param type Tipo enumerado do evento. + * @param payload Objeto de dados associado (pode ser null). + * @param location ID do processo alvo para execução distribuída. */ public SimulationEvent(double timestamp, DESEventType type, Object payload, String location) { this.timestamp = timestamp; @@ -36,7 +51,14 @@ public class SimulationEvent implements Comparable, Serializabl this.location = location; } - /** Cria evento sem localização (para eventos locais) */ + /** + * Construtor de conveniência para eventos locais (dentro do mesmo processo). + * Define {@code location} como null. + * + * @param timestamp Instante de execução. + * @param type Tipo do evento. + * @param payload Objeto de dados associado. + */ public SimulationEvent(double timestamp, DESEventType type, Object payload) { this(timestamp, type, payload, null); } @@ -58,8 +80,18 @@ public class SimulationEvent implements Comparable, Serializabl } /** - * Ordena eventos por timestamp (mais cedo primeiro). - * Em caso de empate, ordena por tipo para determinismo. + * Define a ordem natural de processamento na Fila de Prioridade. + *

+ * Lógica de Ordenação: + *

    + *
  1. Primária (Tempo): Eventos com menor timestamp ocorrem primeiro.
  2. + *
  3. Secundária (Determinismo): Em caso de empate temporal (simultaneidade), + * ordena alfabeticamente pelo nome do tipo. Isto garante que execuções repetidas + * da simulação produzam exatamente o mesmo resultado (determinismo estrito).
  4. + *
+ * + * @param other O outro evento a comparar. + * @return Inteiro negativo, zero ou positivo conforme a ordem. */ @Override public int compareTo(SimulationEvent other) { @@ -67,7 +99,7 @@ public class SimulationEvent implements Comparable, Serializabl if (timeComparison != 0) { return timeComparison; } - // Tie-breaker: order by event type name + // Tie-breaker: order by event type name to ensure reproducible runs return this.type.name().compareTo(other.type.name()); } @@ -95,4 +127,4 @@ public class SimulationEvent implements Comparable, Serializabl result = 31 * result + (location != null ? location.hashCode() : 0); return result; } -} +} \ No newline at end of file diff --git a/main/src/main/java/sd/des/TrafficLightEvent.java b/main/src/main/java/sd/des/TrafficLightEvent.java index 9ca1357..0b27107 100644 --- a/main/src/main/java/sd/des/TrafficLightEvent.java +++ b/main/src/main/java/sd/des/TrafficLightEvent.java @@ -3,28 +3,47 @@ package sd.des; import sd.model.TrafficLight; /** - * Payload for traffic light change events. - * Contains the traffic light and its direction. + * Encapsula o contexto de dados para eventos de mudança de estado de semáforos. + *

+ * Este objeto atua como o payload transportado por um {@link SimulationEvent} + * quando o tipo de evento é relacionado com controlo de tráfego (ex: mudança Verde -> Amarelo). + * Permite que o motor DES identifique exatamente qual instância de {@link TrafficLight} + * deve ser atualizada numa determinada interseção e direção. */ public class TrafficLightEvent { private final TrafficLight light; private final String direction; private final String intersectionId; + /** + * Cria um novo payload de evento de semáforo. + * @param light A instância do objeto semáforo a ser manipulado. + * @param direction A direção cardeal associada (ex: "North", "East"). + * @param intersectionId O identificador da interseção onde o semáforo reside. + */ public TrafficLightEvent(TrafficLight light, String direction, String intersectionId) { this.light = light; this.direction = direction; this.intersectionId = intersectionId; } + /** + * @return A referência direta para o objeto de domínio do semáforo. + */ public TrafficLight getLight() { return light; } + /** + * @return A direção do fluxo controlado por este semáforo. + */ public String getDirection() { return direction; } + /** + * @return O ID da interseção pai. + */ public String getIntersectionId() { return intersectionId; } @@ -33,4 +52,4 @@ public class TrafficLightEvent { public String toString() { return String.format("TrafficLightEvent[%s-%s]", intersectionId, direction); } -} +} \ No newline at end of file diff --git a/main/src/main/java/sd/logging/EventLogger.java b/main/src/main/java/sd/logging/EventLogger.java index f653ff9..dd08b23 100644 --- a/main/src/main/java/sd/logging/EventLogger.java +++ b/main/src/main/java/sd/logging/EventLogger.java @@ -11,10 +11,19 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; /** - * Sistema de registo centralizado de eventos para a simulação distribuída. - * - *

Regista todos os eventos da simulação num ficheiro com timestamps e categorização. - * Thread-safe e não-bloqueante para impacto mínimo na performance.

+ * Motor de logging assíncrono e thread-safe para a simulação distribuída. + *

+ * Implementa o padrão Singleton para garantir um ponto centralizado de registo. + * Utiliza o padrão Producer-Consumer com uma {@link BlockingQueue} para desacoplar + * a geração de eventos (crítica para a performance da simulação) da persistência em disco + * (operação de I/O lenta). + *

+ * Garantias: + *

    + *
  • Non-blocking writes (para a thread chamadora, na maioria dos casos).
  • + *
  • Ordering cronológico aproximado (FIFO na fila).
  • + *
  • Graceful Shutdown (flush de logs pendentes ao terminar).
  • + *
*/ public class EventLogger { @@ -22,20 +31,33 @@ public class EventLogger { private static final Object instanceLock = new Object(); private final PrintWriter writer; + + /** Buffer de memória para absorver picos de eventos (Burst traffic). */ private final BlockingQueue logQueue; + + /** Thread dedicada (Consumer) para escrita em ficheiro. */ private final Thread writerThread; + private final AtomicBoolean running; private final SimpleDateFormat timestampFormat; private final long simulationStartMillis; - /** Construtor privado para padrão singleton */ + /** + * Inicializa o sistema de logs. + * Abre o ficheiro, escreve o cabeçalho e inicia a thread consumidora. + * + * @param logFilePath Caminho relativo ou absoluto do ficheiro de log. + * @throws IOException Se não for possível criar ou escrever no ficheiro. + */ private EventLogger(String logFilePath) throws IOException { + // Auto-flush ativado para garantir persistência, mas gerido pelo buffer do BufferedWriter this.writer = new PrintWriter(new BufferedWriter(new FileWriter(logFilePath, false)), true); - this.logQueue = new LinkedBlockingQueue<>(10000); + this.logQueue = new LinkedBlockingQueue<>(10000); // Backpressure: limita a 10k eventos pendentes this.running = new AtomicBoolean(true); this.timestampFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); this.simulationStartMillis = System.currentTimeMillis(); + // Header inicial do log writer.println("=".repeat(80)); writer.println("SIMULATION EVENT LOG"); writer.println("Started: " + timestampFormat.format(new Date())); @@ -47,11 +69,16 @@ public class EventLogger { writer.flush(); this.writerThread = new Thread(this::processLogQueue, "EventLogger-Writer"); - this.writerThread.setDaemon(true); + this.writerThread.setDaemon(true); // Permite que a JVM termine se apenas esta thread sobrar this.writerThread.start(); } - /** Obtém ou cria a instância singleton */ + /** + * Obtém a instância única do logger (Lazy Initialization). + * Se não existir, cria uma predefinida em "logs/simulation-events.log". + * + * @return A instância singleton. + */ public static EventLogger getInstance() { if (instance == null) { synchronized (instanceLock) { @@ -72,7 +99,8 @@ public class EventLogger { } /** - * Initialize with custom log file path. + * Reinicializa o logger com um ficheiro específico. + * Útil para testes ou configurações personalizadas. */ public static void initialize(String logFilePath) throws IOException { synchronized (instanceLock) { @@ -84,7 +112,13 @@ public class EventLogger { } /** - * Logs an event (non-blocking). + * Regista um evento genérico. + * Esta operação é não-bloqueante (retorna imediatamente após colocar na fila), + * exceto se a fila estiver cheia (backpressure). + * + * @param eventType Categoria do evento. + * @param component Nome do componente (ex: "Coordinator", "IntersectionProcess"). + * @param description Detalhes do evento. */ public void log(EventType eventType, String component, String description) { if (!running.get()) return; @@ -96,7 +130,7 @@ public class EventLogger { description ); - // Non-blocking offer - if queue is full, drop oldest + // Non-blocking offer - if queue is full, drop oldest or warn if (!logQueue.offer(entry)) { // Queue full - this shouldn't happen with 10k buffer, but handle gracefully System.err.println("EventLogger queue full - dropping event: " + eventType); @@ -104,14 +138,14 @@ public class EventLogger { } /** - * Logs an event with vehicle context. + * Regista um evento associado a um veículo específico (Helper method). */ public void logVehicle(EventType eventType, String component, String vehicleId, String description) { log(eventType, component, "[" + vehicleId + "] " + description); } /** - * Logs an error event. + * Regista um erro ou exceção com formatação apropriada. */ public void logError(String component, String description, Exception e) { String fullDescription = description + (e != null ? ": " + e.getMessage() : ""); @@ -119,11 +153,13 @@ public class EventLogger { } /** - * Background thread that writes log entries to file. + * Lógica da thread consumidora (Worker Thread). + * Retira eventos da fila e escreve no disco continuamente. */ private void processLogQueue() { while (running.get() || !logQueue.isEmpty()) { try { + // Poll com timeout para permitir verificar a flag 'running' periodicamente LogEntry entry = logQueue.poll(100, java.util.concurrent.TimeUnit.MILLISECONDS); if (entry != null) { writeEntry(entry); @@ -134,7 +170,7 @@ public class EventLogger { } } - // Flush remaining entries + // Flush final: garantir que eventos restantes na fila são escritos antes de morrer while (!logQueue.isEmpty()) { LogEntry entry = logQueue.poll(); if (entry != null) { @@ -144,7 +180,7 @@ public class EventLogger { } /** - * Writes a single log entry to file. + * Formata e escreve uma entrada de log no PrintWriter. */ private void writeEntry(LogEntry entry) { String timestamp = timestampFormat.format(new Date(entry.timestampMillis)); @@ -158,7 +194,7 @@ public class EventLogger { entry.description ); - // Flush periodically for real-time viewing + // Flush periódico inteligente: se a carga for baixa, garante que vemos logs em tempo real if (logQueue.size() < 10) { writer.flush(); } @@ -170,15 +206,17 @@ public class EventLogger { } /** - * Shuts down the logger and flushes all pending entries. + * Encerra o logger de forma segura. + * Desativa a aceitação de novos eventos, aguarda que a fila esvazie (flush) + * e fecha o ficheiro. */ public void shutdown() { if (!running.compareAndSet(true, false)) { - return; // Already shut down + return; // Já encerrado } try { - // Wait for writer thread to finish + // Wait for writer thread to finish flushing writerThread.join(5000); // Wait up to 5 seconds // Write footer @@ -195,7 +233,7 @@ public class EventLogger { } /** - * Internal class to represent a log entry. + * DTO interno imutável para armazenar dados do evento na fila. */ private static class LogEntry { final long timestampMillis; @@ -210,4 +248,4 @@ public class EventLogger { this.description = description; } } -} +} \ No newline at end of file diff --git a/main/src/main/java/sd/logging/EventType.java b/main/src/main/java/sd/logging/EventType.java index 634fabd..910ae94 100644 --- a/main/src/main/java/sd/logging/EventType.java +++ b/main/src/main/java/sd/logging/EventType.java @@ -1,34 +1,46 @@ package sd.logging; /** - * Tipos de eventos que podem ocorrer na simulação. - * Usados para categorizar e filtrar logs. + * Taxonomia oficial de eventos para o subsistema de logging centralizado. + *

+ * Este enumerado padroniza a categorização de todas as ocorrências na simulação, permitindo: + *

    + *
  • Filtragem granular de logs (ex: ver apenas erros ou apenas tráfego de rede).
  • + *
  • Análise estatística post-mortem (parsear logs para calcular latências).
  • + *
  • Correlação de eventos distribuídos (seguir o rastro de um veículo através de vários nós).
  • + *
*/ public enum EventType { + // --- Ciclo de Vida do Veículo --- VEHICLE_GENERATED("Vehicle Generated"), VEHICLE_ARRIVED("Vehicle Arrived"), VEHICLE_QUEUED("Vehicle Queued"), VEHICLE_DEPARTED("Vehicle Departed"), VEHICLE_EXITED("Vehicle Exited"), + // --- Controlo de Semáforos e Exclusão Mútua --- LIGHT_CHANGED_GREEN("Light Changed to Green"), LIGHT_CHANGED_RED("Light Changed to Red"), LIGHT_REQUEST_GREEN("Light Requested Green"), LIGHT_RELEASE_GREEN("Light Released Green"), + // --- Ciclo de Vida da Simulação/Processos --- SIMULATION_STARTED("Simulation Started"), SIMULATION_STOPPED("Simulation Stopped"), PROCESS_STARTED("Process Started"), PROCESS_STOPPED("Process Stopped"), + // --- Configuração e Telemetria --- STATS_UPDATE("Statistics Update"), CONFIG_CHANGED("Configuration Changed"), + // --- Camada de Rede (TCP/Sockets) --- CONNECTION_ESTABLISHED("Connection Established"), CONNECTION_LOST("Connection Lost"), MESSAGE_SENT("Message Sent"), MESSAGE_RECEIVED("Message Received"), + // --- Tratamento de Exceções --- ERROR("Error"); private final String displayName; @@ -45,4 +57,4 @@ public enum EventType { public String toString() { return displayName; } -} +} \ No newline at end of file diff --git a/main/src/main/java/sd/logging/VehicleTracer.java b/main/src/main/java/sd/logging/VehicleTracer.java index 194f319..611e6aa 100644 --- a/main/src/main/java/sd/logging/VehicleTracer.java +++ b/main/src/main/java/sd/logging/VehicleTracer.java @@ -12,16 +12,18 @@ import java.util.concurrent.ConcurrentHashMap; import sd.model.Vehicle; /** - * Rastreia e regista a viagem completa de veículos individuais. - * + * Subsistema de auditoria granular responsável pelo rastreio detalhado (Tracing) de veículos individuais. *

- * Cria ficheiros de trace detalhados com: + * Diferente do {@link EventLogger} (que regista eventos globais do sistema), esta classe foca-se + * na perspetiva do agente. Cria um ficheiro de rastro dedicado (`.trace`) para cada veículo + * monitorizado, registando cronologicamente cada interação com a infraestrutura (interseções, + * filas, semáforos). + *

+ * Funcionalidades: *

    - *
  • Timestamps de todos os eventos - *
  • Localizações (interseções) - *
  • Tempos de espera em cada semáforo - *
  • Tempos de travessia - *
  • Tempo total no sistema + *
  • Análise forense de percursos individuais.
  • + *
  • Validação de tempos de espera e travessia por nó.
  • + *
  • Cálculo de eficiência de rota (tempo em movimento vs. tempo parado).
  • *
*/ public class VehicleTracer { @@ -29,12 +31,18 @@ public class VehicleTracer { private static VehicleTracer instance; private static final Object instanceLock = new Object(); + /** Mapa thread-safe de sessões de trace ativas (VehicleID -> TraceHandler). */ private final Map trackedVehicles; + private final SimpleDateFormat timestampFormat; private final long simulationStartMillis; private final String traceDirectory; - /** Construtor privado (singleton) */ + /** + * Inicializa o tracer e prepara o diretório de saída. + * + * @param traceDirectory Caminho para armazenamento dos ficheiros .trace. + */ private VehicleTracer(String traceDirectory) { this.trackedVehicles = new ConcurrentHashMap<>(); this.timestampFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); @@ -48,7 +56,10 @@ public class VehicleTracer { } } - /** Obtém ou cria a instância singleton */ + /** + * Obtém a instância única do tracer (Singleton). + * @return A instância global. + */ public static VehicleTracer getInstance() { if (instance == null) { synchronized (instanceLock) { @@ -60,7 +71,10 @@ public class VehicleTracer { return instance; } - /** Inicializa com diretório de trace customizado */ + /** + * Reinicializa o tracer com um diretório personalizado. + * Útil para isolar logs de diferentes execuções em lote. + */ public static void initialize(String traceDirectory) { synchronized (instanceLock) { if (instance != null) { @@ -71,12 +85,14 @@ public class VehicleTracer { } /** - * Começa a rastrear um veículo específico. - * Cria ficheiro de trace para este veículo. + * Inicia a sessão de rastreio para um veículo específico. + * Cria o ficheiro {@code logs/traces/vehicle-{id}.trace} e escreve o cabeçalho. + * + * @param vehicleId O identificador único do veículo. */ public void startTracking(String vehicleId) { if (trackedVehicles.containsKey(vehicleId)) { - return; // Already tracking + return; // Já está a ser rastreado } VehicleTrace trace = new VehicleTrace(vehicleId, traceDirectory); @@ -86,7 +102,7 @@ public class VehicleTracer { } /** - * Stops tracking a vehicle and closes its trace file. + * Encerra a sessão de rastreio, fecha o descritor de ficheiro e remove da memória. */ public void stopTracking(String vehicleId) { VehicleTrace trace = trackedVehicles.remove(vehicleId); @@ -97,14 +113,14 @@ public class VehicleTracer { } /** - * Checks if a vehicle is being tracked. + * Verifica se um veículo está atualmente sob auditoria. */ public boolean isTracking(String vehicleId) { return trackedVehicles.containsKey(vehicleId); } /** - * Logs when a vehicle is generated. + * Regista o evento de instanciação do veículo pelo Coordenador. */ public void logGenerated(Vehicle vehicle) { if (!isTracking(vehicle.getId())) @@ -119,7 +135,7 @@ public class VehicleTracer { } /** - * Logs when a vehicle arrives at an intersection. + * Regista a chegada física do veículo à zona de deteção de uma interseção. */ public void logArrival(String vehicleId, String intersection, double simulationTime) { if (!isTracking(vehicleId)) @@ -133,7 +149,7 @@ public class VehicleTracer { } /** - * Logs when a vehicle is queued at a traffic light. + * Regista a entrada do veículo na estrutura de fila de um semáforo. */ public void logQueued(String vehicleId, String intersection, String direction, int queuePosition) { if (!isTracking(vehicleId)) @@ -147,7 +163,7 @@ public class VehicleTracer { } /** - * Logs when a vehicle starts waiting at a red light. + * Regista o início da espera ativa (veículo parado no Vermelho). */ public void logWaitingStart(String vehicleId, String intersection, String direction) { if (!isTracking(vehicleId)) @@ -161,7 +177,8 @@ public class VehicleTracer { } /** - * Logs when a vehicle finishes waiting (light turns green). + * Regista o fim da espera (Sinal Verde). + * @param waitTime Duração total da paragem nesta instância. */ public void logWaitingEnd(String vehicleId, String intersection, String direction, double waitTime) { if (!isTracking(vehicleId)) @@ -175,7 +192,7 @@ public class VehicleTracer { } /** - * Logs when a vehicle starts crossing an intersection. + * Regista o início da travessia da interseção (ocupação da zona crítica). */ public void logCrossingStart(String vehicleId, String intersection, String direction) { if (!isTracking(vehicleId)) @@ -189,7 +206,7 @@ public class VehicleTracer { } /** - * Logs when a vehicle finishes crossing an intersection. + * Regista a libertação da zona crítica da interseção. */ public void logCrossingEnd(String vehicleId, String intersection, double crossingTime) { if (!isTracking(vehicleId)) @@ -203,7 +220,7 @@ public class VehicleTracer { } /** - * Logs when a vehicle departs from an intersection. + * Regista a partida da interseção em direção ao próximo nó. */ public void logDeparture(String vehicleId, String intersection, String nextDestination) { if (!isTracking(vehicleId)) @@ -217,7 +234,10 @@ public class VehicleTracer { } /** - * Logs when a vehicle exits the system. + * Regista a saída do sistema (no Exit Node). + *

+ * Este método também desencadeia a escrita do Sumário de Viagem no final do log + * e fecha o ficheiro automaticamente. */ public void logExit(Vehicle vehicle, double systemTime) { if (!isTracking(vehicle.getId())) @@ -229,7 +249,7 @@ public class VehicleTracer { String.format("Exited system - Total time: %.2fs, Waiting: %.2fs, Crossing: %.2fs", systemTime, vehicle.getTotalWaitingTime(), vehicle.getTotalCrossingTime())); - // Write summary + // Escreve estatísticas sumarizadas trace.writeSummary(vehicle, systemTime); // Stop tracking and close file @@ -238,7 +258,8 @@ public class VehicleTracer { } /** - * Shuts down the tracer and closes all trace files. + * Fecha forçosamente todos os traces abertos. + * Deve ser chamado no shutdown da simulação para evitar corrupção de logs. */ public void shutdown() { for (VehicleTrace trace : trackedVehicles.values()) { @@ -248,7 +269,7 @@ public class VehicleTracer { } /** - * Internal class to handle tracing for a single vehicle. + * Classe interna auxiliar que gere o descritor de ficheiro e a formatação para um único veículo. */ private class VehicleTrace { private final String vehicleId; @@ -340,4 +361,4 @@ public class VehicleTracer { return str.length() <= maxLength ? str : str.substring(0, maxLength); } } -} +} \ No newline at end of file From 926245986f7da6893ed1776618ffc8141d005e3d Mon Sep 17 00:00:00 2001 From: Leandro Afonso Date: Sun, 7 Dec 2025 22:39:21 +0000 Subject: [PATCH 2/2] finish 1st javadoc round --- main/src/main/java/sd/ExitNodeProcess.java | 172 +++---- .../src/main/java/sd/IntersectionProcess.java | 433 ++++++++++-------- main/src/main/java/sd/model/Message.java | 80 ++-- .../java/sd/protocol/SocketConnection.java | 103 +++-- .../serialization/JsonMessageSerializer.java | 68 +-- .../sd/serialization/MessageSerializer.java | 61 +-- .../serialization/SerializationException.java | 31 +- .../sd/serialization/SerializerFactory.java | 50 +- .../main/java/sd/util/RandomGenerator.java | 96 ++-- .../main/java/sd/util/VehicleGenerator.java | 117 +++-- 10 files changed, 634 insertions(+), 577 deletions(-) diff --git a/main/src/main/java/sd/ExitNodeProcess.java b/main/src/main/java/sd/ExitNodeProcess.java index 725fd44..b82f275 100644 --- a/main/src/main/java/sd/ExitNodeProcess.java +++ b/main/src/main/java/sd/ExitNodeProcess.java @@ -27,22 +27,23 @@ import sd.protocol.MessageProtocol; import sd.protocol.SocketConnection; /** - * Destino final de todos os veículos da simulação (nó de saída S). - * - *

Opera como sumidouro da rede: - *

    - *
  1. Recebe veículos que completaram a viagem - *
  2. Regista estatísticas finais (tempo total, espera, travessia) - *
  3. Envia métricas ao dashboard em tempo real - *
- * - *

Participa no DES rastreando eventos, mas opera principalmente - * de forma reativa, aguardando chegadas via socket. + * Ponto terminal da malha de simulação (Sink Node). + *

+ * Este processo atua como o sumidouro da rede de filas. A sua função primária é + * a coleta de telemetria final. Diferente das interseções, não encaminha veículos; + * em vez disso, retira-os do sistema, calcula as métricas de latência "end-to-end" + * (tempo no sistema, tempo de espera acumulado) e reporta ao Dashboard. + *

+ * Arquitetura de Concorrência: + * Utiliza um {@link ServerSocket} multithreaded para aceitar conexões simultâneas de + * qualquer interseção de fronteira (Cr1, Cr5, etc.) que envie veículos para fora da malha. */ public class ExitNodeProcess { private final SimulationConfig config; private ServerSocket serverSocket; + + /** Pool de threads elástica para tratamento de conexões de entrada. */ private final ExecutorService connectionHandlerPool; // DES components @@ -51,37 +52,37 @@ public class ExitNodeProcess { private final EventLogger eventLogger; private Thread eventProcessorThread; - /** Flag de controlo (volatile para visibilidade entre threads) */ + /** Flag de controlo (volatile para visibilidade entre threads de I/O e lógica). */ private volatile boolean running; - /** Instante de início da simulação (milissegundos) */ + /** Instante de início da simulação (milissegundos) sincronizado com o Coordenador. */ private long simulationStartMillis; - /** Contador de veículos que completaram a rota */ + /** Contador atómico (via synchronized) de throughput total. */ private int totalVehiclesReceived; - /** Tempo acumulado no sistema de todos os veículos */ + /** Tempo acumulado no sistema (System Time) de todos os veículos. */ private double totalSystemTime; - /** Tempo acumulado em espera de todos os veículos */ + /** Tempo acumulado em espera (Waiting Time) de todos os veículos. */ private double totalWaitingTime; - /** Tempo acumulado em travessia de todos os veículos */ + /** Tempo acumulado em travessia (Service Time) de todos os veículos. */ private double totalCrossingTime; - /** Contagem de veículos por tipo */ + /** Agregação por categoria de veículo. */ private final Map vehicleTypeCount; - /** Tempo de espera acumulado por tipo de veículo */ + /** Latência acumulada por categoria. */ private final Map vehicleTypeWaitTime; - /** Cliente socket para envio de estatísticas ao dashboard */ + /** Cliente TCP persistente para push de métricas ao Dashboard. */ private SocketClient dashboardClient; /** - * Ponto de entrada do processo. - * - * @param args args[0] (opcional) = caminho do ficheiro de configuração + * Bootstrap do processo ExitNode. + * Carrega configuração, inicializa subsistemas e entra no loop de serviço. + * * @param args Argumentos de CLI (caminho do config). */ public static void main(String[] args) { System.out.println("=".repeat(60)); @@ -117,13 +118,9 @@ public class ExitNodeProcess { } /** - * Configura o Nó de Saída. - * - * Inicializamos os nossos contadores, preparamos a pool de threads para tratar - * das ligações de veículos recebidas, - * e configuramos os componentes DES para rastreio de eventos. - * - * @param config A configuração da simulação. + * Instancia o nó de saída. + * Prepara os acumuladores estatísticos e a infraestrutura de logging distribuído. + * * @param config A configuração global da simulação. */ public ExitNodeProcess(SimulationConfig config) { this.config = config; @@ -157,9 +154,8 @@ public class ExitNodeProcess { } /** - * Tenta estabelecer uma ligação ao dashboard. - * Se for bem-sucedido, poderemos enviar estatísticas em tempo real. Se não, - * apenas registamos localmente. + * Estabelece o canal de controlo (Control Plane) com o Dashboard. + * Essencial para a visualização em tempo real das métricas de saída. */ public void initialize() { System.out.println("Connecting to dashboard..."); @@ -179,10 +175,9 @@ public class ExitNodeProcess { } /** - * Starts the DES event processing thread. - * Currently, ExitNode is primarily reactive (receives vehicles via network), - * but maintains event queue for potential scheduled events and history - * tracking. + * Inicia a thread de processamento de eventos DES. + * Embora o ExitNode seja primariamente reativo (Network-driven), o motor DES + * é mantido para consistência de relógio e agendamento de fim de simulação. */ private void startEventProcessor() { eventProcessorThread = new Thread(() -> { @@ -218,8 +213,8 @@ public class ExitNodeProcess { } /** - * Processes a discrete event based on its type. - * Currently supports VEHICLE_EXIT and SIMULATION_END events. + * Dispatcher de eventos discretos. + * Trata eventos de fim de simulação. Chegadas de veículos são tratadas via Socket. */ private void processEvent(SimulationEvent event) { try { @@ -244,7 +239,7 @@ public class ExitNodeProcess { } /** - * Handles simulation end event. + * Executa a lógica de encerramento desencadeada pelo evento DES. */ private void handleSimulationEndEvent(SimulationEvent event) { eventLogger.log(EventType.SIMULATION_STOPPED, "ExitNode", @@ -256,9 +251,8 @@ public class ExitNodeProcess { } /** - * Exports the complete event history for the exit node. - * This satisfies the spec requirement: "Deve ser possível verificar a lista - * completa de eventos" + * Exporta o histórico completo de eventos para auditoria. + * Requisito funcional para verificação de trace. */ public void exportEventHistory(String outputPath) { String history = eventQueue.exportEventHistory(); @@ -271,9 +265,8 @@ public class ExitNodeProcess { } /** - * Schedules a simulation end event at the specified time. - * - * @param endTime The simulation time when the simulation should end + * Agenda o fim determinístico da simulação. + * * @param endTime Tempo virtual de paragem. */ public void scheduleSimulationEnd(double endTime) { SimulationEvent endEvent = new SimulationEvent( @@ -285,22 +278,16 @@ public class ExitNodeProcess { } /** - * Abre o socket do servidor e começa a escutar por veículos. - * - * Este é o loop principal. Aceitamos ligações das interseções (de onde vêm os - * veículos) - * e passamo-las para a nossa pool de threads para processamento. - * - * @throws IOException Se não conseguirmos fazer bind à porta. + * Inicia o servidor TCP em modo de bloqueio (Blocking I/O). + * @throws IOException Se ocorrer erro no bind da porta. */ public void start() throws IOException { start(true); // Default to DES mode } /** - * Starts the exit node process. - * - * @param useDES If true, starts event processor for DES mode tracking + * Inicia o processo com opção de ativar o rastreio DES. + * * @param useDES Se verdadeiro, ativa a thread do processador de eventos. */ public void start(boolean useDES) throws IOException { int port = config.getExitPort(); @@ -310,15 +297,15 @@ public class ExitNodeProcess { System.out.println("Exit node started on port " + port); if (useDES) { - // Note: ExitNode is primarily reactive (network-driven), but maintains - // event queue for simulation end events and history tracking System.out.println("Running in DES mode (event history tracking enabled)"); } System.out.println("Waiting for vehicles...\\n"); + // Loop de aceitação principal while (running) { try { Socket clientSocket = serverSocket.accept(); + // Delega o processamento da conexão para o Thread Pool connectionHandlerPool.submit(() -> handleIncomingConnection(clientSocket)); } catch (IOException e) { if (running) { @@ -329,12 +316,11 @@ public class ExitNodeProcess { } /** - * Trata uma ligação de uma interseção. - * - * Mantemos a ligação aberta e escutamos por mensagens `VEHICLE_TRANSFER`. - * Cada mensagem contém um veículo que acabou de terminar a sua viagem. - * - * @param clientSocket O socket ligado à interseção. + * Worker method para tratar uma conexão persistente vinda de uma interseção. + *

+ * Mantém o socket aberto e consome mensagens num loop até que a conexão seja fechada + * pelo remetente. Responsável pela desserialização polimórfica (JSON/Gson). + * * @param clientSocket O socket conectado. */ private void handleIncomingConnection(Socket clientSocket) { String clientAddress = clientSocket.getInetAddress().getHostAddress(); @@ -350,14 +336,14 @@ public class ExitNodeProcess { " from " + message.getSourceNode()); if (message.getType() == MessageType.SIMULATION_START) { - // Coordinator sends start time - use it instead of our local start + // Sincronização de relógio com o Coordenador simulationStartMillis = ((Number) message.getPayload()).longValue(); System.out.println("[Exit] Simulation start time synchronized"); } else if (message.getType() == MessageType.VEHICLE_TRANSFER) { Object payload = message.getPayload(); System.out.println("[Exit] Payload type: " + payload.getClass().getName()); - // Handle Gson LinkedHashMap + // Tratamento de artefatos de desserialização do Gson (LinkedTreeMap -> POJO) Vehicle vehicle; if (payload instanceof com.google.gson.internal.LinkedTreeMap || payload instanceof java.util.LinkedHashMap) { @@ -390,26 +376,21 @@ public class ExitNodeProcess { } /** - * Processa um veículo que acabou de sair do sistema. - * - * Calculamos quanto tempo demorou, atualizamos as nossas estatísticas globais e - * notificamos o dashboard. - * Este método é sincronizado porque múltiplos veículos podem chegar ao mesmo - * tempo. - * - * @param vehicle O veículo que completou a sua rota. + * Processa atomicamente a saída de um veículo. + *

+ * Secção Crítica: Método {@code synchronized} para garantir que a atualização + * das estatísticas globais (totalSystemTime, contadores) é atómica, prevenindo + * Race Conditions quando múltiplos veículos chegam simultaneamente de interseções diferentes. + * * @param vehicle O veículo que completou a rota. */ private synchronized void processExitingVehicle(Vehicle vehicle) { totalVehiclesReceived++; - // Use simulation time instead of wall-clock time - // System time = total time vehicle spent in system (wait + crossing times) - // This represents the actual simulation time elapsed, not real-time + // Cálculo de métricas finais baseadas no tempo virtual de simulação acumulado no veículo double waitTime = vehicle.getTotalWaitingTime(); double crossingTime = vehicle.getTotalCrossingTime(); double systemTime = waitTime + crossingTime; - // Store times in seconds, will be converted to ms when sending to dashboard totalSystemTime += systemTime; totalWaitingTime += waitTime; totalCrossingTime += crossingTime; @@ -421,23 +402,20 @@ public class ExitNodeProcess { System.out.printf("[Exit] Vehicle %s completed (type=%s, system_time=%.2fs, wait=%.2fs, crossing=%.2fs)%n", vehicle.getId(), vehicle.getType(), systemTime, waitTime, crossingTime); - // Log vehicle exit + // Logging estruturado EventLogger.getInstance().logVehicle(EventType.VEHICLE_EXITED, "ExitNode", vehicle.getId(), String.format("Completed - System: %.2fs, Wait: %.2fs, Crossing: %.2fs", systemTime, waitTime, crossingTime)); - // Complete vehicle trace if tracking + // Finaliza o trace individual do veículo VehicleTracer.getInstance().logExit(vehicle, systemTime); - // Send stats after every vehicle to ensure dashboard updates quickly + // Push imediato para o Dashboard para visualização em tempo real sendStatsToDashboard(); } /** - * Envia as estatísticas mais recentes para o dashboard. - * - * Empacotamos as contagens totais e os tempos médios num `StatsUpdatePayload` - * e enviamo-lo. + * Constrói e transmite o DTO de atualização de estatísticas. */ private void sendStatsToDashboard() { if (dashboardClient == null || !dashboardClient.isConnected()) { @@ -448,29 +426,28 @@ public class ExitNodeProcess { // Create stats payload StatsUpdatePayload payload = new StatsUpdatePayload(); - // Set global stats - convert seconds to milliseconds + // Set global stats - convert seconds to milliseconds for display consistency payload.setTotalVehiclesCompleted(totalVehiclesReceived); - payload.setTotalSystemTime((long) (totalSystemTime * 1000.0)); // s -> ms - payload.setTotalWaitingTime((long) (totalWaitingTime * 1000.0)); // s -> ms + payload.setTotalSystemTime((long) (totalSystemTime * 1000.0)); + payload.setTotalWaitingTime((long) (totalWaitingTime * 1000.0)); - // Set intersection-like stats so it shows up correctly in the dashboard table + // Hack: Usar campos de interseção para mostrar throughput no dashboard payload.setIntersectionArrivals(totalVehiclesReceived); payload.setIntersectionDepartures(totalVehiclesReceived); payload.setIntersectionQueueSize(0); - // Set vehicle type stats + // Detailed breakdown Map typeCounts = new HashMap<>(); Map typeWaitTimes = new HashMap<>(); for (VehicleType type : VehicleType.values()) { typeCounts.put(type, vehicleTypeCount.get(type)); - typeWaitTimes.put(type, (long) (vehicleTypeWaitTime.get(type) * 1000.0)); // s -> ms + typeWaitTimes.put(type, (long) (vehicleTypeWaitTime.get(type) * 1000.0)); } payload.setVehicleTypeCounts(typeCounts); payload.setVehicleTypeWaitTimes(typeWaitTimes); - // Send message Message message = new Message( MessageType.STATS_UPDATE, "ExitNode", @@ -489,9 +466,8 @@ public class ExitNodeProcess { } /** - * Encerra graciosamente o processo. - * - * Imprimimos as estatísticas finais, fechamos ligações e limpamos threads. + * Encerramento gracioso do processo. + * Fecha sockets, termina a pool de threads e liberta recursos. */ public void shutdown() { System.out.println("\n[Exit] Shutting down..."); @@ -527,9 +503,7 @@ public class ExitNodeProcess { } /** - * Imprime um resumo dos resultados da simulação na consola. - * Isto dá-nos uma visão rápida de como a simulação correu (médias, contagens de - * veículos, etc.). + * Imprime o relatório final no stdout. */ private void printFinalStatistics() { System.out.println("\n=== EXIT NODE STATISTICS ==="); @@ -554,4 +528,4 @@ public class ExitNodeProcess { } } -} +} \ No newline at end of file diff --git a/main/src/main/java/sd/IntersectionProcess.java b/main/src/main/java/sd/IntersectionProcess.java index 3d359bc..230b9b1 100644 --- a/main/src/main/java/sd/IntersectionProcess.java +++ b/main/src/main/java/sd/IntersectionProcess.java @@ -33,19 +33,22 @@ import sd.protocol.SocketConnection; import sd.serialization.SerializationException; /** - * Representa uma única interseção na nossa simulação de tráfego distribuída. - * - * Esta classe opera como um processo independente (uma aplicação Java autónoma) - * e é responsável por: - * 1. Gerir os semáforos e a sua temporização. - * 2. Processar as chegadas e partidas de veículos. - * 3. Comunicar com outras interseções e com o dashboard. - * - * Utiliza uma abordagem de Simulação de Eventos Discretos (DES), onde as - * mudanças de estado (como semáforos a mudar para verde) - * são agendadas como eventos numa fila de prioridade, em vez de depender de - * loops contínuos ou threads em espera. - * Isto garante uma temporização precisa e uma execução eficiente. + * Representa um nó de processamento autónomo na malha de simulação distribuída + * (Worker Node). + *

+ * Esta classe implementa a lógica de uma interseção rodoviária utilizando uma + * arquitetura híbrida: + *

    + *
  1. Reativa (Network I/O): Threads dedicadas aceitam conexões TCP e + * injetam veículos nas filas de entrada assim que chegam.
  2. + *
  3. Proativa (DES Engine): Uma thread de processamento de eventos gere + * a lógica temporal (mudança de semáforos, tempos de travessia) baseada num + * relógio virtual monotónico.
  4. + *
+ *

+ * A sincronização entre a chegada assíncrona de veículos (Rede) e o + * processamento determinístico (DES) é gerida através de estruturas de dados + * concorrentes e bloqueios justos (Fair Locks). */ public class IntersectionProcess { @@ -57,48 +60,56 @@ public class IntersectionProcess { private ServerSocket serverSocket; + /** + * Tabela de encaminhamento dinâmico para conexões de saída (Next-Hop Cache). + */ private final Map outgoingConnections; + /** Pool de threads para tratamento de I/O de rede (entrada de veículos). */ private final ExecutorService connectionHandlerPool; private ScheduledExecutorService statsExecutor; private ScheduledExecutorService departureExecutor; private volatile boolean running; - /** Escala temporal para visualização: tempo_real = tempo_simulado * escala */ + /** Fator de dilatação temporal (0.0 = Velocidade Máxima, 1.0 = Tempo Real). */ private double timeScale; - /** Relógio central da simulação */ + // --- Componentes DES (Simulação de Eventos Discretos) --- + /** Relógio central virtual da interseção. */ private final SimulationClock clock; - /** Fila de eventos discretos agendados */ + /** Fila de prioridade (Min-Heap) para agendamento temporal de eventos. */ private final EventQueue eventQueue; - /** Sistema de registo de eventos */ private final EventLogger eventLogger; - /** Thread dedicada ao processamento sequencial de eventos DES */ + /** Thread "Single-Writer" responsável pela mutação de estado da simulação. */ private Thread eventProcessorThread; /** - * Lock para exclusão mútua entre semáforos. - * Garante que apenas um semáforo pode estar verde de cada vez nesta interseção. + * Mecanismo de exclusão mútua para controlo de fases semafóricas. + * Configurado com política de justiça (fairness=true) para evitar inanição + * (starvation) de direções com menos tráfego. */ private final Lock trafficCoordinationLock; /** - * Regista qual direção tem atualmente o sinal verde. - * {@code null} significa que todos os semáforos estão vermelhos. + * Estado volátil que indica a direção ativa. Apenas uma direção pode deter o + * token 'Green' por vez. */ private volatile String currentGreenDirection; private SocketClient dashboardClient; + + // Métricas voláteis para acesso atómico sem bloqueio private volatile int totalArrivals = 0; private volatile int totalDepartures = 0; /** - * Inicializa o processo da interseção. - * - * @param intersectionId O identificador único para esta interseção (ex: "Cr1"). - * @param configFilePath O caminho para o ficheiro de configuração. - * @throws IOException Se houver algum problema ao ler a configuração. + * Inicializa o processo da interseção, carregando a topologia e preparando o + * motor DES. + * + * @param intersectionId O identificador único na malha (ex: "Cr1"). + * @param configFilePath Caminho para o ficheiro de propriedades. + * @throws IOException Se falhar o bind da porta ou leitura de config. */ public IntersectionProcess(String intersectionId, String configFilePath) throws IOException { this.intersectionId = intersectionId; @@ -127,13 +138,16 @@ public class IntersectionProcess { } /** - * Inicia o ciclo de processamento de eventos. - * - * Esta thread é o coração do modelo DES para esta interseção. Retira eventos da - * fila - * e executa-os por ordem cronológica. Enquanto a thread principal trata das - * operações de I/O de rede (receção de veículos), - * esta thread trata da lógica da simulação (semáforos, travessias de veículos). + * Inicia o ciclo principal do motor de simulação (DES Engine Loop). + *

+ * Executa o ciclo "Fetch-Decode-Execute": + *

    + *
  1. Remove o evento com menor timestamp da fila (Fetch).
  2. + *
  3. Avança o relógio virtual para o tempo do evento.
  4. + *
  5. Aplica atraso artificial se {@code timeScale > 0} (para visualização + * humana).
  6. + *
  7. Despacha o evento para o manipulador apropriado (Execute).
  8. + *
*/ private void startEventProcessor() { eventProcessorThread = new Thread(() -> { @@ -145,9 +159,9 @@ public class IntersectionProcess { while (running) { SimulationEvent event = eventQueue.poll(); if (event == null) { - // No events currently, wait a bit before checking again + // Backoff exponencial ou sleep curto para evitar busy-waiting em idle try { - Thread.sleep(50); // Short sleep to avoid busy-waiting + Thread.sleep(50); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; @@ -155,7 +169,7 @@ public class IntersectionProcess { continue; } - // Apply time scaling for visualization + // Aplicação de escala temporal (Throttle) if (timeScale > 0) { double simTimeDelta = event.getTimestamp() - lastTime; long realDelayMs = (long) (simTimeDelta * timeScale * 1000); @@ -170,10 +184,10 @@ public class IntersectionProcess { lastTime = event.getTimestamp(); } - // Advance clock to event time + // Atualização atómica do tempo de simulação clock.advanceTo(event.getTimestamp()); - // Process the event + // Processamento polimórfico processEvent(event); } @@ -185,10 +199,12 @@ public class IntersectionProcess { } /** - * Processa um evento da fila de simulação. - * Cada tipo de evento é encaminhado para o seu tratador específico. - * - * @param event o evento a processar + * Despachante central de eventos. + *

+ * Encaminha o evento para a lógica de negócio específica baseada no tipo + * {@link DESEventType}. + * + * @param event O evento de simulação a ser processado. */ private void processEvent(SimulationEvent event) { try { @@ -198,8 +214,8 @@ public class IntersectionProcess { break; case VEHICLE_ARRIVAL: - // Vehicle arrivals are still handled via network messages - // This event type is for internal scheduling if needed + // Chegadas são tratadas reativamente via Socket, mas eventos podem ser usados + // para métricas break; case VEHICLE_CROSSING_START: @@ -225,12 +241,18 @@ public class IntersectionProcess { } /** - * Trata da mudança dos semáforos. - * - * Quando um semáforo muda de estado, registamos o evento, atualizamos o modelo - * e, se tiver mudado para VERDE, - * verificamos imediatamente se há veículos à espera para atravessar. - * Também agendamos aqui o *próximo* evento de mudança, mantendo o ciclo ativo. + * Gere a máquina de estados dos semáforos. + *

+ * O fluxo de execução é o seguinte: + *

    + *
  1. Atualiza o estado do semáforo (Verde <-> Vermelho).
  2. + *
  3. Se o novo estado for Verde: Calcula a capacidade de vazão e agenda + * travessias (Service Events).
  4. + *
  5. Agenda recursivamente a próxima mudança de estado para manter o ciclo + * infinito.
  6. + *
+ * + * @param event O evento que desencadeou a mudança de estado. */ private void handleTrafficLightChangeEvent(SimulationEvent event) { TrafficLightEvent tlEvent = (TrafficLightEvent) event.getPayload(); @@ -252,12 +274,12 @@ public class IntersectionProcess { String.format("Direction %s changed to %s at time %.2f", direction, newState, event.getTimestamp())); - // If light turned GREEN, process queued vehicles + // Processamento de lote (Batch Processing) para a fase Verde if (newState == TrafficLightState.GREEN) { processQueuedVehiclesForLight(light, event.getTimestamp()); } - // Schedule next state change + // Agendamento do próximo ciclo (Feedback Loop) double nextChangeTime = event.getTimestamp() + (newState == TrafficLightState.GREEN ? light.getGreenTime() : light.getRedTime()); @@ -269,23 +291,19 @@ public class IntersectionProcess { } /** - * Processa a fila de veículos quando um semáforo fica verde. - * + * Calcula a vazão da interseção durante uma fase verde. *

- * Para cada veículo na fila: - *

+ * Implementa uma lógica de previsão ("Look-ahead"): *
    - *
  1. Calcula o tempo de travessia com base no tipo de veículo
  2. - *
  3. Verifica se cabe na duração restante do sinal verde
  4. - *
  5. Agenda o evento de partida do veículo
  6. + *
  7. Itera sobre a fila de espera do semáforo.
  8. + *
  9. Calcula o tempo de serviço acumulado (Service Time) baseado no tipo de + * veículo.
  10. + *
  11. Agenda a partida apenas se o veículo couber na janela temporal restante + * do sinal verde.
  12. *
- * - *

- * Os veículos que não couberem no tempo verde ficam à espera do próximo ciclo. - *

- * - * @param light o semáforo que acabou de ficar verde - * @param currentTime o tempo atual da simulação em segundos + * + * @param light O semáforo ativo. + * @param currentTime O instante de início da fase verde. */ private void processQueuedVehiclesForLight(TrafficLight light, double currentTime) { double greenDuration = light.getGreenTime(); @@ -295,30 +313,29 @@ public class IntersectionProcess { System.out.printf("[%s] Processing queue for %s (GREEN for %.2fs, queue size: %d, currentTime=%.2f)%n", intersectionId, light.getId(), greenDuration, queueSize, currentTime); - // Process vehicles while queue not empty and within green light duration + // Algoritmo de esvaziamento de fila baseado em Time Budget while (light.getQueueSize() > 0) { - // Calculate crossing time for next vehicle (peek at queue size to estimate) - // We'll use LIGHT vehicle as default for estimation + // Estimativa inicial (optimista) double crossingTime = config.getLightVehicleCrossingTime(); - // Check if another vehicle can fit in remaining green time + // Verificação de limite de tempo (Hard Deadline do sinal vermelho) if (timeOffset + crossingTime > greenDuration) { - break; // No more vehicles can cross this green phase + break; // Veículo não cabe no ciclo atual } - // Remove vehicle from queue with current simulation time + // Commit: Remove da fila Vehicle vehicle = light.removeVehicle(currentTime + timeOffset); if (vehicle == null) break; - // Get actual crossing time for this vehicle + // Recálculo preciso baseado no tipo real do veículo crossingTime = getCrossingTimeForVehicle(vehicle); - // Schedule crossing + // Agendamento do evento futuro de término de travessia double crossingStartTime = currentTime + timeOffset; scheduleVehicleCrossing(vehicle, crossingStartTime, crossingTime); - // Update offset for next vehicle + // Incrementa offset para serializar as travessias (Head-of-Line Blocking) timeOffset += crossingTime; System.out.printf("[%s] Scheduled vehicle %s to cross at t=%.2f (duration=%.2fs)%n", @@ -327,12 +344,11 @@ public class IntersectionProcess { } /** - * Agenda a travessia e partida de um veículo. - * Cria um evento de fim de travessia agendado para o tempo correto. - * - * @param vehicle o veículo que vai atravessar - * @param startTime quando a travessia começa (segundos de simulação) - * @param crossingDuration quanto tempo demora a atravessar (segundos) + * Cria e agenda o evento de conclusão de travessia (Partida). + * + * @param vehicle O veículo que está a atravessar. + * @param startTime Instante de início da travessia. + * @param crossingDuration Duração estimada da travessia. */ private void scheduleVehicleCrossing(Vehicle vehicle, double startTime, double crossingDuration) { // Schedule crossing end (when vehicle departs) @@ -351,11 +367,10 @@ public class IntersectionProcess { } /** - * Calcula o tempo de travessia com base no tipo de veículo. - * Bicicletas são mais rápidas, veículos pesados mais lentos. - * - * @param vehicle o veículo para calcular o tempo - * @return tempo de travessia em segundos + * Determina o custo temporal da travessia baseado na física do veículo. + * + * @param vehicle O veículo em questão. + * @return O tempo em segundos necessário para atravessar a interseção. */ private double getCrossingTimeForVehicle(Vehicle vehicle) { return switch (vehicle.getType()) { @@ -367,36 +382,45 @@ public class IntersectionProcess { } /** - * Trata o evento de início de travessia de um veículo. - * (Implementação futura - atualmente apenas regista o evento) - * - * @param event o evento de início de travessia + * Manipula o evento de início de travessia de um veículo. + *

+ * Atualmente serve como placeholder para lógica futura de animação ou + * ocupação de zonas críticas na interseção. + * + * @param event O evento de início de travessia. */ private void handleVehicleCrossingStartEvent(SimulationEvent event) { - // Implementation will depend on how vehicle crossing is modeled - // For now, log the event + // Placeholder para lógica futura de animação ou ocupação de zona crítica eventLogger.log(sd.logging.EventType.VEHICLE_DEPARTED, intersectionId, "Vehicle crossing started at time " + event.getTimestamp()); } /** - * Trata o fim da travessia de um veículo pela interseção. - * Atualiza estatísticas, regista o tempo de travessia e envia o veículo - * para o próximo destino na sua rota. - * - * @param event evento contendo o veículo que terminou a travessia + * Finaliza a lógica de travessia e inicia a transferência (handover) para o + * próximo nó. + *

+ * Este método é invocado quando o tempo de travessia expira no relógio virtual. + * Executa as seguintes ações: + *

    + *
  1. Atualiza as métricas de tempo de travessia do veículo.
  2. + *
  3. Incrementa contadores locais de veículos processados.
  4. + *
  5. Transfere a responsabilidade do veículo para a rede, enviando-o ao + * próximo destino.
  6. + *
+ * + * @param event O evento de fim de travessia. */ private void handleVehicleCrossingEndEvent(SimulationEvent event) { Vehicle vehicle = (Vehicle) event.getPayload(); - // Add crossing time to vehicle stats + // Atualiza métricas do veículo double crossingTime = getCrossingTimeForVehicle(vehicle); vehicle.addCrossingTime(crossingTime); - // Update intersection statistics + // Atualiza métricas locais intersection.incrementVehiclesSent(); - // Send vehicle to next destination + // Handover: Transfere a responsabilidade do veículo para a rede sendVehicleToNextDestination(vehicle); eventLogger.log(sd.logging.EventType.VEHICLE_DEPARTED, intersectionId, @@ -404,10 +428,9 @@ public class IntersectionProcess { } /** - * Trata o evento de fim da simulação. - * Define a flag de execução como falsa para terminar o processamento. + * Finaliza a execução do processo de simulação. * - * @param event o evento de fim de simulação + * @param event O evento de fim de simulação. */ private void handleSimulationEndEvent(SimulationEvent event) { eventLogger.log(sd.logging.EventType.SIMULATION_STOPPED, intersectionId, @@ -416,10 +439,9 @@ public class IntersectionProcess { } /** - * Exporta o histórico completo de eventos para um ficheiro. - * Útil para análise posterior e debugging da simulação. - * - * @param outputPath caminho do ficheiro onde guardar o histórico + * Exporta o histórico completo de eventos para análise post-mortem. + * + * @param outputPath O caminho do ficheiro onde o histórico será guardado. */ public void exportEventHistory(String outputPath) { String history = eventQueue.exportEventHistory(); @@ -431,7 +453,12 @@ public class IntersectionProcess { } } - // Main entry point for running an intersection process + /** + * Ponto de entrada principal da aplicação. + * + * @param args Argumentos da linha de comando (ID da interseção e ficheiro de + * configuração opcional). + */ public static void main(String[] args) { if (args.length < 1) { System.err.println("Usage: java IntersectionProcess [configFile]"); @@ -460,6 +487,12 @@ public class IntersectionProcess { } } + /** + * Realiza o bootstrap dos componentes lógicos e de rede da interseção. + *

+ * Inclui a criação de semáforos, configuração de encaminhamento e conexão ao + * Dashboard. + */ public void initialize() { System.out.println("\n[" + intersectionId + "] Initializing intersection..."); @@ -473,7 +506,7 @@ public class IntersectionProcess { } /** - * Estabelece ligação ao servidor do dashboard para reportar estatísticas. + * Estabelece a conexão com o Dashboard para envio de telemetria em tempo real. */ private void connectToDashboard() { try { @@ -497,9 +530,7 @@ public class IntersectionProcess { } /** - * Cria os semáforos para esta interseção com base nas suas ligações físicas. - * Cada interseção tem um número e direções de semáforos diferentes de acordo - * com a topologia da rede. + * Inicializa os semáforos da interseção com base na configuração carregada. */ private void createTrafficLights() { System.out.println("\n[" + intersectionId + "] Creating traffic lights..."); @@ -528,6 +559,13 @@ public class IntersectionProcess { } } + /** + * Obtém a configuração específica para esta interseção a partir da configuração + * global. + * + * @return O objeto de configuração da interseção. + * @throws RuntimeException Se a configuração estiver em falta. + */ private SimulationConfig.IntersectionConfig getIntersectionConfig() { if (config.getNetworkConfig() == null || config.getNetworkConfig().getIntersections() == null) { throw new RuntimeException("Network configuration not loaded or empty."); @@ -538,6 +576,11 @@ public class IntersectionProcess { .orElseThrow(() -> new RuntimeException("Intersection config not found for " + intersectionId)); } + /** + * Configura a tabela de encaminhamento (routing) da interseção. + *

+ * Define para cada destino qual a direção de saída (semáforo) correspondente. + */ private void configureRouting() { System.out.println("\n[" + intersectionId + "] Configuring routing..."); @@ -559,11 +602,10 @@ public class IntersectionProcess { } /** - * Solicita permissão para um semáforo ficar verde. - * Bloqueia até que a permissão seja concedida (nenhum outro semáforo está - * verde). - * - * @param direction A direção que solicita o sinal verde + * Primitiva de bloqueio: Solicita acesso exclusivo à zona crítica da + * interseção. + * + * @param direction A direção que solicita passagem. */ public void requestGreenLight(String direction) { trafficCoordinationLock.lock(); @@ -571,10 +613,9 @@ public class IntersectionProcess { } /** - * Liberta a permissão de sinal verde, permitindo que outro semáforo fique - * verde. - * - * @param direction A direção que liberta o sinal verde + * Primitiva de bloqueio: Liberta o acesso exclusivo à zona crítica. + * + * @param direction A direção que está a libertar a passagem. */ public void releaseGreenLight(String direction) { if (direction.equals(currentGreenDirection)) { @@ -584,8 +625,10 @@ public class IntersectionProcess { } /** - * Modo DES: Agenda os eventos iniciais de mudança de semáforo. - * Isto substitui a antiga abordagem baseada em threads startTrafficLights(). + * Inicializa o estado dos semáforos no arranque da simulação (t=0). + *

+ * Garante que apenas um semáforo começa em Verde e os restantes em Vermelho, + * agendando os eventos iniciais na fila do DES. */ private void scheduleInitialTrafficLightEvents() { System.out.println("\n[" + intersectionId + "] Scheduling initial traffic light events (DES mode)..."); @@ -596,12 +639,12 @@ public class IntersectionProcess { for (TrafficLight light : intersection.getTrafficLights()) { String direction = light.getDirection(); - // Set initial state (first light starts green, others red) + // Lógica de arranque: Primeiro da lista = Verde, outros = Vermelho boolean isFirstLight = intersection.getTrafficLights().indexOf(light) == 0; TrafficLightState initialState = isFirstLight ? TrafficLightState.GREEN : TrafficLightState.RED; light.changeState(initialState); - // Schedule first state change + // Agenda a primeira transição double firstChangeTime = currentTime + (initialState == TrafficLightState.GREEN ? light.getGreenTime() : light.getRedTime()); @@ -624,14 +667,16 @@ public class IntersectionProcess { } /** - * Envia um veículo para o seu próximo destino via ligação socket. + * Encaminhamento de rede: Serializa e envia o objeto veículo para o próximo nó. + *

+ * Calcula também o tempo de viagem virtual entre nós (Edge Weight). * - * @param vehicle O veículo que atravessou esta interseção. + * @param vehicle O veículo a ser enviado. */ public void sendVehicleToNextDestination(Vehicle vehicle) { String nextDestination = vehicle.getCurrentDestination(); - // Calculate travel time + // Cálculo de latência de viagem (Edge Weight) double baseTime = config.getBaseTravelTime(); double multiplier = 1.0; switch (vehicle.getType()) { @@ -644,22 +689,25 @@ public class IntersectionProcess { System.out.printf("[%s] Vehicle %s departing to %s. Travel time: %.2fs%n", intersectionId, vehicle.getId(), nextDestination, travelTime); - // Record departure immediately as it leaves the intersection recordVehicleDeparture(); - // In DES mode, send immediately (no real-time delay) + // Envio imediato (o delay de viagem é implícito no tempo de chegada no próximo + // nó ou simulado aqui) sendVehicleImmediately(vehicle, nextDestination); } /** - * Envia imediatamente um veículo para o seu destino via rede. + * Envia o veículo imediatamente para o próximo nó via conexão TCP persistente. + * + * @param vehicle O veículo a ser enviado. + * @param nextDestination O identificador do próximo nó destino. */ private void sendVehicleImmediately(Vehicle vehicle, String nextDestination) { try { - // Get or create connection to next destination + // Lazy loading da conexão SocketConnection connection = getOrCreateConnection(nextDestination); - // Create and send message using Message class + // Encapsulamento da mensagem MessageProtocol message = new Message( MessageType.VEHICLE_TRANSFER, intersectionId, @@ -672,8 +720,6 @@ public class IntersectionProcess { System.out.println("[" + intersectionId + "] Vehicle " + vehicle.getId() + " arrived at " + nextDestination + " (msg sent)"); - // 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()); @@ -681,12 +727,15 @@ public class IntersectionProcess { } /** - * Obtém uma ligação existente para um destino ou cria uma nova. + * Obtém ou cria uma conexão para o destino especificado (Singleton por + * destino). + *

+ * Este método é thread-safe. * - * @param destinationId O ID do nó de destino. - * @return A SocketConnection para esse destino. - * @throws IOException Se a ligação não puder ser estabelecida. - * @throws InterruptedException Se a tentativa de ligação for interrompida. + * @param destinationId O identificador do nó destino. + * @return A conexão TCP estabelecida. + * @throws IOException Se ocorrer um erro de I/O na criação da conexão. + * @throws InterruptedException Se a thread for interrompida durante a espera. */ private synchronized SocketConnection getOrCreateConnection(String destinationId) throws IOException, InterruptedException { @@ -706,10 +755,10 @@ public class IntersectionProcess { } /** - * Obtém o endereço host para um nó de destino a partir da configuração. + * Resolve o hostname ou endereço IP para um determinado destino. * - * @param destinationId O ID do nó de destino. - * @return O endereço host. + * @param destinationId O ID do destino. + * @return O endereço do host. */ private String getHostForDestination(String destinationId) { if (destinationId.equals("S")) { @@ -720,9 +769,9 @@ public class IntersectionProcess { } /** - * Obtém o número da porta para um nó de destino a partir da configuração. + * Resolve a porta TCP para um determinado destino. * - * @param destinationId O ID do nó de destino. + * @param destinationId O ID do destino. * @return O número da porta. */ private int getPortForDestination(String destinationId) { @@ -734,10 +783,11 @@ public class IntersectionProcess { } /** - * Inicia o socket do servidor e começa a aceitar ligações recebidas. - * Este é o loop principal de escuta do processo. + * Inicia o servidor e o loop de aceitação de conexões. + *

+ * Este método bloqueia a thread chamadora durante a execução do servidor. * - * @throws IOException Se o socket do servidor não puder ser criado. + * @throws IOException Se ocorrer um erro ao fazer bind da porta. */ public void start() throws IOException { int port = config.getIntersectionPort(intersectionId); @@ -751,12 +801,12 @@ public class IntersectionProcess { startEventProcessor(); System.out.println("[" + intersectionId + "] Running in DES mode"); - // Start stats updater + // Background task para telemetria statsExecutor.scheduleAtFixedRate(this::sendStatsToDashboard, 1, 1, TimeUnit.SECONDS); System.out.println("[" + intersectionId + "] Waiting for incoming connections...\n"); - // Main accept loop + // Loop principal de aceitação de conexões while (running) { try { Socket clientSocket = serverSocket.accept(); @@ -764,13 +814,12 @@ public class IntersectionProcess { System.out.println("[" + intersectionId + "] New connection accepted from " + clientSocket.getInetAddress().getHostAddress()); - // Check running flag again before handling if (!running) { clientSocket.close(); break; } - // **Set timeout before submitting to handler** + // Configura timeout para evitar bloqueios infinitos em leitura try { clientSocket.setSoTimeout(1000); } catch (java.net.SocketException e) { @@ -779,13 +828,12 @@ public class IntersectionProcess { continue; } - // Handle each connection in a separate thread + // Delega processamento para thread pool (NIO style) connectionHandlerPool.submit(() -> handleIncomingConnection(clientSocket)); } catch (IOException e) { - // Expected when serverSocket.close() is called during shutdown if (!running) { - break; // Normal shutdown + break; // Shutdown normal } System.err.println("[" + intersectionId + "] Error accepting connection: " + e.getMessage()); @@ -794,10 +842,13 @@ public class IntersectionProcess { } /** - * Trata uma ligação recebida de outro processo. - * Escuta continuamente mensagens de transferência de veículos. + * Lógica de tratamento de conexões de entrada (Consumer). + *

+ * Lê continuamente do socket até que a conexão seja fechada, processando + * mensagens + * de chegada de veículos ou comandos de simulação. * - * @param clientSocket A ligação socket aceite. + * @param clientSocket O socket do cliente conectado. */ private void handleIncomingConnection(Socket clientSocket) { try { @@ -813,27 +864,24 @@ public class IntersectionProcess { 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(); - // Handle simulation start time synchronization if (message.getType() == MessageType.SIMULATION_START) { System.out.println("[" + intersectionId + "] Simulation start time synchronized"); continue; } - // Accept both VEHICLE_TRANSFER and VEHICLE_SPAWN (from coordinator) if (message.getType() == MessageType.VEHICLE_TRANSFER || message.getType() == MessageType.VEHICLE_SPAWN) { - // Cast payload to Vehicle - handle Gson deserialization + + // Lógica de desserialização polimórfica (Vehicle ou Map) Vehicle vehicle; Object payload = message.getPayload(); if (payload instanceof Vehicle) { vehicle = (Vehicle) payload; } else if (payload instanceof java.util.Map) { - // Gson deserialized as LinkedHashMap - re-serialize and deserialize as Vehicle com.google.gson.Gson gson = new com.google.gson.Gson(); String json = gson.toJson(payload); vehicle = gson.fromJson(json, Vehicle.class); @@ -845,43 +893,37 @@ public class IntersectionProcess { System.out.println("[" + intersectionId + "] Received vehicle: " + vehicle.getId() + " from " + message.getSourceNode()); - // Advance vehicle to next destination in its route + // Lógica de Roteamento Local vehicle.advanceRoute(); - - // Add vehicle to appropriate queue with current simulation time intersection.receiveVehicle(vehicle, clock.getCurrentTime()); - // Log queue status after adding vehicle System.out.printf("[%s] Vehicle %s queued. Total queue size: %d%n", intersectionId, vehicle.getId(), intersection.getTotalQueueSize()); - // Record arrival for statistics recordVehicleArrival(); + } else if (message.getType() == MessageType.SHUTDOWN) { System.out.println( "[" + intersectionId + "] Received SHUTDOWN command from " + message.getSourceNode()); running = false; - // Close this specific connection break; } } catch (java.net.SocketTimeoutException e) { - // Timeout - 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()); - break; // Invalid message, close connection + break; } catch (IOException e) { if (running) { System.err.println("[" + intersectionId + "] Failed to deserialize message: " + e.getMessage()); - e.printStackTrace(); // For debugging - maybe change//remove later + e.printStackTrace(); } - break; // Connection error, close connection + break; } } @@ -889,27 +931,29 @@ public class IntersectionProcess { 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. + * Procedimento de Encerramento Gracioso (Graceful Shutdown). + *

    + *
  1. Para a aceitação de novas conexões.
  2. + *
  3. Envia últimas estatísticas.
  4. + *
  5. Encerra pools de threads.
  6. + *
  7. Fecha sockets ativos.
  8. + *
*/ public void shutdown() { - // Check if already shutdown if (!running) { - return; // Already shutdown, do nothing + return; } System.out.println("\n[" + intersectionId + "] Shutting down..."); running = false; - // Send final stats before closing connections sendStatsToDashboard(); - // 1. Close ServerSocket first + // 1. Close ServerSocket if (serverSocket != null && !serverSocket.isClosed()) { try { serverSocket.close(); @@ -918,8 +962,7 @@ public class IntersectionProcess { } } - // 2. Shutdown thread pools with force - + // 2. Shutdown thread pools if (connectionHandlerPool != null && !connectionHandlerPool.isShutdown()) { connectionHandlerPool.shutdownNow(); } @@ -930,9 +973,8 @@ public class IntersectionProcess { departureExecutor.shutdownNow(); } - // 3. Wait briefly for termination (don't block forever) + // 3. Wait briefly for termination try { - if (connectionHandlerPool != null) { connectionHandlerPool.awaitTermination(1, TimeUnit.SECONDS); } @@ -968,31 +1010,32 @@ public class IntersectionProcess { } /** - * Gets the Intersection object managed by this process. - * Useful for testing and monitoring. - * - * @return The Intersection object. + * Obtém o modelo de dados da interseção. + * + * @return O objeto Intersection. */ public Intersection getIntersection() { return intersection; } /** - * Records that a vehicle has arrived at this intersection. + * Regista a chegada de um novo veículo para fins estatísticos. */ public void recordVehicleArrival() { totalArrivals++; } /** - * Records that a vehicle has departed from this intersection. + * Regista a partida de um veículo para fins estatísticos. */ public void recordVehicleDeparture() { totalDepartures++; } /** - * Sends current statistics to the dashboard server. + * Envia um "snapshot" do estado atual para o Dashboard (Telemetria Push). + *

+ * Inclui o número acumulado de chegadas, partidas e o tamanho atual das filas. */ private void sendStatsToDashboard() { if (dashboardClient == null || !dashboardClient.isConnected()) { @@ -1000,7 +1043,6 @@ public class IntersectionProcess { } try { - // Calculate current queue size int currentQueueSize = intersection.getTrafficLights().stream() .mapToInt(TrafficLight::getQueueSize) .sum(); @@ -1010,7 +1052,6 @@ public class IntersectionProcess { .setIntersectionDepartures(totalDepartures) .setIntersectionQueueSize(currentQueueSize); - // Send StatsUpdatePayload directly as the message payload sd.model.Message message = new sd.model.Message( MessageType.STATS_UPDATE, intersectionId, @@ -1026,4 +1067,4 @@ public class IntersectionProcess { System.err.println("[" + intersectionId + "] Failed to send stats to dashboard: " + e.getMessage()); } } -} +} \ No newline at end of file diff --git a/main/src/main/java/sd/model/Message.java b/main/src/main/java/sd/model/Message.java index 623bc0d..d7a9573 100644 --- a/main/src/main/java/sd/model/Message.java +++ b/main/src/main/java/sd/model/Message.java @@ -5,41 +5,52 @@ import java.util.UUID; import sd.protocol.MessageProtocol; /** - * Representa uma mensagem trocada entre processos na simulação distribuída. - * - *

Cada mensagem tem um ID único, tipo, remetente, destino e payload. - * Implementa {@link MessageProtocol} que estende Serializable para transmissão pela rede.

+ * Envelope fundamental do protocolo de comunicação entre processos distribuídos (IPC). + *

+ * Esta classe atua como a Unidade de Dados de Aplicação (ADU), encapsulando tanto + * os metadados de roteamento (origem, destino, tipo) quanto a carga útil (payload) + * polimórfica. É agnóstica ao conteúdo, servindo como contentor genérico para + * transferência de estado (Veículos, Estatísticas) ou sinais de controlo (Semáforos). + *

+ * A imutabilidade dos campos (exceto via serialização) garante a integridade da mensagem + * durante o trânsito na rede. */ public class Message implements MessageProtocol { private static final long serialVersionUID = 1L; - /** Identificador único desta mensagem */ + /** * Identificador único universal (UUID). + * Essencial para rastreabilidade (tracing), logs de auditoria e mecanismos de deduplicação. + */ private final String messageId; - /** Tipo desta mensagem (ex: VEHICLE_TRANSFER, STATS_UPDATE) */ + /** Discriminador semântico que define como o recetor deve processar o payload. */ private final MessageType type; - /** Identificador do processo que enviou esta mensagem */ + /** Identificador lógico do nó emissor (ex: "Cr1", "Coordinator"). */ private final String senderId; - /** Identificador do processo de destino (pode ser null para broadcast) */ + /** * Identificador lógico do nó recetor. + * Se {@code null}, a mensagem deve ser tratada como Broadcast. + */ private final String destinationId; - /** Dados a serem transmitidos (o tipo depende do tipo de mensagem) */ + /** * Carga útil polimórfica. + * Deve implementar {@link java.io.Serializable} para garantir transmissão correta. + */ private final Object payload; - /** Timestamp de criação da mensagem (tempo de simulação ou real) */ + /** Marca temporal da criação da mensagem (Unix Timestamp), usada para cálculo de latência de rede. */ private final long timestamp; /** - * Cria uma nova mensagem com todos os parâmetros. + * Construtor completo para reconstrução de mensagens ou envio com timestamp manual. * - * @param type tipo da mensagem - * @param senderId ID do processo remetente - * @param destinationId ID do processo de destino (null para broadcast) - * @param payload conteúdo da mensagem - * @param timestamp timestamp de criação da mensagem + * @param type Classificação semântica da mensagem. + * @param senderId ID do processo origem. + * @param destinationId ID do processo destino (ou null para broadcast). + * @param payload Objeto de domínio a ser transportado. + * @param timestamp Instante de criação (ms). */ public Message(MessageType type, String senderId, String destinationId, Object payload, long timestamp) { @@ -52,23 +63,24 @@ public class Message implements MessageProtocol { } /** - * Cria uma nova mensagem usando o tempo atual do sistema como timestamp. + * Construtor de conveniência que atribui automaticamente o timestamp atual do sistema. * - * @param type tipo da mensagem - * @param senderId ID do processo remetente - * @param destinationId ID do processo de destino - * @param payload conteúdo da mensagem + * @param type Classificação semântica. + * @param senderId ID do processo origem. + * @param destinationId ID do processo destino. + * @param payload Objeto de domínio. */ public Message(MessageType type, String senderId, String destinationId, Object payload) { this(type, senderId, destinationId, payload, System.currentTimeMillis()); } /** - * Cria uma mensagem de broadcast (sem destino específico). + * Construtor de conveniência para mensagens de difusão (Broadcast). + * Define {@code destinationId} como null. * - * @param type tipo da mensagem - * @param senderId ID do processo remetente - * @param payload conteúdo da mensagem + * @param type Classificação semântica. + * @param senderId ID do processo origem. + * @param payload Objeto de domínio. */ public Message(MessageType type, String senderId, Object payload) { this(type, senderId, null, payload, System.currentTimeMillis()); @@ -101,21 +113,23 @@ public class Message implements MessageProtocol { } /** - * Checks if this is a broadcast message (no specific destination). + * Verifica se a mensagem se destina a todos os nós da rede. * - * @return true if destinationId is null, false otherwise + * @return {@code true} se o destinationId for nulo. */ public boolean isBroadcast() { return destinationId == null; } /** - * Gets the payload cast to a specific type. - * Use with caution and ensure type safety. + * Utilitário para casting seguro e fluente do payload. + *

+ * Evita a necessidade de casts explícitos e supressão de warnings no código cliente. * - * @param The expected payload type - * @return The payload cast to type T - * @throws ClassCastException if the payload is not of type T + * @param O tipo esperado do payload. + * @param clazz A classe do tipo esperado para verificação em runtime (opcional no uso, mas boa prática). + * @return O payload convertido para o tipo T. + * @throws ClassCastException Se o payload não for compatível com o tipo solicitado. */ @SuppressWarnings("unchecked") public T getPayloadAs(Class clazz) { @@ -140,4 +154,4 @@ public class Message implements MessageProtocol { destinationId != null ? destinationId : "BROADCAST", timestamp); } -} +} \ No newline at end of file diff --git a/main/src/main/java/sd/protocol/SocketConnection.java b/main/src/main/java/sd/protocol/SocketConnection.java index 90801b0..58d843e 100644 --- a/main/src/main/java/sd/protocol/SocketConnection.java +++ b/main/src/main/java/sd/protocol/SocketConnection.java @@ -16,10 +16,17 @@ import sd.serialization.MessageSerializer; import sd.serialization.SerializationException; import sd.serialization.SerializerFactory; - /** - * Simplifica comunicação via sockets. - * Inclui lógica de retry para robustez. + * Wrapper de alto nível para gestão robusta de conexões TCP. + *

+ * Esta classe abstrai a complexidade da API nativa {@link java.net.Socket}, oferecendo: + *

    + *
  1. Resiliência: Lógica de reconexão automática (Retry Loop) no arranque, crucial para sistemas + * distribuídos onde a ordem de inicialização dos nós não é garantida.
  2. + *
  3. Framing: Implementação transparente do protocolo "Length-Prefix" (4 bytes de tamanho + payload), + * resolvendo o problema de fragmentação de stream TCP.
  4. + *
  5. Serialização: Integração direta com a camada de serialização JSON.
  6. + *
*/ public class SocketConnection implements Closeable { @@ -28,20 +35,24 @@ public class SocketConnection implements Closeable { private final InputStream inputStream; private final MessageSerializer serializer; - /** Número máximo de tentativas de ligação */ + /** Número máximo de tentativas de ligação antes de desistir (Fail-fast). */ private static final int MAX_RETRIES = 5; - /** Atraso entre tentativas (milissegundos) */ + + /** Janela de espera (backoff) linear entre tentativas (em milissegundos). */ private static final long RETRY_DELAY_MS = 1000; /** - * Construtor do cliente que inicia a ligação. - * Tenta ligar a um servidor já em escuta, com retry. + * Construtor para clientes (Active Open). + * Tenta estabelecer uma conexão TCP com um servidor, aplicando lógica de retry. + *

+ * Este comportamento é vital quando o processo Coordenador inicia antes das Interseções estarem + * prontas para aceitar conexões ({@code accept()}). * - * @param host endereço do host (ex: "localhost") - * @param port número da porta - * @throws IOException se falhar após todas as tentativas - * @throws UnknownHostException se o host não for encontrado - * @throws InterruptedException se a thread for interrompida + * @param host Endereço do nó de destino (ex: "localhost"). + * @param port Porta de serviço. + * @throws IOException Se a conexão falhar após todas as {@code MAX_RETRIES} tentativas. + * @throws UnknownHostException Se o DNS não resolver o hostname. + * @throws InterruptedException Se a thread for interrompida durante o sleep de retry. */ public SocketConnection(String host, int port) throws IOException, UnknownHostException, InterruptedException { Socket tempSocket = null; @@ -52,7 +63,7 @@ public class SocketConnection implements Closeable { // --- Retry Loop --- for (int attempt = 1; attempt <= MAX_RETRIES; attempt++) { try { - // Try to establish the connection + // Try to establish the connection (SYN -> SYN-ACK -> ACK) tempSocket = new Socket(host, port); // If successful, break out of the retry loop @@ -61,17 +72,17 @@ public class SocketConnection implements Closeable { break; } catch (ConnectException | SocketTimeoutException e) { - // These are common errors indicating the server might not be ready. + // Common errors: "Connection refused" (server not up) or "Timeout" (firewall/network) lastException = e; System.out.printf("[SocketConnection] Attempt %d/%d failed: %s. Retrying in %d ms...%n", attempt, MAX_RETRIES, e.getMessage(), RETRY_DELAY_MS); if (attempt < MAX_RETRIES) { - // Wait before the next attempt + // Blocking wait before next attempt TimeUnit.MILLISECONDS.sleep(RETRY_DELAY_MS); } } catch (IOException e) { - // Other IOExceptions might be more permanent, but we retry anyway. + // Other IO errors lastException = e; System.out.printf("[SocketConnection] Attempt %d/%d failed with IOException: %s. Retrying in %d ms...%n", attempt, MAX_RETRIES, e.getMessage(), RETRY_DELAY_MS); @@ -81,51 +92,49 @@ public class SocketConnection implements Closeable { } } // --- End of Retry Loop --- - // If after all retries tempSocket is still null, it means connection failed permanently. + // Final validation if (tempSocket == null) { System.err.printf("[SocketConnection] Failed to connect to %s:%d after %d attempts.%n", host, port, MAX_RETRIES); if (lastException != null) { - throw lastException; // Throw the last exception encountered + throw lastException; // Propagate the root cause } else { - // Should not happen if loop ran, but as a fallback throw new IOException("Failed to connect after " + MAX_RETRIES + " attempts, reason unknown."); } } - // If connection was successful, assign to final variable and create streams + // Initialize streams this.socket = tempSocket; - this.outputStream = socket.getOutputStream(); this.inputStream = socket.getInputStream(); this.serializer = SerializerFactory.createDefault(); - } - /** - * Constructor for the "Server" (who accepts the connection). - * Receives a Socket that has already been accepted by a ServerSocket. - * No retry logic needed here as the connection is already established. + * Construtor para servidores (Passive Open). + * Envolve um socket já conectado (retornado por {@code serverSocket.accept()}). + * Não necessita de retry logic pois a conexão física já existe. * - * @param acceptedSocket The Socket returned by serverSocket.accept(). - * @throws IOException If stream creation fails. + * @param acceptedSocket O socket ativo retornado pelo SO. + * @throws IOException Se falhar a obtenção dos streams de I/O. */ public SocketConnection(Socket acceptedSocket) throws IOException { this.socket = acceptedSocket; this.outputStream = socket.getOutputStream(); this.inputStream = socket.getInputStream(); this.serializer = SerializerFactory.createDefault(); - } /** - * Sends (serializes) a MessageProtocol object over the socket. + * Serializa e transmite uma mensagem através do canal. + *

+ * Utiliza sincronização ({@code synchronized}) para garantir que escritas concorrentes + * na mesma conexão não corrompem a stream de bytes (thread-safety). * - * @param message The "envelope" (which contains the Vehicle) to be sent. - * @throws IOException If writing to the stream fails or socket is not connected. + * @param message O objeto de protocolo a enviar. + * @throws IOException Se o socket estiver fechado ou ocorrer erro de escrita. */ public synchronized void sendMessage(MessageProtocol message) throws IOException { - if (socket == null || !socket.isConnected()) { + if (socket == null || !socket.isConnected()) { throw new IOException("Socket is not connected"); } @@ -133,11 +142,11 @@ public class SocketConnection implements Closeable { // Serializa para bytes JSON byte[] data = serializer.serialize(message); - // Write 4-byte length prefix + // Write 4-byte length prefix (Framing) DataOutputStream dataOut = new DataOutputStream(outputStream); dataOut.writeInt(data.length); dataOut.write(data); - dataOut.flush(); + dataOut.flush(); // Force transmission immediately } catch (SerializationException e) { throw new IOException("Failed to serialize message", e); @@ -145,11 +154,14 @@ public class SocketConnection implements Closeable { } /** - * Tries to read (deserialize) a MessageProtocol object from the socket. + * Bloqueia à espera de uma mensagem completa do socket. + *

+ * Lê primeiro o cabeçalho de tamanho (4 bytes) e depois o payload exato, + * garantindo que processa mensagens completas mesmo se chegarem fragmentadas em múltiplos pacotes TCP. * - * @return The "envelope" (MessageProtocol) that was received. - * @throws IOException If the connection is lost, the stream is corrupted, or socket is not connected. - * @throws ClassNotFoundException If the received object is unknown. + * @return O objeto {@link MessageProtocol} reconstruído. + * @throws IOException Se a conexão for perdida (EOF) ou o stream corrompido. + * @throws ClassNotFoundException Se o tipo desserializado não for encontrado no classpath. */ public MessageProtocol receiveMessage() throws IOException, ClassNotFoundException { if (socket == null || !socket.isConnected()) { @@ -161,15 +173,16 @@ public class SocketConnection implements Closeable { DataInputStream dataIn = new DataInputStream(inputStream); int length = dataIn.readInt(); - if (length <= 0 || length > 10_000_000) { // Sanity check (10MB max) + // Sanity check para evitar OutOfMemory em caso de corrupção de stream + if (length <= 0 || length > 10_000_000) { // Max 10MB payload throw new IOException("Invalid message length: " + length); } - // Ler dados da mensagem + // Ler dados exatos da mensagem byte[] data = new byte[length]; dataIn.readFully(data); - // Deserialize do JSON - use concrete Message class, not interface + // Deserialize do JSON - força o tipo concreto Message return serializer.deserialize(data, sd.model.Message.class); } catch (SerializationException e) { @@ -178,7 +191,8 @@ public class SocketConnection implements Closeable { } /** - * Closes the socket and all streams (Input and Output). + * Encerra a conexão e liberta os descritores de ficheiro. + * Operação idempotente. */ @Override public void close() throws IOException { @@ -188,7 +202,8 @@ public class SocketConnection implements Closeable { } /** - * @return true if the socket is still connected and not closed. + * Verifica o estado operacional da conexão. + * @return true se o socket está aberto e conectado. */ public boolean isConnected() { return socket != null && socket.isConnected() && !socket.isClosed(); diff --git a/main/src/main/java/sd/serialization/JsonMessageSerializer.java b/main/src/main/java/sd/serialization/JsonMessageSerializer.java index 1b70c68..68c6b06 100644 --- a/main/src/main/java/sd/serialization/JsonMessageSerializer.java +++ b/main/src/main/java/sd/serialization/JsonMessageSerializer.java @@ -1,26 +1,25 @@ package sd.serialization; +import java.nio.charset.StandardCharsets; + import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonSyntaxException; -import java.nio.charset.StandardCharsets; - /** - * JSON-based implementation of {@link MessageSerializer} using Google's Gson library. - * - * This serializer converts objects to JSON format for transmission, providing: - * - Human-readable message format (easy debugging) - * - Cross-platform compatibility - * - Smaller message sizes compared to Java native serialization - * - Better security (no code execution during deserialization) - * - * The serializer is configured with pretty printing disabled by default for - * production use, but can be enabled for debugging purposes. - * - * Thread-safety: This class is thread-safe as Gson instances are thread-safe. - * - * @see MessageSerializer + * Implementação baseada em JSON da estratégia {@link MessageSerializer}, utilizando a biblioteca Gson. + *

+ * Este serializador converte objetos Java para o formato de texto JSON antes da transmissão. + * Oferece várias vantagens técnicas sobre a serialização nativa do Java: + *

    + *
  • Legibilidade: O formato de texto facilita a depuração (sniffing de rede) sem ferramentas especializadas.
  • + *
  • Interoperabilidade: Permite futura integração com componentes não-Java (ex: Dashboards web em JS).
  • + *
  • Segurança: Reduz a superfície de ataque para execução remota de código (RCE), pois não desserializa classes arbitrárias, apenas dados.
  • + *
+ *

+ * Thread-Safety: A instância interna do {@code Gson} é imutável e thread-safe, permitindo + * que este serializador seja partilhado entre múltiplas threads (ex: no pool do DashboardServer). + * * @see MessageSerializer */ public class JsonMessageSerializer implements MessageSerializer { @@ -28,16 +27,16 @@ public class JsonMessageSerializer implements MessageSerializer { private final boolean prettyPrint; /** - * Creates a new JSON serializer with default configuration (no pretty printing). + * Cria um novo serializador JSON com configuração otimizada para produção (compacto). */ public JsonMessageSerializer() { this(false); } /** - * Creates a new JSON serializer with optional pretty printing. - * - * @param prettyPrint If true, JSON output will be formatted with indentation + * Cria um novo serializador JSON com formatação opcional. + * * @param prettyPrint Se {@code true}, o JSON gerado incluirá indentação e quebras de linha. + * Útil para debug, mas aumenta significativamente o tamanho do payload. */ public JsonMessageSerializer(boolean prettyPrint) { this.prettyPrint = prettyPrint; @@ -53,6 +52,13 @@ public class JsonMessageSerializer implements MessageSerializer { this.gson = builder.create(); } + /** + * Converte um objeto em memória para um array de bytes JSON (UTF-8). + * + * @param object O objeto a ser serializado. + * @return O payload em bytes pronto para transmissão TCP. + * @throws SerializationException Se o objeto não for compatível com JSON ou ocorrer erro de encoding. + */ @Override public byte[] serialize(Object object) throws SerializationException { if (object == null) { @@ -68,6 +74,16 @@ public class JsonMessageSerializer implements MessageSerializer { } } + /** + * Reconstrói um objeto Java a partir de um array de bytes JSON. + *

+ * Realiza a validação sintática do JSON e a validação de tipo baseada na classe alvo. + * + * @param data O array de bytes recebido da rede. + * @param clazz A classe do objeto esperado (Type Token). + * @return A instância do objeto reconstruído. + * @throws SerializationException Se o JSON for malformado ou incompatível com a classe alvo. + */ @Override public T deserialize(byte[] data, Class clazz) throws SerializationException { if (data == null) { @@ -95,20 +111,18 @@ public class JsonMessageSerializer implements MessageSerializer { } /** - * Returns the underlying Gson instance for advanced usage. - * - * @return The Gson instance + * Retorna a instância subjacente do Gson para configurações avançadas. + * * @return A instância Gson configurada. */ public Gson getGson() { return gson; } /** - * Checks if pretty printing is enabled. - * - * @return true if pretty printing is enabled + * Verifica se a formatação "pretty print" está ativa. + * * @return true se a indentação estiver habilitada. */ public boolean isPrettyPrint() { return prettyPrint; } -} +} \ No newline at end of file diff --git a/main/src/main/java/sd/serialization/MessageSerializer.java b/main/src/main/java/sd/serialization/MessageSerializer.java index 21517f4..3a5ff55 100644 --- a/main/src/main/java/sd/serialization/MessageSerializer.java +++ b/main/src/main/java/sd/serialization/MessageSerializer.java @@ -1,48 +1,49 @@ package sd.serialization; /** - * Interface for serializing and deserializing objects for network transmission. - * - * This interface provides a common abstraction for different serialization strategies - * allowing the system to switch between implementations without changing the communication layer. - * - * Implementations must ensure: - * - Thread-safety if used in concurrent contexts - * - Proper exception handling with meaningful error messages - * - Preservation of object state during round-trip serialization - * - * @see JsonMessageSerializer + * Interface que define o contrato para estratégias de serialização e desserialização de objetos. + *

+ * Esta abstração permite desacoplar a camada de transporte (Sockets TCP) da camada de + * apresentação de dados. Ao implementar o padrão Strategy, o sistema ganha flexibilidade + * para alternar entre diferentes formatos de codificação (JSON, Binário Nativo, XML, Protobuf) + * sem necessidade de refatorização da lógica de rede. + *

+ * Requisitos para Implementações: + *

    + *
  • Thread-Safety: As implementações devem ser seguras para uso concorrente, dado que + * instâncias únicas podem ser partilhadas por múltiplos ClientHandlers.
  • + *
  • Robustez: Falhas de parsing devem resultar em exceções tipificadas ({@link SerializationException}), + * nunca em falhas silenciosas ou estados inconsistentes.
  • + *
+ * * @see JsonMessageSerializer */ public interface MessageSerializer { /** - * Serializes an object into a byte array for transmission. - * - * @param object The object to serialize (must not be null) - * @return A byte array containing the serialized representation - * @throws SerializationException If serialization fails - * @throws IllegalArgumentException If object is null + * Converte (Marshals) um objeto em memória para uma sequência de bytes para transmissão. + * * @param object O objeto de domínio a ser serializado (não pode ser nulo). + * @return Um array de bytes contendo a representação codificada do objeto. + * @throws SerializationException Se ocorrer um erro durante a codificação (ex: ciclo de referências). + * @throws IllegalArgumentException Se o objeto fornecido for nulo. */ byte[] serialize(Object object) throws SerializationException; /** - * Deserializes a byte array back into an object of the specified type. - * - * @param The expected type of the deserialized object - * @param data The byte array containing serialized data (must not be null) - * @param clazz The class of the expected object type (must not be null) - * @return The deserialized object - * @throws SerializationException If deserialization fails - * @throws IllegalArgumentException If data or clazz is null + * Reconstrói (Unmarshals) um objeto a partir de uma sequência de bytes. + * * @param O tipo genérico do objeto esperado. + * @param data O array de bytes contendo os dados serializados (não pode ser nulo). + * @param clazz A classe do tipo esperado para verificação e instancialização. + * @return A instância do objeto reconstruído com o seu estado restaurado. + * @throws SerializationException Se os dados estiverem corrompidos ou incompatíveis com a classe alvo. + * @throws IllegalArgumentException Se os dados ou a classe forem nulos. */ T deserialize(byte[] data, Class clazz) throws SerializationException; /** - * Gets the name of this serialization strategy (e.g., "JSON", "Java Native"). - * Useful for logging and debugging. - * - * @return The serializer name + * Obtém o identificador legível desta estratégia de serialização (ex: "JSON (Gson)", "Native"). + * Utilizado primariamente para logging, auditoria e negociação de conteúdo. + * * @return O nome descritivo do serializador. */ String getName(); -} +} \ No newline at end of file diff --git a/main/src/main/java/sd/serialization/SerializationException.java b/main/src/main/java/sd/serialization/SerializationException.java index 5cf9675..bae51b0 100644 --- a/main/src/main/java/sd/serialization/SerializationException.java +++ b/main/src/main/java/sd/serialization/SerializationException.java @@ -1,41 +1,40 @@ package sd.serialization; /** - * Exception thrown when serialization or deserialization operations fail. - * - * This exception wraps underlying errors (I/O exceptions, parsing errors, etc.) - * and provides context about what went wrong during the serialization process. + * Exceção verificada (Checked Exception) que sinaliza falhas no processo de transformação de dados. + *

+ * Esta classe atua como um wrapper unificador para erros ocorridos na camada de serialização, + * abstraindo falhas de baixo nível (como erros de I/O, sintaxe JSON inválida ou incompatibilidade + * de tipos) numa única exceção de domínio. Permite que o código cliente trate falhas de + * protocolo de forma consistente, independentemente da implementação subjacente (Gson, Nativa, etc.). */ public class SerializationException extends Exception { private static final long serialVersionUID = 1L; // Long(64bits) instead of int(32bits) /** - * Constructs a new serialization exception with the specified detail message. - * - * @param message The detail message + * Constrói uma nova exceção de serialização com uma mensagem descritiva. + * * @param message A mensagem detalhando o erro. */ public SerializationException(String message) { super(message); } /** - * Constructs a new serialization exception with the specified detail message - * and cause. - * - * @param message The detail message - * @param cause The cause of this exception + * Constrói uma nova exceção encapsulando a causa raiz do problema. + * Útil para preservar a stack trace original de erros de bibliotecas terceiras (ex: Gson). + * * @param message A mensagem detalhando o erro. + * @param cause A exceção original que causou a falha. */ public SerializationException(String message, Throwable cause) { super(message, cause); } /** - * Constructs a new serialization exception with the specified cause. - * - * @param cause The cause of this exception + * Constrói uma nova exceção baseada apenas na causa raiz. + * * @param cause A exceção original. */ public SerializationException(Throwable cause) { super(cause); } -} +} \ No newline at end of file diff --git a/main/src/main/java/sd/serialization/SerializerFactory.java b/main/src/main/java/sd/serialization/SerializerFactory.java index a2261d3..70d2f0e 100644 --- a/main/src/main/java/sd/serialization/SerializerFactory.java +++ b/main/src/main/java/sd/serialization/SerializerFactory.java @@ -1,14 +1,14 @@ package sd.serialization; /** - * Factory for creating {@link MessageSerializer} instances. - * - * This factory provides a centralized way to create and configure JSON serializers - * using Gson, making it easy to configure serialization throughout the application. - * - * The factory can be configured via system properties for easy deployment configuration. - * - * Example usage: + * Fábrica estática (Factory Pattern) para instanciação controlada de {@link MessageSerializer}. + *

+ * Esta classe centraliza a criação de estratégias de serialização, garantindo consistência + * de configuração em todo o sistema distribuído. Permite a injeção de configurações via + * Propriedades de Sistema (System Properties), facilitando a alternância entre modos de + * depuração (Pretty Print) e produção (Compacto) sem recompilação. + *

+ * Exemplo de Uso: *

  * MessageSerializer serializer = SerializerFactory.createDefault();
  * byte[] data = serializer.serialize(myObject);
@@ -17,28 +17,27 @@ package sd.serialization;
 public class SerializerFactory {
     
     /**
-     * System property key for enabling pretty-print in JSON serialization.
-     * Set to "true" for debugging, "false" for production.
+     * Chave da propriedade de sistema para ativar a formatação JSON legível (Pretty Print).
+     * Defina {@code -Dsd.serialization.json.prettyPrint=true} na JVM para ativar.
      */
     public static final String JSON_PRETTY_PRINT_PROPERTY = "sd.serialization.json.prettyPrint";
     
-    // Default configuration
+    // Default configuration (Production-ready)
     private static final boolean DEFAULT_JSON_PRETTY_PRINT = false;
     
     /**
-     * Private constructor to prevent instantiation.
+     * Construtor privado para prevenir instanciação acidental desta classe utilitária.
      */
     private SerializerFactory() {
         throw new UnsupportedOperationException("Factory class cannot be instantiated");
     }
     
     /**
-     * Creates a JSON serializer based on system configuration.
-     * 
-     * Pretty-print is determined by checking the system property
-     * {@value #JSON_PRETTY_PRINT_PROPERTY}. If not set, defaults to false.
-     * 
-     * @return A configured JsonMessageSerializer instance
+     * Cria um serializador JSON configurado dinamicamente pelo ambiente.
+     * 

+ * Verifica a propriedade de sistema {@value #JSON_PRETTY_PRINT_PROPERTY}. + * Se não definida, assume o padrão de produção (falso/compacto). + * * @return Uma instância configurada de {@link JsonMessageSerializer}. */ public static MessageSerializer createDefault() { boolean prettyPrint = Boolean.getBoolean(JSON_PRETTY_PRINT_PROPERTY); @@ -46,21 +45,20 @@ public class SerializerFactory { } /** - * Creates a JSON serializer with default configuration (no pretty printing). - * - * @return A JsonMessageSerializer instance + * Cria um serializador JSON com configuração padrão otimizada (sem indentação). + * Ideal para ambientes de produção onde a largura de banda é prioritária. + * * @return Uma instância compacta de {@link JsonMessageSerializer}. */ public static MessageSerializer createSerializer() { return createSerializer(DEFAULT_JSON_PRETTY_PRINT); } /** - * Creates a JSON serializer with specified pretty-print setting. - * - * @param prettyPrint Whether to enable pretty printing - * @return A JsonMessageSerializer instance + * Cria um serializador JSON com configuração explícita de formatação. + * * @param prettyPrint {@code true} para ativar indentação (Debug), {@code false} para compacto. + * @return Uma instância personalizada de {@link JsonMessageSerializer}. */ public static MessageSerializer createSerializer(boolean prettyPrint) { return new JsonMessageSerializer(prettyPrint); } -} +} \ No newline at end of file diff --git a/main/src/main/java/sd/util/RandomGenerator.java b/main/src/main/java/sd/util/RandomGenerator.java index 5b9c65c..e9ab436 100644 --- a/main/src/main/java/sd/util/RandomGenerator.java +++ b/main/src/main/java/sd/util/RandomGenerator.java @@ -3,82 +3,88 @@ package sd.util; import java.util.Random; /** - * Utilitário para gerar valores aleatórios usados na simulação. - * - *

Fornece métodos estáticos para:

+ * Utilitário central de geração estocástica para a simulação. + *

+ * Esta classe fornece primitivas para geração de números pseudo-aleatórios, abstraindo + * a complexidade de distribuições estatísticas. + *

+ * Funcionalidades Principais: *

    - *
  • Gerar intervalos exponencialmente distribuídos (processos de Poisson)
  • - *
  • Gerar inteiros e doubles aleatórios num intervalo
  • - *
  • Tomar decisões baseadas em probabilidade
  • - *
  • Escolher elementos aleatórios de um array
  • + *
  • Modelagem de Poisson: Geração de tempos entre chegadas usando distribuição exponencial inversa.
  • + *
  • Amostragem Uniforme: Geração de inteiros e doubles em intervalos fechados/abertos.
  • + *
  • Decisão Probabilística: Avaliação de eventos booleanos baseados em pesos (Bernoulli trials).
  • + *
  • Determinismo: Suporte a sementes (seeds) manuais para reprodutibilidade exata de cenários de teste.
  • *
- * - *

Usa uma única instância estática de {@link Random}.

*/ public class RandomGenerator { - /** Instância partilhada de Random para toda a simulação */ + /** * Instância singleton estática do gerador PRNG (Pseudo-Random Number Generator). + * Thread-safe (java.util.Random é sincronizado), embora possa haver contenção em alta concorrência. + */ private static final Random random = new Random(); /** - * Retorna um intervalo de tempo que segue uma distribuição exponencial. - * - *

Componente essencial para modelar processos de Poisson, onde os - * tempos entre chegadas seguem uma distribuição exponencial.

- * - *

Fórmula: {@code Time = -ln(1 - U) / λ}
- * onde U é um número aleatório uniforme [0, 1) e λ (lambda) é a taxa média de chegada.

+ * Gera um intervalo de tempo seguindo uma Distribuição Exponencial. + *

+ * Este método implementa o algoritmo de Inverse Transform Sampling para simular + * um Processo de Poisson homogêneo. É fundamental para modelar a chegada natural de + * veículos, onde eventos independentes ocorrem a uma taxa média constante. + *

+ * Fórmula Matemática: {@code T = -ln(1 - U) / λ} + *
Onde: + *

    + *
  • {@code U}: Variável aleatória uniforme no intervalo [0, 1).
  • + *
  • {@code λ (lambda)}: Taxa média de eventos por unidade de tempo (ex: veículos/segundo).
  • + *
* - * @param lambda taxa média de chegada λ (ex: 0.5 veículos por segundo) - * @return intervalo de tempo (segundos) até à próxima chegada + * @param lambda A taxa média de chegada (λ > 0). + * @return O intervalo de tempo (delta t) até o próximo evento, em segundos. */ public static double generateExponentialInterval(double lambda) { return Math.log(1 - random.nextDouble()) / -lambda; } /** - * Retorna um inteiro aleatório entre {@code min} e {@code max}, inclusive. + * Gera um número inteiro uniformemente distribuído no intervalo fechado {@code [min, max]}. * - * @param min valor mínimo possível - * @param max valor máximo possível - * @return inteiro aleatório no intervalo [min, max] + * @param min Limite inferior (inclusivo). + * @param max Limite superior (inclusivo). + * @return Um inteiro aleatório I tal que {@code min <= I <= max}. */ public static int generateRandomInt(int min, int max) { return random.nextInt(max - min + 1) + min; } /** - * Retorna um double aleatório entre {@code min} (inclusive) e {@code max} (exclusivo). + * Gera um número de ponto flutuante uniformemente distribuído no intervalo semi-aberto {@code [min, max)}. * - * @param min valor mínimo possível - * @param max valor máximo possível - * @return double aleatório no intervalo [min, max) + * @param min Limite inferior (inclusivo). + * @param max Limite superior (exclusivo). + * @return Um double aleatório D tal que {@code min <= D < max}. */ public static double generateRandomDouble(double min, double max) { return min + (max - min) * random.nextDouble(); } /** - * Retorna {@code true} com uma dada probabilidade. - * - *

Útil para tomar decisões ponderadas. Por exemplo, - * {@code occursWithProbability(0.3)} retorna {@code true} - * aproximadamente 30% das vezes.

+ * Realiza um teste de Bernoulli (Sim/Não) com uma probabilidade de sucesso especificada. + *

+ * Utilizado para decisões de ramificação estocástica (ex: "Este veículo é um camião?"). * - * @param probability valor entre 0.0 (nunca) e 1.0 (sempre) - * @return {@code true} ou {@code false}, baseado na probabilidade + * @param probability A probabilidade de retorno {@code true} (0.0 a 1.0). + * @return {@code true} se o evento ocorrer, {@code false} caso contrário. */ public static boolean occursWithProbability(double probability) { return random.nextDouble() < probability; } /** - * Escolhe um elemento aleatório do array fornecido. + * Seleciona aleatoriamente um elemento de um array genérico (Amostragem Uniforme Discreta). * - * @param tipo genérico do array - * @param array array de onde escolher - * @return elemento selecionado aleatoriamente - * @throws IllegalArgumentException se o array for null ou vazio + * @param O tipo dos elementos no array. + * @param array A população de onde escolher. + * @return O elemento selecionado. + * @throws IllegalArgumentException Se o array for nulo ou vazio. */ public static T chooseRandom(T[] array) { if (array == null || array.length == 0) { @@ -88,13 +94,13 @@ public class RandomGenerator { } /** - * Define a seed do gerador de números aleatórios partilhado. - * - *

Extremamente útil para debugging e testes, pois permite executar - * a simulação múltiplas vezes com a mesma sequência de eventos "aleatórios", - * tornando os resultados reproduzíveis.

+ * Reinicializa a semente (seed) do gerador global. + *

+ * Importância Crítica: Permite tornar a simulação determinística. Ao fixar a seed, + * a sequência de números "aleatórios" gerada será idêntica em execuções subsequentes, + * facilitando a depuração de race conditions ou lógica complexa. * - * @param seed seed a usar + * @param seed O valor da semente inicial (ex: timestamp ou constante). */ public static void setSeed(long seed) { random.setSeed(seed); diff --git a/main/src/main/java/sd/util/VehicleGenerator.java b/main/src/main/java/sd/util/VehicleGenerator.java index dce42ce..344b4d7 100644 --- a/main/src/main/java/sd/util/VehicleGenerator.java +++ b/main/src/main/java/sd/util/VehicleGenerator.java @@ -9,55 +9,58 @@ import sd.model.VehicleType; import sd.routing.RouteSelector; /** - * Gera veículos para a simulação. - * - *

Esta classe é responsável por duas tarefas principais:

+ * Motor de injeção de carga (Load Injector) para a simulação de tráfego. + *

+ * Esta classe atua como uma fábrica estocástica de veículos, sendo responsável por: *

    - *
  1. Determinar quando o próximo veículo deve chegar, baseado no - * modelo de chegada (POISSON ou FIXED) da {@link SimulationConfig}
  2. - *
  3. Criar um novo objeto {@link Vehicle} com tipo e rota selecionados pela - * política de roteamento configurada ({@link RouteSelector})
  4. + *
  5. Modelagem Temporal: Determinar os instantes de chegada (Inter-arrival times) + * usando processos de Poisson (estocástico) ou intervalos determinísticos.
  6. + *
  7. Caracterização da Entidade: Atribuir tipos de veículo (Bike, Light, Heavy) + * baseado numa Distribuição de Probabilidade Cumulativa (CDF).
  8. + *
  9. Inicialização Espacial: Distribuir a carga uniformemente entre os pontos de entrada (E1-E3).
  10. + *
  11. Atribuição de Rota: Delegar a escolha do percurso à estratégia {@link RouteSelector} ativa.
  12. *
- * - *

As rotas são selecionadas usando uma política de roteamento que pode ser: - * aleatória, caminho mais curto, menor congestionamento, etc.

*/ public class VehicleGenerator { private final SimulationConfig config; private final String arrivalModel; - /** Lambda (λ) para modelo POISSON */ + + /** Parâmetro Lambda (λ) para a distribuição de Poisson (taxa de chegada). */ private final double arrivalRate; - /** Intervalo para modelo FIXED */ + + /** Intervalo determinístico para geração constante (modo debug/teste). */ private final double fixedInterval; - /** Política de roteamento usada para selecionar rotas */ + /** * Estratégia de roteamento atual. + * Não é final para permitir Hot-Swapping durante a execução. + */ private RouteSelector routeSelector; /** - * Cria um novo gerador de veículos com a política de roteamento especificada. - * Lê a configuração necessária. + * Inicializa o gerador com as configurações de simulação e estratégia de roteamento. * - * @param config objeto de {@link SimulationConfig} - * @param routeSelector política de roteamento a usar para selecionar rotas + * @param config A configuração global contendo as taxas e probabilidades. + * @param routeSelector A estratégia inicial de seleção de rotas. */ public VehicleGenerator(SimulationConfig config, RouteSelector routeSelector) { this.config = config; this.routeSelector = routeSelector; - // Cache configuration values for performance + // Cache de valores de configuração para evitar lookups repetitivos em hot-path this.arrivalModel = config.getArrivalModel(); this.arrivalRate = config.getArrivalRate(); this.fixedInterval = config.getFixedArrivalInterval(); } /** - * Calcula o tempo absoluto da próxima chegada de veículo - * baseado no modelo configurado. - * - * @param currentTime tempo atual da simulação, usado como base - * @return tempo absoluto (ex: {@code currentTime + intervalo}) - * em que o próximo veículo deve ser gerado + * Calcula o timestamp absoluto para a próxima injeção de veículo. + *

+ * Se o modelo for "POISSON", utiliza a técnica de Inverse Transform Sampling + * (via {@link RandomGenerator}) para gerar intervalos exponencialmente distribuídos, + * simulando a aleatoriedade natural do tráfego. + * * @param currentTime O tempo atual da simulação (base de cálculo). + * @return O instante futuro (t + delta) para agendamento do evento de geração. */ public double getNextArrivalTime(double currentTime) { if ("POISSON".equalsIgnoreCase(arrivalModel)) { @@ -69,19 +72,19 @@ public class VehicleGenerator { } /** - * Gera um novo objeto {@link Vehicle}. - * - *

Passos executados:

+ * Instancia (Spawn) um novo veículo configurado e roteado. + *

+ * O processo de criação segue um pipeline: *

    - *
  1. Seleciona um {@link VehicleType} aleatório baseado em probabilidades
  2. - *
  3. Seleciona um ponto de entrada aleatório (E1, E2, E3)
  4. - *
  5. Usa a política de roteamento para escolher a rota
  6. + *
  7. Seleção de Tipo (Roda da Fortuna / CDF).
  8. + *
  9. Seleção de Entrada (Uniforme).
  10. + *
  11. Cálculo de Rota (Delegado ao Strategy).
  12. *
* - * @param vehicleId identificador único do novo veículo (ex: "V123") - * @param entryTime tempo de simulação em que o veículo é criado - * @param queueSizes mapa com tamanho das filas (opcional, pode ser null) - * @return novo objeto {@link Vehicle} configurado + * @param vehicleId O identificador único sequencial (ex: "V104"). + * @param entryTime O timestamp de criação. + * @param queueSizes Snapshot atual das filas (usado apenas por estratégias dinâmicas como LEAST_CONGESTED). + * @return A entidade {@link Vehicle} pronta para inserção na malha. */ public Vehicle generateVehicle(String vehicleId, double entryTime, Map queueSizes) { VehicleType type = selectVehicleType(); @@ -92,18 +95,12 @@ public class VehicleGenerator { } /** - * Seleciona um {@link VehicleType} (BIKE, LIGHT, HEAVY) baseado nas - * probabilidades definidas na {@link SimulationConfig}. - * - *

Usa técnica de "probabilidade cumulativa":

- *
    - *
  1. Obtém número aleatório {@code rand} de [0, 1)
  2. - *
  3. Se {@code rand < P(Bike)}, retorna BIKE
  4. - *
  5. Senão se {@code rand < P(Bike) + P(Light)}, retorna LIGHT
  6. - *
  7. Caso contrário, retorna HEAVY
  8. - *
+ * Seleciona o tipo de veículo usando Amostragem por Probabilidade Cumulativa. + *

+ * Normaliza as probabilidades configuradas e mapeia um número aleatório [0, 1) + * para o intervalo correspondente ao tipo de veículo. * - * @return tipo de veículo selecionado + * @return O tipo enumerado {@link VehicleType} selecionado. */ private VehicleType selectVehicleType() { double bikeProbability = config.getBikeVehicleProbability(); @@ -111,7 +108,9 @@ public class VehicleGenerator { double heavyProbability = config.getHeavyVehicleProbability(); double total = bikeProbability + lightProbability + heavyProbability; - if (total == 0) return VehicleType.LIGHT; // Avoid division by zero + if (total == 0) return VehicleType.LIGHT; // Fallback de segurança + + // Normalização bikeProbability /= total; lightProbability /= total; @@ -127,10 +126,10 @@ public class VehicleGenerator { } /** - * Seleciona aleatoriamente um ponto de entrada (E1, E2 ou E3). - * Cada ponto tem probabilidade igual (1/3). + * Seleciona um ponto de injeção na borda da rede (Edge Node). + * Distribuição Uniforme: ~33.3% para cada entrada (E1, E2, E3). * - * @return ponto de entrada selecionado ("E1", "E2" ou "E3") + * @return O ID da interseção de entrada. */ private String selectRandomEntryPoint() { double rand = Math.random(); @@ -145,23 +144,19 @@ public class VehicleGenerator { } /** - * Altera dinamicamente o RouteSelector usado para gerar rotas. - * Permite mudar a política de roteamento durante a simulação. - * - * @param newRouteSelector novo seletor de rotas + * Atualiza a estratégia de roteamento em tempo de execução (Hot-Swap). + *

+ * Permite que o Coordenador altere o comportamento da frota (ex: de RANDOM para SHORTEST_PATH) + * sem necessidade de reiniciar a simulação. + * * @param newRouteSelector A nova implementação de estratégia a utilizar. */ public void setRouteSelector(RouteSelector newRouteSelector) { - // Note: In Java, we can't directly modify the 'final' field, - // but we can create a new VehicleGenerator with the new selector. - // For this implementation, we'll need to remove 'final' from routeSelector. - // This is acceptable since we want dynamic policy changes. - throw new UnsupportedOperationException( - "VehicleGenerator is immutable. Use CoordinatorProcess.changeRoutingPolicy() instead." - ); + this.routeSelector = newRouteSelector; } /** - * @return A string providing information about the generator's configuration. + * Retorna uma representação textual do estado interno do gerador. + * Útil para logs de auditoria e debugging. */ public String getInfo() { return String.format(