diff --git a/base/src/commonMain/kotlin/de/staropensource/engine/base/implementable/PlatformData.kt b/base/src/commonMain/kotlin/de/staropensource/engine/base/implementable/PlatformData.kt new file mode 100644 index 0000000..833b390 --- /dev/null +++ b/base/src/commonMain/kotlin/de/staropensource/engine/base/implementable/PlatformData.kt @@ -0,0 +1,36 @@ +/* + * STAROPENSOURCE ENGINE SOURCE FILE + * Copyright (c) 2024 The StarOpenSource Engine Authors + * Licensed under the GNU General Public License v3. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.staropensource.engine.base.implementable + +/** + * Interface for platform-specific data. + * + * @since v1-alpha10 + */ +interface PlatformData { + /** + * Contains platform-specific data. + * + * **DO NOT VIEW OR MODIFY.** + * + * @since v1-alpha10 + */ + val platformData: MutableMap +} diff --git a/base/src/commonMain/kotlin/de/staropensource/engine/base/implementable/stream/Stream.kt b/base/src/commonMain/kotlin/de/staropensource/engine/base/implementable/stream/Stream.kt index 137a610..fe27045 100644 --- a/base/src/commonMain/kotlin/de/staropensource/engine/base/implementable/stream/Stream.kt +++ b/base/src/commonMain/kotlin/de/staropensource/engine/base/implementable/stream/Stream.kt @@ -19,14 +19,13 @@ package de.staropensource.engine.base.implementable.stream -import de.staropensource.engine.base.Engine.Companion.logger -import de.staropensource.engine.base.annotation.NonKotlinContact import de.staropensource.engine.base.exception.io.IOAccessException -import de.staropensource.engine.base.implementation.stream.JavaReadStream -import de.staropensource.engine.base.implementation.stream.JavaWriteStream -import java.io.IOException -import java.io.InputStream -import java.io.OutputStream +import de.staropensource.engine.base.implementable.PlatformData +import de.staropensource.engine.base.platform.StreamPlatform +import de.staropensource.engine.base.platform.streamPlatform +import de.staropensource.engine.base.platform.streamStandardError +import de.staropensource.engine.base.platform.streamStandardInput +import de.staropensource.engine.base.platform.streamStandardOutput /** * Makes streaming data easy. @@ -40,7 +39,7 @@ import java.io.OutputStream abstract class Stream( val streamMode: StreamMode, val autoFlushAfter: ULong = 100UL, -) : AutoCloseable { +) : AutoCloseable, PlatformData { /** * Companion object of [Stream]. * @@ -54,10 +53,7 @@ abstract class Stream( * @see stdin(3) * @since v1-alpha10 */ - val standardInput: ReadStream = object : JavaReadStream(System.`in`) { - override fun close() = Unit - override fun closeStream() = Unit - } + val standardInput: ReadStream = streamStandardInput() /** * Contains the standard output as a [Stream]. @@ -66,10 +62,7 @@ abstract class Stream( * @see stdout(3) * @since v1-alpha10 */ - val standardOutput: WriteStream = object : JavaWriteStream(System.out) { - override fun close() = Unit - override fun closeStream() = Unit - } + val standardOutput: WriteStream = streamStandardOutput() /** * Contains the standard error as a [Stream]. @@ -77,14 +70,14 @@ abstract class Stream( * @see stderr(3) * @since v1-alpha10 */ - val standardError: WriteStream = object : JavaWriteStream(System.err) { - override fun close() = Unit - override fun closeStream() = Unit - } + val standardError: WriteStream = streamStandardError() } // -----> Properties + override val platformData: MutableMap = mutableMapOf() + private val platform: StreamPlatform = streamPlatform(this) + /** * Indicates whether this stream is closed. * @@ -133,22 +126,6 @@ abstract class Stream( */ val pipes: MutableList = mutableListOf() - /** - * Contains the Java [InputStream] - * for this [Stream] instance. - * - * @since v1-alpha10 - */ - private var inputStream: InputFileStream? = null - - /** - * Contains the Java [OutputStream] - * for this [Stream] instance. - * - * @since v1-alpha10 - */ - private var outputStream: OutputFileStream? = null - // -----> Closure /** @@ -233,7 +210,7 @@ abstract class Stream( * @since v1-alpha10 */ @Throws(IOAccessException::class) - fun writeString(string: String): Stream = writeBytes(string.toByteArray()) + fun writeString(string: String): Stream = writeBytes(string.encodeToByteArray()) /** * Writes (or rather: appends) @@ -245,7 +222,7 @@ abstract class Stream( * @since v1-alpha10 */ @Throws(IOAccessException::class) - fun write(value: Any): Stream = writeBytes(value.toString().toByteArray()) + fun write(value: Any): Stream = writeBytes(value.toString().encodeToByteArray()) /** * Flushes all currently @@ -394,7 +371,6 @@ abstract class Stream( * if wanting to only pipe the bytes * of this [Stream] to other streams. * - * @param threshold amount of bytes to be read before terminating or `0` to watch indefinitely * @return this instance * @throws IOAccessException on IO error * @since v1-alpha10 @@ -406,14 +382,7 @@ abstract class Stream( watching = true - Thread - .ofVirtual() - .name("Stream watch thread for Stream ${javaClass.name}#${hashCode()}") - .uncaughtExceptionHandler { thread, ueh -> logger.crash("Unable to watch Stream ${javaClass.name}#${hashCode()}", throwable = ueh.cause, fatal = true) } - .start { - while (!closed && watching) - skipNextByte() - } + platform.startWatchingThread() return this } @@ -474,7 +443,7 @@ abstract class Stream( * * Make sure to call this method * before returning from - * [readBytes], [readNBytes] + * [readNextByte], [readNBytes] * and [readRemainingBytes]. * * @param byte [Byte] to pipe @@ -494,7 +463,7 @@ abstract class Stream( * * Make sure to call this method * before returning from - * [readBytes], [readNBytes] + * [readNextByte], [readNBytes] * and [readRemainingBytes]. * * @param bytes [ByteArray] to pipe @@ -640,36 +609,6 @@ abstract class Stream( } - // -----> Java interoperability - /** - * Returns this stream as - * a Java [InputStream]. - * - * @since v1-alpha10 - */ - @NonKotlinContact - fun toInputStream(): InputStream { - if (inputStream == null) - inputStream = InputFileStream(this) - - return inputStream!! - } - - /** - * Returns this stream as - * a Java [OutputStream]. - * - * @since v1-alpha10 - */ - @NonKotlinContact - fun toOutputStream(): OutputStream { - if (outputStream == null) - outputStream = OutputFileStream(this) - - return outputStream!! - } - - // -----> Inner classes /** * Represents the mode in which @@ -702,62 +641,4 @@ abstract class Stream( */ READ_WRITE, } - - /** - * An implementation of Java - * [InputStream]s for [Stream]s. - * - * @constructor Initializes this stream - * @param stream [Stream] to use - * @throws IOException on IO error - * @since v1-alpha10 - */ - class InputFileStream - @NonKotlinContact - internal constructor(val stream: Stream) - : InputStream() { - /** - * Reads the next byte. - * - * @return byte read - * @throws IOException on IO error - * @since v1-alpha10 - */ - @Throws(IOException::class) - override fun read(): Int = try { - stream.readNextByte()?.toInt() ?: -1 - } catch (exception: IOAccessException) { - throw IOException("Failed reading the next byte", exception) - } - } - - /** - * An implementation of Java - * [OutputStream]s for [Stream]s. - * - * @constructor Initializes this stream - * @param stream [Stream] to use - * @since v1-alpha10 - */ - @NonKotlinContact - class OutputFileStream - @NonKotlinContact - internal constructor(val stream: Stream) - : OutputStream() { - /** - * Writes (or rather: appends) a byte. - * - * @param byte byte to append - * @throws IOException on IO error - * @since v1-alpha10 - */ - @Throws(IOException::class) - override fun write(byte: Int) { - try { - stream.writeByte(byte.toByte()) - } catch (exception: IOAccessException) { - throw IOException("Failed writing the next byte", exception) - } - } - } } diff --git a/base/src/commonMain/kotlin/de/staropensource/engine/base/platform/StreamPlatform.kt b/base/src/commonMain/kotlin/de/staropensource/engine/base/platform/StreamPlatform.kt new file mode 100644 index 0000000..bf14e81 --- /dev/null +++ b/base/src/commonMain/kotlin/de/staropensource/engine/base/platform/StreamPlatform.kt @@ -0,0 +1,83 @@ +/* + * STAROPENSOURCE ENGINE SOURCE FILE + * Copyright (c) 2024 The StarOpenSource Engine Authors + * Licensed under the GNU General Public License v3. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.staropensource.engine.base.platform + +import de.staropensource.engine.base.implementable.stream.ReadStream +import de.staropensource.engine.base.implementable.stream.Stream +import de.staropensource.engine.base.implementable.stream.WriteStream + +/** + * Platform-specific implementation of [Stream]. + * + * @constructor Initializes this platform implementation + * @param stream [Stream] instance + * @since v1-alpha10 + */ +abstract class StreamPlatform( + val stream: Stream +) { + /** + * Starts a watching thread for the specified stream. + * + * @param stream [Stream] instance + * @since v1-alpha10 + */ + abstract fun startWatchingThread() +} + +/** + * Returns a [StreamPlatform] implementation + * for the supplied [Stream] instance. + * + * @param stream [Stream] instance + * @return matching [StreamPlatform] instance + * @since v1-alpha10 + */ +internal expect fun streamPlatform(stream: Stream): StreamPlatform + +/** + * Returns a [ReadStream] representing + * the standard input of the current + * process. + * + * @return [ReadStream] instance representing the standard input + * @since v1-alpha10 + */ +internal expect fun streamStandardInput(): ReadStream + +/** + * Returns a [WriteStream] representing + * the standard output of the current + * process. + * + * @return [WriteStream] instance representing the standard output + * @since v1-alpha10 + */ +internal expect fun streamStandardOutput(): WriteStream + +/** + * Returns a [WriteStream] representing + * the standard error of the current + * process. + * + * @return [WriteStream] instance representing the standard error + * @since v1-alpha10 + */ +internal expect fun streamStandardError(): WriteStream diff --git a/base/src/commonMain/kotlin/de/staropensource/engine/base/platform/package-info.kt b/base/src/commonMain/kotlin/de/staropensource/engine/base/platform/package-info.kt new file mode 100644 index 0000000..374f989 --- /dev/null +++ b/base/src/commonMain/kotlin/de/staropensource/engine/base/platform/package-info.kt @@ -0,0 +1,26 @@ +/* + * STAROPENSOURCE ENGINE SOURCE FILE + * Copyright (c) 2024 The StarOpenSource Engine Authors + * Licensed under the GNU General Public License v3. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * Platform-specific implementations + * and helper classes. + * + * @since v1-alpha10 + */ +package de.staropensource.engine.base.platform diff --git a/base/src/commonMain/kotlin/de/staropensource/engine/base/utility/DataSize.kt b/base/src/commonMain/kotlin/de/staropensource/engine/base/utility/DataSize.kt index c24dd52..114915d 100644 --- a/base/src/commonMain/kotlin/de/staropensource/engine/base/utility/DataSize.kt +++ b/base/src/commonMain/kotlin/de/staropensource/engine/base/utility/DataSize.kt @@ -19,11 +19,9 @@ package de.staropensource.engine.base.utility -import java.math.RoundingMode -import java.text.DecimalFormat -import java.text.DecimalFormatSymbols -import java.util.Locale import kotlin.jvm.JvmStatic +import kotlin.math.round +import kotlin.math.roundToLong /** * Converts between various data size units. @@ -152,11 +150,7 @@ data class DataSize( * @return modified [Double] * @since v1-alpha10 */ - fun formatTo(number: Double): Double { - val decimalFormat = DecimalFormat("#.####", DecimalFormatSymbols(Locale.ROOT)) - decimalFormat.roundingMode = RoundingMode.HALF_UP - return decimalFormat.format(number).toDouble() - } + fun formatTo(number: Double): Double = round(number.times(1000)).div(1000) /** * Performs miscellaneous operations @@ -167,11 +161,7 @@ data class DataSize( * @return modified [Double] * @since v1-alpha10 */ - fun formatOf(number: Double): ULong { - val decimalFormat = DecimalFormat("#", DecimalFormatSymbols(Locale.ROOT)) - decimalFormat.roundingMode = RoundingMode.HALF_UP - return decimalFormat.format(number).toULong() - } + fun formatOf(number: Double): ULong = number.roundToLong().toULong() } diff --git a/base/src/jvmMain/kotlin/de/staropensource/engine/base/platform/StreamPlatformImpl.kt b/base/src/jvmMain/kotlin/de/staropensource/engine/base/platform/StreamPlatformImpl.kt new file mode 100644 index 0000000..cbeb93d --- /dev/null +++ b/base/src/jvmMain/kotlin/de/staropensource/engine/base/platform/StreamPlatformImpl.kt @@ -0,0 +1,146 @@ +/* + * STAROPENSOURCE ENGINE SOURCE FILE + * Copyright (c) 2024 The StarOpenSource Engine Authors + * Licensed under the GNU General Public License v3. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.staropensource.engine.base.platform + +import de.staropensource.engine.base.Engine.Companion.logger +import de.staropensource.engine.base.annotation.NonKotlinContact +import de.staropensource.engine.base.exception.io.IOAccessException +import de.staropensource.engine.base.implementable.stream.ReadStream +import de.staropensource.engine.base.implementable.stream.Stream +import de.staropensource.engine.base.implementable.stream.WriteStream +import de.staropensource.engine.base.implementation.stream.JavaReadStream +import de.staropensource.engine.base.implementation.stream.JavaWriteStream +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream + +/** + * Java implementation of [StreamPlatform]. + * + * @constructor Initializes this platform implementation + * @param stream [Stream] instance + * @since v1-alpha10 + */ +class StreamPlatformImpl(stream: Stream) : StreamPlatform(stream) { + override fun startWatchingThread() { + Thread + .ofVirtual() + .name("Stream watch thread for Stream ${javaClass.name}#${hashCode()}") + .uncaughtExceptionHandler { thread, ueh -> logger.crash("Unable to watch Stream ${javaClass.name}#${hashCode()}", throwable = ueh.cause, fatal = true) } + .start { + while (!stream.closed && stream.watching) + stream.skipNextByte() + } + } +} + + +// -----> Actual methods +actual fun streamPlatform(stream: Stream): StreamPlatform = stream.platformData.getOrPut("platform") { StreamPlatformImpl(stream) } as StreamPlatform +actual fun streamStandardInput(): ReadStream = object : JavaReadStream(System.`in`) { + override fun close() = Unit + override fun closeStream() = Unit +} +actual fun streamStandardOutput(): WriteStream = object : JavaWriteStream(System.out) { + override fun close() = Unit + override fun closeStream() = Unit +} +actual fun streamStandardError(): WriteStream = object : JavaWriteStream(System.err) { + override fun close() = Unit + override fun closeStream() = Unit +} + + +// -----> Extension methods +/** + * Returns this stream as + * a Java [InputStream]. + * + * @since v1-alpha10 + */ +fun Stream.getJavaInputStream(): InputStream = platformData.getOrPut("inputStream") { InputFileStream(this) } as InputStream + +/** + * Returns this stream as + * a Java [OutputStream]. + * + * @since v1-alpha10 + */ +fun Stream.getJavaOutputStream(): OutputStream = platformData.getOrPut("outputStream") { OutputFileStream(this) } as OutputStream + + +// ----> Helper classes +/** + * An implementation of Java + * [InputStream]s for [Stream]s. + * + * @constructor Initializes this stream + * @param stream [Stream] to use + * @throws IOException on IO error + * @since v1-alpha10 + */ +private class InputFileStream +@NonKotlinContact +constructor(val stream: Stream) + : InputStream() { + /** + * Reads the next byte. + * + * @return byte read + * @throws IOException on IO error + * @since v1-alpha10 + */ + @Throws(IOException::class) + override fun read(): Int = try { + stream.readNextByte()?.toInt() ?: -1 + } catch (exception: IOAccessException) { + throw IOException("Failed reading the next byte", exception) + } +} + +/** + * An implementation of Java + * [OutputStream]s for [Stream]s. + * + * @constructor Initializes this stream + * @param stream [Stream] to use + * @since v1-alpha10 + */ +@NonKotlinContact +private class OutputFileStream +@NonKotlinContact +constructor(val stream: Stream) + : OutputStream() { + /** + * Writes (or rather: appends) a byte. + * + * @param byte byte to append + * @throws IOException on IO error + * @since v1-alpha10 + */ + @Throws(IOException::class) + override fun write(byte: Int) { + try { + stream.writeByte(byte.toByte()) + } catch (exception: IOAccessException) { + throw IOException("Failed writing the next byte", exception) + } + } +} diff --git a/build.gradle.kts b/build.gradle.kts index cb199af..c8a01d0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -251,7 +251,7 @@ allprojects { // -> Copy task tasks.register("copyGitProperties") { dependsOn(tasks.generateGitProperties) - inputs.file("${this@allprojects.projectDir}/build/resources/main/git.properties") + inputs.file("${this@allprojects.projectDir}/build/resources/commonMain/git.properties") doLast { if (rootProject == this@allprojects) @@ -259,7 +259,7 @@ allprojects { file(inputs.files.first()) .copyTo( - file("${this@allprojects.projectDir}/src/main/resources/sosengine-${this@allprojects.name.replace(":", "-")}-git.properties"), + file("${this@allprojects.projectDir}/src/commonMain/resources/sosengine-${this@allprojects.name.replace(":", "-")}-git.properties"), overwrite = true ) } @@ -279,7 +279,7 @@ allprojects { file(inputs.files.first()) .copyTo( - file("${this@allprojects.projectDir}/src/main/resources/sosengine-${this@allprojects.name.replace(":", "-")}-gradle.properties"), + file("${this@allprojects.projectDir}/src/commonMain/resources/sosengine-${this@allprojects.name.replace(":", "-")}-gradle.properties"), overwrite = true ) }