diff --git a/pom.xml b/pom.xml index e95d6798d..52c5217c6 100644 --- a/pom.xml +++ b/pom.xml @@ -43,10 +43,36 @@ shapechange-core - shapechange-ea shapechange-app + + + + ea + + + !skipEa + + + + shapechange-ea + + + + + https://github.com/ShapeChange/ShapeChange scm:git:https://github.com/ShapeChange/ShapeChange.git diff --git a/shapechange-app/pom.xml b/shapechange-app/pom.xml index 9ae55c98d..10d9724cd 100644 --- a/shapechange-app/pom.xml +++ b/shapechange-app/pom.xml @@ -14,11 +14,6 @@ shapechange-core ${project.version} - - ${project.groupId} - shapechange-ea - ${project.version} - org.apache.xmlgraphics fop @@ -67,17 +62,6 @@ *.properties - - ../shapechange-ea/src/main/resources - true - ${project.basedir}/sc-resources - - **/* - - - *.properties - - @@ -413,4 +397,46 @@ + + + + ea + + + !skipEa + + + + + ${project.groupId} + shapechange-ea + ${project.version} + + + + + + ../shapechange-ea/src/main/resources + true + ${project.basedir}/sc-resources + + **/* + + + *.properties + + + + + + + \ No newline at end of file diff --git a/shapechange-app/src/integrationtests/owl/qualifiedCardinalityRestrictions/reference/owl/INPUT/test.ttl b/shapechange-app/src/integrationtests/owl/qualifiedCardinalityRestrictions/reference/owl/INPUT/test.ttl new file mode 100644 index 000000000..971919e3b --- /dev/null +++ b/shapechange-app/src/integrationtests/owl/qualifiedCardinalityRestrictions/reference/owl/INPUT/test.ttl @@ -0,0 +1,48 @@ +@prefix ex: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix xsd: . + +ex:Road.width rdf:type owl:DatatypeProperty; + rdfs:domain ex:Road; + rdfs:range xsd:double . + +ex:Road.lane rdf:type owl:ObjectProperty; + rdfs:domain ex:Road; + rdfs:range ex:Lane . + +ex:Road.length rdf:type owl:DatatypeProperty; + rdfs:domain ex:Road; + rdfs:range xsd:double . + +ex:Road rdf:type owl:Class; + rdfs:subClassOf [ rdf:type owl:Restriction; + owl:onClass ex:Lane; + owl:onProperty ex:Road.lane; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger + ]; + rdfs:subClassOf [ rdf:type owl:Restriction; + owl:maxQualifiedCardinality "1"^^xsd:nonNegativeInteger; + owl:onDataRange xsd:integer; + owl:onProperty ex:Road.speedLimit + ]; + rdfs:subClassOf [ rdf:type owl:Restriction; + owl:minQualifiedCardinality "1"^^xsd:nonNegativeInteger; + owl:onDataRange xsd:double; + owl:onProperty ex:Road.width + ]; + rdfs:subClassOf [ rdf:type owl:Restriction; + owl:onDataRange xsd:double; + owl:onProperty ex:Road.length; + owl:qualifiedCardinality "1"^^xsd:nonNegativeInteger + ] . + +ex:Lane rdf:type owl:Class . + +ex:Road.speedLimit rdf:type owl:DatatypeProperty; + rdfs:domain ex:Road; + rdfs:range xsd:integer . + + + rdf:type owl:Ontology . diff --git a/shapechange-app/src/integrationtests/owl/qualifiedCardinalityRestrictions/testEA_owl_qualifiedCardinalityRestrictions.xml b/shapechange-app/src/integrationtests/owl/qualifiedCardinalityRestrictions/testEA_owl_qualifiedCardinalityRestrictions.xml new file mode 100644 index 000000000..a616c1165 --- /dev/null +++ b/shapechange-app/src/integrationtests/owl/qualifiedCardinalityRestrictions/testEA_owl_qualifiedCardinalityRestrictions.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/shapechange-app/src/integrationtests/owl/qualifiedCardinalityRestrictions/testEA_owl_qualifiedCardinalityRestrictions_runWithSCXML.xml b/shapechange-app/src/integrationtests/owl/qualifiedCardinalityRestrictions/testEA_owl_qualifiedCardinalityRestrictions_runWithSCXML.xml new file mode 100644 index 000000000..bcfb0a3a0 --- /dev/null +++ b/shapechange-app/src/integrationtests/owl/qualifiedCardinalityRestrictions/testEA_owl_qualifiedCardinalityRestrictions_runWithSCXML.xml @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/shapechange-app/src/integrationtests/owl/qualifiedCardinalityRestrictions/test_qualifiedCardinalityRestrictions.qea b/shapechange-app/src/integrationtests/owl/qualifiedCardinalityRestrictions/test_qualifiedCardinalityRestrictions.qea new file mode 100644 index 000000000..2fb781acf Binary files /dev/null and b/shapechange-app/src/integrationtests/owl/qualifiedCardinalityRestrictions/test_qualifiedCardinalityRestrictions.qea differ diff --git a/shapechange-app/src/integrationtests/owl/qualifiedCardinalityRestrictions/test_qualifiedCardinalityRestrictions.zip b/shapechange-app/src/integrationtests/owl/qualifiedCardinalityRestrictions/test_qualifiedCardinalityRestrictions.zip new file mode 100644 index 000000000..c3d4b3a5e Binary files /dev/null and b/shapechange-app/src/integrationtests/owl/qualifiedCardinalityRestrictions/test_qualifiedCardinalityRestrictions.zip differ diff --git a/shapechange-app/src/test/java/de/interactive_instruments/shapechange/app/BasicTest.java b/shapechange-app/src/test/java/de/interactive_instruments/shapechange/app/BasicTest.java index bc7259724..1586e0e35 100644 --- a/shapechange-app/src/test/java/de/interactive_instruments/shapechange/app/BasicTest.java +++ b/shapechange-app/src/test/java/de/interactive_instruments/shapechange/app/BasicTest.java @@ -90,7 +90,6 @@ import de.interactive_instruments.shapechange.core.util.ExternalCallException; import de.interactive_instruments.shapechange.core.util.ExternalCallUtil; import de.interactive_instruments.shapechange.core.util.ZipHandler; -import de.interactive_instruments.shapechange.ea.util.EAModelDiff; /** * Basic unit test for ShapeChange @@ -642,6 +641,9 @@ private void similarYaml(String fileName, String referenceFileName) { } } + /** EA repository comparison helper, resolved reflectively; see similarEaRepo. */ + private static final String EA_MODEL_DIFF_CLASSNAME = "de.interactive_instruments.shapechange.ea.util.EAModelDiff"; + private void similarEaRepo(String fileName, String referenceFileName) { try { @@ -656,12 +658,28 @@ private void similarEaRepo(String fileName, String referenceFileName) { fail("Reference file " + referenceFile.getAbsolutePath() + " does not exist."); } - EAModelDiff differ = new EAModelDiff(); + /* + * Loaded by name, exactly as DefaultModelProvider loads EA model readers, so that + * these test sources compile and run without the shapechange-ea module on the + * classpath. Only .qea comparisons need it; every other assertion in this class is + * EA-independent. + */ + Object differ; + try { + differ = Class.forName(EA_MODEL_DIFF_CLASSNAME).getConstructor().newInstance(); + } catch (ClassNotFoundException e) { + fail("Comparing EA repositories requires the shapechange-ea module, which is not on" + + " the classpath. Build with EA support (omit -DskipEa) to run this test." + + " Result file: " + fileName); + return; + } - boolean similar = differ.similar(file, referenceFile); + boolean similar = (Boolean) differ.getClass().getMethod("similar", File.class, File.class) + .invoke(differ, file, referenceFile); + String diffDetails = (String) differ.getClass().getMethod("getDiffDetails").invoke(differ); assertTrue(similar, "EA repository output differs from reference result. Result file: " + fileName - + " - Reference file: " + referenceFileName + ". Details:\n" + differ.getDiffDetails()); + + " - Reference file: " + referenceFileName + ". Details:\n" + diffDetails); } catch (Exception e) { fail("Exception while comparing EA repository '" + fileName + "' to reference file '" + referenceFileName diff --git a/shapechange-app/src/test/java/de/interactive_instruments/shapechange/app/OntologyTest.java b/shapechange-app/src/test/java/de/interactive_instruments/shapechange/app/OntologyTest.java index f1232acc5..2c2a6e0ee 100644 --- a/shapechange-app/src/test/java/de/interactive_instruments/shapechange/app/OntologyTest.java +++ b/shapechange-app/src/test/java/de/interactive_instruments/shapechange/app/OntologyTest.java @@ -94,4 +94,26 @@ public void testPropertyGeneralizationAndEnrichment() { new String[] { "ttl" }, "testResults/owl/propertyGeneralizationAndEnrichment/owl", "src/integrationtests/owl/propertyGeneralizationAndEnrichment/reference/owl"); } + + @Test + public void testQualifiedCardinalityRestrictions() { + /* + * rule-owl-prop-multiplicityAsQualifiedCardinalityRestriction: qualified + * cardinality restrictions on datatype properties must use owl:onDataRange + * (W3C OWL 2 Structural Specification, Sec. 8.5; Mapping to RDF Graphs, + * Sec. 3.2), while restrictions on object properties use owl:onClass + * (Sec. 8.3). + * + * The original model is the Enterprise Architect repository + * test_qualifiedCardinalityRestrictions.qea; the SCXML based resources + * (test_qualifiedCardinalityRestrictions.zip and the *_runWithSCXML config) + * are derived from it via the standard mechanism (run the test with system + * variable updateOrCreateScxmlResources=true), so the test executes without + * Enterprise Architect on 64bit Java. + */ + multiTest( + "src/integrationtests/owl/qualifiedCardinalityRestrictions/testEA_owl_qualifiedCardinalityRestrictions.xml", + new String[] { "ttl" }, "testResults/owl/qualifiedCardinalityRestrictions", + "src/integrationtests/owl/qualifiedCardinalityRestrictions/reference"); + } } diff --git a/shapechange-core/src/main/java/de/interactive_instruments/shapechange/core/target/ontology/OntologyModel.java b/shapechange-core/src/main/java/de/interactive_instruments/shapechange/core/target/ontology/OntologyModel.java index 12d5ad84e..591dac880 100644 --- a/shapechange-core/src/main/java/de/interactive_instruments/shapechange/core/target/ontology/OntologyModel.java +++ b/shapechange-core/src/main/java/de/interactive_instruments/shapechange/core/target/ontology/OntologyModel.java @@ -1112,6 +1112,25 @@ protected void addUnionSemantics(ClassInfo ci, OntClass c) { } } + /** + * Determines the predicate that links a qualified cardinality restriction to + * its restricting range. + * + * Per the W3C OWL 2 specification, qualified cardinality restrictions on a + * data property MUST use owl:onDataRange with a data range (Structural + * Specification and Functional-Style Syntax, Sec. 8.5 "Data Property + * Cardinality Restrictions"; Mapping to RDF Graphs, Sec. 3.2), whereas + * owl:onClass applies only to object property cardinality restrictions + * (Sec. 8.3). Emitting owl:onClass with an XSD datatype (e.g. xsd:double) + * yields an invalid OWL 2 DL construct. + * + * @param p the property the restriction applies to + * @return OWL2.onDataRange for datatype properties, otherwise OWL2.onClass + */ + protected Property qualifiedRestrictionOnPredicate(OntProperty p) { + return (p != null && p.isDatatypeProperty()) ? OWL2.onDataRange : OWL2.onClass; + } + public OntClass createQCardinalityRestriction(OntProperty p, int cardinality, Resource range) { /* * NOTE: We cannot use the create(Min|Max)CardinalityQRestriction(...) methods @@ -1131,7 +1150,7 @@ public OntClass createQCardinalityRestriction(OntProperty p, int cardinality, Re Literal cardAsNonNegativeInteger = ontmodel.createTypedLiteral(cardinality, "http://www.w3.org/2001/XMLSchema#nonNegativeInteger"); restriction.addLiteral(OWL2.qualifiedCardinality, cardAsNonNegativeInteger); - restriction.addProperty(OWL2.onClass, range); + restriction.addProperty(qualifiedRestrictionOnPredicate(p), range); return restriction; } @@ -1155,7 +1174,7 @@ public OntClass createQMinCardinalityRestriction(OntProperty p, int cardinality, Literal cardAsNonNegativeInteger = ontmodel.createTypedLiteral(cardinality, "http://www.w3.org/2001/XMLSchema#nonNegativeInteger"); restriction.addLiteral(OWL2.minQualifiedCardinality, cardAsNonNegativeInteger); - restriction.addProperty(OWL2.onClass, range); + restriction.addProperty(qualifiedRestrictionOnPredicate(p), range); return restriction; } @@ -1179,7 +1198,7 @@ public OntClass createQMaxCardinalityRestriction(OntProperty p, int cardinality, Literal cardAsNonNegativeInteger = ontmodel.createTypedLiteral(cardinality, "http://www.w3.org/2001/XMLSchema#nonNegativeInteger"); restriction.addLiteral(OWL2.maxQualifiedCardinality, cardAsNonNegativeInteger); - restriction.addProperty(OWL2.onClass, range); + restriction.addProperty(qualifiedRestrictionOnPredicate(p), range); return restriction; }