From 40eee0bfa5378eb02d056f02b3a24c03354f4abb Mon Sep 17 00:00:00 2001 From: Jens Wille Date: Wed, 12 Jan 2022 16:06:01 +0100 Subject: [PATCH 1/3] Allow custom Java functions to be called from Fix. (#14) --- .../java/org/metafacture/metafix/Fix.xtext | 2 +- .../org/metafacture/metafix/FixMethod.java | 64 ++++++++++++------- .../metafix/RecordTransformer.java | 14 ++-- .../metafacture/metafix/api/FixFunction.java | 51 +++++++++++++++ .../metafix/MetafixMethodTest.java | 21 ++++++ .../metafix/util/TestFunction.java | 40 ++++++++++++ misc/vim/syntax/metafacture-fix.vim | 2 +- 7 files changed, 165 insertions(+), 29 deletions(-) create mode 100644 metafix/src/main/java/org/metafacture/metafix/api/FixFunction.java create mode 100644 metafix/src/test/java/org/metafacture/metafix/util/TestFunction.java diff --git a/metafix/src/main/java/org/metafacture/metafix/Fix.xtext b/metafix/src/main/java/org/metafacture/metafix/Fix.xtext index f107b2c2..5bb15f9a 100644 --- a/metafix/src/main/java/org/metafacture/metafix/Fix.xtext +++ b/metafix/src/main/java/org/metafacture/metafix/Fix.xtext @@ -46,7 +46,7 @@ Do: ; MethodCall: - name = ValidID '(' ( params += (QualifiedName|STRING) ( ',' params += (QualifiedName|STRING) )* ','? )? ( options = Options )? ')' + name = QualifiedName '(' ( params += (QualifiedName|STRING) ( ',' params += (QualifiedName|STRING) )* ','? )? ( options = Options )? ')' ; Options: diff --git a/metafix/src/main/java/org/metafacture/metafix/FixMethod.java b/metafix/src/main/java/org/metafacture/metafix/FixMethod.java index 2d3504c1..b54a8590 100644 --- a/metafix/src/main/java/org/metafacture/metafix/FixMethod.java +++ b/metafix/src/main/java/org/metafacture/metafix/FixMethod.java @@ -16,17 +16,16 @@ package org.metafacture.metafix; +import org.metafacture.metafix.api.FixFunction; import org.metafacture.metamorph.api.Maps; import org.metafacture.metamorph.maps.FileMap; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Random; -import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.UnaryOperator; @@ -35,16 +34,18 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -enum FixMethod { +public enum FixMethod implements FixFunction { // SCRIPT-LEVEL METHODS: nothing { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { // do nothing } }, put_filemap { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { final String fileName = params.get(0); final FileMap fileMap = new FileMap(); @@ -56,16 +57,19 @@ public void apply(final Metafix metafix, final Record record, final List } }, put_map { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { metafix.putMap(params.get(0), options); } }, put_var { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { metafix.getVars().put(params.get(0), params.get(1)); } }, put_vars { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { metafix.getVars().putAll(options); } @@ -74,11 +78,13 @@ public void apply(final Metafix metafix, final Record record, final List // RECORD-LEVEL METHODS: add_field { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { record.append(params.get(0), params.get(1)); } }, array { // array-from-hash + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { final String field = params.get(0); @@ -93,11 +99,13 @@ public void apply(final Metafix metafix, final Record record, final List } }, copy_field { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { record.copy(params); } }, format { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { final String field = params.get(0); @@ -108,6 +116,7 @@ public void apply(final Metafix metafix, final Record record, final List } }, hash { // hash-from-array + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { final String field = params.get(0); @@ -119,12 +128,14 @@ public void apply(final Metafix metafix, final Record record, final List } }, move_field { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { record.copy(params); record.removeNested(params.get(0)); } }, parse_text { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { final String field = params.get(0); @@ -162,6 +173,7 @@ public void apply(final Metafix metafix, final Record record, final List } }, paste { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { final String joinChar = options.get("join_char"); record.replace(params.get(0), params.subList(1, params.size()).stream() @@ -175,6 +187,7 @@ private boolean literalString(final String s) { } }, random { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { final String field = params.get(0); final int max = getInteger(params, 1); @@ -183,16 +196,19 @@ public void apply(final Metafix metafix, final Record record, final List } }, reject { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { record.setReject(true); } }, remove_field { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { params.forEach(record::removeNested); } }, rename { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { record.transformField(params.get(0), v -> { final String search = params.get(1); @@ -205,11 +221,13 @@ public void apply(final Metafix metafix, final Record record, final List } }, retain { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { record.retainFields(params); } }, set_array { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { final String field = params.get(0); final List toAdd = params.subList(1, params.size()); @@ -222,6 +240,7 @@ public void apply(final Metafix metafix, final Record record, final List } }, set_field { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { final String field = params.get(0); @@ -230,6 +249,7 @@ public void apply(final Metafix metafix, final Record record, final List } }, set_hash { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { final String field = params.get(0); @@ -245,6 +265,7 @@ public void apply(final Metafix metafix, final Record record, final List } }, vacuum { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { record.removeEmptyValues(); } @@ -255,28 +276,33 @@ public void apply(final Metafix metafix, final Record record, final List // TODO SPEC: switch to morph-style named params in general? append { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { final String value = params.get(1); record.transformFields(params, s -> s + value); } }, capitalize { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { record.transformFields(params, s -> s.substring(0, 1).toUpperCase() + s.substring(1)); } }, count { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { record.transformField(params.get(0), v -> v.isArray() ? new Value(v.asArray().size()) : v.isHash() ? new Value(v.asHash().size()) : null); } }, downcase { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { record.transformFields(params, String::toLowerCase); } }, filter { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { record.transformField(params.get(0), v -> { final Pattern search = Pattern.compile(params.get(1)); @@ -288,12 +314,14 @@ public void apply(final Metafix metafix, final Record record, final List } }, index { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { final String search = params.get(1); record.transformFields(params, s -> String.valueOf(s.indexOf(search))); // TODO: multiple } }, join_field { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { record.transformField(params.get(0), v -> { final String joinChar = params.size() > 1 ? params.get(1) : ""; @@ -302,6 +330,7 @@ public void apply(final Metafix metafix, final Record record, final List } }, lookup { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { final Map map; @@ -323,12 +352,14 @@ public void apply(final Metafix metafix, final Record record, final List } }, prepend { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { final String value = params.get(1); record.transformFields(params, s -> value + s); } }, replace_all { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { final String search = params.get(1); final String replace = params.get(2); @@ -337,6 +368,7 @@ public void apply(final Metafix metafix, final Record record, final List } }, reverse { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { record.transformField(params.get(0), v -> { final Value result; @@ -358,6 +390,7 @@ else if (v.isArray()) { } }, sort_field { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { record.transformField(params.get(0), v -> { final boolean numeric = getBoolean(options, "numeric"); @@ -375,6 +408,7 @@ public void apply(final Metafix metafix, final Record record, final List } }, split_field { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { record.transformField(params.get(0), v -> { final String splitChar = params.size() > 1 ? params.get(1) : "\\s+"; @@ -390,27 +424,32 @@ public void apply(final Metafix metafix, final Record record, final List } }, substring { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { record.transformFields(params, s -> s.substring(getInteger(params, 1), getInteger(params, 2) - 1)); } }, sum { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { record.transformField(params.get(0), v -> v.isArray() ? new Value(v.asArray().stream().map(Value::asString).mapToInt(Integer::parseInt).sum()) : null); } }, trim { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { record.transformFields(params, String::trim); } }, uniq { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { record.transformField(params.get(0), v -> v.isArray() ? newArray(unique(v.asArray().stream())) : null); } }, upcase { + @Override public void apply(final Metafix metafix, final Record record, final List params, final Map options) { record.transformFields(params, String::toUpperCase); } @@ -426,23 +465,4 @@ public void apply(final Metafix metafix, final Record record, final List private static final Random RANDOM = new Random(); - private static boolean getBoolean(final Map options, final String key) { - return Boolean.parseBoolean(options.get(key)); - } - - private static int getInteger(final List params, final int index) { - return Integer.parseInt(params.get(index)); - } - - private static Value newArray(final Stream stream) { - return Value.newArray(a -> stream.forEach(a::add)); - } - - private static Stream unique(final Stream stream) { - final Set set = new HashSet<>(); - return stream.filter(set::add); - } - - abstract void apply(Metafix metafix, Record record, List params, Map options); - } diff --git a/metafix/src/main/java/org/metafacture/metafix/RecordTransformer.java b/metafix/src/main/java/org/metafacture/metafix/RecordTransformer.java index 38b314d2..80f6e0d0 100644 --- a/metafix/src/main/java/org/metafacture/metafix/RecordTransformer.java +++ b/metafix/src/main/java/org/metafacture/metafix/RecordTransformer.java @@ -17,8 +17,10 @@ package org.metafacture.metafix; import org.metafacture.commons.StringUtil; +import org.metafacture.commons.reflection.ReflectionUtil; import org.metafacture.framework.MetafactureException; import org.metafacture.metafix.FixPredicate.Quantifier; +import org.metafacture.metafix.api.FixFunction; import org.metafacture.metafix.fix.Do; import org.metafacture.metafix.fix.ElsIf; import org.metafacture.metafix.fix.Else; @@ -32,7 +34,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -184,10 +186,12 @@ private boolean testConditional(final String conditional, final List par } private void processFunction(final Expression expression, final List params) { + final String name = expression.getName(); + try { - final FixMethod method = FixMethod.valueOf(expression.getName()); - final Map options = options(((MethodCall) expression).getOptions()); - method.apply(metafix, record, params, options); + final FixFunction function = name.contains(".") ? + ReflectionUtil.loadClass(name, FixFunction.class).newInstance() : FixMethod.valueOf(name); + function.apply(metafix, record, params, options(((MethodCall) expression).getOptions())); } catch (final IllegalArgumentException e) { throw new MetafactureException(e); @@ -195,7 +199,7 @@ private void processFunction(final Expression expression, final List par } private Map options(final Options options) { - final Map map = new HashMap<>(); + final Map map = new LinkedHashMap<>(); if (options != null) { for (int i = 0; i < options.getKeys().size(); i += 1) { map.put(options.getKeys().get(i), options.getValues().get(i)); diff --git a/metafix/src/main/java/org/metafacture/metafix/api/FixFunction.java b/metafix/src/main/java/org/metafacture/metafix/api/FixFunction.java new file mode 100644 index 00000000..842ecc3f --- /dev/null +++ b/metafix/src/main/java/org/metafacture/metafix/api/FixFunction.java @@ -0,0 +1,51 @@ +/* + * Copyright 2022 hbz NRW + * + * Licensed under the Apache License, Version 2.0 the "License"; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.metafacture.metafix.api; + +import org.metafacture.metafix.Metafix; +import org.metafacture.metafix.Record; +import org.metafacture.metafix.Value; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; + +@FunctionalInterface +public interface FixFunction { + + void apply(Metafix metafix, Record record, List params, Map options); + + default boolean getBoolean(final Map options, final String key) { + return Boolean.parseBoolean(options.get(key)); + } + + default int getInteger(final List params, final int index) { + return Integer.parseInt(params.get(index)); + } + + default Value newArray(final Stream stream) { + return Value.newArray(a -> stream.forEach(a::add)); + } + + default Stream unique(final Stream stream) { + final Set set = new HashSet<>(); + return stream.filter(set::add); + } + +} diff --git a/metafix/src/test/java/org/metafacture/metafix/MetafixMethodTest.java b/metafix/src/test/java/org/metafacture/metafix/MetafixMethodTest.java index d4d1e6b9..d58793ac 100644 --- a/metafix/src/test/java/org/metafacture/metafix/MetafixMethodTest.java +++ b/metafix/src/test/java/org/metafacture/metafix/MetafixMethodTest.java @@ -999,6 +999,27 @@ public void shouldRemoveDuplicates() { ); } + @Test + public void shouldApplyCustomJavaFunction() { + MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( + "org.metafacture.metafix.util.TestFunction(data, foo: '42', bar: 'baz')" + ), + i -> { + i.startRecord("1"); + i.literal("title", "marc"); + i.endRecord(); + }, + o -> { + o.get().startRecord("1"); + o.get().literal("title", "marc"); + o.get().literal("test", "DATA"); + o.get().literal("foo", "42"); + o.get().literal("bar", "baz"); + o.get().endRecord(); + } + ); + } + private void assertThrows(final Class expectedClass, final String expectedMessage, final Executable executable) { final Throwable exception = Assertions.assertThrows(MetafactureException.class, executable).getCause(); Assertions.assertSame(expectedClass, exception.getClass()); diff --git a/metafix/src/test/java/org/metafacture/metafix/util/TestFunction.java b/metafix/src/test/java/org/metafacture/metafix/util/TestFunction.java new file mode 100644 index 00000000..7d25825e --- /dev/null +++ b/metafix/src/test/java/org/metafacture/metafix/util/TestFunction.java @@ -0,0 +1,40 @@ +/* + * Copyright 2022 hbz NRW + * + * Licensed under the Apache License, Version 2.0 the "License"; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.metafacture.metafix.util; + +import org.metafacture.metafix.FixMethod; +import org.metafacture.metafix.Metafix; +import org.metafacture.metafix.Record; +import org.metafacture.metafix.api.FixFunction; + +import java.util.List; +import java.util.Map; + +public class TestFunction implements FixFunction { + + public TestFunction() { + } + + public void apply(final Metafix metafix, final Record record, final List params, final Map options) { + params.add(params.get(0).toUpperCase()); + params.set(0, "test"); + FixMethod.add_field.apply(metafix, record, params, options); + + options.forEach(record::append); + } + +} diff --git a/misc/vim/syntax/metafacture-fix.vim b/misc/vim/syntax/metafacture-fix.vim index 4a5030f0..bc35c151 100644 --- a/misc/vim/syntax/metafacture-fix.vim +++ b/misc/vim/syntax/metafacture-fix.vim @@ -10,7 +10,7 @@ syn keyword fixPreProc use syn keyword fixSelector reject select syn match fixBareString /\v[^[:space:]\\,;:=>()"'\$*]+/ syn match fixComment /\v(#|\/\/).*$/ -syn match fixFunction /\v([a-z][_0-9a-zA-Z]*\.)*[a-z][_0-9a-zA-Z]*\s*\(/me=e-1,he=e-1 +syn match fixFunction /\v([a-z][_0-9a-zA-Z]*\.)*[a-zA-Z][_0-9a-zA-Z]*\s*\(/me=e-1,he=e-1 syn match fixOperator /\v(\&\&|\|\|)/ syn match fixWildcard /\v\$(append|first|last|prepend)>/ syn match fixWildcard /\v\*/ From ee0e578454b0fd16e417e43f0589406e9d209329 Mon Sep 17 00:00:00 2001 From: Jens Wille Date: Thu, 13 Jan 2022 09:09:25 +0100 Subject: [PATCH 2/3] Refactor `FixPredicate` into static conditionals. In preparation for custom Java predicates. (#14) --- .../metafacture/metafix/FixConditional.java | 108 ++++++++++++++++++ .../org/metafacture/metafix/FixPredicate.java | 80 ------------- .../metafix/RecordTransformer.java | 17 +-- 3 files changed, 112 insertions(+), 93 deletions(-) create mode 100644 metafix/src/main/java/org/metafacture/metafix/FixConditional.java delete mode 100644 metafix/src/main/java/org/metafacture/metafix/FixPredicate.java diff --git a/metafix/src/main/java/org/metafacture/metafix/FixConditional.java b/metafix/src/main/java/org/metafacture/metafix/FixConditional.java new file mode 100644 index 00000000..0f940874 --- /dev/null +++ b/metafix/src/main/java/org/metafacture/metafix/FixConditional.java @@ -0,0 +1,108 @@ +/* + * Copyright 2021 Fabian Steeg, hbz + * + * Licensed under the Apache License, Version 2.0 the "License"; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.metafacture.metafix; + +import java.util.List; +import java.util.Map; +import java.util.function.BiPredicate; +import java.util.function.Predicate; +import java.util.stream.Stream; + +enum FixConditional { + + all_contain { + @Override + public boolean test(final Metafix metafix, final Record record, final List params, final Map options) { + return testConditional(record, params, ALL, CONTAINS); + } + }, + any_contain { + @Override + public boolean test(final Metafix metafix, final Record record, final List params, final Map options) { + return testConditional(record, params, ANY, CONTAINS); + } + }, + none_contain { + @Override + public boolean test(final Metafix metafix, final Record record, final List params, final Map options) { + return !any_contain.test(metafix, record, params, options); + } + }, + + all_equal { + @Override + public boolean test(final Metafix metafix, final Record record, final List params, final Map options) { + return testConditional(record, params, ALL, EQUALS); + } + }, + any_equal { + @Override + public boolean test(final Metafix metafix, final Record record, final List params, final Map options) { + return testConditional(record, params, ANY, EQUALS); + } + }, + none_equal { + @Override + public boolean test(final Metafix metafix, final Record record, final List params, final Map options) { + return !any_equal.test(metafix, record, params, options); + } + }, + + exists { + @Override + public boolean test(final Metafix metafix, final Record record, final List params, final Map options) { + return record.containsField(params.get(0)); + } + }, + + all_match { + @Override + public boolean test(final Metafix metafix, final Record record, final List params, final Map options) { + return testConditional(record, params, ALL, MATCHES); + } + }, + any_match { + @Override + public boolean test(final Metafix metafix, final Record record, final List params, final Map options) { + return testConditional(record, params, ANY, MATCHES); + } + }, + none_match { + @Override + public boolean test(final Metafix metafix, final Record record, final List params, final Map options) { + return !any_match.test(metafix, record, params, options); + } + }; + + public static final BiPredicate, Predicate> ALL = (s, p) -> s.allMatch(p); + public static final BiPredicate, Predicate> ANY = (s, p) -> s.anyMatch(p); + + public static final BiPredicate CONTAINS = (v, s) -> v.toString().contains(s); + public static final BiPredicate EQUALS = (v, s) -> v.toString().equals(s); + public static final BiPredicate MATCHES = (v, s) -> v.toString().matches(s); + + private static boolean testConditional(final Record record, final List params, final BiPredicate, Predicate> qualifier, final BiPredicate conditional) { + final String field = params.get(0); + final String string = params.get(1); + + final Value value = record.find(field); + return value != null && qualifier.test(value.asList(null).asArray().stream(), v -> conditional.test(v, string)); + } + + abstract boolean test(Metafix metafix, Record record, List params, Map options); + +} diff --git a/metafix/src/main/java/org/metafacture/metafix/FixPredicate.java b/metafix/src/main/java/org/metafacture/metafix/FixPredicate.java deleted file mode 100644 index 6d280c58..00000000 --- a/metafix/src/main/java/org/metafacture/metafix/FixPredicate.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2021 Fabian Steeg, hbz - * - * Licensed under the Apache License, Version 2.0 the "License"; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.metafacture.metafix; - -import java.util.List; -import java.util.function.Predicate; -import java.util.stream.Stream; - -enum FixPredicate { - - contain { - @Override - public Predicate of(final String string) { - return v -> v.toString().contains(string); - } - }, - equal { - @Override - public Predicate of(final String string) { - return v -> v.toString().equals(string); - } - }, - match { - @Override - public Predicate of(final String string) { - return v -> v.toString().matches(string); - } - }; - - abstract Predicate of(String string); - - enum Quantifier { - - all { - @Override - protected boolean test(final Record record, final String fieldName, final Predicate p) { - return testStream(record, fieldName, s -> s.allMatch(p)); - } - - }, - any { - @Override - protected boolean test(final Record record, final String fieldName, final Predicate p) { - return testStream(record, fieldName, s -> s.anyMatch(p)); - } - }, - none { - @Override - protected boolean test(final Record record, final String fieldName, final Predicate p) { - return !any.test(record, fieldName, p); - } - }; - - boolean testStream(final Record record, final String fieldName, final Predicate> p) { - final Value value = record.find(fieldName); - return value != null && p.test(value.asList(null).asArray().stream()); - } - - public boolean test(final Record record, final FixPredicate p, final List params) { - return test(record, params.get(0), p.of(params.get(1))); - } - - protected abstract boolean test(Record record, String fieldName, Predicate p); - } - -} diff --git a/metafix/src/main/java/org/metafacture/metafix/RecordTransformer.java b/metafix/src/main/java/org/metafacture/metafix/RecordTransformer.java index 80f6e0d0..1c259a00 100644 --- a/metafix/src/main/java/org/metafacture/metafix/RecordTransformer.java +++ b/metafix/src/main/java/org/metafacture/metafix/RecordTransformer.java @@ -19,7 +19,6 @@ import org.metafacture.commons.StringUtil; import org.metafacture.commons.reflection.ReflectionUtil; import org.metafacture.framework.MetafactureException; -import org.metafacture.metafix.FixPredicate.Quantifier; import org.metafacture.metafix.api.FixFunction; import org.metafacture.metafix.fix.Do; import org.metafacture.metafix.fix.ElsIf; @@ -160,29 +159,21 @@ private void processUnless(final Unless unless, final List params) { private boolean testConditional(final String conditional, final List params) { LOG.debug(": {} parameters: {}", conditional, params); - boolean result = false; - if ("exists".equals(conditional)) { - return record.containsField(params.get(0)); - } - if (!conditional.contains("_")) { - throw new IllegalArgumentException("Missing quantifier prefix (all_, any_, none_) for " + conditional); - } - final String[] quantifierAndPredicate = conditional.split("_"); + try { - final Quantifier quantifier = FixPredicate.Quantifier.valueOf(quantifierAndPredicate[0]); - final FixPredicate predicate = FixPredicate.valueOf(quantifierAndPredicate[1]); - result = quantifier.test(record, predicate, params); + final FixConditional predicate = FixConditional.valueOf(conditional); + return predicate.test(metafix, record, params, options(null)); // TODO: options } catch (final IllegalArgumentException e) { throw new MetafactureException(e); } + // TODO, possibly: use morph functions here (& in processFunction): // final FunctionFactory functionFactory = new FunctionFactory(); // functionFactory.registerClass("not_equals", NotEquals.class); // functionFactory.registerClass("replace_all", Replace.class); // final Function function = functionFactory.newInstance(conditional, // resolvedAttributeMap(params, theIf.getOptions())); - return result; } private void processFunction(final Expression expression, final List params) { From 01cac682c4a20be1a30436e432459c23fc87d720 Mon Sep 17 00:00:00 2001 From: Jens Wille Date: Thu, 13 Jan 2022 09:30:31 +0100 Subject: [PATCH 3/3] Allow custom Java predicates to be called from Fix. (#14) --- .../java/org/metafacture/metafix/Fix.xtext | 6 +-- .../metafacture/metafix/FixConditional.java | 24 ++------- .../metafix/RecordTransformer.java | 13 +++-- .../metafacture/metafix/api/FixPredicate.java | 49 +++++++++++++++++++ .../metafacture/metafix/MetafixIfTest.java | 38 ++++++++++++++ .../metafix/util/TestPredicate.java | 37 ++++++++++++++ 6 files changed, 138 insertions(+), 29 deletions(-) create mode 100644 metafix/src/main/java/org/metafacture/metafix/api/FixPredicate.java create mode 100644 metafix/src/test/java/org/metafacture/metafix/util/TestPredicate.java diff --git a/metafix/src/main/java/org/metafacture/metafix/Fix.xtext b/metafix/src/main/java/org/metafacture/metafix/Fix.xtext index 5bb15f9a..09bd671b 100644 --- a/metafix/src/main/java/org/metafacture/metafix/Fix.xtext +++ b/metafix/src/main/java/org/metafacture/metafix/Fix.xtext @@ -16,13 +16,13 @@ Expression: ; Unless: - 'unless' name = ValidID '(' ( params += (QualifiedName|STRING) ( ',' params += (QualifiedName|STRING) )* )? ')' + 'unless' name = QualifiedName '(' ( params += (QualifiedName|STRING) ( ',' params += (QualifiedName|STRING) )* )? ')' elements += Expression* 'end' ; If: - 'if' name = ValidID '(' ( params += (QualifiedName|STRING) ( ',' params += (QualifiedName|STRING) )* )? ')' + 'if' name = QualifiedName '(' ( params += (QualifiedName|STRING) ( ',' params += (QualifiedName|STRING) )* )? ')' elements += Expression* elseIf = ElsIf? else = Else? @@ -30,7 +30,7 @@ If: ; ElsIf: - 'elsif' name = ValidID '(' ( params += (QualifiedName|STRING) ( ',' params += (QualifiedName|STRING) )* )? ')' + 'elsif' name = QualifiedName '(' ( params += (QualifiedName|STRING) ( ',' params += (QualifiedName|STRING) )* )? ')' elements += Expression* ; diff --git a/metafix/src/main/java/org/metafacture/metafix/FixConditional.java b/metafix/src/main/java/org/metafacture/metafix/FixConditional.java index 0f940874..d3f61b37 100644 --- a/metafix/src/main/java/org/metafacture/metafix/FixConditional.java +++ b/metafix/src/main/java/org/metafacture/metafix/FixConditional.java @@ -16,13 +16,12 @@ package org.metafacture.metafix; +import org.metafacture.metafix.api.FixPredicate; + import java.util.List; import java.util.Map; -import java.util.function.BiPredicate; -import java.util.function.Predicate; -import java.util.stream.Stream; -enum FixConditional { +public enum FixConditional implements FixPredicate { all_contain { @Override @@ -86,23 +85,6 @@ public boolean test(final Metafix metafix, final Record record, final List params, final Map options) { return !any_match.test(metafix, record, params, options); } - }; - - public static final BiPredicate, Predicate> ALL = (s, p) -> s.allMatch(p); - public static final BiPredicate, Predicate> ANY = (s, p) -> s.anyMatch(p); - - public static final BiPredicate CONTAINS = (v, s) -> v.toString().contains(s); - public static final BiPredicate EQUALS = (v, s) -> v.toString().equals(s); - public static final BiPredicate MATCHES = (v, s) -> v.toString().matches(s); - - private static boolean testConditional(final Record record, final List params, final BiPredicate, Predicate> qualifier, final BiPredicate conditional) { - final String field = params.get(0); - final String string = params.get(1); - - final Value value = record.find(field); - return value != null && qualifier.test(value.asList(null).asArray().stream(), v -> conditional.test(v, string)); } - abstract boolean test(Metafix metafix, Record record, List params, Map options); - } diff --git a/metafix/src/main/java/org/metafacture/metafix/RecordTransformer.java b/metafix/src/main/java/org/metafacture/metafix/RecordTransformer.java index 1c259a00..5a10fa81 100644 --- a/metafix/src/main/java/org/metafacture/metafix/RecordTransformer.java +++ b/metafix/src/main/java/org/metafacture/metafix/RecordTransformer.java @@ -20,6 +20,7 @@ import org.metafacture.commons.reflection.ReflectionUtil; import org.metafacture.framework.MetafactureException; import org.metafacture.metafix.api.FixFunction; +import org.metafacture.metafix.api.FixPredicate; import org.metafacture.metafix.fix.Do; import org.metafacture.metafix.fix.ElsIf; import org.metafacture.metafix.fix.Else; @@ -36,6 +37,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.stream.Collectors; /** @@ -161,7 +163,7 @@ private boolean testConditional(final String conditional, final List par LOG.debug(": {} parameters: {}", conditional, params); try { - final FixConditional predicate = FixConditional.valueOf(conditional); + final FixPredicate predicate = getInstance(conditional, FixPredicate.class, FixConditional::valueOf); return predicate.test(metafix, record, params, options(null)); // TODO: options } catch (final IllegalArgumentException e) { @@ -177,11 +179,8 @@ private boolean testConditional(final String conditional, final List par } private void processFunction(final Expression expression, final List params) { - final String name = expression.getName(); - try { - final FixFunction function = name.contains(".") ? - ReflectionUtil.loadClass(name, FixFunction.class).newInstance() : FixMethod.valueOf(name); + final FixFunction function = getInstance(expression.getName(), FixFunction.class, FixMethod::valueOf); function.apply(metafix, record, params, options(((MethodCall) expression).getOptions())); } catch (final IllegalArgumentException e) { @@ -189,6 +188,10 @@ private void processFunction(final Expression expression, final List par } } + private T getInstance(final String name, final Class baseType, final Function enumFunction) { + return name.contains(".") ? ReflectionUtil.loadClass(name, baseType).newInstance() : enumFunction.apply(name); + } + private Map options(final Options options) { final Map map = new LinkedHashMap<>(); if (options != null) { diff --git a/metafix/src/main/java/org/metafacture/metafix/api/FixPredicate.java b/metafix/src/main/java/org/metafacture/metafix/api/FixPredicate.java new file mode 100644 index 00000000..ce621656 --- /dev/null +++ b/metafix/src/main/java/org/metafacture/metafix/api/FixPredicate.java @@ -0,0 +1,49 @@ +/* + * Copyright 2022 hbz NRW + * + * Licensed under the Apache License, Version 2.0 the "License"; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.metafacture.metafix.api; + +import org.metafacture.metafix.Metafix; +import org.metafacture.metafix.Record; +import org.metafacture.metafix.Value; + +import java.util.List; +import java.util.Map; +import java.util.function.BiPredicate; +import java.util.function.Predicate; +import java.util.stream.Stream; + +@FunctionalInterface +public interface FixPredicate { + + BiPredicate, Predicate> ALL = Stream::allMatch; + BiPredicate, Predicate> ANY = Stream::anyMatch; + + BiPredicate CONTAINS = String::contains; + BiPredicate EQUALS = String::equals; + BiPredicate MATCHES = String::matches; + + boolean test(Metafix metafix, Record record, List params, Map options); + + default boolean testConditional(final Record record, final List params, final BiPredicate, Predicate> qualifier, final BiPredicate conditional) { + final String field = params.get(0); + final String string = params.get(1); + + final Value value = record.find(field); + return value != null && qualifier.test(value.asList(null).asArray().stream(), v -> conditional.test(v.toString(), string)); + } + +} diff --git a/metafix/src/test/java/org/metafacture/metafix/MetafixIfTest.java b/metafix/src/test/java/org/metafacture/metafix/MetafixIfTest.java index d9e71e03..d57a963a 100644 --- a/metafix/src/test/java/org/metafacture/metafix/MetafixIfTest.java +++ b/metafix/src/test/java/org/metafacture/metafix/MetafixIfTest.java @@ -635,4 +635,42 @@ public void shouldResolveVariablesInUnless() { ); } + @Test + public void shouldApplyCustomJavaPredicate() { + MetafixTestHelpers.assertFix(streamReceiver, Arrays.asList( + "if org.metafacture.metafix.util.TestPredicate(name, Test)", + " add_field('type', 'TEST')", + "end" + ), + i -> { + i.startRecord("1"); + i.literal("name", "Max"); + i.endRecord(); + + i.startRecord("2"); + i.literal("name", "Test"); + i.endRecord(); + + i.startRecord("3"); + i.literal("test", "Some University"); + i.endRecord(); + }, + o -> { + o.get().startRecord("1"); + o.get().literal("name", "Max"); + o.get().endRecord(); + + o.get().startRecord("2"); + o.get().literal("name", "Test"); + o.get().literal("type", "TEST"); + o.get().endRecord(); + + o.get().startRecord("3"); + o.get().literal("test", "Some University"); + o.get().literal("type", "TEST"); + o.get().endRecord(); + } + ); + } + } diff --git a/metafix/src/test/java/org/metafacture/metafix/util/TestPredicate.java b/metafix/src/test/java/org/metafacture/metafix/util/TestPredicate.java new file mode 100644 index 00000000..c9d1cd3a --- /dev/null +++ b/metafix/src/test/java/org/metafacture/metafix/util/TestPredicate.java @@ -0,0 +1,37 @@ +/* + * Copyright 2022 hbz NRW + * + * Licensed under the Apache License, Version 2.0 the "License"; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.metafacture.metafix.util; + +import org.metafacture.metafix.FixConditional; +import org.metafacture.metafix.Metafix; +import org.metafacture.metafix.Record; +import org.metafacture.metafix.api.FixPredicate; + +import java.util.List; +import java.util.Map; + +public class TestPredicate implements FixPredicate { + + public TestPredicate() { + } + + public boolean test(final Metafix metafix, final Record record, final List params, final Map options) { + return !FixConditional.exists.test(metafix, record, params, options) || + FixConditional.any_equal.test(metafix, record, params, options); + } + +}