Skip to content

Commit eb6636f

Browse files
authored
Rewrites loadCurrentElement to be more efficient (#100)
1 parent c2957eb commit eb6636f

File tree

1 file changed

+64
-87
lines changed

1 file changed

+64
-87
lines changed

src/main/kotlin/com/amazon/ionelement/impl/IonElementLoaderImpl.kt

Lines changed: 64 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import com.amazon.ion.SpanProvider
2424
import com.amazon.ion.TextSpan
2525
import com.amazon.ion.system.IonReaderBuilder
2626
import com.amazon.ionelement.api.*
27+
import kotlinx.collections.immutable.toPersistentMap
2728

2829
internal class IonElementLoaderImpl(private val options: IonElementLoaderOptions) : IonElementLoader {
2930

@@ -59,9 +60,7 @@ internal class IonElementLoaderImpl(private val options: IonElementLoaderOptions
5960
}
6061

6162
override fun loadSingleElement(ionText: String): AnyElement =
62-
IonReaderBuilder.standard().build(ionText).use { ionReader ->
63-
loadSingleElement(ionReader)
64-
}
63+
IonReaderBuilder.standard().build(ionText).use(::loadSingleElement)
6564

6665
override fun loadSingleElement(ionReader: IonReader): AnyElement {
6766
return handleReaderException(ionReader) {
@@ -75,28 +74,22 @@ internal class IonElementLoaderImpl(private val options: IonElementLoaderOptions
7574

7675
override fun loadAllElements(ionReader: IonReader): List<AnyElement> {
7776
return handleReaderException(ionReader) {
78-
mutableListOf<AnyElement>().also { fields ->
79-
ionReader.forEachValue { fields.add(loadCurrentElement(ionReader)) }
77+
val elements = mutableListOf<AnyElement>()
78+
while (ionReader.next() != null) {
79+
elements.add(loadCurrentElement(ionReader))
8080
}
81+
elements
8182
}
8283
}
8384

8485
override fun loadAllElements(ionText: String): List<AnyElement> =
85-
IonReaderBuilder.standard().build(ionText).use { ionReader ->
86-
return ArrayList<AnyElement>().also { list ->
87-
ionReader.forEachValue {
88-
list.add(loadCurrentElement(ionReader))
89-
}
90-
}.toList()
91-
}
86+
IonReaderBuilder.standard().build(ionText).use(::loadAllElements)
9287

9388
override fun loadCurrentElement(ionReader: IonReader): AnyElement {
9489
return handleReaderException(ionReader) {
95-
require(ionReader.type != null) { "The IonReader was not positioned at an element." }
90+
val valueType = requireNotNull(ionReader.type) { "The IonReader was not positioned at an element." }
9691

97-
val valueType = ionReader.type
98-
99-
val annotations = ionReader.typeAnnotations!!
92+
val annotations = ionReader.typeAnnotations!!.asList().toEmptyOrPersistentList()
10093

10194
val metas = when {
10295
options.includeLocationMeta -> {
@@ -107,83 +100,67 @@ internal class IonElementLoaderImpl(private val options: IonElementLoaderOptions
107100
}
108101
}
109102
else -> emptyMetaContainer()
110-
}
111-
112-
var element: AnyElement = when {
113-
ionReader.type == IonType.DATAGRAM -> error("IonElementLoaderImpl does not know what to do with IonType.DATAGRAM")
114-
ionReader.isNullValue -> ionNull(valueType.toElementType())
115-
else -> {
116-
when {
117-
!IonType.isContainer(valueType) -> {
118-
when (valueType) {
119-
IonType.BOOL -> ionBool(ionReader.booleanValue())
120-
IonType.INT -> when (ionReader.integerSize!!) {
121-
IntegerSize.BIG_INTEGER -> {
122-
val bigIntValue = ionReader.bigIntegerValue()
123-
// Ion java's IonReader appears to determine integerSize based on number of bits,
124-
// not on the actual value, which means if we have a padded int that is > 63 bits,
125-
// but whose value only uses <= 63 bits then integerSize is still BIG_INTEGER.
126-
// Compensate for that here...
127-
if (bigIntValue !in RANGE_OF_LONG)
128-
ionInt(bigIntValue)
129-
else {
130-
ionInt(ionReader.longValue())
131-
}
132-
}
133-
IntegerSize.LONG, IntegerSize.INT -> ionInt(ionReader.longValue())
134-
}
135-
IonType.FLOAT -> ionFloat(ionReader.doubleValue())
136-
IonType.DECIMAL -> ionDecimal(ionReader.decimalValue())
137-
IonType.TIMESTAMP -> ionTimestamp(ionReader.timestampValue())
138-
IonType.STRING -> ionString(ionReader.stringValue())
139-
IonType.SYMBOL -> ionSymbol(ionReader.stringValue())
140-
IonType.CLOB -> ionClob(ionReader.newBytes())
141-
IonType.BLOB -> ionBlob(ionReader.newBytes())
142-
else ->
143-
error("Unexpected Ion type for scalar Ion data type ${ionReader.type}.")
103+
}.toPersistentMap()
104+
105+
if (ionReader.isNullValue) {
106+
ionNull(valueType.toElementType(), annotations, metas)
107+
} else {
108+
when (valueType) {
109+
IonType.BOOL -> BoolElementImpl(ionReader.booleanValue(), annotations, metas)
110+
IonType.INT -> when (ionReader.integerSize!!) {
111+
IntegerSize.BIG_INTEGER -> {
112+
val bigIntValue = ionReader.bigIntegerValue()
113+
// Ion java's IonReader appears to determine integerSize based on number of bits,
114+
// not on the actual value, which means if we have a padded int that is > 63 bits,
115+
// but whose value only uses <= 63 bits then integerSize is still BIG_INTEGER.
116+
// Compensate for that here...
117+
if (bigIntValue !in RANGE_OF_LONG)
118+
BigIntIntElementImpl(bigIntValue, annotations, metas)
119+
else {
120+
LongIntElementImpl(ionReader.longValue(), annotations, metas)
144121
}
145122
}
146-
else -> {
147-
ionReader.stepIn()
148-
when (valueType) {
149-
IonType.LIST -> {
150-
ionListOf(loadAllElements(ionReader))
151-
}
152-
IonType.SEXP -> {
153-
ionSexpOf(loadAllElements(ionReader))
154-
}
155-
IonType.STRUCT -> {
156-
val fields = mutableListOf<StructField>()
157-
ionReader.forEachValue { fields.add(StructFieldImpl(ionReader.fieldName, loadCurrentElement(ionReader))) }
158-
ionStructOf(fields)
159-
}
160-
else -> error("Unexpected Ion type for container Ion data type ${ionReader.type}.")
161-
}.also {
162-
ionReader.stepOut()
163-
}
123+
IntegerSize.LONG,
124+
IntegerSize.INT -> LongIntElementImpl(ionReader.longValue(), annotations, metas)
125+
}
126+
127+
IonType.FLOAT -> FloatElementImpl(ionReader.doubleValue(), annotations, metas)
128+
IonType.DECIMAL -> DecimalElementImpl(ionReader.decimalValue(), annotations, metas)
129+
IonType.TIMESTAMP -> TimestampElementImpl(ionReader.timestampValue(), annotations, metas)
130+
IonType.STRING -> StringElementImpl(ionReader.stringValue(), annotations, metas)
131+
IonType.SYMBOL -> SymbolElementImpl(ionReader.stringValue(), annotations, metas)
132+
IonType.CLOB -> ClobElementImpl(ionReader.newBytes(), annotations, metas)
133+
IonType.BLOB -> BlobElementImpl(ionReader.newBytes(), annotations, metas)
134+
IonType.LIST -> {
135+
ionReader.stepIn()
136+
val list = ListElementImpl(loadAllElements(ionReader).toEmptyOrPersistentList(), annotations, metas)
137+
ionReader.stepOut()
138+
list
139+
}
140+
IonType.SEXP -> {
141+
ionReader.stepIn()
142+
val sexp = SexpElementImpl(loadAllElements(ionReader).toEmptyOrPersistentList(), annotations, metas)
143+
ionReader.stepOut()
144+
sexp
145+
}
146+
IonType.STRUCT -> {
147+
val fields = mutableListOf<StructField>()
148+
ionReader.stepIn()
149+
while (ionReader.next() != null) {
150+
fields.add(
151+
StructFieldImpl(
152+
ionReader.fieldName,
153+
loadCurrentElement(ionReader)
154+
)
155+
)
164156
}
157+
ionReader.stepOut()
158+
StructElementImpl(fields.toEmptyOrPersistentList(), annotations, metas)
165159
}
160+
IonType.DATAGRAM -> error("IonElementLoaderImpl does not know what to do with IonType.DATAGRAM")
161+
IonType.NULL -> error("IonType.NULL branch should be unreachable")
166162
}
167163
}.asAnyElement()
168-
169-
if (annotations.any()) {
170-
element = element._withAnnotations(*annotations)
171-
}
172-
if (metas.any()) {
173-
element = element._withMetas(metas)
174-
}
175-
176-
element
177164
}
178165
}
179166
}
180-
181-
/**
182-
* Calls [IonReader.next] and invokes [block] until all values at the current level in the [IonReader]
183-
* have been exhausted.
184-
* */
185-
private fun <T> IonReader.forEachValue(block: () -> T) {
186-
while (this.next() != null) {
187-
block()
188-
}
189-
}

0 commit comments

Comments
 (0)