Skip to content

Commit

Permalink
Adds BufferedAppendableFastAppendable
Browse files Browse the repository at this point in the history
  • Loading branch information
popematt committed May 10, 2024
1 parent a7a1f9f commit b1cc314
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package com.amazon.ion.impl

import com.amazon.ion.impl.bin.*
import com.amazon.ion.util.*
import java.io.Closeable
import java.io.Flushable

/**
* A [_Private_FastAppendable] that buffers data to a [StringBuilder]. Only when
* [flush] is called is the data written to the wrapped [Appendable].
*
* This is necessary for cases where an [IonManagedWriter_1_1] over Ion text needs to emit encoding directives that are
* not known in advance. The [AppendableFastAppendable] class has no buffering, so system and user values would be
* emitted in the wrong order.
*
* Once [IonManagedWriter_1_1] supports an auto-flush feature, then this class will have very little practical
* difference from [AppendableFastAppendable] for the case where no system values are needed.
*
* TODO:
* - Add proper tests
*
* @see BufferedOutputStreamFastAppendable
* @see AppendableFastAppendable
*/
internal class BufferedAppendableFastAppendable(
private val wrapped: Appendable,
private val buffer: StringBuilder = StringBuilder()
) : _Private_FastAppendable, Flushable, Closeable, Appendable by buffer {

override fun appendAscii(c: Char) { append(c) }
override fun appendAscii(csq: CharSequence?) { append(csq) }
override fun appendAscii(csq: CharSequence?, start: Int, end: Int) { append(csq, start, end) }
override fun appendUtf16(c: Char) { append(c) }

override fun appendUtf16Surrogate(leadSurrogate: Char, trailSurrogate: Char) {
append(leadSurrogate)
append(trailSurrogate)
}

override fun close() {
flush()
if (wrapped is Closeable) wrapped.close()
}

override fun flush() {
wrapped.append(buffer)
if (wrapped is Flushable) wrapped.flush()
buffer.setLength(0)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@ import java.io.OutputStream
*
* TODO:
* - Add proper tests
*
* @see BufferedAppendableFastAppendable
* @see OutputStreamFastAppendable
*/
internal class BlockBufferingOutputStreamFastAppendable(
internal class BufferedOutputStreamFastAppendable(
private val out: OutputStream,
private val allocator: BlockAllocator,
/**
Expand Down Expand Up @@ -54,9 +57,13 @@ internal class BlockBufferingOutputStreamFastAppendable(
}

override fun close() {
flush()
blocks.onEach { it.close() }.clear()
index = Int.MIN_VALUE
try {
flush()
} finally {
blocks.onEach { it.close() }.clear()
index = Int.MIN_VALUE
out.close()
}
}

override fun flush() {
Expand All @@ -66,6 +73,7 @@ internal class BlockBufferingOutputStreamFastAppendable(
}
index = 0
current = blocks[index]
out.flush()
}

override fun write(b: Int) {
Expand Down
25 changes: 24 additions & 1 deletion src/main/java/com/amazon/ion/impl/bin/IonManagedWriter_1_1.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ internal class IonManagedWriter_1_1(
textOptions as _Private_IonTextWriterBuilder

val appender = {
val bufferedOutput = BlockBufferingOutputStreamFastAppendable(output, BlockAllocatorProviders.basicProvider().vendAllocator(4096))
val bufferedOutput = BufferedOutputStreamFastAppendable(output, BlockAllocatorProviders.basicProvider().vendAllocator(4096))
_Private_IonTextAppender.forFastAppendable(bufferedOutput, Charsets.UTF_8)
}

Expand All @@ -58,6 +58,29 @@ internal class IonManagedWriter_1_1(
)
}

@JvmStatic
fun textWriter(output: Appendable, managedWriterOptions: ManagedWriterOptions_1_1, textOptions: IonTextWriterBuilder): IonManagedWriter_1_1 {
textOptions as _Private_IonTextWriterBuilder

val appender = {
val bufferedOutput = BufferedAppendableFastAppendable(output)
_Private_IonTextAppender.forFastAppendable(bufferedOutput, Charsets.UTF_8)
}

return IonManagedWriter_1_1(
userData = IonRawTextWriter_1_1(
options = textOptions,
output = appender(),
),
systemData = IonRawTextWriter_1_1(
options = textOptions,
output = appender(),
),
options = managedWriterOptions.copy(internEncodingDirectiveSymbols = false),
onClose = {},
)
}

@JvmStatic
fun binaryWriter(output: OutputStream, managedWriterOptions: ManagedWriterOptions_1_1, binaryOptions: _Private_IonBinaryWriterBuilder_1_1): IonManagedWriter_1_1 {
// TODO: Add autoflush
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ data class ManagedWriterOptions_1_1(
* For binary, almost certainly want this to be true, and for text, it's
* more readable if it's false.
*/
val internEncodingDirectiveSymbols: Boolean = false,
val internEncodingDirectiveSymbols: Boolean,
val symbolInliningStrategy: SymbolInliningStrategy,
val delimitedContainerStrategy: DelimitedContainerStrategy,
) : SymbolInliningStrategy by symbolInliningStrategy, DelimitedContainerStrategy by delimitedContainerStrategy {
Expand Down
36 changes: 34 additions & 2 deletions src/test/java/com/amazon/ion/Ion11Test.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import com.amazon.ion.TestUtils.GOOD_IONTESTS_FILES
import com.amazon.ion.TestUtils.TEXT_ONLY_FILTER
import com.amazon.ion.TestUtils.hexStringToByteArray
import com.amazon.ion.TestUtils.testdataFiles
import com.amazon.ion.impl.bin.DelimitedContainerStrategy
import com.amazon.ion.impl.bin.SymbolInliningStrategy
import com.amazon.ion.impl.bin.*
import com.amazon.ion.system.IonBinaryWriterBuilder
import com.amazon.ion.system.IonSystemBuilder
import com.amazon.ion.system.IonTextWriterBuilder
Expand Down Expand Up @@ -88,6 +87,27 @@ class Ion11Test {
}
}

@ParameterizedTest(name = "{0}")
@MethodSource("ionData")
fun writeIon11TextToAppendable(name: String, ion: ByteArray) {
val textOptions = IonTextWriterBuilder
.standard()
.withNewLineType(IonTextWriterBuilder.NewLineType.LF)

textTestAppendable(ion) {
IonManagedWriter_1_1.textWriter(
output = it,
managedWriterOptions = ManagedWriterOptions_1_1(
internEncodingDirectiveSymbols = false,
// Test using NEVER_INLINE to make sure that the BufferedAppendableFastAppendable works correctly.
symbolInliningStrategy = SymbolInliningStrategy.NEVER_INLINE,
delimitedContainerStrategy = DelimitedContainerStrategy.ALWAYS_DELIMITED
),
textOptions = textOptions,
)
}
}

@ParameterizedTest(name = "{0}")
@MethodSource("ionData")
fun writeIon11TextWithSymtab(name: String, ion: ByteArray) {
Expand Down Expand Up @@ -143,6 +163,18 @@ class Ion11Test {
assertEquals(data, loadedData.toList())
}

fun textTestAppendable(ion: ByteArray, writerFn: (Appendable) -> IonWriter) {
val data: List<IonValue> = ION.loader.load(ion).map { it }
val appendable = StringBuilder()
val writer = writerFn(appendable)
data.forEach { it.writeTo(writer) }
writer.close()
println(appendable.toString())
val loadedData = ION.loader.load(appendable.toString())
println(loadedData)
assertEquals(data, loadedData.toList())
}

fun binaryTest(ion: ByteArray, writerFn: (OutputStream) -> IonWriter) {
val data: List<IonValue> = ION.loader.load(ion).map { it }
val baos = ByteArrayOutputStream()
Expand Down

0 comments on commit b1cc314

Please sign in to comment.