From 413998cdf9548bdc940681c4c9dc01f06174c695 Mon Sep 17 00:00:00 2001 From: jackassmc <84429870+jackassmc@users.noreply.github.com> Date: Sat, 7 May 2022 16:37:27 +0200 Subject: [PATCH] Add JsonWriter --- .../net/fabricmc/mappingio/MappingWriter.java | 2 + .../fabricmc/mappingio/format/JsonUtil.java | 81 +++ .../fabricmc/mappingio/format/JsonWriter.java | 464 ++++++++++++++++++ .../mappingio/format/MappingFormat.java | 3 +- 4 files changed, 549 insertions(+), 1 deletion(-) create mode 100644 src/main/java/net/fabricmc/mappingio/format/JsonUtil.java create mode 100644 src/main/java/net/fabricmc/mappingio/format/JsonWriter.java diff --git a/src/main/java/net/fabricmc/mappingio/MappingWriter.java b/src/main/java/net/fabricmc/mappingio/MappingWriter.java index 4d7150f8..89c79501 100644 --- a/src/main/java/net/fabricmc/mappingio/MappingWriter.java +++ b/src/main/java/net/fabricmc/mappingio/MappingWriter.java @@ -23,6 +23,7 @@ import java.nio.file.Path; import net.fabricmc.mappingio.format.EnigmaWriter; +import net.fabricmc.mappingio.format.JsonWriter; import net.fabricmc.mappingio.format.MappingFormat; import net.fabricmc.mappingio.format.Tiny1Writer; import net.fabricmc.mappingio.format.Tiny2Writer; @@ -45,6 +46,7 @@ static MappingWriter create(Writer writer, MappingFormat format) throws IOExcept switch (format) { case TINY: return new Tiny1Writer(writer); case TINY_2: return new Tiny2Writer(writer, false); + case JSON: return new JsonWriter(writer); default: throw new UnsupportedOperationException("format "+format+" is not implemented"); } } diff --git a/src/main/java/net/fabricmc/mappingio/format/JsonUtil.java b/src/main/java/net/fabricmc/mappingio/format/JsonUtil.java new file mode 100644 index 00000000..80f29334 --- /dev/null +++ b/src/main/java/net/fabricmc/mappingio/format/JsonUtil.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2021 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.mappingio.format; + +import java.io.IOException; +import java.io.Writer; + +final class JsonUtil { + public static boolean needEscape(String s) { + for (int pos = 0, len = s.length(); pos < len; pos++) { + char c = s.charAt(pos); + if (toEscape.indexOf(c) >= 0) return true; + } + + return false; + } + + public static void writeEscaped(String s, Writer out) throws IOException { + final int len = s.length(); + int start = 0; + + for (int pos = 0; pos < len; pos++) { + char c = s.charAt(pos); + int idx = toEscape.indexOf(c); + + if (idx >= 0) { + out.write(s, start, pos - start); + out.write('\\'); + out.write(escaped.charAt(idx)); + start = pos + 1; + } + } + + out.write(s, start, len - start); + } + + public static String unescape(String str) { + int pos = str.indexOf('\\'); + if (pos < 0) return str; + + StringBuilder ret = new StringBuilder(str.length() - 1); + int start = 0; + + do { + ret.append(str, start, pos); + pos++; + int type; + + if (pos >= str.length()) { + throw new RuntimeException("incomplete escape sequence at the end"); + } else if ((type = escaped.indexOf(str.charAt(pos))) < 0) { + throw new RuntimeException("invalid escape character: \\"+str.charAt(pos)); + } else { + ret.append(toEscape.charAt(type)); + } + + start = pos + 1; + } while ((pos = str.indexOf('\\', start)) >= 0); + + ret.append(str, start, str.length()); + + return ret.toString(); + } + + private static final String toEscape = "\"\\\b\f\n\r\t"; + private static final String escaped = "\"\\bfnrt"; +} diff --git a/src/main/java/net/fabricmc/mappingio/format/JsonWriter.java b/src/main/java/net/fabricmc/mappingio/format/JsonWriter.java new file mode 100644 index 00000000..5dfd360e --- /dev/null +++ b/src/main/java/net/fabricmc/mappingio/format/JsonWriter.java @@ -0,0 +1,464 @@ +/* + * Copyright (c) 2021 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.mappingio.format; + +import java.io.IOException; +import java.io.Writer; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; + +import net.fabricmc.mappingio.MappedElementKind; +import net.fabricmc.mappingio.MappingFlag; +import net.fabricmc.mappingio.MappingWriter; + +public final class JsonWriter implements MappingWriter { + public JsonWriter(Writer writer) { + this.writer = writer; + this.firstMetadata = true; + this.firstClass = true; + } + + @Override + public Set getFlags() { + return flags; + } + + @Override + public boolean visitHeader() throws IOException { + // open root + write("{"); + + writeLn(); + writeTab(); + writeKey("version"); + write("1,"); + + return true; + } + + @Override + public void visitNamespaces(String srcNamespace, List dstNamespaces) throws IOException { + names = new String[dstNamespaces.size() + 1]; + + writeLn(); + writeTab(); + writeKey("namespaces"); + write("["); + + writeJsonString(srcNamespace); + + for (String dstNamespace : dstNamespaces) { + write(", "); + writeJsonString(dstNamespace); + } + + write("],"); + } + + @Override + public void visitMetadata(String key, String value) throws IOException { + if (firstMetadata) { + // open metadata + writeLn(); + writeTab(); + writeKey("meta"); + write("{"); + + firstMetadata = false; + } else { + write(","); + } + + writeLn(); + writeTabs(2); + writeKey(key); + writeJsonString(value); + } + + @Override + public boolean visitClass(String srcName) throws IOException { + names[0] = srcName; + + if (firstClass) { + if (!firstMetadata) { + // close metadata + writeLn(); + writeTab(); + write("},"); + } + + // open classes array + writeLn(); + writeTab(); + writeKey("classes"); + write("["); + + firstClass = false; + } else { + if (!firstField || !firstMethod) { + if (!firstMethod && (!firstMethodArg || !firstMethodVar)) { + // close last method arg/var entry + writeLn(); + writeTabs(6); + write("}"); + + // close args/vars array + writeLn(); + writeTabs(5); + write("]"); + } + + // close last field/method entry + writeLn(); + writeTabs(4); + write("}"); + + // close fields/methods array + writeLn(); + writeTabs(3); + write("]"); + } + + // close class entry + writeLn(); + writeTabs(2); + write("},"); + } + + // open class entry + writeLn(); + writeTabs(2); + write("{"); + + firstField = true; + firstMethod = true; + + return true; + } + + @Override + public boolean visitField(String srcName, String srcDesc) throws IOException { + names[0] = srcName; + + if (firstField) { + write(","); + + // open fields array + writeLn(); + writeTabs(3); + writeKey("fields"); + write("["); + + firstField = false; + } else { + // close field entry + writeLn(); + writeTabs(4); + write("},"); + } + + // open field entry + writeLn(); + writeTabs(4); + write("{"); + + writeLn(); + writeTabs(5); + writeKey("desc"); + writeJsonString(srcDesc); + write(","); + + return true; + } + + @Override + public boolean visitMethod(String srcName, String srcDesc) throws IOException { + names[0] = srcName; + + if (firstMethod) { + if (!firstField) { + // close last field entry + writeLn(); + writeTabs(4); + write("}"); + + // close fields array + writeLn(); + writeTabs(3); + write("],"); + } else { + write(","); + } + + // open methods array + writeLn(); + writeTabs(3); + writeKey("methods"); + write("["); + + firstMethod = false; + } else { + if (!firstMethodArg || !firstMethodVar) { + // close last method arg/var entry + writeLn(); + writeTabs(6); + write("}"); + + // close args/vars array + writeLn(); + writeTabs(5); + write("]"); + } + + // close method entry + writeLn(); + writeTabs(4); + write("},"); + } + + // open method entry + writeLn(); + writeTabs(4); + write("{"); + + writeLn(); + writeTabs(5); + writeKey("desc"); + writeJsonString(srcDesc); + write(","); + + firstMethodArg = true; + firstMethodVar = true; + + return true; + } + + @Override + public boolean visitMethodArg(int argPosition, int lvIndex, String srcName) throws IOException { + names[0] = srcName; + + if (firstMethodArg) { + // close method args array + write(","); + writeLn(); + writeTabs(5); + writeKey("parameters"); + write("["); + + firstMethodArg = false; + } else { + // close method arg entry + writeLn(); + writeTabs(6); + write("},"); + } + + // open method arg entry + writeLn(); + writeTabs(6); + write("{"); + + writeLn(); + writeTabs(7); + writeKey("lvIndex"); + write(lvIndex); + write(","); + + return true; + } + + @Override + public boolean visitMethodVar(int lvtRowIndex, int lvIndex, int startOpIdx, String srcName) throws IOException { + names[0] = srcName; + + if (firstMethodVar) { + // close method vars array + write(","); + writeLn(); + writeTabs(5); + writeKey("variables"); + write("["); + + firstMethodVar = false; + } else { + // close method var entry + writeLn(); + writeTabs(6); + write("},"); + } + + // open method var entry + writeLn(); + writeTabs(6); + write("{"); + + writeLn(); + writeTabs(7); + writeKey("lvIndex"); + write(lvIndex); + write(","); + + writeLn(); + writeTabs(7); + writeKey("lvStartOffset"); + write(startOpIdx); + write(","); + + writeLn(); + writeTabs(7); + writeKey("lvtIndex"); + write(lvtRowIndex); + + return true; + } + + @Override + public void visitDstName(MappedElementKind targetKind, int namespace, String name) { + names[namespace + 1] = name; + } + + @Override + public boolean visitElementContent(MappedElementKind targetKind) throws IOException { + writeLn(); + writeTabs(targetKind.level * 2 + 3); + writeKey("name"); + + write("["); + + boolean firstName = true; + + for (String name : names) { + if (!firstName) { + write(", "); + } + + firstName = false; + + if (name == null) { + write("null"); + } else { + writeJsonString(name); + } + } + + write("]"); + + Arrays.fill(names, null); + + return true; + } + + @Override + public void visitComment(MappedElementKind targetKind, String comment) throws IOException { + write(","); + writeLn(); + writeTabs(targetKind.level * 2 + 3); + writeKey("comment"); + writeJsonString(comment); + } + + @Override + public void close() throws IOException { + if (!firstMethod && (!firstMethodArg || !firstMethodVar)) { + // close last method arg/var entry + writeLn(); + writeTabs(6); + write("}"); + + // close args/vars array + writeLn(); + writeTabs(5); + write("]"); + } + + if (!firstField || !firstMethod) { + // close last field/method entry + writeLn(); + writeTabs(4); + write("}"); + + // close field/method array + writeLn(); + writeTabs(3); + write("]"); + } + + if (!firstClass) { + // close last class entry + writeLn(); + writeTabs(2); + write("}"); + + // close classes array + writeLn(); + writeTab(); + write("]"); + } + + // close root + writeLn(); + write("}"); + + writer.close(); + } + + private void write(String str) throws IOException { + writer.write(str); + } + + private void write(int i) throws IOException { + write(Integer.toString(i)); + } + + private void writeJsonString(String str) throws IOException { + writer.write("\""); + JsonUtil.writeEscaped(str, writer); + writer.write("\""); + } + + private void writeKey(String key) throws IOException { + writeJsonString(key); + write(": "); + } + + private void writeLn() throws IOException { + writer.write('\n'); + } + + private void writeTab() throws IOException { + writer.write('\t'); + } + + private void writeTabs(int count) throws IOException { + for (int i = 0; i < count; i++) { + writer.write('\t'); + } + } + + private static final Set flags = EnumSet.of(MappingFlag.NEEDS_HEADER_METADATA, MappingFlag.NEEDS_UNIQUENESS, MappingFlag.NEEDS_SRC_FIELD_DESC, MappingFlag.NEEDS_SRC_METHOD_DESC); + + private final Writer writer; + private String[] names; + private boolean firstMetadata; + private boolean firstClass; + private boolean firstField; + private boolean firstMethod; + private boolean firstMethodArg; + private boolean firstMethodVar; +} diff --git a/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java b/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java index ce8ae97f..940f3932 100644 --- a/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java +++ b/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java @@ -24,7 +24,8 @@ public enum MappingFormat { SRG("SRG", "srg", false, false, false, false, false), TSRG("TSRG", "tsrg", false, false, false, false, false), TSRG2("TSRG2", "tsrg", true, false, false, true, false), - PROGUARD("ProGuard", "map", false, true, false, false, false); + PROGUARD("ProGuard", "map", false, true, false, false, false), + JSON("JSON", "json", true, true, true, true, true); MappingFormat(String name, String fileExt, boolean hasNamespaces, boolean hasFieldDescriptors,