diff --git a/jena-arq/src/test/java/org/apache/jena/arq/junit/manifest/TestMakers.java b/jena-arq/src/test/java/org/apache/jena/arq/junit/manifest/TestMakers.java index 3d4ee22ec45..b37c8f75997 100644 --- a/jena-arq/src/test/java/org/apache/jena/arq/junit/manifest/TestMakers.java +++ b/jena-arq/src/test/java/org/apache/jena/arq/junit/manifest/TestMakers.java @@ -21,6 +21,7 @@ package org.apache.jena.arq.junit.manifest; +import java.util.ArrayList; import java.util.List; import org.apache.jena.arq.junit.SurpressedTest; @@ -52,7 +53,9 @@ private static TestMakers systemSetup() { * Add a test maker to the system-wide test makers list */ public static void install(TestMaker testMaker) { - systemSetup.add(testMaker); + List next = new ArrayList<>(systemSetup.installed); + next.add(testMaker); + set(next); } public static void reset() { @@ -77,10 +80,6 @@ private TestMakers(List testMakers) { installed = testMakers; } - public void add(TestMaker testMaker) { - installed.add(testMaker); - } - public void clear() { installed.clear(); } diff --git a/jena-cmds/src/main/java/shacl/shacl_validate.java b/jena-cmds/src/main/java/shacl/shacl_validate.java index 96df4d54e51..bdd2d5728ec 100644 --- a/jena-cmds/src/main/java/shacl/shacl_validate.java +++ b/jena-cmds/src/main/java/shacl/shacl_validate.java @@ -21,6 +21,8 @@ package shacl; +import java.util.List; + import org.apache.jena.atlas.logging.LogCtl; import org.apache.jena.cmd.ArgDecl; import org.apache.jena.cmd.CmdException; @@ -36,11 +38,14 @@ import org.apache.jena.shacl.ValidationReport; import org.apache.jena.shacl.engine.ValidationContext; import org.apache.jena.shacl.lib.ShLib; +import org.apache.jena.sparql.graph.GraphFactory; import org.apache.jena.sys.JenaSystem; /** SHACL validation. - *

- * Usage: shacl validate [--text] --shapes SHAPES --data DATA + *

+ * Usage: shacl validate [--text] [-v|--verbose] [--target=] --shapes SHAPES --data DATA
+ * Usage: shacl validate [--text] [-v|--verbose] [--target=] FILE
+ * 
*/ public class shacl_validate extends CmdMain { @@ -55,8 +60,10 @@ public class shacl_validate extends CmdMain { private ArgDecl argShapes = new ArgDecl(true, "--shapes", "--shapesfile", "--shapefile", "-s"); private ArgDecl argTargetNode = new ArgDecl(true, "--target", "--node", "-n", "-t"); - private String datafile = null; - private String shapesfile = null; + // Allow multiple files for each - combine into one graph each for data and for shapes. + private List datafiles = null; + private List shapesfiles = null; + private String shapesURL = null; private String targetNode = null; // Parse later. private boolean textOutput = false; @@ -75,44 +82,43 @@ public shacl_validate(String[] argv) { @Override protected String getSummary() { - return getCommandName()+" [--target URI] --shapes shapesFile --data dataFile"; + return getCommandName()+" [--target URI] [--shapes shapesFile --data dataFile] [FILE ...]"; } @Override protected void processModulesAndArgs() { super.processModulesAndArgs(); - datafile = super.getValue(argData); - shapesfile = super.getValue(argShapes); + // --data can be empty in which case shapes and data are in the shapes files. + datafiles = super.getValues(argData); + shapesfiles = super.getValues(argShapes); - // No -- arguments, use act on single file of shapes and data. - if ( datafile == null && shapesfile == null ) { - if ( positionals.size() == 1 ) { - datafile = positionals.get(0); - shapesfile = positionals.get(0); - } + // If there are no arguments, act the commandline positional arguments as shapes and data. + if ( datafiles.isEmpty() && shapesfiles.isEmpty() ) { + if ( positionals.isEmpty() ) + throw new CmdException("No input"); + shapesfiles = positionals; } - - if ( datafile == null ) - throw new CmdException("Usage: "+getSummary()); - if ( shapesfile == null ) - shapesfile = datafile; + if ( shapesfiles == null || shapesfiles.isEmpty() ) + throw new CmdException("No shapes files"); textOutput = super.hasArg(argOutputText); - if ( contains(argTargetNode) ) { + if ( contains(argTargetNode) ) targetNode = getValue(argTargetNode); - } + + // For imports. + shapesURL = shapesfiles.getFirst(); } @Override protected void exec() { - Graph shapesGraph = load(shapesfile, "shapes file"); + Graph shapesGraph = load(shapesfiles); Graph dataGraph; - if ( datafile.equals(shapesfile) ) + if ( datafiles.isEmpty() ) dataGraph = shapesGraph; else - dataGraph = load(datafile, "data file"); + dataGraph = load(datafiles); Node node = null; if ( targetNode != null ) { @@ -120,7 +126,7 @@ protected void exec() { node = NodeFactory.createURI(x); } - shapesGraph = Imports.withImports(shapesfile, shapesGraph); + shapesGraph = Imports.withImports(shapesURL, shapesGraph); if ( isVerbose() ) ValidationContext.VERBOSE = true; @@ -138,14 +144,17 @@ protected void exec() { RDFDataMgr.write(System.out, report.getGraph(), Lang.TTL); } - private Graph load(String filename, String scope) { - try { - Graph graph = RDFDataMgr.loadGraph(filename); - return graph; - } catch (RiotException ex) { - System.err.println("Loading "+scope); - throw ex; - } + private Graph load(List files) { + Graph graph = GraphFactory.createDefaultGraph(); + files.forEach(fn-> { + try { + RDFDataMgr.read(graph, fn); + } catch (RiotException ex) { + System.err.println("Error loading "+fn); + throw ex; + } + }); + return graph; } @Override diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ConstraintList.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ConstraintList.java new file mode 100644 index 00000000000..8493c8f339b --- /dev/null +++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ConstraintList.java @@ -0,0 +1,67 @@ +/* + * 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 + * + * https://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.shacl.engine.constraint; + +import java.util.Set; + +import org.apache.jena.graph.Graph; +import org.apache.jena.graph.Node; +import org.apache.jena.shacl.engine.ValidationContext; +import org.apache.jena.shacl.parser.Constraint; +import org.apache.jena.shacl.parser.Shape; +import org.apache.jena.shacl.validation.ReportItem; +import org.apache.jena.shacl.validation.event.ConstraintEvaluatedOnFocusNodeEvent; +import org.apache.jena.shacl.validation.event.ConstraintEvaluatedOnSinglePathNodeEvent; +import org.apache.jena.sparql.path.Path; + +public abstract class ConstraintList implements Constraint { + + protected ConstraintList() {} + + @Override + final + public void validatePropertyShape(ValidationContext vCxt, Graph data, Shape shape, Node focusNode, Path path, Set valueNodes) { + valueNodes.forEach(x->applyConstraintList(vCxt, shape, focusNode, path, data, x)); + } + + @Override + final + public void validateNodeShape(ValidationContext vCxt, Graph data, Shape shape, Node focusNode) { + applyConstraintList(vCxt, shape, focusNode, null, data, focusNode); + } + + private void applyConstraintList(ValidationContext vCxt, Shape shape, Node focusNode, Path path, Graph data, Node listHead) { + ReportItem item = validateList(vCxt, data, listHead); + boolean passed = (item == null); + if (path == null) { + vCxt.notifyValidationListener(() -> new ConstraintEvaluatedOnFocusNodeEvent(vCxt, shape, focusNode, this, passed)); + } else { + vCxt.notifyValidationListener(() -> new ConstraintEvaluatedOnSinglePathNodeEvent(vCxt, shape, focusNode, this, path, + listHead, passed)); + } + if ( passed ) + return; + vCxt.reportEntry(item, shape, focusNode, path, this); + } + + protected abstract ReportItem validateList(ValidationContext vCxt, Graph data, Node n) ; +} diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ConstraintTerm.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ConstraintTerm.java index 284830cf88f..0264c6fdb6f 100644 --- a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ConstraintTerm.java +++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ConstraintTerm.java @@ -52,7 +52,7 @@ public void validateNodeShape(ValidationContext vCxt, Graph data, Shape shape, N private void applyConstraintTerm(ValidationContext vCxt, Shape shape, Node focusNode, Path path, Node term) { ReportItem item = validate(vCxt, term); - boolean passed = item == null; + boolean passed = (item == null); if (path == null) { vCxt.notifyValidationListener(() -> new ConstraintEvaluatedOnFocusNodeEvent(vCxt, shape, focusNode, this, passed)); } else { @@ -65,5 +65,5 @@ private void applyConstraintTerm(ValidationContext vCxt, Shape shape, Node focus vCxt.reportEntry(item, shape, focusNode, path, this); } - public abstract ReportItem validate(ValidationContext vCxt, Node n) ; + protected abstract ReportItem validate(ValidationContext vCxt, Node n) ; } diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/DatatypeConstraint.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/DatatypeConstraint.java index 35d329fe051..b47ad94d576 100644 --- a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/DatatypeConstraint.java +++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/DatatypeConstraint.java @@ -75,7 +75,7 @@ public RDFDatatype getRDFDatatype() { } @Override - public ReportItem validate(ValidationContext vCxt, Node n) { + protected ReportItem validate(ValidationContext vCxt, Node n) { if ( n.isLiteral() && dtURI.equals(n.getLiteralDatatypeURI()) ) { // Must be valid for the type if ( ! rdfDatatype.isValid(n.getLiteralLexicalForm()) ) { @@ -131,6 +131,7 @@ public void printCompact(IndentedWriter out, NodeFormatter nodeFmt) { @Override public String toString() { + // DRY (with ListMmeberShape, others? String x; if ( datatype.isURI() ) { if ( dtURI.startsWith(XSD.getURI()) ) diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/InConstraint.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/InConstraint.java index 0d9bd00d86c..82f1300c5cf 100644 --- a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/InConstraint.java +++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/InConstraint.java @@ -56,7 +56,7 @@ public Node getComponent() { } @Override - public ReportItem validate(ValidationContext vCxt, Node n) { + protected ReportItem validate(ValidationContext vCxt, Node n) { if ( values.contains(n) ) return null; String errMsg = toString()+" : RDF term "+displayStr(n)+" not in expected values"; diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/JLogConstraint.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/JLogConstraint.java index 4a4bd6b6975..4fdd687c75c 100644 --- a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/JLogConstraint.java +++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/JLogConstraint.java @@ -50,7 +50,7 @@ public Node getComponent() { } @Override - public ReportItem validate(ValidationContext vCxt, Node n) { + protected ReportItem validate(ValidationContext vCxt, Node n) { String msg = String.format("%s[%s]", message, ShLib.displayStr(n)); ShaclSystem.shaclSystemLogger.warn(msg); return null; diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/JViolationConstraint.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/JViolationConstraint.java index ac8430b65d4..5945e106e5c 100644 --- a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/JViolationConstraint.java +++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/JViolationConstraint.java @@ -49,7 +49,7 @@ public Node getComponent() { } @Override - public ReportItem validate(ValidationContext vCxt, Node n) { + protected ReportItem validate(ValidationContext vCxt, Node n) { if ( ! generateViolation ) return null; return new ReportItem("Violation"); diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ListMaxLength.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ListMaxLength.java new file mode 100644 index 00000000000..36253ed3f3f --- /dev/null +++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ListMaxLength.java @@ -0,0 +1,86 @@ +/* + * 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 + * + * https://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.shacl.engine.constraint; + +import org.apache.jena.atlas.io.IndentedWriter; +import org.apache.jena.atlas.lib.NotImplemented; +import org.apache.jena.graph.Graph; +import org.apache.jena.graph.Node; +import org.apache.jena.riot.out.NodeFormatter; +import org.apache.jena.shacl.engine.ValidationContext; +import org.apache.jena.shacl.parser.ConstraintVisitor; +import org.apache.jena.shacl.validation.ReportItem; +import org.apache.jena.shacl.vocabulary.SHACL; + +/** sh:memberShape */ + +public class ListMaxLength extends ConstraintList { + + public ListMaxLength(Node node) {} + + @Override + public void visit(ConstraintVisitor visitor){ + visitor.visit(this); + } + + @Override + public void printCompact(IndentedWriter out, NodeFormatter nodeFmt) { + throw new NotImplemented(); + } + + @Override + protected ReportItem validateList(ValidationContext vCxt, Graph data, Node headNode) { + throw new NotImplemented(); + } + + @Override + public Node getComponent() { + return SHACL.ListMaxLengthConstraintComponent; + } + + @Override + public void print(IndentedWriter out, NodeFormatter nodeFmt) { + out.print(toString()); + } + + @Override + public String toString() { + return "ListMemberShape[]"; + } + + @Override + public int hashCode() { + throw new NotImplemented(); + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) + return true; + if ( obj == null ) + return false; + if ( !(obj instanceof ListMaxLength other) ) + return false; + throw new NotImplemented(); + } + +} diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ListMemberShape.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ListMemberShape.java new file mode 100644 index 00000000000..133edec8e9c --- /dev/null +++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ListMemberShape.java @@ -0,0 +1,114 @@ +/* + * 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 + * + * https://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.shacl.engine.constraint; + +import static org.apache.jena.shacl.compact.writer.CompactOut.compact; +import static org.apache.jena.shacl.lib.ShLib.displayStr; + +import java.util.List; +import java.util.Objects; + +import org.apache.jena.atlas.io.IndentedWriter; +import org.apache.jena.graph.Graph; +import org.apache.jena.graph.Node; +import org.apache.jena.riot.out.NodeFormatter; +import org.apache.jena.shacl.engine.ValidationContext; +import org.apache.jena.shacl.parser.ConstraintVisitor; +import org.apache.jena.shacl.parser.Shape; +import org.apache.jena.shacl.validation.ReportItem; +import org.apache.jena.shacl.validation.ValidationProc; +import org.apache.jena.shacl.vocabulary.SHACL; +import org.apache.jena.sparql.util.graph.GNode; +import org.apache.jena.sparql.util.graph.GraphList; + +/** sh:memberShape */ + +public class ListMemberShape extends ConstraintList { + + protected final Node shape; + + public ListMemberShape(Node node) { + this.shape = node; + } + + @Override + public void visit(ConstraintVisitor visitor){ + visitor.visit(this); + } + + @Override + public void printCompact(IndentedWriter out, NodeFormatter nodeFmt) { + compact(out, nodeFmt, "memberShape", shape); + } + + @Override + protected ReportItem validateList(ValidationContext vCxt, Graph data, Node headNode) { + Shape memberShape = vCxt.getShapes().getShape(shape); + if ( memberShape == null ) { + // XXX + //vCxt.reportEntry(, shape, focusNode, path, valueNode, constraint); + // No shape. Error? + return null; + } + // XXX Check for valid lists. + GNode gNode = GNode.create(data, headNode); + List members = GraphList.members(gNode); + + members.forEach(x->{ + ValidationProc.execValidateShape(vCxt, data, memberShape, x); + }); + // XXX Isolate validation and wrap violations? + return null; + } + + @Override + public Node getComponent() { + return SHACL.MemberShapeConstraintComponent; + } + + @Override + public void print(IndentedWriter out, NodeFormatter nodeFmt) { + out.print(toString()); + } + + @Override + public String toString() { + // XXX Prefixes + return "ListMemberShape["+displayStr(shape)+"]"; + } + + @Override + public int hashCode() { + return Objects.hash(shape); + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) + return true; + if ( obj == null ) + return false; + if ( !(obj instanceof ListMemberShape other) ) + return false; + return shape.sameTermAs(other.shape); + } +} diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ListMinLength.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ListMinLength.java new file mode 100644 index 00000000000..6fff10ba560 --- /dev/null +++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ListMinLength.java @@ -0,0 +1,86 @@ +/* + * 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 + * + * https://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.shacl.engine.constraint; + +import org.apache.jena.atlas.io.IndentedWriter; +import org.apache.jena.atlas.lib.NotImplemented; +import org.apache.jena.graph.Graph; +import org.apache.jena.graph.Node; +import org.apache.jena.riot.out.NodeFormatter; +import org.apache.jena.shacl.engine.ValidationContext; +import org.apache.jena.shacl.parser.ConstraintVisitor; +import org.apache.jena.shacl.validation.ReportItem; +import org.apache.jena.shacl.vocabulary.SHACL; + +/** sh:memberShape */ + +public class ListMinLength extends ConstraintList { + + public ListMinLength(Node node) { } + + @Override + public void visit(ConstraintVisitor visitor){ + visitor.visit(this); + } + + @Override + public void printCompact(IndentedWriter out, NodeFormatter nodeFmt) { + throw new NotImplemented(); + } + + @Override + protected ReportItem validateList(ValidationContext vCxt, Graph data, Node headNode) { + throw new NotImplemented(); + } + + @Override + public Node getComponent() { + return SHACL.ListMinLengthConstraintComponent; + } + + @Override + public void print(IndentedWriter out, NodeFormatter nodeFmt) { + out.print(toString()); + } + + @Override + public String toString() { + return "ListMemberShape[]"; + } + + @Override + public int hashCode() { + throw new NotImplemented(); + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) + return true; + if ( obj == null ) + return false; + if ( !(obj instanceof ListMinLength other) ) + return false; + throw new NotImplemented(); + } + +} diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ListUniqueMembers.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ListUniqueMembers.java new file mode 100644 index 00000000000..4c7893015a8 --- /dev/null +++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ListUniqueMembers.java @@ -0,0 +1,89 @@ +/* + * 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 + * + * https://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.shacl.engine.constraint; + +import org.apache.jena.atlas.io.IndentedWriter; +import org.apache.jena.atlas.lib.NotImplemented; +import org.apache.jena.graph.Graph; +import org.apache.jena.graph.Node; +import org.apache.jena.riot.out.NodeFormatter; +import org.apache.jena.shacl.engine.ValidationContext; +import org.apache.jena.shacl.parser.ConstraintVisitor; +import org.apache.jena.shacl.validation.ReportItem; +import org.apache.jena.shacl.vocabulary.SHACL; + +/** sh:memberShape */ + +public class ListUniqueMembers extends ConstraintList { + + public ListUniqueMembers(Node node) {} + + @Override + public void visit(ConstraintVisitor visitor){ + visitor.visit(this); + } + + @Override + public void printCompact(IndentedWriter out, NodeFormatter nodeFmt) { + //compact(out, nodeFmt, "nodeKind", getKind()); + // Property context only. +// String s = getKind().getLocalName(); +// out.print(s); + } + + @Override + protected ReportItem validateList(ValidationContext vCxt, Graph data, Node headNode) { + throw new NotImplemented(); + } + + @Override + public Node getComponent() { + return SHACL.UniqueMembersConstraintComponent; + } + + @Override + public void print(IndentedWriter out, NodeFormatter nodeFmt) { + out.print(toString()); + } + + @Override + public String toString() { + return "ListMemberShape[]"; + } + + @Override + public int hashCode() { + throw new NotImplemented(); + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) + return true; + if ( obj == null ) + return false; + if ( !(obj instanceof ListUniqueMembers other) ) + return false; + throw new NotImplemented(); + } + +} diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/NodeKindConstraint.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/NodeKindConstraint.java index ee955f022b8..6404af002e9 100644 --- a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/NodeKindConstraint.java +++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/NodeKindConstraint.java @@ -39,25 +39,28 @@ public class NodeKindConstraint extends ConstraintTerm { //sh:NodeKind: sh:BlankNode, sh:IRI, sh:Literal sh:BlankNodeOrIRI, sh:BlankNodeOrLiteral and sh:IRIOrLiteral. + // SHACL 1.2 -- sh:TripleTerm private final Node kind; private final boolean canBeIRI; private final boolean canBeBlankNode; private final boolean canBeLiteral; + private final boolean canBeTripleTerm; public NodeKindConstraint(Node kind) { Objects.requireNonNull(kind); if ( ! kind.isURI() ) throw new IllegalArgumentException("NodeKindConstraint; not an IRI for the kind kind"); this.kind = kind; - this.canBeIRI = kind.equals(SHACL.IRI) || kind.equals(SHACL.BlankNodeOrIRI) || kind.equals(SHACL.IRIOrLiteral); - this.canBeBlankNode = kind.equals(SHACL.BlankNode) || kind.equals(SHACL.BlankNodeOrIRI) || kind.equals(SHACL.BlankNodeOrLiteral); - this.canBeLiteral = kind.equals(SHACL.Literal) || kind.equals(SHACL.BlankNodeOrLiteral) || kind.equals(SHACL.IRIOrLiteral); + this.canBeIRI = kind.equals(SHACL.IRI) || kind.equals(SHACL.BlankNodeOrIRI) || kind.equals(SHACL.IRIOrLiteral); + this.canBeBlankNode = kind.equals(SHACL.BlankNode) || kind.equals(SHACL.BlankNodeOrIRI) || kind.equals(SHACL.BlankNodeOrLiteral); + this.canBeLiteral = kind.equals(SHACL.Literal) || kind.equals(SHACL.BlankNodeOrLiteral) || kind.equals(SHACL.IRIOrLiteral); + this.canBeTripleTerm = kind.equals(SHACL.TripleTerm); - if ( ! canBeIRI && ! canBeBlankNode && ! canBeLiteral ) + if ( ! canBeIRI && ! canBeBlankNode && ! canBeLiteral && ! canBeTripleTerm ) throw new IllegalArgumentException( "NodeKind["+kind.getLocalName()+"] : "+ - "not one of sh:BlankNode, sh:IRI, sh:Literal sh:BlankNodeOrIRI, sh:BlankNodeOrLiteral and sh:IRIOrLiteral"); + "not one of sh:BlankNode, sh:IRI, sh:Literal sh:BlankNodeOrIRI, sh:BlankNodeOrLiteral, sh:IRIOrLiteral, or sh:TripleTerm"); } public Node getKind() { return kind; } @@ -88,10 +91,11 @@ public void printCompact(IndentedWriter out, NodeFormatter nodeFmt) { } @Override - public ReportItem validate(ValidationContext vCxt, Node n) { - if ( canBeIRI && n.isURI() ) return null; - if ( canBeBlankNode && n.isBlank() ) return null; - if ( canBeLiteral && n.isLiteral() ) return null; + protected ReportItem validate(ValidationContext vCxt, Node n) { + if ( canBeIRI && n.isURI() ) return null; + if ( canBeBlankNode && n.isBlank() ) return null; + if ( canBeLiteral && n.isLiteral() ) return null; + if ( canBeTripleTerm && n.isTripleTerm() ) return null; String msg = toString()+" : Expected "+kind.getLocalName()+" for "+displayStr(n); return new ReportItem(msg, n); } diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/PatternConstraint.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/PatternConstraint.java index 0e1b551befb..02088e026cc 100644 --- a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/PatternConstraint.java +++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/PatternConstraint.java @@ -66,7 +66,7 @@ public String getFlagsStr() { } @Override - public ReportItem validate(ValidationContext vCxt, Node n) { + protected ReportItem validate(ValidationContext vCxt, Node n) { if ( n.isBlank() ) { String msg = toString()+": Blank node: "+ShLib.displayStr(n); return new ReportItem(msg, n); diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ShXone.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ShXone.java index 727e5824c02..9b4ab0f4778 100644 --- a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ShXone.java +++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ShXone.java @@ -52,7 +52,7 @@ public ReportItem validate(ValidationContext vCxt, Graph data, Node node) { for ( Shape sh : others ) { ValidationContext vCxt2 = ValidationContext.create(vCxt); ValidationProc.execValidateShape(vCxt2, data, sh, node); - boolean innerConforms = vCxt2.generateReport().conforms(); + boolean innerConforms = ! vCxt2.hasViolation(); if ( innerConforms ) { c++; // Choice: count all vs break as soon as error detected diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/StrLanguageIn.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/StrLanguageIn.java index c4d7167f9d4..a7c139274d6 100644 --- a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/StrLanguageIn.java +++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/StrLanguageIn.java @@ -47,7 +47,7 @@ public StrLanguageIn(List langs) { } @Override - public ReportItem validate(ValidationContext vCxt, Node n) { + protected ReportItem validate(ValidationContext vCxt, Node n) { if ( ! n.isLiteral() ) return new ReportItem(toString()+": Not a literal",n); String langTag = n.getLiteralLanguage(); diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/StrMaxLengthConstraint.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/StrMaxLengthConstraint.java index 8c450ba8e0b..4504448c81e 100644 --- a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/StrMaxLengthConstraint.java +++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/StrMaxLengthConstraint.java @@ -49,7 +49,7 @@ public int getMaxLength() { } @Override - public ReportItem validate(ValidationContext vCxt, Node n) { + protected ReportItem validate(ValidationContext vCxt, Node n) { if ( n.isBlank() ) { String msg = toString()+": Blank node: "+ShLib.displayStr(n); return new ReportItem(msg, n); diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/StrMinLengthConstraint.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/StrMinLengthConstraint.java index c043ec9a48c..ee77623dc36 100644 --- a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/StrMinLengthConstraint.java +++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/StrMinLengthConstraint.java @@ -49,7 +49,7 @@ public int getMinLength() { } @Override - public ReportItem validate(ValidationContext vCxt, Node n) { + protected ReportItem validate(ValidationContext vCxt, Node n) { if ( n.isBlank() ) { String msg = toString()+": Blank node: "+ShLib.displayStr(n); return new ReportItem(msg, n); diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ValueRangeConstraint.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ValueRangeConstraint.java index da605c88a1e..813d248181a 100644 --- a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ValueRangeConstraint.java +++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/ValueRangeConstraint.java @@ -51,7 +51,8 @@ public NodeValue getNodeValue() { } @Override - final public ReportItem validate(ValidationContext vCxt, Node n) { + protected + final ReportItem validate(ValidationContext vCxt, Node n) { NodeValue nv = NodeValue.makeNode(n); ValueSpace vs = NodeValue.classifyValueOp(nodeValue, nv); try { diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/parser/ConstraintVisitor.java b/jena-shacl/src/main/java/org/apache/jena/shacl/parser/ConstraintVisitor.java index 401879d29e2..9a44b18a8db 100644 --- a/jena-shacl/src/main/java/org/apache/jena/shacl/parser/ConstraintVisitor.java +++ b/jena-shacl/src/main/java/org/apache/jena/shacl/parser/ConstraintVisitor.java @@ -55,6 +55,11 @@ public interface ConstraintVisitor { void visit(ConstraintComponentSPARQL constraint); void visit(SparqlConstraint constraint); + void visit(ListMemberShape constraint); + void visit(ListMinLength constraint); + void visit(ListMaxLength constraint); + void visit(ListUniqueMembers constraint); + // Other Constraints void visit(JViolationConstraint constraint); void visit(JLogConstraint constraint); diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/parser/Constraints.java b/jena-shacl/src/main/java/org/apache/jena/shacl/parser/Constraints.java index 24708af8d9d..6e54e27d08b 100644 --- a/jena-shacl/src/main/java/org/apache/jena/shacl/parser/Constraints.java +++ b/jena-shacl/src/main/java/org/apache/jena/shacl/parser/Constraints.java @@ -108,6 +108,11 @@ public class Constraints { dispatch.put( SHACL.in, (g, s, p, o) -> new InConstraint(list(g,o)) ); dispatch.put( SHACL.closed, (g, s, p, o) -> new ClosedConstraint(g,s,booleanValue(o)) ); + dispatch.put( SHACL.memberShape, (g, s, p, o) -> new ListMemberShape(o)); + dispatch.put( SHACL.minListLength, (g, s, p, o) -> new ListMinLength(o)); + dispatch.put( SHACL.maxListLength, (g, s, p, o) -> new ListMaxLength(o)); + dispatch.put( SHACL.uniqueMembers, (g, s, p, o) -> new ListUniqueMembers(o)); + // Below //dispatch.put( SHACL.not, (g, s, p, o) -> notImplemented(p) ); //dispatch.put( SHACL.and, (g, s, p, o) -> notImplemented(p) ); @@ -202,6 +207,10 @@ private static Constraint parseConstraint(Graph g, Node s, Node p, Node o, MapSpecifies the node kind (e.g. IRI or literal) each value node.

*/ public static final Node nodeKind = createProperty( "http://www.w3.org/ns/shacl#nodeKind" ); + /** SHACL 1.2: sh:memberShape */ + public static final Node memberShape = createProperty( "http://www.w3.org/ns/shacl#memberShape" ); + + /** SHACL 1.2: minListLength */ + public static final Node minListLength = createProperty( "http://www.w3.org/ns/shacl#minListLength" ); + + /** SHACL 1.2: maxListLength */ + public static final Node maxListLength = createProperty( "http://www.w3.org/ns/shacl#maxListLength" ); + + /** SHACL 1.2: uniqueMembers */ + public static final Node uniqueMembers = createProperty( "http://www.w3.org/ns/shacl#uniqueMembers" ); + + /**

Node kind -- literal.

*/ + public static final Node Literal = createResource( "http://www.w3.org/ns/shacl#Literal" ); + + /**

Node kind -- IRI.

*/ + public static final Node IRI = createResource( "http://www.w3.org/ns/shacl#IRI" ); + + /**

Node kind -- blank node.

*/ + public static final Node BlankNode = createResource( "http://www.w3.org/ns/shacl#BlankNode" ); + + /**

Node kind -- triple term

*/ + public static final Node TripleTerm = createResource( "http://www.w3.org/ns/shacl#TripleTerm" ); + + /**

Node kind -- IRI or literal.

*/ + public static final Node IRIOrLiteral = createResource( "http://www.w3.org/ns/shacl#IRIOrLiteral" ); + + /**

Node kind -- blank node or IRI.

*/ + public static final Node BlankNodeOrIRI = createResource( "http://www.w3.org/ns/shacl#BlankNodeOrIRI" ); + + /**

Node kind -- blank nodes or literals.

*/ + public static final Node BlankNodeOrLiteral = createResource( "http://www.w3.org/ns/shacl#BlankNodeOrLiteral" ); + /**

The validator(s) used to evaluate a constraint in the context of a node shape.

*/ public static final Node nodeValidator = createProperty( "http://www.w3.org/ns/shacl#nodeValidator" ); @@ -430,15 +464,6 @@ public class SHACL { public static final Node AndConstraintComponent_and = createResource( "http://www.w3.org/ns/shacl#AndConstraintComponent-and" ); - /**

The node kind of all blank nodes.

*/ - public static final Node BlankNode = createResource( "http://www.w3.org/ns/shacl#BlankNode" ); - - /**

The node kind of all blank nodes or IRIs.

*/ - public static final Node BlankNodeOrIRI = createResource( "http://www.w3.org/ns/shacl#BlankNodeOrIRI" ); - - /**

The node kind of all blank nodes or literals.

*/ - public static final Node BlankNodeOrLiteral = createResource( "http://www.w3.org/ns/shacl#BlankNodeOrLiteral" ); - /**

A constraint component that can be used to verify that each value node is * an instance of a given type.

*/ @@ -499,12 +524,6 @@ public class SHACL { public static final Node HasValueConstraintComponent_hasValue = createResource( "http://www.w3.org/ns/shacl#HasValueConstraintComponent-hasValue" ); - /**

The node kind of all IRIs.

*/ - public static final Node IRI = createResource( "http://www.w3.org/ns/shacl#IRI" ); - - /**

The node kind of all IRIs or literals.

*/ - public static final Node IRIOrLiteral = createResource( "http://www.w3.org/ns/shacl#IRIOrLiteral" ); - /**

A constraint component that can be used to exclusively enumerate the permitted * value nodes.

*/ @@ -575,9 +594,6 @@ public class SHACL { public static final Node LessThanOrEqualsConstraintComponent_lessThanOrEquals = createResource( "http://www.w3.org/ns/shacl#LessThanOrEqualsConstraintComponent-lessThanOrEquals" ); - /**

The node kind of all literals.

*/ - public static final Node Literal = createResource( "http://www.w3.org/ns/shacl#Literal" ); - /**

A constraint component that can be used to restrict the maximum number of * value nodes.

*/ @@ -653,6 +669,14 @@ public class SHACL { public static final Node NodeKindConstraintComponent_nodeKind = createResource( "http://www.w3.org/ns/shacl#NodeKindConstraintComponent-nodeKind" ); + public static final Node MemberShapeConstraintComponent = createResource( "http://www.w3.org/ns/shacl#MemberShapeConstraintComponent" ); + + public static final Node ListMinLengthConstraintComponent = createResource( "http://www.w3.org/ns/shacl#ListMinLengthConstraintComponent" ); + + public static final Node ListMaxLengthConstraintComponent = createResource( "http://www.w3.org/ns/shacl#ListMaxLengthConstraintComponent" ); + + public static final Node UniqueMembersConstraintComponent = createResource( "http://www.w3.org/ns/shacl#UniqueMembersConstraintComponent" ); + /**

A node shape is a shape that specifies constraint that need to be met with * respect to focus nodes.

*/