@@ -25,9 +25,19 @@ import com.amazon.ion.TextSpan
2525import com.amazon.ion.system.IonReaderBuilder
2626import com.amazon.ionelement.api.*
2727import com.amazon.ionelement.impl.collections.*
28+ import java.util.ArrayDeque
29+ import java.util.ArrayList
30+ import kotlinx.collections.immutable.adapters.ImmutableListAdapter
2831
2932internal class IonElementLoaderImpl (private val options : IonElementLoaderOptions ) : IonElementLoader {
3033
34+ // TODO: It seems like some data can be read faster with a recursive approach, but other data is
35+ // faster with an iterative approach. Consider making this configurable. It probably doesn't
36+ // need to be finely-tune-able—just 0 or 100 (i.e. on/off) is probably sufficient.
37+ companion object {
38+ private const val MAX_RECURSION_DEPTH : Int = 100
39+ }
40+
3141 /* *
3242 * Catches an [IonException] occurring in [block] and throws an [IonElementLoaderException] with
3343 * the current [IonLocation] of the fault, if one is available. Note that depending on the state of the
@@ -86,6 +96,10 @@ internal class IonElementLoaderImpl(private val options: IonElementLoaderOptions
8696 IonReaderBuilder .standard().build(ionText).use(::loadAllElements)
8797
8898 override fun loadCurrentElement (ionReader : IonReader ): AnyElement {
99+ return loadCurrentElementRecursively(ionReader)
100+ }
101+
102+ private fun loadCurrentElementRecursively (ionReader : IonReader ): AnyElement {
89103 return handleReaderException(ionReader) {
90104 val valueType = requireNotNull(ionReader.type) { " The IonReader was not positioned at an element." }
91105
@@ -128,26 +142,41 @@ internal class IonElementLoaderImpl(private val options: IonElementLoaderOptions
128142 IonType .BLOB -> BlobElementImpl (ionReader.newBytes(), annotations, metas)
129143 IonType .LIST -> {
130144 ionReader.stepIn()
131- val list = ListElementImpl (loadAllElements(ionReader).toImmutableListUnsafe(), annotations, metas)
145+ val listContent = ArrayList <AnyElement >()
146+ if (ionReader.depth < MAX_RECURSION_DEPTH ) {
147+ while (ionReader.next() != null ) {
148+ listContent.add(loadCurrentElementRecursively(ionReader))
149+ }
150+ } else {
151+ loadAllElementsIteratively(ionReader, listContent as MutableList <Any >)
152+ }
132153 ionReader.stepOut()
133- list
154+ ListElementImpl (listContent.toImmutableListUnsafe(), annotations, metas)
134155 }
135156 IonType .SEXP -> {
136157 ionReader.stepIn()
137- val sexp = SexpElementImpl (loadAllElements(ionReader).toImmutableListUnsafe(), annotations, metas)
158+ val sexpContent = ArrayList <AnyElement >()
159+ if (ionReader.depth < MAX_RECURSION_DEPTH ) {
160+ while (ionReader.next() != null ) {
161+ sexpContent.add(loadCurrentElementRecursively(ionReader))
162+ }
163+ } else {
164+ loadAllElementsIteratively(ionReader, sexpContent as MutableList <Any >)
165+ }
138166 ionReader.stepOut()
139- sexp
167+ SexpElementImpl (sexpContent.toImmutableListUnsafe(), annotations, metas)
140168 }
141169 IonType .STRUCT -> {
142- val fields = mutableListOf <StructField >()
170+ val fields = ArrayList <StructField >()
143171 ionReader.stepIn()
144- while (ionReader.next() != null ) {
145- fields.add(
146- StructFieldImpl (
147- ionReader.fieldName,
148- loadCurrentElement(ionReader)
149- )
150- )
172+ if (ionReader.depth < MAX_RECURSION_DEPTH ) {
173+ while (ionReader.next() != null ) {
174+ val fieldName = ionReader.fieldName
175+ val element = loadCurrentElementRecursively(ionReader)
176+ fields.add(StructFieldImpl (fieldName, element))
177+ }
178+ } else {
179+ loadAllElementsIteratively(ionReader, fields as MutableList <Any >)
151180 }
152181 ionReader.stepOut()
153182 StructElementImpl (fields.toImmutableListUnsafe(), annotations, metas)
@@ -158,4 +187,109 @@ internal class IonElementLoaderImpl(private val options: IonElementLoaderOptions
158187 }.asAnyElement()
159188 }
160189 }
190+
191+ private fun loadAllElementsIteratively (ionReader : IonReader , into : MutableList <Any >) {
192+ // Intentionally not using a "recycling" stack because we have mutable lists that we are going to wrap as
193+ // ImmutableLists and then forget about the reference to the mutable list.
194+ val openContainerStack = ArrayDeque <MutableList <Any >>()
195+ var elements: MutableList <Any > = into
196+
197+ while (true ) {
198+ val valueType = ionReader.next()
199+
200+ // End of container or input
201+ if (valueType == null ) {
202+ // We're at the top (relative to where we started)
203+ if (elements == = into) {
204+ return
205+ } else {
206+ ionReader.stepOut()
207+ elements = openContainerStack.pop()
208+ continue
209+ }
210+ }
211+
212+ // Read a value
213+ val annotations = ionReader.typeAnnotations!! .toImmutableListUnsafe()
214+
215+ var metas = EMPTY_METAS
216+ if (options.includeLocationMeta) {
217+ val location = ionReader.currentLocation()
218+ if (location != null ) metas = location.toMetaContainer()
219+ }
220+
221+ if (ionReader.isNullValue) {
222+ elements.addContainerElement(ionReader, ionNull(valueType.toElementType(), annotations, metas).asAnyElement())
223+ continue
224+ } else when (valueType) {
225+ IonType .BOOL -> elements.addContainerElement(ionReader, BoolElementImpl (ionReader.booleanValue(), annotations, metas))
226+ IonType .INT -> {
227+ val intValue = when (ionReader.integerSize!! ) {
228+ IntegerSize .BIG_INTEGER -> {
229+ val bigIntValue = ionReader.bigIntegerValue()
230+ if (bigIntValue !in RANGE_OF_LONG )
231+ BigIntIntElementImpl (bigIntValue, annotations, metas)
232+ else {
233+ LongIntElementImpl (ionReader.longValue(), annotations, metas)
234+ }
235+ }
236+ IntegerSize .LONG ,
237+ IntegerSize .INT -> LongIntElementImpl (ionReader.longValue(), annotations, metas)
238+ }
239+ elements.addContainerElement(ionReader, intValue)
240+ }
241+ IonType .FLOAT -> elements.addContainerElement(ionReader, FloatElementImpl (ionReader.doubleValue(), annotations, metas))
242+ IonType .DECIMAL -> elements.addContainerElement(ionReader, DecimalElementImpl (ionReader.decimalValue(), annotations, metas))
243+ IonType .TIMESTAMP -> elements.addContainerElement(ionReader, TimestampElementImpl (ionReader.timestampValue(), annotations, metas))
244+ IonType .STRING -> elements.addContainerElement(ionReader, StringElementImpl (ionReader.stringValue(), annotations, metas))
245+ IonType .SYMBOL -> elements.addContainerElement(ionReader, SymbolElementImpl (ionReader.stringValue(), annotations, metas))
246+ IonType .CLOB -> elements.addContainerElement(ionReader, ClobElementImpl (ionReader.newBytes(), annotations, metas))
247+ IonType .BLOB -> elements.addContainerElement(ionReader, BlobElementImpl (ionReader.newBytes(), annotations, metas))
248+ IonType .LIST -> {
249+ val listContent = ArrayList <AnyElement >()
250+ // `listContent` gets wrapped in an `ImmutableListWrapper` so that we can create a ListElementImpl
251+ // right away. Then, we add child elements to `ListContent`. Technically, this is a violation of the
252+ // contract for `ImmutableListAdapter`, but it is safe to do so here because no reads will occur
253+ // after we are done modifying the backing list.
254+ // Same thing applies for `sexpContent` and `structContent` in their respective branches.
255+ elements.addContainerElement(ionReader, ListElementImpl (ImmutableListAdapter (listContent), annotations, metas))
256+ ionReader.stepIn()
257+ openContainerStack.push(elements)
258+ elements = listContent as MutableList <Any >
259+ }
260+ IonType .SEXP -> {
261+ val sexpContent = ArrayList <AnyElement >()
262+ elements.addContainerElement(ionReader, SexpElementImpl (ImmutableListAdapter (sexpContent), annotations, metas))
263+ ionReader.stepIn()
264+ openContainerStack.push(elements)
265+ elements = sexpContent as MutableList <Any >
266+ }
267+ IonType .STRUCT -> {
268+ val structContent = ArrayList <StructField >()
269+ elements.addContainerElement(
270+ ionReader,
271+ StructElementImpl (
272+ ImmutableListAdapter (structContent),
273+ annotations,
274+ metas
275+ )
276+ )
277+ ionReader.stepIn()
278+ openContainerStack.push(elements)
279+ elements = structContent as MutableList <Any >
280+ }
281+ IonType .DATAGRAM -> error(" IonElementLoaderImpl does not know what to do with IonType.DATAGRAM" )
282+ IonType .NULL -> error(" IonType.NULL branch should be unreachable" )
283+ }
284+ }
285+ }
286+
287+ private fun MutableList<Any>.addContainerElement (ionReader : IonReader , value : AnyElement ) {
288+ val fieldName = ionReader.fieldName
289+ if (fieldName != null ) {
290+ add(StructFieldImpl (fieldName, value))
291+ } else {
292+ add(value)
293+ }
294+ }
161295}
0 commit comments