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 c829fba30a8de6dce8128dd97dac1033e8007a44 Author: Andy Seaborne <[email protected]> AuthorDate: Mon Jan 5 21:34:55 2026 +0000 GH-3685: Update string functions for RDF 1.2 base direction --- .../apache/jena/riot/writer/JsonLD11Writer.java | 9 +- .../jena/sparql/expr/nodevalue/NodeValueOps.java | 84 ++-- .../jena/sparql/expr/nodevalue/XSDFuncOp.java | 82 ++-- .../org/apache/jena/arq/junit/OmittedTest.java | 4 +- .../org/apache/jena/rdfs/TestDatasetGraphRDFS.java | 4 +- .../java/org/apache/jena/sparql/expr/TS_Expr.java | 3 +- .../org/apache/jena/sparql/expr/TestFunctions.java | 141 +----- ...tions2.java => TestSparqlKeywordFunctions.java} | 500 +++++++++++++-------- .../sparql/expr/TestStringArgCompatibility.java | 83 ++++ .../main/java/org/apache/jena/atlas/lib/Lib.java | 8 +- .../org/apache/jena/fuseki/servlets/ActionLib.java | 4 +- 11 files changed, 507 insertions(+), 415 deletions(-) diff --git a/jena-arq/src/main/java/org/apache/jena/riot/writer/JsonLD11Writer.java b/jena-arq/src/main/java/org/apache/jena/riot/writer/JsonLD11Writer.java index 9b4d261474..40ce746117 100644 --- a/jena-arq/src/main/java/org/apache/jena/riot/writer/JsonLD11Writer.java +++ b/jena-arq/src/main/java/org/apache/jena/riot/writer/JsonLD11Writer.java @@ -18,6 +18,8 @@ package org.apache.jena.riot.writer; +import static org.apache.jena.atlas.lib.Lib.equalsOrNulls; + import java.io.IOException; import java.io.OutputStream; import java.io.Writer; @@ -37,7 +39,6 @@ import com.apicatalog.rdf.api.RdfConsumerException; import jakarta.json.*; import jakarta.json.stream.JsonGenerator; -import org.apache.jena.atlas.lib.Lib; import org.apache.jena.atlas.logging.FmtLog; import org.apache.jena.graph.Node; import org.apache.jena.riot.Lang; @@ -96,11 +97,11 @@ public class JsonLD11Writer implements WriterDatasetRIOT { boolean indented = true; // Choose algorithms - if ( Lib.equals(variant, RDFFormat.PRETTY) ) { + if ( equalsOrNulls(variant, RDFFormat.PRETTY) ) { writeThis = writePretty(array, dsg); - } else if ( variant == null || Lib.equals(variant, RDFFormat.PLAIN) ) { + } else if ( variant == null || equalsOrNulls(variant, RDFFormat.PLAIN) ) { writeThis = writePlain(array, dsg); - } else if ( Lib.equals(variant, RDFFormat.FLAT) ) { + } else if ( equalsOrNulls(variant, RDFFormat.FLAT) ) { writeThis = writePlain(array, dsg); indented = false; } else { diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/expr/nodevalue/NodeValueOps.java b/jena-arq/src/main/java/org/apache/jena/sparql/expr/nodevalue/NodeValueOps.java index 5b397efb6f..2a5534ee3f 100644 --- a/jena-arq/src/main/java/org/apache/jena/sparql/expr/nodevalue/NodeValueOps.java +++ b/jena-arq/src/main/java/org/apache/jena/sparql/expr/nodevalue/NodeValueOps.java @@ -27,14 +27,17 @@ import java.util.GregorianCalendar; import javax.xml.datatype.Duration; import javax.xml.datatype.XMLGregorianCalendar; +import org.apache.jena.atlas.lib.Lib; +import org.apache.jena.datatypes.RDFDatatype; import org.apache.jena.datatypes.xsd.XSDDatatype; import org.apache.jena.graph.Node; import org.apache.jena.graph.NodeFactory; +import org.apache.jena.graph.TextDirection; import org.apache.jena.sparql.expr.ExprEvalException; import org.apache.jena.sparql.expr.ExprEvalTypeException; import org.apache.jena.sparql.expr.NodeValue; import org.apache.jena.sparql.expr.ValueSpace; -import org.apache.jena.sparql.util.NodeUtils; +import org.apache.jena.vocabulary.RDF; /** * Operations relating to {@link NodeValue NodeValues}. @@ -301,18 +304,36 @@ public class NodeValueOps { Node n = nv.asNode(); if ( !n.isLiteral() ) throw new ExprEvalException(label + ": Not a literal: " + nv); - String lang = n.getLiteralLanguage(); - if ( NodeUtils.isLangString(n) ) - // Language tag. Legal. + if ( nv.isString() ) + // Includes derived types of xsd:string. return n; - // No language tag : either no datatype or a datatype of xsd:string - // Includes the case of rdf:langString and no language ==> Illegal as a - // compatible string. + RDFDatatype dt = n.getLiteralDatatype(); + if ( ! RDF.dtLangString.equals(dt) && ! RDF.dtDirLangString.equals(dt) ) + throw new ExprEvalException(label + ": Not a string literal: " + nv); - if ( nv.isString() ) + // Check for malformed: + // e.g. "abc"^^rdf:langString, and "abc"^^rdf:dirLangString + + // Must have a language. + String lang = n.getLiteralLanguage(); + if ( lang == null || lang.isEmpty() ) + throw new ExprEvalException(label + ": Not a string literal (no langtag): " + nv); + if ( RDF.dtLangString.equals(dt) ) { + // Must not have a text direction + if ( n.getLiteralBaseDirection() != null ) + throw new ExprEvalException(label + ": Not a string literal (rdf:langString + text direction): " + nv); return n; + } + if ( RDF.dtDirLangString.equals(dt) ) { + // Must have a text direction + if ( n.getLiteralBaseDirection() == null ) + throw new ExprEvalException(label + ": Not a string literal (no text direction): " + nv); + return n; + } + + // Should not get here. throw new ExprEvalException(label + ": Not a string literal: " + nv); } @@ -322,19 +343,36 @@ public class NodeValueOps { * is not symmetric. * <ul> * <li>"abc"@en is compatible with "abc" + * <li>"abc"@en--ltr is compatible with "abc" * <li>"abc" is NOT compatible with "abc"@en + * <li>"abc"@en--ltr is NOT compatible with "abc"@en * </ul> */ public static void checkTwoArgumentStringLiterals(String label, NodeValue arg1, NodeValue arg2) { - /* Quote the spec: - * Compatibility of two arguments is defined as: - * The arguments are simple literals or literals typed as xsd:string - * The arguments are plain literals with identical language tags - * The first argument is a plain literal with language tag and the second argument is a simple literal or literal typed as xsd:string + /* Compatibility of two arguments: + * The arguments are both xsd:string + * The arguments are rdf:langString with identical language tags + * The arguments are rdf:dirLangString with identical language tags and text direction + * The first argument a string literal (rdf:langString, rdf:dirLangString) and the second argument is an xsd:string + * + * which simplifies to + * Both arguments are string literals + * The second argument is an xsd:string. + * The first and second arguments have the same lang and text direction. */ + // Common case + if ( arg1.isString() && arg2.isString() ) + // Includes derived datatypes of xsd:string. + return; + + // Robust checking. Node n1 = checkAndGetStringLiteral(label, arg1); Node n2 = checkAndGetStringLiteral(label, arg2); + if ( arg2.isString() ) + // args1 is some kind of string literal. + return; + // same lane, same text direction. String lang1 = n1.getLiteralLanguage(); String lang2 = n2.getLiteralLanguage(); if ( lang1 == null ) @@ -342,21 +380,13 @@ public class NodeValueOps { if ( lang2 == null ) lang2 = ""; - // Case 1 - if ( lang1.equals("") ) { - if ( lang2.equals("") ) - return; + if ( ! Lib.equalsOrNulls(lang1, lang2) ) + // Different languages. throw new ExprEvalException(label + ": Incompatible: " + arg1 + " and " + arg2); - } - // Case 2 - if ( lang1.equalsIgnoreCase(lang2) ) - return; - - // Case 3 - if ( lang2.equals("") ) - return; - - throw new ExprEvalException(label + ": Incompatible: " + arg1 + " and " + arg2); + TextDirection textDir1 = n1.getLiteralBaseDirection(); + TextDirection textDir2 = n2.getLiteralBaseDirection(); + if ( ! Lib.equalsOrNulls(textDir1, textDir2) ) + throw new ExprEvalException(label + ": Incompatible: " + arg1 + " and " + arg2); } } diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/expr/nodevalue/XSDFuncOp.java b/jena-arq/src/main/java/org/apache/jena/sparql/expr/nodevalue/XSDFuncOp.java index 6fd848817d..aef4a5ecc5 100644 --- a/jena-arq/src/main/java/org/apache/jena/sparql/expr/nodevalue/XSDFuncOp.java +++ b/jena-arq/src/main/java/org/apache/jena/sparql/expr/nodevalue/XSDFuncOp.java @@ -42,11 +42,13 @@ import javax.xml.datatype.Duration; import javax.xml.datatype.XMLGregorianCalendar; import org.apache.jena.atlas.lib.IRILib; +import org.apache.jena.atlas.lib.Lib; import org.apache.jena.atlas.lib.StrUtils; import org.apache.jena.datatypes.RDFDatatype; import org.apache.jena.datatypes.xsd.XSDDatatype; import org.apache.jena.graph.Node; import org.apache.jena.graph.NodeFactory; +import org.apache.jena.graph.TextDirection; import org.apache.jena.rdf.model.impl.Util; import org.apache.jena.sparql.ARQInternalErrorException; import org.apache.jena.sparql.SystemARQ; @@ -745,7 +747,7 @@ public class XSDFuncOp /** Build a NodeValue with lexical form, and same language and datatype as the Node argument */ private static NodeValue calcReturn(String result, Node arg) { - Node n2 = NodeFactory.createLiteral(result, arg.getLiteralLanguage(), arg.getLiteralDatatype()); + Node n2 = NodeFactory.createLiteral(result, arg.getLiteralLanguage(), arg.getLiteralBaseDirection(), arg.getLiteralDatatype()); return NodeValue.makeNode(n2); } @@ -823,57 +825,57 @@ public class XSDFuncOp } /** SPARQL CONCAT (no implicit casts to strings) */ - public static NodeValue strConcat(List<NodeValue> args) { - // Step 1 : Choose type. - // One lang tag -> that lang tag - String lang = null; - boolean mixedLang = false; - boolean xsdString = false; - boolean simpleLiteral = false; + public static NodeValue /*XSDFuncOp*/strConcat(List<NodeValue> args) { + // Is this list of argument known to result in xsd:string by being mixed or seen an xsd:string? + boolean outputSimpleString = false; + + // A candidate has been set (happens at first argument) + boolean candidateSet = false; + String concatLang = null; + TextDirection concatTextDir = null; StringBuilder sb = new StringBuilder(); for (NodeValue nv : args) { + // ExprEvalException is is not a string/langString or dirLangString. Node n = NodeValueOps.checkAndGetStringLiteral("CONCAT", nv); - String lang1 = n.getLiteralLanguage(); - if ( !lang1.equals("") ) { - if ( lang != null && !lang1.equals(lang) ) - // throw new - // ExprEvalException("CONCAT: Mixed language tags: "+args); - mixedLang = true; - lang = lang1; - } else if ( n.getLiteralDatatype() != null ) - xsdString = true; - else - simpleLiteral = true; - sb.append(n.getLiteralLexicalForm()); + if ( outputSimpleString ) + continue; + String nLang = n.getLiteralLanguage(); + TextDirection nTextDir = n.getLiteralBaseDirection(); + + // Not mixed, + if ( ! candidateSet ) { + if ( nLang.isEmpty() ) { + // No language => outcome is an xsd:string + outputSimpleString = nLang.isEmpty(); + continue; + } + // It is the first argument, then set candidate + concatLang = nLang; + concatTextDir = nTextDir; + candidateSet = true; + continue; + } + // candidateSet is true + // Possible lang/textDir + if ( ! Lib.equalsOrNulls(concatLang, nLang) || ! Lib.equalsOrNulls(concatTextDir, nTextDir) ) { + // Different => outcome is an xsd:string + outputSimpleString = true; + candidateSet = false; + } } - if ( mixedLang ) - return NodeValue.makeString(sb.toString()); + String string = sb.toString(); - // Must be all one lang. - if ( lang != null ) { - if ( !xsdString && !simpleLiteral ) - return NodeValue.makeNode(sb.toString(), lang, (String)null); - else - // Lang and one or more of xsd:string or simpleLiteral. - return NodeValue.makeString(sb.toString()); - } - - if ( simpleLiteral && xsdString ) - return NodeValue.makeString(sb.toString()); - // All xsdString - if ( xsdString ) - return NodeValue.makeNode(sb.toString(), XSDDatatype.XSDstring); - if ( simpleLiteral ) + if ( outputSimpleString ) return NodeValue.makeString(sb.toString()); - // No types - i.e. no arguments - return NodeValue.makeString(sb.toString()); + // Handles "textDir == null" + Node nOut = NodeFactory.createLiteralDirLang(string, concatLang, concatTextDir); + return NodeValue.makeNode(nOut); } - /** fn:normalizeSpace */ public static NodeValue strNormalizeSpace(NodeValue v){ String str = v.asString(); diff --git a/jena-arq/src/test/java/org/apache/jena/arq/junit/OmittedTest.java b/jena-arq/src/test/java/org/apache/jena/arq/junit/OmittedTest.java index 0dd1098c44..c21a3ff273 100644 --- a/jena-arq/src/test/java/org/apache/jena/arq/junit/OmittedTest.java +++ b/jena-arq/src/test/java/org/apache/jena/arq/junit/OmittedTest.java @@ -21,7 +21,7 @@ package org.apache.jena.arq.junit; import org.apache.jena.arq.junit.manifest.ManifestEntry; /** - * Omitted test -= no marked as "suppressed" but not run. + * Omitted test = not marked as "suppressed" but not run. */ public class OmittedTest extends SkipTest { public final boolean verbose; @@ -38,7 +38,7 @@ public class OmittedTest extends SkipTest { @Override public void runTest() { - //if ( verbose ) + if ( verbose ) System.err.println("Omitted test: " + manifestEntry.getName()); } } diff --git a/jena-arq/src/test/java/org/apache/jena/rdfs/TestDatasetGraphRDFS.java b/jena-arq/src/test/java/org/apache/jena/rdfs/TestDatasetGraphRDFS.java index 095a366c3e..6588923d2c 100644 --- a/jena-arq/src/test/java/org/apache/jena/rdfs/TestDatasetGraphRDFS.java +++ b/jena-arq/src/test/java/org/apache/jena/rdfs/TestDatasetGraphRDFS.java @@ -19,6 +19,7 @@ package org.apache.jena.rdfs; import static org.apache.jena.atlas.iterator.Iter.iter; +import static org.apache.jena.atlas.lib.Lib.equalsOrNulls; import static org.apache.jena.graph.Node.ANY; import static org.apache.jena.rdfs.LibTestRDFS.node; import static org.apache.jena.rdfs.engine.ConstRDFS.rdfType; @@ -36,7 +37,6 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.apache.jena.atlas.iterator.Iter; -import org.apache.jena.atlas.lib.Lib; import org.apache.jena.atlas.lib.ListUtils; import org.apache.jena.atlas.lib.StrUtils; import org.apache.jena.graph.Graph; @@ -136,7 +136,7 @@ public class TestDatasetGraphRDFS { } private static boolean hasNG(List<Quad> quads, Node graphName) { - return quads.stream().map(Quad::getGraph).anyMatch(gn -> Lib.equals(gn, graphName)); + return quads.stream().map(Quad::getGraph).anyMatch(gn -> equalsOrNulls(gn, graphName)); } private void testContains(Node g, Node s, Node p, Node o, boolean expected) { 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 06efb7eee7..1ef5eb3696 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,7 +37,8 @@ import org.apache.jena.sparql.expr.nodevalue.TestNodeValueSortKey; , TestNodeFunctions.class , TestExpressionsMath.class , TestFunctions.class - , TestFunctions2.class + , TestStringArgCompatibility.class + , TestSparqlKeywordFunctions.class , TestFunctionsByURI.class , TestExprTripleTerms.class , TestLeviathanFunctions.class diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestFunctions.java b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestFunctions.java index df04ffe1cf..fa873d2997 100644 --- a/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestFunctions.java +++ b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestFunctions.java @@ -19,7 +19,6 @@ package org.apache.jena.sparql.expr; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -31,15 +30,13 @@ import java.util.function.Predicate; import org.junit.jupiter.api.Test; -import org.apache.jena.datatypes.xsd.XSDDatatype; -import org.apache.jena.graph.Node; -import org.apache.jena.graph.NodeFactory; import org.apache.jena.sparql.ARQConstants; import org.apache.jena.sparql.util.ExprUtils; public class TestFunctions { // Test of fn;* are in org.apache.jena.sparql.function.library.* + // TestFunctions2 has the SPARQl keyword functions. private static final NodeValue TRUE = NodeValue.TRUE; private static final NodeValue FALSE = NodeValue.FALSE; @@ -121,150 +118,14 @@ public class TestFunctions // Timezone +11:00 can be a day behind @Test public void exprSprintf_25() { test_exprSprintf_tz_possibilites("2005-10-14T10:09:43+11:00", "10 13,2005", "10 14,2005"); } - @Test public void exprStrStart10() { test("STRSTARTS('abc', 'abcd')", FALSE); } - @Test public void exprStrStart11() { test("STRSTARTS('abc'@en, 'ab')", TRUE); } - @Test public void exprStrStart12() { test("STRSTARTS('abc'^^xsd:string, 'ab')", TRUE); } - @Test public void exprStrStart13() { test("STRSTARTS('abc'^^xsd:string, 'ab'^^xsd:string)", TRUE); } - @Test public void exprStrStart14() { test("STRSTARTS('abc', 'ab'^^xsd:string)", TRUE); } - @Test public void exprStrStart15() { test("STRSTARTS('abc'@en, 'ab'@en)", TRUE); } - - @Test public void exprStrStart16() { testEvalException("STRSTARTS('ab'@en, 'ab'@fr)"); } - @Test public void exprStrStart17() { testEvalException("STRSTARTS(123, 'ab'@fr)"); } - @Test public void exprStrStart18() { testEvalException("STRSTARTS('123'^^xsd:string, 12.3)"); } - - @Test public void exprStrBefore0() { test("STRBEFORE('abc', 'abcd')", NodeValue.nvEmptyString); } - @Test public void exprStrBefore1() { test("STRBEFORE('abc'@en, 'b')", NodeValue.makeNode("a", "en", (String)null)); } - @Test public void exprStrBefore2() { test("STRBEFORE('abc'^^xsd:string, 'c')", NodeValue.makeNode("ab", XSDDatatype.XSDstring)); } - @Test public void exprStrBefore3() { test("STRBEFORE('abc'^^xsd:string, ''^^xsd:string)", NodeValue.makeNode("", XSDDatatype.XSDstring)); } - @Test public void exprStrBefore4() { test("STRBEFORE('abc', 'ab'^^xsd:string)", NodeValue.nvEmptyString); } - @Test public void exprStrBefore5() { test("STRBEFORE('abc'@en, 'b'@en)", NodeValue.makeNode("a", "en", (String)null)); } - - @Test public void exprStrBefore6() { testEvalException("STRBEFORE('ab'@en, 'ab'@fr)"); } - @Test public void exprStrBefore7() { testEvalException("STRBEFORE(123, 'ab'@fr)"); } - @Test public void exprStrBefore8() { testEvalException("STRBEFORE('123'^^xsd:string, 12.3)"); } - // No match case - @Test public void exprStrBefore9() { test("STRBEFORE('abc'^^xsd:string, 'z')", NodeValue.nvEmptyString); } - // Empty string case - @Test public void exprStrBefore10() { test("STRBEFORE('abc'^^xsd:string, '')", NodeValue.makeNode("", XSDDatatype.XSDstring)); } - - @Test public void exprStrAfter0() { test("STRAFTER('abc', 'abcd')", NodeValue.nvEmptyString); } - @Test public void exprStrAfter1() { test("STRAFTER('abc'@en, 'b')", NodeValue.makeNode("c", "en", (String)null)); } - @Test public void exprStrAfter2() { test("STRAFTER('abc'^^xsd:string, 'a')", NodeValue.makeNode("bc", XSDDatatype.XSDstring)); } - @Test public void exprStrAfter3() { test("STRAFTER('abc'^^xsd:string, ''^^xsd:string)", NodeValue.makeNode("abc", XSDDatatype.XSDstring)); } - @Test public void exprStrAfter4() { test("STRAFTER('abc', 'bc'^^xsd:string)", NodeValue.nvEmptyString); } - @Test public void exprStrAfter5() { test("STRAFTER('abc'@en, 'b'@en)", NodeValue.makeNode("c", "en", (String)null)); } - - @Test public void exprStrAfter6() { testEvalException("STRAFTER('ab'@en, 'ab'@fr)"); } - @Test public void exprStrAfter7() { testEvalException("STRAFTER(123, 'ab'@fr)"); } - @Test public void exprStrAfter8() { testEvalException("STRAFTER('123'^^xsd:string, 12.3)"); } - // No match case - @Test public void exprStrAfter9() { test("STRAFTER('abc'^^xsd:string, 'z')", NodeValue.nvEmptyString); } - // Empty string case - @Test public void exprStrAfter10() { test("STRAFTER('abc'^^xsd:string, '')", NodeValue.makeNode("abc", XSDDatatype.XSDstring)); } - - @Test public void exprStrEnds10() { test("STRENDS('abc', 'abcd')", FALSE); } - @Test public void exprStrEnds11() { test("STRENDS('abc'@en, 'bc')", TRUE); } - @Test public void exprStrEnds12() { test("STRENDS('abc'^^xsd:string, 'c')", TRUE); } - @Test public void exprStrEnds13() { test("STRENDS('abc'^^xsd:string, 'c'^^xsd:string)", TRUE); } - @Test public void exprStrEnds14() { test("STRENDS('abc', 'ab'^^xsd:string)", FALSE); } - @Test public void exprStrEnds15() { test("STRENDS('abc'@en, 'abc'@en)", TRUE); } - - @Test public void exprStrEnds16() { testEvalException("STRENDS('ab'@en, 'ab'@fr)"); } - @Test public void exprStrEnds17() { testEvalException("STRENDS(123, 'ab'@fr)"); } - @Test public void exprStrEnds18() { testEvalException("STRENDS('123'^^xsd:string, 12.3)"); } - - @Test public void exprContains10() { test("Contains('abc', 'abcd')", FALSE); } - @Test public void exprContains11() { test("Contains('abc'@en, 'bc')", TRUE); } - @Test public void exprContains12() { test("Contains('abc'^^xsd:string, 'c')", TRUE); } - @Test public void exprContains13() { test("Contains('abc'^^xsd:string, 'c'^^xsd:string)", TRUE); } - @Test public void exprContains14() { test("Contains('abc', 'z'^^xsd:string)", FALSE); } - @Test public void exprContains15() { test("Contains('abc'@en, 'abc'@en)", TRUE); } - - @Test public void exprContains16() { testEvalException("Contains('ab'@en, 'ab'@fr)"); } - @Test public void exprContains17() { testEvalException("Contains(123, 'ab'@fr)"); } - @Test public void exprContains18() { testEvalException("STRENDS('123'^^xsd:string, 12.3)"); } - - @Test public void exprReplace01() { test("REPLACE('abc', 'b', 'Z')", NodeValue.makeString("aZc")); } - @Test public void exprReplace02() { test("REPLACE('abc', 'b.', 'Z')", NodeValue.makeString("aZ")); } - @Test public void exprReplace03() { test("REPLACE('abcbd', 'b.', 'Z')", NodeValue.makeString("aZZ")); } - - @Test public void exprReplace04() { test("REPLACE('abcbd'^^xsd:string, 'b.', 'Z')", NodeValue.makeNode("aZZ", XSDDatatype.XSDstring)); } - @Test public void exprReplace05() { test("REPLACE('abcbd'@en, 'b.', 'Z')", NodeValue.makeNode("aZZ", "en", (String)null)); } - @Test public void exprReplace06() { test("REPLACE('abcbd', 'B.', 'Z', 'i')", NodeValue.makeString("aZZ")); } - - // See JENA-740 - // ARQ provides replacement of the potentially empty string. - @Test public void exprReplace07() { test("REPLACE('abc', '.*', 'Z')", NodeValue.makeString("Z")); } - @Test public void exprReplace08() { test("REPLACE('', '.*', 'Z')", NodeValue.makeString("Z")); } - @Test public void exprReplace09() { test("REPLACE('abc', '.?', 'Z')", NodeValue.makeString("ZZZ")); } - - @Test public void exprReplace10() { test("REPLACE('abc', 'XXX', 'Z')", NodeValue.makeString("abc")); } - @Test public void exprReplace11() { test("REPLACE('', '.', 'Z')", NodeValue.makeString("")); } - @Test public void exprReplace12() { test("REPLACE('', '(a|b)?', 'Z')", NodeValue.makeString("Z")); } - - // Bad group - @Test - public void exprReplace13() { - testEvalException("REPLACE('abc', '.*', '$1')"); - } - - // Bad pattern; static (parse or build time) compilation. - @Test - public void exprReplace14() { - assertThrows(ExprException.class, - ()-> ExprUtils.parse("REPLACE('abc', '^(a){-9}', 'ABC')") - ); - } - // Better name! @Test public void localTimezone_2() { test("afn:timezone()", nv->nv.isDayTimeDuration()); } - @Test public void localDateTime_1() { test("afn:nowtz()", nv-> nv.isDateTime()); } // Test field defined. @Test public void localDateTime_2() { test("afn:nowtz()", nv-> nv.getDateTime().getTimezone() >= -14 * 60 ); } @Test public void localDateTime_3() { test("afn:nowtz() = NOW()", NodeValue.TRUE); } - @Test public void exprSameTerm1() { test("sameTerm(1,1)", TRUE); } - @Test public void exprSameTerm2() { test("sameTerm(1,1.0)", FALSE); } - @Test public void exprSameTerm3() { test("sameTerm(1,1e0)", FALSE); } - @Test public void exprSameTerm4() { test("sameTerm(<_:a>, <_:a>)", TRUE); } - @Test public void exprSameTerm5() { test("sameTerm(<x>, <x>)", TRUE); } - @Test public void exprSameTerm6() { test("sameTerm(<x>, <y>)", FALSE); } - - @Test public void exprOneOf_01() { test("57 in (xsd:integer, '123')", FALSE); } - @Test public void exprOneOf_02() { test("57 in (57)", TRUE); } - @Test public void exprOneOf_03() { test("57 in (123, 57)", TRUE); } - @Test public void exprOneOf_04() { test("57 in (57, 456)", TRUE); } - @Test public void exprOneOf_05() { test("57 in (123, 57, 456)", TRUE); } - @Test public void exprOneOf_06() { test("57 in (1,2,3)", FALSE); } - - @Test public void exprNotOneOf_01() { test("57 not in (xsd:integer, '123')", TRUE); } - @Test public void exprNotOneOf_02() { test("57 not in (57)", FALSE); } - @Test public void exprNotOneOf_03() { test("57 not in (123, 57)", FALSE); } - @Test public void exprNotOneOf_04() { test("57 not in (57, 456)", FALSE); } - @Test public void exprNotOneOf_05() { test("57 not in (123, 57, 456)", FALSE); } - @Test public void exprNotOneOf_06() { test("57 not in (1,2,3)", TRUE); } - - - static Node xyz_en = NodeFactory.createLiteralLang("xyz", "en"); - static NodeValue nv_xyz_en = NodeValue.makeNode(xyz_en); - - static Node xyz_xsd_string = NodeFactory.createLiteralDT("xyz", XSDDatatype.XSDstring); - static NodeValue nv_xyz_string = NodeValue.makeNode(xyz_xsd_string); - - - @Test public void exprStrLang1() { test("strlang('xyz', 'en')", nv_xyz_en); } - @Test - public void exprStrLang2() { assertThrows(ExprEvalException.class, ()-> test("strlang('xyz', '')", x->false) ); } - - @Test public void exprStrDatatype1() { test("strdt('123', xsd:integer)", NodeValue.makeInteger(123)); } - @Test public void exprStrDatatype2() { test("strdt('xyz', xsd:string)", nv_xyz_string); } - @Test public void exprStrDatatype3() { testEvalException("strdt('123', 'datatype')"); } - - static Node n_uri = NodeFactory.createURI("http://example/"); - static NodeValue nv_uri = NodeValue.makeNode(n_uri); - private void test(String exprStr, NodeValue result) { Expr expr = ExprUtils.parse(exprStr); NodeValue r = expr.eval(null, LibTestExpr.createTest()); 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/TestSparqlKeywordFunctions.java similarity index 53% rename from jena-arq/src/test/java/org/apache/jena/sparql/expr/TestFunctions2.java rename to jena-arq/src/test/java/org/apache/jena/sparql/expr/TestSparqlKeywordFunctions.java index 44b5ef44e1..b4b06b6362 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/TestSparqlKeywordFunctions.java @@ -21,6 +21,7 @@ package org.apache.jena.sparql.expr; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -35,7 +36,7 @@ import org.apache.jena.sparql.util.ExprUtils; import org.apache.jena.sparql.util.NodeFactoryExtra; import org.apache.jena.sys.JenaSystem; -public class TestFunctions2 +public class TestSparqlKeywordFunctions { static { JenaSystem.init(); } // Some overlap with TestFunctions except those are direct function calls and these are via SPARQL 1.1 syntax. @@ -43,16 +44,21 @@ public class TestFunctions2 static boolean warnOnBadLexicalForms = true; - @BeforeAll public static void beforeClass() - { + @BeforeAll + public static void beforeClass() { warnOnBadLexicalForms = NodeValue.VerboseWarnings; NodeValue.VerboseWarnings = false; } - @AfterAll public static void afterClass() - { + + @AfterAll + public static void afterClass() { NodeValue.VerboseWarnings = warnOnBadLexicalForms; } + private final static String kwTRUE = "true"; + private final static String kwFALSE = "false"; + private final static String kwEmptyString = "''";; + // tests for strings. strlen, substr, strucase, strlcase, contains, concat // Some overlap with NodeFunctions. // Tests for IRI(..) are in TestUpdateOperations.insert_with_iri_function_resolution* @@ -105,6 +111,7 @@ public class TestFunctions2 @Test public void round_13() { test("round('-0'^^xsd:float)", "'-0.0'^^xsd:float"); } @Test public void round_14() { test("round('-0'^^xsd:double)", "'-0.0e0'^^xsd:double"); } + // ABS @Test public void abs_01() { test("abs(1)", "1"); } @Test public void abs_02() { test("abs(1.0)", "1.0"); } @Test public void abs_03() { test("abs(1.0e0)", "1.0e0"); } @@ -132,34 +139,40 @@ public class TestFunctions2 @Test public void floor_07() { test("floor(-9.5)", "'-10.0'^^xsd:decimal"); } @Test public void floor_08() { test("floor(0)", "0"); } - // CONCAT - @Test public void concat_00() { test("concat()", "''"); } + @Test public void concat_00() { test("concat()", kwEmptyString); } @Test public void concat_01() { test("concat('a')", "'a'"); } @Test public void concat_02() { test("concat('a', 'b')", "'ab'"); } @Test public void concat_03() { test("concat('a'@en, 'b')", "'ab'"); } @Test public void concat_04() { test("concat('a'@en, 'b'@en)", "'ab'@en"); } - //@Test public void concat_05() { test("concat('a'^^xsd:string, 'b')", "'ab'^^xsd:string"); } - @Test public void concat_05() { test("concat('a'^^xsd:string, 'b')", "'ab'"); } - @Test public void concat_06() { test("concat('a'^^xsd:string, 'b'^^xsd:string)", "'ab'^^xsd:string"); } + @Test public void concat_05() { test("concat('a'@en, 'b'@fr)", "'ab'"); } + @Test public void concat_06() { test("concat('a'^^xsd:string, 'b')", "'ab'"); } @Test public void concat_07() { test("concat('a'^^xsd:string, 'b'^^xsd:string)", "'ab'^^xsd:string"); } - //@Test public void concat_08() { test("concat('a', 'b'^^xsd:string)", "'ab'^^xsd:string"); } - @Test public void concat_08() { test("concat('a', 'b'^^xsd:string)", "'ab'"); } - @Test public void concat_09() { test("concat('a'@en, 'b'^^xsd:string)", "'ab'"); } - @Test public void concat_10() { test("concat('a'^^xsd:string, 'b'@en)", "'ab'"); } - @Test public void concat_11() { test("concat()", "''"); } - - @Test - public void concat_90() { assertThrows(ExprEvalException.class, ()-> test("concat(1)", "1") ); } - - @Test - public void concat_91() { test("concat('a'@en, 'b'@fr)", "'ab'"); } + @Test public void concat_08() { test("concat('a'^^xsd:string, 'b'^^xsd:string)", "'ab'^^xsd:string"); } + @Test public void concat_09() { test("concat('a', 'b'^^xsd:string)", "'ab'"); } + @Test public void concat_10() { test("concat('a'@en, 'b'^^xsd:string)", "'ab'"); } + @Test public void concat_11() { test("concat('a'^^xsd:string, 'b'@en)", "'ab'"); } + @Test public void concat_12() { test("concat()", kwEmptyString); } + + @Test public void concat_20() { test("concat('a', 'b', 'c')", "'abc'"); } + @Test public void concat_21() { test("concat('a'@en, 'b'@en, 'c')", "'abc'"); } + @Test public void concat_22() { test("concat('a', 'b'@en, 'c')", "'abc'"); } + @Test public void concat_23() { test("concat('a', 'b', 'c'@en)", "'abc'"); } + + @Test public void concat_30() { test("concat('a'@en--ltr, 'b'@en--ltr)", "'ab'@en--ltr"); } + @Test public void concat_31() { test("concat('a'@en--ltr, 'b'@en--rtl)", "'ab'"); } + @Test public void concat_32() { test("concat('a'@en--ltr, 'b'@fr--ltr)", "'ab'"); } + @Test public void concat_33() { test("concat('a'@en--ltr, 'b'@fr--ltr)", "'ab'"); } + @Test public void concat_34() { test("concat('a'@en--ltr, 'b')", "'ab'"); } + @Test public void concat_35() { test("concat('a'@en--ltr, 'b'@en)", "'ab'"); } + @Test public void concat_36() { test("concat('a', 'b'@en--ltr)", "'ab'"); } + @Test public void concat_37() { test("concat('a'@en, 'b'@en--ltr)", "'ab'"); } // SUBSTR @Test public void substr_01() { test("substr('abc',1)", "'abc'"); } @Test public void substr_02() { test("substr('abc',2)", "'bc'"); } @Test public void substr_03() { test("substr('abc',2,1)", "'b'"); } - @Test public void substr_04() { test("substr('abc',2,0)", "''"); } + @Test public void substr_04() { test("substr('abc',2,0)", kwEmptyString); } @Test public void substr_05() { test("substr('12345',0,3)", "'12'"); } @Test public void substr_06() { test("substr('12345',-1,3)", "'1'"); } @@ -168,18 +181,15 @@ public class TestFunctions2 @Test public void substr_11() { test("substr('metadata', 4, 3)", "'ada'"); } @Test public void substr_12() { test("substr('12345', 1.5, 2.6)", "'234'"); } @Test public void substr_13() { test("substr('12345', 0, 3)", "'12'"); } - @Test public void substr_14() { test("substr('12345', 5, -3)", "''"); } + @Test public void substr_14() { test("substr('12345', 5, -3)", kwEmptyString); } @Test public void substr_15() { test("substr('12345', -3, 5)", "'1'"); } - @Test public void substr_16() { test("substr('12345', 0/0E0, 3)", "''"); } - @Test public void substr_17() { test("substr('12345', 1, 0/0E0)", "''"); } - @Test public void substr_18() { test("substr('', 1, 3)", "''"); } + @Test public void substr_16() { test("substr('12345', 0/0E0, 3)", kwEmptyString); } + @Test public void substr_17() { test("substr('12345', 1, 0/0E0)", kwEmptyString); } + @Test public void substr_18() { test("substr('', 1, 3)", kwEmptyString); } - @Test - public void substr_20() { assertThrows(ExprEvalException.class, ()-> test("substr(1, 1, 3)", "''") ); } - @Test - public void substr_21() { assertThrows(ExprEvalException.class, ()-> test("substr('', 'one', 3)", "''") ); } - @Test - public void substr_22() { assertThrows(ExprEvalException.class, ()-> test("substr('', 1, 'three')", "''") ); } + @Test public void substr_20() { testEvalException("substr(1, 1, 3)"); } + @Test public void substr_21() { testEvalException("substr('', 'one', 3)"); } + @Test public void substr_22() { testEvalException("substr('', 1, 'three')"); } // Codepoint outside UTF-16. // These are U+0001F46A 👪 - FAMILY @@ -192,7 +202,7 @@ public class TestFunctions2 @Test public void substr_30() { test("substr('\\uD83D\\uDC6A', 1)", "'\\uD83D\\uDC6A'"); } // Same using Java string escapes. @Test public void substr_30b() { test("substr('\uD83D\uDC6A', 1)", "'\uD83D\uDC6A'"); } - @Test public void substr_31() { test("substr('\\uD83D\\uDC6A', 2)", "''"); } + @Test public void substr_31() { test("substr('\\uD83D\\uDC6A', 2)", kwEmptyString); } @Test public void substr_32() { test("substr('ABC\\uD83D\\uDC6ADEF', 4, 1)", "'\\uD83D\\uDC6A'"); } @Test public void substr_33() { test("substr('\\uD83D\\uDC6A!', -1, 3)", "'\\uD83D\\uDC6A'"); } @@ -206,13 +216,13 @@ public class TestFunctions2 @Test public void ucase_01() { test("ucase('abc')", "'ABC'"); } @Test public void ucase_02() { test("ucase('ABC')", "'ABC'"); } @Test public void ucase_03() { test("ucase('Ab 123 Cd')", "'AB 123 CD'"); } - @Test public void ucase_04() { test("ucase('')", "''"); } + @Test public void ucase_04() { test("ucase('')", kwEmptyString); } // LCASE @Test public void lcase_01() { test("lcase('abc')", "'abc'"); } @Test public void lcase_02() { test("lcase('ABC')", "'abc'"); } @Test public void lcase_03() { test("lcase('Ab 123 Cd')", "'ab 123 cd'"); } - @Test public void lcase_04() { test("lcase('')", "''"); } + @Test public void lcase_04() { test("lcase('')", kwEmptyString); } // ENCODE_FOR_URI @Test public void encodeURI_01() { test("encode_for_uri('a:b cd/~')", "'a%3Ab%20cd%2F~'"); } @@ -222,92 +232,210 @@ public class TestFunctions2 @Test public void encodeURI_05() { test("encode_for_uri('abc'@en)", "'abc'"); } @Test - public void encodeURI_09() { assertThrows(ExprEvalException.class, ()-> test("encode_for_uri(1234)", "'1234'") ); } - - /* Compatibility rules - # pairs of simple literals, - # pairs of xsd:string typed literals - # pairs of plain literals with identical language tags - # pairs of an xsd:string typed literal (arg1 or arg2) and a simple literal (arg2 or arg1) - # pairs of a plain literal with language tag (arg1) and a simple literal (arg2) - # pairs of a plain literal with language tag (arg1) and an xsd:string typed literal (arg2) - */ - - // CONTAINS - @Test public void contains_01() { test("contains('abc', 'a')", "true"); } - @Test public void contains_02() { test("contains('abc', 'b')", "true"); } - @Test public void contains_03() { test("contains('ABC', 'a')", "false"); } - @Test public void contains_04() { test("contains('abc', '')", "true"); } - @Test public void contains_05() { test("contains('', '')", "true"); } - @Test public void contains_06() { test("contains('', 'a')", "false"); } - @Test public void contains_07() { test("contains('12345', '34')", "true"); } - @Test public void contains_08() { test("contains('12345', '123456')", "false"); } - - @Test public void contains_10() { test("contains('abc', 'a'^^xsd:string)", "true"); } - @Test public void contains_11() { assertThrows(ExprEvalException.class, ()-> test("contains('abc', 'a'@en)", "true") ); } - - @Test public void contains_12() { test("contains('abc'@en, 'a')", "true"); } - @Test public void contains_13() { test("contains('abc'@en, 'a'^^xsd:string)", "true"); } - @Test public void contains_14() { test("contains('abc'@en, 'a'@en)", "true"); } - @Test public void contains_15() { assertThrows(ExprEvalException.class, ()-> test("contains('abc'@en, 'a'@fr)", "true") ); } - - @Test public void contains_16() { test("contains('abc'^^xsd:string, 'a')", "true"); } - - @Test public void contains_17() { assertThrows(ExprEvalException.class, ()-> test("contains('abc'^^xsd:string, 'a'@en)", "true") ); } - @Test public void contains_18() { test("contains('abc'^^xsd:string, 'a'^^xsd:string)", "true"); } - - @Test public void contains_20() { assertThrows(ExprEvalException.class, ()-> test("contains(1816, 'a'^^xsd:string)", "true") ); } - @Test public void contains_21() { assertThrows(ExprEvalException.class, ()-> test("contains('abc', 1066)", "true") ); } - - @Test public void strstarts_01() { test("strstarts('abc', 'a')", "true"); } - @Test public void strstarts_02() { test("strstarts('abc', 'b')", "false"); } - @Test public void strstarts_03() { test("strstarts('ABC', 'a')", "false"); } - @Test public void strstarts_04() { test("strstarts('abc', '')", "true"); } - @Test public void strstarts_05() { test("strstarts('', '')", "true"); } - @Test public void strstarts_06() { test("strstarts('', 'a')", "false"); } - - @Test public void strstarts_10() { test("strstarts('abc', 'a'^^xsd:string)", "true"); } - @Test public void strstarts_11() { assertThrows(ExprEvalException.class, ()-> test("strstarts('abc', 'a'@en)", "true") ); } - - @Test public void strstarts_12() { test("strstarts('abc'@en, 'a')", "true"); } - @Test public void strstarts_13() { test("strstarts('abc'@en, 'a'^^xsd:string)", "true"); } - @Test public void strstarts_14() { test("strstarts('abc'@en, 'a'@en)", "true"); } - @Test - public void strstarts_15() { assertThrows(ExprEvalException.class, ()-> test("strstarts('abc'@en, 'a'@fr)", "true") ); } - - @Test public void strstarts_16() { test("strstarts('abc'^^xsd:string, 'a')", "true"); } + public void encodeURI_09() { testEvalException("encode_for_uri(1234)"); } - @Test - public void strstarts_17() { assertThrows(ExprEvalException.class, ()-> test("strstarts('abc'^^xsd:string, 'a'@en)", "true") ); } - @Test public void strstarts_18() { test("strstarts('abc'^^xsd:string, 'a'^^xsd:string)", "true"); } + /* Compatibility rules */ - @Test public void strstarts_20() { assertThrows(ExprEvalException.class, ()-> test("strstarts(1816, 'a'^^xsd:string)", "true") ); } - @Test public void strstarts_21() { assertThrows(ExprEvalException.class, ()-> test("strstarts('abc', 1066)", "true") ); } + // CONTAINS + @Test public void contains_01() { test("contains('abc', 'a')", kwTRUE); } + @Test public void contains_02() { test("contains('abc', 'b')", kwTRUE); } + @Test public void contains_03() { test("contains('ABC', 'a')", kwFALSE); } + @Test public void contains_04() { test("contains('abc', '')", kwTRUE); } + @Test public void contains_05() { test("contains('', '')", kwTRUE); } + @Test public void contains_06() { test("contains('', 'a')", kwFALSE); } + @Test public void contains_07() { test("contains('12345', '34')", kwTRUE); } + @Test public void contains_08() { test("contains('12345', '123456')", kwFALSE); } + + @Test public void contains_10() { test("contains('abc', 'abcd')", kwFALSE); } + @Test public void contains_11() { test("contains('abc'@en, 'bc')", kwTRUE); } + @Test public void contains_12() { test("contains('abc'^^xsd:string, 'c')", kwTRUE); } + @Test public void contains_13() { test("contains('abc'^^xsd:string, 'c'^^xsd:string)", kwTRUE); } + @Test public void contains_14() { test("contains('abc', 'z'^^xsd:string)", kwFALSE); } + @Test public void contains_15() { test("contains('abc'@en, 'abc'@en)", kwTRUE); } + + @Test public void contains_16() { testEvalException("contains('ab'@en, 'ab'@fr)"); } + @Test public void contains_17() { testEvalException("contains(123, 'ab'@fr)"); } + + @Test public void contains_20() { test("contains('abc', 'a'^^xsd:string)", kwTRUE); } + @Test public void contains_21() { testEvalException("contains('abc', 'a'@en)"); } + + @Test public void contains_22() { test("contains('abc'@en, 'a')", kwTRUE); } + @Test public void contains_23() { test("contains('abc'@en, 'a'^^xsd:string)", kwTRUE); } + @Test public void contains_24() { test("contains('abc'@en, 'a'@en)", kwTRUE); } + @Test public void contains_25() { testEvalException("contains('abc'@en, 'a'@fr)"); } + + @Test public void contains_26() { test("contains('abc'^^xsd:string, 'a')", kwTRUE); } + + @Test public void contains_27() { testEvalException("contains('abc'^^xsd:string, 'a'@en)"); } + @Test public void contains_28() { test("contains('abc'^^xsd:string, 'a'^^xsd:string)", kwTRUE); } + + @Test public void contains_30() { testEvalException("contains(1816, 'a'^^xsd:string)"); } + @Test public void contains_31() { testEvalException("contains('abc', 1066)"); } + + @Test public void contains_40() { test("contains('abc'@en--ltr, 'a')", kwTRUE); } + @Test public void contains_41() { testEvalException("contains('abc'@en--ltr, 'a'@en)"); } + @Test public void contains_42() { testEvalException("contains('abc'@en, 'a'@en--ltr)"); } + @Test public void contains_43() { testEvalException("contains('abc', 'a'@en--ltr)"); } + + // STRSTARTS + @Test public void strstarts_01() { test("strstarts('abc', 'a')", kwTRUE); } + @Test public void strstarts_02() { test("strstarts('abc', 'b')", kwFALSE); } + @Test public void strstarts_03() { test("strstarts('ABC', 'a')", kwFALSE); } + @Test public void strstarts_04() { test("strstarts('abc', '')", kwTRUE); } + @Test public void strstarts_05() { test("strstarts('', '')", kwTRUE); } + @Test public void strstarts_06() { test("strstarts('', 'a')", kwFALSE); } + @Test public void strstarts_07() { test("STRSTARTS('abc', 'abcd')", kwFALSE); } + + @Test public void strstarts_10() { test("strstarts('abc', 'a'^^xsd:string)", kwTRUE); } + @Test public void strstarts_11() { testEvalException("strstarts('abc', 'a'@en)"); } + @Test public void strstarts_12() { test("strstarts('abc'@en, 'a')", kwTRUE); } + @Test public void strstarts_13() { test("strstarts('abc'@en, 'a'^^xsd:string)", kwTRUE); } + @Test public void strstarts_14() { test("strstarts('abc'@en, 'a'@en)", kwTRUE); } + @Test public void strstarts_15() { testEvalException("strstarts('abc'@en, 'a'@fr)"); } + + @Test public void strstarts_16() { test("strstarts('abc'^^xsd:string, 'a')", kwTRUE); } + @Test public void strstarts_17() { testEvalException("strstarts('abc'^^xsd:string, 'a'@en)"); } + @Test public void strstarts_18() { test("strstarts('abc'^^xsd:string, 'a'^^xsd:string)", kwTRUE); } + + @Test public void strstarts_20() { testEvalException("strstarts(1816, 'a'^^xsd:string)"); } + @Test public void strstarts_21() { testEvalException("strstarts('abc', 1066)"); } + + @Test public void strstarts_22() { testEvalException("STRSTARTS('ab'@en, 'ab'@fr)"); } + @Test public void strstarts_23() { testEvalException("STRSTARTS(123, 'ab'@fr)"); } + @Test public void strstarts_24() { testEvalException("STRSTARTS('123'^^xsd:string, 12.3)"); } + + @Test public void strstarts_30() { test("STRSTARTS('ab'@en--ltr, 'z')", kwFALSE); } + @Test public void strstarts_31() { test("STRSTARTS('ab'@en--ltr, 'z'@en--ltr)", kwFALSE); } + @Test public void strstarts_32() { testEvalException("STRSTARTS('ab'@en, 'z'@en--rtl)"); } // STRENDS - @Test public void strends_01() { test("strends('abc', 'c')", "true"); } - @Test public void strends_02() { test("strends('abc', 'b')", "false"); } - @Test public void strends_03() { test("strends('ABC', 'c')", "false"); } - @Test public void strends_04() { test("strends('abc', '')", "true"); } - @Test public void strends_05() { test("strends('', '')", "true"); } - @Test public void strends_06() { test("strends('', 'a')", "false"); } - - @Test public void strends_10() { test("strends('abc', 'c'^^xsd:string)", "true"); } - @Test public void strends11() { assertThrows(ExprEvalException.class, ()-> test("strends('abc', 'c'@en)", "true") ); } - - @Test public void strends_12() { test("strends('abc'@en, 'c')", "true"); } - @Test public void strends_13() { test("strends('abc'@en, 'c'^^xsd:string)", "true"); } - @Test public void strends_14() { test("strends('abc'@en, 'c'@en)", "true"); } - @Test - public void strends_15() { assertThrows(ExprEvalException.class, ()-> test("strends('abc'@en, 'c'@fr)", "true") ); } - - @Test public void strends_16() { test("strends('abc'^^xsd:string, 'bc')", "true"); } - @Test - public void strends_17() { assertThrows(ExprEvalException.class, ()-> test("strends('abc'^^xsd:string, 'a'@en)", "true") ); } - @Test public void strends_18() { test("strends('abc'^^xsd:string, 'abc'^^xsd:string)", "true"); } - - @Test public void strends_20() { assertThrows(ExprEvalException.class, ()-> test("strends(1816, '6'^^xsd:string)", "true") ); } - @Test public void strends_21() { assertThrows(ExprEvalException.class, ()-> test("strends('abc', 1066)", "true") ); } + @Test public void strends_01() { test("strends('abc', 'c')", kwTRUE); } + @Test public void strends_02() { test("strends('abc', 'b')", kwFALSE); } + @Test public void strends_03() { test("strends('ABC', 'c')", kwFALSE); } + @Test public void strends_04() { test("strends('abc', '')", kwTRUE); } + @Test public void strends_05() { test("strends('', '')", kwTRUE); } + @Test public void strends_06() { test("strends('', 'a')", kwFALSE); } + + @Test public void strends_10() { test("STRENDS('abc', 'abcd')", kwFALSE); } + @Test public void strends_11() { test("STRENDS('abc'@en, 'bc')", kwTRUE); } + @Test public void strends_12() { test("STRENDS('abc'^^xsd:string, 'c')", kwTRUE); } + @Test public void strends_13() { test("STRENDS('abc'^^xsd:string, 'c'^^xsd:string)", kwTRUE); } + @Test public void strends_14() { test("STRENDS('abc', 'ab'^^xsd:string)", kwFALSE); } + @Test public void strends_15() { test("STRENDS('abc'@en, 'abc'@en)", kwTRUE); } + + @Test public void strends_16() { testEvalException("STRENDS('ab'@en, 'ab'@fr)"); } + @Test public void strends_17() { testEvalException("STRENDS(123, 'ab'@fr)"); } + @Test public void strends_18() { testEvalException("STRENDS('123'^^xsd:string, 12.3)"); } + + @Test public void strends_20() { test("strends('abc', 'c'^^xsd:string)", kwTRUE); } + @Test public void strends_21() { testEvalException("strends('abc', 'c'@en)"); } + + @Test public void strends_22() { testEvalException("STRSTARTS('ab'@en, 'ab'@fr)"); } + @Test public void strends_23() { testEvalException("STRSTARTS(123, 'ab'@fr)"); } + @Test public void strends_24() { testEvalException("STRSTARTS('123'^^xsd:string, 12.3)"); } + + @Test public void strends_30() { test("STRENDS('ab'@en--ltr, 'z')", kwFALSE); } + @Test public void strends_31() { test("STRENDS('ab'@en--ltr, 'z'@en--ltr)", kwFALSE); } + @Test public void strends_32() { testEvalException("STRENDS('ab'@en, 'z'@en--rtl)"); } + + // STRBEFORE + @Test public void strbefore_01() { test("STRBEFORE('abc', 'abcd')", kwEmptyString); } + @Test public void strbefore_02() { test("STRBEFORE('abc'@en, 'b')", "'a'@en"); } + @Test public void strbefore_03() { test("STRBEFORE('abc'^^xsd:string, 'c')", "'ab'^^xsd:string"); } + @Test public void strbefore_04() { test("STRBEFORE('abc'^^xsd:string, ''^^xsd:string)", "''^^xsd:string"); } + @Test public void strbefore_05() { test("STRBEFORE('abc', 'ab'^^xsd:string)", kwEmptyString); } + @Test public void strbefore_06() { test("STRBEFORE('abc'@en, 'b'@en)", "'a'@en"); } + + @Test public void strbefore_07() { testEvalException("STRBEFORE('ab'@en, 'ab'@fr)"); } + @Test public void strbefore_08() { testEvalException("STRBEFORE(123, 'ab'@fr)"); } + @Test public void strbefore_09() { testEvalException("STRBEFORE('123'^^xsd:string, 12.3)"); } + // No match case + @Test public void strbefore_10() { test("STRBEFORE('abc'^^xsd:string, 'z')", kwEmptyString); } + // Empty string case + @Test public void strbefore_11() { test("STRBEFORE('abc'^^xsd:string, '')", kwEmptyString); } + @Test public void strbefore_12() { testEvalException("STRBEFORE('abc', ''@en)"); } + + @Test public void strbefore_30() { test("STRBEFORE('abc'@en--ltr, 'a')", "''@en--ltr"); } + @Test public void strbefore_31() { test("STRBEFORE('abc'@en--ltr, 'a'@en--ltr)", "''@en--ltr"); } + @Test public void strbefore_32() { testEvalException("STRBEFORE('abc'@en--ltr, 'a'@en)"); } + + // STRAFTER + @Test public void strafter_01() { test("STRAFTER('abc', 'abcd')", kwEmptyString); } + @Test public void strafter_02() { test("STRAFTER('abc'@en, 'b')", "'c'@en"); } + @Test public void strafter_03() { test("STRAFTER('abc'^^xsd:string, 'a')", "'bc'^^xsd:string"); } + @Test public void strafter_04() { test("STRAFTER('abc'^^xsd:string, ''^^xsd:string)", "'abc'"); } + @Test public void strafter_05() { test("STRAFTER('abc', 'bc'^^xsd:string)", kwEmptyString); } + @Test public void strafter_06() { test("STRAFTER('abc'@en, 'b'@en)", "'c'@en"); } + + @Test public void strafter_07() { testEvalException("STRAFTER('ab'@en, 'ab'@fr)"); } + @Test public void strafter_08() { testEvalException("STRAFTER(123, 'ab'@fr)"); } + @Test public void strafter_09() { testEvalException("STRAFTER('123'^^xsd:string, 12.3)"); } + // No match case + @Test public void strafter_10() { test("STRAFTER('abc'^^xsd:string, 'z')", kwEmptyString); } + // Empty string case + @Test public void strafter_11() { test("STRAFTER('abc'^^xsd:string, '')", "'abc'"); } + @Test public void strafter_12() { testEvalException("STRAFTER('abc', ''@en)"); } + + @Test public void strafter_30() { test("STRAFTER('abc'@en--ltr, '')", "'abc'@en--ltr"); } + @Test public void strafter_31() { test("STRAFTER('abc'@en--ltr, 'a'@en--ltr)", "'bc'@en--ltr"); } + @Test public void strafter_32() { testEvalException("STRAFTER('abc'@en--ltr, 'a'@en)"); } + @Test public void strafter_33() { testEvalException("STRAFTER('abc'@en, 'a'@en--ltr)"); } + + // STRREPLACE + @Test public void replace01() { test("REPLACE('abc', 'b', 'Z')", "'aZc'"); } + @Test public void replace02() { test("REPLACE('abc', 'b.', 'Z')", "'aZ'"); } + @Test public void replace03() { test("REPLACE('abcbd', 'b.', 'Z')", "'aZZ'"); } + + @Test public void replace04() { test("REPLACE('abcbd'^^xsd:string, 'b.', 'Z')", "'aZZ'"); } + @Test public void replace05() { test("REPLACE('abcbd'@en, 'b.', 'Z')", "'aZZ'@en"); } + @Test public void replace06() { test("REPLACE('abcbd', 'B.', 'Z', 'i')", "'aZZ'"); } + + // See JENA-740 + // ARQ provides replacement of the potentially empty string. + @Test public void replace07() { test("REPLACE('abc', '.*', 'Z')", "'Z'"); } + @Test public void replace08() { test("REPLACE('', '.*', 'Z')", "'Z'"); } + @Test public void replace09() { test("REPLACE('abc', '.?', 'Z')", "'ZZZ'"); } + + @Test public void replace10() { test("REPLACE('abc', 'XXX', 'Z')", "'abc'"); } + @Test public void replace11() { test("REPLACE('', '.', 'Z')", kwEmptyString); } + @Test public void replace12() { test("REPLACE('', '(a|b)?', 'Z')", "'Z'"); } + @Test public void replace13() { test("REPLACE('abc'@en, 'XXX', 'Z')", "'abc'@en"); } + + // Bad group + @Test public void replace20() { testEvalException("REPLACE('abc', '.*', '$1')"); } + // Bad pattern; static (parse or build time) compilation. + @Test public void replace21() { assertThrows(ExprException.class, ()-> ExprUtils.parse("REPLACE('abc', '^(a){-9}', 'ABC')")); } + + @Test public void replace30() { test("REPLACE('b'@en--ltr, '(a|b)?', 'Z')", "'Z'@en--ltr"); } + @Test public void replace31() { test("REPLACE('b'@en--ltr, '(a|b)?', 'Z'@en--ltr)", "'Z'@en--ltr"); } + + @Test public void sameTerm1() { test("sameTerm(1,1)", kwTRUE); } + @Test public void sameTerm2() { test("sameTerm(1,1.0)", kwFALSE); } + @Test public void sameTerm3() { test("sameTerm(1,1e0)", kwFALSE); } + @Test public void sameTerm4() { test("sameTerm(<_:a>, <_:a>)", kwTRUE); } + @Test public void sameTerm5() { test("sameTerm(<x>, <x>)", kwTRUE); } + @Test public void sameTerm6() { test("sameTerm(<x>, <y>)", kwFALSE); } + + @Test public void OneOf_01() { test("57 in (xsd:integer, '123')", kwFALSE); } + @Test public void OneOf_02() { test("57 in (57)", kwTRUE); } + @Test public void OneOf_03() { test("57 in (123, 57)", kwTRUE); } + @Test public void OneOf_04() { test("57 in (57, 456)", kwTRUE); } + @Test public void OneOf_05() { test("57 in (123, 57, 456)", kwTRUE); } + @Test public void OneOf_06() { test("57 in (1,2,3)", kwFALSE); } + + @Test public void NotOneOf_01() { test("57 not in (xsd:integer, '123')", kwTRUE); } + @Test public void NotOneOf_02() { test("57 not in (57)", kwFALSE); } + @Test public void NotOneOf_03() { test("57 not in (123, 57)", kwFALSE); } + @Test public void NotOneOf_04() { test("57 not in (57, 456)", kwFALSE); } + @Test public void NotOneOf_05() { test("57 not in (123, 57, 456)", kwFALSE); } + @Test public void NotOneOf_06() { test("57 not in (1,2,3)", kwTRUE); } + + @Test public void StrLang1() { test("strlang('xyz', 'en')", "'xyz'@en"); } + @Test public void StrLang2() { testEvalException("strlang('xyz', '')"); } + + @Test public void StrDatatype1() { test("strdt('123', xsd:integer)", "123"); } + @Test public void StrDatatype2() { test("strdt('xyz', xsd:string)", "'xyz'"); } + @Test public void StrDatatype3() { testEvalException("strdt('123', 'datatype')"); } // YEAR @Test public void year_01() { test("year('2010-12-24T16:24:01.123'^^xsd:dateTime)", "2010"); } @@ -315,106 +443,82 @@ public class TestFunctions2 @Test public void year_03() { test("year('2010'^^xsd:gYear)", "2010"); } @Test public void year_04() { test("year('2010-12'^^xsd:gYearMonth)", "2010"); } - @Test - public void year_05() { assertThrows(ExprEvalException.class, ()-> test("year('--12'^^xsd:gMonth)", "2010") ); } - @Test - public void year_06() { assertThrows(ExprEvalException.class, ()-> test("year('--12-24'^^xsd:gMonthDay)", "2010") ); } - @Test - public void year_07() { assertThrows(ExprEvalException.class, ()-> test("year('---24'^^xsd:gDay)", "2010") ); } + @Test public void year_05() { testEvalException("year('--12'^^xsd:gMonth)"); } + @Test public void year_06() { testEvalException("year('--12-24'^^xsd:gMonthDay)"); } + @Test public void year_07() { testEvalException("year('---24'^^xsd:gDay)"); } @Test public void year_11() { test("year('2010-12-24T16:24:01.123Z'^^xsd:dateTime)", "2010"); } @Test public void year_12() { test("year('2010-12-24Z'^^xsd:date)", "2010"); } @Test public void year_13() { test("year('2010Z'^^xsd:gYear)", "2010"); } @Test public void year_14() { test("year('2010-12Z'^^xsd:gYearMonth)", "2010"); } - @Test - public void year_15() { assertThrows(ExprEvalException.class, ()-> test("year('--12Z'^^xsd:gMonth)", "2010") ); } - @Test - public void year_16() { assertThrows(ExprEvalException.class, ()-> test("year('--12-24Z'^^xsd:gMonthDay)", "2010") ); } - @Test - public void year_17() { assertThrows(ExprEvalException.class, ()-> test("year('---24Z'^^xsd:gDay)", "2010") ); } + @Test public void year_15() { testEvalException("year('--12Z'^^xsd:gMonth)"); } + @Test public void year_16() { testEvalException("year('--12-24Z'^^xsd:gMonthDay)"); } + @Test public void year_17() { testEvalException("year('---24Z'^^xsd:gDay)"); } @Test public void year_21() { test("year('2010-12-24T16:24:01.123-08:00'^^xsd:dateTime)", "2010"); } @Test public void year_22() { test("year('2010-12-24-08:00'^^xsd:date)", "2010"); } @Test public void year_23() { test("year('2010-08:00'^^xsd:gYear)", "2010"); } @Test public void year_24() { test("year('2010-12-08:00'^^xsd:gYearMonth)", "2010"); } - @Test - public void year_25() { assertThrows(ExprEvalException.class, ()-> test("year('--12-08:00'^^xsd:gMonth)", "2010") ); } - @Test - public void year_26() { assertThrows(ExprEvalException.class, ()-> test("year('--12-24-08:00'^^xsd:gMonthDay)", "2010") ); } - @Test - public void year_27() { assertThrows(ExprEvalException.class, ()-> test("year('---24-08:00'^^xsd:gDay)", "2010") ); } + @Test public void year_25() { testEvalException("year('--12-08:00'^^xsd:gMonth)"); } + @Test public void year_26() { testEvalException("year('--12-24-08:00'^^xsd:gMonthDay)"); } + @Test public void year_27() { testEvalException("year('---24-08:00'^^xsd:gDay)"); } @Test public void year_dur_01() { test("year('P1Y2M3DT4H5M6S'^^xsd:duration)", "1"); } - // MONTH @Test public void month_01() { test("month('2010-12-24T16:24:01.123'^^xsd:dateTime)", "12"); } @Test public void month_02() { test("month('2010-12-24'^^xsd:date)", "12"); } - @Test - public void month_03() { assertThrows(ExprEvalException.class, ()-> test("month('2010'^^xsd:gYear)", "12") ); } + @Test public void month_03() { testEvalException("month('2010'^^xsd:gYear)"); } @Test public void month_04() { test("month('2010-12'^^xsd:gYearMonth)", "12"); } @Test public void month_05() { test("month('--12'^^xsd:gMonth)", "12"); } @Test public void month_06() { test("month('--12-24'^^xsd:gMonthDay)", "12"); } - @Test - public void month_07() { assertThrows(ExprEvalException.class, ()-> test("month('---24'^^xsd:gDay)", "12") ); } + @Test public void month_07() { testEvalException("month('---24'^^xsd:gDay)"); } @Test public void month_11() { test("month('2010-12-24T16:24:01.123Z'^^xsd:dateTime)", "12"); } @Test public void month_12() { test("month('2010-12-24Z'^^xsd:date)", "12"); } - @Test - public void month_13() { assertThrows(ExprEvalException.class, ()-> test("month('2010Z'^^xsd:gYear)", "12") ); } + @Test public void month_13() { testEvalException("month('2010Z'^^xsd:gYear)"); } @Test public void month_14() { test("month('2010-12Z'^^xsd:gYearMonth)", "12"); } @Test public void month_15() { test("month('--12Z'^^xsd:gMonth)", "12"); } @Test public void month_16() { test("month('--12-24Z'^^xsd:gMonthDay)", "12"); } - @Test - public void month_17() { assertThrows(ExprEvalException.class, ()-> test("month('---24Z'^^xsd:gDay)", "12") ); } + @Test public void month_17() { testEvalException("month('---24Z'^^xsd:gDay)"); } @Test public void month_21() { test("month('2010-12-24T16:24:01.123-08:00'^^xsd:dateTime)", "12"); } @Test public void month_22() { test("month('2010-12-24-08:00'^^xsd:date)", "12"); } - @Test - public void month_23() { assertThrows(ExprEvalException.class, ()-> test("month('2010-08:00'^^xsd:gYear)", "12") ); } + @Test public void month_23() { testEvalException("month('2010-08:00'^^xsd:gYear)"); } @Test public void month_24() { test("month('2010-12-08:00'^^xsd:gYearMonth)", "12"); } @Test public void month_25() { test("month('--12-08:00'^^xsd:gMonth)", "12"); } - public void month_26() { test("month('--12-24-08:00'^^xsd:gMonthDay)", "12"); } - @Test - public void month_27() { assertThrows(ExprEvalException.class, ()-> test("month('---24-08:00'^^xsd:gDay)", "12") ); } + @Test public void month_26() { test("month('--12-24-08:00'^^xsd:gMonthDay)", "12"); } + @Test public void month_27() { testEvalException("month('---24-08:00'^^xsd:gDay)"); } @Test public void month_dur_01() { test("month('P1Y2M3DT4H5M6S'^^xsd:duration)", "2"); } // DAY @Test public void day_01() { test("day('2010-12-24T16:24:01.123'^^xsd:dateTime)", "24"); } @Test public void day_02() { test("day('2010-12-24'^^xsd:date)", "24"); } - @Test - public void day_03() { assertThrows(ExprEvalException.class, ()-> test("day('2010'^^xsd:gYear)", "24") ); } - @Test - public void day_04() { assertThrows(ExprEvalException.class, ()-> test("day('2010-12'^^xsd:gYearMonth)", "24") ); } + @Test public void day_03() { testEvalException("day('2010'^^xsd:gYear)"); } + @Test public void day_04() { testEvalException("day('2010-12'^^xsd:gYearMonth)"); } - @Test - public void day_05() { assertThrows(ExprEvalException.class, ()-> test("day('--12'^^xsd:gMonth)", "24") ); } + @Test public void day_05() { testEvalException("day('--12'^^xsd:gMonth)"); } @Test public void day_06() { test("day('--12-24'^^xsd:gMonthDay)", "24"); } @Test public void day_07() { test("day('---24'^^xsd:gDay)", "24"); } @Test public void day_11() { test("day('2010-12-24T16:24:01.123Z'^^xsd:dateTime)", "24"); } @Test public void day_12() { test("day('2010-12-24Z'^^xsd:date)", "24"); } - @Test - public void day_13() { assertThrows(ExprEvalException.class, ()-> test("day('2010Z'^^xsd:gYear)", "24") ); } - @Test - public void day_14() { assertThrows(ExprEvalException.class, ()-> test("day('2010-12Z'^^xsd:gYearMonth)", "24") ); } - @Test - public void day_15() { assertThrows(ExprEvalException.class, ()-> test("day('--12Z'^^xsd:gMonth)", "24") ); } + + @Test public void day_13() { testEvalException("day('2010Z'^^xsd:gYear)"); } + @Test public void day_14() { testEvalException("day('2010-12Z'^^xsd:gYearMonth)"); } + @Test public void day_15() { testEvalException("day('--12Z'^^xsd:gMonth)"); } @Test public void day_16() { test("day('--12-24Z'^^xsd:gMonthDay)", "24"); } @Test public void day_17() { test("day('---24Z'^^xsd:gDay)", "24"); } @Test public void day_21() { test("day('2010-12-24T16:24:01.123-08:00'^^xsd:dateTime)", "24"); } @Test public void day_22() { test("day('2010-12-24-08:00'^^xsd:date)", "24"); } - @Test - public void day_23() { assertThrows(ExprEvalException.class, ()-> test("day('2010-08:00'^^xsd:gYear)", "24") ); } - @Test - public void day_24() { assertThrows(ExprEvalException.class, ()-> test("day('2010-12-08:00'^^xsd:gYearMonth)", "24") ); } - @Test - public void day_25() { assertThrows(ExprEvalException.class, ()-> test("day('--12-08:00'^^xsd:gMonth)", "24") ); } + @Test public void day_23() { testEvalException("day('2010-08:00'^^xsd:gYear)"); } + @Test public void day_24() { testEvalException("day('2010-12-08:00'^^xsd:gYearMonth)"); } + @Test public void day_25() { testEvalException("day('--12-08:00'^^xsd:gMonth)"); } @Test public void day_26() { test("day('--12-24-08:00'^^xsd:gMonthDay)", "24"); } @Test public void day_27() { test("day('---24-08:00'^^xsd:gDay)", "24"); } @@ -423,8 +527,7 @@ public class TestFunctions2 // HOURS @Test public void hours_01() { test("hours('2010-12-24T16:24:01.123'^^xsd:dateTime)", "16"); } - @Test - public void hours_02() { assertThrows(ExprEvalException.class, ()-> test("hours('2010-12-24'^^xsd:date)", "16") ); } + @Test public void hours_02() { testEvalException("hours('2010-12-24'^^xsd:date)"); } @Test public void hours_03() { test("hours('16:24:01'^^xsd:time)", "16"); } @Test public void hours_10() { test("hours('2010-12-24T16:24:01.123Z'^^xsd:dateTime)", "16"); } @@ -437,7 +540,7 @@ public class TestFunctions2 // MINUTES @Test public void minutes_01() { test("minutes('2010-12-24T16:24:01.123'^^xsd:dateTime)", "24"); } - @Test public void minutes_02() { assertThrows(ExprEvalException.class, ()-> test("minutes('2010-12-24'^^xsd:date)", "") ); } + @Test public void minutes_02() { testEvalException("minutes('2010-12-24'^^xsd:date)"); } @Test public void minutes_03() { test("minutes('16:24:01'^^xsd:time)", "24"); } @Test public void minutes_10() { test("minutes('2010-12-24T16:24:01.123Z'^^xsd:dateTime)", "24"); } @@ -450,8 +553,7 @@ public class TestFunctions2 // SECONDS @Test public void seconds_01() { test("seconds('2010-12-24T16:24:01.123'^^xsd:dateTime)", "01.123"); } - @Test - public void seconds_02() { assertThrows(ExprEvalException.class, ()-> test("seconds('2010-12-24'^^xsd:date)", "") ); } + @Test public void seconds_02() { testEvalException("seconds('2010-12-24'^^xsd:date)"); } @Test public void seconds_03() { test("seconds('16:24:01'^^xsd:time)", "'01'^^xsd:decimal"); } @Test public void seconds_10() { test("seconds('2010-12-24T16:24:31.123Z'^^xsd:dateTime)", "31.123"); } @@ -469,9 +571,9 @@ public class TestFunctions2 @Test public void timezone_04() { test("timezone('2010-12-24T16:24:35.123-00:00'^^xsd:dateTime)", "'-PT0S'^^xsd:dayTimeDuration"); } @Test public void timezone_05() { test("timezone('2010-12-24T16:24:35.123+00:00'^^xsd:dateTime)", "'PT0S'^^xsd:dayTimeDuration"); } - @Test public void timezone_09() { assertThrows(ExprEvalException.class, ()-> test("timezone('2010-12-24T16:24:35'^^xsd:dateTime)", "'PT0S'^^xsd:dayTimeDuration") ); } - @Test public void timezone_10() { assertThrows(ExprEvalException.class, ()-> test("timezone(2010)", "'PT0S'^^xsd:dayTimeDuration") ); } - @Test public void timezone_11() { assertThrows(ExprEvalException.class, ()-> test("timezone('2010-junk'^^xsd:gYear)", "'PT0S'^^xsd:dayTimeDuration") ); } + @Test public void timezone_09() { testEvalException("timezone('2010-12-24T16:24:35'^^xsd:dateTime)"); } + @Test public void timezone_10() { testEvalException("timezone(2010)"); } + @Test public void timezone_11() { testEvalException("timezone('2010-junk'^^xsd:gYear)"); } // General "adjust-to-timezone" @Test public void adjust_1() { @@ -480,9 +582,7 @@ public class TestFunctions2 @Test public void adjust_2() { - assertThrows(ExprEvalException.class, ()-> - test("adjust('2022-12-21T05:05:07', 'PT08H'^^xsd:duration)", "'2022-12-21T05:05:07+08:00'^^xsd:dateTime") - ); + test("adjust('2022-12-21T05:05:07'^^xsd:dateTime, 'PT08H'^^xsd:duration)", "'2022-12-21T05:05:07+08:00'^^xsd:dateTime"); } // General "adjust-to-timezone" @@ -494,63 +594,62 @@ public class TestFunctions2 test("adjust(xsd:dateTime('2022-12-21T05:05:07'))", "'2022-12-21T05:05:07Z'^^xsd:dateTime"); } - // TZ @Test public void tz_01() { test("tz('2010-12-24T16:24:35.123Z'^^xsd:dateTime)", "'Z'"); } @Test public void tz_02() { test("tz('2010-12-24T16:24:35.123-08:00'^^xsd:dateTime)", "'-08:00'"); } @Test public void tz_03() { test("tz('2010-12-24T16:24:35.123+01:00'^^xsd:dateTime)", "'+01:00'"); } @Test public void tz_04() { test("tz('2010-12-24T16:24:35.123-00:00'^^xsd:dateTime)", "'-00:00'"); } @Test public void tz_05() { test("tz('2010-12-24T16:24:35.123+00:00'^^xsd:dateTime)", "'+00:00'"); } - @Test public void tz_06() { test("tz('2010-12-24T16:24:35.123'^^xsd:dateTime)", "''"); } + @Test public void tz_06() { test("tz('2010-12-24T16:24:35.123'^^xsd:dateTime)", kwEmptyString); } - @Test public void tz_10() { assertThrows(ExprEvalException.class, ()-> test("tz(2010)", "''") ); } - @Test public void tz_11() { assertThrows(ExprEvalException.class, ()-> test("tz('2010-junk'^^xsd:gYear)", "''") ); } + @Test public void tz_10() { testEvalException("tz(2010)"); } + @Test public void tz_11() { testEvalException("tz('2010-junk'^^xsd:gYear)"); } // NOW - //@Test public void now_01() { test("now() > '2010-12-24T16:24:35.123-08:00'^^xsd:dateTime", "true"); } + //@Test public void now_01() { test("now() > '2010-12-24T16:24:35.123-08:00'^^xsd:dateTime", kwTRUE); } // MD5 @Test public void md5_01() { test("md5('abcd')","'e2fc714c4727ee9395f324cd2e7f331f'"); } @Test public void md5_02() { test("md5('abcd'^^xsd:string)","'e2fc714c4727ee9395f324cd2e7f331f'"); } - @Test public void md5_03() { assertThrows(ExprEvalException.class, ()-> test("md5('abcd'@en)","'e2fc714c4727ee9395f324cd2e7f331f'") ); } - @Test public void md5_04() { assertThrows(ExprEvalException.class, ()-> test("md5(1234)","'e2fc714c4727ee9395f324cd2e7f331f'") ); } + @Test public void md5_03() { testEvalException("md5('abcd'@en)"); } + @Test public void md5_04() { testEvalException("md5(1234)"); } // SHA1 @Test public void sha1_01() { test("sha1('abcd')","'81fe8bfe87576c3ecb22426f8e57847382917acf'"); } @Test public void sha1_02() { test("sha1('abcd'^^xsd:string)","'81fe8bfe87576c3ecb22426f8e57847382917acf'"); } - @Test public void sha1_03() { assertThrows(ExprEvalException.class, ()-> test("sha1('abcd'@en)","'81fe8bfe87576c3ecb22426f8e57847382917acf'") ); } - @Test public void sha1_04() { assertThrows(ExprEvalException.class, ()-> test("sha1(123)","'81fe8bfe87576c3ecb22426f8e57847382917acf'") ); } + @Test public void sha1_03() { testEvalException("sha1('abcd'@en)"); } + @Test public void sha1_04() { testEvalException("sha1(123)"); } // // SHA224 // @Test public void sha224_01() { test("sha224('abcd')","'e2fc714c4727ee9395f324cd2e7f331f'"); } // // @Test -// public void sha224_02() { assertThrows(ExprEvalException.class, ()-> test("sha224('abcd'^^xsd:string)","'e2fc714c4727ee9395f324cd2e7f331f'") ); } +// public void sha224_02() { testEvalException("sha224('abcd'^^xsd:string)"); } // // @Test -// public void sha224_03() { assertThrows(ExprEvalException.class, ()-> test("sha224('abcd'@en)","'e2fc714c4727ee9395f324cd2e7f331f'") ); } +// public void sha224_03() { testEvalException("sha224('abcd'@en)"); } // // @Test -// public void sha224_04() { assertThrows(ExprEvalException.class, ()-> test("sha224(1234)","'e2fc714c4727ee9395f324cd2e7f331f'") ); } +// public void sha224_04() { testEvalException("sha224(1234)"); } // SHA256 @Test public void sha256_01() { test("sha256('abcd')","'88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589'"); } @Test public void sha256_02() { test("sha256('abcd'^^xsd:string)","'88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589'"); } - @Test public void sha256_03() { assertThrows(ExprEvalException.class, ()-> test("sha256('abcd'@en)","'88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589'") ); } - @Test public void sha256_04() { assertThrows(ExprEvalException.class, ()-> test("sha256(<uri>)","'88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589'") ); } + @Test public void sha256_03() { testEvalException("sha256('abcd'@en)"); } + @Test public void sha256_04() { testEvalException("sha256(<uri>)"); } // SHA384 @Test public void sha384_01() { test("sha384('abcd')","'1165b3406ff0b52a3d24721f785462ca2276c9f454a116c2b2ba20171a7905ea5a026682eb659c4d5f115c363aa3c79b'"); } @Test public void sha384_02() { test("sha384('abcd'^^xsd:string)","'1165b3406ff0b52a3d24721f785462ca2276c9f454a116c2b2ba20171a7905ea5a026682eb659c4d5f115c363aa3c79b'"); } - @Test public void sha384_03() { assertThrows(ExprEvalException.class, ()-> test("sha384('abcd'@en)","'1165b3406ff0b52a3d24721f785462ca2276c9f454a116c2b2ba20171a7905ea5a026682eb659c4d5f115c363aa3c79b'") ); } - @Test public void sha384_04() { assertThrows(ExprEvalException.class, ()-> test("sha384(123.45)","'1165b3406ff0b52a3d24721f785462ca2276c9f454a116c2b2ba20171a7905ea5a026682eb659c4d5f115c363aa3c79b'") ); } + @Test public void sha384_03() { testEvalException("sha384('abcd'@en)"); } + @Test public void sha384_04() { testEvalException("sha384(123.45)"); } // SHA512 @Test public void sha512_01() { test("sha512('abcd')","'d8022f2060ad6efd297ab73dcc5355c9b214054b0d1776a136a669d26a7d3b14f73aa0d0ebff19ee333368f0164b6419a96da49e3e481753e7e96b716bdccb6f'"); } @Test public void sha512_02() { test("sha512('abcd'^^xsd:string)","'d8022f2060ad6efd297ab73dcc5355c9b214054b0d1776a136a669d26a7d3b14f73aa0d0ebff19ee333368f0164b6419a96da49e3e481753e7e96b716bdccb6f'"); } - @Test public void sha512_03() { assertThrows(ExprEvalException.class, ()-> test("md5('abcd'@en)","'d8022f2060ad6efd297ab73dcc5355c9b214054b0d1776a136a669d26a7d3b14f73aa0d0ebff19ee333368f0164b6419a96da49e3e481753e7e96b716bdccb6f'") ); } - @Test public void sha512_04() { assertThrows(ExprEvalException.class, ()-> test("md5(0.0e0)","'d8022f2060ad6efd297ab73dcc5355c9b214054b0d1776a136a669d26a7d3b14f73aa0d0ebff19ee333368f0164b6419a96da49e3e481753e7e96b716bdccb6f'") ); } + @Test public void sha512_03() { testEvalException("md5('abcd'@en)"); } + @Test public void sha512_04() { testEvalException("md5(0.0e0)"); } // -------- @@ -576,4 +675,13 @@ public class TestFunctions2 // test result must be lexical form exact. assertEquals(r, nv.asNode()); } + + private void testEvalException(String exprStr) { + Expr expr = ExprUtils.parse(exprStr); + try { + NodeValue r = expr.eval(null, LibTestExpr.createTest()); + fail("No exception raised"); + } + catch (ExprEvalException ex) {} + } } diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestStringArgCompatibility.java b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestStringArgCompatibility.java new file mode 100644 index 0000000000..5623d80317 --- /dev/null +++ b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestStringArgCompatibility.java @@ -0,0 +1,83 @@ +/* + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.apache.jena.sparql.expr; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +import org.apache.jena.graph.Node; +import org.apache.jena.sparql.expr.nodevalue.NodeValueOps; +import org.apache.jena.sparql.sse.SSE; + +public class TestStringArgCompatibility { + + @Test public void arg_01() { testArgGood("'abc'", "'abc'"); } + @Test public void arg_02() { testArgGood("'abc'@en", "'abc'@en"); } + @Test public void arg_03() { testArgGood("'abc'@en--ltr", "'abc'@en--ltr"); } + + @Test public void arg_04() { testArgBad("123"); } + @Test public void arg_05() { testArgBad("<123>"); } + + @Test public void args2_01() { test2ArgsGood("'abc'", "''"); } + @Test public void args2_02() { test2ArgsGood("'abc'", "'ab'"); } + @Test public void args2_03() { test2ArgsGood("'abc'@en", "'ab'"); } + @Test public void args2_04() { test2ArgsGood("'abc'@en--ltr", "'ab'"); } + @Test public void args2_05() { test2ArgsGood("'abc'@en", "'ab'@en"); } + @Test public void args2_06() { test2ArgsGood("'abc'@en--ltr", "'ab'@en--ltr"); } + + @Test public void args2_20() { test2ArgsBad("123", "123"); } // Not strings. + @Test public void args2_21() { test2ArgsBad("'abc'@en", "'a'@fr"); } + @Test public void args2_22() { test2ArgsBad("'abc'@en--ltr", "'a'@en"); } + @Test public void args2_23() { test2ArgsBad("'abc'@en", "'a'@en--ltr"); } + + private void testArgGood(String arg, String expected) { + Node n = SSE.parseNode(arg); + NodeValue nv = NodeValue.makeNode(n); + Node r = NodeValueOps.checkAndGetStringLiteral("test", nv); + Node nExpected = SSE.parseNode(expected); + assertEquals(nExpected, r); + } + + private void testArgBad(String arg) { + Node n = SSE.parseNode(arg); + NodeValue nv = NodeValue.makeNode(n); + assertThrows(ExprEvalException.class, ()->NodeValueOps.checkAndGetStringLiteral("test", nv)); + } + + private void test2ArgsGood(String arg1, String arg2) { + Node n1 = SSE.parseNode(arg1); + Node n2 = SSE.parseNode(arg2); + NodeValue nv1 = NodeValue.makeNode(n1); + NodeValue nv2 = NodeValue.makeNode(n2); + NodeValueOps.checkTwoArgumentStringLiterals("test", nv1, nv2); + } + + private void test2ArgsBad(String arg1, String arg2) { + Node n1 = SSE.parseNode(arg1); + Node n2 = SSE.parseNode(arg2); + NodeValue nv1 = NodeValue.makeNode(n1); + NodeValue nv2 = NodeValue.makeNode(n2); + assertThrows(ExprEvalException.class, ()->NodeValueOps.checkTwoArgumentStringLiterals("test", nv1, nv2)); + } + +} diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/Lib.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/Lib.java index e31eee664d..504362cb42 100644 --- a/jena-base/src/main/java/org/apache/jena/atlas/lib/Lib.java +++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/Lib.java @@ -65,13 +65,19 @@ public class Lib /** * Return true if obj1 and obj are both null or are .equals, else return false - * Prefer {@link Objects#equals(Object, Object)} + * @deprecated Use {@link #equalsOrNulls(Object, Object)} {@link Objects#equals(Object, Object)} */ + @Deprecated(forRemoval = true) public static final <T> boolean equals(T obj1, T obj2) { // Include because other equals* operations are here. return Objects.equals(obj1, obj2); } + public static final <T> boolean equalsOrNulls(T obj1, T obj2) { + // Include because static import works. + return Objects.equals(obj1, obj2); + } + /** Return true if obj1 and obj are both null or are .equals, else return false */ public static final boolean equalsIgnoreCase(String str1, String str2) { if ( str1 == null ) diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionLib.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionLib.java index 099a6afea9..e8c3feeae0 100644 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionLib.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionLib.java @@ -19,6 +19,7 @@ package org.apache.jena.fuseki.servlets; import static java.lang.String.format; +import static org.apache.jena.atlas.lib.Lib.equalsOrNulls; import static org.apache.jena.riot.web.HttpNames.METHOD_POST; import java.io.ByteArrayOutputStream; @@ -35,7 +36,6 @@ import jakarta.servlet.http.HttpServletResponse; import org.apache.jena.atlas.RuntimeIOException; import org.apache.jena.atlas.io.IO; -import org.apache.jena.atlas.lib.Lib; import org.apache.jena.atlas.web.AcceptList; import org.apache.jena.atlas.web.ContentType; import org.apache.jena.atlas.web.MediaType; @@ -193,7 +193,7 @@ public class ActionLib { public static boolean splitContains(String[] elts, String str) { for ( int i = 0 ; i < elts.length ; i++ ) { - if ( Lib.equals(elts[i], str) ) + if ( equalsOrNulls(elts[i], str) ) return true; } return false;
