diff --git a/build.sbt b/build.sbt index 2b9db2a..469b74a 100644 --- a/build.sbt +++ b/build.sbt @@ -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") \ No newline at end of file +antlr4PackageName in Antlr4 := Some("net.woggioni.worth.antlr") + +testOptions += Tests.Argument(TestFrameworks.JUnit, "-q", "-a") \ No newline at end of file diff --git a/src/main/java/net/woggioni/worth/serialization/binary/JBONDumper.java b/src/main/java/net/woggioni/worth/serialization/binary/JBONDumper.java index 7d0af1e..5dfdd58 100644 --- a/src/main/java/net/woggioni/worth/serialization/binary/JBONDumper.java +++ b/src/main/java/net/woggioni/worth/serialization/binary/JBONDumper.java @@ -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() { } diff --git a/src/main/java/net/woggioni/worth/traversal/ArrayStackElement.java b/src/main/java/net/woggioni/worth/traversal/ArrayStackElement.java new file mode 100644 index 0000000..41ad3b6 --- /dev/null +++ b/src/main/java/net/woggioni/worth/traversal/ArrayStackElement.java @@ -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 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(); + } +} diff --git a/src/main/java/net/woggioni/worth/traversal/ObjectStackElement.java b/src/main/java/net/woggioni/worth/traversal/ObjectStackElement.java new file mode 100644 index 0000000..25e1283 --- /dev/null +++ b/src/main/java/net/woggioni/worth/traversal/ObjectStackElement.java @@ -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> 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 result = it.next(); + currentKey = result.getKey(); + currentIndex++; + currentValue = result.getValue(); + return currentValue; + } + + @Override + boolean hasNext() { + return it.hasNext(); + } +} diff --git a/src/main/java/net/woggioni/worth/traversal/StackElement.java b/src/main/java/net/woggioni/worth/traversal/StackElement.java new file mode 100644 index 0000000..d9c014b --- /dev/null +++ b/src/main/java/net/woggioni/worth/traversal/StackElement.java @@ -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(); +} diff --git a/src/main/java/net/woggioni/worth/traversal/TraversalContext.java b/src/main/java/net/woggioni/worth/traversal/TraversalContext.java new file mode 100644 index 0000000..f340053 --- /dev/null +++ b/src/main/java/net/woggioni/worth/traversal/TraversalContext.java @@ -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 getStack(); + String getPath(); +} diff --git a/src/main/java/net/woggioni/worth/traversal/ValueVisitor.java b/src/main/java/net/woggioni/worth/traversal/ValueVisitor.java new file mode 100644 index 0000000..35fd165 --- /dev/null +++ b/src/main/java/net/woggioni/worth/traversal/ValueVisitor.java @@ -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); +} \ No newline at end of file diff --git a/src/main/java/net/woggioni/worth/traversal/ValueWalker.java b/src/main/java/net/woggioni/worth/traversal/ValueWalker.java new file mode 100644 index 0000000..4736a9a --- /dev/null +++ b/src/main/java/net/woggioni/worth/traversal/ValueWalker.java @@ -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 Optional map(Function callback) { + if(isPresent()) { + return Optional.of(callback.apply(parent)); + } else { + return Optional.empty(); + } + } + + public Optional flatMap(Function> 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 stack = new ArrayList<>(); + List immutableStack = Collections.unmodifiableList(stack); + + TraversalContext ctx = new TraversalContext() { + + @Override + public Value getRoot() { + return root; + } + + @Override + public List 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; + } + } + } + } +} diff --git a/src/main/java/net/woggioni/worth/utils/ValueWalker.java b/src/main/java/net/woggioni/worth/utils/ValueWalker.java deleted file mode 100644 index 26b42fa..0000000 --- a/src/main/java/net/woggioni/worth/utils/ValueWalker.java +++ /dev/null @@ -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 Optional map(Function callback) { - if(isPresent()) { - return Optional.of(callback.apply(parent)); - } else { - return Optional.empty(); - } - } - - public Optional flatMap(Function> 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; - } -} diff --git a/src/main/java/net/woggioni/worth/value/ObjectValue.java b/src/main/java/net/woggioni/worth/value/ObjectValue.java index f32bbeb..e8732d2 100644 --- a/src/main/java/net/woggioni/worth/value/ObjectValue.java +++ b/src/main/java/net/woggioni/worth/value/ObjectValue.java @@ -47,6 +47,7 @@ public interface ObjectValue extends Value, Iterable> { } } +@EqualsAndHashCode final class ObjectEntry implements Map.Entry { 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> value = new ArrayList<>(); public ListObjectValue(Map map) { diff --git a/src/test/java/net/woggioni/worth/serialization/binary/JBONTest.java b/src/test/java/net/woggioni/worth/serialization/binary/JBONTest.java index 66926b6..fa938b1 100644 --- a/src/test/java/net/woggioni/worth/serialization/binary/JBONTest.java +++ b/src/test/java/net/woggioni/worth/serialization/binary/JBONTest.java @@ -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); diff --git a/src/test/java/net/woggioni/worth/serialization/json/JSONTest.java b/src/test/java/net/woggioni/worth/serialization/json/JSONTest.java index 11c25e7..f7c4aef 100644 --- a/src/test/java/net/woggioni/worth/serialization/json/JSONTest.java +++ b/src/test/java/net/woggioni/worth/serialization/json/JSONTest.java @@ -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(); diff --git a/src/test/java/net/woggioni/worth/traversal/ValueWalkerTest.java b/src/test/java/net/woggioni/worth/traversal/ValueWalkerTest.java new file mode 100644 index 0000000..624daca --- /dev/null +++ b/src/test/java/net/woggioni/worth/traversal/ValueWalkerTest.java @@ -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 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 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 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 entry : ov) { + walk(result, entry.getValue()); + } + } + result.add(value); + } + + private List recursiveWalk(Value root) { + List 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); + } +} diff --git a/src/test/java/net/woggioni/worth/utils/ValueWalkerTest.java b/src/test/java/net/woggioni/worth/utils/ValueWalkerTest.java deleted file mode 100644 index 37019fd..0000000 --- a/src/test/java/net/woggioni/worth/utils/ValueWalkerTest.java +++ /dev/null @@ -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 text = valueWalker.get("widget").get("image").get("tags").get(1).map(Value::asString); - Assert.assertTrue(text.isPresent()); - Assert.assertEquals("Amazon", text.get()); - } -} diff --git a/src/test/java/net/woggioni/worth/value/ObjectValueImplementationTest.java b/src/test/java/net/woggioni/worth/value/ObjectValueImplementationTest.java index caab28c..a3e1b18 100644 --- a/src/test/java/net/woggioni/worth/value/ObjectValueImplementationTest.java +++ b/src/test/java/net/woggioni/worth/value/ObjectValueImplementationTest.java @@ -23,10 +23,8 @@ public class ObjectValueImplementationTest { public void test() { List>> 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 expectedClass = mapping.stream().filter(t -> t._1 == expectedImplementation).findFirst().get()._2; ObjectValue obj = ObjectValue.newInstance(); diff --git a/src/test/resources/test.json b/src/test/resources/test.json index 79787ff..03107c0 100644 --- a/src/test/resources/test.json +++ b/src/test/resources/test.json @@ -6,7 +6,7 @@ "title": "Sample Konfabulator Widget", "name": "main_window", "width": 500, - "height": 500 + "height": 501 }, "image": { "src": "Images/Sun.png",