Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Mutable View for Struct Elements #84

Merged
merged 8 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions api/IonElement.api
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,10 @@ public final class com/amazon/ionelement/api/IntElementSize : java/lang/Enum {
}

public final class com/amazon/ionelement/api/Ion {
public static final fun buildStruct (Ljava/util/List;Ljava/util/Map;Lkotlin/jvm/functions/Function1;)Lcom/amazon/ionelement/api/StructElement;
public static final fun buildStruct (Ljava/util/List;Lkotlin/jvm/functions/Function1;)Lcom/amazon/ionelement/api/StructElement;
public static final fun buildStruct (Lkotlin/jvm/functions/Function1;)Lcom/amazon/ionelement/api/StructElement;
public static synthetic fun buildStruct$default (Ljava/util/List;Ljava/util/Map;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/amazon/ionelement/api/StructElement;
public static final fun emptyBlob ()Lcom/amazon/ionelement/api/BlobElement;
public static final fun emptyClob ()Lcom/amazon/ionelement/api/ClobElement;
public static final fun emptyIonList ()Lcom/amazon/ionelement/api/ListElement;
Expand Down Expand Up @@ -573,6 +577,21 @@ public abstract interface class com/amazon/ionelement/api/LobElement : com/amazo
public abstract fun withoutMetas ()Lcom/amazon/ionelement/api/LobElement;
}

public abstract interface class com/amazon/ionelement/api/MutableStructFields : java/util/Collection, kotlin/jvm/internal/markers/KMutableCollection {
public abstract fun add (Lcom/amazon/ionelement/api/StructField;)Z
public abstract fun add (Ljava/lang/String;Lcom/amazon/ionelement/api/IonElement;)Z
public abstract fun clearField (Ljava/lang/String;)Z
public abstract fun containsField (Ljava/lang/String;)Z
public abstract fun get (Ljava/lang/String;)Lcom/amazon/ionelement/api/AnyElement;
public abstract fun getAll (Ljava/lang/String;)Ljava/util/Collection;
public abstract fun getOptional (Ljava/lang/String;)Lcom/amazon/ionelement/api/AnyElement;
public abstract fun plusAssign (Ljava/util/Collection;)V
public abstract fun remove (Lcom/amazon/ionelement/api/StructField;)Z
public abstract fun removeAll (Ljava/util/Collection;)Z
public abstract fun set (Ljava/lang/String;Lcom/amazon/ionelement/api/IonElement;)V
public abstract fun setAll (Ljava/lang/Iterable;)V
}

public abstract interface class com/amazon/ionelement/api/SeqElement : com/amazon/ionelement/api/ContainerElement {
public abstract fun copy (Ljava/util/List;Ljava/util/Map;)Lcom/amazon/ionelement/api/SeqElement;
public abstract fun getValues ()Ljava/util/List;
Expand Down Expand Up @@ -611,6 +630,8 @@ public abstract interface class com/amazon/ionelement/api/StructElement : com/am
public abstract fun getAll (Ljava/lang/String;)Ljava/lang/Iterable;
public abstract fun getFields ()Ljava/util/Collection;
public abstract fun getOptional (Ljava/lang/String;)Lcom/amazon/ionelement/api/AnyElement;
public abstract fun mutableFields ()Lcom/amazon/ionelement/api/MutableStructFields;
public abstract fun update (Lkotlin/jvm/functions/Function1;)Lcom/amazon/ionelement/api/StructElement;
public abstract fun withAnnotations (Ljava/lang/Iterable;)Lcom/amazon/ionelement/api/StructElement;
public abstract fun withAnnotations ([Ljava/lang/String;)Lcom/amazon/ionelement/api/StructElement;
public abstract fun withMeta (Ljava/lang/String;Ljava/lang/Object;)Lcom/amazon/ionelement/api/StructElement;
Expand Down
11 changes: 11 additions & 0 deletions src/com/amazon/ionelement/api/Ion.kt
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,17 @@ public fun ionStructOf(
metas
)

@JvmOverloads
public fun buildStruct(
annotations: Annotations = emptyList(),
metas: MetaContainer = emptyMetaContainer(),
body: MutableStructFields.() -> Unit
): StructElement {
val fields = emptyIonStruct().mutableFields()
body(fields)
return ionStructOf(fields, annotations, metas)
}

// Memoized empty PersistentList for the memoized container types and null values
private val EMPTY_PERSISTENT_LIST: PersistentList<Nothing> = emptyList<Nothing>().toPersistentList()

Expand Down
10 changes: 10 additions & 0 deletions src/com/amazon/ionelement/api/IonElement.kt
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,16 @@ public interface StructElement : ContainerElement {
/** Returns true if this StructElement has at least one field with the given field name. */
public fun containsField(fieldName: String): Boolean

/** A mutable shallow copy of this struct's fields */
public fun mutableFields(): MutableStructFields

/** Creates a new struct from this struct after executing the lambda body with [MutableStructFields] as the
* receiver.
*
* Annotations and metas are preserved.
*/
public fun update(body: MutableStructFields.() -> Unit): StructElement

override fun copy(annotations: List<String>, metas: MetaContainer): StructElement
override fun withAnnotations(vararg additionalAnnotations: String): StructElement
override fun withAnnotations(additionalAnnotations: Iterable<String>): StructElement
Expand Down
67 changes: 67 additions & 0 deletions src/com/amazon/ionelement/api/MutableStructFields.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.amazon.ionelement.api

/**
* Represents a mutable view of a collection of struct fields.
*/
public interface MutableStructFields : MutableCollection<StructField> {

/**
* Retrieves the value of the first field found with the specified name.
*
* In the case of multiple fields with the specified name, the caller assumes that one is picked at random.
tjcsrc16 marked this conversation as resolved.
Show resolved Hide resolved
*
* @throws IonElementException If there are no fields with the specified [fieldName].
*/
public operator fun get(fieldName: String): AnyElement

/** The same as [get] but returns a null reference if the field does not exist. */
public fun getOptional(fieldName: String): AnyElement?

/** Retrieves all values with a given field name. Returns an empty iterable if the field does not exist. */
public fun getAll(fieldName: String): Collection<AnyElement>

/** Returns true if this StructElement has at least one field with the given field name. */
public fun containsField(fieldName: String): Boolean

/**
* If one or more fields with the specified name already exists, this replaces all of them with the value provided.
*
* Otherwise, a new field with the given name and value is added to the collection.
*/
public operator fun set(fieldName: String, value: IonElement)

/**
* Adds all the given fields to the collection. For existing fields with the same names as the fields provided, all
* instances of those fields will be removed.
*/
public fun setAll(fields: Iterable<StructField>)

/**
* Adds a new field to the collection with the given name and value. The collection may have multiple fields with
* the same name.
*/
public fun add(fieldName: String, value: IonElement): Boolean

/** Adds the given field to the collection. The collection may have multiple fields with the same name.
*
* Repeated fields are allowed, so this will always return true. */
public override fun add(element: StructField): Boolean

/** Removes a random occurrence of a field the matches the given field, or does nothing if no field exists.
*
* Returns true is a field was removed. */
public override fun remove(element: StructField): Boolean

/** Removes all occurrence of a field the matches the given field name, or does nothing if no field exists.
*
* Returns true if a field was removed. */
public fun clearField(fieldName: String): Boolean

/** Removes all of this collection's elements that are also contained in the specified collection.
*
* At most one field per element in the give collection is removed. */
public override fun removeAll(elements: Collection<StructField>): Boolean

/** Adds all the given fields to the collection */
public operator fun plusAssign(fields: Collection<StructField>)
}
177 changes: 177 additions & 0 deletions src/com/amazon/ionelement/impl/MutableStructFieldsImpl.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package com.amazon.ionelement.impl

import com.amazon.ionelement.api.AnyElement
import com.amazon.ionelement.api.IonElement
import com.amazon.ionelement.api.MutableStructFields
import com.amazon.ionelement.api.StructField
import com.amazon.ionelement.api.field
import java.util.NoSuchElementException

internal class MutableStructFieldsImpl(private val fields: MutableMap<String, MutableList<StructField>>) :
MutableStructFields {
override val size: Int
get() = fields.values.sumBy { it.size }

override fun get(fieldName: String): AnyElement {
return requireNotNull(fields[fieldName]?.firstOrNull()?.value) {
"Required struct field '$fieldName' missing"
}
}

override fun getOptional(fieldName: String): AnyElement? {
return fields[fieldName]?.firstOrNull()?.value
}

override fun getAll(fieldName: String): Collection<AnyElement> {
return fields[fieldName]?.map { it.value } ?: mutableListOf()
}

override fun containsField(fieldName: String): Boolean {
return fieldName in fields
}

override fun set(fieldName: String, value: IonElement) {
val values = fields.getOrPut(fieldName, ::mutableListOf)
values.clear()
values.add(field(fieldName, value))
}

override fun setAll(fields: Iterable<StructField>) {
fields.groupByTo(mutableMapOf(), { it.name }, { it.value }).forEach { (name, values) ->
this.fields[name] = values.map { field(name, it) }.toMutableList()
}
}

override fun add(fieldName: String, value: IonElement): Boolean {
value as AnyElement
val values = fields[fieldName]
if (values == null) {
fields[fieldName] = mutableListOf(field(fieldName, value))
} else {
values.add(field(fieldName, value))
}
return true
}

override fun add(element: StructField): Boolean {
return add(element.name, element.value)
}

override fun remove(element: StructField): Boolean {
return fields[element.name]?.remove(element) ?: false
}

override fun clearField(fieldName: String): Boolean {
fields[fieldName]?.clear() ?: return false
return true
}

override fun removeAll(elements: Collection<StructField>): Boolean {
var modified = false
for (element in elements) {
modified = remove(element) || modified
}
return modified
}

override fun plusAssign(fields: Collection<StructField>) {
addAll(fields)
}

override fun addAll(elements: Collection<StructField>): Boolean {
elements.forEach { add(it) }
return true
}

override fun clear() {
fields.clear()
}

override fun contains(element: StructField): Boolean {
return fields[element.name]?.contains(element) ?: false
}

override fun containsAll(elements: Collection<StructField>): Boolean {
return elements.all { contains(it) }
}

override fun isEmpty(): Boolean {
return fields.all { it.value.isEmpty() }
}

override fun iterator(): MutableIterator<StructField> {
return MutableStructFieldsIterator(fields)
}

override fun retainAll(elements: Collection<StructField>): Boolean {
var modified = false
val it = iterator()
while (it.hasNext()) {
val field = it.next()
if (field !in elements) {
it.remove()
modified = true
}
}
return modified
}
}

internal class MutableStructFieldsIterator(fieldsMap: MutableMap<String, MutableList<StructField>>) :
MutableIterator<StructField> {

private val mapIterator = fieldsMap.iterator()
private var listIterator: MutableIterator<StructField> =
if (mapIterator.hasNext()) {
mapIterator.next().value.iterator()
} else {
EMPTY_ITERATOR
}

override fun hasNext(): Boolean {
if (listIterator.hasNext()) {
return true
}

nextFieldList()

return listIterator.hasNext()
}

override fun next(): StructField {
nextFieldList()

return listIterator.next()
}

override fun remove() {
listIterator.remove()
}

private fun nextFieldList() {
while (!listIterator.hasNext()) {
if (mapIterator.hasNext()) {
listIterator = mapIterator.next().value.iterator()
} else {
listIterator = EMPTY_ITERATOR
break
}
}
}

companion object {
private val EMPTY_ITERATOR = object : MutableIterator<StructField> {
override fun hasNext(): Boolean {
return false
}

override fun next(): StructField {
throw NoSuchElementException()
}

override fun remove() {
throw IllegalStateException()
}
}
}
}
17 changes: 17 additions & 0 deletions src/com/amazon/ionelement/impl/StructElementImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,21 @@ internal class StructElementImpl(
return fieldsByNameBackingField!!
}

override fun mutableFields(): MutableStructFields {
val internalMap = mutableMapOf<String, MutableList<StructField>>()
return MutableStructFieldsImpl(
fieldsByName.mapValuesTo(internalMap) { (name, values) ->
values.map { field(name, it) }.toMutableList()
}
)
}

override fun update(body: MutableStructFields.() -> Unit): StructElement {
val mutableFields = mutableFields()
body(mutableFields)
return ionStructOf(mutableFields, annotations, metas)
}

override fun get(fieldName: String): AnyElement =
fieldsByName[fieldName]?.firstOrNull() ?: constraintError(this, "Required struct field '$fieldName' missing")

Expand All @@ -80,7 +95,9 @@ internal class StructElementImpl(
StructElementImpl(allFields, annotations.toPersistentList(), metas.toPersistentMap())

override fun withAnnotations(vararg additionalAnnotations: String): StructElementImpl = _withAnnotations(*additionalAnnotations)
tjcsrc16 marked this conversation as resolved.
Show resolved Hide resolved

override fun withAnnotations(additionalAnnotations: Iterable<String>): StructElementImpl = _withAnnotations(additionalAnnotations)

override fun withoutAnnotations(): StructElementImpl = _withoutAnnotations()
override fun withMetas(additionalMetas: MetaContainer): StructElementImpl = _withMetas(additionalMetas)
override fun withMeta(key: String, value: Any): StructElementImpl = _withMeta(key, value)
Expand Down
Loading
Loading