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