Replace ItemTag API with new API that also expands to Tiles and Entities

By: Bjarne Koll <LynxPlay101@gmail.com>
This commit is contained in:
CraftBukkit/Spigot
2019-04-25 14:36:46 +10:00
parent 4198bf7e21
commit c9a23d73a0
16 changed files with 797 additions and 221 deletions

View File

@@ -0,0 +1,22 @@
package org.bukkit.craftbukkit.persistence;
import org.bukkit.persistence.PersistentDataAdapterContext;
public final class CraftPersistentDataAdapterContext implements PersistentDataAdapterContext {
private final CraftPersistentDataTypeRegistry registry;
public CraftPersistentDataAdapterContext(CraftPersistentDataTypeRegistry registry) {
this.registry = registry;
}
/**
* Creates a new and empty tag container instance
*
* @return the fresh container instance
*/
@Override
public CraftPersistentDataContainer newPersistentDataContainer() {
return new CraftPersistentDataContainer(this.registry);
}
}

View File

@@ -0,0 +1,142 @@
package org.bukkit.craftbukkit.persistence;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import net.minecraft.server.NBTBase;
import net.minecraft.server.NBTTagCompound;
import org.apache.commons.lang.Validate;
import org.bukkit.NamespacedKey;
import org.bukkit.craftbukkit.util.CraftNBTTagConfigSerializer;
import org.bukkit.persistence.PersistentDataAdapterContext;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
public final class CraftPersistentDataContainer implements PersistentDataContainer {
private final Map<String, NBTBase> customDataTags = new HashMap<>();
private final CraftPersistentDataTypeRegistry registry;
private final CraftPersistentDataAdapterContext adapterContext;
public CraftPersistentDataContainer(Map<String, NBTBase> customTags, CraftPersistentDataTypeRegistry registry) {
this(registry);
this.customDataTags.putAll(customTags);
}
public CraftPersistentDataContainer(CraftPersistentDataTypeRegistry registry) {
this.registry = registry;
this.adapterContext = new CraftPersistentDataAdapterContext(this.registry);
}
@Override
public <T, Z> void set(NamespacedKey key, PersistentDataType<T, Z> type, Z value) {
Validate.notNull(key, "The provided key for the custom value was null");
Validate.notNull(type, "The provided type for the custom value was null");
Validate.notNull(value, "The provided value for the custom value was null");
this.customDataTags.put(key.toString(), registry.wrap(type.getPrimitiveType(), type.toPrimitive(value, adapterContext)));
}
@Override
public <T, Z> boolean has(NamespacedKey key, PersistentDataType<T, Z> type) {
Validate.notNull(key, "The provided key for the custom value was null");
Validate.notNull(type, "The provided type for the custom value was null");
NBTBase value = this.customDataTags.get(key.toString());
if (value == null) {
return false;
}
return registry.isInstanceOf(type.getPrimitiveType(), value);
}
@Override
public <T, Z> Z get(NamespacedKey key, PersistentDataType<T, Z> type) {
Validate.notNull(key, "The provided key for the custom value was null");
Validate.notNull(type, "The provided type for the custom value was null");
NBTBase value = this.customDataTags.get(key.toString());
if (value == null) {
return null;
}
return type.fromPrimitive(registry.extract(type.getPrimitiveType(), value), adapterContext);
}
@Override
public <T, Z> Z getOrDefault(NamespacedKey key, PersistentDataType<T, Z> type, Z defaultValue) {
Z z = get(key, type);
return z != null ? z : defaultValue;
}
@Override
public void remove(NamespacedKey key) {
Validate.notNull(key, "The provided key for the custom value was null");
this.customDataTags.remove(key.toString());
}
@Override
public boolean isEmpty() {
return this.customDataTags.isEmpty();
}
@Override
public PersistentDataAdapterContext getAdapterContext() {
return this.adapterContext;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof CraftPersistentDataContainer)) {
return false;
}
Map<String, NBTBase> myRawMap = getRaw();
Map<String, NBTBase> theirRawMap = ((CraftPersistentDataContainer) obj).getRaw();
return Objects.equals(myRawMap, theirRawMap);
}
public NBTTagCompound toTagCompound() {
NBTTagCompound tag = new NBTTagCompound();
for (Entry<String, NBTBase> entry : this.customDataTags.entrySet()) {
tag.set(entry.getKey(), entry.getValue());
}
return tag;
}
public void put(String key, NBTBase base) {
this.customDataTags.put(key, base);
}
public void putAll(Map<String, NBTBase> map) {
this.customDataTags.putAll(map);
}
public void putAll(NBTTagCompound compound) {
for (String key : compound.getKeys()) {
this.customDataTags.put(key, compound.get(key));
}
}
public Map<String, NBTBase> getRaw() {
return this.customDataTags;
}
public CraftPersistentDataTypeRegistry getDataTagTypeRegistry() {
return registry;
}
@Override
public int hashCode() {
int hashCode = 3;
hashCode += this.customDataTags.hashCode(); // We will simply add the maps hashcode
return hashCode;
}
public Map<String, Object> serialize() {
return (Map<String, Object>) CraftNBTTagConfigSerializer.serialize(toTagCompound());
}
}

View File

@@ -0,0 +1,229 @@
package org.bukkit.craftbukkit.persistence;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import com.google.common.primitives.Primitives;
import net.minecraft.server.NBTBase;
import net.minecraft.server.NBTTagByte;
import net.minecraft.server.NBTTagByteArray;
import net.minecraft.server.NBTTagCompound;
import net.minecraft.server.NBTTagDouble;
import net.minecraft.server.NBTTagFloat;
import net.minecraft.server.NBTTagInt;
import net.minecraft.server.NBTTagIntArray;
import net.minecraft.server.NBTTagLong;
import net.minecraft.server.NBTTagLongArray;
import net.minecraft.server.NBTTagShort;
import net.minecraft.server.NBTTagString;
import org.apache.commons.lang3.Validate;
import org.bukkit.persistence.PersistentDataContainer;
/**
* This class represents a registry that contains the used adapters for.
*/
public final class CraftPersistentDataTypeRegistry {
private final Function<Class, TagAdapter> CREATE_ADAPTER = this::createAdapter;
private class TagAdapter<T, Z extends NBTBase> {
private final Function<T, Z> builder;
private final Function<Z, T> extractor;
private final Class<T> primitiveType;
private final Class<Z> nbtBaseType;
public TagAdapter(Class<T> primitiveType, Class<Z> nbtBaseType, Function<T, Z> builder, Function<Z, T> extractor) {
this.primitiveType = primitiveType;
this.nbtBaseType = nbtBaseType;
this.builder = builder;
this.extractor = extractor;
}
/**
* This method will extract the value stored in the tag, according to
* the expected primitive type.
*
* @param base the base to extract from
*
* @return the value stored inside of the tag
*
* @throws ClassCastException if the passed base is not an instanced of
* the defined base type and therefore is not applicable to the
* extractor function
*/
T extract(NBTBase base) {
Validate.isInstanceOf(nbtBaseType, base, "The provided NBTBase was of the type %s. Expected type %s", base.getClass().getSimpleName(), nbtBaseType.getSimpleName());
return this.extractor.apply(nbtBaseType.cast(base));
}
/**
* Builds a tag instance wrapping around the provided value object.
*
* @param value the value to store inside the created tag
*
* @return the new tag instance
*
* @throws ClassCastException if the passed value object is not of the
* defined primitive type and therefore is not applicable to the builder
* function
*/
Z build(Object value) {
Validate.isInstanceOf(primitiveType, value, "The provided value was of the type %s. Expected type %s", value.getClass().getSimpleName(), primitiveType.getSimpleName());
return this.builder.apply(primitiveType.cast(value));
}
/**
* Returns if the tag instance matches the adapters one.
*
* @param base the base to check
*
* @return if the tag was an instance of the set type
*/
boolean isInstance(NBTBase base) {
return this.nbtBaseType.isInstance(base);
}
}
private final Map<Class, TagAdapter> adapters = new HashMap<>();
/**
* Creates a suitable adapter instance for the primitive class type
*
* @param type the type to create an adapter for
* @param <T> the generic type of that class
*
* @return the created adapter instance
*
* @throws IllegalArgumentException if no suitable tag type adapter for this
* type was found
*/
private <T> TagAdapter createAdapter(Class<T> type) {
if (!Primitives.isWrapperType(type)) {
type = Primitives.wrap(type); //Make sure we will always "switch" over the wrapper types
}
/*
Primitives
*/
if (Objects.equals(Byte.class, type)) {
return createAdapter(Byte.class, NBTTagByte.class, NBTTagByte::new, NBTTagByte::asByte);
}
if (Objects.equals(Short.class, type)) {
return createAdapter(Short.class, NBTTagShort.class, NBTTagShort::new, NBTTagShort::asShort);
}
if (Objects.equals(Integer.class, type)) {
return createAdapter(Integer.class, NBTTagInt.class, NBTTagInt::new, NBTTagInt::asInt);
}
if (Objects.equals(Long.class, type)) {
return createAdapter(Long.class, NBTTagLong.class, NBTTagLong::new, NBTTagLong::asLong);
}
if (Objects.equals(Float.class, type)) {
return createAdapter(Float.class, NBTTagFloat.class, NBTTagFloat::new, NBTTagFloat::asFloat);
}
if (Objects.equals(Double.class, type)) {
return createAdapter(Double.class, NBTTagDouble.class, NBTTagDouble::new, NBTTagDouble::asDouble);
}
/*
String
*/
if (Objects.equals(String.class, type)) {
return createAdapter(String.class, NBTTagString.class, NBTTagString::new, NBTTagString::asString);
}
/*
Primitive Arrays
*/
if (Objects.equals(byte[].class, type)) {
return createAdapter(byte[].class, NBTTagByteArray.class, array -> new NBTTagByteArray(Arrays.copyOf(array, array.length)), n -> Arrays.copyOf(n.getBytes(), n.size()));
}
if (Objects.equals(int[].class, type)) {
return createAdapter(int[].class, NBTTagIntArray.class, array -> new NBTTagIntArray(Arrays.copyOf(array, array.length)), n -> Arrays.copyOf(n.getInts(), n.size()));
}
if (Objects.equals(long[].class, type)) {
return createAdapter(long[].class, NBTTagLongArray.class, array -> new NBTTagLongArray(Arrays.copyOf(array, array.length)), n -> Arrays.copyOf(n.getLongs(), n.size()));
}
/*
Note that this will map the interface PersistentMetadataContainer directly to the CraftBukkit implementation
Passing any other instance of this form to the tag type registry will throw a ClassCastException as defined in TagAdapter#build
*/
if (Objects.equals(PersistentDataContainer.class, type)) {
return createAdapter(CraftPersistentDataContainer.class, NBTTagCompound.class, CraftPersistentDataContainer::toTagCompound, tag -> {
CraftPersistentDataContainer container = new CraftPersistentDataContainer(this);
for (String key : tag.getKeys()) {
container.put(key, tag.get(key));
}
return container;
});
}
throw new IllegalArgumentException("Could not find a valid TagAdapter implementation for the requested type " + type.getSimpleName());
}
private <T, Z extends NBTBase> TagAdapter<T, Z> createAdapter(Class<T> primitiveType, Class<Z> nbtBaseType, Function<T, Z> builder, Function<Z, T> extractor) {
return new TagAdapter<>(primitiveType, nbtBaseType, builder, extractor);
}
/**
* Wraps the passed value into a tag instance.
*
* @param type the type of the passed value
* @param value the value to be stored in the tag
* @param <T> the generic type of the value
*
* @return the created tag instance
*
* @throws IllegalArgumentException if no suitable tag type adapter for this
* type was found
*/
public <T> NBTBase wrap(Class<T> type, T value) {
return this.adapters.computeIfAbsent(type, CREATE_ADAPTER).build(value);
}
/**
* Returns if the tag instance matches the provided primitive type.
*
* @param type the type of the primitive value
* @param base the base instance to check
* @param <T> the generic type of the type
*
* @return if the base stores values of the primitive type passed
*
* @throws IllegalArgumentException if no suitable tag type adapter for this
* type was found
*/
public <T> boolean isInstanceOf(Class<T> type, NBTBase base) {
return this.adapters.computeIfAbsent(type, CREATE_ADAPTER).isInstance(base);
}
/**
* Extracts the value out of the provided tag.
*
* @param type the type of the value to extract
* @param tag the tag to extract the value from
* @param <T> the generic type of the value stored inside the tag
*
* @return the extracted value
*
* @throws IllegalArgumentException if the passed base is not an instanced
* of the defined base type and therefore is not applicable to the extractor
* function
* @throws IllegalArgumentException if the found object is not of type
* passed
* @throws IllegalArgumentException if no suitable tag type adapter for this
* type was found
*/
public <T> T extract(Class<T> type, NBTBase tag) throws ClassCastException, IllegalArgumentException {
TagAdapter adapter = this.adapters.computeIfAbsent(type, CREATE_ADAPTER);
Validate.isTrue(adapter.isInstance(tag), "`The found tag instance cannot store %s as it is a %s", type.getSimpleName(), tag.getClass().getSimpleName());
Object foundValue = adapter.extract(tag);
Validate.isInstanceOf(type, foundValue, "The found object is of the type %s. Expected type %s", foundValue.getClass().getSimpleName(), type.getSimpleName());
return type.cast(foundValue);
}
}