diff options
4 files changed, 178 insertions, 7 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/const/Const.kt b/src/main/java/at/hannibal2/skyhanni/utils/const/Const.kt index 8da2498e0..30d8ef0c6 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/const/Const.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/const/Const.kt @@ -16,8 +16,9 @@ package at.hannibal2.skyhanni.utils.const value class Const<T> private constructor( /** * Unsafely access the underlying object. Callers of this method promise not to modify the returned instance, or to - * leak this instance to any other codepaths which modify the instance. Whenever possible callers should wrap - * objects they return which offer a view into this object into a [Const] of its own. + * leak this instance or references/views into this instance to any other codepaths which modify it. + * Whenever possible callers should wrap objects they return which offer a view into this object into a [Const] of + * its own. */ @PublishedApi internal val unsafeMutable: T, @@ -28,11 +29,69 @@ value class Const<T> private constructor( * internally. This should ideally be done by every instance of [value] being wrapped in a [Const] (and other * references to be discarded as quickly as possible). */ - fun <T> fromOwned(value: T): Const<T> { + fun <T> newUnchecked(value: T): Const<T> { return Const(value) } } + + /** + * Map the contained object into a new [Const]. + */ + inline fun <U> unsafeMap( + /** + * This lambda needs to hold the same guarantees for its parameter like for access to [unsafeMutable]. + * It can however assume that the return value will never be modified by other code. + */ + mapper: (T) -> U + ): Const<U> { + return unsafeMutable + .let(mapper) + .let(::newUnchecked) + } + + /** + * Flat map the contained object into a new [Const]. Behaves like [unsafeMap] but allowing the [mapper] to return a [Const] + */ + inline fun <U> unsafeFlatMap( + /** + * This lambda needs to hold the same guarantees for its parameter like for access to [unsafeMutable]. + */ + mapper: (T) -> Const<U> + ): Const<U> { + return unsafeMutable + .let(mapper) + } } +/** + * Flatten two nested [Const] into just one. + */ +fun <T> Const<Const<T>>.flatten(): Const<T> = unsafeFlatMap { it } + +/** + * Lift nullability out of a [Const] to allow for easier `?.` operations. + */ +fun <T : Any> Const<T?>.liftNull(): Const<T>? = unsafeMutable?.let(Const.Companion::newUnchecked) + +inline fun <reified U : T, T> Const<T>.tryCast(): Const<U>? = (unsafeMutable as? U)?.let(Const.Companion::newUnchecked) +/** + * List a list out of a const. This is legal since [List] does not allow for mutating unless it's elements are individually + * mutable as long. The caller may never cast the returned instance to [MutableList]. + */ +fun <T> Const<List<T>>.liftList(): List<Const<T>> { + @Suppress("UNCHECKED_CAST") + // This cast is valid since List<T> and List<Const<T>> are always the same at runtime, guaranteed by `@JvmInline` + return unsafeMutable as List<Const<T>> +} +/** + * Lift const out of a [List]. The caller must guarantee that the list instance is never modified. This means it was + * either constructed directly as a [List] or if it originally comes from a [MutableList], mutations operations on this + * instance are never used. + */ +fun <T> List<Const<T>>.unliftList(): Const<List<T>> { + @Suppress("UNCHECKED_CAST") + // This cast is valid since List<T> and List<Const<T>> are always the same at runtime, guaranteed by `@JvmInline` + return Const.newUnchecked(this as List<T>) +} diff --git a/src/main/java/at/hannibal2/skyhanni/utils/const/ConstItemStack.kt b/src/main/java/at/hannibal2/skyhanni/utils/const/ConstItemStack.kt index 5e9484ffd..176ea5e47 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/const/ConstItemStack.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/const/ConstItemStack.kt @@ -3,7 +3,9 @@ package at.hannibal2.skyhanni.utils.const import net.minecraft.item.EnumDyeColor import net.minecraft.item.Item import net.minecraft.item.ItemStack +import net.minecraft.nbt.NBTBase import net.minecraft.nbt.NBTTagCompound +import net.minecraft.nbt.NBTTagString /** * Access a shallow copy of the underlying item stack. Callers of this method promise not to modify the nbt data of @@ -26,17 +28,17 @@ fun Const<ItemStack>.getOwnedDeepCopy(): ItemStack { /** * Returns the [ItemStack.stackSize] */ -inline val Const<ItemStack>.stackSize: Int get() = unsafeMutable.stackSize +inline val Const<ItemStack>.stackSize: Int get() = unsafeMap(ItemStack::stackSize).unconst /** * Returns the [item type](ItemStack.item) */ -inline val Const<ItemStack>.itemType: Item get() = unsafeMutable.item +inline val Const<ItemStack>.itemType: Item get() = unsafeMap(ItemStack::getItem).unconst /** * Returns the [damage or metadata](ItemStack.metadata) */ -inline val Const<ItemStack>.damage: Int get() = unsafeMutable.metadata +inline val Const<ItemStack>.damage: Int get() = unsafeMap(ItemStack::getItemDamage).unconst /** * Interprets the [damage] of this item as a [color](EnumDyeColor). This is only valid for some [item types](itemType), @@ -44,5 +46,34 @@ inline val Const<ItemStack>.damage: Int get() = unsafeMutable.metadata */ inline val Const<ItemStack>.color: EnumDyeColor get() = EnumDyeColor.byDyeDamage(damage) +/** + * Access the underlying NBT data of this item stack in a [Const]. + */ inline val Const<ItemStack>.nbt: Const<NBTTagCompound>? - get() = unsafeMutable.tagCompound?.let(Const.Companion::fromOwned) + get() = unsafeMap(ItemStack::getTagCompound).liftNull() + +/** + * Access the NBT data relating to display data. + */ +inline val Const<ItemStack>.displayNbt: Const<NBTTagCompound>? + get() = nbt?.getTagCompound("display") + +/** + * Get the display name of this item stack according to its NBT. + */ +fun Const<ItemStack>.getDisplayName(): String? = + displayNbt?.getString("Name")?.getString() + +/** + * Get the lore of this item stack according to its NBT. + * @throws NullPointerException if `display.Lore[*]` is not a string in the NBT + */ +fun Const<ItemStack>.getLore(): List<String>? = + displayNbt + ?.getList("Lore") + ?.intoList() + ?.liftList() + ?.map { it.tryCast<NBTTagString, NBTBase>()!!.getString() } + + + diff --git a/src/main/java/at/hannibal2/skyhanni/utils/const/ConstNBT.kt b/src/main/java/at/hannibal2/skyhanni/utils/const/ConstNBT.kt new file mode 100644 index 000000000..1725747f8 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/const/ConstNBT.kt @@ -0,0 +1,67 @@ +package at.hannibal2.skyhanni.utils.const + +import net.minecraft.nbt.NBTBase +import net.minecraft.nbt.NBTTagCompound +import net.minecraft.nbt.NBTTagInt +import net.minecraft.nbt.NBTTagList +import net.minecraft.nbt.NBTTagLong +import net.minecraft.nbt.NBTTagString + + +/** + * This method indexes into an NBTTagCompound searching for a tag. + * The caller must guarantee that the [T]s type identifier is [tag]. + * This means for any `instance` of [NBTBase], `instance.getId() == tag` iff `instance is T`. + */ +@PublishedApi +internal fun <T : NBTBase> Const<NBTTagCompound>.getAndCast(name: String, tag: Int): Const<T>? = + unsafeMap { + if (it.hasKey(name, tag)) + @Suppress("UNCHECKED_CAST") + it.getTag(name) as T + else + null + }.liftNull() + +private val TAG_COMPOUND_KEY = NBTTagCompound().id.toInt() +private val TAG_COMPOUND_STRING = NBTTagString().id.toInt() +private val TAG_COMPOUND_INT = NBTTagInt(0).id.toInt() +private val TAG_COMPOUND_LONG = NBTTagLong(0L).id.toInt() +private val TAG_COMPOUND_LIST = NBTTagList().id.toInt() + +fun Const<NBTTagCompound>.getTagCompound(name: String): Const<NBTTagCompound>? = + getAndCast(name, TAG_COMPOUND_KEY) + +fun Const<NBTTagCompound>.getString(name: String): Const<NBTTagString>? = + getAndCast(name, TAG_COMPOUND_STRING) + +fun Const<NBTTagCompound>.getInt(name: String): Const<NBTTagInt>? = + getAndCast(name, TAG_COMPOUND_INT) + +fun Const<NBTTagCompound>.getLong(name: String): Const<NBTTagLong>? = + getAndCast(name, TAG_COMPOUND_LONG) + +fun Const<NBTTagCompound>.getList(name: String): Const<NBTTagList>? = + getAndCast(name, TAG_COMPOUND_LIST) + +val Const<NBTTagList>.size: Int + get() = unsafeMap(NBTTagList::tagCount).unconst + +fun Const<NBTTagList>.getTag(i: Int): Const<NBTBase> { + return unsafeMap { it.get(i) } +} + +inline fun Const<NBTTagList>.intoList(): Const<List<NBTBase>> { + return unsafeMap { + val build = mutableListOf<NBTBase>() + for (i in 0..<it.tagCount()) { + build.add(it.get(i)) + } + build + } +} + +fun Const<NBTTagString>.getString(): String = unsafeMap(NBTTagString::getString).unconst +fun Const<NBTTagInt>.getInt(): Int = unsafeMap(NBTTagInt::getInt).unconst +fun Const<NBTTagLong>.getLong(): Long = unsafeMap(NBTTagLong::getLong).unconst + diff --git a/src/main/java/at/hannibal2/skyhanni/utils/const/Unconst.kt b/src/main/java/at/hannibal2/skyhanni/utils/const/Unconst.kt new file mode 100644 index 000000000..5a2f2ce21 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/const/Unconst.kt @@ -0,0 +1,14 @@ +/** + * This file contains safe dereferences for [Const.unsafeMutable]. By convention `unconst` wrappers should only available + * for types without interior mutability. + */ + +package at.hannibal2.skyhanni.utils.const + +import net.minecraft.item.Item + + +inline val Const<String>.unconst: String get() = unsafeMutable +inline val Const<Int>.unconst: Int get() = unsafeMutable +inline val Const<Long>.unconst: Long get() = unsafeMutable +inline val Const<Item>.unconst: Item get() = unsafeMutable |
