1 | package com.reallifedeveloper.common.infrastructure; | |
2 | ||
3 | import java.io.IOException; | |
4 | import java.lang.reflect.Type; | |
5 | import java.time.LocalDate; | |
6 | import java.time.LocalDateTime; | |
7 | import java.time.ZonedDateTime; | |
8 | import java.time.format.DateTimeFormatter; | |
9 | ||
10 | import com.google.gson.Gson; | |
11 | import com.google.gson.GsonBuilder; | |
12 | import com.google.gson.JsonDeserializationContext; | |
13 | import com.google.gson.JsonDeserializer; | |
14 | import com.google.gson.JsonElement; | |
15 | import com.google.gson.JsonObject; | |
16 | import com.google.gson.JsonSyntaxException; | |
17 | import com.google.gson.TypeAdapter; | |
18 | import com.google.gson.stream.JsonReader; | |
19 | import com.google.gson.stream.JsonWriter; | |
20 | ||
21 | import com.reallifedeveloper.common.application.notification.Notification; | |
22 | import com.reallifedeveloper.common.domain.ErrorHandling; | |
23 | import com.reallifedeveloper.common.domain.ObjectSerializer; | |
24 | import com.reallifedeveloper.common.domain.event.DomainEvent; | |
25 | ||
26 | /** | |
27 | * An implementation of the {@link ObjectSerializer} that uses JSON as the serialized form. | |
28 | * | |
29 | * @author RealLifeDeveloper | |
30 | */ | |
31 | public class GsonObjectSerializer implements ObjectSerializer<String> { | |
32 | ||
33 | /** | |
34 | * The format used to parse and format date objects. The string is a pattern that can be used by a {@code java.time.DateTimeFormatter}. | |
35 | */ | |
36 | public static final String DATE_FORMAT = "yyyy-MM-dd"; | |
37 | ||
38 | /** | |
39 | * The format used to parse and format {@code java.time.LocalDateTime} objects. The string is a pattern that can be used by a | |
40 | * {@code DateTimeFormatter}. | |
41 | */ | |
42 | public static final String LOCAL_DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS"; | |
43 | ||
44 | /** | |
45 | * The format used to parse and format {@code java.time.ZonedDateTime} objects. The string is a pattern that can be used by a | |
46 | * {@code DateTimeFormatter}. | |
47 | */ | |
48 | public static final String DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSX"; | |
49 | ||
50 | private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern(DATE_FORMAT); | |
51 | private static final DateTimeFormatter LOCAL_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(LOCAL_DATE_TIME_FORMAT); | |
52 | private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(DATE_TIME_FORMAT); | |
53 | ||
54 | private final Gson gson; | |
55 | ||
56 | /** | |
57 | * Creates a new {@code GsonObjectSerializer} with default values. | |
58 | * <p> | |
59 | * The default values include using the pattern {@value #DATE_TIME_FORMAT} when working with {@code java.time.ZonedDateTime} objects. | |
60 | */ | |
61 | public GsonObjectSerializer() { | |
62 | gson = new GsonBuilder().setDateFormat(DATE_TIME_FORMAT).registerTypeAdapter(Notification.class, new NotificationDeserializer()) | |
63 | .registerTypeAdapter(LocalDate.class, new LocalDateAdapter().nullSafe()) | |
64 | .registerTypeAdapter(LocalDateTime.class, new LocalDateTmeAdapter().nullSafe()) | |
65 | .registerTypeAdapter(ZonedDateTime.class, new ZonedDateTmeAdapter().nullSafe()).create(); | |
66 | } | |
67 | ||
68 | @Override | |
69 | public String serialize(Object object) { | |
70 |
1
1. serialize : replaced return value with "" for com/reallifedeveloper/common/infrastructure/GsonObjectSerializer::serialize → KILLED |
return gson.toJson(object); |
71 | } | |
72 | ||
73 | @Override | |
74 | public <U> U deserialize(String serializedObject, Class<U> objectType) { | |
75 |
2
1. deserialize : negated conditional → KILLED 2. deserialize : negated conditional → KILLED |
if (serializedObject == null || objectType == null) { |
76 | throw new IllegalArgumentException( | |
77 | "Arguments must not be null: serializedObject=" + serializedObject + ", objectType=" + objectType); | |
78 | } | |
79 | try { | |
80 |
1
1. deserialize : replaced return value with null for com/reallifedeveloper/common/infrastructure/GsonObjectSerializer::deserialize → KILLED |
return gson.fromJson(serializedObject, objectType); |
81 | } catch (JsonSyntaxException e) { | |
82 | throw new IllegalArgumentException("serializedObject cannot be parsed as JSON: " + serializedObject, e); | |
83 | } | |
84 | } | |
85 | ||
86 | private static final class NotificationDeserializer implements JsonDeserializer<Notification> { | |
87 | @Override | |
88 | public Notification deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) { | |
89 |
1
1. deserialize : negated conditional → KILLED |
if (!Notification.class.getTypeName().equals(typeOfT.getTypeName())) { |
90 | throw new IllegalStateException("Unexpected type in deserialize method, expected 'Notification'. typeOfT=" + typeOfT); | |
91 | } | |
92 | try { | |
93 | JsonObject jsonObject = json.getAsJsonObject(); | |
94 | String eventType = jsonObject.get("eventType").getAsString(); | |
95 | long storedEventId = jsonObject.get("storedEventId").getAsLong(); | |
96 | ZonedDateTime occurredOn = context.deserialize(jsonObject.get("occurredOn"), ZonedDateTime.class); | |
97 | @SuppressWarnings("unchecked") | |
98 | Class<DomainEvent> eventClass = (Class<DomainEvent>) Class.forName(eventType); | |
99 | DomainEvent event = context.deserialize(jsonObject.get("event"), eventClass); | |
100 |
1
1. deserialize : removed call to com/reallifedeveloper/common/domain/ErrorHandling::checkNull → KILLED |
ErrorHandling.checkNull("JSON notification is missing event: json=" + json, event); |
101 |
1
1. deserialize : replaced return value with null for com/reallifedeveloper/common/infrastructure/GsonObjectSerializer$NotificationDeserializer::deserialize → KILLED |
return new Notification(eventType, storedEventId, occurredOn, event); |
102 | } catch (ClassNotFoundException e) { | |
103 | throw new IllegalStateException("Internal error: " + e.toString(), e); | |
104 | } | |
105 | } | |
106 | } | |
107 | ||
108 | private static final class LocalDateAdapter extends TypeAdapter<LocalDate> { | |
109 | @Override | |
110 | public void write(final JsonWriter jsonWriter, final LocalDate localDate) throws IOException { | |
111 | jsonWriter.value(localDate.format(DATE_FORMATTER)); | |
112 | } | |
113 | ||
114 | @Override | |
115 | public LocalDate read(final JsonReader jsonReader) throws IOException { | |
116 |
1
1. read : replaced return value with null for com/reallifedeveloper/common/infrastructure/GsonObjectSerializer$LocalDateAdapter::read → KILLED |
return LocalDate.parse(jsonReader.nextString(), DATE_FORMATTER); |
117 | } | |
118 | } | |
119 | ||
120 | private static final class LocalDateTmeAdapter extends TypeAdapter<LocalDateTime> { | |
121 | @Override | |
122 | public void write(final JsonWriter jsonWriter, final LocalDateTime localDateTime) throws IOException { | |
123 | jsonWriter.value(localDateTime.format(LOCAL_DATE_TIME_FORMATTER)); | |
124 | } | |
125 | ||
126 | @Override | |
127 | public LocalDateTime read(final JsonReader jsonReader) throws IOException { | |
128 |
1
1. read : replaced return value with null for com/reallifedeveloper/common/infrastructure/GsonObjectSerializer$LocalDateTmeAdapter::read → KILLED |
return LocalDateTime.parse(jsonReader.nextString(), LOCAL_DATE_TIME_FORMATTER); |
129 | } | |
130 | } | |
131 | ||
132 | private static final class ZonedDateTmeAdapter extends TypeAdapter<ZonedDateTime> { | |
133 | @Override | |
134 | public void write(final JsonWriter jsonWriter, final ZonedDateTime zonedDateTime) throws IOException { | |
135 | jsonWriter.value(zonedDateTime.format(DATE_TIME_FORMATTER)); | |
136 | } | |
137 | ||
138 | @Override | |
139 | public ZonedDateTime read(final JsonReader jsonReader) throws IOException { | |
140 |
1
1. read : replaced return value with null for com/reallifedeveloper/common/infrastructure/GsonObjectSerializer$ZonedDateTmeAdapter::read → KILLED |
return ZonedDateTime.parse(jsonReader.nextString(), DATE_TIME_FORMATTER); |
141 | } | |
142 | } | |
143 | } | |
Mutations | ||
70 |
1.1 |
|
75 |
1.1 2.2 |
|
80 |
1.1 |
|
89 |
1.1 |
|
100 |
1.1 |
|
101 |
1.1 |
|
116 |
1.1 |
|
128 |
1.1 |
|
140 |
1.1 |