Class Remapper

java.lang.Object
org.stianloader.remapper.Remapper

public final class Remapper extends Object
Simple in-memory remapping engine. Unlike many other remappers it is able to take in already parsed Objectweb ASM Classnodes as input and output them without having to go through an intermediary store-to-file mode.

Additionally, this is a remapper that is only a remapper. More specifically, it will only remap - but not change your access flags, LVT entries or anything else that might not be intuitive. That being said, existing LVT entries might get edited in order to support parameter remapping, though it is not guaranteed to do so at this time (2025-12-20) and it will depend on the structure of the method. Further, it will not generate LVT entries for parameters without LVT entries.

The names of the destination namespace (or the remapped names in laymen's terms) are provided by the MappingLookup instance supplied through the constructor of this class. After construction, the remapper itself cannot be mutated and all changes need to be performed through the MappingLookup instance.

ClassNodes can be remapped via remapNode(ClassNode, StringBuilder). A similar method also exists to remap MethodNodes and FieldNodes.

Thread safety and concurrency

While a single instance of this class can be used in a concurrent environment and be shared across multiple threads, the same may not apply to a MappingLookup instance. On a similar note a single call to the remapNode methods do not cause any parallelisation to happen. As such if it is known that the remapper is called on larger classes, it might be useful to be aware that methods can be individually remapped via remapNode(String, MethodNode, StringBuilder). However, remapNode(ClassNode, StringBuilder) implies remapNode(String, MethodNode, StringBuilder) so this strategy has serious flaws. If a serious performance updraft is expected when employing parallelisation on a ClassNode level, then please open up an issue on the project's repository.

Method overloading, inheritance and other remapping restrictions

The burden of handling restrictions on method overloading and inheritance falls upon the used MappingLookup instance. If the MappingLookup instance is erroneously implemented or used (i.e. in the case of SimpleMappingLookup the instance being fed invalid data), it is possible to modify the way inheritance and overloading behaves - potentially causing a method to no longer override another or creating an override where no one existed beforehand. More acutely, it is also possible that inheritance applies to fields, too - this is notably the case when making use of anonymous classes at enum members. Unlike sl-deobf, the burden of handling inheritance for fields also falls upon the MappingLookup instance.

The Remapper instance is unable to verify mapping collisions and it is the job of the MappingLookup implementation to ensure that such events do not happen - note that some implementations such as SimpleMappingLookup do not check for such inconsistencies; for more information on this topic, see the manual of your lookup implementation.

While the Remapper instance allows nonsensical remapping requests (such as remapping methods from/to <clinit> or <init>), it is imperative that this behaviour is not relied on and that MappingLookup instances take the necessary precautions to prohibit such requests.

Layered mappings, that is mappings that are built ontop of other mappings, are not directly supported by this remapper. However, it is possible to easily obtain this behaviour by calling the remapNode methods multiple times - more specifically once per layer of mappings. That being said, some MappingLookup instances might not necessarily support such behaviour, especially when it comes to computing the hierarchy of classes as the class name might not necessarily be known to the MappingLookup instance. As such the alternative is directly "squashing" the mapping layers into a single MappingLookup. In case of doubt, consult the manual of the used lookup implementation for further guidance on how layered mappings may be implemented.

Reflection

Remapping reflective calls are not supported due to the complexity required for such a niche feature. If absolutely needed (we generally recommend wrapping the reflective operations in a way that they are redirected as needed at runtime), 3rd party tools should be used. The same applies to method handles or other string constants. That being said, java.lang.Class constants will get remapped so very simple reflective operations might still behave as intended.

  • Constructor Details

    • Remapper

      public Remapper(@NotNull @NotNull MappingLookup lookup)
      Constructor. Create a Remapper instance which uses a given MappingLookup instance to remap nodes.
      Parameters:
      lookup - The lookup to use for all remapping requests, may not be null
  • Method Details

    • getRemappedFieldDescriptor

      @NotNull public static @NotNull String getRemappedFieldDescriptor(@NotNull @NotNull MappingLookup lookup, @NotNull @NotNull String fieldDesc, @NotNull @NotNull StringBuilder sharedBuilder)
      Remaps a field descriptor.
      Parameters:
      lookup - The MappingLookup to use in order to remap the descriptor.
      fieldDesc - The old (unmapped) field descriptor
      sharedBuilder - A shared cached string builder. The contents of the string builder are wiped and after the invocation the contents are undefined
      Returns:
      The new (remapped) field descriptor. It can be identity identical to the "fieldDesc" if it didn't need to be altered
    • getRemappedMethodDescriptor

      @NotNull public static @NotNull String getRemappedMethodDescriptor(@NotNull @NotNull MappingLookup lookup, @NotNull @NotNull String methodDesc, @NotNull @NotNull StringBuilder sharedBuilder)
      Remaps a method descriptor.

      Note: This method completely disregards bridges or other context-specific circumstances. Overall, it aims to be the most generically applicable method.

      Although this method was initially written to remap method descriptors, this method also can work with method signatures.

      Parameters:
      lookup - The MappingLookup to use in order to remap the descriptor.
      methodDesc - The old (unmapped) method descriptor
      sharedBuilder - A shared cached string builder. The contents of the string builder are wiped and after the invocation the contents are undefined
      Returns:
      The new (remapped) method descriptor. It can be identity identical to the "methodDesc" if it didn't need to be altered
    • remapInternalName

      @NotNull public static @NotNull String remapInternalName(@NotNull @NotNull MappingLookup lookup, @NotNull @NotNull String internalName, @NotNull @NotNull StringBuilder sharedStringBuilder)
      Remap an internal name or array String, meaning that this method accepts the same kind of strings as Type.getObjectType(String).

      The contents of the StringBuilder instance passed to this method might be overwritten and the contents afterwards should be considered unknown. It is especially not guaranteed (in fact, it usually won't be) that the content of the StringBuilder is equal to the returned String.

      Parameters:
      lookup - The MappingLookup to use in order to remap the descriptor.
      internalName - The internal name in the source namespace.
      sharedStringBuilder - A shared StringBuilder instance of object pooling purposes (note: The instance should not be used across multiple threads!)
      Returns:
      The remapped internal name in the destination namespace.
      See Also:
      • Type.getInternalName()
    • remapSignature

      public static boolean remapSignature(@NotNull @NotNull MappingLookup lookup, @NotNull @NotNull String signature, int start, int end, @NotNull @NotNull StringBuilder signatureOut)
      Remap a generic signature string, as used for example in MethodNode.signature, FieldNode.signature or ClassNode.signature. As this method is fairly generic it is even capable of remapping method, field or type descriptors. However, this method is not capable of remapping internal names. If internal names should be remapped, use remapInternalName(MappingLookup, String, StringBuilder) instead.

      Internally, this method is recursive (in order to be able to correctly remap nested generics), which is why this method accepts a start and end pointer, which are the codepoints which should be remapped and pushed to the StringBuilder buffer. This algorithm evaluates the input signature from left to right.

      In the case that end is greater than start, a crash is likely, although the type of crash is not defined. It may also deadlock or cause an OOM situation. Furthermore, if end does not correctly align with a type boundary (usually a semicolon or a character that represents a primitive), then unexpected behaviour is likely - more likely than not it will cause a crash, deadlock or OutOfMemoryError. As similar behaviour also applies to start, both start and end should be chosen carefully. More often than not, this method can be considered overkill and instead remapSignature(MappingLookup, String, StringBuilder) can be used safely as an alternative - however that method will remap the entire signature while this method can (if start and end are chosen accordingly) remap parts of it.

      Parameters:
      lookup - The MappingLookup to use in order to remap the descriptor.
      signature - The signature to remap in the source namespace.
      start - The start of signature.
      end - The last codepoint of the signature that should be handled by this method. Everything beyond it is plainly ignored.
      signatureOut - The StringBuilder instance to which the remapped signature should be stored into.
      Returns:
      True if a modification happened while remapping the signature, false otherwise.
    • remapSignature

      public static boolean remapSignature(@NotNull @NotNull MappingLookup lookup, @NotNull @NotNull String signature, @NotNull @NotNull StringBuilder out)
      Remap a generic signature string, as used for example in MethodNode.signature, FieldNode.signature or ClassNode.signature. As this method is fairly generic it is even capable of remapping method, field or type descriptors. However, this method is not capable of remapping internal names. If internal names should be remapped, use remapInternalName(MappingLookup, String, StringBuilder) instead.

      Internally, this method is recursive (in order to be able to correctly remap nested generics). This algorithm evaluates the input signature from left to right.

      Parameters:
      lookup - The MappingLookup to use in order to remap the descriptor.
      signature - The signature to remap in the source namespace.
      out - The StringBuilder instance to which the remapped signature should be stored into.
      Returns:
      True if a modification happened while remapping the signature, false otherwise - that is if false, StringBuilder.toString() of out will be equal to signature.
    • getLookup

      @NotNull @Contract(pure=true) public final @NotNull MappingLookup getLookup()
      Obtain the MappingLookup instance from which this Remapper sources all source to destination namespace name mappings. This instance is set through the constructor.
      Returns:
      The MappingLookup instance used by this Remapper.
    • remapNode

      @Contract(pure=false, mutates="param1,param2", value="_, _ -> this") @NotNull public @NotNull Remapper remapNode(@NotNull @NotNull org.objectweb.asm.tree.ClassNode node, @NotNull @NotNull StringBuilder sharedBuilder)
      Remap a ClassNode, modifying it and it's contents.

      Warning: The given StringBuilder instance may be overwritten. The contents of the StringBuilder before the method call are completely irrelevant, and the contents after the call may be garbage. The reason Remapper allows supplying StringBuilder instances is mostly for performance reasons in order to cut down the amount of times a StringBuilder instance is allocated only to be instantly discarded again.

      Parameters:
      node - The node to remap
      sharedBuilder - The StringBuilder instance to use for string manipulation.
      Returns:
      The current Remapper instance, for chaining
    • remapNode

      @NotNull @Contract(pure=false, mutates="param2, param3", value="_, _, _ -> this") public @NotNull Remapper remapNode(@NotNull @NotNull String owner, @NotNull @NotNull org.objectweb.asm.tree.FieldNode field, @NotNull @NotNull StringBuilder sharedStringBuilder)
      Remap a FieldNode, modifying it and it's contents.

      Warning: The given StringBuilder instance may be overwritten. The contents of the StringBuilder before the method call are completely irrelevant, and the contents after the call may be garbage. The reason Remapper allows supplying StringBuilder instances is mostly for performance reasons in order to cut down the amount of times a StringBuilder instance is allocated only to be instantly discarded again.

      Parameters:
      owner - The owner of the member (i.e. the class where it is defined), represented as an internal name
      field - The node to remap
      sharedStringBuilder - The StringBuilder instance to use for string manipulation.
      Returns:
      The current Remapper instance, for chaining
    • remapNode

      @NotNull @Contract(pure=false, mutates="param2, param3", value="_, _, _ -> this") public @NotNull Remapper remapNode(@NotNull @NotNull String owner, @NotNull @NotNull org.objectweb.asm.tree.MethodNode method, @NotNull @NotNull StringBuilder sharedStringBuilder)
      Remap a MethodNode, modifying it and it's contents.

      Warning: The given StringBuilder instance may be overwritten. The contents of the StringBuilder before the method call are completely irrelevant, and the contents after the call may be garbage. The reason Remapper allows supplying StringBuilder instances is mostly for performance reasons in order to cut down the amount of times a StringBuilder instance is allocated only to be instantly discarded again.

      Parameters:
      owner - The owner of the member (i.e. the class where it is defined), represented as an internal name
      method - The node to remap
      sharedStringBuilder - The StringBuilder instance to use for string manipulation.
      Returns:
      The current Remapper instance, for chaining