#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.PersistentDataAdapterContext;
import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType; 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; this.deprecated = deprecated;
} }
@NotNull
@Override @Override
public Class<PersistentDataContainer> getPrimitiveType() { public Class<PersistentDataContainer> getPrimitiveType() {
return PersistentDataContainer.class; return PersistentDataContainer.class;
} }
@NotNull
@Override @Override
public Class<Z> getComplexType() { public Class<C> getComplexType() {
return deprecated.getComplexType(); return deprecated.getComplexType();
} }
@NotNull
@Override @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)); 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()); 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()); return new CraftPersistentDataContainer(craftTagContainer.getRaw(), craftTagContainer.getDataTagTypeRegistry());
} }
@NotNull
@Override @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()); 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)); 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.inventory.meta.tags.ItemTagType;
import org.bukkit.persistence.PersistentDataAdapterContext; import org.bukkit.persistence.PersistentDataAdapterContext;
import org.bukkit.persistence.PersistentDataType; 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; this.deprecated = deprecated;
} }
@NotNull
@Override @Override
public Class<T> getPrimitiveType() { public Class<P> getPrimitiveType() {
return deprecated.getPrimitiveType(); return deprecated.getPrimitiveType();
} }
@NotNull
@Override @Override
public Class<Z> getComplexType() { public Class<C> getComplexType() {
return deprecated.getComplexType(); return deprecated.getComplexType();
} }
@NotNull
@Override @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)); return this.deprecated.toPrimitive(complex, new DeprecatedItemAdapterContext(context));
} }
@NotNull
@Override @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)); 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.PersistentDataAdapterContext;
import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType; import org.bukkit.persistence.PersistentDataType;
import org.jetbrains.annotations.NotNull;
public class CraftPersistentDataContainer implements PersistentDataContainer { public class CraftPersistentDataContainer implements PersistentDataContainer {
@ -33,16 +34,16 @@ public class CraftPersistentDataContainer implements PersistentDataContainer {
@Override @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(key != null, "The NamespacedKey key cannot be null");
Preconditions.checkArgument(type != null, "The provided type cannot be null"); Preconditions.checkArgument(type != null, "The provided type cannot be null");
Preconditions.checkArgument(value != null, "The provided value 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 @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(key != null, "The NamespacedKey key cannot be null");
Preconditions.checkArgument(type != null, "The provided type 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 false;
} }
return registry.isInstanceOf(type.getPrimitiveType(), value); return this.registry.isInstanceOf(type, value);
} }
@Override @Override
@ -60,7 +61,7 @@ public class CraftPersistentDataContainer implements PersistentDataContainer {
} }
@Override @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(key != null, "The NamespacedKey key cannot be null");
Preconditions.checkArgument(type != null, "The provided type 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 null;
} }
return type.fromPrimitive(registry.extract(type.getPrimitiveType(), value), adapterContext); return type.fromPrimitive(this.registry.extract(type, value), adapterContext);
} }
@NotNull
@Override @Override
public <T, Z> Z getOrDefault(NamespacedKey key, PersistentDataType<T, Z> type, Z defaultValue) { public <T, Z> Z getOrDefault(@NotNull NamespacedKey key, @NotNull PersistentDataType<T, Z> type, @NotNull Z defaultValue) {
Z z = get(key, type); Z z = this.get(key, type);
return z != null ? z : defaultValue; return z != null ? z : defaultValue;
} }
@NotNull
@Override @Override
public Set<NamespacedKey> getKeys() { public Set<NamespacedKey> getKeys() {
Set<NamespacedKey> keys = new HashSet<>(); Set<NamespacedKey> keys = new HashSet<>();
@ -93,7 +96,7 @@ public class CraftPersistentDataContainer implements PersistentDataContainer {
} }
@Override @Override
public void remove(NamespacedKey key) { public void remove(@NotNull NamespacedKey key) {
Preconditions.checkArgument(key != null, "The NamespacedKey key cannot be null"); Preconditions.checkArgument(key != null, "The NamespacedKey key cannot be null");
this.customDataTags.remove(key.toString()); this.customDataTags.remove(key.toString());
@ -104,6 +107,7 @@ public class CraftPersistentDataContainer implements PersistentDataContainer {
return this.customDataTags.isEmpty(); return this.customDataTags.isEmpty();
} }
@NotNull
@Override @Override
public void copyTo(PersistentDataContainer other, boolean replace) { public void copyTo(PersistentDataContainer other, boolean replace) {
Preconditions.checkArgument(other != null, "The target container cannot be null"); Preconditions.checkArgument(other != null, "The target container cannot be null");
@ -127,7 +131,7 @@ public class CraftPersistentDataContainer implements PersistentDataContainer {
return false; return false;
} }
Map<String, NBTBase> myRawMap = getRaw(); Map<String, NBTBase> myRawMap = this.getRaw();
Map<String, NBTBase> theirRawMap = ((CraftPersistentDataContainer) obj).getRaw(); Map<String, NBTBase> theirRawMap = ((CraftPersistentDataContainer) obj).getRaw();
return Objects.equals(myRawMap, theirRawMap); return Objects.equals(myRawMap, theirRawMap);
@ -160,7 +164,7 @@ public class CraftPersistentDataContainer implements PersistentDataContainer {
} }
public CraftPersistentDataTypeRegistry getDataTagTypeRegistry() { public CraftPersistentDataTypeRegistry getDataTagTypeRegistry() {
return registry; return this.registry;
} }
@Override @Override

View File

@ -1,11 +1,16 @@
package org.bukkit.craftbukkit.persistence; package org.bukkit.craftbukkit.persistence;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.primitives.Primitives; import com.google.common.primitives.Primitives;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Function; import java.util.function.Function;
import net.minecraft.nbt.NBTBase; import net.minecraft.nbt.NBTBase;
import net.minecraft.nbt.NBTTagByte; import net.minecraft.nbt.NBTTagByte;
@ -20,85 +25,110 @@ import net.minecraft.nbt.NBTTagLong;
import net.minecraft.nbt.NBTTagLongArray; import net.minecraft.nbt.NBTTagLongArray;
import net.minecraft.nbt.NBTTagShort; import net.minecraft.nbt.NBTTagShort;
import net.minecraft.nbt.NBTTagString; import net.minecraft.nbt.NBTTagString;
import org.bukkit.persistence.ListPersistentDataType;
import org.bukkit.persistence.PersistentDataContainer; 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 { public final class CraftPersistentDataTypeRegistry {
private final Function<Class, TagAdapter> CREATE_ADAPTER = this::createAdapter; private final Function<Class, TagAdapter> CREATE_ADAPTER = this::createAdapter;
private class TagAdapter<T, Z extends NBTBase> { /**
* A tag adapter is a closely related type to a specific implementation of
private final Function<T, Z> builder; * the {@link NBTBase} interface. It exists to convert from and to the
private final Function<Z, T> extractor; * respective value of a {@link NBTBase} to a "primitive type" for later
* usage in {@link PersistentDataType}.
private final Class<T> primitiveType; *
private final Class<Z> nbtBaseType; * @param primitiveType the class of the primitive type, e.g.
* {@link String}.
public TagAdapter(Class<T> primitiveType, Class<Z> nbtBaseType, Function<T, Z> builder, Function<Z, T> extractor) { * @param nbtBaseType the class of the tag implementation that is used to
this.primitiveType = primitiveType; * store this primitive type, e.g {@link NBTTagString}.
this.nbtBaseType = nbtBaseType; * @param nmsTypeByte the byte identifier of the tag as defined by
this.builder = builder; * {@link NBTBase#getId()}.
this.extractor = extractor; * @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 * Extract the primitive value from the {@link NBTBase}.
* the expected primitive type.
* *
* @param base the base to extract from * @param base the base to extract from
* * @return the value stored inside the tag
* @return the value stored inside of the tag
*
* @throws ClassCastException if the passed base is not an instanced of * @throws ClassCastException if the passed base is not an instanced of
* the defined base type and therefore is not applicable to the * the defined base type and therefore is not applicable to the
* extractor function * extractor function.
*/ */
T extract(NBTBase base) { private P extract(final PersistentDataType<P, ?> dataType, final NBTBase base) {
Preconditions.checkArgument(nbtBaseType.isInstance(base), "The provided NBTBase was of the type %s. Expected type %s", base.getClass().getSimpleName(), nbtBaseType.getSimpleName()); 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(nbtBaseType.cast(base)); 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 * @param value the value to store inside the created tag
*
* @return the new tag instance * @return the new tag instance
*
* @throws ClassCastException if the passed value object is not of the * @throws ClassCastException if the passed value object is not of the
* defined primitive type and therefore is not applicable to the builder * defined primitive type and therefore is not applicable to the builder
* function * function.
*/ */
Z build(Object value) { private T build(final PersistentDataType<P, ?> dataType, final Object value) {
Preconditions.checkArgument(primitiveType.isInstance(value), "The provided value was of the type %s. Expected type %s", value.getClass().getSimpleName(), primitiveType.getSimpleName()); 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(primitiveType.cast(value)); return this.builder.apply(dataType, this.primitiveType.cast(value));
} }
/** /**
* Returns if the tag instance matches the adapters one. * Computes if the provided persistent data type's primitive type is a
* * representation of the {@link NBTBase}.
* @param base the base to check
* *
* @param base the base tag instance to check against
* @return if the tag was an instance of the set type * @return if the tag was an instance of the set type
*/ */
boolean isInstance(NBTBase base) { private boolean isInstance(final PersistentDataType<P, ?> persistentDataType, final NBTBase base) {
return this.nbtBaseType.isInstance(base); return this.matcher.test(persistentDataType, base);
} }
} }
private final Map<Class, TagAdapter> adapters = new HashMap<>(); 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 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 * @return the created adapter instance
*
* @throws IllegalArgumentException if no suitable tag type adapter for this * @throws IllegalArgumentException if no suitable tag type adapter for this
* type was found * 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 type = Primitives.wrap(type); //Make sure we will always "switch" over the wrapper types
} }
/* // Primitives
Primitives
*/
if (Objects.equals(Byte.class, type)) { 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)) { 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)) { 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)) { 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)) { 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)) { 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)) { 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 non-list arrays
Primitive Arrays
*/
if (Objects.equals(byte[].class, type)) { 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)) { 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)) { 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())
);
} }
/* // Previously "emulated" compound lists, now useless as a proper list type exists.
Complex Arrays
*/
if (Objects.equals(PersistentDataContainer[].class, type)) { if (Objects.equals(PersistentDataContainer[].class, type)) {
return createAdapter(PersistentDataContainer[].class, NBTTagList.class, return this.createAdapter(
PersistentDataContainer[].class, NBTTagList.class, NBTBase.TAG_LIST,
(containerArray) -> { (containerArray) -> {
NBTTagList list = new NBTTagList(); final NBTTagList list = new NBTTagList();
for (int i = 0; i < containerArray.length; i++) { for (final PersistentDataContainer persistentDataContainer : containerArray) {
list.add(((CraftPersistentDataContainer) containerArray[i]).toTagCompound()); list.add(((CraftPersistentDataContainer) persistentDataContainer).toTagCompound());
} }
return list; return list;
}, },
(tag) -> { (tag) -> {
PersistentDataContainer[] containerArray = new CraftPersistentDataContainer[tag.size()]; final PersistentDataContainer[] containerArray = new CraftPersistentDataContainer[tag.size()];
for (int i = 0; i < tag.size(); i++) { for (int i = 0; i < tag.size(); i++) {
CraftPersistentDataContainer container = new CraftPersistentDataContainer(this); final CraftPersistentDataContainer container = new CraftPersistentDataContainer(this);
NBTTagCompound compound = tag.getCompound(i); final NBTTagCompound compound = tag.getCompound(i);
for (String key : compound.getAllKeys()) { for (final String key : compound.getAllKeys()) {
container.put(key, compound.get(key)); container.put(key, compound.get(key));
} }
containerArray[i] = container; containerArray[i] = container;
} }
return containerArray; 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;
}); });
} }
/* if (Objects.equals(List.class, type)) {
Note that this will map the interface PersistentMetadataContainer directly to the CraftBukkit implementation return createAdapter(
Passing any other instance of this form to the tag type registry will throw a ClassCastException as defined in TagAdapter#build List.class,
*/ net.minecraft.nbt.NBTTagList.class,
if (Objects.equals(PersistentDataContainer.class, type)) { NBTBase.TAG_LIST,
return createAdapter(CraftPersistentDataContainer.class, NBTTagCompound.class, CraftPersistentDataContainer::toTagCompound, tag -> { this::constructList,
CraftPersistentDataContainer container = new CraftPersistentDataContainer(this); this::extractList,
for (String key : tag.getAllKeys()) { this::matchesListTag
container.put(key, tag.get(key)); );
}
return container;
});
} }
throw new IllegalArgumentException("Could not find a valid TagAdapter implementation for the requested type " + type.getSimpleName()); 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) { // Plain constructor helper method.
return new TagAdapter<>(primitiveType, nbtBaseType, builder, extractor); 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 type the type of the passed value
* @param value the value to be stored in the tag * @param value the value to be stored in the tag
* @param <T> the generic type of the value * @param <T> the generic type of the value
*
* @return the created tag instance * @return the created tag instance
*
* @throws IllegalArgumentException if no suitable tag type adapter for this * @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) { public <T> NBTBase wrap(final PersistentDataType<T, ?> type, final T value) {
return this.adapters.computeIfAbsent(type, CREATE_ADAPTER).build(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 type the type of the primitive value
* @param base the base instance to check * @param base the base instance to check
* @param <T> the generic type of the type * @param <T> the generic type of the type
*
* @return if the base stores values of the primitive type passed * @return if the base stores values of the primitive type passed
*
* @throws IllegalArgumentException if no suitable tag type adapter for this * @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) { public <T> boolean isInstanceOf(final PersistentDataType<T, ?> type, final NBTBase base) {
return this.adapters.computeIfAbsent(type, CREATE_ADAPTER).isInstance(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 type the type of the value to extract
* @param tag the tag to extract the value from * @param tag the tag to extract the value from
* @param <T> the generic type of the value stored inside the tag * @param <T> the generic type of the value stored inside the tag
*
* @return the extracted value * @return the extracted value
*
* @throws IllegalArgumentException if the passed base is not an instanced * @throws IllegalArgumentException if the passed base is not an instanced
* of the defined base type and therefore is not applicable to the extractor * 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 * @throws IllegalArgumentException if the found object is not of type
* passed * passed.
* @throws IllegalArgumentException if no suitable tag type adapter for this * @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 { public <T, Z extends NBTBase> T extract(final PersistentDataType<T, ?> type, final NBTBase tag) throws ClassCastException, IllegalArgumentException {
TagAdapter adapter = this.adapters.computeIfAbsent(type, CREATE_ADAPTER); final Class<T> primitiveType = type.getPrimitiveType();
Preconditions.checkArgument(adapter.isInstance(tag), "The found tag instance (%s) cannot store %s", tag.getClass().getSimpleName(), type.getSimpleName()); 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); final Object foundValue = adapter.extract(type, tag);
Preconditions.checkArgument(type.isInstance(foundValue), "The found object is of the type %s. Expected type %s", foundValue.getClass().getSimpleName(), type.getSimpleName()); Preconditions.checkArgument(primitiveType.isInstance(foundValue), "The found object is of the type %s. Expected type %s", foundValue.getClass().getSimpleName(), primitiveType.getSimpleName());
return type.cast(foundValue); 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 net.minecraft.nbt.NBTTagCompound;
import org.bukkit.NamespacedKey; import org.bukkit.NamespacedKey;
import org.bukkit.persistence.PersistentDataType; import org.bukkit.persistence.PersistentDataType;
import org.jetbrains.annotations.NotNull;
/** /**
* A child class of the persistent data container that recalls if it has been * 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 @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); super.set(key, type, value);
this.dirty(true); this.dirty(true);
} }
@Override @Override
public void remove(NamespacedKey key) { public void remove(@NotNull NamespacedKey key) {
super.remove(key); super.remove(key);
this.dirty(true); this.dirty(true);
} }

View File

@ -5,8 +5,12 @@ import java.io.IOException;
import java.io.StringReader; import java.io.StringReader;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.function.BiConsumer;
import java.util.stream.Stream;
import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagCompound;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Material; import org.bukkit.Material;
@ -15,12 +19,18 @@ import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.ListPersistentDataType;
import org.bukkit.persistence.PersistentDataAdapterContext; import org.bukkit.persistence.PersistentDataAdapterContext;
import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType; import org.bukkit.persistence.PersistentDataType;
import org.bukkit.support.AbstractTestingBase; 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.BeforeAll;
import org.junit.jupiter.api.Test; 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 { public class PersistentDataContainerTest extends AbstractTestingBase {
@ -31,18 +41,14 @@ public class PersistentDataContainerTest extends AbstractTestingBase {
VALID_KEY = new NamespacedKey("test", "validkey"); VALID_KEY = new NamespacedKey("test", "validkey");
} }
/* // Sets a test
Sets a test
*/
@Test @Test
public void testSetNoAdapter() { public void testSetNoAdapter() {
ItemMeta itemMeta = createNewItemMeta(); ItemMeta itemMeta = createNewItemMeta();
assertThrows(IllegalArgumentException.class, () -> itemMeta.getPersistentDataContainer().set(VALID_KEY, new PrimitiveTagType<>(boolean.class), true)); assertThrows(IllegalArgumentException.class, () -> itemMeta.getPersistentDataContainer().set(VALID_KEY, new PrimitiveTagType<>(boolean.class), true));
} }
/* // Contains a tag
Contains a tag
*/
@Test @Test
public void testHasNoAdapter() { public void testHasNoAdapter() {
ItemMeta itemMeta = createNewItemMeta(); ItemMeta itemMeta = createNewItemMeta();
@ -57,9 +63,7 @@ public class PersistentDataContainerTest extends AbstractTestingBase {
assertTrue(itemMeta.getPersistentDataContainer().has(VALID_KEY)); assertTrue(itemMeta.getPersistentDataContainer().has(VALID_KEY));
} }
/* // Getting a tag
Getting a tag
*/
@Test @Test
public void testGetNoAdapter() { public void testGetNoAdapter() {
ItemMeta itemMeta = createNewItemMeta(); ItemMeta itemMeta = createNewItemMeta();
@ -87,11 +91,11 @@ public class PersistentDataContainerTest extends AbstractTestingBase {
assertEquals(160L, (long) meta.getPersistentDataContainer().get(namespacedKeyB, PersistentDataType.LONG)); assertEquals(160L, (long) meta.getPersistentDataContainer().get(namespacedKeyB, PersistentDataType.LONG));
} }
private ItemMeta createNewItemMeta() { private static ItemMeta createNewItemMeta() {
return Bukkit.getItemFactory().getItemMeta(Material.DIAMOND_PICKAXE); 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()); return new NamespacedKey("test-plugin", keyName.toLowerCase());
} }
@ -117,9 +121,7 @@ public class PersistentDataContainerTest extends AbstractTestingBase {
assertEquals(container, target); assertEquals(container, target);
} }
/* // Removing a tag
Removing a tag
*/
@Test @Test
public void testNBTTagStoring() { public void testNBTTagStoring() {
CraftMetaItem itemMeta = createComplexItemMeta(); 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-string"), PersistentDataType.STRING, "Hello there world");
itemMeta.getPersistentDataContainer().set(requestKey("custom-int"), PersistentDataType.INTEGER, 3); itemMeta.getPersistentDataContainer().set(requestKey("custom-int"), PersistentDataType.INTEGER, 3);
itemMeta.getPersistentDataContainer().set(requestKey("custom-double"), PersistentDataType.DOUBLE, 3.123); 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 PersistentDataContainer innerContainer = itemMeta.getPersistentDataContainer().getAdapterContext().newPersistentDataContainer(); //Add a inner container
innerContainer.set(VALID_KEY, PersistentDataType.LONG, 5L); innerContainer.set(VALID_KEY, PersistentDataType.LONG, 5L);
@ -195,9 +203,7 @@ public class PersistentDataContainerTest extends AbstractTestingBase {
return itemMeta; return itemMeta;
} }
/* // Test edge cases with strings
Test edge cases with strings
*/
@Test @Test
public void testStringEdgeCases() throws IOException, InvalidConfigurationException { public void testStringEdgeCases() throws IOException, InvalidConfigurationException {
final ItemStack stack = new ItemStack(Material.DIAMOND); 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)); assertEquals(jsonLookalike, loadedPdc.get(requestKey("string_json_lookalike"), PersistentDataType.STRING));
} }
/* // Test complex object storage
Test complex object storage
*/
@Test @Test
public void storeUUIDOnItemTest() { public void storeUUIDOnItemTest() {
ItemMeta itemMeta = createNewItemMeta(); ItemMeta itemMeta = createNewItemMeta();
@ -285,25 +289,29 @@ public class PersistentDataContainerTest extends AbstractTestingBase {
class UUIDPersistentDataType implements PersistentDataType<byte[], UUID> { class UUIDPersistentDataType implements PersistentDataType<byte[], UUID> {
@Override @Override
@NotNull
public Class<byte[]> getPrimitiveType() { public Class<byte[]> getPrimitiveType() {
return byte[].class; return byte[].class;
} }
@NotNull
@Override @Override
public Class<UUID> getComplexType() { public Class<UUID> getComplexType() {
return UUID.class; return UUID.class;
} }
@NotNull
@Override @Override
public byte[] toPrimitive(UUID complex, PersistentDataAdapterContext context) { public byte[] toPrimitive(@NotNull UUID complex, @NotNull PersistentDataAdapterContext context) {
ByteBuffer bb = ByteBuffer.wrap(new byte[16]); ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
bb.putLong(complex.getMostSignificantBits()); bb.putLong(complex.getMostSignificantBits());
bb.putLong(complex.getLeastSignificantBits()); bb.putLong(complex.getLeastSignificantBits());
return bb.array(); return bb.array();
} }
@NotNull
@Override @Override
public UUID fromPrimitive(byte[] primitive, PersistentDataAdapterContext context) { public UUID fromPrimitive(@NotNull byte[] primitive, @NotNull PersistentDataAdapterContext context) {
ByteBuffer bb = ByteBuffer.wrap(primitive); ByteBuffer bb = ByteBuffer.wrap(primitive);
long firstLong = bb.getLong(); long firstLong = bb.getLong();
long secondLong = bb.getLong(); long secondLong = bb.getLong();
@ -315,22 +323,22 @@ public class PersistentDataContainerTest extends AbstractTestingBase {
public void testPrimitiveCustomTags() { public void testPrimitiveCustomTags() {
ItemMeta itemMeta = createNewItemMeta(); ItemMeta itemMeta = createNewItemMeta();
testPrimitiveCustomTag(itemMeta, PersistentDataType.BYTE, (byte) 1); this.testPrimitiveCustomTag(itemMeta, PersistentDataType.BYTE, (byte) 1);
testPrimitiveCustomTag(itemMeta, PersistentDataType.SHORT, (short) 1); this.testPrimitiveCustomTag(itemMeta, PersistentDataType.SHORT, (short) 1);
testPrimitiveCustomTag(itemMeta, PersistentDataType.INTEGER, 1); this.testPrimitiveCustomTag(itemMeta, PersistentDataType.INTEGER, 1);
testPrimitiveCustomTag(itemMeta, PersistentDataType.LONG, 1L); this.testPrimitiveCustomTag(itemMeta, PersistentDataType.LONG, 1L);
testPrimitiveCustomTag(itemMeta, PersistentDataType.FLOAT, 1.34F); this.testPrimitiveCustomTag(itemMeta, PersistentDataType.FLOAT, 1.34F);
testPrimitiveCustomTag(itemMeta, PersistentDataType.DOUBLE, 151.123); 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 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 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 1L, 4L, 2L, Long.MAX_VALUE
}); });
} }
@ -356,31 +364,35 @@ public class PersistentDataContainerTest extends AbstractTestingBase {
assertFalse(meta.getPersistentDataContainer().has(tagKey, type)); 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; this.primitiveType = primitiveType;
} }
@NotNull
@Override @Override
public Class<T> getPrimitiveType() { public Class<P> getPrimitiveType() {
return primitiveType; return this.primitiveType;
} }
@NotNull
@Override @Override
public Class<T> getComplexType() { public Class<P> getComplexType() {
return primitiveType; return this.primitiveType;
} }
@NotNull
@Override @Override
public T toPrimitive(T complex, PersistentDataAdapterContext context) { public P toPrimitive(@NotNull P complex, @NotNull PersistentDataAdapterContext context) {
return complex; return complex;
} }
@NotNull
@Override @Override
public T fromPrimitive(T primitive, PersistentDataAdapterContext context) { public P fromPrimitive(@NotNull P primitive, @NotNull PersistentDataAdapterContext context) {
return primitive; return primitive;
} }
} }
@ -397,7 +409,105 @@ public class PersistentDataContainerTest extends AbstractTestingBase {
assertNotSame(container, clonedContainer); assertNotSame(container, clonedContainer);
assertEquals(container, clonedContainer); assertEquals(container, clonedContainer);
clonedContainer.set(VALID_KEY, PersistentDataType.STRING, "dinnerbone"); clonedContainer.set(PersistentDataContainerTest.VALID_KEY, PersistentDataType.STRING, "dinnerbone");
assertNotEquals(container, clonedContainer); 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));
}
} }