Skip to content

Commit

Permalink
feat: add more conversion methods for JVM streams (#1121)
Browse files Browse the repository at this point in the history
  • Loading branch information
ianbotsf authored Jul 12, 2024
1 parent 153a731 commit b3a6d97
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .changes/9a48ae92-96dc-48f4-995b-5faa00f890cd.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"id": "9a48ae92-96dc-48f4-995b-5faa00f890cd",
"type": "feature",
"description": "Add new Kotlin/JVM methods for converting `InputStream` to `ByteStream` and for writing `ByteStream` to `OutputStream`",
"issues": [
"awslabs/aws-sdk-kotlin#1352"
]
}
5 changes: 5 additions & 0 deletions runtime/runtime-core/api/runtime-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -418,14 +418,19 @@ public abstract class aws/smithy/kotlin/runtime/content/ByteStream$SourceStream
public final class aws/smithy/kotlin/runtime/content/ByteStreamJVMKt {
public static final fun asByteStream (Ljava/io/File;JJ)Laws/smithy/kotlin/runtime/content/ByteStream;
public static final fun asByteStream (Ljava/io/File;Lkotlin/ranges/LongRange;)Laws/smithy/kotlin/runtime/content/ByteStream;
public static final fun asByteStream (Ljava/io/InputStream;Ljava/lang/Long;)Laws/smithy/kotlin/runtime/content/ByteStream$SourceStream;
public static final fun asByteStream (Ljava/nio/file/Path;JJ)Laws/smithy/kotlin/runtime/content/ByteStream;
public static final fun asByteStream (Ljava/nio/file/Path;Lkotlin/ranges/LongRange;)Laws/smithy/kotlin/runtime/content/ByteStream;
public static synthetic fun asByteStream$default (Ljava/io/File;JJILjava/lang/Object;)Laws/smithy/kotlin/runtime/content/ByteStream;
public static synthetic fun asByteStream$default (Ljava/io/InputStream;Ljava/lang/Long;ILjava/lang/Object;)Laws/smithy/kotlin/runtime/content/ByteStream$SourceStream;
public static synthetic fun asByteStream$default (Ljava/nio/file/Path;JJILjava/lang/Object;)Laws/smithy/kotlin/runtime/content/ByteStream;
public static final fun fromFile (Laws/smithy/kotlin/runtime/content/ByteStream$Companion;Ljava/io/File;)Laws/smithy/kotlin/runtime/content/ByteStream;
public static final fun fromInputStream (Laws/smithy/kotlin/runtime/content/ByteStream$Companion;Ljava/io/InputStream;Ljava/lang/Long;)Laws/smithy/kotlin/runtime/content/ByteStream$SourceStream;
public static synthetic fun fromInputStream$default (Laws/smithy/kotlin/runtime/content/ByteStream$Companion;Ljava/io/InputStream;Ljava/lang/Long;ILjava/lang/Object;)Laws/smithy/kotlin/runtime/content/ByteStream$SourceStream;
public static final fun toInputStream (Laws/smithy/kotlin/runtime/content/ByteStream;)Ljava/io/InputStream;
public static final fun writeToFile (Laws/smithy/kotlin/runtime/content/ByteStream;Ljava/io/File;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun writeToFile (Laws/smithy/kotlin/runtime/content/ByteStream;Ljava/nio/file/Path;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun writeToOutputStream (Laws/smithy/kotlin/runtime/content/ByteStream;Ljava/io/OutputStream;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class aws/smithy/kotlin/runtime/content/ByteStreamKt {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import kotlinx.coroutines.withContext
import java.io.ByteArrayInputStream
import java.io.File
import java.io.InputStream
import java.io.OutputStream
import java.nio.file.Path
import kotlin.io.use

Expand Down Expand Up @@ -97,3 +98,49 @@ public fun ByteStream.toInputStream(): InputStream = when (this) {
is ByteStream.ChannelStream -> readFrom().toInputStream()
is ByteStream.SourceStream -> readFrom().buffer().inputStream()
}

/**
* Create a [ByteStream.SourceStream] that reads from the given [InputStream]
* @param inputStream The [InputStream] from which to create a [ByteStream]
* @param contentLength If specified, indicates how many bytes remain in the input stream. Defaults to `null`.
*/
public fun ByteStream.Companion.fromInputStream(
inputStream: InputStream,
contentLength: Long? = null,
): ByteStream.SourceStream = inputStream.asByteStream(contentLength)

/**
* Create a [ByteStream.SourceStream] that reads from this [InputStream]
* @param contentLength If specified, indicates how many bytes remain in this stream. Defaults to `null`.
*/
public fun InputStream.asByteStream(contentLength: Long? = null): ByteStream.SourceStream {
val source = source()
return object : ByteStream.SourceStream() {
override val contentLength: Long? = contentLength
override val isOneShot: Boolean = true
override fun readFrom(): SdkSource = source
}
}

/**
* Writes this stream to the given [OutputStream]. This method does not flush or close the given [OutputStream].
* @param outputStream The [OutputStream] to which the contents of this stream will be written
*/
public suspend fun ByteStream.writeToOutputStream(outputStream: OutputStream): Long = withContext(Dispatchers.IO) {
val src = when (val stream = this@writeToOutputStream) {
is ByteStream.ChannelStream -> return@withContext outputStream.writeAll(stream.readFrom())
is ByteStream.Buffer -> stream.bytes().source()
is ByteStream.SourceStream -> stream.readFrom()
}

outputStream.sink().use {
it.buffer().use { bufferedSink ->
bufferedSink.writeAll(src)
}
}
}

private suspend fun OutputStream.writeAll(chan: SdkByteReadChannel): Long =
sink().use {
chan.readAll(it)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ package aws.smithy.kotlin.runtime.content

import aws.smithy.kotlin.runtime.testing.RandomTempFile
import kotlinx.coroutines.test.runTest
import java.io.ByteArrayOutputStream
import java.nio.file.Files
import kotlin.test.*

private val binaryData = ByteArray(1024) { it.toByte() }

class ByteStreamJVMTest {
@Test
fun testFileAsByteStreamValidatesStart() = runTest {
Expand Down Expand Up @@ -149,4 +152,38 @@ class ByteStreamJVMTest {
val byteStreamFromPath = file.toPath().asByteStream()
assertEquals(0, byteStreamFromPath.contentLength)
}

@Test
fun testInputStreamAsByteStream() = runTest {
binaryData.inputStream().use { inputStream ->
val byteStream = inputStream.asByteStream()
assertNull(byteStream.contentLength)
assertTrue(byteStream.isOneShot)

val output = byteStream.toByteArray()
assertContentEquals(binaryData, output)
}
}

@Test
fun testInputStreamAsByteStreamWithLength() = runTest {
binaryData.inputStream().use { inputStream ->
val byteStream = inputStream.asByteStream(binaryData.size.toLong())
assertEquals(binaryData.size.toLong(), byteStream.contentLength)
assertTrue(byteStream.isOneShot)

val output = byteStream.toByteArray()
assertContentEquals(binaryData, output)
}
}

@Test
fun testByteStreamToOutputStream() = runTest {
val byteStream = ByteStream.fromBytes(binaryData)
ByteArrayOutputStream().use { outputStream ->
byteStream.writeToOutputStream(outputStream)
val output = outputStream.toByteArray()
assertContentEquals(binaryData, output)
}
}
}

0 comments on commit b3a6d97

Please sign in to comment.