diff --git a/java/src/org/openqa/selenium/json/CollectionCoercer.java b/java/src/org/openqa/selenium/json/CollectionCoercer.java index 0101083931fba..1b28499490cc9 100644 --- a/java/src/org/openqa/selenium/json/CollectionCoercer.java +++ b/java/src/org/openqa/selenium/json/CollectionCoercer.java @@ -24,20 +24,21 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; import org.openqa.selenium.internal.Require; -class CollectionCoercer extends TypeCoercer { +class CollectionCoercer, I extends T> extends TypeCoercer { private final Class stereotype; private final JsonTypeCoercer coercer; private final Supplier supplier; - private final Function> consumerFactory; + private final Function> consumerFactory; public CollectionCoercer( Class stereotype, JsonTypeCoercer coercer, Supplier supplier, - Function> consumerFactory) { + Function> consumerFactory) { this.stereotype = Require.nonNull("Stereotype", stereotype); this.coercer = Require.nonNull("Coercer", coercer); this.supplier = Require.nonNull("Supplier", supplier); @@ -65,7 +66,7 @@ public BiFunction apply(Type type) { return (jsonInput, setting) -> { jsonInput.beginArray(); I toReturn = supplier.get(); - Consumer consumer = consumerFactory.apply(toReturn); + Consumer<@Nullable Object> consumer = consumerFactory.apply(toReturn); while (jsonInput.hasNext()) { consumer.accept(coercer.coerce(jsonInput, valueType, setting)); } diff --git a/java/src/org/openqa/selenium/json/ConstructorCoercer.java b/java/src/org/openqa/selenium/json/ConstructorCoercer.java index f525ea08af466..1fa9e97d1ebfe 100644 --- a/java/src/org/openqa/selenium/json/ConstructorCoercer.java +++ b/java/src/org/openqa/selenium/json/ConstructorCoercer.java @@ -126,7 +126,9 @@ public BiFunction apply(Type type) { List candidates = getConstructorCandidates(type); return (jsonInput, setting) -> { - Map properties = coercer.coerce(jsonInput, Json.MAP_TYPE, setting); + Map properties = + Require.nonNull( + "Properties for " + type, coercer.coerce(jsonInput, Json.MAP_TYPE, setting)); ConstructorCandidate candidate = findConstructor(type, candidates, properties.keySet()); return candidate.create(type, properties, setting); @@ -228,6 +230,7 @@ private Map getParameterIndexes(Parameter[] parameters) { return indexes; } + @Nullable private Object coerceValue(Object value, Type type, PropertySetting setting) { StringWriter rawJson = new StringWriter(); try (JsonOutput output = new JsonOutput(rawJson)) { diff --git a/java/src/org/openqa/selenium/json/EnumCoercer.java b/java/src/org/openqa/selenium/json/EnumCoercer.java index 9727a8ab1bc5a..bed161813eefb 100644 --- a/java/src/org/openqa/selenium/json/EnumCoercer.java +++ b/java/src/org/openqa/selenium/json/EnumCoercer.java @@ -22,7 +22,7 @@ import java.lang.reflect.Type; import java.util.function.BiFunction; -public class EnumCoercer extends TypeCoercer { +public class EnumCoercer> extends TypeCoercer { @Override public boolean test(Class aClass) { diff --git a/java/src/org/openqa/selenium/json/InstanceCoercer.java b/java/src/org/openqa/selenium/json/InstanceCoercer.java index 04c18aa6e4563..2f95a0ee7da82 100644 --- a/java/src/org/openqa/selenium/json/InstanceCoercer.java +++ b/java/src/org/openqa/selenium/json/InstanceCoercer.java @@ -32,6 +32,7 @@ import java.util.function.BiFunction; import java.util.function.Function; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.openqa.selenium.internal.Require; class InstanceCoercer extends TypeCoercer { @@ -168,9 +169,9 @@ private static Class getClss(Type type) { private static class TypeAndWriter { private final Type type; - private final BiConsumer writer; + private final BiConsumer writer; - TypeAndWriter(Type type, BiConsumer writer) { + TypeAndWriter(Type type, BiConsumer writer) { this.type = type; this.writer = writer; } @@ -189,7 +190,7 @@ public TypeAndWriter apply(Field field) { } } - private static class FieldWriter implements BiConsumer { + private static class FieldWriter implements BiConsumer { private final Field field; FieldWriter(Field field) { @@ -197,7 +198,7 @@ private static class FieldWriter implements BiConsumer { } @Override - public void accept(Object instance, Object value) { + public void accept(Object instance, @Nullable Object value) { try { field.set(instance, value); } catch (IllegalAccessException e) { @@ -226,7 +227,7 @@ public TypeAndWriter apply(SimplePropertyDescriptor desc) { } } - private static class SimplePropertyWriter implements BiConsumer { + private static class SimplePropertyWriter implements BiConsumer { private final SimplePropertyDescriptor desc; private final Method method; @@ -236,7 +237,7 @@ private static class SimplePropertyWriter implements BiConsumer } @Override - public void accept(Object instance, Object value) { + public void accept(Object instance, @Nullable Object value) { method.setAccessible(true); try { method.invoke(instance, value); diff --git a/java/src/org/openqa/selenium/json/JsonInput.java b/java/src/org/openqa/selenium/json/JsonInput.java index d4b23c9760b47..e1ef82c48d7ce 100644 --- a/java/src/org/openqa/selenium/json/JsonInput.java +++ b/java/src/org/openqa/selenium/json/JsonInput.java @@ -540,8 +540,8 @@ public T readMapElement(String key) { * @throws JsonException if coercion of the next element to the specified type fails * @throws UncheckedIOException if an I/O exception is encountered */ - public List readArray(Type type) { - List toReturn = new ArrayList<>(); + public List<@Nullable T> readArray(Type type) { + List<@Nullable T> toReturn = new ArrayList<>(); beginArray(); while (hasNext()) { diff --git a/java/src/org/openqa/selenium/json/JsonTypeCoercer.java b/java/src/org/openqa/selenium/json/JsonTypeCoercer.java index 0c5d2f696d94a..aae28ddbc9266 100644 --- a/java/src/org/openqa/selenium/json/JsonTypeCoercer.java +++ b/java/src/org/openqa/selenium/json/JsonTypeCoercer.java @@ -37,6 +37,7 @@ import java.util.function.BiFunction; import java.util.stream.Stream; import java.util.stream.StreamSupport; +import org.jspecify.annotations.Nullable; import org.openqa.selenium.Capabilities; import org.openqa.selenium.MutableCapabilities; import org.openqa.selenium.internal.Require; @@ -113,11 +114,8 @@ private JsonTypeCoercer(Stream> coercers) { (caps) -> ((k, v) -> caps.setCapability((String) k, v)))); // Container types - //noinspection unchecked builder.add(new CollectionCoercer<>(List.class, this, ArrayList::new, (list) -> list::add)); - //noinspection unchecked builder.add(new CollectionCoercer<>(Set.class, this, HashSet::new, (set) -> set::add)); - //noinspection unchecked builder.add( new CollectionCoercer<>( Collection.class, this, ArrayList::new, (collection) -> collection::add)); @@ -139,7 +137,7 @@ private JsonTypeCoercer(Stream> coercers) { this.coercers = Collections.unmodifiableSet(builder); } - T coerce(JsonInput json, Type typeOfT, PropertySetting setter) { + @Nullable T coerce(JsonInput json, Type typeOfT, PropertySetting setter) { BiFunction coercer = knownCoercers.computeIfAbsent(typeOfT, this::buildCoercer); diff --git a/java/src/org/openqa/selenium/json/MapCoercer.java b/java/src/org/openqa/selenium/json/MapCoercer.java index ad254741a5dbf..8345cc476fd00 100644 --- a/java/src/org/openqa/selenium/json/MapCoercer.java +++ b/java/src/org/openqa/selenium/json/MapCoercer.java @@ -17,25 +17,28 @@ package org.openqa.selenium.json; +import static java.util.Objects.requireNonNull; + import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; class MapCoercer extends TypeCoercer { private final Class stereotype; private final JsonTypeCoercer coercer; private final Supplier supplier; - private final Function> consumerFactory; + private final Function> consumerFactory; public MapCoercer( Class stereotype, JsonTypeCoercer coercer, Supplier supplier, - Function> consumerFactory) { + Function> consumerFactory) { this.stereotype = stereotype; this.coercer = coercer; this.supplier = supplier; @@ -66,7 +69,7 @@ public BiFunction apply(Type type) { return (jsonInput, setting) -> { jsonInput.beginObject(); I toReturn = supplier.get(); - BiConsumer consumer = consumerFactory.apply(toReturn); + BiConsumer consumer = consumerFactory.apply(toReturn); // JSON should always have a string key, so we can take the fastpath boolean stringKey = String.class.equals(keyType); @@ -76,7 +79,7 @@ public BiFunction apply(Type type) { if (stringKey) { key = jsonInput.nextName(); } else { - key = coercer.coerce(jsonInput, keyType, setting); + key = requireNonNull(coercer.coerce(jsonInput, keyType, setting)); } Object value = coercer.coerce(jsonInput, valueType, setting); diff --git a/java/src/org/openqa/selenium/json/ObjectCoercer.java b/java/src/org/openqa/selenium/json/ObjectCoercer.java index 65980294197c0..f6a80a6bd1b2f 100644 --- a/java/src/org/openqa/selenium/json/ObjectCoercer.java +++ b/java/src/org/openqa/selenium/json/ObjectCoercer.java @@ -20,6 +20,7 @@ import java.lang.reflect.Type; import java.util.List; import java.util.function.BiFunction; +import org.jspecify.annotations.Nullable; import org.openqa.selenium.internal.Require; class ObjectCoercer extends TypeCoercer { @@ -36,7 +37,7 @@ public boolean test(Class type) { } @Override - public BiFunction apply(Type type) { + public BiFunction apply(Type type) { return (jsonInput, setting) -> { Type target; diff --git a/java/src/org/openqa/selenium/json/TypeCoercer.java b/java/src/org/openqa/selenium/json/TypeCoercer.java index d3bde66d2f84e..6092a040e3e48 100644 --- a/java/src/org/openqa/selenium/json/TypeCoercer.java +++ b/java/src/org/openqa/selenium/json/TypeCoercer.java @@ -21,13 +21,15 @@ import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; +import org.jspecify.annotations.Nullable; public abstract class TypeCoercer - implements Predicate>, Function> { + implements Predicate>, + Function> { @Override public abstract boolean test(Class aClass); @Override - public abstract BiFunction apply(Type type); + public abstract BiFunction apply(Type type); } diff --git a/java/test/org/openqa/selenium/json/JsonInputTest.java b/java/test/org/openqa/selenium/json/JsonInputTest.java index 23ba6734759f8..f8825a33f85b0 100644 --- a/java/test/org/openqa/selenium/json/JsonInputTest.java +++ b/java/test/org/openqa/selenium/json/JsonInputTest.java @@ -39,7 +39,6 @@ import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; -import java.util.List; import java.util.Map; import java.util.Random; import java.util.stream.Collectors; @@ -234,7 +233,7 @@ void shouldDecodeUnicodeEscapesProperly() { String raw = "{\"text\": \"\\u003Chtml\"}"; try (JsonInput in = new JsonInput(new StringReader(raw), new JsonTypeCoercer(), BY_NAME)) { - Map map = in.read(MAP_TYPE); + Map map = in.readMap(); assertThat(map.get("text")).isEqualTo(" array = in.readArray(Integer.class); + var array = in.readArray(Integer.class); assertThat(array).containsExactly(1, 2, 3, 4); } } + @Test + void canReadListOfType_null() { + String raw = "[null, null]"; + + try (JsonInput in = new JsonInput(new StringReader(raw), new JsonTypeCoercer(), BY_NAME)) { + var array = in.readArray(Integer.class); + + assertThat(array).containsExactly(null, null); + } + } + @Test void shouldBeAbleToReadDataLongerThanReadBuffer() { char[] chars = new char[] {'c', 'h', 'e', 's'}; diff --git a/java/test/org/openqa/selenium/json/JsonTest.java b/java/test/org/openqa/selenium/json/JsonTest.java index df45725e65647..476b9182caf14 100644 --- a/java/test/org/openqa/selenium/json/JsonTest.java +++ b/java/test/org/openqa/selenium/json/JsonTest.java @@ -167,6 +167,13 @@ void canConstructASimpleString() { assertThat(text).isEqualTo("cheese"); } + @Test + void toTypeReturnsNullForTopLevelJsonNull() { + String text = new Json().toType("null", String.class); + + assertThat(text).isNull(); + } + @Test void canPopulateAMap() { String raw = "{\"cheese\": \"brie\", \"foodstuff\": \"cheese\"}";