Skip to content

Commit

Permalink
Use Immutable collection adapters from kotlinx.collections.immutables
Browse files Browse the repository at this point in the history
  • Loading branch information
popematt committed Aug 5, 2024
1 parent 06cb803 commit effed62
Show file tree
Hide file tree
Showing 9 changed files with 355 additions and 550 deletions.
12 changes: 6 additions & 6 deletions src/main/kotlin/com/amazon/ionelement/api/Ion.kt
Original file line number Diff line number Diff line change
Expand Up @@ -520,13 +520,13 @@ public fun buildStruct(
}

// Memoized empty instances of our container types.
private val EMPTY_LIST = ListElementImpl(ImmutableList.EMPTY, ImmutableList.EMPTY, EMPTY_METAS)
private val EMPTY_SEXP = SexpElementImpl(ImmutableList.EMPTY, ImmutableList.EMPTY, EMPTY_METAS)
private val EMPTY_STRUCT = StructElementImpl(ImmutableList.EMPTY, ImmutableList.EMPTY, EMPTY_METAS)
private val EMPTY_BLOB = BlobElementImpl(ByteArray(0), ImmutableList.EMPTY, EMPTY_METAS)
private val EMPTY_CLOB = ClobElementImpl(ByteArray(0), ImmutableList.EMPTY, EMPTY_METAS)
private val EMPTY_LIST = ListElementImpl(EMPTY_IMMUTABLE_LIST, EMPTY_IMMUTABLE_LIST, EMPTY_METAS)
private val EMPTY_SEXP = SexpElementImpl(EMPTY_IMMUTABLE_LIST, EMPTY_IMMUTABLE_LIST, EMPTY_METAS)
private val EMPTY_STRUCT = StructElementImpl(EMPTY_IMMUTABLE_LIST, EMPTY_IMMUTABLE_LIST, EMPTY_METAS)
private val EMPTY_BLOB = BlobElementImpl(ByteArray(0), EMPTY_IMMUTABLE_LIST, EMPTY_METAS)
private val EMPTY_CLOB = ClobElementImpl(ByteArray(0), EMPTY_IMMUTABLE_LIST, EMPTY_METAS)

// Memoized instances of all of our null values.
private val ALL_NULLS = ElementType.values().map {
it to NullElementImpl(it, ImmutableList.EMPTY, EMPTY_METAS) as IonElement
it to NullElementImpl(it, EMPTY_IMMUTABLE_LIST, EMPTY_METAS) as IonElement
}.toMap()

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package com.amazon.ionelement.impl.collections

import com.amazon.ionelement.api.*
import java.util.AbstractMap.SimpleImmutableEntry
import kotlinx.collections.immutable.ImmutableCollection
import kotlinx.collections.immutable.ImmutableSet
import kotlinx.collections.immutable.adapters.ImmutableSetAdapter

/**
* Specialized implementation of [Map] that always has a size of 1 and contains only the key [ION_LOCATION_META_TAG].
*
* This exists so that we can populate location metadata with as little overhead as possible.
* On 64-bit Hotspot JVM, this has an object size of only 16 bytes compared to [java.util.Collections.singletonMap]
* which creates a map with an object size of 40 bytes.
*
* We assume that by far the most common use case for this class is calling `get(ION_LOCATION_META_TAG)`
* rather than general `Map` operations.
*/
internal class IonLocationBackedImmutableMap(private val value: IonLocation) : ImmutableMap<String, IonLocation> {
override val size: Int get() = 1
override fun isEmpty(): Boolean = false

override fun get(key: String): IonLocation? = if (key == ION_LOCATION_META_TAG) value else null
override fun containsValue(value: IonLocation): Boolean = value == this.value
override fun containsKey(key: String): Boolean = key == ION_LOCATION_META_TAG

override val keys: ImmutableSet<String> get() = KEY_SET
// We could memoize these values, but that would increase the memory footprint of this class.
override val values: ImmutableCollection<IonLocation> get() = ImmutableSetAdapter(setOf(value))
override val entries: ImmutableSet<Map.Entry<String, IonLocation>> get() = ImmutableSetAdapter(setOf(SimpleImmutableEntry(ION_LOCATION_META_TAG, value)))

override fun toString(): String = "{$ION_LOCATION_META_TAG=$value}"
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Map<*, *>) return false
if (other.size != 1) return false
return other[ION_LOCATION_META_TAG] == value
}
override fun hashCode(): Int = ION_LOCATION_META_TAG.hashCode() xor value.hashCode()

companion object {
private val KEY_SET = ImmutableSetAdapter(setOf(ION_LOCATION_META_TAG))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.amazon.ionelement.impl.collections

import com.amazon.ionelement.api.*
import kotlinx.collections.immutable.adapters.ImmutableListAdapter
import kotlinx.collections.immutable.adapters.ImmutableMapAdapter

typealias ImmutableList<E> = kotlinx.collections.immutable.ImmutableList<E>
typealias ImmutableMap<K, V> = kotlinx.collections.immutable.ImmutableMap<K, V>

internal val EMPTY_IMMUTABLE_MAP = ImmutableMapAdapter<Any?, Nothing>(emptyMap())
internal val EMPTY_IMMUTABLE_LIST = ImmutableListAdapter(emptyList<Nothing>())

/**
* Creates a [ImmutableMap] for [this] without making a defensive copy.
* Only call this method if you are sure that [this] cannot leak anywhere it could be mutated.
*/
internal fun <K, V> Map<K, V>.toImmutableMapUnsafe(): ImmutableMap<K, V> {
if (this is ImmutableMap) return this
// Empty ImmutableMap can be safely cast to any `<K, V>` because it is empty.
@Suppress("UNCHECKED_CAST")
if (isEmpty()) return EMPTY_IMMUTABLE_MAP as ImmutableMap<K, V>
return ImmutableMapAdapter(this)
}

/**
* Creates a [ImmutableMap] for [this].
* This function creates a defensive copy of [this] unless [this] is already a [ImmutableMap].
*/
internal fun <K, V> Map<K, V>.toImmutableMap(): ImmutableMap<K, V> {
if (this is ImmutableMap) return this
// Empty ImmutableMap can be safely cast to any `<K, V>` because it is empty.
@Suppress("UNCHECKED_CAST")
if (isEmpty()) return (EMPTY_IMMUTABLE_MAP as ImmutableMap<K, V>)
return ImmutableMapAdapter(toMap())
}

/**
* Creates an [ImmutableMetaContainer] ([ImmutableMap]) that holds [this] [IonLocation] instance.
*/
internal fun IonLocation.toMetaContainer(): ImmutableMap<String, Any> {
return IonLocationBackedImmutableMap(this)
}

/**
* Creates a [ImmutableList] for [this].
* This function creates a defensive copy of [this] unless [this] is already a [ImmutableList].
*/
internal fun <E> Iterable<E>.toImmutableList(): ImmutableList<E> {
if (this is ImmutableList<E>) return this
val isEmpty = if (this is Collection<*>) {
this.isEmpty()
} else {
!this.iterator().hasNext()
}
return if (isEmpty) EMPTY_IMMUTABLE_LIST else ImmutableListAdapter(this.toList())
}

/**
* Creates a [ImmutableList] for [this].
* This function creates a defensive copy of [this] unless [this] is already a [ImmutableList].
*/
internal fun <E> List<E>.toImmutableList(): ImmutableList<E> {
if (this is ImmutableList<E>) return this
if (isEmpty()) return EMPTY_IMMUTABLE_LIST
return ImmutableListAdapter(this.toList())
}

/**
* Creates a [ImmutableList] for [this] without making a defensive copy.
* Only call this method if you are sure that [this] cannot leak anywhere it could be mutated.
*/
internal fun <E> List<E>.toImmutableListUnsafe(): ImmutableList<E> {
if (this is ImmutableList<E>) return this
if (isEmpty()) return EMPTY_IMMUTABLE_LIST
return ImmutableListAdapter(this)
}

/**
* Creates a [ImmutableList] for [this] without making a defensive copy.
* Only call this method if you are sure that [this] cannot leak anywhere it could be mutated.
*/
internal fun <E> Array<E>.toImmutableListUnsafe(): ImmutableList<E> {
if (isEmpty()) return EMPTY_IMMUTABLE_LIST
// This wraps the array in an ArrayList and then in an ImmutableListAdapter. In theory, we could reduce the overhead
// even further but using only a single wrapper layer, if we created such a thing.
return ImmutableListAdapter(asList())
}
Loading

0 comments on commit effed62

Please sign in to comment.