Skip to content

Commit b3a6d97

Browse files
authored
feat: add more conversion methods for JVM streams (#1121)
1 parent 153a731 commit b3a6d97

File tree

4 files changed

+97
-0
lines changed

4 files changed

+97
-0
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"id": "9a48ae92-96dc-48f4-995b-5faa00f890cd",
3+
"type": "feature",
4+
"description": "Add new Kotlin/JVM methods for converting `InputStream` to `ByteStream` and for writing `ByteStream` to `OutputStream`",
5+
"issues": [
6+
"awslabs/aws-sdk-kotlin#1352"
7+
]
8+
}

runtime/runtime-core/api/runtime-core.api

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,14 +418,19 @@ public abstract class aws/smithy/kotlin/runtime/content/ByteStream$SourceStream
418418
public final class aws/smithy/kotlin/runtime/content/ByteStreamJVMKt {
419419
public static final fun asByteStream (Ljava/io/File;JJ)Laws/smithy/kotlin/runtime/content/ByteStream;
420420
public static final fun asByteStream (Ljava/io/File;Lkotlin/ranges/LongRange;)Laws/smithy/kotlin/runtime/content/ByteStream;
421+
public static final fun asByteStream (Ljava/io/InputStream;Ljava/lang/Long;)Laws/smithy/kotlin/runtime/content/ByteStream$SourceStream;
421422
public static final fun asByteStream (Ljava/nio/file/Path;JJ)Laws/smithy/kotlin/runtime/content/ByteStream;
422423
public static final fun asByteStream (Ljava/nio/file/Path;Lkotlin/ranges/LongRange;)Laws/smithy/kotlin/runtime/content/ByteStream;
423424
public static synthetic fun asByteStream$default (Ljava/io/File;JJILjava/lang/Object;)Laws/smithy/kotlin/runtime/content/ByteStream;
425+
public static synthetic fun asByteStream$default (Ljava/io/InputStream;Ljava/lang/Long;ILjava/lang/Object;)Laws/smithy/kotlin/runtime/content/ByteStream$SourceStream;
424426
public static synthetic fun asByteStream$default (Ljava/nio/file/Path;JJILjava/lang/Object;)Laws/smithy/kotlin/runtime/content/ByteStream;
425427
public static final fun fromFile (Laws/smithy/kotlin/runtime/content/ByteStream$Companion;Ljava/io/File;)Laws/smithy/kotlin/runtime/content/ByteStream;
428+
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;
429+
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;
426430
public static final fun toInputStream (Laws/smithy/kotlin/runtime/content/ByteStream;)Ljava/io/InputStream;
427431
public static final fun writeToFile (Laws/smithy/kotlin/runtime/content/ByteStream;Ljava/io/File;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
428432
public static final fun writeToFile (Laws/smithy/kotlin/runtime/content/ByteStream;Ljava/nio/file/Path;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
433+
public static final fun writeToOutputStream (Laws/smithy/kotlin/runtime/content/ByteStream;Ljava/io/OutputStream;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
429434
}
430435

431436
public final class aws/smithy/kotlin/runtime/content/ByteStreamKt {

runtime/runtime-core/jvm/src/aws/smithy/kotlin/runtime/content/ByteStreamJVM.kt

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import kotlinx.coroutines.withContext
1111
import java.io.ByteArrayInputStream
1212
import java.io.File
1313
import java.io.InputStream
14+
import java.io.OutputStream
1415
import java.nio.file.Path
1516
import kotlin.io.use
1617

@@ -97,3 +98,49 @@ public fun ByteStream.toInputStream(): InputStream = when (this) {
9798
is ByteStream.ChannelStream -> readFrom().toInputStream()
9899
is ByteStream.SourceStream -> readFrom().buffer().inputStream()
99100
}
101+
102+
/**
103+
* Create a [ByteStream.SourceStream] that reads from the given [InputStream]
104+
* @param inputStream The [InputStream] from which to create a [ByteStream]
105+
* @param contentLength If specified, indicates how many bytes remain in the input stream. Defaults to `null`.
106+
*/
107+
public fun ByteStream.Companion.fromInputStream(
108+
inputStream: InputStream,
109+
contentLength: Long? = null,
110+
): ByteStream.SourceStream = inputStream.asByteStream(contentLength)
111+
112+
/**
113+
* Create a [ByteStream.SourceStream] that reads from this [InputStream]
114+
* @param contentLength If specified, indicates how many bytes remain in this stream. Defaults to `null`.
115+
*/
116+
public fun InputStream.asByteStream(contentLength: Long? = null): ByteStream.SourceStream {
117+
val source = source()
118+
return object : ByteStream.SourceStream() {
119+
override val contentLength: Long? = contentLength
120+
override val isOneShot: Boolean = true
121+
override fun readFrom(): SdkSource = source
122+
}
123+
}
124+
125+
/**
126+
* Writes this stream to the given [OutputStream]. This method does not flush or close the given [OutputStream].
127+
* @param outputStream The [OutputStream] to which the contents of this stream will be written
128+
*/
129+
public suspend fun ByteStream.writeToOutputStream(outputStream: OutputStream): Long = withContext(Dispatchers.IO) {
130+
val src = when (val stream = this@writeToOutputStream) {
131+
is ByteStream.ChannelStream -> return@withContext outputStream.writeAll(stream.readFrom())
132+
is ByteStream.Buffer -> stream.bytes().source()
133+
is ByteStream.SourceStream -> stream.readFrom()
134+
}
135+
136+
outputStream.sink().use {
137+
it.buffer().use { bufferedSink ->
138+
bufferedSink.writeAll(src)
139+
}
140+
}
141+
}
142+
143+
private suspend fun OutputStream.writeAll(chan: SdkByteReadChannel): Long =
144+
sink().use {
145+
chan.readAll(it)
146+
}

runtime/runtime-core/jvm/test/aws/smithy/kotlin/runtime/content/ByteStreamJVMTest.kt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@ package aws.smithy.kotlin.runtime.content
77

88
import aws.smithy.kotlin.runtime.testing.RandomTempFile
99
import kotlinx.coroutines.test.runTest
10+
import java.io.ByteArrayOutputStream
1011
import java.nio.file.Files
1112
import kotlin.test.*
1213

14+
private val binaryData = ByteArray(1024) { it.toByte() }
15+
1316
class ByteStreamJVMTest {
1417
@Test
1518
fun testFileAsByteStreamValidatesStart() = runTest {
@@ -149,4 +152,38 @@ class ByteStreamJVMTest {
149152
val byteStreamFromPath = file.toPath().asByteStream()
150153
assertEquals(0, byteStreamFromPath.contentLength)
151154
}
155+
156+
@Test
157+
fun testInputStreamAsByteStream() = runTest {
158+
binaryData.inputStream().use { inputStream ->
159+
val byteStream = inputStream.asByteStream()
160+
assertNull(byteStream.contentLength)
161+
assertTrue(byteStream.isOneShot)
162+
163+
val output = byteStream.toByteArray()
164+
assertContentEquals(binaryData, output)
165+
}
166+
}
167+
168+
@Test
169+
fun testInputStreamAsByteStreamWithLength() = runTest {
170+
binaryData.inputStream().use { inputStream ->
171+
val byteStream = inputStream.asByteStream(binaryData.size.toLong())
172+
assertEquals(binaryData.size.toLong(), byteStream.contentLength)
173+
assertTrue(byteStream.isOneShot)
174+
175+
val output = byteStream.toByteArray()
176+
assertContentEquals(binaryData, output)
177+
}
178+
}
179+
180+
@Test
181+
fun testByteStreamToOutputStream() = runTest {
182+
val byteStream = ByteStream.fromBytes(binaryData)
183+
ByteArrayOutputStream().use { outputStream ->
184+
byteStream.writeToOutputStream(outputStream)
185+
val output = outputStream.toByteArray()
186+
assertContentEquals(binaryData, output)
187+
}
188+
}
152189
}

0 commit comments

Comments
 (0)