Change-Id: I7c3cf7be95eaf451be806cb75b7b2a34fc534debtags/8.0.0.alpha2
@@ -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()); | |||
} | |||
} |
@@ -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()); | |||
} | |||
} |
@@ -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", |