Skip to content

Commit

Permalink
refactor: moved parser logic to concrete JenaParser class
Browse files Browse the repository at this point in the history
  • Loading branch information
jenspots committed Jul 12, 2024
1 parent a421d03 commit 6954b4e
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 87 deletions.
2 changes: 1 addition & 1 deletion src/main/kotlin/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ fun main(args: Array<String>) = runBlocking {
val file = File(path)

// Parse said config to a IRPipeline.
val parser = Parser(file)
val parser = Parser.using(file)

// Parse the pipeline out of the configuration file.
val pipeline = parser.pipelines[0]
Expand Down
25 changes: 25 additions & 0 deletions src/main/kotlin/extensions/Model.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package technology.idlab.extensions

import java.io.ByteArrayOutputStream
import org.apache.jena.rdf.model.Model
import org.apache.jena.rdf.model.Property
import org.apache.jena.rdf.model.RDFNode
import org.apache.jena.rdf.model.Resource
import org.apache.jena.shacl.ShaclValidator
import technology.idlab.util.Log

Expand All @@ -19,3 +22,25 @@ internal fun Model.validate(): Model {

return this
}

/**
* Return the first object which corresponds to a subject and predicate. Returns null if not found.
*/
internal fun Model.objectOfProperty(resource: Resource, property: Property): RDFNode? {
return try {
this.listObjectsOfProperty(resource, property).next()
} catch (e: NoSuchElementException) {
null
}
}

/**
* Return the first subject which corresponds to a predicate and object. Returns null if not found.
*/
internal fun Model.subjectWithProperty(property: Property, obj: RDFNode): Resource? {
return try {
this.listSubjectsWithProperty(property, obj).next()
} catch (e: NoSuchElementException) {
null
}
}
69 changes: 8 additions & 61 deletions src/main/kotlin/parser/Parser.kt
Original file line number Diff line number Diff line change
@@ -1,81 +1,28 @@
package technology.idlab.parser

import java.io.File
import org.apache.jena.ontology.OntModelSpec
import org.apache.jena.rdf.model.Model
import org.apache.jena.rdf.model.ModelFactory
import org.apache.jena.rdf.model.Resource
import technology.idlab.extensions.validate
import technology.idlab.parser.impl.parseDependencies
import technology.idlab.parser.impl.parsePackages
import technology.idlab.parser.impl.parsePipelines
import technology.idlab.parser.intermediate.IRDependency
import technology.idlab.parser.impl.JenaParser
import technology.idlab.parser.intermediate.IRPackage
import technology.idlab.parser.intermediate.IRPipeline
import technology.idlab.parser.intermediate.IRProcessor
import technology.idlab.resolver.Resolver

/**
* Parse an RDF file into an intermediate representation, and validate it against the ontology and
* SHACL shapes.
*/
class Parser(file: File) {
/** The Apache Jena model. */
private val model: Model = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM)

abstract class Parser(file: File) {
/** The pipelines in the current configuration. */
val pipelines: List<IRPipeline>
abstract val pipelines: List<IRPipeline>

/** The packages in the current configuration. */
val packages: List<IRPackage>
abstract val packages: List<IRPackage>

/** List of all known processors. */
val processors: List<IRProcessor>

init {
// Load the RDF-Connect ontology.
val resource = this::class.java.getResource("/pipeline.ttl")
val config = resource!!.path!!
this.load(config)

// Load the pipeline file into the parser.
this.load(file.path)

// Retrieve dependencies.
val dependencies = this.dependencies()
abstract val processors: List<IRProcessor>

// Resolve all dependencies.
dependencies.forEach {
val path = Resolver.resolve(it)
this.load(path.toString())
companion object {
fun using(file: File): Parser {
return JenaParser(file)
}

// Since we updated the model, we will once again check if the SHACL shapes are valid.
this.model.validate()

// Parse the file.
this.pipelines = this.pipelines()
this.packages = this.packages()
this.processors = this.packages.map { it.processors }.flatten()
}

/** Parse the file as a list of pipelines, returning its containing stages and dependencies. */
private fun pipelines(): List<IRPipeline> {
return model.parsePipelines()
}

/** Parse the model as a list of packages, returning the provided processors inside. */
private fun packages(): List<IRPackage> {
return model.parsePackages()
}

/** Retrieve all dependencies in a given file. */
private fun dependencies(): List<IRDependency> {
return model.parseDependencies(null as Resource?)
}

/** Load an additional file into the parser. */
private fun load(path: String) {
this.model.read(path, "TURTLE")
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
package technology.idlab.parser.impl

import java.io.File
import org.apache.jena.ontology.OntModelSpec
import org.apache.jena.rdf.model.Model
import org.apache.jena.rdf.model.Property
import org.apache.jena.rdf.model.ModelFactory
import org.apache.jena.rdf.model.RDFNode
import org.apache.jena.rdf.model.Resource
import org.apache.jena.shacl.vocabulary.SHACLM
import org.apache.jena.vocabulary.RDF
import runner.Runner
import technology.idlab.extensions.objectOfProperty
import technology.idlab.extensions.subjectWithProperty
import technology.idlab.extensions.validate
import technology.idlab.parser.Parser
import technology.idlab.parser.RDFC
import technology.idlab.parser.intermediate.IRArgument
import technology.idlab.parser.intermediate.IRDependency
Expand All @@ -15,6 +21,7 @@ import technology.idlab.parser.intermediate.IRParameter
import technology.idlab.parser.intermediate.IRPipeline
import technology.idlab.parser.intermediate.IRProcessor
import technology.idlab.parser.intermediate.IRStage
import technology.idlab.resolver.Resolver
import technology.idlab.util.Log

internal fun Resource.toRunnerTarget(): Runner.Target {
Expand Down Expand Up @@ -44,28 +51,6 @@ internal fun Resource.toIRParameterType(): IRParameter.Type {
}
}

/**
* Return the first object which corresponds to a subject and predicate. Returns null if not found.
*/
internal fun Model.objectOfProperty(resource: Resource, property: Property): RDFNode? {
return try {
this.listObjectsOfProperty(resource, property).next()
} catch (e: NoSuchElementException) {
null
}
}

/**
* Return the first subject which corresponds to a predicate and object. Returns null if not found.
*/
internal fun Model.subjectWithProperty(property: Property, obj: RDFNode): Resource? {
return try {
this.listSubjectsWithProperty(property, obj).next()
} catch (e: NoSuchElementException) {
null
}
}

/**
* Create a mapping of String to IRParameter from a SHACL property. This is a recursive
* implementation that will automatically parse nested classes.
Expand Down Expand Up @@ -258,3 +243,64 @@ internal fun Model.parsePipeline(pipeline: Resource): IRPipeline {
internal fun Model.parsePipelines(): List<IRPipeline> {
return listSubjectsWithProperty(RDF.type, RDFC.pipeline).toList().map { parsePipeline(it) }
}

class JenaParser(file: File) : Parser(file) {
/** The Apache Jena model. */
private val model: Model = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM)

/** The pipelines in the current configuration. */
override val pipelines: List<IRPipeline>

/** The packages in the current configuration. */
override val packages: List<IRPackage>

/** List of all known processors. */
override val processors: List<IRProcessor>

init {
// Load the RDF-Connect ontology.
val resource = this::class.java.getResource("/pipeline.ttl")
val config = resource!!.path!!
this.load(config)

// Load the pipeline file into the parser.
this.load(file.path)

// Retrieve dependencies.
val dependencies = this.dependencies()

// Resolve all dependencies.
dependencies.forEach {
val path = Resolver.resolve(it)
this.load(path.toString())
}

// Since we updated the model, we will once again check if the SHACL shapes are valid.
this.model.validate()

// Parse the file.
this.pipelines = this.pipelines()
this.packages = this.packages()
this.processors = this.packages.map { it.processors }.flatten()
}

/** Parse the file as a list of pipelines, returning its containing stages and dependencies. */
private fun pipelines(): List<IRPipeline> {
return model.parsePipelines()
}

/** Parse the model as a list of packages, returning the provided processors inside. */
private fun packages(): List<IRPackage> {
return model.parsePackages()
}

/** Retrieve all dependencies in a given file. */
private fun dependencies(): List<IRDependency> {
return model.parseDependencies(null as Resource?)
}

/** Load an additional file into the parser. */
private fun load(path: String) {
this.model.read(path, "TURTLE")
}
}
4 changes: 2 additions & 2 deletions src/test/kotlin/parser/ParserTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class ParserTest {
private fun parse(resource: String): Parser {
val uri = this::class.java.getResource(resource)
val file = File(uri!!.toURI())
return Parser(file)
return Parser.using(file)
}

@Test
Expand Down Expand Up @@ -102,7 +102,7 @@ class ParserTest {
fun stages() {
val uri = this::class.java.getResource("/pipelines/basic/index.ttl")
val file = File(uri!!.toURI())
val parser = Parser(file)
val parser = Parser.using(file)

val stages = parser.pipelines[0].stages

Expand Down

0 comments on commit 6954b4e

Please sign in to comment.