From 9ca58dded1edb5fde0de9038b326619c0f4aa372 Mon Sep 17 00:00:00 2001 From: Nicolas Laval Date: Tue, 23 Jun 2026 17:43:07 +0200 Subject: [PATCH 1/3] Define function through providers --- vtl-engine/README.md | 6 +- .../fr/insee/vtl/engine/VtlNativeMethods.java | 135 ---------------- .../fr/insee/vtl/engine/VtlScriptEngine.java | 97 +++-------- .../functions/BuiltinFunctionProvider.java | 11 ++ .../functions/NativeFunctionProviders.java | 55 +++++++ .../functions/NativeFunctionRegistry.java | 116 +++++++++++++ .../ArithmeticFunctionsProvider.java | 153 ++++++++++++++++++ .../providers/BooleanFunctionsProvider.java | 45 ++++++ .../ComparisonFunctionsProvider.java | 56 +++++++ .../ComparisonOperatorFunctionsProvider.java | 116 +++++++++++++ .../ConditionalFunctionsProvider.java | 81 ++++++++++ .../providers/DistanceFunctionsProvider.java | 23 +++ .../providers/NumericFunctionsProvider.java | 139 ++++++++++++++++ .../providers/StringFunctionsProvider.java | 119 ++++++++++++++ .../providers/TemporalFunctionsProvider.java} | 153 ++++++++---------- .../providers/UnaryFunctionsProvider.java | 53 ++++++ .../fr/insee/vtl/engine/package-info.java | 6 +- .../functions/LevenshteinProvider.java | 14 -- .../ArithmeticExprOrConcatVisitor.java | 78 +-------- .../expression/ArithmeticVisitor.java | 69 +------- .../visitors/expression/BooleanVisitor.java | 41 +---- .../expression/ComparisonVisitor.java | 122 +------------- .../expression/ConditionalVisitor.java | 82 +--------- .../visitors/expression/UnaryVisitor.java | 42 +---- .../functions/ComparisonFunctionsVisitor.java | 70 +------- .../functions/DistanceFunctionsVisitor.java | 26 +-- .../functions/NumericFunctionsVisitor.java | 139 +--------------- .../functions/StringFunctionsVisitor.java | 132 +-------------- vtl-engine/src/main/java/module-info.java | 3 - .../fr.insee.vtl.model.FunctionProvider | 1 - .../fr/insee/vtl/model/FunctionProvider.java | 9 +- 31 files changed, 1075 insertions(+), 1117 deletions(-) delete mode 100644 vtl-engine/src/main/java/fr/insee/vtl/engine/VtlNativeMethods.java create mode 100644 vtl-engine/src/main/java/fr/insee/vtl/engine/functions/BuiltinFunctionProvider.java create mode 100644 vtl-engine/src/main/java/fr/insee/vtl/engine/functions/NativeFunctionProviders.java create mode 100644 vtl-engine/src/main/java/fr/insee/vtl/engine/functions/NativeFunctionRegistry.java create mode 100644 vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/ArithmeticFunctionsProvider.java create mode 100644 vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/BooleanFunctionsProvider.java create mode 100644 vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/ComparisonFunctionsProvider.java create mode 100644 vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/ComparisonOperatorFunctionsProvider.java create mode 100644 vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/ConditionalFunctionsProvider.java create mode 100644 vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/DistanceFunctionsProvider.java create mode 100644 vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/NumericFunctionsProvider.java create mode 100644 vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/StringFunctionsProvider.java rename vtl-engine/src/main/java/fr/insee/vtl/engine/{expressions/TemporalFunctions.java => functions/providers/TemporalFunctionsProvider.java} (60%) create mode 100644 vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/UnaryFunctionsProvider.java delete mode 100644 vtl-engine/src/main/java/fr/insee/vtl/engine/semantics/functions/LevenshteinProvider.java delete mode 100644 vtl-engine/src/main/resources/META-INF/services/fr.insee.vtl.model.FunctionProvider diff --git a/vtl-engine/README.md b/vtl-engine/README.md index bbaeaefda..aa1c83d74 100644 --- a/vtl-engine/README.md +++ b/vtl-engine/README.md @@ -35,8 +35,8 @@ flowchart TB DR["DatasetResults.withStructure"] end - subgraph scalar["expressions — scalar natives"] - TF["TemporalFunctions, …"] + subgraph scalar["functions/providers — scalar natives"] + TF["NumericFunctionsProvider, …"] end subgraph processors["processors — default runtime"] @@ -95,5 +95,5 @@ flowchart TB | **vtl-engine** | `semantics.` | VTL operator logic (`join`, `aggregation`, `validation`, `time`, …) | | **vtl-engine** | `semantics.attribute` | Cross-cutting viral attribute propagation | | **vtl-engine** | `processors.InMemoryProcessingEngine` | Default in-memory `ProcessingEngine` | -| **vtl-engine** | `expressions` | Scalar native helpers (e.g. `TemporalFunctions`) | +| **vtl-engine** | `functions.providers` | Built-in scalar native functions (`Map>`) | | **vtl-spark** / **vtl-spark4** | `SparkProcessingEngine` | Spark-backed `ProcessingEngine` | diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/VtlNativeMethods.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/VtlNativeMethods.java deleted file mode 100644 index 334004852..000000000 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/VtlNativeMethods.java +++ /dev/null @@ -1,135 +0,0 @@ -package fr.insee.vtl.engine; - -import com.github.hervian.reflection.Fun; -import fr.insee.vtl.engine.expressions.TemporalFunctions; -import fr.insee.vtl.engine.visitors.expression.*; -import fr.insee.vtl.engine.visitors.expression.functions.ComparisonFunctionsVisitor; -import fr.insee.vtl.engine.visitors.expression.functions.DistanceFunctionsVisitor; -import fr.insee.vtl.engine.visitors.expression.functions.NumericFunctionsVisitor; -import fr.insee.vtl.engine.visitors.expression.functions.StringFunctionsVisitor; -import java.lang.reflect.Method; -import java.time.*; -import java.util.Set; -import org.threeten.extra.Interval; -import org.threeten.extra.PeriodDuration; - -public class VtlNativeMethods { - - public static final Set NATIVE_METHODS = - Set.of( - // NumericFunctionsVisitor - Fun.toMethod(NumericFunctionsVisitor::ceil), - Fun.toMethod(NumericFunctionsVisitor::floor), - Fun.toMethod(NumericFunctionsVisitor::abs), - Fun.toMethod(NumericFunctionsVisitor::exp), - Fun.toMethod(NumericFunctionsVisitor::ln), - Fun.toMethod(NumericFunctionsVisitor::sqrt), - Fun.toMethod(NumericFunctionsVisitor::round), - Fun.toMethod(NumericFunctionsVisitor::trunc), - Fun.toMethod(NumericFunctionsVisitor::mod), - Fun.toMethod(NumericFunctionsVisitor::power), - Fun.toMethod(NumericFunctionsVisitor::random), - Fun.toMethod(NumericFunctionsVisitor::log), - // ArithmeticExprOrConcatVisitor - Fun.toMethod(ArithmeticExprOrConcatVisitor::addition), - Fun.toMethod(ArithmeticExprOrConcatVisitor::addition), - Fun.toMethod(ArithmeticExprOrConcatVisitor::addition), - Fun.toMethod(ArithmeticExprOrConcatVisitor::addition), - Fun.toMethod(ArithmeticExprOrConcatVisitor::subtraction), - Fun.toMethod(ArithmeticExprOrConcatVisitor::subtraction), - Fun.toMethod(ArithmeticExprOrConcatVisitor::subtraction), - Fun.toMethod(ArithmeticExprOrConcatVisitor::subtraction), - Fun.toMethod(ArithmeticExprOrConcatVisitor::concat), - // Conditional - Fun.toMethod(ConditionalVisitor::ifThenElse), - Fun.toMethod(ConditionalVisitor::ifThenElse), - Fun.toMethod(ConditionalVisitor::ifThenElse), - Fun.toMethod(ConditionalVisitor::ifThenElse), - Fun.toMethod(ConditionalVisitor::nvl), - Fun.toMethod(ConditionalVisitor::nvl), - Fun.toMethod(ConditionalVisitor::nvl), - Fun.toMethod(ConditionalVisitor::nvl), - Fun.toMethod(ConditionalVisitor::nvl), - Fun.toMethod(ConditionalVisitor::nvl), - // ArithmeticVisitor - Fun.toMethod(ArithmeticVisitor::multiplication), - Fun.toMethod(ArithmeticVisitor::multiplication), - Fun.toMethod(ArithmeticVisitor::multiplication), - Fun.toMethod(ArithmeticVisitor::multiplication), - Fun.toMethod(ArithmeticVisitor::division), - Fun.toMethod(ArithmeticVisitor::division), - Fun.toMethod(ArithmeticVisitor::division), - Fun.toMethod(ArithmeticVisitor::division), - // DistanceFunctionsVisitor - Fun.toMethod(DistanceFunctionsVisitor::levenshtein), - // String function visitor - Fun.toMethod(StringFunctionsVisitor::trim), - Fun.toMethod(StringFunctionsVisitor::ltrim), - Fun.toMethod(StringFunctionsVisitor::rtrim), - Fun.toMethod(StringFunctionsVisitor::ucase), - Fun.toMethod(StringFunctionsVisitor::lcase), - Fun.toMethod(StringFunctionsVisitor::len), - Fun.toMethod(StringFunctionsVisitor::substr), - Fun.toMethod(StringFunctionsVisitor::replace), - Fun.toMethod(StringFunctionsVisitor::instr), - // ComparisonFunctionsVisitor - Fun.toMethod(ComparisonFunctionsVisitor::between), - Fun.toMethod(ComparisonFunctionsVisitor::charsetMatch), - Fun.toMethod(ComparisonFunctionsVisitor::isNull), - // BooleanVisitor - Fun.toMethod(BooleanVisitor::and), - Fun.toMethod(BooleanVisitor::or), - Fun.toMethod(BooleanVisitor::xor), - // UnaryVisitor - Fun.toMethod(UnaryVisitor::plus), - Fun.toMethod(UnaryVisitor::plus), - Fun.toMethod(UnaryVisitor::minus), - Fun.toMethod(UnaryVisitor::minus), - Fun.toMethod(UnaryVisitor::not), - // ComparisonVisitor - Fun.toMethod(ComparisonVisitor::isEqual), - Fun.toMethod(ComparisonVisitor::isNotEqual), - Fun.toMethod(ComparisonVisitor::isLessThan), - Fun.toMethod(ComparisonVisitor::isGreaterThan), - Fun.toMethod(ComparisonVisitor::isGreaterThanOrEqual), - Fun.toMethod(ComparisonVisitor::isLessThanOrEqual), - Fun.toMethod(ComparisonVisitor::in), - Fun.toMethod(ComparisonVisitor::notIn), - - // Temporal functions - Fun.toMethod(TemporalFunctions::addition), - Fun.toMethod(TemporalFunctions::addition), - Fun.toMethod(TemporalFunctions::addition), - Fun.toMethod(TemporalFunctions::addition), - Fun.toMethod(TemporalFunctions::addition), - Fun.toMethod(TemporalFunctions::addition), - Fun.toMethod(TemporalFunctions::subtraction), - Fun.toMethod(TemporalFunctions::subtraction), - Fun.toMethod(TemporalFunctions::subtraction), - Fun.toMethod(TemporalFunctions::subtraction), - Fun.toMethod(TemporalFunctions::subtraction), - Fun.toMethod(TemporalFunctions::subtraction), - Fun.toMethod(TemporalFunctions::subtraction), - Fun.toMethod(TemporalFunctions::subtraction), - Fun.toMethod(TemporalFunctions::subtraction), - Fun.toMethod(TemporalFunctions::subtraction), - Fun.toMethod(TemporalFunctions::subtraction), - Fun.toMethod(TemporalFunctions::subtraction), - Fun.toMethod(TemporalFunctions::subtraction), - Fun.toMethod(TemporalFunctions::subtraction), - Fun.toMethod(TemporalFunctions::subtraction), - Fun.toMethod(TemporalFunctions::multiplication), - Fun.toMethod(TemporalFunctions::multiplication), - Fun.toMethod(TemporalFunctions::timeshift), - Fun.toMethod(TemporalFunctions::truncate_time), - Fun.toMethod(TemporalFunctions::truncate_time), - Fun.toMethod(TemporalFunctions::truncate_time), - Fun.toMethod(TemporalFunctions::truncate_time), - Fun.toMethod(TemporalFunctions::truncate_time), - Fun.toMethod(TemporalFunctions::truncate_time), - Fun.toMethod(TemporalFunctions::at_zone)); - - private VtlNativeMethods() { - throw new IllegalStateException("Utility class"); - } -} diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/VtlScriptEngine.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/VtlScriptEngine.java index 29fb9be25..54d33e945 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/VtlScriptEngine.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/VtlScriptEngine.java @@ -1,13 +1,12 @@ package fr.insee.vtl.engine; -import static fr.insee.vtl.engine.VtlNativeMethods.NATIVE_METHODS; - import fr.insee.vtl.antlr.runtime.*; import fr.insee.vtl.antlr.runtime.misc.Interval; import fr.insee.vtl.antlr.runtime.tree.ParseTree; import fr.insee.vtl.antlr.runtime.tree.TerminalNode; import fr.insee.vtl.engine.exceptions.VtlRuntimeException; import fr.insee.vtl.engine.exceptions.VtlSyntaxException; +import fr.insee.vtl.engine.functions.NativeFunctionRegistry; import fr.insee.vtl.engine.visitors.AssignmentVisitor; import fr.insee.vtl.model.*; import fr.insee.vtl.model.exceptions.VtlScriptException; @@ -21,7 +20,6 @@ import java.lang.reflect.TypeVariable; import java.util.*; import java.util.stream.Collectors; -import java.util.stream.Stream; import javax.script.*; /** @@ -50,9 +48,9 @@ public class VtlScriptEngine extends AbstractScriptEngine { private final ScriptEngineFactory factory; private final VtlParseCache parseCache = new VtlParseCache(); - private Map methodCache; - - private Map globalMethodCache; + private final NativeFunctionRegistry builtinRegistry = NativeFunctionRegistry.builtins(); + private NativeFunctionRegistry extensionRegistry; + private NativeFunctionRegistry globalRegistry; private volatile Map processingEngineFactories; private volatile String cachedProcessingEngineName; @@ -124,7 +122,7 @@ public static Positioned fromTokens(Token from, Token to) { return () -> position; } - static boolean matchParameters(Method method, Class... classes) { + public static boolean matchParameters(Method method, Class... classes) { Type[] genericParameterTypes = method.getGenericParameterTypes(); Class[] parameterTypes = method.getParameterTypes(); @@ -431,91 +429,42 @@ public ScriptEngineFactory getFactory() { } public VtlMethod findMethod(String name, Collection types) throws NoSuchMethodException { - Set customMethods = - methodCache == null ? Set.of() : new HashSet<>(methodCache.values()); - Set methods = - Stream.concat(NATIVE_METHODS.stream(), customMethods.stream()).collect(Collectors.toSet()); - - List candidates = - methods.stream() - .filter(method -> method.getName().equals(name)) - .filter(method -> matchParameters(method, types.toArray(Class[]::new))) - .collect(Collectors.toList()); - if (candidates.size() == 1) { - return new VtlMethod(candidates.get(0)); - } - // TODO: Handle parameter resolution. - for (Method method : methods) { - if (method.getName().equals(name) - && types.equals(Arrays.asList(method.getParameterTypes()))) { - return new VtlMethod(method); - } + ensureExtensionRegistryLoaded(); + try { + return extensionRegistry.resolve(name, types); + } catch (NoSuchMethodException ignored) { + return builtinRegistry.resolve(name, types); } - throw new NoSuchMethodException(methodToString(name, types)); } public VtlMethod findGlobalMethod(String name, Collection types) throws NoSuchMethodException { - if (globalMethodCache == null) return null; - Set methods = new HashSet<>(globalMethodCache.values()); - - List candidates = - methods.stream() - .filter(method -> method.getName().equals(name)) - .filter(method -> matchParameters(method, types.toArray(Class[]::new))) - .collect(Collectors.toList()); - - if (candidates.size() == 0) { - // It's not a global method + if (globalRegistry == null) { return null; } - - if (candidates.size() == 1) { - return new VtlMethod(candidates.get(0)); - } - // TODO: Handle parameter resolution. - for (Method method : methods) { - if (method.getName().equals(name) - && types.equals(Arrays.asList(method.getParameterTypes()))) { - return new VtlMethod(method); - } - } - throw new NoSuchMethodException(methodToString(name, types)); - } - - private String methodToString(String name, Collection argTypes) { - StringJoiner sj = new StringJoiner(", ", name + "(", ")"); - if (argTypes != null) { - for (Class c : argTypes) { - sj.add(c == null ? "null" : c.getSimpleName()); - } - } - return sj.toString(); + return globalRegistry.resolveOrNull(name, types); } public Method registerMethod(String name, Method method) { - if (methodCache == null) { - loadMethods(); - } - return methodCache.put(name, method); + ensureExtensionRegistryLoaded(); + return extensionRegistry.putAndReturnPrevious(name, method); } public Method registerGlobalMethod(String name, Method method) { - if (globalMethodCache == null) { - globalMethodCache = new LinkedHashMap<>(); + if (globalRegistry == null) { + globalRegistry = NativeFunctionRegistry.empty(); } - return globalMethodCache.put(name, method); + return globalRegistry.putAndReturnPrevious(name, method); } - private void loadMethods() { - methodCache = new LinkedHashMap<>(); + private void ensureExtensionRegistryLoaded() { + if (extensionRegistry != null) { + return; + } + extensionRegistry = NativeFunctionRegistry.empty(); ServiceLoader providers = ServiceLoader.load(FunctionProvider.class); for (FunctionProvider provider : providers) { - Map functions = provider.getFunctions(this); - // TODO: rename function name with 'name' instead of java name - for (String name : functions.keySet()) { - methodCache.put(name, functions.get(name)); - } + extensionRegistry.registerAll(provider.getFunctions(this)); } } } diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/BuiltinFunctionProvider.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/BuiltinFunctionProvider.java new file mode 100644 index 000000000..3b2db793e --- /dev/null +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/BuiltinFunctionProvider.java @@ -0,0 +1,11 @@ +package fr.insee.vtl.engine.functions; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; + +/** Built-in scalar function catalogue for the VTL engine. */ +public interface BuiltinFunctionProvider { + + Map> getFunctions(); +} diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/NativeFunctionProviders.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/NativeFunctionProviders.java new file mode 100644 index 000000000..0c99abba2 --- /dev/null +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/NativeFunctionProviders.java @@ -0,0 +1,55 @@ +package fr.insee.vtl.engine.functions; + +import fr.insee.vtl.engine.functions.providers.ArithmeticFunctionsProvider; +import fr.insee.vtl.engine.functions.providers.BooleanFunctionsProvider; +import fr.insee.vtl.engine.functions.providers.ComparisonFunctionsProvider; +import fr.insee.vtl.engine.functions.providers.ComparisonOperatorFunctionsProvider; +import fr.insee.vtl.engine.functions.providers.ConditionalFunctionsProvider; +import fr.insee.vtl.engine.functions.providers.DistanceFunctionsProvider; +import fr.insee.vtl.engine.functions.providers.NumericFunctionsProvider; +import fr.insee.vtl.engine.functions.providers.StringFunctionsProvider; +import fr.insee.vtl.engine.functions.providers.TemporalFunctionsProvider; +import fr.insee.vtl.engine.functions.providers.UnaryFunctionsProvider; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** Aggregates all built-in scalar function providers. */ +public final class NativeFunctionProviders { + + private static final List BUILTINS = + List.of( + new NumericFunctionsProvider(), + new StringFunctionsProvider(), + new DistanceFunctionsProvider(), + new ArithmeticFunctionsProvider(), + new ConditionalFunctionsProvider(), + new BooleanFunctionsProvider(), + new UnaryFunctionsProvider(), + new ComparisonOperatorFunctionsProvider(), + new ComparisonFunctionsProvider(), + new TemporalFunctionsProvider()); + + private NativeFunctionProviders() {} + + public static Map> builtinFunctions() { + Map> functions = new LinkedHashMap<>(); + for (BuiltinFunctionProvider provider : BUILTINS) { + provider + .getFunctions() + .forEach( + (vtlName, methods) -> + functions.merge( + vtlName, + methods, + (left, right) -> { + var merged = new ArrayList<>(left); + merged.addAll(right); + return List.copyOf(merged); + })); + } + return functions; + } +} diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/NativeFunctionRegistry.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/NativeFunctionRegistry.java new file mode 100644 index 000000000..0ae08d4ec --- /dev/null +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/NativeFunctionRegistry.java @@ -0,0 +1,116 @@ +package fr.insee.vtl.engine.functions; + +import fr.insee.vtl.engine.VtlScriptEngine; +import fr.insee.vtl.model.VtlMethod; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.StringJoiner; +import java.util.stream.Collectors; + +/** VTL-name keyed catalogue of native {@link Method} bindings with overload resolution. */ +public final class NativeFunctionRegistry { + + private static final NativeFunctionRegistry BUILTINS = loadBuiltins(); + + private final Map> byVtlName = new LinkedHashMap<>(); + + private NativeFunctionRegistry() {} + + public static NativeFunctionRegistry builtins() { + return BUILTINS; + } + + public static NativeFunctionRegistry empty() { + return new NativeFunctionRegistry(); + } + + private static NativeFunctionRegistry loadBuiltins() { + NativeFunctionRegistry registry = new NativeFunctionRegistry(); + registry.registerAll(NativeFunctionProviders.builtinFunctions()); + return registry; + } + + public void registerAll(Map> functions) { + functions.forEach((vtlName, methods) -> methods.forEach(method -> register(vtlName, method))); + } + + public void register(String vtlName, Method method) { + Objects.requireNonNull(vtlName); + Objects.requireNonNull(method); + byVtlName.compute( + vtlName, + (name, methods) -> { + List updated = methods == null ? new ArrayList<>() : new ArrayList<>(methods); + if (updated.stream().anyMatch(existing -> signaturesEqual(existing, method))) { + throw new IllegalStateException( + "duplicate native function binding for " + vtlName + ": " + method); + } + updated.add(method); + return List.copyOf(updated); + }); + } + + public void put(String vtlName, Method method) { + Objects.requireNonNull(vtlName); + Objects.requireNonNull(method); + byVtlName.put(vtlName, List.of(method)); + } + + public Method putAndReturnPrevious(String vtlName, Method method) { + Objects.requireNonNull(vtlName); + Objects.requireNonNull(method); + List previous = byVtlName.put(vtlName, List.of(method)); + return previous == null || previous.isEmpty() ? null : previous.get(0); + } + + public VtlMethod resolve(String vtlName, Collection types) throws NoSuchMethodException { + List candidates = byVtlName.get(vtlName); + if (candidates == null || candidates.isEmpty()) { + throw new NoSuchMethodException(methodToString(vtlName, types)); + } + + List matches = + candidates.stream() + .filter(method -> VtlScriptEngine.matchParameters(method, types.toArray(Class[]::new))) + .collect(Collectors.toList()); + if (matches.size() == 1) { + return new VtlMethod(matches.get(0)); + } + + for (Method method : candidates) { + if (types.equals(Arrays.asList(method.getParameterTypes()))) { + return new VtlMethod(method); + } + } + throw new NoSuchMethodException(methodToString(vtlName, types)); + } + + public VtlMethod resolveOrNull(String vtlName, Collection types) { + try { + return resolve(vtlName, types); + } catch (NoSuchMethodException e) { + return null; + } + } + + private static boolean signaturesEqual(Method left, Method right) { + return left.getName().equals(right.getName()) + && Arrays.equals(left.getParameterTypes(), right.getParameterTypes()); + } + + private static String methodToString(String name, Collection argTypes) { + StringJoiner sj = new StringJoiner(", ", name + "(", ")"); + if (argTypes != null) { + for (Class c : argTypes) { + sj.add(c == null ? "null" : c.getSimpleName()); + } + } + return sj.toString(); + } +} diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/ArithmeticFunctionsProvider.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/ArithmeticFunctionsProvider.java new file mode 100644 index 000000000..4c196b985 --- /dev/null +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/ArithmeticFunctionsProvider.java @@ -0,0 +1,153 @@ +package fr.insee.vtl.engine.functions.providers; + +import com.github.hervian.reflection.Fun; +import fr.insee.vtl.engine.functions.BuiltinFunctionProvider; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; + +public final class ArithmeticFunctionsProvider implements BuiltinFunctionProvider { + + public static Long addition(Long valueA, Long valueB) { + if (valueA == null || valueB == null) { + return null; + } + return valueA + valueB; + } + + public static Double addition(Long valueA, Double valueB) { + if (valueA == null || valueB == null) { + return null; + } + return valueA + valueB; + } + + public static Double addition(Double valueA, Long valueB) { + return addition(valueB, valueA); + } + + public static Double addition(Double valueA, Double valueB) { + if (valueA == null || valueB == null) { + return null; + } + return valueA + valueB; + } + + public static Long subtraction(Long valueA, Long valueB) { + if (valueA == null || valueB == null) { + return null; + } + return valueA - valueB; + } + + public static Double subtraction(Long valueA, Double valueB) { + if (valueA == null || valueB == null) { + return null; + } + return valueA - valueB; + } + + public static Double subtraction(Double valueA, Long valueB) { + if (valueA == null || valueB == null) { + return null; + } + return valueA - valueB.doubleValue(); + } + + public static Double subtraction(Double valueA, Double valueB) { + if (valueA == null || valueB == null) { + return null; + } + return valueA - valueB; + } + + public static String concat(String valueA, String valueB) { + if (valueA == null || valueB == null) { + return null; + } + return valueA + valueB; + } + + public static Long multiplication(Long valueA, Long valueB) { + if (valueA == null || valueB == null) { + return null; + } + return valueA * valueB; + } + + public static Double multiplication(Long valueA, Double valueB) { + if (valueA == null || valueB == null) { + return null; + } + return valueA.doubleValue() * valueB; + } + + public static Double multiplication(Double valueA, Long valueB) { + return multiplication(valueB, valueA); + } + + public static Double multiplication(Double valueA, Double valueB) { + if (valueA == null || valueB == null) { + return null; + } + return valueA * valueB; + } + + public static Double division(Long valueA, Double valueB) { + if (valueA == null || valueB == null) { + return null; + } + return valueA.doubleValue() / valueB; + } + + public static Double division(Double valueA, Long valueB) { + if (valueA == null || valueB == null) { + return null; + } + return valueA / valueB.doubleValue(); + } + + public static Double division(Long valueA, Long valueB) { + if (valueA == null || valueB == null) { + return null; + } + return ((double) valueA / valueB); + } + + public static Double division(Double valueA, Double valueB) { + if (valueA == null || valueB == null) { + return null; + } + return valueA / valueB; + } + + @Override + public Map> getFunctions() { + return Map.of( + "addition", + List.of( + Fun.toMethod(ArithmeticFunctionsProvider::addition), + Fun.toMethod(ArithmeticFunctionsProvider::addition), + Fun.toMethod(ArithmeticFunctionsProvider::addition), + Fun.toMethod(ArithmeticFunctionsProvider::addition)), + "subtraction", + List.of( + Fun.toMethod(ArithmeticFunctionsProvider::subtraction), + Fun.toMethod(ArithmeticFunctionsProvider::subtraction), + Fun.toMethod(ArithmeticFunctionsProvider::subtraction), + Fun.toMethod(ArithmeticFunctionsProvider::subtraction)), + "concat", List.of(Fun.toMethod(ArithmeticFunctionsProvider::concat)), + "multiplication", + List.of( + Fun.toMethod(ArithmeticFunctionsProvider::multiplication), + Fun.toMethod(ArithmeticFunctionsProvider::multiplication), + Fun.toMethod(ArithmeticFunctionsProvider::multiplication), + Fun.toMethod(ArithmeticFunctionsProvider::multiplication)), + "division", + List.of( + Fun.toMethod(ArithmeticFunctionsProvider::division), + Fun.toMethod(ArithmeticFunctionsProvider::division), + Fun.toMethod(ArithmeticFunctionsProvider::division), + Fun.toMethod(ArithmeticFunctionsProvider::division))); + } +} diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/BooleanFunctionsProvider.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/BooleanFunctionsProvider.java new file mode 100644 index 000000000..e78032a9e --- /dev/null +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/BooleanFunctionsProvider.java @@ -0,0 +1,45 @@ +package fr.insee.vtl.engine.functions.providers; + +import com.github.hervian.reflection.Fun; +import fr.insee.vtl.engine.functions.BuiltinFunctionProvider; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; + +public final class BooleanFunctionsProvider implements BuiltinFunctionProvider { + + public static Boolean and(Boolean left, Boolean right) { + if (left != null && !left) return false; + if (right != null && !right) return false; + if (left == null || right == null) return null; + return true; + } + + public static Boolean or(Boolean left, Boolean right) { + if (left != null && left) { + return true; + } + if (right != null && right) { + return true; + } + if (left == null || right == null) { + return null; + } + return false; + } + + public static Boolean xor(Boolean left, Boolean right) { + if (left == null || right == null) { + return null; + } + return left ^ right; + } + + @Override + public Map> getFunctions() { + return Map.of( + "and", List.of(Fun.toMethod(BooleanFunctionsProvider::and)), + "or", List.of(Fun.toMethod(BooleanFunctionsProvider::or)), + "xor", List.of(Fun.toMethod(BooleanFunctionsProvider::xor))); + } +} diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/ComparisonFunctionsProvider.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/ComparisonFunctionsProvider.java new file mode 100644 index 000000000..32dd9eb64 --- /dev/null +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/ComparisonFunctionsProvider.java @@ -0,0 +1,56 @@ +package fr.insee.vtl.engine.functions.providers; + +import com.github.hervian.reflection.Fun; +import fr.insee.vtl.engine.functions.BuiltinFunctionProvider; +import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class ComparisonFunctionsProvider implements BuiltinFunctionProvider { + + public static Boolean between(Number operand, Number from, Number to) { + if (operand == null || from == null || to == null) { + return null; + } + BigDecimal operandValue = + operand instanceof Long + ? BigDecimal.valueOf(operand.longValue()) + : BigDecimal.valueOf(operand.doubleValue()); + BigDecimal fromValue = + from instanceof Long + ? BigDecimal.valueOf(from.longValue()) + : BigDecimal.valueOf(from.doubleValue()); + BigDecimal toValue = + to instanceof Long + ? BigDecimal.valueOf(to.longValue()) + : BigDecimal.valueOf(to.doubleValue()); + return operandValue.compareTo(fromValue) >= 0 && operandValue.compareTo(toValue) <= 0; + } + + public static Boolean charsetMatch(String operandValue, String patternValue) { + if (operandValue == null || patternValue == null) { + return null; + } + Pattern pattern = Pattern.compile(patternValue); + Matcher matcher = pattern.matcher(operandValue); + return matcher.matches(); + } + + public static Boolean isNull(Object obj) { + if (obj == null) { + return Boolean.TRUE; + } + return Boolean.FALSE; + } + + @Override + public Map> getFunctions() { + return Map.of( + "between", List.of(Fun.toMethod(ComparisonFunctionsProvider::between)), + "charsetMatch", List.of(Fun.toMethod(ComparisonFunctionsProvider::charsetMatch)), + "isNull", List.of(Fun.toMethod(ComparisonFunctionsProvider::isNull))); + } +} diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/ComparisonOperatorFunctionsProvider.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/ComparisonOperatorFunctionsProvider.java new file mode 100644 index 000000000..fa4ba0380 --- /dev/null +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/ComparisonOperatorFunctionsProvider.java @@ -0,0 +1,116 @@ +package fr.insee.vtl.engine.functions.providers; + +import com.github.hervian.reflection.Fun; +import fr.insee.vtl.engine.functions.BuiltinFunctionProvider; +import java.lang.reflect.Method; +import java.util.Date; +import java.util.List; +import java.util.Map; + +public final class ComparisonOperatorFunctionsProvider implements BuiltinFunctionProvider { + + private static Integer compare(Object left, Object right) throws Exception { + if (left == null || right == null) { + return null; + } + if (left instanceof Number leftNumber && right instanceof Number rightNumber) { + if (left instanceof Long leftLong && right instanceof Long rightLong) { + return Long.compare(leftLong, rightLong); + } + return Double.compare(leftNumber.doubleValue(), rightNumber.doubleValue()); + } + if (left instanceof Boolean leftBoolean && right instanceof Boolean rightBoolean) { + return Boolean.compare(leftBoolean, rightBoolean); + } + if (left instanceof String leftString && right instanceof String rightString) { + return leftString.compareTo(rightString); + } + if (left instanceof Date leftDate && right instanceof Date rightDate) { + return leftDate.compareTo(rightDate); + } + throw new Exception("Comparisons require Comparable params"); + } + + public static Boolean isEqual(Object left, Object right) throws Exception { + Integer result = compare(left, right); + if (result == null) { + return null; + } + return result == 0; + } + + public static Boolean isNotEqual(Object left, Object right) throws Exception { + Integer result = compare(left, right); + if (result == null) { + return null; + } + return result != 0; + } + + public static Boolean isLessThan(Object left, Object right) throws Exception { + Integer result = compare(left, right); + if (result == null) { + return null; + } + return result < 0; + } + + public static Boolean isGreaterThan(Object left, Object right) throws Exception { + Integer result = compare(left, right); + if (result == null) { + return null; + } + return result > 0; + } + + public static Boolean isLessThanOrEqual(Object left, Object right) throws Exception { + Integer result = compare(left, right); + if (result == null) { + return null; + } + return result <= 0; + } + + public static Boolean isGreaterThanOrEqual(Object left, Object right) throws Exception { + Integer result = compare(left, right); + if (result == null) { + return null; + } + return result >= 0; + } + + public static Boolean in(Object obj, List list) { + if (obj == null) { + return null; + } + return list.contains(obj); + } + + public static Boolean notIn(Object obj, List list) { + if (obj == null) { + return null; + } + return !list.contains(obj); + } + + @Override + public Map> getFunctions() { + Map> functions = new java.util.LinkedHashMap<>(); + functions.put("isEqual", List.of(Fun.toMethod(ComparisonOperatorFunctionsProvider::isEqual))); + functions.put( + "isNotEqual", List.of(Fun.toMethod(ComparisonOperatorFunctionsProvider::isNotEqual))); + functions.put( + "isLessThan", List.of(Fun.toMethod(ComparisonOperatorFunctionsProvider::isLessThan))); + functions.put( + "isGreaterThan", List.of(Fun.toMethod(ComparisonOperatorFunctionsProvider::isGreaterThan))); + functions.put( + "isLessThanOrEqual", + List.of(Fun.toMethod(ComparisonOperatorFunctionsProvider::isLessThanOrEqual))); + functions.put( + "isGreaterThanOrEqual", + List.of(Fun.toMethod(ComparisonOperatorFunctionsProvider::isGreaterThanOrEqual))); + functions.put("in", List.of(Fun.toMethod(ComparisonOperatorFunctionsProvider::in))); + functions.put("notIn", List.of(Fun.toMethod(ComparisonOperatorFunctionsProvider::notIn))); + return functions; + } +} diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/ConditionalFunctionsProvider.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/ConditionalFunctionsProvider.java new file mode 100644 index 000000000..979110399 --- /dev/null +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/ConditionalFunctionsProvider.java @@ -0,0 +1,81 @@ +package fr.insee.vtl.engine.functions.providers; + +import com.github.hervian.reflection.Fun; +import fr.insee.vtl.engine.functions.BuiltinFunctionProvider; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; + +public final class ConditionalFunctionsProvider implements BuiltinFunctionProvider { + + public static Long ifThenElse(Boolean condition, Long thenExpr, Long elseExpr) { + if (condition == null) { + return null; + } + return condition ? thenExpr : elseExpr; + } + + public static Double ifThenElse(Boolean condition, Double thenExpr, Double elseExpr) { + if (condition == null) { + return null; + } + return condition ? thenExpr : elseExpr; + } + + public static String ifThenElse(Boolean condition, String thenExpr, String elseExpr) { + if (condition == null) { + return null; + } + return condition ? thenExpr : elseExpr; + } + + public static Boolean ifThenElse(Boolean condition, Boolean thenExpr, Boolean elseExpr) { + if (condition == null) { + return null; + } + return condition ? thenExpr : elseExpr; + } + + public static Long nvl(Long value, Long defaultValue) { + return value == null ? defaultValue : value; + } + + public static Double nvl(Double value, Double defaultValue) { + return value == null ? defaultValue : value; + } + + public static Double nvl(Double value, Long defaultValue) { + return value == null ? defaultValue.doubleValue() : value; + } + + public static Double nvl(Long value, Double defaultValue) { + return value == null ? defaultValue : value.doubleValue(); + } + + public static String nvl(String value, String defaultValue) { + return value == null ? defaultValue : value; + } + + public static Boolean nvl(Boolean value, Boolean defaultValue) { + return value == null ? defaultValue : value; + } + + @Override + public Map> getFunctions() { + return Map.of( + "ifThenElse", + List.of( + Fun.toMethod(ConditionalFunctionsProvider::ifThenElse), + Fun.toMethod(ConditionalFunctionsProvider::ifThenElse), + Fun.toMethod(ConditionalFunctionsProvider::ifThenElse), + Fun.toMethod(ConditionalFunctionsProvider::ifThenElse)), + "nvl", + List.of( + Fun.toMethod(ConditionalFunctionsProvider::nvl), + Fun.toMethod(ConditionalFunctionsProvider::nvl), + Fun.toMethod(ConditionalFunctionsProvider::nvl), + Fun.toMethod(ConditionalFunctionsProvider::nvl), + Fun.toMethod(ConditionalFunctionsProvider::nvl), + Fun.toMethod(ConditionalFunctionsProvider::nvl))); + } +} diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/DistanceFunctionsProvider.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/DistanceFunctionsProvider.java new file mode 100644 index 000000000..2ce464eae --- /dev/null +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/DistanceFunctionsProvider.java @@ -0,0 +1,23 @@ +package fr.insee.vtl.engine.functions.providers; + +import com.github.hervian.reflection.Fun; +import fr.insee.vtl.engine.functions.BuiltinFunctionProvider; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; +import org.apache.commons.text.similarity.LevenshteinDistance; + +public final class DistanceFunctionsProvider implements BuiltinFunctionProvider { + + public static Long levenshtein(String stringA, String stringB) { + if (stringA == null || stringB == null) { + return null; + } + return Long.valueOf(LevenshteinDistance.getDefaultInstance().apply(stringA, stringB)); + } + + @Override + public Map> getFunctions() { + return Map.of("levenshtein", List.of(Fun.toMethod(DistanceFunctionsProvider::levenshtein))); + } +} diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/NumericFunctionsProvider.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/NumericFunctionsProvider.java new file mode 100644 index 000000000..68a9aba3f --- /dev/null +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/NumericFunctionsProvider.java @@ -0,0 +1,139 @@ +package fr.insee.vtl.engine.functions.providers; + +import com.github.hervian.reflection.Fun; +import fr.insee.vtl.engine.functions.BuiltinFunctionProvider; +import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.List; +import java.util.Map; +import java.util.Random; + +public final class NumericFunctionsProvider implements BuiltinFunctionProvider { + + public static Long ceil(Number value) { + if (value == null) { + return null; + } + return (long) Math.ceil(value.doubleValue()); + } + + public static Long floor(Number value) { + if (value == null) { + return null; + } + return (long) Math.floor(value.doubleValue()); + } + + public static Double abs(Number value) { + if (value == null) { + return null; + } + return Math.abs(value.doubleValue()); + } + + public static Double exp(Number value) { + if (value == null) { + return null; + } + return Math.exp(value.doubleValue()); + } + + public static Double ln(Number value) { + if (value == null) { + return null; + } + return Math.log(value.doubleValue()); + } + + public static Double sqrt(Number value) { + if (value == null) { + return null; + } + if (value.doubleValue() < 0) { + throw new IllegalArgumentException("operand has to be 0 or positive"); + } + return Math.sqrt(value.doubleValue()); + } + + public static Double round(Number value, Long decimal) { + if (decimal == null) { + decimal = 0L; + } + if (value == null) { + return null; + } + BigDecimal bd = new BigDecimal(Double.toString(value.doubleValue())); + bd = bd.setScale(decimal.intValue(), RoundingMode.HALF_UP); + return bd.doubleValue(); + } + + public static Double trunc(Number value, Long decimal) { + if (decimal == null) { + decimal = 0L; + } + if (value == null) { + return null; + } + BigDecimal bd = new BigDecimal(Double.toString(value.doubleValue())); + bd = bd.setScale(decimal.intValue(), RoundingMode.DOWN); + return bd.doubleValue(); + } + + public static Double mod(Number left, Number right) { + if (left == null || right == null) { + return null; + } + if (right.doubleValue() == 0) { + return left.doubleValue(); + } + return (left.doubleValue() % right.doubleValue()) * (right.doubleValue() < 0 ? -1 : 1); + } + + public static Double power(Number left, Number right) { + if (left == null || right == null) { + return null; + } + return Math.pow(left.doubleValue(), right.doubleValue()); + } + + public static Double random(Long left, Long right) { + if (left == null || right == null) { + return null; + } + Double res = null; + Random random = new Random(left); + for (int i = 0; i < right; i++) { + res = random.nextDouble(); + } + return res; + } + + public static Double log(Number operand, Number base) { + if (operand == null || base == null) { + return null; + } + if (operand.doubleValue() <= 0) throw new IllegalArgumentException("operand must be positive"); + if (base.doubleValue() < 1) + throw new IllegalArgumentException("base must be greater or equal than 1"); + return Math.log(operand.doubleValue()) / Math.log(base.doubleValue()); + } + + @Override + public Map> getFunctions() { + Map> functions = new java.util.LinkedHashMap<>(); + functions.put("ceil", List.of(Fun.toMethod(NumericFunctionsProvider::ceil))); + functions.put("floor", List.of(Fun.toMethod(NumericFunctionsProvider::floor))); + functions.put("abs", List.of(Fun.toMethod(NumericFunctionsProvider::abs))); + functions.put("exp", List.of(Fun.toMethod(NumericFunctionsProvider::exp))); + functions.put("ln", List.of(Fun.toMethod(NumericFunctionsProvider::ln))); + functions.put("sqrt", List.of(Fun.toMethod(NumericFunctionsProvider::sqrt))); + functions.put("round", List.of(Fun.toMethod(NumericFunctionsProvider::round))); + functions.put("trunc", List.of(Fun.toMethod(NumericFunctionsProvider::trunc))); + functions.put("mod", List.of(Fun.toMethod(NumericFunctionsProvider::mod))); + functions.put("power", List.of(Fun.toMethod(NumericFunctionsProvider::power))); + functions.put("random", List.of(Fun.toMethod(NumericFunctionsProvider::random))); + functions.put("log", List.of(Fun.toMethod(NumericFunctionsProvider::log))); + return functions; + } +} diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/StringFunctionsProvider.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/StringFunctionsProvider.java new file mode 100644 index 000000000..b507d39bf --- /dev/null +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/StringFunctionsProvider.java @@ -0,0 +1,119 @@ +package fr.insee.vtl.engine.functions.providers; + +import com.github.hervian.reflection.Fun; +import fr.insee.vtl.engine.functions.BuiltinFunctionProvider; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; +import org.apache.commons.lang3.StringUtils; + +public final class StringFunctionsProvider implements BuiltinFunctionProvider { + + private static final Pattern LTRIM = Pattern.compile("^\\s+"); + private static final Pattern RTRIM = Pattern.compile("\\s+$"); + + public static String trim(String value) { + if (value == null) { + return null; + } + return value.trim(); + } + + public static String ltrim(String value) { + if (value == null) { + return null; + } + return LTRIM.matcher(value).replaceAll(""); + } + + public static String rtrim(String value) { + if (value == null) { + return null; + } + return RTRIM.matcher(value).replaceAll(""); + } + + public static String ucase(String value) { + if (value == null) { + return null; + } + return value.toUpperCase(); + } + + public static String lcase(String value) { + if (value == null) { + return null; + } + return value.toLowerCase(); + } + + public static Long len(String value) { + if (value == null) { + return null; + } + return (long) value.length(); + } + + public static String substr(String value, Long start, Long len) { + if (value == null) { + return null; + } + if (start == null) { + start = 1L; + } + if (len == null) { + len = Long.valueOf(value.length()); + } + if (start > value.length()) { + return ""; + } + if (start != 0) { + start = start - 1; + } + + var end = start + len; + if (end > value.length()) { + return value.substring(Math.toIntExact(start)); + } + return value.substring(Math.toIntExact(start), Math.toIntExact(end)); + } + + public static String replace(String value, String pattern, String replacement) { + if (value == null || pattern == null) { + return null; + } + if (replacement == null) { + replacement = ""; + } + return value.replaceAll(pattern, replacement); + } + + public static Long instr(String v, String v2, Long start, Long occurence) { + if (v == null || v2 == null) { + return null; + } + if (start == null) { + start = 0L; + } + if (occurence == null) { + occurence = 1L; + } + return StringUtils.ordinalIndexOf(v.substring(start.intValue()), v2, occurence.intValue()) + 1L; + } + + @Override + public Map> getFunctions() { + Map> functions = new java.util.LinkedHashMap<>(); + functions.put("trim", List.of(Fun.toMethod(StringFunctionsProvider::trim))); + functions.put("ltrim", List.of(Fun.toMethod(StringFunctionsProvider::ltrim))); + functions.put("rtrim", List.of(Fun.toMethod(StringFunctionsProvider::rtrim))); + functions.put("ucase", List.of(Fun.toMethod(StringFunctionsProvider::ucase))); + functions.put("lcase", List.of(Fun.toMethod(StringFunctionsProvider::lcase))); + functions.put("len", List.of(Fun.toMethod(StringFunctionsProvider::len))); + functions.put("substr", List.of(Fun.toMethod(StringFunctionsProvider::substr))); + functions.put("replace", List.of(Fun.toMethod(StringFunctionsProvider::replace))); + functions.put("instr", List.of(Fun.toMethod(StringFunctionsProvider::instr))); + return functions; + } +} diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/expressions/TemporalFunctions.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/TemporalFunctionsProvider.java similarity index 60% rename from vtl-engine/src/main/java/fr/insee/vtl/engine/expressions/TemporalFunctions.java rename to vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/TemporalFunctionsProvider.java index fd48c61a7..475b5d228 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/expressions/TemporalFunctions.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/TemporalFunctionsProvider.java @@ -1,95 +1,21 @@ -package fr.insee.vtl.engine.expressions; - -import java.time.*; -import java.time.temporal.*; +package fr.insee.vtl.engine.functions.providers; + +import com.github.hervian.reflection.Fun; +import fr.insee.vtl.engine.functions.BuiltinFunctionProvider; +import java.lang.reflect.Method; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Map; import org.threeten.extra.Interval; import org.threeten.extra.PeriodDuration; -/** - * This comment explains the temporal functionality supported by Trevas, as defined in the VTL 2.0 - * specification. - * - *

The specification describes temporal types such as date & time_period, time, and duration. - * However, Trevas authors find these descriptions unsatisfactory. This section outlines our - * implementation choices and how they differ from the spec. - * - *

Supported Java types in Trevas: - * - *

    - *
  • java.time.Instant: Represents a specific moment in time. - *
  • java.time.ZonedDateTime: Combines java.time.Instant with time zone information. - *
  • java.time.OffsetDateTime: Combines java.time.Instant with a time-zone offset. - *
  • org.threeten.extra.PeriodDuration: Represents a duration using both calendar units (years, - * months, etc.) and a precise duration between two time points. - *
- * - *

In the specification, the types date and time_period are presented as compound types with a - * start and end, which complicates implementation. By defining a clear algebra between the types, - * users can combine simple functions to achieve the desired results. - * - *

Algebraic operations: - * - *

    - *
  • Add or subtract PeriodDuration to/from Instant, ZonedDateTime, or OffsetDateTime, resulting - * in the same type. - *
  • Subtract one time point from another (Instant, ZonedDateTime, OffsetDateTime), resulting in - * PeriodDuration. - *
- * - *

Note: Using Instant with PeriodDuration may yield unreliable results without a time zone or - * offset. - * - *

The VTL's definition of duration as "regular duration" and "frequency" is ambiguous. Mapping - * this to PeriodDuration allows expression of both concepts, though operations may fail without - * Zoned or Offset types. - * - *

Examples of temporal functions re-implemented in Trevas: period_indicator - * - *

Extracts the period of an interval. - * - *

flow_to_stock - * - *

Replaces flow_to_stock with a summation over time identifiers: - * - *

- *     result := flow_to_stock(dataset)
- *     result := sum ( dataset over ( partition by Id_1, Id_Time ) );
- * 
- * - * stock_to_flow - * - *

Implements stock_to_flow using a lag function over time identifiers: - * - *

- *     result := stock_to_flow(dataset)
- *     result := dataset[calc lag_Me_1 := lag(Me_1, 1 over(partition by Id_1, order by Id_2))]
- *                        [calc Me_1     := Me_1 - nvl(lag_Me_1, 0)]
- *                        [drop lag_Me_1];
- * 
- * - * timeshift - * - *

Expresses timeshift as a multiplication operation on duration: - * - *

- *     result := timeshift(dataset, 1);
- *     result := dataset[calc id_time := id_time + duration * 1];
- * 
- * - * time_agg - * - *

Recommends using the truncate_time function before aggregation rather than using time_agg: - * - *

- *     result := sum(dataset) group all time_agg("A", _, Me_1)
- *     result := dataset[calc id_time := truncate_time(id_time, "year")]
- *                       [aggr Me_1 := sum(Me_1) group by id_time]
- *
- *     // Alternative method
- *     result := ds1[aggr test := sum(me1) group all truncate_time(t, "year")];
- * 
- */ -public class TemporalFunctions { +/** Native temporal scalar operators. */ +public final class TemporalFunctionsProvider implements BuiltinFunctionProvider { public static Instant addition(Instant op, PeriodDuration dur) { return op.plus(dur); @@ -256,4 +182,53 @@ private static ChronoUnit toChronoUnit(String unit) { default -> throw new IllegalArgumentException("Unsupported unit: " + unit); }; } + + @Override + public Map> getFunctions() { + Map> functions = new java.util.LinkedHashMap<>(); + functions.put( + "addition", + List.of( + Fun.toMethod(TemporalFunctionsProvider::addition), + Fun.toMethod(TemporalFunctionsProvider::addition), + Fun.toMethod(TemporalFunctionsProvider::addition), + Fun.toMethod(TemporalFunctionsProvider::addition), + Fun.toMethod(TemporalFunctionsProvider::addition), + Fun.toMethod(TemporalFunctionsProvider::addition))); + functions.put( + "subtraction", + List.of( + Fun.toMethod(TemporalFunctionsProvider::subtraction), + Fun.toMethod(TemporalFunctionsProvider::subtraction), + Fun.toMethod(TemporalFunctionsProvider::subtraction), + Fun.toMethod(TemporalFunctionsProvider::subtraction), + Fun.toMethod(TemporalFunctionsProvider::subtraction), + Fun.toMethod(TemporalFunctionsProvider::subtraction), + Fun.toMethod(TemporalFunctionsProvider::subtraction), + Fun.toMethod(TemporalFunctionsProvider::subtraction), + Fun.toMethod(TemporalFunctionsProvider::subtraction), + Fun.toMethod(TemporalFunctionsProvider::subtraction), + Fun.toMethod(TemporalFunctionsProvider::subtraction), + Fun.toMethod(TemporalFunctionsProvider::subtraction), + Fun.toMethod(TemporalFunctionsProvider::subtraction), + Fun.toMethod(TemporalFunctionsProvider::subtraction), + Fun.toMethod(TemporalFunctionsProvider::subtraction))); + functions.put( + "multiplication", + List.of( + Fun.toMethod(TemporalFunctionsProvider::multiplication), + Fun.toMethod(TemporalFunctionsProvider::multiplication))); + functions.put("timeshift", List.of(Fun.toMethod(TemporalFunctionsProvider::timeshift))); + functions.put( + "truncate_time", + List.of( + Fun.toMethod(TemporalFunctionsProvider::truncate_time), + Fun.toMethod(TemporalFunctionsProvider::truncate_time), + Fun.toMethod(TemporalFunctionsProvider::truncate_time), + Fun.toMethod(TemporalFunctionsProvider::truncate_time), + Fun.toMethod(TemporalFunctionsProvider::truncate_time), + Fun.toMethod(TemporalFunctionsProvider::truncate_time))); + functions.put("at_zone", List.of(Fun.toMethod(TemporalFunctionsProvider::at_zone))); + return functions; + } } diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/UnaryFunctionsProvider.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/UnaryFunctionsProvider.java new file mode 100644 index 000000000..70f1dac4b --- /dev/null +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/UnaryFunctionsProvider.java @@ -0,0 +1,53 @@ +package fr.insee.vtl.engine.functions.providers; + +import com.github.hervian.reflection.Fun; +import fr.insee.vtl.engine.functions.BuiltinFunctionProvider; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; + +public final class UnaryFunctionsProvider implements BuiltinFunctionProvider { + + public static Long plus(Long right) { + return right; + } + + public static Double plus(Double right) { + return right; + } + + public static Long minus(Long right) { + if (right == null) { + return null; + } + return -right; + } + + public static Double minus(Double right) { + if (right == null) { + return null; + } + return -right; + } + + public static Boolean not(Boolean right) { + if (right == null) { + return null; + } + return !right; + } + + @Override + public Map> getFunctions() { + return Map.of( + "plus", + List.of( + Fun.toMethod(UnaryFunctionsProvider::plus), + Fun.toMethod(UnaryFunctionsProvider::plus)), + "minus", + List.of( + Fun.toMethod(UnaryFunctionsProvider::minus), + Fun.toMethod(UnaryFunctionsProvider::minus)), + "not", List.of(Fun.toMethod(UnaryFunctionsProvider::not))); + } +} diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/package-info.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/package-info.java index 05f3de892..723510cd1 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/package-info.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/package-info.java @@ -23,9 +23,9 @@ * * *

Cross-cutting attribute propagation lives in {@code semantics/attribute/}. Scalar native - * helpers ({@link fr.insee.vtl.engine.expressions.TemporalFunctions}) live in {@code expressions/}. - * Mechanical row algorithms (e.g. {@code InMemoryJoinExecutor}) live in {@code processors/} or - * under {@code semantics.join} with an {@code InMemory*} prefix. + * helpers ({@link fr.insee.vtl.engine.functions.providers.TemporalFunctionsProvider}) live in + * {@code functions/providers/}. Mechanical row algorithms (e.g. {@code InMemoryJoinExecutor}) live + * in {@code processors/} or under {@code semantics.join} with an {@code InMemory*} prefix. * *

Tests

* diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/semantics/functions/LevenshteinProvider.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/semantics/functions/LevenshteinProvider.java deleted file mode 100644 index ff388c32e..000000000 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/semantics/functions/LevenshteinProvider.java +++ /dev/null @@ -1,14 +0,0 @@ -package fr.insee.vtl.engine.semantics.functions; - -import fr.insee.vtl.model.FunctionProvider; -import java.lang.reflect.Method; -import java.util.Map; -import javax.script.ScriptEngine; - -public class LevenshteinProvider implements FunctionProvider { - - @Override - public Map getFunctions(ScriptEngine vtlEngine) { - return Map.of(); - } -} diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/ArithmeticExprOrConcatVisitor.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/ArithmeticExprOrConcatVisitor.java index 37e23b24d..f77684821 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/ArithmeticExprOrConcatVisitor.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/ArithmeticExprOrConcatVisitor.java @@ -11,94 +11,18 @@ import java.util.List; import java.util.Objects; -/** - * ArithmeticExprOrConcatVisitor is the base visitor for plus, minus or concatenation - * expressions. - */ +/** Dispatch for plus, minus or concatenation expressions. */ public class ArithmeticExprOrConcatVisitor extends VtlBaseVisitor { private final ExpressionVisitor exprVisitor; private final GenericFunctionsVisitor genericFunctionsVisitor; - /** - * Constructor taking an expression visitor. - * - * @param expressionVisitor The visitor for the enclosing expression. - * @param genericFunctionsVisitor - */ public ArithmeticExprOrConcatVisitor( ExpressionVisitor expressionVisitor, GenericFunctionsVisitor genericFunctionsVisitor) { exprVisitor = Objects.requireNonNull(expressionVisitor); this.genericFunctionsVisitor = Objects.requireNonNull(genericFunctionsVisitor); } - public static Long addition(Long valueA, Long valueB) { - if (valueA == null || valueB == null) { - return null; - } - return valueA + valueB; - } - - public static Double addition(Long valueA, Double valueB) { - if (valueA == null || valueB == null) { - return null; - } - return valueA + valueB; - } - - public static Double addition(Double valueA, Long valueB) { - return addition(valueB, valueA); - } - - public static Double addition(Double valueA, Double valueB) { - if (valueA == null || valueB == null) { - return null; - } - return valueA + valueB; - } - - public static Long subtraction(Long valueA, Long valueB) { - if (valueA == null || valueB == null) { - return null; - } - return valueA - valueB; - } - - public static Double subtraction(Long valueA, Double valueB) { - if (valueA == null || valueB == null) { - return null; - } - return valueA - valueB; - } - - public static Double subtraction(Double valueA, Long valueB) { - if (valueA == null || valueB == null) { - return null; - } - return valueA - valueB.doubleValue(); - } - - public static Double subtraction(Double valueA, Double valueB) { - if (valueA == null || valueB == null) { - return null; - } - return valueA - valueB; - } - - public static String concat(String valueA, String valueB) { - if (valueA == null || valueB == null) { - return null; - } - return valueA + valueB; - } - - /** - * Visits expressions with plus, minus or concatenation operators. - * - * @param ctx The scripting context for the expression. - * @return A ResolvableExpression resolving to the result of the plus, minus or - * concatenation operation. - */ @Override public ResolvableExpression visitArithmeticExprOrConcat( VtlParser.ArithmeticExprOrConcatContext ctx) { diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/ArithmeticVisitor.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/ArithmeticVisitor.java index 24294fe4f..35210f0ca 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/ArithmeticVisitor.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/ArithmeticVisitor.java @@ -11,85 +11,18 @@ import java.util.List; import java.util.Objects; -/** - * ArithmeticVisitor is the base visitor for multiplication or division expressions. - */ +/** Dispatch for multiplication or division expressions. */ public class ArithmeticVisitor extends VtlBaseVisitor { private final ExpressionVisitor exprVisitor; private final GenericFunctionsVisitor genericFunctionsVisitor; - /** - * Constructor taking an expression visitor. - * - * @param expressionVisitor The visitor for the enclosing expression. - */ public ArithmeticVisitor( ExpressionVisitor expressionVisitor, GenericFunctionsVisitor genericFunctionsVisitor) { exprVisitor = Objects.requireNonNull(expressionVisitor); this.genericFunctionsVisitor = Objects.requireNonNull(genericFunctionsVisitor); } - public static Long multiplication(Long valueA, Long valueB) { - if (valueA == null || valueB == null) { - return null; - } - return valueA * valueB; - } - - public static Double multiplication(Long valueA, Double valueB) { - if (valueA == null || valueB == null) { - return null; - } - return valueA.doubleValue() * valueB; - } - - public static Double multiplication(Double valueA, Long valueB) { - return multiplication(valueB, valueA); - } - - public static Double multiplication(Double valueA, Double valueB) { - if (valueA == null || valueB == null) { - return null; - } - return valueA * valueB; - } - - public static Double division(Long valueA, Double valueB) { - if (valueA == null || valueB == null) { - return null; - } - return valueA.doubleValue() / valueB; - } - - public static Double division(Double valueA, Long valueB) { - if (valueA == null || valueB == null) { - return null; - } - return valueA / valueB.doubleValue(); - } - - public static Double division(Long valueA, Long valueB) { - if (valueA == null || valueB == null) { - return null; - } - return ((double) valueA / valueB); - } - - public static Double division(Double valueA, Double valueB) { - if (valueA == null || valueB == null) { - return null; - } - return valueA / valueB; - } - - /** - * Visits expressions with multiplication or division operators. - * - * @param ctx The scripting context for the expression. - * @return A ResolvableExpression resolving to the result of the multiplication or - * division operation. - */ @Override public ResolvableExpression visitArithmeticExpr(VtlParser.ArithmeticExprContext ctx) { try { diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/BooleanVisitor.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/BooleanVisitor.java index ae04f086e..9bd3797d8 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/BooleanVisitor.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/BooleanVisitor.java @@ -11,57 +11,18 @@ import java.util.List; import java.util.Objects; -/** BooleanVisitor is the base visitor for expressions involving boolean operations. */ +/** Dispatch for boolean expressions. */ public class BooleanVisitor extends VtlBaseVisitor { private final ExpressionVisitor exprVisitor; private final GenericFunctionsVisitor genericFunctionsVisitor; - /** - * Constructor taking an expression visitor. - * - * @param expressionVisitor the parent expression visitor. - * @param genericFunctionsVisitor the parent generic functions visitor. - */ public BooleanVisitor( ExpressionVisitor expressionVisitor, GenericFunctionsVisitor genericFunctionsVisitor) { exprVisitor = Objects.requireNonNull(expressionVisitor); this.genericFunctionsVisitor = Objects.requireNonNull(genericFunctionsVisitor); } - public static Boolean and(Boolean left, Boolean right) { - if (left != null && !left) return false; - if (right != null && !right) return false; - if (left == null || right == null) return null; - return true; - } - - public static Boolean or(Boolean left, Boolean right) { - if (left != null && left) { - return true; - } - if (right != null && right) { - return true; - } - if (left == null || right == null) { - return null; - } - return false; - } - - public static Boolean xor(Boolean left, Boolean right) { - if (left == null || right == null) { - return null; - } - return left ^ right; - } - - /** - * Visits expressions with boolean operators. - * - * @param ctx The scripting context for the expression. - * @return A ResolvableExpression resolving to the result of the boolean operation. - */ @Override public ResolvableExpression visitBooleanExpr(VtlParser.BooleanExprContext ctx) { try { diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/ComparisonVisitor.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/ComparisonVisitor.java index 5040d1871..018cd0aeb 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/ComparisonVisitor.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/ComparisonVisitor.java @@ -18,118 +18,19 @@ import java.util.*; import java.util.stream.Collectors; -/** - * ComparisonVisitor is the base visitor for comparison, 'element of' and list - * expressions. - */ +/** Dispatch for comparison, 'element of' and list expressions. */ public class ComparisonVisitor extends VtlBaseVisitor { private static final String unknownOperator = "unknown operator "; private final ExpressionVisitor exprVisitor; private final GenericFunctionsVisitor genericFunctionsVisitor; - /** - * Constructor taking an expression visitor. - * - * @param expressionVisitor the parent expression visitor. - */ public ComparisonVisitor( ExpressionVisitor expressionVisitor, GenericFunctionsVisitor genericFunctionsVisitor) { exprVisitor = Objects.requireNonNull(expressionVisitor); this.genericFunctionsVisitor = genericFunctionsVisitor; } - private static Integer compare(Object left, Object right) throws Exception { - if (left == null || right == null) { - return null; - } - if (left instanceof Number leftNumber && right instanceof Number rightNumber) { - if (left instanceof Long leftLong && right instanceof Long rightLong) { - return Long.compare(leftLong, rightLong); - } - return Double.compare(leftNumber.doubleValue(), rightNumber.doubleValue()); - } - if (left instanceof Boolean leftBoolean && right instanceof Boolean rightBoolean) { - return Boolean.compare(leftBoolean, rightBoolean); - } - if (left instanceof String leftString && right instanceof String rightString) { - return leftString.compareTo(rightString); - } - if (left instanceof Date leftDate && right instanceof Date rightDate) { - return leftDate.compareTo(rightDate); - } else { - throw new Exception("Comparisons require Comparable params"); - } - } - - public static Boolean isEqual(Object left, Object right) throws Exception { - Integer compare = compare(left, right); - if (compare == null) { - return null; - } - return compare == 0; - } - - public static Boolean isNotEqual(Object left, Object right) throws Exception { - Integer compare = compare(left, right); - if (compare == null) { - return null; - } - return compare != 0; - } - - public static Boolean isLessThan(Object left, Object right) throws Exception { - Integer compare = compare(left, right); - if (compare == null) { - return null; - } - return compare < 0; - } - - public static Boolean isGreaterThan(Object left, Object right) throws Exception { - Integer compare = compare(left, right); - if (compare == null) { - return null; - } - return compare > 0; - } - - public static Boolean isLessThanOrEqual(Object left, Object right) throws Exception { - Integer compare = compare(left, right); - if (compare == null) { - return null; - } - return compare <= 0; - } - - public static Boolean isGreaterThanOrEqual(Object left, Object right) throws Exception { - Integer compare = compare(left, right); - if (compare == null) { - return null; - } - return compare >= 0; - } - - public static Boolean in(Object obj, List list) { - if (obj == null) { - return null; - } - return list.contains(obj); - } - - public static Boolean notIn(Object obj, List list) { - if (obj == null) { - return null; - } - return !list.contains(obj); - } - - /** - * Visits expressions with comparisons. - * - * @param ctx The scripting context for the expression. - * @return A ResolvableExpression resolving to the boolean result of the comparison. - */ @Override public ResolvableExpression visitComparisonExpr(VtlParser.ComparisonExprContext ctx) { try { @@ -137,12 +38,10 @@ public ResolvableExpression visitComparisonExpr(VtlParser.ComparisonExprContext var leftExpression = exprVisitor.visit(ctx.left); var rightExpression = exprVisitor.visit(ctx.right); List parameters = List.of(leftExpression, rightExpression); - // Check 2 parameters have the same types if (!TypeChecking.hasSameTypeOrNumberOrNull(parameters)) { var types = List.of(leftExpression.getType(), rightExpression.getType()); throw new ConflictingTypesException(types, fromContext(ctx)); } - // If a parameter is the null token if (parameters.stream().map(TypedExpression::getType).anyMatch(Object.class::equals)) { return ResolvableExpression.withType(Boolean.class) .withPosition(fromContext(ctx)) @@ -170,13 +69,6 @@ public ResolvableExpression visitComparisonExpr(VtlParser.ComparisonExprContext } } - /** - * Visits 'element of' ('In' or 'Not in') expressions. - * - * @param ctx The scripting context for the expression. - * @return A ResolvableExpression resolving to the boolean result of the 'element of' - * expression. - */ @Override public ResolvableExpression visitInNotInExpr(VtlParser.InNotInExprContext ctx) { try { @@ -194,21 +86,12 @@ public ResolvableExpression visitInNotInExpr(VtlParser.InNotInExprContext ctx) { } } - /** - * Visits list expressions. - * - * @param ctx The scripting context for the expression. - * @return A ListExpression resolving to the list of given values with the given - * contained type. - */ @Override public ResolvableExpression visitLists(VtlParser.ListsContext ctx) { - // Transform all the constants. List listExpressions = ctx.constant().stream().map(exprVisitor::visitConstant).collect(Collectors.toList()); - // Find the type of the list. Set> types = listExpressions.stream().map(TypedExpression::getType).collect(Collectors.toSet()); @@ -218,11 +101,8 @@ public ResolvableExpression visitLists(VtlParser.ListsContext ctx) { throw new VtlRuntimeException(new ConflictingTypesException(types, pos)); } - // The grammar defines list with minimum one constant so the types will never - // be empty. Class type = types.iterator().next(); - // Since all expression are constant we don't need any context. List values = listExpressions.stream() .map(expression -> expression.resolve(Map.of())) diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/ConditionalVisitor.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/ConditionalVisitor.java index b10c94acc..6a02510df 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/ConditionalVisitor.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/ConditionalVisitor.java @@ -15,84 +15,18 @@ import java.util.*; import java.util.stream.Collectors; -/** IfVisitor is the base visitor for if-then-else expressions. */ +/** Dispatch for if-then-else and nvl expressions. */ public class ConditionalVisitor extends VtlBaseVisitor { private final ExpressionVisitor exprVisitor; - private final GenericFunctionsVisitor genericFunctionsVisitor; - /** - * Constructor taking an expression visitor. - * - * @param expressionVisitor The visitor for the enclosing expression. - * @param genericFunctionsVisitor - */ public ConditionalVisitor( ExpressionVisitor expressionVisitor, GenericFunctionsVisitor genericFunctionsVisitor) { this.exprVisitor = Objects.requireNonNull(expressionVisitor); this.genericFunctionsVisitor = Objects.requireNonNull(genericFunctionsVisitor); } - public static Long ifThenElse(Boolean condition, Long thenExpr, Long elseExpr) { - if (condition == null) { - return null; - } - return condition ? thenExpr : elseExpr; - } - - public static Double ifThenElse(Boolean condition, Double thenExpr, Double elseExpr) { - if (condition == null) { - return null; - } - return condition ? thenExpr : elseExpr; - } - - public static String ifThenElse(Boolean condition, String thenExpr, String elseExpr) { - if (condition == null) { - return null; - } - return condition ? thenExpr : elseExpr; - } - - public static Boolean ifThenElse(Boolean condition, Boolean thenExpr, Boolean elseExpr) { - if (condition == null) { - return null; - } - return condition ? thenExpr : elseExpr; - } - - public static Long nvl(Long value, Long defaultValue) { - return value == null ? defaultValue : value; - } - - public static Double nvl(Double value, Double defaultValue) { - return value == null ? defaultValue : value; - } - - public static Double nvl(Double value, Long defaultValue) { - return value == null ? defaultValue.doubleValue() : value; - } - - public static Double nvl(Long value, Double defaultValue) { - return value == null ? defaultValue : value.doubleValue(); - } - - public static String nvl(String value, String defaultValue) { - return value == null ? defaultValue : value; - } - - public static Boolean nvl(Boolean value, Boolean defaultValue) { - return value == null ? defaultValue : value; - } - - /** - * Visits if-then-else expressions. - * - * @param ctx The scripting context for the expression. - * @return A ResolvableExpression resolving to the if or else clause resolution - * depending on the condition resolution. - */ @Override public ResolvableExpression visitIfExpr(VtlParser.IfExprContext ctx) { try { @@ -110,13 +44,6 @@ public ResolvableExpression visitIfExpr(VtlParser.IfExprContext ctx) { } } - /** - * Visits case expressions. - * - * @param ctx The scripting context for the expression. - * @return A ResolvableExpression resolving to the case resolution depending on the - * condition resolution. - */ @Override public ResolvableExpression visitCaseExpr(VtlParser.CaseExprContext ctx) { try { @@ -135,7 +62,6 @@ public ResolvableExpression visitCaseExpr(VtlParser.CaseExprContext ctx) { ResolvableExpression elseExpression = exprVisitor.visit(exprs.get(exprs.size() - 1)); List forTypeCheck = (new ArrayList<>(thenExpressions)); forTypeCheck.add(elseExpression); - // TODO: handle better the default element position if (!hasSameTypeOrNull(forTypeCheck)) { try { throw new InvalidTypeException( @@ -176,12 +102,6 @@ private ResolvableExpression caseToIfIt( caseCondition); } - /** - * Visits nvl expressions. - * - * @param ctx The scripting context for the expression. - * @return A ResolvableExpression resolving to the null value clause. - */ @Override public ResolvableExpression visitNvlAtom(VtlParser.NvlAtomContext ctx) { try { diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/UnaryVisitor.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/UnaryVisitor.java index 3c6d91450..e61329855 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/UnaryVisitor.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/UnaryVisitor.java @@ -11,58 +11,18 @@ import java.util.List; import java.util.Objects; -/** UnaryVisitor is the base visitor for unary expressions (plus, minus, not). */ +/** Dispatch for unary expressions (plus, minus, not). */ public class UnaryVisitor extends VtlBaseVisitor { private final ExpressionVisitor exprVisitor; private final GenericFunctionsVisitor genericFunctionsVisitor; - /** - * Constructor taking an expression visitor. - * - * @param expressionVisitor The visitor for the enclosing expression. - */ public UnaryVisitor( ExpressionVisitor expressionVisitor, GenericFunctionsVisitor genericFunctionsVisitor) { exprVisitor = Objects.requireNonNull(expressionVisitor); this.genericFunctionsVisitor = genericFunctionsVisitor; } - public static Long plus(Long right) { - return right; - } - - public static Double plus(Double right) { - return right; - } - - public static Long minus(Long right) { - if (right == null) { - return null; - } - return -right; - } - - public static Double minus(Double right) { - if (right == null) { - return null; - } - return -right; - } - - public static Boolean not(Boolean right) { - if (right == null) { - return null; - } - return !right; - } - - /** - * Visits unary expressions. - * - * @param ctx The scripting context for the expression. - * @return A ResolvableExpression resolving to the result of the unary operation. - */ @Override public ResolvableExpression visitUnaryExpr(VtlParser.UnaryExprContext ctx) { try { diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/ComparisonFunctionsVisitor.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/ComparisonFunctionsVisitor.java index 414f7842c..fe054b078 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/ComparisonFunctionsVisitor.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/ComparisonFunctionsVisitor.java @@ -8,75 +8,21 @@ import fr.insee.vtl.model.exceptions.VtlScriptException; import fr.insee.vtl.parser.VtlBaseVisitor; import fr.insee.vtl.parser.VtlParser; -import java.math.BigDecimal; import java.util.List; import java.util.Objects; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -/** - * ComparisonFunctionsVisitor is the base visitor for expressions involving comparison - * functions. - */ +/** Dispatch for comparison function parse-tree nodes. */ public class ComparisonFunctionsVisitor extends VtlBaseVisitor { private final ExpressionVisitor exprVisitor; private final GenericFunctionsVisitor genericFunctionsVisitor; - public static Boolean between(Number operand, Number from, Number to) { - // TODO: handle other types (dates?) - if (operand == null || from == null || to == null) { - return null; - } - BigDecimal operandValue = - operand instanceof Long - ? BigDecimal.valueOf(operand.longValue()) - : BigDecimal.valueOf(operand.doubleValue()); - BigDecimal fromValue = - from instanceof Long - ? BigDecimal.valueOf(from.longValue()) - : BigDecimal.valueOf(from.doubleValue()); - BigDecimal toValue = - to instanceof Long - ? BigDecimal.valueOf(to.longValue()) - : BigDecimal.valueOf(to.doubleValue()); - return operandValue.compareTo(fromValue) >= 0 && operandValue.compareTo(toValue) <= 0; - } - - public static Boolean charsetMatch(String operandValue, String patternValue) { - if (operandValue == null || patternValue == null) { - return null; - } - Pattern pattern = Pattern.compile(patternValue); - Matcher matcher = pattern.matcher(operandValue); - return matcher.matches(); - } - - public static Boolean isNull(Object obj) { - if (obj == null) { - return Boolean.TRUE; - } - return Boolean.FALSE; - } - - /** - * Constructor taking an expression visitor. - * - * @param expressionVisitor The visitor for the enclosing expression. - */ public ComparisonFunctionsVisitor( ExpressionVisitor expressionVisitor, GenericFunctionsVisitor genericFunctionsVisitor) { exprVisitor = Objects.requireNonNull(expressionVisitor); this.genericFunctionsVisitor = Objects.requireNonNull(genericFunctionsVisitor); } - /** - * Visits a 'between' expression with scalar operand and delimiters. - * - * @param ctx The scripting context for the expression. - * @return A ResolvableExpression resolving to a boolean (true if the - * operand is between the delimiters). - */ @Override public ResolvableExpression visitBetweenAtom(VtlParser.BetweenAtomContext ctx) { try { @@ -89,13 +35,6 @@ public ResolvableExpression visitBetweenAtom(VtlParser.BetweenAtomContext ctx) { } } - /** - * Visits a pattern matching expression with string operand and regular expression. - * - * @param ctx The scripting context for the expression. - * @return A ResolvableExpression resolving to a boolean (true if the - * operand matches the pattern). - */ @Override public ResolvableExpression visitCharsetMatchAtom(VtlParser.CharsetMatchAtomContext ctx) { try { @@ -107,13 +46,6 @@ public ResolvableExpression visitCharsetMatchAtom(VtlParser.CharsetMatchAtomCont } } - /** - * Visits a null testing expression with scalar operand. - * - * @param ctx The scripting context for the expression. - * @return A ResolvableExpression resolving to a boolean (true if the - * operand is null). - */ @Override public ResolvableExpression visitIsNullAtom(VtlParser.IsNullAtomContext ctx) { try { diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/DistanceFunctionsVisitor.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/DistanceFunctionsVisitor.java index 0bb01e1ea..4ae410de4 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/DistanceFunctionsVisitor.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/DistanceFunctionsVisitor.java @@ -10,43 +10,19 @@ import fr.insee.vtl.parser.VtlParser; import java.util.List; import java.util.Objects; -import org.apache.commons.text.similarity.LevenshteinDistance; -/** - * DistanceFunctionsVisitor is the base visitor for expressions involving distance - * functions. - */ +/** Dispatch for distance function parse-tree nodes. */ public class DistanceFunctionsVisitor extends VtlBaseVisitor { private final ExpressionVisitor exprVisitor; private final GenericFunctionsVisitor genericFunctionsVisitor; - /** - * Constructor taking an expression visitor. - * - * @param expressionVisitor The visitor for the enclosing expression. - */ public DistanceFunctionsVisitor( ExpressionVisitor expressionVisitor, GenericFunctionsVisitor genericFunctionsVisitor) { exprVisitor = Objects.requireNonNull(expressionVisitor); this.genericFunctionsVisitor = Objects.requireNonNull(genericFunctionsVisitor); } - public static Long levenshtein(String stringA, String stringB) { - if (stringA == null || stringB == null) { - return null; - } - return Long.valueOf(LevenshteinDistance.getDefaultInstance().apply(stringA, stringB)); - } - - /** - * Visits a 'Levenshtein distance' expression with two strings parameters. - * - * @param ctx The scripting context for the expression (left and right expressions should be the - * string parameters). - * @return A ResolvableExpression resolving to a long integer representing the - * Levenshtein distance between the parameters. - */ @Override public ResolvableExpression visitLevenshteinAtom(VtlParser.LevenshteinAtomContext ctx) { try { diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/NumericFunctionsVisitor.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/NumericFunctionsVisitor.java index 521f4779d..d2adeeefa 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/NumericFunctionsVisitor.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/NumericFunctionsVisitor.java @@ -8,15 +8,10 @@ import fr.insee.vtl.model.exceptions.VtlScriptException; import fr.insee.vtl.parser.VtlBaseVisitor; import fr.insee.vtl.parser.VtlParser; -import java.math.BigDecimal; -import java.math.RoundingMode; import java.util.List; import java.util.Objects; -import java.util.Random; -/** - * NumericFunctionsVisitor is the visitor for expressions involving numeric functions. - */ +/** Dispatch for numeric function parse-tree nodes. */ public class NumericFunctionsVisitor extends VtlBaseVisitor { private final ExpressionVisitor exprVisitor; @@ -24,132 +19,12 @@ public class NumericFunctionsVisitor extends VtlBaseVisitorResolvableExpression resolving to a double. - */ @Override public ResolvableExpression visitUnaryNumeric(VtlParser.UnaryNumericContext ctx) { try { @@ -175,12 +50,6 @@ public ResolvableExpression visitUnaryNumeric(VtlParser.UnaryNumericContext ctx) } } - /** - * Visits a 'unaryWithOptionalNumeric' expression. - * - * @param ctx The scripting context for the expression. - * @return A ResolvableExpression resolving to a double). - */ @Override public ResolvableExpression visitUnaryWithOptionalNumeric( VtlParser.UnaryWithOptionalNumericContext ctx) { @@ -204,12 +73,6 @@ public ResolvableExpression visitUnaryWithOptionalNumeric( } } - /** - * Visits a 'binaryNumeric' expression. - * - * @param ctx The scripting context for the expression. - * @return A ResolvableExpression resolving to a double. - */ @Override public ResolvableExpression visitBinaryNumeric(VtlParser.BinaryNumericContext ctx) { try { diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/StringFunctionsVisitor.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/StringFunctionsVisitor.java index d70c7300c..3e7ec8333 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/StringFunctionsVisitor.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/visitors/expression/functions/StringFunctionsVisitor.java @@ -10,128 +10,19 @@ import fr.insee.vtl.parser.VtlParser; import java.util.List; import java.util.Objects; -import java.util.regex.Pattern; -import org.apache.commons.lang3.StringUtils; -/** - * ComparisonFunctionsVisitor is the base visitor for expressions involving string - * functions. - */ +/** Dispatch for string function parse-tree nodes. */ public class StringFunctionsVisitor extends VtlBaseVisitor { - static final Pattern LTRIM = Pattern.compile("^\\s+"); - static final Pattern RTRIM = Pattern.compile("\\s+$"); - private final ExpressionVisitor exprVisitor; private final GenericFunctionsVisitor genericFunctionsVisitor; - /** - * Constructor taking an expression visitor. - * - * @param expressionVisitor The visitor for the enclosing expression. - */ public StringFunctionsVisitor( ExpressionVisitor expressionVisitor, GenericFunctionsVisitor genericFunctionsVisitor) { this.exprVisitor = Objects.requireNonNull(expressionVisitor); this.genericFunctionsVisitor = Objects.requireNonNull(genericFunctionsVisitor); } - public static String trim(String value) { - if (value == null) { - return null; - } - return value.trim(); - } - - public static String ltrim(String value) { - if (value == null) { - return null; - } - return LTRIM.matcher(value).replaceAll(""); - } - - public static String rtrim(String value) { - if (value == null) { - return null; - } - return RTRIM.matcher(value).replaceAll(""); - } - - public static String ucase(String value) { - if (value == null) { - return null; - } - return value.toUpperCase(); - } - - public static String lcase(String value) { - if (value == null) { - return null; - } - return value.toLowerCase(); - } - - public static Long len(String value) { - if (value == null) { - return null; - } - return (long) value.length(); - } - - public static String substr(String value, Long start, Long len) { - if (value == null) { - return null; - } - if (start == null) { - start = 1L; - } - if (len == null) { - len = Long.valueOf(value.length()); - } - if (start > value.length()) { - return ""; - } - if (start != 0) { - start = start - 1; - } - - var end = start + len; - if (end > value.length()) { - return value.substring(Math.toIntExact(start)); - } - return value.substring(Math.toIntExact(start), Math.toIntExact(end)); - } - - public static String replace(String value, String pattern, String replacement) { - if (value == null || pattern == null) { - return null; - } - if (replacement == null) { - replacement = ""; - } - return value.replaceAll(pattern, replacement); - } - - public static Long instr(String v, String v2, Long start, Long occurence) { - if (v == null || v2 == null) { - return null; - } - if (start == null) { - start = 0L; - } - if (occurence == null) { - occurence = 1L; - } - return StringUtils.ordinalIndexOf(v.substring(start.intValue()), v2, occurence.intValue()) + 1L; - } - - /** - * Visits expressions corresponding to unary string functions. - * - * @param ctx The scripting context for the expression. - * @return A ResolvableExpression resolving to the result of the string function on - * the operand. - */ @Override public ResolvableExpression visitUnaryStringFunction(VtlParser.UnaryStringFunctionContext ctx) { try { @@ -151,13 +42,6 @@ public ResolvableExpression visitUnaryStringFunction(VtlParser.UnaryStringFuncti } } - /** - * Visits expressions corresponding to the substring function on a string operand. - * - * @param ctx The scripting context for the expression. - * @return A ResolvableExpression resolving to the result of the substring function - * on the operand. - */ @Override public ResolvableExpression visitSubstrAtom(VtlParser.SubstrAtomContext ctx) { try { @@ -181,13 +65,6 @@ public ResolvableExpression visitSubstrAtom(VtlParser.SubstrAtomContext ctx) { } } - /** - * Visits expressions corresponding to the replace function on a string operand. - * - * @param ctx The scripting context for the expression. - * @return A ResolvableExpression resolving to the result of the replace function on - * the operand. - */ @Override public ResolvableExpression visitReplaceAtom(VtlParser.ReplaceAtomContext ctx) { try { @@ -212,13 +89,6 @@ public ResolvableExpression visitReplaceAtom(VtlParser.ReplaceAtomContext ctx) { } } - /** - * Visits expressions corresponding to the pattern location function on a string operand. - * - * @param ctx The scripting context for the expression. - * @return A ResolvableExpression resolving to the result of the pattern location - * function on the operand. - */ @Override public ResolvableExpression visitInstrAtom(VtlParser.InstrAtomContext ctx) { try { diff --git a/vtl-engine/src/main/java/module-info.java b/vtl-engine/src/main/java/module-info.java index 781c580a4..d041d7612 100644 --- a/vtl-engine/src/main/java/module-info.java +++ b/vtl-engine/src/main/java/module-info.java @@ -1,6 +1,5 @@ import fr.insee.vtl.engine.VtlScriptEngineFactory; import fr.insee.vtl.engine.processors.InMemoryProcessingEngine; -import fr.insee.vtl.engine.semantics.functions.LevenshteinProvider; import fr.insee.vtl.model.FunctionProvider; import fr.insee.vtl.model.ProcessingEngine; import fr.insee.vtl.model.ProcessingEngineFactory; @@ -24,8 +23,6 @@ uses ProcessingEngineFactory; uses FunctionProvider; - provides FunctionProvider with - LevenshteinProvider; provides ProcessingEngineFactory with InMemoryProcessingEngine.Factory; provides ScriptEngineFactory with diff --git a/vtl-engine/src/main/resources/META-INF/services/fr.insee.vtl.model.FunctionProvider b/vtl-engine/src/main/resources/META-INF/services/fr.insee.vtl.model.FunctionProvider deleted file mode 100644 index b0eb2a3f4..000000000 --- a/vtl-engine/src/main/resources/META-INF/services/fr.insee.vtl.model.FunctionProvider +++ /dev/null @@ -1 +0,0 @@ -fr.insee.vtl.engine.semantics.functions.LevenshteinProvider \ No newline at end of file diff --git a/vtl-model/src/main/java/fr/insee/vtl/model/FunctionProvider.java b/vtl-model/src/main/java/fr/insee/vtl/model/FunctionProvider.java index 5e6210e29..a462652c2 100644 --- a/vtl-model/src/main/java/fr/insee/vtl/model/FunctionProvider.java +++ b/vtl-model/src/main/java/fr/insee/vtl/model/FunctionProvider.java @@ -1,17 +1,18 @@ package fr.insee.vtl.model; import java.lang.reflect.Method; +import java.util.List; import java.util.Map; import javax.script.ScriptEngine; -/** The function provider is used to register new function to be made available in the VTLEngine. */ +/** Registers extension functions to be made available in the VTL engine. */ public interface FunctionProvider { /** - * Return a map of functions to add to the VTL engine. + * Returns functions to add to the VTL engine. * * @param vtlEngine the VTL implementation of the {@link ScriptEngine}. - * @return a map of function name and {@link Method}. + * @return VTL function name to reflective {@link Method} bindings (supports overloads). */ - Map getFunctions(ScriptEngine vtlEngine); + Map> getFunctions(ScriptEngine vtlEngine); } From 5668cea1ad6224840eadfda5c03d591c7db23688 Mon Sep 17 00:00:00 2001 From: Nicolas Laval Date: Tue, 23 Jun 2026 20:47:43 +0200 Subject: [PATCH 2/3] Add default impl for FunctionProvider getFunctions --- .../fr/insee/vtl/model/FunctionProvider.java | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/vtl-model/src/main/java/fr/insee/vtl/model/FunctionProvider.java b/vtl-model/src/main/java/fr/insee/vtl/model/FunctionProvider.java index a462652c2..1c9cb2333 100644 --- a/vtl-model/src/main/java/fr/insee/vtl/model/FunctionProvider.java +++ b/vtl-model/src/main/java/fr/insee/vtl/model/FunctionProvider.java @@ -1,6 +1,7 @@ package fr.insee.vtl.model; import java.lang.reflect.Method; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.script.ScriptEngine; @@ -11,8 +12,34 @@ public interface FunctionProvider { /** * Returns functions to add to the VTL engine. * + *

Default implementation wraps {@link #getFunctionBindings(ScriptEngine)} so SPI modules that + * expose one {@link Method} per VTL name keep working. Override this method directly to register + * overloads. + * * @param vtlEngine the VTL implementation of the {@link ScriptEngine}. * @return VTL function name to reflective {@link Method} bindings (supports overloads). */ - Map> getFunctions(ScriptEngine vtlEngine); + default Map> getFunctions(ScriptEngine vtlEngine) { + Map bindings = getFunctionBindings(vtlEngine); + if (bindings.isEmpty()) { + return Map.of(); + } + Map> result = new LinkedHashMap<>(); + bindings.forEach((name, method) -> result.put(name, List.of(method))); + return Map.copyOf(result); + } + + /** + * Legacy SPI hook: one reflective binding per VTL function name. + * + *

Override this when migrating modules that previously implemented {@code Map + * getFunctions(ScriptEngine)}. New modules should override {@link #getFunctions(ScriptEngine)} + * instead. + * + * @param vtlEngine the VTL implementation of the {@link ScriptEngine}. + * @return VTL function name to reflective {@link Method} binding. + */ + default Map getFunctionBindings(ScriptEngine vtlEngine) { + return Map.of(); + } } From b9668163ca1da0446dbd902a634f508718bb5639 Mon Sep 17 00:00:00 2001 From: Nicolas Laval Date: Tue, 23 Jun 2026 21:03:31 +0200 Subject: [PATCH 3/3] Delete BuiltinFunctionProvider, add registerMethods in VTLScriptEngine --- .../fr/insee/vtl/engine/VtlScriptEngine.java | 34 ++++++---------- .../functions/BuiltinFunctionProvider.java | 11 ----- .../functions/NativeFunctionProviders.java | 40 +++++++++++-------- .../functions/NativeFunctionRegistry.java | 20 ---------- .../ArithmeticFunctionsProvider.java | 4 +- .../providers/BooleanFunctionsProvider.java | 4 +- .../ComparisonFunctionsProvider.java | 4 +- .../ComparisonOperatorFunctionsProvider.java | 4 +- .../ConditionalFunctionsProvider.java | 4 +- .../providers/DistanceFunctionsProvider.java | 4 +- .../providers/NumericFunctionsProvider.java | 4 +- .../providers/StringFunctionsProvider.java | 4 +- .../providers/TemporalFunctionsProvider.java | 4 +- .../providers/UnaryFunctionsProvider.java | 4 +- 14 files changed, 46 insertions(+), 99 deletions(-) delete mode 100644 vtl-engine/src/main/java/fr/insee/vtl/engine/functions/BuiltinFunctionProvider.java diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/VtlScriptEngine.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/VtlScriptEngine.java index 54d33e945..bceee0e19 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/VtlScriptEngine.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/VtlScriptEngine.java @@ -6,6 +6,7 @@ import fr.insee.vtl.antlr.runtime.tree.TerminalNode; import fr.insee.vtl.engine.exceptions.VtlRuntimeException; import fr.insee.vtl.engine.exceptions.VtlSyntaxException; +import fr.insee.vtl.engine.functions.NativeFunctionProviders; import fr.insee.vtl.engine.functions.NativeFunctionRegistry; import fr.insee.vtl.engine.visitors.AssignmentVisitor; import fr.insee.vtl.model.*; @@ -48,8 +49,7 @@ public class VtlScriptEngine extends AbstractScriptEngine { private final ScriptEngineFactory factory; private final VtlParseCache parseCache = new VtlParseCache(); - private final NativeFunctionRegistry builtinRegistry = NativeFunctionRegistry.builtins(); - private NativeFunctionRegistry extensionRegistry; + private final NativeFunctionRegistry functionRegistry = NativeFunctionRegistry.empty(); private NativeFunctionRegistry globalRegistry; private volatile Map processingEngineFactories; @@ -63,6 +63,10 @@ public class VtlScriptEngine extends AbstractScriptEngine { */ public VtlScriptEngine(ScriptEngineFactory factory) { this.factory = factory; + registerProvider(NativeFunctionProviders.INSTANCE); + for (FunctionProvider provider : ServiceLoader.load(FunctionProvider.class)) { + registerProvider(provider); + } } public static Positioned toPositioned(ParseTree tree) { @@ -429,12 +433,7 @@ public ScriptEngineFactory getFactory() { } public VtlMethod findMethod(String name, Collection types) throws NoSuchMethodException { - ensureExtensionRegistryLoaded(); - try { - return extensionRegistry.resolve(name, types); - } catch (NoSuchMethodException ignored) { - return builtinRegistry.resolve(name, types); - } + return functionRegistry.resolve(name, types); } public VtlMethod findGlobalMethod(String name, Collection types) @@ -445,9 +444,13 @@ public VtlMethod findGlobalMethod(String name, Collection types) return globalRegistry.resolveOrNull(name, types); } + public void registerProvider(FunctionProvider provider) { + Objects.requireNonNull(provider); + functionRegistry.registerAll(provider.getFunctions(this)); + } + public Method registerMethod(String name, Method method) { - ensureExtensionRegistryLoaded(); - return extensionRegistry.putAndReturnPrevious(name, method); + return functionRegistry.putAndReturnPrevious(name, method); } public Method registerGlobalMethod(String name, Method method) { @@ -456,15 +459,4 @@ public Method registerGlobalMethod(String name, Method method) { } return globalRegistry.putAndReturnPrevious(name, method); } - - private void ensureExtensionRegistryLoaded() { - if (extensionRegistry != null) { - return; - } - extensionRegistry = NativeFunctionRegistry.empty(); - ServiceLoader providers = ServiceLoader.load(FunctionProvider.class); - for (FunctionProvider provider : providers) { - extensionRegistry.registerAll(provider.getFunctions(this)); - } - } } diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/BuiltinFunctionProvider.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/BuiltinFunctionProvider.java deleted file mode 100644 index 3b2db793e..000000000 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/BuiltinFunctionProvider.java +++ /dev/null @@ -1,11 +0,0 @@ -package fr.insee.vtl.engine.functions; - -import java.lang.reflect.Method; -import java.util.List; -import java.util.Map; - -/** Built-in scalar function catalogue for the VTL engine. */ -public interface BuiltinFunctionProvider { - - Map> getFunctions(); -} diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/NativeFunctionProviders.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/NativeFunctionProviders.java index 0c99abba2..4471ac627 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/NativeFunctionProviders.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/NativeFunctionProviders.java @@ -10,35 +10,41 @@ import fr.insee.vtl.engine.functions.providers.StringFunctionsProvider; import fr.insee.vtl.engine.functions.providers.TemporalFunctionsProvider; import fr.insee.vtl.engine.functions.providers.UnaryFunctionsProvider; +import fr.insee.vtl.model.FunctionProvider; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.function.Supplier; +import javax.script.ScriptEngine; -/** Aggregates all built-in scalar function providers. */ -public final class NativeFunctionProviders { +/** Built-in scalar function catalogue exposed as a {@link FunctionProvider}. */ +public enum NativeFunctionProviders implements FunctionProvider { + INSTANCE; - private static final List BUILTINS = + private static final List>>> PARTS = List.of( - new NumericFunctionsProvider(), - new StringFunctionsProvider(), - new DistanceFunctionsProvider(), - new ArithmeticFunctionsProvider(), - new ConditionalFunctionsProvider(), - new BooleanFunctionsProvider(), - new UnaryFunctionsProvider(), - new ComparisonOperatorFunctionsProvider(), - new ComparisonFunctionsProvider(), - new TemporalFunctionsProvider()); + () -> new NumericFunctionsProvider().getFunctions(), + () -> new StringFunctionsProvider().getFunctions(), + () -> new DistanceFunctionsProvider().getFunctions(), + () -> new ArithmeticFunctionsProvider().getFunctions(), + () -> new ConditionalFunctionsProvider().getFunctions(), + () -> new BooleanFunctionsProvider().getFunctions(), + () -> new UnaryFunctionsProvider().getFunctions(), + () -> new ComparisonOperatorFunctionsProvider().getFunctions(), + () -> new ComparisonFunctionsProvider().getFunctions(), + () -> new TemporalFunctionsProvider().getFunctions()); - private NativeFunctionProviders() {} + @Override + public Map> getFunctions(ScriptEngine vtlEngine) { + return builtinFunctions(); + } public static Map> builtinFunctions() { Map> functions = new LinkedHashMap<>(); - for (BuiltinFunctionProvider provider : BUILTINS) { - provider - .getFunctions() + for (Supplier>> part : PARTS) { + part.get() .forEach( (vtlName, methods) -> functions.merge( diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/NativeFunctionRegistry.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/NativeFunctionRegistry.java index 0ae08d4ec..ed826b614 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/NativeFunctionRegistry.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/NativeFunctionRegistry.java @@ -16,26 +16,12 @@ /** VTL-name keyed catalogue of native {@link Method} bindings with overload resolution. */ public final class NativeFunctionRegistry { - private static final NativeFunctionRegistry BUILTINS = loadBuiltins(); - private final Map> byVtlName = new LinkedHashMap<>(); - private NativeFunctionRegistry() {} - - public static NativeFunctionRegistry builtins() { - return BUILTINS; - } - public static NativeFunctionRegistry empty() { return new NativeFunctionRegistry(); } - private static NativeFunctionRegistry loadBuiltins() { - NativeFunctionRegistry registry = new NativeFunctionRegistry(); - registry.registerAll(NativeFunctionProviders.builtinFunctions()); - return registry; - } - public void registerAll(Map> functions) { functions.forEach((vtlName, methods) -> methods.forEach(method -> register(vtlName, method))); } @@ -56,12 +42,6 @@ public void register(String vtlName, Method method) { }); } - public void put(String vtlName, Method method) { - Objects.requireNonNull(vtlName); - Objects.requireNonNull(method); - byVtlName.put(vtlName, List.of(method)); - } - public Method putAndReturnPrevious(String vtlName, Method method) { Objects.requireNonNull(vtlName); Objects.requireNonNull(method); diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/ArithmeticFunctionsProvider.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/ArithmeticFunctionsProvider.java index 4c196b985..00f5adde4 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/ArithmeticFunctionsProvider.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/ArithmeticFunctionsProvider.java @@ -1,12 +1,11 @@ package fr.insee.vtl.engine.functions.providers; import com.github.hervian.reflection.Fun; -import fr.insee.vtl.engine.functions.BuiltinFunctionProvider; import java.lang.reflect.Method; import java.util.List; import java.util.Map; -public final class ArithmeticFunctionsProvider implements BuiltinFunctionProvider { +public final class ArithmeticFunctionsProvider { public static Long addition(Long valueA, Long valueB) { if (valueA == null || valueB == null) { @@ -121,7 +120,6 @@ public static Double division(Double valueA, Double valueB) { return valueA / valueB; } - @Override public Map> getFunctions() { return Map.of( "addition", diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/BooleanFunctionsProvider.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/BooleanFunctionsProvider.java index e78032a9e..b9f97543d 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/BooleanFunctionsProvider.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/BooleanFunctionsProvider.java @@ -1,12 +1,11 @@ package fr.insee.vtl.engine.functions.providers; import com.github.hervian.reflection.Fun; -import fr.insee.vtl.engine.functions.BuiltinFunctionProvider; import java.lang.reflect.Method; import java.util.List; import java.util.Map; -public final class BooleanFunctionsProvider implements BuiltinFunctionProvider { +public final class BooleanFunctionsProvider { public static Boolean and(Boolean left, Boolean right) { if (left != null && !left) return false; @@ -35,7 +34,6 @@ public static Boolean xor(Boolean left, Boolean right) { return left ^ right; } - @Override public Map> getFunctions() { return Map.of( "and", List.of(Fun.toMethod(BooleanFunctionsProvider::and)), diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/ComparisonFunctionsProvider.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/ComparisonFunctionsProvider.java index 32dd9eb64..6b5f45ca4 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/ComparisonFunctionsProvider.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/ComparisonFunctionsProvider.java @@ -1,7 +1,6 @@ package fr.insee.vtl.engine.functions.providers; import com.github.hervian.reflection.Fun; -import fr.insee.vtl.engine.functions.BuiltinFunctionProvider; import java.lang.reflect.Method; import java.math.BigDecimal; import java.util.List; @@ -9,7 +8,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -public final class ComparisonFunctionsProvider implements BuiltinFunctionProvider { +public final class ComparisonFunctionsProvider { public static Boolean between(Number operand, Number from, Number to) { if (operand == null || from == null || to == null) { @@ -46,7 +45,6 @@ public static Boolean isNull(Object obj) { return Boolean.FALSE; } - @Override public Map> getFunctions() { return Map.of( "between", List.of(Fun.toMethod(ComparisonFunctionsProvider::between)), diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/ComparisonOperatorFunctionsProvider.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/ComparisonOperatorFunctionsProvider.java index fa4ba0380..60f28a0a5 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/ComparisonOperatorFunctionsProvider.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/ComparisonOperatorFunctionsProvider.java @@ -1,13 +1,12 @@ package fr.insee.vtl.engine.functions.providers; import com.github.hervian.reflection.Fun; -import fr.insee.vtl.engine.functions.BuiltinFunctionProvider; import java.lang.reflect.Method; import java.util.Date; import java.util.List; import java.util.Map; -public final class ComparisonOperatorFunctionsProvider implements BuiltinFunctionProvider { +public final class ComparisonOperatorFunctionsProvider { private static Integer compare(Object left, Object right) throws Exception { if (left == null || right == null) { @@ -93,7 +92,6 @@ public static Boolean notIn(Object obj, List list) { return !list.contains(obj); } - @Override public Map> getFunctions() { Map> functions = new java.util.LinkedHashMap<>(); functions.put("isEqual", List.of(Fun.toMethod(ComparisonOperatorFunctionsProvider::isEqual))); diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/ConditionalFunctionsProvider.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/ConditionalFunctionsProvider.java index 979110399..3f2bbb350 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/ConditionalFunctionsProvider.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/ConditionalFunctionsProvider.java @@ -1,12 +1,11 @@ package fr.insee.vtl.engine.functions.providers; import com.github.hervian.reflection.Fun; -import fr.insee.vtl.engine.functions.BuiltinFunctionProvider; import java.lang.reflect.Method; import java.util.List; import java.util.Map; -public final class ConditionalFunctionsProvider implements BuiltinFunctionProvider { +public final class ConditionalFunctionsProvider { public static Long ifThenElse(Boolean condition, Long thenExpr, Long elseExpr) { if (condition == null) { @@ -60,7 +59,6 @@ public static Boolean nvl(Boolean value, Boolean defaultValue) { return value == null ? defaultValue : value; } - @Override public Map> getFunctions() { return Map.of( "ifThenElse", diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/DistanceFunctionsProvider.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/DistanceFunctionsProvider.java index 2ce464eae..c57561e38 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/DistanceFunctionsProvider.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/DistanceFunctionsProvider.java @@ -1,13 +1,12 @@ package fr.insee.vtl.engine.functions.providers; import com.github.hervian.reflection.Fun; -import fr.insee.vtl.engine.functions.BuiltinFunctionProvider; import java.lang.reflect.Method; import java.util.List; import java.util.Map; import org.apache.commons.text.similarity.LevenshteinDistance; -public final class DistanceFunctionsProvider implements BuiltinFunctionProvider { +public final class DistanceFunctionsProvider { public static Long levenshtein(String stringA, String stringB) { if (stringA == null || stringB == null) { @@ -16,7 +15,6 @@ public static Long levenshtein(String stringA, String stringB) { return Long.valueOf(LevenshteinDistance.getDefaultInstance().apply(stringA, stringB)); } - @Override public Map> getFunctions() { return Map.of("levenshtein", List.of(Fun.toMethod(DistanceFunctionsProvider::levenshtein))); } diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/NumericFunctionsProvider.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/NumericFunctionsProvider.java index 68a9aba3f..cb9eccb51 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/NumericFunctionsProvider.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/NumericFunctionsProvider.java @@ -1,7 +1,6 @@ package fr.insee.vtl.engine.functions.providers; import com.github.hervian.reflection.Fun; -import fr.insee.vtl.engine.functions.BuiltinFunctionProvider; import java.lang.reflect.Method; import java.math.BigDecimal; import java.math.RoundingMode; @@ -9,7 +8,7 @@ import java.util.Map; import java.util.Random; -public final class NumericFunctionsProvider implements BuiltinFunctionProvider { +public final class NumericFunctionsProvider { public static Long ceil(Number value) { if (value == null) { @@ -119,7 +118,6 @@ public static Double log(Number operand, Number base) { return Math.log(operand.doubleValue()) / Math.log(base.doubleValue()); } - @Override public Map> getFunctions() { Map> functions = new java.util.LinkedHashMap<>(); functions.put("ceil", List.of(Fun.toMethod(NumericFunctionsProvider::ceil))); diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/StringFunctionsProvider.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/StringFunctionsProvider.java index b507d39bf..3d4729896 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/StringFunctionsProvider.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/StringFunctionsProvider.java @@ -1,14 +1,13 @@ package fr.insee.vtl.engine.functions.providers; import com.github.hervian.reflection.Fun; -import fr.insee.vtl.engine.functions.BuiltinFunctionProvider; import java.lang.reflect.Method; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import org.apache.commons.lang3.StringUtils; -public final class StringFunctionsProvider implements BuiltinFunctionProvider { +public final class StringFunctionsProvider { private static final Pattern LTRIM = Pattern.compile("^\\s+"); private static final Pattern RTRIM = Pattern.compile("\\s+$"); @@ -102,7 +101,6 @@ public static Long instr(String v, String v2, Long start, Long occurence) { return StringUtils.ordinalIndexOf(v.substring(start.intValue()), v2, occurence.intValue()) + 1L; } - @Override public Map> getFunctions() { Map> functions = new java.util.LinkedHashMap<>(); functions.put("trim", List.of(Fun.toMethod(StringFunctionsProvider::trim))); diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/TemporalFunctionsProvider.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/TemporalFunctionsProvider.java index 475b5d228..b2aabec47 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/TemporalFunctionsProvider.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/TemporalFunctionsProvider.java @@ -1,7 +1,6 @@ package fr.insee.vtl.engine.functions.providers; import com.github.hervian.reflection.Fun; -import fr.insee.vtl.engine.functions.BuiltinFunctionProvider; import java.lang.reflect.Method; import java.time.Instant; import java.time.OffsetDateTime; @@ -15,7 +14,7 @@ import org.threeten.extra.PeriodDuration; /** Native temporal scalar operators. */ -public final class TemporalFunctionsProvider implements BuiltinFunctionProvider { +public final class TemporalFunctionsProvider { public static Instant addition(Instant op, PeriodDuration dur) { return op.plus(dur); @@ -183,7 +182,6 @@ private static ChronoUnit toChronoUnit(String unit) { }; } - @Override public Map> getFunctions() { Map> functions = new java.util.LinkedHashMap<>(); functions.put( diff --git a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/UnaryFunctionsProvider.java b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/UnaryFunctionsProvider.java index 70f1dac4b..2e3fa520d 100644 --- a/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/UnaryFunctionsProvider.java +++ b/vtl-engine/src/main/java/fr/insee/vtl/engine/functions/providers/UnaryFunctionsProvider.java @@ -1,12 +1,11 @@ package fr.insee.vtl.engine.functions.providers; import com.github.hervian.reflection.Fun; -import fr.insee.vtl.engine.functions.BuiltinFunctionProvider; import java.lang.reflect.Method; import java.util.List; import java.util.Map; -public final class UnaryFunctionsProvider implements BuiltinFunctionProvider { +public final class UnaryFunctionsProvider { public static Long plus(Long right) { return right; @@ -37,7 +36,6 @@ public static Boolean not(Boolean right) { return !right; } - @Override public Map> getFunctions() { return Map.of( "plus",