Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inverse field and method mapper #226

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions src/main/java/net/fabricmc/loader/FabricMappingResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ private static class NamespaceData {
private final Map<String, String> classNames = new HashMap<>();
private final Map<String, String> classNamesInverse = new HashMap<>();
private final Map<EntryTriple, String> fieldNames = new HashMap<>();
// set this to null and lazily compute when needed to save on memory
private Map<EntryTriple, String> fieldNamesInverse = null;
private final Map<EntryTriple, String> methodNames = new HashMap<>();
private Map<EntryTriple, String> methodNamesInverse = null;
}

FabricMappingResolver(Supplier<TinyTree> mappingsSupplier, String targetNamespace) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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<EntryTriple, String> forwardMap, Map<EntryTriple, String> inverseMap) {
for (Map.Entry<EntryTriple, String> 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());
}
}
}
24 changes: 23 additions & 1 deletion src/main/java/net/fabricmc/loader/api/MappingResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand All @@ -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.
*
Expand All @@ -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);
}
48 changes: 48 additions & 0 deletions src/test/java/net/fabricmc/loader/FabricMappingResolverTest.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
}