diff options
author | Artur Signell <artur@vaadin.com> | 2016-09-13 21:47:03 +0300 |
---|---|---|
committer | Artur Signell <artur@vaadin.com> | 2016-09-14 10:24:35 +0300 |
commit | 7c2c1e614a1de491473877aba06cd6d81b7b2530 (patch) | |
tree | d64c5b8e5269b127b7b81f221de7e827be343d03 /server | |
parent | 37e488d86b0bc59bf085a64eda3e258a01c496f0 (diff) | |
download | vaadin-framework-7c2c1e614a1de491473877aba06cd6d81b7b2530.tar.gz vaadin-framework-7c2c1e614a1de491473877aba06cd6d81b7b2530.zip |
Utility functions for helping to use elemental.json with Java 8
Change-Id: I7c3cf7be95eaf451be806cb75b7b2a34fc534deb
Diffstat (limited to 'server')
3 files changed, 453 insertions, 0 deletions
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 index 0000000000..93f9782ac6 --- /dev/null +++ b/server/src/main/java/com/vaadin/data/util/JsonUtil.java @@ -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 index 0000000000..80a3855a16 --- /dev/null +++ b/server/src/test/java/com/vaadin/data/util/JsonUtilTest.java @@ -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()); + } + +} diff --git a/server/src/test/java/com/vaadin/tests/server/ClassesSerializableTest.java b/server/src/test/java/com/vaadin/tests/server/ClassesSerializableTest.java index 964ba585eb..ea002f37f6 100644 --- a/server/src/test/java/com/vaadin/tests/server/ClassesSerializableTest.java +++ b/server/src/test/java/com/vaadin/tests/server/ClassesSerializableTest.java @@ -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", |