Skip to content

Commit

Permalink
Change collection NbtTags to not implementing Kotlin collections
Browse files Browse the repository at this point in the history
Now `Nbt*Array`/`NbtList`/`NbtCompound` have `collection` properties, and only have methods useful for introspection/deserialization. (Resolves #28)

The `Nbt*Array` types are now backed by `List`s instead of `Array`s. (See #34)
  • Loading branch information
BenWoodworth committed Apr 23, 2023
1 parent d7bed78 commit e1a347e
Show file tree
Hide file tree
Showing 23 changed files with 3,406 additions and 384 deletions.
557 changes: 373 additions & 184 deletions api/knbt.api

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ kotlin {
languageSettings.apply {
optIn("kotlin.contracts.ExperimentalContracts")
optIn("net.benwoodworth.knbt.InternalNbtApi")
optIn("net.benwoodworth.knbt.MIGRATION Acknowledge that NbtCompound now has a stricter get")

if (isTest) optIn("kotlinx.coroutines.ExperimentalCoroutinesApi")
}
Expand Down
138 changes: 88 additions & 50 deletions src/commonMain/kotlin/NbtTag.kt
Original file line number Diff line number Diff line change
Expand Up @@ -81,28 +81,27 @@ public value class NbtDouble(public val value: Double) : NbtTag {
}

@Serializable(NbtByteArraySerializer::class)
public class NbtByteArray private constructor(
internal val content: ByteArray,
private val list: List<Byte>,
) : NbtTag, List<Byte> by list {
public class NbtByteArray(public val content: List<Byte>) : NbtTag, @Suppress("DEPRECATION") NbtByteArrayDeprecations() {
override val type: NbtTagType get() = NbtTagType.TAG_Byte_Array

public constructor(content: ByteArray) : this(content, content.asList())
public val size: Int
get() = content.size

override fun equals(other: Any?): Boolean = when {
this === other -> true
other is NbtTag -> other is NbtByteArray && content.contentEquals(other.content)
else -> list == other
}
public operator fun get(index: Int): Byte =
content[index]

override fun equals(other: Any?): Boolean =
this === other || (other is NbtByteArray && content == other.content)

override fun hashCode(): Int = content.hashCode()

override fun toString(): String =
content.joinToString(separator = ",", prefix = "[B;", postfix = "]") { "${it}B" }

override fun iterator(): ByteIterator = content.iterator()
}

public fun NbtByteArray.getOrNull(index: Int): Byte? =
content.getOrNull(index)

@JvmInline
@Serializable(NbtStringSerializer::class)
public value class NbtString(public val value: String) : NbtTag {
Expand All @@ -113,19 +112,21 @@ public value class NbtString(public val value: String) : NbtTag {

@Serializable(NbtListSerializer::class)
public class NbtList<out T : NbtTag> private constructor(
private val content: List<T>,
) : NbtTag, List<T> by content {
public val content: List<T>,
) : NbtTag, @Suppress("DEPRECATION") NbtListLikeDeprecations<T>() {
override val type: NbtTagType get() = NbtTagType.TAG_List

internal val elementType: NbtTagType
get() = if (isEmpty()) NbtTagType.TAG_End else first().type
get() = if (content.isEmpty()) NbtTagType.TAG_End else content.first().type

public val size: Int
get() = content.size

public operator fun get(index: Int): T =
content[index]

override fun equals(other: Any?): Boolean =
if (content.isEmpty() && other is NbtTag) {
other is NbtList<*> && other.isEmpty()
} else {
content == other
}
this === other || (other is NbtList<*> && content == other.content)

override fun hashCode(): Int = content.hashCode()

Expand Down Expand Up @@ -193,13 +194,46 @@ public class NbtList<out T : NbtTag> private constructor(
}
}

public fun <T : NbtTag> NbtList<T>.getOrNull(index: Int): T? =
content.getOrNull(index)

@Serializable(NbtCompoundSerializer::class)
public class NbtCompound(
private val content: Map<String, NbtTag>,
) : NbtTag, Map<String, NbtTag> by content {
public class NbtCompound(public val content: Map<String, NbtTag>) : NbtTag, @Suppress("DEPRECATION") NbtCompoundDeprecations() {
override val type: NbtTagType get() = NbtTagType.TAG_Compound

override fun equals(other: Any?): Boolean = content == other
/**
* Returns the number of tags in this compound.
*/
public val size: Int
get() = content.size

/**
* Returns the [tag][NbtTag] in this compound with the given [name].
*
* ### knbt v0.12 migration note:
* [NbtCompound.get] now throws [NoSuchElementException] instead of returning `null`.
* Opting in will not be needed after the migration release.
*
* #### How to migrate:
* ```
* compound["name"]!! // Change to: compound["name"]
* compound["name"] // Change to: compound.getOrNull("name")
* ```
*
* @throws NoSuchElementException if this compound does not [contain][contains] a tag with the given [name].
*/
@`MIGRATION Acknowledge that NbtCompound now has a stricter get` // When removed, also remove optIn from build script
public override operator fun get(name: String): NbtTag =
content[name] ?: throw NoSuchElementException("does not contain a tag named \"$name\"")

/**
* Returns `true` if this compound contains a tag with the given [name].
*/
public operator fun contains(name: String): Boolean =
name in content

override fun equals(other: Any?): Boolean =
this === other || (other is NbtCompound && content == other.content)

override fun hashCode(): Int = content.hashCode()

Expand All @@ -213,52 +247,56 @@ public class NbtCompound(
}
}

/**
* Returns the [tag][NbtTag] in this compound with the given [name], or `null` if there is no such tag.
*/
public fun NbtCompound.getOrNull(name: String): NbtTag? =
content[name]

@Serializable(NbtIntArraySerializer::class)
public class NbtIntArray private constructor(
internal val content: IntArray,
private val list: List<Int>,
) : NbtTag, List<Int> by list {
public class NbtIntArray(public val content: List<Int>) : NbtTag, @Suppress("DEPRECATION") NbtIntArrayDeprecations() {
override val type: NbtTagType get() = NbtTagType.TAG_Int_Array

public constructor(content: IntArray) : this(content, content.asList())
public val size: Int
get() = content.size

override fun equals(other: Any?): Boolean = when {
this === other -> true
other is NbtTag -> other is NbtIntArray && content.contentEquals(other.content)
else -> list == other
}
public operator fun get(index: Int): Int =
content[index]

override fun equals(other: Any?): Boolean =
this === other || (other is NbtIntArray && content == other.content)

override fun hashCode(): Int = content.hashCode()

override fun toString(): String =
content.joinToString(separator = ",", prefix = "[I;", postfix = "]")

override fun iterator(): IntIterator = content.iterator()
}

public fun NbtIntArray.getOrNull(index: Int): Int? =
content.getOrNull(index)

@Serializable(NbtLongArraySerializer::class)
public class NbtLongArray private constructor(
internal val content: LongArray,
private val list: List<Long>,
) : NbtTag, List<Long> by list {
public class NbtLongArray(public val content: List<Long>) : NbtTag, @Suppress("DEPRECATION") NbtLongArrayDeprecations() {
override val type: NbtTagType get() = NbtTagType.TAG_Long_Array

public constructor(content: LongArray) : this(content, content.asList())
public val size: Int
get() = content.size

override fun equals(other: Any?): Boolean = when {
this === other -> true
other is NbtTag -> other is NbtLongArray && content.contentEquals(other.content)
else -> list == other
}
public operator fun get(index: Int): Long =
content[index]

override fun equals(other: Any?): Boolean =
this === other || (other is NbtLongArray && content == other.content)

override fun hashCode(): Int = content.hashCode()

override fun toString(): String =
content.joinToString(separator = ",", prefix = "[L;", postfix = "]") { "${it}L" }

override fun iterator(): LongIterator = content.iterator()
}

public fun NbtLongArray.getOrNull(index: Int): Long? =
content.getOrNull(index)

//region NbtTag casting methods
private inline fun <reified T : NbtTag> NbtTag.cast(): T =
this as? T ?: throw IllegalArgumentException("Element ${this::class.simpleName} is not an ${T::class.simpleName}")
Expand Down Expand Up @@ -348,8 +386,8 @@ internal fun <T : NbtTag> NbtTag.nbtList(type: KClass<T>): NbtList<T> = when {
this !is NbtList<*> -> {
throw IllegalArgumentException("Element ${this::class.simpleName} is not an NbtList<${type.simpleName}>")
}
isNotEmpty() && !type.isInstance(first()) -> {
throw IllegalArgumentException("Element NbtList<${first()::class.simpleName}> is not an NbtList<${type.simpleName}>")
size > 0 && !type.isInstance(this[0]) -> {
throw IllegalArgumentException("Element NbtList<${this[0]::class.simpleName}> is not an NbtList<${type.simpleName}>")
}
else -> this as NbtList<T>
}
Expand Down
12 changes: 6 additions & 6 deletions src/commonMain/kotlin/NbtTagBuilders.kt
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,10 @@ public fun NbtListBuilder<NbtInt>.add(value: Int): Boolean = add(NbtInt(value))
public fun NbtListBuilder<NbtLong>.add(value: Long): Boolean = add(NbtLong(value))
public fun NbtListBuilder<NbtFloat>.add(value: Float): Boolean = add(NbtFloat(value))
public fun NbtListBuilder<NbtDouble>.add(value: Double): Boolean = add(NbtDouble(value))
public fun NbtListBuilder<NbtByteArray>.add(value: ByteArray): Boolean = add(NbtByteArray(value))
public fun NbtListBuilder<NbtByteArray>.add(value: ByteArray): Boolean = add(NbtByteArray(value.asList()))
public fun NbtListBuilder<NbtString>.add(value: String): Boolean = add(NbtString(value))
public fun NbtListBuilder<NbtIntArray>.add(value: IntArray): Boolean = add(NbtIntArray(value))
public fun NbtListBuilder<NbtLongArray>.add(value: LongArray): Boolean = add(NbtLongArray(value))
public fun NbtListBuilder<NbtIntArray>.add(value: IntArray): Boolean = add(NbtIntArray(value.asList()))
public fun NbtListBuilder<NbtLongArray>.add(value: LongArray): Boolean = add(NbtLongArray(value.asList()))

@OptIn(ExperimentalTypeInference::class)
public inline fun <T : NbtTag> NbtListBuilder<NbtList<NbtTag>>.addNbtList(
Expand Down Expand Up @@ -155,10 +155,10 @@ public fun NbtCompoundBuilder.put(key: String, value: Int): NbtTag? = put(key, N
public fun NbtCompoundBuilder.put(key: String, value: Long): NbtTag? = put(key, NbtLong(value))
public fun NbtCompoundBuilder.put(key: String, value: Float): NbtTag? = put(key, NbtFloat(value))
public fun NbtCompoundBuilder.put(key: String, value: Double): NbtTag? = put(key, NbtDouble(value))
public fun NbtCompoundBuilder.put(key: String, value: ByteArray): NbtTag? = put(key, NbtByteArray(value))
public fun NbtCompoundBuilder.put(key: String, value: ByteArray): NbtTag? = put(key, NbtByteArray(value.asList()))
public fun NbtCompoundBuilder.put(key: String, value: String): NbtTag? = put(key, NbtString(value))
public fun NbtCompoundBuilder.put(key: String, value: IntArray): NbtTag? = put(key, NbtIntArray(value))
public fun NbtCompoundBuilder.put(key: String, value: LongArray): NbtTag? = put(key, NbtLongArray(value))
public fun NbtCompoundBuilder.put(key: String, value: IntArray): NbtTag? = put(key, NbtIntArray(value.asList()))
public fun NbtCompoundBuilder.put(key: String, value: LongArray): NbtTag? = put(key, NbtLongArray(value.asList()))

@OptIn(ExperimentalTypeInference::class)
@JvmName("putNbtList\$T")
Expand Down
12 changes: 6 additions & 6 deletions src/commonMain/kotlin/NbtTagSerializers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,12 @@ internal object NbtByteArraySerializer : KSerializer<NbtByteArray> {
override val descriptor: SerialDescriptor = NbtByteArrayDescriptor

override fun serialize(encoder: Encoder, value: NbtByteArray): Unit =
encoder.asNbtEncoder().encodeCollection(descriptor, value) { index, element ->
encoder.asNbtEncoder().encodeCollection(descriptor, value.content) { index, element ->
encodeByteElement(descriptor, index, element)
}

override fun deserialize(decoder: Decoder): NbtByteArray =
NbtByteArray(decoder.decodeList(descriptor, CompositeDecoder::decodeByteElement).toByteArray())
NbtByteArray(decoder.decodeList(descriptor, CompositeDecoder::decodeByteElement))
}

internal object NbtStringSerializer : KSerializer<NbtString> {
Expand Down Expand Up @@ -169,12 +169,12 @@ internal object NbtIntArraySerializer : KSerializer<NbtIntArray> {
override val descriptor: SerialDescriptor = NbtIntArrayDescriptor

override fun serialize(encoder: Encoder, value: NbtIntArray): Unit =
encoder.asNbtEncoder().encodeCollection(descriptor, value) { index, element ->
encoder.asNbtEncoder().encodeCollection(descriptor, value.content) { index, element ->
encodeIntElement(descriptor, index, element)
}

override fun deserialize(decoder: Decoder): NbtIntArray =
NbtIntArray(decoder.decodeList(descriptor, CompositeDecoder::decodeIntElement).toIntArray())
NbtIntArray(decoder.decodeList(descriptor, CompositeDecoder::decodeIntElement))
}

internal object NbtLongArraySerializer : KSerializer<NbtLongArray> {
Expand All @@ -187,12 +187,12 @@ internal object NbtLongArraySerializer : KSerializer<NbtLongArray> {
override val descriptor: SerialDescriptor = NbtLongArrayDescriptor

override fun serialize(encoder: Encoder, value: NbtLongArray): Unit =
encoder.asNbtEncoder().encodeCollection(descriptor, value) { index, element ->
encoder.asNbtEncoder().encodeCollection(descriptor, value.content) { index, element ->
encodeLongElement(descriptor, index, element)
}

override fun deserialize(decoder: Decoder): NbtLongArray =
NbtLongArray(decoder.decodeList(descriptor, CompositeDecoder::decodeLongElement).toLongArray())
NbtLongArray(decoder.decodeList(descriptor, CompositeDecoder::decodeLongElement))
}

internal fun Encoder.asNbtEncoder(): NbtEncoder =
Expand Down
Loading

0 comments on commit e1a347e

Please sign in to comment.