diff --git a/src/main/java/net/fabricmc/mappingio/MappingReader.java b/src/main/java/net/fabricmc/mappingio/MappingReader.java index ee2bef85..26afbf1e 100644 --- a/src/main/java/net/fabricmc/mappingio/MappingReader.java +++ b/src/main/java/net/fabricmc/mappingio/MappingReader.java @@ -30,6 +30,7 @@ import net.fabricmc.mappingio.format.enigma.EnigmaDirReader; 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.SrgFileReader; import net.fabricmc.mappingio.format.srg.TsrgFileReader; import net.fabricmc.mappingio.format.tiny.Tiny1FileReader; @@ -198,6 +199,9 @@ public static void read(Reader reader, MappingFormat format, MappingVisitor visi case PROGUARD_FILE: ProGuardFileReader.read(reader, visitor); break; + case RECAF_SIMPLE_FILE: + RecafSimpleFileReader.read(reader, visitor); + break; default: throw new IllegalStateException(); } diff --git a/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java b/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java index 76285670..db4cad1c 100644 --- a/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java +++ b/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java @@ -133,7 +133,12 @@ public enum MappingFormat { /** * ProGuard's mapping format, as specified here. */ - PROGUARD_FILE("ProGuard file", "txt", false, true, false, false, false); + PROGUARD_FILE("ProGuard file", "txt", false, true, false, false, false), + + /** + * Recaf's {@code Simple} mapping format, as specified here. + */ + RECAF_SIMPLE_FILE("Recaf Simple file", "txt", false, true, false, false, false); MappingFormat(String name, String fileExt, boolean hasNamespaces, boolean hasFieldDescriptors, diff --git a/src/main/java/net/fabricmc/mappingio/format/simple/RecafSimpleFileReader.java b/src/main/java/net/fabricmc/mappingio/format/simple/RecafSimpleFileReader.java new file mode 100644 index 00000000..2c1cfc9e --- /dev/null +++ b/src/main/java/net/fabricmc/mappingio/format/simple/RecafSimpleFileReader.java @@ -0,0 +1,135 @@ +/* + * 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.simple; + +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.tree.MappingTree; +import net.fabricmc.mappingio.tree.MemoryMappingTree; + +public final class RecafSimpleFileReader { + private RecafSimpleFileReader() { + } + + 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 line; + String lastClass = null; + boolean visitClass = false; + + do { + line = reader.nextCols(true); + + // Skip comments and empty lines + if (line == null || line.trim().isEmpty() || line.trim().startsWith("#")) continue; + + String[] parts = line.split(" "); + int dotPos = parts[0].lastIndexOf('.'); + String clsSrcName; + String clsDstName = null; + String memberSrcName = null; + String memberSrcDesc = null; + String memberDstName = null; + boolean isMethod = false; + + if (dotPos < 0) { // class + clsSrcName = parts[0]; + clsDstName = parts[1]; + } else { // member + clsSrcName = parts[0].substring(0, dotPos); + String memberIdentifier = parts[0].substring(dotPos + 1); + memberDstName = parts[1]; + + if (parts.length >= 3) { // field with descriptor + memberSrcName = memberIdentifier; + memberSrcDesc = parts[1]; + memberDstName = parts[2]; + } else if (parts.length == 2) { // field without descriptor or method + int mthDescPos = memberIdentifier.lastIndexOf("("); + + if (mthDescPos < 0) { // field + memberSrcName = memberIdentifier; + } else { // method + isMethod = true; + memberSrcName = memberIdentifier.substring(0, mthDescPos); + memberSrcDesc = memberIdentifier.substring(mthDescPos); + } + } else { + throw new IOException("Invalid Recaf Simple line "+reader.getLineNumber()+": Insufficient column count!"); + } + } + + if (!clsSrcName.equals(lastClass)) { + visitClass = visitor.visitClass(clsSrcName); + lastClass = clsSrcName; + + if (visitClass) { + if (clsDstName != null) visitor.visitDstName(MappedElementKind.CLASS, 0, clsDstName); + visitClass = visitor.visitElementContent(MappedElementKind.CLASS); + } + } + + if (visitClass && memberSrcName != null) { + if (!isMethod && visitor.visitField(memberSrcName, memberSrcDesc)) { + visitor.visitDstName(MappedElementKind.FIELD, 0, memberDstName); + } else if (isMethod && visitor.visitMethod(memberSrcName, memberSrcDesc)) { + visitor.visitDstName(MappedElementKind.METHOD, 0, memberDstName); + } + } + } while (reader.nextLine(0)); + } + + if (visitor.visitEnd()) break; + reader.reset(); + } + + if (parentVisitor != null) { + ((MappingTree) visitor).accept(parentVisitor); + } + } +} diff --git a/src/test/java/net/fabricmc/mappingio/read/EmptyContentReadTest.java b/src/test/java/net/fabricmc/mappingio/read/EmptyContentReadTest.java index a915520e..ba8807d1 100644 --- a/src/test/java/net/fabricmc/mappingio/read/EmptyContentReadTest.java +++ b/src/test/java/net/fabricmc/mappingio/read/EmptyContentReadTest.java @@ -25,6 +25,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.SrgFileReader; import net.fabricmc.mappingio.format.srg.TsrgFileReader; import net.fabricmc.mappingio.format.tiny.Tiny1FileReader; @@ -64,4 +65,9 @@ public void emptySrgFile() throws Exception { public void emptyTsrgFile() throws Exception { TsrgFileReader.read(new StringReader(""), tree); } + + @Test + public void emptyRecafSimpleFile() throws Exception { + RecafSimpleFileReader.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 b50f8bb6..b1484139 100644 --- a/src/test/java/net/fabricmc/mappingio/read/ValidContentReadTest.java +++ b/src/test/java/net/fabricmc/mappingio/read/ValidContentReadTest.java @@ -113,6 +113,14 @@ public void tsrg2File() throws Exception { checkHoles(filename, format); } + @Test + public void recafSimpleFile() throws Exception { + MappingFormat format = MappingFormat.RECAF_SIMPLE_FILE; + String filename = "recaf-simple.txt"; + checkDefault(filename, format); + checkHoles(filename, format); + } + private VisitableMappingTree checkDefault(String path, MappingFormat format) throws Exception { VisitableMappingTree tree = new MemoryMappingTree(); MappingReader.read(dir.resolve("valid/" + path), format, tree); diff --git a/src/test/resources/read/valid-with-holes/recaf-simple.txt b/src/test/resources/read/valid-with-holes/recaf-simple.txt new file mode 100644 index 00000000..32f3af0f --- /dev/null +++ b/src/test/resources/read/valid-with-holes/recaf-simple.txt @@ -0,0 +1,10 @@ +class_2 class2Ns0Rename +class_5 class5Ns0Rename +class_7$class_8 class_7$class8Ns0Rename +class_13$class_14 class_13$class14Ns0Rename +class_17$class_18$class_19 class_17$class_18$class19Ns0Rename +class_26$class_27$class_28 class_26$class_27$class28Ns0Rename +class_32.field_2 field2Ns0Rename +class_32.field_5 field5Ns0Rename +class_32.method_2()I method2Ns0Rename +class_32.method_5()I method5Ns0Rename diff --git a/src/test/resources/read/valid/recaf-simple.txt b/src/test/resources/read/valid/recaf-simple.txt new file mode 100644 index 00000000..7ca04bbf --- /dev/null +++ b/src/test/resources/read/valid/recaf-simple.txt @@ -0,0 +1,6 @@ +class_1 class1Ns0Rename +class_1.field_1 I field1Ns0Rename +class_1.method_1()I method1Ns0Rename +class_1$class_2 class1Ns0Rename$class2Ns0Rename +class_1$class_2.field_2 I field2Ns0Rename +class_3 class3Ns0Rename