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

Mutations

43

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

46

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

53

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

62

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

68

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

74

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

80

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

85

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

90

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

95

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

100

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

105

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

112

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

117

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

122

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:eventDoubleValueNull()]
replaced Double return value with 0 for com/reallifedeveloper/common/infrastructure/GsonNotificationReader$JsonUtil::doubleValue → KILLED

127

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

132

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

155

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()]
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:eventVersionForNotificationWithNullEventVersion()]
changed conditional boundary → KILLED

157

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

160

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

165

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

166

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

169

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

173

1.1
Location : isNull
Killed by : com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest.[engine:junit-jupiter]/[class:com.reallifedeveloper.common.infrastructure.GsonNotificationReaderTest]/[method:eventVersionForNotificationWitNullEvent()]
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:eventVersionForNotificationWithNullEventVersion()]
replaced boolean return with true for com/reallifedeveloper/common/infrastructure/GsonNotificationReader$JsonUtil::isNull → KILLED

Active mutators

Tests examined


Report generated by PIT 1.20.2