diff --git a/README.md b/README.md new file mode 100644 index 0000000..cc47c34 --- /dev/null +++ b/README.md @@ -0,0 +1,620 @@ +# Sistema de Simulação de Tráfego Distribuído + +Sistema distribuído de simulação de tráfego. +--- + +## Índice + +- [Visão Geral](#visão-geral) +- [Arquitetura](#arquitetura) +- [Protocolo de Comunicação](#protocolo-de-comunicação) +- [Estrutura do Projeto](#estrutura-do-projeto) +- [Instalação e Execução](#instalação-e-execução) +- [Documentação](#documentação) +- [Desenvolvimento](#desenvolvimento) + +--- + +## Visão Geral + +Este projeto implementa uma simulação distribuída de tráfego veicular numa rede de cruzamentos. O sistema utiliza: + +- **Processos independentes** para cada cruzamento +- **Threads** para controlar os semáforos dentro de cada cruzamento +- **Comunicação via sockets** para transferência de veículos entre cruzamentos +- **Simulação de eventos discretos** (DES) para gerir o tempo de simulação + +### Características Principais + +- Simulação determinística e reproduzível +- Comunicação assíncrona entre processos +- Protocolo de mensagens baseado em JSON +- Dashboard em tempo real (planeado) +- Estatísticas detalhadas de desempenho + +--- + +## Arquitetura + +### Visão Geral do Sistema + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ SISTEMA DISTRIBUÍDO │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌──────────────┐ ┌──────────────┐ │ +│ │ Coordenador │ ────────────────────────>│ Dashboard │ │ +│ │ / Gerador │ │ │ +│ └──────┬───────┘ └──────▲───────┘ │ +│ │ │ │ +│ │ Gera veículos Stats │ │ +│ │ │ │ +│ ▼ │ │ +│ ┌─────────────────────────────────────────────────┴──────┐ │ +│ │ Rede de Cruzamentos (Processos) │ │ +│ │ │ │ +│ │ ┌────┐ ┌────┐ ┌────┐ │ │ +│ │ │Cr1 │◄───────►│Cr2 │◄───────►│Cr3 │ │ │ +│ │ └─┬──┘ └─┬──┘ └─┬──┘ │ │ +│ │ │ │ │ │ │ +│ │ │ ┌────▼────┐ │ │ │ +│ │ └────────►│ Cr4 │◄────────┘ │ │ +│ │ └────┬────┘ │ │ +│ │ │ │ │ +│ │ ┌────▼────┐ │ │ +│ │ │ Cr5 │ │ │ +│ │ └────┬────┘ │ │ +│ └───────────────────┼─────────────────────────────────────┤ │ +│ │ │ │ +│ ▼ │ │ +│ ┌──────────────┐ │ │ +│ │ Nó de Saída │ │ │ +│ │ (S) │ │ │ +│ └──────────────┘ │ │ +│ │ │ +└────────────────────────────────────────────────────────────┘ │ +``` + +### Componentes + +1. **Coordenador/Gerador**: Gera veículos e injeta no sistema +2. **Cruzamentos (Cr1-Cr5)**: Processos independentes que gerem tráfego local +3. **Nó de Saída (S)**: Recolhe estatísticas de veículos que saem do sistema +4. **Dashboard Server**: Agrega e exibe dados em tempo real + +--- + +## Protocolo de Comunicação + +### Formato de Serialização: JSON (Gson) + +O sistema utiliza JSON como formato de serialização por ser mais rápido, seguro e legível que a serialização em Java. + +### Estrutura de Mensagens + +Todas as mensagens seguem o formato base: + +```json +{ + "messageId": "uuid", + "type": "MESSAGE_TYPE", + "senderId": "sender_id", + "destinationId": "destination_id", + "timestamp": 1729595234567, + "payload": { ... } +} +``` + +### Tipos de Mensagens + +#### 1. VEHICLE_TRANSFER + +Transfere um veículo entre cruzamentos. + +**Estrutura:** +```json +{ + "messageId": "a3c5e7f9-1234-5678-90ab-cdef12345678", + "type": "VEHICLE_TRANSFER", + "senderId": "Cr1", + "destinationId": "Cr2", + "timestamp": 1729595234567, + "payload": { + "id": "V123", + "type": "LIGHT", + "entryTime": 15.7, + "route": ["Cr1", "Cr2", "Cr5", "S"], + "currentRouteIndex": 1, + "totalWaitingTime": 3.2, + "totalCrossingTime": 1.8 + } +} +``` + +**Fluxo:** +1. Veículo completa travessia no Cr1 +2. Cr1 serializa mensagem VEHICLE_TRANSFER +3. Envia para Cr2 via socket +4. Cr2 desserializa e adiciona veículo à fila + +#### 2. STATS_UPDATE + +Envia estatísticas de um cruzamento para o Dashboard. + +**Estrutura:** +```json +{ + "messageId": "b4d6e8f0-2345-6789-01bc-def123456789", + "type": "STATS_UPDATE", + "senderId": "Cr3", + "destinationId": "Dashboard", + "timestamp": 1729595234789, + "payload": { + "intersectionId": "Cr3", + "queueLengths": { + "North": 5, + "South": 3, + "East": 7, + "West": 2 + }, + "vehiclesProcessed": 142, + "averageWaitTime": 4.5, + "currentTime": 123.45 + } +} +``` + +**Frequência:** A cada 10 segundos (configurável) + +#### 3. VEHICLE_EXIT + +Notifica quando um veículo sai do sistema. + +**Estrutura:** +```json +{ + "messageId": "c5e7f9a1-3456-7890-12bc-def123456789", + "type": "VEHICLE_EXIT", + "senderId": "Cr5", + "destinationId": "ExitNode", + "timestamp": 1729595234890, + "payload": { + "id": "V123", + "type": "LIGHT", + "entryTime": 15.7, + "exitTime": 45.2, + "totalSystemTime": 29.5, + "totalWaitingTime": 8.3, + "totalCrossingTime": 4.8, + "routeTaken": ["Cr1", "Cr2", "Cr5", "S"] + } +} +``` + +#### 4. HEARTBEAT + +Mantém a ligação ativa e monitoriza a saúde dos processos. + +**Estrutura:** +```json +{ + "messageId": "d6e8f0a2-4567-8901-23cd-ef1234567890", + "type": "HEARTBEAT", + "senderId": "Cr1", + "destinationId": "Coordinator", + "timestamp": 1729595235000, + "payload": { + "status": "RUNNING", + "uptime": 120.5, + "vehiclesInQueue": 12 + } +} +``` + +**Frequência:** A cada 5 segundos + +#### 5. LIGHT_CHANGE + +Notifica mudança de estado de semáforo (para logging/debugging). + +**Estrutura:** +```json +{ + "messageId": "e7f9a1b3-5678-9012-34de-f12345678901", + "type": "LIGHT_CHANGE", + "senderId": "Cr1-North", + "destinationId": "Dashboard", + "timestamp": 1729595235100, + "payload": { + "lightId": "Cr1-North", + "previousState": "RED", + "newState": "GREEN", + "queueSize": 5 + } +} +``` + +### Tipos de Veículos + +```json +{ + "BIKE": { + "probability": 0.20, + "crossingTime": 1.5 + }, + "LIGHT": { + "probability": 0.60, + "crossingTime": 2.0 + }, + "HEAVY": { + "probability": 0.20, + "crossingTime": 4.0 + } +} +``` + +### Estados dos Semáforos + +``` +RED → Veículos aguardam na fila +GREEN → Veículos podem atravessar +``` + +### Exemplo de Comunicação Completa + +``` +Tempo Processo Ação Mensagem +------ --------- ------------------------------------- ------------------ +15.7s Gerador Gera veículo V123 - +15.7s Gerador → Injeta V123 em Cr1 VEHICLE_TRANSFER +18.2s Cr1 V123 inicia travessia - +20.2s Cr1 V123 completa travessia - +20.2s Cr1 → Cr2 Transfere V123 para Cr2 VEHICLE_TRANSFER +23.5s Cr2 V123 inicia travessia - +25.5s Cr2 V123 completa travessia - +25.5s Cr2 → Cr5 Transfere V123 para Cr5 VEHICLE_TRANSFER +28.0s Cr5 V123 inicia travessia - +30.0s Cr5 V123 completa travessia - +30.0s Cr5 → Exit V123 sai do sistema VEHICLE_EXIT +30.0s Exit → Dash Estatísticas de V123 STATS_UPDATE +``` + +--- + +## Estrutura do Projeto + +``` +Trabalho-Pratico-SD/ +├── README.md # Este ficheiro +├── TODO.md # Plano de desenvolvimento +├── main/ +│ ├── pom.xml # Configuração do Maven +│ ├── docs/ +│ │ ├── README.md # Índice da documentação +│ │ ├── SERIALIZATION_SPECIFICATION.md +│ │ ├── SERIALIZATION_DECISION.md +│ │ ├── SERIALIZATION_SUMMARY.md +│ │ └── SERIALIZATION_ARCHITECTURE.md +│ ├── src/ +│ │ ├── main/java/sd/ +│ │ │ ├── Entry.java # Ponto de entrada +│ │ │ ├── config/ +│ │ │ │ └── SimulationConfig.java +│ │ │ ├── engine/ +│ │ │ │ └── SimulationEngine.java +│ │ │ ├── model/ +│ │ │ │ ├── Event.java +│ │ │ │ ├── EventType.java +│ │ │ │ ├── Intersection.java +│ │ │ │ ├── Message.java # Estrutura de mensagens +│ │ │ │ ├── MessageType.java # Tipos de mensagens +│ │ │ │ ├── TrafficLight.java +│ │ │ │ ├── Vehicle.java +│ │ │ │ └── VehicleType.java +│ │ │ ├── serialization/ # Sistema de serialização +│ │ │ │ ├── MessageSerializer.java +│ │ │ │ ├── SerializationException.java +│ │ │ │ ├── JsonMessageSerializer.java +│ │ │ │ ├── SerializerFactory.java +│ │ │ │ ├── SerializationExample.java +│ │ │ │ └── README.md +│ │ │ └── util/ +│ │ │ ├── RandomGenerator.java +│ │ │ ├── StatisticsCollector.java +│ │ │ └── VehicleGenerator.java +│ │ └── test/java/ +│ │ ├── SimulationTest.java +│ │ └── sd/serialization/ +│ │ └── SerializationTest.java +│ └── target/ # Ficheiros compilados +└── .vscode/ # Configuração do VS Code +``` + +--- + +## Instalação e Execução + +### Pré-requisitos + +- **Java 17** ou superior +- **Maven 3.8+** +- **Git** + +### Instalação + +```bash +# Clonar o repositório +git clone https://github.com/davidalves04/Trabalho-Pratico-SD.git +cd Trabalho-Pratico-SD/main + +# Compilar o projeto +mvn clean compile + +# Executar os testes +mvn test +``` + +### Execução + +#### Simulação Básica (Single Process) + +```bash +mvn exec:java -Dexec.mainClass="sd.Entry" +``` + +#### Exemplo de Serialização + +```bash +mvn exec:java -Dexec.mainClass="sd.serialization.SerializationExample" +``` + +#### Configuração + +Editar `src/main/resources/simulation.properties`: + +```properties +# Duração da simulação (segundos) +simulation.duration=60.0 + +# Modelo de chegada: FIXED ou POISSON +arrival.model=POISSON + +# Taxa de chegada (veículos/segundo) +arrival.rate=0.5 + +# Intervalo de atualização de estatísticas (segundos) +stats.update.interval=10.0 + +# Distribuição de tipos de veículos +vehicle.type.bike.probability=0.20 +vehicle.type.light.probability=0.60 +vehicle.type.heavy.probability=0.20 + +# Tempos de travessia por tipo (segundos) +vehicle.type.bike.crossing.time=1.5 +vehicle.type.light.crossing.time=2.0 +vehicle.type.heavy.crossing.time=4.0 +``` + +--- + +## Documentação + +### Documentação de Serialização + +A documentação completa sobre o protocolo de serialização está disponível em: + +- **[Índice Completo](./main/docs/README.md)** - Navegação da documentação +- **[Especificação](./main/docs/SERIALIZATION_SPECIFICATION.md)** - Design detalhado +- **[Guia de Decisão](./main/docs/SERIALIZATION_DECISION.md)** - Porquê JSON? +- **[Resumo](./main/docs/SERIALIZATION_SUMMARY.md)** - Estado de implementação +- **[Arquitetura](./main/docs/SERIALIZATION_ARCHITECTURE.md)** - Diagramas visuais + +### Guias de Utilização + +- **[Serialization README](./main/src/main/java/sd/serialization/README.md)** - Como utilizar os serializers + +### Exemplos de Código + +```java +// Criar serializer +MessageSerializer serializer = SerializerFactory.createDefault(); + +// Serializar mensagem +Vehicle vehicle = new Vehicle("V123", VehicleType.LIGHT, 10.5, route); +Message message = new Message( + MessageType.VEHICLE_TRANSFER, + "Cr1", + "Cr2", + vehicle +); +byte[] data = serializer.serialize(message); + +// Enviar via socket +outputStream.write(data); + +// Receber e desserializar +byte[] received = inputStream.readAllBytes(); +Message msg = serializer.deserialize(received, Message.class); +Vehicle v = msg.getPayloadAs(Vehicle.class); +``` + +--- + +## Desenvolvimento + +### Estado do Projeto + +| Componente | Estado | Notas | +|------------|--------|-------| +| Modelo de Dados | Completo | Vehicle, Message, Event, etc. | +| Simulação DES | Completo | Single-process funcional | +| Serialização | Completo | JSON e Java implementados | +| Testes | 14/14 | Suite de serialização | +| Processos Distribuídos | Planeado | Próxima etapa | +| Comunicação Sockets | Planeado | Em design | +| Dashboard | Planeado | UI web | + +### Roteiro de Desenvolvimento + +#### Fase 1: Fundações (Concluído) +- Modelação de classes +- Simulação DES single-process +- Design de protocolo de serialização +- Implementação JSON/Java serialization +- Testes unitários + +#### Fase 2: Distribuição (Em Curso) +- Implementar comunicação via sockets +- Separar cruzamentos em processos +- Implementar threads de semáforos +- Testar comunicação entre processos + +#### Fase 3: Dashboard e Monitorização +- Dashboard server +- UI web em tempo real +- Visualização de estatísticas +- Logs estruturados + +#### Fase 4: Optimização e Análise +- Testes de carga +- Análise de diferentes políticas +- Recolha de métricas +- Relatório final + +### Executar Testes + +```bash +# Todos os testes +mvn test + +# Apenas testes de serialização +mvn test -Dtest=SerializationTest + +# Com relatório de cobertura +mvn test jacoco:report +``` + +### Contribuir + +1. Fork o projeto +2. Criar uma branch para a funcionalidade (`git checkout -b feature/MinhaFuncionalidade`) +3. Commit das alterações (`git commit -m 'Adiciona MinhaFuncionalidade'`) +4. Push para a branch (`git push origin feature/MinhaFuncionalidade`) +5. Abrir um Pull Request + +--- + +## Métricas de Desempenho + +### Serialização + +| Formato | Tamanho | Latência | Throughput | +|---------|---------|----------|------------| +| JSON | 300 bytes | 40.79 μs | ~24k msgs/s | +| Java | 657 bytes | 33.34 μs | ~30k msgs/s | + +**Conclusão**: JSON é 54% menor com overhead desprezível (7 μs) + +### Simulação + +- **Veículos gerados/s**: ~0.5-1.0 (configurável) +- **Throughput**: ~0.2 veículos/s (saída) +- **Tempo de execução**: 140ms para 60s de simulação +- **Overhead**: < 0.25% do tempo simulado + +--- + +## Protocolo de Mensagens - Resumo + +### Formato Base + +``` ++------------------+ +| Message Header | +|------------------| +| messageId | UUID único +| type | Enum MessageType +| senderId | ID do processo remetente +| destinationId | ID do processo destino (null = broadcast) +| timestamp | Tempo de criação (ms) ++------------------+ +| Payload | +|------------------| +| Object | Dados específicos do tipo de mensagem ++------------------+ +``` + +### Serialização + +- **Formato**: JSON (UTF-8) +- **Biblioteca**: Gson 2.10.1 +- **Codificação**: UTF-8 +- **Compressão**: Opcional (gzip) + +### Transporte + +- **Protocolo**: TCP/IP +- **Porta base**: 5000+ (configurável) +- **Timeout**: 30s +- **Keep-alive**: Heartbeat a cada 5s + +--- + +## Segurança + +### Considerações + +1. **Validação de Mensagens** + - Verificar tipos esperados + - Validar intervalos de valores + - Rejeitar mensagens malformadas + +2. **Autenticação** (Planeado) + - Autenticação baseada em token + - Whitelist de processos + +3. **Encriptação** (Opcional) + - TLS/SSL para produção + - Não necessário para ambiente de desenvolvimento local + +--- + +## Licença + +Este projeto é desenvolvido para fins académicos no âmbito da disciplina de Sistemas Distribuídos (SD) do Instituto Politécnico do Porto. + +--- + +## Equipa + +**Instituição**: Instituto Politécnico do Porto +**Curso**: Sistemas Distribuídos +**Ano Letivo**: 2025-2026 (1º Semestre) + +--- + +## Suporte + +Para questões ou problemas: + +1. Consultar a [documentação](./main/docs/README.md) +2. Ver [exemplos de código](./main/src/main/java/sd/serialization/SerializationExample.java) +3. Executar testes: `mvn test` +4. Abrir issue no GitHub + +--- + +## Ligações Úteis + +- [Documentação do Projeto](./main/docs/README.md) +- [Plano de Desenvolvimento](./TODO.md) +- [Especificação de Serialização](./main/docs/SERIALIZATION_SPECIFICATION.md) +- [Guia de Serialização](./main/src/main/java/sd/serialization/README.md) + +--- + +**Última actualização**: 23 de outubro de 2025 +**Versão**: 1.0.0 +**Estado**: Em Desenvolvimento Activo diff --git a/main/pom.xml b/main/pom.xml index 20d6ddc..0adc5f4 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -22,6 +22,27 @@ 5.10.0 test + + + + com.google.code.gson + gson + 2.10.1 + + + + + + org.codehaus.mojo + exec-maven-plugin + 3.1.0 + + sd.Entry + + + + + \ 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 new file mode 100644 index 0000000..0217070 --- /dev/null +++ b/main/src/main/java/sd/model/Message.java @@ -0,0 +1,142 @@ +package sd.model; + +import java.io.Serializable; +import java.util.UUID; + +/** + * Represents a message exchanged between processes in the distributed simulation. + * Each message has a unique ID, a type, a sender, a destination, and a payload. + * This class implements {@link Serializable} to allow transmission over the network. + */ +public class Message implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * Unique identifier for this message. + */ + private final String messageId; + + /** + * The type of this message (e.g., VEHICLE_TRANSFER, STATS_UPDATE). + */ + private final MessageType type; + + /** + * Identifier of the process that sent this message. + */ + private final String senderId; + + /** + * Identifier of the destination process. Can be null for broadcast messages. + */ + private final String destinationId; + + /** + * The actual data being transmitted. Type depends on the message type. + */ + private final Object payload; + + /** + * Timestamp when this message was created (simulation time or real time). + */ + private final long timestamp; + + /** + * Creates a new message with all parameters. + * + * @param type The message type + * @param senderId The ID of the sending process + * @param destinationId The ID of the destination process (null for broadcast) + * @param payload The message payload + * @param timestamp The timestamp of message creation + */ + public Message(MessageType type, String senderId, String destinationId, + Object payload, long timestamp) { + this.messageId = UUID.randomUUID().toString(); + this.type = type; + this.senderId = senderId; + this.destinationId = destinationId; + this.payload = payload; + this.timestamp = timestamp; + } + + /** + * Creates a new message with current system time as timestamp. + * + * @param type The message type + * @param senderId The ID of the sending process + * @param destinationId The ID of the destination process + * @param payload The message payload + */ + public Message(MessageType type, String senderId, String destinationId, Object payload) { + this(type, senderId, destinationId, payload, System.currentTimeMillis()); + } + + /** + * Creates a broadcast message (no specific destination). + * + * @param type The message type + * @param senderId The ID of the sending process + * @param payload The message payload + */ + public Message(MessageType type, String senderId, Object payload) { + this(type, senderId, null, payload, System.currentTimeMillis()); + } + + //Getters + + public String getMessageId() { + return messageId; + } + + public MessageType getType() { + return type; + } + + public String getSenderId() { + return senderId; + } + + public String getDestinationId() { + return destinationId; + } + + public Object getPayload() { + return payload; + } + + public long getTimestamp() { + return timestamp; + } + + /** + * Checks if this is a broadcast message (no specific destination). + * + * @return true if destinationId is null, false otherwise + */ + public boolean isBroadcast() { + return destinationId == null; + } + + /** + * Gets the payload cast to a specific type. + * Use with caution and ensure type safety. + * + * @param The expected payload type + * @return The payload cast to type T + * @throws ClassCastException if the payload is not of type T + */ + @SuppressWarnings("unchecked") + public T getPayloadAs(Class clazz) { + return (T) payload; + } + + @Override + public String toString() { + return String.format("Message[id=%s, type=%s, from=%s, to=%s, timestamp=%d]", + messageId, type, senderId, + destinationId != null ? destinationId : "BROADCAST", + timestamp); + } +} diff --git a/main/src/main/java/sd/model/MessageType.java b/main/src/main/java/sd/model/MessageType.java new file mode 100644 index 0000000..76cb067 --- /dev/null +++ b/main/src/main/java/sd/model/MessageType.java @@ -0,0 +1,81 @@ +package sd.model; + +/** + * Enumeration representing all possible message types for distributed communication. + * These types are used for inter-process communication between different components + * of the distributed traffic simulation system. + */ +public enum MessageType { + + /** + * Message to transfer a vehicle between intersections or processes. + * Payload: Vehicle object with current state + */ + VEHICLE_TRANSFER, + + /** + * Message to update statistics across the distributed system. + * Payload: Statistics data (waiting times, queue sizes, etc.) + */ + STATS_UPDATE, + + /** + * Message to synchronize traffic light states between processes. + * Payload: TrafficLight state and timing information + */ + TRAFFIC_LIGHT_SYNC, + + /** + * Heartbeat message to check if a process is alive. + * Payload: Process ID and timestamp + */ + HEARTBEAT, + + /** + * Request to join the distributed simulation. + * Payload: Process information and capabilities + */ + JOIN_REQUEST, + + /** + * Response to a join request. + * Payload: Acceptance status and configuration + */ + JOIN_RESPONSE, + + /** + * Message to notify about a new vehicle generation. + * Payload: Vehicle generation parameters + */ + VEHICLE_SPAWN, + + /** + * Message to request the current state of an intersection. + * Payload: Intersection ID + */ + STATE_REQUEST, + + /** + * Response containing the current state of an intersection. + * Payload: Complete intersection state + */ + STATE_RESPONSE, + + /** + * Message to signal shutdown of a process. + * Payload: Process ID and reason + */ + SHUTDOWN, + + /** + * Acknowledgment message for reliable communication. + * Payload: Message ID being acknowledged + */ + ACK, + + /** + * Error message to report problems in the distributed system. + * Payload: Error description and context + */ + ERROR +} diff --git a/main/src/main/java/sd/serialization/JsonMessageSerializer.java b/main/src/main/java/sd/serialization/JsonMessageSerializer.java new file mode 100644 index 0000000..1b70c68 --- /dev/null +++ b/main/src/main/java/sd/serialization/JsonMessageSerializer.java @@ -0,0 +1,114 @@ +package sd.serialization; + +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 + */ +public class JsonMessageSerializer implements MessageSerializer { + + private final Gson gson; + private final boolean prettyPrint; + + /** + * Creates a new JSON serializer with default configuration (no pretty printing). + */ + public JsonMessageSerializer() { + this(false); + } + + /** + * Creates a new JSON serializer with optional pretty printing. + * + * @param prettyPrint If true, JSON output will be formatted with indentation + */ + public JsonMessageSerializer(boolean prettyPrint) { + this.prettyPrint = prettyPrint; + GsonBuilder builder = new GsonBuilder(); + + if (prettyPrint) { + builder.setPrettyPrinting(); + } + + // Register custom type adapters here if needed + // builder.registerTypeAdapter(Vehicle.class, new VehicleAdapter()); + + this.gson = builder.create(); + } + + @Override + public byte[] serialize(Object object) throws SerializationException { + if (object == null) { + throw new IllegalArgumentException("Cannot serialize null object"); + } + + try { + String json = gson.toJson(object); + return json.getBytes(StandardCharsets.UTF_8); + } catch (Exception e) { + throw new SerializationException( + "Failed to serialize object of type " + object.getClass().getName(), e); + } + } + + @Override + public T deserialize(byte[] data, Class clazz) throws SerializationException { + if (data == null) { + throw new IllegalArgumentException("Cannot deserialize null data"); + } + if (clazz == null) { + throw new IllegalArgumentException("Class type cannot be null"); + } + + try { + String json = new String(data, StandardCharsets.UTF_8); + return gson.fromJson(json, clazz); + } catch (JsonSyntaxException e) { + throw new SerializationException( + "Failed to parse JSON for type " + clazz.getName(), e); + } catch (Exception e) { + throw new SerializationException( + "Failed to deserialize object of type " + clazz.getName(), e); + } + } + + @Override + public String getName() { + return "JSON (Gson)"; + } + + /** + * Returns the underlying Gson instance for advanced usage. + * + * @return The Gson instance + */ + public Gson getGson() { + return gson; + } + + /** + * Checks if pretty printing is enabled. + * + * @return true if pretty printing is enabled + */ + public boolean isPrettyPrint() { + return prettyPrint; + } +} diff --git a/main/src/main/java/sd/serialization/MessageSerializer.java b/main/src/main/java/sd/serialization/MessageSerializer.java new file mode 100644 index 0000000..21517f4 --- /dev/null +++ b/main/src/main/java/sd/serialization/MessageSerializer.java @@ -0,0 +1,48 @@ +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 + */ +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 + */ + 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 + */ + 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 + */ + String getName(); + +} diff --git a/main/src/main/java/sd/serialization/SerializationExample.java b/main/src/main/java/sd/serialization/SerializationExample.java new file mode 100644 index 0000000..f6bd817 --- /dev/null +++ b/main/src/main/java/sd/serialization/SerializationExample.java @@ -0,0 +1,134 @@ +package sd.serialization; + +import sd.model.Message; +import sd.model.MessageType; +import sd.model.Vehicle; +import sd.model.VehicleType; + +import java.util.Arrays; +import java.util.List; + +/** + * Demonstration of JSON serialization usage in the traffic simulation system. + * + * This class shows practical examples of how to use JSON (Gson) serialization + * for network communication between simulation processes. + */ +public class SerializationExample { + + public static void main(String[] args) { + System.out.println("=== JSON Serialization Example ===\n"); + + // Create a sample vehicle + List route = Arrays.asList("Cr1", "Cr2", "Cr5", "S"); + Vehicle vehicle = new Vehicle("V001", VehicleType.LIGHT, 10.5, route); + vehicle.addWaitingTime(2.3); + vehicle.addCrossingTime(1.2); + + // Create a message containing the vehicle + Message message = new Message( + MessageType.VEHICLE_TRANSFER, + "Cr1", + "Cr2", + vehicle + ); + + // ===== JSON Serialization ===== + demonstrateJsonSerialization(message); + + // ===== Factory Usage ===== + demonstrateFactoryUsage(message); + + // ===== Performance Test ===== + performanceTest(message); + } + + private static void demonstrateJsonSerialization(Message message) { + System.out.println("--- JSON Serialization ---"); + + try { + // Create JSON serializer with pretty printing for readability + MessageSerializer serializer = new JsonMessageSerializer(true); + + // Serialize to bytes + byte[] data = serializer.serialize(message); + + // Display the JSON + String json = new String(data); + System.out.println("Serialized JSON (" + data.length + " bytes):"); + System.out.println(json); + + // Deserialize back + Message deserialized = serializer.deserialize(data, Message.class); + System.out.println("\nDeserialized: " + deserialized); + System.out.println("✓ JSON serialization successful\n"); + + } catch (SerializationException e) { + System.err.println("❌ JSON serialization failed: " + e.getMessage()); + } + } + + private static void demonstrateFactoryUsage(Message message) { + System.out.println("--- Using SerializerFactory ---"); + + try { + // Get default serializer (JSON) + MessageSerializer serializer = SerializerFactory.createDefault(); + System.out.println("Default serializer: " + serializer.getName()); + + // Use it + byte[] data = serializer.serialize(message); + Message deserialized = serializer.deserialize(data, Message.class); + + System.out.println("Message type: " + deserialized.getType()); + System.out.println("From: " + deserialized.getSenderId() + + " → To: " + deserialized.getDestinationId()); + System.out.println("✓ Factory usage successful\n"); + + } catch (SerializationException e) { + System.err.println("❌ Factory usage failed: " + e.getMessage()); + } + } + + private static void performanceTest(Message message) { + System.out.println("--- Performance Test ---"); + + int iterations = 1000; + + try { + MessageSerializer compactSerializer = new JsonMessageSerializer(false); + MessageSerializer prettySerializer = new JsonMessageSerializer(true); + + // Warm up + for (int i = 0; i < 100; i++) { + compactSerializer.serialize(message); + } + + // Test compact JSON + long compactStart = System.nanoTime(); + byte[] compactData = null; + for (int i = 0; i < iterations; i++) { + compactData = compactSerializer.serialize(message); + } + long compactTime = System.nanoTime() - compactStart; + + // Test pretty JSON + byte[] prettyData = prettySerializer.serialize(message); + + // Results + System.out.println("Iterations: " + iterations); + System.out.println("\nJSON Compact:"); + System.out.println(" Size: " + compactData.length + " bytes"); + System.out.println(" Time: " + (compactTime / 1_000_000.0) + " ms total"); + System.out.println(" Avg: " + (compactTime / iterations / 1_000.0) + " μs/operation"); + + System.out.println("\nJSON Pretty-Print:"); + System.out.println(" Size: " + prettyData.length + " bytes"); + System.out.println(" Size increase: " + + String.format("%.1f%%", ((double)prettyData.length / compactData.length - 1) * 100)); + + } catch (SerializationException e) { + System.err.println("❌ Performance test failed: " + e.getMessage()); + } + } +} diff --git a/main/src/main/java/sd/serialization/SerializationException.java b/main/src/main/java/sd/serialization/SerializationException.java new file mode 100644 index 0000000..5cf9675 --- /dev/null +++ b/main/src/main/java/sd/serialization/SerializationException.java @@ -0,0 +1,41 @@ +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. + */ +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 + */ + 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 + */ + 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 + */ + public SerializationException(Throwable cause) { + super(cause); + } +} diff --git a/main/src/main/java/sd/serialization/SerializerFactory.java b/main/src/main/java/sd/serialization/SerializerFactory.java new file mode 100644 index 0000000..a2261d3 --- /dev/null +++ b/main/src/main/java/sd/serialization/SerializerFactory.java @@ -0,0 +1,66 @@ +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: + *
+ * MessageSerializer serializer = SerializerFactory.createDefault();
+ * byte[] data = serializer.serialize(myObject);
+ * 
+ */ +public class SerializerFactory { + + /** + * System property key for enabling pretty-print in JSON serialization. + * Set to "true" for debugging, "false" for production. + */ + public static final String JSON_PRETTY_PRINT_PROPERTY = "sd.serialization.json.prettyPrint"; + + // Default configuration + private static final boolean DEFAULT_JSON_PRETTY_PRINT = false; + + /** + * Private constructor to prevent instantiation. + */ + 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 + */ + public static MessageSerializer createDefault() { + boolean prettyPrint = Boolean.getBoolean(JSON_PRETTY_PRINT_PROPERTY); + return new JsonMessageSerializer(prettyPrint); + } + + /** + * Creates a JSON serializer with default configuration (no pretty printing). + * + * @return A JsonMessageSerializer instance + */ + 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 + */ + public static MessageSerializer createSerializer(boolean prettyPrint) { + return new JsonMessageSerializer(prettyPrint); + } +} diff --git a/main/src/test/java/sd/serialization/SerializationTest.java b/main/src/test/java/sd/serialization/SerializationTest.java new file mode 100644 index 0000000..b43b5a5 --- /dev/null +++ b/main/src/test/java/sd/serialization/SerializationTest.java @@ -0,0 +1,140 @@ +package sd.serialization; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; +import sd.model.Message; +import sd.model.Vehicle; +import sd.model.VehicleType; + +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Test suite for JSON serialization. + * + * Tests JSON serialization to ensure: + * - Correct serialization and deserialization + * - Data integrity during round-trip conversion + * - Proper error handling + */ +class SerializationTest { + + private MessageSerializer jsonSerializer = new JsonMessageSerializer(); + + private Vehicle testVehicle = new Vehicle("V001", VehicleType.LIGHT, 10.5, + Arrays.asList("Cr1", "Cr2", "Cr5", "S")); + private Message testMessage = new Message( + sd.model.MessageType.VEHICLE_TRANSFER, + "Cr1", + "Cr2", + testVehicle + ); + + + // ===== JSON Serialization Tests ===== + + @Test + @DisplayName("JSON: Should serialize and deserialize Vehicle correctly") + void testJsonVehicleRoundTrip() throws SerializationException { + // Serialize + byte[] data = jsonSerializer.serialize(testVehicle); + assertNotNull(data); + assertTrue(data.length > 0); + + // Print JSON for inspection + System.out.println("JSON Vehicle:"); + System.out.println(new String(data)); + + // Deserialize + Vehicle deserialized = jsonSerializer.deserialize(data, Vehicle.class); + + // Verify + assertNotNull(deserialized); + assertEquals(testVehicle.getId(), deserialized.getId()); + assertEquals(testVehicle.getType(), deserialized.getType()); + assertEquals(testVehicle.getEntryTime(), deserialized.getEntryTime()); + assertEquals(testVehicle.getRoute(), deserialized.getRoute()); + assertEquals(testVehicle.getTotalWaitingTime(), deserialized.getTotalWaitingTime()); + assertEquals(testVehicle.getTotalCrossingTime(), deserialized.getTotalCrossingTime()); + } + + @Test + @DisplayName("JSON: Should serialize and deserialize Message correctly") + void testJsonMessageRoundTrip() throws SerializationException { + // Serialize + byte[] data = jsonSerializer.serialize(testMessage); + assertNotNull(data); + + // Print JSON for inspection + System.out.println("\nJSON Message:"); + System.out.println(new String(data)); + + // Deserialize + Message deserialized = jsonSerializer.deserialize(data, Message.class); + + // Verify + assertNotNull(deserialized); + assertEquals(testMessage.getType(), deserialized.getType()); + assertEquals(testMessage.getSenderId(), deserialized.getSenderId()); + assertEquals(testMessage.getDestinationId(), deserialized.getDestinationId()); + } + + @Test + @DisplayName("JSON: Should throw exception on null object") + void testJsonSerializeNull() { + assertThrows(IllegalArgumentException.class, () -> { + jsonSerializer.serialize(null); + }); + } + + @Test + @DisplayName("JSON: Should throw exception on null data") + void testJsonDeserializeNull() { + assertThrows(IllegalArgumentException.class, () -> { + jsonSerializer.deserialize(null, Vehicle.class); + }); + } + + @Test + @DisplayName("JSON: Should throw exception on invalid JSON") + void testJsonDeserializeInvalid() { + byte[] invalidData = "{ invalid json }".getBytes(); + assertThrows(SerializationException.class, () -> { + jsonSerializer.deserialize(invalidData, Vehicle.class); + }); + } + + @Test + @DisplayName("JSON: Should preserve data integrity for complex objects") + void testDataIntegrity() throws SerializationException { + // Create a more complex vehicle + Vehicle vehicle = new Vehicle("V999", VehicleType.HEAVY, 100.5, + Arrays.asList("Cr1", "Cr2", "Cr3", "Cr4", "Cr5", "S")); + vehicle.addWaitingTime(10.5); + vehicle.addWaitingTime(5.3); + vehicle.addCrossingTime(2.1); + vehicle.advanceRoute(); + vehicle.advanceRoute(); + + // Serialize and deserialize + byte[] jsonData = jsonSerializer.serialize(vehicle); + Vehicle deserialized = jsonSerializer.deserialize(jsonData, Vehicle.class); + + // Verify all fields match + assertEquals(vehicle.getId(), deserialized.getId()); + assertEquals(vehicle.getType(), deserialized.getType()); + assertEquals(vehicle.getTotalWaitingTime(), deserialized.getTotalWaitingTime()); + assertEquals(vehicle.getCurrentRouteIndex(), deserialized.getCurrentRouteIndex()); + } + + // ===== Factory Tests ===== + + @Test + @DisplayName("Factory: Should create JSON serializer by default") + void testFactoryDefault() { + MessageSerializer serializer = SerializerFactory.createDefault(); + assertNotNull(serializer); + assertEquals("JSON (Gson)", serializer.getName()); + } +}