GsonNotificationReader.java

1
package com.reallifedeveloper.common.infrastructure;
2
3
import java.time.ZonedDateTime;
4
import java.time.format.DateTimeFormatter;
5
import java.util.Optional;
6
7
import org.checkerframework.checker.nullness.qual.NonNull;
8
import org.checkerframework.checker.nullness.qual.Nullable;
9
10
import com.google.gson.JsonElement;
11
import com.google.gson.JsonNull;
12
import com.google.gson.JsonObject;
13
import com.google.gson.JsonParseException;
14
import com.google.gson.JsonParser;
15
16
import com.reallifedeveloper.common.application.notification.NotificationReader;
17
import com.reallifedeveloper.common.domain.ErrorHandling;
18
19
/**
20
 * An implementation of the {@link NotificationReader} interface that works with JSON as the serialized form, using
21
 * <a href="https://code.google.com/p/google-gson/">Gson</a> to parse the JSON string.
22
 *
23
 * @author RealLifeDeveloper
24
 */
25
@SuppressWarnings("PMD.UnnecessaryCast") // We use casts to @NonNull that PMD considers unnecessary.
26
public final class GsonNotificationReader implements NotificationReader {
27
28
    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(GsonObjectSerializer.DATE_TIME_FORMAT);
29
30
    private final JsonObject notification;
31
    private final JsonObject event;
32
33
    /**
34
     * Creates a new {@code GsonNotificationReader} that parses the given JSON-serialized notification.
35
     *
36
     * @param jsonNotification the JSON representation of the notification to read
37
     *
38
     * @throws IllegalArgumentException if {@code jsonNotification} is {@code null} or not a valid JSON object
39
     */
40
    public GsonNotificationReader(String jsonNotification) {
41 1 1. <init> : removed call to com/reallifedeveloper/common/domain/ErrorHandling::checkNull → KILLED
        ErrorHandling.checkNull("jsonNotification must not be null", jsonNotification);
42
        try {
43
            JsonElement element = JsonParser.parseString(jsonNotification);
44 1 1. <init> : negated conditional → KILLED
            if (!element.isJsonObject()) {
45
                throw new IllegalArgumentException("Not a JSON object: " + jsonNotification);
46
            }
47
            this.notification = element.getAsJsonObject();
48
        } catch (JsonParseException e) {
49
            throw new IllegalArgumentException("Not legal JSON: " + jsonNotification, e);
50
        }
51 1 1. <init> : negated conditional → KILLED
        if (JsonUtil.isNull(notification.get("event"))) {
52
            throw new IllegalArgumentException("event not found in JSON string: " + jsonNotification);
53
        }
54
        this.event = notification.get("event").getAsJsonObject();
55
    }
56
57
    @Override
58
    public String eventType() {
59 1 1. eventType : replaced return value with "" for com/reallifedeveloper/common/infrastructure/GsonNotificationReader::eventType → KILLED
        return (@NonNull String) JsonUtil.stringValue(notification, "eventType", true);
60
    }
61
62
    @Override
63
    public Long storedEventId() {
64 1 1. storedEventId : replaced Long return value with 0L for com/reallifedeveloper/common/infrastructure/GsonNotificationReader::storedEventId → KILLED
        return (@NonNull Long) JsonUtil.longValue(notification, "storedEventId", true);
65
    }
66
67
    @Override
68
    public ZonedDateTime occurredOn() {
69 1 1. occurredOn : replaced return value with null for com/reallifedeveloper/common/infrastructure/GsonNotificationReader::occurredOn → KILLED
        return (@NonNull ZonedDateTime) JsonUtil.zonedDateTimeValue(notification, "occurredOn", true);
70
    }
71
72
    @Override
73
    public Integer eventVersion() {
74 1 1. eventVersion : replaced Integer return value with 0 for com/reallifedeveloper/common/infrastructure/GsonNotificationReader::eventVersion → KILLED
        return (@NonNull Integer) JsonUtil.intValue(event, "eventVersion", true);
75
    }
76
77
    @Override
78
    public Optional<Integer> eventIntValue(String fieldName) {
79 1 1. eventIntValue : replaced return value with Optional.empty for com/reallifedeveloper/common/infrastructure/GsonNotificationReader::eventIntValue → KILLED
        return Optional.ofNullable(JsonUtil.intValue(event, fieldName, false));
80
    }
81
82
    @Override
83
    public Optional<Long> eventLongValue(String fieldName) {
84 1 1. eventLongValue : replaced return value with Optional.empty for com/reallifedeveloper/common/infrastructure/GsonNotificationReader::eventLongValue → KILLED
        return Optional.ofNullable(JsonUtil.longValue(event, fieldName, false));
85
    }
86
87
    @Override
88
    public Optional<Double> eventDoubleValue(String fieldName) {
89 1 1. eventDoubleValue : replaced return value with Optional.empty for com/reallifedeveloper/common/infrastructure/GsonNotificationReader::eventDoubleValue → KILLED
        return Optional.ofNullable(JsonUtil.doubleValue(event, fieldName, false));
90
    }
91
92
    @Override
93
    public Optional<String> eventStringValue(String fieldName) {
94 1 1. eventStringValue : replaced return value with Optional.empty for com/reallifedeveloper/common/infrastructure/GsonNotificationReader::eventStringValue → KILLED
        return Optional.ofNullable(JsonUtil.stringValue(event, fieldName, false));
95
    }
96
97
    @Override
98
    public Optional<ZonedDateTime> zonedDateTimeValue(String fieldName) {
99 1 1. zonedDateTimeValue : replaced return value with Optional.empty for com/reallifedeveloper/common/infrastructure/GsonNotificationReader::zonedDateTimeValue → KILLED
        return Optional.ofNullable(JsonUtil.zonedDateTimeValue(event, fieldName, false));
100
    }
101
102
    private static final class JsonUtil {
103
104
        private static @Nullable Integer intValue(JsonObject object, String fieldName, boolean required) {
105
            JsonElement jsonElement = fieldValue(object, fieldName, required);
106 2 1. intValue : replaced Integer return value with 0 for com/reallifedeveloper/common/infrastructure/GsonNotificationReader$JsonUtil::intValue → KILLED
2. intValue : negated conditional → KILLED
            return isNull(jsonElement) ? null : jsonElement.getAsInt();
107
        }
108
109
        private static @Nullable Long longValue(JsonObject object, String fieldName, boolean required) {
110
            JsonElement jsonElement = fieldValue(object, fieldName, required);
111 2 1. longValue : negated conditional → KILLED
2. longValue : replaced Long return value with 0L for com/reallifedeveloper/common/infrastructure/GsonNotificationReader$JsonUtil::longValue → KILLED
            return isNull(jsonElement) ? null : jsonElement.getAsLong();
112
        }
113
114
        private static @Nullable Double doubleValue(JsonObject object, String fieldName, boolean required) {
115
            JsonElement jsonElement = fieldValue(object, fieldName, required);
116 2 1. doubleValue : negated conditional → KILLED
2. doubleValue : replaced Double return value with 0 for com/reallifedeveloper/common/infrastructure/GsonNotificationReader$JsonUtil::doubleValue → KILLED
            return isNull(jsonElement) ? null : jsonElement.getAsDouble();
117
        }
118
119
        private static @Nullable String stringValue(JsonObject object, String fieldName, boolean required) {
120
            JsonElement jsonElement = fieldValue(object, fieldName, required);
121 2 1. stringValue : replaced return value with "" for com/reallifedeveloper/common/infrastructure/GsonNotificationReader$JsonUtil::stringValue → KILLED
2. stringValue : negated conditional → KILLED
            return isNull(jsonElement) ? null : jsonElement.getAsString();
122
        }
123
124
        private static @Nullable ZonedDateTime zonedDateTimeValue(JsonObject object, String fieldName, boolean required) {
125
            JsonElement jsonElement = fieldValue(object, fieldName, required);
126 2 1. zonedDateTimeValue : replaced return value with null for com/reallifedeveloper/common/infrastructure/GsonNotificationReader$JsonUtil::zonedDateTimeValue → KILLED
2. zonedDateTimeValue : negated conditional → KILLED
            return isNull(jsonElement) ? null : ZonedDateTime.parse(jsonElement.getAsString(), DATE_TIME_FORMATTER);
127
        }
128
129
        /**
130
         * Gives the value of a, potentially nested, field in a {@code JsonObject}. The field name can be simple, e.g., "foo", or nested,
131
         * e.g., "foo.bar". If the name is nested, the sub-components should be the names of nested objects.
132
         * <p>
133
         * For example, if {@code fieldName} is "foo.bar.baz", the object "foo" is first looked up in {@code object}, then the object "bar"
134
         * is looked up in the result, and finally the value of the field "baz" in the resulting object is returned.
135
         * <p>
136
         * If {@code required} is {@code true}, this method never returns {@code null}.
137
         *
138
         * @param rootObject the {@code JsonObject} to use to look up the value of the field
139
         * @param fieldName  the name of the field to look up, potentially nested, e.g., "foo.bar"
140
         * @param required   if {@code true}, throws an exception if the field does not exist or is {@code null}
141
         *
142
         * @return the value of the, potentially nested, field as a {@code JsonElement}
143
         *
144
         * @throws IllegalArgumentException if {@code required} is {@code true} and the field does not exist or is {@code null}
145
         */
146
        private static JsonElement fieldValue(JsonObject rootObject, String fieldName, boolean required) {
147
            JsonObject object = rootObject;
148
            String[] fieldNames = fieldName.split("\\.");
149 3 1. fieldValue : negated conditional → KILLED
2. fieldValue : Replaced integer subtraction with addition → KILLED
3. fieldValue : changed conditional boundary → KILLED
            for (int i = 0; i < fieldNames.length - 1; i++) {
150
                JsonElement element = object.get(fieldNames[i]);
151 1 1. fieldValue : negated conditional → KILLED
                if (element == null) {
152
                    throw new IllegalArgumentException("Field " + fieldName + " not found: object=" + rootObject);
153
                }
154 1 1. fieldValue : negated conditional → KILLED
                if (!element.isJsonObject()) {
155
                    throw new IllegalArgumentException("Field " + fieldName + " not an object: object=" + rootObject);
156
                }
157
                object = element.getAsJsonObject();
158
            }
159 1 1. fieldValue : Replaced integer subtraction with addition → KILLED
            JsonElement jsonElement = object.get(fieldNames[fieldNames.length - 1]);
160 2 1. fieldValue : negated conditional → KILLED
2. fieldValue : negated conditional → KILLED
            if (required && isNull(jsonElement)) {
161
                throw new IllegalArgumentException("Field " + fieldName + " is missing or null: object=" + rootObject);
162
            }
163 1 1. fieldValue : replaced return value with null for com/reallifedeveloper/common/infrastructure/GsonNotificationReader$JsonUtil::fieldValue → KILLED
            return jsonElement;
164
        }
165
166
        private static boolean isNull(JsonElement jsonElement) {
167 3 1. isNull : negated conditional → KILLED
2. isNull : negated conditional → KILLED
3. isNull : replaced boolean return with true for com/reallifedeveloper/common/infrastructure/GsonNotificationReader$JsonUtil::isNull → KILLED
            return jsonElement == null || jsonElement == JsonNull.INSTANCE;
168
        }
169
    }
170
}

Mutations

41

1.1
Location : <init>
Killed by : com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest]/[method:constructorNullJsonObject()]
removed call to com/reallifedeveloper/common/domain/ErrorHandling::checkNull → KILLED

44

1.1
Location : <init>
Killed by : com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest]/[method:validJsonButNotValidEventMessage()]
negated conditional → KILLED

51

1.1
Location : <init>
Killed by : com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest]/[method:validJsonButNotValidEventMessage()]
negated conditional → KILLED

59

1.1
Location : eventType
Killed by : com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest]/[method:realNotification()]
replaced return value with "" for com/reallifedeveloper/common/infrastructure/GsonNotificationReader::eventType → KILLED

64

1.1
Location : storedEventId
Killed by : com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest]/[method:realNotification()]
replaced Long return value with 0L for com/reallifedeveloper/common/infrastructure/GsonNotificationReader::storedEventId → KILLED

69

1.1
Location : occurredOn
Killed by : com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest]/[method:realNotification()]
replaced return value with null for com/reallifedeveloper/common/infrastructure/GsonNotificationReader::occurredOn → KILLED

74

1.1
Location : eventVersion
Killed by : com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest]/[method:realNotification()]
replaced Integer return value with 0 for com/reallifedeveloper/common/infrastructure/GsonNotificationReader::eventVersion → KILLED

79

1.1
Location : eventIntValue
Killed by : com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest]/[method:eventIntValueMin()]
replaced return value with Optional.empty for com/reallifedeveloper/common/infrastructure/GsonNotificationReader::eventIntValue → KILLED

84

1.1
Location : eventLongValue
Killed by : com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest]/[method:eventLongValueMax()]
replaced return value with Optional.empty for com/reallifedeveloper/common/infrastructure/GsonNotificationReader::eventLongValue → KILLED

89

1.1
Location : eventDoubleValue
Killed by : com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest]/[method:eventDoubleValueMin()]
replaced return value with Optional.empty for com/reallifedeveloper/common/infrastructure/GsonNotificationReader::eventDoubleValue → KILLED

94

1.1
Location : eventStringValue
Killed by : com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest]/[method:eventStringValue()]
replaced return value with Optional.empty for com/reallifedeveloper/common/infrastructure/GsonNotificationReader::eventStringValue → KILLED

99

1.1
Location : zonedDateTimeValue
Killed by : com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest]/[method:eventDateValue()]
replaced return value with Optional.empty for com/reallifedeveloper/common/infrastructure/GsonNotificationReader::zonedDateTimeValue → KILLED

106

1.1
Location : intValue
Killed by : com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest]/[method:eventIntValueEmpty()]
replaced Integer return value with 0 for com/reallifedeveloper/common/infrastructure/GsonNotificationReader$JsonUtil::intValue → KILLED

2.2
Location : intValue
Killed by : com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest]/[method:eventIntValueEmpty()]
negated conditional → KILLED

111

1.1
Location : longValue
Killed by : com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest]/[method:eventLongValueNonExisting()]
negated conditional → KILLED

2.2
Location : longValue
Killed by : com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest]/[method:eventLongValueNonExisting()]
replaced Long return value with 0L for com/reallifedeveloper/common/infrastructure/GsonNotificationReader$JsonUtil::longValue → KILLED

116

1.1
Location : doubleValue
Killed by : com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest]/[method:eventDoubleValueNotNumber()]
negated conditional → KILLED

2.2
Location : doubleValue
Killed by : com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest]/[method:eventDoubleValueMax()]
replaced Double return value with 0 for com/reallifedeveloper/common/infrastructure/GsonNotificationReader$JsonUtil::doubleValue → KILLED

121

1.1
Location : stringValue
Killed by : com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest]/[method:eventStringValueNull()]
replaced return value with "" for com/reallifedeveloper/common/infrastructure/GsonNotificationReader$JsonUtil::stringValue → KILLED

2.2
Location : stringValue
Killed by : com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest]/[method:eventStringValueNull()]
negated conditional → KILLED

126

1.1
Location : zonedDateTimeValue
Killed by : com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest]/[method:eventDateValue()]
replaced return value with null for com/reallifedeveloper/common/infrastructure/GsonNotificationReader$JsonUtil::zonedDateTimeValue → KILLED

2.2
Location : zonedDateTimeValue
Killed by : com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest]/[method:eventDateValueNull()]
negated conditional → KILLED

149

1.1
Location : fieldValue
Killed by : com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest]/[method:eventLongValueNotInteger()]
negated conditional → KILLED

2.2
Location : fieldValue
Killed by : com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest]/[method:eventLongValueNotInteger()]
Replaced integer subtraction with addition → KILLED

3.3
Location : fieldValue
Killed by : com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest]/[method:eventLongValueNotInteger()]
changed conditional boundary → KILLED

151

1.1
Location : fieldValue
Killed by : com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest]/[method:nestedFieldNameNonExistingField()]
negated conditional → KILLED

154

1.1
Location : fieldValue
Killed by : com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest]/[method:nestedFieldNameNotAnObject()]
negated conditional → KILLED

159

1.1
Location : fieldValue
Killed by : com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest]/[method:eventLongValueNotInteger()]
Replaced integer subtraction with addition → KILLED

160

1.1
Location : fieldValue
Killed by : com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest]/[method:eventVersionForNotificationWithNullEventVersion()]
negated conditional → KILLED

2.2
Location : fieldValue
Killed by : com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest]/[method:eventVersionForNotificationWithNullEventVersion()]
negated conditional → KILLED

163

1.1
Location : fieldValue
Killed by : com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest]/[method:eventLongValueNotInteger()]
replaced return value with null for com/reallifedeveloper/common/infrastructure/GsonNotificationReader$JsonUtil::fieldValue → KILLED

167

1.1
Location : isNull
Killed by : com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest]/[method:eventLongValueNotInteger()]
negated conditional → KILLED

2.2
Location : isNull
Killed by : com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest]/[method:validJsonButNotValidEventMessage()]
negated conditional → KILLED

3.3
Location : isNull
Killed by : com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest]/[method:eventLongValueNotInteger()]
replaced boolean return with true for com/reallifedeveloper/common/infrastructure/GsonNotificationReader$JsonUtil::isNull → KILLED

Active mutators

Tests examined


Report generated by PIT 1.20.0