From 593c17ad0c18e4d5aead0aa25b955fea78b2feef Mon Sep 17 00:00:00 2001 From: Simon Stewart Date: Thu, 13 Jun 2019 17:43:50 +0100 Subject: [PATCH] Remove dependency on guava --- java/client/src/org/openqa/selenium/json/BUCK | 1 - .../src/org/openqa/selenium/json/BUILD.bazel | 1 - .../src/org/openqa/selenium/json/Json.java | 2 - .../org/openqa/selenium/json/JsonOutput.java | 138 ++++++++--------- .../openqa/selenium/json/JsonTypeCoercer.java | 140 +++++++++--------- .../openqa/selenium/json/NumberCoercer.java | 20 ++- .../org/openqa/selenium/json/TypeToken.java | 40 +++++ .../test/org/openqa/selenium/json/BUILD.bazel | 15 ++ 8 files changed, 212 insertions(+), 145 deletions(-) create mode 100644 java/client/src/org/openqa/selenium/json/TypeToken.java create mode 100644 java/client/test/org/openqa/selenium/json/BUILD.bazel diff --git a/java/client/src/org/openqa/selenium/json/BUCK b/java/client/src/org/openqa/selenium/json/BUCK index 6e3fe8236ca1a..3462e2424d0f7 100644 --- a/java/client/src/org/openqa/selenium/json/BUCK +++ b/java/client/src/org/openqa/selenium/json/BUCK @@ -4,7 +4,6 @@ java_library( deps = [ "//java/client/src/org/openqa/selenium:core", "//java/client/src/org/openqa/selenium/remote:api", - "//third_party/java/guava:guava", ], visibility = [ "//java/client/src/org/openqa/...", diff --git a/java/client/src/org/openqa/selenium/json/BUILD.bazel b/java/client/src/org/openqa/selenium/json/BUILD.bazel index 32ed1a1d9341f..efd0fcc69a350 100644 --- a/java/client/src/org/openqa/selenium/json/BUILD.bazel +++ b/java/client/src/org/openqa/selenium/json/BUILD.bazel @@ -10,6 +10,5 @@ java_library( deps = [ "//java/client/src/org/openqa/selenium:core", "//java/client/src/org/openqa/selenium/remote:api", - "//third_party/java/guava", ], ) diff --git a/java/client/src/org/openqa/selenium/json/Json.java b/java/client/src/org/openqa/selenium/json/Json.java index abed6045cc9a4..3fba0e70a0019 100644 --- a/java/client/src/org/openqa/selenium/json/Json.java +++ b/java/client/src/org/openqa/selenium/json/Json.java @@ -17,8 +17,6 @@ package org.openqa.selenium.json; -import com.google.common.reflect.TypeToken; - import java.io.IOException; import java.io.Reader; import java.io.StringReader; diff --git a/java/client/src/org/openqa/selenium/json/JsonOutput.java b/java/client/src/org/openqa/selenium/json/JsonOutput.java index 241400efe4034..d9fd9d0362b56 100644 --- a/java/client/src/org/openqa/selenium/json/JsonOutput.java +++ b/java/client/src/org/openqa/selenium/json/JsonOutput.java @@ -17,10 +17,6 @@ package org.openqa.selenium.json; -import static java.util.concurrent.TimeUnit.MILLISECONDS; - -import com.google.common.collect.ImmutableMap; - import org.openqa.selenium.logging.LogLevelMapping; import org.openqa.selenium.remote.SessionId; @@ -32,8 +28,10 @@ import java.net.URL; import java.util.ArrayDeque; import java.util.Collection; +import java.util.Collections; import java.util.Date; import java.util.Deque; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; import java.util.UUID; @@ -44,6 +42,8 @@ import java.util.logging.Logger; import java.util.stream.Stream; +import static java.util.concurrent.TimeUnit.MILLISECONDS; + public class JsonOutput implements Closeable { private static final Logger LOG = Logger.getLogger(JsonOutput.class.getName()); private static final int MAX_DEPTH = 5; @@ -69,7 +69,7 @@ public class JsonOutput implements Closeable { // we'll also escape "<" and "&" private static final Map ESCAPES; static { - ImmutableMap.Builder builder = ImmutableMap.builder(); + Map builder = new LinkedHashMap<>(); for (int i = 0; i <= 0x1f; i++) { // We want nice looking escapes for these, which are called out @@ -91,7 +91,7 @@ public class JsonOutput implements Closeable { builder.put((int) '\u2028', "\\u2028"); builder.put((int) '<', String.format("\\u%04x", (int) '<')); builder.put((int) '&', String.format("\\u%04x", (int) '&')); - ESCAPES = builder.build(); + ESCAPES = Collections.unmodifiableMap(builder); } private final Map>, SafeBiConsumer> converters; @@ -119,69 +119,69 @@ public class JsonOutput implements Closeable { // Order matters, since we want to handle null values first to avoid exceptions, and then then // common kinds of inputs next. - this.converters = ImmutableMap.>, SafeBiConsumer>builder() - .put(Objects::isNull, (obj, depth) -> append("null")) - .put(CharSequence.class::isAssignableFrom, (obj, depth) -> append(asString(obj))) - .put(Number.class::isAssignableFrom, (obj, depth) -> append(obj.toString())) - .put(Boolean.class::isAssignableFrom, (obj, depth) -> append((Boolean) obj ? "true" : "false")) - .put(Date.class::isAssignableFrom, (obj, depth) -> append(String.valueOf(MILLISECONDS.toSeconds(((Date) obj).getTime())))) - .put(Enum.class::isAssignableFrom, (obj, depth) -> append(asString(obj))) - .put(File.class::isAssignableFrom, (obj, depth) -> append(((File) obj).getAbsolutePath())) - .put(URI.class::isAssignableFrom, (obj, depth) -> append(asString((obj).toString()))) - .put(URL.class::isAssignableFrom, (obj, depth) -> append(asString(((URL) obj).toExternalForm()))) - .put(UUID.class::isAssignableFrom, (obj, depth) -> append(asString(((UUID) obj).toString()))) - .put(Level.class::isAssignableFrom, (obj, depth) -> append(asString(LogLevelMapping.getName((Level) obj)))) - .put(SessionId.class::isAssignableFrom, (obj, depth) -> append(asString(obj))) - .put( - GSON_ELEMENT, - (obj, depth) -> { - LOG.log( - Level.WARNING, - "Attempt to convert JsonElement from GSON. This functionality is deprecated. " - + "Diagnostic stacktrace follows", - new JsonException("Stack trace to determine cause of warning")); - append(obj.toString()); - }) - // Special handling of asMap and toJson - .put( - cls -> getMethod(cls, "toJson") != null, - (obj, depth) -> convertUsingMethod("toJson", obj, depth)) - .put( - cls -> getMethod(cls, "asMap") != null, - (obj, depth) -> convertUsingMethod("asMap", obj, depth)) - .put( - cls -> getMethod(cls, "toMap") != null, - (obj, depth) -> convertUsingMethod("toMap", obj, depth)) - - // And then the collection types - .put( - Collection.class::isAssignableFrom, - (obj, depth) -> { - beginArray(); - ((Collection) obj).forEach(o -> write(o, depth - 1)); - endArray(); - }) - - .put( - Map.class::isAssignableFrom, - (obj, depth) -> { - beginObject(); - ((Map) obj).forEach( - (key, value) -> name(String.valueOf(key)).write(value, depth - 1)); - endObject(); - }) - .put( - Class::isArray, - (obj, depth) -> { - beginArray(); - Stream.of((Object[]) obj).forEach(o -> write(o, depth - 1)); - endArray(); - }) - - // Finally, attempt to convert as an object - .put(cls -> true, (obj, depth) -> mapObject(obj, depth - 1)) - - .build(); + Map>, SafeBiConsumer> builder = new LinkedHashMap<>(); + builder.put(Objects::isNull, (obj, depth) -> append("null")); + builder.put(CharSequence.class::isAssignableFrom, (obj, depth) -> append(asString(obj))); + builder.put(Number.class::isAssignableFrom, (obj, depth) -> append(obj.toString())); + builder.put(Boolean.class::isAssignableFrom, (obj, depth) -> append((Boolean) obj ? "true" : "false")); + builder.put(Date.class::isAssignableFrom, (obj, depth) -> append(String.valueOf(MILLISECONDS.toSeconds(((Date) obj).getTime())))); + builder.put(Enum.class::isAssignableFrom, (obj, depth) -> append(asString(obj))); + builder.put(File.class::isAssignableFrom, (obj, depth) -> append(((File) obj).getAbsolutePath())); + builder.put(URI.class::isAssignableFrom, (obj, depth) -> append(asString((obj).toString()))); + builder.put(URL.class::isAssignableFrom, (obj, depth) -> append(asString(((URL) obj).toExternalForm()))); + builder.put(UUID.class::isAssignableFrom, (obj, depth) -> append(asString(((UUID) obj).toString()))); + builder.put(Level.class::isAssignableFrom, (obj, depth) -> append(asString(LogLevelMapping.getName((Level) obj)))); + builder.put(SessionId.class::isAssignableFrom, (obj, depth) -> append(asString(obj))); + builder.put( + GSON_ELEMENT, + (obj, depth) -> { + LOG.log( + Level.WARNING, + "Attempt to convert JsonElement from GSON. This functionality is deprecated. " + + "Diagnostic stacktrace follows", + new JsonException("Stack trace to determine cause of warning")); + append(obj.toString()); + }); + // Special handling of asMap and toJson + builder.put( + cls -> getMethod(cls, "toJson") != null, + (obj, depth) -> convertUsingMethod("toJson", obj, depth)); + builder.put( + cls -> getMethod(cls, "asMap") != null, + (obj, depth) -> convertUsingMethod("asMap", obj, depth)); + builder.put( + cls -> getMethod(cls, "toMap") != null, + (obj, depth) -> convertUsingMethod("toMap", obj, depth)); + + // And then the collection types + builder.put( + Collection.class::isAssignableFrom, + (obj, depth) -> { + beginArray(); + ((Collection) obj).forEach(o -> write(o, depth - 1)); + endArray(); + }); + + builder.put( + Map.class::isAssignableFrom, + (obj, depth) -> { + beginObject(); + ((Map) obj).forEach( + (key, value) -> name(String.valueOf(key)).write(value, depth - 1)); + endObject(); + }); + builder.put( + Class::isArray, + (obj, depth) -> { + beginArray(); + Stream.of((Object[]) obj).forEach(o -> write(o, depth - 1)); + endArray(); + }); + + // Finally, attempt to convert as an object + builder.put(cls -> true, (obj, depth) -> mapObject(obj, depth - 1)); + + this.converters = Collections.unmodifiableMap(builder); } public JsonOutput setPrettyPrint(boolean enablePrettyPrinting) { diff --git a/java/client/src/org/openqa/selenium/json/JsonTypeCoercer.java b/java/client/src/org/openqa/selenium/json/JsonTypeCoercer.java index 23fdaf2c51f00..7797b4b2dc090 100644 --- a/java/client/src/org/openqa/selenium/json/JsonTypeCoercer.java +++ b/java/client/src/org/openqa/selenium/json/JsonTypeCoercer.java @@ -17,19 +17,15 @@ package org.openqa.selenium.json; -import static java.util.stream.Collector.Characteristics.CONCURRENT; -import static java.util.stream.Collector.Characteristics.UNORDERED; -import static org.openqa.selenium.json.Types.narrow; - -import com.google.common.collect.ImmutableSet; - import org.openqa.selenium.Capabilities; import org.openqa.selenium.MutableCapabilities; import java.lang.reflect.Type; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -38,6 +34,14 @@ import java.util.function.BiFunction; import java.util.stream.Collector; import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import static java.util.stream.Collector.Characteristics.CONCURRENT; +import static java.util.stream.Collector.Characteristics.UNORDERED; +import static java.util.stream.Collectors.collectingAndThen; +import static java.util.stream.Collectors.toSet; +import static org.openqa.selenium.json.Types.narrow; class JsonTypeCoercer { @@ -46,76 +50,74 @@ class JsonTypeCoercer { private final Map> knownCoercers = new ConcurrentHashMap<>(); JsonTypeCoercer() { - this(ImmutableSet.of()); + this(Stream.of()); } JsonTypeCoercer(JsonTypeCoercer coercer, Iterable> coercers) { this( - ImmutableSet.>builder() - .addAll(coercers) - .addAll(coercer.additionalCoercers) - .build()); + Stream.concat(StreamSupport.stream(coercers.spliterator(), false), coercer.additionalCoercers.stream())); } - JsonTypeCoercer(Iterable> coercers) { - this.additionalCoercers = ImmutableSet.copyOf(coercers); - + private JsonTypeCoercer(Stream> coercers) { + this.additionalCoercers = coercers.collect(collectingAndThen(toSet(), Collections::unmodifiableSet)); + + // Note: we call out when ordering matters. + Set> builder = new LinkedHashSet<>(additionalCoercers); + + // Types that don't contain other types first + // From java + builder.add(new BooleanCoercer()); + + builder.add(new NumberCoercer<>(Byte.class, Number::byteValue)); + builder.add(new NumberCoercer<>(Double.class, Number::doubleValue)); + builder.add(new NumberCoercer<>(Float.class, Number::floatValue)); + builder.add(new NumberCoercer<>(Integer.class, Number::intValue)); + builder.add(new NumberCoercer<>(Long.class, Number::longValue)); + builder.add( + new NumberCoercer<>( + Number.class, + num -> { + if (num.doubleValue() % 1 != 0) { + return num.doubleValue(); + } + return num.longValue(); + })); + builder.add(new NumberCoercer<>(Short.class, Number::shortValue)); + builder.add(new StringCoercer()); + builder.add(new EnumCoercer()); + builder.add(new UriCoercer()); + builder.add(new UrlCoercer()); + builder.add(new UuidCoercer()); + + // From Selenium + builder.add(new MapCoercer<>( + Capabilities.class, + this, + Collector.of(MutableCapabilities::new, (caps, entry) -> caps.setCapability((String) entry.getKey(), entry.getValue()), MutableCapabilities::merge, UNORDERED))); + builder.add(new CommandCoercer()); + builder.add(new ResponseCoercer(this)); + builder.add(new SessionIdCoercer()); + + // Container types //noinspection unchecked - this.coercers = - // Note: we call out when ordering matters. - ImmutableSet.builder() - .addAll(coercers) - // Types that don't contain other types first - // From java - .add(new BooleanCoercer()) - - .add(new NumberCoercer<>(Byte.class, Number::byteValue)) - .add(new NumberCoercer<>(Double.class, Number::doubleValue)) - .add(new NumberCoercer<>(Float.class, Number::floatValue)) - .add(new NumberCoercer<>(Integer.class, Number::intValue)) - .add(new NumberCoercer<>(Long.class, Number::longValue)) - .add( - new NumberCoercer<>( - Number.class, - num -> { - if (num.doubleValue() % 1 != 0) { - return num.doubleValue(); - } - return num.longValue(); - })) - .add(new NumberCoercer<>(Short.class, Number::shortValue)) - .add(new StringCoercer()) - .add(new EnumCoercer()) - .add(new UriCoercer()) - .add(new UrlCoercer()) - .add(new UuidCoercer()) - - // From Selenium - .add(new MapCoercer<>( - Capabilities.class, - this, - Collector.of(MutableCapabilities::new, (caps, entry) -> caps.setCapability((String) entry.getKey(), entry.getValue()), MutableCapabilities::merge, UNORDERED))) - .add(new CommandCoercer()) - .add(new ResponseCoercer(this)) - .add(new SessionIdCoercer()) - - // Container types - .add(new CollectionCoercer<>(List.class, this, Collectors.toCollection(ArrayList::new))) - .add(new CollectionCoercer<>(Set.class, this, Collectors.toCollection(HashSet::new))) - - .add(new MapCoercer<>( - Map.class, - this, - Collector.of(LinkedHashMap::new, (map, entry) -> map.put(entry.getKey(), entry.getValue()), (l, r) -> { l.putAll(r); return l; }, UNORDERED, CONCURRENT))) - - // If the requested type is exactly "Object", do some guess work - .add(new ObjectCoercer(this)) - - .add(new StaticInitializerCoercer()) - - // Order matters here: we want this to be the last called coercer - .add(new InstanceCoercer(this)) - .build(); + builder.add(new CollectionCoercer<>(List.class, this, Collectors.toCollection(ArrayList::new))); + //noinspection unchecked + builder.add(new CollectionCoercer<>(Set.class, this, Collectors.toCollection(HashSet::new))); + + builder.add(new MapCoercer<>( + Map.class, + this, + Collector.of(LinkedHashMap::new, (map, entry) -> map.put(entry.getKey(), entry.getValue()), (l, r) -> { l.putAll(r); return l; }, UNORDERED, CONCURRENT))); + + // If the requested type is exactly "Object", do some guess work + builder.add(new ObjectCoercer(this)); + + builder.add(new StaticInitializerCoercer()); + + // Order matters here: we want this to be the last called coercer + builder.add(new InstanceCoercer(this)); + + this.coercers = Collections.unmodifiableSet(builder); } T coerce(JsonInput json, Type typeOfT, PropertySetting setter) { diff --git a/java/client/src/org/openqa/selenium/json/NumberCoercer.java b/java/client/src/org/openqa/selenium/json/NumberCoercer.java index 26a5494886f19..103728c646c24 100644 --- a/java/client/src/org/openqa/selenium/json/NumberCoercer.java +++ b/java/client/src/org/openqa/selenium/json/NumberCoercer.java @@ -17,16 +17,30 @@ package org.openqa.selenium.json; -import com.google.common.primitives.Primitives; - import java.lang.reflect.Type; import java.math.BigDecimal; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; import java.util.function.BiFunction; import java.util.function.Function; class NumberCoercer extends TypeCoercer { + private final static Map, Class> PRIMITIVE_NUMBER_TYPES; + static { + Map, Class> builder = new HashMap<>(); + builder.put(byte.class, Byte.class); + builder.put(double.class, Double.class); + builder.put(float.class, Float.class); + builder.put(int.class, Integer.class); + builder.put(long.class, Long.class); + builder.put(short.class, Short.class); + + PRIMITIVE_NUMBER_TYPES = Collections.unmodifiableMap(builder); + } + private final Class stereotype; private final Function mapper; @@ -37,7 +51,7 @@ class NumberCoercer extends TypeCoercer { @Override public boolean test(Class type) { - return stereotype.isAssignableFrom(Primitives.wrap(type)); + return stereotype.isAssignableFrom(PRIMITIVE_NUMBER_TYPES.getOrDefault(type, type)); } @Override diff --git a/java/client/src/org/openqa/selenium/json/TypeToken.java b/java/client/src/org/openqa/selenium/json/TypeToken.java new file mode 100644 index 0000000000000..f306e160c3996 --- /dev/null +++ b/java/client/src/org/openqa/selenium/json/TypeToken.java @@ -0,0 +1,40 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.openqa.selenium.json; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + + +abstract class TypeToken { + + private final Type type; + + TypeToken() { + // This code is taken from Guava's TypeToken class. + Type superclass = getClass().getGenericSuperclass(); + if (!(superclass instanceof ParameterizedType)) { + throw new IllegalStateException(String.format("%s isn't parameterized", superclass)); + } + type = ((ParameterizedType) superclass).getActualTypeArguments()[0]; + } + + public Type getType() { + return type; + } +} diff --git a/java/client/test/org/openqa/selenium/json/BUILD.bazel b/java/client/test/org/openqa/selenium/json/BUILD.bazel new file mode 100644 index 0000000000000..e050f67a6a5ce --- /dev/null +++ b/java/client/test/org/openqa/selenium/json/BUILD.bazel @@ -0,0 +1,15 @@ +load("//java:rules.bzl", "java_test_suite") + +java_test_suite( + name = "SmallTests", + size = "small", + srcs = glob(["*.java"]), + deps = [ + "//java/client/src/org/openqa/selenium/json", + "//java/client/src/org/openqa/selenium/remote", + "//third_party/java/assertj", + "//third_party/java/gson", + "//third_party/java/guava", + "//third_party/java/junit", + ] +)