Skip to content

Commit 55041d4

Browse files
Feature/11.1 var reference single (#26)
* Refactor URI responsibility onto Container and Var interfaces * Add NetCDF reference path concept and impl * Add method to alias interface to determine whether a property is a reference * Add variable reference vocab * Add Model context concept combining prefix mappings and aliases Move alias definition class to context package * Refactor attribute URI responsibility onto interface Refactor binary array impl to incorporate model context * Misc changes * Remove unused classes * Add integration test * Restore tests for refactored model context functionality * Add test for var reference functionality on netcdf impl Add binary array test utils * Refactor existing tests to use test utils * Add missing comments * Test and fix circular reference error * Replace subject with resource
1 parent 8d65e91 commit 55041d4

File tree

27 files changed

+756
-105
lines changed

27 files changed

+756
-105
lines changed

binary-array-ld-cli/src/test/kotlin/net/bald/BinaryArrayConvertCliTest.kt

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package net.bald
22

3+
import bald.TestVocab
34
import bald.jsonld.ResourceFileConverter
45
import bald.model.ModelVerifier
56
import bald.netcdf.CdlConverter.writeToNetCdf
@@ -331,4 +332,66 @@ class BinaryArrayConvertCliTest {
331332
}
332333
}
333334
}
335+
336+
/**
337+
* Requirements class E-1, E-2
338+
*/
339+
@Test
340+
fun run_withVariableReferenceAttributes_outputsVariableReferences() {
341+
val inputFile = writeToNetCdf("/netcdf/var-ref.cdl")
342+
val outputFile = createTempFile()
343+
val contextFile = ResourceFileConverter.toFile("/jsonld/context.json")
344+
val aliasFile = ResourceFileConverter.toFile("/turtle/var-alias.ttl", "ttl")
345+
346+
run(
347+
"--uri", "http://test.binary-array-ld.net/example",
348+
"--context", contextFile.absolutePath,
349+
"--alias", aliasFile.absolutePath,
350+
inputFile.absolutePath,
351+
outputFile.absolutePath
352+
)
353+
354+
val model = createDefaultModel().read(outputFile.toURI().toString(), "ttl")
355+
ModelVerifier(model).apply {
356+
prefix("bald", BALD.prefix)
357+
prefix("skos", SKOS.uri)
358+
prefix("dct", DCTerms.NS)
359+
resource("http://test.binary-array-ld.net/example") {
360+
statement(RDF.type, BALD.Container)
361+
statement(BALD.contains, createResource("http://test.binary-array-ld.net/example/")) {
362+
statement(TestVocab.rootVar, createResource("http://test.binary-array-ld.net/example/var0"))
363+
statement(RDF.type, BALD.Container)
364+
statement(SKOS.prefLabel, createPlainLiteral("Variable reference metadata example"))
365+
statement(BALD.contains, createResource("http://test.binary-array-ld.net/example/baz")) {
366+
statement(RDF.type, BALD.Container)
367+
statement(BALD.contains, createResource("http://test.binary-array-ld.net/example/baz/var3")) {
368+
statement(RDF.type, BALD.Resource)
369+
}
370+
}
371+
statement(BALD.contains, createResource("http://test.binary-array-ld.net/example/foo")) {
372+
statement(TestVocab.rootVar, createResource("http://test.binary-array-ld.net/example/var0"))
373+
statement(TestVocab.siblingVar, createResource("http://test.binary-array-ld.net/example/baz/var3"))
374+
statement(RDF.type, BALD.Container)
375+
statement(BALD.contains, createResource("http://test.binary-array-ld.net/example/foo/bar")) {
376+
statement(RDF.type, BALD.Container)
377+
statement(BALD.contains, createResource("http://test.binary-array-ld.net/example/foo/bar/var2")) {
378+
statement(TestVocab.parentVar, createResource("http://test.binary-array-ld.net/example/foo/var1"))
379+
statement(RDF.type, BALD.Resource)
380+
statement(SKOS.prefLabel, createPlainLiteral("var2"))
381+
}
382+
}
383+
statement(BALD.contains, createResource("http://test.binary-array-ld.net/example/foo/var1")) {
384+
statement(TestVocab.siblingVar, createResource("http://test.binary-array-ld.net/example/foo/bar/var2"))
385+
statement(RDF.type, BALD.Resource)
386+
statement(BALD.references, createPlainLiteral("var9"))
387+
}
388+
}
389+
statement(BALD.contains, createResource("http://test.binary-array-ld.net/example/var0")) {
390+
statement(RDF.type, BALD.Resource)
391+
}
392+
statement(BALD.isPrefixedBy, createPlainLiteral("prefix_list"))
393+
}
394+
}
395+
}
396+
}
334397
}

binary-array-ld-demo/src/main/java/net/bald/NetCdfConvertJava.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import net.bald.context.AliasDefinition;
44
import net.bald.context.ModelContext;
55
import net.bald.model.ModelAliasDefinition;
6-
import net.bald.context.ContextBinaryArray;
76
import net.bald.model.ModelBinaryArrayConverter;
87
import net.bald.netcdf.NetCdfBinaryArray;
98
import org.apache.jena.rdf.model.Model;

binary-array-ld-lib/src/main/kotlin/net/bald/context/AliasDefinition.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ interface AliasDefinition {
2121
*/
2222
fun resource(identifier: String): Resource?
2323

24+
/**
25+
* Determine whether the given property represents a reference to another variable.
26+
* The property may be obtained from [property] or from another model (or no model).
27+
* @param prop The property to check.
28+
* @return True if the property is a reference to another variable. Otherwise, false.
29+
*/
30+
fun isReferenceProperty(prop: Property): Boolean
31+
2432
object Empty: AliasDefinition {
2533
override fun property(identifier: String): Property? {
2634
return null
@@ -29,5 +37,9 @@ interface AliasDefinition {
2937
override fun resource(identifier: String): Resource? {
3038
return null
3139
}
40+
41+
override fun isReferenceProperty(prop: Property): Boolean {
42+
return false
43+
}
3244
}
3345
}

binary-array-ld-lib/src/main/kotlin/net/bald/context/ModelContext.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ interface ModelContext: AliasDefinition {
2323
override val prefixMapping: PrefixMapping get() = PrefixMapping.Factory.create()
2424
override fun property(identifier: String): Property? = null
2525
override fun resource(identifier: String): Resource? = null
26+
override fun isReferenceProperty(prop: Property): Boolean = false
2627
}
2728

2829
/**
@@ -39,6 +40,10 @@ interface ModelContext: AliasDefinition {
3940
override fun resource(identifier: String): Resource? {
4041
return alias.resource(identifier)
4142
}
43+
44+
override fun isReferenceProperty(prop: Property): Boolean {
45+
return alias.isReferenceProperty(prop)
46+
}
4247
}
4348

4449
companion object {

binary-array-ld-lib/src/main/kotlin/net/bald/model/ModelAliasDefinition.kt

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
package net.bald.model
22

33
import net.bald.context.AliasDefinition
4-
import org.apache.jena.rdf.model.Model
5-
import org.apache.jena.rdf.model.Property
6-
import org.apache.jena.rdf.model.Resource
7-
import org.apache.jena.rdf.model.ResourceFactory.createProperty
4+
import net.bald.vocab.BALD
5+
import org.apache.jena.rdf.model.*
86
import org.apache.jena.vocabulary.DCTerms
97
import org.apache.jena.vocabulary.OWL
108
import org.apache.jena.vocabulary.RDF
9+
import org.apache.jena.vocabulary.RDFS
1110

1211
/**
1312
* Implementation of [AliasDefinition] that derives aliases from an RDF graph ([Model]).
@@ -16,14 +15,12 @@ class ModelAliasDefinition(
1615
private val model: Model
1716
): AliasDefinition {
1817
override fun property(identifier: String): Property? {
19-
val resources = identifyResources(identifier)
20-
val props = resources.filter { resource ->
21-
resource.hasProperty(RDF.type, RDF.Property) || resource.hasProperty(RDF.type, OWL.ObjectProperty)
22-
}.toList()
18+
val props = identifyProperties(identifier).toList()
2319

24-
return if (props.isEmpty()) null else {
25-
props.singleOrNull()?.uri?.let(::createProperty)
26-
?: throw IllegalStateException("Property alias $identifier is ambiguous: $props")
20+
return when (props.size) {
21+
0 -> null
22+
1 -> props.single().uri.let(model::createProperty)
23+
else -> throw IllegalStateException("Property alias $identifier is ambiguous: $props")
2724
}
2825
}
2926

@@ -35,10 +32,44 @@ class ModelAliasDefinition(
3532
}
3633
}
3734

35+
private fun identifyProperties(identifier: String): Sequence<Resource> {
36+
return identifyResources(identifier).filter { resource ->
37+
resource.hasProperty(RDF.type, RDF.Property) || resource.hasProperty(RDF.type, OWL.ObjectProperty)
38+
}
39+
}
40+
3841
private fun identifyResources(identifier: String): Sequence<Resource> {
3942
return model.listResourcesWithProperty(DCTerms.identifier, identifier).asSequence()
4043
}
4144

45+
override fun isReferenceProperty(prop: Property): Boolean {
46+
return prop.inModel(model).let { modelProp ->
47+
modelProp.hasProperty(RDFS.range, BALD.Resource)
48+
|| modelProp.listProperties(RDFS.range).let(::containsReferenceCls)
49+
}
50+
}
51+
52+
private fun containsReferenceCls(stmts: StmtIterator, clsUris: Set<String> = emptySet()): Boolean {
53+
return stmts.asSequence()
54+
.map(Statement::getObject)
55+
.filter(RDFNode::isResource)
56+
.map(RDFNode::asResource)
57+
.any { cls -> isReferenceCls(cls, clsUris) }
58+
}
59+
60+
private fun isReferenceCls(cls: Resource, clsUris: Set<String>): Boolean {
61+
return when {
62+
cls.hasProperty(RDFS.subClassOf, BALD.Resource) -> true
63+
clsUris.contains(cls.uri) -> false
64+
else -> {
65+
val nextClsUris = cls.uri?.let(clsUris::plus) ?: clsUris
66+
cls.listProperties(RDFS.subClassOf).let { parents ->
67+
containsReferenceCls(parents, nextClsUris)
68+
}
69+
}
70+
}
71+
}
72+
4273
companion object {
4374
/**
4475
* Instantiate an alias definition based on a given RDF model.

binary-array-ld-lib/src/main/kotlin/net/bald/vocab/BALD.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,5 @@ object BALD {
2525
*/
2626
val contains: Property = createProperty("${prefix}contains")
2727
val isPrefixedBy: Property = createProperty("${prefix}isPrefixedBy")
28+
val references: Property = createProperty("${prefix}references")
2829
}

binary-array-ld-lib/src/test/kotlin/net/bald/model/ModelAliasDefinitionTest.kt

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@ package net.bald.model
22

33
import net.bald.vocab.BALD
44
import org.apache.jena.rdf.model.ModelFactory
5+
import org.apache.jena.rdf.model.ResourceFactory.createProperty
56
import org.apache.jena.vocabulary.DCTerms
67
import org.apache.jena.vocabulary.RDFS
78
import org.apache.jena.vocabulary.SKOS
89
import org.junit.jupiter.api.Test
910
import org.junit.jupiter.api.assertThrows
1011
import java.lang.IllegalStateException
1112
import kotlin.test.assertEquals
13+
import kotlin.test.assertFalse
1214
import kotlin.test.assertNull
15+
import kotlin.test.assertTrue
1316

1417
class ModelAliasDefinitionTest {
1518
private val model = ModelFactory.createDefaultModel().apply {
@@ -35,7 +38,7 @@ class ModelAliasDefinitionTest {
3538
* Requirements class C-3
3639
*/
3740
@Test
38-
fun propertyUri_propertyAliasDefined_returnsUri() {
41+
fun propertyUri_propertyAliasDefined_returnsProperty() {
3942
val result = alias.property("name")
4043
assertEquals(RDFS.label, result)
4144
}
@@ -44,11 +47,54 @@ class ModelAliasDefinitionTest {
4447
* Requirements class C-3
4548
*/
4649
@Test
47-
fun propertyUri_objectPropertyAliasDefined_returnsUri() {
50+
fun propertyUri_objectPropertyAliasDefined_returnsProperty() {
4851
val result = alias.property("dct_publisher")
4952
assertEquals(DCTerms.publisher, result)
5053
}
5154

55+
@Test
56+
fun isReferenceProperty_withoutDefinition_returnsFalse() {
57+
val result = alias.isReferenceProperty(SKOS.broader)
58+
assertFalse(result)
59+
}
60+
61+
@Test
62+
fun isReferenceProperty_withDefinition_withoutSubjectRange_returnsFalse() {
63+
val result = alias.isReferenceProperty(DCTerms.publisher)
64+
assertFalse(result)
65+
}
66+
67+
@Test
68+
fun isReferenceProperty_withSubjectRange_returnsTrue() {
69+
val result = alias.isReferenceProperty(BALD.references)
70+
assertTrue(result)
71+
}
72+
73+
@Test
74+
fun isReferenceProperty_withSubjectDirectSubClassRange_returnsTrue() {
75+
val prop = createProperty("http://test.binary-array-ld.net/vocab/direct")
76+
val result = alias.isReferenceProperty(prop)
77+
assertTrue(result)
78+
}
79+
80+
@Test
81+
fun isReferenceProperty_withSubjectIndirectSubClassRange_returnsTrue() {
82+
val prop = createProperty("http://test.binary-array-ld.net/vocab/indirect")
83+
val result = alias.isReferenceProperty(prop)
84+
assertTrue(result)
85+
}
86+
87+
@Test
88+
fun isReferenceProperty_circularClassHierarchy_returnsFalse() {
89+
val model = ModelFactory.createDefaultModel().apply {
90+
javaClass.getResourceAsStream("/alias/circular.ttl").use { ttl ->
91+
read(ttl, null, "ttl")
92+
}
93+
}
94+
val alias = ModelAliasDefinition.create(model)
95+
assertEquals(false, alias.isReferenceProperty(createProperty("http://test.binary-array-ld.net/vocab/direct")))
96+
}
97+
5298
/**
5399
* Requirements class C-2
54100
*/

binary-array-ld-lib/src/test/resources/alias/alias.ttl

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
@prefix owl: <http://www.w3.org/2002/07/owl#>
55
@prefix skos: <http://www.w3.org/2004/02/skos/core#>
66
@prefix bald: <https://www.opengis.net/def/binary-array-ld/>
7+
@prefix test: <http://test.binary-array-ld.net/vocab/>
78

89
dct:publisher a owl:ObjectProperty ;
910
dct:identifier "dct_publisher" .
@@ -14,5 +15,19 @@ rdfs:label a rdf:Property ;
1415
skos:prefLabel a rdf:Property ;
1516
dct:identifier "prefLabel" .
1617

18+
bald:references a rdf:Property ;
19+
dct:identifier "references" ;
20+
rdfs:range bald:Resource .
21+
1722
bald:Organisation dct:identifier "binary-array-ld-org" .
1823

24+
test:Subject rdfs:subClassOf bald:Resource .
25+
26+
test:direct a rdf:Property ;
27+
dct:identifier "test_direct" ;
28+
rdfs:range test:Subject .
29+
30+
test:indirect a rdf:Property ;
31+
dct:identifier "test_indirect" ;
32+
rdfs:range [ rdfs:subClassOf test:Subject ] .
33+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
2+
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
3+
@prefix dct: <http://purl.org/dc/terms/>
4+
@prefix owl: <http://www.w3.org/2002/07/owl#>
5+
@prefix skos: <http://www.w3.org/2004/02/skos/core#>
6+
@prefix bald: <https://www.opengis.net/def/binary-array-ld/>
7+
@prefix test: <http://test.binary-array-ld.net/vocab/>
8+
9+
test:Subject rdfs:subClassOf [ rdfs:subClassOf test:Subject ] .
10+
11+
test:direct a rdf:Property ;
12+
dct:identifier "test_direct" ;
13+
rdfs:range test:Subject .

binary-array-ld-netcdf/src/main/kotlin/net/bald/netcdf/NetCdfAttribute.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class NetCdfAttribute(
3333
}
3434

3535
private fun node(value: String): RDFNode {
36-
return parent.parseRdfNode(value)
36+
return parent.parseRdfNode(prop, value)
3737
}
3838

3939
override fun toString(): String {

0 commit comments

Comments
 (0)