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: + *

+ *

+ * 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: + *

+ * * @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(