#1295: Define native persistent data types for lists

By: Bjarne Koll <lynxplay101@gmail.com>
This commit is contained in:
CraftBukkit/Spigot 2024-01-06 16:03:58 +11:00
parent 71ca5a7bdf
commit 8cd8851498
7 changed files with 482 additions and 175 deletions

View file

@ -0,0 +1,11 @@
--- a/net/minecraft/nbt/NBTTagList.java
+++ b/net/minecraft/nbt/NBTTagList.java
@@ -145,7 +145,7 @@
private final List<NBTBase> list;
private byte type;
- NBTTagList(List<NBTBase> list, byte b0) {
+ public NBTTagList(List<NBTBase> list, byte b0) { // PAIL: package-private -> public
this.list = list;
this.type = b0;
}

View file

@ -7,27 +7,31 @@ import org.bukkit.inventory.meta.tags.ItemTagType;
import org.bukkit.persistence.PersistentDataAdapterContext;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import org.jetbrains.annotations.NotNull;
public final class DeprecatedContainerTagType<Z> implements PersistentDataType<PersistentDataContainer, Z> {
public final class DeprecatedContainerTagType<C> implements PersistentDataType<PersistentDataContainer, C> {
private final ItemTagType<CustomItemTagContainer, Z> deprecated;
private final ItemTagType<CustomItemTagContainer, C> deprecated;
DeprecatedContainerTagType(ItemTagType<CustomItemTagContainer, Z> deprecated) {
DeprecatedContainerTagType(ItemTagType<CustomItemTagContainer, C> deprecated) {
this.deprecated = deprecated;
}
@NotNull
@Override
public Class<PersistentDataContainer> getPrimitiveType() {
return PersistentDataContainer.class;
}
@NotNull
@Override
public Class<Z> getComplexType() {
public Class<C> getComplexType() {
return deprecated.getComplexType();
}
@NotNull
@Override
public PersistentDataContainer toPrimitive(Z complex, PersistentDataAdapterContext context) {
public PersistentDataContainer toPrimitive(@NotNull C complex, @NotNull PersistentDataAdapterContext context) {
CustomItemTagContainer deprecated = this.deprecated.toPrimitive(complex, new DeprecatedItemAdapterContext(context));
Preconditions.checkArgument(deprecated instanceof DeprecatedCustomTagContainer, "Could not wrap deprecated API due to foreign CustomItemTagContainer implementation %s", deprecated.getClass().getSimpleName());
@ -39,8 +43,9 @@ public final class DeprecatedContainerTagType<Z> implements PersistentDataType<P
return new CraftPersistentDataContainer(craftTagContainer.getRaw(), craftTagContainer.getDataTagTypeRegistry());
}
@NotNull
@Override
public Z fromPrimitive(PersistentDataContainer primitive, PersistentDataAdapterContext context) {
public C fromPrimitive(@NotNull PersistentDataContainer primitive, @NotNull PersistentDataAdapterContext context) {
Preconditions.checkArgument(primitive instanceof CraftPersistentDataContainer, "Could not wrap deprecated API due to foreign PersistentMetadataContainer implementation %s", primitive.getClass().getSimpleName());
return this.deprecated.fromPrimitive(new DeprecatedCustomTagContainer(primitive), new DeprecatedItemAdapterContext(context));

View file

@ -3,32 +3,37 @@ package org.bukkit.craftbukkit.inventory.tags;
import org.bukkit.inventory.meta.tags.ItemTagType;
import org.bukkit.persistence.PersistentDataAdapterContext;
import org.bukkit.persistence.PersistentDataType;
import org.jetbrains.annotations.NotNull;
public final class DeprecatedItemTagType<T, Z> implements PersistentDataType<T, Z> {
public final class DeprecatedItemTagType<P, C> implements PersistentDataType<P, C> {
private final ItemTagType<T, Z> deprecated;
private final ItemTagType<P, C> deprecated;
public DeprecatedItemTagType(ItemTagType<T, Z> deprecated) {
public DeprecatedItemTagType(ItemTagType<P, C> deprecated) {
this.deprecated = deprecated;
}
@NotNull
@Override
public Class<T> getPrimitiveType() {
public Class<P> getPrimitiveType() {
return deprecated.getPrimitiveType();
}
@NotNull
@Override
public Class<Z> getComplexType() {
public Class<C> getComplexType() {
return deprecated.getComplexType();
}
@NotNull
@Override
public T toPrimitive(Z complex, PersistentDataAdapterContext context) {
public P toPrimitive(@NotNull C complex, @NotNull PersistentDataAdapterContext context) {
return this.deprecated.toPrimitive(complex, new DeprecatedItemAdapterContext(context));
}
@NotNull
@Override
public Z fromPrimitive(T primitive, PersistentDataAdapterContext context) {
public C fromPrimitive(@NotNull P primitive, @NotNull PersistentDataAdapterContext context) {
return this.deprecated.fromPrimitive(primitive, new DeprecatedItemAdapterContext(context));
}
}

View file

@ -14,6 +14,7 @@ import org.bukkit.craftbukkit.util.CraftNBTTagConfigSerializer;
import org.bukkit.persistence.PersistentDataAdapterContext;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import org.jetbrains.annotations.NotNull;
public class CraftPersistentDataContainer implements PersistentDataContainer {
@ -33,16 +34,16 @@ public class CraftPersistentDataContainer implements PersistentDataContainer {
@Override
public <T, Z> void set(NamespacedKey key, PersistentDataType<T, Z> type, Z value) {
public <T, Z> void set(@NotNull NamespacedKey key, @NotNull PersistentDataType<T, Z> type, @NotNull Z value) {
Preconditions.checkArgument(key != null, "The NamespacedKey key cannot be null");
Preconditions.checkArgument(type != null, "The provided type cannot be null");
Preconditions.checkArgument(value != null, "The provided value cannot be null");
this.customDataTags.put(key.toString(), registry.wrap(type.getPrimitiveType(), type.toPrimitive(value, adapterContext)));
this.customDataTags.put(key.toString(), this.registry.wrap(type, type.toPrimitive(value, adapterContext)));
}
@Override
public <T, Z> boolean has(NamespacedKey key, PersistentDataType<T, Z> type) {
public <T, Z> boolean has(@NotNull NamespacedKey key, @NotNull PersistentDataType<T, Z> type) {
Preconditions.checkArgument(key != null, "The NamespacedKey key cannot be null");
Preconditions.checkArgument(type != null, "The provided type cannot be null");
@ -51,7 +52,7 @@ public class CraftPersistentDataContainer implements PersistentDataContainer {
return false;
}
return registry.isInstanceOf(type.getPrimitiveType(), value);
return this.registry.isInstanceOf(type, value);
}
@Override
@ -60,7 +61,7 @@ public class CraftPersistentDataContainer implements PersistentDataContainer {
}
@Override
public <T, Z> Z get(NamespacedKey key, PersistentDataType<T, Z> type) {
public <T, Z> Z get(@NotNull NamespacedKey key, @NotNull PersistentDataType<T, Z> type) {
Preconditions.checkArgument(key != null, "The NamespacedKey key cannot be null");
Preconditions.checkArgument(type != null, "The provided type cannot be null");
@ -69,15 +70,17 @@ public class CraftPersistentDataContainer implements PersistentDataContainer {
return null;
}
return type.fromPrimitive(registry.extract(type.getPrimitiveType(), value), adapterContext);
return type.fromPrimitive(this.registry.extract(type, value), adapterContext);
}
@NotNull
@Override
public <T, Z> Z getOrDefault(NamespacedKey key, PersistentDataType<T, Z> type, Z defaultValue) {
Z z = get(key, type);
public <T, Z> Z getOrDefault(@NotNull NamespacedKey key, @NotNull PersistentDataType<T, Z> type, @NotNull Z defaultValue) {
Z z = this.get(key, type);
return z != null ? z : defaultValue;
}
@NotNull
@Override
public Set<NamespacedKey> getKeys() {
Set<NamespacedKey> keys = new HashSet<>();
@ -93,7 +96,7 @@ public class CraftPersistentDataContainer implements PersistentDataContainer {
}
@Override
public void remove(NamespacedKey key) {
public void remove(@NotNull NamespacedKey key) {
Preconditions.checkArgument(key != null, "The NamespacedKey key cannot be null");
this.customDataTags.remove(key.toString());
@ -104,6 +107,7 @@ public class CraftPersistentDataContainer implements PersistentDataContainer {
return this.customDataTags.isEmpty();
}
@NotNull
@Override
public void copyTo(PersistentDataContainer other, boolean replace) {
Preconditions.checkArgument(other != null, "The target container cannot be null");
@ -127,7 +131,7 @@ public class CraftPersistentDataContainer implements PersistentDataContainer {
return false;
}
Map<String, NBTBase> myRawMap = getRaw();
Map<String, NBTBase> myRawMap = this.getRaw();
Map<String, NBTBase> theirRawMap = ((CraftPersistentDataContainer) obj).getRaw();
return Objects.equals(myRawMap, theirRawMap);
@ -160,7 +164,7 @@ public class CraftPersistentDataContainer implements PersistentDataContainer {
}
public CraftPersistentDataTypeRegistry getDataTagTypeRegistry() {
return registry;
return this.registry;
}
@Override

View file

@ -1,11 +1,16 @@
package org.bukkit.craftbukkit.persistence;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.primitives.Primitives;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Function;
import net.minecraft.nbt.NBTBase;
import net.minecraft.nbt.NBTTagByte;
@ -20,85 +25,110 @@ import net.minecraft.nbt.NBTTagLong;
import net.minecraft.nbt.NBTTagLongArray;
import net.minecraft.nbt.NBTTagShort;
import net.minecraft.nbt.NBTTagString;
import org.bukkit.persistence.ListPersistentDataType;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import org.jetbrains.annotations.NotNull;
/**
* This class represents a registry that contains the used adapters for.
* The craft persistent data type registry, at its core, is responsible for the
* conversion process between a {@link PersistentDataType} and a respective
* {@link NBTBase} instance.
* <p>
* It does so by creating {@link TagAdapter} instances that are capable of
* mappings the supported "primitive types" of {@link PersistentDataType}s to
* their respective {@link NBTBase} instances.
* <p>
* To accomplish this, the class makes <b>heavy</b> use of raw arguments. Their
* validity is enforced by the mapping of class to {@link TagAdapter}
* internally.
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public final class CraftPersistentDataTypeRegistry {
private final Function<Class, TagAdapter> CREATE_ADAPTER = this::createAdapter;
private class TagAdapter<T, Z extends NBTBase> {
private final Function<T, Z> builder;
private final Function<Z, T> extractor;
private final Class<T> primitiveType;
private final Class<Z> nbtBaseType;
public TagAdapter(Class<T> primitiveType, Class<Z> nbtBaseType, Function<T, Z> builder, Function<Z, T> extractor) {
this.primitiveType = primitiveType;
this.nbtBaseType = nbtBaseType;
this.builder = builder;
this.extractor = extractor;
}
/**
* A tag adapter is a closely related type to a specific implementation of
* the {@link NBTBase} interface. It exists to convert from and to the
* respective value of a {@link NBTBase} to a "primitive type" for later
* usage in {@link PersistentDataType}.
*
* @param primitiveType the class of the primitive type, e.g.
* {@link String}.
* @param nbtBaseType the class of the tag implementation that is used to
* store this primitive type, e.g {@link NBTTagString}.
* @param nmsTypeByte the byte identifier of the tag as defined by
* {@link NBTBase#getId()}.
* @param builder a bi function that is responsible for mapping a "primitive
* type" and its respective {@link PersistentDataType} to a {@link NBTBase}.
* @param extractor a bi function that is responsible for extracting a
* "primitive type" from a {@link NBTBase} given a
* {@link PersistentDataType}.
* @param matcher a bi predicate that is responsible for computing if the
* passed {@link NBTBase} holds a value that the {@link PersistentDataType}
* can extract.
* @param <P> the generic type of the primitive the persistent data type
* expects.
* @param <T> the generic type of the concrete {@link NBTBase}
* implementation that the primitive type is mapped into.
*/
private record TagAdapter<P, T extends NBTBase>(
Class<P> primitiveType,
Class<T> nbtBaseType,
byte nmsTypeByte,
BiFunction<PersistentDataType<P, ?>, P, T> builder,
BiFunction<PersistentDataType<P, ?>, T, P> extractor,
BiPredicate<PersistentDataType<P, ?>, NBTBase> matcher) {
/**
* This method will extract the value stored in the tag, according to
* the expected primitive type.
* Extract the primitive value from the {@link NBTBase}.
*
* @param base the base to extract from
*
* @return the value stored inside of the tag
*
* @return the value stored inside the tag
* @throws ClassCastException if the passed base is not an instanced of
* the defined base type and therefore is not applicable to the
* extractor function
* extractor function.
*/
T extract(NBTBase base) {
Preconditions.checkArgument(nbtBaseType.isInstance(base), "The provided NBTBase was of the type %s. Expected type %s", base.getClass().getSimpleName(), nbtBaseType.getSimpleName());
return this.extractor.apply(nbtBaseType.cast(base));
private P extract(final PersistentDataType<P, ?> dataType, final NBTBase base) {
Preconditions.checkArgument(this.nbtBaseType.isInstance(base), "The provided NBTBase was of the type %s. Expected type %s", base.getClass().getSimpleName(), this.nbtBaseType.getSimpleName());
return this.extractor.apply(dataType, this.nbtBaseType.cast(base));
}
/**
* Builds a tag instance wrapping around the provided value object.
* Builds a tag instance wrapping around the provided primitive value.
*
* @param value the value to store inside the created tag
*
* @return the new tag instance
*
* @throws ClassCastException if the passed value object is not of the
* defined primitive type and therefore is not applicable to the builder
* function
* function.
*/
Z build(Object value) {
Preconditions.checkArgument(primitiveType.isInstance(value), "The provided value was of the type %s. Expected type %s", value.getClass().getSimpleName(), primitiveType.getSimpleName());
return this.builder.apply(primitiveType.cast(value));
private T build(final PersistentDataType<P, ?> dataType, final Object value) {
Preconditions.checkArgument(this.primitiveType.isInstance(value), "The provided value was of the type %s. Expected type %s", value.getClass().getSimpleName(), this.primitiveType.getSimpleName());
return this.builder.apply(dataType, this.primitiveType.cast(value));
}
/**
* Returns if the tag instance matches the adapters one.
*
* @param base the base to check
* Computes if the provided persistent data type's primitive type is a
* representation of the {@link NBTBase}.
*
* @param base the base tag instance to check against
* @return if the tag was an instance of the set type
*/
boolean isInstance(NBTBase base) {
return this.nbtBaseType.isInstance(base);
private boolean isInstance(final PersistentDataType<P, ?> persistentDataType, final NBTBase base) {
return this.matcher.test(persistentDataType, base);
}
}
private final Map<Class, TagAdapter> adapters = new HashMap<>();
/**
* Creates a suitable adapter instance for the primitive class type
* Creates a suitable adapter instance for the primitive class type.
*
* @param type the type to create an adapter for
* @param <T> the generic type of that class
*
* @param <T> the generic type of the primitive type
* @return the created adapter instance
*
* @throws IllegalArgumentException if no suitable tag type adapter for this
* type was found
*/
@ -107,109 +137,163 @@ public final class CraftPersistentDataTypeRegistry {
type = Primitives.wrap(type); //Make sure we will always "switch" over the wrapper types
}
/*
Primitives
*/
// Primitives
if (Objects.equals(Byte.class, type)) {
return createAdapter(Byte.class, NBTTagByte.class, NBTTagByte::valueOf, NBTTagByte::getAsByte);
return this.createAdapter(
Byte.class, NBTTagByte.class, NBTBase.TAG_BYTE,
NBTTagByte::valueOf, NBTTagByte::getAsByte
);
}
if (Objects.equals(Short.class, type)) {
return createAdapter(Short.class, NBTTagShort.class, NBTTagShort::valueOf, NBTTagShort::getAsShort);
return this.createAdapter(
Short.class, NBTTagShort.class, NBTBase.TAG_SHORT, NBTTagShort::valueOf, NBTTagShort::getAsShort
);
}
if (Objects.equals(Integer.class, type)) {
return createAdapter(Integer.class, NBTTagInt.class, NBTTagInt::valueOf, NBTTagInt::getAsInt);
return this.createAdapter(
Integer.class, NBTTagInt.class, NBTBase.TAG_INT, NBTTagInt::valueOf, NBTTagInt::getAsInt
);
}
if (Objects.equals(Long.class, type)) {
return createAdapter(Long.class, NBTTagLong.class, NBTTagLong::valueOf, NBTTagLong::getAsLong);
return this.createAdapter(
Long.class, NBTTagLong.class, NBTBase.TAG_LONG, NBTTagLong::valueOf, NBTTagLong::getAsLong
);
}
if (Objects.equals(Float.class, type)) {
return createAdapter(Float.class, NBTTagFloat.class, NBTTagFloat::valueOf, NBTTagFloat::getAsFloat);
return this.createAdapter(
Float.class, NBTTagFloat.class, NBTBase.TAG_FLOAT,
NBTTagFloat::valueOf, NBTTagFloat::getAsFloat
);
}
if (Objects.equals(Double.class, type)) {
return createAdapter(Double.class, NBTTagDouble.class, NBTTagDouble::valueOf, NBTTagDouble::getAsDouble);
return this.createAdapter(
Double.class, NBTTagDouble.class, NBTBase.TAG_DOUBLE,
NBTTagDouble::valueOf, NBTTagDouble::getAsDouble
);
}
/*
String
*/
if (Objects.equals(String.class, type)) {
return createAdapter(String.class, NBTTagString.class, NBTTagString::valueOf, NBTTagString::getAsString);
return this.createAdapter(
String.class, NBTTagString.class, NBTBase.TAG_STRING,
NBTTagString::valueOf, NBTTagString::getAsString
);
}
/*
Primitive Arrays
*/
// Primitive non-list arrays
if (Objects.equals(byte[].class, type)) {
return createAdapter(byte[].class, NBTTagByteArray.class, array -> new NBTTagByteArray(Arrays.copyOf(array, array.length)), n -> Arrays.copyOf(n.getAsByteArray(), n.size()));
return this.createAdapter(
byte[].class, NBTTagByteArray.class, NBTBase.TAG_BYTE_ARRAY,
array -> new NBTTagByteArray(Arrays.copyOf(array, array.length)),
n -> Arrays.copyOf(n.getAsByteArray(), n.size())
);
}
if (Objects.equals(int[].class, type)) {
return createAdapter(int[].class, NBTTagIntArray.class, array -> new NBTTagIntArray(Arrays.copyOf(array, array.length)), n -> Arrays.copyOf(n.getAsIntArray(), n.size()));
return this.createAdapter(
int[].class, NBTTagIntArray.class, NBTBase.TAG_INT_ARRAY,
array -> new NBTTagIntArray(Arrays.copyOf(array, array.length)),
n -> Arrays.copyOf(n.getAsIntArray(), n.size())
);
}
if (Objects.equals(long[].class, type)) {
return createAdapter(long[].class, NBTTagLongArray.class, array -> new NBTTagLongArray(Arrays.copyOf(array, array.length)), n -> Arrays.copyOf(n.getAsLongArray(), n.size()));
return this.createAdapter(
long[].class, NBTTagLongArray.class, NBTBase.TAG_LONG_ARRAY,
array -> new NBTTagLongArray(Arrays.copyOf(array, array.length)),
n -> Arrays.copyOf(n.getAsLongArray(), n.size())
);
}
/*
Complex Arrays
*/
// Previously "emulated" compound lists, now useless as a proper list type exists.
if (Objects.equals(PersistentDataContainer[].class, type)) {
return createAdapter(PersistentDataContainer[].class, NBTTagList.class,
return this.createAdapter(
PersistentDataContainer[].class, NBTTagList.class, NBTBase.TAG_LIST,
(containerArray) -> {
NBTTagList list = new NBTTagList();
for (int i = 0; i < containerArray.length; i++) {
list.add(((CraftPersistentDataContainer) containerArray[i]).toTagCompound());
final NBTTagList list = new NBTTagList();
for (final PersistentDataContainer persistentDataContainer : containerArray) {
list.add(((CraftPersistentDataContainer) persistentDataContainer).toTagCompound());
}
return list;
},
(tag) -> {
PersistentDataContainer[] containerArray = new CraftPersistentDataContainer[tag.size()];
final PersistentDataContainer[] containerArray = new CraftPersistentDataContainer[tag.size()];
for (int i = 0; i < tag.size(); i++) {
CraftPersistentDataContainer container = new CraftPersistentDataContainer(this);
NBTTagCompound compound = tag.getCompound(i);
for (String key : compound.getAllKeys()) {
final CraftPersistentDataContainer container = new CraftPersistentDataContainer(this);
final NBTTagCompound compound = tag.getCompound(i);
for (final String key : compound.getAllKeys()) {
container.put(key, compound.get(key));
}
containerArray[i] = container;
}
return containerArray;
}
);
}
// Note that this will map the interface PersistentMetadataContainer directly to the CraftBukkit implementation
// Passing any other instance of this form to the tag type registry will throw a ClassCastException
// as defined in TagAdapter#build.
if (Objects.equals(PersistentDataContainer.class, type)) {
return this.createAdapter(
CraftPersistentDataContainer.class, NBTTagCompound.class, NBTBase.TAG_COMPOUND,
CraftPersistentDataContainer::toTagCompound,
tag -> {
final CraftPersistentDataContainer container = new CraftPersistentDataContainer(this);
for (final String key : tag.getAllKeys()) {
container.put(key, tag.get(key));
}
return container;
});
}
/*
Note that this will map the interface PersistentMetadataContainer directly to the CraftBukkit implementation
Passing any other instance of this form to the tag type registry will throw a ClassCastException as defined in TagAdapter#build
*/
if (Objects.equals(PersistentDataContainer.class, type)) {
return createAdapter(CraftPersistentDataContainer.class, NBTTagCompound.class, CraftPersistentDataContainer::toTagCompound, tag -> {
CraftPersistentDataContainer container = new CraftPersistentDataContainer(this);
for (String key : tag.getAllKeys()) {
container.put(key, tag.get(key));
}
return container;
});
if (Objects.equals(List.class, type)) {
return createAdapter(
List.class,
net.minecraft.nbt.NBTTagList.class,
NBTBase.TAG_LIST,
this::constructList,
this::extractList,
this::matchesListTag
);
}
throw new IllegalArgumentException("Could not find a valid TagAdapter implementation for the requested type " + type.getSimpleName());
}
private <T, Z extends NBTBase> TagAdapter<T, Z> createAdapter(Class<T> primitiveType, Class<Z> nbtBaseType, Function<T, Z> builder, Function<Z, T> extractor) {
return new TagAdapter<>(primitiveType, nbtBaseType, builder, extractor);
// Plain constructor helper method.
private <T, Z extends NBTBase> TagAdapter<T, Z> createAdapter(
final Class<T> primitiveType, final Class<Z> nbtBaseType, final byte nmsTypeByte,
final Function<T, Z> builder, final Function<Z, T> extractor
) {
return createAdapter(
primitiveType,
nbtBaseType,
nmsTypeByte,
(type, t) -> builder.apply(t),
(type, z) -> extractor.apply(z),
(type, t) -> nbtBaseType.isInstance(t)
);
}
// Plain constructor helper method.
private <T, Z extends NBTBase> TagAdapter<T, Z> createAdapter(
final Class<T> primitiveType, final Class<Z> nbtBaseType, final byte nmsTypeByte,
final BiFunction<PersistentDataType<T, ?>, T, Z> builder,
final BiFunction<PersistentDataType<T, ?>, Z, T> extractor,
final BiPredicate<PersistentDataType<T, ?>, NBTBase> matcher
) {
return new TagAdapter<>(primitiveType, nbtBaseType, nmsTypeByte, builder, extractor, matcher);
}
/**
* Wraps the passed value into a tag instance.
* Wraps the passed primitive value into a tag instance.
*
* @param type the type of the passed value
* @param value the value to be stored in the tag
* @param <T> the generic type of the value
*
* @return the created tag instance
*
* @throws IllegalArgumentException if no suitable tag type adapter for this
* type was found
* type was found.
*/
public <T> NBTBase wrap(Class<T> type, T value) {
return this.adapters.computeIfAbsent(type, CREATE_ADAPTER).build(value);
public <T> NBTBase wrap(final PersistentDataType<T, ?> type, final T value) {
return this.getOrCreateAdapter(type).build(type, value);
}
/**
@ -218,14 +302,29 @@ public final class CraftPersistentDataTypeRegistry {
* @param type the type of the primitive value
* @param base the base instance to check
* @param <T> the generic type of the type
*
* @return if the base stores values of the primitive type passed
*
* @throws IllegalArgumentException if no suitable tag type adapter for this
* type was found
* type was found.
*/
public <T> boolean isInstanceOf(Class<T> type, NBTBase base) {
return this.adapters.computeIfAbsent(type, CREATE_ADAPTER).isInstance(base);
public <T> boolean isInstanceOf(final PersistentDataType<T, ?> type, final NBTBase base) {
return this.getOrCreateAdapter(type).isInstance(type, base);
}
/**
* Fetches or creates an adapter for the requested persistent data type.
*
* @param type the persistent data type to find or create an adapter for.
* @param <T> the generic type of the primitive type of the persistent data
* type.
* @param <Z> the generic type of the complex type of the persistent data
* type.
* @return the tag adapter instance that was found or created.
* @throws IllegalArgumentException if no adapter can be created for the
* persistent data type.
*/
@NotNull
private <T, Z extends NBTBase> TagAdapter<T, Z> getOrCreateAdapter(@NotNull final PersistentDataType<T, ?> type) {
return this.adapters.computeIfAbsent(type.getPrimitiveType(), CREATE_ADAPTER);
}
/**
@ -234,23 +333,95 @@ public final class CraftPersistentDataTypeRegistry {
* @param type the type of the value to extract
* @param tag the tag to extract the value from
* @param <T> the generic type of the value stored inside the tag
*
* @return the extracted value
*
* @throws IllegalArgumentException if the passed base is not an instanced
* of the defined base type and therefore is not applicable to the extractor
* function
* function.
* @throws IllegalArgumentException if the found object is not of type
* passed
* passed.
* @throws IllegalArgumentException if no suitable tag type adapter for this
* type was found
* type was found.
*/
public <T> T extract(Class<T> type, NBTBase tag) throws ClassCastException, IllegalArgumentException {
TagAdapter adapter = this.adapters.computeIfAbsent(type, CREATE_ADAPTER);
Preconditions.checkArgument(adapter.isInstance(tag), "The found tag instance (%s) cannot store %s", tag.getClass().getSimpleName(), type.getSimpleName());
public <T, Z extends NBTBase> T extract(final PersistentDataType<T, ?> type, final NBTBase tag) throws ClassCastException, IllegalArgumentException {
final Class<T> primitiveType = type.getPrimitiveType();
final TagAdapter<T, Z> adapter = this.getOrCreateAdapter(type);
Preconditions.checkArgument(adapter.isInstance(type, tag), "The found tag instance (%s) cannot store %s", tag.getClass().getSimpleName(), primitiveType.getSimpleName());
Object foundValue = adapter.extract(tag);
Preconditions.checkArgument(type.isInstance(foundValue), "The found object is of the type %s. Expected type %s", foundValue.getClass().getSimpleName(), type.getSimpleName());
return type.cast(foundValue);
final Object foundValue = adapter.extract(type, tag);
Preconditions.checkArgument(primitiveType.isInstance(foundValue), "The found object is of the type %s. Expected type %s", foundValue.getClass().getSimpleName(), primitiveType.getSimpleName());
return primitiveType.cast(foundValue);
}
/**
* Constructs a {@link NBTTagList} from a {@link List} instance by using the
* passed persistent data type.
*
* @param type the persistent data type of the list.
* @param list the list or primitive values.
* @param <P> the generic type of the primitive values in the list.
* @return the constructed {@link NBTTagList}.
*/
private <P, T extends List<P>> NBTTagList constructList(@NotNull final PersistentDataType<T, ?> type, @NotNull final List<P> list) {
Preconditions.checkArgument(type instanceof ListPersistentDataType<?, ?>, "The passed list cannot be written to the PDC with a %s (expected a list data type)", type.getClass().getSimpleName());
final ListPersistentDataType<P, ?> listPersistentDataType = (ListPersistentDataType<P, ?>) type;
final TagAdapter<P, NBTBase> elementAdapter = this.getOrCreateAdapter(listPersistentDataType.elementType());
final List<NBTBase> values = Lists.newArrayListWithCapacity(list.size());
for (final P primitiveValue : list) {
values.add(this.wrap(listPersistentDataType.elementType(), primitiveValue));
}
return new NBTTagList(values, elementAdapter.nmsTypeByte());
}
/**
* Extracts a {@link List} from a {@link NBTTagList} and a respective
* {@link PersistentDataType}.
*
* @param type the persistent data type of the list.
* @param listTag the list tag to extract the {@link List} from.
* @param <P> the generic type of the primitive values stored in the
* {@link List}.
* @return the extracted {@link List} instance.
* @throws IllegalArgumentException if the passed {@link PersistentDataType}
* is not a {@link ListPersistentDataType} and can hence not be used to
* extract a {@link List}.
*/
private <P> List<P> extractList(@NotNull final PersistentDataType<P, ?> type,
@NotNull final NBTTagList listTag) {
Preconditions.checkArgument(type instanceof ListPersistentDataType<?, ?>, "The found list tag cannot be read with a %s (expected a list data type)", type.getClass().getSimpleName());
final ListPersistentDataType<P, ?> listPersistentDataType = (ListPersistentDataType<P, ?>) type;
final List<P> output = new ObjectArrayList<>(listTag.size());
for (final NBTBase tag : listTag) {
output.add(this.extract(listPersistentDataType.elementType(), tag));
}
return output;
}
/**
* Computes if the passed {@link NBTBase} is a {@link NBTTagList} and it,
* including its elements, can be read/written via the passed
* {@link PersistentDataType}.
*
* @param type the persistent data type for which to check if the tag
* matches.
* @param tag the tag that is to be checked if it matches the data type.
* @return whether the passed tag can be read/written via the passed type.
*/
private boolean matchesListTag(final PersistentDataType<List, ?> type, final NBTBase tag) {
if ((!(type instanceof final ListPersistentDataType listPersistentDataType))) {
return false;
}
if (!(tag instanceof final NBTTagList listTag)) {
return false;
}
final byte elementType = listTag.getElementType();
final TagAdapter elementAdapter = this.getOrCreateAdapter(listPersistentDataType.elementType());
return elementAdapter.nmsTypeByte() == elementType;
}
}

View file

@ -5,6 +5,7 @@ import net.minecraft.nbt.NBTBase;
import net.minecraft.nbt.NBTTagCompound;
import org.bukkit.NamespacedKey;
import org.bukkit.persistence.PersistentDataType;
import org.jetbrains.annotations.NotNull;
/**
* A child class of the persistent data container that recalls if it has been
@ -31,13 +32,13 @@ public final class DirtyCraftPersistentDataContainer extends CraftPersistentData
}
@Override
public <T, Z> void set(NamespacedKey key, PersistentDataType<T, Z> type, Z value) {
public <T, Z> void set(@NotNull NamespacedKey key, @NotNull PersistentDataType<T, Z> type, @NotNull Z value) {
super.set(key, type, value);
this.dirty(true);
}
@Override
public void remove(NamespacedKey key) {
public void remove(@NotNull NamespacedKey key) {
super.remove(key);
this.dirty(true);
}

View file

@ -5,8 +5,12 @@ import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.Array;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.BiConsumer;
import java.util.stream.Stream;
import net.minecraft.nbt.NBTTagCompound;
import org.bukkit.Bukkit;
import org.bukkit.Material;
@ -15,12 +19,18 @@ import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.ListPersistentDataType;
import org.bukkit.persistence.PersistentDataAdapterContext;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import org.bukkit.support.AbstractTestingBase;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
public class PersistentDataContainerTest extends AbstractTestingBase {
@ -31,18 +41,14 @@ public class PersistentDataContainerTest extends AbstractTestingBase {
VALID_KEY = new NamespacedKey("test", "validkey");
}
/*
Sets a test
*/
// Sets a test
@Test
public void testSetNoAdapter() {
ItemMeta itemMeta = createNewItemMeta();
assertThrows(IllegalArgumentException.class, () -> itemMeta.getPersistentDataContainer().set(VALID_KEY, new PrimitiveTagType<>(boolean.class), true));
}
/*
Contains a tag
*/
// Contains a tag
@Test
public void testHasNoAdapter() {
ItemMeta itemMeta = createNewItemMeta();
@ -57,9 +63,7 @@ public class PersistentDataContainerTest extends AbstractTestingBase {
assertTrue(itemMeta.getPersistentDataContainer().has(VALID_KEY));
}
/*
Getting a tag
*/
// Getting a tag
@Test
public void testGetNoAdapter() {
ItemMeta itemMeta = createNewItemMeta();
@ -87,11 +91,11 @@ public class PersistentDataContainerTest extends AbstractTestingBase {
assertEquals(160L, (long) meta.getPersistentDataContainer().get(namespacedKeyB, PersistentDataType.LONG));
}
private ItemMeta createNewItemMeta() {
private static ItemMeta createNewItemMeta() {
return Bukkit.getItemFactory().getItemMeta(Material.DIAMOND_PICKAXE);
}
private NamespacedKey requestKey(String keyName) {
private static NamespacedKey requestKey(String keyName) {
return new NamespacedKey("test-plugin", keyName.toLowerCase());
}
@ -117,9 +121,7 @@ public class PersistentDataContainerTest extends AbstractTestingBase {
assertEquals(container, target);
}
/*
Removing a tag
*/
// Removing a tag
@Test
public void testNBTTagStoring() {
CraftMetaItem itemMeta = createComplexItemMeta();
@ -188,6 +190,12 @@ public class PersistentDataContainerTest extends AbstractTestingBase {
itemMeta.getPersistentDataContainer().set(requestKey("custom-string"), PersistentDataType.STRING, "Hello there world");
itemMeta.getPersistentDataContainer().set(requestKey("custom-int"), PersistentDataType.INTEGER, 3);
itemMeta.getPersistentDataContainer().set(requestKey("custom-double"), PersistentDataType.DOUBLE, 3.123);
itemMeta.getPersistentDataContainer().set(
requestKey("custom-list-string"), PersistentDataType.LIST.strings(), List.of("first[]", "second{}", "third()")
);
itemMeta.getPersistentDataContainer().set(
requestKey("custom-list-bytes"), PersistentDataType.LIST.bytes(), List.of((byte) 1, (byte) 2, (byte) 3)
);
PersistentDataContainer innerContainer = itemMeta.getPersistentDataContainer().getAdapterContext().newPersistentDataContainer(); //Add a inner container
innerContainer.set(VALID_KEY, PersistentDataType.LONG, 5L);
@ -195,9 +203,7 @@ public class PersistentDataContainerTest extends AbstractTestingBase {
return itemMeta;
}
/*
Test edge cases with strings
*/
// Test edge cases with strings
@Test
public void testStringEdgeCases() throws IOException, InvalidConfigurationException {
final ItemStack stack = new ItemStack(Material.DIAMOND);
@ -238,9 +244,7 @@ public class PersistentDataContainerTest extends AbstractTestingBase {
assertEquals(jsonLookalike, loadedPdc.get(requestKey("string_json_lookalike"), PersistentDataType.STRING));
}
/*
Test complex object storage
*/
// Test complex object storage
@Test
public void storeUUIDOnItemTest() {
ItemMeta itemMeta = createNewItemMeta();
@ -285,25 +289,29 @@ public class PersistentDataContainerTest extends AbstractTestingBase {
class UUIDPersistentDataType implements PersistentDataType<byte[], UUID> {
@Override
@NotNull
public Class<byte[]> getPrimitiveType() {
return byte[].class;
}
@NotNull
@Override
public Class<UUID> getComplexType() {
return UUID.class;
}
@NotNull
@Override
public byte[] toPrimitive(UUID complex, PersistentDataAdapterContext context) {
public byte[] toPrimitive(@NotNull UUID complex, @NotNull PersistentDataAdapterContext context) {
ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
bb.putLong(complex.getMostSignificantBits());
bb.putLong(complex.getLeastSignificantBits());
return bb.array();
}
@NotNull
@Override
public UUID fromPrimitive(byte[] primitive, PersistentDataAdapterContext context) {
public UUID fromPrimitive(@NotNull byte[] primitive, @NotNull PersistentDataAdapterContext context) {
ByteBuffer bb = ByteBuffer.wrap(primitive);
long firstLong = bb.getLong();
long secondLong = bb.getLong();
@ -315,22 +323,22 @@ public class PersistentDataContainerTest extends AbstractTestingBase {
public void testPrimitiveCustomTags() {
ItemMeta itemMeta = createNewItemMeta();
testPrimitiveCustomTag(itemMeta, PersistentDataType.BYTE, (byte) 1);
testPrimitiveCustomTag(itemMeta, PersistentDataType.SHORT, (short) 1);
testPrimitiveCustomTag(itemMeta, PersistentDataType.INTEGER, 1);
testPrimitiveCustomTag(itemMeta, PersistentDataType.LONG, 1L);
testPrimitiveCustomTag(itemMeta, PersistentDataType.FLOAT, 1.34F);
testPrimitiveCustomTag(itemMeta, PersistentDataType.DOUBLE, 151.123);
this.testPrimitiveCustomTag(itemMeta, PersistentDataType.BYTE, (byte) 1);
this.testPrimitiveCustomTag(itemMeta, PersistentDataType.SHORT, (short) 1);
this.testPrimitiveCustomTag(itemMeta, PersistentDataType.INTEGER, 1);
this.testPrimitiveCustomTag(itemMeta, PersistentDataType.LONG, 1L);
this.testPrimitiveCustomTag(itemMeta, PersistentDataType.FLOAT, 1.34F);
this.testPrimitiveCustomTag(itemMeta, PersistentDataType.DOUBLE, 151.123);
testPrimitiveCustomTag(itemMeta, PersistentDataType.STRING, "test");
this.testPrimitiveCustomTag(itemMeta, PersistentDataType.STRING, "test");
testPrimitiveCustomTag(itemMeta, PersistentDataType.BYTE_ARRAY, new byte[]{
this.testPrimitiveCustomTag(itemMeta, PersistentDataType.BYTE_ARRAY, new byte[]{
1, 4, 2, Byte.MAX_VALUE
});
testPrimitiveCustomTag(itemMeta, PersistentDataType.INTEGER_ARRAY, new int[]{
this.testPrimitiveCustomTag(itemMeta, PersistentDataType.INTEGER_ARRAY, new int[]{
1, 4, 2, Integer.MAX_VALUE
});
testPrimitiveCustomTag(itemMeta, PersistentDataType.LONG_ARRAY, new long[]{
this.testPrimitiveCustomTag(itemMeta, PersistentDataType.LONG_ARRAY, new long[]{
1L, 4L, 2L, Long.MAX_VALUE
});
}
@ -356,31 +364,35 @@ public class PersistentDataContainerTest extends AbstractTestingBase {
assertFalse(meta.getPersistentDataContainer().has(tagKey, type));
}
class PrimitiveTagType<T> implements PersistentDataType<T, T> {
class PrimitiveTagType<P> implements PersistentDataType<P, P> {
private final Class<T> primitiveType;
private final Class<P> primitiveType;
PrimitiveTagType(Class<T> primitiveType) {
PrimitiveTagType(Class<P> primitiveType) {
this.primitiveType = primitiveType;
}
@NotNull
@Override
public Class<T> getPrimitiveType() {
return primitiveType;
public Class<P> getPrimitiveType() {
return this.primitiveType;
}
@NotNull
@Override
public Class<T> getComplexType() {
return primitiveType;
public Class<P> getComplexType() {
return this.primitiveType;
}
@NotNull
@Override
public T toPrimitive(T complex, PersistentDataAdapterContext context) {
public P toPrimitive(@NotNull P complex, @NotNull PersistentDataAdapterContext context) {
return complex;
}
@NotNull
@Override
public T fromPrimitive(T primitive, PersistentDataAdapterContext context) {
public P fromPrimitive(@NotNull P primitive, @NotNull PersistentDataAdapterContext context) {
return primitive;
}
}
@ -397,7 +409,105 @@ public class PersistentDataContainerTest extends AbstractTestingBase {
assertNotSame(container, clonedContainer);
assertEquals(container, clonedContainer);
clonedContainer.set(VALID_KEY, PersistentDataType.STRING, "dinnerbone");
clonedContainer.set(PersistentDataContainerTest.VALID_KEY, PersistentDataType.STRING, "dinnerbone");
assertNotEquals(container, clonedContainer);
}
@ParameterizedTest
@MethodSource("testListTypeArgumentSource")
public <T> void testListType(@NotNull final ListPersistentDataType<T, T> type, @NotNull final List<T> list, @NotNull final BiConsumer<T, T> equalsCheck) {
final ItemMeta meta = createNewItemMeta();
final PersistentDataContainer container = meta.getPersistentDataContainer();
container.set(requestKey("list"), type, list);
final List<T> returnedList = container.get(requestKey("list"), type);
assertNotNull(returnedList);
assertEquals(list.size(), returnedList.size());
for (int i = 0; i < list.size(); i++) {
final T expectedValue = list.get(i);
final T foundValue = returnedList.get(i);
equalsCheck.accept(expectedValue, foundValue);
}
}
@NotNull
private static Stream<Arguments> testListTypeArgumentSource() {
final PersistentDataContainer first = createNewItemMeta().getPersistentDataContainer();
final PersistentDataContainer second = first.getAdapterContext().newPersistentDataContainer();
first.set(requestKey("a"), PersistentDataType.STRING, "hello world");
second.set(requestKey("b"), PersistentDataType.BOOLEAN, true);
final BiConsumer<Object, Object> objectAssertion = Assertions::assertEquals;
final BiConsumer<byte[], byte[]> byteArrayAssertion = Assertions::assertArrayEquals;
final BiConsumer<int[], int[]> intArrayAssertion = Assertions::assertArrayEquals;
final BiConsumer<long[], long[]> longArrayAssertion = Assertions::assertArrayEquals;
return Stream.of(
Arguments.of(PersistentDataType.LIST.bytes(), List.of((byte) 1, (byte) 2, (byte) 3), objectAssertion),
Arguments.of(PersistentDataType.LIST.shorts(), List.of((short) 1, (short) 2, (short) 3), objectAssertion),
Arguments.of(PersistentDataType.LIST.integers(), List.of(1, 2, 3), objectAssertion),
Arguments.of(PersistentDataType.LIST.longs(), List.of(1L, 2L, 3L), objectAssertion),
Arguments.of(PersistentDataType.LIST.floats(), List.of(1F, 2F, 3F), objectAssertion),
Arguments.of(PersistentDataType.LIST.doubles(), List.of(1D, 2D, 3D), objectAssertion),
Arguments.of(PersistentDataType.LIST.booleans(), List.of(true, true, false), objectAssertion),
Arguments.of(PersistentDataType.LIST.strings(), List.of("a", "b", "c"), objectAssertion),
Arguments.of(PersistentDataType.LIST.byteArrays(), List.of(new byte[]{1, 2, 3}, new byte[]{4, 5, 6}), byteArrayAssertion),
Arguments.of(PersistentDataType.LIST.integerArrays(), List.of(new int[]{1, 2, 3}, new int[]{4, 5, 6}), intArrayAssertion),
Arguments.of(PersistentDataType.LIST.longArrays(), List.of(new long[]{1, 2, 3}, new long[]{4, 5, 6}), longArrayAssertion),
Arguments.of(PersistentDataType.LIST.dataContainers(), List.of(first, second), objectAssertion));
}
@Test
public void testEmptyListDataMaintainType() {
final ItemMeta meta = createNewItemMeta();
final PersistentDataContainer container = meta.getPersistentDataContainer();
container.set(requestKey("list"), PersistentDataType.LIST.strings(), List.of());
assertTrue(container.has(requestKey("list"), PersistentDataType.LIST.strings()));
assertFalse(container.has(requestKey("list"), PersistentDataType.LIST.bytes()));
}
// This is a horrific marriage of tag container array "primitive" types the API offered and the new list types.
// We are essentially testing if these two play nice as tag container array was an emulated primitive type
// that used lists under the hood, hence this is testing the extra handling of TAG_CONTAINER_ARRAY in combination
// with lists. Plain lists in lists are tested above.
//
// Little faith is to be had when it comes to abominations constructed by plugin developers, this test ensures
// even this disgrace of a combination functions in PDCs.
@Test
public void testListOfListViaContainerArray() {
final ListPersistentDataType<PersistentDataContainer[], PersistentDataContainer[]> listPersistentDataType = PersistentDataType.LIST.listTypeFrom(PersistentDataType.TAG_CONTAINER_ARRAY);
final ItemMeta meta = createNewItemMeta();
final PersistentDataContainer container = meta.getPersistentDataContainer();
final PersistentDataAdapterContext adapterContext = container.getAdapterContext();
final PersistentDataContainer first = adapterContext.newPersistentDataContainer();
first.set(requestKey("a"), PersistentDataType.STRING, "hi");
final PersistentDataContainer second = adapterContext.newPersistentDataContainer();
second.set(requestKey("a"), PersistentDataType.INTEGER, 2);
final List<PersistentDataContainer[]> listOfArrays = new ArrayList<>();
listOfArrays.add(new PersistentDataContainer[]{first, second});
container.set(requestKey("containerListList"), listPersistentDataType, listOfArrays);
assertTrue(container.has(requestKey("containerListList"), listPersistentDataType));
final List<PersistentDataContainer[]> containerListList = container.get(requestKey("containerListList"), listPersistentDataType);
assertNotNull(containerListList);
assertEquals(1, containerListList.size());
final PersistentDataContainer[] arrayOfPDC = containerListList.get(0);
assertEquals(2, arrayOfPDC.length);
assertEquals("hi", arrayOfPDC[0].get(requestKey("a"), PersistentDataType.STRING));
assertEquals(2, arrayOfPDC[1].get(requestKey("a"), PersistentDataType.INTEGER));
}
}