This is an automated email from the ASF dual-hosted git repository.

andy pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/jena.git

commit da41c836247353f502c6f6b168b196df7b94e513
Author: Andy Seaborne <[email protected]>
AuthorDate: Wed Apr 23 09:24:03 2025 +0100

    GH-3144: SPARQL function call by URI
---
 .../src/main/java/org/apache/jena/query/ARQ.java   |   2 +
 .../java/org/apache/jena/sparql/ARQConstants.java  |  20 +-
 .../sparql/expr/urifunctions/SPARQLDispatch.java   | 253 ++++++++++++++++
 .../expr/urifunctions/SPARQLEvalException.java     |  25 ++
 .../sparql/expr/urifunctions/SPARQLFuncOp.java     | 321 +++++++++++++++++++++
 .../java/org/apache/jena/sparql/expr/TS_Expr.java  |   1 +
 .../apache/jena/sparql/expr/TestFunctions2.java    |  19 +-
 .../jena/sparql/expr/TestFunctionsByURI.java       |  69 +++++
 8 files changed, 689 insertions(+), 21 deletions(-)

diff --git a/jena-arq/src/main/java/org/apache/jena/query/ARQ.java 
b/jena-arq/src/main/java/org/apache/jena/query/ARQ.java
index 2d6649babf..2448cc3c73 100644
--- a/jena-arq/src/main/java/org/apache/jena/query/ARQ.java
+++ b/jena-arq/src/main/java/org/apache/jena/query/ARQ.java
@@ -36,6 +36,7 @@ import 
org.apache.jena.sparql.algebra.optimize.TransformOrderByDistinctApplicati
 import org.apache.jena.sparql.core.assembler.AssemblerUtils;
 import org.apache.jena.sparql.exec.http.QuerySendMode;
 import org.apache.jena.sparql.expr.aggregate.AggregateRegistry;
+import org.apache.jena.sparql.expr.urifunctions.SPARQLFuncOp;
 import org.apache.jena.sparql.function.FunctionRegistry;
 import org.apache.jena.sparql.function.scripting.ScriptLangSymbols;
 import org.apache.jena.sparql.mgt.Explain;
@@ -685,6 +686,7 @@ public class ARQ
             ServiceExecutorRegistry.init();
             AggregateRegistry.init();
             PropertyFunctionRegistry.init();
+            SPARQLFuncOp.init();
 
             // Register the datatypes for the CDT literals
             
TypeMapper.getInstance().registerDatatype(CompositeDatatypeList.type) ;
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/ARQConstants.java 
b/jena-arq/src/main/java/org/apache/jena/sparql/ARQConstants.java
index 97f99c1860..464c51223e 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/ARQConstants.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/ARQConstants.java
@@ -126,17 +126,17 @@ public class ARQConstants
     protected static final PrefixMapping globalPrefixMap = new 
PrefixMappingImpl() ;
     static {
         //globalPrefixMap.setNsPrefixes(PrefixMapping.Standard) ;
-        globalPrefixMap.setNsPrefix("rdf",  rdfPrefix) ;
-        globalPrefixMap.setNsPrefix("rdfs", rdfsPrefix) ;
-        globalPrefixMap.setNsPrefix("xsd",  xsdPrefix) ;
-        globalPrefixMap.setNsPrefix("owl" , owlPrefix) ;
-        globalPrefixMap.setNsPrefix("fn" ,  fnPrefix) ;
-        globalPrefixMap.setNsPrefix("sparql",  sparqlPrefix) ;
+        globalPrefixMap.setNsPrefix("rdf",      rdfPrefix) ;
+        globalPrefixMap.setNsPrefix("rdfs",     rdfsPrefix) ;
+        globalPrefixMap.setNsPrefix("xsd",      xsdPrefix) ;
+        globalPrefixMap.setNsPrefix("owl" ,     owlPrefix) ;
+        globalPrefixMap.setNsPrefix("fn" ,      fnPrefix) ;
+        globalPrefixMap.setNsPrefix("sparql",   sparqlPrefix) ;
         // Treat op: as fn: (op: has no namespace in XSD F&O).
-        globalPrefixMap.setNsPrefix("op" ,  fnPrefix) ;
-        globalPrefixMap.setNsPrefix("math" ,  mathPrefix) ;
-        globalPrefixMap.setNsPrefix("afn",  ARQFunctionLibraryURI) ;
-        globalPrefixMap.setNsPrefix("apf",  ARQPropertyFunctionLibraryURI) ;
+        globalPrefixMap.setNsPrefix("op" ,      fnPrefix) ;
+        globalPrefixMap.setNsPrefix("math" ,    mathPrefix) ;
+        globalPrefixMap.setNsPrefix("afn",      ARQFunctionLibraryURI) ;
+        globalPrefixMap.setNsPrefix("apf",      ARQPropertyFunctionLibraryURI) 
;
     }
     public static PrefixMapping getGlobalPrefixMap() { return globalPrefixMap 
; }
 
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/expr/urifunctions/SPARQLDispatch.java
 
b/jena-arq/src/main/java/org/apache/jena/sparql/expr/urifunctions/SPARQLDispatch.java
new file mode 100644
index 0000000000..c1cc858c04
--- /dev/null
+++ 
b/jena-arq/src/main/java/org/apache/jena/sparql/expr/urifunctions/SPARQLDispatch.java
@@ -0,0 +1,253 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.sparql.expr.urifunctions;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import org.apache.jena.atlas.lib.InternalErrorException;
+import org.apache.jena.sparql.expr.NodeValue;
+
+/**
+ * Dispatch to SPARQL function by URI.
+ */
+public class SPARQLDispatch {
+
+    static final String NS0 = SPARQLFuncOp.NS.substring(0, 
SPARQLFuncOp.NS.length()-1);
+
+    public static NodeValue exec(String uri, List<NodeValue>args) {
+        Objects.requireNonNull(uri);
+        Objects.requireNonNull(args);
+
+        NodeValue[] a = args.toArray(NodeValue[]::new);
+        return exec(uri,a);
+    }
+
+    public static NodeValue exec(String uri, NodeValue...args) {
+        if ( uri.startsWith("#") ) {
+            // Short name.
+            uri = SPARQLDispatch.NS0+uri;
+        }
+
+        Call call = getDispatchMap().get(uri);
+        if ( call == null )
+            throw new SPARQLEvalException("No such function: "+uri);
+        return call.exec(args);
+    }
+
+    private static RuntimeException exception(String format, Object...args) {
+        String msg = String.format(format, args);
+        return new SPARQLEvalException(msg);
+    }
+
+    static void register(Map<String, Call> map, String uri, Function0 
function) {
+        Call call = args->{
+            if ( args.length != 0 ) throw exception("%s: Expected zero 
arguments. Got %d", uri, args.length);
+            return function.exec();
+        };
+        registerCall(map, uri, call);
+    }
+
+    static void register(Map<String, Call> map, String uri, Function1 
function) {
+        Call call = args->{
+            if ( args.length != 1 ) throw exception("%s: Expected one 
argument. Got %d", uri, args.length);
+            return function.exec(args[0]);
+        };
+        registerCall(map, uri, call);
+    }
+
+    static void register(Map<String, Call> map, String uri, Function2 
function) {
+        Call call = args->{
+            if ( args.length != 2 ) throw exception("%s: Expected two 
arguments. Got %d", uri, args.length);
+            return function.exec(args[0], args[1]);
+        };
+        registerCall(map, uri, call);
+    }
+
+    static void register(Map<String, Call> map, String uri, Function3 
function) {
+        Call call = args->{
+            if ( args.length != 3 ) throw exception("%s: Expected three 
arguments. Got %d", uri, args.length);
+            return function.exec(args[0], args[1], args[2]);
+        };
+        registerCall(map, uri, call);
+    }
+
+    static void register(Map<String, Call> map, String uri, Function4 
function) {
+        Call call = args->{
+            if ( args.length != 3 ) throw exception("%s: Expected  arguments. 
Got %d", uri, args.length);
+            return function.exec(args[0], args[1], args[2], args[3]);
+        };
+        registerCall(map, uri, call);
+    }
+
+    // Switch on arity
+    static void register(Map<String, Call> map, String uri, Function1 
function1, Function2 function2) {
+        Call call = args->{
+            if ( args.length == 1 )
+                return function1.exec(args[0]);
+            if ( args.length == 2 )
+                return function2.exec(args[0], args[1]);
+            throw exception("%s: Expected one or two arguments. Got %d", uri, 
args.length);
+        };
+        registerCall(map, uri, call);
+    }
+
+    // Switch on arity
+    static void register(Map<String, Call> map, String uri, Function2 
function2, Function3 function3) {
+        Call call = args->{
+            if ( args.length == 2 )
+                return function2.exec(args[0], args[1]);
+            if ( args.length == 3 )
+                return function3.exec(args[0], args[1], args[2]);
+            throw exception("%s: Expected two or three arguments. Got %d", 
uri, args.length);
+        };
+        registerCall(map, uri, call);
+    }
+
+    // Switch on arity
+    static void register(Map<String, Call> map, String uri, FunctionN 
function) {
+        Call call = args->{
+            return function.exec(args);
+        };
+        registerCall(map, uri, call);
+    }
+
+    static void registerCall(Map<String, Call> map, String localName, Call 
call) {
+        String uri = SPARQLFuncOp.NS+localName;
+        Call oldCall = map.put(uri, call);
+        if ( oldCall != null )
+            throw new InternalErrorException("Multiple registration of "+uri);
+    }
+
+    private static class LazyDispatchMap {
+        private static final Map<String, Call> INITIALIZED_MAP = 
buildDispatchMap();
+        private static Map<String, Call> dispatchMap() {
+            return INITIALIZED_MAP;
+        }
+    }
+
+    static Map<String, Call> getDispatchMap() {
+        return LazyDispatchMap.dispatchMap();
+    }
+
+    static Map<String, Call> buildDispatchMap() {
+        Map<String, Call> map = new HashMap<>();
+        register(map, "plus", SPARQLFuncOp::sparql_plus );
+        register(map, "add", SPARQLFuncOp::sparql_plus );           // Alt 
name.
+        register(map, "subtract", SPARQLFuncOp::sparql_subtract );
+        register(map, "minus", SPARQLFuncOp::sparql_subtract );     // Alt 
name.
+        register(map, "multiply", SPARQLFuncOp::sparql_multiply );
+        register(map, "divide", SPARQLFuncOp::sparql_divide );
+
+        register(map, "equals", SPARQLFuncOp::sparql_equals );
+        register(map, "not-equals", SPARQLFuncOp::sparql_not_equals );
+        register(map, "greaterThan", SPARQLFuncOp::sparql_greaterThan );
+        register(map, "greaterThanOrEquals", 
SPARQLFuncOp::sparql_greaterThanOrEquals );
+        register(map, "lessThat", SPARQLFuncOp::sparql_lessThat );
+        register(map, "lessThanOrEquals", 
SPARQLFuncOp::sparql_lessThanOrEquals );
+
+        register(map, "and", SPARQLFuncOp::sparql_function_and );
+        register(map, "or", SPARQLFuncOp::sparql_function_or );
+        register(map, "not", SPARQLFuncOp::sparql_function_not );
+
+        register(map, "unary-minus", SPARQLFuncOp::sparql_unary_minus );
+        register(map, "unary-plus", SPARQLFuncOp::sparql_unary_plus );
+
+        register(map, "abs", SPARQLFuncOp::sparql_abs);
+        register(map, "bnode", SPARQLFuncOp::sparql_bnode);
+        register(map, "ceil", SPARQLFuncOp::sparql_ceil);
+
+        // List arity
+        register(map, "concat", (NodeValue[] 
args)->SPARQLFuncOp.sparql_concat(args));
+        register(map, "contains", SPARQLFuncOp::sparql_contains);
+        register(map, "datatype", SPARQLFuncOp::sparql_datatype);
+
+        register(map, "encode", SPARQLFuncOp::sparql_encode);
+        register(map, "floor", SPARQLFuncOp::sparql_floor);
+        register(map, "haslang", SPARQLFuncOp::sparql_haslang);
+        register(map, "haslangdir", SPARQLFuncOp::sparql_haslangdir);
+        // Arity 1/2
+        register(map, "iri", x->SPARQLFuncOp.sparql_iri(x), 
(x,b)->SPARQLFuncOp.sparql_iri(x,b));
+        register(map, "uri", SPARQLFuncOp::sparql_uri);
+        register(map, "isBlank", SPARQLFuncOp::sparql_isBlank);
+        register(map, "isLiteral", SPARQLFuncOp::sparql_isLiteral);
+        register(map, "isNumeric", SPARQLFuncOp::sparql_isNumeric);
+        register(map, "isIRI", SPARQLFuncOp::sparql_isIRI);
+        register(map, "isURI", SPARQLFuncOp::sparql_isURI);
+        register(map, "lang", SPARQLFuncOp::sparql_lang);
+        register(map, "langMatches", SPARQLFuncOp::sparql_langMatches);
+        register(map, "langdir", SPARQLFuncOp::sparql_langdir);
+        register(map, "lcase", SPARQLFuncOp::sparql_lcase);
+        register(map, "ucase", SPARQLFuncOp::sparql_ucase);
+        register(map, "now", SPARQLFuncOp::sparql_now);
+        register(map, "rand", SPARQLFuncOp::sparql_rand);
+        register(map, "regex", SPARQLFuncOp::sparql_regex);
+        register(map, "replace", SPARQLFuncOp::sparql_replace);
+        register(map, "round", SPARQLFuncOp::sparql_round);
+        register(map, "sameTerm", SPARQLFuncOp::sparql_sameTerm);
+        register(map, "sameValue", SPARQLFuncOp::sparql_sameValue);
+        register(map, "uuid", SPARQLFuncOp::sparql_uuid);
+
+        register(map, "year", SPARQLFuncOp::sparql_year);
+        register(map, "month", SPARQLFuncOp::sparql_month);
+        register(map, "day", SPARQLFuncOp::sparql_day);
+        register(map, "hours", SPARQLFuncOp::sparql_hours);
+        register(map, "minutes", SPARQLFuncOp::sparql_minutes);
+        register(map, "seconds", SPARQLFuncOp::sparql_seconds);
+        register(map, "tz", SPARQLFuncOp::sparql_tz);
+        register(map, "timezone", SPARQLFuncOp::sparql_timezone);
+
+        register(map, "subject", SPARQLFuncOp::sparql_subject);
+        register(map, "object", SPARQLFuncOp::sparql_object);
+        register(map, "predicate", SPARQLFuncOp::sparql_predicate);
+        register(map, "isTriple", SPARQLFuncOp::sparql_isTriple);
+        register(map, "triple", SPARQLFuncOp::sparql_triple);
+
+        register(map, "md5", SPARQLFuncOp::sparql_md5);
+        register(map, "sha1", SPARQLFuncOp::sparql_sha1);
+        register(map, "sha256", SPARQLFuncOp::sparql_sha256);
+        register(map, "sha384", SPARQLFuncOp::sparql_sha384);
+        register(map, "sha512", SPARQLFuncOp::sparql_sha512);
+
+        register(map, "str", SPARQLFuncOp::sparql_str);
+        register(map, "strafter", SPARQLFuncOp::sparql_strafter);
+        register(map, "strbefore", SPARQLFuncOp::sparql_strbefore);
+        register(map, "strdt", SPARQLFuncOp::sparql_strdt);
+        register(map, "strends", SPARQLFuncOp::sparql_strends);
+        register(map, "strlang", SPARQLFuncOp::sparql_strlang);
+        register(map, "strlangdir", SPARQLFuncOp::sparql_strlangdir);
+        register(map, "strlen", SPARQLFuncOp::sparql_strlen);
+        register(map, "strstarts", SPARQLFuncOp::sparql_strstarts);
+        // Arity 2/3
+        register(map, "substr", (s,x)->SPARQLFuncOp.sparql_substr(s,x), 
(s,x,y)->SPARQLFuncOp.sparql_substr(s,x,y));
+        register(map, "struuid", SPARQLFuncOp::sparql_struuid);
+
+        return Map.copyOf(map);
+     }
+
+    interface Call { NodeValue exec(NodeValue... nv); }
+    interface Function0 { NodeValue exec(); }
+    interface Function1 { NodeValue exec(NodeValue nv); }
+    interface Function2 { NodeValue exec(NodeValue nv1, NodeValue nv2); }
+    interface Function3 { NodeValue exec(NodeValue nv1, NodeValue nv2, 
NodeValue nv3); }
+    interface Function4 { NodeValue exec(NodeValue nv1, NodeValue nv2, 
NodeValue nv3, NodeValue nv4); }
+    interface FunctionN { NodeValue exec(NodeValue... nv); }
+}
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/expr/urifunctions/SPARQLEvalException.java
 
b/jena-arq/src/main/java/org/apache/jena/sparql/expr/urifunctions/SPARQLEvalException.java
new file mode 100644
index 0000000000..345c237954
--- /dev/null
+++ 
b/jena-arq/src/main/java/org/apache/jena/sparql/expr/urifunctions/SPARQLEvalException.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.sparql.expr.urifunctions;
+
+import org.apache.jena.shared.JenaException;
+
+class SPARQLEvalException extends JenaException {
+    SPARQLEvalException(String msg) { super(msg); }
+}
\ No newline at end of file
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/expr/urifunctions/SPARQLFuncOp.java
 
b/jena-arq/src/main/java/org/apache/jena/sparql/expr/urifunctions/SPARQLFuncOp.java
new file mode 100644
index 0000000000..24493cc72a
--- /dev/null
+++ 
b/jena-arq/src/main/java/org/apache/jena/sparql/expr/urifunctions/SPARQLFuncOp.java
@@ -0,0 +1,321 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.sparql.expr.urifunctions;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.jena.atlas.lib.*;
+import org.apache.jena.query.ARQ;
+import org.apache.jena.sparql.expr.*;
+import org.apache.jena.sparql.expr.nodevalue.NodeFunctions;
+import org.apache.jena.sparql.expr.nodevalue.NodeValueDigest;
+import org.apache.jena.sparql.expr.nodevalue.NodeValueOps;
+import org.apache.jena.sparql.expr.nodevalue.XSDFuncOp;
+import org.apache.jena.sparql.expr.urifunctions.SPARQLDispatch.Call;
+import org.apache.jena.sparql.function.FunctionBase;
+import org.apache.jena.sparql.function.FunctionFactory;
+import org.apache.jena.sparql.function.FunctionRegistry;
+import org.apache.jena.sparql.function.library.triple.TripleTermOps;
+
+/**
+ * SPARQL Functions and Operators, including extensions.
+ * <p>
+ * This class does not cover functional forms e.g. IF, COALESCE and BOUND.
+ * <p>
+ * Some function-like use of keywords in SPARQL do not make sense as 
standalone functions.
+ * Some functions depend on the execution context and do not make sense 
outside of such a context
+ * e.g. {@code BNODE(string)}, {@code NOW()}.
+ */
+public class SPARQLFuncOp {
+
+    public static final String NS = "https://www.w3.org/ns/sparql#";;
+
+    // ---- Integration into ARQ function execution
+
+    static boolean initialized = false;
+    public static void init() {
+        if ( ! initialized ) {
+            initialized = true;
+            loadFunctionRegistry(FunctionRegistry.get());
+        }
+    }
+
+    /** Load the SPARQL functions into a {@link FunctionRegistry}. */
+    public static void loadFunctionRegistry(FunctionRegistry reg) {
+        Map<String, Call> map = SPARQLDispatch.getDispatchMap();
+        // Add to the system FunctionRegistry once.
+        addToFunctionRegistry(FunctionRegistry.get(), map);
+    }
+
+    private static void addToFunctionRegistry(FunctionRegistry reg, 
Map<String, Call> map) {
+        FunctionFactory ff = createFunctionFactory();
+        map.forEach((uri,call) -> FunctionRegistry.get().put(uri, ff));
+    }
+
+    private static FunctionFactory createFunctionFactory() {
+        return uri -> new FunctionBase() {
+            @Override
+            public NodeValue exec(List<NodeValue> args) {
+                return SPARQLFuncOp.exec(uri, args);
+            }
+            @Override public void checkBuild(String uri, ExprList args) {}
+        };
+    }
+
+    // ---- Execution
+
+    public static NodeValue exec(String uri, List<NodeValue>args) {
+        return SPARQLDispatch.exec(uri, args);
+    }
+
+    public static NodeValue exec(String uri, NodeValue...args) {
+        return SPARQLDispatch.exec(uri, args);
+    }
+
+    private static boolean strict() {
+        return  ARQ.isStrictMode();
+    }
+
+    // ---- All the implementations
+
+    public static NodeValue sparql_plus(NodeValue nv1, NodeValue nv2) {
+        if ( strict() )
+            return XSDFuncOp.numAdd(nv1, nv2);
+        return NodeValueOps.additionNV(nv1, nv2);
+    }
+
+    public static NodeValue sparql_subtract(NodeValue nv1, NodeValue nv2) {
+        if ( strict() )
+            return XSDFuncOp.numSubtract(nv1, nv2);
+        return NodeValueOps.subtractionNV(nv1, nv2);
+    }
+
+    public static NodeValue sparql_multiply(NodeValue nv1, NodeValue nv2) {
+        if ( strict() )
+            return XSDFuncOp.numMultiply(nv1, nv2);
+
+        return NodeValueOps.multiplicationNV(nv1, nv2);
+    }
+
+    public static NodeValue sparql_divide(NodeValue nv1, NodeValue nv2) {
+        if ( strict() )
+            return XSDFuncOp.numDivide(nv1, nv2);
+        return NodeValueOps.divisionNV(nv1, nv2);
+    }
+
+    public static NodeValue sparql_unary_minus(NodeValue nv) {
+        return XSDFuncOp.unaryMinus(nv);    }
+
+    public static NodeValue sparql_unary_plus(NodeValue nv) {
+       return XSDFuncOp.unaryPlus(nv);
+    }
+
+    public static NodeValue sparql_equals(NodeValue nv1, NodeValue nv2) {
+        boolean b = NodeValue.sameValueAs(nv1, nv2);
+        return NodeValue.booleanReturn(b);
+    }
+
+    public static NodeValue sparql_not_equals(NodeValue nv1, NodeValue nv2) {
+        boolean b = NodeValue.notSameValueAs(nv1, nv2);
+        return NodeValue.booleanReturn(b);
+    }
+
+    public static NodeValue sparql_greaterThan(NodeValue nv1, NodeValue nv2) {
+        int r = NodeValue.compare(nv1, nv2);
+        return NodeValue.booleanReturn(r == Expr.CMP_GREATER);
+    }
+
+    public static NodeValue sparql_lessThat(NodeValue nv1, NodeValue nv2) {
+        int r = NodeValue.compare(nv1, nv2);
+        return NodeValue.booleanReturn(r == Expr.CMP_LESS);
+    }
+
+    public static NodeValue sparql_greaterThanOrEquals(NodeValue nv1, 
NodeValue nv2) {
+        int r = NodeValue.compare(nv1, nv2);
+        return NodeValue.booleanReturn(r == Expr.CMP_GREATER || r == 
Expr.CMP_EQUAL);
+    }
+
+    public static NodeValue sparql_lessThanOrEquals(NodeValue nv1, NodeValue 
nv2) {
+        int r = NodeValue.compare(nv1, nv2);
+        return NodeValue.booleanReturn(r == Expr.CMP_LESS || r == 
Expr.CMP_EQUAL);
+    }
+
+    // and,or,not as functions (arguments have been evaluated)
+
+    public static NodeValue sparql_function_and(NodeValue nv1, NodeValue nv2) {
+        boolean arg1 = XSDFuncOp.booleanEffectiveValue(nv1);
+        boolean arg2 = XSDFuncOp.booleanEffectiveValue(nv2);
+        return NodeValue.booleanReturn(arg1 && arg2);
+    }
+
+    public static NodeValue sparql_function_or(NodeValue nv1, NodeValue nv2) {
+        boolean arg1 = XSDFuncOp.booleanEffectiveValue(nv1);
+        boolean arg2 = XSDFuncOp.booleanEffectiveValue(nv2);
+        return NodeValue.booleanReturn(arg1 || arg2);
+    }
+
+  public static NodeValue sparql_function_not(NodeValue nv) {
+      boolean arg = XSDFuncOp.booleanEffectiveValue(nv);
+      return NodeValue.booleanReturn(!arg);
+  }
+
+    // These are not functions.
+//    public static NodeValue sparql_operator_and() { return null; }
+//    public static NodeValue sparql_operator_or() { return null; }
+//    public static NodeValue sparql_operator_not() { return null; }
+
+//    public static NodeValue sparql_bound() { return null; }
+//    public static NodeValue sparql_if() { return null; }
+//    public static NodeValue sparql_coalesce() { return null; }
+
+//    public static NodeValue sparql_filter_exists() { return null; }
+//    public static NodeValue sparql_filter_not_exists() { return null; }
+
+//    public static NodeValue sparql_logical_or() { return null; }
+//    public static NodeValue sparql_logical_and() { return null; }
+
+//    public static NodeValue sparql_in(NodeValue... nv) { return null; }
+//    public static NodeValue sparql_not_in(NodeValue... nv) { return null; }
+
+    public static NodeValue sparql_sameTerm(NodeValue nv1, NodeValue nv2) { 
return NodeFunctions.sameTerm(nv1, nv2); }
+
+    public static NodeValue sparql_sameValue(NodeValue nv1, NodeValue nv2) {
+        // Need to deal with NaN.
+        if ( ( nv1.isDouble()||nv1.isFloat() ) && ( 
nv2.isDouble()||nv2.isFloat() ) ) {
+            double d1 = nv1.getDouble();
+            double d2 = nv2.getDouble();
+            if ( Double.isNaN(d1) &&Double.isNaN(d2) )
+                return NodeValue.TRUE;
+        }
+        boolean b = NodeValue.sameValueAs(nv1, nv2);
+        return NodeValue.booleanReturn(b);
+    }
+
+// Old name:: public static NodeValue sparql_RDFterm_equal(NodeValue nv1, 
NodeValue nv2) { return null; }
+    public static NodeValue sparql_isIRI(NodeValue nv)      { return 
NodeFunctions.isIRI(nv); }
+    public static NodeValue sparql_isURI(NodeValue nv)      { return 
NodeFunctions.isURI(nv); }
+    public static NodeValue sparql_isBlank(NodeValue nv)    { return 
NodeFunctions.isBlank(nv); }
+    public static NodeValue sparql_isLiteral(NodeValue nv)  { return 
NodeFunctions.isLiteral(nv); }
+    public static NodeValue sparql_isNumeric(NodeValue nv)  { return 
NodeFunctions.isNumeric(nv); }
+    public static NodeValue sparql_str(NodeValue nv)        { return 
NodeFunctions.str(nv); }
+
+    public static NodeValue sparql_lang(NodeValue nv)       { return 
NodeFunctions.str(nv); }
+    public static NodeValue sparql_langdir(NodeValue nv)    { return 
NodeFunctions.langdir(nv); }
+    public static NodeValue sparql_haslang(NodeValue nv)    { return 
NodeFunctions.hasLang(nv); }
+    public static NodeValue sparql_haslangdir(NodeValue nv) { return 
NodeFunctions.hasLangDir(nv); }
+    public static NodeValue sparql_datatype(NodeValue nv)   { return 
NodeFunctions.datatype(nv); }
+
+    // Term functions : NodeFunctions
+
+    public static NodeValue sparql_iri(NodeValue nv) { return 
NodeFunctions.iri(nv, null); }
+    // Extension
+    public static NodeValue sparql_iri(NodeValue nv, NodeValue nvBase) { 
return NodeFunctions.iri(nv, nvBase.getString()); }
+
+    public static NodeValue sparql_uri(NodeValue nv) { return sparql_iri(nv); }
+
+    // Only BNODE(), not BNODE(str)
+    public static NodeValue sparql_bnode() { return null; }
+
+    // Not a function - depends on "current row".
+    //public static NodeValue sparql_bnode(NodeValue nv) { return null; }
+
+    public static NodeValue sparql_strdt(NodeValue nv1, NodeValue nv2) { 
return NodeFunctions.strDatatype(nv1, nv2); }
+    public static NodeValue sparql_strlang(NodeValue nv1, NodeValue nv2) { 
return NodeFunctions.strLang(nv1, nv2); }
+    public static NodeValue sparql_strlangdir(NodeValue nv1, NodeValue nv2, 
NodeValue nv3) { return NodeFunctions.strLangDir(nv1, nv2, nv3); }
+
+    public static NodeValue sparql_uuid() { return NodeFunctions.uuid(); }
+    public static NodeValue sparql_struuid() { return NodeFunctions.struuid(); 
}
+
+    public static NodeValue sparql_strlen(NodeValue nv) { return 
XSDFuncOp.strlen(nv); }
+    public static NodeValue sparql_substr(NodeValue nv1, NodeValue nv2) { 
return XSDFuncOp.substring(nv1, nv2) ; }
+    public static NodeValue sparql_substr(NodeValue nv1, NodeValue nv2, 
NodeValue nv3) { return XSDFuncOp.substring(nv1, nv2, nv3) ; }
+
+    public static NodeValue sparql_ucase(NodeValue nv) { return 
XSDFuncOp.strUpperCase(nv); }
+    public static NodeValue sparql_lcase(NodeValue nv) { return 
XSDFuncOp.strLowerCase(nv); }
+    public static NodeValue sparql_strstarts(NodeValue nv1, NodeValue nv2) { 
return XSDFuncOp.strStartsWith(nv1, nv2); }
+    public static NodeValue sparql_strends(NodeValue nv1, NodeValue nv2) { 
return XSDFuncOp.strEndsWith(nv1, nv2); }
+
+    public static NodeValue sparql_contains(NodeValue nv1, NodeValue nv2) { 
return XSDFuncOp.strContains(nv1, nv2); }
+    public static NodeValue sparql_strbefore(NodeValue nv1, NodeValue nv2) { 
return XSDFuncOp.strBefore(nv1, nv2); }
+    public static NodeValue sparql_strafter(NodeValue nv1, NodeValue nv2) { 
return XSDFuncOp.strAfter(nv1, nv2); }
+
+    public static NodeValue sparql_concat(NodeValue...args) {
+        return XSDFuncOp.strConcat(List.of(args));
+    }
+
+    public static NodeValue sparql_langMatches(NodeValue nv1, NodeValue nv2) { 
return NodeFunctions.langMatches(nv1, nv2); }
+
+    private static record CacheKey(String pattern, String flags) {}
+    private static Cache<CacheKey, RegexEngine> regexEngineCache = 
CacheFactory.createCache(20);
+    private static RegexEngine getRegexEngine(String patternStr, String 
flagsStr) {
+        CacheKey cacheKey = new CacheKey(patternStr, flagsStr);
+        return regexEngineCache.get(cacheKey, 
(key)->E_Regex.makeRegexEngine(key.pattern, key.flags));
+    }
+
+    public static NodeValue sparql_regex(NodeValue string, NodeValue pattern, 
NodeValue flags) {
+        // Cache - this means regexes are compiled once.
+        String patternStr = pattern.getString();
+        String flagsStr = flags.getString();
+        RegexEngine regexEngine = getRegexEngine(patternStr, flagsStr);
+        String str = string.getString();
+        boolean b = regexEngine.match(str);
+        return NodeValue.booleanReturn(b);
+    }
+
+    public static NodeValue sparql_replace(NodeValue nvStr, NodeValue 
nvPattern, NodeValue nvReplacement)
+    { return XSDFuncOp.strReplace(nvStr, nvPattern, nvReplacement); }
+
+    public static NodeValue sparql_encode(NodeValue nv) { return 
XSDFuncOp.strEncodeForURI(nv); }
+    public static NodeValue sparql_abs(NodeValue nv)    { return 
XSDFuncOp.abs(nv); }
+    public static NodeValue sparql_round(NodeValue nv)  { return 
XSDFuncOp.round(nv); }
+    public static NodeValue sparql_ceil(NodeValue nv)   { return 
XSDFuncOp.ceiling(nv); }
+    public static NodeValue sparql_floor(NodeValue nv)  { return 
XSDFuncOp.floor(nv); }
+
+    public static NodeValue sparql_rand() {
+        double d = RandomLib.random.nextDouble();
+        return NodeValue.makeDouble(d);
+    }
+
+    // Warning : not scoped to evaluation.
+    public static NodeValue sparql_now() { return 
NodeValue.makeDateTime(DateTimeUtils.nowAsXSDDateTimeString()); }
+
+    public static NodeValue sparql_year(NodeValue nv)       { return 
XSDFuncOp.getYear(nv); }
+    public static NodeValue sparql_month(NodeValue nv)      { return 
XSDFuncOp.getMonth(nv); }
+    public static NodeValue sparql_day(NodeValue nv)        { return 
XSDFuncOp.getDay(nv); }
+    public static NodeValue sparql_hours(NodeValue nv)      { return 
XSDFuncOp.getHours(nv); }
+    public static NodeValue sparql_minutes(NodeValue nv)    { return 
XSDFuncOp.getMinutes(nv); }
+    public static NodeValue sparql_seconds(NodeValue nv)    { return 
XSDFuncOp.getSeconds(nv); }
+    // Returns duration
+    public static NodeValue sparql_timezone(NodeValue nv)   { return 
XSDFuncOp.dtGetTimezone(nv); }
+    // Returns string
+    public static NodeValue sparql_tz(NodeValue nv)         { return 
XSDFuncOp.dtGetTZ(nv); }
+
+    public static NodeValue sparql_triple(NodeValue s, NodeValue p, NodeValue 
o)    { return TripleTermOps.fnTriple(s, p, o); }
+    public static NodeValue sparql_subject(NodeValue tripleTerm)               
     { return TripleTermOps.tripleSubject(tripleTerm); }
+    public static NodeValue sparql_predicate(NodeValue tripleTerm)             
     { return TripleTermOps.triplePredicate(tripleTerm); }
+    public static NodeValue sparql_object(NodeValue tripleTerm)                
     { return TripleTermOps.tripleObject(tripleTerm); }
+    public static NodeValue sparql_isTriple(NodeValue nv)                      
     { return TripleTermOps.isTriple(nv); }
+
+    public static NodeValue sparql_md5(NodeValue nv)    { return 
NodeValueDigest.calculateDigest(nv, "MD-5"); }
+    public static NodeValue sparql_sha1(NodeValue nv)   { return 
NodeValueDigest.calculateDigest(nv, "SHA-1"); }
+    public static NodeValue sparql_sha256(NodeValue nv) { return 
NodeValueDigest.calculateDigest(nv, "SHA-256"); }
+    public static NodeValue sparql_sha384(NodeValue nv) { return 
NodeValueDigest.calculateDigest(nv, "SHA-384"); }
+    public static NodeValue sparql_sha512(NodeValue nv) { return 
NodeValueDigest.calculateDigest(nv, "SHA-512"); }
+}
diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/expr/TS_Expr.java 
b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TS_Expr.java
index 0fa08aa625..7a6e0ef9e0 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/expr/TS_Expr.java
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TS_Expr.java
@@ -37,6 +37,7 @@ import org.junit.runners.Suite.SuiteClasses;
     , TestExpressionsMath.class
     , TestFunctions.class
     , TestFunctions2.class
+    , TestFunctionsByURI.class
     , TestTripleTermFunctions.class
     , TestLeviathanFunctions.class
     , TestNodeValueOps.class
diff --git 
a/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestFunctions2.java 
b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestFunctions2.java
index 1812d2b5e5..835f9e37c8 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestFunctions2.java
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestFunctions2.java
@@ -581,19 +581,16 @@ public class TestFunctions2
 
     // --------
 
-    private static PrefixMapping pmap = ARQConstants.getGlobalPrefixMap() ;
+    private static PrefixMapping pmap = ARQConstants.getGlobalPrefixMap();
 
-    private static void test(String string, String result)
-    {
-        Expr expr = ExprUtils.parse(string, pmap) ;
-        NodeValue nv = expr.eval(null, new FunctionEnvBase()) ;
-        Node r = NodeFactoryExtra.parseNode(result) ;
-        NodeValue nvr = NodeValue.makeNode(r) ;
+    private static void test(String string, String result) {
+        Expr expr = ExprUtils.parse(string, pmap);
+        NodeValue nv = expr.eval(null, new FunctionEnvBase());
+        Node r = NodeFactoryExtra.parseNode(result);
+        NodeValue nvr = NodeValue.makeNode(r);
 
-        assertTrue("Not same value: Expected: "+nvr+" : Actual = "+nv, 
NodeValue.sameValueAs(nvr, nv)) ;
+        assertTrue("Not same value: Expected: " + nvr + " : Actual = " + nv, 
NodeValue.sameValueAs(nvr, nv));
         // test result must be lexical form exact.
-        assertEquals(r, nv.asNode()) ;
+        assertEquals(r, nv.asNode());
     }
-
-    // ROUND to TestXSDFuncOps.
 }
diff --git 
a/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestFunctionsByURI.java 
b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestFunctionsByURI.java
new file mode 100644
index 0000000000..f9290ad091
--- /dev/null
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestFunctionsByURI.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.sparql.expr;
+
+import static org.junit.Assert.assertTrue;
+
+import org.junit.AfterClass ;
+import org.junit.BeforeClass ;
+import org.junit.Test ;
+
+import org.apache.jena.shared.PrefixMapping ;
+import org.apache.jena.sparql.ARQConstants ;
+import org.apache.jena.sparql.function.FunctionEnvBase ;
+import org.apache.jena.sparql.sse.SSE;
+import org.apache.jena.sparql.util.ExprUtils ;
+import org.apache.jena.sys.JenaSystem;
+
+/**
+ * Test that SPARQl functions can be called by their URI as given in the 
SPARQL 1.2
+ * namespace <a 
href="https://www.w3.org/ns/sparql#";>https://www.w3.org/ns/sparql#</a>.
+ */
+public class TestFunctionsByURI
+{
+    private static boolean warnOnBadLexicalForms;
+
+    static { JenaSystem.init(); }
+
+    @BeforeClass
+    public static void beforeClass() {
+        warnOnBadLexicalForms = NodeValue.VerboseWarnings;
+        NodeValue.VerboseWarnings = false;
+    }
+
+    @AfterClass
+    public static void afterClass() {
+        NodeValue.VerboseWarnings = warnOnBadLexicalForms;
+    }
+
+    @Test public void sparql_function_uri_1()  { 
test("sparql:unary-minus(123)",                "-123") ; }
+    @Test public void sparql_function_uri_2()  { test("sparql:subtract(12 
,sparql:multiply(5,2))",  "2") ; }
+    @Test public void sparql_function_uri_3()  { test("sparql:not(123)",   
"false") ; }          // EBV - effective boolean value
+    // --------
+
+    private static PrefixMapping pmap = ARQConstants.getGlobalPrefixMap() ;
+
+    private static void test(String string, String result) {
+        Expr expr = ExprUtils.parse(string, pmap);
+        NodeValue nv = expr.eval(null, new FunctionEnvBase());
+        NodeValue nvr = SSE.parseNodeValue(result);
+        boolean b = NodeValue.sameValueAs(nvr, nv);
+        assertTrue("Not same value: Expected: " + nvr + " : Actual = " + nv, 
b);
+    }
+}


Reply via email to