diff --git a/CHANGELOG.md b/CHANGELOG.md index 60545ba7..ad172178 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - Added CSRG writer - Added TSRG and TSRG2 writer +- Added JAM reader and writer - Added Recaf Simple reader and writer - Added `MappingFormat#hasWriter` boolean diff --git a/src/main/java/net/fabricmc/mappingio/MappingReader.java b/src/main/java/net/fabricmc/mappingio/MappingReader.java index 5642b8f5..17d1c685 100644 --- a/src/main/java/net/fabricmc/mappingio/MappingReader.java +++ b/src/main/java/net/fabricmc/mappingio/MappingReader.java @@ -33,6 +33,7 @@ import net.fabricmc.mappingio.format.enigma.EnigmaFileReader; import net.fabricmc.mappingio.format.proguard.ProGuardFileReader; import net.fabricmc.mappingio.format.simple.RecafSimpleFileReader; +import net.fabricmc.mappingio.format.srg.JamFileReader; import net.fabricmc.mappingio.format.srg.SrgFileReader; import net.fabricmc.mappingio.format.srg.TsrgFileReader; import net.fabricmc.mappingio.format.tiny.Tiny1FileReader; @@ -83,9 +84,14 @@ public static MappingFormat detectFormat(Reader reader) throws IOException { return MappingFormat.ENIGMA_FILE; case "PK:": case "CL:": - case "MD:": case "FD:": + case "MD:": return detectSrgOrXsrg(br); + case "CL ": + case "FD ": + case "MD ": + case "MP ": + return MappingFormat.JAM_FILE; } String headerStr = String.valueOf(buffer, 0, pos); @@ -262,6 +268,9 @@ public static void read(Reader reader, MappingFormat format, MappingVisitor visi case XSRG_FILE: SrgFileReader.read(reader, visitor); break; + case JAM_FILE: + JamFileReader.read(reader, visitor); + break; case CSRG_FILE: case TSRG_FILE: case TSRG_2_FILE: diff --git a/src/main/java/net/fabricmc/mappingio/MappingWriter.java b/src/main/java/net/fabricmc/mappingio/MappingWriter.java index 0e973ee4..b5a5d3f3 100644 --- a/src/main/java/net/fabricmc/mappingio/MappingWriter.java +++ b/src/main/java/net/fabricmc/mappingio/MappingWriter.java @@ -29,6 +29,7 @@ import net.fabricmc.mappingio.format.enigma.EnigmaFileWriter; import net.fabricmc.mappingio.format.proguard.ProGuardFileWriter; import net.fabricmc.mappingio.format.simple.RecafSimpleFileWriter; +import net.fabricmc.mappingio.format.srg.JamFileWriter; import net.fabricmc.mappingio.format.srg.CsrgFileWriter; import net.fabricmc.mappingio.format.srg.SrgFileWriter; import net.fabricmc.mappingio.format.srg.TsrgFileWriter; @@ -58,6 +59,7 @@ static MappingWriter create(Writer writer, MappingFormat format) throws IOExcept case ENIGMA_FILE: return new EnigmaFileWriter(writer); case SRG_FILE: return new SrgFileWriter(writer, false); case XSRG_FILE: return new SrgFileWriter(writer, true); + case JAM_FILE: return new JamFileWriter(writer); case CSRG_FILE: return new CsrgFileWriter(writer); case TSRG_FILE: return new TsrgFileWriter(writer, false); case TSRG_2_FILE: return new TsrgFileWriter(writer, true); diff --git a/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java b/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java index 73ab0a50..f7e8a5a6 100644 --- a/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java +++ b/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java @@ -46,8 +46,8 @@ * ✔ * src * ✔ - * ✔ - * ✔ + * lvIdx & srcName + * lvIdx, lvtIdx, startOpIdx & srcName * ✔ * * @@ -55,7 +55,7 @@ * - * src * ✔ - * ✔ + * lvIdx * - * - * @@ -78,6 +78,15 @@ * - * * + * JAM + * - + * src + * - + * argPos + * - + * - + * + * * CSRG/TSRG * - * - @@ -91,7 +100,7 @@ * ✔ * src * - - * ✔ + * lvIdx & srcName * - * - * @@ -148,6 +157,11 @@ public enum MappingFormat { */ XSRG_FILE("XSRG file", "xsrg", false, true, false, false, false, true), + /** + * The {@code JAM} ("Java Associated Mapping"; formerly {@code SRGX}) mapping format, as specified here. + */ + JAM_FILE("JAM file", "jam", false, true, false, true, false, true), + /** * The {@code CSRG} ("Compact SRG", since it saves disk space over SRG) mapping format, as specified here. */ diff --git a/src/main/java/net/fabricmc/mappingio/format/srg/JamFileReader.java b/src/main/java/net/fabricmc/mappingio/format/srg/JamFileReader.java new file mode 100644 index 00000000..2f9b57a4 --- /dev/null +++ b/src/main/java/net/fabricmc/mappingio/format/srg/JamFileReader.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2023 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.srg; + +import java.io.IOException; +import java.io.Reader; +import java.util.Collections; +import java.util.Set; + +import net.fabricmc.mappingio.MappedElementKind; +import net.fabricmc.mappingio.MappingFlag; +import net.fabricmc.mappingio.MappingUtil; +import net.fabricmc.mappingio.MappingVisitor; +import net.fabricmc.mappingio.format.ColumnFileReader; +import net.fabricmc.mappingio.format.MappingFormat; +import net.fabricmc.mappingio.tree.MappingTree; +import net.fabricmc.mappingio.tree.MemoryMappingTree; + +/** + * {@linkplain MappingFormat#JAM_FILE JAM file} reader. + * + *

Crashes if a second visit pass is requested without + * {@link MappingFlag#NEEDS_MULTIPLE_PASSES} having been passed beforehand. + */ +public final class JamFileReader { + private JamFileReader() { + } + + public static void read(Reader reader, MappingVisitor visitor) throws IOException { + read(reader, MappingUtil.NS_SOURCE_FALLBACK, MappingUtil.NS_TARGET_FALLBACK, visitor); + } + + public static void read(Reader reader, String sourceNs, String targetNs, MappingVisitor visitor) throws IOException { + read(new ColumnFileReader(reader, ' '), sourceNs, targetNs, visitor); + } + + private static void read(ColumnFileReader reader, String sourceNs, String targetNs, MappingVisitor visitor) throws IOException { + Set flags = visitor.getFlags(); + MappingVisitor parentVisitor = null; + + if (flags.contains(MappingFlag.NEEDS_ELEMENT_UNIQUENESS)) { + parentVisitor = visitor; + visitor = new MemoryMappingTree(); + } else if (flags.contains(MappingFlag.NEEDS_MULTIPLE_PASSES)) { + reader.mark(); + } + + for (;;) { + if (visitor.visitHeader()) { + visitor.visitNamespaces(sourceNs, Collections.singletonList(targetNs)); + } + + if (visitor.visitContent()) { + String lastClass = null; + boolean visitLastClass = false; + + do { + boolean isMethod; + boolean isArg = false; + + if (reader.nextCol("CL")) { // class: CL + String srcName = reader.nextCol(); + if (srcName == null || srcName.isEmpty()) throw new IOException("missing class-name-a in line "+reader.getLineNumber()); + + if (!srcName.equals(lastClass)) { + lastClass = srcName; + visitLastClass = visitor.visitClass(srcName); + + if (visitLastClass) { + String dstName = reader.nextCol(); + if (dstName == null || dstName.isEmpty()) throw new IOException("missing class-name-b in line "+reader.getLineNumber()); + + visitor.visitDstName(MappedElementKind.CLASS, 0, dstName); + visitLastClass = visitor.visitElementContent(MappedElementKind.CLASS); + } + } + } else if ((isMethod = reader.nextCol("MD")) || reader.nextCol("FD") // method/field: MD/FD + || (isArg = reader.nextCol("MP"))) { // parameter: MP [] + String clsSrcClsName = reader.nextCol(); + if (clsSrcClsName == null) throw new IOException("missing class-name-a in line "+reader.getLineNumber()); + + String memberSrcName = reader.nextCol(); + if (memberSrcName == null || memberSrcName.isEmpty()) throw new IOException("missing member-name-a in line "+reader.getLineNumber()); + + String memberSrcDesc = reader.nextCol(); + if (memberSrcDesc == null || memberSrcDesc.isEmpty()) throw new IOException("missing member-desc-a in line "+reader.getLineNumber()); + + String col5 = reader.nextCol(); + String col6 = reader.nextCol(); + String col7 = reader.nextCol(); + + int argSrcPos = -1; + String dstName; + String argSrcDesc; + + if (!isArg) { + dstName = col5; + } else { + argSrcPos = Integer.parseInt(col5); + + if (col7 == null || col7.isEmpty()) { + dstName = col6; + } else { + argSrcDesc = col6; + if (argSrcDesc == null || argSrcDesc.isEmpty()) throw new IOException("missing parameter-desc-a in line "+reader.getLineNumber()); + + dstName = col7; + } + } + + if (dstName == null || dstName.isEmpty()) throw new IOException("missing name-b in line "+reader.getLineNumber()); + + if (!clsSrcClsName.equals(lastClass)) { + lastClass = clsSrcClsName; + visitLastClass = visitor.visitClass(clsSrcClsName); + + if (visitLastClass) { + visitLastClass = visitor.visitElementContent(MappedElementKind.CLASS); + } + } + + if (!visitLastClass) continue; + boolean visitMethod = false; + + if (isMethod || isArg) { + visitMethod = visitor.visitMethod(memberSrcName, memberSrcDesc); + } + + if (visitMethod) { + if (isMethod) { + visitor.visitDstName(MappedElementKind.METHOD, 0, dstName); + visitor.visitElementContent(MappedElementKind.METHOD); + } else { + visitor.visitMethodArg(argSrcPos, -1, null); + visitor.visitDstName(MappedElementKind.METHOD_ARG, 0, dstName); + visitor.visitElementContent(MappedElementKind.METHOD_ARG); + } + } else if (!isMethod && !isArg && visitor.visitField(memberSrcName, memberSrcDesc)) { + visitor.visitDstName(MappedElementKind.FIELD, 0, dstName); + visitor.visitElementContent(MappedElementKind.FIELD); + } + } + } while (reader.nextLine(0)); + } + + if (visitor.visitEnd()) break; + + reader.reset(); + } + + if (parentVisitor != null) { + ((MappingTree) visitor).accept(parentVisitor); + } + } +} diff --git a/src/main/java/net/fabricmc/mappingio/format/srg/JamFileWriter.java b/src/main/java/net/fabricmc/mappingio/format/srg/JamFileWriter.java new file mode 100644 index 00000000..d9c993d9 --- /dev/null +++ b/src/main/java/net/fabricmc/mappingio/format/srg/JamFileWriter.java @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2023 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.srg; + +import java.io.IOException; +import java.io.Writer; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; + +import org.jetbrains.annotations.Nullable; + +import net.fabricmc.mappingio.MappedElementKind; +import net.fabricmc.mappingio.MappingFlag; +import net.fabricmc.mappingio.MappingWriter; + +/** + * {@linkplain net.fabricmc.mappingio.format.MappingFormat#JAM_FILE JAM file} writer. + */ +public final class JamFileWriter implements MappingWriter { + public JamFileWriter(Writer writer) { + this.writer = writer; + } + + @Override + public void close() throws IOException { + writer.close(); + } + + @Override + public Set getFlags() { + return flags; + } + + @Override + public void visitNamespaces(String srcNamespace, List dstNamespaces) throws IOException { + } + + @Override + public boolean visitClass(String srcName) throws IOException { + classSrcName = srcName; + classDstName = null; + + return true; + } + + @Override + public boolean visitField(String srcName, @Nullable String srcDesc) throws IOException { + memberSrcName = srcName; + memberSrcDesc = srcDesc; + memberDstName = null; + + return true; + } + + @Override + public boolean visitMethod(String srcName, @Nullable String srcDesc) throws IOException { + memberSrcName = srcName; + memberSrcDesc = srcDesc; + memberDstName = null; + + return true; + } + + @Override + public boolean visitMethodArg(int argPosition, int lvIndex, @Nullable String srcName) throws IOException { + argSrcPosition = argPosition; + argDstName = null; + + return true; + } + + @Override + public boolean visitMethodVar(int lvtRowIndex, int lvIndex, int startOpIdx, int endOpIdx, @Nullable String srcName) throws IOException { + return false; // not supported, skip + } + + @Override + public void visitDstName(MappedElementKind targetKind, int namespace, String name) { + if (namespace != 0) return; + + switch (targetKind) { + case CLASS: + classDstName = name; + break; + case FIELD: + case METHOD: + memberDstName = name; + break; + case METHOD_ARG: + argDstName = name; + break; + default: + throw new IllegalStateException("unexpected invocation for "+targetKind); + } + } + + @Override + public boolean visitElementContent(MappedElementKind targetKind) throws IOException { + boolean isClass = targetKind == MappedElementKind.CLASS; + boolean isMethod = false; + boolean isArg = false; + + if (isClass) { + if (!classOnlyPass || classDstName == null) { + return true; + } + + write("CL "); + } else if (targetKind == MappedElementKind.FIELD + || (isMethod = targetKind == MappedElementKind.METHOD) + || (isArg = targetKind == MappedElementKind.METHOD_ARG)) { + if (classOnlyPass || memberSrcDesc == null || memberDstName == null) { + return isMethod; + } + + if (isMethod) { + write("MD "); + } else if (!isArg) { + write("FD "); + } else { + if (argSrcPosition == -1 || argDstName == null) return false; + write("MP "); + } + } else { + throw new IllegalStateException("unexpected invocation for "+targetKind); + } + + write(classSrcName); + writeSpace(); + + if (isClass) { + write(classDstName); + } else { + write(memberSrcName); + writeSpace(); + + write(memberSrcDesc); + writeSpace(); + + if (!isArg) { + write(memberDstName); + } else { + write(Integer.toString(argSrcPosition)); + writeSpace(); + write(argDstName); + } + } + + writeLn(); + + return isClass || isMethod; + } + + @Override + public void visitComment(MappedElementKind targetKind, String comment) throws IOException { + // not supported, skip + } + + @Override + public boolean visitEnd() throws IOException { + if (classOnlyPass) { + classOnlyPass = false; + return false; + } + + classOnlyPass = true; + return MappingWriter.super.visitEnd(); + } + + private void write(String str) throws IOException { + writer.write(str); + } + + private void writeSpace() throws IOException { + writer.write(' '); + } + + private void writeLn() throws IOException { + writer.write('\n'); + } + + private static final Set flags = EnumSet.of( + MappingFlag.NEEDS_SRC_FIELD_DESC, + MappingFlag.NEEDS_SRC_METHOD_DESC, + MappingFlag.NEEDS_MULTIPLE_PASSES); + + private final Writer writer; + private boolean classOnlyPass = true; + private String classSrcName; + private String memberSrcName; + private String memberSrcDesc; + private int argSrcPosition = -1; + private String classDstName; + private String memberDstName; + private String argDstName; +} diff --git a/src/main/java/net/fabricmc/mappingio/format/srg/SrgFileWriter.java b/src/main/java/net/fabricmc/mappingio/format/srg/SrgFileWriter.java index f6379a7d..b74db414 100644 --- a/src/main/java/net/fabricmc/mappingio/format/srg/SrgFileWriter.java +++ b/src/main/java/net/fabricmc/mappingio/format/srg/SrgFileWriter.java @@ -103,7 +103,7 @@ public void visitDstName(MappedElementKind targetKind, int namespace, String nam memberDstName = name; break; default: - break; + throw new IllegalStateException("unexpected invocation for "+targetKind); } } @@ -122,11 +122,11 @@ public boolean visitElementContent(MappedElementKind targetKind) throws IOExcept write("CL: "); break; case FIELD: - if (memberDstName == null) return false; + if (memberSrcDesc == null || memberDstName == null || (xsrg && memberDstDesc == null)) return false; write("FD: "); break; case METHOD: - if (memberDstName == null || memberDstDesc == null) return false; + if (memberSrcDesc == null || memberDstName == null || memberDstDesc == null) return false; write("MD: "); break; default: diff --git a/src/test/java/net/fabricmc/mappingio/TestHelper.java b/src/test/java/net/fabricmc/mappingio/TestHelper.java index 2d5cc115..1157d705 100644 --- a/src/test/java/net/fabricmc/mappingio/TestHelper.java +++ b/src/test/java/net/fabricmc/mappingio/TestHelper.java @@ -53,6 +53,8 @@ public static String getFileName(MappingFormat format) { return "srg.srg"; case XSRG_FILE: return "xsrg.xsrg"; + case JAM_FILE: + return "jam.jam"; case CSRG_FILE: return "csrg.csrg"; case TSRG_FILE: diff --git a/src/test/java/net/fabricmc/mappingio/read/DetectionTest.java b/src/test/java/net/fabricmc/mappingio/read/DetectionTest.java index 88ded7a7..ad727170 100644 --- a/src/test/java/net/fabricmc/mappingio/read/DetectionTest.java +++ b/src/test/java/net/fabricmc/mappingio/read/DetectionTest.java @@ -72,6 +72,12 @@ public void xrgFile() throws Exception { check(format); } + @Test + public void jamFile() throws Exception { + MappingFormat format = MappingFormat.JAM_FILE; + check(format); + } + @Test public void csrgFile() throws Exception { MappingFormat format = MappingFormat.CSRG_FILE; diff --git a/src/test/java/net/fabricmc/mappingio/read/EmptyContentReadTest.java b/src/test/java/net/fabricmc/mappingio/read/EmptyContentReadTest.java index ba8807d1..6aea7581 100644 --- a/src/test/java/net/fabricmc/mappingio/read/EmptyContentReadTest.java +++ b/src/test/java/net/fabricmc/mappingio/read/EmptyContentReadTest.java @@ -26,6 +26,7 @@ import net.fabricmc.mappingio.format.enigma.EnigmaFileReader; import net.fabricmc.mappingio.format.proguard.ProGuardFileReader; import net.fabricmc.mappingio.format.simple.RecafSimpleFileReader; +import net.fabricmc.mappingio.format.srg.JamFileReader; import net.fabricmc.mappingio.format.srg.SrgFileReader; import net.fabricmc.mappingio.format.srg.TsrgFileReader; import net.fabricmc.mappingio.format.tiny.Tiny1FileReader; @@ -61,6 +62,11 @@ public void emptySrgFile() throws Exception { SrgFileReader.read(new StringReader(""), tree); } + @Test + public void emptyJamFile() throws Exception { + JamFileReader.read(new StringReader(""), tree); + } + @Test public void emptyTsrgFile() throws Exception { TsrgFileReader.read(new StringReader(""), tree); diff --git a/src/test/java/net/fabricmc/mappingio/read/ValidContentReadTest.java b/src/test/java/net/fabricmc/mappingio/read/ValidContentReadTest.java index 329279f0..4aa5670d 100644 --- a/src/test/java/net/fabricmc/mappingio/read/ValidContentReadTest.java +++ b/src/test/java/net/fabricmc/mappingio/read/ValidContentReadTest.java @@ -81,6 +81,13 @@ public void xsrgFile() throws Exception { checkHoles(format); } + @Test + public void jamFile() throws Exception { + MappingFormat format = MappingFormat.JAM_FILE; + checkDefault(format); + checkHoles(format); + } + @Test public void csrgFile() throws Exception { MappingFormat format = MappingFormat.CSRG_FILE; diff --git a/src/test/java/net/fabricmc/mappingio/visiting/VisitEndTest.java b/src/test/java/net/fabricmc/mappingio/visiting/VisitEndTest.java index e2db8117..58a8ab96 100644 --- a/src/test/java/net/fabricmc/mappingio/visiting/VisitEndTest.java +++ b/src/test/java/net/fabricmc/mappingio/visiting/VisitEndTest.java @@ -76,6 +76,12 @@ public void xrgFile() throws Exception { check(format); } + @Test + public void jamFile() throws Exception { + MappingFormat format = MappingFormat.JAM_FILE; + check(format); + } + @Test public void csrgFile() throws Exception { MappingFormat format = MappingFormat.CSRG_FILE; diff --git a/src/test/java/net/fabricmc/mappingio/write/WriteTest.java b/src/test/java/net/fabricmc/mappingio/write/WriteTest.java index 425101c6..02ab4994 100644 --- a/src/test/java/net/fabricmc/mappingio/write/WriteTest.java +++ b/src/test/java/net/fabricmc/mappingio/write/WriteTest.java @@ -73,6 +73,11 @@ public void xsrgFile() throws Exception { check(MappingFormat.XSRG_FILE); } + @Test + public void jamFile() throws Exception { + check(MappingFormat.JAM_FILE); + } + @Test public void csrgFile() throws Exception { check(MappingFormat.CSRG_FILE); diff --git a/src/test/resources/detection/jam.jam b/src/test/resources/detection/jam.jam new file mode 100644 index 00000000..c1c9f5e3 --- /dev/null +++ b/src/test/resources/detection/jam.jam @@ -0,0 +1 @@ +CL class_1 RenamedClass diff --git a/src/test/resources/read/valid-with-holes/jam.jam b/src/test/resources/read/valid-with-holes/jam.jam new file mode 100644 index 00000000..bd2f77fd --- /dev/null +++ b/src/test/resources/read/valid-with-holes/jam.jam @@ -0,0 +1,10 @@ +CL class_2 class2Ns0Rename +CL class_5 class5Ns0Rename +CL class_7$class_8 class_7$class8Ns0Rename +CL class_13$class_14 class_13$class14Ns0Rename +CL class_17$class_18$class_19 class_17$class_18$class19Ns0Rename +CL class_26$class_27$class_28 class_26$class_27$class28Ns0Rename +FD class_32 field_2 I field2Ns0Rename +FD class_32 field_5 I field5Ns0Rename +MD class_32 method_2 ()I method2Ns0Rename +MD class_32 method_5 ()I method5Ns0Rename diff --git a/src/test/resources/read/valid/jam.jam b/src/test/resources/read/valid/jam.jam new file mode 100644 index 00000000..64972fd5 --- /dev/null +++ b/src/test/resources/read/valid/jam.jam @@ -0,0 +1,7 @@ +CL class_1 class1Ns0Rename +CL class_1$class_2 class1Ns0Rename$class2Ns0Rename +CL class_3 class3Ns0Rename +FD class_1 field_1 I field1Ns0Rename +MD class_1 method_1 ()I method1Ns0Rename +MP class_1 method_1 ()I 0 param1Ns0Rename +FD class_1$class_2 field_2 I field2Ns0Rename