From a27adb1e2529eeb6054cf89426ae3c8d4d3264f6 Mon Sep 17 00:00:00 2001 From: Joe Date: Mon, 13 Apr 2020 12:52:19 +0100 Subject: [PATCH] Inverse field and method mapper --- .../loader/FabricMappingResolver.java | 53 +++++++++++++++++++ .../fabricmc/loader/api/MappingResolver.java | 24 ++++++++- .../loader/FabricMappingResolverTest.java | 48 +++++++++++++++++ 3 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 src/test/java/net/fabricmc/loader/FabricMappingResolverTest.java diff --git a/src/main/java/net/fabricmc/loader/FabricMappingResolver.java b/src/main/java/net/fabricmc/loader/FabricMappingResolver.java index 9962c64d3..1ddcd74f4 100644 --- a/src/main/java/net/fabricmc/loader/FabricMappingResolver.java +++ b/src/main/java/net/fabricmc/loader/FabricMappingResolver.java @@ -40,7 +40,10 @@ private static class NamespaceData { private final Map classNames = new HashMap<>(); private final Map classNamesInverse = new HashMap<>(); private final Map fieldNames = new HashMap<>(); + // set this to null and lazily compute when needed to save on memory + private Map fieldNamesInverse = null; private final Map methodNames = new HashMap<>(); + private Map methodNamesInverse = null; } FabricMappingResolver(Supplier mappingsSupplier, String targetNamespace) { @@ -128,6 +131,21 @@ public String mapFieldName(String namespace, String owner, String name, String d return getNamespaceData(namespace).fieldNames.getOrDefault(new EntryTriple(owner, name, descriptor), name); } + @Override + public String unmapFieldName(String namespace, String owner, String name, String descriptor) { + if (owner.indexOf('/') >= 0) { + throw new IllegalArgumentException("Class name must be provided in dot format: " + owner); + } + + NamespaceData namespaceData = getNamespaceData(namespace); + if (namespaceData.fieldNamesInverse == null) { + namespaceData.fieldNamesInverse = new HashMap<>(); + computeInverseMappings(namespaceData, namespaceData.fieldNames, namespaceData.fieldNamesInverse); + } + + return namespaceData.fieldNamesInverse.getOrDefault(new EntryTriple(owner, name, descriptor), name); + } + @Override public String mapMethodName(String namespace, String owner, String name, String descriptor) { if (owner.indexOf('/') >= 0) { @@ -136,4 +154,39 @@ public String mapMethodName(String namespace, String owner, String name, String return getNamespaceData(namespace).methodNames.getOrDefault(new EntryTriple(owner, name, descriptor), name); } + + @Override + public String unmapMethodName(String namespace, String owner, String name, String descriptor) { + if (owner.indexOf('/') >= 0) { + throw new IllegalArgumentException("Class names must be provided in dot format: " + owner); + } + + NamespaceData namespaceData = getNamespaceData(namespace); + if (namespaceData.methodNamesInverse == null) { + namespaceData.methodNamesInverse = new HashMap<>(); + computeInverseMappings(namespaceData, namespaceData.methodNames, namespaceData.methodNamesInverse); + } + + return namespaceData.methodNamesInverse.getOrDefault(new EntryTriple(owner, name, descriptor), name); + } + + private void computeInverseMappings(NamespaceData namespaceData, Map forwardMap, Map inverseMap) { + for (Map.Entry forwardEntry : forwardMap.entrySet()) { + String forwardOwner = forwardEntry.getKey().getOwner(); + String inverseOwner = namespaceData.classNames.getOrDefault(forwardOwner, forwardOwner); + + String forwardDesc = forwardEntry.getKey().getDesc(); + StringBuilder inverseDesc = new StringBuilder(forwardDesc); + for (int lIndex = inverseDesc.indexOf("L"); lIndex >= 0; lIndex = inverseDesc.indexOf("L", lIndex)) { + int semicolonIndex = inverseDesc.indexOf(";", lIndex); + String forwardClassName = inverseDesc.substring(lIndex + 1, semicolonIndex).replace('/', '.'); + String inverseClassName = namespaceData.classNames.getOrDefault(forwardClassName, forwardClassName); + inverseDesc.replace(lIndex + 1, semicolonIndex, inverseClassName.replace('.', '/')); + // Add on length of LinverseClass; + lIndex += inverseClassName.length() + 2; + } + + inverseMap.put(new EntryTriple(inverseOwner, forwardEntry.getValue(), inverseDesc.toString()), forwardEntry.getKey().getName()); + } + } } diff --git a/src/main/java/net/fabricmc/loader/api/MappingResolver.java b/src/main/java/net/fabricmc/loader/api/MappingResolver.java index 46df416b3..3354c7a72 100644 --- a/src/main/java/net/fabricmc/loader/api/MappingResolver.java +++ b/src/main/java/net/fabricmc/loader/api/MappingResolver.java @@ -50,7 +50,7 @@ public interface MappingResolver { String mapClassName(String namespace, String className); /** - * Unmap a class name to the mapping currently used at runtime. + * Unmap a class name from the mapping currently used at runtime. * * @param targetNamespace The target namespace for unmapping. * @param className The provided class name, in dot-format ("mypackage.MyClass$Inner"), @@ -70,6 +70,17 @@ public interface MappingResolver { */ String mapFieldName(String namespace, String owner, String name, String descriptor); + /** + * Unmap a field name from the mapping currently used at runtime. + * + * @param targetNamespace The target namespace for unmapping. + * @param owner The owner of the field, in dot-format ("mypackage.MyClass$Inner"). + * @param name The name of the field. + * @param descriptor The descriptor of the field. + * @return The unmapped field name, or name if such a mapping is not present + */ + String unmapFieldName(String targetNamespace, String owner, String name, String descriptor); + /** * Map a method name to the mapping currently used at runtime. * @@ -80,4 +91,15 @@ public interface MappingResolver { * @return The mapped method name, or name if such a mapping is not present. */ String mapMethodName(String namespace, String owner, String name, String descriptor); + + /** + * Unmap a method name from the mapping currently used at runtime. + * + * @param targetNamespace The target namespace for unmapping. + * @param owner The owner of the method, in dot-format ("mypackage.MyClass$Inner"). + * @param name The name of the method. + * @param descriptor The descriptor of the method. + * @return The unmapped method name, or name if such a mapping is not present. + */ + String unmapMethodName(String targetNamespace, String owner, String name, String descriptor); } diff --git a/src/test/java/net/fabricmc/loader/FabricMappingResolverTest.java b/src/test/java/net/fabricmc/loader/FabricMappingResolverTest.java new file mode 100644 index 000000000..10fd7b2bb --- /dev/null +++ b/src/test/java/net/fabricmc/loader/FabricMappingResolverTest.java @@ -0,0 +1,48 @@ +package net.fabricmc.loader; + +import net.fabricmc.mapping.tree.TinyMappingFactory; +import net.fabricmc.mapping.tree.TinyTree; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; + +public class FabricMappingResolverTest { + public static void main(String[] args) { + FabricMappingResolver mappingResolver = new FabricMappingResolver(FabricMappingResolverTest::createTestMappings, "bar"); + + assertEquals("class_bar", mappingResolver.mapClassName("foo", "class_foo")); + assertEquals("class_bar", mappingResolver.mapClassName("baz", "class_baz")); + assertEquals("field_bar", mappingResolver.mapFieldName("foo", "class_foo", "field_foo", "Lclass_foo;")); + assertEquals("field_bar", mappingResolver.mapFieldName("baz", "class_baz", "field_baz", "Lclass_baz;")); + assertEquals("method_bar", mappingResolver.mapMethodName("foo", "class_foo", "method_foo", "(ILclass_foo;)Lclass_foo;")); + assertEquals("method_bar", mappingResolver.mapMethodName("baz", "class_baz", "method_baz", "(ILclass_baz;)Lclass_baz;")); + + assertEquals("class_foo", mappingResolver.unmapClassName("foo", "class_bar")); + assertEquals("class_baz", mappingResolver.unmapClassName("baz", "class_bar")); + assertEquals("field_foo", mappingResolver.unmapFieldName("foo", "class_bar", "field_bar", "Lclass_bar;")); + assertEquals("field_baz", mappingResolver.unmapFieldName("baz", "class_bar", "field_bar", "Lclass_bar;")); + assertEquals("method_foo", mappingResolver.unmapMethodName("foo", "class_bar", "method_bar", "(ILclass_bar;)Lclass_bar;")); + assertEquals("method_baz", mappingResolver.unmapMethodName("baz", "class_bar", "method_bar", "(ILclass_bar;)Lclass_bar;")); + + System.out.println("All tests successful"); + } + + private static void assertEquals(String a, String b) { + if (!a.equals(b)) { + throw new AssertionError(a + " != " + b); + } + } + + private static TinyTree createTestMappings() { + String mappings = "tiny\t2\t0\tfoo\tbar\tbaz\n" + + "c\tclass_foo\tclass_bar\tclass_baz\n" + + "\tf\tLclass_foo;\tfield_foo\tfield_bar\tfield_baz\n" + + "\tm\t(ILclass_foo;)Lclass_foo;\tmethod_foo\tmethod_bar\tmethod_baz\n"; + try { + return TinyMappingFactory.load(new BufferedReader(new StringReader(mappings))); + } catch (IOException e) { + throw new AssertionError("Failed to load mappings", e); + } + } +}