diff --git a/src/main/kotlin/logging/Log.kt b/src/main/kotlin/logging/Log.kt index 825f6f9..0c9fd07 100644 --- a/src/main/kotlin/logging/Log.kt +++ b/src/main/kotlin/logging/Log.kt @@ -64,16 +64,19 @@ class Log private constructor() { fun fatal(message: String): Nothing { print(message, "FATAL") + print(Throwable().stackTraceToString()) exitProcess(1) } fun fatal(exception: Exception): Nothing { print(exception.message.toString(), "FATAL") + print(Throwable().stackTraceToString()) exitProcess(1) } fun fatal(message: String, exception: Exception) { print("$message - ${exception.message}") + print(Throwable().stackTraceToString()) exitProcess(1) } diff --git a/src/main/kotlin/runner/Processor.kt b/src/main/kotlin/runner/Processor.kt index 953ad57..3a5dad8 100644 --- a/src/main/kotlin/runner/Processor.kt +++ b/src/main/kotlin/runner/Processor.kt @@ -21,6 +21,10 @@ abstract class Processor( return result } + fun getNullableArgument(name: String): T? { + return arguments[name] as T? + } + fun getOptionalArgument(name: String): Optional { val result = arguments[name] ?: log.fatal("Argument $name is missing") diff --git a/src/main/kotlin/std/FileReader.kt b/src/main/kotlin/std/FileReader.kt new file mode 100644 index 0000000..bdf8f3d --- /dev/null +++ b/src/main/kotlin/std/FileReader.kt @@ -0,0 +1,19 @@ +package technology.idlab.std + +import bridge.Writer +import java.io.File +import technology.idlab.runner.Processor + +class FileReader(args: Map) : Processor(args) { + /** Arguments */ + private val path: String = this.getArgument("path") + private val output: Writer = this.getArgument("output") + + /** Read the file as a single byte array and push it down the pipeline. */ + override fun exec() { + val file = File(path) + val bytes = file.readBytes() + output.pushSync(bytes) + output.close() + } +} diff --git a/src/main/kotlin/std/FileWriter.kt b/src/main/kotlin/std/FileWriter.kt new file mode 100644 index 0000000..f528a7f --- /dev/null +++ b/src/main/kotlin/std/FileWriter.kt @@ -0,0 +1,48 @@ +package technology.idlab.std + +import bridge.Reader +import java.io.File +import technology.idlab.runner.Processor + +class FileWriter(args: Map) : Processor(args) { + /** Processor default values. */ + private val overwriteDefault = true + private val appendDefault = false + + /** Arguments */ + private val file = File(this.getArgument("path")) + private val input: Reader = this.getArgument("input") + private val overwrite = this.getOptionalArgument("overwrite") + private val append = this.getOptionalArgument("append") + + init { + // Sanity check. + if (overwrite.orElse(false) && append.orElse(false)) { + log.fatal("Cannot overwrite and append at the same time") + } + + // Do not overwrite the file if it exists. + if (file.exists() && !overwrite.orElse(overwriteDefault)) { + log.fatal("File ${file.path} already exists") + } + + // Overwrite file if not exists. + if (file.exists() && !append.orElse(appendDefault)) { + file.writeBytes(ByteArray(0)) + } + } + + /** All incoming values are parsed as byte and appended onto the file. */ + override fun exec() { + while (true) { + // Read the next incoming value. + val result = input.readSync() + if (result.isClosed()) { + break + } + + // Append it to the file. + file.appendBytes(result.value) + } + } +} diff --git a/src/main/resources/std/file_reader.ttl b/src/main/resources/std/file_reader.ttl new file mode 100644 index 0000000..bdc267d --- /dev/null +++ b/src/main/resources/std/file_reader.ttl @@ -0,0 +1,26 @@ +@prefix jvm: . +@prefix owl: . +@prefix sh: . +@prefix xsd: . + +<> owl:imports <../pipeline.ttl>. + +jvm:FileReader a jvm:Processor; + jvm:file <../../kotlin/std/FileReader.kt>; + jvm:language "Kotlin". + +[] a sh:NodeShape; + sh:targetClass jvm:FileReader; + sh:property [ + sh:path jvm:path; + sh:name "path"; + sh:datatype xsd:string; + ], [ + sh:path jvm:output; + sh:name "output"; + sh:class jvm:ChannelWriter; + sh:minCount 1; + sh:maxCount 1; + ]; + sh:closed true; + sh:ignoredProperties (rdf:type). diff --git a/src/main/resources/std/file_writer.ttl b/src/main/resources/std/file_writer.ttl new file mode 100644 index 0000000..2e11ed9 --- /dev/null +++ b/src/main/resources/std/file_writer.ttl @@ -0,0 +1,38 @@ +@prefix jvm: . +@prefix owl: . +@prefix sh: . +@prefix xsd: . + +<> owl:imports <../pipeline.ttl>. + +jvm:FileWriter a jvm:Processor; + jvm:file <../../kotlin/std/FileWriter.kt>; + jvm:language "Kotlin". + +[] a sh:NodeShape; + sh:targetClass jvm:FileWriter; + sh:property [ + sh:path jvm:path; + sh:name "path"; + sh:datatype xsd:string; + ], [ + sh:path jvm:input; + sh:name "input"; + sh:class jvm:ChannelReader; + sh:minCount 1; + sh:maxCount 1; + ], [ + sh:path jvm:overwrite; + sh:name "overwrite"; + sh:datatype xsd:boolean; + sh:minCount 0; + sh:maxCount 1; + ], [ + sh:path jvm:append; + sh:name "append"; + sh:datatype xsd:boolean; + sh:minCount 0; + sh:maxCount 1; + ]; + sh:closed true; + sh:ignoredProperties (rdf:type). diff --git a/src/test/kotlin/std/FileUtilities.kt b/src/test/kotlin/std/FileUtilities.kt new file mode 100644 index 0000000..8da29d0 --- /dev/null +++ b/src/test/kotlin/std/FileUtilities.kt @@ -0,0 +1,54 @@ +package std + +import java.io.File +import java.util.* +import kotlin.concurrent.thread +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlinx.coroutines.channels.Channel +import technology.idlab.bridge.MemoryReader +import technology.idlab.bridge.MemoryWriter +import technology.idlab.std.FileReader +import technology.idlab.std.FileWriter + +class FileUtilities { + @Test + fun e2e() { + val input = File.createTempFile("input", "txt") + val output = File.createTempFile("output", "txt") + + // Write to the input file. + input.writeText("Hello, World!") + + // Configure the FileReader processor. + val channel = Channel(1) + val reader = MemoryReader() + val writer = MemoryWriter() + + reader.setChannel(channel) + writer.setChannel(channel) + + val fileReader = + FileReader( + mapOf( + "path" to input.path, + "output" to writer, + )) + + val fileWriter = + FileWriter( + mapOf( + "path" to output.path, + "input" to reader, + "overwrite" to Optional.of(true), + "append" to Optional.of(false), + )) + + // Execute the FileReader processor. + listOf(fileReader, fileWriter).map { thread { it.exec() } }.forEach { it.join() } + + // Check if the output is correct. + val result = output.readText() + assertEquals("Hello, World!", result) + } +}