Merge pull request #21 from davidalves04/9-design-message-protocol-specification

Mmessage protocol specification
This commit is contained in:
2025-10-24 20:12:18 +01:00
committed by GitHub
10 changed files with 1407 additions and 0 deletions

View File

@@ -22,6 +22,27 @@
<version>5.10.0</version>
<scope>test</scope>
</dependency>
<!-- Gson for JSON serialization -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Maven Exec Plugin for running examples -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<mainClass>sd.Entry</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -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 <T> 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> T getPayloadAs(Class<T> 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);
}
}

View File

@@ -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
}

View File

@@ -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> T deserialize(byte[] data, Class<T> 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;
}
}

View File

@@ -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 <T> 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> T deserialize(byte[] data, Class<T> 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();
}

View File

@@ -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<String> 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());
}
}
}

View File

@@ -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);
}
}

View File

@@ -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:
* <pre>
* MessageSerializer serializer = SerializerFactory.createDefault();
* byte[] data = serializer.serialize(myObject);
* </pre>
*/
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);
}
}

View File

@@ -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());
}
}