Skip to content
This repository has been archived by the owner on Jan 27, 2025. It is now read-only.

Commit

Permalink
Implement transformIn using findIn & insertInto (#102, #121, #147)
Browse files Browse the repository at this point in the history
For cases where the findIn path does not work with insertInto
(like `title-?` or `some.*.nested.*`), use a new Value#path field
  • Loading branch information
fsteeg committed Feb 28, 2022
1 parent 8c42874 commit c25ede3
Show file tree
Hide file tree
Showing 9 changed files with 394 additions and 210 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ public void apply(final Metafix metafix, final Record record, final List<String>
final String field = params.get(0);

record.getList(field, oldValues -> {
final String newValue = String.format(params.get(1), oldValues.stream().toArray());
final String newValue = String.format(params.get(1), oldValues.stream().map(v -> v.asString()).toArray());
record.replace(field, new Value(Arrays.asList(new Value(newValue))));
});
}
Expand Down
164 changes: 82 additions & 82 deletions metafix/src/main/java/org/metafacture/metafix/FixPath.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;

/**
* Our goal here is something like https://metacpan.org/pod/Catmandu::Path::simple
Expand All @@ -51,12 +52,10 @@ private FixPath(final String[] path) {
/*package-private*/ Value findIn(final Hash hash) {
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 remainingPath.findIn(hash);
}

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)))
Expand All @@ -70,7 +69,15 @@ private FixPath(final String[] path) {
if (path.length > 0) {
final String currentSegment = path[0];
if (currentSegment.equals(ASTERISK)) {
result = Value.newArray(a -> array.forEach(v -> a.add(findInValue(v, tail(path)))));
result = Value.newArray(resultArray -> array.forEach(v -> {
final Value findInValue = findInValue(v, tail(path));
if (findInValue != null) {
findInValue.matchType()
// flatten result arrays (use Value#path for structure)
.ifArray(a -> a.forEach(resultArray::add))
.orElse(c -> resultArray.add(findInValue));
}
}));
}
else if (Value.isNumber(currentSegment)) {
final int index = Integer.parseInt(currentSegment) - 1; // TODO: 0-based Catmandu vs. 1-based Metafacture
Expand Down Expand Up @@ -111,37 +118,58 @@ public String toString() {
}

/*package-private*/ void transformIn(final Hash hash, final UnaryOperator<String> operator) {
// basic idea: reuse findIn logic here? setIn(hash, operator.apply(findIn(hash)))
final String currentSegment = path[0];
final String[] remainingPath = tail(path);
final Value findIn = findIn(hash);
// System.out.println("Found in hash for '" + Arrays.asList(path) + "': " + findIn);
Value.asList(findIn, results -> {
for (int i = 0; i < results.size(); ++i) {
final Value oldValue = results.get(i);
final FixPath p = pathTo(oldValue, i);
oldValue.matchType()
.ifString(s -> {
final Value newValue = new Value(operator.apply(s));
p.insertInto(hash, InsertMode.REPLACE, newValue);
// System.out.printf("Inserted at '%s': '%s' into '%s'\n", p, newValue, hash);
})
.orElseThrow();
}
});
}

if (currentSegment.equals(ASTERISK)) {
// TODO: search in all elements of value.asHash()?
new FixPath(remainingPath).transformIn(hash, operator);
return;
/*package-private*/ void transformIn(final Hash hash, final BiConsumer<TypeMatcher, Consumer<Value>> consumer) {
final Value oldValue = findIn(hash);

if (oldValue != null) {
final Value newValue = oldValue.extractType(consumer);

if (newValue != null) {
insertInto(hash, InsertMode.REPLACE, newValue);
}
}
}

hash.modifyFields(currentSegment, f -> {
final Value value = hash.getField(f);
private FixPath pathTo(final Value oldValue, final int i) {
FixPath result = this;
// One *: replace with index of current result
if (countAsterisks() == 1) {
result = new FixPath(replaceInPath(ASTERISK, i));
}
// Multiple * or wildcards: use the old value's path
else if (oldValue.getPath() != null && (countAsterisks() >= 2) || hasWildcard()) {
result = new FixPath(oldValue.getPath());
}
return result;
}

if (value != null) {
if (remainingPath.length == 0) {
hash.removeField(f);
private String[] replaceInPath(final String find, final int i) {
return Arrays.asList(path).stream().map(s -> s.equals(find) ? String.valueOf(i + 1) : s).collect(Collectors.toList()).toArray(new String[] {});
}

if (operator != null) {
value.matchType()
.ifString(s -> new FixPath(f).insertInto(hash, InsertMode.APPEND, new Value(operator.apply(s))))
.orElseThrow();
}
}
else {
value.matchType()
.ifArray(a -> new FixPath(remainingPath).transformIn(a, operator))
.ifHash(h -> new FixPath(remainingPath).transformIn(h, operator))
.orElseThrow();
}
}
});
private boolean hasWildcard() {
return Arrays.asList(path).stream().filter(s -> s.contains("?") || s.contains("|") || s.matches(".*?\\[.+?\\].*?")).findAny().isPresent();
}

private long countAsterisks() {
return Arrays.asList(path).stream().filter(s -> s.equals(ASTERISK)).count();
}

/* package-private */ enum InsertMode {
Expand All @@ -151,54 +179,28 @@ public String toString() {
void apply(final Hash hash, final String field, final Value value) {
hash.put(field, value);
}

@Override
void apply(final Array array, final String field, final Value value) {
array.set(Integer.valueOf(field) - 1, value);
}
},
APPEND {
@Override
void apply(final Hash hash, final String field, final Value value) {
hash.add(field, value);
}
};

abstract void apply(Hash hash, String field, Value value);

}

/*package-private*/ void transformIn(final Hash hash, final BiConsumer<TypeMatcher, Consumer<Value>> consumer) {
final Value oldValue = findIn(hash);

if (oldValue != null) {
final Value newValue = oldValue.extractType(consumer);

if (newValue != null) {
insertInto(hash, InsertMode.REPLACE, newValue);
@Override
void apply(final Array array, final String field, final Value value) {
array.add(value);
}
}
}
};

/*package-private*/ void transformIn(final Array array, final UnaryOperator<String> operator) {
// basic idea: reuse findIn logic here? setIn(findIn(array), newValue)
final String currentSegment = path[0];
final int size = array.size();
abstract void apply(Hash hash, String field, Value value);

if (path.length == 0 || currentSegment.equals(ASTERISK)) {
for (int i = 0; i < size; ++i) {
transformValueAt(array, i, tail(path), operator);
}
}
else if (Value.isNumber(currentSegment)) {
final int index = Integer.parseInt(currentSegment) - 1; // TODO: 0-based Catmandu vs. 1-based Metafacture
if (index >= 0 && index < size) {
transformValueAt(array, index, tail(path), operator);
}
}
// TODO: WDCD? copy_field('your.name','author[].name'), where name is an array
else {
for (int i = 0; i < size; ++i) {
transformValueAt(array, i, path, operator);
}
}
abstract void apply(Array array, String field, Value newValue);

array.removeIf(v -> Value.isNull(v));
}

/*package-private*/ void removeNestedFrom(final Array array) {
Expand Down Expand Up @@ -236,10 +238,21 @@ else if (hash.containsField(field)) {
final String field = path[0];
if (path.length == 1) {
if (field.equals(ASTERISK)) {
//TODO: WDCD? insert into each element?
for (int i = 0; i < array.size(); ++i) {
mode.apply(array, "" + (i + 1), newValue);
}
}
else {
array.add(newValue);
// TODO unify ref usage from below
if ("$append".equals(field)) {
array.add(newValue);
}
else if (Value.isNumber(field)) {
mode.apply(array, field, newValue);
}
else {
throw new IllegalStateException("Non-index access to array at " + field);
}
}
}
else {
Expand All @@ -257,7 +270,7 @@ else if (hash.containsField(field)) {
final String field = path[0];
if (path.length == 1) {
if (field.equals(ASTERISK)) {
//TODO: WDCD? insert into each element?
hash.forEach((k, v) -> mode.apply(hash, k, newValue)); //TODO: WDCD? insert into each element?
}
else {
mode.apply(hash, field, newValue);
Expand Down Expand Up @@ -288,19 +301,6 @@ private String[] tail(final String[] fields) {
return Arrays.copyOfRange(fields, 1, fields.length);
}

private void transformValueAt(final Array array, final int index, final String[] p, final UnaryOperator<String> operator) {
final Value value = array.get(index);
if (value != null) {
value.matchType()
.ifString(s -> array.set(index, operator != null ? new Value(operator.apply(s)) : null))
.orElse(v -> v.matchType()
.ifArray(a -> new FixPath(p).transformIn(a, operator))
.ifHash(h -> new FixPath(p).transformIn(h, operator))
.orElseThrow()
);
}
}

private Value processRef(final Value referencedValue, final InsertMode mode, final Value newValue, final String field,
final String[] tail) {
if (referencedValue != null) {
Expand Down
15 changes: 8 additions & 7 deletions metafix/src/main/java/org/metafacture/metafix/Metafix.java
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,14 @@ public class Metafix implements StreamPipe<StreamReceiver>, Maps { // checkstyle

public Metafix() {
flattener.setReceiver(new DefaultStreamReceiver() {

@Override
public void literal(final String name, final String value) {
// TODO: keep flattener as option?
// add(currentRecord, name, value);
final String[] split = Value.split(name);
addValue(split[split.length - 1], new Value(value, name));
// TODO could this help with https://github.com/metafacture/metafacture-fix/issues/147?
// TODO use full path here to insert only once?
// new FixPath(name).insertInto(currentRecord, InsertMode.APPEND, new Value(value));
}
});
}
Expand Down Expand Up @@ -197,7 +201,7 @@ private void emit(final String field, final Value value) {
h.forEach(this::emit);
outputStreamReceiver.endEntity();
})
.orElse(v -> outputStreamReceiver.literal(fieldName, v.toString()));
.orElse(v -> outputStreamReceiver.literal(fieldName, v.asString()));
}

if (isMulti) {
Expand Down Expand Up @@ -230,7 +234,6 @@ public void startEntity(final String name) {
}

final Value value = isArrayName(name) ? Value.newArray() : Value.newHash();

addValue(name, value);
entities.add(value);

Expand All @@ -247,9 +250,7 @@ public void endEntity() {
@Override
public void literal(final String name, final String value) {
LOG.debug("Putting '{}': '{}'", name, value);
addValue(name, new Value(value));
// TODO: keep flattener as option?
// flattener.literal(name, value);
flattener.literal(name, value);
}

@Override
Expand Down
28 changes: 24 additions & 4 deletions metafix/src/main/java/org/metafacture/metafix/Value.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,15 @@ public class Value {

private final Type type;

private final String path;

public Value(final Array array) {
type = array != null ? Type.Array : null;

this.array = array;
this.hash = null;
this.string = null;
this.path = null;
}

public Value(final List<Value> array) {
Expand All @@ -71,6 +74,7 @@ public Value(final Hash hash) {
this.array = null;
this.hash = hash;
this.string = null;
this.path = null;
}

public Value(final Map<String, Value> hash) {
Expand All @@ -87,12 +91,22 @@ public Value(final String string) {
this.array = null;
this.hash = null;
this.string = string;
this.path = null;
}

public Value(final int integer) {
this(String.valueOf(integer));
}

public Value(final String string, final String path) {
type = string != null ? Type.String : null;

this.array = null;
this.hash = null;
this.string = string;
this.path = path;
}

public static Value newArray() {
return newArray(null);
}
Expand Down Expand Up @@ -213,23 +227,25 @@ public final boolean equals(final Object object) {
return Objects.equals(type, other.type) &&
Objects.equals(array, other.array) &&
Objects.equals(hash, other.hash) &&
Objects.equals(string, other.string);
Objects.equals(string, other.string) &&
Objects.equals(path, other.path);
}

@Override
public final int hashCode() {
return Objects.hashCode(type) +
Objects.hashCode(array) +
Objects.hashCode(hash) +
Objects.hashCode(string);
Objects.hashCode(string) +
Objects.hashCode(path);
}

@Override
public String toString() {
return isNull() ? null : extractType((m, c) -> m
.ifArray(a -> c.accept(a.toString()))
.ifHash(h -> c.accept(h.toString()))
.ifString(c)
.ifString(s -> c.accept(path == null ? s : String.format("<%s>@<%s>", s, path)))
.orElseThrow()
);
}
Expand All @@ -238,6 +254,10 @@ public String toString() {
return fieldPath.split(FIELD_PATH_SEPARATOR);
}

public String getPath() {
return path;
}

enum Type {
Array,
Hash,
Expand Down Expand Up @@ -538,7 +558,7 @@ public void addAll(final Hash hash) {
*/
public void add(final String field, final Value newValue) {
final Value oldValue = new FixPath(field).findIn(this);
put(field, oldValue == null ? newValue : oldValue.asList(a1 -> newValue.asList(a2 -> a2.forEach(a1::add))));
put(field, oldValue == null ? newValue : oldValue.asList(oldVals -> newValue.asList(newVals -> newVals.forEach(oldVals::add))));
}

/**
Expand Down
Loading

0 comments on commit c25ede3

Please sign in to comment.