复活Bukkit的PDC(5)

前言

本篇文章作为复活PDC的最后一章,主要会将现代化的PDC(以11605为例)类型存储和一些现代化PDC拥有的方法移植到11202,并且提供一些测试PDC是否正常工作的测试用例

移植现代化PDC数据类型

接口改造

首先移植Bukkit为现代化PDC提供的新接口
PersistentDataType中添加

java 复制代码
    /*
        Boolean.
     */
    /**
     * A convenience implementation to convert between Byte and Boolean as there is
     * no native implementation for booleans. <br>
     * Any byte value not equal to 0 is considered to be true.
     */
    PersistentDataType<Byte, Boolean> BOOLEAN = new BooleanPersistentDataType();

    /*
        Complex Arrays.
     */
    /**
     * @deprecated Use {@link #LIST}'s {@link ListPersistentDataTypeProvider#dataContainers()} instead as
     * {@link ListPersistentDataType}s offer full support for primitive types, such as the
     * {@link PersistentDataContainer}.
     */
    @Deprecated
    PersistentDataType<PersistentDataContainer[], PersistentDataContainer[]> TAG_CONTAINER_ARRAY = new PrimitivePersistentDataType<>(PersistentDataContainer[].class);

    /**
     * A data type provider type that itself cannot be used as a
     * {@link PersistentDataType}.
     *
     * {@link ListPersistentDataTypeProvider} exposes shared persistent data
     * types for storing lists of other data types, however.
     * <p>
     * Its existence in the {@link PersistentDataType} interface does not permit
     * {@link java.util.List} as a primitive type in combination with a plain
     * {@link PersistentDataType}. {@link java.util.List}s are only valid
     * primitive types when used via a {@link ListPersistentDataType}.
     *
     * @see ListPersistentDataTypeProvider
     */
    ListPersistentDataTypeProvider LIST = new ListPersistentDataTypeProvider();

    /**
     * A convenience implementation to convert between Byte and Boolean as there is
     * no native implementation for booleans. <br>
     * Any byte value not equal to 0 is considered to be true.
     */
    class BooleanPersistentDataType implements PersistentDataType<Byte, Boolean> {

        @NotNull
        @Override
        public Class<Byte> getPrimitiveType() {
            return Byte.class;
        }

        @NotNull
        @Override
        public Class<Boolean> getComplexType() {
            return Boolean.class;
        }

        @NotNull
        @Override
        public Byte toPrimitive(@NotNull Boolean complex, @NotNull PersistentDataAdapterContext context) {
            return (byte) (complex ? 1 : 0);
        }

        @NotNull
        @Override
        public Boolean fromPrimitive(@NotNull Byte primitive, @NotNull PersistentDataAdapterContext context) {
            return primitive != 0;
        }
    }

与此同时,添加高版本中存在的ListPersistentDataTypeListPersistentDataTypeProvider

PersistentDataContainer中添加

java 复制代码
    /**
     * Returns if the persistent metadata provider has metadata registered matching
     * the provided parameters.
     * <p>
     * This method will return true as long as a value with the given key exists,
     * regardless of its type.
     * <p>
     * This method is only usable for custom object keys. Overwriting existing tags,
     * like the display name, will not work as the values are stored using your
     * namespace.
     *
     * @param key the key the value is stored under
     *
     * @return if a value with the provided key exists
     *
     * @throws IllegalArgumentException if the key to look up is null
     */
    boolean has(@NotNull NamespacedKey key);

    /**
     * Get the set of keys present on this {@link PersistentDataContainer}
     * instance.
     *
     * Any changes made to the returned set will not be reflected on the
     * instance.
     *
     * @return the key set
     */
    @NotNull
    Set<NamespacedKey> getKeys();

    /**
     * Copies all values from this {@link PersistentDataContainer} to the provided
     * container.
     * <p>
     * This method only copies custom object keys. Existing tags, like the display
     * name, will not be copied as the values are stored using your namespace.
     *
     * @param other   the container to copy to
     * @param replace whether to replace any matching values in the target container
     *
     * @throws IllegalArgumentException if the other container is null
     */
    void copyTo(@NotNull PersistentDataContainer other, boolean replace);

    // Paper start - byte array serialization
    /**
     * Serialize this {@link PersistentDataContainer} instance to a
     * byte array.
     *
     * @return a binary representation of this container
     * @throws java.io.IOException if we fail to write this container to a byte array
     */
    byte @NotNull [] serializeToBytes() throws java.io.IOException;

    /**
     * Read values from a serialised byte array into this
     * {@link PersistentDataContainer} instance.
     *
     * @param bytes the byte array to read from
     * @param clear if true, this {@link PersistentDataContainer} instance
     *              will be cleared before reading
     * @throws java.io.IOException if the byte array has an invalid format
     */
    void readFromBytes(byte @NotNull [] bytes, boolean clear) throws java.io.IOException;

    /**
     * Read values from a serialised byte array into this
     * {@link PersistentDataContainer} instance.
     * This method has the same effect as
     * <code>PersistentDataContainer#readFromBytes(bytes, true)</code>
     *
     * @param bytes the byte array to read from
     * @throws java.io.IOException if the byte array has an invalid format
     */
    default void readFromBytes(final byte @NotNull [] bytes) throws java.io.IOException {
        this.readFromBytes(bytes, true);
    }
    // Paper end - byte array serialization

实现接口

CraftPersistentDataContainer中实现我们刚刚添加的方法

java 复制代码
    @Override
    public boolean has(NamespacedKey key) {
        Preconditions.checkArgument(key != null, "The provided key for the custom value was null"); // Paper
        return this.customDataTags.get(key.toString()) != null;
    }

    @NotNull
    @Override
    public Set<NamespacedKey> getKeys() {
        Set<NamespacedKey> keys = new HashSet<>();

        this.customDataTags.keySet().forEach(key -> {
            String[] keyData = key.split(":", 2);
            if (keyData.length == 2) {
                keys.add(new NamespacedKey(keyData[0], keyData[1]));
            }
        });

        return keys;
    }

    @NotNull
    @Override
    public void copyTo(PersistentDataContainer other, boolean replace) {
        Preconditions.checkArgument(other != null, "The target container cannot be null");

        DelegateCraftPersistentDataContainer target = (DelegateCraftPersistentDataContainer) other;
        if (replace) {
            target.customDataTags.putAll(this.customDataTags);
        } else {
            this.customDataTags.forEach(target.customDataTags::putIfAbsent);
        }
    }

    // Paper start
    public void clear() {
        this.customDataTags.clear();
    }
    // Paper end

    // Paper start - byte array serialization
    @Override
    public byte[] serializeToBytes() throws java.io.IOException {
        final NBTTagCompound root = this.toTagCompound();
        final java.io.ByteArrayOutputStream byteArrayOutput = new java.io.ByteArrayOutputStream();
        try (final java.io.DataOutputStream dataOutput = new java.io.DataOutputStream(byteArrayOutput)) {
            net.minecraft.server.NBTCompressedStreamTools.writeNBT(root, dataOutput);
            return byteArrayOutput.toByteArray();
        }
    }

    @Override
    public void readFromBytes(final byte[] bytes, final boolean clear) throws java.io.IOException {
        if (clear) {
            this.clear();
        }
        try (final java.io.DataInputStream dataInput = new java.io.DataInputStream(new java.io.ByteArrayInputStream(bytes))) {
            final NBTTagCompound compound = net.minecraft.server.NBTCompressedStreamTools.readNBT(dataInput);
            this.putAll(compound);
        }
    }
    // Paper end - byte array serialization

    // Paper start - deep clone tags
    public Map<String, NBTBase> getTagsCloned() {
        final Map<String, NBTBase> tags = new HashMap<>();
        this.customDataTags.forEach((key, tag) -> tags.put(key, tag.clone()));
        return tags;
    }
    // Paper end - deep clone tags

其中getTagsClonedclear方法是paper加入的,用于优化性能,读者可根据11605服务端源码找到它们的用途并加以优化

CraftPersistentDataType中重新现代化PDC的类型适配器,此部分之前提到过,不再过多阐述

测试用例

平台: junit 4.13.1

java 复制代码
package org.bukkit.craftbukkit.inventory;

import net.minecraft.server.NBTCompressedStreamTools;
import net.minecraft.server.NBTTagCompound;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
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.junit.Test;

import java.io.*;
import java.lang.reflect.Array;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.function.BiConsumer;
import static org.junit.Assert.*;

public class PersistentDataContainerTest extends AbstractTestingBase {

    private static NamespacedKey VALID_KEY;

    public static void setup() {
        VALID_KEY = new NamespacedKey("test", "validkey");
    }

    /*
        Sets a test
     */
    @Test(expected = IllegalArgumentException.class)
    public void testSetNoAdapter() {
        ItemMeta itemMeta = createNewItemMeta();
        itemMeta.getPersistentDataContainer().set(VALID_KEY, new PrimitiveTagType<>(boolean.class), true);
    }

    /*
        Contains a tag
     */
    @Test(expected = IllegalArgumentException.class)
    public void testHasNoAdapter() {
        ItemMeta itemMeta = createNewItemMeta();
        itemMeta.getPersistentDataContainer().set(VALID_KEY, PersistentDataType.INTEGER, 1); // We gotta set this so we at least try to compare it
        itemMeta.getPersistentDataContainer().has(VALID_KEY, new PrimitiveTagType<>(boolean.class));
    }

    /*
        Getting a tag
     */
    @Test(expected = IllegalArgumentException.class)
    public void testGetNoAdapter() {
        ItemMeta itemMeta = createNewItemMeta();
        itemMeta.getPersistentDataContainer().set(VALID_KEY, PersistentDataType.INTEGER, 1); //We gotta set this so we at least try to compare it
        itemMeta.getPersistentDataContainer().get(VALID_KEY, new PrimitiveTagType<>(boolean.class));
    }

    @Test(expected = IllegalArgumentException.class)
    public void testGetWrongType() {
        ItemMeta itemMeta = createNewItemMeta();
        itemMeta.getPersistentDataContainer().set(VALID_KEY, PersistentDataType.INTEGER, 1);
        itemMeta.getPersistentDataContainer().get(VALID_KEY, PersistentDataType.STRING);
    }

    @Test
    public void testDifferentNamespace() {
        NamespacedKey namespacedKeyA = new NamespacedKey("plugin-a", "damage");
        NamespacedKey namespacedKeyB = new NamespacedKey("plugin-b", "damage");

        ItemMeta meta = createNewItemMeta();
        meta.getPersistentDataContainer().set(namespacedKeyA, PersistentDataType.LONG, 15L);
        meta.getPersistentDataContainer().set(namespacedKeyB, PersistentDataType.LONG, 160L);

        assertEquals(15L, (long) meta.getPersistentDataContainer().get(namespacedKeyA, PersistentDataType.LONG));
        assertEquals(160L, (long) meta.getPersistentDataContainer().get(namespacedKeyB, PersistentDataType.LONG));
    }

    private static ItemMeta createNewItemMeta() {
        return Bukkit.getItemFactory().getItemMeta(Material.DIAMOND_PICKAXE);
    }

    private NamespacedKey requestKey(String keyName) {
        return new NamespacedKey("test-plugin", keyName.toLowerCase());
    }

    /*
        Removing a tag
     */
    @Test
    public void testNBTTagStoring() {
        CraftMetaItem itemMeta = createComplexItemMeta();

        NBTTagCompound compound = new NBTTagCompound();
        itemMeta.applyToItem(compound);

        assertEquals(itemMeta, new CraftMetaItem(compound));
    }

    @Test
    public void testMapStoring() {
        CraftMetaItem itemMeta = createComplexItemMeta();

        Map<String, Object> serialize = itemMeta.serialize();
        assertEquals(itemMeta, new CraftMetaItem(serialize));
    }

    @Test
    public void testYAMLStoring() {
        ItemStack stack = new ItemStack(Material.DIAMOND);
        CraftMetaItem meta = createComplexItemMeta();
        stack.setItemMeta(meta);

        YamlConfiguration configuration = new YamlConfiguration();
        configuration.set("testpath", stack);

        String configValue = configuration.saveToString();
        YamlConfiguration loadedConfig = YamlConfiguration.loadConfiguration(new StringReader(configValue));

        assertEquals("testYAMLStoring: 124, should be equal", stack, loadedConfig.getSerializable("testpath", ItemStack.class));
    }

    @Test
    public void testCorrectType() {
        ItemStack stack = new ItemStack(Material.DIAMOND);
        CraftMetaItem meta = createComplexItemMeta();

        meta.getPersistentDataContainer().set(requestKey("int"), PersistentDataType.STRING, "1");
        meta.getPersistentDataContainer().set(requestKey("double"), PersistentDataType.STRING, "1.33");

        meta.getPersistentDataContainer().set(requestKey("normal-int"), PersistentDataType.INTEGER, 123);
        meta.getPersistentDataContainer().set(requestKey("normal-double"), PersistentDataType.DOUBLE, 1.23);
        meta.getPersistentDataContainer().set(requestKey("normal-byte"), PersistentDataType.BYTE, (byte) 123);
        meta.getPersistentDataContainer().set(requestKey("normal-float"), PersistentDataType.FLOAT, 1.34F);
        meta.getPersistentDataContainer().set(requestKey("normal-string"), PersistentDataType.STRING, "quite normal string");
        meta.getPersistentDataContainer().set(requestKey("normal-long"), PersistentDataType.LONG, 32769L);
        meta.getPersistentDataContainer().set(requestKey("normal-boolean-true"), PersistentDataType.BOOLEAN, true);
        meta.getPersistentDataContainer().set(requestKey("normal-boolean-false"), PersistentDataType.BOOLEAN, false);
        stack.setItemMeta(meta);

        YamlConfiguration configuration = new YamlConfiguration();
        configuration.set("testpath", stack);

        String configValue = configuration.saveToString();
        YamlConfiguration loadedConfig = YamlConfiguration.loadConfiguration(new StringReader(configValue));
        ItemStack newStack = loadedConfig.getSerializable("testpath", ItemStack.class);

        assertTrue(newStack.getItemMeta().getPersistentDataContainer().has(requestKey("normal-int"), PersistentDataType.INTEGER));
        assertEquals(newStack.getItemMeta().getPersistentDataContainer().get(requestKey("normal-int"), PersistentDataType.INTEGER).intValue(), 123);

        assertTrue(newStack.getItemMeta().getPersistentDataContainer().has(requestKey("normal-double"), PersistentDataType.DOUBLE));
        assertTrue(newStack.getItemMeta().getPersistentDataContainer().get(requestKey("normal-double"), PersistentDataType.DOUBLE).doubleValue() - 1.23 < 1e-6);

        assertTrue(newStack.getItemMeta().getPersistentDataContainer().has(requestKey("normal-byte"), PersistentDataType.BYTE));
        assertEquals(newStack.getItemMeta().getPersistentDataContainer().get(requestKey("normal-byte"), PersistentDataType.BYTE).byteValue(), (byte) 123);

        assertTrue(newStack.getItemMeta().getPersistentDataContainer().has(requestKey("normal-float"), PersistentDataType.FLOAT));
        assertTrue(newStack.getItemMeta().getPersistentDataContainer().get(requestKey("normal-float"), PersistentDataType.FLOAT).floatValue() - 1.34F < 1e-6);

        assertTrue(newStack.getItemMeta().getPersistentDataContainer().has(requestKey("normal-string"), PersistentDataType.STRING));
        assertEquals(newStack.getItemMeta().getPersistentDataContainer().get(requestKey("normal-string"), PersistentDataType.STRING), "quite normal string");

        assertTrue(newStack.getItemMeta().getPersistentDataContainer().has(requestKey("normal-long"), PersistentDataType.LONG));
        assertEquals(newStack.getItemMeta().getPersistentDataContainer().get(requestKey("normal-long"), PersistentDataType.LONG).longValue(), 32769L);

        assertTrue(newStack.getItemMeta().getPersistentDataContainer().has(requestKey("normal-boolean-true"), PersistentDataType.BOOLEAN));
        assertEquals(newStack.getItemMeta().getPersistentDataContainer().get(requestKey("normal-boolean-true"), PersistentDataType.BOOLEAN).booleanValue(), true);

        assertTrue(newStack.getItemMeta().getPersistentDataContainer().has(requestKey("normal-boolean-false"), PersistentDataType.BOOLEAN));
        assertEquals(newStack.getItemMeta().getPersistentDataContainer().get(requestKey("normal-boolean-false"), PersistentDataType.BOOLEAN).booleanValue(), false);

        assertTrue("testCorrectType: 144, should be true", newStack.getItemMeta().getPersistentDataContainer().has(requestKey("double"), PersistentDataType.STRING));
        assertEquals("testCorrectType: 145, should equal", newStack.getItemMeta().getPersistentDataContainer().get(requestKey("int"), PersistentDataType.STRING), "1");

        assertTrue(newStack.getItemMeta().getPersistentDataContainer().has(requestKey("double"), PersistentDataType.STRING));
        assertEquals(newStack.getItemMeta().getPersistentDataContainer().get(requestKey("double"), PersistentDataType.STRING), "1.33");
    }

    private CraftMetaItem createComplexItemMeta() {
        CraftMetaItem itemMeta = (CraftMetaItem) createNewItemMeta();
        itemMeta.setDisplayName("Item Display Name");

        itemMeta.getPersistentDataContainer().set(requestKey("custom-long"), PersistentDataType.LONG, 4L); //Add random primitive values
        itemMeta.getPersistentDataContainer().set(requestKey("custom-byte-array"), PersistentDataType.BYTE_ARRAY, new byte[]{
            0, 1, 2, 10
        });
        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);

        PersistentDataContainer innerContainer = itemMeta.getPersistentDataContainer().getAdapterContext().newPersistentDataContainer(); //Add a inner container
        innerContainer.set(VALID_KEY, PersistentDataType.LONG, 5L);
        itemMeta.getPersistentDataContainer().set(requestKey("custom-inner-compound"), PersistentDataType.TAG_CONTAINER, innerContainer);
        return itemMeta;
    }

    /*
        Test complex object storage
     */
    @Test
    public void storeUUIDOnItemTest() {
        ItemMeta itemMeta = createNewItemMeta();
        UUIDPersistentDataType uuidPersistentDataType = new UUIDPersistentDataType();
        UUID uuid = UUID.fromString("434eea72-22a6-4c61-b5ef-945874a5c478");

        itemMeta.getPersistentDataContainer().set(VALID_KEY, uuidPersistentDataType, uuid);
        assertTrue(itemMeta.getPersistentDataContainer().has(VALID_KEY, uuidPersistentDataType));
        assertEquals(uuid, itemMeta.getPersistentDataContainer().get(VALID_KEY, uuidPersistentDataType));
    }

    @Test
    public void encapsulatedContainers() {
        NamespacedKey innerKey = new NamespacedKey("plugin-a", "inner");

        ItemMeta meta = createNewItemMeta();
        PersistentDataAdapterContext context = meta.getPersistentDataContainer().getAdapterContext();

        PersistentDataContainer thirdContainer = context.newPersistentDataContainer();
        thirdContainer.set(VALID_KEY, PersistentDataType.LONG, 3L);

        PersistentDataContainer secondContainer = context.newPersistentDataContainer();
        secondContainer.set(VALID_KEY, PersistentDataType.LONG, 2L);
        secondContainer.set(innerKey, PersistentDataType.TAG_CONTAINER, thirdContainer);

        meta.getPersistentDataContainer().set(VALID_KEY, PersistentDataType.LONG, 1L);
        meta.getPersistentDataContainer().set(innerKey, PersistentDataType.TAG_CONTAINER, secondContainer);

        assertEquals(3L, meta.getPersistentDataContainer()
                .get(innerKey, PersistentDataType.TAG_CONTAINER)
                .get(innerKey, PersistentDataType.TAG_CONTAINER)
                .get(VALID_KEY, PersistentDataType.LONG).longValue());

        assertEquals(2L, meta.getPersistentDataContainer()
                .get(innerKey, PersistentDataType.TAG_CONTAINER)
                .get(VALID_KEY, PersistentDataType.LONG).longValue());

        assertEquals(1L, meta.getPersistentDataContainer()
                .get(VALID_KEY, PersistentDataType.LONG).longValue());
    }

    class UUIDPersistentDataType implements PersistentDataType<byte[], UUID> {

        @Override
        public Class<byte[]> getPrimitiveType() {
            return byte[].class;
        }

        @Override
        public Class<UUID> getComplexType() {
            return UUID.class;
        }

        @Override
        public byte[] toPrimitive(UUID complex, PersistentDataAdapterContext context) {
            ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
            bb.putLong(complex.getMostSignificantBits());
            bb.putLong(complex.getLeastSignificantBits());
            return bb.array();
        }

        @Override
        public UUID fromPrimitive(byte[] primitive, PersistentDataAdapterContext context) {
            ByteBuffer bb = ByteBuffer.wrap(primitive);
            long firstLong = bb.getLong();
            long secondLong = bb.getLong();
            return new UUID(firstLong, secondLong);
        }
    }

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

        testPrimitiveCustomTag(itemMeta, PersistentDataType.STRING, "test");

        testPrimitiveCustomTag(itemMeta, PersistentDataType.BYTE_ARRAY, new byte[]{
            1, 4, 2, Byte.MAX_VALUE
        });
        testPrimitiveCustomTag(itemMeta, PersistentDataType.INTEGER_ARRAY, new int[]{
            1, 4, 2, Integer.MAX_VALUE
        });
        testPrimitiveCustomTag(itemMeta, PersistentDataType.LONG_ARRAY, new long[]{
            1L, 4L, 2L, Long.MAX_VALUE
        });
    }

    private <T, Z> void testPrimitiveCustomTag(ItemMeta meta, PersistentDataType<T, Z> type, Z value) {
        NamespacedKey tagKey = new NamespacedKey("test", String.valueOf(type.hashCode()));

        meta.getPersistentDataContainer().set(tagKey, type, value);
        assertTrue(meta.getPersistentDataContainer().has(tagKey, type));

        Z foundValue = meta.getPersistentDataContainer().get(tagKey, type);
        if (foundValue.getClass().isArray()) { // Compare arrays using reflection access
            int length = Array.getLength(foundValue);
            int originalLength = Array.getLength(value);
            for (int i = 0; i < length && i < originalLength; i++) {
                assertEquals(Array.get(value, i), Array.get(foundValue, i));
            }
        } else {
            assertEquals(foundValue, value);
        }

        meta.getPersistentDataContainer().remove(tagKey);
        assertFalse(meta.getPersistentDataContainer().has(tagKey, type));
    }

    class PrimitiveTagType<T> implements PersistentDataType<T, T> {

        private final Class<T> primitiveType;

        PrimitiveTagType(Class<T> primitiveType) {
            this.primitiveType = primitiveType;
        }

        @Override
        public Class<T> getPrimitiveType() {
            return primitiveType;
        }

        @Override
        public Class<T> getComplexType() {
            return primitiveType;
        }

        @Override
        public T toPrimitive(T complex, PersistentDataAdapterContext context) {
            return complex;
        }

        @Override
        public T fromPrimitive(T primitive, PersistentDataAdapterContext context) {
            return primitive;
        }
    }

    @Test
    public void testItemMetaClone() {
        ItemMeta itemMeta = PersistentDataContainerTest.createNewItemMeta();
        PersistentDataContainer container = itemMeta.getPersistentDataContainer();
        itemMeta.getPersistentDataContainer().set(PersistentDataContainerTest.VALID_KEY, PersistentDataType.STRING, "notch");

        ItemMeta clonedMeta = itemMeta.clone();
        PersistentDataContainer clonedContainer = clonedMeta.getPersistentDataContainer();

        assertNotSame(container, clonedContainer);
        assertEquals(container, clonedContainer);

        clonedContainer.set(PersistentDataContainerTest.VALID_KEY, PersistentDataType.STRING, "dinnerbone");
        assertNotEquals(container, clonedContainer);
    }

    @Test
    public void testListTypeBytes() {
        testListType(PersistentDataType.LIST.bytes(), Arrays.asList((byte) 1, (byte) 2, (byte) 3), (a, b) -> assertEquals(a, b));
    }

    @Test
    public void testListTypeShorts() {
        testListType(PersistentDataType.LIST.shorts(), Arrays.asList((short) 1, (short) 2, (short) 3), (a, b) -> assertEquals(a, b));
    }

    @Test
    public void testListTypeIntegers() {
        testListType(PersistentDataType.LIST.integers(), Arrays.asList(1, 2, 3), (a, b) -> assertEquals(a, b));
    }

    @Test
    public void testListTypeLongs() {
        testListType(PersistentDataType.LIST.longs(), Arrays.asList(1L, 2L, 3L), (a, b) -> assertEquals(a, b));
    }

    @Test
    public void testListTypeFloats() {
        testListType(PersistentDataType.LIST.floats(), Arrays.asList(1F, 2F, 3F), (a, b) -> assertEquals(a, b));
    }

    @Test
    public void testListTypeDoubles() {
        testListType(PersistentDataType.LIST.doubles(), Arrays.asList(1D, 2D, 3D), (a, b) -> assertEquals(a, b));
    }

    @Test
    public void testListTypeBooleans() {
        testListType(PersistentDataType.LIST.booleans(), Arrays.asList(true, true, false), (a, b) -> assertEquals(a, b));
    }

    @Test
    public void testListTypeStrings() {
        testListType(PersistentDataType.LIST.strings(), Arrays.asList("a", "b", "c"), (a, b) -> assertEquals(a, b));
    }

    @Test
    public void testListTypeByteArrays() {
        List<byte[]> byteArrayList = new ArrayList<>();
        byteArrayList.add(new byte[]{1, 2, 3});
        byteArrayList.add(new byte[]{4, 5, 6});
        testListType(PersistentDataType.LIST.byteArrays(), byteArrayList, (a, b) -> assertArrayEquals(a, b));
    }

    @Test
    public void testListTypeIntegerArrays() {
        List<int[]> intArrayList = new ArrayList<>();
        intArrayList.add(new int[]{1, 2, 3});
        intArrayList.add(new int[]{4, 5, 6});
        testListType(PersistentDataType.LIST.integerArrays(), intArrayList, (a, b) -> assertArrayEquals(a, b));
    }

    @Test
    public void testListTypeLongArrays() {
        List<long[]> longArrayList = new ArrayList<>();
        longArrayList.add(new long[]{1, 2, 3});
        longArrayList.add(new long[]{4, 5, 6});
        testListType(PersistentDataType.LIST.longArrays(), longArrayList, (a, b) -> assertArrayEquals(a, b));
    }

    @Test
    public void testListTypeDataContainers() {
        PersistentDataContainer first = createNewItemMeta().getPersistentDataContainer();
        PersistentDataContainer second = first.getAdapterContext().newPersistentDataContainer();
        first.set(requestKey("a"), PersistentDataType.STRING, "hello world");
        second.set(requestKey("b"), PersistentDataType.BOOLEAN, true);

        List<PersistentDataContainer> containerList = new ArrayList<>();
        containerList.add(first);
        containerList.add(second);

        testListType(PersistentDataType.LIST.dataContainers(), containerList, (a, b) -> assertEquals(a, b));
    }

    private <T, Z> void testListType(ListPersistentDataType<T, Z> type, List<Z> list, BiConsumer<Z, Z> equalsCheck) {
        ItemMeta meta = createNewItemMeta();
        PersistentDataContainer container = meta.getPersistentDataContainer();

        container.set(requestKey("list"), type, list);

        List<Z> returnedList = container.get(requestKey("list"), type);

        assertNotNull(returnedList);
        assertEquals(list.size(), returnedList.size());

        for (int i = 0; i < list.size(); i++) {
            Z expectedValue = list.get(i);
            Z foundValue = returnedList.get(i);
            equalsCheck.accept(expectedValue, foundValue);
        }
    }

    @Test
    public void testEmptyListApplicationToAnyType() throws IOException {
        final CraftMetaItem craftItem = new CraftMetaItem(new NBTTagCompound());
        final PersistentDataContainer container = craftItem.getPersistentDataContainer();

        container.set(requestKey("list"), PersistentDataType.LIST.strings(), Collections.emptyList());
        assertTrue(container.has(requestKey("list"), PersistentDataType.LIST.strings()));
        assertTrue(container.has(requestKey("list"), PersistentDataType.LIST.bytes()));
        assertFalse(container.has(requestKey("list"), PersistentDataType.STRING));
        assertEquals(Collections.emptyList(), container.get(requestKey("list"), PersistentDataType.LIST.strings()));

        // Write and read the entire container to NBT
        final NBTTagCompound storage = new NBTTagCompound();
        craftItem.applyToItem(storage);

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        DataOutputStream dataOutput = new DataOutputStream(byteArrayOutputStream);
        NBTCompressedStreamTools.writeNBT(storage, dataOutput);

        final NBTTagCompound readStorage = NBTCompressedStreamTools.readNBT(
                new DataInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()))
        );
        final CraftMetaItem readItem = new CraftMetaItem(readStorage);
        final PersistentDataContainer readContainer = readItem.getPersistentDataContainer();

        assertTrue(readContainer.has(requestKey("list"), PersistentDataType.LIST.strings()));
        assertTrue(readContainer.has(requestKey("list"), PersistentDataType.LIST.bytes()));
        assertFalse(readContainer.has(requestKey("list"), PersistentDataType.STRING));
        assertEquals(Collections.emptyList(), readContainer.get(requestKey("list"), PersistentDataType.LIST.strings()));
    }

    // 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 = PersistentDataContainerTest.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).intValue());
    }
}

最后

至此,PDC的移植工作已经完全结束,本系列文章暂时告一段落

游客

全部评论 (0)

暂无评论,快来抢沙发吧~