added traversal library

fixed bug with missing .equals method implementation on net.woggioni.worth.value.ObjectEntry
fixed multithreaded testing
This commit is contained in:
Walter Oggioni
2019-07-19 20:07:32 +02:00
committed by Walter Oggioni
parent 2e4588a280
commit 89fb8ac1b7
16 changed files with 404 additions and 110 deletions

View File

@@ -16,10 +16,9 @@ scalacOptions ++= Seq(
git.useGitDescribe := true
fork := true
//javaOptions in Test += "-Dnet.woggioni.worth.value.ObjectValue.listBasedImplementation=true"
//javaOptions in Test += "-Xmx14G"
//scalafmtOnCompile := true
libraryDependencies += "org.projectlombok" % "lombok" % "1.18.2"
libraryDependencies += "org.projectlombok" % "lombok" % "1.18.8"
libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % Test
libraryDependencies += "com.fasterxml.jackson.core" % "jackson-databind" % "2.9.6" % Test
@@ -33,4 +32,6 @@ artifactName := { (sv: ScalaVersion, module: ModuleID, artifact: Artifact) =>
enablePlugins(Antlr4Plugin)
antlr4Version in Antlr4 := "4.7.1"
antlr4PackageName in Antlr4 := Some("net.woggioni.worth.antlr")
antlr4PackageName in Antlr4 := Some("net.woggioni.worth.antlr")
testOptions += Tests.Argument(TestFrameworks.JUnit, "-q", "-a")

View File

@@ -116,7 +116,6 @@ public class JBONDumper extends ValueDumper {
}
@Override
@SneakyThrows
protected void endObject() {
}
@@ -134,7 +133,6 @@ public class JBONDumper extends ValueDumper {
}
@Override
@SneakyThrows
protected void endArray() {
}

View File

@@ -0,0 +1,42 @@
package net.woggioni.worth.traversal;
import lombok.Getter;
import net.woggioni.worth.value.ArrayValue;
import net.woggioni.worth.xface.Value;
import java.util.Iterator;
public class ArrayStackElement extends StackElement {
@Getter
private final ArrayValue value;
private final Iterator<Value> it;
@Getter
private int currentIndex;
private Value currentValue = null;
ArrayStackElement(ArrayValue av) {
this.value = av;
it = av.iterator();
}
@Override
Value current() {
return currentValue;
}
@Override
Value next() {
currentValue = it.next();
currentIndex++;
return currentValue;
}
@Override
boolean hasNext() {
return it.hasNext();
}
}

View File

@@ -0,0 +1,49 @@
package net.woggioni.worth.traversal;
import lombok.Getter;
import net.woggioni.worth.value.ObjectValue;
import net.woggioni.worth.xface.Value;
import java.util.Iterator;
import java.util.Map;
public class ObjectStackElement extends StackElement {
private final Iterator<Map.Entry<String, Value>> it;
@Getter
private final ObjectValue value;
@Getter
private int currentIndex;
@Getter
private String currentKey;
private Value currentValue;
public ObjectStackElement(ObjectValue ov) {
value = ov;
it = ov.iterator();
currentIndex = -1;
}
@Override
Value current() {
return currentValue;
}
@Override
Value next() {
Map.Entry<String, Value> result = it.next();
currentKey = result.getKey();
currentIndex++;
currentValue = result.getValue();
return currentValue;
}
@Override
boolean hasNext() {
return it.hasNext();
}
}

View File

@@ -0,0 +1,9 @@
package net.woggioni.worth.traversal;
import net.woggioni.worth.xface.Value;
abstract class StackElement {
abstract Value current();
abstract Value next();
abstract boolean hasNext();
}

View File

@@ -0,0 +1,11 @@
package net.woggioni.worth.traversal;
import net.woggioni.worth.xface.Value;
import java.util.List;
public interface TraversalContext {
Value getRoot();
List<StackElement> getStack();
String getPath();
}

View File

@@ -0,0 +1,14 @@
package net.woggioni.worth.traversal;
import net.woggioni.worth.value.*;
public interface ValueVisitor {
void visit(ObjectValue value, TraversalContext ctx);
void visit(ArrayValue value, TraversalContext ctx);
void visit(BooleanValue value, TraversalContext ctx);
void visit(StringValue value, TraversalContext ctx);
void visit(IntegerValue value, TraversalContext ctx);
void visit(FloatValue value, TraversalContext ctx);
void visit(NullValue value, TraversalContext ctx);
}

View File

@@ -0,0 +1,153 @@
package net.woggioni.worth.traversal;
import net.woggioni.worth.value.*;
import net.woggioni.worth.xface.Value;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import static net.woggioni.worth.utils.WorthUtils.dynamicCast;
public class ValueWalker {
private Value parent;
public ValueWalker(Value root) {
parent = root;
}
public ValueWalker get(String key) {
if(parent.type() == Value.Type.OBJECT) {
parent = parent.get(key);
} else {
parent = Value.Null;
}
return this;
}
public ValueWalker get(int index) {
if(parent.type() == Value.Type.ARRAY) {
parent = parent.get(index);
} else {
parent = Value.Null;
}
return this;
}
public Value get() {
return parent;
}
public <T> Optional<T> map(Function<Value, T> callback) {
if(isPresent()) {
return Optional.of(callback.apply(parent));
} else {
return Optional.empty();
}
}
public <T> Optional<T> flatMap(Function<Value, Optional<T>> callback) {
if(isPresent()) {
return callback.apply(parent);
} else {
return Optional.empty();
}
}
public boolean isPresent() {
return !isEmpty();
}
public boolean isEmpty() {
return parent.type() == Value.Type.NULL;
}
public static void walk(Value root, ValueVisitor visitor) {
List<StackElement> stack = new ArrayList<>();
List<StackElement> immutableStack = Collections.unmodifiableList(stack);
TraversalContext ctx = new TraversalContext() {
@Override
public Value getRoot() {
return root;
}
@Override
public List<StackElement> getStack() {
return immutableStack;
}
@Override
public String getPath() {
StringBuilder sb = new StringBuilder();
for(StackElement se : stack) {
ArrayStackElement ase;
ObjectStackElement ose;
if((ase = dynamicCast(se, ArrayStackElement.class)) != null) {
sb.append("[");
sb.append(ase.getCurrentIndex());
sb.append("]");
} else if((ose = dynamicCast(se, ObjectStackElement.class)) != null) {
sb.append("[\"");
sb.append(ose.getCurrentKey());
sb.append("\"]");
}
}
return sb.toString();
}
};
ObjectValue ov;
ArrayValue av = new ArrayValue();
av.add(root);
ArrayStackElement ase = new ArrayStackElement(av);
stack.add(ase);
while(true) {
Value currentValue = stack.get(stack.size() - 1).next();
if((av = dynamicCast(currentValue, ArrayValue.class)) != null) {
ase = new ArrayStackElement(av);
stack.add(ase);
} else if((ov = dynamicCast(currentValue, ObjectValue.class)) != null) {
ObjectStackElement ose = new ObjectStackElement(ov);
stack.add(ose);
} else {
IntegerValue iv;
BooleanValue bv;
NullValue nv;
FloatValue fv;
StringValue sv;
if((iv = dynamicCast(currentValue, IntegerValue.class)) != null) {
visitor.visit(iv, ctx);
} else if((fv = dynamicCast(currentValue, FloatValue.class)) != null) {
visitor.visit(fv, ctx);
} else if((bv = dynamicCast(currentValue, BooleanValue.class)) != null) {
visitor.visit(bv, ctx);
} else if ((sv = dynamicCast(currentValue, StringValue.class)) != null) {
visitor.visit(sv, ctx);
} else if ((nv = dynamicCast(currentValue, NullValue.class)) != null) {
visitor.visit(nv, ctx);
}
}
while(true) {
if(stack.size() == 1) return;
int lastIndex = stack.size() - 1;
StackElement se = stack.get(lastIndex);
if(!se.hasNext()) {
ObjectStackElement ose;
if((ase = dynamicCast(se, ArrayStackElement.class)) != null) {
visitor.visit(ase.getValue(), ctx);
} else if((ose = dynamicCast(se, ObjectStackElement.class)) != null) {
visitor.visit(ose.getValue(), ctx);
}
stack.remove(lastIndex);
} else {
break;
}
}
}
}
}

View File

@@ -1,61 +0,0 @@
package net.woggioni.worth.utils;
import net.woggioni.worth.xface.Value;
import java.util.Optional;
import java.util.function.Function;
public class ValueWalker {
private Value parent;
public ValueWalker(Value root) {
parent = root;
}
public ValueWalker get(String key) {
if(parent.type() == Value.Type.OBJECT) {
parent = parent.get(key);
} else {
parent = Value.Null;
}
return this;
}
public ValueWalker get(int index) {
if(parent.type() == Value.Type.ARRAY) {
parent = parent.get(index);
} else {
parent = Value.Null;
}
return this;
}
public Value get() {
return parent;
}
public <T> Optional<T> map(Function<Value, T> callback) {
if(isPresent()) {
return Optional.of(callback.apply(parent));
} else {
return Optional.empty();
}
}
public <T> Optional<T> flatMap(Function<Value, Optional<T>> callback) {
if(isPresent()) {
return callback.apply(parent);
} else {
return Optional.empty();
}
}
public boolean isPresent() {
return !isEmpty();
}
public boolean isEmpty() {
return parent.type() == Value.Type.NULL;
}
}

View File

@@ -47,6 +47,7 @@ public interface ObjectValue extends Value, Iterable<Map.Entry<String, Value>> {
}
}
@EqualsAndHashCode
final class ObjectEntry<K, V> implements Map.Entry<K, V> {
private final K key;
private V value;
@@ -130,7 +131,7 @@ abstract class MapObjectValue implements ObjectValue {
}
}
@EqualsAndHashCode
@EqualsAndHashCode(callSuper = true)
class HashMapObjectValue extends MapObjectValue {
public HashMapObjectValue() {
@@ -138,7 +139,7 @@ class HashMapObjectValue extends MapObjectValue {
}
}
@EqualsAndHashCode
@EqualsAndHashCode(callSuper = true)
class LinkedHashMapObjectValue extends MapObjectValue {
public LinkedHashMapObjectValue() {
@@ -146,7 +147,7 @@ class LinkedHashMapObjectValue extends MapObjectValue {
}
}
@EqualsAndHashCode
@EqualsAndHashCode(callSuper = true)
class TreeMapObjectValue extends MapObjectValue {
public TreeMapObjectValue() {
@@ -159,6 +160,7 @@ class TreeMapObjectValue extends MapObjectValue {
@EqualsAndHashCode
class ListObjectValue implements ObjectValue {
@EqualsAndHashCode.Include
private final List<Map.Entry<String, Value>> value = new ArrayList<>();
public ListObjectValue(Map<String, Value> map) {

View File

@@ -26,11 +26,12 @@ public class JBONTest {
@Test
@SneakyThrows
public void consistencyTest() {
System.setProperty(ObjectValue.class.getName() + ".implementation", "TreeMap");
Value.Configuration cfg = Value.Configuration.builder()
.objectValueImplementation(ObjectValue.Implementation.TreeMap).build();
for (String testFile : testFiles) {
Value parsedValue;
try(InputStream is = getTestSource(testFile)) {
Parser parser = new JSONParser();
Parser parser = new JSONParser(cfg);
parsedValue = parser.parse(is);
}
byte[] dumpedJBON;
@@ -40,7 +41,7 @@ public class JBONTest {
}
Value reParsedValue;
try(InputStream is = new ByteArrayInputStream(dumpedJBON)) {
Parser parser = new JBONParser();
Parser parser = new JBONParser(cfg);
reParsedValue = parser.parse(is);
}
Assert.assertEquals(parsedValue, reParsedValue);

View File

@@ -25,8 +25,8 @@ public class JSONTest {
private String[] testFiles = new String[]{"/test.json", "/wordpress.json"};
private InputStream getTestSource(String filename) {
return getClass().getResourceAsStream(filename);
public static InputStream getTestSource(String filename) {
return JSONTest.class.getResourceAsStream(filename);
}
private boolean compareValueAndJsonNode(Value value, JsonNode jsonNode) {
@@ -186,16 +186,17 @@ public class JSONTest {
@Test
@SneakyThrows
public void consistencyTest() {
System.setProperty(ObjectValue.class.getName() + ".implementation", "ArrayList");
Value.Configuration cfg = Value.Configuration.builder()
.objectValueImplementation(ObjectValue.Implementation.ArrayList).build();
for (String testFile : testFiles) {
Parser parser = new JSONParser();
Parser parser = new JSONParser(cfg);
Value parsedValue = parser.parse(getTestSource(testFile));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
JSONDumper.newInstance().dump(parsedValue, baos);
String dumpedJSON = new String(baos.toByteArray());
byte[] barray = baos.toByteArray();
ByteArrayInputStream bais = new ByteArrayInputStream(barray);
parser = new JSONParser();
parser = new JSONParser(cfg);
Value reParsedValue = parser.parse(bais);
Assert.assertEquals(parsedValue, reParsedValue);
baos = new ByteArrayOutputStream();

View File

@@ -0,0 +1,105 @@
package net.woggioni.worth.traversal;
import lombok.SneakyThrows;
import net.woggioni.worth.serialization.json.JSONParser;
import net.woggioni.worth.serialization.json.JSONTest;
import net.woggioni.worth.utils.WorthUtils;
import net.woggioni.worth.value.*;
import net.woggioni.worth.xface.Parser;
import net.woggioni.worth.xface.Value;
import org.junit.Assert;
import org.junit.Test;
import java.io.BufferedInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class ValueWalkerTest {
@Test
@SneakyThrows
public void test() {
Value value;
try(InputStream is = new BufferedInputStream(getClass().getResourceAsStream("/test.json"))) {
Parser parser = JSONParser.newInstance();
value = parser.parse(is);
}
ValueWalker valueWalker = new ValueWalker(value);
Optional<String> text = valueWalker.get("widget").get("image").get("tags").get(1).map(Value::asString);
Assert.assertTrue(text.isPresent());
Assert.assertEquals("Amazon", text.get());
}
private static class TestVisitor implements ValueVisitor {
public List<Value> values = new ArrayList<>();
@Override
public void visit(ObjectValue value, TraversalContext ctx) {
values.add(value);
}
@Override
public void visit(ArrayValue value, TraversalContext ctx) {
values.add(value);
}
@Override
public void visit(BooleanValue value, TraversalContext ctx) {
values.add(value);
}
@Override
public void visit(StringValue value, TraversalContext ctx) {
values.add(value);
}
@Override
public void visit(IntegerValue value, TraversalContext ctx) {
values.add(value);
}
@Override
public void visit(FloatValue value, TraversalContext ctx) {
values.add(value);
}
@Override
public void visit(NullValue value, TraversalContext ctx) {
values.add(value);
}
}
private void walk(List<Value> result, Value value) {
ObjectValue ov;
ArrayValue av;
if((av = WorthUtils.dynamicCast(value, ArrayValue.class)) != null) {
for(Value v : av) {
walk(result, v);
}
} else if((ov = WorthUtils.dynamicCast(value, ObjectValue.class)) != null) {
for(Map.Entry<String, Value> entry : ov) {
walk(result, entry.getValue());
}
}
result.add(value);
}
private List<Value> recursiveWalk(Value root) {
List<Value> result = new ArrayList<>();
walk(result, root);
return result;
}
@Test
public void testWalk() {
Value v = JSONParser.newInstance().parse(JSONTest.getTestSource("/test.json"));
TestVisitor visitor = new TestVisitor();
ValueWalker.walk(v, visitor);
Assert.assertFalse(visitor.values.isEmpty());
Assert.assertEquals(recursiveWalk(v), visitor.values);
}
}

View File

@@ -1,29 +0,0 @@
package net.woggioni.worth.utils;
import lombok.SneakyThrows;
import net.woggioni.worth.serialization.json.JSONParser;
import net.woggioni.worth.xface.Parser;
import net.woggioni.worth.xface.Value;
import org.junit.Assert;
import org.junit.Test;
import java.io.BufferedInputStream;
import java.io.InputStream;
import java.util.Optional;
public class ValueWalkerTest {
@Test
@SneakyThrows
public void test() {
Value value;
try(InputStream is = new BufferedInputStream(getClass().getResourceAsStream("/test.json"))) {
Parser parser = JSONParser.newInstance();
value = parser.parse(is);
}
ValueWalker valueWalker = new ValueWalker(value);
Optional<String> text = valueWalker.get("widget").get("image").get("tags").get(1).map(Value::asString);
Assert.assertTrue(text.isPresent());
Assert.assertEquals("Amazon", text.get());
}
}

View File

@@ -23,10 +23,8 @@ public class ObjectValueImplementationTest {
public void test() {
List<Tuple2<ObjectValue.Implementation, Class<? extends ObjectValue>>> mapping =
getImplementationMapping();
System.setProperty(ObjectValue.class.getName() + ".implementation",
ObjectValue.Implementation.ArrayList.toString());
ObjectValue.Implementation expectedImplementation =
ObjectValue.Implementation.valueOf(System.getProperty(ObjectValue.class.getName() + ".implementation"));
ObjectValue.Implementation.valueOf(System.getProperty(ObjectValue.class.getName() + ".implementation", "TreeMap"));
Class<? extends ObjectValue> expectedClass =
mapping.stream().filter(t -> t._1 == expectedImplementation).findFirst().get()._2;
ObjectValue obj = ObjectValue.newInstance();

View File

@@ -6,7 +6,7 @@
"title": "Sample Konfabulator Widget",
"name": "main_window",
"width": 500,
"height": 500
"height": 501
},
"image": {
"src": "Images/Sun.png",