From 181d36d52e86d24e1f16c285edc98ea1b6c15ab7 Mon Sep 17 00:00:00 2001 From: Fabian Steeg Date: Thu, 18 Nov 2021 11:20:51 +0100 Subject: [PATCH 1/7] Don't add `[]` automatically, leave it to the user to set (#65) --- .../java/org/metafacture/metafix/Metafix.java | 2 +- .../metafacture/metafix/MetafixBindTest.java | 20 ++++++------- .../metafacture/metafix/MetafixIfTest.java | 18 +++++------ .../metafix/MetafixLookupTest.java | 10 +++---- .../metafix/MetafixMethodTest.java | 18 +++++------ .../metafix/MetafixRecordTest.java | 30 +++++++++---------- 6 files changed, 49 insertions(+), 49 deletions(-) diff --git a/metafix/src/main/java/org/metafacture/metafix/Metafix.java b/metafix/src/main/java/org/metafacture/metafix/Metafix.java index 1e03be87..a36505a2 100644 --- a/metafix/src/main/java/org/metafacture/metafix/Metafix.java +++ b/metafix/src/main/java/org/metafacture/metafix/Metafix.java @@ -149,7 +149,7 @@ private void emit(final String field, final Value value) { Value.asList(value, array -> { final boolean isMulti = array.size() > 1 || value.isArray(); if (isMulti) { - outputStreamReceiver.startEntity(field + "[]"); + outputStreamReceiver.startEntity(field); } for (int i = 0; i < array.size(); ++i) { diff --git a/metafix/src/test/java/org/metafacture/metafix/MetafixBindTest.java b/metafix/src/test/java/org/metafacture/metafix/MetafixBindTest.java index 71d066bf..96f6c6c1 100644 --- a/metafix/src/test/java/org/metafacture/metafix/MetafixBindTest.java +++ b/metafix/src/test/java/org/metafacture/metafix/MetafixBindTest.java @@ -62,7 +62,7 @@ public void doList() { i.endRecord(); }, o -> { o.get().startRecord("1"); - o.get().startEntity("author[]"); + o.get().startEntity("author"); o.get().literal("1", "A UNIVERSITY"); o.get().literal("2", "MAX"); o.get().endEntity(); @@ -88,7 +88,7 @@ public void doListPathWithDots() { i.endRecord(); }, o -> { o.get().startRecord("1"); - o.get().startEntity("author[]"); + o.get().startEntity("author"); o.get().literal("1", "A UNIVERSITY"); o.get().literal("2", "MAX"); o.get().endEntity(); @@ -100,9 +100,9 @@ public void doListPathWithDots() { public void doListWithAppendAndLast() { MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( "do list('path': 'creator', 'var': 'c')", - " set_array('author')", - " copy_field('c.name', 'author.$append.name')", - " add_field('author.$last.type', 'Default')", + " set_array('author[]')", + " copy_field('c.name', 'author[].$append.name')", + " add_field('author[].$last.type', 'Default')", "end", "remove_field('creator')"), i -> { @@ -149,7 +149,7 @@ public void doListEntitesToLiterals() { i.endRecord(); }, o -> { o.get().startRecord("1"); - o.get().startEntity("author[]"); + o.get().startEntity("author"); o.get().literal("1", "A UNIVERSITY"); o.get().literal("2", "MAX"); o.get().endEntity(); @@ -161,12 +161,12 @@ public void doListEntitesToLiterals() { public void doListEntitesToEntities() { MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( "do list('path': 'creator.name', 'var': 'c')", - " set_array('author')", - " copy_field('c', 'author.$append.name')", + " set_array('author[]')", + " copy_field('c', 'author[].$append.name')", " if all_contain('c', 'University')", - " add_field('author.$last.type', 'Organization')", + " add_field('author[].$last.type', 'Organization')", " else", - " add_field('author.$last.type', 'Person')", //", + " add_field('author[].$last.type', 'Person')", //", " end", "end", "remove_field('creator')"), diff --git a/metafix/src/test/java/org/metafacture/metafix/MetafixIfTest.java b/metafix/src/test/java/org/metafacture/metafix/MetafixIfTest.java index 958c49b1..bfa040f1 100644 --- a/metafix/src/test/java/org/metafacture/metafix/MetafixIfTest.java +++ b/metafix/src/test/java/org/metafacture/metafix/MetafixIfTest.java @@ -66,7 +66,7 @@ public void ifAny() { i.endRecord(); }, o -> { o.get().startRecord("1"); - o.get().startEntity("name[]"); + o.get().startEntity("name"); o.get().literal("1", "Mary"); o.get().literal("2", "A University"); o.get().endEntity(); @@ -74,7 +74,7 @@ public void ifAny() { o.get().endRecord(); o.get().startRecord("2"); - o.get().startEntity("name[]"); + o.get().startEntity("name"); o.get().literal("1", "Mary"); o.get().literal("2", "Max"); o.get().endEntity(); @@ -106,14 +106,14 @@ public void ifAll() { i.endRecord(); }, o -> { o.get().startRecord("1"); - o.get().startEntity("name[]"); + o.get().startEntity("name"); o.get().literal("1", "Mary"); o.get().literal("2", "A University"); o.get().endEntity(); o.get().endRecord(); o.get().startRecord("2"); - o.get().startEntity("name[]"); + o.get().startEntity("name"); o.get().literal("1", "Great University"); o.get().literal("2", "A University"); o.get().endEntity(); @@ -151,7 +151,7 @@ public void ifNone() { }, (o, f) -> { o.get().startRecord("1"); o.get().startEntity("author"); - o.get().startEntity("name[]"); + o.get().startEntity("name"); o.get().literal("1", "Mary"); o.get().literal("2", "A University"); f.apply(2).endEntity(); @@ -159,7 +159,7 @@ public void ifNone() { o.get().startRecord("2"); o.get().startEntity("author"); - o.get().startEntity("name[]"); + o.get().startEntity("name"); o.get().literal("1", "Max"); o.get().literal("2", "Mary"); f.apply(2).endEntity(); @@ -285,7 +285,7 @@ public void ifAnyMatch() { o.get().endRecord(); o.get().startRecord("2"); - o.get().startEntity("name[]"); + o.get().startEntity("name"); o.get().literal("1", "Some University"); o.get().literal("2", "Filibandrina"); o.get().endEntity(); @@ -312,14 +312,14 @@ public void ifAllMatch() { i.endRecord(); }, o -> { o.get().startRecord("1"); - o.get().startEntity("name[]"); + o.get().startEntity("name"); o.get().literal("1", "Max"); o.get().literal("2", "A University"); o.get().endEntity(); o.get().endRecord(); o.get().startRecord("2"); - o.get().startEntity("name[]"); + o.get().startEntity("name"); o.get().literal("1", "Some University"); o.get().literal("2", "University Filibandrina"); o.get().endEntity(); diff --git a/metafix/src/test/java/org/metafacture/metafix/MetafixLookupTest.java b/metafix/src/test/java/org/metafacture/metafix/MetafixLookupTest.java index a9fc99a2..25f9ad5d 100644 --- a/metafix/src/test/java/org/metafacture/metafix/MetafixLookupTest.java +++ b/metafix/src/test/java/org/metafacture/metafix/MetafixLookupTest.java @@ -67,7 +67,7 @@ public void inline() { o.get().endRecord(); o.get().startRecord("2"); - o.get().startEntity("title[]"); + o.get().startEntity("title"); o.get().literal("1", "Alohaeha"); o.get().literal("2", "Moin zäme"); o.get().literal("3", "Tach"); @@ -92,7 +92,7 @@ public void inlineMultilineIndent() { i.endRecord(); }, o -> { o.get().startRecord("1"); - o.get().startEntity("title[]"); + o.get().startEntity("title"); o.get().literal("1", "Alohaeha"); o.get().literal("2", "Moin zäme"); o.get().endEntity(); @@ -115,7 +115,7 @@ public void inlineDotNotationNested() { }, (o, f) -> { o.get().startRecord("1"); o.get().startEntity("data"); - o.get().startEntity("title[]"); + o.get().startEntity("title"); o.get().literal("1", "Alohaeha"); o.get().literal("2", "Moin zäme"); o.get().literal("3", "Tach"); @@ -146,7 +146,7 @@ public void csv() { o.get().endRecord(); o.get().startRecord("2"); - o.get().startEntity("title[]"); + o.get().startEntity("title"); o.get().literal("1", "Alohaeha"); o.get().literal("2", "Moin zäme"); o.get().literal("3", "Tach"); @@ -179,7 +179,7 @@ public void tsv() { o.get().endRecord(); o.get().startRecord("2"); - o.get().startEntity("title[]"); + o.get().startEntity("title"); o.get().literal("1", "Alohaeha"); o.get().literal("2", "Moin zäme"); o.get().literal("3", "Tach"); diff --git a/metafix/src/test/java/org/metafacture/metafix/MetafixMethodTest.java b/metafix/src/test/java/org/metafacture/metafix/MetafixMethodTest.java index c27bb225..d2219bfd 100644 --- a/metafix/src/test/java/org/metafacture/metafix/MetafixMethodTest.java +++ b/metafix/src/test/java/org/metafacture/metafix/MetafixMethodTest.java @@ -70,7 +70,7 @@ public void upcase() { o.get().endRecord(); o.get().startRecord("2"); - o.get().startEntity("title[]"); + o.get().startEntity("title"); o.get().literal("1", "MARC"); o.get().literal("2", "JSON"); o.get().endEntity(); @@ -95,7 +95,7 @@ public void upcaseDotNotationNested() { }, (o, f) -> { o.get().startRecord("1"); o.get().startEntity("data"); - o.get().startEntity("title[]"); + o.get().startEntity("title"); o.get().literal("1", "MARC"); o.get().literal("2", "JSON"); f.apply(2).endEntity(); @@ -123,7 +123,7 @@ public void downcase() { o.get().endRecord(); o.get().startRecord("2"); - o.get().startEntity("title[]"); + o.get().startEntity("title"); o.get().literal("1", "marc"); o.get().literal("2", "json"); o.get().endEntity(); @@ -154,7 +154,7 @@ public void capitalize() { o.get().endRecord(); o.get().startRecord("2"); - o.get().startEntity("title[]"); + o.get().startEntity("title"); o.get().literal("1", "Marc"); o.get().literal("2", "Json"); o.get().endEntity(); @@ -185,7 +185,7 @@ public void substring() { o.get().endRecord(); o.get().startRecord("2"); - o.get().startEntity("title[]"); + o.get().startEntity("title"); o.get().literal("1", "m"); o.get().literal("2", "j"); o.get().endEntity(); @@ -217,7 +217,7 @@ public void substringWithVar() { o.get().endRecord(); o.get().startRecord("2"); - o.get().startEntity("title[]"); + o.get().startEntity("title"); o.get().literal("1", "ma"); o.get().literal("2", "js"); o.get().endEntity(); @@ -248,7 +248,7 @@ public void trim() { o.get().endRecord(); o.get().startRecord("2"); - o.get().startEntity("title[]"); + o.get().startEntity("title"); o.get().literal("1", "marc"); o.get().literal("2", "json"); o.get().endEntity(); @@ -270,7 +270,7 @@ public void format() { i.endRecord(); }, o -> { o.get().startRecord("1"); - o.get().startEntity("number[]"); + o.get().startEntity("number"); o.get().literal("1", "41 : 15"); o.get().endEntity(); o.get().endRecord(); @@ -287,7 +287,7 @@ public void parseText() { i.endRecord(); }, o -> { o.get().startRecord("1"); - o.get().startEntity("date[]"); + o.get().startEntity("date"); o.get().literal("1", "2015"); o.get().literal("2", "03"); o.get().literal("3", "07"); diff --git a/metafix/src/test/java/org/metafacture/metafix/MetafixRecordTest.java b/metafix/src/test/java/org/metafacture/metafix/MetafixRecordTest.java index aef164ed..b560038e 100644 --- a/metafix/src/test/java/org/metafacture/metafix/MetafixRecordTest.java +++ b/metafix/src/test/java/org/metafacture/metafix/MetafixRecordTest.java @@ -129,7 +129,7 @@ public void entitiesPassThroughRepeatNestedEntity() { o.get().startRecord("1"); o.get().startEntity("deep"); o.get().startEntity("nested"); - o.get().startEntity("field[]"); + o.get().startEntity("field"); o.get().literal("1", "value1"); o.get().literal("2", "value2"); f.apply(3).endEntity(); @@ -211,7 +211,7 @@ public void add() { }, (o, f) -> { o.get().startRecord("1"); o.get().startEntity("my"); - o.get().startEntity("name[]"); + o.get().startEntity("name"); o.get().literal("1", "patrick"); o.get().literal("2", "nicolas"); f.apply(2).endEntity(); @@ -219,7 +219,7 @@ public void add() { o.get().startRecord("2"); o.get().startEntity("my"); - o.get().startEntity("name[]"); + o.get().startEntity("name"); o.get().literal("1", "max"); o.get().literal("2", "patrick"); o.get().literal("3", "nicolas"); @@ -228,7 +228,7 @@ public void add() { o.get().startRecord("3"); o.get().startEntity("my"); - o.get().startEntity("name[]"); + o.get().startEntity("name"); o.get().literal("1", "patrick"); o.get().literal("2", "nicolas"); f.apply(2).endEntity(); @@ -306,7 +306,7 @@ public void copy() { public void copyIntoArrayOfStrings() { MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( // "set_array('author')", <- results in separate objects/entities here - "copy_field('your.name','author.name')", + "copy_field('your.name','author.name[]')", "remove_field('your')"), i -> { i.startRecord("1"); @@ -329,8 +329,8 @@ public void copyIntoArrayOfStrings() { @Test public void copyIntoArrayOfObjects() { MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( - "set_array('author')", - "copy_field('your.name','author.name')", + "set_array('author[]')", + "copy_field('your.name','author[].name')", "remove_field('your')"), i -> { i.startRecord("1"); @@ -357,8 +357,8 @@ public void copyIntoArrayOfObjects() { @Test public void copyIntoArrayTopLevel() { MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( - "set_array('author')", - "copy_field('your.name', 'author')", + "set_array('author[]')", + "copy_field('your.name', 'author[]')", "remove_field('your')"), i -> { i.startRecord("1"); @@ -467,7 +467,7 @@ public void removeEntity() { @Test public void setArray() { MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( - "set_array('foo','a','b','c')"), + "set_array('foo[]','a','b','c')"), i -> { i.startRecord("1"); i.endRecord(); @@ -594,7 +594,7 @@ public void arrayFromHash() { i.endRecord(); }, o -> { o.get().startRecord("1"); - o.get().startEntity("foo[]"); + o.get().startEntity("foo"); o.get().literal("1", "a"); o.get().literal("2", "b"); o.get().literal("3", "c"); @@ -625,8 +625,8 @@ public void reject() { @Test public void appendArray() { MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( - "set_array('nums', '1')", - "set_array('nums.$append', '2', '3')"), + "set_array('nums[]', '1')", + "set_array('nums[].$append', '2', '3')"), i -> { i.startRecord("1"); i.endRecord(); @@ -644,8 +644,8 @@ public void appendArray() { @Test public void mixedArray() { MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( - "set_array('@context', 'https://w3id.org/kim/lrmi-profile/draft/context.jsonld')", - "set_hash('@context.$append', '@language': 'de')"), + "set_array('@context[]', 'https://w3id.org/kim/lrmi-profile/draft/context.jsonld')", + "set_hash('@context[].$append', '@language': 'de')"), i -> { i.startRecord("1"); i.endRecord(); From bd902fdbb50e7974f3dcd9ba641230b7b2e034bc Mon Sep 17 00:00:00 2001 From: Fabian Steeg Date: Fri, 19 Nov 2021 16:36:40 +0100 Subject: [PATCH 2/7] Use index as entity name in arrays of objects (#65) --- .../src/main/java/org/metafacture/metafix/Metafix.java | 2 +- .../java/org/metafacture/metafix/MetafixBindTest.java | 8 ++++---- .../java/org/metafacture/metafix/MetafixRecordTest.java | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/metafix/src/main/java/org/metafacture/metafix/Metafix.java b/metafix/src/main/java/org/metafacture/metafix/Metafix.java index a36505a2..05b8671c 100644 --- a/metafix/src/main/java/org/metafacture/metafix/Metafix.java +++ b/metafix/src/main/java/org/metafacture/metafix/Metafix.java @@ -156,7 +156,7 @@ private void emit(final String field, final Value value) { final Value arrayValue = array.get(i); if (arrayValue.isHash()) { - outputStreamReceiver.startEntity(isMulti ? "" : field); + outputStreamReceiver.startEntity(isMulti ? (i + 1) + "" : field); arrayValue.asHash().forEach(this::emit); outputStreamReceiver.endEntity(); } diff --git a/metafix/src/test/java/org/metafacture/metafix/MetafixBindTest.java b/metafix/src/test/java/org/metafacture/metafix/MetafixBindTest.java index 96f6c6c1..5b060372 100644 --- a/metafix/src/test/java/org/metafacture/metafix/MetafixBindTest.java +++ b/metafix/src/test/java/org/metafacture/metafix/MetafixBindTest.java @@ -117,11 +117,11 @@ public void doListWithAppendAndLast() { }, (o, f) -> { o.get().startRecord("1"); o.get().startEntity("author[]"); - o.get().startEntity(""); + o.get().startEntity("1"); o.get().literal("name", "A University"); // o.get().literal("type", "Default"); // FIXME: bind scope broken o.get().endEntity(); - o.get().startEntity(""); + o.get().startEntity("2"); o.get().literal("name", "Max"); o.get().literal("type", "Default"); f.apply(2).endEntity(); @@ -182,11 +182,11 @@ public void doListEntitesToEntities() { }, (o, f) -> { o.get().startRecord("1"); o.get().startEntity("author[]"); - o.get().startEntity(""); + o.get().startEntity("1"); o.get().literal("name", "A University"); o.get().literal("type", "Organization"); o.get().endEntity(); - o.get().startEntity(""); + o.get().startEntity("2"); o.get().literal("name", "Max"); o.get().literal("type", "Person"); f.apply(2).endEntity(); diff --git a/metafix/src/test/java/org/metafacture/metafix/MetafixRecordTest.java b/metafix/src/test/java/org/metafacture/metafix/MetafixRecordTest.java index b560038e..45dddecb 100644 --- a/metafix/src/test/java/org/metafacture/metafix/MetafixRecordTest.java +++ b/metafix/src/test/java/org/metafacture/metafix/MetafixRecordTest.java @@ -344,10 +344,10 @@ public void copyIntoArrayOfObjects() { }, (o, f) -> { o.get().startRecord("1"); o.get().startEntity("author[]"); - o.get().startEntity(""); + o.get().startEntity("1"); o.get().literal("name", "max"); o.get().endEntity(); - o.get().startEntity(""); + o.get().startEntity("2"); o.get().literal("name", "mo"); f.apply(2).endEntity(); o.get().endRecord(); @@ -653,7 +653,7 @@ public void mixedArray() { o.get().startRecord("1"); o.get().startEntity("@context[]"); o.get().literal("1", "https://w3id.org/kim/lrmi-profile/draft/context.jsonld"); - o.get().startEntity(""); + o.get().startEntity("2"); o.get().literal("@language", "de"); f.apply(2).endEntity(); o.get().endRecord(); From 7b0cc120e4bcee96d99c6f6f24ba5327e14eab12 Mon Sep 17 00:00:00 2001 From: Fabian Steeg Date: Fri, 19 Nov 2021 17:02:01 +0100 Subject: [PATCH 3/7] Support accessing strings in arrays via index and wildcard (#65) --- .../java/org/metafacture/metafix/Value.java | 49 +++++- .../metafix/MetafixRecordTest.java | 165 +++++++++++++++--- 2 files changed, 183 insertions(+), 31 deletions(-) diff --git a/metafix/src/main/java/org/metafacture/metafix/Value.java b/metafix/src/main/java/org/metafacture/metafix/Value.java index f31f4259..0f120bd8 100644 --- a/metafix/src/main/java/org/metafacture/metafix/Value.java +++ b/metafix/src/main/java/org/metafacture/metafix/Value.java @@ -148,6 +148,10 @@ public static boolean isNull(final Value value) { return value == null || value.isNull(); } + private static boolean isNumber(final String s) { + return s.matches("\\d+"); + } + public Array asArray() { if (isArray()) { return array; @@ -292,6 +296,21 @@ public String asString() { return list.toString(); } + public void remove(final int index) { + list.remove(index); + } + + private void removeNested(final String[] fields, final Value value) { + if (fields.length > 1 && isNumber(fields[1])) { + final int index = Integer.parseInt(fields[1]) - 1; + if (index >= 0 && index < value.asArray().size()) { + if (value.asArray().get(index).isString()) { + value.asArray().remove(index); + } + } + } + } + } /** @@ -397,7 +416,18 @@ private Value find(final String[] fields) { private Value findNested(final String field, final String[] remainingFields) { final Value value = get(field); - // TODO: array of maps, like in insert nested + if (value.isArray()) { + if (remainingFields.length > 0 && isNumber(remainingFields[0])) { + final int index = Integer.parseInt(remainingFields[0]) - 1; + if (index >= 0 && index < value.asArray().size()) { + final Value nestedValue = value.asArray().get(index); + if (nestedValue.isString()) { + return nestedValue; + } + // TODO: array of maps, like in insert nested + } + } + } if (value.isHash()) { return value.asHash().find(remainingFields); @@ -467,7 +497,12 @@ else if (value.isArray()) { } break; default: - array.add(newHash(h -> h.insert(mode, remainingFields, newValue))); + if (isNumber(remainingFields[0])) { + array.add(new Value(newValue)); + } + else { + array.add(newHash(h -> h.insert(mode, remainingFields, newValue))); + } break; } } @@ -499,7 +534,13 @@ private void removeNested(final String[] fields) { remove(field); } else if (containsField(field)) { - get(field).asHash().removeNested(Arrays.copyOfRange(fields, 1, fields.length)); + final Value value = get(field); + if (value.isArray()) { + value.asArray().removeNested(fields, value); + } + if (value.isHash()) { + value.asHash().removeNested(Arrays.copyOfRange(fields, 1, fields.length)); + } } } @@ -510,7 +551,7 @@ public void copy(final List params) { } public void transformFields(final List params, final UnaryOperator operator) { - final String field = params.get(0); + final String field = params.get(0).replace(".*", ""); final Value value = find(field); if (value != null) { diff --git a/metafix/src/test/java/org/metafacture/metafix/MetafixRecordTest.java b/metafix/src/test/java/org/metafacture/metafix/MetafixRecordTest.java index 45dddecb..1a0ce1f3 100644 --- a/metafix/src/test/java/org/metafacture/metafix/MetafixRecordTest.java +++ b/metafix/src/test/java/org/metafacture/metafix/MetafixRecordTest.java @@ -83,33 +83,6 @@ public void internalIdUsage() { }); } - @Test - @Disabled("TODO: how to handle repeated entities: turn to array vs. merge because it's the same?") - public void entitiesPassThroughRepeatEntity() { - MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( - "vacuum()"), - i -> { - i.startRecord("1"); - i.startEntity("some"); - i.literal("field", "value1"); - i.endEntity(); - i.startEntity("some"); - i.literal("field", "value2"); - i.endEntity(); - i.endRecord(); - }, (o, f) -> { - o.get().startRecord("1"); - o.get().startEntity("some[]"); - o.get().startEntity(""); - o.get().literal("field", "value1"); - o.get().endEntity(); - o.get().startEntity(""); - o.get().literal("field", "value2"); - f.apply(2).endEntity(); - o.get().endRecord(); - }); - } - @Test public void entitiesPassThroughRepeatNestedEntity() { MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( @@ -715,4 +688,142 @@ public void nulls() { o.get().endRecord(); }); } + + @Test + public void repeatToArray() { + MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( + "vacuum()"), + i -> { + i.startRecord("1"); + i.literal("name", "max"); + i.literal("name", "mo"); + i.endRecord(); + }, (o, f) -> { + o.get().startRecord("1"); + o.get().startEntity("name"); + o.get().literal("1", "max"); + o.get().literal("2", "mo"); + o.get().endEntity(); + o.get().endRecord(); + }); + } + + @Test + public void accessArrayByIndex() { + MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( + "upcase('name.2')"), + i -> { + i.startRecord("1"); + i.literal("name", "max"); + i.literal("name", "mo"); + i.endRecord(); + }, (o, f) -> { + o.get().startRecord("1"); + o.get().startEntity("name"); + o.get().literal("1", "max"); + o.get().literal("2", "MO"); + o.get().endEntity(); + o.get().endRecord(); + }); + } + + @Test + public void accessArrayByWildcard() { + MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( + "upcase('name.*')"), + i -> { + i.startRecord("1"); + i.literal("name", "max"); + i.literal("name", "mo"); + i.endRecord(); + }, (o, f) -> { + o.get().startRecord("1"); + o.get().startEntity("name"); + o.get().literal("1", "MAX"); + o.get().literal("2", "MO"); + o.get().endEntity(); + o.get().endRecord(); + }); + } + + @Test + @Disabled("TODO: Repeated entities should become arrays, see #65") + public void repeatToArrayOfObjects() { + MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( + "vacuum()"), + i -> { + i.startRecord("1"); + i.startEntity("author"); + i.literal("name", "max"); + i.endEntity(); + i.startEntity("author"); + i.literal("name", "mo"); + i.endEntity(); + i.endRecord(); + }, (o, f) -> { + o.get().startRecord("1"); + o.get().startEntity("author"); + o.get().startEntity("1"); + o.get().literal("name", "max"); + o.get().endEntity(); + o.get().startEntity("2"); + o.get().literal("name", "mo"); + f.apply(2).endEntity(); + o.get().endRecord(); + }); + } + + @Test + @Disabled("TODO: Access arrays of objects by index, see #65") + public void accessArrayOfObjectsByIndex() { + MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( + "upcase('author.2.name')"), + i -> { + i.startRecord("1"); + i.startEntity("author"); + i.literal("name", "max"); + i.endEntity(); + i.startEntity("author"); + i.literal("name", "mo"); + i.endEntity(); + i.endRecord(); + }, (o, f) -> { + o.get().startRecord("1"); + o.get().startEntity("author"); + o.get().startEntity("1"); + o.get().literal("name", "max"); + o.get().endEntity(); + o.get().startEntity("2"); + o.get().literal("name", "MO"); + f.apply(2).endEntity(); + o.get().endRecord(); + }); + } + + @Test + @Disabled("TODO: Access arrays of objects by wildcard, see #65") + public void accessArrayOfObjectsByWildcard() { + MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( + "upcase('author.*.name')"), + i -> { + i.startRecord("1"); + i.startEntity("author"); + i.literal("name", "max"); + i.endEntity(); + i.startEntity("author"); + i.literal("name", "mo"); + i.endEntity(); + i.endRecord(); + }, (o, f) -> { + o.get().startRecord("1"); + o.get().startEntity("author"); + o.get().startEntity("1"); + o.get().literal("name", "MAX"); + o.get().endEntity(); + o.get().startEntity("2"); + o.get().literal("name", "MO"); + f.apply(2).endEntity(); + o.get().endRecord(); + }); + } } From 5fa6e03b06324ab3259101a14d2038375bd2829a Mon Sep 17 00:00:00 2001 From: Fabian Steeg Date: Thu, 25 Nov 2021 16:56:35 +0100 Subject: [PATCH 4/7] Handle and access repeated entities as arrays of hashes (#65) --- .../org/metafacture/metafix/FixPredicate.java | 2 +- .../java/org/metafacture/metafix/Metafix.java | 18 +- .../java/org/metafacture/metafix/Value.java | 239 ++++++++++++------ .../metafacture/metafix/MetafixBindTest.java | 47 ++-- .../metafix/MetafixRecordTest.java | 44 +++- 5 files changed, 235 insertions(+), 115 deletions(-) diff --git a/metafix/src/main/java/org/metafacture/metafix/FixPredicate.java b/metafix/src/main/java/org/metafacture/metafix/FixPredicate.java index 866eafc2..6d280c58 100644 --- a/metafix/src/main/java/org/metafacture/metafix/FixPredicate.java +++ b/metafix/src/main/java/org/metafacture/metafix/FixPredicate.java @@ -31,7 +31,7 @@ public Predicate of(final String string) { equal { @Override public Predicate of(final String string) { - return v -> v.toString().equals(string); + return v -> v.toString().equals(string); } }, match { diff --git a/metafix/src/main/java/org/metafacture/metafix/Metafix.java b/metafix/src/main/java/org/metafacture/metafix/Metafix.java index 05b8671c..e3daed4d 100644 --- a/metafix/src/main/java/org/metafacture/metafix/Metafix.java +++ b/metafix/src/main/java/org/metafacture/metafix/Metafix.java @@ -182,21 +182,9 @@ public void startEntity(final String name) { entities.size() <= currentEntityIndex ? null : entities.get(currentEntityIndex); entityCountStack.push(Integer.valueOf(entityCount)); flattener.startEntity(name); - entities.add(currentEntity(name, previousEntity != null ? previousEntity : currentRecord)); - } - - private Value.Hash currentEntity(final String name, final Value.Hash previousEntity) { - final Value existingValue = previousEntity != null ? previousEntity.get(name) : null; - final Value.Hash currentEntity; - if (existingValue != null && existingValue.isHash()) { - currentEntity = previousEntity.get(name).asHash(); - } - else { - final Value value = Value.newHash(); - currentEntity = value.asHash(); - (previousEntity != null ? previousEntity : currentRecord).add(name, value); - } - return currentEntity; + final Value value = Value.newHash(); + (previousEntity != null ? previousEntity : currentRecord).add(name, value); + entities.add(value.asHash()); } @Override diff --git a/metafix/src/main/java/org/metafacture/metafix/Value.java b/metafix/src/main/java/org/metafacture/metafix/Value.java index 0f120bd8..e041adf5 100644 --- a/metafix/src/main/java/org/metafacture/metafix/Value.java +++ b/metafix/src/main/java/org/metafacture/metafix/Value.java @@ -25,6 +25,7 @@ import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.UnaryOperator; +import java.util.stream.Collectors; import java.util.stream.Stream; /** @@ -33,6 +34,8 @@ */ public class Value { + private static final String ASTERISK = "*"; + private final Array array; private final Hash hash; private final String string; @@ -203,14 +206,7 @@ public Value asList(final Consumer consumer) { } public Value merge(final Value value) { - if (isHash() && value.isHash()) { - final Hash asHash = asHash(); - value.asHash().forEach(asHash::put); - return this; - } - else { - return asList(a1 -> value.asList(a2 -> a2.forEach(a1::add))); - } + return asList(a1 -> value.asList(a2 -> a2.forEach(a1::add))); } @Override @@ -239,6 +235,10 @@ public String toString() { return result; } + static String[] tail(final String[] fields) { + return Arrays.copyOfRange(fields, 1, fields.length); + } + enum Type { Array, Hash, @@ -300,17 +300,61 @@ public void remove(final int index) { list.remove(index); } - private void removeNested(final String[] fields, final Value value) { - if (fields.length > 1 && isNumber(fields[1])) { - final int index = Integer.parseInt(fields[1]) - 1; - if (index >= 0 && index < value.asArray().size()) { - if (value.asArray().get(index).isString()) { - value.asArray().remove(index); - } + private void removeNested(final String[] fields) { + if (fields.length >= 1 && fields[0].equals(ASTERISK)) { + for (int i = 0; i < size(); ++i) { + remove(i); + } + } + else if (fields.length >= 1 && isNumber(fields[0])) { + final int index = Integer.parseInt(fields[0]) - 1; + if (index >= 0 && index < size()) { + remove(index); } } } + public Value find(final String[] fields) { + Value result = null; + if (fields.length > 0) { + if (fields[0].equals(ASTERISK)) { + result = find(tail(fields)); + } + else if (isNumber(fields[0])) { + final int index = Integer.parseInt(fields[0]) - 1; + if (index >= 0 && index < size()) { + final Value value = get(index); + // TODO: move impl into enum elements, here call only value.find + if (value != null) { + switch (value.type) { + case Hash: + result = value.asHash().find(tail(fields)); + break; + case Array: + result = find(tail(fields)); + break; + case String: + result = value; + break; + default: + break; + } + } + } + } + else { + final Value newResult = newArray(); + forEach(c -> { + newResult.asArray().add(c.asHash().find(fields[0])); /* TODO: non-hash */ + }); + result = newResult; + } + } + else { + result = new Value(this); + } + return result; + } } /** @@ -410,30 +454,37 @@ private Value find(final String[] fields) { final String field = fields[0]; return fields.length == 1 || !containsField(field) ? get(field) : - findNested(field, Arrays.copyOfRange(fields, 1, fields.length)); + findNested(field, tail(fields)); } private Value findNested(final String field, final String[] remainingFields) { final Value value = get(field); - - if (value.isArray()) { - if (remainingFields.length > 0 && isNumber(remainingFields[0])) { - final int index = Integer.parseInt(remainingFields[0]) - 1; - if (index >= 0 && index < value.asArray().size()) { - final Value nestedValue = value.asArray().get(index); - if (nestedValue.isString()) { - return nestedValue; + Value result = null; + if (value != null) { + switch (value.type) { + case Array: + if (remainingFields[0].equals(ASTERISK)) { + result = value.asArray().find(tail(remainingFields)); } - // TODO: array of maps, like in insert nested - } + else { + result = value.asArray().find(remainingFields); + } + break; + case Hash: + if (remainingFields[0].equals(ASTERISK)) { + result = value.asHash().find(tail(remainingFields)); + } + else { + result = value.asHash().find(remainingFields); + } + break; + case String: + throw new IllegalStateException("expected string, got " + value.type); + default: + throw new IllegalStateException("unexpected, got " + value.type); } } - - if (value.isHash()) { - return value.asHash().find(remainingFields); - } - - throw new IllegalStateException("expected hash, got " + value.type); + return result; } public Value findList(final String fieldPath, final Consumer consumer) { @@ -468,7 +519,7 @@ public Value insert(final InsertMode mode, final String fieldPath, final String private Value insert(final InsertMode mode, final String[] fields, final String newValue) { final String field = fields[0]; - if (fields.length == 1) { + if (fields.length == 1 && !fields[0].equals(ASTERISK)) { mode.apply(this, field, newValue); } else { @@ -476,44 +527,64 @@ private Value insert(final InsertMode mode, final String[] fields, final String put(field, newHash()); } - final String[] remainingFields = Arrays.copyOfRange(fields, 1, fields.length); - final String[] nestedFields = Arrays.copyOfRange(remainingFields, 1, remainingFields.length); final Value value = get(field); - - if (value.isHash()) { - value.asHash().insert(mode, remainingFields, newValue); - } - else if (value.isArray()) { - final Array array = value.asArray(); - - switch (remainingFields[0]) { - case APPEND_FIELD: - array.add(newHash(h -> h.insert(mode, nestedFields, newValue))); + if (value != null) { + switch (value.type) { + // TODO: move impl into enum elements, here call only value.insert + case Hash: + final String[] tail = tail(fields); + final String[] rest = tail[0].startsWith("$") ? tail(tail) : tail; // TODO: why? + value.asHash().insert(mode, rest, newValue); break; - case LAST_FIELD: - final Value last = array.get(array.size() - 1); - if (last.isHash()) { - last.asHash().insert(mode, nestedFields, newValue); - } + case Array: + insertArray(mode, newValue, tail(fields), value.asArray()); break; + case String: + throw new IllegalStateException("expected array or hash, got " + value.type); default: - if (isNumber(remainingFields[0])) { - array.add(new Value(newValue)); - } - else { - array.add(newHash(h -> h.insert(mode, remainingFields, newValue))); - } - break; + throw new IllegalStateException("expected array or hash, got " + value.type); } } - else { - throw new IllegalStateException("expected array or hash, got " + value.type); - } } return new Value(this); } + private void insertArray(final InsertMode mode, final String newValue, final String[] fields, + final Array array) { + switch (fields[0]) { + case ASTERISK: + break; + case APPEND_FIELD: + array.add(newHash(h -> h.insert(mode, tail(fields), newValue))); + break; + case LAST_FIELD: + if (array.size() > 0) { + final Value last = array.get(array.size() - 1); + if (last.isHash()) { + last.asHash().insert(mode, tail(fields), newValue); + } + } + break; + default: + if (isNumber(fields[0])) { + if (fields.length == 1) { + array.add(new Value(newValue)); + } + if (fields.length > 1) { + final Value newHash = Value.newHash(); + newHash.asHash().put(fields[1], new Value(newValue)); + array.add(newHash); + } + } + else { + final String[] rem = fields; + array.add(newHash(h -> h.insert(mode, rem, newValue))); + } + break; + } + } + /** * Removes the given field/value pair from this hash. * @@ -535,11 +606,20 @@ private void removeNested(final String[] fields) { } else if (containsField(field)) { final Value value = get(field); - if (value.isArray()) { - value.asArray().removeNested(fields, value); - } - if (value.isHash()) { - value.asHash().removeNested(Arrays.copyOfRange(fields, 1, fields.length)); + // TODO: impl and call just value.remove + if (value != null) { + switch (value.type) { + case String: + break; + case Array: + value.asArray().removeNested(tail(fields)); + break; + case Hash: + value.asHash().removeNested(tail(fields)); + break; + default: + break; + } } } } @@ -547,18 +627,34 @@ else if (containsField(field)) { public void copy(final List params) { final String oldName = params.get(0); final String newName = params.get(1); - findList(oldName, a -> a.forEach(v -> append(newName, v.toString()))); + findList(oldName, a -> a.forEach(v -> appendValue(split(newName), v))); + } + + private void appendValue(final String[] newName, final Value v) { + // TODO: impl and call just value.append + if (v != null) { + switch (v.type) { + case String: + append(Arrays.asList(newName).stream().collect(Collectors.joining(".")), v.asString()); + break; + case Array: + break; + case Hash: + appendValue(newName, v.asHash().find(tail(newName))); + break; + default: + break; + } + } } public void transformFields(final List params, final UnaryOperator operator) { - final String field = params.get(0).replace(".*", ""); + final String field = params.get(0); final Value value = find(field); - if (value != null) { - removeNested(field); - + removeNested(field.replace(".*", "")); if (operator != null) { - value.asList(a -> a.forEach(v -> append(field, operator.apply(v.toString())))); + value.asList(a -> a.forEach(v -> append(field.replace(".*", ""), operator.apply(v.toString())))); } } } @@ -621,5 +717,4 @@ void apply(final Hash hash, final String field, final String value) { } } - } diff --git a/metafix/src/test/java/org/metafacture/metafix/MetafixBindTest.java b/metafix/src/test/java/org/metafacture/metafix/MetafixBindTest.java index 5b060372..6d15db98 100644 --- a/metafix/src/test/java/org/metafacture/metafix/MetafixBindTest.java +++ b/metafix/src/test/java/org/metafacture/metafix/MetafixBindTest.java @@ -119,7 +119,7 @@ public void doListWithAppendAndLast() { o.get().startEntity("author[]"); o.get().startEntity("1"); o.get().literal("name", "A University"); - // o.get().literal("type", "Default"); // FIXME: bind scope broken + o.get().literal("type", "Default"); o.get().endEntity(); o.get().startEntity("2"); o.get().literal("name", "Max"); @@ -160,10 +160,10 @@ public void doListEntitesToLiterals() { @Test public void doListEntitesToEntities() { MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( - "do list('path': 'creator.name', 'var': 'c')", - " set_array('author[]')", - " copy_field('c', 'author[].$append.name')", - " if all_contain('c', 'University')", + "set_array('author[]')", + "do list('path': 'creator', 'var': 'c')", + " copy_field('c.name', 'author[].$append.name')", + " if all_contain('c.name', 'University')", " add_field('author[].$last.type', 'Organization')", " else", " add_field('author[].$last.type', 'Person')", //", @@ -195,17 +195,12 @@ public void doListEntitesToEntities() { } @Test - @Disabled("TODO: how to handle repeated entities: turn to array vs. merge because it's the same?") - public void doListEntitesWithFieldsToEntities() { + public void wildcardForNestedEntities() { MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( + "set_array('author[]')", "do list('path': 'creator', 'var': 'c')", - " set_array('author')", - " copy_field('c.name', 'author.$append.name')", - " if all_contain('c.type', 'corporate')", - " add_field('author.$last.type', 'Organization')", - " end", - " if all_contain('c.type', 'personal')", - " add_field('author.$last.type', 'Person')", //", + " if any_match('c.role.*.roleTerm.*.value','aut|cre')", + " copy_field('c.name', 'author[].$append.name')", " end", "end", "remove_field('creator')"), @@ -213,23 +208,35 @@ public void doListEntitesWithFieldsToEntities() { i.startRecord("1"); i.startEntity("creator"); i.literal("name", "A University"); - i.literal("type", "corporate"); + i.startEntity("role"); + i.startEntity("roleTerm"); + i.literal("value", "aut"); + i.endEntity(); + i.startEntity("roleTerm"); + i.literal("value", "tau"); + i.endEntity(); + i.endEntity(); i.endEntity(); i.startEntity("creator"); i.literal("name", "Max"); - i.literal("type", "personal"); + i.startEntity("role"); + i.startEntity("roleTerm"); + i.literal("value", "cre"); + i.endEntity(); + i.startEntity("roleTerm"); + i.literal("value", "rec"); + i.endEntity(); + i.endEntity(); i.endEntity(); i.endRecord(); }, (o, f) -> { o.get().startRecord("1"); o.get().startEntity("author[]"); - o.get().startEntity(""); + o.get().startEntity("1"); o.get().literal("name", "A University"); - o.get().literal("type", "Organization"); o.get().endEntity(); - o.get().startEntity(""); + o.get().startEntity("2"); o.get().literal("name", "Max"); - o.get().literal("type", "Person"); f.apply(2).endEntity(); o.get().endRecord(); }); diff --git a/metafix/src/test/java/org/metafacture/metafix/MetafixRecordTest.java b/metafix/src/test/java/org/metafacture/metafix/MetafixRecordTest.java index 1a0ce1f3..b622f27f 100644 --- a/metafix/src/test/java/org/metafacture/metafix/MetafixRecordTest.java +++ b/metafix/src/test/java/org/metafacture/metafix/MetafixRecordTest.java @@ -102,9 +102,11 @@ public void entitiesPassThroughRepeatNestedEntity() { o.get().startRecord("1"); o.get().startEntity("deep"); o.get().startEntity("nested"); - o.get().startEntity("field"); - o.get().literal("1", "value1"); - o.get().literal("2", "value2"); + o.get().startEntity("1"); + o.get().literal("field", "value1"); + o.get().endEntity(); + o.get().startEntity("2"); + o.get().literal("field", "value2"); f.apply(3).endEntity(); o.get().endRecord(); }); @@ -747,7 +749,6 @@ public void accessArrayByWildcard() { } @Test - @Disabled("TODO: Repeated entities should become arrays, see #65") public void repeatToArrayOfObjects() { MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( "vacuum()"), @@ -774,7 +775,6 @@ public void repeatToArrayOfObjects() { } @Test - @Disabled("TODO: Access arrays of objects by index, see #65") public void accessArrayOfObjectsByIndex() { MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( "upcase('author.2.name')"), @@ -801,10 +801,40 @@ public void accessArrayOfObjectsByIndex() { } @Test - @Disabled("TODO: Access arrays of objects by wildcard, see #65") + @Disabled("TODO: implement implicit iteration?") public void accessArrayOfObjectsByWildcard() { MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( - "upcase('author.*.name')"), + "upcase('author.*.name')", + "vacuum()"), + i -> { + i.startRecord("1"); + i.startEntity("author"); + i.literal("name", "max"); + i.endEntity(); + i.startEntity("author"); + i.literal("name", "mo"); + i.endEntity(); + i.endRecord(); + }, (o, f) -> { + o.get().startRecord("1"); + o.get().startEntity("author"); + o.get().startEntity("1"); + o.get().literal("name", "MAX"); + o.get().endEntity(); + o.get().startEntity("2"); + o.get().literal("name", "MO"); + f.apply(2).endEntity(); + o.get().endRecord(); + }); + } + + @Test + public void accessArrayOfObjectsByDoListBind() { + MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( + "do list('path':'author','var':'a')", + " upcase('a.name')", + "end", + "vacuum()"), i -> { i.startRecord("1"); i.startEntity("author"); From 77dcd2baf9a70ecd5464ecb4954e3985505a1ce0 Mon Sep 17 00:00:00 2001 From: Fabian Steeg Date: Tue, 30 Nov 2021 10:43:49 +0100 Subject: [PATCH 5/7] Integrate feedback from PR #78 (see #65) Co-authored-by: Jens Wille --- .../java/org/metafacture/metafix/Value.java | 141 +++++++++--------- .../metafix/MetafixRecordTest.java | 53 +++++++ 2 files changed, 126 insertions(+), 68 deletions(-) diff --git a/metafix/src/main/java/org/metafacture/metafix/Value.java b/metafix/src/main/java/org/metafacture/metafix/Value.java index e041adf5..b3f6f7c3 100644 --- a/metafix/src/main/java/org/metafacture/metafix/Value.java +++ b/metafix/src/main/java/org/metafacture/metafix/Value.java @@ -25,7 +25,6 @@ import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.UnaryOperator; -import java.util.stream.Collectors; import java.util.stream.Stream; /** @@ -235,7 +234,7 @@ public String toString() { return result; } - static String[] tail(final String[] fields) { + private static String[] tail(final String[] fields) { return Arrays.copyOfRange(fields, 1, fields.length); } @@ -302,52 +301,34 @@ public void remove(final int index) { private void removeNested(final String[] fields) { if (fields.length >= 1 && fields[0].equals(ASTERISK)) { - for (int i = 0; i < size(); ++i) { - remove(i); - } + list.clear(); } else if (fields.length >= 1 && isNumber(fields[0])) { - final int index = Integer.parseInt(fields[0]) - 1; + final int index = Integer.parseInt(fields[0]) - 1; // TODO: 0-based Catmandu vs. 1-based Metafacture if (index >= 0 && index < size()) { remove(index); } } } - public Value find(final String[] fields) { - Value result = null; - if (fields.length > 0) { - if (fields[0].equals(ASTERISK)) { - result = find(tail(fields)); + private Value find(final String[] path) { + final Value result; + if (path.length > 0) { + if (path[0].equals(ASTERISK)) { + result = newArray(a -> forEach(v -> a.add(findInValue(tail(path), v)))); } - else if (isNumber(fields[0])) { - final int index = Integer.parseInt(fields[0]) - 1; + else if (isNumber(path[0])) { + final int index = Integer.parseInt(path[0]) - 1; // TODO: 0-based Catmandu vs. 1-based Metafacture if (index >= 0 && index < size()) { - final Value value = get(index); - // TODO: move impl into enum elements, here call only value.find - if (value != null) { - switch (value.type) { - case Hash: - result = value.asHash().find(tail(fields)); - break; - case Array: - result = find(tail(fields)); - break; - case String: - result = value; - break; - default: - break; - } - } + result = findInValue(tail(path), get(index)); + } + else { + result = null; } } + // TODO: WDCD? copy_field('your.name','author[].name'), where name is an array else { - final Value newResult = newArray(); - forEach(c -> { - newResult.asArray().add(c.asHash().find(fields[0])); /* TODO: non-hash */ - }); - result = newResult; + result = newArray(a -> forEach(v -> a.add(findInValue(path, v)))); } } else { @@ -355,6 +336,31 @@ else if (isNumber(fields[0])) { } return result; } + + private Value findInValue(final String[] path, final Value value) { + final Value result; + // TODO: move impl into enum elements, here call only value.find + if (value != null) { + switch (value.type) { + case Hash: + result = value.asHash().find(path); + break; + case Array: + result = value.asArray().find(path); + break; + case String: + result = value; + break; + default: + result = null; + break; + } + } + else { + result = null; + } + return result; + } } /** @@ -367,6 +373,8 @@ public static class Hash extends AbstractValueType { private static final String FIELD_PATH_SEPARATOR = "\\."; + private static final String UNEXPECTED = "expected array or hash, got "; + private final Map map = new LinkedHashMap<>(); /** @@ -452,7 +460,10 @@ public Value find(final String fieldPath) { private Value find(final String[] fields) { final String field = fields[0]; - + if (field.equals(ASTERISK)) { + // TODO: search in all elements of value.asHash()? + return find(tail(fields)); + } return fields.length == 1 || !containsField(field) ? get(field) : findNested(field, tail(fields)); } @@ -463,25 +474,13 @@ private Value findNested(final String field, final String[] remainingFields) { if (value != null) { switch (value.type) { case Array: - if (remainingFields[0].equals(ASTERISK)) { - result = value.asArray().find(tail(remainingFields)); - } - else { - result = value.asArray().find(remainingFields); - } + result = value.asArray().find(remainingFields); break; case Hash: - if (remainingFields[0].equals(ASTERISK)) { - result = value.asHash().find(tail(remainingFields)); - } - else { - result = value.asHash().find(remainingFields); - } + result = value.asHash().find(remainingFields); break; - case String: - throw new IllegalStateException("expected string, got " + value.type); default: - throw new IllegalStateException("unexpected, got " + value.type); + throw new IllegalStateException(UNEXPECTED + value.type); } } return result; @@ -518,9 +517,17 @@ public Value insert(final InsertMode mode, final String fieldPath, final String private Value insert(final InsertMode mode, final String[] fields, final String newValue) { final String field = fields[0]; - - if (fields.length == 1 && !fields[0].equals(ASTERISK)) { - mode.apply(this, field, newValue); + if (field.equals(APPEND_FIELD) || field.equals(LAST_FIELD)) { + // TODO: WDCD? $last, $append skipped for hashes here: + return insert(mode, tail(fields), newValue); + } + if (fields.length == 1) { + if (fields[0].equals(ASTERISK)) { + //TODO: WDCD? insert into each element? + } + else { + mode.apply(this, field, newValue); + } } else { if (!containsField(field)) { @@ -532,17 +539,13 @@ private Value insert(final InsertMode mode, final String[] fields, final String switch (value.type) { // TODO: move impl into enum elements, here call only value.insert case Hash: - final String[] tail = tail(fields); - final String[] rest = tail[0].startsWith("$") ? tail(tail) : tail; // TODO: why? - value.asHash().insert(mode, rest, newValue); + value.asHash().insert(mode, tail(fields), newValue); break; case Array: insertArray(mode, newValue, tail(fields), value.asArray()); break; - case String: - throw new IllegalStateException("expected array or hash, got " + value.type); default: - throw new IllegalStateException("expected array or hash, got " + value.type); + throw new IllegalStateException(UNEXPECTED + value.type); } } } @@ -554,12 +557,13 @@ private void insertArray(final InsertMode mode, final String newValue, final Str final Array array) { switch (fields[0]) { case ASTERISK: + // TODO: WDCD? descend into the array? break; case APPEND_FIELD: array.add(newHash(h -> h.insert(mode, tail(fields), newValue))); break; case LAST_FIELD: - if (array.size() > 0) { + if (size() > 0) { final Value last = array.get(array.size() - 1); if (last.isHash()) { last.asHash().insert(mode, tail(fields), newValue); @@ -568,18 +572,18 @@ private void insertArray(final InsertMode mode, final String newValue, final Str break; default: if (isNumber(fields[0])) { + // TODO: WDCD? insert at the given index? also descend into the array? if (fields.length == 1) { array.add(new Value(newValue)); } if (fields.length > 1) { final Value newHash = Value.newHash(); - newHash.asHash().put(fields[1], new Value(newValue)); + mode.apply(newHash.asHash(), fields[1], newValue); array.add(newHash); } } else { - final String[] rem = fields; - array.add(newHash(h -> h.insert(mode, rem, newValue))); + array.add(newHash(h -> h.insert(mode, fields, newValue))); } break; } @@ -610,7 +614,7 @@ else if (containsField(field)) { if (value != null) { switch (value.type) { case String: - break; + throw new IllegalStateException(UNEXPECTED + value.type); case Array: value.asArray().removeNested(tail(fields)); break; @@ -635,9 +639,10 @@ private void appendValue(final String[] newName, final Value v) { if (v != null) { switch (v.type) { case String: - append(Arrays.asList(newName).stream().collect(Collectors.joining(".")), v.asString()); + append(String.join(".", newName), v.asString()); break; case Array: + // TODO: do something here? break; case Hash: appendValue(newName, v.asHash().find(tail(newName))); @@ -652,9 +657,9 @@ public void transformFields(final List params, final UnaryOperator a.forEach(v -> append(field.replace(".*", ""), operator.apply(v.toString())))); + value.asList(a -> a.forEach(v -> append(field, operator.apply(v.toString())))); } } } diff --git a/metafix/src/test/java/org/metafacture/metafix/MetafixRecordTest.java b/metafix/src/test/java/org/metafacture/metafix/MetafixRecordTest.java index b622f27f..8cf6c446 100644 --- a/metafix/src/test/java/org/metafacture/metafix/MetafixRecordTest.java +++ b/metafix/src/test/java/org/metafacture/metafix/MetafixRecordTest.java @@ -439,6 +439,38 @@ public void removeEntity() { }); } + @Test + public void removeArray() { + MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( + "remove_field('name')"), + i -> { + i.startRecord("1"); + i.literal("name", "max"); + i.literal("name", "mo"); + i.endRecord(); + }, o -> { + o.get().startRecord("1"); + o.get().endRecord(); + }); + } + + @Test + public void removeArrayElementsByWildcard() { + MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( + "remove_field('name.*')"), + i -> { + i.startRecord("1"); + i.literal("name", "max"); + i.literal("name", "mo"); + i.endRecord(); + }, o -> { + o.get().startRecord("1"); + o.get().startEntity("name"); + o.get().endEntity(); + o.get().endRecord(); + }); + } + @Test public void setArray() { MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( @@ -730,6 +762,26 @@ public void accessArrayByIndex() { } @Test + public void accessArrayImplicit() { + MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( + "upcase('name')"), + i -> { + i.startRecord("1"); + i.literal("name", "max"); + i.literal("name", "mo"); + i.endRecord(); + }, (o, f) -> { + o.get().startRecord("1"); + o.get().startEntity("name"); + o.get().literal("1", "MAX"); + o.get().literal("2", "MO"); + o.get().endEntity(); + o.get().endRecord(); + }); + } + + @Test + @Disabled("TODO: WDCD? explicit * for array fields?") public void accessArrayByWildcard() { MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( "upcase('name.*')"), @@ -856,4 +908,5 @@ public void accessArrayOfObjectsByDoListBind() { o.get().endRecord(); }); } + } From 4d956a7ac8c30dd9dafa998635d0b50271ccf9f8 Mon Sep 17 00:00:00 2001 From: Fabian Steeg Date: Tue, 30 Nov 2021 10:50:25 +0100 Subject: [PATCH 6/7] Move `Hash#insertArray` to `Array#insert` (#65) --- .../org/metafacture/metafix/FixMethod.java | 2 +- .../java/org/metafacture/metafix/Value.java | 112 +++++++++--------- 2 files changed, 56 insertions(+), 58 deletions(-) diff --git a/metafix/src/main/java/org/metafacture/metafix/FixMethod.java b/metafix/src/main/java/org/metafacture/metafix/FixMethod.java index c8c6181c..4f7e964c 100644 --- a/metafix/src/main/java/org/metafacture/metafix/FixMethod.java +++ b/metafix/src/main/java/org/metafacture/metafix/FixMethod.java @@ -244,7 +244,7 @@ private Map fileMap(final String location, final String separato private static final Pattern NAMED_GROUP_PATTERN = Pattern.compile("\\(\\?<(.+?)>"); private static final String EMPTY = ""; - private static final String DOT_APPEND = "." + Value.Hash.APPEND_FIELD; + private static final String DOT_APPEND = "." + Value.APPEND_FIELD; abstract void apply(Record record, List params, Map options); diff --git a/metafix/src/main/java/org/metafacture/metafix/Value.java b/metafix/src/main/java/org/metafacture/metafix/Value.java index b3f6f7c3..b6193646 100644 --- a/metafix/src/main/java/org/metafacture/metafix/Value.java +++ b/metafix/src/main/java/org/metafacture/metafix/Value.java @@ -33,6 +33,8 @@ */ public class Value { + /*package-private*/ static final String APPEND_FIELD = "$append"; + private static final String LAST_FIELD = "$last"; private static final String ASTERISK = "*"; private final Array array; @@ -361,6 +363,41 @@ private Value findInValue(final String[] path, final Value value) { } return result; } + + private void insert(final InsertMode mode, final String[] fields, final String newValue) { + switch (fields[0]) { + case ASTERISK: + // TODO: WDCD? descend into the array? + break; + case APPEND_FIELD: + add(newHash(h -> h.insert(mode, tail(fields), newValue))); + break; + case LAST_FIELD: + if (size() > 0) { + final Value last = get(size() - 1); + if (last.isHash()) { + last.asHash().insert(mode, tail(fields), newValue); + } + } + break; + default: + if (isNumber(fields[0])) { + // TODO: WDCD? insert at the given index? also descend into the array? + if (fields.length == 1) { + add(new Value(newValue)); + } + if (fields.length > 1) { + final Value newHash = Value.newHash(); + mode.apply(newHash.asHash(), fields[1], newValue); + add(newHash); + } + } + else { + add(newHash(h -> h.insert(mode, fields, newValue))); + } + break; + } + } } /** @@ -368,9 +405,6 @@ private Value findInValue(final String[] path, final Value value) { */ public static class Hash extends AbstractValueType { - /*package-private*/ static final String APPEND_FIELD = "$append"; - private static final String LAST_FIELD = "$last"; - private static final String FIELD_PATH_SEPARATOR = "\\."; private static final String UNEXPECTED = "expected array or hash, got "; @@ -542,7 +576,7 @@ private Value insert(final InsertMode mode, final String[] fields, final String value.asHash().insert(mode, tail(fields), newValue); break; case Array: - insertArray(mode, newValue, tail(fields), value.asArray()); + value.asArray().insert(mode, tail(fields), newValue); break; default: throw new IllegalStateException(UNEXPECTED + value.type); @@ -553,42 +587,6 @@ private Value insert(final InsertMode mode, final String[] fields, final String return new Value(this); } - private void insertArray(final InsertMode mode, final String newValue, final String[] fields, - final Array array) { - switch (fields[0]) { - case ASTERISK: - // TODO: WDCD? descend into the array? - break; - case APPEND_FIELD: - array.add(newHash(h -> h.insert(mode, tail(fields), newValue))); - break; - case LAST_FIELD: - if (size() > 0) { - final Value last = array.get(array.size() - 1); - if (last.isHash()) { - last.asHash().insert(mode, tail(fields), newValue); - } - } - break; - default: - if (isNumber(fields[0])) { - // TODO: WDCD? insert at the given index? also descend into the array? - if (fields.length == 1) { - array.add(new Value(newValue)); - } - if (fields.length > 1) { - final Value newHash = Value.newHash(); - mode.apply(newHash.asHash(), fields[1], newValue); - array.add(newHash); - } - } - else { - array.add(newHash(h -> h.insert(mode, fields, newValue))); - } - break; - } - } - /** * Removes the given field/value pair from this hash. * @@ -700,26 +698,26 @@ public String asString() { return map.toString(); } - private enum InsertMode { + } - REPLACE { - @Override - void apply(final Hash hash, final String field, final String value) { - hash.put(field, new Value(value)); - } - }, - APPEND { - @Override - void apply(final Hash hash, final String field, final String value) { - final Value oldValue = hash.get(field); - final Value newValue = new Value(value); - hash.put(field, oldValue == null ? newValue : oldValue.merge(newValue)); - } - }; + private enum InsertMode { - abstract void apply(Hash hash, String field, String value); + REPLACE { + @Override + void apply(final Hash h, final String field, final String value) { + h.put(field, new Value(value)); + } + }, + APPEND { + @Override + void apply(final Hash h, final String field, final String value) { + final Value oldValue = h.get(field); + final Value newValue = new Value(value); + h.put(field, oldValue == null ? newValue : oldValue.merge(newValue)); + } + }; - } + abstract void apply(Hash h, String field, String value); } } From 1904e0d5900f7410015a4a875a38cdb50a2b80db Mon Sep 17 00:00:00 2001 From: Fabian Steeg Date: Tue, 30 Nov 2021 14:31:14 +0100 Subject: [PATCH 7/7] Move InsertMode instead of renaming `hash` to avoid hiding (#65) --- .../java/org/metafacture/metafix/Value.java | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/metafix/src/main/java/org/metafacture/metafix/Value.java b/metafix/src/main/java/org/metafacture/metafix/Value.java index b6193646..2fddd53d 100644 --- a/metafix/src/main/java/org/metafacture/metafix/Value.java +++ b/metafix/src/main/java/org/metafacture/metafix/Value.java @@ -255,6 +255,27 @@ public String toString() { public abstract String asString(); + private enum InsertMode { + + REPLACE { + @Override + void apply(final Hash hash, final String field, final String value) { + hash.put(field, new Value(value)); + } + }, + APPEND { + @Override + void apply(final Hash hash, final String field, final String value) { + final Value oldValue = hash.get(field); + final Value newValue = new Value(value); + hash.put(field, oldValue == null ? newValue : oldValue.merge(newValue)); + } + }; + + abstract void apply(Hash hash, String field, String value); + + } + } /** @@ -364,7 +385,7 @@ private Value findInValue(final String[] path, final Value value) { return result; } - private void insert(final InsertMode mode, final String[] fields, final String newValue) { + private void insert(final AbstractValueType.InsertMode mode, final String[] fields, final String newValue) { switch (fields[0]) { case ASTERISK: // TODO: WDCD? descend into the array? @@ -471,11 +492,11 @@ public void replace(final String field, final Value value) { } public Value replace(final String fieldPath, final String newValue) { - return insert(InsertMode.REPLACE, fieldPath, newValue); + return insert(AbstractValueType.InsertMode.REPLACE, fieldPath, newValue); } public Value append(final String fieldPath, final String newValue) { - return insert(InsertMode.APPEND, fieldPath, newValue); + return insert(AbstractValueType.InsertMode.APPEND, fieldPath, newValue); } /** @@ -545,11 +566,11 @@ public void add(final String field, final Value newValue) { put(field, oldValue == null ? newValue : oldValue.merge(newValue)); } - public Value insert(final InsertMode mode, final String fieldPath, final String newValue) { + public Value insert(final AbstractValueType.InsertMode mode, final String fieldPath, final String newValue) { return insert(mode, split(fieldPath), newValue); } - private Value insert(final InsertMode mode, final String[] fields, final String newValue) { + private Value insert(final AbstractValueType.InsertMode mode, final String[] fields, final String newValue) { final String field = fields[0]; if (field.equals(APPEND_FIELD) || field.equals(LAST_FIELD)) { // TODO: WDCD? $last, $append skipped for hashes here: @@ -700,24 +721,4 @@ public String asString() { } - private enum InsertMode { - - REPLACE { - @Override - void apply(final Hash h, final String field, final String value) { - h.put(field, new Value(value)); - } - }, - APPEND { - @Override - void apply(final Hash h, final String field, final String value) { - final Value oldValue = h.get(field); - final Value newValue = new Value(value); - h.put(field, oldValue == null ? newValue : oldValue.merge(newValue)); - } - }; - - abstract void apply(Hash h, String field, String value); - - } }