package org.stianloader.remapper; import java.util.HashMap; import java.util.Map; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Type; /** * A simple implementation of {@link MappingLookup} which can be mutated via the implemented {@link MappingSink} * interface. * *

Thread safety and concurrency

* *

Instances of this class can be shared across multiple threads for as long as no instance is mutated via the * methods provided by {@link MappingSink}. More specifically, the thread safety and concurrency semantics of this class * are identical to the semantics of {@link HashMap}. It is as such not recommended to make use of this class * in asynchronous or concurrent environments. * *

Instances of this class can be freely shared by multiple {@link Remapper} instances within the bounds of * aforementioned restrictions. * *

Method overloading, inheritance and other remapping restrictions

* *

Instances of this class do not handle method overloading and inheritance overall - that is every method * and field is considered to be independent and is considered to exist as-is. Thus, * {@link MappingSink#remapMember(MemberRef, String)} must be called for each child class that might override * the method. Furthermore, {@link #remapMember(MemberRef, String)} should also be called even if an override * does not explicitly occur as it is still possible for java-bytecode to refer the members that might not exist * as-is but exist in the parent class. * *

This implementation prohibits renaming the initialisation methods <init> and <clinit> or renaming * methods to this name - fields may be renamed from and to this name regardless in case it is required for more * advanced obfuscation. * *

When making use of this lookup implementation, layered mappings should either be implemented by squashing/chaining * multiple {@link MappingLookup} instances together in an uber-lookup instance or by calling * {@link Remapper#remapNode(org.objectweb.asm.tree.ClassNode, StringBuilder)} at least once per mapping layer. * It is generally recommended to go with the former approach as that is the most performance-friendly option, but the latter * approach can also work. * *

This implementation does not verify the integrity of the remapping requests, so illegal names might be emitted * by this lookup instance. Similarly, this implementation does to scan for mapping collisions. * Attempting to map a class or class member to two different names will cause the latter remapping request to override * the former, omitting no warning nor other indicator while doing so. */ public class SimpleMappingLookup implements MappingLookup, MappingSink { private final Map classNames = new HashMap<>(); private final Map memberNames = new HashMap<>(); private final Map parameterNames = new HashMap<>(); @SuppressWarnings("null") // The default value is non-null so #getOrDefault cannot return a null value @Override @NotNull public String getRemappedClassName(@NotNull String srcName) { return this.classNames.getOrDefault(srcName, srcName); } @Override @Nullable public String getRemappedClassNameFast(@NotNull String srcName) { return this.classNames.get(srcName); } @SuppressWarnings("null") @Override @NotNull public String getRemappedFieldName(@NotNull String srcOwner, @NotNull String srcName, @NotNull String srcDesc) { return this.memberNames.getOrDefault(new MemberRef(srcOwner, srcName, srcDesc), srcName); } @SuppressWarnings("null") @Override @NotNull public String getRemappedMethodName(@NotNull String srcOwner, @NotNull String srcName, @NotNull String srcDesc) { return this.memberNames.getOrDefault(new MemberRef(srcOwner, srcName, srcDesc), srcName); } @Override @Nullable public String getRemappedParameterName(@NotNull String srcOwner, @NotNull String srcName, @NotNull String srcDesc, int paramIndex, boolean isStatic) { String[] params = this.parameterNames.get(new MemberRef(srcOwner, srcName, srcDesc)); return params == null ? null : params[paramIndex]; } @Contract(mutates = "this", pure = false, value = "_, _ -> this") @Override @NotNull public SimpleMappingLookup remapClass(@NotNull String srcName, @NotNull String dstName) { this.classNames.put(srcName, dstName); return this; } @Contract(mutates = "this", pure = false, value = "_, _ -> this") @Override @NotNull public SimpleMappingLookup remapMember(@NotNull MemberRef srcRef, @NotNull String dstName) { if (srcRef.getDesc().codePointAt(0) == '(') { if (dstName.equals("") || dstName.equals("")) { if (dstName.equals(srcRef.getName())) { // This would be a NOP mapping anyways. I don't know why one would do this, but I cannot really see // anything acutely wrong with it either so we'll let it slide to make it easier for the API consumer. return this; } throw new IllegalArgumentException("Illegal destination name for src member " + srcRef + ": " + dstName); } else if (srcRef.getName().equals("") || srcRef.getName().equals("")) { throw new IllegalArgumentException("Illegal attempt at renaming src member " + srcRef + " to " + dstName); } } this.memberNames.put(srcRef, dstName); return this; } @Override @NotNull public MappingSink remapParameter(@NotNull String srcOwner, @NotNull String srcMethodName, @NotNull String srcDesc, int paramIndex, @NotNull String destParamName) { this.parameterNames.compute(new MemberRef(srcOwner, destParamName, srcDesc), (key, parameters) -> { if (parameters == null) { parameters = new String[Type.getArgumentCount(srcDesc)]; } parameters[paramIndex] = destParamName; return parameters; }); return this; } }