diff --git a/api/IonElement.api b/api/IonElement.api index bec2bf5..ea3992a 100644 --- a/api/IonElement.api +++ b/api/IonElement.api @@ -631,7 +631,8 @@ public abstract interface class com/amazon/ionelement/api/StructElement : com/am 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 update (Ljava/util/function/Consumer;)Lcom/amazon/ionelement/api/StructElement; + public abstract synthetic 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; diff --git a/src/com/amazon/ionelement/api/IonElement.kt b/src/com/amazon/ionelement/api/IonElement.kt index 932d90c..e64ee33 100644 --- a/src/com/amazon/ionelement/api/IonElement.kt +++ b/src/com/amazon/ionelement/api/IonElement.kt @@ -4,6 +4,7 @@ import com.amazon.ion.Decimal import com.amazon.ion.IonWriter import com.amazon.ion.Timestamp import java.math.BigInteger +import java.util.function.Consumer /** * Represents an immutable Ion element. @@ -446,12 +447,43 @@ public interface StructElement : ContainerElement { /** 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. + /* + * Note about the update() functions + * + * fun update(body: MutableStructFields.() -> Unit) + * + * This gives us an idiomatic Kotlin experience, but requires Java callers to add `return Unit.INSTANCE` to any + * lambda functions. Therefore, we also have: + * + * fun update(mutator: Consumer) + * + * This gives us an idiomatic Java experience because Java callers can now pass in a lambda function with a + * void return. However, it clashes with the Kotlin-specific function if you try to pass in a lambda without the + * enclosing curly braces. E.g.: + * + * myStruct.update(s -> s.clearField("foo")); + * + * Ambiguous method call. Both update (Function1) in StructElement and + * update(Consumer) in StructElement match + * + * In order to solve this, the Kotlin-specific version of the function is marked with @JvmSynthetic to make it + * inaccessible from Java at compile time. + */ + + /** + * Creates a new copy of this struct with the updates in [mutator] applied to the fields of the new struct. + * + * Annotations and metas are preserved. + */ + @JvmSynthetic + public fun update(mutator: MutableStructFields.() -> Unit): StructElement + + /** + * Creates a new copy of this struct with the updates in [mutator] applied to the fields of the new struct. * * Annotations and metas are preserved. */ - public fun update(body: MutableStructFields.() -> Unit): StructElement + public fun update(mutator: Consumer): StructElement override fun copy(annotations: List, metas: MetaContainer): StructElement override fun withAnnotations(vararg additionalAnnotations: String): StructElement diff --git a/src/com/amazon/ionelement/impl/StructElementImpl.kt b/src/com/amazon/ionelement/impl/StructElementImpl.kt index 5ae3a19..9387922 100644 --- a/src/com/amazon/ionelement/impl/StructElementImpl.kt +++ b/src/com/amazon/ionelement/impl/StructElementImpl.kt @@ -20,6 +20,7 @@ import com.amazon.ion.IonWriter import com.amazon.ionelement.api.* import com.amazon.ionelement.api.PersistentMetaContainer import com.amazon.ionelement.api.constraintError +import java.util.function.Consumer import kotlinx.collections.immutable.PersistentCollection import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.PersistentMap @@ -75,9 +76,15 @@ internal class StructElementImpl( ) } - override fun update(body: MutableStructFields.() -> Unit): StructElement { + override fun update(mutator: MutableStructFields.() -> Unit): StructElement { val mutableFields = mutableFields() - body(mutableFields) + mutableFields.apply(mutator) + return ionStructOf(mutableFields, annotations, metas) + } + + override fun update(mutator: Consumer): StructElement { + val mutableFields = mutableFields() + mutator.accept(mutableFields) return ionStructOf(mutableFields, annotations, metas) } diff --git a/test/com/amazon/ionelement/demos/java/MutableStructFieldsDemo.java b/test/com/amazon/ionelement/demos/java/MutableStructFieldsDemo.java index af96eb1..0c0532f 100644 --- a/test/com/amazon/ionelement/demos/java/MutableStructFieldsDemo.java +++ b/test/com/amazon/ionelement/demos/java/MutableStructFieldsDemo.java @@ -18,15 +18,13 @@ void createUpdatedStructFromExistingStruct() { StructElement expected = loadSingleElement( "{name:\"Alice\",scores:{darts:100,billiards:30,pingPong:200,}}").asStruct(); - StructElement updated = original.update(fields -> { + StructElement updated = original.update(fields -> fields.set("scores", - original.get("scores").asStruct().update(scoreFields -> { + fields.get("scores").asStruct().update(scoreFields -> { scoreFields.add("pingPong", Ion.ionInt(200)); scoreFields.set("billiards", Ion.ionInt(30)); - return Unit.INSTANCE; - })); - return Unit.INSTANCE; - }); + }) + )); assertEquals(expected, updated); }