/* * Copyright (C) 2013-2016 Gonçalo Baltazar <me@goncalomb.com> * * This file is part of NBTEditor. * * NBTEditor is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * NBTEditor is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with NBTEditor. If not, see <http://www.gnu.org/licenses/>. */ package com.goncalomb.bukkit.mylib.reflect; import java.lang.reflect.Method; import java.util.Map.Entry; public class NBTBase { private static boolean _isPrepared = false; protected static Class<?> _nbtBaseClass; protected static Class<?> _nbtTagCompoundClass; protected static Class<?> _nbtTagListClass; protected static Class<?> _nbtTagStringClass; private static Method _getTypeId; private static Method _clone; final Object _handle; // The wrapped Minecraft NBTBase instance. public static final void prepareReflection() { if (!_isPrepared) { _nbtBaseClass = BukkitReflect.getMinecraftClass("NBTBase"); _nbtTagCompoundClass = BukkitReflect.getMinecraftClass("NBTTagCompound"); _nbtTagListClass = BukkitReflect.getMinecraftClass("NBTTagList"); _nbtTagStringClass = BukkitReflect.getMinecraftClass("NBTTagString"); try { _getTypeId = _nbtBaseClass.getMethod("getTypeId"); _clone = _nbtBaseClass.getMethod("clone"); NBTTagCompound.prepareReflectionz(); NBTTagList.prepareReflectionz(); NBTTypes.prepareReflection(); NBTUtils.prepareReflection(); } catch (Exception e) { throw new RuntimeException("Error while preparing NBT wrapper classes.", e); } _isPrepared = true; } } // Wraps any Minecraft tags in MyLib tags. // Primitives and strings are wrapped with NBTBase. protected static final NBTBase wrap(Object object) { if (_nbtTagCompoundClass.isInstance(object)) { return new NBTTagCompound(object); } else if (_nbtTagListClass.isInstance(object)) { return new NBTTagList(object); } else if (_nbtBaseClass.isInstance(object)) { return new NBTBase(object); } else { throw new RuntimeException(object.getClass() + " is not a valid NBT tag type."); } } // Helper method for NBTTagCompoundWrapper.merge(). // Clones any internal Minecraft tags. protected static final Object clone(Object nbtBaseObject) { return BukkitReflect.invokeMethod(nbtBaseObject, _clone); } protected NBTBase(Object handle) { _handle = handle; } protected final Object invokeMethod(Method method, Object... args) { return BukkitReflect.invokeMethod(_handle, method, args); } static byte getTypeId(Object handle) { return (Byte) BukkitReflect.invokeMethod(handle, _getTypeId); } public NBTBase clone() { return wrap(invokeMethod(_clone)); } private String toStringAny(Object object) { // Some "dirty" code to fix the internal Mojanson encoder. StringBuilder buffer = new StringBuilder(); if (_nbtTagCompoundClass.isInstance(object)) { // We need this to force using this method on all compound values. buffer.append("{"); int i = 0; for (Entry<String, Object> entry : (new NBTTagCompound(object))._map.entrySet()) { if (i++ != 0) buffer.append(","); buffer.append(entry.getKey()); buffer.append(":"); buffer.append(toStringAny(entry.getValue())); } buffer.append("}"); } else if (_nbtTagListClass.isInstance(object)) { // We need this to force using this method on all list values. // Mojang, WHY do lists need to be numbered!?!?. // Strings with ':' on unnumbered lists break the parser. // The numbers don't even determine the position on the list. // Le sigh. buffer.append("["); int i = 0; for (Object obj : (new NBTTagList(object))._list) { if (i != 0) buffer.append(","); buffer.append(i++); buffer.append(":"); buffer.append(toStringAny(obj)); } buffer.append("]"); } else if (_nbtTagStringClass.isInstance(object)) { // This is the actual fix. // The " character on Strings needs to be escaped. String str = (String) NBTTypes.fromInternal(object); buffer.append('"'); int j = 0, l = str.length(); for (int i = 0; i < l; ++i) { char c = str.charAt(i); // There is a problem with the internal decoder, it does not recognize \\ as an escaped \. // It's not possible to encode strings that end with \. Mojang, fix it. if (/*c == '\' || */c == '"') { buffer.append(str.substring(j, i)); buffer.append("\\" + c); j = i + 1; } } if (j != l) { buffer.append(str.substring(j, l)); } buffer.append('"'); } else { return object.toString(); } return buffer.toString(); } @Override public String toString() { return toStringAny(_handle); } }