getFlags() {
}
/**
- * Reset the visitor including any chained visitors to allow for another independent visit (excluding visitEnd=false).
+ * Reset the visitor, including any chained visitors, to allow for another independent visit (excluding visitEnd=false).
*/
default void reset() {
throw new UnsupportedOperationException();
@@ -63,7 +63,7 @@ default void reset() {
/**
* Determine whether the header (namespaces, metadata if part of the header) should be visited.
*
- * @return true if the header is to be visited, false otherwise
+ * @return {@code true} if the header is to be visited, {@code false} otherwise.
*/
default boolean visitHeader() throws IOException {
return true;
@@ -76,21 +76,61 @@ default void visitMetadata(String key, @Nullable String value) throws IOExceptio
/**
* Determine whether the mapping content (classes and anything below, metadata if not part of the header) should be visited.
*
- * @return true if content is to be visited, false otherwise
+ * @return {@code true} if content is to be visited, {@code false} otherwise.
*/
default boolean visitContent() throws IOException {
return true;
}
+ /**
+ * Visit a class.
+ *
+ * @param srcName The fully qualified source name of the class, in internal form
+ * (slashes instead of dots, dollar signs for delimiting inner classes).
+ * @return Whether or not the class's content should be visited too.
+ */
boolean visitClass(String srcName) throws IOException;
boolean visitField(String srcName, @Nullable String srcDesc) throws IOException;
boolean visitMethod(String srcName, @Nullable String srcDesc) throws IOException;
+
+ /**
+ * Visit a parameter.
+ *
+ * @param argPosition Always starts at 0 and gets incremented by 1 for each additional parameter.
+ * @param lvIndex The parameter's local variable index in the current method.
+ * Starts at 0 for static methods, 1 otherwise. For each additional parameter,
+ * it gets incremented by 1, or by 2 if it's a primitive {@code long} or {@code double}.
+ * @param srcName The optional source name of the parameter.
+ * @return Whether or not the arg's content should be visited too.
+ */
boolean visitMethodArg(int argPosition, int lvIndex, @Nullable String srcName) throws IOException;
+
+ /**
+ * Visit a variable.
+ *
+ * @param lvtRowIndex The variable's index in the method's LVT
+ * (local variable table). It is optional, so -1 can be passed instead.
+ * This is the case since LVTs themselves are optional debug information, see
+ * JVMS 4.7.13.
+ * @param lvIndex The var's local variable index in the current method. For each additional variable,
+ * it gets incremented by 1, or by 2 if it's a primitive {@code long} or {@code double}.
+ * The first variable starts where the last parameter left off (plus the offset).
+ * @param startOpIdx Required for cases when the lvIndex alone doesn't uniquely identify a local variable.
+ * This is the case when variables get re-defined later on, in which case most decompilers opt to
+ * not re-define the existing var, but instead generate a new one (with both sharing the same lvIndex).
+ * @param endOpIdx Counterpart to startOpIdx. Exclusive.
+ * @param srcName The optional source name of the variable.
+ * @return Whether or not the var's content should be visited too.
+ */
boolean visitMethodVar(int lvtRowIndex, int lvIndex, int startOpIdx, int endOpIdx, @Nullable String srcName) throws IOException;
/**
* Finish the visitation pass.
- * @return true if the visitation pass is final, false if it should be started over
+ *
+ * Implementors may throw an exception if a second pass is requested without the {@code NEEDS_MULTIPLE_PASSES}
+ * flag having been passed beforehand, but only if that behavior is documented.
+ *
+ * @return {@code true} if the visitation pass is final, {@code false} if it should be started over.
*/
default boolean visitEnd() throws IOException {
return true;
@@ -99,8 +139,8 @@ default boolean visitEnd() throws IOException {
/**
* Destination name for the current element.
*
- * @param namespace namespace index, index into the dstNamespaces List in {@link #visitNamespaces}
- * @param name destination name
+ * @param namespace Namespace index (index into the dstNamespaces list in {@link #visitNamespaces}).
+ * @param name Destination name.
*/
void visitDstName(MappedElementKind targetKind, int namespace, String name) throws IOException;
@@ -114,7 +154,7 @@ default void visitDstDesc(MappedElementKind targetKind, int namespace, String de
*
*
This is also a notification about all available dst names having been passed on.
*
- * @return true if the contents are to be visited, false otherwise
+ * @return {@code true} if the contents are to be visited, {@code false} otherwise
*/
default boolean visitElementContent(MappedElementKind targetKind) throws IOException {
return true;
@@ -123,7 +163,7 @@ default boolean visitElementContent(MappedElementKind targetKind) throws IOExcep
/**
* Comment for the specified element (last content-visited or any parent).
*
- * @param comment comment as a potentially multi-line string
+ * @param comment Comment as a potentially multi-line string.
*/
void visitComment(MappedElementKind targetKind, String comment) throws IOException;
}
diff --git a/src/main/java/net/fabricmc/mappingio/adapter/FlatAsRegularMappingVisitor.java b/src/main/java/net/fabricmc/mappingio/adapter/FlatAsRegularMappingVisitor.java
index f5cbf412..0ce63765 100644
--- a/src/main/java/net/fabricmc/mappingio/adapter/FlatAsRegularMappingVisitor.java
+++ b/src/main/java/net/fabricmc/mappingio/adapter/FlatAsRegularMappingVisitor.java
@@ -28,6 +28,13 @@
import net.fabricmc.mappingio.MappingFlag;
import net.fabricmc.mappingio.MappingVisitor;
+/**
+ * A mapping visitor that forwards all relevant data to a {@link FlatMappingVisitor}.
+ *
+ *
Element data is relayed upon {@link #visitElementContent(MappedElementKind)}
+ * or {@link #visitComment(MappedElementKind, String)} invocation.
+ * If no data was collected for the current element, the corresponding {@link FlatMappingVisitor}'s visit method is not called.
+ */
public final class FlatAsRegularMappingVisitor implements MappingVisitor {
public FlatAsRegularMappingVisitor(FlatMappingVisitor out) {
this.next = out;
diff --git a/src/main/java/net/fabricmc/mappingio/adapter/ForwardingMappingVisitor.java b/src/main/java/net/fabricmc/mappingio/adapter/ForwardingMappingVisitor.java
index 962dc888..876f9df1 100644
--- a/src/main/java/net/fabricmc/mappingio/adapter/ForwardingMappingVisitor.java
+++ b/src/main/java/net/fabricmc/mappingio/adapter/ForwardingMappingVisitor.java
@@ -27,6 +27,11 @@
import net.fabricmc.mappingio.MappingFlag;
import net.fabricmc.mappingio.MappingVisitor;
+/**
+ * A mapping visitor that forwards all visit calls to another {@link MappingVisitor}.
+ *
+ *
Subclasses should override the visit methods they want to intercept.
+ */
public abstract class ForwardingMappingVisitor implements MappingVisitor {
protected ForwardingMappingVisitor(MappingVisitor next) {
Objects.requireNonNull(next, "null next");
diff --git a/src/main/java/net/fabricmc/mappingio/adapter/MappingDstNsReorder.java b/src/main/java/net/fabricmc/mappingio/adapter/MappingDstNsReorder.java
index b0232d25..c7317475 100644
--- a/src/main/java/net/fabricmc/mappingio/adapter/MappingDstNsReorder.java
+++ b/src/main/java/net/fabricmc/mappingio/adapter/MappingDstNsReorder.java
@@ -24,7 +24,15 @@
import net.fabricmc.mappingio.MappedElementKind;
import net.fabricmc.mappingio.MappingVisitor;
+/**
+ * A mapping visitor that reorders and/or drops destination namespaces.
+ */
public final class MappingDstNsReorder extends ForwardingMappingVisitor {
+ /**
+ * @param next The next visitor to forward the data to.
+ * @param newDstNs The destination namespaces, in the desired order.
+ * Omitting entries from the list is going to drop them.
+ */
public MappingDstNsReorder(MappingVisitor next, List newDstNs) {
super(next);
@@ -33,6 +41,11 @@ public MappingDstNsReorder(MappingVisitor next, List newDstNs) {
this.newDstNs = newDstNs;
}
+ /**
+ * @param next The next visitor to forward the data to
+ * @param newDstNs The destination namespaces, in the desired order.
+ * Omitting entries from the list is going to drop them.
+ */
public MappingDstNsReorder(MappingVisitor next, String... newDstNs) {
this(next, Arrays.asList(newDstNs));
}
diff --git a/src/main/java/net/fabricmc/mappingio/adapter/MappingNsCompleter.java b/src/main/java/net/fabricmc/mappingio/adapter/MappingNsCompleter.java
index 90bf35d2..18472b42 100644
--- a/src/main/java/net/fabricmc/mappingio/adapter/MappingNsCompleter.java
+++ b/src/main/java/net/fabricmc/mappingio/adapter/MappingNsCompleter.java
@@ -27,11 +27,26 @@
import net.fabricmc.mappingio.MappedElementKind;
import net.fabricmc.mappingio.MappingVisitor;
+/**
+ * A mapping visitor that completes missing destination names.
+ *
+ * Some mapping formats allow omitting destination names if equal to the source name.
+ * This visitor fills in these "holes" by copying names from another namespace.
+ */
public final class MappingNsCompleter extends ForwardingMappingVisitor {
+ /**
+ * @param next The next visitor to forward the data to.
+ * @param alternatives A map of which namespaces should copy from which others.
+ */
public MappingNsCompleter(MappingVisitor next, Map alternatives) {
this(next, alternatives, false);
}
+ /**
+ * @param next The next visitor to forward the data to.
+ * @param alternatives A map of which namespaces should copy from which others.
+ * @param addMissingNs Whether or not to copy namespaces from the alternatives keyset if not already present.
+ */
public MappingNsCompleter(MappingVisitor next, Map alternatives, boolean addMissingNs) {
super(next);
diff --git a/src/main/java/net/fabricmc/mappingio/adapter/MappingNsRenamer.java b/src/main/java/net/fabricmc/mappingio/adapter/MappingNsRenamer.java
index a72de16c..48604b9a 100644
--- a/src/main/java/net/fabricmc/mappingio/adapter/MappingNsRenamer.java
+++ b/src/main/java/net/fabricmc/mappingio/adapter/MappingNsRenamer.java
@@ -24,7 +24,14 @@
import net.fabricmc.mappingio.MappingVisitor;
+/**
+ * A mapping visitor that renames namespaces.
+ */
public final class MappingNsRenamer extends ForwardingMappingVisitor {
+ /**
+ * @param next The next visitor to forward the data to.
+ * @param nameMap A map of which namespaces should be renamed to which new names.
+ */
public MappingNsRenamer(MappingVisitor next, Map nameMap) {
super(next);
diff --git a/src/main/java/net/fabricmc/mappingio/adapter/MappingSourceNsSwitch.java b/src/main/java/net/fabricmc/mappingio/adapter/MappingSourceNsSwitch.java
index fb931c63..991bbc32 100644
--- a/src/main/java/net/fabricmc/mappingio/adapter/MappingSourceNsSwitch.java
+++ b/src/main/java/net/fabricmc/mappingio/adapter/MappingSourceNsSwitch.java
@@ -52,9 +52,9 @@ public MappingSourceNsSwitch(MappingVisitor next, String newSourceNs) {
/**
* Create a new MappingSourceNsSwitch instance.
*
- * @param next MappingVisitor to pass the output to
- * @param newSourceNs namespace to use for the new source name
- * @param dropMissingNewSrcName whether to drop elements without a name in newSourceNs, will use original srcName otherwise
+ * @param next MappingVisitor to pass the output to.
+ * @param newSourceNs Namespace to use for the new source name.
+ * @param dropMissingNewSrcName Whether to drop elements without a name in newSourceNs, will use original srcName otherwise.
*/
public MappingSourceNsSwitch(MappingVisitor next, String newSourceNs, boolean dropMissingNewSrcName) {
super(next);
diff --git a/src/main/java/net/fabricmc/mappingio/adapter/MissingDescFilter.java b/src/main/java/net/fabricmc/mappingio/adapter/MissingDescFilter.java
index 760afd2b..16929851 100644
--- a/src/main/java/net/fabricmc/mappingio/adapter/MissingDescFilter.java
+++ b/src/main/java/net/fabricmc/mappingio/adapter/MissingDescFilter.java
@@ -22,6 +22,9 @@
import net.fabricmc.mappingio.MappingVisitor;
+/**
+ * A mapping visitor that filters out elements with missing source descriptors.
+ */
public final class MissingDescFilter extends ForwardingMappingVisitor {
public MissingDescFilter(MappingVisitor next) {
super(next);
diff --git a/src/main/java/net/fabricmc/mappingio/adapter/RegularAsFlatMappingVisitor.java b/src/main/java/net/fabricmc/mappingio/adapter/RegularAsFlatMappingVisitor.java
index 6dc8b0fb..0e9abba6 100644
--- a/src/main/java/net/fabricmc/mappingio/adapter/RegularAsFlatMappingVisitor.java
+++ b/src/main/java/net/fabricmc/mappingio/adapter/RegularAsFlatMappingVisitor.java
@@ -27,6 +27,9 @@
import net.fabricmc.mappingio.MappingFlag;
import net.fabricmc.mappingio.MappingVisitor;
+/**
+ * A {@link FlatMappingVisitor} that forwards all relevant data to a regular {@link MappingVisitor}.
+ */
public final class RegularAsFlatMappingVisitor implements FlatMappingVisitor {
public RegularAsFlatMappingVisitor(MappingVisitor next) {
this.next = next;
diff --git a/src/main/java/net/fabricmc/mappingio/format/ColumnFileReader.java b/src/main/java/net/fabricmc/mappingio/format/ColumnFileReader.java
index 17fb1077..8b3b996a 100644
--- a/src/main/java/net/fabricmc/mappingio/format/ColumnFileReader.java
+++ b/src/main/java/net/fabricmc/mappingio/format/ColumnFileReader.java
@@ -26,6 +26,9 @@
import net.fabricmc.mappingio.format.tiny.Tiny2Util;
+/**
+ * Reader for column-based files.
+ */
@ApiStatus.Internal
public final class ColumnFileReader implements Closeable {
public ColumnFileReader(Reader reader, char columnSeparator) {
@@ -43,9 +46,8 @@ public void close() throws IOException {
*
* The reader will point to the next column or end of line if successful, otherwise remains unchanged.
*
- * @param expect content to expect
- * @return true if the column was read and had the expected content, false otherwise
- * @throws IOException
+ * @param expect Content to expect.
+ * @return {@code true} if the column was read and had the expected content, false otherwise.
*/
public boolean nextCol(String expect) throws IOException {
if (eol) return false;
diff --git a/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java b/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java
index f0fa135e..c81e5249 100644
--- a/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java
+++ b/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java
@@ -33,16 +33,16 @@
*
* Tiny v1 |
* ✔ |
- * ✔ |
- * ✖ |
- * ✖ |
- * ✖ |
+ * src |
+ * - |
+ * - |
+ * - |
* ✔ (Currently limited support) |
*
*
* Tiny v2 |
* ✔ |
- * ✔ |
+ * src |
* ✔ |
* ✔ |
* ✔ |
@@ -50,57 +50,57 @@
*
*
* Enigma |
- * ✖ |
- * ✔ |
+ * - |
+ * src |
* ✔ |
* ✔ |
- * ✖ |
- * ✖ |
+ * - |
+ * - |
*
*
* SRG |
- * ✖ |
- * ✖ |
- * ✖ |
- * ✖ |
- * ✖ |
- * ✖ |
+ * - |
+ * - |
+ * - |
+ * - |
+ * - |
+ * - |
*
*
* XSRG |
- * ✖ |
- * ✔ |
- * ✖ |
- * ✖ |
- * ✖ |
- * ✖ |
+ * - |
+ * src & dst |
+ * - |
+ * - |
+ * - |
+ * - |
*
*
* CSRG/TSRG |
- * ✖ |
- * ✖ |
- * ✖ |
- * ✖ |
- * ✖ |
- * ✖ |
+ * - |
+ * - |
+ * - |
+ * - |
+ * - |
+ * - |
*
*
* TSRG2 |
* ✔ |
+ * src |
+ * - |
* ✔ |
- * ✖ |
- * ✔ |
- * ✖ |
- * ✖ |
+ * - |
+ * - |
*
*
* ProGuard |
- * ✖ |
- * ✔ |
- * ✖ |
- * ✖ |
- * ✖ |
- * ✖ |
+ * - |
+ * src |
+ * - |
+ * - |
+ * - |
+ * - |
*
*
*/
@@ -127,23 +127,23 @@ public enum MappingFormat {
ENIGMA_DIR("Enigma directory", null, false, true, true, true, false),
/**
- * The {@code SRG} ({@code Searge RetroGuard}) mapping format, as specified here.
+ * The {@code SRG} ("Searge RetroGuard") mapping format, as specified here.
*/
SRG_FILE("SRG file", "srg", false, false, false, false, false),
/**
- * The {@code XSRG} ({@code Extended SRG}) mapping format, as specified here.
- * Same as SRG, but with field descriptors..
+ * The {@code XSRG} ("Extended SRG") mapping format, as specified here.
+ * Same as SRG, but with field descriptors.
*/
XSRG_FILE("XSRG file", "xsrg", false, true, false, false, false),
/**
- * The {@code CSRG} ({@code Compact SRG}, since it saves disk space over SRG) mapping format, as specified here.
+ * The {@code CSRG} ("Compact SRG", since it saves disk space over SRG) mapping format, as specified here.
*/
CSRG_FILE("CSRG file", "csrg", false, false, false, false, false),
/**
- * The {@code TSRG} ({@code Tiny SRG}, since it saves disk space over SRG) mapping format, as specified here.
+ * The {@code TSRG} ("Tiny SRG", since it saves disk space over SRG) mapping format, as specified here.
* Same as CSRG, but hierarchical instead of flat.
*/
TSRG_FILE("TSRG file", "tsrg", false, false, false, false, false),
diff --git a/src/main/java/net/fabricmc/mappingio/format/enigma/EnigmaDirReader.java b/src/main/java/net/fabricmc/mappingio/format/enigma/EnigmaDirReader.java
index eeb404f1..376a50d8 100644
--- a/src/main/java/net/fabricmc/mappingio/format/enigma/EnigmaDirReader.java
+++ b/src/main/java/net/fabricmc/mappingio/format/enigma/EnigmaDirReader.java
@@ -33,6 +33,12 @@
import net.fabricmc.mappingio.tree.MappingTree;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
+/**
+ * {@linkplain MappingFormat#ENIGMA_DIRECTORY Enigma directory} reader.
+ *
+ * Crashes if a second visit pass is requested without
+ * {@link MappingFlag#NEEDS_MULTIPLE_PASSES} having been passed beforehand.
+ */
public final class EnigmaDirReader {
private EnigmaDirReader() {
}
diff --git a/src/main/java/net/fabricmc/mappingio/format/enigma/EnigmaDirWriter.java b/src/main/java/net/fabricmc/mappingio/format/enigma/EnigmaDirWriter.java
index 4a2cb3b3..4a5aa2a6 100644
--- a/src/main/java/net/fabricmc/mappingio/format/enigma/EnigmaDirWriter.java
+++ b/src/main/java/net/fabricmc/mappingio/format/enigma/EnigmaDirWriter.java
@@ -31,6 +31,9 @@
import net.fabricmc.mappingio.MappedElementKind;
import net.fabricmc.mappingio.format.MappingFormat;
+/**
+ * {@linkplain MappingFormat#ENIGMA_DIRECTORY Enigma directory} writer.
+ */
public final class EnigmaDirWriter extends EnigmaWriterBase {
public EnigmaDirWriter(Path dir, boolean deleteExistingFiles) throws IOException {
super(null);
diff --git a/src/main/java/net/fabricmc/mappingio/format/enigma/EnigmaFileReader.java b/src/main/java/net/fabricmc/mappingio/format/enigma/EnigmaFileReader.java
index cafb1811..276dace1 100644
--- a/src/main/java/net/fabricmc/mappingio/format/enigma/EnigmaFileReader.java
+++ b/src/main/java/net/fabricmc/mappingio/format/enigma/EnigmaFileReader.java
@@ -26,9 +26,16 @@
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#ENIGMA_FILE Enigma file} reader.
+ *
+ *
Crashes if a second visit pass is requested without
+ * {@link MappingFlag#NEEDS_MULTIPLE_PASSES} having been passed beforehand.
+ */
public final class EnigmaFileReader {
private EnigmaFileReader() {
}
diff --git a/src/main/java/net/fabricmc/mappingio/format/enigma/EnigmaFileWriter.java b/src/main/java/net/fabricmc/mappingio/format/enigma/EnigmaFileWriter.java
index 4c285707..bc920c37 100644
--- a/src/main/java/net/fabricmc/mappingio/format/enigma/EnigmaFileWriter.java
+++ b/src/main/java/net/fabricmc/mappingio/format/enigma/EnigmaFileWriter.java
@@ -20,7 +20,11 @@
import java.io.Writer;
import net.fabricmc.mappingio.MappedElementKind;
+import net.fabricmc.mappingio.format.MappingFormat;
+/**
+ * {@linkplain MappingFormat#ENIGMA_FILE Enigma file} writer.
+ */
public final class EnigmaFileWriter extends EnigmaWriterBase {
public EnigmaFileWriter(Writer writer) throws IOException {
super(writer);
diff --git a/src/main/java/net/fabricmc/mappingio/format/proguard/ProGuardFileReader.java b/src/main/java/net/fabricmc/mappingio/format/proguard/ProGuardFileReader.java
index d1670b7d..e2d8ea02 100644
--- a/src/main/java/net/fabricmc/mappingio/format/proguard/ProGuardFileReader.java
+++ b/src/main/java/net/fabricmc/mappingio/format/proguard/ProGuardFileReader.java
@@ -27,7 +27,14 @@
import net.fabricmc.mappingio.MappingFlag;
import net.fabricmc.mappingio.MappingUtil;
import net.fabricmc.mappingio.MappingVisitor;
+import net.fabricmc.mappingio.format.MappingFormat;
+/**
+ * {@linkplain MappingFormat#PROGUARD_FILE ProGuard file} reader.
+ *
+ *
Crashes if a second visit pass is requested without
+ * {@link MappingFlag#NEEDS_MULTIPLE_PASSES} having been passed beforehand.
+ */
public final class ProGuardFileReader {
private ProGuardFileReader() {
}
diff --git a/src/main/java/net/fabricmc/mappingio/format/proguard/ProGuardFileWriter.java b/src/main/java/net/fabricmc/mappingio/format/proguard/ProGuardFileWriter.java
index b566f930..e20d518a 100644
--- a/src/main/java/net/fabricmc/mappingio/format/proguard/ProGuardFileWriter.java
+++ b/src/main/java/net/fabricmc/mappingio/format/proguard/ProGuardFileWriter.java
@@ -26,14 +26,10 @@
import net.fabricmc.mappingio.MappedElementKind;
import net.fabricmc.mappingio.MappingWriter;
+import net.fabricmc.mappingio.format.MappingFormat;
/**
- * A mapping writer for the ProGuard mapping format.
- * Note that this format is very basic: it only supports
- * one namespace pair and only classes, methods and fields
- * without comments.
- *
- * @see Official format documentation
+ * {@linkplain MappingFormat#PROGUARD_FILE ProGuard file} writer.
*/
public final class ProGuardFileWriter implements MappingWriter {
private final Writer writer;
@@ -44,7 +40,7 @@ public final class ProGuardFileWriter implements MappingWriter {
* Constructs a ProGuard mapping writer that uses
* the first destination namespace (index 0).
*
- * @param writer the writer where the mappings will be written
+ * @param writer The writer where the mappings will be written.
*/
public ProGuardFileWriter(Writer writer) {
this(writer, 0);
@@ -53,8 +49,8 @@ public ProGuardFileWriter(Writer writer) {
/**
* Constructs a ProGuard mapping writer.
*
- * @param writer the writer where the mappings will be written
- * @param dstNamespace the namespace index to write as the destination namespace, must be at least 0
+ * @param writer The writer where the mappings will be written.
+ * @param dstNamespace The namespace index to write as the destination namespace, must be at least 0.
*/
public ProGuardFileWriter(Writer writer, int dstNamespace) {
this.writer = Objects.requireNonNull(writer, "writer cannot be null");
@@ -69,8 +65,8 @@ public ProGuardFileWriter(Writer writer, int dstNamespace) {
/**
* Constructs a ProGuard mapping writer.
*
- * @param writer the writer where the mappings will be written
- * @param dstNamespace the namespace name to write as the destination namespace
+ * @param writer The writer where the mappings will be written.
+ * @param dstNamespace The namespace name to write as the destination namespace.
*/
public ProGuardFileWriter(Writer writer, String dstNamespace) {
this.writer = Objects.requireNonNull(writer, "writer cannot be null");
@@ -79,8 +75,6 @@ public ProGuardFileWriter(Writer writer, String dstNamespace) {
/**
* Closes the internal {@link Writer}.
- *
- * @throws IOException if an IO error occurs
*/
@Override
public void close() throws IOException {
diff --git a/src/main/java/net/fabricmc/mappingio/format/srg/SrgFileReader.java b/src/main/java/net/fabricmc/mappingio/format/srg/SrgFileReader.java
index 8fe35146..912fc6a7 100644
--- a/src/main/java/net/fabricmc/mappingio/format/srg/SrgFileReader.java
+++ b/src/main/java/net/fabricmc/mappingio/format/srg/SrgFileReader.java
@@ -30,6 +30,13 @@
import net.fabricmc.mappingio.tree.MappingTree;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
+/**
+ * {@linkplain MappingFormat#SRG_FILE SRG file} and
+ * {@linkplain MappingFormat#XSRG_FILE XSRG file} reader.
+ *
+ *
Crashes if a second visit pass is requested without
+ * {@link MappingFlag#NEEDS_MULTIPLE_PASSES} having been passed beforehand.
+ */
public final class SrgFileReader {
private SrgFileReader() {
}
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 d7db765c..f6379a7d 100644
--- a/src/main/java/net/fabricmc/mappingio/format/srg/SrgFileWriter.java
+++ b/src/main/java/net/fabricmc/mappingio/format/srg/SrgFileWriter.java
@@ -28,6 +28,10 @@
import net.fabricmc.mappingio.MappingFlag;
import net.fabricmc.mappingio.MappingWriter;
+/**
+ * {@linkplain net.fabricmc.mappingio.format.MappingFormat#SRG_FILE SRG file} and
+ * {@linkplain net.fabricmc.mappingio.format.MappingFormat#XSRG_FILE XSRG file} writer.
+ */
public final class SrgFileWriter implements MappingWriter {
public SrgFileWriter(Writer writer, boolean xsrg) {
this.writer = writer;
diff --git a/src/main/java/net/fabricmc/mappingio/format/srg/TsrgFileReader.java b/src/main/java/net/fabricmc/mappingio/format/srg/TsrgFileReader.java
index 59582b07..dec67e7a 100644
--- a/src/main/java/net/fabricmc/mappingio/format/srg/TsrgFileReader.java
+++ b/src/main/java/net/fabricmc/mappingio/format/srg/TsrgFileReader.java
@@ -31,6 +31,14 @@
import net.fabricmc.mappingio.format.ColumnFileReader;
import net.fabricmc.mappingio.format.MappingFormat;
+/**
+ * {@linkplain MappingFormat#CSRG_FILE CSRG file},
+ * {@linkplain MappingFormat#TSRG_FILE TSRG file} and
+ * {@linkplain MappingFormat#TSRG_2_FILE TSRG2 file} reader.
+ *
+ *
Crashes if a second visit pass is requested without
+ * {@link MappingFlag#NEEDS_MULTIPLE_PASSES} having been passed beforehand.
+ */
public final class TsrgFileReader {
private TsrgFileReader() {
}
diff --git a/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny1FileReader.java b/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny1FileReader.java
index d0ddcc36..f74f7f7b 100644
--- a/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny1FileReader.java
+++ b/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny1FileReader.java
@@ -26,9 +26,16 @@
import net.fabricmc.mappingio.MappingFlag;
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#TINY_1 Tiny v1 file} reader.
+ *
+ *
Crashes if a second visit pass is requested without
+ * {@link MappingFlag#NEEDS_MULTIPLE_PASSES} having been passed beforehand.
+ */
public final class Tiny1FileReader {
private Tiny1FileReader() {
}
diff --git a/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny1FileWriter.java b/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny1FileWriter.java
index 54575acd..f419e3ef 100644
--- a/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny1FileWriter.java
+++ b/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny1FileWriter.java
@@ -28,7 +28,11 @@
import net.fabricmc.mappingio.MappedElementKind;
import net.fabricmc.mappingio.MappingFlag;
import net.fabricmc.mappingio.MappingWriter;
+import net.fabricmc.mappingio.format.MappingFormat;
+/**
+ * {@linkplain MappingFormat#TINY_1 Tiny v1 file} writer.
+ */
public final class Tiny1FileWriter implements MappingWriter {
public Tiny1FileWriter(Writer writer) {
this.writer = writer;
diff --git a/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny2FileReader.java b/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny2FileReader.java
index 6381d80f..be4b408b 100644
--- a/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny2FileReader.java
+++ b/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny2FileReader.java
@@ -25,7 +25,14 @@
import net.fabricmc.mappingio.MappingFlag;
import net.fabricmc.mappingio.MappingVisitor;
import net.fabricmc.mappingio.format.ColumnFileReader;
+import net.fabricmc.mappingio.format.MappingFormat;
+/**
+ * {@linkplain MappingFormat#TINY_2 Tiny v2 file} reader.
+ *
+ *
Crashes if a second visit pass is requested without
+ * {@link MappingFlag#NEEDS_MULTIPLE_PASSES} having been passed beforehand.
+ */
public final class Tiny2FileReader {
private Tiny2FileReader() {
}
diff --git a/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny2FileWriter.java b/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny2FileWriter.java
index f2a14b0c..f2b5b017 100644
--- a/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny2FileWriter.java
+++ b/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny2FileWriter.java
@@ -28,7 +28,11 @@
import net.fabricmc.mappingio.MappedElementKind;
import net.fabricmc.mappingio.MappingFlag;
import net.fabricmc.mappingio.MappingWriter;
+import net.fabricmc.mappingio.format.MappingFormat;
+/**
+ * {@linkplain MappingFormat#TINY_2 Tiny v2 file} writer.
+ */
public final class Tiny2FileWriter implements MappingWriter {
public Tiny2FileWriter(Writer writer, boolean escapeNames) {
this.writer = writer;
diff --git a/src/main/java/net/fabricmc/mappingio/tree/MappingTree.java b/src/main/java/net/fabricmc/mappingio/tree/MappingTree.java
index 78b29bd9..df8db66d 100644
--- a/src/main/java/net/fabricmc/mappingio/tree/MappingTree.java
+++ b/src/main/java/net/fabricmc/mappingio/tree/MappingTree.java
@@ -21,6 +21,9 @@
import org.jetbrains.annotations.Nullable;
+/**
+ * Mutable mapping tree.
+ */
public interface MappingTree extends MappingTreeView {
@Nullable
String setSrcNamespace(String namespace);
@@ -45,6 +48,7 @@ public interface MappingTree extends MappingTreeView {
/**
* Removes all metadata entries whose key is equal to the passed one.
+ *
* @return Whether or not any entries have been removed.
*/
boolean removeMetadata(String key);
diff --git a/src/main/java/net/fabricmc/mappingio/tree/MappingTreeView.java b/src/main/java/net/fabricmc/mappingio/tree/MappingTreeView.java
index b9665e1e..d6f3eac4 100644
--- a/src/main/java/net/fabricmc/mappingio/tree/MappingTreeView.java
+++ b/src/main/java/net/fabricmc/mappingio/tree/MappingTreeView.java
@@ -24,6 +24,9 @@
import net.fabricmc.mappingio.MappingVisitor;
+/**
+ * Read-only mapping tree.
+ */
public interface MappingTreeView {
/**
* @return The source namespace, or {@code null} if the tree is uninitialized.
@@ -38,14 +41,14 @@ public interface MappingTreeView {
List getDstNamespaces();
/**
- * Get the maximum available namespace ID (exclusive).
+ * @return The maximum available namespace ID (exclusive).
*/
default int getMaxNamespaceId() {
return getDstNamespaces().size();
}
/**
- * Get the minimum available namespace ID (inclusive).
+ * @return The minimum available namespace ID (inclusive).
*/
default int getMinNamespaceId() {
return MIN_NAMESPACE_ID;
diff --git a/src/main/java/net/fabricmc/mappingio/tree/MemoryMappingTree.java b/src/main/java/net/fabricmc/mappingio/tree/MemoryMappingTree.java
index 2836c320..fcf6829b 100644
--- a/src/main/java/net/fabricmc/mappingio/tree/MemoryMappingTree.java
+++ b/src/main/java/net/fabricmc/mappingio/tree/MemoryMappingTree.java
@@ -40,6 +40,9 @@
import net.fabricmc.mappingio.MappingFlag;
import net.fabricmc.mappingio.MappingVisitor;
+/**
+ * {@link VisitableMappingTree} implementation that stores all data in memory.
+ */
public final class MemoryMappingTree implements VisitableMappingTree {
public MemoryMappingTree() {
this(false);
diff --git a/src/main/java/net/fabricmc/mappingio/tree/VisitableMappingTree.java b/src/main/java/net/fabricmc/mappingio/tree/VisitableMappingTree.java
index 19272660..4e9b3a97 100644
--- a/src/main/java/net/fabricmc/mappingio/tree/VisitableMappingTree.java
+++ b/src/main/java/net/fabricmc/mappingio/tree/VisitableMappingTree.java
@@ -18,5 +18,8 @@
import net.fabricmc.mappingio.MappingVisitor;
+/**
+ * {@link MappingTree} that can be visited.
+ */
public interface VisitableMappingTree extends MappingTree, MappingVisitor {
}
From 23f090b56818f8d3b6e935459db00007fe246d2a Mon Sep 17 00:00:00 2001
From: Julian Burner <48808497+NebelNidas@users.noreply.github.com>
Date: Thu, 30 Nov 2023 22:46:30 +0100
Subject: [PATCH 03/12] Fix Enigma reader throwing incorrect error message
(#87)
* Fix Enigma reader error message
* Add changes to changelog
---------
Co-authored-by: modmuss
---
CHANGELOG.md | 1 +
.../fabricmc/mappingio/format/enigma/EnigmaFileReader.java | 4 ++--
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4c0a2b00..e24c70ce 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
+- Fixed Enigma reader throwing incorrect error message
- Improved documentation
- Fixed NPE in `MemoryMappingTree`
- Fixed TSRG2 reader not handling multiple passes correctly
diff --git a/src/main/java/net/fabricmc/mappingio/format/enigma/EnigmaFileReader.java b/src/main/java/net/fabricmc/mappingio/format/enigma/EnigmaFileReader.java
index 276dace1..3590ccde 100644
--- a/src/main/java/net/fabricmc/mappingio/format/enigma/EnigmaFileReader.java
+++ b/src/main/java/net/fabricmc/mappingio/format/enigma/EnigmaFileReader.java
@@ -131,10 +131,10 @@ private static void readClassBody(ColumnFileReader reader, int indent, String sr
if (state < 0) continue;
String srcName = reader.nextCol();
- if (srcName == null || srcName.isEmpty()) throw new IOException("missing field-name-a in line "+reader.getLineNumber());
+ if (srcName == null || srcName.isEmpty()) throw new IOException("missing member-name-a in line "+reader.getLineNumber());
String dstNameOrSrcDesc = reader.nextCol();
- if (dstNameOrSrcDesc == null || dstNameOrSrcDesc.isEmpty()) throw new IOException("missing field-desc-b in line "+reader.getLineNumber());
+ if (dstNameOrSrcDesc == null || dstNameOrSrcDesc.isEmpty()) throw new IOException("missing member-name-b/member-desc-a in line "+reader.getLineNumber());
String srcDesc = reader.nextCol();
String dstName;
From f72510e1aab9090ea9efe3e60395909974890ad2 Mon Sep 17 00:00:00 2001
From: Julian Burner <48808497+NebelNidas@users.noreply.github.com>
Date: Thu, 30 Nov 2023 23:01:14 +0100
Subject: [PATCH 04/12] Add dogfooding test (#85)
* Expand `VisitEndTest`; fix test trees being inconsistent
* Fix NPE in `MemoryMappingTree`
* Fix TSRG reader not handling multiple passes correctly
* Add changes to changelog
* Add dogfooding test
* Fix Proguard writer not handling missing destination names correctly
---------
Co-authored-by: modmuss
---
CHANGELOG.md | 5 +-
.../format/proguard/ProGuardFileWriter.java | 93 ++++++++++++-------
.../net/fabricmc/mappingio/TestHelper.java | 10 +-
.../fabricmc/mappingio/write/WriteTest.java | 44 ++++++---
4 files changed, 97 insertions(+), 55 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e24c70ce..23708508 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,10 +4,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
-- Fixed Enigma reader throwing incorrect error message
- Improved documentation
- Fixed NPE in `MemoryMappingTree`
- Fixed TSRG2 reader not handling multiple passes correctly
+- Fixed ProGuard writer producing invalid files when missing destination names are present
+- Fixed Enigma reader throwing incorrect error message
+- Fixed NPE in `MemoryMappingTree`
+- Fixed TSRG2 reader not handling multiple passes correctly
## [0.5.0] - 2023-11-15
- Actually marked `HierarchyInfoProvider` as experimental
diff --git a/src/main/java/net/fabricmc/mappingio/format/proguard/ProGuardFileWriter.java b/src/main/java/net/fabricmc/mappingio/format/proguard/ProGuardFileWriter.java
index e20d518a..68bc8108 100644
--- a/src/main/java/net/fabricmc/mappingio/format/proguard/ProGuardFileWriter.java
+++ b/src/main/java/net/fabricmc/mappingio/format/proguard/ProGuardFileWriter.java
@@ -33,8 +33,11 @@
*/
public final class ProGuardFileWriter implements MappingWriter {
private final Writer writer;
- private int dstNamespace = -1;
private final String dstNamespaceString;
+ private int dstNamespace = -1;
+ private String srcName;
+ private String srcDesc;
+ private String dstName;
/**
* Constructs a ProGuard mapping writer that uses
@@ -98,54 +101,35 @@ public void visitNamespaces(String srcNamespace, List dstNamespaces) thr
@Override
public boolean visitClass(String srcName) throws IOException {
- writer.write(toJavaClassName(srcName));
- writeArrow();
+ this.srcName = srcName;
+
return true;
}
@Override
public boolean visitField(String srcName, @Nullable String srcDesc) throws IOException {
- writeIndent();
- writer.write(toJavaType(srcDesc));
- writer.write(' ');
- writer.write(srcName);
- writeArrow();
+ this.srcName = srcName;
+ this.srcDesc = srcDesc;
+
return true;
}
@Override
public boolean visitMethod(String srcName, @Nullable String srcDesc) throws IOException {
- Type type = Type.getMethodType(srcDesc);
- writeIndent();
- writer.write(toJavaType(type.getReturnType().getDescriptor()));
- writer.write(' ');
- writer.write(srcName);
- writer.write('(');
- Type[] args = type.getArgumentTypes();
-
- for (int i = 0; i < args.length; i++) {
- if (i > 0) {
- writer.write(',');
- }
+ this.srcName = srcName;
+ this.srcDesc = srcDesc;
- writer.write(toJavaType(args[i].getDescriptor()));
- }
-
- writer.write(')');
- writeArrow();
return true;
}
@Override
public boolean visitMethodArg(int argPosition, int lvIndex, @Nullable String srcName) throws IOException {
- // ignored
- return false;
+ return false; // not supported, skip
}
@Override
public boolean visitMethodVar(int lvtRowIndex, int lvIndex, int startOpIdx, int endOpIdx, @Nullable String srcName) throws IOException {
- // ignored
- return false;
+ return false; // not supported, skip
}
@Override
@@ -154,19 +138,58 @@ public void visitDstName(MappedElementKind targetKind, int namespace, String nam
return;
}
- if (targetKind == MappedElementKind.CLASS) {
- writer.write(toJavaClassName(name));
- writer.write(':');
- } else {
- writer.write(name);
+ dstName = name;
+ }
+
+ @Override
+ public boolean visitElementContent(MappedElementKind targetKind) throws IOException {
+ if (dstName == null) dstName = srcName;
+
+ switch (targetKind) {
+ case CLASS:
+ writer.write(toJavaClassName(srcName));
+ dstName = toJavaClassName(dstName) + ":";
+ break;
+ case FIELD:
+ writeIndent();
+ writer.write(toJavaType(srcDesc));
+ writer.write(' ');
+ writer.write(srcName);
+ break;
+ case METHOD:
+ Type type = Type.getMethodType(srcDesc);
+ writeIndent();
+ writer.write(toJavaType(type.getReturnType().getDescriptor()));
+ writer.write(' ');
+ writer.write(srcName);
+ writer.write('(');
+ Type[] args = type.getArgumentTypes();
+
+ for (int i = 0; i < args.length; i++) {
+ if (i > 0) {
+ writer.write(',');
+ }
+
+ writer.write(toJavaType(args[i].getDescriptor()));
+ }
+
+ writer.write(')');
+ break;
+ default:
+ throw new IllegalStateException("unexpected invocation for "+targetKind);
}
+ writeArrow();
+ writer.write(dstName);
writer.write('\n');
+
+ srcName = srcDesc = dstName = null;
+ return true;
}
@Override
public void visitComment(MappedElementKind targetKind, String comment) throws IOException {
- // ignored
+ // not supported, skip
}
private void writeArrow() throws IOException {
diff --git a/src/test/java/net/fabricmc/mappingio/TestHelper.java b/src/test/java/net/fabricmc/mappingio/TestHelper.java
index 4b1dbabb..df7a5a0a 100644
--- a/src/test/java/net/fabricmc/mappingio/TestHelper.java
+++ b/src/test/java/net/fabricmc/mappingio/TestHelper.java
@@ -26,7 +26,7 @@
import org.jetbrains.annotations.Nullable;
import net.fabricmc.mappingio.format.MappingFormat;
-import net.fabricmc.mappingio.tree.MappingTree;
+import net.fabricmc.mappingio.tree.MappingTreeView;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
public final class TestHelper {
@@ -66,10 +66,10 @@ public static String getFileName(MappingFormat format) {
}
}
- public static void writeToDir(MappingTree tree, MappingFormat format, Path dir) throws IOException {
- MappingWriter writer = MappingWriter.create(dir.resolve(format.name() + "." + format.fileExt), format);
- tree.accept(writer);
- writer.close();
+ public static Path writeToDir(MappingTreeView tree, Path dir, MappingFormat format) throws IOException {
+ Path path = dir.resolve(getFileName(format));
+ tree.accept(MappingWriter.create(path, format));
+ return path;
}
// Has to be kept in sync with /resources/read/valid/* test mappings!
diff --git a/src/test/java/net/fabricmc/mappingio/write/WriteTest.java b/src/test/java/net/fabricmc/mappingio/write/WriteTest.java
index 23ee6313..4daed966 100644
--- a/src/test/java/net/fabricmc/mappingio/write/WriteTest.java
+++ b/src/test/java/net/fabricmc/mappingio/write/WriteTest.java
@@ -22,58 +22,74 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
+import net.fabricmc.mappingio.MappingReader;
+import net.fabricmc.mappingio.SubsetAssertingVisitor;
import net.fabricmc.mappingio.TestHelper;
+import net.fabricmc.mappingio.adapter.FlatAsRegularMappingVisitor;
import net.fabricmc.mappingio.format.MappingFormat;
+import net.fabricmc.mappingio.tree.MappingTreeView;
+import net.fabricmc.mappingio.tree.MemoryMappingTree;
import net.fabricmc.mappingio.tree.VisitableMappingTree;
public class WriteTest {
@TempDir
private static Path dir;
- private static VisitableMappingTree tree;
- private static VisitableMappingTree treeWithHoles;
+ private static MappingTreeView validTree;
+ private static MappingTreeView validWithHolesTree;
@BeforeAll
public static void setup() throws Exception {
- tree = TestHelper.createTestTree();
- treeWithHoles = TestHelper.createTestTreeWithHoles();
+ validTree = TestHelper.createTestTree();
+ validWithHolesTree = TestHelper.createTestTreeWithHoles();
}
@Test
public void enigmaFile() throws Exception {
- write(MappingFormat.ENIGMA_FILE);
+ check(MappingFormat.ENIGMA_FILE);
}
@Test
public void enigmaDirectory() throws Exception {
- write(MappingFormat.ENIGMA_DIR);
+ check(MappingFormat.ENIGMA_DIR);
}
@Test
public void tinyFile() throws Exception {
- write(MappingFormat.TINY_FILE);
+ check(MappingFormat.TINY_FILE);
}
@Test
public void tinyV2File() throws Exception {
- write(MappingFormat.TINY_2_FILE);
+ check(MappingFormat.TINY_2_FILE);
}
@Test
public void srgFile() throws Exception {
- write(MappingFormat.SRG_FILE);
+ check(MappingFormat.SRG_FILE);
}
@Test
public void xsrgFile() throws Exception {
- write(MappingFormat.XSRG_FILE);
+ check(MappingFormat.XSRG_FILE);
}
+ @Test
public void proguardFile() throws Exception {
- write(MappingFormat.PROGUARD_FILE);
+ check(MappingFormat.PROGUARD_FILE);
+ }
+
+ private void check(MappingFormat format) throws Exception {
+ dogfood(validTree, dir, format);
+ dogfood(validWithHolesTree, dir, format);
}
- private void write(MappingFormat format) throws Exception {
- TestHelper.writeToDir(tree, format, dir);
- TestHelper.writeToDir(treeWithHoles, format, dir);
+ private void dogfood(MappingTreeView origTree, Path outputPath, MappingFormat outputFormat) throws Exception {
+ outputPath = TestHelper.writeToDir(origTree, dir, outputFormat);
+ VisitableMappingTree writtenTree = new MemoryMappingTree();
+
+ MappingReader.read(outputPath, outputFormat, writtenTree);
+
+ writtenTree.accept(new FlatAsRegularMappingVisitor(new SubsetAssertingVisitor(origTree, null, outputFormat)));
+ origTree.accept(new FlatAsRegularMappingVisitor(new SubsetAssertingVisitor(writtenTree, outputFormat, null)));
}
}
From 1f021665a5c23e5c75e116e9f19e4c43397e13fd Mon Sep 17 00:00:00 2001
From: modmuss
Date: Thu, 30 Nov 2023 22:04:22 +0000
Subject: [PATCH 05/12] Bump version
---
CHANGELOG.md | 2 ++
gradle.properties | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 23708508..5b427a0c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
+
+## [0.5.1] - 2023-11-30
- Improved documentation
- Fixed NPE in `MemoryMappingTree`
- Fixed TSRG2 reader not handling multiple passes correctly
diff --git a/gradle.properties b/gradle.properties
index 5e8ba050..b370aeb5 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -2,7 +2,7 @@
org.gradle.jvmargs = -Xmx2G
# Project properties
-version = 0.5.0
+version = 0.5.1
# Dependencies
asm_version = 9.6
From 7e7e77ef06d155ccdf067463752e1ede702cb241 Mon Sep 17 00:00:00 2001
From: modmuss
Date: Thu, 30 Nov 2023 22:06:48 +0000
Subject: [PATCH 06/12] Update changelog
---
CHANGELOG.md | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5b427a0c..2d39ca3a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,9 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [0.5.1] - 2023-11-30
- Improved documentation
-- Fixed NPE in `MemoryMappingTree`
-- Fixed TSRG2 reader not handling multiple passes correctly
-- Fixed ProGuard writer producing invalid files when missing destination names are present
+- Fixed ProGuard writer producing invalid files when missing destination names
- Fixed Enigma reader throwing incorrect error message
- Fixed NPE in `MemoryMappingTree`
- Fixed TSRG2 reader not handling multiple passes correctly
From c772a56fe13c5278ed0a61f40ad79bea4ca40226 Mon Sep 17 00:00:00 2001
From: NebelNidas <48808497+NebelNidas@users.noreply.github.com>
Date: Thu, 7 Mar 2024 13:54:16 +0100
Subject: [PATCH 07/12] Add `MappingFormat#hasWriter` (#83)
* Add `MappingFormat#hasWriter`
* Add changes to changelog
---
CHANGELOG.md | 1 +
.../mappingio/format/MappingFormat.java | 29 +++++++++++--------
2 files changed, 18 insertions(+), 12 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2d39ca3a..d6ec59f2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
+- Added `MappingFormat#hasWriter` boolean
## [0.5.1] - 2023-11-30
- Improved documentation
diff --git a/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java b/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java
index c81e5249..22714ca6 100644
--- a/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java
+++ b/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java
@@ -19,7 +19,9 @@
import org.jetbrains.annotations.Nullable;
/**
- * Represents a supported mapping format. Feature comparison table:
+ * Represents a supported mapping format. Every format can be assumed to have an associated reader available.
+ *
+ * Feature comparison table:
*
*
* Format |
@@ -109,58 +111,59 @@ public enum MappingFormat {
/**
* The {@code Tiny} mapping format, as specified here.
*/
- TINY_FILE("Tiny file", "tiny", true, true, false, false, false),
+ TINY_FILE("Tiny file", "tiny", true, true, false, false, false, true),
/**
* The {@code Tiny v2} mapping format, as specified here.
*/
- TINY_2_FILE("Tiny v2 file", "tiny", true, true, true, true, true),
+ TINY_2_FILE("Tiny v2 file", "tiny", true, true, true, true, true, true),
/**
* Enigma's mapping format, as specified here.
*/
- ENIGMA_FILE("Enigma file", "mapping", false, true, true, true, false),
+ ENIGMA_FILE("Enigma file", "mapping", false, true, true, true, false, true),
/**
* Enigma's mapping format (in directory form), as specified here.
*/
- ENIGMA_DIR("Enigma directory", null, false, true, true, true, false),
+ ENIGMA_DIR("Enigma directory", null, false, true, true, true, false, true),
/**
* The {@code SRG} ("Searge RetroGuard") mapping format, as specified here.
*/
- SRG_FILE("SRG file", "srg", false, false, false, false, false),
+ SRG_FILE("SRG file", "srg", false, false, false, false, false, true),
/**
* The {@code XSRG} ("Extended SRG") mapping format, as specified here.
* Same as SRG, but with field descriptors.
*/
- XSRG_FILE("XSRG file", "xsrg", false, true, false, false, false),
+ XSRG_FILE("XSRG file", "xsrg", false, true, false, false, false, true),
/**
* The {@code CSRG} ("Compact SRG", since it saves disk space over SRG) mapping format, as specified here.
*/
- CSRG_FILE("CSRG file", "csrg", false, false, false, false, false),
+ CSRG_FILE("CSRG file", "csrg", false, false, false, false, false, false),
/**
* The {@code TSRG} ("Tiny SRG", since it saves disk space over SRG) mapping format, as specified here.
* Same as CSRG, but hierarchical instead of flat.
*/
- TSRG_FILE("TSRG file", "tsrg", false, false, false, false, false),
+ TSRG_FILE("TSRG file", "tsrg", false, false, false, false, false, false),
/**
* The {@code TSRG v2} mapping format, as specified here.
*/
- TSRG_2_FILE("TSRG2 file", "tsrg", true, true, false, true, false),
+ TSRG_2_FILE("TSRG2 file", "tsrg", true, true, false, true, false, false),
/**
* 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, true);
MappingFormat(String name, @Nullable String fileExt,
boolean hasNamespaces, boolean hasFieldDescriptors,
- boolean supportsComments, boolean supportsArgs, boolean supportsLocals) {
+ boolean supportsComments, boolean supportsArgs, boolean supportsLocals,
+ boolean hasWriter) {
this.name = name;
this.fileExt = fileExt;
this.hasNamespaces = hasNamespaces;
@@ -168,6 +171,7 @@ public enum MappingFormat {
this.supportsComments = supportsComments;
this.supportsArgs = supportsArgs;
this.supportsLocals = supportsLocals;
+ this.hasWriter = hasWriter;
}
public boolean hasSingleFile() {
@@ -188,4 +192,5 @@ public String getGlobPattern() {
public final boolean supportsComments;
public final boolean supportsArgs;
public final boolean supportsLocals;
+ public final boolean hasWriter;
}
From 1cd9184880e372b81c5f04ea3f1dc13f872fa174 Mon Sep 17 00:00:00 2001
From: NebelNidas <48808497+NebelNidas@users.noreply.github.com>
Date: Thu, 7 Mar 2024 16:42:21 +0100
Subject: [PATCH 08/12] Add Recaf Simple reader and writer (#58)
---
CHANGELOG.md | 1 +
.../net/fabricmc/mappingio/MappingReader.java | 6 +-
.../net/fabricmc/mappingio/MappingWriter.java | 2 +
.../mappingio/format/MappingFormat.java | 16 +-
.../format/simple/RecafSimpleFileReader.java | 139 ++++++++++++++++++
.../format/simple/RecafSimpleFileWriter.java | 139 ++++++++++++++++++
.../net/fabricmc/mappingio/TestHelper.java | 2 +
.../mappingio/read/DetectionTest.java | 6 +
.../mappingio/read/EmptyContentReadTest.java | 6 +
.../mappingio/read/ValidContentReadTest.java | 7 +
.../fabricmc/mappingio/write/WriteTest.java | 5 +
src/test/resources/detection/recaf-simple.txt | 2 +
.../read/valid-with-holes/recaf-simple.txt | 10 ++
.../resources/read/valid/recaf-simple.txt | 6 +
14 files changed, 345 insertions(+), 2 deletions(-)
create mode 100644 src/main/java/net/fabricmc/mappingio/format/simple/RecafSimpleFileReader.java
create mode 100644 src/main/java/net/fabricmc/mappingio/format/simple/RecafSimpleFileWriter.java
create mode 100644 src/test/resources/detection/recaf-simple.txt
create mode 100644 src/test/resources/read/valid-with-holes/recaf-simple.txt
create mode 100644 src/test/resources/read/valid/recaf-simple.txt
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d6ec59f2..bd60d251 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
+- Added Recaf Simple reader and writer
- Added `MappingFormat#hasWriter` boolean
## [0.5.1] - 2023-11-30
diff --git a/src/main/java/net/fabricmc/mappingio/MappingReader.java b/src/main/java/net/fabricmc/mappingio/MappingReader.java
index 0deae9bf..5642b8f5 100644
--- a/src/main/java/net/fabricmc/mappingio/MappingReader.java
+++ b/src/main/java/net/fabricmc/mappingio/MappingReader.java
@@ -32,6 +32,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;
@@ -95,7 +96,7 @@ public static MappingFormat detectFormat(Reader reader) throws IOException {
return MappingFormat.TSRG_FILE;
}
- // TODO: CSRG
+ // TODO: CSRG, Recaf Simple
return null; // unknown format or corrupted
}
@@ -269,6 +270,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/MappingWriter.java b/src/main/java/net/fabricmc/mappingio/MappingWriter.java
index 1cbd99e3..94832e3d 100644
--- a/src/main/java/net/fabricmc/mappingio/MappingWriter.java
+++ b/src/main/java/net/fabricmc/mappingio/MappingWriter.java
@@ -28,6 +28,7 @@
import net.fabricmc.mappingio.format.enigma.EnigmaDirWriter;
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.SrgFileWriter;
import net.fabricmc.mappingio.format.tiny.Tiny1FileWriter;
import net.fabricmc.mappingio.format.tiny.Tiny2FileWriter;
@@ -56,6 +57,7 @@ static MappingWriter create(Writer writer, MappingFormat format) throws IOExcept
case SRG_FILE: return new SrgFileWriter(writer, false);
case XSRG_FILE: return new SrgFileWriter(writer, true);
case PROGUARD_FILE: return new ProGuardFileWriter(writer);
+ case RECAF_SIMPLE_FILE: return new RecafSimpleFileWriter(writer);
default: return null;
}
}
diff --git a/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java b/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java
index 22714ca6..5d35cfcc 100644
--- a/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java
+++ b/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java
@@ -104,6 +104,15 @@
* - |
* - |
*
+ *
+ * Recaf Simple |
+ * - |
+ * src & dst |
+ * - |
+ * - |
+ * - |
+ * - |
+ *
*
*/
// Format order is determined by importance to Fabric tooling, format family and release order therein.
@@ -158,7 +167,12 @@ public enum MappingFormat {
/**
* ProGuard's mapping format, as specified here.
*/
- PROGUARD_FILE("ProGuard file", "txt", false, true, false, false, false, true);
+ PROGUARD_FILE("ProGuard file", "txt", false, true, false, false, false, true),
+
+ /**
+ * Recaf's {@code Simple} mapping format, as specified here.
+ */
+ RECAF_SIMPLE_FILE("Recaf Simple file", "txt", false, true, false, false, false, true);
MappingFormat(String name, @Nullable 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..4bff3ee1
--- /dev/null
+++ b/src/main/java/net/fabricmc/mappingio/format/simple/RecafSimpleFileReader.java
@@ -0,0 +1,139 @@
+/*
+ * 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.format.MappingFormat;
+import net.fabricmc.mappingio.tree.MappingTree;
+import net.fabricmc.mappingio.tree.MemoryMappingTree;
+
+/**
+ * {@linkplain MappingFormat#RECAF_SIMPLE Recaf Simple file} reader.
+ */
+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/main/java/net/fabricmc/mappingio/format/simple/RecafSimpleFileWriter.java b/src/main/java/net/fabricmc/mappingio/format/simple/RecafSimpleFileWriter.java
new file mode 100644
index 00000000..61f28b21
--- /dev/null
+++ b/src/main/java/net/fabricmc/mappingio/format/simple/RecafSimpleFileWriter.java
@@ -0,0 +1,139 @@
+/*
+ * 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.Writer;
+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;
+import net.fabricmc.mappingio.format.MappingFormat;
+
+/**
+ * {@linkplain MappingFormat#RECAF_SIMPLE Recaf Simple file} writer.
+ */
+public final class RecafSimpleFileWriter implements MappingWriter {
+ public RecafSimpleFileWriter(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;
+
+ return true;
+ }
+
+ @Override
+ public boolean visitField(String srcName, String srcDesc) throws IOException {
+ memberSrcName = srcName;
+ memberSrcDesc = srcDesc;
+
+ return true;
+ }
+
+ @Override
+ public boolean visitMethod(String srcName, String srcDesc) throws IOException {
+ memberSrcName = srcName;
+ memberSrcDesc = srcDesc;
+
+ return true;
+ }
+
+ @Override
+ public boolean visitMethodArg(int argPosition, int lvIndex, String srcName) throws IOException {
+ return false; // not supported, skip
+ }
+
+ @Override
+ public boolean visitMethodVar(int lvtRowIndex, int lvIndex, int startOpIdx, int endOpIdx, String srcName) throws IOException {
+ return false; // not supported, skip
+ }
+
+ @Override
+ public void visitDstName(MappedElementKind targetKind, int namespace, String name) {
+ if (namespace != 0) return;
+ dstName = name;
+ }
+
+ @Override
+ public boolean visitElementContent(MappedElementKind targetKind) throws IOException {
+ if (dstName == null) return true;
+ write(classSrcName);
+
+ if (targetKind != MappedElementKind.CLASS) {
+ if (memberSrcName == null) throw new IllegalArgumentException("member source name cannot be null!");
+ writer.write('.');
+ write(memberSrcName);
+
+ if (memberSrcDesc != null) {
+ if (targetKind == MappedElementKind.FIELD) writeSpace();
+ write(memberSrcDesc);
+ }
+ }
+
+ writeSpace();
+ write(dstName);
+ writeLn();
+ dstName = null;
+
+ return targetKind == MappedElementKind.CLASS; // only members are supported, skip anything but class contents
+ }
+
+ @Override
+ public void visitComment(MappedElementKind targetKind, String comment) throws IOException {
+ // not supported, skip
+ }
+
+ private void write(String str) throws IOException {
+ writer.write(str);
+ }
+
+ private void writeLn() throws IOException {
+ writer.write('\n');
+ }
+
+ private void writeSpace() throws IOException {
+ writer.write(' ');
+ }
+
+ private static final Set flags = EnumSet.of(MappingFlag.NEEDS_SRC_FIELD_DESC, MappingFlag.NEEDS_SRC_METHOD_DESC);
+
+ private final Writer writer;
+ private String classSrcName;
+ private String memberSrcName;
+ private String memberSrcDesc;
+ private String dstName;
+}
diff --git a/src/test/java/net/fabricmc/mappingio/TestHelper.java b/src/test/java/net/fabricmc/mappingio/TestHelper.java
index df7a5a0a..2d5cc115 100644
--- a/src/test/java/net/fabricmc/mappingio/TestHelper.java
+++ b/src/test/java/net/fabricmc/mappingio/TestHelper.java
@@ -61,6 +61,8 @@ public static String getFileName(MappingFormat format) {
return "tsrg2.tsrg";
case PROGUARD_FILE:
return "proguard.txt";
+ case RECAF_SIMPLE_FILE:
+ return "recaf-simple.txt";
default:
return null;
}
diff --git a/src/test/java/net/fabricmc/mappingio/read/DetectionTest.java b/src/test/java/net/fabricmc/mappingio/read/DetectionTest.java
index 69a6c706..88ded7a7 100644
--- a/src/test/java/net/fabricmc/mappingio/read/DetectionTest.java
+++ b/src/test/java/net/fabricmc/mappingio/read/DetectionTest.java
@@ -96,6 +96,12 @@ public void proguardFile() throws Exception {
check(format);
}
+ @Test
+ public void recafSimpleFile() throws Exception {
+ MappingFormat format = MappingFormat.RECAF_SIMPLE_FILE;
+ assertThrows(AssertionFailedError.class, () -> check(format));
+ }
+
private void check(MappingFormat format) throws Exception {
Path path = dir.resolve(TestHelper.getFileName(format));
assertEquals(format, MappingReader.detectFormat(path));
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 fe1a4279..329279f0 100644
--- a/src/test/java/net/fabricmc/mappingio/read/ValidContentReadTest.java
+++ b/src/test/java/net/fabricmc/mappingio/read/ValidContentReadTest.java
@@ -109,6 +109,13 @@ public void proguardFile() throws Exception {
checkHoles(format);
}
+ @Test
+ public void recafSimpleFile() throws Exception {
+ MappingFormat format = MappingFormat.RECAF_SIMPLE_FILE;
+ checkDefault(format);
+ checkHoles(format);
+ }
+
private VisitableMappingTree checkDefault(MappingFormat format) throws Exception {
VisitableMappingTree tree = new MemoryMappingTree();
MappingReader.read(TestHelper.MappingDirs.VALID.resolve(TestHelper.getFileName(format)), format, tree);
diff --git a/src/test/java/net/fabricmc/mappingio/write/WriteTest.java b/src/test/java/net/fabricmc/mappingio/write/WriteTest.java
index 4daed966..dc8083f6 100644
--- a/src/test/java/net/fabricmc/mappingio/write/WriteTest.java
+++ b/src/test/java/net/fabricmc/mappingio/write/WriteTest.java
@@ -78,6 +78,11 @@ public void proguardFile() throws Exception {
check(MappingFormat.PROGUARD_FILE);
}
+ @Test
+ public void recafSimpleFile() throws Exception {
+ check(MappingFormat.RECAF_SIMPLE_FILE);
+ }
+
private void check(MappingFormat format) throws Exception {
dogfood(validTree, dir, format);
dogfood(validWithHolesTree, dir, format);
diff --git a/src/test/resources/detection/recaf-simple.txt b/src/test/resources/detection/recaf-simple.txt
new file mode 100644
index 00000000..ebe67c6e
--- /dev/null
+++ b/src/test/resources/detection/recaf-simple.txt
@@ -0,0 +1,2 @@
+class_1 RenamedClass
+class_1.field_1 I renamedField
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..03d02a35
--- /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 I field2Ns0Rename
+class_32.field_5 I 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
From f7ace9466ee1847a27a55c0ab23c94c9670cf062 Mon Sep 17 00:00:00 2001
From: NebelNidas <48808497+NebelNidas@users.noreply.github.com>
Date: Thu, 7 Mar 2024 16:45:27 +0100
Subject: [PATCH 09/12] Add CSRG, TSRG and TSRG2 writers (#86)
---
CHANGELOG.md | 2 +
.../net/fabricmc/mappingio/MappingWriter.java | 5 +
.../mappingio/format/MappingFormat.java | 6 +-
.../mappingio/format/srg/CsrgFileWriter.java | 145 +++++++++++++
.../mappingio/format/srg/TsrgFileWriter.java | 202 ++++++++++++++++++
.../fabricmc/mappingio/write/WriteTest.java | 15 ++
6 files changed, 372 insertions(+), 3 deletions(-)
create mode 100644 src/main/java/net/fabricmc/mappingio/format/srg/CsrgFileWriter.java
create mode 100644 src/main/java/net/fabricmc/mappingio/format/srg/TsrgFileWriter.java
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bd60d251..60545ba7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
+- Added CSRG writer
+- Added TSRG and TSRG2 writer
- Added Recaf Simple reader and writer
- Added `MappingFormat#hasWriter` boolean
diff --git a/src/main/java/net/fabricmc/mappingio/MappingWriter.java b/src/main/java/net/fabricmc/mappingio/MappingWriter.java
index 94832e3d..0e973ee4 100644
--- a/src/main/java/net/fabricmc/mappingio/MappingWriter.java
+++ b/src/main/java/net/fabricmc/mappingio/MappingWriter.java
@@ -29,7 +29,9 @@
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.CsrgFileWriter;
import net.fabricmc.mappingio.format.srg.SrgFileWriter;
+import net.fabricmc.mappingio.format.srg.TsrgFileWriter;
import net.fabricmc.mappingio.format.tiny.Tiny1FileWriter;
import net.fabricmc.mappingio.format.tiny.Tiny2FileWriter;
@@ -56,6 +58,9 @@ 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 CSRG_FILE: return new CsrgFileWriter(writer);
+ case TSRG_FILE: return new TsrgFileWriter(writer, false);
+ case TSRG_2_FILE: return new TsrgFileWriter(writer, true);
case PROGUARD_FILE: return new ProGuardFileWriter(writer);
case RECAF_SIMPLE_FILE: return new RecafSimpleFileWriter(writer);
default: return null;
diff --git a/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java b/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java
index 5d35cfcc..73ab0a50 100644
--- a/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java
+++ b/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java
@@ -151,18 +151,18 @@ public enum MappingFormat {
/**
* The {@code CSRG} ("Compact SRG", since it saves disk space over SRG) mapping format, as specified here.
*/
- CSRG_FILE("CSRG file", "csrg", false, false, false, false, false, false),
+ CSRG_FILE("CSRG file", "csrg", false, false, false, false, false, true),
/**
* The {@code TSRG} ("Tiny SRG", since it saves disk space over SRG) mapping format, as specified here.
* Same as CSRG, but hierarchical instead of flat.
*/
- TSRG_FILE("TSRG file", "tsrg", false, false, false, false, false, false),
+ TSRG_FILE("TSRG file", "tsrg", false, false, false, false, false, true),
/**
* The {@code TSRG v2} mapping format, as specified here.
*/
- TSRG_2_FILE("TSRG2 file", "tsrg", true, true, false, true, false, false),
+ TSRG_2_FILE("TSRG2 file", "tsrg", true, true, false, true, false, true),
/**
* ProGuard's mapping format, as specified here.
diff --git a/src/main/java/net/fabricmc/mappingio/format/srg/CsrgFileWriter.java b/src/main/java/net/fabricmc/mappingio/format/srg/CsrgFileWriter.java
new file mode 100644
index 00000000..a434d4cf
--- /dev/null
+++ b/src/main/java/net/fabricmc/mappingio/format/srg/CsrgFileWriter.java
@@ -0,0 +1,145 @@
+/*
+ * 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;
+import net.fabricmc.mappingio.format.MappingFormat;
+
+/**
+ * {@linkplain MappingFormat#CSRG_FILE CSRG file} writer.
+ */
+public final class CsrgFileWriter implements MappingWriter {
+ public CsrgFileWriter(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 {
+ // not supported, skip
+ }
+
+ @Override
+ public boolean visitClass(String srcName) throws IOException {
+ classSrcName = srcName;
+
+ return true;
+ }
+
+ @Override
+ public boolean visitField(String srcName, @Nullable String srcDesc) throws IOException {
+ memberSrcName = srcName;
+
+ return true;
+ }
+
+ @Override
+ public boolean visitMethod(String srcName, @Nullable String srcDesc) throws IOException {
+ memberSrcName = srcName;
+ methodSrcDesc = srcDesc;
+
+ return true;
+ }
+
+ @Override
+ public boolean visitMethodArg(int argPosition, int lvIndex, @Nullable String srcName) throws IOException {
+ return false; // not supported, skip
+ }
+
+ @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;
+
+ dstName = name;
+ }
+
+ @Override
+ public boolean visitElementContent(MappedElementKind targetKind) throws IOException {
+ if (dstName == null) return false;
+
+ write(classSrcName);
+
+ if (targetKind != MappedElementKind.CLASS) {
+ writeSpace();
+ write(memberSrcName);
+
+ if (targetKind == MappedElementKind.METHOD) {
+ writeSpace();
+ write(methodSrcDesc);
+ }
+
+ memberSrcName = methodSrcDesc = null;
+ }
+
+ writeSpace();
+ write(dstName);
+ writeLn();
+
+ dstName = null;
+
+ return targetKind == MappedElementKind.CLASS; // only members are supported, skip anything but class contents
+ }
+
+ @Override
+ public void visitComment(MappedElementKind targetKind, String comment) throws IOException {
+ // not supported, skip
+ }
+
+ 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_METHOD_DESC);
+
+ private final Writer writer;
+ private String classSrcName;
+ private String memberSrcName;
+ private String methodSrcDesc;
+ private String dstName;
+}
diff --git a/src/main/java/net/fabricmc/mappingio/format/srg/TsrgFileWriter.java b/src/main/java/net/fabricmc/mappingio/format/srg/TsrgFileWriter.java
new file mode 100644
index 00000000..e1726e73
--- /dev/null
+++ b/src/main/java/net/fabricmc/mappingio/format/srg/TsrgFileWriter.java
@@ -0,0 +1,202 @@
+/*
+ * 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.Arrays;
+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;
+import net.fabricmc.mappingio.format.MappingFormat;
+
+/**
+ * {@linkplain MappingFormat#TSRG_FILE TSRG file} and
+ * {@linkplain MappingFormat#TSRG_2_FILE TSRG2 file} writer.
+ */
+public final class TsrgFileWriter implements MappingWriter {
+ public TsrgFileWriter(Writer writer, boolean tsrg2) {
+ this.writer = writer;
+ this.tsrg2 = tsrg2;
+ }
+
+ @Override
+ public void close() throws IOException {
+ writer.close();
+ }
+
+ @Override
+ public Set getFlags() {
+ return tsrg2 ? tsrg2Flags : tsrgFlags;
+ }
+
+ @Override
+ public void visitNamespaces(String srcNamespace, List dstNamespaces) throws IOException {
+ dstNames = new String[dstNamespaces.size()];
+
+ if (tsrg2) {
+ write("tsrg2 ");
+ write(srcNamespace);
+
+ for (String dstNamespace : dstNamespaces) {
+ writeSpace();
+ write(dstNamespace);
+ }
+
+ writeLn();
+ }
+ }
+
+ @Override
+ public void visitMetadata(String key, @Nullable String value) throws IOException {
+ // TODO: Support the static method marker once https://github.com/FabricMC/mapping-io/pull/41 is merged
+ }
+
+ @Override
+ public boolean visitClass(String srcName) throws IOException {
+ this.srcName = srcName;
+
+ return true;
+ }
+
+ @Override
+ public boolean visitField(String srcName, @Nullable String srcDesc) throws IOException {
+ this.srcName = srcName;
+ this.srcDesc = srcDesc;
+
+ return true;
+ }
+
+ @Override
+ public boolean visitMethod(String srcName, @Nullable String srcDesc) throws IOException {
+ this.srcName = srcName;
+ this.srcDesc = srcDesc;
+
+ return true;
+ }
+
+ @Override
+ public boolean visitMethodArg(int argPosition, int lvIndex, @Nullable String srcName) throws IOException {
+ if (tsrg2) {
+ this.srcName = srcName;
+ this.lvIndex = lvIndex;
+ return true;
+ }
+
+ return false;
+ }
+
+ @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 (!tsrg2 && namespace != 0) return;
+
+ dstNames[namespace] = name;
+ }
+
+ @Override
+ public boolean visitElementContent(MappedElementKind targetKind) throws IOException {
+ switch (targetKind) {
+ case CLASS:
+ break;
+ case FIELD:
+ case METHOD:
+ writeTab();
+ break;
+ case METHOD_ARG:
+ assert tsrg2;
+ writeTab();
+ writeTab();
+ write(Integer.toString(lvIndex));
+ writeSpace();
+ case METHOD_VAR:
+ assert tsrg2;
+ break;
+ }
+
+ write(srcName);
+
+ if (targetKind == MappedElementKind.METHOD
+ || (targetKind == MappedElementKind.FIELD && tsrg2)) {
+ writeSpace();
+ write(srcDesc);
+ }
+
+ int dstNsCount = tsrg2 ? dstNames.length : 1;
+
+ for (int i = 0; i < dstNsCount; i++) {
+ String dstName = dstNames[i];
+ writeSpace();
+ write(dstName != null ? dstName : srcName);
+ }
+
+ writeLn();
+
+ srcName = srcDesc = null;
+ Arrays.fill(dstNames, null);
+ lvIndex = -1;
+
+ return targetKind == MappedElementKind.CLASS
+ || (tsrg2 && targetKind == MappedElementKind.METHOD);
+ }
+
+ @Override
+ public void visitComment(MappedElementKind targetKind, String comment) throws IOException {
+ // not supported, skip
+ }
+
+ private void write(String str) throws IOException {
+ writer.write(str);
+ }
+
+ private void writeTab() throws IOException {
+ writer.write('\t');
+ }
+
+ private void writeSpace() throws IOException {
+ writer.write(' ');
+ }
+
+ private void writeLn() throws IOException {
+ writer.write('\n');
+ }
+
+ private static final Set tsrgFlags = EnumSet.of(MappingFlag.NEEDS_ELEMENT_UNIQUENESS, MappingFlag.NEEDS_SRC_METHOD_DESC);
+ private static final Set tsrg2Flags;
+
+ static {
+ tsrg2Flags = EnumSet.copyOf(tsrgFlags);
+ tsrg2Flags.add(MappingFlag.NEEDS_SRC_FIELD_DESC);
+ }
+
+ private final Writer writer;
+ private final boolean tsrg2;
+ private String srcName;
+ private String srcDesc;
+ private String[] dstNames;
+ private int lvIndex = -1;
+}
diff --git a/src/test/java/net/fabricmc/mappingio/write/WriteTest.java b/src/test/java/net/fabricmc/mappingio/write/WriteTest.java
index dc8083f6..425101c6 100644
--- a/src/test/java/net/fabricmc/mappingio/write/WriteTest.java
+++ b/src/test/java/net/fabricmc/mappingio/write/WriteTest.java
@@ -73,6 +73,21 @@ public void xsrgFile() throws Exception {
check(MappingFormat.XSRG_FILE);
}
+ @Test
+ public void csrgFile() throws Exception {
+ check(MappingFormat.CSRG_FILE);
+ }
+
+ @Test
+ public void tsrgFile() throws Exception {
+ check(MappingFormat.TSRG_FILE);
+ }
+
+ @Test
+ public void tsrg2File() throws Exception {
+ check(MappingFormat.TSRG_2_FILE);
+ }
+
@Test
public void proguardFile() throws Exception {
check(MappingFormat.PROGUARD_FILE);
From 8f976ac70d769c5230c5bac9c37dc8be05f9213a Mon Sep 17 00:00:00 2001
From: NebelNidas <48808497+NebelNidas@users.noreply.github.com>
Date: Thu, 7 Mar 2024 16:51:35 +0100
Subject: [PATCH 10/12] Add JAM reader and writer (#90)
---
CHANGELOG.md | 1 +
.../net/fabricmc/mappingio/MappingReader.java | 11 +-
.../net/fabricmc/mappingio/MappingWriter.java | 2 +
.../mappingio/format/MappingFormat.java | 22 +-
.../mappingio/format/srg/JamFileReader.java | 169 ++++++++++++++
.../mappingio/format/srg/JamFileWriter.java | 211 ++++++++++++++++++
.../mappingio/format/srg/SrgFileWriter.java | 6 +-
.../net/fabricmc/mappingio/TestHelper.java | 2 +
.../mappingio/read/DetectionTest.java | 6 +
.../mappingio/read/EmptyContentReadTest.java | 6 +
.../mappingio/read/ValidContentReadTest.java | 7 +
.../mappingio/visiting/VisitEndTest.java | 6 +
.../fabricmc/mappingio/write/WriteTest.java | 5 +
src/test/resources/detection/jam.jam | 1 +
.../resources/read/valid-with-holes/jam.jam | 10 +
src/test/resources/read/valid/jam.jam | 7 +
16 files changed, 464 insertions(+), 8 deletions(-)
create mode 100644 src/main/java/net/fabricmc/mappingio/format/srg/JamFileReader.java
create mode 100644 src/main/java/net/fabricmc/mappingio/format/srg/JamFileWriter.java
create mode 100644 src/test/resources/detection/jam.jam
create mode 100644 src/test/resources/read/valid-with-holes/jam.jam
create mode 100644 src/test/resources/read/valid/jam.jam
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
From 9c627c7bc16cc3c0c503ebfdcd6cc7d35dc6bfce Mon Sep 17 00:00:00 2001
From: NebelNidas <48808497+NebelNidas@users.noreply.github.com>
Date: Thu, 7 Mar 2024 17:09:10 +0100
Subject: [PATCH 11/12] Add JOBF reader and writer (#91)
---
CHANGELOG.md | 1 +
.../net/fabricmc/mappingio/MappingReader.java | 12 +-
.../net/fabricmc/mappingio/MappingWriter.java | 2 +
.../mappingio/format/MappingFormat.java | 16 +-
.../mappingio/format/jobf/JobfFileReader.java | 147 +++++++++++++++++
.../mappingio/format/jobf/JobfFileWriter.java | 150 ++++++++++++++++++
.../net/fabricmc/mappingio/TestHelper.java | 2 +
.../mappingio/read/DetectionTest.java | 6 +
.../mappingio/read/EmptyContentReadTest.java | 6 +
.../mappingio/read/ValidContentReadTest.java | 7 +
.../mappingio/visiting/VisitEndTest.java | 6 +
.../fabricmc/mappingio/write/WriteTest.java | 5 +
src/test/resources/detection/jobf.jobf | 1 +
.../resources/read/valid-with-holes/jobf.jobf | 10 ++
src/test/resources/read/valid/jobf.jobf | 6 +
15 files changed, 375 insertions(+), 2 deletions(-)
create mode 100644 src/main/java/net/fabricmc/mappingio/format/jobf/JobfFileReader.java
create mode 100644 src/main/java/net/fabricmc/mappingio/format/jobf/JobfFileWriter.java
create mode 100644 src/test/resources/detection/jobf.jobf
create mode 100644 src/test/resources/read/valid-with-holes/jobf.jobf
create mode 100644 src/test/resources/read/valid/jobf.jobf
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ad172178..4b65a66d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added CSRG writer
- Added TSRG and TSRG2 writer
- Added JAM reader and writer
+- Added JOBF 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 17d1c685..d9aba106 100644
--- a/src/main/java/net/fabricmc/mappingio/MappingReader.java
+++ b/src/main/java/net/fabricmc/mappingio/MappingReader.java
@@ -31,6 +31,7 @@
import net.fabricmc.mappingio.format.MappingFormat;
import net.fabricmc.mappingio.format.enigma.EnigmaDirReader;
import net.fabricmc.mappingio.format.enigma.EnigmaFileReader;
+import net.fabricmc.mappingio.format.jobf.JobfFileReader;
import net.fabricmc.mappingio.format.proguard.ProGuardFileReader;
import net.fabricmc.mappingio.format.simple.RecafSimpleFileReader;
import net.fabricmc.mappingio.format.srg.JamFileReader;
@@ -96,7 +97,13 @@ public static MappingFormat detectFormat(Reader reader) throws IOException {
String headerStr = String.valueOf(buffer, 0, pos);
- if (headerStr.contains(" -> ")) {
+ if ((headerStr.startsWith("p ")
+ || headerStr.startsWith("c ")
+ || headerStr.startsWith("f ")
+ || headerStr.startsWith("m "))
+ && headerStr.contains(" = ")) {
+ return MappingFormat.JOBF_FILE;
+ } else if (headerStr.contains(" -> ")) {
return MappingFormat.PROGUARD_FILE;
} else if (headerStr.contains("\n\t")) {
return MappingFormat.TSRG_FILE;
@@ -282,6 +289,9 @@ public static void read(Reader reader, MappingFormat format, MappingVisitor visi
case RECAF_SIMPLE_FILE:
RecafSimpleFileReader.read(reader, visitor);
break;
+ case JOBF_FILE:
+ JobfFileReader.read(reader, visitor);
+ break;
default:
throw new IllegalStateException();
}
diff --git a/src/main/java/net/fabricmc/mappingio/MappingWriter.java b/src/main/java/net/fabricmc/mappingio/MappingWriter.java
index b5a5d3f3..f7a11cf9 100644
--- a/src/main/java/net/fabricmc/mappingio/MappingWriter.java
+++ b/src/main/java/net/fabricmc/mappingio/MappingWriter.java
@@ -27,6 +27,7 @@
import net.fabricmc.mappingio.format.MappingFormat;
import net.fabricmc.mappingio.format.enigma.EnigmaDirWriter;
import net.fabricmc.mappingio.format.enigma.EnigmaFileWriter;
+import net.fabricmc.mappingio.format.jobf.JobfFileWriter;
import net.fabricmc.mappingio.format.proguard.ProGuardFileWriter;
import net.fabricmc.mappingio.format.simple.RecafSimpleFileWriter;
import net.fabricmc.mappingio.format.srg.JamFileWriter;
@@ -65,6 +66,7 @@ static MappingWriter create(Writer writer, MappingFormat format) throws IOExcept
case TSRG_2_FILE: return new TsrgFileWriter(writer, true);
case PROGUARD_FILE: return new ProGuardFileWriter(writer);
case RECAF_SIMPLE_FILE: return new RecafSimpleFileWriter(writer);
+ case JOBF_FILE: return new JobfFileWriter(writer);
default: return null;
}
}
diff --git a/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java b/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java
index f7e8a5a6..4ecf455a 100644
--- a/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java
+++ b/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java
@@ -122,6 +122,15 @@
* - |
* - |
*
+ *
+ * JOBF |
+ * - |
+ * src |
+ * - |
+ * - |
+ * - |
+ * - |
+ *
*
*/
// Format order is determined by importance to Fabric tooling, format family and release order therein.
@@ -186,7 +195,12 @@ public enum MappingFormat {
/**
* Recaf's {@code Simple} mapping format, as specified here.
*/
- RECAF_SIMPLE_FILE("Recaf Simple file", "txt", false, true, false, false, false, true);
+ RECAF_SIMPLE_FILE("Recaf Simple file", "txt", false, true, false, false, false, true),
+
+ /**
+ * The {@code JOBF} mapping format, as specified here.
+ */
+ JOBF_FILE("JOBF file", "jobf", false, true, false, false, false, true);
MappingFormat(String name, @Nullable String fileExt,
boolean hasNamespaces, boolean hasFieldDescriptors,
diff --git a/src/main/java/net/fabricmc/mappingio/format/jobf/JobfFileReader.java b/src/main/java/net/fabricmc/mappingio/format/jobf/JobfFileReader.java
new file mode 100644
index 00000000..76b4a842
--- /dev/null
+++ b/src/main/java/net/fabricmc/mappingio/format/jobf/JobfFileReader.java
@@ -0,0 +1,147 @@
+/*
+ * 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.jobf;
+
+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#JOBF_FILE JOBF file} reader.
+ */
+public class JobfFileReader {
+ 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 isField;
+
+ if (reader.nextCol("c")) { // class: c =
+ String srcName = reader.nextCol();
+ if (srcName == null || srcName.isEmpty()) throw new IOException("missing class-name-a in line "+reader.getLineNumber());
+ srcName = srcName.replace('.', '/');
+
+ if (!srcName.equals(lastClass)) {
+ lastClass = srcName;
+ visitLastClass = visitor.visitClass(srcName);
+
+ if (visitLastClass) {
+ readSeparator(reader);
+
+ 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 ((isField = reader.nextCol("f")) || reader.nextCol("m")) {
+ // field: f .: =
+ // method: m . =
+ String src = reader.nextCol();
+ if (src == null || src.isEmpty()) throw new IOException("missing class/name/desc a in line "+reader.getLineNumber());
+
+ int nameSepPos = src.lastIndexOf('.');
+ if (nameSepPos <= 0 || nameSepPos == src.length() - 1) throw new IOException("invalid class/name/desc a in line "+reader.getLineNumber());
+
+ int descSepPos = src.lastIndexOf(isField ? ':' : '(');
+ if (descSepPos <= 0 || descSepPos == src.length() - 1) throw new IOException("invalid name/desc a in line "+reader.getLineNumber());
+
+ readSeparator(reader);
+
+ String dstName = reader.nextCol();
+ if (dstName == null || dstName.isEmpty()) throw new IOException("missing name-b in line "+reader.getLineNumber());
+
+ String srcOwner = src.substring(0, nameSepPos).replace('.', '/');
+
+ if (!srcOwner.equals(lastClass)) {
+ lastClass = srcOwner;
+ visitLastClass = visitor.visitClass(srcOwner);
+
+ if (visitLastClass) {
+ visitLastClass = visitor.visitElementContent(MappedElementKind.CLASS);
+ }
+ }
+
+ if (visitLastClass) {
+ String srcName = src.substring(nameSepPos + 1, descSepPos);
+ String srcDesc = src.substring(descSepPos + (isField ? 1 : 0));
+
+ if (isField && visitor.visitField(srcName, srcDesc)
+ || !isField && visitor.visitMethod(srcName, srcDesc)) {
+ MappedElementKind kind = isField ? MappedElementKind.FIELD : MappedElementKind.METHOD;
+ visitor.visitDstName(kind, 0, dstName);
+ visitor.visitElementContent(kind);
+ }
+ }
+ } else if (reader.nextCol("p")) { // package: p =
+ // TODO
+ }
+ } while (reader.nextLine(0));
+ }
+
+ if (visitor.visitEnd()) break;
+
+ reader.reset();
+ }
+
+ if (parentVisitor != null) {
+ ((MappingTree) visitor).accept(parentVisitor);
+ }
+ }
+
+ private static void readSeparator(ColumnFileReader reader) throws IOException {
+ if (!reader.nextCol("=")) {
+ throw new IOException("missing separator in line "+reader.getLineNumber()+" (expected \" = \")");
+ }
+ }
+}
diff --git a/src/main/java/net/fabricmc/mappingio/format/jobf/JobfFileWriter.java b/src/main/java/net/fabricmc/mappingio/format/jobf/JobfFileWriter.java
new file mode 100644
index 00000000..c3dcd8ff
--- /dev/null
+++ b/src/main/java/net/fabricmc/mappingio/format/jobf/JobfFileWriter.java
@@ -0,0 +1,150 @@
+/*
+ * 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.jobf;
+
+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;
+import net.fabricmc.mappingio.format.MappingFormat;
+
+/**
+ * {@linkplain MappingFormat#JOBF_FILE JOBF file} writer.
+ */
+public final class JobfFileWriter implements MappingWriter {
+ public JobfFileWriter(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.replace('/', '.');
+
+ return true;
+ }
+
+ @Override
+ public boolean visitField(String srcName, @Nullable String srcDesc) throws IOException {
+ memberSrcName = srcName;
+ memberSrcDesc = srcDesc;
+
+ return true;
+ }
+
+ @Override
+ public boolean visitMethod(String srcName, @Nullable String srcDesc) throws IOException {
+ memberSrcName = srcName;
+ memberSrcDesc = srcDesc;
+
+ return true;
+ }
+
+ @Override
+ public boolean visitMethodArg(int argPosition, int lvIndex, @Nullable String srcName) throws IOException {
+ return false; // not supported, skip
+ }
+
+ @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;
+
+ dstName = name;
+ }
+
+ @Override
+ public boolean visitElementContent(MappedElementKind targetKind) throws IOException {
+ boolean isClass = targetKind == MappedElementKind.CLASS;
+ boolean isField = false;
+
+ if (dstName == null) return isClass;
+
+ if ((isClass)) {
+ write("c ");
+ } else if ((isField = targetKind == MappedElementKind.FIELD)
+ || targetKind == MappedElementKind.METHOD) {
+ if (memberSrcDesc == null) return false;
+ write(isField ? "f " : "m ");
+ } else {
+ throw new IllegalStateException("unexpected invocation for "+targetKind);
+ }
+
+ write(classSrcName);
+
+ if (!isClass) {
+ write(".");
+ write(memberSrcName);
+
+ if (isField) write(":");
+ write(memberSrcDesc);
+ }
+
+ write(" = ");
+ write(dstName);
+
+ writeLn();
+
+ dstName = null;
+ return isClass; // only members are supported, skip anything but class contents
+ }
+
+ @Override
+ public void visitComment(MappedElementKind targetKind, String comment) throws IOException {
+ // not supported, skip
+ }
+
+ private void write(String str) throws IOException {
+ writer.write(str);
+ }
+
+ 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);
+
+ private final Writer writer;
+ private String classSrcName;
+ private String memberSrcName;
+ private String memberSrcDesc;
+ private String dstName;
+}
diff --git a/src/test/java/net/fabricmc/mappingio/TestHelper.java b/src/test/java/net/fabricmc/mappingio/TestHelper.java
index 1157d705..ad7c1e0f 100644
--- a/src/test/java/net/fabricmc/mappingio/TestHelper.java
+++ b/src/test/java/net/fabricmc/mappingio/TestHelper.java
@@ -65,6 +65,8 @@ public static String getFileName(MappingFormat format) {
return "proguard.txt";
case RECAF_SIMPLE_FILE:
return "recaf-simple.txt";
+ case JOBF_FILE:
+ return "jobf.jobf";
default:
return null;
}
diff --git a/src/test/java/net/fabricmc/mappingio/read/DetectionTest.java b/src/test/java/net/fabricmc/mappingio/read/DetectionTest.java
index ad727170..c75e3a33 100644
--- a/src/test/java/net/fabricmc/mappingio/read/DetectionTest.java
+++ b/src/test/java/net/fabricmc/mappingio/read/DetectionTest.java
@@ -108,6 +108,12 @@ public void recafSimpleFile() throws Exception {
assertThrows(AssertionFailedError.class, () -> check(format));
}
+ @Test
+ public void jobfFile() throws Exception {
+ MappingFormat format = MappingFormat.JOBF_FILE;
+ check(format);
+ }
+
private void check(MappingFormat format) throws Exception {
Path path = dir.resolve(TestHelper.getFileName(format));
assertEquals(format, MappingReader.detectFormat(path));
diff --git a/src/test/java/net/fabricmc/mappingio/read/EmptyContentReadTest.java b/src/test/java/net/fabricmc/mappingio/read/EmptyContentReadTest.java
index 6aea7581..1eef51a8 100644
--- a/src/test/java/net/fabricmc/mappingio/read/EmptyContentReadTest.java
+++ b/src/test/java/net/fabricmc/mappingio/read/EmptyContentReadTest.java
@@ -24,6 +24,7 @@
import org.junit.jupiter.api.Test;
import net.fabricmc.mappingio.format.enigma.EnigmaFileReader;
+import net.fabricmc.mappingio.format.jobf.JobfFileReader;
import net.fabricmc.mappingio.format.proguard.ProGuardFileReader;
import net.fabricmc.mappingio.format.simple.RecafSimpleFileReader;
import net.fabricmc.mappingio.format.srg.JamFileReader;
@@ -76,4 +77,9 @@ public void emptyTsrgFile() throws Exception {
public void emptyRecafSimpleFile() throws Exception {
RecafSimpleFileReader.read(new StringReader(""), tree);
}
+
+ @Test
+ public void emptyJobfFile() throws Exception {
+ JobfFileReader.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 4aa5670d..e0fa4e3e 100644
--- a/src/test/java/net/fabricmc/mappingio/read/ValidContentReadTest.java
+++ b/src/test/java/net/fabricmc/mappingio/read/ValidContentReadTest.java
@@ -123,6 +123,13 @@ public void recafSimpleFile() throws Exception {
checkHoles(format);
}
+ @Test
+ public void jobfFile() throws Exception {
+ MappingFormat format = MappingFormat.JOBF_FILE;
+ checkDefault(format);
+ checkHoles(format);
+ }
+
private VisitableMappingTree checkDefault(MappingFormat format) throws Exception {
VisitableMappingTree tree = new MemoryMappingTree();
MappingReader.read(TestHelper.MappingDirs.VALID.resolve(TestHelper.getFileName(format)), format, tree);
diff --git a/src/test/java/net/fabricmc/mappingio/visiting/VisitEndTest.java b/src/test/java/net/fabricmc/mappingio/visiting/VisitEndTest.java
index 58a8ab96..11a94457 100644
--- a/src/test/java/net/fabricmc/mappingio/visiting/VisitEndTest.java
+++ b/src/test/java/net/fabricmc/mappingio/visiting/VisitEndTest.java
@@ -106,6 +106,12 @@ public void proguardFile() throws Exception {
check(format);
}
+ @Test
+ public void jobfFile() throws Exception {
+ MappingFormat format = MappingFormat.JOBF_FILE;
+ check(format);
+ }
+
private void check(MappingFormat format) throws Exception {
checkDir(TestHelper.MappingDirs.DETECTION, format);
checkDir(TestHelper.MappingDirs.VALID, format);
diff --git a/src/test/java/net/fabricmc/mappingio/write/WriteTest.java b/src/test/java/net/fabricmc/mappingio/write/WriteTest.java
index 02ab4994..0fc6bdb2 100644
--- a/src/test/java/net/fabricmc/mappingio/write/WriteTest.java
+++ b/src/test/java/net/fabricmc/mappingio/write/WriteTest.java
@@ -103,6 +103,11 @@ public void recafSimpleFile() throws Exception {
check(MappingFormat.RECAF_SIMPLE_FILE);
}
+ @Test
+ public void jobfFile() throws Exception {
+ check(MappingFormat.JOBF_FILE);
+ }
+
private void check(MappingFormat format) throws Exception {
dogfood(validTree, dir, format);
dogfood(validWithHolesTree, dir, format);
diff --git a/src/test/resources/detection/jobf.jobf b/src/test/resources/detection/jobf.jobf
new file mode 100644
index 00000000..193f195f
--- /dev/null
+++ b/src/test/resources/detection/jobf.jobf
@@ -0,0 +1 @@
+c class_1 = RenamedClass
diff --git a/src/test/resources/read/valid-with-holes/jobf.jobf b/src/test/resources/read/valid-with-holes/jobf.jobf
new file mode 100644
index 00000000..9825b88d
--- /dev/null
+++ b/src/test/resources/read/valid-with-holes/jobf.jobf
@@ -0,0 +1,10 @@
+c class_2 = class2Ns0Rename
+c class_5 = class5Ns0Rename
+c class_7$class_8 = class_7$class8Ns0Rename
+c class_13$class_14 = class_13$class14Ns0Rename
+c class_17$class_18$class_19 = class_17$class_18$class19Ns0Rename
+c class_26$class_27$class_28 = class_26$class_27$class28Ns0Rename
+f class_32.field_2:I = field2Ns0Rename
+f class_32.field_5:I = field5Ns0Rename
+m class_32.method_2()I = method2Ns0Rename
+m class_32.method_5()I = method5Ns0Rename
diff --git a/src/test/resources/read/valid/jobf.jobf b/src/test/resources/read/valid/jobf.jobf
new file mode 100644
index 00000000..cb332365
--- /dev/null
+++ b/src/test/resources/read/valid/jobf.jobf
@@ -0,0 +1,6 @@
+c class_1 = class1Ns0Rename
+f class_1.field_1:I = field1Ns0Rename
+m class_1.method_1()I = method1Ns0Rename
+c class_1$class_2 = class1Ns0Rename$class2Ns0Rename
+f class_1$class_2.field_2:I = field2Ns0Rename
+c class_3 = class3Ns0Rename
From 1046304d933dd4927cd8ed59c88de8e7a19404f5 Mon Sep 17 00:00:00 2001
From: NebelNidas <48808497+NebelNidas@users.noreply.github.com>
Date: Thu, 7 Mar 2024 17:24:36 +0100
Subject: [PATCH 12/12] Add outer class name inheriting visitor (#89)
---
CHANGELOG.md | 1 +
.../OuterClassNameInheritingVisitor.java | 203 +++++++++++++
.../OuterClassNameInheritingVisitorTest.java | 267 ++++++++++++++++++
3 files changed, 471 insertions(+)
create mode 100644 src/main/java/net/fabricmc/mappingio/adapter/OuterClassNameInheritingVisitor.java
create mode 100644 src/test/java/net/fabricmc/mappingio/adapter/OuterClassNameInheritingVisitorTest.java
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4b65a66d..091d1141 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added JAM reader and writer
- Added JOBF reader and writer
- Added Recaf Simple reader and writer
+- Added `OuterClassNameInheritingVisitor`
- Added `MappingFormat#hasWriter` boolean
## [0.5.1] - 2023-11-30
diff --git a/src/main/java/net/fabricmc/mappingio/adapter/OuterClassNameInheritingVisitor.java b/src/main/java/net/fabricmc/mappingio/adapter/OuterClassNameInheritingVisitor.java
new file mode 100644
index 00000000..1812cb24
--- /dev/null
+++ b/src/main/java/net/fabricmc/mappingio/adapter/OuterClassNameInheritingVisitor.java
@@ -0,0 +1,203 @@
+/*
+ * 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.adapter;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import org.jetbrains.annotations.Nullable;
+
+import net.fabricmc.mappingio.MappedElementKind;
+import net.fabricmc.mappingio.MappingFlag;
+import net.fabricmc.mappingio.MappingUtil;
+import net.fabricmc.mappingio.MappingVisitor;
+
+/**
+ * Searches for inner classes with no mapped name, whose enclosing classes do have mapped names,
+ * and applies those to the outer part of the inner classes' fully qualified name.
+ *
+ * For example, it takes a class {@code class_1$class_2} that doesn't have a mapping,
+ * tries to find {@code class_1}, which let's say has the mapping {@code SomeClass},
+ * and changes the former's destination name to {@code SomeClass$class_2}.
+ */
+public class OuterClassNameInheritingVisitor extends ForwardingMappingVisitor {
+ protected OuterClassNameInheritingVisitor(MappingVisitor next) {
+ super(next);
+ }
+
+ @Override
+ public Set getFlags() {
+ Set ret = EnumSet.noneOf(MappingFlag.class);
+ ret.addAll(next.getFlags());
+ ret.add(MappingFlag.NEEDS_MULTIPLE_PASSES);
+
+ return ret;
+ }
+
+ @Override
+ public boolean visitHeader() throws IOException {
+ if (pass < firstEmitPass) return true;
+
+ return super.visitHeader();
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void visitNamespaces(String srcNamespace, List dstNamespaces) throws IOException {
+ dstNsCount = dstNamespaces.size();
+
+ if (pass == collectClassesPass) {
+ visitedDstName = new boolean[dstNsCount];
+ dstNameBySrcNameByNamespace = new HashMap[dstNsCount];
+ } else if (pass >= firstEmitPass) {
+ super.visitNamespaces(srcNamespace, dstNamespaces);
+ }
+ }
+
+ @Override
+ public void visitMetadata(String key, @Nullable String value) throws IOException {
+ if (pass < firstEmitPass) return;
+
+ super.visitMetadata(key, value);
+ }
+
+ @Override
+ public boolean visitContent() throws IOException {
+ if (pass < firstEmitPass) return true;
+
+ return super.visitContent();
+ }
+
+ @Override
+ public boolean visitClass(String srcName) throws IOException {
+ this.srcName = srcName;
+
+ if (pass == collectClassesPass) {
+ dstNamesBySrcName.putIfAbsent(srcName, new String[dstNsCount]);
+ } else if (pass >= firstEmitPass) {
+ super.visitClass(srcName);
+ }
+
+ return true;
+ }
+
+ @Override
+ public void visitDstName(MappedElementKind targetKind, int namespace, String name) throws IOException {
+ if (pass == collectClassesPass) {
+ if (targetKind != MappedElementKind.CLASS) return;
+
+ dstNamesBySrcName.get(srcName)[namespace] = name;
+ } else if (pass >= firstEmitPass) {
+ if (targetKind == MappedElementKind.CLASS) {
+ visitedDstName[namespace] = true;
+ name = dstNamesBySrcName.get(srcName)[namespace];
+ }
+
+ super.visitDstName(targetKind, namespace, name);
+ }
+ }
+
+ @Override
+ public void visitDstDesc(MappedElementKind targetKind, int namespace, String desc) throws IOException {
+ if (pass < firstEmitPass) return;
+
+ if (modifiedClasses.contains(srcName)) {
+ Map nsDstNameBySrcName = dstNameBySrcNameByNamespace[namespace];
+
+ if (nsDstNameBySrcName == null) {
+ dstNameBySrcNameByNamespace[namespace] = nsDstNameBySrcName = dstNamesBySrcName.entrySet()
+ .stream()
+ .filter(entry -> entry.getValue()[namespace] != null)
+ .collect(HashMap::new, (map, entry) -> map.put(entry.getKey(), entry.getValue()[namespace]), HashMap::putAll);
+ }
+
+ desc = MappingUtil.mapDesc(desc, nsDstNameBySrcName);
+ }
+
+ super.visitDstDesc(targetKind, namespace, desc);
+ }
+
+ @Override
+ public boolean visitElementContent(MappedElementKind targetKind) throws IOException {
+ if (targetKind == MappedElementKind.CLASS && pass > collectClassesPass) {
+ String[] dstNames = dstNamesBySrcName.get(srcName);
+
+ for (int ns = 0; ns < dstNames.length; ns++) {
+ String dstName = dstNames[ns];
+
+ if (pass == fixOuterClassesPass) {
+ if (dstName != null) continue; // skip if already mapped
+
+ String[] parts = srcName.split(Pattern.quote("$"));
+
+ for (int pos = parts.length - 2; pos >= 0; pos--) {
+ String outerSrcName = String.join("$", Arrays.copyOfRange(parts, 0, pos + 1));
+ String outerDstName = dstNamesBySrcName.get(outerSrcName)[ns];
+
+ if (outerDstName != null) {
+ dstName = outerDstName + "$" + String.join("$", Arrays.copyOfRange(parts, pos + 1, parts.length));
+
+ dstNames[ns] = dstName;
+ modifiedClasses.add(srcName);
+ break;
+ }
+ }
+ } else if (!visitedDstName[ns]) {
+ if (dstName == null) continue; // skip if not mapped
+
+ // Class didn't have a mapping before we added one,
+ // so we have to call visitDstName manually.
+ super.visitDstName(targetKind, ns, dstName);
+ }
+ }
+ }
+
+ if (pass < firstEmitPass) {
+ return false; // prevent other element visits, we only care about classes here
+ }
+
+ Arrays.fill(visitedDstName, false);
+ return super.visitElementContent(targetKind);
+ }
+
+ @Override
+ public boolean visitEnd() throws IOException {
+ if (pass++ < firstEmitPass) {
+ return false;
+ }
+
+ return super.visitEnd();
+ }
+
+ private static final int collectClassesPass = 1;
+ private static final int fixOuterClassesPass = 2;
+ private static final int firstEmitPass = 3;
+ private final Map dstNamesBySrcName = new HashMap<>();
+ private final Set modifiedClasses = new HashSet<>();
+ private int pass = 1;
+ private int dstNsCount = -1;
+ private String srcName;
+ private boolean[] visitedDstName;
+ private Map[] dstNameBySrcNameByNamespace;
+}
diff --git a/src/test/java/net/fabricmc/mappingio/adapter/OuterClassNameInheritingVisitorTest.java b/src/test/java/net/fabricmc/mappingio/adapter/OuterClassNameInheritingVisitorTest.java
new file mode 100644
index 00000000..7d351a19
--- /dev/null
+++ b/src/test/java/net/fabricmc/mappingio/adapter/OuterClassNameInheritingVisitorTest.java
@@ -0,0 +1,267 @@
+/*
+ * 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.adapter;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+
+import net.fabricmc.mappingio.MappedElementKind;
+import net.fabricmc.mappingio.MappingFlag;
+import net.fabricmc.mappingio.MappingVisitor;
+import net.fabricmc.mappingio.NopMappingVisitor;
+import net.fabricmc.mappingio.tree.MemoryMappingTree;
+import net.fabricmc.mappingio.tree.VisitableMappingTree;
+
+public class OuterClassNameInheritingVisitorTest {
+ private static void accept(MappingVisitor visitor) throws IOException {
+ do {
+ if (visitor.visitHeader()) {
+ visitor.visitNamespaces("source", Arrays.asList("dstNs0", "dstNs1", "dstNs2", "dstNs3", "dstNs4", "dstNs5", "dstNs6"));
+ }
+
+ if (visitor.visitContent()) {
+ if (visitor.visitClass("class_1")) {
+ visitor.visitDstName(MappedElementKind.CLASS, 0, "class1Ns0Rename");
+ visitor.visitDstName(MappedElementKind.CLASS, 1, "class1Ns1Rename");
+ visitor.visitDstName(MappedElementKind.CLASS, 2, "class1Ns2Rename");
+ visitor.visitDstName(MappedElementKind.CLASS, 4, "class1Ns4Rename");
+
+ if (visitor.visitElementContent(MappedElementKind.CLASS)) {
+ if (visitor.visitField("field_1", "Lclass_1;")) {
+ for (int i = 0; i <= 6; i++) {
+ visitor.visitDstDesc(MappedElementKind.FIELD, i, "Lclass_1;");
+ visitor.visitElementContent(MappedElementKind.FIELD);
+ }
+ }
+ }
+ }
+
+ if (visitor.visitClass("class_1$class_2")) {
+ visitor.visitDstName(MappedElementKind.CLASS, 2, "class1Ns2Rename$class2Ns2Rename");
+ visitor.visitDstName(MappedElementKind.CLASS, 3, "class_1$class2Ns3Rename");
+ visitor.visitDstName(MappedElementKind.CLASS, 4, "class_1$class_2");
+ visitor.visitDstName(MappedElementKind.CLASS, 5, "class_1$class2Ns5Rename");
+
+ if (visitor.visitElementContent(MappedElementKind.CLASS)) {
+ if (visitor.visitField("field_2", "Lclass_1$class_2;")) {
+ for (int i = 0; i <= 6; i++) {
+ visitor.visitDstDesc(MappedElementKind.FIELD, i, "Lclass_1$class_2;");
+ visitor.visitElementContent(MappedElementKind.FIELD);
+ }
+ }
+ }
+ }
+
+ if (visitor.visitClass("class_1$class_2$class_3")) {
+ visitor.visitDstName(MappedElementKind.CLASS, 5, "class_1$class2Ns5Rename$class3Ns5Rename");
+ visitor.visitDstName(MappedElementKind.CLASS, 6, "class_1$class_2$class3Ns6Rename");
+
+ if (visitor.visitElementContent(MappedElementKind.CLASS)) {
+ if (visitor.visitField("field_2", "Lclass_1$class_2$class_3;")) {
+ for (int i = 0; i <= 6; i++) {
+ visitor.visitDstDesc(MappedElementKind.FIELD, i, "Lclass_1$class_2$class_3;");
+ visitor.visitElementContent(MappedElementKind.FIELD);
+ }
+ }
+ }
+ }
+ }
+ } while (!visitor.visitEnd());
+ }
+
+ @Test
+ public void directVisit() throws IOException {
+ accept(new OuterClassNameInheritingVisitor(new CheckingVisitor(false)));
+ }
+
+ @Test
+ public void tree() throws IOException {
+ VisitableMappingTree tree = new MemoryMappingTree();
+ accept(new OuterClassNameInheritingVisitor(tree));
+ tree.accept(new CheckingVisitor(true));
+ }
+
+ private static class CheckingVisitor extends NopMappingVisitor {
+ CheckingVisitor(boolean tree) {
+ super(true);
+ this.tree = tree;
+ }
+
+ @Override
+ public Set getFlags() {
+ return EnumSet.of(MappingFlag.NEEDS_DST_FIELD_DESC, MappingFlag.NEEDS_DST_METHOD_DESC);
+ }
+
+ @Override
+ public boolean visitClass(String srcName) throws IOException {
+ clsSrcName = srcName;
+ return true;
+ }
+
+ @Override
+ public void visitDstDesc(MappedElementKind targetKind, int namespace, String desc) throws IOException {
+ if (tree) return; // trees handle destination descriptor remapping themselves
+
+ switch (clsSrcName) {
+ case "class_1":
+ assertEquals("Lclass_1;", desc);
+ break;
+ case "class_1$class_2":
+ switch (namespace) {
+ case 0:
+ assertEquals("Lclass1Ns0Rename$class_2;", desc);
+ break;
+ case 1:
+ assertEquals("Lclass1Ns1Rename$class_2;", desc);
+ break;
+ case 2:
+ assertEquals("Lclass1Ns2Rename$class2Ns2Rename;", desc);
+ break;
+ case 3:
+ assertEquals("Lclass_1$class2Ns3Rename;", desc);
+ break;
+ case 4:
+ assertEquals("Lclass_1$class_2;", desc);
+ break;
+ case 5:
+ assertEquals("Lclass_1$class2Ns5Rename;", desc);
+ break;
+ case 6:
+ assertEquals("Lclass_1$class_2;", desc);
+ break;
+ default:
+ throw new IllegalStateException();
+ }
+
+ break;
+ case "class_1$class_2$class_3":
+ switch (namespace) {
+ case 0:
+ assertEquals("Lclass1Ns0Rename$class_2$class_3;", desc);
+ break;
+ case 1:
+ assertEquals("Lclass1Ns1Rename$class_2$class_3;", desc);
+ break;
+ case 2:
+ assertEquals("Lclass1Ns2Rename$class2Ns2Rename$class_3;", desc);
+ break;
+ case 3:
+ assertEquals("Lclass_1$class2Ns3Rename$class_3;", desc);
+ break;
+ case 4:
+ assertEquals("Lclass_1$class_2$class_3;", desc);
+ break;
+ case 5:
+ assertEquals("Lclass_1$class2Ns5Rename$class3Ns5Rename;", desc);
+ break;
+ case 6:
+ assertEquals("Lclass_1$class_2$class3Ns6Rename;", desc);
+ break;
+ default:
+ throw new IllegalStateException();
+ }
+
+ break;
+ default:
+ throw new IllegalStateException();
+ }
+ }
+
+ @Override
+ public void visitDstName(MappedElementKind targetKind, int namespace, String name) throws IOException {
+ if (targetKind != MappedElementKind.CLASS) return;
+
+ switch (clsSrcName) {
+ case "class_1":
+ break;
+ case "class_1$class_2":
+ switch (namespace) {
+ case 0:
+ assertEquals("class1Ns0Rename$class_2", name);
+ break;
+ case 1:
+ assertEquals("class1Ns1Rename$class_2", name);
+ break;
+ case 2:
+ assertEquals("class1Ns2Rename$class2Ns2Rename", name);
+ break;
+ case 3:
+ assertEquals("class_1$class2Ns3Rename", name);
+ break;
+ case 4:
+ assertEquals("class_1$class_2", name);
+ break;
+ case 5:
+ assertEquals("class_1$class2Ns5Rename", name);
+ break;
+ case 6:
+ assertEquals("class_1$class_2", name);
+ break;
+ default:
+ throw new IllegalStateException();
+ }
+
+ break;
+ case "class_1$class_2$class_3":
+ switch (namespace) {
+ case 0:
+ assertEquals("class1Ns0Rename$class_2$class_3", name);
+ break;
+ case 1:
+ assertEquals("class1Ns1Rename$class_2$class_3", name);
+ break;
+ case 2:
+ assertEquals("class1Ns2Rename$class2Ns2Rename$class_3", name);
+ break;
+ case 3:
+ assertEquals("class_1$class2Ns3Rename$class_3", name);
+ break;
+ case 4:
+ assertEquals("class_1$class_2$class_3", name);
+ break;
+ case 5:
+ assertEquals("class_1$class2Ns5Rename$class3Ns5Rename", name);
+ break;
+ case 6:
+ assertEquals("class_1$class_2$class3Ns6Rename", name);
+ break;
+ default:
+ throw new IllegalStateException();
+ }
+
+ break;
+ default:
+ throw new IllegalStateException();
+ }
+ }
+
+ @Override
+ public boolean visitEnd() throws IOException {
+ return ++passesDone == 2;
+ }
+
+ private final boolean tree;
+ private byte passesDone = 0;
+ private String clsSrcName;
+ }
+}