From 345b4e65bb12c6b4b1e495f2efde87af39e65f72 Mon Sep 17 00:00:00 2001 From: Julian Burner <48808497+NebelNidas@users.noreply.github.com> Date: Thu, 30 Nov 2023 22:44:03 +0100 Subject: [PATCH 01/12] Expand `VisitEndTest`, fix NPE and restore TSRG2 multipass support (#81) * Expand `VisitEndTest`; fix test trees being inconsistent * Fix NPE in `MemoryMappingTree` * Fix TSRG reader not handling multiple passes correctly * Add changes to changelog --- CHANGELOG.md | 4 + .../mappingio/format/srg/TsrgFileReader.java | 37 ++- .../mappingio/tree/MemoryMappingTree.java | 7 +- .../mappingio/SubsetAssertingVisitor.java | 259 ++++++++++++++++++ .../net/fabricmc/mappingio/TestHelper.java | 19 +- .../mappingio/read/ValidContentReadTest.java | 224 +-------------- .../mappingio/visiting/VisitEndTest.java | 177 ++++++++++-- .../enigma-dir/class_32.mapping | 10 +- .../read/valid-with-holes/enigma.mappings | 10 +- .../read/valid-with-holes/tinyV2.tiny | 24 +- .../read/valid-with-holes/tsrg2.tsrg | 12 +- 11 files changed, 474 insertions(+), 309 deletions(-) create mode 100644 src/test/java/net/fabricmc/mappingio/SubsetAssertingVisitor.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 35a5ded2..4c18ffd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file.
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 NPE in `MemoryMappingTree` +- Fixed TSRG2 reader not handling multiple passes correctly + ## [0.5.0] - 2023-11-15 - Actually marked `HierarchyInfoProvider` as experimental - Added changelog 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 f0e32c28..59582b07 100644 --- a/src/main/java/net/fabricmc/mappingio/format/srg/TsrgFileReader.java +++ b/src/main/java/net/fabricmc/mappingio/format/srg/TsrgFileReader.java @@ -79,32 +79,27 @@ public static void read(Reader r, String sourceNs, String targetNs, MappingVisit } MappingFormat format = MappingFormat.TSRG_FILE; - if (reader.nextCol("tsrg2")) format = MappingFormat.TSRG_2_FILE; - String srcNamespace; - List dstNamespaces; + String srcNamespace = sourceNs; + List dstNamespaces = Collections.singletonList(targetNs); - if (format == MappingFormat.TSRG_2_FILE) { // tsrg2 magic - srcNamespace = reader.nextCol(); - dstNamespaces = new ArrayList<>(); - String dstNamespace; + for (;;) { + if (reader.nextCol("tsrg2")) { // tsrg2 magic + format = MappingFormat.TSRG_2_FILE; + srcNamespace = reader.nextCol(); + dstNamespaces = new ArrayList<>(); + String dstNamespace; + + while ((dstNamespace = reader.nextCol()) != null) { + dstNamespaces.add(dstNamespace); + } - while ((dstNamespace = reader.nextCol()) != null) { - dstNamespaces.add(dstNamespace); + reader.nextLine(0); } - reader.nextLine(0); - } else { - srcNamespace = sourceNs; - dstNamespaces = Collections.singletonList(targetNs); - } - - int dstNsCount = dstNamespaces.size(); - List nameTmp = dstNamespaces.size() > 1 ? new ArrayList<>(dstNamespaces.size() - 1) : null; - - for (;;) { - boolean visitHeader = visitor.visitHeader(); + int dstNsCount = dstNamespaces.size(); + List nameTmp = dstNamespaces.size() > 1 ? new ArrayList<>(dstNamespaces.size() - 1) : null; - if (visitHeader) { + if (visitor.visitHeader()) { visitor.visitNamespaces(srcNamespace, dstNamespaces); } diff --git a/src/main/java/net/fabricmc/mappingio/tree/MemoryMappingTree.java b/src/main/java/net/fabricmc/mappingio/tree/MemoryMappingTree.java index a3515874..2836c320 100644 --- a/src/main/java/net/fabricmc/mappingio/tree/MemoryMappingTree.java +++ b/src/main/java/net/fabricmc/mappingio/tree/MemoryMappingTree.java @@ -709,6 +709,7 @@ public void visitComment(MappedElementKind targetKind, String comment) { abstract static class Entry> implements ElementMapping { protected Entry(MemoryMappingTree tree, String srcName) { + this.tree = tree; this.srcName = srcName; this.dstNames = new String[tree.dstNamespaces.size()]; } @@ -814,6 +815,7 @@ protected void copyFrom(T o, boolean replace) { // TODO: copy args+vars } + protected final MemoryMappingTree tree; protected String srcName; protected String[] dstNames; protected String comment; @@ -822,15 +824,11 @@ protected void copyFrom(T o, boolean replace) { static final class ClassEntry extends Entry implements ClassMapping { ClassEntry(MemoryMappingTree tree, String srcName) { super(tree, srcName); - - this.tree = tree; } ClassEntry(MemoryMappingTree tree, ClassMapping src, int srcNsEquivalent) { super(tree, src, srcNsEquivalent); - this.tree = tree; - for (FieldMapping field : src.getFields()) { addField(field); } @@ -1125,7 +1123,6 @@ public String toString() { private static final byte FLAG_HAS_ANY_METHOD_DESC = 4; private static final byte FLAG_MISSES_ANY_METHOD_DESC = 8; - protected final MemoryMappingTree tree; private Map fields = null; private Map methods = null; private byte flags; diff --git a/src/test/java/net/fabricmc/mappingio/SubsetAssertingVisitor.java b/src/test/java/net/fabricmc/mappingio/SubsetAssertingVisitor.java new file mode 100644 index 00000000..907bc033 --- /dev/null +++ b/src/test/java/net/fabricmc/mappingio/SubsetAssertingVisitor.java @@ -0,0 +1,259 @@ +/* + * 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; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.jetbrains.annotations.Nullable; + +import net.fabricmc.mappingio.format.MappingFormat; +import net.fabricmc.mappingio.tree.MappingTreeView; +import net.fabricmc.mappingio.tree.MappingTreeView.ClassMappingView; +import net.fabricmc.mappingio.tree.MappingTreeView.FieldMappingView; +import net.fabricmc.mappingio.tree.MappingTreeView.MethodArgMappingView; +import net.fabricmc.mappingio.tree.MappingTreeView.MethodMappingView; +import net.fabricmc.mappingio.tree.MappingTreeView.MethodVarMappingView; + +public class SubsetAssertingVisitor implements FlatMappingVisitor { + public SubsetAssertingVisitor(MappingTreeView supTree, @Nullable MappingFormat supFormat, @Nullable MappingFormat subFormat) { + this.supTree = supTree; + this.supDstNsCount = supTree.getMaxNamespaceId(); + this.subHasNamespaces = subFormat == null ? true : subFormat.hasNamespaces; + this.supHasNamespaces = supFormat == null ? true : supFormat.hasNamespaces; + this.supHasFieldDesc = supFormat == null ? true : supFormat.hasFieldDescriptors; + this.supHasArgs = supFormat == null ? true : supFormat.supportsArgs; + this.supHasVars = supFormat == null ? true : supFormat.supportsLocals; + this.supHasComments = supFormat == null ? true : supFormat.supportsComments; + } + + @Override + public void visitNamespaces(String srcNamespace, List dstNamespaces) throws IOException { + assertTrue(srcNamespace.equals(subHasNamespaces ? supTree.getSrcNamespace() : MappingUtil.NS_SOURCE_FALLBACK)); + this.dstNamespaces = dstNamespaces; + + if (!subHasNamespaces) { + assertTrue(dstNamespaces.size() == 1); + assertTrue(dstNamespaces.get(0).equals(MappingUtil.NS_TARGET_FALLBACK)); + return; + } + + for (int i = 0; i < dstNamespaces.size(); i++) { + String dstNs = dstNamespaces.get(i); + boolean contained = supTree.getDstNamespaces().contains(dstNs); + + if (!supHasNamespaces) { + if (contained) return; + } else { + assertTrue(contained); + } + } + + if (!supHasNamespaces) throw new RuntimeException("SubTree namespace not contained in SupTree"); + } + + @Override + public boolean visitClass(String srcName, String[] dstNames) throws IOException { + ClassMappingView supCls = supTree.getClass(srcName); + Map supDstNamesByNsName = new HashMap<>(); + + if (supCls == null) { + String[] tmpDst = supHasNamespaces ? dstNames : new String[]{dstNames[0]}; + if (!Arrays.stream(tmpDst).anyMatch(Objects::nonNull)) return false; + throw new RuntimeException("SubTree class not contained in SupTree: " + srcName); + } + + for (int supNs = 0; supNs < supDstNsCount; supNs++) { + supDstNamesByNsName.put(supTree.getNamespaceName(supNs), supCls.getDstName(supNs)); + } + + for (int subNs = 0; subNs < dstNames.length; subNs++) { + String supDstName = supDstNamesByNsName.get(dstNamespaces.get(subNs)); + if (!supHasNamespaces && supDstName == null) continue; + assertTrue(dstNames[subNs] == null || dstNames[subNs].equals(supDstName) || (supDstName == null && dstNames[subNs].equals(srcName))); + } + + return true; + } + + @Override + public void visitClassComment(String srcName, String[] dstNames, String comment) throws IOException { + if (!supHasComments) return; + assertEquals(supTree.getClass(srcName).getComment(), comment); + } + + @Override + public boolean visitField(String srcClsName, String srcName, String srcDesc, + String[] dstClsNames, String[] dstNames, String[] dstDescs) throws IOException { + FieldMappingView supFld = supTree.getClass(srcClsName).getField(srcName, srcDesc); + Map supDstDataByNsName = new HashMap<>(); + + if (supFld == null) { + String[] tmpDst = supHasNamespaces ? dstNames : new String[]{dstNames[0]}; + if (!Arrays.stream(tmpDst).anyMatch(Objects::nonNull)) return false; + throw new RuntimeException("SubTree field not contained in SupTree: " + srcName); + } + + for (int supNs = 0; supNs < supDstNsCount; supNs++) { + supDstDataByNsName.put(supTree.getNamespaceName(supNs), new String[]{supFld.getDstName(supNs), supFld.getDstDesc(supNs)}); + } + + for (int subNs = 0; subNs < dstNames.length; subNs++) { + String[] supDstData = supDstDataByNsName.get(dstNamespaces.get(subNs)); + if (!supHasNamespaces && supDstData == null) continue; + + String supDstName = supDstData[0]; + assertTrue(dstNames[subNs] == null || dstNames[subNs].equals(supDstName) || (supDstName == null && dstNames[subNs].equals(srcName))); + + if (!supHasFieldDesc) continue; + String supDstDesc = supDstData[1]; + assertTrue(dstDescs == null || dstDescs[subNs] == null || dstDescs[subNs].equals(supDstDesc)); + } + + return true; + } + + @Override + public void visitFieldComment(String srcClsName, String srcName, String srcDesc, + String[] dstClsNames, String[] dstNames, String[] dstDescs, String comment) throws IOException { + if (!supHasComments) return; + assertEquals(supTree.getClass(srcClsName).getField(srcName, srcDesc).getComment(), comment); + } + + @Override + public boolean visitMethod(String srcClsName, String srcName, String srcDesc, + String[] dstClsNames, String[] dstNames, String[] dstDescs) throws IOException { + MethodMappingView supMth = supTree.getClass(srcClsName).getMethod(srcName, srcDesc); + Map supDstDataByNsName = new HashMap<>(); + + if (supMth == null) { + String[] tmpDst = supHasNamespaces ? dstNames : new String[]{dstNames[0]}; + if (!Arrays.stream(tmpDst).anyMatch(Objects::nonNull)) return false; + throw new RuntimeException("SubTree method not contained in SupTree: " + srcName); + } + + for (int supNs = 0; supNs < supDstNsCount; supNs++) { + supDstDataByNsName.put(supTree.getNamespaceName(supNs), new String[]{supMth.getDstName(supNs), supMth.getDstDesc(supNs)}); + } + + for (int subNs = 0; subNs < dstNames.length; subNs++) { + String[] supDstData = supDstDataByNsName.get(dstNamespaces.get(subNs)); + if (!supHasNamespaces && supDstData == null) continue; + + String supDstName = supDstData[0]; + assertTrue(dstNames[subNs] == null || dstNames[subNs].equals(supDstName) || (supDstName == null && dstNames[subNs].equals(srcName))); + + String supDstDesc = supDstData[1]; + assertTrue(dstDescs == null || dstDescs[subNs] == null || dstDescs[subNs].equals(supDstDesc)); + } + + return true; + } + + @Override + public void visitMethodComment(String srcClsName, String srcName, String srcDesc, + String[] dstClsNames, String[] dstNames, String[] dstDescs, String comment) throws IOException { + if (!supHasComments) return; + assertEquals(supTree.getClass(srcClsName).getMethod(srcName, srcDesc).getComment(), comment); + } + + @Override + public boolean visitMethodArg(String srcClsName, String srcMethodName, String srcMethodDesc, int argPosition, int lvIndex, String srcName, + String[] dstClsNames, String[] dstMethodNames, String[] dstMethodDescs, String[] dstNames) throws IOException { + if (!supHasArgs) return false; + MethodArgMappingView supArg = supTree.getClass(srcClsName).getMethod(srcMethodName, srcMethodDesc).getArg(argPosition, lvIndex, srcName); + Map supDstNamesByNsName = new HashMap<>(); + + if (supArg == null) { + String[] tmpDst = supHasNamespaces ? dstNames : new String[]{dstNames[0]}; + if (!Arrays.stream(tmpDst).anyMatch(Objects::nonNull)) return false; + throw new RuntimeException("SubTree arg not contained in SupTree: " + srcName); + } + + for (int supNs = 0; supNs < supDstNsCount; supNs++) { + supDstNamesByNsName.put(supTree.getNamespaceName(supNs), supArg.getDstName(supNs)); + } + + for (int subNs = 0; subNs < dstNames.length; subNs++) { + String supDstName = supDstNamesByNsName.get(dstNamespaces.get(subNs)); + if (!supHasNamespaces && supDstName == null) continue; + assertTrue(dstNames[subNs] == null || dstNames[subNs].equals(supDstName) || (supDstName == null && dstNames[subNs].equals(srcName))); + } + + return true; + } + + @Override + public void visitMethodArgComment(String srcClsName, String srcMethodName, String srcMethodDesc, int argPosition, int lvIndex, String srcArgName, + String[] dstClsNames, String[] dstMethodNames, String[] dstMethodDescs, String[] dstNames, String comment) throws IOException { + if (!supHasComments) return; + assertEquals(supTree.getClass(srcClsName).getMethod(srcMethodName, srcMethodDesc).getArg(argPosition, lvIndex, srcArgName).getComment(), comment); + } + + @Override + public boolean visitMethodVar(String srcClsName, String srcMethodName, String srcMethodDesc, + int lvtRowIndex, int lvIndex, int startOpIdx, int endOpIdx, String srcName, + String[] dstClsNames, String[] dstMethodNames, String[] dstMethodDescs, String[] dstNames) throws IOException { + if (!supHasVars) return false; + MethodVarMappingView supVar = supTree.getClass(srcClsName).getMethod(srcMethodName, srcMethodDesc).getVar(lvtRowIndex, lvIndex, startOpIdx, endOpIdx, srcName); + Map supDstNamesByNsName = new HashMap<>(); + + if (supVar == null) { + String[] tmpDst = supHasNamespaces ? dstNames : new String[]{dstNames[0]}; + if (!Arrays.stream(tmpDst).anyMatch(Objects::nonNull)) return false; + throw new RuntimeException("SubTree var not contained in SupTree: " + srcName); + } + + for (int supNs = 0; supNs < supDstNsCount; supNs++) { + supDstNamesByNsName.put(supTree.getNamespaceName(supNs), supVar.getDstName(supNs)); + } + + for (int subNs = 0; subNs < dstNames.length; subNs++) { + String supDstName = supDstNamesByNsName.get(dstNamespaces.get(subNs)); + if (!supHasNamespaces && supDstName == null) continue; + assertTrue(dstNames[subNs] == null || dstNames[subNs].equals(supDstName) || (supDstName == null && dstNames[subNs].equals(srcName))); + } + + return true; + } + + @Override + public void visitMethodVarComment(String srcClsName, String srcMethodName, String srcMethodDesc, + int lvtRowIndex, int lvIndex, int startOpIdx, int endOpIdx, String srcVarName, + String[] dstClsNames, String[] dstMethodNames, String[] dstMethodDescs, String[] dstNames, String comment) throws IOException { + if (!supHasComments) return; + assertEquals(supTree.getClass(srcClsName).getMethod(srcMethodName, srcMethodDesc).getVar(lvtRowIndex, lvIndex, startOpIdx, endOpIdx, srcVarName).getComment(), comment); + } + + private final MappingTreeView supTree; + private final int supDstNsCount; + private final boolean subHasNamespaces; + private final boolean supHasNamespaces; + private final boolean supHasFieldDesc; + private final boolean supHasArgs; + private final boolean supHasVars; + private final boolean supHasComments; + private List dstNamespaces; +} + diff --git a/src/test/java/net/fabricmc/mappingio/TestHelper.java b/src/test/java/net/fabricmc/mappingio/TestHelper.java index c563d422..4b1dbabb 100644 --- a/src/test/java/net/fabricmc/mappingio/TestHelper.java +++ b/src/test/java/net/fabricmc/mappingio/TestHelper.java @@ -204,7 +204,7 @@ private static void visitMethod(MemoryMappingTree tree, int... dstNs) { } private static void visitMethodArg(MemoryMappingTree tree, int... dstNs) { - tree.visitMethodArg(counter.getAndIncrement(), counter.getAndIncrement(), nameGen.src(argKind)); + tree.visitMethodArg(nameGen.getCounter().getAndIncrement(), nameGen.getCounter().getAndIncrement(), nameGen.src(argKind)); for (int ns : dstNs) { tree.visitDstName(argKind, ns, nameGen.dst(argKind, ns)); @@ -212,7 +212,8 @@ private static void visitMethodArg(MemoryMappingTree tree, int... dstNs) { } private static void visitMethodVar(MemoryMappingTree tree, int... dstNs) { - tree.visitMethodVar(counter.get(), counter.get(), counter.getAndIncrement(), counter.getAndIncrement(), nameGen.src(varKind)); + tree.visitMethodVar(nameGen.getCounter().get(), nameGen.getCounter().get(), + nameGen.getCounter().getAndIncrement(), nameGen.getCounter().getAndIncrement(), nameGen.src(varKind)); for (int ns : dstNs) { tree.visitDstName(varKind, ns, nameGen.dst(varKind, ns)); @@ -233,6 +234,7 @@ public void reset() { argNum.get().set(0); varNum.get().set(0); nsNum.get().set(0); + counter.get().set(0); } private void resetNsNum() { @@ -300,6 +302,10 @@ public String dst(MappedElementKind kind, int ns) { return sb.toString(); } + public AtomicInteger getCounter() { + return counter.get(); + } + private AtomicInteger getCounter(MappedElementKind kind) { switch (kind) { case CLASS: @@ -348,9 +354,17 @@ private String getPrefix(MappedElementKind kind) { private ThreadLocal argNum = ThreadLocal.withInitial(() -> new AtomicInteger()); private ThreadLocal varNum = ThreadLocal.withInitial(() -> new AtomicInteger()); private ThreadLocal nsNum = ThreadLocal.withInitial(() -> new AtomicInteger()); + private ThreadLocal counter = ThreadLocal.withInitial(() -> new AtomicInteger()); } public static class MappingDirs { + @Nullable + public static MemoryMappingTree getCorrespondingTree(Path dir) { + if (dir.equals(VALID)) return createTestTree(); + if (dir.equals(VALID_WITH_HOLES)) return createTestTreeWithHoles(); + return null; + } + public static final Path DETECTION = getResource("/detection/"); public static final Path VALID = getResource("/read/valid/"); public static final Path VALID_WITH_HOLES = getResource("/read/valid-with-holes/"); @@ -360,7 +374,6 @@ public static class MappingDirs { private static final String mthDesc = "()I"; private static final String comment = "This is a comment"; private static final NameGen nameGen = new NameGen(); - private static final AtomicInteger counter = new AtomicInteger(); private static final MappedElementKind clsKind = MappedElementKind.CLASS; private static final MappedElementKind fldKind = MappedElementKind.FIELD; private static final MappedElementKind mthKind = MappedElementKind.METHOD; diff --git a/src/test/java/net/fabricmc/mappingio/read/ValidContentReadTest.java b/src/test/java/net/fabricmc/mappingio/read/ValidContentReadTest.java index ec8e6889..fe1a4279 100644 --- a/src/test/java/net/fabricmc/mappingio/read/ValidContentReadTest.java +++ b/src/test/java/net/fabricmc/mappingio/read/ValidContentReadTest.java @@ -16,32 +16,16 @@ package net.fabricmc.mappingio.read; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.io.IOException; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; - import org.jetbrains.annotations.Nullable; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import net.fabricmc.mappingio.FlatMappingVisitor; import net.fabricmc.mappingio.MappingReader; -import net.fabricmc.mappingio.MappingUtil; +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.MappingTree; -import net.fabricmc.mappingio.tree.MappingTree.ClassMapping; -import net.fabricmc.mappingio.tree.MappingTree.FieldMapping; -import net.fabricmc.mappingio.tree.MappingTree.MethodArgMapping; -import net.fabricmc.mappingio.tree.MappingTree.MethodMapping; -import net.fabricmc.mappingio.tree.MappingTree.MethodVarMapping; import net.fabricmc.mappingio.tree.MemoryMappingTree; import net.fabricmc.mappingio.tree.VisitableMappingTree; @@ -146,210 +130,6 @@ private VisitableMappingTree checkHoles(MappingFormat format) throws Exception { } private void assertSubset(MappingTree subTree, @Nullable MappingFormat subFormat, MappingTree supTree, @Nullable MappingFormat supFormat) throws Exception { - int supDstNsCount = supTree.getMaxNamespaceId(); - boolean subHasNamespaces = subFormat == null ? true : subFormat.hasNamespaces; - boolean supHasNamespaces = supFormat == null ? true : supFormat.hasNamespaces; - boolean supHasFieldDesc = supFormat == null ? true : supFormat.hasFieldDescriptors; - boolean supHasArgs = supFormat == null ? true : supFormat.supportsArgs; - boolean supHasVars = supFormat == null ? true : supFormat.supportsLocals; - boolean supHasComments = supFormat == null ? true : supFormat.supportsComments; - - subTree.accept(new FlatAsRegularMappingVisitor(new FlatMappingVisitor() { - @Override - public void visitNamespaces(String srcNamespace, List dstNamespaces) throws IOException { - assertTrue(srcNamespace.equals(subHasNamespaces ? supTree.getSrcNamespace() : MappingUtil.NS_SOURCE_FALLBACK)); - - if (!subHasNamespaces) { - assertTrue(dstNamespaces.size() == 1); - assertTrue(dstNamespaces.get(0).equals(MappingUtil.NS_TARGET_FALLBACK)); - return; - } - - for (String dstNs : dstNamespaces) { - boolean contained = supTree.getDstNamespaces().contains(dstNs); - - if (!supHasNamespaces) { - if (contained) return; - } else { - assertTrue(contained); - } - } - - if (!supHasNamespaces) throw new RuntimeException("SubTree namespace not contained in SupTree"); - } - - @Override - public boolean visitClass(String srcName, String[] dstNames) throws IOException { - ClassMapping supCls = supTree.getClass(srcName); - Map supDstNamesByNsName = new HashMap<>(); - - if (supCls == null) { - String[] tmpDst = supHasNamespaces ? dstNames : new String[]{dstNames[0]}; - if (!Arrays.stream(tmpDst).anyMatch(Objects::nonNull)) return false; - throw new RuntimeException("SubTree class not contained in SupTree: " + srcName); - } - - for (int supNs = 0; supNs < supDstNsCount; supNs++) { - supDstNamesByNsName.put(supTree.getNamespaceName(supNs), supCls.getDstName(supNs)); - } - - for (int subNs = 0; subNs < dstNames.length; subNs++) { - String supDstName = supDstNamesByNsName.get(subTree.getNamespaceName(subNs)); - if (!supHasNamespaces && supDstName == null) continue; - assertTrue(dstNames[subNs] == null || dstNames[subNs].equals(supDstName) || (supDstName == null && dstNames[subNs].equals(srcName))); - } - - return true; - } - - @Override - public void visitClassComment(String srcName, String[] dstNames, String comment) throws IOException { - if (!supHasComments) return; - assertEquals(supTree.getClass(srcName).getComment(), comment); - } - - @Override - public boolean visitField(String srcClsName, String srcName, String srcDesc, - String[] dstClsNames, String[] dstNames, String[] dstDescs) throws IOException { - FieldMapping supFld = supTree.getClass(srcClsName).getField(srcName, srcDesc); - Map supDstDataByNsName = new HashMap<>(); - - if (supFld == null) { - String[] tmpDst = supHasNamespaces ? dstNames : new String[]{dstNames[0]}; - if (!Arrays.stream(tmpDst).anyMatch(Objects::nonNull)) return false; - throw new RuntimeException("SubTree field not contained in SupTree: " + srcName); - } - - for (int supNs = 0; supNs < supDstNsCount; supNs++) { - supDstDataByNsName.put(supTree.getNamespaceName(supNs), new String[]{supFld.getDstName(supNs), supFld.getDstDesc(supNs)}); - } - - for (int subNs = 0; subNs < dstNames.length; subNs++) { - String[] supDstData = supDstDataByNsName.get(subTree.getNamespaceName(subNs)); - if (!supHasNamespaces && supDstData == null) continue; - - String supDstName = supDstData[0]; - assertTrue(dstNames[subNs] == null || dstNames[subNs].equals(supDstName) || (supDstName == null && dstNames[subNs].equals(srcName))); - - if (!supHasFieldDesc) continue; - String supDstDesc = supDstData[1]; - assertTrue(dstDescs == null || dstDescs[subNs] == null || dstDescs[subNs].equals(supDstDesc)); - } - - return true; - } - - @Override - public void visitFieldComment(String srcClsName, String srcName, String srcDesc, - String[] dstClsNames, String[] dstNames, String[] dstDescs, String comment) throws IOException { - if (!supHasComments) return; - assertEquals(supTree.getClass(srcClsName).getField(srcName, srcDesc).getComment(), comment); - } - - @Override - public boolean visitMethod(String srcClsName, String srcName, String srcDesc, - String[] dstClsNames, String[] dstNames, String[] dstDescs) throws IOException { - MethodMapping supMth = supTree.getClass(srcClsName).getMethod(srcName, srcDesc); - Map supDstDataByNsName = new HashMap<>(); - - if (supMth == null) { - String[] tmpDst = supHasNamespaces ? dstNames : new String[]{dstNames[0]}; - if (!Arrays.stream(tmpDst).anyMatch(Objects::nonNull)) return false; - throw new RuntimeException("SubTree method not contained in SupTree: " + srcName); - } - - for (int supNs = 0; supNs < supDstNsCount; supNs++) { - supDstDataByNsName.put(supTree.getNamespaceName(supNs), new String[]{supMth.getDstName(supNs), supMth.getDstDesc(supNs)}); - } - - for (int subNs = 0; subNs < dstNames.length; subNs++) { - String[] supDstData = supDstDataByNsName.get(subTree.getNamespaceName(subNs)); - if (!supHasNamespaces && supDstData == null) continue; - - String supDstName = supDstData[0]; - assertTrue(dstNames[subNs] == null || dstNames[subNs].equals(supDstName) || (supDstName == null && dstNames[subNs].equals(srcName))); - - String supDstDesc = supDstData[1]; - assertTrue(dstDescs == null || dstDescs[subNs] == null || dstDescs[subNs].equals(supDstDesc)); - } - - return true; - } - - @Override - public void visitMethodComment(String srcClsName, String srcName, String srcDesc, - String[] dstClsNames, String[] dstNames, String[] dstDescs, String comment) throws IOException { - if (!supHasComments) return; - assertEquals(supTree.getClass(srcClsName).getMethod(srcName, srcDesc).getComment(), comment); - } - - @Override - public boolean visitMethodArg(String srcClsName, String srcMethodName, String srcMethodDesc, int argPosition, int lvIndex, String srcName, - String[] dstClsNames, String[] dstMethodNames, String[] dstMethodDescs, String[] dstNames) throws IOException { - if (!supHasArgs) return false; - MethodArgMapping supArg = supTree.getClass(srcClsName).getMethod(srcMethodName, srcMethodDesc).getArg(argPosition, lvIndex, srcName); - Map supDstNamesByNsName = new HashMap<>(); - - if (supArg == null) { - String[] tmpDst = supHasNamespaces ? dstNames : new String[]{dstNames[0]}; - if (!Arrays.stream(tmpDst).anyMatch(Objects::nonNull)) return false; - throw new RuntimeException("SubTree arg not contained in SupTree: " + srcName); - } - - for (int supNs = 0; supNs < supDstNsCount; supNs++) { - supDstNamesByNsName.put(supTree.getNamespaceName(supNs), supArg.getDstName(supNs)); - } - - for (int subNs = 0; subNs < dstNames.length; subNs++) { - String supDstName = supDstNamesByNsName.get(subTree.getNamespaceName(subNs)); - if (!supHasNamespaces && supDstName == null) continue; - assertTrue(dstNames[subNs] == null || dstNames[subNs].equals(supDstName) || (supDstName == null && dstNames[subNs].equals(srcName))); - } - - return true; - } - - @Override - public void visitMethodArgComment(String srcClsName, String srcMethodName, String srcMethodDesc, int argPosition, int lvIndex, String srcArgName, - String[] dstClsNames, String[] dstMethodNames, String[] dstMethodDescs, String[] dstNames, String comment) throws IOException { - if (!supHasComments) return; - assertEquals(supTree.getClass(srcClsName).getMethod(srcMethodName, srcMethodDesc).getArg(argPosition, lvIndex, srcArgName).getComment(), comment); - } - - @Override - public boolean visitMethodVar(String srcClsName, String srcMethodName, String srcMethodDesc, - int lvtRowIndex, int lvIndex, int startOpIdx, int endOpIdx, String srcName, - String[] dstClsNames, String[] dstMethodNames, String[] dstMethodDescs, String[] dstNames) throws IOException { - if (!supHasVars) return false; - MethodVarMapping supVar = supTree.getClass(srcClsName).getMethod(srcMethodName, srcMethodDesc).getVar(lvtRowIndex, lvIndex, startOpIdx, endOpIdx, srcName); - Map supDstNamesByNsName = new HashMap<>(); - - if (supVar == null) { - String[] tmpDst = supHasNamespaces ? dstNames : new String[]{dstNames[0]}; - if (!Arrays.stream(tmpDst).anyMatch(Objects::nonNull)) return false; - throw new RuntimeException("SubTree var not contained in SupTree: " + srcName); - } - - for (int supNs = 0; supNs < supDstNsCount; supNs++) { - supDstNamesByNsName.put(supTree.getNamespaceName(supNs), supVar.getDstName(supNs)); - } - - for (int subNs = 0; subNs < dstNames.length; subNs++) { - String supDstName = supDstNamesByNsName.get(subTree.getNamespaceName(subNs)); - if (!supHasNamespaces && supDstName == null) continue; - assertTrue(dstNames[subNs] == null || dstNames[subNs].equals(supDstName) || (supDstName == null && dstNames[subNs].equals(srcName))); - } - - return true; - } - - @Override - public void visitMethodVarComment(String srcClsName, String srcMethodName, String srcMethodDesc, - int lvtRowIndex, int lvIndex, int startOpIdx, int endOpIdx, String srcVarName, - String[] dstClsNames, String[] dstMethodNames, String[] dstMethodDescs, String[] dstNames, String comment) throws IOException { - if (!supHasComments) return; - assertEquals(supTree.getClass(srcClsName).getMethod(srcMethodName, srcMethodDesc).getVar(lvtRowIndex, lvIndex, startOpIdx, endOpIdx, srcVarName).getComment(), comment); - } - })); + subTree.accept(new FlatAsRegularMappingVisitor(new SubsetAssertingVisitor(supTree, supFormat, subFormat))); } } diff --git a/src/test/java/net/fabricmc/mappingio/visiting/VisitEndTest.java b/src/test/java/net/fabricmc/mappingio/visiting/VisitEndTest.java index e0266869..e2db8117 100644 --- a/src/test/java/net/fabricmc/mappingio/visiting/VisitEndTest.java +++ b/src/test/java/net/fabricmc/mappingio/visiting/VisitEndTest.java @@ -21,152 +21,269 @@ import java.io.IOException; import java.nio.file.Path; import java.util.EnumSet; -import java.util.HashSet; import java.util.List; import java.util.Set; import org.jetbrains.annotations.Nullable; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import net.fabricmc.mappingio.MappedElementKind; import net.fabricmc.mappingio.MappingFlag; import net.fabricmc.mappingio.MappingReader; import net.fabricmc.mappingio.MappingVisitor; +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.MappingTree; +import net.fabricmc.mappingio.tree.MappingTreeView; +import net.fabricmc.mappingio.tree.MemoryMappingTree; public class VisitEndTest { - private static Set dirs = new HashSet<>(); + @Test + public void enigmaFile() throws Exception { + MappingFormat format = MappingFormat.ENIGMA_FILE; + check(format); + } + + @Test + public void enigmaDirectory() throws Exception { + MappingFormat format = MappingFormat.ENIGMA_DIR; + check(format); + } + + @Test + public void tinyFile() throws Exception { + MappingFormat format = MappingFormat.TINY_FILE; + check(format); + } + + @Test + public void tinyV2File() throws Exception { + MappingFormat format = MappingFormat.TINY_2_FILE; + check(format); + } - @BeforeAll - public static void setup() throws Exception { - dirs.add(TestHelper.MappingDirs.DETECTION); - dirs.add(TestHelper.MappingDirs.VALID); - dirs.add(TestHelper.MappingDirs.VALID_WITH_HOLES); + @Test + public void srgFile() throws Exception { + MappingFormat format = MappingFormat.SRG_FILE; + check(format); } @Test - public void testVisitEnd() throws Exception { - for (MappingFormat format : MappingFormat.values()) { - String filename = TestHelper.getFileName(format); - if (filename == null) continue; + public void xrgFile() throws Exception { + MappingFormat format = MappingFormat.XSRG_FILE; + check(format); + } - for (Path dir : dirs) { - MappingReader.read(dir.resolve(filename), format, new VisitEndTestVisitor(1, true)); - MappingReader.read(dir.resolve(filename), format, new VisitEndTestVisitor(1, false)); + @Test + public void csrgFile() throws Exception { + MappingFormat format = MappingFormat.CSRG_FILE; + check(format); + } - VisitEndTestVisitor threePassVisitor = new VisitEndTestVisitor(2, true); - MappingReader.read(dir.resolve(filename), format, threePassVisitor); - assertTrue(threePassVisitor.finishedVisitPassCount == threePassVisitor.visitPassCountToFinish); + @Test + public void tsrgFile() throws Exception { + MappingFormat format = MappingFormat.TSRG_FILE; + check(format); + } - threePassVisitor = new VisitEndTestVisitor(2, false); + @Test + public void tsrg2File() throws Exception { + MappingFormat format = MappingFormat.TSRG_2_FILE; + check(format); + } - try { - MappingReader.read(dir.resolve(filename), format, threePassVisitor); - } catch (Exception e) { - continue; // Reader doesn't support multiple passes without NEEDS_MULTIPLE_PASSES - } + @Test + public void proguardFile() throws Exception { + MappingFormat format = MappingFormat.PROGUARD_FILE; + check(format); + } - // Reader didn't throw an exception, make sure it actually behaved as expected - assertTrue(threePassVisitor.finishedVisitPassCount == threePassVisitor.visitPassCountToFinish); - } + private void check(MappingFormat format) throws Exception { + checkDir(TestHelper.MappingDirs.DETECTION, format); + checkDir(TestHelper.MappingDirs.VALID, format); + checkDir(TestHelper.MappingDirs.VALID_WITH_HOLES, format); + } + + private void checkDir(Path dir, MappingFormat format) throws Exception { + Path path = dir.resolve(TestHelper.getFileName(format)); + MappingTreeView supTree = TestHelper.MappingDirs.getCorrespondingTree(dir); + + checkCompliance(format, path, 1, true, supTree); + checkCompliance(format, path, 1, false, supTree); + + checkCompliance(format, path, 2, true, supTree); + checkCompliance(format, path, 3, true, supTree); + + VisitEndTestVisitor nonFlaggedVisitor; + + try { + nonFlaggedVisitor = checkCompliance(format, path, 2, false, supTree); + } catch (Exception e) { + return; // Reader doesn't support multiple passes without NEEDS_MULTIPLE_PASSES } + + // Reader didn't throw an exception, make sure it actually behaved as expected + assertTrue(nonFlaggedVisitor.finishedVisitPassCount == nonFlaggedVisitor.visitPassCountToFinish); + } + + private VisitEndTestVisitor checkCompliance(MappingFormat format, Path path, int visitPassCountToFinish, boolean setFlag, MappingTreeView supTree) throws Exception { + VisitEndTestVisitor visitor = new VisitEndTestVisitor(visitPassCountToFinish, setFlag, supTree, format); + MappingReader.read(path, format, visitor); + assertTrue(visitor.finishedVisitPassCount == visitPassCountToFinish); + return visitor; } private static class VisitEndTestVisitor implements MappingVisitor { - private VisitEndTestVisitor(int visitPassCountToFinish, boolean setFlag) { + private VisitEndTestVisitor(int visitPassCountToFinish, boolean setFlag, MappingTreeView supTree, MappingFormat subFormat) { this.visitPassCountToFinish = visitPassCountToFinish; this.setFlag = setFlag; + this.supTree = supTree; + this.subFormat = subFormat; + this.tree = new MemoryMappingTree(); + this.oldTrees = new MappingTree[visitPassCountToFinish - 1]; } @Override public Set getFlags() { return setFlag ? EnumSet.of(MappingFlag.NEEDS_MULTIPLE_PASSES) - : MappingVisitor.super.getFlags(); + : MappingFlag.NONE; } @Override public boolean visitHeader() throws IOException { check(); + tree.visitHeader(); return true; } @Override public void visitNamespaces(String srcNamespace, List dstNamespaces) throws IOException { check(); + tree.visitNamespaces(srcNamespace, dstNamespaces); + } + + @Override + public void visitMetadata(String key, @Nullable String value) throws IOException { + check(); + tree.visitMetadata(key, value); } @Override public boolean visitContent() throws IOException { check(); + tree.visitContent(); return true; } @Override public boolean visitClass(String srcName) throws IOException { check(); + tree.visitClass(srcName); return true; } @Override public boolean visitField(String srcName, @Nullable String srcDesc) throws IOException { check(); + tree.visitField(srcName, srcDesc); return true; } @Override public boolean visitMethod(String srcName, @Nullable String srcDesc) throws IOException { check(); + tree.visitMethod(srcName, srcDesc); return true; } @Override public boolean visitMethodArg(int argPosition, int lvIndex, @Nullable String srcName) throws IOException { check(); + tree.visitMethodArg(argPosition, lvIndex, srcName); return true; } @Override public boolean visitMethodVar(int lvtRowIndex, int lvIndex, int startOpIdx, int endOpIdx, @Nullable String srcName) throws IOException { check(); + tree.visitMethodVar(lvtRowIndex, lvIndex, startOpIdx, endOpIdx, srcName); return true; } @Override public void visitDstName(MappedElementKind targetKind, int namespace, String name) throws IOException { check(); + tree.visitDstName(targetKind, namespace, name); } @Override public void visitDstDesc(MappedElementKind targetKind, int namespace, String desc) throws IOException { check(); + tree.visitDstDesc(targetKind, namespace, desc); } @Override public boolean visitElementContent(MappedElementKind targetKind) throws IOException { check(); + tree.visitElementContent(targetKind); return true; } @Override public void visitComment(MappedElementKind targetKind, String comment) throws IOException { check(); + tree.visitComment(targetKind, comment); } @Override public boolean visitEnd() throws IOException { + check(); + tree.visitEnd(); + checkContent(); finishedVisitPassCount++; - return finishedVisitPassCount == visitPassCountToFinish; + + if (finishedVisitPassCount == visitPassCountToFinish) { + return true; + } + + oldTrees[finishedVisitPassCount - 1] = new MemoryMappingTree(tree); + tree = new MemoryMappingTree(); + return false; } private void check() { assertTrue(finishedVisitPassCount < visitPassCountToFinish); } + /** + * Ensures every visit pass contains the same content. + */ + private void checkContent() throws IOException { + MappingTreeView subTree, supTree; + MappingFormat supFormat = null; + + if (finishedVisitPassCount == 0) { + if (this.supTree == null) return; + supTree = this.supTree; + } else { + supTree = oldTrees[finishedVisitPassCount - 1]; + if (finishedVisitPassCount > 1) supFormat = subFormat; + } + + subTree = tree; + subTree.accept(new FlatAsRegularMappingVisitor(new SubsetAssertingVisitor(supTree, supFormat, subFormat))); + supTree.accept(new FlatAsRegularMappingVisitor(new SubsetAssertingVisitor(subTree, subFormat, supFormat))); + } + private final int visitPassCountToFinish; private final boolean setFlag; + private final MappingTreeView supTree; + private final MappingFormat subFormat; + private final MappingTree[] oldTrees; private int finishedVisitPassCount; + private MemoryMappingTree tree; } } diff --git a/src/test/resources/read/valid-with-holes/enigma-dir/class_32.mapping b/src/test/resources/read/valid-with-holes/enigma-dir/class_32.mapping index 22751d60..6a269a1e 100644 --- a/src/test/resources/read/valid-with-holes/enigma-dir/class_32.mapping +++ b/src/test/resources/read/valid-with-holes/enigma-dir/class_32.mapping @@ -18,13 +18,13 @@ CLASS class_32 METHOD method_6 ()I COMMENT This is a comment METHOD method_7 ()I - ARG 5 + ARG 1 + ARG 3 + ARG 5 param3Ns0Rename ARG 7 - ARG 9 param3Ns0Rename - ARG 11 COMMENT This is a comment - ARG 13 param5Ns0Rename + ARG 9 param5Ns0Rename COMMENT This is a comment - ARG 15 + ARG 11 COMMENT This is a comment METHOD method_8 ()I diff --git a/src/test/resources/read/valid-with-holes/enigma.mappings b/src/test/resources/read/valid-with-holes/enigma.mappings index 059a030e..f40fb44f 100644 --- a/src/test/resources/read/valid-with-holes/enigma.mappings +++ b/src/test/resources/read/valid-with-holes/enigma.mappings @@ -58,13 +58,13 @@ CLASS class_32 METHOD method_6 ()I COMMENT This is a comment METHOD method_7 ()I - ARG 5 + ARG 1 + ARG 3 + ARG 5 param3Ns0Rename ARG 7 - ARG 9 param3Ns0Rename - ARG 11 COMMENT This is a comment - ARG 13 param5Ns0Rename + ARG 9 param5Ns0Rename COMMENT This is a comment - ARG 15 + ARG 11 COMMENT This is a comment METHOD method_8 ()I diff --git a/src/test/resources/read/valid-with-holes/tinyV2.tiny b/src/test/resources/read/valid-with-holes/tinyV2.tiny index 19c97d02..bc6c49fe 100644 --- a/src/test/resources/read/valid-with-holes/tinyV2.tiny +++ b/src/test/resources/read/valid-with-holes/tinyV2.tiny @@ -46,22 +46,22 @@ c class_32 m ()I method_6 method6Ns1Rename c This is a comment m ()I method_7 - p 5 param_1 - p 7 param_2 param2Ns1Rename - p 9 param_3 param3Ns0Rename - p 11 param_4 + p 1 param_1 + p 3 param_2 param2Ns1Rename + p 5 param_3 param3Ns0Rename + p 7 param_4 c This is a comment - p 13 param_5 param5Ns0Rename + p 9 param_5 param5Ns0Rename c This is a comment - p 15 param_6 param6Ns1Rename + p 11 param_6 param6Ns1Rename c This is a comment m ()I method_8 - v 16 16 16 var_1 - v 18 18 18 var_2 var2Ns1Rename - v 20 20 20 var_3 var3Ns0Rename - v 22 22 22 var_4 + v 12 12 12 var_1 + v 14 14 14 var_2 var2Ns1Rename + v 16 16 16 var_3 var3Ns0Rename + v 18 18 18 var_4 c This is a comment - v 24 24 24 var_5 var5Ns0Rename + v 20 20 20 var_5 var5Ns0Rename c This is a comment - v 26 26 26 var_6 var6Ns1Rename + v 22 22 22 var_6 var6Ns1Rename c This is a comment diff --git a/src/test/resources/read/valid-with-holes/tsrg2.tsrg b/src/test/resources/read/valid-with-holes/tsrg2.tsrg index c656ad3d..3fd2de0e 100644 --- a/src/test/resources/read/valid-with-holes/tsrg2.tsrg +++ b/src/test/resources/read/valid-with-holes/tsrg2.tsrg @@ -31,10 +31,10 @@ class_32 class_32 class_32 method_5 ()I method5Ns0Rename method_5 method_6 ()I method_6 method6Ns1Rename method_7 ()I method_7 method_7 - 5 param_1 param_1 param_1 - 7 param_2 param_2 param2Ns1Rename - 9 param_3 param3Ns0Rename param_3 - 11 param_4 param_4 param_4 - 13 param_5 param5Ns0Rename param_5 - 15 param_6 param_6 param6Ns1Rename + 1 param_1 param_1 param_1 + 3 param_2 param_2 param2Ns1Rename + 5 param_3 param3Ns0Rename param_3 + 7 param_4 param_4 param_4 + 9 param_5 param5Ns0Rename param_5 + 11 param_6 param_6 param6Ns1Rename method_8 ()I method_8 method_8 From ddf27b880270d56e8912b53f130d633f6c26c129 Mon Sep 17 00:00:00 2001 From: Julian Burner <48808497+NebelNidas@users.noreply.github.com> Date: Thu, 30 Nov 2023 22:45:46 +0100 Subject: [PATCH 02/12] Improve documentation (#84) * Improve documentation * `MappingTreeView` isn't immutable Co-authored-by: Juuz <6596629+Juuxel@users.noreply.github.com> * Fix typo Co-authored-by: Juuz <6596629+Juuxel@users.noreply.github.com> * Remove redundant `ForwardingMappingVisitor` comment * Fix inconsistencies * `TsrgFileReader` also reads CSRG * Fix typo * `@link` -> `@linkplain` * `endOpIdx` is exclusive --------- Co-authored-by: Juuz <6596629+Juuxel@users.noreply.github.com> Co-authored-by: modmuss --- CHANGELOG.md | 1 + README.md | 29 +++++-- .../mappingio/extras/MappingTreeRemapper.java | 8 +- .../mappingio/FlatMappingVisitor.java | 10 ++- .../fabricmc/mappingio/MappedElementKind.java | 3 + .../net/fabricmc/mappingio/MappingFlag.java | 3 + .../net/fabricmc/mappingio/MappingReader.java | 44 ++++++++-- .../fabricmc/mappingio/MappingVisitor.java | 56 +++++++++++-- .../adapter/FlatAsRegularMappingVisitor.java | 7 ++ .../adapter/ForwardingMappingVisitor.java | 5 ++ .../adapter/MappingDstNsReorder.java | 13 +++ .../mappingio/adapter/MappingNsCompleter.java | 15 ++++ .../mappingio/adapter/MappingNsRenamer.java | 7 ++ .../adapter/MappingSourceNsSwitch.java | 6 +- .../mappingio/adapter/MissingDescFilter.java | 3 + .../adapter/RegularAsFlatMappingVisitor.java | 3 + .../mappingio/format/ColumnFileReader.java | 8 +- .../mappingio/format/MappingFormat.java | 84 +++++++++---------- .../format/enigma/EnigmaDirReader.java | 6 ++ .../format/enigma/EnigmaDirWriter.java | 3 + .../format/enigma/EnigmaFileReader.java | 7 ++ .../format/enigma/EnigmaFileWriter.java | 4 + .../format/proguard/ProGuardFileReader.java | 7 ++ .../format/proguard/ProGuardFileWriter.java | 20 ++--- .../mappingio/format/srg/SrgFileReader.java | 7 ++ .../mappingio/format/srg/SrgFileWriter.java | 4 + .../mappingio/format/srg/TsrgFileReader.java | 8 ++ .../format/tiny/Tiny1FileReader.java | 7 ++ .../format/tiny/Tiny1FileWriter.java | 4 + .../format/tiny/Tiny2FileReader.java | 7 ++ .../format/tiny/Tiny2FileWriter.java | 4 + .../fabricmc/mappingio/tree/MappingTree.java | 4 + .../mappingio/tree/MappingTreeView.java | 7 +- .../mappingio/tree/MemoryMappingTree.java | 3 + .../mappingio/tree/VisitableMappingTree.java | 3 + 35 files changed, 318 insertions(+), 92 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c18ffd8..4c0a2b00 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] +- Improved documentation - Fixed NPE in `MemoryMappingTree` - Fixed TSRG2 reader not handling multiple passes correctly diff --git a/README.md b/README.md index a0ef3337..56633184 100644 --- a/README.md +++ b/README.md @@ -3,18 +3,18 @@ Mapping-IO is a small and efficient library for working with deobfuscation mappi The API is inspired by ObjectWeb's [ASM](https://asm.ow2.io/): At its core, Mapping-IO is [visitor-based](./src/main/java/net/fabricmc/mappingio/MappingVisitor.java), but it also provides a [tree API](./src/main/java/net/fabricmc/mappingio/tree/) for in-memory storage and easier data manipulation. -Utilities for more sophisticated use cases can be found in the [mapping-io-extras](./mapping-io-extras/) module; they've been moved out from the core publication due to their additional dependency requirements. +Utilities for more sophisticated use cases can be found in the [mapping-io-extras](./mapping-io-extras/) module; they've been moved out from the core publication due to their additional dependencies. ## Usage -Reading and writing can be easily be achieved via the [`MappingReader`](./src/main/java/net/fabricmc/mappingio/MappingReader.java) and [`MappingWriter`](./src/main/java/net/fabricmc/mappingio/MappingWriter.java) interfaces: +Reading and writing can be easily achieved via the [`MappingReader`](./src/main/java/net/fabricmc/mappingio/MappingReader.java) and [`MappingWriter`](./src/main/java/net/fabricmc/mappingio/MappingWriter.java) interfaces: ```java MappingReader.read(inputPath, /* optional */ inputFormat, MappingWriter.create(outputPath, outputFormat)); ``` -The above example reads mappings from the input path directly into a mapping writer, writing all contents to disk in the specified mapping format. -Keep in mind that the conversion process might be lossy if the two formats' feature sets differ; see the comparison table [here](./src/main/java/net/fabricmc/mappingio/format/MappingFormat.java) for more information. +The above example reads mappings from the input path directly into a `MappingWriter`, writing all contents to disk in the specified format. +Keep in mind that the conversion process might be lossy if the two formats' feature sets differ; see the comparison table [here](./src/main/java/net/fabricmc/mappingio/format/MappingFormat.java) for more details. You can also read into a tree first: ```java @@ -25,8 +25,23 @@ MappingReader.read(inputPath, inputFormat, tree); tree.accept(MappingWriter.create(outputPath, outputFormat)); ``` -If the input format is known and more direct control over specific reading parameters is desired, the formats' readers (or writers) may also be invoked directly. +If the input format is known beforehand and more direct control over specific reading parameters is desired, the formats' readers (or writers) may also be invoked directly. -Mapping manipulation is achieved either via the tree API and/or specialized `MappingVisitor`s, hand-crafted or using pre-made ones found in the [adapter](./src/main/java/net/fabricmc/mappingio/adapter/) package. +Mapping manipulation is achieved either via the tree API or specialized `MappingVisitor`s, hand-crafted or utilizing first-party ones found in the [adapter](./src/main/java/net/fabricmc/mappingio/adapter/) package. -For more information, please consult the project's Javadoc comments. +For further information, please consult the project's Javadocs. + + +### Maven +Mapping-IO is available from the [FabricMC Maven](https://maven.fabricmc.net/net/fabricmc/mapping-io), version 0.4.2 and onwards can also be found on Maven Central. + +Gradle snippet: +```gradle +repositories { + mavenCentral() +} + +dependencies { + api 'net.fabricmc:mapping-io:${mappingio_version}' +} +``` diff --git a/mapping-io-extras/src/main/java/net/fabricmc/mappingio/extras/MappingTreeRemapper.java b/mapping-io-extras/src/main/java/net/fabricmc/mappingio/extras/MappingTreeRemapper.java index fd19ca35..b6701c43 100644 --- a/mapping-io-extras/src/main/java/net/fabricmc/mappingio/extras/MappingTreeRemapper.java +++ b/mapping-io-extras/src/main/java/net/fabricmc/mappingio/extras/MappingTreeRemapper.java @@ -23,7 +23,7 @@ import net.fabricmc.mappingio.tree.MappingTreeView; /** - * An ASM remapper that remaps between two namespaces in a {@link MappingTreeView}. + * An ASM {@link Remapper} that remaps between two namespaces in a {@link MappingTreeView}. */ public final class MappingTreeRemapper extends Remapper { private final MappingTreeView tree; @@ -33,9 +33,9 @@ public final class MappingTreeRemapper extends Remapper { /** * Constructs a {@code MappingTreeRemapper}. * - * @param tree the mapping tree view - * @param from the input namespace, must be in the tree - * @param to the output namespace, must be in the tree + * @param tree The mapping tree view. + * @param from The input namespace, must be in the tree. + * @param to The output namespace, must be in the tree. */ public MappingTreeRemapper(MappingTreeView tree, String from, String to) { Objects.requireNonNull(tree, "Mapping tree cannot be null"); diff --git a/src/main/java/net/fabricmc/mappingio/FlatMappingVisitor.java b/src/main/java/net/fabricmc/mappingio/FlatMappingVisitor.java index e8e3ae5f..a495f8a0 100644 --- a/src/main/java/net/fabricmc/mappingio/FlatMappingVisitor.java +++ b/src/main/java/net/fabricmc/mappingio/FlatMappingVisitor.java @@ -28,6 +28,9 @@ import net.fabricmc.mappingio.adapter.FlatAsRegularMappingVisitor; import net.fabricmc.mappingio.adapter.RegularAsFlatMappingVisitor; +/** + * A mapping visitor that provides the entire data for a given element within a single visit call. + */ public interface FlatMappingVisitor { default Set getFlags() { return MappingFlag.NONE; @@ -40,7 +43,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; @@ -53,7 +56,7 @@ 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; @@ -96,7 +99,8 @@ void visitMethodVarComment(String srcClsName, String srcMethodName, @Nullable St /** * Finish the visitation pass. - * @return true if the visitation pass is final, false if it should be started over + * + * @return {@code true} if the visitation pass is final, {@code false} if it should be started over. */ default boolean visitEnd() throws IOException { return true; diff --git a/src/main/java/net/fabricmc/mappingio/MappedElementKind.java b/src/main/java/net/fabricmc/mappingio/MappedElementKind.java index 87c20e35..a046c5f1 100644 --- a/src/main/java/net/fabricmc/mappingio/MappedElementKind.java +++ b/src/main/java/net/fabricmc/mappingio/MappedElementKind.java @@ -16,6 +16,9 @@ package net.fabricmc.mappingio; +/** + * A kind of element that can be mapped. + */ public enum MappedElementKind { CLASS(0), FIELD(1), diff --git a/src/main/java/net/fabricmc/mappingio/MappingFlag.java b/src/main/java/net/fabricmc/mappingio/MappingFlag.java index 0285d41f..2271b9f0 100644 --- a/src/main/java/net/fabricmc/mappingio/MappingFlag.java +++ b/src/main/java/net/fabricmc/mappingio/MappingFlag.java @@ -20,6 +20,9 @@ import java.util.EnumSet; import java.util.Set; +/** + * Flags a {@link MappingVisitor} may provide to inform the caller about certain requirements. + */ public enum MappingFlag { /** * Indication that the visitor may require multiple passes. diff --git a/src/main/java/net/fabricmc/mappingio/MappingReader.java b/src/main/java/net/fabricmc/mappingio/MappingReader.java index 854f7c1e..0deae9bf 100644 --- a/src/main/java/net/fabricmc/mappingio/MappingReader.java +++ b/src/main/java/net/fabricmc/mappingio/MappingReader.java @@ -58,7 +58,7 @@ public static MappingFormat detectFormat(Reader reader) throws IOException { int pos = 0; int len; - // Be careful not to close the reader, thats upto the caller. + // Be careful not to close the reader, that's up to the caller. BufferedReader br = reader instanceof BufferedReader ? (BufferedReader) reader : new BufferedReader(reader); br.mark(DETECT_HEADER_LEN); @@ -177,24 +177,39 @@ public static List getNamespaces(Reader reader, MappingFormat format) th } } - public static void read(Path file, MappingVisitor visitor) throws IOException { - read(file, null, visitor); + /** + * Tries to detect the format of the given path and read it. + * + * @param path The path to read from. Can be a file or a directory. + * @param visitor The receiving visitor. + * @throws IOException If the format can't be detected or reading fails. + */ + public static void read(Path path, MappingVisitor visitor) throws IOException { + read(path, null, visitor); } - public static void read(Path file, MappingFormat format, MappingVisitor visitor) throws IOException { + /** + * Tries to read the given path using the passed format's reader. + * + * @param path The path to read from. Can be a file or a directory. + * @param format The format to use. Has to match the path's format. + * @param visitor The receiving visitor. + * @throws IOException If reading fails. + */ + public static void read(Path path, MappingFormat format, MappingVisitor visitor) throws IOException { if (format == null) { - format = detectFormat(file); + format = detectFormat(path); if (format == null) throw new IOException("invalid/unsupported mapping format"); } if (format.hasSingleFile()) { - try (Reader reader = Files.newBufferedReader(file)) { + try (Reader reader = Files.newBufferedReader(path)) { read(reader, format, visitor); } } else { switch (format) { case ENIGMA_DIR: - EnigmaDirReader.read(file, visitor); + EnigmaDirReader.read(path, visitor); break; default: throw new IllegalStateException(); @@ -202,10 +217,25 @@ public static void read(Path file, MappingFormat format, MappingVisitor visitor) } } + /** + * Tries to detect the reader's content's format and read it. + * + * @param reader The reader to read from. + * @param visitor The receiving visitor. + * @throws IOException If the format can't be detected or reading fails. + */ public static void read(Reader reader, MappingVisitor visitor) throws IOException { read(reader, null, visitor); } + /** + * Tries to read the reader's content using the passed format's mapping reader. + * + * @param reader The reader to read from. + * @param format The format to use. Has to match the reader's content's format. + * @param visitor The receiving visitor. + * @throws IOException If reading fails. + */ public static void read(Reader reader, MappingFormat format, MappingVisitor visitor) throws IOException { if (format == null) { if (!reader.markSupported()) reader = new BufferedReader(reader); diff --git a/src/main/java/net/fabricmc/mappingio/MappingVisitor.java b/src/main/java/net/fabricmc/mappingio/MappingVisitor.java index c4931584..24f78dec 100644 --- a/src/main/java/net/fabricmc/mappingio/MappingVisitor.java +++ b/src/main/java/net/fabricmc/mappingio/MappingVisitor.java @@ -54,7 +54,7 @@ default Set 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: * * * @@ -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 @@ * * * + * + * + * + * + * + * + * + * + * *
Format--
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; + } +}