-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use Immutable collection adapters from kotlinx.collections.immutables
- Loading branch information
Showing
9 changed files
with
355 additions
and
550 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
92 changes: 0 additions & 92 deletions
92
src/main/kotlin/com/amazon/ionelement/impl/collections/ImmutableList.kt
This file was deleted.
Oops, something went wrong.
96 changes: 0 additions & 96 deletions
96
src/main/kotlin/com/amazon/ionelement/impl/collections/ImmutableMap.kt
This file was deleted.
Oops, something went wrong.
46 changes: 46 additions & 0 deletions
46
src/main/kotlin/com/amazon/ionelement/impl/collections/IonLocationBackedImmutableMap.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | ||
} | ||
} |
87 changes: 87 additions & 0 deletions
87
src/main/kotlin/com/amazon/ionelement/impl/collections/extensions.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()) | ||
} |
Oops, something went wrong.