Skip to content

Commit 43226dc

Browse files
committed
mark JsonTypeCoercer.coerce as nullable
de-facto it can return null. Now IDEA will know it, and show warning for unsafe usages.
1 parent 1a6530f commit 43226dc

10 files changed

Lines changed: 44 additions & 25 deletions

File tree

java/src/org/openqa/selenium/json/CollectionCoercer.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,21 @@
2424
import java.util.function.Consumer;
2525
import java.util.function.Function;
2626
import java.util.function.Supplier;
27+
import org.jspecify.annotations.Nullable;
2728
import org.openqa.selenium.internal.Require;
2829

29-
class CollectionCoercer<T extends Collection, I extends T> extends TypeCoercer<T> {
30+
class CollectionCoercer<T extends Collection<?>, I extends T> extends TypeCoercer<T> {
3031

3132
private final Class<T> stereotype;
3233
private final JsonTypeCoercer coercer;
3334
private final Supplier<I> supplier;
34-
private final Function<I, Consumer<Object>> consumerFactory;
35+
private final Function<I, Consumer<@Nullable Object>> consumerFactory;
3536

3637
public CollectionCoercer(
3738
Class<T> stereotype,
3839
JsonTypeCoercer coercer,
3940
Supplier<I> supplier,
40-
Function<I, Consumer<Object>> consumerFactory) {
41+
Function<I, Consumer<@Nullable Object>> consumerFactory) {
4142
this.stereotype = Require.nonNull("Stereotype", stereotype);
4243
this.coercer = Require.nonNull("Coercer", coercer);
4344
this.supplier = Require.nonNull("Supplier", supplier);
@@ -65,7 +66,7 @@ public BiFunction<JsonInput, PropertySetting, T> apply(Type type) {
6566
return (jsonInput, setting) -> {
6667
jsonInput.beginArray();
6768
I toReturn = supplier.get();
68-
Consumer<Object> consumer = consumerFactory.apply(toReturn);
69+
Consumer<@Nullable Object> consumer = consumerFactory.apply(toReturn);
6970
while (jsonInput.hasNext()) {
7071
consumer.accept(coercer.coerce(jsonInput, valueType, setting));
7172
}

java/src/org/openqa/selenium/json/ConstructorCoercer.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import java.util.Set;
3737
import java.util.function.BiFunction;
3838
import java.util.stream.Stream;
39+
import org.jspecify.annotations.Nullable;
3940
import org.openqa.selenium.internal.Require;
4041

4142
class ConstructorCoercer extends TypeCoercer<Object> {
@@ -147,6 +148,7 @@ private Map<String, Integer> getParameterIndexes(Parameter[] parameters) {
147148
return indexes;
148149
}
149150

151+
@Nullable
150152
private Object coerceValue(Object value, Type type, PropertySetting setting) {
151153
StringWriter rawJson = new StringWriter();
152154
try (JsonOutput output = new JsonOutput(rawJson)) {

java/src/org/openqa/selenium/json/EnumCoercer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
import java.lang.reflect.Type;
2323
import java.util.function.BiFunction;
2424

25-
public class EnumCoercer<T extends Enum> extends TypeCoercer<T> {
25+
public class EnumCoercer<T extends Enum<T>> extends TypeCoercer<T> {
2626

2727
@Override
2828
public boolean test(Class<?> aClass) {

java/src/org/openqa/selenium/json/InstanceCoercer.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import java.util.function.BiFunction;
3333
import java.util.function.Function;
3434
import java.util.stream.Stream;
35+
import org.jspecify.annotations.Nullable;
3536
import org.openqa.selenium.internal.Require;
3637

3738
class InstanceCoercer extends TypeCoercer<Object> {
@@ -168,9 +169,9 @@ private static Class<?> getClss(Type type) {
168169

169170
private static class TypeAndWriter {
170171
private final Type type;
171-
private final BiConsumer<Object, Object> writer;
172+
private final BiConsumer<Object, @Nullable Object> writer;
172173

173-
TypeAndWriter(Type type, BiConsumer<Object, Object> writer) {
174+
TypeAndWriter(Type type, BiConsumer<Object, @Nullable Object> writer) {
174175
this.type = type;
175176
this.writer = writer;
176177
}
@@ -189,15 +190,15 @@ public TypeAndWriter apply(Field field) {
189190
}
190191
}
191192

192-
private static class FieldWriter implements BiConsumer<Object, Object> {
193+
private static class FieldWriter implements BiConsumer<Object, @Nullable Object> {
193194
private final Field field;
194195

195196
FieldWriter(Field field) {
196197
this.field = field;
197198
}
198199

199200
@Override
200-
public void accept(Object instance, Object value) {
201+
public void accept(Object instance, @Nullable Object value) {
201202
try {
202203
field.set(instance, value);
203204
} catch (IllegalAccessException e) {
@@ -226,7 +227,7 @@ public TypeAndWriter apply(SimplePropertyDescriptor desc) {
226227
}
227228
}
228229

229-
private static class SimplePropertyWriter implements BiConsumer<Object, Object> {
230+
private static class SimplePropertyWriter implements BiConsumer<Object, @Nullable Object> {
230231
private final SimplePropertyDescriptor desc;
231232
private final Method method;
232233

@@ -236,7 +237,7 @@ private static class SimplePropertyWriter implements BiConsumer<Object, Object>
236237
}
237238

238239
@Override
239-
public void accept(Object instance, Object value) {
240+
public void accept(Object instance, @Nullable Object value) {
240241
method.setAccessible(true);
241242
try {
242243
method.invoke(instance, value);

java/src/org/openqa/selenium/json/JsonInput.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -540,8 +540,8 @@ public <T> T readMapElement(String key) {
540540
* @throws JsonException if coercion of the next element to the specified type fails
541541
* @throws UncheckedIOException if an I/O exception is encountered
542542
*/
543-
public <T> List<T> readArray(Type type) {
544-
List<T> toReturn = new ArrayList<>();
543+
public <T> List<@Nullable T> readArray(Type type) {
544+
List<@Nullable T> toReturn = new ArrayList<>();
545545

546546
beginArray();
547547
while (hasNext()) {

java/src/org/openqa/selenium/json/JsonTypeCoercer.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import java.util.function.BiFunction;
3838
import java.util.stream.Stream;
3939
import java.util.stream.StreamSupport;
40+
import org.jspecify.annotations.Nullable;
4041
import org.openqa.selenium.Capabilities;
4142
import org.openqa.selenium.MutableCapabilities;
4243
import org.openqa.selenium.internal.Require;
@@ -113,11 +114,8 @@ private JsonTypeCoercer(Stream<TypeCoercer<?>> coercers) {
113114
(caps) -> ((k, v) -> caps.setCapability((String) k, v))));
114115

115116
// Container types
116-
//noinspection unchecked
117117
builder.add(new CollectionCoercer<>(List.class, this, ArrayList::new, (list) -> list::add));
118-
//noinspection unchecked
119118
builder.add(new CollectionCoercer<>(Set.class, this, HashSet::new, (set) -> set::add));
120-
//noinspection unchecked
121119
builder.add(
122120
new CollectionCoercer<>(
123121
Collection.class, this, ArrayList::new, (collection) -> collection::add));
@@ -139,7 +137,7 @@ private JsonTypeCoercer(Stream<TypeCoercer<?>> coercers) {
139137
this.coercers = Collections.unmodifiableSet(builder);
140138
}
141139

142-
<T> T coerce(JsonInput json, Type typeOfT, PropertySetting setter) {
140+
@Nullable <T> T coerce(JsonInput json, Type typeOfT, PropertySetting setter) {
143141
BiFunction<JsonInput, PropertySetting, Object> coercer =
144142
knownCoercers.computeIfAbsent(typeOfT, this::buildCoercer);
145143

java/src/org/openqa/selenium/json/MapCoercer.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,28 @@
1717

1818
package org.openqa.selenium.json;
1919

20+
import static java.util.Objects.requireNonNull;
21+
2022
import java.lang.reflect.ParameterizedType;
2123
import java.lang.reflect.Type;
2224
import java.util.function.BiConsumer;
2325
import java.util.function.BiFunction;
2426
import java.util.function.Function;
2527
import java.util.function.Supplier;
28+
import org.jspecify.annotations.Nullable;
2629

2730
class MapCoercer<T, I extends T> extends TypeCoercer<T> {
2831

2932
private final Class<T> stereotype;
3033
private final JsonTypeCoercer coercer;
3134
private final Supplier<I> supplier;
32-
private final Function<I, BiConsumer<Object, Object>> consumerFactory;
35+
private final Function<I, BiConsumer<Object, @Nullable Object>> consumerFactory;
3336

3437
public MapCoercer(
3538
Class<T> stereotype,
3639
JsonTypeCoercer coercer,
3740
Supplier<I> supplier,
38-
Function<I, BiConsumer<Object, Object>> consumerFactory) {
41+
Function<I, BiConsumer<Object, @Nullable Object>> consumerFactory) {
3942
this.stereotype = stereotype;
4043
this.coercer = coercer;
4144
this.supplier = supplier;
@@ -66,7 +69,7 @@ public BiFunction<JsonInput, PropertySetting, T> apply(Type type) {
6669
return (jsonInput, setting) -> {
6770
jsonInput.beginObject();
6871
I toReturn = supplier.get();
69-
BiConsumer<Object, Object> consumer = consumerFactory.apply(toReturn);
72+
BiConsumer<Object, @Nullable Object> consumer = consumerFactory.apply(toReturn);
7073
// JSON should always have a string key, so we can take the fastpath
7174
boolean stringKey = String.class.equals(keyType);
7275

@@ -76,7 +79,7 @@ public BiFunction<JsonInput, PropertySetting, T> apply(Type type) {
7679
if (stringKey) {
7780
key = jsonInput.nextName();
7881
} else {
79-
key = coercer.coerce(jsonInput, keyType, setting);
82+
key = requireNonNull(coercer.coerce(jsonInput, keyType, setting));
8083
}
8184
Object value = coercer.coerce(jsonInput, valueType, setting);
8285

java/src/org/openqa/selenium/json/ObjectCoercer.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.lang.reflect.Type;
2121
import java.util.List;
2222
import java.util.function.BiFunction;
23+
import org.jspecify.annotations.Nullable;
2324
import org.openqa.selenium.internal.Require;
2425

2526
class ObjectCoercer extends TypeCoercer<Object> {
@@ -36,7 +37,7 @@ public boolean test(Class type) {
3637
}
3738

3839
@Override
39-
public BiFunction<JsonInput, PropertySetting, Object> apply(Type type) {
40+
public BiFunction<JsonInput, PropertySetting, @Nullable Object> apply(Type type) {
4041
return (jsonInput, setting) -> {
4142
Type target;
4243

java/src/org/openqa/selenium/json/TypeCoercer.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.function.BiFunction;
2222
import java.util.function.Function;
2323
import java.util.function.Predicate;
24+
import org.jspecify.annotations.Nullable;
2425

2526
public abstract class TypeCoercer<T>
2627
implements Predicate<Class<?>>, Function<Type, BiFunction<JsonInput, PropertySetting, T>> {
@@ -29,5 +30,5 @@ public abstract class TypeCoercer<T>
2930
public abstract boolean test(Class<?> aClass);
3031

3132
@Override
32-
public abstract BiFunction<JsonInput, PropertySetting, T> apply(Type type);
33+
public abstract BiFunction<JsonInput, PropertySetting, @Nullable T> apply(Type type);
3334
}

java/test/org/openqa/selenium/json/JsonInputTest.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ void shouldDecodeUnicodeEscapesProperly() {
234234
String raw = "{\"text\": \"\\u003Chtml\"}";
235235

236236
try (JsonInput in = new JsonInput(new StringReader(raw), new JsonTypeCoercer(), BY_NAME)) {
237-
Map<String, Object> map = in.read(MAP_TYPE);
237+
Map<String, Object> map = in.readMap();
238238

239239
assertThat(map.get("text")).isEqualTo("<html");
240240
}
@@ -245,7 +245,8 @@ void shouldCallFromJsonWithJsonInputParameter() {
245245
String raw = "{\"message\": \"Cheese!\"}";
246246

247247
try (JsonInput in = new JsonInput(new StringReader(raw), new JsonTypeCoercer(), BY_NAME)) {
248-
HasFromJsonWithJsonInputParameter obj = in.read(HasFromJsonWithJsonInputParameter.class);
248+
HasFromJsonWithJsonInputParameter obj =
249+
in.readNonNull(HasFromJsonWithJsonInputParameter.class);
249250

250251
assertThat(obj.getMessage()).isEqualTo("Cheese!");
251252
}
@@ -262,6 +263,17 @@ void canReadListOfType() {
262263
}
263264
}
264265

266+
@Test
267+
void canReadListOfType_null() {
268+
String raw = "[null, null]";
269+
270+
try (JsonInput in = new JsonInput(new StringReader(raw), new JsonTypeCoercer(), BY_NAME)) {
271+
List<Integer> array = in.readArray(Integer.class);
272+
273+
assertThat(array).containsExactly(null, null);
274+
}
275+
}
276+
265277
@Test
266278
void shouldBeAbleToReadDataLongerThanReadBuffer() {
267279
char[] chars = new char[] {'c', 'h', 'e', 's'};

0 commit comments

Comments
 (0)