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