diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/SNBTDeserializer.java b/core/src/main/java/net/momirealms/craftengine/core/util/SNBTDeserializer.java deleted file mode 100644 index 573493ce5..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/util/SNBTDeserializer.java +++ /dev/null @@ -1,334 +0,0 @@ -package net.momirealms.craftengine.core.util; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -public final class SNBTDeserializer { - private static final char COMPOUND_START = '{'; - private static final char COMPOUND_END = '}'; - private static final char LIST_START = '['; - private static final char LIST_END = ']'; - private static final char STRING_DELIMITER = '"'; - private static final char KEY_VALUE_SEPARATOR = ':'; - private static final char ELEMENT_SEPARATOR = ','; - private static final char ESCAPE_CHAR = '\\'; - - // 数字类型后缀 - private static final char BYTE_SUFFIX = 'b'; - private static final char SHORT_SUFFIX = 's'; - private static final char LONG_SUFFIX = 'l'; - private static final char FLOAT_SUFFIX = 'f'; - private static final char DOUBLE_SUFFIX = 'd'; - private static final char BOOLEAN_SUFFIX = 'B'; - - // 布尔值常量 - private static final String TRUE_LITERAL = "true"; - private static final String FALSE_LITERAL = "false"; - private static final int TRUE_LENGTH = 4; - private static final int FALSE_LENGTH = 5; - - private final char[] sourceContent; - private final int length; - private int position = 0; - - private SNBTDeserializer(String content) { - this.sourceContent = content.toCharArray(); - this.length = sourceContent.length; - } - - // 入口API - public static Object deserializeAsJava(String input) throws IllegalArgumentException { - SNBTDeserializer parser = new SNBTDeserializer(input); - Object result = parser.parseValue(); - parser.skipWhitespace(); - - if (parser.position != parser.length) { - throw new IllegalArgumentException("Extra content at end: " + - new String(parser.sourceContent, parser.position, - parser.length - parser.position)); - } - return result; - } - - // 开始解析, 步进字符. - private Object parseValue() { - skipWhitespace(); - return switch (peekCurrentChar()) { - case COMPOUND_START -> parseCompound(); - case LIST_START -> parseList(); - case STRING_DELIMITER -> parseString(); - default -> parsePrimitive(); - }; - } - - // 解析包小肠 {} - private Map parseCompound() { - position++; // 跳过 '{' - skipWhitespace(); - - Map compoundMap = new LinkedHashMap<>(16); // 避免一次扩容, 应该有一定的性能提升 - - if (position < length && sourceContent[position] != COMPOUND_END) { - do { - String key = parseKey(); - if (position >= length || sourceContent[position] != KEY_VALUE_SEPARATOR) { - throw new IllegalArgumentException("Expected ':' at position " + position); - } - position++; // 跳过 ':' - Object value = parseValue(); - compoundMap.put(key, value); - skipWhitespace(); - } while (position < length && sourceContent[position] == ELEMENT_SEPARATOR && ++position > 0); - } - - if (position >= length || sourceContent[position] != COMPOUND_END) { - throw new IllegalArgumentException("Expected '}' at position " + position); - } - position++; // 跳过 '}' - return compoundMap; - } - - // 解析列表值 [1, 2, 3] - private List parseList() { - position++; // 跳过 '[' - skipWhitespace(); - List elementList = new ArrayList<>(); - - if (position < length && sourceContent[position] != LIST_END) { - do { - elementList.add(parseValue()); - skipWhitespace(); - } while (position < length && sourceContent[position] == ELEMENT_SEPARATOR && ++position > 0); - } - - if (position >= length || sourceContent[position] != LIST_END) { - throw new IllegalArgumentException("Expected ']' at position " + position); - } - position++; // 跳过 ']' - return elementList; - } - - // 解析字符串 - private String parseString() { - position++; // 跳过开始的引号 - int start = position; - - // 扫一次字符串, 如果没有发现转义就直接返回, 发现了就再走转义解析. - // 这样可以避免创建一次 StringBuilder. - while (position < length) { - char c = sourceContent[position]; - if (c == STRING_DELIMITER) { - String result = new String(sourceContent, start, position - start); - position++; // 跳过结束引号 - return result; // 没有转义直接返回字符串. - } - // 如果发现转义字符, - else if (c == ESCAPE_CHAR) { - return parseStringWithEscape(start); - } - position++; - } - // 没有扫描到结束引号 - throw new IllegalArgumentException("Unterminated string at " + start); - } - - // 处理含转义的字符串 - private String parseStringWithEscape(int start) { - StringBuilder sb = new StringBuilder(position - start + 16); - sb.append(sourceContent, start, position - start); - - while (position < length) { - char c = sourceContent[position++]; - if (c == ESCAPE_CHAR && position < length) { - sb.append(getEscapedChar(sourceContent[position++])); - } else if (c == STRING_DELIMITER) { // 字符 - return sb.toString(); - } else { - sb.append(c); - } - } - // 没有扫描到结束引号 - throw new IllegalArgumentException("Unterminated string at " + start); - } - - // 解析Key值 - private String parseKey() { - skipWhitespace(); - // 如果有双引号就委托给string解析处理. - if (position < length && sourceContent[position] == STRING_DELIMITER) { - return parseString(); - } - - int start = position; - while (position < length) { - char c = sourceContent[position]; - if (Character.isJavaIdentifierPart(c)) { - position++; - } else { - break; - } - } - - skipWhitespace(); - return new String(sourceContent, start, position - start); - } - - // 解析原生值 - private Object parsePrimitive() { - skipWhitespace(); - int tokenStart = position; - - // 先解析获取值的长度 - while (position < length) { - char c = sourceContent[position]; - if (c <= ' ' || c == ',' || c == ']' || c == '}') break; - position++; - } - int tokenLength = position - tokenStart; - if (tokenLength == 0) { - throw new IllegalArgumentException("Empty value at position " + tokenStart); - } - - // 布尔值快速检查 - if (tokenLength == TRUE_LENGTH && matchesAt(tokenStart, TRUE_LITERAL)) { - return Boolean.TRUE; - } - if (tokenLength == FALSE_LENGTH && matchesAt(tokenStart, FALSE_LITERAL)) { - return Boolean.FALSE; - } - - // 带后缀的值处理 - try { - char lastChar = sourceContent[tokenStart + tokenLength - 1]; - if (tokenLength > 1 && isTypeSuffix(lastChar)) { - return switch (lastChar) { - case BYTE_SUFFIX -> parseByte(tokenStart, tokenLength - 1); - case SHORT_SUFFIX -> parseShort(tokenStart, tokenLength - 1); - case LONG_SUFFIX -> parseLong(tokenStart, tokenLength - 1); - case FLOAT_SUFFIX -> Float.parseFloat(new String(sourceContent, tokenStart, tokenLength - 1)); - case DOUBLE_SUFFIX -> Double.parseDouble(new String(sourceContent, tokenStart, tokenLength - 1)); - case BOOLEAN_SUFFIX -> parseBoolean(new String(sourceContent, tokenStart, tokenLength - 1)); - default -> throw new IllegalArgumentException("Invalid suffixed value " + new String(sourceContent, tokenStart, tokenLength - 1)); - }; - } - // 没有后缀就默认为 double 喵 - return Double.parseDouble(new String(sourceContent, tokenStart, tokenLength)); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("Invalid number value at position " + tokenStart, e); - } - } - - // 工具函数: 快速检查布尔值字符串匹配, 忽略大小写. - private boolean matchesAt(int start, String target) { - for (int i = 0; i < target.length(); i++) { - char c1 = sourceContent[start + i]; - char c2 = target.charAt(i); - if (c1 != c2 && c1 != (c2 ^ 32)) return false; // 忽略大小写比较 - } - return true; - } - - // 工具函数: 合法后缀检查 - private boolean isTypeSuffix(char c) { - return c == BYTE_SUFFIX || c == SHORT_SUFFIX || c == LONG_SUFFIX || - c == FLOAT_SUFFIX || c == DOUBLE_SUFFIX || c == BOOLEAN_SUFFIX; - } - - - // 手动解析值 - private byte parseByte(int start, int length) { - int value = parseInteger(start, length); - if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) - throw new IllegalArgumentException("Byte value out of range"); - return (byte) value; - } - - private short parseShort(int start, int length) { - int value = parseInteger(start, length); - if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) - throw new NumberFormatException("Short value out of range"); - return (short) value; - } - - private Boolean parseBoolean(String content) { - if ("1".equals(content)) return Boolean.TRUE; - if ("0".equals(content)) return Boolean.FALSE; - throw new NumberFormatException("Invalid boolean value"); - } - - private int parseInteger(int start, int length) { - int result = 0; - boolean negative = false; - int i = 0; - - if (sourceContent[start] == '-') { - negative = true; - i = 1; - } - - for (; i < length; i++) { - char c = sourceContent[start + i]; - if (c < '0' || c > '9') throw new NumberFormatException("Invalid integer"); - result = result * 10 + (c - '0'); - } - - return negative ? -result : result; - } - - private long parseLong(int start, int length) { - long result = 0; - boolean negative = false; - int i = 0; - - if (sourceContent[start] == '-') { - negative = true; - i = 1; - } - - for (; i < length; i++) { - char c = sourceContent[start + i]; - if (c < '0' || c > '9') throw new NumberFormatException("Invalid long"); - result = result * 10 + (c - '0'); - } - - return negative ? -result : result; - } - - // 转义字符处理 - private char getEscapedChar(char escapedChar) { - return switch (escapedChar) { - case STRING_DELIMITER -> '"'; - case ESCAPE_CHAR -> '\\'; - case COMPOUND_START -> '{'; - case COMPOUND_END -> '}'; - case LIST_START -> '['; - case LIST_END -> ']'; - case KEY_VALUE_SEPARATOR -> ':'; - case ELEMENT_SEPARATOR -> ','; - default -> escapedChar; - }; - } - - // 获取当前字符 - private char peekCurrentChar() { - if (position >= length) throw new IllegalArgumentException("Unexpected end of input at position " + position); - return sourceContent[position]; - } - - - // 跳过空格 - private void skipWhitespace() { - while (position < length) { - char c = sourceContent[position]; - if (c > ' ') { break; } // 大于空格的字符都不是空白字符 - if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { - position++; - } else { - break; - } - } - } - -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/SNBTReader.java b/core/src/main/java/net/momirealms/craftengine/core/util/SNBTReader.java new file mode 100644 index 000000000..e90ff7d67 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/SNBTReader.java @@ -0,0 +1,283 @@ +package net.momirealms.craftengine.core.util; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public final class SNBTReader extends DefaultStringReader { + private static final char COMPOUND_START = '{'; + private static final char COMPOUND_END = '}'; + private static final char LIST_START = '['; + private static final char LIST_END = ']'; + private static final char STRING_DELIMITER = '"'; + private static final char SINGLE_QUOTES = '\''; + private static final char DOUBLE_QUOTES = '"'; + private static final char KEY_VALUE_SEPARATOR = ':'; + private static final char ELEMENT_SEPARATOR = ','; + private static final char ESCAPE_CHAR = '\\'; + + // 数字类型后缀 + private static final char BYTE_SUFFIX = 'b'; + private static final char SHORT_SUFFIX = 's'; + private static final char LONG_SUFFIX = 'l'; + private static final char FLOAT_SUFFIX = 'f'; + private static final char DOUBLE_SUFFIX = 'd'; + + private final int length; + private SNBTReader(String content) { + super(content); + this.length = this.string.length(); + } + + // 入口API + public static Object deserializeAsJava(String input) throws IllegalArgumentException { + SNBTReader parser = new SNBTReader(input); + Object result = parser.parseValue(); + parser.skipWhitespace(); + + if (parser.cursor != parser.length) + throw new IllegalArgumentException("Extra content at end: " + parser.string.substring(parser.cursor, parser.length - parser.cursor)); + + return result; + } + + // 开始解析, 步进字符. + private Object parseValue() { + skipWhitespace(); + return switch (peek()) { + case COMPOUND_START -> parseCompound(); + case LIST_START -> parseList(); + case DOUBLE_QUOTES -> parseString(DOUBLE_QUOTES); + case SINGLE_QUOTES -> parseString(SINGLE_QUOTES); + default -> parsePrimitive(); + }; + } + + // 解析包小肠 {} + private Map parseCompound() { + cursor++; // 跳过 '{' + skipWhitespace(); + + Map compoundMap = new LinkedHashMap<>(); + + if (cursor < length && peek() != COMPOUND_END) { + do { + String key = parseKey(); + if (cursor >= length || peek() != KEY_VALUE_SEPARATOR) { + throw new IllegalArgumentException("Expected ':' at position " + cursor); + } + cursor++; // 跳过 ':' + Object value = parseValue(); + compoundMap.put(key, value); + skipWhitespace(); + } while (cursor < length && peek() == ELEMENT_SEPARATOR && ++cursor > 0); + } + + if (cursor >= length || peek() != COMPOUND_END) { + throw new IllegalArgumentException("Expected '}' at position " + cursor); + } + cursor++; // 跳过 '}' + return compoundMap; + } + + // 解析列表值 [1, 2, 3] + private List parseList() { + cursor++; // 跳过 '[' + skipWhitespace(); + List elementList = new ArrayList<>(); + + if (cursor < length && peek() != LIST_END) { + do { + elementList.add(parseValue()); + skipWhitespace(); + } while (cursor < length && peek() == ELEMENT_SEPARATOR && ++cursor > 0); + } + + if (cursor >= length || peek() != LIST_END) { + throw new IllegalArgumentException("Expected ']' at position " + cursor); + } + cursor++; // 跳过 ']' + return elementList; + } + + // 解析字符串 + private String parseString(char delimiter) { + cursor++; // 跳过开始的引号 + int start = cursor; + + // 扫一次字符串, 如果没有发现转义就直接返回, 发现了就再走转义解析. + // 这样可以避免创建一次 StringBuilder. + while (cursor < length) { + char c = peek(); + if (c == delimiter) { + String result = string.substring(start, cursor); + cursor++; // 跳过结束引号 + return result; // 没有转义直接返回字符串. + } + // 如果发现转义字符,进行转义处理. + else if (c == ESCAPE_CHAR) { + return parseStringWithEscape(start, delimiter); + } + cursor++; + } + // 没有扫描到结束引号 + throw new IllegalArgumentException("Unterminated string at " + start); + } + + // 处理含转义的字符串 + private String parseStringWithEscape(int start, char delimiter) { + // 先把之前遍历没有包含转义的字符保存 + StringBuilder sb = new StringBuilder(cursor - start + 16); + sb.append(string, start, cursor); + + while (cursor < length) { + char c = read(); + if (c == ESCAPE_CHAR && cursor < length) { + sb.append(getEscapedChar(read())); // 解析转义. + } else if (c == delimiter) { // 发现结束分隔字符, 返回. + return sb.toString(); + } else { + sb.append(c); + } + } + // 没有扫描到结束引号 + throw new IllegalArgumentException("Unterminated string at " + start); + } + + // 解析Key值 + private String parseKey() { + skipWhitespace(); + // 如果有双引号就委托给string解析处理. + if (cursor < length) { + if (peek() == STRING_DELIMITER) return parseString(STRING_DELIMITER); + if (peek() == SINGLE_QUOTES) return parseString(SINGLE_QUOTES); + } + + int start = cursor; + while (cursor < length) { + char c = peek(); + if (c == ' ') break; // 忽略 key 后面的空格, { a :1} 应当解析成 {a:1} + if (Character.isJavaIdentifierPart(c)) cursor++; else break; + } + + String key = string.substring(start, cursor); + skipWhitespace(); // 跳过 key 后面的空格. + return key; + } + + // 解析原生值 + private Object parsePrimitive() { + // 先解析获取值的长度 + int tokenStart = cursor; + int lastWhitespace = 0; // 记录值末尾的空格数量,{a:炒鸡 大保健} 和 {a: 炒鸡 大保健 } 都应解析成 "炒鸡 大保健". + boolean contentHasWhitespace = false; // 记录值中有没有空格. + while (cursor < length) { + char c = peek(); + if (c == ',' || c == ']' || c == '}') break; + cursor++; + if (c == ' ') { + lastWhitespace++; // 遇到空格先增加值, 代表值尾部空格数量. + continue; + } + if (lastWhitespace > 0) { + lastWhitespace = 0; // 遇到正常字符时清空记录的尾部空格数. + contentHasWhitespace = true; + } + } + int tokenLength = cursor - tokenStart - lastWhitespace; // 计算值长度需要再减去尾部空格. + if (tokenLength == 0) throw new IllegalArgumentException("Empty value at position " + tokenStart); + String fullContent = string.substring(tokenStart, tokenStart + tokenLength); + if (contentHasWhitespace) return fullContent; // 如果值的中间有空格, 一定是字符串, 可直接返回. + + // 布尔值检查 + if ("1B".equals(fullContent) || (tokenLength == 4 && matchesAt(tokenStart, "true"))) return Boolean.TRUE; + if ("0B".equals(fullContent) || (tokenLength == 5 && matchesAt(tokenStart, "false"))) return Boolean.FALSE; + + // 无后缀数字处理 + if (isNumber(tokenStart, tokenStart + tokenLength) == 1) return Integer.parseInt(fullContent); + if (isNumber(tokenStart, tokenStart + tokenLength) == 2) return Double.parseDouble(fullContent); + + // 带后缀的值处理 + char lastChar = string.charAt(tokenStart + tokenLength - 1); + if (tokenLength > 1 && // 要求: 长度>1 + isTypeSuffix(lastChar) && // 要求: 有效后缀 + isNumber(tokenStart, tokenStart + tokenLength - 1) > 0 // 要求: 除后缀外是合法数字 + ) { + final String content = string.substring(tokenStart, tokenStart + tokenLength - 1); + try { + return switch (lastChar) { + case BYTE_SUFFIX -> Byte.parseByte(content); + case SHORT_SUFFIX -> Short.parseShort(content); + case LONG_SUFFIX -> Long.parseLong(content); + case FLOAT_SUFFIX -> Float.parseFloat(content); + case DOUBLE_SUFFIX -> Double.parseDouble(content); + default -> new IllegalArgumentException("Parse Error with: " + content); // 永远不应进入此 case. + }; + } catch (NumberFormatException e) { + return fullContent; // 如果有神人写了 128b 一类的超范围值, 就当做字符串返回好了. + } + } + + // 都无法匹配就默认为 String 喵~ + return fullContent; + } + + // 工具函数: 快速检查布尔值字符串匹配, 忽略大小写. + private boolean matchesAt(int start, String target) { + for (int i = 0; i < target.length(); i++) { + char c1 = string.charAt(start + i); + char c2 = target.charAt(i); + if (c1 != c2 && c1 != (c2 ^ 32)) return false; // 忽略大小写比较 + } + return true; + } + + // 工具函数: 合法后缀检查 + private boolean isTypeSuffix(char c) { + return c == BYTE_SUFFIX || c == SHORT_SUFFIX || c == LONG_SUFFIX || c == FLOAT_SUFFIX || c == DOUBLE_SUFFIX; + } + + // 转义字符处理 + private char getEscapedChar(char escapedChar) { + return switch (escapedChar) { + case DOUBLE_QUOTES -> '"'; + case SINGLE_QUOTES -> '\''; + case ESCAPE_CHAR -> '\\'; + case COMPOUND_START -> '{'; + case COMPOUND_END -> '}'; + case LIST_START -> '['; + case LIST_END -> ']'; + case KEY_VALUE_SEPARATOR -> ':'; + case ELEMENT_SEPARATOR -> ','; + default -> escapedChar; + }; + } + + // 检查是不是合法数字. + // 返回0代表不合法, 1代表整数, 2代表小数. + private int isNumber(int start, int end) { + // 跳过负号 + if (string.charAt(start) == '-') start++; + + // 除负号外第一个字符必须是数字. + char c1 = string.charAt(start); + if (c1 < '0' || c1 > '9') return 0; + + // 检查剩余的数字, 只能有一个小数点 + boolean hasDecimal = false; + for (; start < end; start++) { + char c = string.charAt(start); + if (c < '0' || c > '9') { + if (c == '.') { + if (hasDecimal) return 0; // bro 不能有2个小数点. + hasDecimal = true; + } else { + return 0; + } + } + } + + return hasDecimal ? 2 : 1; + } +}