From 519d3ee2d6a1db81086f16addeba3eedee214300 Mon Sep 17 00:00:00 2001 From: Traks <58818927+traksag@users.noreply.github.com> Date: Mon, 23 Dec 2019 21:35:37 +0100 Subject: [PATCH] Fix 'Unable to find method createTag' on 1.15 servers (#2642) * Fix 'Unable to find method createTag' on 1.15 servers (#2629) Mojang apparently refactored their NBT code in 1.15, so the NBT parsing code in NbtFactory that used Mojang's NBT code via Reflection broke. Since PlotSquared now depends on WorldEdit, it is much easier to use their NBT parsing library than to update the Reflection-based code. * Clean up NBT streams properly --- .../plotsquared/bukkit/util/NbtFactory.java | 1041 ----------------- .../bukkit/uuid/FileUUIDHandler.java | 53 +- 2 files changed, 34 insertions(+), 1060 deletions(-) delete mode 100644 Bukkit/src/main/java/com/github/intellectualsites/plotsquared/bukkit/util/NbtFactory.java diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/bukkit/util/NbtFactory.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/bukkit/util/NbtFactory.java deleted file mode 100644 index 10407f5ec..000000000 --- a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/bukkit/util/NbtFactory.java +++ /dev/null @@ -1,1041 +0,0 @@ -package com.github.intellectualsites.plotsquared.bukkit.util; - -import com.google.common.base.Splitter; -import com.google.common.collect.BiMap; -import com.google.common.collect.HashBiMap; -import com.google.common.collect.Lists; -import com.google.common.collect.MapMaker; -import com.google.common.io.ByteSink; -import com.google.common.io.Closeables; -import com.google.common.primitives.Primitives; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.Server; -import org.bukkit.inventory.ItemStack; - -import java.io.BufferedInputStream; -import java.io.DataInput; -import java.io.DataInputStream; -import java.io.DataOutput; -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.AbstractList; -import java.util.AbstractMap; -import java.util.AbstractSet; -import java.util.Arrays; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentMap; -import java.util.zip.GZIPInputStream; -import java.util.zip.GZIPOutputStream; - -public class NbtFactory { - - // Convert between NBT type and the equivalent class in java - private static final BiMap> NBT_CLASS = HashBiMap.create(); - private static final BiMap NBT_ENUM = HashBiMap.create(); - // Shared instance - private static NbtFactory INSTANCE; - private final Field[] DATA_FIELD = new Field[12]; - // The NBT base class - private Class BASE_CLASS; - private Class COMPOUND_CLASS; - private Class STREAM_TOOLS; - private Class READ_LIMITER_CLASS; - private Method NBT_CREATE_TAG; - private Method NBT_GET_TYPE; - private Field NBT_LIST_TYPE; - // CraftItemStack - private Class CRAFT_STACK; - private Field CRAFT_HANDLE; - private Field STACK_TAG; - // Loading/saving compounds - private LoadCompoundMethod LOAD_COMPOUND; - private Method SAVE_COMPOUND; - - /** - * Construct an instance of the NBT factory by deducing the class of NBTBase. - */ - private NbtFactory() { - if (this.BASE_CLASS == null) { - try { - // Keep in mind that I do use hard-coded field names - but it's okay as long as we're dealing - // with CraftBukkit or its derivatives. This does not work in MCPC+ however. - ClassLoader loader = NbtFactory.class.getClassLoader(); - - String packageName = getPackageName(); - String craftpackageName = getCraftPackageName(); - Class offlinePlayer = loader.loadClass(packageName + ".CraftOfflinePlayer"); - - // Prepare NBT - this.COMPOUND_CLASS = - getMethod(0, Modifier.STATIC, offlinePlayer, "getData").getReturnType(); - this.BASE_CLASS = loader.loadClass(craftpackageName + ".NBTBase"); - this.NBT_GET_TYPE = getMethod(0, Modifier.STATIC, this.BASE_CLASS, "getTypeId"); - this.NBT_CREATE_TAG = - getMethod(Modifier.STATIC, 0, this.BASE_CLASS, "createTag", byte.class); - - // Prepare CraftItemStack - this.CRAFT_STACK = loader.loadClass(packageName + ".inventory.CraftItemStack"); - this.CRAFT_HANDLE = getField(null, this.CRAFT_STACK, "handle"); - this.STACK_TAG = getField(null, this.CRAFT_HANDLE.getType(), "tag"); - - // Loading/saving - String nmsPackage = this.BASE_CLASS.getPackage().getName(); - initializeNMS(loader, nmsPackage); - - if (this.READ_LIMITER_CLASS != null) { - this.LOAD_COMPOUND = - new LoadMethodSkinUpdate(this.STREAM_TOOLS, this.READ_LIMITER_CLASS); - } else { - this.LOAD_COMPOUND = new LoadMethodWorldUpdate(this.STREAM_TOOLS); - } - this.SAVE_COMPOUND = - getMethod(Modifier.STATIC, 0, this.STREAM_TOOLS, null, this.BASE_CLASS, - DataOutput.class); - - } catch (ClassNotFoundException e) { - throw new IllegalStateException("Unable to find offline player.", e); - } - } - } - - /** - * Retrieve or construct a shared NBT factory. - * - * @return The factory. - */ - private static NbtFactory get() { - if (INSTANCE == null) { - INSTANCE = new NbtFactory(); - } - return INSTANCE; - } - - /** - * Construct a new NBT list of an unspecified type. - * - * @return The NBT list. - */ - public static NbtList createList(Object... content) { - return createList(Arrays.asList(content)); - } - - /** - * Construct a new NBT list of an unspecified type. - * - * @return The NBT list. - */ - public static NbtList createList(Iterable iterable) { - NbtList list = get().new NbtList(INSTANCE.createNbtTag(NbtType.TAG_LIST, null)); - - // Add the content as well - for (Object obj : iterable) { - list.add(obj); - } - return list; - } - - /** - * Construct a new NBT compound. - * - * @return The NBT compound. - */ - public static NbtCompound createCompound() { - return get().new NbtCompound(INSTANCE.createNbtTag(NbtType.TAG_COMPOUND, null)); - } - - /** - * Construct a new NBT wrapper from a list. - * - * @param nmsList - the NBT list. - * @return The wrapper. - */ - public static NbtList fromList(Object nmsList) { - return get().new NbtList(nmsList); - } - - /** - * Load the content of a file from a stream. - * - * @param input - the stream. - * @param option - whether or not to decompress the input stream. - * @return The decoded NBT compound. - * @throws IOException If anything went wrong. - */ - @SuppressWarnings({"IOResourceOpenedButNotSafelyClosed", "resource"}) - public static NbtCompound fromStream(InputStream input, StreamOptions option) - throws IOException { - DataInputStream data = null; - boolean suppress = true; - - try { - if (option == StreamOptions.GZIP_COMPRESSION) { - data = new DataInputStream(new BufferedInputStream(new GZIPInputStream(input))); - } else { - data = new DataInputStream(new BufferedInputStream(input)); - } - - NbtCompound result = fromCompound(get().LOAD_COMPOUND.loadNbt(data)); - suppress = false; - return result; - - } finally { - if (data != null) { - Closeables.close(data, suppress); - } else if (input != null) { - Closeables.close(input, suppress); - } - } - } - - /** - * Save the content of a NBT compound to a stream. - * - * @param source - the NBT compound to save. - * @param stream - the stream. - * @param option - whether or not to compress the output. - * @throws IOException If anything went wrong. - */ - public static void saveStream(NbtCompound source, ByteSink stream, StreamOptions option) - throws IOException { - - try (OutputStream output = stream.openStream(); - DataOutputStream data = new DataOutputStream( - option == StreamOptions.GZIP_COMPRESSION ? new GZIPOutputStream(output) : output)) { - invokeMethod(get().SAVE_COMPOUND, null, source.getHandle(), data); - } - } - - /** - * Construct a new NBT wrapper from a compound. - * - * @param nmsCompound - the NBT compound. - * @return The wrapper. - */ - public static NbtCompound fromCompound(Object nmsCompound) { - return get().new NbtCompound(nmsCompound); - } - - /** - * Set the NBT compound tag of a given item stack. - *

- * - * @param stack - the item stack, cannot be air. - * @param compound - the new NBT compound, or NULL to remove it. - * @throws IllegalArgumentException If the stack is not a CraftItemStack, or it represents air. - */ - public static void setItemTag(ItemStack stack, NbtCompound compound) { - checkItemStack(stack); - Object nms = getFieldValue(get().CRAFT_HANDLE, stack); - - // Now update the tag compound - setFieldValue(get().STACK_TAG, nms, compound.getHandle()); - } - - /** - * Construct a wrapper for an NBT tag stored (in memory) in an item stack. This is where - * auxiliary data such as enchanting, name and lore is stored. It does not include items - * material, damage value or count. - *

- * The item stack must be a wrapper for a CraftItemStack. - * - * @param stack - the item stack. - * @return A wrapper for its NBT tag. - */ - public static NbtCompound fromItemTag(ItemStack stack) { - checkItemStack(stack); - Object nms = getFieldValue(get().CRAFT_HANDLE, stack); - Object tag = getFieldValue(get().STACK_TAG, nms); - - // Create the tag if it doesn't exist - if (tag == null) { - NbtCompound compound = createCompound(); - setItemTag(stack, compound); - return compound; - } - return fromCompound(tag); - } - - /** - * Retrieve a CraftItemStack version of the stack. - * - * @param stack - the stack to convert. - * @return The CraftItemStack version. - */ - public static ItemStack getCraftItemStack(ItemStack stack) { - // Any need to convert? - if ((stack == null) || get().CRAFT_STACK.isAssignableFrom(stack.getClass())) { - return stack; - } - try { - // Call the private constructor - Constructor caller = INSTANCE.CRAFT_STACK.getDeclaredConstructor(ItemStack.class); - caller.setAccessible(true); - return (ItemStack) caller.newInstance(stack); - } catch (Exception ignored) { - throw new IllegalStateException( - "Unable to convert " + stack + " + to a CraftItemStack."); - } - } - - /** - * Ensure that the given stack can store arbitrary NBT information. - * - * @param stack - the stack to check. - */ - private static void checkItemStack(ItemStack stack) { - if (stack == null) { - throw new IllegalArgumentException("Stack cannot be NULL."); - } - if (!get().CRAFT_STACK.isAssignableFrom(stack.getClass())) { - throw new IllegalArgumentException("Stack must be a CraftItemStack."); - } - if (stack.getType() == Material.AIR) { - throw new IllegalArgumentException( - "ItemStacks representing air cannot store NMS information."); - } - } - - /** - * Invoke a method on the given target instance using the provided parameters. - * - * @param method - the method to invoke. - * @param target - the target. - * @param params - the parameters to supply. - * @return The result of the method. - */ - private static Object invokeMethod(Method method, Object target, Object... params) { - try { - return method.invoke(target, params); - } catch (IllegalAccessException | InvocationTargetException | IllegalArgumentException e) { - throw new RuntimeException("Unable to invoke method " + method + " for " + target, e); - } - } - - private static void setFieldValue(Field field, Object target, Object value) { - try { - field.set(target, value); - } catch (IllegalAccessException | IllegalArgumentException e) { - throw new RuntimeException("Unable to set " + field + " for " + target, e); - } - } - - private static Object getFieldValue(Field field, Object target) { - try { - return field.get(target); - } catch (IllegalAccessException | IllegalArgumentException e) { - throw new RuntimeException("Unable to retrieve " + field + " for " + target, e); - } - } - - /** - * Search for the first publicly and privately defined method of the given name and parameter count. - * - * @param requireMod - modifiers that are required. - * @param bannedMod - modifiers that are banned. - * @param clazz - a class to start with. - * @param methodName - the method name, or NULL to skip. - * @param params - the expected parameters. - * @return The first method by this name. - * @throws IllegalStateException If we cannot find this method. - */ - private static Method getMethod(int requireMod, int bannedMod, Class clazz, - String methodName, Class... params) { - for (Method method : clazz.getDeclaredMethods()) { - // Limitation: Doesn't handle overloads - if (((method.getModifiers() & requireMod) == requireMod) && ( - (method.getModifiers() & bannedMod) == 0) && ((methodName == null) || method - .getName().equals(methodName)) && Arrays - .equals(method.getParameterTypes(), params)) { - - method.setAccessible(true); - return method; - } - } - // Search in every superclass - if (clazz.getSuperclass() != null) { - return getMethod(requireMod, bannedMod, clazz.getSuperclass(), methodName, params); - } - throw new IllegalStateException( - String.format("Unable to find method %s (%s).", methodName, Arrays.asList(params))); - } - - /** - * Search for the first publicly and privately defined field of the given name. - * - * @param instance - an instance of the class with the field. - * @param clazz - an optional class to start with, or NULL to deduce it from instance. - * @param fieldName - the field name. - * @return The first field by this name. - * @throws IllegalStateException If we cannot find this field. - */ - private static Field getField(Object instance, Class clazz, String fieldName) { - if (clazz == null) { - clazz = instance.getClass(); - } - // Ignore access rules - for (Field field : clazz.getDeclaredFields()) { - if (field.getName().equals(fieldName)) { - field.setAccessible(true); - return field; - } - } - // Recursively fild the correct field - if (clazz.getSuperclass() != null) { - return getField(instance, clazz.getSuperclass(), fieldName); - } - throw new IllegalStateException("Unable to find field " + fieldName + " in " + instance); - } - - private void initializeNMS(ClassLoader loader, String nmsPackage) { - try { - this.STREAM_TOOLS = loader.loadClass(nmsPackage + ".NBTCompressedStreamTools"); - this.READ_LIMITER_CLASS = loader.loadClass(nmsPackage + ".NBTReadLimiter"); - } catch (ClassNotFoundException ignored) { - } - } - - private String getPackageName() { - Server server = Bukkit.getServer(); - String name = server != null ? server.getClass().getPackage().getName() : null; - - if ((name != null) && name.contains("craftbukkit")) { - return name; - } else { - // Fallback - return "org.bukkit.craftbukkit.v1_13_R1"; - } - } - - private String getCraftPackageName() { - String version = - Bukkit.getServer().getClass().getPackage().getName().replace(".", ",").split(",")[3]; - return "net.minecraft.server." + version; - } - - @SuppressWarnings("unchecked") private Map getDataMap(Object handle) { - return (Map) getFieldValue(getDataField(NbtType.TAG_COMPOUND, handle), - handle); - } - - @SuppressWarnings("unchecked") private List getDataList(Object handle) { - return (List) getFieldValue(getDataField(NbtType.TAG_LIST, handle), handle); - } - - /** - * Convert wrapped List and Map objects into their respective NBT counterparts. - * - * @param value - the value of the element to create. Can be a List or a Map. - * @return The NBT element. - */ - private Object unwrapValue(Object value) { - if (value == null) { - return null; - } - - if (value instanceof Wrapper) { - return ((Wrapper) value).getHandle(); - } else if (value instanceof List) { - throw new IllegalArgumentException("Can only insert a WrappedList."); - } else if (value instanceof Map) { - throw new IllegalArgumentException("Can only insert a WrappedCompound."); - } else { - return createNbtTag(getPrimitiveType(value), value); - } - } - - /** - * Convert a given NBT element to a primitive wrapper or List/Map equivalent. - *

All changes to any mutable objects will be reflected in the underlying NBT element(s). - * - * @param nms - the NBT element. - * @return The wrapper equivalent. - */ - private Object wrapNative(Object nms) { - if (nms == null) { - return null; - } - - if (this.BASE_CLASS.isAssignableFrom(nms.getClass())) { - NbtType type = getNbtType(nms); - - // Handle the different types - switch (type) { - case TAG_COMPOUND: - return new NbtCompound(nms); - case TAG_LIST: - return new NbtList(nms); - default: - return getFieldValue(getDataField(type, nms), nms); - } - } - throw new IllegalArgumentException("Unexpected type: " + nms); - } - - /** - * Construct a new NMS NBT tag initialized with the given value. - * - * @param type - the NBT type. - * @param value - the value, or NULL to keep the original value. - * @return The created tag. - */ - private Object createNbtTag(NbtType type, Object value) { - Object tag = invokeMethod(this.NBT_CREATE_TAG, null, (byte) type.id); - - if (value != null) { - setFieldValue(getDataField(type, tag), tag, value); - } - return tag; - } - - /** - * Retrieve the field where the NBT class stores its value. - * - * @param type - the NBT type. - * @param nms - the NBT class instance. - * @return The corresponding field. - */ - private Field getDataField(NbtType type, Object nms) { - if (this.DATA_FIELD[type.id] == null) { - this.DATA_FIELD[type.id] = getField(nms, null, type.getFieldName()); - } - return this.DATA_FIELD[type.id]; - } - - /** - * Retrieve the NBT type from a given NMS NBT tag. - * - * @param nms - the native NBT tag. - * @return The corresponding type. - */ - private NbtType getNbtType(Object nms) { - int type = (Byte) invokeMethod(this.NBT_GET_TYPE, nms); - return NBT_ENUM.get(type); - } - - /** - * Retrieve the nearest NBT type for a given primitive type. - * - * @param primitive - the primitive type. - * @return The corresponding type. - */ - private NbtType getPrimitiveType(Object primitive) { - NbtType type = - NBT_ENUM.get(NBT_CLASS.inverse().get(Primitives.unwrap(primitive.getClass()))); - - // Display the illegal value at least - if (type == null) { - throw new IllegalArgumentException( - String.format("Illegal type: %s (%s)", primitive.getClass(), primitive)); - } - return type; - } - - /** - * Whether or not to enable stream compression. - * - * @author Kristian - */ - public enum StreamOptions { - NO_COMPRESSION, GZIP_COMPRESSION, - } - - - private enum NbtType { - TAG_END(0, Void.class), TAG_BYTE(1, byte.class), TAG_SHORT(2, short.class), TAG_INT(3, - int.class), TAG_LONG(4, long.class), TAG_FLOAT(5, float.class), TAG_DOUBLE(6, - double.class), TAG_BYTE_ARRAY(7, byte[].class), TAG_INT_ARRAY(11, - int[].class), TAG_STRING(8, String.class), TAG_LIST(9, List.class), TAG_COMPOUND(10, - Map.class); - - // Unique NBT type - public final int id; - - NbtType(int id, Class type) { - this.id = id; - NBT_CLASS.put(id, type); - NBT_ENUM.put(id, this); - } - - private String getFieldName() { - if (this == TAG_COMPOUND) { - return "map"; - } else if (this == TAG_LIST) { - return "list"; - } else { - return "data"; - } - } - } - - - /** - * Represents an object that provides a view of a native NMS class. - * - * @author Kristian - */ - public interface Wrapper { - - /** - * Retrieve the underlying native NBT tag. - * - * @return The underlying NBT. - */ - Object getHandle(); - } - - - /** - * Represents a method for loading an NBT compound. - * - * @author Kristian - */ - private static abstract class LoadCompoundMethod { - - protected Method staticMethod; - - protected void setMethod(Method method) { - this.staticMethod = method; - this.staticMethod.setAccessible(true); - } - - /** - * Load an NBT compound from a given stream. - * - * @param input - the input stream. - * @return The loaded NBT compound. - */ - public abstract Object loadNbt(DataInput input); - } - - - /** - * Load an NBT compound from the NBTCompressedStreamTools static method in 1.7.2 - 1.7.5 - */ - private static class LoadMethodWorldUpdate extends LoadCompoundMethod { - - LoadMethodWorldUpdate(Class streamClass) { - setMethod(getMethod(Modifier.STATIC, 0, streamClass, null, DataInput.class)); - } - - @Override public Object loadNbt(DataInput input) { - return invokeMethod(this.staticMethod, null, input); - } - } - - - /** - * Load an NBT compound from the NBTCompressedStreamTools static method in 1.7.8 - */ - private static class LoadMethodSkinUpdate extends LoadCompoundMethod { - - private Object readLimiter; - - LoadMethodSkinUpdate(Class streamClass, Class readLimiterClass) { - setMethod(getMethod(Modifier.STATIC, 0, streamClass, null, DataInput.class, - readLimiterClass)); - - // Find the unlimited read limiter - for (Field field : readLimiterClass.getDeclaredFields()) { - if (readLimiterClass.isAssignableFrom(field.getType())) { - try { - this.readLimiter = field.get(null); - } catch (Exception e) { - throw new RuntimeException("Cannot retrieve read limiter.", e); - } - } - } - } - - @Override public Object loadNbt(DataInput input) { - return invokeMethod(this.staticMethod, null, input, this.readLimiter); - } - } - - - /** - * Represents a root NBT compound. - *

All changes to this map will be reflected in the underlying NBT compound. Values may only be one of the following: - *

    - *
  • Primitive types
  • - *
  • {@link String String}
  • - *
  • {@link NbtList}
  • - *
  • {@link NbtCompound}
  • - *
- *

- * See also: - *

    - *
  • {@link NbtFactory#createCompound()}
  • - *
  • {@link NbtFactory#fromCompound(Object)}
  • - *
- * - * @author Kristian - */ - public final class NbtCompound extends ConvertedMap { - - private NbtCompound(Object handle) { - super(handle, getDataMap(handle)); - } - - // Simplifying access to each value - public Byte getByte(String key, Byte defaultValue) { - return containsKey(key) ? (Byte) get(key) : defaultValue; - } - - public Short getShort(String key, Short defaultValue) { - return containsKey(key) ? (Short) get(key) : defaultValue; - } - - public Integer getInteger(String key, Integer defaultValue) { - return containsKey(key) ? (Integer) get(key) : defaultValue; - } - - public Long getLong(String key, Long defaultValue) { - return containsKey(key) ? (Long) get(key) : defaultValue; - } - - public Float getFloat(String key, Float defaultValue) { - return containsKey(key) ? (Float) get(key) : defaultValue; - } - - public Double getDouble(String key, Double defaultValue) { - return containsKey(key) ? (Double) get(key) : defaultValue; - } - - public String getString(String key, String defaultValue) { - return containsKey(key) ? (String) get(key) : defaultValue; - } - - public byte[] getByteArray(String key, byte[] defaultValue) { - return containsKey(key) ? (byte[]) get(key) : defaultValue; - } - - public int[] getIntegerArray(String key, int[] defaultValue) { - return containsKey(key) ? (int[]) get(key) : defaultValue; - } - - /** - * Retrieve the list by the given name. - * - * @param key - the name of the list. - * @param createNew - whether or not to create a new list if its missing. - * @return An existing list, a new list or NULL. - */ - public NbtList getList(String key, boolean createNew) { - NbtList list = (NbtList) get(key); - - if ((list == null) && createNew) { - put(key, list = createList()); - } - return list; - } - - /** - * Retrieve the map by the given name. - * - * @param key - the name of the map. - * @param createNew - whether or not to create a new map if its missing. - * @return An existing map, a new map or NULL. - */ - public NbtCompound getMap(String key, boolean createNew) { - return getMap(Collections.singletonList(key), createNew); - } - - // Done - - /** - * Set the value of an entry at a given location. - *

- * Every element of the path (except the end) are assumed to be compounds, and will - * be created if they are missing. - * - * @param path - the path to the entry. - * @param value - the new value of this entry. - * @return This compound, for chaining. - */ - public NbtCompound putPath(String path, Object value) { - List entries = getPathElements(path); - Map map = getMap(entries.subList(0, entries.size() - 1), true); - - map.put(entries.get(entries.size() - 1), value); - return this; - } - - /** - * Retrieve the value of a given entry in the tree. - *

- * Every element of the path (except the end) are assumed to be compounds. The - * retrieval operation will be cancelled if any of them are missing. - * - * @param path - path to the entry. - * @return The value, or NULL if not found. - */ - @SuppressWarnings("unchecked") public T getPath(String path) { - List entries = getPathElements(path); - NbtCompound map = getMap(entries.subList(0, entries.size() - 1), false); - - if (map != null) { - return (T) map.get(entries.get(entries.size() - 1)); - } - return null; - } - - /** - * Save the content of a NBT compound to a stream. - * - * @param stream - the output stream. - * @param option - whether or not to compress the output. - * @throws IOException If anything went wrong. - */ - public void saveTo(ByteSink stream, StreamOptions option) throws IOException { - saveStream(this, stream, option); - } - - /** - * Retrieve a map from a given path. - * - * @param path - path of compounds to look up. - * @param createNew - whether or not to create new compounds on the way. - * @return The map at this location. - */ - private NbtCompound getMap(Iterable path, boolean createNew) { - NbtCompound current = this; - - for (String entry : path) { - NbtCompound child = (NbtCompound) current.get(entry); - - if (child == null) { - if (!createNew) { - return null; - } - current.put(entry, child = createCompound()); - } - current = child; - } - return current; - } - - /** - * Split the path into separate elements. - * - * @param path - the path to split. - * @return The elements. - */ - private List getPathElements(String path) { - return Lists.newArrayList(Splitter.on(".").omitEmptyStrings().split(path)); - } - } - - - /** - * Represents a root NBT list. - * See also: - *

    - *
  • {@link NbtFactory#createList(Iterable)}}
  • - *
  • {@link NbtFactory#fromList(Object)}
  • - *
- * - * @author Kristian - */ - public final class NbtList extends ConvertedList { - - private NbtList(Object handle) { - super(handle, getDataList(handle)); - } - } - - - /** - * Represents a class for caching wrappers. - * - * @author Kristian - */ - private final class CachedNativeWrapper { - - // Don't recreate wrapper objects - private final ConcurrentMap cache = new MapMaker().weakKeys().makeMap(); - - public Object wrap(Object value) { - Object current = this.cache.get(value); - - if (current == null) { - current = wrapNative(value); - - // Only cache composite objects - if ((current instanceof ConvertedMap) || (current instanceof ConvertedList)) { - this.cache.put(value, current); - } - } - return current; - } - } - - - /** - * Represents a map that wraps another map and automatically - * converts entries of its type and another exposed type. - * - * @author Kristian - */ - private class ConvertedMap extends AbstractMap implements Wrapper { - - private final Object handle; - private final Map original; - - private final CachedNativeWrapper cache = new CachedNativeWrapper(); - - public ConvertedMap(Object handle, Map original) { - this.handle = handle; - this.original = original; - } - - // For converting back and forth - protected Object wrapOutgoing(Object value) { - return this.cache.wrap(value); - } - - protected Object unwrapIncoming(Object wrapped) { - return unwrapValue(wrapped); - } - - // Modification - @Override public Object put(String key, Object value) { - return wrapOutgoing(this.original.put(key, unwrapIncoming(value))); - } - - // Performance - @Override public Object get(Object key) { - return wrapOutgoing(this.original.get(key)); - } - - @Override public Object remove(Object key) { - return wrapOutgoing(this.original.remove(key)); - } - - @Override public boolean containsKey(Object key) { - return this.original.containsKey(key); - } - - @Override public Set> entrySet() { - return new AbstractSet>() { - @Override public boolean add(Entry e) { - String key = e.getKey(); - Object value = e.getValue(); - - ConvertedMap.this.original.put(key, unwrapIncoming(value)); - return true; - } - - @Override public int size() { - return ConvertedMap.this.original.size(); - } - - @Override public Iterator> iterator() { - return ConvertedMap.this.iterator(); - } - }; - } - - private Iterator> iterator() { - final Iterator> proxy = this.original.entrySet().iterator(); - - return new Iterator>() { - @Override public boolean hasNext() { - return proxy.hasNext(); - } - - @Override public Entry next() { - Entry entry = proxy.next(); - - return new SimpleEntry(entry.getKey(), - wrapOutgoing(entry.getValue())); - } - - @Override public void remove() { - proxy.remove(); - } - }; - } - - @Override public Object getHandle() { - return this.handle; - } - } - - - /** - * Represents a list that wraps another list and converts elements - * of its type and another exposed type. - * - * @author Kristian - */ - private class ConvertedList extends AbstractList implements Wrapper { - - private final Object handle; - - private final List original; - private final CachedNativeWrapper cache = new CachedNativeWrapper(); - - public ConvertedList(Object handle, List original) { - if (NbtFactory.this.NBT_LIST_TYPE == null) { - NbtFactory.this.NBT_LIST_TYPE = getField(handle, null, "type"); - } - this.handle = handle; - this.original = original; - } - - protected Object wrapOutgoing(Object value) { - return this.cache.wrap(value); - } - - protected Object unwrapIncoming(Object wrapped) { - return unwrapValue(wrapped); - } - - @Override public Object get(int index) { - return wrapOutgoing(this.original.get(index)); - } - - @Override public int size() { - return this.original.size(); - } - - @Override public Object set(int index, Object element) { - return wrapOutgoing(this.original.set(index, unwrapIncoming(element))); - } - - @Override public void add(int index, Object element) { - Object nbt = unwrapIncoming(element); - - // Set the list type if its the first element - if (size() == 0) { - setFieldValue(NbtFactory.this.NBT_LIST_TYPE, this.handle, - (byte) getNbtType(nbt).id); - } - this.original.add(index, nbt); - } - - @Override public Object remove(int index) { - return wrapOutgoing(this.original.remove(index)); - } - - @Override public boolean remove(Object o) { - return this.original.remove(unwrapIncoming(o)); - } - - @Override public Object getHandle() { - return this.handle; - } - } -} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/bukkit/uuid/FileUUIDHandler.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/bukkit/uuid/FileUUIDHandler.java index 440aca318..df19ed711 100644 --- a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/bukkit/uuid/FileUUIDHandler.java +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/bukkit/uuid/FileUUIDHandler.java @@ -1,6 +1,5 @@ package com.github.intellectualsites.plotsquared.bukkit.uuid; -import com.github.intellectualsites.plotsquared.bukkit.util.NbtFactory; import com.github.intellectualsites.plotsquared.plot.PlotSquared; import com.github.intellectualsites.plotsquared.plot.config.Captions; import com.github.intellectualsites.plotsquared.plot.config.Settings; @@ -15,6 +14,13 @@ import com.github.intellectualsites.plotsquared.plot.util.expiry.ExpireManager; import com.github.intellectualsites.plotsquared.plot.uuid.UUIDWrapper; import com.google.common.collect.HashBiMap; import com.google.common.collect.Sets; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.jnbt.NBTInputStream; +import com.sk89q.jnbt.Tag; +import java.io.BufferedInputStream; +import java.io.FileNotFoundException; +import java.util.Map; +import java.util.zip.GZIPInputStream; import org.bukkit.Bukkit; import org.bukkit.World; @@ -38,6 +44,16 @@ public class FileUUIDHandler extends UUIDHandlerImplementation { return super.startCaching(whenDone) && cache(whenDone); } + private Tag readTag(File file) throws IOException { + // Don't chain the creation of the GZIP stream and the NBT stream, because their + // constructors may throw an IOException. + try (BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file)); + GZIPInputStream gzipInputStream = new GZIPInputStream(inputStream); + NBTInputStream nbtInputStream = new NBTInputStream(gzipInputStream)) { + return nbtInputStream.readNamedTag().getTag(); + } + } + public boolean cache(final Runnable whenDone) { final File container = Bukkit.getWorldContainer(); List worlds = Bukkit.getWorlds(); @@ -94,18 +110,18 @@ public class FileUUIDHandler extends UUIDHandlerImplementation { UUID uuid = UUID.fromString(s); if (check || all.remove(uuid)) { File file = new File(playerDataFolder, current); - NbtFactory.NbtCompound compound = NbtFactory - .fromStream(new FileInputStream(file), - NbtFactory.StreamOptions.GZIP_COMPRESSION); + CompoundTag compound = (CompoundTag) readTag(file); if (!compound.containsKey("bukkit")) { PlotSquared.debug("ERROR: Player data (" + uuid.toString() + ".dat) does not contain the the key \"bukkit\""); } else { - NbtFactory.NbtCompound bukkit = - (NbtFactory.NbtCompound) compound.get("bukkit"); - String name = (String) bukkit.get("lastKnownName"); - long last = (long) bukkit.get("lastPlayed"); - long first = (long) bukkit.get("firstPlayed"); + Map compoundMap = compound.getValue(); + CompoundTag bukkit = (CompoundTag) compoundMap.get("bukkit"); + Map bukkitMap = bukkit.getValue(); + String name = + (String) bukkitMap.get("lastKnownName").getValue(); + long last = (long) bukkitMap.get("lastPlayed").getValue(); + long first = (long) bukkitMap.get("firstPlayed").getValue(); if (ExpireManager.IMP != null) { ExpireManager.IMP.storeDate(uuid, last); ExpireManager.IMP.storeAccountAge(uuid, last - first); @@ -167,27 +183,26 @@ public class FileUUIDHandler extends UUIDHandlerImplementation { if (!file.exists()) { continue; } - NbtFactory.NbtCompound compound = NbtFactory - .fromStream(new FileInputStream(file), - NbtFactory.StreamOptions.GZIP_COMPRESSION); + CompoundTag compound = (CompoundTag) readTag(file); if (!compound.containsKey("bukkit")) { PlotSquared.debug("ERROR: Player data (" + uuid.toString() + ".dat) does not contain the the key \"bukkit\""); } else { - NbtFactory.NbtCompound bukkit = - (NbtFactory.NbtCompound) compound.get("bukkit"); - String name = (String) bukkit.get("lastKnownName"); + Map compoundMap = compound.getValue(); + CompoundTag bukkit = (CompoundTag) compoundMap.get("bukkit"); + Map bukkitMap = bukkit.getValue(); + String name = (String) bukkitMap.get("lastKnownName").getValue(); StringWrapper wrap = new StringWrapper(name); if (!toAdd.containsKey(wrap)) { - long last = (long) bukkit.get("lastPlayed"); - long first = (long) bukkit.get("firstPlayed"); + long last = (long) bukkitMap.get("lastPlayed").getValue(); + long first = (long) bukkitMap.get("firstPlayed").getValue(); if (Settings.UUID.OFFLINE) { if (Settings.UUID.FORCE_LOWERCASE && !name.toLowerCase() .equals(name)) { uuid = FileUUIDHandler.this.uuidWrapper.getUUID(name); } else { - long most = (long) compound.get("UUIDMost"); - long least = (long) compound.get("UUIDLeast"); + long most = (long) compoundMap.get("UUIDMost").getValue(); + long least = (long) compoundMap.get("UUIDLeast").getValue(); uuid = new UUID(most, least); } }