]> source.dussan.org Git - vaadin-framework.git/commitdiff
Utility functions for helping to use elemental.json with Java 8
authorArtur Signell <artur@vaadin.com>
Tue, 13 Sep 2016 18:47:03 +0000 (21:47 +0300)
committerArtur Signell <artur@vaadin.com>
Wed, 14 Sep 2016 07:24:35 +0000 (10:24 +0300)
Change-Id: I7c3cf7be95eaf451be806cb75b7b2a34fc534deb

server/src/main/java/com/vaadin/data/util/JsonUtil.java [new file with mode: 0644]
server/src/test/java/com/vaadin/data/util/JsonUtilTest.java [new file with mode: 0644]
server/src/test/java/com/vaadin/tests/server/ClassesSerializableTest.java

diff --git a/server/src/main/java/com/vaadin/data/util/JsonUtil.java b/server/src/main/java/com/vaadin/data/util/JsonUtil.java
new file mode 100644 (file)
index 0000000..93f9782
--- /dev/null
@@ -0,0 +1,247 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed 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 com.vaadin.data.util;
+
+import java.util.AbstractList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.BinaryOperator;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collector;
+import java.util.stream.DoubleStream;
+import java.util.stream.Stream;
+
+import elemental.json.Json;
+import elemental.json.JsonArray;
+import elemental.json.JsonNumber;
+import elemental.json.JsonObject;
+import elemental.json.JsonType;
+import elemental.json.JsonValue;
+
+/**
+ * Helpers for using <code>elemental.json</code>.
+ *
+ * @author Vaadin Ltd
+ */
+public class JsonUtil {
+
+    /**
+     * Collects a stream of JSON values to a JSON array.
+     */
+    private static final class JsonArrayCollector
+            implements Collector<JsonValue, JsonArray, JsonArray> {
+        @Override
+        public Supplier<JsonArray> supplier() {
+            return Json::createArray;
+        }
+
+        @Override
+        public BiConsumer<JsonArray, JsonValue> accumulator() {
+            return (array, value) -> array.set(array.length(), value);
+        }
+
+        @Override
+        public BinaryOperator<JsonArray> combiner() {
+            return (left, right) -> {
+                for (int i = 0; i < right.length(); i++) {
+                    left.set(left.length(), right.<JsonValue> get(i));
+                }
+                return left;
+            };
+        }
+
+        @Override
+        public Function<JsonArray, JsonArray> finisher() {
+            return Function.identity();
+        }
+
+        @Override
+        public Set<Collector.Characteristics> characteristics() {
+            return ARRAY_COLLECTOR_CHARACTERISTICS;
+        }
+    }
+
+    private static final Set<Collector.Characteristics> ARRAY_COLLECTOR_CHARACTERISTICS = Collections
+            .unmodifiableSet(
+                    EnumSet.of(Collector.Characteristics.IDENTITY_FINISH));
+
+    private JsonUtil() {
+        // Static-only class
+    }
+
+    /**
+     * Compares two JSON values for deep equality.
+     * <p>
+     * This is a helper for overcoming the fact that {@link JsonValue} doesn't
+     * override {@link Object#equals(Object)} and
+     * {@link JsonValue#jsEquals(JsonValue)} is defined to use JavaScript
+     * semantics where arrays and objects are equals only based on identity.
+     *
+     * @param a
+     *            the first JSON value to check, may not be null
+     * @param b
+     *            the second JSON value to check, may not be null
+     * @return <code>true</code> if both JSON values are the same;
+     *         <code>false</code> otherwise
+     */
+    public static boolean jsonEquals(JsonValue a, JsonValue b) {
+        assert a != null;
+        assert b != null;
+
+        if (a == b) {
+            return true;
+        }
+
+        JsonType type = a.getType();
+        if (type != b.getType()) {
+            return false;
+        }
+
+        switch (type) {
+        case NULL:
+            return true;
+        case BOOLEAN:
+            return a.asBoolean() == b.asBoolean();
+        case NUMBER:
+            return Double.doubleToRawLongBits(a.asNumber()) == Double
+                    .doubleToRawLongBits(b.asNumber());
+        case STRING:
+            return a.asString().equals(b.asString());
+        case OBJECT:
+            return jsonObjectEquals((JsonObject) a, (JsonObject) b);
+        case ARRAY:
+            return jsonArrayEquals((JsonArray) a, (JsonArray) b);
+        default:
+            throw new IllegalArgumentException("Unsupported JsonType: " + type);
+        }
+    }
+
+    private static boolean jsonObjectEquals(JsonObject a, JsonObject b) {
+        assert a != null;
+        assert b != null;
+
+        if (a == b) {
+            return true;
+        }
+
+        String[] keys = a.keys();
+
+        if (keys.length != b.keys().length) {
+            return false;
+        }
+
+        for (String key : keys) {
+            JsonValue value = b.get(key);
+            if (value == null || !jsonEquals(a.get(key), value)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    private static boolean jsonArrayEquals(JsonArray a, JsonArray b) {
+        assert a != null;
+        assert b != null;
+
+        if (a == b) {
+            return true;
+        }
+
+        if (a.length() != b.length()) {
+            return false;
+        }
+        for (int i = 0; i < a.length(); i++) {
+            if (!jsonEquals(a.get(i), b.get(i))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Creates a stream from a JSON array.
+     *
+     * @param array
+     *            the JSON array to create a stream from
+     * @return a stream of JSON values
+     */
+    public static <T extends JsonValue> Stream<T> stream(JsonArray array) {
+        assert array != null;
+        return new AbstractList<T>() {
+            @Override
+            public T get(int index) {
+                return array.get(index);
+            }
+
+            @Override
+            public int size() {
+                return array.length();
+            }
+        }.stream();
+    }
+
+    /**
+     * Creates a stream from a JSON array of objects. This method does not
+     * verify that all items in the array are actually JSON objects instead of
+     * some other JSON type.
+     *
+     * @param array
+     *            the JSON array to create a stream from
+     * @return a stream of JSON objects
+     */
+    public static Stream<JsonObject> objectStream(JsonArray array) {
+        return stream(array);
+    }
+
+    /**
+     * Creates a double stream from a JSON array of numbers. This method does
+     * not verify that all items in the array are actually JSON numbers instead
+     * of some other JSON type.
+     *
+     * @param array
+     *            the JSON array to create a stream from
+     * @return a double stream of the values in the array
+     */
+    public static DoubleStream numberStream(JsonArray array) {
+        return JsonUtil.<JsonNumber> stream(array)
+                .mapToDouble(JsonNumber::getNumber);
+    }
+
+    /**
+     * Creates a collector that collects values into a JSON array.
+     *
+     * @return the collector
+     */
+    public static Collector<JsonValue, ?, JsonArray> asArray() {
+        return new JsonArrayCollector();
+    }
+
+    /**
+     * Creates a new JSON array with the given values.
+     *
+     * @param values
+     *            the values that should be in the created array
+     * @return the created array
+     */
+    public static JsonArray createArray(JsonValue... values) {
+        return Stream.of(values).collect(asArray());
+    }
+}
diff --git a/server/src/test/java/com/vaadin/data/util/JsonUtilTest.java b/server/src/test/java/com/vaadin/data/util/JsonUtilTest.java
new file mode 100644 (file)
index 0000000..80a3855
--- /dev/null
@@ -0,0 +1,205 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed 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 com.vaadin.data.util;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.DoubleStream;
+import java.util.stream.Stream;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import elemental.json.Json;
+import elemental.json.JsonArray;
+import elemental.json.JsonObject;
+import elemental.json.JsonValue;
+import elemental.json.impl.JreJsonNull;
+
+public class JsonUtilTest {
+    @Test
+    public void testEquals() {
+        // Equal
+        Assert.assertTrue(
+                JsonUtil.jsonEquals(Json.create(true), Json.create(true)));
+        Assert.assertTrue(
+                JsonUtil.jsonEquals(Json.create("foo"), Json.create("foo")));
+        Assert.assertTrue(
+                JsonUtil.jsonEquals(Json.create(3.14), Json.create(3.14)));
+        Assert.assertTrue(
+                JsonUtil.jsonEquals(Json.createNull(), Json.createNull()));
+        Assert.assertTrue(
+                JsonUtil.jsonEquals(createTestObject1(), createTestObject1()));
+        Assert.assertTrue(
+                JsonUtil.jsonEquals(createTestArray1(), createTestArray1()));
+
+        // Non-equal with matching types
+        Assert.assertFalse(
+                JsonUtil.jsonEquals(Json.create(true), Json.create(false)));
+        Assert.assertFalse(
+                JsonUtil.jsonEquals(Json.create("foo"), Json.create("oof")));
+        Assert.assertFalse(
+                JsonUtil.jsonEquals(Json.create(3.14), Json.create(3.142)));
+        Assert.assertFalse(
+                JsonUtil.jsonEquals(createTestObject1(), createTestObject2()));
+        Assert.assertFalse(
+                JsonUtil.jsonEquals(createTestArray1(), createTestArray2()));
+
+        // Non-equal with different types
+        Assert.assertFalse(
+                JsonUtil.jsonEquals(Json.create(true), Json.create("true")));
+        Assert.assertFalse(
+                JsonUtil.jsonEquals(Json.create(3.14), Json.create("3.14")));
+        Assert.assertFalse(
+                JsonUtil.jsonEquals(Json.createNull(), Json.create("null")));
+        Assert.assertFalse(
+                JsonUtil.jsonEquals(Json.createObject(), Json.create("{}")));
+        Assert.assertFalse(
+                JsonUtil.jsonEquals(Json.createArray(), Json.create(0)));
+        Assert.assertFalse(
+                JsonUtil.jsonEquals(createTestArray1(), createTestObject1()));
+    }
+
+    @Test(expected = AssertionError.class)
+    public void testEquals_firstNull_throws() {
+        JsonUtil.jsonEquals(null, Json.createNull());
+    }
+
+    @Test(expected = AssertionError.class)
+    public void testEquals_secondNull_throws() {
+        JsonUtil.jsonEquals(Json.createNull(), null);
+    }
+
+    private static JsonObject createTestObject1() {
+        JsonObject object = Json.createObject();
+
+        object.put("foo", "foo");
+        object.put("bar", createTestArray1());
+        object.put("baz", Json.createObject());
+
+        return object;
+    }
+
+    private static JsonObject createTestObject2() {
+        JsonObject object = Json.createObject();
+
+        object.put("foo", "oof");
+        object.put("bar", createTestArray2());
+        object.put("baz", Json.createArray());
+
+        return object;
+    }
+
+    private static JsonArray createTestArray1() {
+        return Stream.of(Json.create("foo"), Json.createObject())
+                .collect(JsonUtil.asArray());
+    }
+
+    private static JsonArray createTestArray2() {
+        return Stream.of(Json.create("bar"), Json.createArray())
+                .collect(JsonUtil.asArray());
+    }
+
+    @Test
+    public void collectEmptyStream() {
+        Stream<JsonValue> jsonValueStream = Stream.empty();
+        JsonArray a = jsonValueStream.collect(JsonUtil.asArray());
+        Assert.assertEquals(0, a.length());
+    }
+
+    @Test(expected = AssertionError.class)
+    public void createObjectStreamForNull() {
+        JsonUtil.objectStream(null);
+    }
+
+    @Test(expected = AssertionError.class)
+    public void createNumberStreamForNull() {
+        JsonUtil.numberStream(null);
+    }
+
+    @Test(expected = AssertionError.class)
+    public void createStreamForNull() {
+        JsonUtil.stream(null);
+    }
+
+    @Test
+    public void testStream() {
+        JsonArray array = createTestArray1();
+        List<JsonValue> list = JsonUtil.stream(array)
+                .collect(Collectors.toList());
+
+        Assert.assertEquals(2, list.size());
+        Assert.assertEquals("foo", list.get(0).asString());
+        Assert.assertTrue(
+                JsonUtil.jsonEquals(list.get(1), Json.createObject()));
+    }
+
+    @Test
+    public void testObjectStream() {
+        JsonArray array = Stream.of(Json.createObject(), createTestObject1(),
+                createTestObject2()).collect(JsonUtil.asArray());
+
+        List<JsonObject> objects = JsonUtil.objectStream(array)
+                .collect(Collectors.toList());
+
+        Assert.assertEquals(3, objects.size());
+        Assert.assertTrue(
+                JsonUtil.jsonEquals(Json.createObject(), objects.get(0)));
+        Assert.assertTrue(
+                JsonUtil.jsonEquals(createTestObject1(), objects.get(1)));
+        Assert.assertTrue(
+                JsonUtil.jsonEquals(createTestObject2(), objects.get(2)));
+    }
+
+    @Test
+    public void testNumberStream() {
+        double[] values = new double[] { 3.14, 42, Double.MAX_VALUE };
+
+        JsonArray array = DoubleStream.of(values).mapToObj(Json::create)
+                .collect(JsonUtil.asArray());
+
+        DoubleStream numberStream = JsonUtil.numberStream(array);
+
+        Assert.assertArrayEquals(values, numberStream.toArray(), 0);
+    }
+
+    @Test
+    public void testAsArray() {
+        Stream<JsonValue> stream = JsonUtil.stream(createTestArray1());
+
+        JsonArray array = stream.collect(JsonUtil.asArray());
+
+        Assert.assertTrue(JsonUtil.jsonEquals(createTestArray1(), array));
+    }
+
+    @Test
+    public void testCreateArray() {
+        JsonArray array = JsonUtil.createArray(Json.create("string"),
+                Json.createNull());
+
+        Assert.assertEquals(2, array.length());
+        Assert.assertEquals("string", array.getString(0));
+        Assert.assertSame(JreJsonNull.class, array.get(1).getClass());
+    }
+
+    @Test
+    public void testCreateEmptyArray() {
+        JsonArray array = JsonUtil.createArray();
+
+        Assert.assertEquals(0, array.length());
+    }
+
+}
index 964ba585eb6f1315ef6e4729661a7dc4e48d36e7..ea002f37f65b598342020114a6ea88c38552c405 100644 (file)
@@ -60,6 +60,7 @@ public class ClassesSerializableTest {
             "com\\.vaadin\\.buildhelpers.*", //
             "com\\.vaadin\\.util\\.ReflectTools.*", //
             "com\\.vaadin\\.data\\.util\\.ReflectTools.*", //
+            "com\\.vaadin\\.data\\.util\\.JsonUtil.*", //
             "com\\.vaadin\\.data\\.util.BeanItemContainerGenerator.*",
             "com\\.vaadin\\.data\\.util\\.sqlcontainer\\.connection\\.MockInitialContextFactory",
             "com\\.vaadin\\.data\\.util\\.sqlcontainer\\.DataGenerator",