diff --git a/module/extension/protobuf/src/main/java/org/openbase/jul/extension/protobuf/ProtoBufBuilderProcessor.kt b/module/extension/protobuf/src/main/java/org/openbase/jul/extension/protobuf/ProtoBufBuilderProcessor.kt index 3a367b4a0..e963bb9db 100644 --- a/module/extension/protobuf/src/main/java/org/openbase/jul/extension/protobuf/ProtoBufBuilderProcessor.kt +++ b/module/extension/protobuf/src/main/java/org/openbase/jul/extension/protobuf/ProtoBufBuilderProcessor.kt @@ -426,5 +426,41 @@ object ProtoBufBuilderProcessor { } } + /** + * This method clears all repeated fields from the given message before the `mergeFrom` is performed. + * This functionality can be useful, since `mergeFrom` would always duplicate all repeated fields + * which can be avoided by using this function instead. + * @param message the message to merge from. + */ + @JvmStatic + fun MB.mergeFromWithoutRepeatedFields(builder: Message.Builder): MB = + // we should not modify the passed builder, so we need to transform it into a message first. + mergeFromWithoutRepeatedFields(builder.build()) + + /** + * This method clears all repeated fields from the given message before the `mergeFrom` is performed. + * This functionality can be useful, since `mergeFrom` would always duplicate all repeated fields + * which can be avoided by using this function instead. + * @param message the message to merge from. + */ + @JvmStatic + fun MB.mergeFromWithoutRepeatedFields(message: Message): MB = + message.toBuilder() + .let { messageBuilder -> messageBuilder.clearRepeatedFields() } + .let { messageBuilder -> mergeFrom(messageBuilder.build()) as MB } + /** + * This method recursively clears all repeated fields from the builder instance. + */ + @JvmStatic + fun MB.clearRepeatedFields(): MB = also { builder -> + allFields.keys.forEach { descriptor -> + descriptor + .takeIf { it.isRepeated } + ?.also {clearField(it) ; return@forEach } + descriptor + .takeIf { it.javaType == Descriptors.FieldDescriptor.JavaType.MESSAGE } + ?.also { builder.getFieldBuilder(it).clearRepeatedFields() } + } + } } diff --git a/module/extension/protobuf/src/test/java/org/openbase/jul/extension/protobuf/ProtoBufBuilderProcessorTest.kt b/module/extension/protobuf/src/test/java/org/openbase/jul/extension/protobuf/ProtoBufBuilderProcessorTest.kt new file mode 100644 index 000000000..623a05c58 --- /dev/null +++ b/module/extension/protobuf/src/test/java/org/openbase/jul/extension/protobuf/ProtoBufBuilderProcessorTest.kt @@ -0,0 +1,65 @@ +package org.openbase.jul.extension.protobuf + +import io.kotest.matchers.comparables.shouldBeEqualComparingTo +import io.kotest.matchers.ints.shouldBeExactly +import org.junit.jupiter.api.Test + +import org.openbase.jul.extension.protobuf.ProtoBufBuilderProcessor.clearRepeatedFields +import org.openbase.jul.extension.protobuf.ProtoBufBuilderProcessor.mergeFromWithoutRepeatedFields +import org.openbase.type.domotic.action.ActionDescriptionType.ActionDescription +import org.openbase.type.language.MultiLanguageTextType.MultiLanguageText + +internal class ProtoBufBuilderProcessorTest { + + companion object { + private const val DE = "de" + private const val EN = "en" + private const val ES = "es" + private const val EN_DOG = "dog" + private const val DE_DOG = "hund" + private const val ES_DOG = "perro" + } + + @Test + fun `should remove repeated fields` () { + ActionDescription.newBuilder().also { + it.descriptionBuilder.addAllEntry( + mutableListOf( + MultiLanguageText.MapFieldEntry.newBuilder().setKey(DE).setValue(DE_DOG).build(), + MultiLanguageText.MapFieldEntry.newBuilder().setKey(EN).setValue(EN_DOG).build(), + MultiLanguageText.MapFieldEntry.newBuilder().setKey(ES).setValue(ES_DOG).build(), + ) + ).build() + }.clearRepeatedFields().apply { + descriptionBuilder.entryCount shouldBeExactly 0 + } + } + + @Test + fun `should only merge from non-repeated fields`() { + val originBuilder = ActionDescription.newBuilder().also { + it.descriptionBuilder.addAllEntry( + mutableListOf( + MultiLanguageText.MapFieldEntry.newBuilder().setKey(DE).setValue(DE_DOG).build(), + MultiLanguageText.MapFieldEntry.newBuilder().setKey(EN).setValue(EN_DOG).build(), + ) + ).build() + } + + val builderToMerge = ActionDescription.newBuilder().also { + it.descriptionBuilder.addAllEntry( + mutableListOf( + MultiLanguageText.MapFieldEntry.newBuilder().setKey(ES).setValue(ES_DOG).build(), + ) + ).build() + } + + originBuilder.mergeFromWithoutRepeatedFields(builderToMerge).apply { + descriptionBuilder.entryCount shouldBeExactly 2 + descriptionBuilder.entryBuilderList[0].key shouldBeEqualComparingTo DE + descriptionBuilder.entryBuilderList[0].value shouldBeEqualComparingTo DE_DOG + descriptionBuilder.entryBuilderList[1].key shouldBeEqualComparingTo EN + descriptionBuilder.entryBuilderList[1].value shouldBeEqualComparingTo EN_DOG + } + } +}