Skip to content

Instantly share code, notes, and snippets.

@Alvin-LB
Created August 8, 2017 16:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Alvin-LB/58b523942c221340b6f150a9d7e8c0be to your computer and use it in GitHub Desktop.
Save Alvin-LB/58b523942c221340b6f150a9d7e8c0be to your computer and use it in GitHub Desktop.
import com.google.common.collect.Maps;
import org.bukkit.block.BlockState;
import org.bukkit.entity.Entity;
import org.bukkit.inventory.ItemStack;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.charset.Charset;
import java.util.*;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
/**
* A class to extract and provide wrappers for various NBT data.
*
* The code, relying heavily on NMS, uses reflection, which
* should make it somewhat version-independent. Wrappers are also
* provided for all types of NBT so users don't have to deal with
* any NMS.
*
* NOTE: This requires ReflectUtil.java
*
* @author AlvinB
*/
@SuppressWarnings({"SimplifiableIfStatement", "WeakerAccess", "unused", "SameParameterValue"})
public class NBTUtil {
private static final Class<?> TAG_COMPOUND_CLASS = ReflectUtil.getNMSClass("NBTTagCompound").getOrThrow();
private static final Method AS_NMS_COPY = ReflectUtil.getMethod(ReflectUtil.getCBClass("inventory.CraftItemStack").getOrThrow(), "asNMSCopy", ItemStack.class).getOrThrow();
private static final Field ITEM_STACK_TAG = ReflectUtil.getDeclaredFieldByType(ReflectUtil.getNMSClass("ItemStack").getOrThrow(), TAG_COMPOUND_CLASS, 0, true).getOrThrow();
private static final Method AS_BUKKIT_COPY = ReflectUtil.getMethod(ReflectUtil.getCBClass("inventory.CraftItemStack").getOrThrow(), "asBukkitCopy", ReflectUtil.getNMSClass("ItemStack").getOrThrow()).getOrThrow();
private static final Method ENTITY_GET_HANDLE = ReflectUtil.getMethod(ReflectUtil.getCBClass("entity.CraftEntity").getOrThrow(), "getHandle").getOrThrow();
private static final Method ENTITY_SAVE_TO_NBT = ReflectUtil.getMethodByTypeAndParams(ReflectUtil.getNMSClass("Entity").getOrThrow(), TAG_COMPOUND_CLASS, 0, TAG_COMPOUND_CLASS).getOrThrow();
private static final Method ENTITY_LOAD_FROM_NBT = ReflectUtil.getMethodByPredicate(ReflectUtil.getNMSClass("Entity").getOrThrow(), new ReflectUtil.MethodPredicate()
.withParams(TAG_COMPOUND_CLASS).withoutModifiers(Modifier.ABSTRACT).withReturnType(void.class), 0).getOrThrow();
private static final Class<?> TILE_ENTITY_CLASS = ReflectUtil.getNMSClass("TileEntity").getOrThrow();
private static final Method GET_TILE_ENTITY = ReflectUtil.getMethodByType(ReflectUtil.getCBClass("block.CraftBlockState").getOrThrow(), TILE_ENTITY_CLASS, 0).getOrThrow();
private static final Method TILE_ENTITY_SAVE_TO_NBT = ReflectUtil.getMethodByTypeAndParams(TILE_ENTITY_CLASS, TAG_COMPOUND_CLASS, 0, TAG_COMPOUND_CLASS).getOrThrow();
private static final Method TILE_ENTITY_LOAD_FROM_NBT = ReflectUtil.getMethodByTypeAndParams(TILE_ENTITY_CLASS, void.class, 0, TAG_COMPOUND_CLASS).getOrThrow();
/**
* Loads a compressed NBTTagCompound from the specified stream.
*
* The stream must start with a compound, and it should be compressed
* using GZIP compression.
*
* NOTE: If an exception occurs doing the reading of the NBT data, it will be
* rethrown as a RuntimeException.
*
* @param inputStream the uncompressed InputStream
* @return the read NBTTagCompound
*/
public static NBTTagCompound readCompressedNBTFromStream(InputStream inputStream) {
try {
return readUncompressedNBTFromStream(new GZIPInputStream(inputStream));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Loads an NBTTagCompound from the specified stream.
*
* The stream must start with a compound, and it should be entirely
* uncompressed. If the data is compressed, use readCompressedNBTFromStream().
*
* NOTE: If an exception occurs doing the reading of the NBT data, it will be
* rethrown as a RuntimeException.
*
* @param inputStream the InputStream
* @return the read NBTTagCompound
*/
public static NBTTagCompound readUncompressedNBTFromStream(InputStream inputStream) {
DataInputStream dataInputStream = (inputStream instanceof DataInputStream ? (DataInputStream) inputStream : new DataInputStream(inputStream));
return (NBTTagCompound) readTag(dataInputStream, null);
}
private static NBTTagBase readTag(DataInputStream inputStream, NBTTagCompound compound) {
try {
int tagId = inputStream.readUnsignedByte();
String name = null;
// End Tags don't have a name
if (tagId != 0) {
name = inputStream.readUTF();
}
return readTagValue(inputStream, tagId, name, compound);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static NBTTagBase readTagValue(DataInputStream inputStream, int tagId, String name, NBTTagCompound compound) {
try {
NBTTagBase tag;
switch (tagId) {
case 0:
return new NBTTagEnd();
case 1:
tag = new NBTTagByte(inputStream.readByte());
break;
case 2:
tag = new NBTTagShort(inputStream.readShort());
break;
case 3:
tag = new NBTTagInt(inputStream.readInt());
break;
case 4:
tag = new NBTTagLong(inputStream.readLong());
break;
case 5:
tag = new NBTTagFloat(inputStream.readFloat());
break;
case 6:
tag = new NBTTagDouble(inputStream.readDouble());
break;
case 7:
int length = inputStream.readInt();
byte[] value = new byte[length];
inputStream.readFully(value);
tag = new NBTTagByteArray(value);
break;
case 8:
tag = new NBTTagString(inputStream.readUTF());
break;
case 9:
int type = inputStream.readUnsignedByte();
int listLength = inputStream.readInt();
NBTTagList<NBTTagBase> listTag = new NBTTagList<>();
for (int i = 0; i < listLength; i++) {
listTag.add(readTagValue(inputStream, type, null, null));
}
tag = listTag;
break;
case 10:
NBTTagCompound compoundTag = new NBTTagCompound();
NBTTagBase childTag = readTag(inputStream, compoundTag);
while (childTag != null && childTag.getId() != 0) {
childTag = readTag(inputStream, compoundTag);
}
tag = compoundTag;
break;
case 11:
int arrayLength = inputStream.readInt();
int[] array = new int[arrayLength];
for (int i = 0; i < arrayLength; i++) {
array[i] = inputStream.readInt();
}
tag = new NBTTagIntArray(array);
break;
default:
throw new RuntimeException("NBT tag id " + tagId + " is unknown!");
}
if (compound != null) {
compound.set(name, tag);
}
return tag;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Writes a compressed NBTTagCompound to the specified stream.
*
* NOTE: If an exception occurs doing the writing of the NBT data, it will be
* rethrown as a RuntimeException.
*
* @param outputStream the uncompressed OutputStream
*/
public static void writeCompressedNBTToStream(OutputStream outputStream, NBTTagCompound compound) {
try {
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream);
writeUncompressedNBTToStream(gzipOutputStream, compound);
gzipOutputStream.finish();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Writes an NBTTagCompound to the specified stream.
*
* NOTE: If an exception occurs doing the reading of the NBT data, it will be
* rethrown as a RuntimeException.
*
* @param outputStream the OutputStream
*/
public static void writeUncompressedNBTToStream(OutputStream outputStream, NBTTagCompound compound) {
DataOutputStream dataOutputStream = (outputStream instanceof DataOutputStream ? (DataOutputStream) outputStream : new DataOutputStream(outputStream));
writeTag(dataOutputStream, compound, "");
}
private static void writeTag(DataOutputStream outputStream, NBTTagBase tag, String name) {
try {
outputStream.writeByte(tag.getId());
// End tags don't have a name
if (tag.getId() != 0) {
outputStream.writeUTF(name);
}
writeTagValue(outputStream, tag);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static void writeTagValue(DataOutputStream outputStream, NBTTagBase tag) {
try {
switch (tag.getId()) {
case 0:
// End tags don't have any data
break;
case 1:
outputStream.writeByte(((NBTTagByte) tag).getValue());
break;
case 2:
outputStream.writeShort(((NBTTagShort) tag).getValue());
break;
case 3:
outputStream.writeInt(((NBTTagInt) tag).getValue());
break;
case 4:
outputStream.writeLong(((NBTTagLong) tag).getValue());
break;
case 5:
outputStream.writeFloat(((NBTTagFloat) tag).getValue());
break;
case 6:
outputStream.writeDouble(((NBTTagDouble) tag).getValue());
break;
case 7:
byte[] value = ((NBTTagByteArray) tag).getValue();
outputStream.writeInt(value.length);
outputStream.write(value);
break;
case 8:
outputStream.writeUTF(((NBTTagString) tag).getValue());
break;
case 9:
@SuppressWarnings("unchecked") NBTTagList<NBTTagBase> listTag = (NBTTagList<NBTTagBase>) tag;
outputStream.write(listTag.getType() & 0xFF);
outputStream.writeInt(listTag.size());
for (NBTTagBase baseTag : listTag.getContents()) {
writeTagValue(outputStream, baseTag);
}
break;
case 10:
NBTTagCompound compound = (NBTTagCompound) tag;
for (Map.Entry<String, NBTTagBase> entry : compound.getContents().entrySet()) {
writeTag(outputStream, entry.getValue(), entry.getKey());
}
writeTag(outputStream, new NBTTagEnd(), null);
break;
case 11:
int[] array = ((NBTTagIntArray) tag).getValue();
outputStream.writeInt(array.length);
for (int i : array) {
outputStream.writeInt(i);
}
break;
default:
throw new RuntimeException("tag id " + tag.getId() + " is unknown");
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Gets the NBT data from an ItemStack
*
* @param itemStack the ItemStack
* @return an NBTTagCompound containing the NBT data.
*/
public static NBTTagCompound getItemNBT(ItemStack itemStack) {
Object handle = ReflectUtil.invokeMethod(null, AS_NMS_COPY, itemStack).getOrThrow();
Object tagCompound = ReflectUtil.getFieldValue(handle, ITEM_STACK_TAG).getOrThrow();
if (tagCompound == null) {
return new NBTTagCompound();
}
return NBTTagCompound.fromHandle(tagCompound);
}
/**
* Sets the NBT data to an ItemStack
*
* This may or may not set the NBT to the
* ItemStack directly, depending on how the
* ItemStack was created, which is why an
* ItemStack is returned. It is recommended
* that you only use the returned ItemStack,
* as the ItemStack passed as the argument
* might not have been affected.
*
* @param itemStack the ItemStack
* @param compound the compound to set
* @return the modified ItemStack
*/
public static ItemStack setItemNBT(ItemStack itemStack, NBTTagCompound compound) {
Object handle = ReflectUtil.invokeMethod(null, AS_NMS_COPY, itemStack).getOrThrow();
ReflectUtil.setFieldValue(handle, ITEM_STACK_TAG, compound.getHandle()).getOrThrow();
return (ItemStack) ReflectUtil.invokeMethod(null, AS_BUKKIT_COPY, handle).getOrThrow();
}
/**
* Gets the NBT data from an Entity
*
* @param entity the entity
* @return an NBTTagCompound containing the NBT data.
*/
public static NBTTagCompound getEntityNBT(Entity entity) {
Object handle = ReflectUtil.invokeMethod(entity, ENTITY_GET_HANDLE).getOrThrow();
Object tagCompound = new NBTTagCompound().getHandle();
ReflectUtil.invokeMethod(handle, ENTITY_SAVE_TO_NBT, tagCompound).getOrThrow();
return NBTTagCompound.fromHandle(tagCompound);
}
/**
* Sets the NBT data to an Entity
*
* @param entity the Entity
* @param compound the compound to set
*/
public static void setEntityNBT(Entity entity, NBTTagCompound compound) {
Object handle = ReflectUtil.invokeMethod(entity, ENTITY_GET_HANDLE).getOrThrow();
Object tagCompound = compound.getHandle();
ReflectUtil.invokeMethod(handle, ENTITY_LOAD_FROM_NBT, tagCompound).getOrThrow();
}
/**
* Gets the NBT data from a TileEntity
*
* NOTE: If the BlockState is not connected to a TileEntity,
* null is returned.
*
* @param blockState the TileEntity's BlockState
* @return an NBTTagCompound containing the NBT data.
*/
public static NBTTagCompound getTileEntityNBT(BlockState blockState) {
Object handle = ReflectUtil.invokeMethod(blockState, GET_TILE_ENTITY).getOrThrow();
if (handle == null) {
return null;
}
Object tagCompound = new NBTTagCompound().getHandle();
ReflectUtil.invokeMethod(handle, TILE_ENTITY_SAVE_TO_NBT, tagCompound).getOrThrow();
return NBTTagCompound.fromHandle(tagCompound);
}
/**
* Sets the NBT data to a TileEntity
*
* NOTE: If the BlockState is not connected to a TileEntity,
* nothing will happen.
*
* @param blockState the TileEntity's BlockState
* @param compound the compound to set
*/
public static void setTileEntityNBT(BlockState blockState, NBTTagCompound compound) {
Object handle = ReflectUtil.invokeMethod(blockState, GET_TILE_ENTITY).getOrThrow();
if (handle == null) {
return;
}
Object tagCompound = compound.getHandle();
ReflectUtil.invokeMethod(handle, TILE_ENTITY_LOAD_FROM_NBT, tagCompound).getOrThrow();
}
/**
* The abstract base of all the NBT wrappers.
*/
public static abstract class NBTTagBase {
/**
* Gets the NMS equivalent to this NBT wrapper.
*
* @return the NMS NBT tag
*/
public abstract Object getHandle();
/**
* Gets the id of this tag
*
* @return the id
*/
public abstract int getId();
/**
* Gets the NBT wrapper equivalent to this NMS NBT tag
*
* @param handle the NMS NBT tag
* @return the NBT Wrapper
*/
public static NBTTagBase fromHandle(Object handle) {
switch (handle.getClass().getSimpleName()) {
case "NBTTagEnd":
return new NBTTagEnd();
case "NBTTagByte":
return NBTTagByte.fromHandle(handle);
case "NBTTagShort":
return NBTTagShort.fromHandle(handle);
case "NBTTagInt":
return NBTTagInt.fromHandle(handle);
case "NBTTagLong":
return NBTTagLong.fromHandle(handle);
case "NBTTagFloat":
return NBTTagFloat.fromHandle(handle);
case "NBTTagDouble":
return NBTTagDouble.fromHandle(handle);
case "NBTTagByteArray":
return NBTTagByteArray.fromHandle(handle);
case "NBTTagString":
return NBTTagString.fromHandle(handle);
case "NBTTagList":
return NBTTagList.fromHandle(handle);
case "NBTTagCompound":
return NBTTagCompound.fromHandle(handle);
case "NBTTagIntArray":
return NBTTagIntArray.fromHandle(handle);
}
return null;
}
}
/**
* A wrapper for NBTTagCompound
*
* Compounds contain several other tags nested inside them and are
* usually what the objects using NBT use to store the data.
*/
public static class NBTTagCompound extends NBTTagBase {
private static final Constructor<?> TAG_COMPOUND_CONSTRUCTOR = ReflectUtil.getConstructor(ReflectUtil.getNMSClass("NBTTagCompound").getOrThrow()).getOrThrow();
private static final Field MAP = ReflectUtil.getDeclaredFieldByType(ReflectUtil.getNMSClass("NBTTagCompound").getOrThrow(), Map.class, 0, true).getOrThrow();
/**
* Gets the NBT wrapper equivalent to this NMS NBT tag
*
* @param handle the NMS NBT tag
* @return the NBT Wrapper
*/
public static NBTTagCompound fromHandle(Object handle) {
NBTTagCompound tagCompound = new NBTTagCompound();
//noinspection unchecked
Map<String, ?> map = (Map<String, ?>) ReflectUtil.getFieldValue(handle, MAP).getOrThrow();
for (Map.Entry<String, ?> entry : map.entrySet()) {
tagCompound.set(entry.getKey(), NBTTagBase.fromHandle(entry.getValue()));
}
return tagCompound;
}
private Map<String, NBTTagBase> values = Maps.newHashMap();
/**
* Initializes an empty NBTTagCompound
*/
public NBTTagCompound() {
}
/**
* Initializes an NBTTagCompound using the values in the map provided
*
* @param map the map containing the values to carry over
*/
public NBTTagCompound(Map<String, NBTTagBase> map) {
values.putAll(map);
}
/**
* Sets a key to a certain value
*
* @param key the key
* @param value the value
*/
public void set(String key, NBTTagBase value) {
values.put(key, value);
}
/**
* Sets a key to a certain byte
*
* @param key the key
* @param value the byte
*/
public void setByte(String key, byte value) {
this.set(key, new NBTTagByte(value));
}
/**
* Sets a key to a certain byte
*
* NOTE: There is no boolean tag, so this is internally stored as a byte with either 1 (representing true)
* or 0 (representing false) as its value. Any byte value other than zero will be interpreted as true.
*
* @param key the key
* @param value the boolean
*/
public void setBoolean(String key, boolean value) {
this.set(key, new NBTTagByte((byte) (value ? 1 : 0)));
}
/**
* Sets a key to a certain short
*
* @param key the key
* @param value the short
*/
public void setShort(String key, short value) {
this.set(key, new NBTTagShort(value));
}
/**
* Sets a key to a certain int
*
* @param key the key
* @param value the int
*/
public void setInt(String key, int value) {
this.set(key, new NBTTagInt(value));
}
/**
* Sets a key to a certain long
*
* @param key the key
* @param value the long
*/
public void setLong(String key, long value) {
this.set(key, new NBTTagLong(value));
}
/**
* Sets a key to a certain float
*
* @param key the key
* @param value the float
*/
public void setFloat(String key, float value) {
this.set(key, new NBTTagFloat(value));
}
/**
* Sets a key to a certain double
*
* @param key the key
* @param value the double
*/
public void setDouble(String key, double value) {
this.set(key, new NBTTagDouble(value));
}
/**
* Sets a key to a certain byte array
*
* @param key the key
* @param value the byte array
*/
public void setByteArray(String key, byte[] value) {
this.set(key, new NBTTagByteArray(value));
}
/**
* Sets a key to a certain String
*
* @param key the key
* @param value the String
*/
public void setString(String key, String value) {
this.set(key, new NBTTagString(value));
}
/**
* Sets a key to a certain List
*
* @param key the key
* @param value the List
*/
public void setList(String key, List<NBTTagBase> value) {
this.set(key, new NBTTagList<>(value));
}
/**
* Sets a key to a certain int array
*
* @param key the key
* @param value the int array
*/
public void setIntArray(String key, int[] value) {
this.set(key, new NBTTagIntArray(value));
}
/**
* Gets the value for a certain key
*
* NOTE: If the key doesn't correspond to a value, an exception will be raised.
*
* @param key the key
* @return the value
*/
public NBTTagBase get(String key) {
return values.get(key);
}
/**
* Gets the byte value for a certain key
*
* NOTE: If the key doesn't correspond to a value or the value is not of
* type byte, an exception will be raised.
*
* @param key the key
* @return the byte
*/
public byte getByte(String key) {
return ((NBTTagByte) this.get(key)).getValue();
}
/**
* Gets the boolean value for a certain key
*
* NOTE: If the key doesn't correspond to a value or the value is not of
* type byte, an exception will be raised.
*
* NOTE #2: There is no boolean tag, so this is internally stored as a byte with either 1 (representing true)
* or 0 (representing false) as its value. Any byte value other than zero will be interpreted as true.
*
* @param key the key
* @return the byte
*/
public boolean getBoolean(String key) {
return ((NBTTagByte) this.get(key)).getValue() != 0;
}
/**
* Gets the short value for a certain key
*
* NOTE: If the key doesn't correspond to a value or the value is not of
* type short, an exception will be raised.
*
* @param key the key
* @return the short
*/
public short getShort(String key) {
return ((NBTTagShort) this.get(key)).getValue();
}
/**
* Gets the int value for a certain key
*
* NOTE: If the key doesn't correspond to a value or the value is not of
* type int, an exception will be raised.
*
* @param key the key
* @return the int
*/
public int getInt(String key) {
return ((NBTTagInt) this.get(key)).getValue();
}
/**
* Gets the long value for a certain key
*
* NOTE: If the key doesn't correspond to a value or the value is not of
* type long, an exception will be raised.
*
* @param key the key
* @return the long
*/
public long getLong(String key) {
return ((NBTTagLong) this.get(key)).getValue();
}
/**
* Gets the float value for a certain key
*
* NOTE: If the key doesn't correspond to a value or the value is not of
* type float, an exception will be raised.
*
* @param key the key
* @return the float
*/
public float getFloat(String key) {
return ((NBTTagFloat) this.get(key)).getValue();
}
/**
* Gets the double value for a certain key
*
* NOTE: If the key doesn't correspond to a value or the value is not of
* type double, an exception will be raised.
*
* @param key the key
* @return the double
*/
public double getDouble(String key) {
return ((NBTTagDouble) this.get(key)).getValue();
}
/**
* Gets the byte array value for a certain key
*
* NOTE: If the key doesn't correspond to a value or the value is not of
* type byte array, an exception will be raised.
*
* @param key the key
* @return the byte array
*/
public byte[] getByteArray(String key) {
return ((NBTTagByteArray) this.get(key)).getValue();
}
/**
* Gets the String value for a certain key
*
* NOTE: If the key doesn't correspond to a value or the value is not of
* type String, an exception will be raised.
*
* @param key the key
* @return the String
*/
public String getString(String key) {
return ((NBTTagString) this.get(key)).getValue();
}
/**
* Gets the List value for a certain key
*
* NOTE: If the key doesn't correspond to a value or the value is not of
* type List, an exception will be raised.
*
* @param key the key
* @return the List
*/
public List<NBTTagBase> getList(String key) {
//noinspection unchecked
return ((NBTTagList<NBTTagBase>) this.get(key)).getContents();
}
/**
* Gets the int array value for a certain key
*
* NOTE: If the key doesn't correspond to a value or the value is not of
* type int array, an exception will be raised.
*
* @param key the key
* @return the int array
*/
public int[] getIntArray(String key) {
return ((NBTTagIntArray) this.get(key)).getValue();
}
/**
* Returns an unmodifiable map with the current contents of this compound
*
* @return the map
*/
public Map<String, NBTTagBase> getContents() {
return Collections.unmodifiableMap(this.values);
}
/**
* Tells whether the compound currently has a specified key
*
* @param key the key
* @return whether the key exists
*/
public boolean hasKey(String key) {
return this.values.containsKey(key);
}
/**
* Tells whether the compound currently has a specified key with a specified type
*
* @param key the key
* @param type the type
* @return whether the key exists and is of the correct type
*/
public boolean hasKeyWithType(String key, Class<? extends NBTTagBase> type) {
return this.values.containsKey(key) && this.values.get(key).getClass() == type;
}
/**
* Gets the NMS equivalent to this NBT wrapper.
*
* @return the NMS NBT tag
*/
@Override
public Object getHandle() {
Object nbtTagCompound = ReflectUtil.invokeConstructor(TAG_COMPOUND_CONSTRUCTOR).getOrThrow();
//noinspection unchecked
Map<String, Object> handleMap = (Map<String, Object>) ReflectUtil.getFieldValue(nbtTagCompound, MAP).getOrThrow();
for (Map.Entry<String, NBTTagBase> entry : this.values.entrySet()) {
handleMap.put(entry.getKey(), entry.getValue().getHandle());
}
return nbtTagCompound;
}
/**
* Gets the id of this tag
*
* @return the id
*/
@Override
public int getId() {
return 10;
}
@Override
public String toString() {
return "NBTTagCompound{values=" + this.values + "}";
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof NBTTagCompound)) {
return false;
}
return this.values.equals(((NBTTagCompound) obj).values);
}
@Override
public int hashCode() {
return Objects.hash(this.values);
}
}
/**
* A wrapper for NBTTagEnd
*
* An end tag contains no data and is only used in serialization of NBT. It's only included
* for completeness purposes.
*/
public static class NBTTagEnd extends NBTTagBase {
private static final Constructor<?> TAG_END_CONSTRUCTOR = ReflectUtil.getConstructor(ReflectUtil.getNMSClass("NBTTagCompound").getOrThrow()).getOrThrow();
/**
* Gets the NMS equivalent to this NBT wrapper.
*
* @return the NMS NBT tag
*/
@Override
public Object getHandle() {
return ReflectUtil.invokeConstructor(TAG_END_CONSTRUCTOR).getOrThrow();
}
/**
* Gets the id of this tag
*
* @return the id
*/
@Override
public int getId() {
return 0;
}
@Override
public String toString() {
return "NBTTagEnd";
}
@Override
public boolean equals(Object obj) {
return obj instanceof NBTTagEnd;
}
@Override
public int hashCode() {
return 0;
}
}
/**
* A wrapper for NBTTagByte
*
* Bytes are an 8 bit data type, like in Java. They are also used for booleans,
* with 0 representing false and 1 representing true.
*/
public static class NBTTagByte extends NBTTagBase {
private static final Constructor<?> TAG_BYTE_CONSTRUCTOR = ReflectUtil.getConstructor(ReflectUtil.getNMSClass("NBTTagByte").getOrThrow(), byte.class).getOrThrow();
private static final Method GET_VALUE = findValueMethod(ReflectUtil.getNMSClass("NBTTagByte").getOrThrow(), byte.class);
/**
* Gets the NBT wrapper equivalent to this NMS NBT tag
*
* @param handle the NMS NBT tag
* @return the NBT Wrapper
*/
public static NBTTagByte fromHandle(Object handle) {
byte b = (byte) ReflectUtil.invokeMethod(handle, GET_VALUE).getOrThrow();
return new NBTTagByte(b);
}
private byte value;
/**
* Initializes this byte tag with the specified value
*
* @param value the byte
*/
public NBTTagByte(byte value) {
this.value = value;
}
/**
* Sets the value of this byte tag
*
* @param value the byte
*/
public void setValue(byte value) {
this.value = value;
}
/**
* Gets the value of this byte tag
*
* @return the byte
*/
public byte getValue() {
return value;
}
/**
* Gets the NMS equivalent to this NBT wrapper.
*
* @return the NMS NBT tag
*/
@Override
public Object getHandle() {
return ReflectUtil.invokeConstructor(TAG_BYTE_CONSTRUCTOR, value).getOrThrow();
}
/**
* Gets the id of this tag
*
* @return the id
*/
@Override
public int getId() {
return 1;
}
@Override
public String toString() {
return "NBTTagByte{value=" + value + "}";
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof NBTTagByte)) {
return false;
}
return this.value == ((NBTTagByte) obj).value;
}
@Override
public int hashCode() {
return Objects.hash(this.value);
}
}
/**
* A wrapper for NBTTagShort
*
* Shorts are 16 bit numbers, like in Java. They should only be used
* if you are sure that the number won't exceed 16 bits. If you aren't, use
* long or int instead.
*/
public static class NBTTagShort extends NBTTagBase {
private static final Constructor<?> TAG_SHORT_CONSTRUCTOR = ReflectUtil.getConstructor(ReflectUtil.getNMSClass("NBTTagShort").getOrThrow(), short.class).getOrThrow();
private static final Method GET_VALUE = findValueMethod(ReflectUtil.getNMSClass("NBTTagShort").getOrThrow(), short.class);
/**
* Gets the NBT wrapper equivalent to this NMS NBT tag
*
* @param handle the NMS NBT tag
* @return the NBT Wrapper
*/
public static NBTTagShort fromHandle(Object handle) {
short s = (short) ReflectUtil.invokeMethod(handle, GET_VALUE).getOrThrow();
return new NBTTagShort(s);
}
private short value;
/**
* Initializes this short tag with the specified value
*
* @param value the short
*/
public NBTTagShort(short value) {
this.value = value;
}
/**
* Sets the value of this short tag
*
* @param value the short
*/
public void setValue(short value) {
this.value = value;
}
/**
* Gets the value of this short tag
*
* @return the short
*/
public short getValue() {
return value;
}
/**
* Gets the NMS equivalent to this NBT wrapper.
*
* @return the NMS NBT tag
*/
@Override
public Object getHandle() {
return ReflectUtil.invokeConstructor(TAG_SHORT_CONSTRUCTOR, value).getOrThrow();
}
/**
* Gets the id of this tag
*
* @return the id
*/
@Override
public int getId() {
return 2;
}
@Override
public String toString() {
return "NBTTagShort{value=" + value + "}";
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof NBTTagShort)) {
return false;
}
return this.value == ((NBTTagShort) obj).value;
}
@Override
public int hashCode() {
return Objects.hash(this.value);
}
}
/**
* A wrapper for NBTTagInt
*
* Ints are 32 bit numbers, like in Java, and are the most common data type for storing integer
* values.
*/
public static class NBTTagInt extends NBTTagBase {
private static final Constructor<?> TAG_INT_CONSTRUCTOR = ReflectUtil.getConstructor(ReflectUtil.getNMSClass("NBTTagInt").getOrThrow(), int.class).getOrThrow();
private static final Method GET_VALUE = findValueMethod(ReflectUtil.getNMSClass("NBTTagInt").getOrThrow(), int.class);
/**
* Gets the NBT wrapper equivalent to this NMS NBT tag
*
* @param handle the NMS NBT tag
* @return the NBT Wrapper
*/
public static NBTTagInt fromHandle(Object handle) {
int i = (int) ReflectUtil.invokeMethod(handle, GET_VALUE).getOrThrow();
return new NBTTagInt(i);
}
private int value;
/**
* Initializes this int tag with the specified value
*
* @param value the int
*/
public NBTTagInt(int value) {
this.value = value;
}
/**
* Sets the value of this int tag
*
* @param value the int
*/
public void setValue(int value) {
this.value = value;
}
/**
* Gets the value of this int tag
*
* @return the int
*/
public int getValue() {
return value;
}
/**
* Gets the NMS equivalent to this NBT wrapper.
*
* @return the NMS NBT tag
*/
@Override
public Object getHandle() {
return ReflectUtil.invokeConstructor(TAG_INT_CONSTRUCTOR, value).getOrThrow();
}
/**
* Gets the id of this tag
*
* @return the id
*/
@Override
public int getId() {
return 3;
}
@Override
public String toString() {
return "NBTTagInt{value=" + value + "}";
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof NBTTagInt)) {
return false;
}
return this.value == ((NBTTagInt) obj).value;
}
@Override
public int hashCode() {
return Objects.hash(this.value);
}
}
/**
* A wrapper for NBTTagLong
*
* Longs are 64 bit numbers, like in Java, and are most commonly used to store timestamps.
*/
public static class NBTTagLong extends NBTTagBase {
private static final Constructor<?> TAG_LONG_CONSTRUCTOR = ReflectUtil.getConstructor(ReflectUtil.getNMSClass("NBTTagLong").getOrThrow(), long.class).getOrThrow();
private static final Method GET_VALUE = findValueMethod(ReflectUtil.getNMSClass("NBTTagLong").getOrThrow(), long.class);
/**
* Gets the NBT wrapper equivalent to this NMS NBT tag
*
* @param handle the NMS NBT tag
* @return the NBT Wrapper
*/
public static NBTTagLong fromHandle(Object handle) {
long l = (long) ReflectUtil.invokeMethod(handle, GET_VALUE).getOrThrow();
return new NBTTagLong(l);
}
private long value;
/**
* Initializes this long tag with the specified value
*
* @param value the long
*/
public NBTTagLong(long value) {
this.value = value;
}
/**
* Sets the value of this long tag
*
* @param value the long
*/
public void setValue(long value) {
this.value = value;
}
/**
* Gets the value of this long tag
*
* @return the long
*/
public long getValue() {
return value;
}
/**
* Gets the NMS equivalent to this NBT wrapper.
*
* @return the NMS NBT tag
*/
@Override
public Object getHandle() {
return ReflectUtil.invokeConstructor(TAG_LONG_CONSTRUCTOR, this.value).getOrThrow();
}
/**
* Gets the id of this tag
*
* @return the id
*/
@Override
public int getId() {
return 4;
}
@Override
public String toString() {
return "NBTTagLong{value=" + this.value + "}";
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof NBTTagLong)) {
return false;
}
return this.value == ((NBTTagLong) obj).value;
}
@Override
public int hashCode() {
return Objects.hash(this.value);
}
}
/**
* A wrapper for NBTTagFloat
*
* Floats are 32 bit floating point numbers, like in Java. They should only be used if you are sure
* the number will not exceed 32 bits. If you aren't, use double instead.
*/
public static class NBTTagFloat extends NBTTagBase {
private static final Constructor<?> TAG_FLOAT_CONSTRUCTOR = ReflectUtil.getConstructor(ReflectUtil.getNMSClass("NBTTagFloat").getOrThrow(), float.class).getOrThrow();
private static final Method GET_VALUE = findValueMethod(ReflectUtil.getNMSClass("NBTTagFloat").getOrThrow(), float.class);
/**
* Gets the NBT wrapper equivalent to this NMS NBT tag
*
* @param handle the NMS NBT tag
* @return the NBT Wrapper
*/
public static NBTTagFloat fromHandle(Object handle) {
float f = (float) ReflectUtil.invokeMethod(handle, GET_VALUE).getOrThrow();
return new NBTTagFloat(f);
}
private float value;
/**
* Initializes this float tag with the specified value
*
* @param value the float
*/
public NBTTagFloat(float value) {
this.value = value;
}
/**
* Sets the value of this float tag
*
* @param value the float
*/
public void setValue(float value) {
this.value = value;
}
/**
* Gets the value of this float tag
*
* @return the float
*/
public float getValue() {
return value;
}
/**
* Gets the NMS equivalent to this NBT wrapper.
*
* @return the NMS NBT tag
*/
@Override
public Object getHandle() {
return ReflectUtil.invokeConstructor(TAG_FLOAT_CONSTRUCTOR, this.value).getOrThrow();
}
/**
* Gets the id of this tag
*
* @return the id
*/
@Override
public int getId() {
return 5;
}
@Override
public String toString() {
return "NBTTagFloat{value=" + this.value + "}";
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof NBTTagFloat)) {
return false;
}
return this.value == ((NBTTagFloat) obj).value;
}
@Override
public int hashCode() {
return Objects.hash(this.value);
}
}
/**
* A wrapper for NBTTagDouble
*
* Doubles are 64 bit floating point numbers, like in Java, and are the most
* common data type to store floating point numbers.
*
*/
public static class NBTTagDouble extends NBTTagBase {
private static final Constructor<?> TAG_DOUBLE_CONSTRUCTOR = ReflectUtil.getConstructor(ReflectUtil.getNMSClass("NBTTagDouble").getOrThrow(), double.class).getOrThrow();
private static final Method GET_VALUE = findValueMethod(ReflectUtil.getNMSClass("NBTTagDouble").getOrThrow(), double.class);
/**
* Gets the NBT wrapper equivalent to this NMS NBT tag
*
* @param handle the NMS NBT tag
* @return the NBT Wrapper
*/
public static NBTTagDouble fromHandle(Object handle) {
double d = ((double) ReflectUtil.invokeMethod(handle, GET_VALUE).getOrThrow());
return new NBTTagDouble(d);
}
private double value;
/**
* Initializes this double tag with the specified value
*
* @param value the double
*/
public NBTTagDouble(double value) {
this.value = value;
}
/**
* Sets the value of this double tag
*
* @param value the double
*/
public void setValue(double value) {
this.value = value;
}
/**
* Gets the value of this double tag
*
* @return the double
*/
public double getValue() {
return value;
}
/**
* Gets the NMS equivalent to this NBT wrapper.
*
* @return the NMS NBT tag
*/
@Override
public Object getHandle() {
return ReflectUtil.invokeConstructor(TAG_DOUBLE_CONSTRUCTOR, this.value).getOrThrow();
}
/**
* Gets the id of this tag
*
* @return the id
*/
@Override
public int getId() {
return 6;
}
@Override
public String toString() {
return "NBTTagDouble{value=" + this.value + "}";
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof NBTTagDouble)) {
return false;
}
return this.value == ((NBTTagDouble) obj).value;
}
}
/**
* A wrapper for NBTTagByteArray
*
* Array of bytes with any length from 0 to roughly 2^31
*/
public static class NBTTagByteArray extends NBTTagBase {
private static final Constructor<?> TAG_BYTE_ARRAY_CONSTRUCTOR = ReflectUtil.getConstructor(ReflectUtil.getNMSClass("NBTTagByteArray").getOrThrow(), byte[].class).getOrThrow();
private static final Method GET_VALUE = findValueMethod(ReflectUtil.getNMSClass("NBTTagByteArray").getOrThrow(), byte[].class);
/**
* Gets the NBT wrapper equivalent to this NMS NBT tag
*
* @param handle the NMS NBT tag
* @return the NBT Wrapper
*/
public static NBTTagByteArray fromHandle(Object handle) {
byte[] b = (byte[]) ReflectUtil.invokeMethod(handle, GET_VALUE).getOrThrow();
return new NBTTagByteArray(b);
}
private byte[] value;
/**
* Initializes this byte array tag with the specified value
*
* @param value the byte array
*/
public NBTTagByteArray(byte[] value) {
this.value = value;
}
/**
* Sets the value of this byte array tag
*
* @param value the byte array
*/
public void setValue(byte[] value) {
this.value = value;
}
/**
* Gets the value of this byte array tag
*
* @return the byte array
*/
public byte[] getValue() {
return value;
}
/**
* Gets the NMS equivalent to this NBT wrapper.
*
* @return the NMS NBT tag
*/
@Override
public Object getHandle() {
return ReflectUtil.invokeConstructor(TAG_BYTE_ARRAY_CONSTRUCTOR, (Object) this.value).getOrThrow();
}
/**
* Gets the id of this tag
*
* @return the id
*/
@Override
public int getId() {
return 7;
}
@Override
public String toString() {
return "NBTTagByteArray{value=" + Arrays.toString(this.value) + "}";
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof NBTTagByteArray)) {
return false;
}
return Arrays.equals(this.value, ((NBTTagByteArray) obj).value);
}
@Override
public int hashCode() {
return Objects.hash((Object) this.value);
}
}
/**
* A wrapper for NBTTagString
*
* A String of UTF-8 characters with any length from 0 to roughly 2^15
*/
public static class NBTTagString extends NBTTagBase {
private static final Constructor<?> TAG_STRING_CONSTRUCTOR = ReflectUtil.getConstructor(ReflectUtil.getNMSClass("NBTTagString").getOrThrow(), String.class).getOrThrow();
private static final Method GET_VALUE = findValueMethod(ReflectUtil.getNMSClass("NBTTagString").getOrThrow(), String.class);
/**
* Gets the NBT wrapper equivalent to this NMS NBT tag
*
* @param handle the NMS NBT tag
* @return the NBT Wrapper
*/
public static NBTTagString fromHandle(Object handle) {
String s = (String) ReflectUtil.invokeMethod(handle, GET_VALUE).getOrThrow();
return new NBTTagString(s);
}
private String value;
/**
* Initializes this String tag with the specified value
*
* @param value the String
*/
public NBTTagString(String value) {
this.value = value;
}
/**
* Sets the value of this String tag
*
* @param value the String
*/
public void setValue(String value) {
this.value = value;
}
/**
* Gets the value of this String tag
*
* @return the String
*/
public String getValue() {
return value;
}
/**
* Gets the NMS equivalent to this NBT wrapper.
*
* @return the NMS NBT tag
*/
@Override
public Object getHandle() {
return ReflectUtil.invokeConstructor(TAG_STRING_CONSTRUCTOR, this.value).getOrThrow();
}
/**
* Gets the id of this tag
*
* @return the id
*/
@Override
public int getId() {
return 8;
}
@Override
public String toString() {
return "NBTTagString{value=" + this.value + "}";
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof NBTTagString)) {
return false;
}
return this.value.equals(((NBTTagString) obj).value);
}
@Override
public int hashCode() {
return Objects.hash(this.value);
}
}
/**
* A wrapper for NBTTagList
*
* A List containing NBT tags with a maximum length of roughly 2^31
*
* @param <T> the type of this list
*/
public static class NBTTagList<T extends NBTTagBase> extends NBTTagBase {
private static final Constructor<?> TAG_LIST_CONSTRUCTOR = ReflectUtil.getConstructor(ReflectUtil.getNMSClass("NBTTagList").getOrThrow()).getOrThrow();
private static final Field LIST = ReflectUtil.getDeclaredFieldByType(ReflectUtil.getNMSClass("NBTTagList").getOrThrow(), List.class, 0, true).getOrThrow();
private static final Method ADD_METHOD = ReflectUtil.getMethodByParams(ReflectUtil.getNMSClass("NBTTagList").getOrThrow(), 0, ReflectUtil.getNMSClass("NBTBase").getOrThrow()).getOrThrow();
/**
* Gets the NBT wrapper equivalent to this NMS NBT tag
*
* @param handle the NMS NBT tag
* @return the NBT Wrapper
*/
public static NBTTagList<NBTTagBase> fromHandle(Object handle) {
List<?> list = (List<?>) ReflectUtil.getFieldValue(handle, LIST).getOrThrow();
NBTTagList<NBTTagBase> tagList = new NBTTagList<>();
for (Object o : list) {
tagList.add(NBTTagBase.fromHandle(o));
}
return tagList;
}
private List<T> value;
private int type = -1;
/**
* Initializes this List tag with the specified value
*
* @param value the List
*/
public NBTTagList(List<T> value) {
this.value = value;
if (value.size() > 0) {
type = value.get(0).getId();
}
for (NBTTagBase tag : value) {
if (tag.getId() != type) {
throw new RuntimeException("id " + tag.getId() + " does not match List type " + type);
}
}
}
/**
* Initializes this List tag with the specified value
*
* @param value the contents
*/
@SafeVarargs
public NBTTagList(T... value) {
// Arrays.asList() returns an immutable List, so we make a new, mutable one.
this.value = new ArrayList<>(Arrays.asList(value));
if (this.value.size() > 0) {
type = this.value.get(0).getId();
}
for (NBTTagBase tag : value) {
if (tag.getId() != type) {
throw new RuntimeException("id " + tag.getId() + " does not match List type " + type);
}
}
}
/**
* Adds an element to the List
*
* @param t the element to add
* @return whether the List was changed (see {@link List#add(Object)})
*/
public boolean add(T t) {
if (t == null) {
throw new IllegalArgumentException("element to add cannot be null");
}
if (type == -1) {
type = t.getId();
} else if (t.getId() != type) {
throw new IllegalArgumentException("id " + t.getId() + " does not match List type " + type);
}
return value.add(t);
}
/**
* Removes an element from the List
*
* @param t the element to remove
* @return whether the List contained this element (see {@link List#remove(Object)})
*/
public boolean remove(T t) {
return value.remove(t);
}
/**
* Gets the element at the specified index
*
* @param index the index
* @return the element
*/
public T get(int index) {
return value.get(index);
}
/**
* Sets the element at the specified index
* @param index the index
* @param t the element to set
* @return the element previously at the index
*/
public T set(int index, T t) {
if (t == null) {
throw new IllegalArgumentException("element to add cannot be null");
}
if (type == -1) {
type = t.getId();
} else if (t.getId() != type) {
throw new IllegalArgumentException("id " + t.getId() + " does not match List type " + type);
}
return value.set(index, t);
}
/**
* Gets the size of the List
*
* @return the size
*/
public int size() {
return value.size();
}
/**
* Clears the List
*/
public void clear() {
this.value.clear();
}
/**
* Adds all elements in the specified collection
*
* @param collection the elements
* @return whether the List was changed (see {@link List#addAll(Collection)})
*/
public boolean addAll(Collection<? extends T> collection) {
if (collection == null) {
throw new IllegalArgumentException("collection to add cannot be null");
}
if (type == -1 && collection.size() > 0) {
type = collection.iterator().next().getId();
}
for (T t : collection) {
if (t.getId() != type) {
throw new IllegalArgumentException("id " + t.getId() + " does not match List type " + type);
}
}
return this.value.addAll(collection);
}
/**
* Gets the contents of this List
*
* @return an unmodifiable List with the contents of this List tag
*/
public List<T> getContents() {
return Collections.unmodifiableList(this.value);
}
/**
* Gets the type of the elements contained in this List
*
* @return the id, or -1 if the list does not yet have an id.
*/
public int getType() {
return this.type;
}
/**
* Gets the NMS equivalent to this NBT wrapper.
*
* @return the NMS NBT tag
*/
@Override
public Object getHandle() {
Object handle = ReflectUtil.invokeConstructor(TAG_LIST_CONSTRUCTOR).getOrThrow();
for (NBTTagBase baseTag : this.value) {
ReflectUtil.invokeMethod(handle, ADD_METHOD, baseTag.getHandle()).getOrThrow();
}
return handle;
}
/**
* Gets the id of this tag
*
* @return the id
*/
@Override
public int getId() {
return 9;
}
@Override
public String toString() {
return "NBTTagList{value=" + this.value + "}";
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof NBTTagList)) {
return false;
}
return this.value.equals(((NBTTagList) obj).value);
}
@Override
public int hashCode() {
return Objects.hash(this.value);
}
}
/**
* A wrapper for NBTTagIntArray
*
* Array of ints with any length from 0 to roughly 2^31
*/
public static class NBTTagIntArray extends NBTTagBase {
private static final Constructor<?> TAG_INT_ARRAY_CONSTRUCTOR = ReflectUtil.getConstructor(ReflectUtil.getNMSClass("NBTTagIntArray").getOrThrow(), int[].class).getOrThrow();
private static final Method GET_VALUE = findValueMethod(ReflectUtil.getNMSClass("NBTTagIntArray").getOrThrow(), int[].class);
/**
* Gets the NBT wrapper equivalent to this NMS NBT tag
*
* @param handle the NMS NBT tag
* @return the NBT Wrapper
*/
public static NBTTagIntArray fromHandle(Object handle) {
int[] i = (int[]) ReflectUtil.invokeMethod(handle, GET_VALUE).getOrThrow();
return new NBTTagIntArray(i);
}
private int[] value;
/**
* Initializes this int array tag with the specified value
*
* @param value the int array
*/
public NBTTagIntArray(int[] value) {
this.value = value;
}
/**
* Sets the value of this int array tag
*
* @param value the int array
*/
public void setValue(int[] value) {
this.value = value;
}
/**
* Gets the value of this int array tag
*
* @return the int array
*/
public int[] getValue() {
return value;
}
/**
* Gets the NMS equivalent to this NBT wrapper.
*
* @return the NMS NBT tag
*/
@Override
public Object getHandle() {
return ReflectUtil.invokeConstructor(TAG_INT_ARRAY_CONSTRUCTOR, (Object) this.value).getOrThrow();
}
/**
* Gets the id of this tag
*
* @return the id
*/
@Override
public int getId() {
return 11;
}
@Override
public String toString() {
return "NBTTagIntArray{value=" + Arrays.toString(this.value) + "}";
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof NBTTagIntArray)) {
return false;
}
return Arrays.equals(this.value, ((NBTTagIntArray) obj).value);
}
@Override
public int hashCode() {
return Objects.hash((Object) this.value);
}
}
private static final List<String> METHOD_NAMES = Arrays.asList("equals", "hashCode", "toString", "getTypeId");;
private static Method findValueMethod(Class<?> clazz, Class<?> type) {
int index = 0;
while (true) {
Method method = ReflectUtil.getMethodByType(clazz, type, index++).getOrThrow();
if (Modifier.isStatic(method.getModifiers()) || !Modifier.isPublic(method.getModifiers())) {
continue;
}
if (METHOD_NAMES.contains(method.getName())) {
continue;
}
return method;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment