aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/const/Const.kt65
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/const/ConstItemStack.kt39
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/const/ConstNBT.kt67
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/const/Unconst.kt14
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