--- /dev/null
+/*
+ * 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());
+ }
+}
--- /dev/null
+/*
+ * 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());
+ }
+
+}