diff --git a/metafix/src/main/java/org/metafacture/metafix/FixPath.java b/metafix/src/main/java/org/metafacture/metafix/FixPath.java index f26d9d8b..a1955aae 100644 --- a/metafix/src/main/java/org/metafacture/metafix/FixPath.java +++ b/metafix/src/main/java/org/metafacture/metafix/FixPath.java @@ -43,18 +43,25 @@ public FixPath(final String path) { this(Value.split(path)); } - /*package-private*/ FixPath(final String[] path) { + private FixPath(final String[] path) { this.path = path; } public Value findIn(final Hash hash) { - final String field = path[0]; - if (field.equals(ASTERISK)) { + final String currentSegment = path[0]; + final FixPath remainingPath = new FixPath(tail(path)); + + if (currentSegment.equals(ASTERISK)) { // TODO: search in all elements of value.asHash()? - return new FixPath(tail(path)).findIn(hash); + return remainingPath.findIn(hash); } - return path.length == 1 || !hash.containsField(field) ? hash.get(field) : - findNested(hash, field, tail(path)); + + final Value value = hash.get(currentSegment); + return value == null || path.length == 1 ? value : value.extractType((m, c) -> m + .ifArray(a -> c.accept(remainingPath.findIn(a))) + .ifHash(h -> c.accept(remainingPath.findIn(h))) + .orElseThrow() + ); } /*package-private*/ Value findIn(final Array array) { @@ -101,6 +108,24 @@ public Value appendIn(final Hash hash, final String newValue) { return new FixPath(path).insertInto(hash, InsertMode.APPEND, new Value(newValue)); } + /*package-private*/ void appendIn(final Hash hash, final Value v) { + // TODO: impl and call just value.append + if (v != null) { + v.matchType() + .ifString(s -> appendIn(hash, s)) + //.ifArray(a -> /* TODO: see MetafixMethodTest.moveToNestedArray */) + .ifHash(h -> { + if (path.length == 1) { + hash.add(path[0], v); + } + else { + appendIn(hash, new FixPath(tail(path)).findIn(h)); + } + }) + .orElseThrow(); + } + } + @Override public String toString() { return Arrays.asList(path).toString(); @@ -285,15 +310,6 @@ private void transformValueAt(final Array array, final int index, final String[] } } - private Value findNested(final Hash hash, final String field, final String[] remainingFields) { - final Value value = hash.get(field); - return value == null ? null : value.extractType((m, c) -> m - .ifArray(a -> c.accept(new FixPath(remainingFields).findIn(a))) - .ifHash(h -> c.accept(new FixPath(remainingFields).findIn(h))) - .orElseThrow() - ); - } - private void insertIntoReferencedObject(final Array array, final InsertMode mode, final Value newValue) { // TODO replace switch, extract to enum behavior like reservedField.insertIntoReferencedObject(this)? switch (ReservedField.fromString(path[0])) { @@ -379,5 +395,4 @@ private Value getReferencedValue(final Hash hash, final String field) { } return referencedValue; } - } diff --git a/metafix/src/main/java/org/metafacture/metafix/Value.java b/metafix/src/main/java/org/metafacture/metafix/Value.java index aaaffeca..507753d0 100644 --- a/metafix/src/main/java/org/metafacture/metafix/Value.java +++ b/metafix/src/main/java/org/metafacture/metafix/Value.java @@ -19,7 +19,6 @@ import org.metafacture.commons.tries.SimpleRegexTrie; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; @@ -38,7 +37,7 @@ /** * Represents a record value, i.e., either an {@link Array}, a {@link Hash}, - * or a {@link java.lang.String String}. + * or a {@link String}. */ public class Value { @@ -250,10 +249,6 @@ public String toString() { ); } - private static String[] tail(final String[] fields) { - return Arrays.copyOfRange(fields, 1, fields.length); - } - /*package-private*/ static String[] split(final String fieldPath) { return fieldPath.split(FIELD_PATH_SEPARATOR); } @@ -577,31 +572,7 @@ public void removeField(final String field) { public void copy(final List params) { final String oldName = params.get(0); final String newName = params.get(1); - asList(new FixPath(oldName).findIn(this), 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: - new FixPath(String.join(".", newName)).appendIn(this, v.asString()); - break; - case Array: - // TODO: do something here? - break; - case Hash: - if (newName.length == 1) { - add(newName[0], v); - } - else { - appendValue(newName, new FixPath(tail(newName)).findIn(v.asHash())); - } - break; - default: - break; - } - } + asList(new FixPath(oldName).findIn(this), a -> a.forEach(v -> new FixPath(newName).appendIn(this, v))); } /** @@ -653,6 +624,12 @@ public String toString() { return map.toString(); } + /** + * Avoids {@link ConcurrentModificationException} when modifying the hash based on matched fields. + * + * @param pattern the field name pattern + * @param consumer the action to be performed for each value + */ /*package-private*/ void modifyFields(final String pattern, final Consumer consumer) { findFields(pattern).collect(Collectors.toSet()).forEach(consumer); } diff --git a/metafix/src/test/java/org/metafacture/metafix/MetafixBindTest.java b/metafix/src/test/java/org/metafacture/metafix/MetafixBindTest.java index 59170088..87eb89f5 100644 --- a/metafix/src/test/java/org/metafacture/metafix/MetafixBindTest.java +++ b/metafix/src/test/java/org/metafacture/metafix/MetafixBindTest.java @@ -615,6 +615,58 @@ public void shouldIterateOverListWithWildcard() { shouldIterateOverList("n?me", 3); } + private void shouldIterateOverListOfHashes(final String path, final int expectedCount) { + MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( + "do list(path: '" + path + "', 'var': '$i')", + " add_field('trace', 'true')", + "end", + "retain('trace')" + ), + i -> { + i.startRecord("1"); + i.startEntity("name"); + i.literal("value", "Mary"); + i.endEntity(); + i.startEntity("name"); + i.literal("value", "University"); + i.endEntity(); + i.startEntity("nome"); + i.literal("value", "Max"); + i.endEntity(); + i.endRecord(); + }, + (o, f) -> { + o.get().startRecord("1"); + f.apply(expectedCount).literal("trace", "true"); + o.get().endRecord(); + } + ); + } + + @Test + public void shouldIterateOverListOfHashes() { + shouldIterateOverListOfHashes("name.value", 2); + } + + @Test + // See https://github.com/metafacture/metafacture-fix/issues/119 + public void shouldIterateOverListOfHashesWithCharacterClass() { + shouldIterateOverListOfHashes("n[ao]me.value", 3); + } + + @Test + // See https://github.com/metafacture/metafacture-fix/issues/119 + @Disabled("See https://github.com/metafacture/metafacture-fix/issues/143") + public void shouldIterateOverListOfHashesWithAlternation() { + shouldIterateOverListOfHashes("name.value|nome.value", 3); + } + + @Test + // See https://github.com/metafacture/metafacture-fix/issues/119 + public void shouldIterateOverListOfHashesWithWildcard() { + shouldIterateOverListOfHashes("n?me.value", 3); + } + @Test // checkstyle-disable-line JavaNCSS // See https://github.com/metafacture/metafacture-fix/issues/119 public void shouldPerformComplexOperationWithPathWildcard() { diff --git a/metafix/src/test/java/org/metafacture/metafix/MetafixMethodTest.java b/metafix/src/test/java/org/metafacture/metafix/MetafixMethodTest.java index 17c7de68..20008a66 100644 --- a/metafix/src/test/java/org/metafacture/metafix/MetafixMethodTest.java +++ b/metafix/src/test/java/org/metafacture/metafix/MetafixMethodTest.java @@ -1717,7 +1717,47 @@ public void shouldSplitArrayField() { } @Test - @Disabled("See https://github.com/metafacture/metafacture-fix/issues/106") + @Disabled("Arrays in arrays need to be preserved. See disabled isArray in FixPath#appendIn.") + public void moveToNestedArray() { + MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( + "move_field('date[]', 'd[]')" + ), + i -> { + i.startRecord("1"); + i.startEntity("date[]"); + i.startEntity("1[]"); + i.literal("1", "1918"); + i.literal("2", "17"); + i.literal("3", "16"); + i.endEntity(); + i.startEntity("2[]"); + i.literal("1", "2021"); + i.literal("2", "22"); + i.literal("3", "23"); + i.endEntity(); + i.endEntity(); + i.endRecord(); + }, + (o, f) -> { + o.get().startRecord("1"); + o.get().startEntity("d[]"); + o.get().startEntity("1[]"); + o.get().literal("1", "1918"); + o.get().literal("2", "17"); + o.get().literal("3", "16"); + o.get().endEntity(); + o.get().startEntity("2[]"); + o.get().literal("1", "2021"); + o.get().literal("2", "22"); + o.get().literal("3", "23"); + f.apply(2).endEntity(); + o.get().endRecord(); + } + ); + } + + @Test + @Disabled("Arrays in arrays need to be preserved. See disabled isArray in FixPath#appendIn.") public void shouldSplitMarkedArrayFieldIntoArrayOfArrays() { MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( "split_field('date[]', '-')"