#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

@@ -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));
}
}